4 * Written by Andrew Morgan <morgan@linux.kernel.org> 1996/6/22
5 * (File syntax and much other inspiration from the shadow package
7 * Field parsing rewritten by Tomas Mraz <tm@t8m.info>
21 #include <sys/types.h>
30 #define PAM_TIME_BUFLEN 1000
31 #define FIELD_SEPARATOR ';' /* this is new as of .02 */
33 #define PAM_DEBUG_ARG 0x0001
34 #define PAM_NO_AUDIT 0x0002
43 typedef enum { AND, OR } operator;
46 * here, we make definitions for the externally accessible functions
47 * in this file (these definitions are required for static modules
48 * but strongly encouraged generally) they are used to instruct the
49 * modules include file to define their prototypes.
52 #define PAM_SM_ACCOUNT
54 #include <security/_pam_macros.h>
55 #include <security/pam_modules.h>
56 #include <security/pam_ext.h>
57 #include <security/pam_modutil.h>
60 _pam_parse (const pam_handle_t *pamh, int argc, const char **argv)
64 /* step through arguments */
65 for (; argc-- > 0; ++argv) {
69 if (!strcmp(*argv, "debug")) {
70 ctrl |= PAM_DEBUG_ARG;
71 } else if (!strcmp(*argv, "noaudit")) {
74 pam_syslog(pamh, LOG_ERR, "unknown option: %s", *argv);
81 /* --- static functions for checking whether the user should be let in --- */
84 shift_buf(char *mem, int from)
87 while ((*mem = mem[from]) != '\0')
89 memset(mem, '\0', PAM_TIME_BUFLEN - (mem - start));
94 trim_spaces(char *buf, char *from)
105 #define STATE_NL 0 /* new line starting */
106 #define STATE_COMMENT 1 /* inside comment */
107 #define STATE_FIELD 2 /* field following */
108 #define STATE_EOF 3 /* end of file or error */
111 read_field(const pam_handle_t *pamh, int fd, char **buf, int *from, int *state)
121 *buf = (char *) calloc(1, PAM_TIME_BUFLEN+1);
123 pam_syslog(pamh, LOG_ERR, "out of memory");
130 fd = open(PAM_TIME_CONF, O_RDONLY);
132 pam_syslog(pamh, LOG_ERR, "error opening %s: %m", PAM_TIME_CONF);
141 to = shift_buf(*buf, *from);
145 while (fd != -1 && to - *buf < PAM_TIME_BUFLEN) {
146 i = pam_modutil_read(fd, to, PAM_TIME_BUFLEN - (to - *buf));
148 pam_syslog(pamh, LOG_ERR, "error reading %s: %m", PAM_TIME_CONF);
150 memset(*buf, 0, PAM_TIME_BUFLEN);
156 fd = -1; /* end of file reached */
163 /* nothing previously in buf, nothing read */
169 memset(to, '\0', PAM_TIME_BUFLEN - (to - *buf));
172 onspace = 1; /* delete any leading spaces */
174 for (src = to; (c=*src) != '\0'; ++src) {
175 if (*state == STATE_COMMENT && c != '\n') {
183 *from = (src - *buf) + 1;
184 trim_spaces(*buf, to);
196 onspace = 1; /* ignore following spaces */
201 *state = STATE_COMMENT;
204 case FIELD_SEPARATOR:
205 *state = STATE_FIELD;
207 *from = (src - *buf) + 1;
208 trim_spaces(*buf, to);
212 if (src[1] == '\n') {
221 *src = '\0'; /* clearing */
224 if (*state != STATE_COMMENT) {
225 *state = STATE_COMMENT;
226 pam_syslog(pamh, LOG_ERR, "field too long - ignored");
230 trim_spaces(*buf, to);
237 /* read a member from a field */
240 logic_member(const char *string, int *at)
267 if (isalpha(c) || c == '*' || isdigit(c) || c == '_'
268 || c == '-' || c == '.' || c == '/' || c == ':') {
282 typedef enum { VAL, OP } expect;
285 logic_field(pam_handle_t *pamh, const void *me, const char *x, int rule,
286 int (*agrees)(pam_handle_t *pamh,
287 const void *, const char *, int, int))
289 int left=FALSE, right, not=FALSE;
294 while ((l = logic_member(x,&at))) {
300 else if (isalpha(c) || c == '*' || isdigit(c) || c == '_'
301 || c == '-' || c == '.' || c == '/' || c == ':') {
302 right = not ^ agrees(pamh, me, x+at, l, rule);
309 pam_syslog(pamh, LOG_ERR,
310 "garbled syntax; expected name (rule #%d)",
323 pam_syslog(pamh, LOG_ERR,
324 "garbled syntax; expected & or | (rule #%d)",
326 D(("%c at %d",c,at));
338 is_same(pam_handle_t *pamh UNUSED, const void *A, const char *b,
339 int len, int rule UNUSED)
345 for (i=0; len > 0; ++i, --len) {
348 return (!--len || !strncmp(b+i,a+strlen(a)-len,len));
354 /* Ok, we know that b is a substring from A and does not contain
355 wildcards, but now the length of both strings must be the same,
356 too. In this case it means, a[i] has to be the end of the string. */
364 int day; /* array of 7 bits, one set for today */
365 int minute; /* integer, hour*100+minute for now */
392 the_time = time((time_t *)0); /* get the current time */
393 local = localtime(&the_time);
394 this.day = days[local->tm_wday].bit;
395 this.minute = local->tm_hour*100 + local->tm_min;
397 D(("day: 0%o, time: %.4d", this.day, this.minute));
401 /* take the current date and see if the range "date" passes it */
403 check_time(pam_handle_t *pamh, const void *AT, const char *times,
407 int marked_day, time_start, time_end;
412 D(("chcking: 0%o/%.4d vs. %s", at->day, at->minute, times));
415 /* this should not happen */
416 pam_syslog(pamh, LOG_CRIT,
417 "internal error in file %s at line %d",
422 if (times[j] == '!') {
429 for (marked_day = 0; len > 0 && isalpha(times[j]); --len) {
432 D(("%c%c ?", times[j], times[j+1]));
433 for (i=0; days[i].d != NULL; ++i) {
434 if (tolower(times[j]) == days[i].d[0]
435 && tolower(times[j+1]) == days[i].d[1] ) {
436 this_day = days[i].bit;
441 if (this_day == -1) {
442 pam_syslog(pamh, LOG_ERR, "bad day specified (rule #%d)", rule);
445 marked_day ^= this_day;
447 if (marked_day == 0) {
448 pam_syslog(pamh, LOG_ERR, "no day specified");
451 D(("day range = 0%o", marked_day));
454 for (i=0; len > 0 && i < 4 && isdigit(times[i+j]); ++i, --len) {
456 time_start += times[i+j]-'0'; /* is this portable? */
460 if (times[j] == '-') {
462 for (i=1; len > 0 && i < 5 && isdigit(times[i+j]); ++i, --len) {
464 time_end += times[i+j]-'0'; /* is this portable */
470 D(("i=%d, time_end=%d, times[j]='%c'", i, time_end, times[j]));
471 if (i != 5 || time_end == -1) {
472 pam_syslog(pamh, LOG_ERR, "no/bad times specified (rule #%d)", rule);
475 D(("times(%d to %d)", time_start,time_end));
476 D(("marked_day = 0%o", marked_day));
478 /* compare with the actual time now */
481 if (time_start < time_end) { /* start < end ? --> same day */
482 if ((at->day & marked_day) && (at->minute >= time_start)
483 && (at->minute < time_end)) {
484 D(("time is listed"));
487 } else { /* spans two days */
488 if ((at->day & marked_day) && (at->minute >= time_start)) {
489 D(("caught on first day"));
493 marked_day |= (marked_day & 0200) ? 1:0;
494 D(("next day = 0%o", marked_day));
495 if ((at->day & marked_day) && (at->minute <= time_end)) {
496 D(("caught on second day"));
506 check_account(pam_handle_t *pamh, const char *service,
507 const char *tty, const char *user)
509 int from=0, state=STATE_NL, fd=-1;
513 int retval=PAM_SUCCESS;
515 here_and_now = time_now(); /* find current time */
517 int good=TRUE,intime;
519 /* here we get the service name field */
521 fd = read_field(pamh, fd, &buffer, &from, &state);
522 if (!buffer || !buffer[0]) {
523 /* empty line .. ? */
528 if (state != STATE_FIELD) {
529 pam_syslog(pamh, LOG_ERR,
530 "%s: malformed rule #%d", PAM_TIME_CONF, count);
534 good = logic_field(pamh, service, buffer, count, is_same);
535 D(("with service: %s", good ? "passes":"fails" ));
537 /* here we get the terminal name field */
539 fd = read_field(pamh, fd, &buffer, &from, &state);
540 if (state != STATE_FIELD) {
541 pam_syslog(pamh, LOG_ERR,
542 "%s: malformed rule #%d", PAM_TIME_CONF, count);
545 good &= logic_field(pamh, tty, buffer, count, is_same);
546 D(("with tty: %s", good ? "passes":"fails" ));
548 /* here we get the username field */
550 fd = read_field(pamh, fd, &buffer, &from, &state);
551 if (state != STATE_FIELD) {
552 pam_syslog(pamh, LOG_ERR,
553 "%s: malformed rule #%d", PAM_TIME_CONF, count);
556 /* If buffer starts with @, we are using netgroups */
557 if (buffer[0] == '@')
558 good &= innetgr (&buffer[1], NULL, user, NULL);
560 good &= logic_field(pamh, user, buffer, count, is_same);
561 D(("with user: %s", good ? "passes":"fails" ));
563 /* here we get the time field */
565 fd = read_field(pamh, fd, &buffer, &from, &state);
566 if (state == STATE_FIELD) {
567 pam_syslog(pamh, LOG_ERR,
568 "%s: poorly terminated rule #%d", PAM_TIME_CONF, count);
572 intime = logic_field(pamh, &here_and_now, buffer, count, check_time);
573 D(("with time: %s", intime ? "passes":"fails" ));
575 if (good && !intime) {
577 * for security parse whole file.. also need to ensure
578 * that the buffer is free()'d and the file is closed.
580 retval = PAM_PERM_DENIED;
584 } while (state != STATE_EOF);
589 /* --- public account management functions --- */
592 pam_sm_acct_mgmt(pam_handle_t *pamh, int flags UNUSED,
593 int argc, const char **argv)
595 const void *service=NULL, *void_tty=NULL;
597 const char *user=NULL;
601 ctrl = _pam_parse(pamh, argc, argv);
603 /* set service name */
605 if (pam_get_item(pamh, PAM_SERVICE, &service)
606 != PAM_SUCCESS || service == NULL) {
607 pam_syslog(pamh, LOG_ERR, "cannot find the current service name");
613 if (pam_get_user(pamh, &user, NULL) != PAM_SUCCESS || user == NULL
615 pam_syslog(pamh, LOG_ERR, "can not get the username");
616 return PAM_USER_UNKNOWN;
621 if (pam_get_item(pamh, PAM_TTY, &void_tty) != PAM_SUCCESS
622 || void_tty == NULL) {
623 D(("PAM_TTY not set, probing stdin"));
624 tty = ttyname(STDIN_FILENO);
628 if (pam_set_item(pamh, PAM_TTY, tty) != PAM_SUCCESS) {
629 pam_syslog(pamh, LOG_ERR, "couldn't set tty name");
636 if (tty[0] == '/') { /* full path */
639 if ((t = strchr(tty, '/')) != NULL) {
644 /* good, now we have the service name, the user and the terminal name */
646 D(("service=%s", service));
647 D(("user=%s", user));
650 rv = check_account(pamh, service, tty, user);
651 if (rv != PAM_SUCCESS) {
653 if (!(ctrl & PAM_NO_AUDIT)) {
654 pam_modutil_audit_write(pamh, AUDIT_ANOM_LOGIN_TIME,
655 "pam_time", rv); /* ignore return value as we fail anyway */
658 if (ctrl & PAM_DEBUG_ARG) {
659 pam_syslog(pamh, LOG_DEBUG, "user %s rejected", user);
665 /* end of module definition */
669 /* static module data */
671 struct pam_module _pam_time_modstruct = {