summaryrefslogtreecommitdiffstats
path: root/login-utils/logindefs.c
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 14:30:35 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 14:30:35 +0000
commit378c18e5f024ac5a8aef4cb40d7c9aa9633d144c (patch)
tree44dfb6ca500d32cabd450649b322a42e70a30683 /login-utils/logindefs.c
parentInitial commit. (diff)
downloadutil-linux-378c18e5f024ac5a8aef4cb40d7c9aa9633d144c.tar.xz
util-linux-378c18e5f024ac5a8aef4cb40d7c9aa9633d144c.zip
Adding upstream version 2.38.1.upstream/2.38.1upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'login-utils/logindefs.c')
-rw-r--r--login-utils/logindefs.c609
1 files changed, 609 insertions, 0 deletions
diff --git a/login-utils/logindefs.c b/login-utils/logindefs.c
new file mode 100644
index 0000000..9563122
--- /dev/null
+++ b/login-utils/logindefs.c
@@ -0,0 +1,609 @@
+/*
+ * Copyright (C) 2003, 2004, 2005 Thorsten Kukuk
+ * Author: Thorsten Kukuk <kukuk@suse.de>
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * 1. Redistributions of source code must retain any existing copyright
+ * notice, and this entire permission notice in its entirety,
+ * including the disclaimer of warranties.
+ *
+ * 2. Redistributions in binary form must reproduce all prior and current
+ * copyright notices, this list of conditions, and the following
+ * disclaimer in the documentation and/or other materials provided
+ * with the distribution.
+ *
+ * 3. The name of any author may not be used to endorse or promote
+ * products derived from this software without their specific prior
+ * written permission.
+ */
+#include <assert.h>
+#include <ctype.h>
+#include <errno.h>
+#include <limits.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/syslog.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <pwd.h>
+
+#include "c.h"
+#include "closestream.h"
+#include "logindefs.h"
+#include "nls.h"
+#include "pathnames.h"
+#include "xalloc.h"
+
+
+static void (*logindefs_loader)(void *) = NULL;
+static void *logindefs_loader_data = NULL;
+
+void logindefs_set_loader(void (*loader)(void *data), void *data)
+{
+ logindefs_loader = loader;
+ logindefs_loader_data = data;
+}
+
+#ifndef HAVE_LIBECONF
+
+struct item {
+ char *name; /* name of the option. */
+ char *value; /* value of the option. */
+ char *path; /* name of config file for this option. */
+
+ struct item *next; /* pointer to next option. */
+};
+
+static struct item *list = NULL;
+
+void free_getlogindefs_data(void)
+{
+ struct item *ptr;
+
+ ptr = list;
+ while (ptr) {
+ struct item *tmp = ptr->next;
+
+ free(ptr->path);
+ free(ptr->name);
+ free(ptr->value);
+ free(ptr);
+ ptr = tmp;
+ }
+
+ list = NULL;
+}
+
+static void store(const char *name, const char *value, const char *path)
+{
+ struct item *new = xmalloc(sizeof(struct item));
+
+ if (!name)
+ abort();
+
+ new->name = xstrdup(name);
+ new->value = value && *value ? xstrdup(value) : NULL;
+ new->path = xstrdup(path);
+ new->next = list;
+ list = new;
+}
+
+void logindefs_load_file(const char *filename)
+{
+ FILE *f;
+ char buf[BUFSIZ];
+
+ f = fopen(filename, "r");
+ if (!f)
+ return;
+
+ while (fgets(buf, sizeof(buf), f)) {
+
+ char *p, *name, *data = NULL;
+
+ if (*buf == '#' || *buf == '\n')
+ continue; /* only comment or empty line */
+
+ p = strchr(buf, '#');
+ if (p)
+ *p = '\0';
+ else {
+ size_t n = strlen(buf);
+ if (n && *(buf + n - 1) == '\n')
+ *(buf + n - 1) = '\0';
+ }
+
+ if (!*buf)
+ continue; /* empty line */
+
+ /* ignore space at begin of the line */
+ name = buf;
+ while (*name && isspace((unsigned)*name))
+ name++;
+
+ /* go to the end of the name */
+ data = name;
+ while (*data && !(isspace((unsigned)*data) || *data == '='))
+ data++;
+ if (data > name && *data)
+ *data++ = '\0';
+
+ if (!*name || data == name)
+ continue;
+
+ /* go to the begin of the value */
+ while (*data
+ && (isspace((unsigned)*data) || *data == '='
+ || *data == '"'))
+ data++;
+
+ /* remove space at the end of the value */
+ p = data + strlen(data);
+ if (p > data)
+ p--;
+ while (p > data && (isspace((unsigned)*p) || *p == '"'))
+ *p-- = '\0';
+
+ store(name, data, filename);
+ }
+
+ fclose(f);
+}
+
+static void load_defaults(void)
+{
+ if (logindefs_loader)
+ logindefs_loader(logindefs_loader_data);
+ else
+ logindefs_load_file(_PATH_LOGINDEFS);
+}
+
+static struct item *search(const char *name)
+{
+ struct item *ptr;
+
+ if (!list)
+ load_defaults();
+
+ ptr = list;
+ while (ptr != NULL) {
+ if (strcasecmp(name, ptr->name) == 0)
+ return ptr;
+ ptr = ptr->next;
+ }
+
+ return NULL;
+}
+
+static const char *search_config(const char *name)
+{
+ struct item *ptr;
+
+ ptr = list;
+ while (ptr != NULL) {
+ if (strcasecmp(name, ptr->name) == 0)
+ return ptr->path;
+ ptr = ptr->next;
+ }
+
+ return NULL;
+}
+
+int getlogindefs_bool(const char *name, int dflt)
+{
+ struct item *ptr = search(name);
+ return ptr && ptr->value ? (strcasecmp(ptr->value, "yes") == 0) : dflt;
+}
+
+unsigned long getlogindefs_num(const char *name, unsigned long dflt)
+{
+ struct item *ptr = search(name);
+ char *end = NULL;
+ unsigned long retval;
+
+ if (!ptr || !ptr->value)
+ return dflt;
+
+ errno = 0;
+ retval = strtoul(ptr->value, &end, 0);
+ if (end && *end == '\0' && !errno)
+ return retval;
+
+ syslog(LOG_NOTICE, _("%s: %s contains invalid numerical value: %s"),
+ search_config(name), name, ptr->value);
+ return dflt;
+}
+
+/*
+ * Returns:
+ * @dflt if @name not found
+ * "" (empty string) if found, but value not defined
+ * "string" if found
+ */
+const char *getlogindefs_str(const char *name, const char *dflt)
+{
+ struct item *ptr = search(name);
+
+ if (!ptr)
+ return dflt;
+ if (!ptr->value)
+ return "";
+ return ptr->value;
+}
+
+#else /* !HAVE_LIBECONF */
+
+#include <libeconf.h>
+
+static econf_file *file = NULL;
+
+void free_getlogindefs_data(void)
+{
+ econf_free (file);
+ file = NULL;
+}
+
+static void load_defaults(void)
+{
+ econf_err error;
+
+ if (file != NULL)
+ free_getlogindefs_data();
+
+ error = econf_readDirs(&file,
+#if USE_VENDORDIR
+ _PATH_VENDORDIR,
+#else
+ NULL,
+#endif
+ "/etc", "login", "defs", "= \t", "#");
+
+ if (error)
+ syslog(LOG_NOTICE, _("Error reading login.defs: %s"),
+ econf_errString(error));
+
+ if (logindefs_loader)
+ logindefs_loader(logindefs_loader_data);
+
+}
+
+void logindefs_load_file(const char *filename)
+{
+ econf_file *file_l = NULL, *file_m = NULL;
+ char *path;
+
+ logindefs_loader = NULL; /* No recursion */
+
+#if USE_VENDORDIR
+ xasprintf(&path, _PATH_VENDORDIR"/%s", filename);
+
+ if (!econf_readFile(&file_l, path, "= \t", "#")) {
+ if (file == NULL)
+ file = file_l;
+ else if (!econf_mergeFiles(&file_m, file, file_l)) {
+ econf_free(file);
+ file = file_m;
+ econf_free(file_l);
+ }
+ }
+ free (path);
+#endif
+
+ xasprintf(&path, "/etc/%s", filename);
+
+ if (!econf_readFile(&file_l, path, "= \t", "#")) {
+ if (file == NULL)
+ file = file_l;
+ else if (!econf_mergeFiles(&file_m, file, file_l)) {
+ econf_free(file);
+ file = file_m;
+ econf_free(file_l);
+ }
+
+ /* Try original filename, could be relative */
+ } else if (!econf_readFile(&file_l, filename, "= \t", "#")) {
+ if (file == NULL)
+ file = file_l;
+ else if (!econf_mergeFiles(&file_m, file, file_l)) {
+ econf_free(file);
+ file = file_m;
+ econf_free(file_l);
+ }
+ }
+ free (path);
+}
+
+int getlogindefs_bool(const char *name, int dflt)
+{
+ bool value;
+ econf_err error;
+
+ if (!file)
+ load_defaults();
+
+ if (!file)
+ return dflt;
+
+ if ((error = econf_getBoolValue(file, NULL, name, &value))) {
+ if (error != ECONF_NOKEY)
+ syslog(LOG_NOTICE, _("couldn't fetch %s: %s"), name,
+ econf_errString(error));
+ return dflt;
+ }
+ return value;
+}
+
+unsigned long getlogindefs_num(const char *name, unsigned long dflt)
+{
+ uint64_t value;
+ econf_err error;
+
+ if (!file)
+ load_defaults();
+
+ if (!file)
+ return dflt;
+
+ if ((error = econf_getUInt64Value(file, NULL, name, &value))) {
+ if (error != ECONF_NOKEY)
+ syslog(LOG_NOTICE, _("couldn't fetch %s: %s"), name,
+ econf_errString(error));
+ return dflt;
+ }
+ return value;
+}
+
+/*
+ * Returns:
+ * @dflt if @name not found
+ * "" (empty string) if found, but value not defined
+ * "string" if found
+ */
+const char *getlogindefs_str(const char *name, const char *dflt)
+{
+ char *value;
+ econf_err error;
+
+ if (!file)
+ load_defaults();
+
+ if (!file)
+ return dflt;
+
+ if ((error = econf_getStringValue(file, NULL, name, &value))) {
+ if (error != ECONF_NOKEY)
+ syslog(LOG_NOTICE, _("couldn't fetch %s: %s"), name,
+ econf_errString(error));
+ return dflt;
+ }
+ if (value)
+ return value;
+
+ return xstrdup("");
+}
+#endif /* !HAVE_LIBECONF */
+
+/*
+ * For compatibility with shadow-utils we have to support additional
+ * syntax for environment variables in login.defs(5) file. The standard
+ * syntax is:
+ *
+ * ENV_FOO data
+ *
+ * but shadow-utils supports also
+ *
+ * ENV_FOO FOO=data
+ *
+ * the FOO= prefix has to be remove before we call setenv().
+ */
+int logindefs_setenv(const char *name, const char *conf, const char *dflt)
+{
+ const char *val = getlogindefs_str(conf, dflt);
+ const char *p;
+
+ if (!val)
+ return -1;
+
+ p = strchr(val, '=');
+ if (p) {
+ size_t sz = strlen(name);
+
+ if (strncmp(val, name, sz) == 0 && *(p + 1)) {
+ val = p + 1;
+ if (*val == '"')
+ val++;
+ if (!*val)
+ val = dflt;
+ }
+ }
+
+ return val ? setenv(name, val, 1) : -1;
+}
+
+/*
+ * We need to check the effective UID/GID. For example, $HOME could be on a
+ * root-squashed NFS or on an NFS with UID mapping, and access(2) uses the
+ * real UID/GID. Then open(2) seems as the surest solution.
+ * -- kzak@redhat.com (10-Apr-2009)
+ */
+int effective_access(const char *path, int mode)
+{
+ int fd = open(path, mode);
+ if (fd != -1)
+ close(fd);
+ return fd == -1 ? -1 : 0;
+}
+
+/*
+ * Check the per-account or the global hush-login setting.
+ *
+ * Hushed mode is enabled:
+ *
+ * a) if a global (e.g. /etc/hushlogins) hush file exists:
+ * 1) for ALL ACCOUNTS if the file is empty
+ * 2) for the current user if the username or shell is found in the file
+ *
+ * b) if a ~/.hushlogin file exists
+ *
+ * The ~/.hushlogin file is ignored if the global hush file exists.
+ *
+ * The HUSHLOGIN_FILE login.def variable overrides the default hush filename.
+ *
+ * Note that shadow-utils login(1) does not support "a1)". The "a1)" is
+ * necessary if you want to use PAM for "Last login" message.
+ *
+ * -- Karel Zak <kzak@redhat.com> (26-Aug-2011)
+ *
+ *
+ * The per-account check requires some explanation: As root we may not be able
+ * to read the directory of the user if it is on an NFS-mounted filesystem. We
+ * temporarily set our effective uid to the user-uid, making sure that we keep
+ * root privileges in the real uid.
+ *
+ * A portable solution would require a fork(), but we rely on Linux having the
+ * BSD setreuid().
+ */
+
+int get_hushlogin_status(struct passwd *pwd, int force_check)
+{
+ const char *files[] = { _PATH_HUSHLOGINS, _PATH_HUSHLOGIN, NULL };
+ const char *file;
+ char buf[BUFSIZ];
+ int i;
+
+ file = getlogindefs_str("HUSHLOGIN_FILE", NULL);
+ if (file) {
+ if (!*file)
+ return 0; /* empty HUSHLOGIN_FILE defined */
+
+ files[0] = file;
+ files[1] = NULL;
+ }
+
+ for (i = 0; files[i]; i++) {
+ int ok = 0;
+
+ file = files[i];
+
+ /* global hush-file */
+ if (*file == '/') {
+ struct stat st;
+ FILE *f;
+
+ if (stat(file, &st) != 0)
+ continue; /* file does not exist */
+
+ if (st.st_size == 0)
+ return 1; /* for all accounts */
+
+ f = fopen(file, "r");
+ if (!f)
+ continue; /* ignore errors... */
+
+ while (ok == 0 && fgets(buf, sizeof(buf), f)) {
+ if (buf[0] != '\0')
+ buf[strlen(buf) - 1] = '\0';
+ ok = !strcmp(buf, *buf == '/' ? pwd->pw_shell :
+ pwd->pw_name);
+ }
+ fclose(f);
+ if (ok)
+ return 1; /* found username/shell */
+
+ return 0; /* ignore per-account files */
+ }
+
+ /* per-account setting */
+ if (strlen(pwd->pw_dir) + strlen(file) + 2 > sizeof(buf))
+ continue;
+
+ if (snprintf(buf, sizeof(buf), "%s/%s", pwd->pw_dir, file) < 0)
+ continue;
+
+ if (force_check) {
+ uid_t ruid = getuid();
+ gid_t egid = getegid();
+
+ if (setregid(-1, pwd->pw_gid) == 0 &&
+ setreuid(0, pwd->pw_uid) == 0)
+ ok = effective_access(buf, O_RDONLY) == 0;
+
+ if (setuid(0) != 0 ||
+ setreuid(ruid, 0) != 0 ||
+ setregid(-1, egid) != 0) {
+ syslog(LOG_ALERT, _("hush login status: restore original IDs failed"));
+ exit(EXIT_FAILURE);
+ }
+ if (ok)
+ return 1; /* enabled by user */
+ }
+ else {
+ int rc;
+ rc = effective_access(buf, O_RDONLY);
+ if (rc == 0)
+ return 1;
+
+ if (rc == -1 && errno == EACCES)
+ return -1;
+ }
+
+ }
+
+ return 0;
+}
+#ifdef TEST_PROGRAM
+int main(int argc, char *argv[])
+{
+ char *name, *type;
+ close_stdout_atexit();
+
+ if (argc <= 1)
+ errx(EXIT_FAILURE, "usage: %s <filename> "
+ "[<str|num|bool> <valname>]", argv[0]);
+
+ logindefs_load_file(argv[1]);
+
+ if (argc != 4) { /* list all */
+#ifdef HAVE_LIBECONF
+ int i;
+ char *keys[] = {"END", "EMPTY", "CRAZY3", "CRAZY2", "CRAZY1",
+ "BOOLEAN", "NUMBER", "STRING", "HELLO_WORLD",
+ NULL};
+
+ for (i = 0; keys[i] != NULL; i++) {
+ char *value = NULL;
+
+ econf_getStringValue(file, NULL, keys[i], &value);
+ printf ("%s: $%s: '%s'\n", argv[1], keys[i], value);
+ }
+
+ econf_free (file);
+
+#else
+ struct item *ptr;
+
+ for (ptr = list; ptr; ptr = ptr->next)
+ printf("%s: $%s: '%s'\n", ptr->path, ptr->name,
+ ptr->value);
+#endif
+ return EXIT_SUCCESS;
+ }
+
+ type = argv[2];
+ name = argv[3];
+
+ if (strcmp(type, "str") == 0)
+ printf("$%s: '%s'\n", name, getlogindefs_str(name, "DEFAULT"));
+ else if (strcmp(type, "num") == 0)
+ printf("$%s: '%ld'\n", name, getlogindefs_num(name, 0));
+ else if (strcmp(type, "bool") == 0)
+ printf("$%s: '%s'\n", name,
+ getlogindefs_bool(name, 0) ? "Y" : "N");
+
+ return EXIT_SUCCESS;
+}
+#endif