/* pam_issue module - a simple /etc/issue parser to set PAM_USER_PROMPT * * Copyright 1999 by Ben Collins * * Needs to be called before any other auth modules so we can setup the * user prompt before it's first used. Allows one argument option, which * is the full path to a file to be used for issue (uses /etc/issue as a * default) such as "issue=/etc/issue.telnet". * * We can also parse escapes within the the issue file (enabled by * default, but can be disabled with the "noesc" option). It's the exact * same parsing as util-linux's agetty program performs. * * Released under the GNU LGPL version 2 or later */ #include "config.h" #include #include #include #include #include #include #include #include #include #include #include #include #define PAM_SM_AUTH #include #include #include static int _user_prompt_set = 0; static int read_issue_raw(pam_handle_t *pamh, FILE *fp, char **prompt); static int read_issue_quoted(pam_handle_t *pamh, FILE *fp, char **prompt); /* --- authentication management functions (only) --- */ int pam_sm_authenticate (pam_handle_t *pamh, int flags UNUSED, int argc, const char **argv) { int retval = PAM_SERVICE_ERR; FILE *fp; const char *issue_file = NULL; int parse_esc = 1; const void *item = NULL; const char *cur_prompt; char *issue_prompt = NULL; /* If we've already set the prompt, don't set it again */ if(_user_prompt_set) return PAM_IGNORE; /* We set this here so if we fail below, we wont get further than this next time around (only one real failure) */ _user_prompt_set = 1; for ( ; argc-- > 0 ; ++argv ) { if (!strncmp(*argv,"issue=",6)) { issue_file = 6 + *argv; D(("set issue_file to: %s", issue_file)); } else if (!strcmp(*argv,"noesc")) { parse_esc = 0; D(("turning off escape parsing by request")); } else D(("unknown option passed: %s", *argv)); } if (issue_file == NULL) issue_file = "/etc/issue"; if ((fp = fopen(issue_file, "r")) == NULL) { pam_syslog(pamh, LOG_ERR, "error opening %s: %m", issue_file); return PAM_SERVICE_ERR; } if ((retval = pam_get_item(pamh, PAM_USER_PROMPT, &item)) != PAM_SUCCESS) { fclose(fp); return retval; } cur_prompt = item; if (cur_prompt == NULL) cur_prompt = ""; if (parse_esc) retval = read_issue_quoted(pamh, fp, &issue_prompt); else retval = read_issue_raw(pamh, fp, &issue_prompt); fclose(fp); if (retval != PAM_SUCCESS) goto out; { size_t size = strlen(issue_prompt) + strlen(cur_prompt) + 1; char *new_prompt = realloc(issue_prompt, size); if (new_prompt == NULL) { pam_syslog(pamh, LOG_CRIT, "out of memory"); retval = PAM_BUF_ERR; goto out; } issue_prompt = new_prompt; } strcat(issue_prompt, cur_prompt); retval = pam_set_item(pamh, PAM_USER_PROMPT, (const void *) issue_prompt); out: _pam_drop(issue_prompt); return (retval == PAM_SUCCESS) ? PAM_IGNORE : retval; } int pam_sm_setcred (pam_handle_t *pamh UNUSED, int flags UNUSED, int argc UNUSED, const char **argv UNUSED) { return PAM_IGNORE; } static int read_issue_raw(pam_handle_t *pamh, FILE *fp, char **prompt) { char *issue; struct stat st; *prompt = NULL; if (fstat(fileno(fp), &st) < 0) { pam_syslog(pamh, LOG_ERR, "stat error: %m"); return PAM_SERVICE_ERR; } if ((issue = malloc(st.st_size + 1)) == NULL) { pam_syslog(pamh, LOG_CRIT, "out of memory"); return PAM_BUF_ERR; } if ((off_t)fread(issue, 1, st.st_size, fp) != st.st_size) { pam_syslog(pamh, LOG_ERR, "read error: %m"); _pam_drop(issue); return PAM_SERVICE_ERR; } issue[st.st_size] = '\0'; *prompt = issue; return PAM_SUCCESS; } static int read_issue_quoted(pam_handle_t *pamh, FILE *fp, char **prompt) { int c; size_t size = 1024; char *issue; struct utsname uts; *prompt = NULL; if ((issue = malloc(size)) == NULL) { pam_syslog(pamh, LOG_CRIT, "out of memory"); return PAM_BUF_ERR; } issue[0] = '\0'; (void) uname(&uts); while ((c = getc(fp)) != EOF) { char buf[1024]; buf[0] = '\0'; if (c == '\\') { if ((c = getc(fp)) == EOF) break; switch (c) { case 's': strncat(buf, uts.sysname, sizeof(buf) - 1); break; case 'n': strncat(buf, uts.nodename, sizeof(buf) - 1); break; case 'r': strncat(buf, uts.release, sizeof(buf) - 1); break; case 'v': strncat(buf, uts.version, sizeof(buf) - 1); break; case 'm': strncat(buf, uts.machine, sizeof(buf) - 1); break; case 'o': { char domainname[256]; if (getdomainname(domainname, sizeof(domainname)) >= 0) { domainname[sizeof(domainname)-1] = '\0'; strncat(buf, domainname, sizeof(buf) - 1); } } break; case 'd': case 't': { const char *weekday[] = { "Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat" }; const char *month[] = { "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec" }; time_t now; struct tm *tm; (void) time (&now); tm = localtime(&now); if (c == 'd') snprintf (buf, sizeof buf, "%s %s %d %d", weekday[tm->tm_wday], month[tm->tm_mon], tm->tm_mday, tm->tm_year + 1900); else snprintf (buf, sizeof buf, "%02d:%02d:%02d", tm->tm_hour, tm->tm_min, tm->tm_sec); } break; case 'l': { char *ttyn = ttyname(1); if (ttyn) { if (!strncmp(ttyn, "/dev/", 5)) ttyn += 5; strncat(buf, ttyn, sizeof(buf) - 1); } } break; case 'u': case 'U': { unsigned int users = 0; struct utmp *ut; setutent(); while ((ut = getutent())) { if (ut->ut_type == USER_PROCESS) ++users; } endutent(); if (c == 'U') snprintf (buf, sizeof buf, "%u %s", users, (users == 1) ? "user" : "users"); else snprintf (buf, sizeof buf, "%u", users); break; } default: buf[0] = c; buf[1] = '\0'; } } else { buf[0] = c; buf[1] = '\0'; } if ((strlen(issue) + strlen(buf)) + 1 > size) { char *new_issue; size += strlen(buf) + 1; new_issue = (char *) realloc (issue, size); if (new_issue == NULL) { _pam_drop(issue); return PAM_BUF_ERR; } issue = new_issue; } strcat(issue, buf); } if (ferror(fp)) { pam_syslog(pamh, LOG_ERR, "read error: %m"); _pam_drop(issue); return PAM_SERVICE_ERR; } *prompt = issue; return PAM_SUCCESS; } /* end of module definition */