/* * pam_env module * * Written by Dave Kinchlea 1997/01/31 * Inspired by Andrew Morgan , who also supplied the * template for this file (via pam_mail) */ #include "config.h" #include #include #include #include #include #include #include #include #include #include #include #include #ifdef USE_ECONF #include "pam_econf.h" #endif #include #include #include #include #include "pam_inline.h" #ifndef USE_ECONF #include "pam_line.h" #endif /* This little structure makes it easier to keep variables together */ typedef struct var { char *name; char *value; char *defval; char *override; } VAR; #define DEFAULT_ETC_ENVFILE "/etc/environment" #ifdef VENDORDIR #define VENDOR_DEFAULT_ETC_ENVFILE (VENDORDIR "/environment") #endif #define DEFAULT_READ_ENVFILE 1 #define DEFAULT_USER_ENVFILE ".pam_environment" #define DEFAULT_USER_READ_ENVFILE 0 #define DEFAULT_CONF_FILE (SCONFIG_DIR "/pam_env.conf") #ifdef VENDOR_SCONFIG_DIR #define VENDOR_DEFAULT_CONF_FILE (VENDOR_SCONFIG_DIR "/pam_env.conf") #endif #define GOOD_LINE 0 #define BAD_LINE 100 /* This must be > the largest PAM_* error code */ #define DEFINE_VAR 101 #define UNDEFINE_VAR 102 #define ILLEGAL_VAR 103 struct string_buffer { char *str; size_t len; size_t size; }; /* This is a special value used to designate an empty string */ static char quote='\0'; static void free_string_array(char **array) { if (array == NULL) return; for (char **entry = array; *entry != NULL; ++entry) { pam_overwrite_string(*entry); free(*entry); } free(array); } /* argument parsing */ #define PAM_DEBUG_ARG 0x01 static int _pam_parse (const pam_handle_t *pamh, int argc, const char **argv, const char **conffile, const char **envfile, int *readenv, const char **user_envfile, int *user_readenv) { int ctrl=0; *user_envfile = DEFAULT_USER_ENVFILE; *envfile = NULL; *readenv = DEFAULT_READ_ENVFILE; *user_readenv = DEFAULT_USER_READ_ENVFILE; *conffile = NULL; /* step through arguments */ for (; argc-- > 0; ++argv) { const char *str; /* generic options */ if (!strcmp(*argv,"debug")) ctrl |= PAM_DEBUG_ARG; else if ((str = pam_str_skip_prefix(*argv, "conffile=")) != NULL) { if (str[0] == '\0') { pam_syslog(pamh, LOG_ERR, "conffile= specification missing argument - ignored"); } else { *conffile = str; D(("new Configuration File: %s", *conffile)); } } else if ((str = pam_str_skip_prefix(*argv, "envfile=")) != NULL) { if (str[0] == '\0') { pam_syslog (pamh, LOG_ERR, "envfile= specification missing argument - ignored"); } else { *envfile = str; D(("new Env File: %s", *envfile)); } } else if ((str = pam_str_skip_prefix(*argv, "user_envfile=")) != NULL) { if (str[0] == '\0') { pam_syslog (pamh, LOG_ERR, "user_envfile= specification missing argument - ignored"); } else { *user_envfile = str; D(("new User Env File: %s", *user_envfile)); } } else if ((str = pam_str_skip_prefix(*argv, "readenv=")) != NULL) { *readenv = atoi(str); } else if ((str = pam_str_skip_prefix(*argv, "user_readenv=")) != NULL) { *user_readenv = atoi(str); } else pam_syslog(pamh, LOG_ERR, "unknown option: %s", *argv); } if (*user_readenv) pam_syslog(pamh, LOG_DEBUG, "deprecated reading of user environment enabled"); return ctrl; } #ifdef USE_ECONF #define ENVIRONMENT "environment" #define PAM_ENV "pam_env" static int isDirectory(const char *path) { struct stat statbuf; if (stat(path, &statbuf) != 0) return 0; return S_ISDIR(statbuf.st_mode); } /* * Remove escaped newline from string. * * All occurrences of "\\n" will be removed from string. */ static void econf_unescnl(char *val) { char *dest, *p; dest = p = val; while (*p != '\0') { if (p[0] == '\\' && p[1] == '\n') { p += 2; } else { *dest++ = *p++; } } *dest = '\0'; } static int econf_read_file(const pam_handle_t *pamh, const char *filename, const char *delim, const char *name, const char *suffix, const char *subpath, char ***lines) { econf_file *key_file = NULL; econf_err error; size_t key_number = 0; char **keys = NULL; const char *base_dir = ""; if (filename != NULL) { if (isDirectory(filename)) { /* Set base directory which can be different from root */ D(("filename argument is a directory: %s", filename)); base_dir = filename; } else { /* Read only one file */ error = econf_readFile (&key_file, filename, delim, "#"); D(("File name is: %s", filename)); if (error != ECONF_SUCCESS) { pam_syslog(pamh, LOG_ERR, "Unable to open env file: %s: %s", filename, econf_errString(error)); if (error == ECONF_NOFILE) return PAM_IGNORE; else return PAM_ABORT; } } } if (filename == NULL || base_dir[0] != '\0') { /* Read and merge all setting in e.g. /usr/etc and /etc */ char *vendor_dir = NULL, *sysconf_dir; if (subpath != NULL && subpath[0] != '\0') { #ifdef VENDORDIR if (asprintf(&vendor_dir, "%s%s/%s/", base_dir, VENDORDIR, subpath) < 0) { pam_syslog(pamh, LOG_ERR, "Cannot allocate memory."); return PAM_BUF_ERR; } #endif if (asprintf(&sysconf_dir, "%s%s/%s/", base_dir, SYSCONFDIR, subpath) < 0) { pam_syslog(pamh, LOG_ERR, "Cannot allocate memory."); free(vendor_dir); return PAM_BUF_ERR; } } else { #ifdef VENDORDIR if (asprintf(&vendor_dir, "%s%s/", base_dir, VENDORDIR) < 0) { pam_syslog(pamh, LOG_ERR, "Cannot allocate memory."); return PAM_BUF_ERR; } #endif if (asprintf(&sysconf_dir, "%s%s/", base_dir, SYSCONFDIR) < 0) { pam_syslog(pamh, LOG_ERR, "Cannot allocate memory."); free(vendor_dir); return PAM_BUF_ERR; } } error = pam_econf_readconfig (&key_file, vendor_dir, sysconf_dir, name, suffix, delim, "#", NULL, NULL); free(vendor_dir); free(sysconf_dir); if (error != ECONF_SUCCESS) { if (error == ECONF_NOFILE) { pam_syslog(pamh, LOG_ERR, "Configuration file not found: %s%s", name, suffix); return PAM_IGNORE; } else { char *error_filename = NULL; uint64_t error_line = 0; econf_errLocation(&error_filename, &error_line); pam_syslog(pamh, LOG_ERR, "Unable to read configuration file %s line %ld: %s", error_filename, error_line, econf_errString(error)); free(error_filename); return PAM_ABORT; } } } error = econf_getKeys(key_file, NULL, &key_number, &keys); if (error != ECONF_SUCCESS && error != ECONF_NOKEY) { pam_syslog(pamh, LOG_ERR, "Unable to read keys: %s", econf_errString(error)); econf_freeFile(key_file); return PAM_ABORT; } *lines = calloc((key_number + 1), sizeof(char**)); if (*lines == NULL) { pam_syslog(pamh, LOG_ERR, "Cannot allocate memory."); econf_free(keys); econf_freeFile(key_file); return PAM_BUF_ERR; } size_t n = 0; for (size_t i = 0; i < key_number; i++) { char *val; error = econf_getStringValue (key_file, NULL, keys[i], &val); if (error != ECONF_SUCCESS || val == NULL) { pam_syslog(pamh, LOG_ERR, "Unable to get string from key %s: %s", keys[i], econf_errString(error)); } else { econf_unescnl(val); if (asprintf(&(*lines)[n],"%s%c%s", keys[i], delim[0], val) < 0) { pam_syslog(pamh, LOG_ERR, "Cannot allocate memory."); econf_free(keys); econf_freeFile(key_file); (*lines)[i] = NULL; free_string_array(*lines); free (val); return PAM_BUF_ERR; } free (val); n++; } } econf_free(keys); econf_free(key_file); return PAM_SUCCESS; } #else static int read_file(const pam_handle_t *pamh, const char*filename, char ***lines) { FILE *conf; struct pam_line_buffer buffer; _pam_line_buffer_init(&buffer); D(("Parsed file name is: %s", filename)); if ((conf = fopen(filename,"r")) == NULL) { pam_syslog(pamh, LOG_ERR, "Unable to open env file: %s", filename); return PAM_IGNORE; } size_t i = 0; *lines = malloc((i + 1)* sizeof(char*)); if (*lines == NULL) { pam_syslog(pamh, LOG_ERR, "Cannot allocate memory."); (void) fclose(conf); return PAM_BUF_ERR; } (*lines)[i] = 0; while (_pam_line_assemble(conf, &buffer, '\0') > 0) { char *p = buffer.assembled; char **tmp = NULL; D(("Read line: %s", p)); tmp = realloc(*lines, (++i + 1) * sizeof(char*)); if (tmp == NULL) { pam_syslog(pamh, LOG_ERR, "Cannot allocate memory."); (void) fclose(conf); free_string_array(*lines); _pam_line_buffer_clear(&buffer); return PAM_BUF_ERR; } *lines = tmp; (*lines)[i-1] = strdup(p); if ((*lines)[i-1] == NULL) { pam_syslog(pamh, LOG_ERR, "Cannot allocate memory."); (void) fclose(conf); free_string_array(*lines); _pam_line_buffer_clear(&buffer); return PAM_BUF_ERR; } (*lines)[i] = 0; } (void) fclose(conf); _pam_line_buffer_clear(&buffer); return PAM_SUCCESS; } #endif static int _parse_line(const pam_handle_t *pamh, const char *buffer, VAR *var) { /* * parse buffer into var, legal syntax is * VARIABLE [DEFAULT=[[string]] [OVERRIDE=[value]] * * Any other options defined make this a bad line, * error logged and no var set */ int length, quoteflg=0; const char *ptr, *tmpptr; char **valptr; D(("Called buffer = <%s>", buffer)); length = strcspn(buffer," \t\n"); /* * The first thing on the line HAS to be the variable name, * it may be the only thing though. */ if ((var->name = strndup(buffer, length)) == NULL) { D(("out of memory")); pam_syslog(pamh, LOG_CRIT, "out of memory"); return PAM_BUF_ERR; } D(("var->name = <%s>, length = %d", var->name, length)); /* * Now we check for arguments, we only support two kinds and ('cause I am lazy) * each one can actually be listed any number of times */ ptr = buffer+length; while ((length = strspn(ptr, " \t")) > 0) { ptr += length; /* remove leading whitespace */ D(("%s", ptr)); if ((tmpptr = pam_str_skip_prefix(ptr, "DEFAULT=")) != NULL) { ptr = tmpptr; D(("Default arg found: <%s>", ptr)); valptr=&(var->defval); } else if ((tmpptr = pam_str_skip_prefix(ptr, "OVERRIDE=")) != NULL) { ptr = tmpptr; D(("Override arg found: <%s>", ptr)); valptr=&(var->override); } else { D(("Unrecognized options: <%s> - ignoring line", ptr)); pam_syslog(pamh, LOG_ERR, "Unrecognized Option: %s - ignoring line", ptr); return BAD_LINE; } if ('"' != *ptr) { /* Escaped quotes not supported */ length = strcspn(ptr, " \t\n"); tmpptr = ptr+length; } else { tmpptr = strchr(++ptr, '"'); if (!tmpptr) { D(("Unterminated quoted string: %s", ptr-1)); pam_syslog(pamh, LOG_ERR, "Unterminated quoted string: %s", ptr-1); return BAD_LINE; } length = tmpptr - ptr; if (*++tmpptr && ' ' != *tmpptr && '\t' != *tmpptr && '\n' != *tmpptr) { D(("Quotes must cover the entire string: <%s>", ptr)); pam_syslog(pamh, LOG_ERR, "Quotes must cover the entire string: <%s>", ptr); return BAD_LINE; } quoteflg++; } if (length) { if (*valptr != "e) free(*valptr); if ((*valptr = strndup(ptr, length)) == NULL) { D(("out of memory")); pam_syslog(pamh, LOG_CRIT, "out of memory"); return PAM_BUF_ERR; } } else if (quoteflg) { quoteflg--; *valptr = "e; /* a quick hack to handle the empty string */ } ptr = tmpptr; /* Start the search where we stopped */ } /* while */ /* * The line is parsed, all is well. */ D(("Exit.")); ptr = NULL; tmpptr = NULL; valptr = NULL; return GOOD_LINE; } static const char * _pam_get_item_byname(pam_handle_t *pamh, const char *name) { /* * This function just allows me to use names as given in the config * file and translate them into the appropriate PAM_ITEM macro */ int item; const void *itemval; D(("Called.")); if (strcmp(name, "PAM_USER") == 0 || strcmp(name, "HOME") == 0 || strcmp(name, "SHELL") == 0) { item = PAM_USER; } else if (strcmp(name, "PAM_USER_PROMPT") == 0) { item = PAM_USER_PROMPT; } else if (strcmp(name, "PAM_TTY") == 0) { item = PAM_TTY; } else if (strcmp(name, "PAM_RUSER") == 0) { item = PAM_RUSER; } else if (strcmp(name, "PAM_RHOST") == 0) { item = PAM_RHOST; } else { D(("Unknown PAM_ITEM: <%s>", name)); pam_syslog (pamh, LOG_ERR, "Unknown PAM_ITEM: <%s>", name); return NULL; } if (pam_get_item(pamh, item, &itemval) != PAM_SUCCESS) { D(("pam_get_item failed")); return NULL; /* let pam_get_item() log the error */ } if (itemval && (strcmp(name, "HOME") == 0 || strcmp(name, "SHELL") == 0)) { struct passwd *user_entry; user_entry = pam_modutil_getpwnam (pamh, itemval); if (!user_entry) { pam_syslog(pamh, LOG_ERR, "No such user!?"); return NULL; } return (strcmp(name, "SHELL") == 0) ? user_entry->pw_shell : user_entry->pw_dir; } D(("Exit.")); return itemval; } static void _strbuf_init(struct string_buffer *buffer) { buffer->str = NULL; buffer->len = 0; buffer->size = 0; } static void _strbuf_free(struct string_buffer *buffer) { pam_overwrite_n(buffer->str, buffer->len); _pam_drop(buffer->str); buffer->len = 0; buffer->size = 0; } /* * Allocates given amount of bytes in buffer for addition. * Internally adding an extra byte for NUL character. * * Returns 0 on success, 1 on error. */ static int _strbuf_reserve(struct string_buffer *buffer, size_t add) { char *p; size_t s; /* Is already enough memory allocated? */ if (add < buffer->size - buffer->len) { return 0; } /* Can the requested bytes (plus additional NUL byte) fit at all? */ if (buffer->len >= SIZE_MAX - add) { return 1; } if (buffer->size == 0 && add < 64) { /* Start with 64 bytes if that's enough */ s = 64; } else if (buffer->size >= SIZE_MAX / 2 || buffer->size * 2 < add + 1) { /* If doubling is not enough (or not possible), get as much as needed */ s = buffer->len + add + 1; } else { /* Ideally, double allocated memory */ s = buffer->size * 2; } if ((p = realloc(buffer->str, s)) == NULL) { return 1; } buffer->str = p; buffer->size = s; return 0; } static int _strbuf_add_char(struct string_buffer *buffer, char c) { D(("Called <%s> + <%c>.", buffer->str == NULL ? "" : buffer->str, c)); if (_strbuf_reserve(buffer, 1)) { return 1; } buffer->str[buffer->len++] = c; buffer->str[buffer->len] = '\0'; return 0; } static int _strbuf_add_string(struct string_buffer *buffer, const char *str) { size_t len = strlen(str); D(("Called <%s> + <%s>.", buffer->str == NULL ? "" : buffer->str, str)); if (_strbuf_reserve(buffer, len)) { return 1; } strcpy(buffer->str + buffer->len, str); buffer->len += len; return 0; } static int _expand_arg(pam_handle_t *pamh, char **value) { const char *orig=*value; struct string_buffer buf; /* * Return early if there are no special characters present in the value. */ if ((*value)[strcspn(*value, "\\$@")] == '\0') { return PAM_SUCCESS; } _strbuf_init(&buf); /* * (possibly non-existent) environment variables can be used as values * by prepending a "$" and wrapping in {} (ie: ${HOST}), can escape with "\" * (possibly non-existent) PAM items can be used as values * by prepending a "@" and wrapping in {} (ie: @{PAM_RHOST}, can escape * */ D(("Expanding <%s>",orig)); while (*orig) { /* while there is some input to deal with */ if ('\\' == *orig) { ++orig; if ('$' != *orig && '@' != *orig && '\\' != *orig) { D(("Unrecognized escaped character: <%c> - ignoring", *orig)); pam_syslog(pamh, LOG_ERR, "Unrecognized escaped character: <%c> - ignoring", *orig); } else { /* Note the increment */ if (_strbuf_add_char(&buf, *orig++)) { goto buf_err; } } continue; } if ('$' == *orig || '@' == *orig) { if ('{' != *(orig+1)) { D(("Expandable variables must be wrapped in {}" " <%s> - ignoring", orig)); pam_syslog(pamh, LOG_ERR, "Expandable variables must be wrapped in {}" " <%s> - ignoring", orig); /* Note the increment */ if (_strbuf_add_char(&buf, *orig++)) { goto buf_err; } continue; } else { const char *tmpptr=NULL, *tmpval; char *ptr; /* * Sure would be nice to use tmpptr but it needs to be * a constant so that the compiler will shut up when I * call pam_getenv and _pam_get_item_byname -- sigh */ char type; D(("Expandable argument: <%s>", orig)); type = *orig; orig+=2; /* skip the ${ or @{ characters */ ptr = strchr(orig, '}'); if (ptr) { *ptr++ = '\0'; } else { D(("Unterminated expandable variable: <%s>", orig-2)); pam_syslog(pamh, LOG_ERR, "Unterminated expandable variable: <%s>", orig-2); goto abort_err; } tmpval = orig; orig=ptr; /* * so, we know we need to expand tmpval, it is either * an environment variable or a PAM_ITEM. type will tell us which */ switch (type) { case '$': D(("Expanding env var: <%s>",tmpval)); tmpptr = pam_getenv(pamh, tmpval); D(("Expanded to <%s>", tmpptr)); break; case '@': D(("Expanding pam item: <%s>",tmpval)); tmpptr = _pam_get_item_byname(pamh, tmpval); D(("Expanded to <%s>", tmpptr)); break; default: D(("Impossible error, type == <%c>", type)); pam_syslog(pamh, LOG_CRIT, "Impossible error, type == <%c>", type); goto abort_err; } /* switch */ if (tmpptr) { if (_strbuf_add_string(&buf, tmpptr)) { goto buf_err; } } } /* if ('{' != *orig++) */ } else { /* if ( '$' == *orig || '@' == *orig) */ /* Note the increment */ if (_strbuf_add_char(&buf, *orig++)) { goto buf_err; } } } /* for (;*orig;) */ if (buf.len > strlen(*value)) { free(*value); if ((*value = strdup(buf.str)) == NULL) { goto buf_err; } } else { const char *tmpptr = buf.str == NULL ? "" : buf.str; strcpy(*value, tmpptr); } _strbuf_free(&buf); D(("Exit.")); return PAM_SUCCESS; buf_err: _strbuf_free(&buf); return PAM_BUF_ERR; abort_err: _strbuf_free(&buf); return PAM_ABORT; } static int _check_var(pam_handle_t *pamh, VAR *var) { /* * Examine the variable and determine what action to take. * Returns DEFINE_VAR, UNDEFINE_VAR depending on action to take * or a PAM_* error code if passed back from other routines * * if no DEFAULT provided, the empty string is assumed * if no OVERRIDE provided, the empty string is assumed * if DEFAULT= and OVERRIDE evaluates to the empty string, * this variable should be undefined * if DEFAULT="" and OVERRIDE evaluates to the empty string, * this variable should be defined with no value * if OVERRIDE=value and value turns into the empty string, DEFAULT is used * * If DEFINE_VAR is to be returned, the correct value to define will * be pointed to by var->value */ int retval; D(("Called.")); /* * First thing to do is to expand any arguments, but only * if they are not the special quote values (cause expand_arg * changes memory). */ if (var->defval && ("e != var->defval) && ((retval = _expand_arg(pamh, &(var->defval))) != PAM_SUCCESS)) { return retval; } if (var->override && ("e != var->override) && ((retval = _expand_arg(pamh, &(var->override))) != PAM_SUCCESS)) { return retval; } /* Now it's easy */ if (var->override && *(var->override)) { /* if there is a non-empty string in var->override, we use it */ D(("OVERRIDE variable <%s> being used: <%s>", var->name, var->override)); var->value = var->override; retval = DEFINE_VAR; } else { var->value = var->defval; if ("e == var->defval) { /* * This means that the empty string was given for defval value * which indicates that a variable should be defined with no value */ D(("An empty variable: <%s>", var->name)); retval = DEFINE_VAR; } else if (var->defval) { D(("DEFAULT variable <%s> being used: <%s>", var->name, var->defval)); retval = DEFINE_VAR; } else { D(("UNDEFINE variable <%s>", var->name)); retval = UNDEFINE_VAR; } } D(("Exit.")); return retval; } static void _clean_var(VAR *var) { if (var->name) { pam_overwrite_string(var->name); free(var->name); } if (var->defval && ("e != var->defval)) { pam_overwrite_string(var->defval); free(var->defval); } if (var->override && ("e != var->override)) { pam_overwrite_string(var->override); free(var->override); } var->name = NULL; var->value = NULL; /* never has memory specific to it */ var->defval = NULL; var->override = NULL; } static int _define_var(pam_handle_t *pamh, int ctrl, VAR *var) { /* We have a variable to define, this is a simple function */ char *envvar; int retval = PAM_SUCCESS; D(("Called.")); if (asprintf(&envvar, "%s=%s", var->name, var->value) < 0) { pam_syslog(pamh, LOG_CRIT, "out of memory"); return PAM_BUF_ERR; } retval = pam_putenv(pamh, envvar); if (ctrl & PAM_DEBUG_ARG) { pam_syslog(pamh, LOG_DEBUG, "pam_putenv(\"%s\")", envvar); } _pam_drop(envvar); D(("Exit.")); return retval; } static int _undefine_var(pam_handle_t *pamh, int ctrl, VAR *var) { /* We have a variable to undefine, this is a simple function */ D(("Called and exit.")); if (ctrl & PAM_DEBUG_ARG) { pam_syslog(pamh, LOG_DEBUG, "remove variable \"%s\"", var->name); } return pam_putenv(pamh, var->name); } static int _parse_config_file(pam_handle_t *pamh, int ctrl, const char *file) { int retval; VAR Var, *var=&Var; char **conf_list = NULL; var->name=NULL; var->defval=NULL; var->override=NULL; D(("Called.")); #ifdef USE_ECONF /* If "file" is not NULL, only this file will be parsed. */ retval = econf_read_file(pamh, file, " \t", PAM_ENV, ".conf", "security", &conf_list); #else /* !USE_ECONF */ /* Only one file will be parsed. So, file has to be set. */ if (file == NULL) { /* No filename has been set via argv. */ file = DEFAULT_CONF_FILE; # ifdef VENDOR_DEFAULT_CONF_FILE /* * Check whether DEFAULT_CONF_FILE file is available. * If it does not exist, fall back to VENDOR_DEFAULT_CONF_FILE file. */ struct stat stat_buffer; if (stat(file, &stat_buffer) != 0 && errno == ENOENT) file = VENDOR_DEFAULT_CONF_FILE; # endif } retval = read_file(pamh, file, &conf_list); #endif if (retval != PAM_SUCCESS) return retval; for (char **conf = conf_list; *conf != NULL; ++conf) { if ((retval = _parse_line(pamh, *conf, var)) == GOOD_LINE) { retval = _check_var(pamh, var); if (DEFINE_VAR == retval) { retval = _define_var(pamh, ctrl, var); } else if (UNDEFINE_VAR == retval) { retval = _undefine_var(pamh, ctrl, var); } } if (PAM_SUCCESS != retval && ILLEGAL_VAR != retval && BAD_LINE != retval && PAM_BAD_ITEM != retval) break; _clean_var(var); } /* for */ /* tidy up */ free_string_array(conf_list); _clean_var(var); /* We could have got here prematurely, * this is safe though */ D(("Exit.")); return (retval != 0 ? PAM_ABORT : PAM_SUCCESS); } static int _parse_env_file(pam_handle_t *pamh, int ctrl, const char *file) { int retval=PAM_SUCCESS, i, t; char *key, *mark; char **env_list = NULL; #ifdef USE_ECONF retval = econf_read_file(pamh, file, "=", ENVIRONMENT, "", "", &env_list); #else /* Only one file will be parsed. So, file has to be set. */ if (file == NULL) /* No filename has been set via argv. */ file = DEFAULT_ETC_ENVFILE; #ifdef VENDOR_DEFAULT_ETC_ENVFILE /* * Check whether file is available. * If it does not exist, fall back to VENDOR_DEFAULT_ETC_ENVFILE; file. */ struct stat stat_buffer; if (stat(file, &stat_buffer) != 0 && errno == ENOENT) { file = VENDOR_DEFAULT_ETC_ENVFILE; } #endif retval = read_file(pamh, file, &env_list); #endif if (retval != PAM_SUCCESS) return retval == PAM_IGNORE ? PAM_SUCCESS : retval; for (char **env = env_list; *env != NULL; ++env) { key = *env; /* skip leading white space */ key += strspn(key, " \n\t"); /* skip blanks lines and comments */ if (key[0] == '#') continue; /* skip over "export " if present so we can be compat with bash type declarations */ if (strncmp(key, "export ", (size_t) 7) == 0) key += 7; /* now find the end of value */ mark = key; while(mark[0] != '\n' && mark[0] != '#' && mark[0] != '\0') mark++; if (mark[0] != '\0') mark[0] = '\0'; /* * sanity check, the key must be alphanumeric */ if (key[0] == '=') { pam_syslog(pamh, LOG_ERR, "missing key name '%s' in %s', ignoring", key, file); continue; } for ( i = 0 ; key[i] != '=' && key[i] != '\0' ; i++ ) if (!isalnum((unsigned char)key[i]) && key[i] != '_') { pam_syslog(pamh, LOG_ERR, "non-alphanumeric key '%s' in %s', ignoring", key, file); break; } /* non-alphanumeric key, ignore this line */ if (key[i] != '=' && key[i] != '\0') continue; /* now we try to be smart about quotes around the value, but not too smart, we can't get all fancy with escaped values like bash */ if (key[i] == '=' && (key[++i] == '\"' || key[i] == '\'')) { for ( t = i+1 ; key[t] != '\0' ; t++) if (key[t] != '\"' && key[t] != '\'') key[i++] = key[t]; else if (key[t+1] != '\0') key[i++] = key[t]; key[i] = '\0'; } /* if this is a request to delete a variable, check that it's actually set first, so we don't get a vague error back from pam_putenv() */ for (i = 0; key[i] != '=' && key[i] != '\0'; i++); if (key[i] == '\0' && !pam_getenv(pamh,key)) continue; /* set the env var, if it fails, we break out of the loop */ retval = pam_putenv(pamh, key); if (retval != PAM_SUCCESS) { D(("error setting env \"%s\"", key)); break; } else if (ctrl & PAM_DEBUG_ARG) { pam_syslog(pamh, LOG_DEBUG, "pam_putenv(\"%s\")", key); } } /* tidy up */ free_string_array(env_list); D(("Exit.")); return retval; } /* --- authentication management functions (only) --- */ int pam_sm_authenticate (pam_handle_t *pamh UNUSED, int flags UNUSED, int argc UNUSED, const char **argv UNUSED) { return PAM_IGNORE; } static int handle_env (pam_handle_t *pamh, int argc, const char **argv) { int retval, ctrl, readenv=DEFAULT_READ_ENVFILE; int user_readenv = DEFAULT_USER_READ_ENVFILE; const char *conf_file = NULL, *env_file = NULL, *user_env_file = NULL; /* * this module sets environment variables read in from a file */ D(("Called.")); ctrl = _pam_parse(pamh, argc, argv, &conf_file, &env_file, &readenv, &user_env_file, &user_readenv); retval = _parse_config_file(pamh, ctrl, conf_file); if(readenv && retval == PAM_SUCCESS) { retval = _parse_env_file(pamh, ctrl, env_file); if (retval == PAM_IGNORE) retval = PAM_SUCCESS; } if(user_readenv && retval == PAM_SUCCESS) { char *envpath = NULL; struct passwd *user_entry = NULL; const char *username; struct stat statbuf; username = _pam_get_item_byname(pamh, "PAM_USER"); if (username) user_entry = pam_modutil_getpwnam (pamh, username); if (!user_entry) { pam_syslog(pamh, LOG_ERR, "No such user!?"); } else { if (asprintf(&envpath, "%s/%s", user_entry->pw_dir, user_env_file) < 0) { pam_syslog(pamh, LOG_CRIT, "Out of memory"); return PAM_BUF_ERR; } if (stat(envpath, &statbuf) == 0) { PAM_MODUTIL_DEF_PRIVS(privs); if (pam_modutil_drop_priv(pamh, &privs, user_entry)) { retval = PAM_SESSION_ERR; } else { retval = _parse_config_file(pamh, ctrl, envpath); if (pam_modutil_regain_priv(pamh, &privs)) retval = PAM_SESSION_ERR; } if (retval == PAM_IGNORE) retval = PAM_SUCCESS; } free(envpath); } } /* indicate success or failure */ D(("Exit.")); return retval; } int pam_sm_acct_mgmt (pam_handle_t *pamh UNUSED, int flags UNUSED, int argc UNUSED, const char **argv UNUSED) { pam_syslog (pamh, LOG_NOTICE, "pam_sm_acct_mgmt called inappropriately"); return PAM_SERVICE_ERR; } int pam_sm_setcred (pam_handle_t *pamh, int flags UNUSED, int argc, const char **argv) { D(("Called")); return handle_env (pamh, argc, argv); } int pam_sm_open_session (pam_handle_t *pamh, int flags UNUSED, int argc, const char **argv) { D(("Called")); return handle_env (pamh, argc, argv); } int pam_sm_close_session (pam_handle_t *pamh UNUSED, int flags UNUSED, int argc UNUSED, const char **argv UNUSED) { D(("Called and Exit")); return PAM_SUCCESS; } int pam_sm_chauthtok (pam_handle_t *pamh UNUSED, int flags UNUSED, int argc UNUSED, const char **argv UNUSED) { pam_syslog (pamh, LOG_NOTICE, "pam_sm_chauthtok called inappropriately"); return PAM_SERVICE_ERR; } /* end of module definition */