123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677 |
- /*
- * pam_time module
- *
- * Written by Andrew Morgan <morgan@linux.kernel.org> 1996/6/22
- * (File syntax and much other inspiration from the shadow package
- * shadow-960129)
- * Field parsing rewritten by Tomas Mraz <tm@t8m.info>
- */
- #include "config.h"
- #include <sys/file.h>
- #include <stdio.h>
- #include <stdlib.h>
- #include <ctype.h>
- #include <unistd.h>
- #include <stdarg.h>
- #include <time.h>
- #include <syslog.h>
- #include <string.h>
- #include <sys/types.h>
- #include <sys/stat.h>
- #include <fcntl.h>
- #include <netdb.h>
- #include <security/_pam_macros.h>
- #include <security/pam_modules.h>
- #include <security/pam_ext.h>
- #include <security/pam_modutil.h>
- #include "pam_inline.h"
- #ifdef HAVE_LIBAUDIT
- #include <libaudit.h>
- #endif
- #define PAM_TIME_BUFLEN 1000
- #define FIELD_SEPARATOR ';' /* this is new as of .02 */
- #define PAM_DEBUG_ARG 0x0001
- #define PAM_NO_AUDIT 0x0002
- #ifndef TRUE
- # define TRUE 1
- #endif
- #ifndef FALSE
- # define FALSE 0
- #endif
- typedef enum { AND, OR } operator;
- static int
- _pam_parse (const pam_handle_t *pamh, int argc, const char **argv, const char **conffile)
- {
- int ctrl = 0;
- *conffile = PAM_TIME_CONF;
- /* step through arguments */
- for (; argc-- > 0; ++argv) {
- const char *str;
- /* generic options */
- if (!strcmp(*argv, "debug")) {
- ctrl |= PAM_DEBUG_ARG;
- } else if (!strcmp(*argv, "noaudit")) {
- ctrl |= PAM_NO_AUDIT;
- } 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 {
- pam_syslog(pamh, LOG_ERR, "unknown option: %s", *argv);
- }
- }
- return ctrl;
- }
- /* --- static functions for checking whether the user should be let in --- */
- static char *
- shift_buf(char *mem, int from)
- {
- char *start = mem;
- while ((*mem = mem[from]) != '\0')
- ++mem;
- memset(mem, '\0', PAM_TIME_BUFLEN - (mem - start));
- return mem;
- }
- static void
- trim_spaces(char *buf, char *from)
- {
- while (from > buf) {
- --from;
- if (*from == ' ')
- *from = '\0';
- else
- break;
- }
- }
- #define STATE_NL 0 /* new line starting */
- #define STATE_COMMENT 1 /* inside comment */
- #define STATE_FIELD 2 /* field following */
- #define STATE_EOF 3 /* end of file or error */
- static int
- read_field(const pam_handle_t *pamh, int fd, char **buf, int *from, int *state, const char *file)
- {
- char *to;
- char *src;
- int i;
- char c;
- int onspace;
- /* is buf set ? */
- if (! *buf) {
- *buf = (char *) calloc(1, PAM_TIME_BUFLEN+1);
- if (! *buf) {
- pam_syslog(pamh, LOG_CRIT, "out of memory");
- D(("no memory"));
- *state = STATE_EOF;
- return -1;
- }
- *from = 0;
- *state = STATE_NL;
- fd = open(file, O_RDONLY);
- if (fd < 0) {
- pam_syslog(pamh, LOG_ERR, "error opening %s: %m", file);
- _pam_drop(*buf);
- *state = STATE_EOF;
- return -1;
- }
- }
- if (*from > 0)
- to = shift_buf(*buf, *from);
- else
- to = *buf;
- while (fd != -1 && to - *buf < PAM_TIME_BUFLEN) {
- i = pam_modutil_read(fd, to, PAM_TIME_BUFLEN - (to - *buf));
- if (i < 0) {
- pam_syslog(pamh, LOG_ERR, "error reading %s: %m", file);
- close(fd);
- memset(*buf, 0, PAM_TIME_BUFLEN);
- _pam_drop(*buf);
- *state = STATE_EOF;
- return -1;
- } else if (!i) {
- close(fd);
- fd = -1; /* end of file reached */
- }
- to += i;
- }
- if (to == *buf) {
- /* nothing previously in buf, nothing read */
- _pam_drop(*buf);
- *state = STATE_EOF;
- return -1;
- }
- memset(to, '\0', PAM_TIME_BUFLEN - (to - *buf));
- to = *buf;
- onspace = 1; /* delete any leading spaces */
- for (src = to; (c=*src) != '\0'; ++src) {
- if (*state == STATE_COMMENT && c != '\n') {
- continue;
- }
- switch (c) {
- case '\n':
- *state = STATE_NL;
- *to = '\0';
- *from = (src - *buf) + 1;
- trim_spaces(*buf, to);
- return fd;
- case '\t':
- case ' ':
- if (!onspace) {
- onspace = 1;
- *to++ = ' ';
- }
- break;
- case '!':
- onspace = 1; /* ignore following spaces */
- *to++ = '!';
- break;
- case '#':
- *state = STATE_COMMENT;
- break;
- case FIELD_SEPARATOR:
- *state = STATE_FIELD;
- *to = '\0';
- *from = (src - *buf) + 1;
- trim_spaces(*buf, to);
- return fd;
- case '\\':
- if (src[1] == '\n') {
- ++src; /* skip it */
- break;
- }
- /* fallthrough */
- default:
- *to++ = c;
- onspace = 0;
- }
- if (src > to)
- *src = '\0'; /* clearing */
- }
- if (*state != STATE_COMMENT) {
- *state = STATE_COMMENT;
- pam_syslog(pamh, LOG_ERR, "field too long - ignored");
- **buf = '\0';
- } else {
- *to = '\0';
- trim_spaces(*buf, to);
- }
- *from = 0;
- return fd;
- }
- /* read a member from a field */
- static int
- logic_member(const char *string, int *at)
- {
- int c,to;
- int done=0;
- int token=0;
- to=*at;
- do {
- c = string[to++];
- switch (c) {
- case '\0':
- --to;
- done = 1;
- break;
- case '&':
- case '|':
- case '!':
- if (token) {
- --to;
- }
- done = 1;
- break;
- default:
- if (isalpha(c) || c == '*' || isdigit(c) || c == '_'
- || c == '-' || c == '.' || c == '/' || c == ':') {
- token = 1;
- } else if (token) {
- --to;
- done = 1;
- } else {
- ++*at;
- }
- }
- } while (!done);
- return to - *at;
- }
- typedef enum { VAL, OP } expect;
- static int
- logic_field(pam_handle_t *pamh, const void *me, const char *x, int rule,
- int (*agrees)(pam_handle_t *pamh,
- const void *, const char *, int, int))
- {
- int left=FALSE, right, not=FALSE;
- operator oper=OR;
- int at=0, l;
- expect next=VAL;
- while ((l = logic_member(x,&at))) {
- int c = x[at];
- if (next == VAL) {
- if (c == '!')
- not = !not;
- else if (isalpha(c) || c == '*' || isdigit(c) || c == '_'
- || c == '-' || c == '.' || c == '/' || c == ':') {
- right = not ^ agrees(pamh, me, x+at, l, rule);
- if (oper == AND)
- left &= right;
- else
- left |= right;
- next = OP;
- } else {
- pam_syslog(pamh, LOG_ERR,
- "garbled syntax; expected name (rule #%d)",
- rule);
- return FALSE;
- }
- } else { /* OP */
- switch (c) {
- case '&':
- oper = AND;
- break;
- case '|':
- oper = OR;
- break;
- default:
- pam_syslog(pamh, LOG_ERR,
- "garbled syntax; expected & or | (rule #%d)",
- rule);
- D(("%c at %d",c,at));
- return FALSE;
- }
- next = VAL;
- not = FALSE;
- }
- at += l;
- }
- return left;
- }
- static int
- is_same(pam_handle_t *pamh UNUSED, const void *A, const char *b,
- int len, int rule UNUSED)
- {
- int i;
- const char *a;
- a = A;
- for (i=0; len > 0; ++i, --len) {
- if (b[i] != a[i]) {
- if (b[i++] == '*') {
- return (!--len || !strncmp(b+i,a+strlen(a)-len,len));
- } else
- return FALSE;
- }
- }
- /* Ok, we know that b is a substring from A and does not contain
- wildcards, but now the length of both strings must be the same,
- too. In this case it means, a[i] has to be the end of the string. */
- if (a[i] != '\0')
- return FALSE;
- return ( !len );
- }
- typedef struct {
- int day; /* array of 7 bits, one set for today */
- int minute; /* integer, hour*100+minute for now */
- } TIME;
- static struct day {
- const char *d;
- int bit;
- } const days[11] = {
- { "su", 01 },
- { "mo", 02 },
- { "tu", 04 },
- { "we", 010 },
- { "th", 020 },
- { "fr", 040 },
- { "sa", 0100 },
- { "wk", 076 },
- { "wd", 0101 },
- { "al", 0177 },
- { NULL, 0 }
- };
- static TIME
- time_now(void)
- {
- struct tm *local;
- time_t the_time;
- TIME this;
- the_time = time((time_t *)0); /* get the current time */
- local = localtime(&the_time);
- this.day = days[local->tm_wday].bit;
- this.minute = local->tm_hour*100 + local->tm_min;
- D(("day: 0%o, time: %.4d", this.day, this.minute));
- return this;
- }
- /* take the current date and see if the range "date" passes it */
- static int
- check_time(pam_handle_t *pamh, const void *AT, const char *times,
- int len, int rule)
- {
- int not,pass;
- int marked_day, time_start, time_end;
- const TIME *at;
- int i,j=0;
- at = AT;
- D(("chcking: 0%o/%.4d vs. %s", at->day, at->minute, times));
- if (times == NULL) {
- /* this should not happen */
- pam_syslog(pamh, LOG_CRIT,
- "internal error in file %s at line %d",
- __FILE__, __LINE__);
- return FALSE;
- }
- if (times[j] == '!') {
- ++j;
- not = TRUE;
- } else {
- not = FALSE;
- }
- for (marked_day = 0; len > 0 && isalpha(times[j]); --len) {
- int this_day=-1;
- D(("%c%c ?", times[j], times[j+1]));
- for (i=0; days[i].d != NULL; ++i) {
- if (tolower(times[j]) == days[i].d[0]
- && tolower(times[j+1]) == days[i].d[1] ) {
- this_day = days[i].bit;
- break;
- }
- }
- j += 2;
- if (this_day == -1) {
- pam_syslog(pamh, LOG_ERR, "bad day specified (rule #%d)", rule);
- return FALSE;
- }
- marked_day ^= this_day;
- }
- if (marked_day == 0) {
- pam_syslog(pamh, LOG_ERR, "no day specified");
- return FALSE;
- }
- D(("day range = 0%o", marked_day));
- time_start = 0;
- for (i=0; len > 0 && i < 4 && isdigit(times[i+j]); ++i, --len) {
- time_start *= 10;
- time_start += times[i+j]-'0'; /* is this portable? */
- }
- j += i;
- if (times[j] == '-') {
- time_end = 0;
- for (i=1; len > 0 && i < 5 && isdigit(times[i+j]); ++i, --len) {
- time_end *= 10;
- time_end += times[i+j]-'0'; /* is this portable */
- }
- j += i;
- } else
- time_end = -1;
- D(("i=%d, time_end=%d, times[j]='%c'", i, time_end, times[j]));
- if (i != 5 || time_end == -1) {
- pam_syslog(pamh, LOG_ERR, "no/bad times specified (rule #%d)", rule);
- return TRUE;
- }
- D(("times(%d to %d)", time_start,time_end));
- D(("marked_day = 0%o", marked_day));
- /* compare with the actual time now */
- pass = FALSE;
- if (time_start < time_end) { /* start < end ? --> same day */
- if ((at->day & marked_day) && (at->minute >= time_start)
- && (at->minute < time_end)) {
- D(("time is listed"));
- pass = TRUE;
- }
- } else { /* spans two days */
- if ((at->day & marked_day) && (at->minute >= time_start)) {
- D(("caught on first day"));
- pass = TRUE;
- } else {
- marked_day <<= 1;
- marked_day |= (marked_day & 0200) ? 1:0;
- D(("next day = 0%o", marked_day));
- if ((at->day & marked_day) && (at->minute <= time_end)) {
- D(("caught on second day"));
- pass = TRUE;
- }
- }
- }
- return (not ^ pass);
- }
- static int
- check_account(pam_handle_t *pamh, const char *service,
- const char *tty, const char *user, const char *file)
- {
- int from=0, state=STATE_NL, fd=-1;
- char *buffer=NULL;
- int count=0;
- TIME here_and_now;
- int retval=PAM_SUCCESS;
- here_and_now = time_now(); /* find current time */
- do {
- int good=TRUE,intime;
- /* here we get the service name field */
- fd = read_field(pamh, fd, &buffer, &from, &state, file);
- if (!buffer || !buffer[0]) {
- /* empty line .. ? */
- continue;
- }
- ++count;
- if (state != STATE_FIELD) {
- pam_syslog(pamh, LOG_ERR,
- "%s: malformed rule #%d", file, count);
- continue;
- }
- good = logic_field(pamh, service, buffer, count, is_same);
- D(("with service: %s", good ? "passes":"fails" ));
- /* here we get the terminal name field */
- fd = read_field(pamh, fd, &buffer, &from, &state, file);
- if (state != STATE_FIELD) {
- pam_syslog(pamh, LOG_ERR,
- "%s: malformed rule #%d", file, count);
- continue;
- }
- good &= logic_field(pamh, tty, buffer, count, is_same);
- D(("with tty: %s", good ? "passes":"fails" ));
- /* here we get the username field */
- fd = read_field(pamh, fd, &buffer, &from, &state, file);
- if (state != STATE_FIELD) {
- pam_syslog(pamh, LOG_ERR,
- "%s: malformed rule #%d", file, count);
- continue;
- }
- /* If buffer starts with @, we are using netgroups */
- if (buffer[0] == '@')
- #ifdef HAVE_INNETGR
- good &= innetgr (&buffer[1], NULL, user, NULL);
- #else
- pam_syslog (pamh, LOG_ERR, "pam_time does not have netgroup support");
- #endif
- else
- good &= logic_field(pamh, user, buffer, count, is_same);
- D(("with user: %s", good ? "passes":"fails" ));
- /* here we get the time field */
- fd = read_field(pamh, fd, &buffer, &from, &state, file);
- if (state == STATE_FIELD) {
- pam_syslog(pamh, LOG_ERR,
- "%s: poorly terminated rule #%d", file, count);
- continue;
- }
- intime = logic_field(pamh, &here_and_now, buffer, count, check_time);
- D(("with time: %s", intime ? "passes":"fails" ));
- if (good && !intime) {
- /*
- * for security parse whole file.. also need to ensure
- * that the buffer is free()'d and the file is closed.
- */
- retval = PAM_PERM_DENIED;
- } else {
- D(("rule passed"));
- }
- } while (state != STATE_EOF);
- return retval;
- }
- /* --- public account management functions --- */
- int
- pam_sm_acct_mgmt(pam_handle_t *pamh, int flags UNUSED,
- int argc, const char **argv)
- {
- const void *service=NULL, *void_tty=NULL;
- const char *tty;
- const char *user=NULL;
- const char *conf_file = NULL;
- int ctrl;
- int rv;
- ctrl = _pam_parse(pamh, argc, argv, &conf_file);
- if (ctrl & PAM_DEBUG_ARG) {
- pam_syslog(pamh, LOG_DEBUG, "conffile=%s", conf_file);
- }
- /* set service name */
- if (pam_get_item(pamh, PAM_SERVICE, &service)
- != PAM_SUCCESS || service == NULL) {
- pam_syslog(pamh, LOG_ERR, "cannot find the current service name");
- return PAM_ABORT;
- }
- /* set username */
- if (pam_get_user(pamh, &user, NULL) != PAM_SUCCESS || *user == '\0') {
- pam_syslog(pamh, LOG_NOTICE, "cannot determine user name");
- return PAM_USER_UNKNOWN;
- }
- /* set tty name */
- if (pam_get_item(pamh, PAM_TTY, &void_tty) != PAM_SUCCESS
- || void_tty == NULL) {
- D(("PAM_TTY not set, probing stdin"));
- tty = ttyname(STDIN_FILENO);
- if (tty == NULL) {
- tty = "";
- }
- if (pam_set_item(pamh, PAM_TTY, tty) != PAM_SUCCESS) {
- pam_syslog(pamh, LOG_ERR, "couldn't set tty name");
- return PAM_ABORT;
- }
- }
- else
- tty = void_tty;
- if (tty[0] == '/') { /* full path */
- const char *t;
- tty++;
- if ((t = strchr(tty, '/')) != NULL) {
- tty = t + 1;
- }
- }
- /* good, now we have the service name, the user and the terminal name */
- D(("service=%s", service));
- D(("user=%s", user));
- D(("tty=%s", tty));
- rv = check_account(pamh, service, tty, user, conf_file);
- if (rv != PAM_SUCCESS) {
- #ifdef HAVE_LIBAUDIT
- if (!(ctrl & PAM_NO_AUDIT)) {
- pam_modutil_audit_write(pamh, AUDIT_ANOM_LOGIN_TIME,
- "pam_time", rv); /* ignore return value as we fail anyway */
- }
- #endif
- if (ctrl & PAM_DEBUG_ARG) {
- pam_syslog(pamh, LOG_DEBUG, "user %s rejected", user);
- }
- }
- return rv;
- }
- /* end of module definition */
|