/* pam_issue module - a simple /etc/issue parser to set PAM_USER_PROMPT
*
* Copyright 1999 by Ben Collins <bcollins@debian.org>
*
* 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 <string.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <string.h>
#include <unistd.h>
#include <sys/utsname.h>
#include <utmp.h>
#include <time.h>
#include <syslog.h>
#define PAM_SM_AUTH
#include <security/_pam_macros.h>
#include <security/pam_modules.h>
#include <security/pam_ext.h>
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 */