| /* |
| * 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" |
| |
| 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 (*logindefs_load_defaults)(void) = 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_load_defaults) |
| logindefs_load_defaults(); |
| 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, 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; |
| } |
| |
| /* |
| * 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)) { |
| 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) + sizeof(file) + 2 > sizeof(buf)) |
| continue; |
| |
| sprintf(buf, "%s/%s", pwd->pw_dir, file); |
| |
| 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; |
| else if (rc == -1 && errno == EACCES) |
| return -1; |
| } |
| |
| } |
| |
| return 0; |
| } |
| #ifdef TEST_PROGRAM |
| int main(int argc, char *argv[]) |
| { |
| char *name, *type; |
| atexit(close_stdout); |
| |
| 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 */ |
| struct item *ptr; |
| |
| for (ptr = list; ptr; ptr = ptr->next) |
| printf("%s: $%s: '%s'\n", ptr->path, ptr->name, |
| ptr->value); |
| |
| 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 |