diff -Naur org/configure.ac patch/configure.ac --- org/configure.ac 2022-10-11 12:35:53.558193223 +0200 +++ patch/configure.ac 2022-10-11 12:36:32.502192985 +0200 @@ -511,7 +511,11 @@ [Directory for distribution provided configuration files]) AC_DEFINE_UNQUOTED([VENDOR_SCONFIGDIR], ["$enable_vendordir/security"], [Directory for PAM modules distribution provided configuration files]) - STRINGPARAM_VENDORDIR="--stringparam vendordir '$enable_vendordir' --stringparam profile.condition 'with_vendordir'" + if test "$WITH_ECONF" = "yes" ; then + STRINGPARAM_VENDORDIR="--stringparam vendordir '$enable_vendordir' --stringparam profile.condition 'with_vendordir;with_vendordir_and_with_econf'" + else + STRINGPARAM_VENDORDIR="--stringparam vendordir '$enable_vendordir' --stringparam profile.condition 'with_vendordir;with_vendordir_and_without_econf" + fi else STRINGPARAM_VENDORDIR="--stringparam profile.condition 'without_vendordir'" fi diff -Naur org/modules/pam_env/Makefile.am patch/modules/pam_env/Makefile.am --- org/modules/pam_env/Makefile.am 2022-10-11 12:35:53.574193223 +0200 +++ patch/modules/pam_env/Makefile.am 2022-10-11 12:36:32.518192985 +0200 @@ -12,20 +12,23 @@ endif XMLS = README.xml pam_env.conf.5.xml pam_env.8.xml dist_check_SCRIPTS = tst-pam_env -TESTS = $(dist_check_SCRIPTS) +TESTS = $(dist_check_SCRIPTS) $(check_PROGRAMS) securelibdir = $(SECUREDIR) secureconfdir = $(SCONFIGDIR) AM_CFLAGS = -I$(top_srcdir)/libpam/include -I$(top_srcdir)/libpamc/include \ - $(WARN_CFLAGS) + $(WARN_CFLAGS) -DSYSCONFDIR=\"$(sysconfdir)\" $(ECONF_CFLAGS) AM_LDFLAGS = -no-undefined -avoid-version -module if HAVE_VERSIONING AM_LDFLAGS += -Wl,--version-script=$(srcdir)/../modules.map endif securelib_LTLIBRARIES = pam_env.la -pam_env_la_LIBADD = $(top_builddir)/libpam/libpam.la +pam_env_la_LIBADD = $(top_builddir)/libpam/libpam.la $(ECONF_LIBS) + +check_PROGRAMS = tst-pam_env-retval +tst_pam_env_retval_LDADD = $(top_builddir)/libpam/libpam.la dist_secureconf_DATA = pam_env.conf dist_sysconf_DATA = environment diff -Naur org/modules/pam_env/pam_env.8.xml patch/modules/pam_env/pam_env.8.xml --- org/modules/pam_env/pam_env.8.xml 2022-10-11 12:35:53.574193223 +0200 +++ patch/modules/pam_env/pam_env.8.xml 2022-10-11 12:36:32.518192985 +0200 @@ -52,13 +52,55 @@ variables as well as PAM_ITEMs such as PAM_RHOST. - + + Rules for (un)setting of variables can be defined in an own config + file. The path to this file can be specified with the + conffile option. + If this file does not exist, the default rules are taken from the + config files /etc/security/pam_env.conf and + /etc/security/pam_env.conf.d/*.conf. + If the file /etc/security/pam_env.conf does not + exist, the rules are taken from the files + %vendordir%/security/pam_env.conf, + %vendordir%/security/pam_env.conf.d/*.conf and + /etc/security/pam_env.conf.d/*.conf in that order. + + + By default rules for (un)setting of variables are taken from the + config file /etc/security/pam_env.conf. + If this file does not exist %vendordir%/security/pam_env.conf is used. + An alternate file can be specified with the conffile + option, which overrules all other files. + + By default rules for (un)setting of variables are taken from the config file /etc/security/pam_env.conf. An alternate file can be specified with the conffile option. - + + Environment variables can be defined in a file with simple KEY=VAL + pairs on separate lines. The path to this file can be specified with the + envfile option. + If this file has not been defined, the settings are read from the + files /etc/security/environment and + /etc/security/environment.d/*. + If the file /etc/environment does not exist, the + settings are read from the files %vendordir%/environment, + %vendordir%/environment.d/* and + /etc/environment.d/* in that order. + And last but not least, with the readenv option this mechanism can + be completely disabled. + + + Second a file (/etc/environment by default) with simple + KEY=VAL pairs on separate lines will be read. + If this file does not exist, %vendordir%/etc/environment is used. + With the envfile option an alternate file can be specified, + which overrules all other files. + And with the readenv option this can be completely disabled. + + Second a file (/etc/environment by default) with simple KEY=VAL pairs on separate lines will be read. With the envfile option an alternate file can be specified. @@ -224,12 +266,14 @@ FILES + /usr/etc/security/pam_env.conf /etc/security/pam_env.conf Default configuration file + /usr/etc/environment /etc/environment Default environment file diff -Naur org/modules/pam_env/pam_env.c patch/modules/pam_env/pam_env.c --- org/modules/pam_env/pam_env.c 2022-10-11 12:35:53.574193223 +0200 +++ patch/modules/pam_env/pam_env.c 2022-10-11 12:36:32.518192985 +0200 @@ -7,6 +7,9 @@ */ #define DEFAULT_ETC_ENVFILE "/etc/environment" +#ifdef VENDORDIR +#define VENDOR_DEFAULT_ETC_ENVFILE (VENDORDIR "/etc/environment") +#endif #define DEFAULT_READ_ENVFILE 1 #define DEFAULT_USER_ENVFILE ".pam_environment" @@ -25,6 +28,9 @@ #include #include #include +#ifdef USE_ECONF +#include +#endif #include #include @@ -42,6 +48,9 @@ } VAR; #define DEFAULT_CONF_FILE (SCONFIGDIR "/pam_env.conf") +#ifdef VENDOR_SCONFIGDIR +#define VENDOR_DEFAULT_CONF_FILE (VENDOR_SCONFIGDIR "/pam_env.conf") +#endif #define BUF_SIZE 8192 #define MAX_ENV 8192 @@ -53,18 +62,19 @@ #define UNDEFINE_VAR 102 #define ILLEGAL_VAR 103 -static int _assemble_line(FILE *, char *, int); -static int _parse_line(const pam_handle_t *, const char *, VAR *); -static int _check_var(pam_handle_t *, VAR *); /* This is the real meat */ -static void _clean_var(VAR *); -static int _expand_arg(pam_handle_t *, char **); -static const char * _pam_get_item_byname(pam_handle_t *, const char *); -static int _define_var(pam_handle_t *, int, VAR *); -static int _undefine_var(pam_handle_t *, int, VAR *); - /* 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) { + free(*entry); + } + free(array); +} + /* argument parsing */ #define PAM_DEBUG_ARG 0x01 @@ -77,10 +87,10 @@ int ctrl=0; *user_envfile = DEFAULT_USER_ENVFILE; - *envfile = DEFAULT_ETC_ENVFILE; + *envfile = NULL; *readenv = DEFAULT_READ_ENVFILE; *user_readenv = DEFAULT_USER_READ_ENVFILE; - *conffile = DEFAULT_CONF_FILE; + *conffile = NULL; /* step through arguments */ for (; argc-- > 0; ++argv) { @@ -128,166 +138,145 @@ return ctrl; } -static int -_parse_config_file(pam_handle_t *pamh, int ctrl, const char *file) -{ - int retval; - char buffer[BUF_SIZE]; - FILE *conf; - VAR Var, *var=&Var; - - D(("Called.")); - - var->name=NULL; var->defval=NULL; var->override=NULL; +#ifdef USE_ECONF - D(("Config file name is: %s", file)); +#define ENVIRONMENT "environment" +#define PAM_ENV "pam_env" - /* - * Lets try to open the config file, parse it and process - * any variables found. - */ +static int +isDirectory(const char *path) { + struct stat statbuf; + if (stat(path, &statbuf) != 0) + return 0; + return S_ISDIR(statbuf.st_mode); +} - if ((conf = fopen(file,"r")) == NULL) { - pam_syslog(pamh, LOG_ERR, "Unable to open config file: %s: %m", file); - return PAM_IGNORE; +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; + } + } } - - /* _pam_assemble_line will provide a complete line from the config file, - * with all comments removed and any escaped newlines fixed up - */ - - while (( retval = _assemble_line(conf, buffer, BUF_SIZE)) > 0) { - D(("Read line: %s", buffer)); - - if ((retval = _parse_line(pamh, buffer, 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 (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; } } - if (PAM_SUCCESS != retval && ILLEGAL_VAR != retval - && BAD_LINE != retval && PAM_BAD_ITEM != retval) break; - - _clean_var(var); - - } /* while */ - - (void) fclose(conf); - - /* tidy up */ - _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 buffer[BUF_SIZE], *key, *mark; - FILE *conf; - - D(("Env file name is: %s", file)); - - if ((conf = fopen(file,"r")) == NULL) { - pam_syslog(pamh, LOG_ERR, "Unable to open env file: %s: %m", file); - return PAM_IGNORE; + D(("Read configuration from directory %s and %s", vendor_dir, sysconf_dir)); + error = econf_readDirs (&key_file, vendor_dir, sysconf_dir, name, suffix, + delim, "#"); + free(vendor_dir); + free(sysconf_dir); + if (error != ECONF_SUCCESS) { + pam_syslog(pamh, LOG_ERR, "Unable to read configuration in different directories: %s", + econf_errString(error)); + if (error == ECONF_NOFILE) + return PAM_IGNORE; + else + return PAM_ABORT; + } } - while (_assemble_line(conf, buffer, BUF_SIZE) > 0) { - D(("Read line: %s", buffer)); - key = buffer; - - /* 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(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; + 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; + } - /* 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'; - } + *lines = malloc((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; + } - /* 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++); + (*lines)[key_number] = 0; - if (key[i] == '\0' && !pam_getenv(pamh,key)) - continue; + for (size_t i = 0; i < key_number; i++) { + char *val; - /* 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); + error = econf_getStringValue (key_file, NULL, keys[i], &val); + if (error != ECONF_SUCCESS) { + pam_syslog(pamh, LOG_ERR, "Unable to get string from key %s: %s", + keys[i], + econf_errString(error)); + } else { + if (asprintf(&(*lines)[i],"%s%c%s", keys[i], delim[0], val) < 0) { + pam_syslog(pamh, LOG_ERR, "Cannot allocate memory."); + econf_free(keys); + econf_freeFile(key_file); + free_string_array(*lines); + free (val); + return PAM_BUF_ERR; } + free (val); + } } - (void) fclose(conf); - - /* tidy up */ - D(("Exit.")); - return retval; + econf_free(keys); + econf_free(key_file); + return PAM_SUCCESS; } +#else + /* * This is where we read a line of the PAM config file. The line may be * preceded by lines of comments and also extended with "\\\n" */ - -static int _assemble_line(FILE *f, char *buffer, int buf_len) +static int +_assemble_line(FILE *f, char *buffer, int buf_len) { char *p = buffer; char *s, *os; @@ -375,8 +364,54 @@ return used; } +static int read_file(const pam_handle_t *pamh, const char*filename, char ***lines) +{ + FILE *conf; + char buffer[BUF_SIZE]; + + 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 (_assemble_line(conf, buffer, BUF_SIZE) > 0) { + char **tmp = NULL; + D(("Read line: %s", buffer)); + 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); + return PAM_BUF_ERR; + } + *lines = tmp; + (*lines)[i-1] = strdup(buffer); + if ((*lines)[i-1] == NULL) { + pam_syslog(pamh, LOG_ERR, "Cannot allocate memory."); + (void) fclose(conf); + free_string_array(*lines); + return PAM_BUF_ERR; + } + (*lines)[i] = 0; + } + + (void) fclose(conf); + return PAM_SUCCESS; +} +#endif + static int -_parse_line (const pam_handle_t *pamh, const char *buffer, VAR *var) +_parse_line(const pam_handle_t *pamh, const char *buffer, VAR *var) { /* * parse buffer into var, legal syntax is @@ -471,75 +506,57 @@ return GOOD_LINE; } -static int _check_var(pam_handle_t *pamh, VAR *var) +static const char * +_pam_get_item_byname(pam_handle_t *pamh, const char *name) { /* - * 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 + * This function just allows me to use names as given in the config + * file and translate them into the appropriate PAM_ITEM macro */ - int retval; + int item; + const void *itemval; 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; + 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; } - /* Now its 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 { + if (pam_get_item(pamh, item, &itemval) != PAM_SUCCESS) { + D(("pam_get_item failed")); + return NULL; /* let pam_get_item() log the error */ + } - 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; + 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 retval; + return itemval; } -static int _expand_arg(pam_handle_t *pamh, char **value) +static int +_expand_arg(pam_handle_t *pamh, char **value) { const char *orig=*value, *tmpptr=NULL; char *ptr; /* @@ -679,55 +696,96 @@ return PAM_SUCCESS; } -static const char * _pam_get_item_byname(pam_handle_t *pamh, const char *name) +static int +_check_var(pam_handle_t *pamh, VAR *var) { /* - * This function just allows me to use names as given in the config - * file and translate them into the appropriate PAM_ITEM macro + * 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 item; - const void *itemval; + int retval; 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 */ + /* + * 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; } - 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; + /* Now its 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; } - return (strcmp(name, "SHELL") == 0) ? - user_entry->pw_shell : - user_entry->pw_dir; } D(("Exit.")); - return itemval; + return retval; } -static int _define_var(pam_handle_t *pamh, int ctrl, VAR *var) +static void +_clean_var(VAR *var) +{ + if (var->name) { + free(var->name); + } + if (var->defval && ("e != var->defval)) { + free(var->defval); + } + if (var->override && ("e != var->override)) { + free(var->override); + } + var->name = NULL; + var->value = NULL; /* never has memory specific to it */ + var->defval = NULL; + var->override = NULL; + return; +} + +static int +_define_var(pam_handle_t *pamh, int ctrl, VAR *var) { /* We have a variable to define, this is a simple function */ @@ -749,7 +807,8 @@ return retval; } -static int _undefine_var(pam_handle_t *pamh, int ctrl, VAR *var) +static int +_undefine_var(pam_handle_t *pamh, int ctrl, VAR *var) { /* We have a variable to undefine, this is a simple function */ @@ -760,25 +819,176 @@ return pam_putenv(pamh, var->name); } -static void _clean_var(VAR *var) +static int +_parse_config_file(pam_handle_t *pamh, int ctrl, const char *file) { - if (var->name) { - free(var->name); - } - if (var->defval && ("e != var->defval)) { - free(var->defval); - } - if (var->override && ("e != var->override)) { - free(var->override); - } - var->name = NULL; - var->value = NULL; /* never has memory specific to it */ - var->defval = NULL; - var->override = NULL; - return; + 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 + /* 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 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(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); + } + free(*env); + } + /* tidy up */ + free(env_list); + D(("Exit.")); + return retval; +} /* --- authentication management functions (only) --- */ diff -Naur org/modules/pam_env/pam_env.conf.5.xml patch/modules/pam_env/pam_env.conf.5.xml --- org/modules/pam_env/pam_env.conf.5.xml 2022-10-11 12:35:53.574193223 +0200 +++ patch/modules/pam_env/pam_env.conf.5.xml 2022-10-11 12:36:32.518192985 +0200 @@ -20,7 +20,15 @@ DESCRIPTION - + + The /usr/etc/security/pam_env.conf and + /etc/security/pam_env.conf files specify + the environment variables to be set, unset or modified by + pam_env8. + When someone logs in, these files are read and the environment + variables are set according. + + The /etc/security/pam_env.conf file specifies the environment variables to be set, unset or modified by pam_env8. @@ -61,7 +69,15 @@ at front) can be used to mark this line as a comment line. - + + The /usr/etc/environment and /etc/environment files specify + the environment variables to be set. These files must consist of simple + NAME=VALUE pairs on separate lines. + The pam_env8 + module will read these files after the pam_env.conf + file. + + The /etc/environment file specifies the environment variables to be set. The file must consist of simple NAME=VALUE pairs on separate lines.