6 * Written by Dave Kinchlea <kinch@kinch.ark.com> 1997/01/31
7 * Inspired by Andrew Morgan <morgan@parc.power.net, who also supplied the
8 * template for this file (via pam_mail)
11 #ifndef DEFAULT_CONF_FILE
12 #define DEFAULT_CONF_FILE "/etc/security/pam_env.conf"
15 #define DEFAULT_ETC_ENVFILE "/etc/environment"
16 #define DEFAULT_READ_ENVFILE 1
18 #include <security/_pam_aconf.h>
29 #include <sys/types.h>
33 * here, we make a definition for the externally accessible function
34 * in this file (this definition is required for static a module
35 * but strongly encouraged generally) it is used to instruct the
36 * modules include file to define the function prototypes.
39 #define PAM_SM_AUTH /* This is primarily a AUTH_SETCRED module */
40 #define PAM_SM_SESSION /* But I like to be friendly */
41 #define PAM_SM_PASSWORD /* "" */
42 #define PAM_SM_ACCOUNT /* "" */
44 #include <security/pam_modules.h>
45 #include <security/_pam_macros.h>
47 /* This little structure makes it easier to keep variables together */
60 #define BAD_LINE 100 /* This must be > the largest PAM_* error code */
62 #define DEFINE_VAR 101
63 #define UNDEFINE_VAR 102
64 #define ILLEGAL_VAR 103
66 static int _assemble_line(FILE *, char *, int);
67 static int _parse_line(char *, VAR *);
68 static int _check_var(pam_handle_t *, VAR *); /* This is the real meat */
69 static void _clean_var(VAR *);
70 static int _expand_arg(pam_handle_t *, char **);
71 static const char * _pam_get_item_byname(pam_handle_t *, const char *);
72 static int _define_var(pam_handle_t *, VAR *);
73 static int _undefine_var(pam_handle_t *, VAR *);
75 /* This is a flag used to designate an empty string */
76 static char quote='Z';
80 static void _log_err(int err, const char *format, ...)
84 va_start(args, format);
85 openlog("PAM-env", LOG_CONS|LOG_PID, LOG_AUTH);
86 vsyslog(err, format, args);
91 /* argument parsing */
93 #define PAM_DEBUG_ARG 0x01
94 #define PAM_NEW_CONF_FILE 0x02
95 #define PAM_ENV_SILENT 0x04
96 #define PAM_NEW_ENV_FILE 0x10
98 static int _pam_parse(int flags, int argc, const char **argv, char **conffile,
99 char **envfile, int *readenv)
104 /* step through arguments */
105 for (; argc-- > 0; ++argv) {
107 /* generic options */
109 if (!strcmp(*argv,"debug"))
110 ctrl |= PAM_DEBUG_ARG;
111 else if (!strncmp(*argv,"conffile=",9)) {
112 *conffile = x_strdup(9+*argv);
113 if (*conffile != NULL) {
114 D(("new Configuration File: %s", *conffile));
115 ctrl |= PAM_NEW_CONF_FILE;
118 "Configuration file specification missing argument - ignored");
120 } else if (!strncmp(*argv,"envfile=",8)) {
121 *envfile = x_strdup(8+*argv);
122 if (*envfile != NULL) {
123 D(("new Env File: %s", *envfile));
124 ctrl |= PAM_NEW_ENV_FILE;
127 "Env file specification missing argument - ignored");
129 } else if (!strncmp(*argv,"readenv=",8))
130 *readenv = atoi(8+*argv);
132 _log_err(LOG_ERR,"pam_parse: unknown option; %s",*argv);
138 static int _parse_config_file(pam_handle_t *pamh, int ctrl, char **conffile)
142 char buffer[BUF_SIZE];
146 var->name=NULL; var->defval=NULL; var->override=NULL;
149 if (ctrl & PAM_NEW_CONF_FILE) {
152 file = DEFAULT_CONF_FILE;
155 D(("Config file name is: %s", file));
158 * Lets try to open the config file, parse it and process
159 * any variables found.
162 if ((conf = fopen(file,"r")) == NULL) {
163 _log_err(LOG_ERR, "Unable to open config file: %s",
168 /* _pam_assemble_line will provide a complete line from the config file, with all
169 * comments removed and any escaped newlines fixed up
172 while (( retval = _assemble_line(conf, buffer, BUF_SIZE)) > 0) {
173 D(("Read line: %s", buffer));
175 if ((retval = _parse_line(buffer, var)) == GOOD_LINE) {
176 retval = _check_var(pamh, var);
178 if (DEFINE_VAR == retval) {
179 retval = _define_var(pamh, var);
181 } else if (UNDEFINE_VAR == retval) {
182 retval = _undefine_var(pamh, var);
185 if (PAM_SUCCESS != retval && ILLEGAL_VAR != retval
186 && BAD_LINE != retval && PAM_BAD_ITEM != retval) break;
195 _clean_var(var); /* We could have got here prematurely, this is safe though */
196 _pam_overwrite(*conffile);
197 _pam_drop(*conffile);
200 return (retval<0?PAM_ABORT:PAM_SUCCESS);
203 static int _parse_env_file(pam_handle_t *pamh, int ctrl, char **env_file)
205 int retval=PAM_SUCCESS, i, t;
207 char buffer[BUF_SIZE], *key, *mark;
210 if (ctrl & PAM_NEW_ENV_FILE)
213 file = DEFAULT_ETC_ENVFILE;
215 D(("Env file name is: %s", file));
217 if ((conf = fopen(file,"r")) == NULL) {
218 D(("Unable to open env file: %s", strerror(errno)));
222 while (_assemble_line(conf, buffer, BUF_SIZE) > 0) {
223 D(("Read line: %s", buffer));
226 /* skip leading white space */
227 key += strspn(key, " \n\t");
229 /* skip blanks lines and comments */
230 if (!key || key[0] == '#')
233 /* skip over "export " if present so we can be compat with
234 bash type declerations */
235 if (strncmp(key, "export ", (size_t) 7) == 0)
238 /* now find the end of value */
240 while(mark[0] != '\n' && mark[0] != '#' && mark[0] != '\0')
246 * sanity check, the key must be alpha-numeric
249 for ( i = 0 ; key[i] != '=' && key[i] != '\0' ; i++ )
250 if (!isalnum(key[i]) && key[i] != '_') {
251 D(("key is not alpha numeric - '%s', ignoring", key));
255 /* now we try to be smart about quotes around the value,
256 but not too smart, we can't get all fancy with escaped
258 if (key[i] == '=' && (key[++i] == '\"' || key[i] == '\'')) {
259 for ( t = i+1 ; key[t] != '\0' ; t++)
260 if (key[t] != '\"' && key[t] != '\'')
262 else if (key[t+1] != '\0')
267 /* set the env var, if it fails, we break out of the loop */
268 retval = pam_putenv(pamh, key);
269 if (retval != PAM_SUCCESS) {
270 D(("error setting env \"%s\"", key));
278 _pam_overwrite(*env_file);
279 _pam_drop(*env_file);
282 return (retval<0?PAM_IGNORE:PAM_SUCCESS);
286 * This is where we read a line of the PAM config file. The line may be
287 * preceeded by lines of comments and also extended with "\\\n"
290 static int _assemble_line(FILE *f, char *buffer, int buf_len)
296 /* loop broken with a 'break' when a non-'\\n' ended line is read */
300 if (used >= buf_len) {
302 D(("_assemble_line: overflow"));
305 if (fgets(p, buf_len - used, f) == NULL) {
307 /* Incomplete read */
315 /* skip leading spaces --- line may be blank */
317 s = p + strspn(p, " \n\t");
318 if (*s && (*s != '#')) {
322 * we are only interested in characters before the first '#'
326 while (*s && *s != '#')
331 break; /* the line has been read */
337 * Check for backslash by scanning back from the end of
338 * the entered line, the '\n' has been included since
339 * normally a line is terminated with this
340 * character. fgets() should only return one though!
344 while (s > os && ((*--s == ' ') || (*s == '\t')
347 /* check if it ends with a backslash */
349 *s = '\0'; /* truncate the line here */
351 p = s; /* there is more ... */
353 /* End of the line! */
355 break; /* this is the complete line */
359 /* Nothing in this line */
367 static int _parse_line(char *buffer, VAR *var)
370 * parse buffer into var, legal syntax is
371 * VARIABLE [DEFAULT=[[string]] [OVERRIDE=[value]]
373 * Any other options defined make this a bad line,
374 * error logged and no var set
377 int length, quoteflg=0;
378 char *ptr, **valptr, *tmpptr;
380 D(("Called buffer = <%s>", buffer));
382 length = strcspn(buffer," \t\n");
384 if ((var->name = malloc(length + 1)) == NULL) {
385 _log_err(LOG_ERR, "Couldn't malloc %d bytes", length+1);
390 * The first thing on the line HAS to be the variable name,
391 * it may be the only thing though.
393 strncpy(var->name, buffer, length);
394 var->name[length] = '\0';
395 D(("var->name = <%s>, length = %d", var->name, length));
398 * Now we check for arguments, we only support two kinds and ('cause I am lazy)
399 * each one can actually be listed any number of times
403 while ((length = strspn(ptr, " \t")) > 0) {
404 ptr += length; /* remove leading whitespace */
406 if (strncmp(ptr,"DEFAULT=",8) == 0) {
408 D(("Default arg found: <%s>", ptr));
409 valptr=&(var->defval);
410 } else if (strncmp(ptr, "OVERRIDE=", 9) == 0) {
412 D(("Override arg found: <%s>", ptr));
413 valptr=&(var->override);
415 D(("Unrecognized options: <%s> - ignoring line", ptr));
416 _log_err(LOG_ERR, "Unrecognized Option: %s - ignoring line", ptr);
420 if ('"' != *ptr) { /* Escaped quotes not supported */
421 length = strcspn(ptr, " \t\n");
424 tmpptr = strchr(++ptr, '"');
426 D(("Unterminated quoted string: %s", ptr-1));
427 _log_err(LOG_ERR, "Unterminated quoted string: %s", ptr-1);
430 length = tmpptr - ptr;
431 if (*++tmpptr && ' ' != *tmpptr && '\t' != *tmpptr && '\n' != *tmpptr) {
432 D(("Quotes must cover the entire string: <%s>", ptr));
433 _log_err(LOG_ERR, "Quotes must cover the entire string: <%s>", ptr);
439 if ((*valptr = malloc(length + 1)) == NULL) {
440 D(("Couldn't malloc %d bytes", length+1));
441 _log_err(LOG_ERR, "Couldn't malloc %d bytes", length+1);
444 (void)strncpy(*valptr,ptr,length);
445 (*valptr)[length]='\0';
446 } else if (quoteflg--) {
447 *valptr = "e; /* a quick hack to handle the empty string */
449 ptr = tmpptr; /* Start the search where we stopped */
453 * The line is parsed, all is well.
457 ptr = NULL; tmpptr = NULL; valptr = NULL;
461 static int _check_var(pam_handle_t *pamh, VAR *var)
464 * Examine the variable and determine what action to take.
465 * Returns DEFINE_VAR, UNDEFINE_VAR depending on action to take
466 * or a PAM_* error code if passed back from other routines
468 * if no DEFAULT provided, the empty string is assumed
469 * if no OVERRIDE provided, the empty string is assumed
470 * if DEFAULT= and OVERRIDE evaluates to the empty string,
471 * this variable should be undefined
472 * if DEFAULT="" and OVERRIDE evaluates to the empty string,
473 * this variable should be defined with no value
474 * if OVERRIDE=value and value turns into the empty string, DEFAULT is used
476 * If DEFINE_VAR is to be returned, the correct value to define will
477 * be pointed to by var->value
485 * First thing to do is to expand any arguments, but only
486 * if they are not the special quote values (cause expand_arg
490 if (var->defval && ("e != var->defval) &&
491 ((retval = _expand_arg(pamh, &(var->defval))) != PAM_SUCCESS)) {
494 if (var->override && ("e != var->override) &&
495 ((retval = _expand_arg(pamh, &(var->override))) != PAM_SUCCESS)) {
501 if (var->override && *(var->override) && "e != var->override) {
502 /* if there is a non-empty string in var->override, we use it */
503 D(("OVERRIDE variable <%s> being used: <%s>", var->name, var->override));
504 var->value = var->override;
508 var->value = var->defval;
509 if ("e == var->defval) {
511 * This means that the empty string was given for defval value
512 * which indicates that a variable should be defined with no value
515 D(("An empty variable: <%s>", var->name));
517 } else if (var->defval) {
518 D(("DEFAULT variable <%s> being used: <%s>", var->name, var->defval));
521 D(("UNDEFINE variable <%s>", var->name));
522 retval = UNDEFINE_VAR;
530 static int _expand_arg(pam_handle_t *pamh, char **value)
532 const char *orig=*value, *tmpptr=NULL;
534 * Sure would be nice to use tmpptr but it needs to be
535 * a constant so that the compiler will shut up when I
536 * call pam_getenv and _pam_get_item_byname -- sigh
539 char type, tmpval[BUF_SIZE]; /* No unexpanded variable can be bigger than BUF_SIZE */
540 char tmp[MAX_ENV]; /* I know this shouldn't be hard-coded but it's so
541 * much easier this way */
543 D(("Remember to initialize tmp!"));
547 * (possibly non-existent) environment variables can be used as values
548 * by prepending a "$" and wrapping in {} (ie: ${HOST}), can escape with "\"
549 * (possibly non-existent) PAM items can be used as values
550 * by prepending a "@" and wrapping in {} (ie: @{PAM_RHOST}, can escape
553 D(("Expanding <%s>",orig));
554 while (*orig) { /* while there is some input to deal with */
557 if ('$' != *orig && '@' != *orig) {
558 D(("Unrecognized escaped character: <%c> - ignoring", *orig));
559 _log_err(LOG_ERR, "Unrecognized escaped character: <%c> - ignoring",
561 } else if ((strlen(tmp) + 1) < MAX_ENV) {
562 tmp[strlen(tmp)] = *orig++; /* Note the increment */
564 /* is it really a good idea to try to log this? */
565 D(("Variable buffer overflow: <%s> + <%s>", tmp, tmpptr));
566 _log_err(LOG_ERR, "Variable buffer overflow: <%s> + <%s>", tmp, tmpptr);
570 if ('$' == *orig || '@' == *orig) {
571 if ('{' != *(orig+1)) {
572 D(("Expandable variables must be wrapped in {} <%s> - ignoring", orig));
573 _log_err(LOG_ERR, "Expandable variables must be wrapped in {} <%s> - ignoring",
575 if ((strlen(tmp) + 1) < MAX_ENV) {
576 tmp[strlen(tmp)] = *orig++; /* Note the increment */
580 D(("Expandable argument: <%s>", orig));
582 orig+=2; /* skip the ${ or @{ characters */
583 ptr = strchr(orig, '}');
587 D(("Unterminated expandable variable: <%s>", orig-2));
588 _log_err(LOG_ERR, "Unterminated expandable variable: <%s>", orig-2);
591 strncpy(tmpval, orig, (size_t) BUF_SIZE);
594 * so, we know we need to expand tmpval, it is either
595 * an environment variable or a PAM_ITEM. type will tell us which
600 D(("Expanding env var: <%s>",tmpval));
601 tmpptr = pam_getenv(pamh, tmpval);
602 D(("Expanded to <%s>", tmpptr));
606 D(("Expanding pam item: <%s>",tmpval));
607 tmpptr = _pam_get_item_byname(pamh, tmpval);
608 D(("Expanded to <%s>", tmpptr));
612 D(("Impossible error, type == <%c>", type));
613 _log_err(LOG_CRIT, "Impossible error, type == <%c>", type);
618 if ((strlen(tmp) + strlen(tmpptr)) < MAX_ENV) {
621 /* is it really a good idea to try to log this? */
622 D(("Variable buffer overflow: <%s> + <%s>", tmp, tmpptr));
623 _log_err(LOG_ERR, "Variable buffer overflow: <%s> + <%s>", tmp, tmpptr);
626 } /* if ('{' != *orig++) */
627 } else { /* if ( '$' == *orig || '@' == *orig) */
628 if ((strlen(tmp) + 1) < MAX_ENV) {
629 tmp[strlen(tmp)] = *orig++; /* Note the increment */
631 /* is it really a good idea to try to log this? */
632 D(("Variable buffer overflow: <%s> + <%s>", tmp, tmpptr));
633 _log_err(LOG_ERR, "Variable buffer overflow: <%s> + <%s>", tmp, tmpptr);
636 } /* for (;*orig;) */
638 if (strlen(tmp) > strlen(*value)) {
640 if ((*value = malloc(strlen(tmp) +1)) == NULL) {
641 D(("Couldn't malloc %d bytes for expanded var", strlen(tmp)+1));
642 _log_err(LOG_ERR,"Couldn't malloc %d bytes for expanded var",
648 memset(tmp,'\0',sizeof(tmp));
654 static const char * _pam_get_item_byname(pam_handle_t *pamh, const char *name)
657 * This function just allows me to use names as given in the config
658 * file and translate them into the appropriate PAM_ITEM macro
665 if (strcmp(name, "PAM_USER") == 0) {
667 } else if (strcmp(name, "PAM_USER_PROMPT") == 0) {
668 item = PAM_USER_PROMPT;
669 } else if (strcmp(name, "PAM_TTY") == 0) {
671 } else if (strcmp(name, "PAM_RUSER") == 0) {
673 } else if (strcmp(name, "PAM_RHOST") == 0) {
676 D(("Unknown PAM_ITEM: <%s>", name));
677 _log_err(LOG_ERR, "Unknown PAM_ITEM: <%s>", name);
681 if (pam_get_item(pamh, item, (const void **)&itemval) != PAM_SUCCESS) {
682 D(("pam_get_item failed"));
683 return NULL; /* let pam_get_item() log the error */
689 static int _define_var(pam_handle_t *pamh, VAR *var)
691 /* We have a variable to define, this is a simple function */
694 int size, retval=PAM_SUCCESS;
697 size = strlen(var->name)+strlen(var->value)+2;
698 if ((envvar = malloc(size)) == NULL) {
699 D(("Malloc fail, size = %d", size));
700 _log_err(LOG_ERR, "Malloc fail, size = %d", size);
703 (void) sprintf(envvar,"%s=%s",var->name,var->value);
704 retval = pam_putenv(pamh, envvar);
705 free(envvar); envvar=NULL;
710 static int _undefine_var(pam_handle_t *pamh, VAR *var)
712 /* We have a variable to undefine, this is a simple function */
714 D(("Called and exit."));
715 return pam_putenv(pamh, var->name);
718 static void _clean_var(VAR *var)
723 if (var->defval && ("e != var->defval)) {
726 if (var->override && ("e != var->override)) {
730 var->value = NULL; /* never has memory specific to it */
732 var->override = NULL;
738 /* --- authentication management functions (only) --- */
741 int pam_sm_authenticate(pam_handle_t *pamh, int flags, int argc,
748 int pam_sm_setcred(pam_handle_t *pamh, int flags, int argc,
751 int retval, ctrl, readenv=DEFAULT_READ_ENVFILE;
752 char *conf_file=NULL, *env_file=NULL;
755 * this module sets environment variables read in from a file
759 ctrl = _pam_parse(flags, argc, argv, &conf_file, &env_file, &readenv);
761 retval = _parse_config_file(pamh, ctrl, &conf_file);
764 _parse_env_file(pamh, ctrl, &env_file);
766 /* indicate success or failure */
773 int pam_sm_acct_mgmt(pam_handle_t *pamh, int flags, int argc,
776 _log_err(LOG_NOTICE, "pam_sm_acct_mgmt called inappropriatly");
777 return PAM_SERVICE_ERR;
781 int pam_sm_open_session(pam_handle_t *pamh,int flags,int argc
784 int retval, ctrl, readenv=DEFAULT_READ_ENVFILE;
785 char *conf_file=NULL, *env_file=NULL;
788 * this module sets environment variables read in from a file
792 ctrl = _pam_parse(flags, argc, argv, &conf_file, &env_file, &readenv);
794 retval = _parse_config_file(pamh, ctrl, &conf_file);
797 _parse_env_file(pamh, ctrl, &env_file);
799 /* indicate success or failure */
806 int pam_sm_close_session(pam_handle_t *pamh,int flags,int argc,
809 D(("Called and Exit"));
814 int pam_sm_chauthtok(pam_handle_t *pamh, int flags, int argc,
817 _log_err(LOG_NOTICE, "pam_sm_chauthtok called inappropriatly");
818 return PAM_SERVICE_ERR;
823 /* static module data */
825 struct pam_module _pam_env_modstruct = {
831 pam_sm_close_session,
837 /* end of module definition */