4 * Written by Andrew Morgan <morgan@linux.kernel.org> 1996/6/22
5 * (File syntax and much other inspiration from the shadow package
20 #include <sys/types.h>
25 #define PAM_TIME_BUFLEN 1000
26 #define FIELD_SEPARATOR ';' /* this is new as of .02 */
35 typedef enum { AND, OR } operator;
38 * here, we make definitions for the externally accessible functions
39 * in this file (these definitions are required for static modules
40 * but strongly encouraged generally) they are used to instruct the
41 * modules include file to define their prototypes.
44 #define PAM_SM_ACCOUNT
46 #include <security/_pam_macros.h>
47 #include <security/pam_modules.h>
48 #include <security/pam_ext.h>
50 /* --- static functions for checking whether the user should be let in --- */
53 shift_bytes(char *mem, int from, int by)
62 read_field(pam_handle_t *pamh, int fd, char **buf, int *from, int *to)
67 *buf = (char *) malloc(PAM_TIME_BUFLEN);
69 pam_syslog(pamh, LOG_ERR, "out of memory");
74 fd = open(PAM_TIME_CONF, O_RDONLY);
77 /* do we have a file open ? return error */
79 if (fd < 0 && *to <= 0) {
80 pam_syslog(pamh, LOG_ERR, "error opening %s: %m", PAM_TIME_CONF);
81 memset(*buf, 0, PAM_TIME_BUFLEN);
86 /* check if there was a newline last time */
88 if ((*to > *from) && (*to > 0)
89 && ((*buf)[*from] == '\0')) { /* previous line ended */
95 /* ready for more data: first shift the buffer's remaining data */
98 shift_bytes(*buf, *from, *to);
102 while (fd >= 0 && *to < PAM_TIME_BUFLEN) {
105 /* now try to fill the remainder of the buffer */
107 i = read(fd, *to + *buf, PAM_TIME_BUFLEN - *to);
109 pam_syslog(pamh, LOG_ERR, "error reading %s: %m", PAM_TIME_CONF);
114 fd = -1; /* end of file reached */
119 * contract the buffer. Delete any comments, and replace all
120 * multiple spaces with single commas
125 D(("buffer=<%s>",*buf));
128 if ((*buf)[i] == ',') {
131 for (j=++i; j<*to && (*buf)[j] == ','; ++j);
133 shift_bytes(i + (*buf), j-i, (*to) - j);
140 for (j=i; j < *to && (c = (*buf)[j]) != '\n'; ++j);
142 (*buf)[*to = ++i] = '\0';
143 } else if (c == '\n') {
144 shift_bytes(i + (*buf), j-i, (*to) - j);
148 pam_syslog(pamh, LOG_CRIT,
149 "internal error in file %s at line %d",
156 if ((*buf)[i+1] == '\n') {
157 shift_bytes(i + *buf, 2, *to - (i+2));
160 ++i; /* we don't escape non-newline characters */
166 if ((*buf)[i] != '!')
168 /* delete any trailing spaces */
169 for (j=++i; j < *to && ( (c = (*buf)[j]) == ' '
170 || c == '\t' ); ++j);
171 shift_bytes(i + *buf, j-i, (*to)-j );
182 /* now return the next field (set the from/to markers) */
186 for (i=0; i<*to; ++i) {
189 case '\n': /* end of the line/file */
193 case FIELD_SEPARATOR: /* end of the field */
200 (*buf)[*from] = '\0';
204 D(("[end of text]"));
211 /* read a member from a field */
214 logic_member(const char *string, int *at)
241 if (isalpha(c) || c == '*' || isdigit(c) || c == '_'
242 || c == '-' || c == '.' || c == '/' || c == ':') {
256 typedef enum { VAL, OP } expect;
259 logic_field(pam_handle_t *pamh, const void *me, const char *x, int rule,
260 int (*agrees)(pam_handle_t *pamh,
261 const void *, const char *, int, int))
263 int left=FALSE, right, not=FALSE;
268 while ((l = logic_member(x,&at))) {
274 else if (isalpha(c) || c == '*' || isdigit(c) || c == '_'
275 || c == '-' || c == '.' || c == '/' || c == ':') {
276 right = not ^ agrees(pamh, me, x+at, l, rule);
283 pam_syslog(pamh, LOG_ERR,
284 "garbled syntax; expected name (rule #%d)",
297 pam_syslog(pamh, LOG_ERR,
298 "garbled syntax; expected & or | (rule #%d)",
300 D(("%c at %d",c,at));
312 is_same(pam_handle_t *pamh UNUSED, const void *A, const char *b,
313 int len, int rule UNUSED)
319 for (i=0; len > 0; ++i, --len) {
322 return (!--len || !strncmp(b+i,a+strlen(a)-len,len));
331 int day; /* array of 7 bits, one set for today */
332 int minute; /* integer, hour*100+minute for now */
359 the_time = time((time_t *)0); /* get the current time */
360 local = localtime(&the_time);
361 this.day = days[local->tm_wday].bit;
362 this.minute = local->tm_hour*100 + local->tm_min;
364 D(("day: 0%o, time: %.4d", this.day, this.minute));
368 /* take the current date and see if the range "date" passes it */
370 check_time(pam_handle_t *pamh, const void *AT, const char *times,
374 int marked_day, time_start, time_end;
379 D(("chcking: 0%o/%.4d vs. %s", at->day, at->minute, times));
382 /* this should not happen */
383 pam_syslog(pamh, LOG_CRIT,
384 "internal error in file %s at line %d",
389 if (times[j] == '!') {
396 for (marked_day = 0; len > 0 && isalpha(times[j]); --len) {
399 D(("%c%c ?", times[j], times[j+1]));
400 for (i=0; days[i].d != NULL; ++i) {
401 if (tolower(times[j]) == days[i].d[0]
402 && tolower(times[j+1]) == days[i].d[1] ) {
403 this_day = days[i].bit;
408 if (this_day == -1) {
409 pam_syslog(pamh, LOG_ERR, "bad day specified (rule #%d)", rule);
412 marked_day ^= this_day;
414 if (marked_day == 0) {
415 pam_syslog(pamh, LOG_ERR, "no day specified");
418 D(("day range = 0%o", marked_day));
421 for (i=0; len > 0 && i < 4 && isdigit(times[i+j]); ++i, --len) {
423 time_start += times[i+j]-'0'; /* is this portable? */
427 if (times[j] == '-') {
429 for (i=1; len > 0 && i < 5 && isdigit(times[i+j]); ++i, --len) {
431 time_end += times[i+j]-'0'; /* is this portable */
437 D(("i=%d, time_end=%d, times[j]='%c'", i, time_end, times[j]));
438 if (i != 5 || time_end == -1) {
439 pam_syslog(pamh, LOG_ERR, "no/bad times specified (rule #%d)", rule);
442 D(("times(%d to %d)", time_start,time_end));
443 D(("marked_day = 0%o", marked_day));
445 /* compare with the actual time now */
448 if (time_start < time_end) { /* start < end ? --> same day */
449 if ((at->day & marked_day) && (at->minute >= time_start)
450 && (at->minute < time_end)) {
451 D(("time is listed"));
454 } else { /* spans two days */
455 if ((at->day & marked_day) && (at->minute >= time_start)) {
456 D(("caught on first day"));
460 marked_day |= (marked_day & 0200) ? 1:0;
461 D(("next day = 0%o", marked_day));
462 if ((at->day & marked_day) && (at->minute <= time_end)) {
463 D(("caught on second day"));
473 check_account(pam_handle_t *pamh, const char *service,
474 const char *tty, const char *user)
476 int from=0,to=0,fd=-1;
480 int retval=PAM_SUCCESS;
482 here_and_now = time_now(); /* find current time */
484 int good=TRUE,intime;
486 /* here we get the service name field */
488 fd = read_field(pamh, fd, &buffer, &from, &to);
490 if (!buffer || !buffer[0]) {
491 /* empty line .. ? */
496 good = logic_field(pamh, service, buffer, count, is_same);
497 D(("with service: %s", good ? "passes":"fails" ));
499 /* here we get the terminal name field */
501 fd = read_field(pamh, fd, &buffer, &from, &to);
502 if (!buffer || !buffer[0]) {
503 pam_syslog(pamh, LOG_ERR,
504 "%s: no tty entry #%d", PAM_TIME_CONF, count);
507 good &= logic_field(pamh, tty, buffer, count, is_same);
508 D(("with tty: %s", good ? "passes":"fails" ));
510 /* here we get the username field */
512 fd = read_field(pamh, fd, &buffer, &from, &to);
513 if (!buffer || !buffer[0]) {
514 pam_syslog(pamh, LOG_ERR,
515 "%s: no user entry #%d", PAM_TIME_CONF, count);
518 /* If buffer starts with @, we are using netgroups */
519 if (buffer[0] == '@')
520 good &= innetgr (&buffer[1], NULL, user, NULL);
522 good &= logic_field(pamh, user, buffer, count, is_same);
523 D(("with user: %s", good ? "passes":"fails" ));
525 /* here we get the time field */
527 fd = read_field(pamh, fd, &buffer, &from, &to);
528 if (!buffer || !buffer[0]) {
529 pam_syslog(pamh, LOG_ERR,
530 "%s: no time entry #%d", PAM_TIME_CONF, count);
534 intime = logic_field(pamh, &here_and_now, buffer, count, check_time);
535 D(("with time: %s", intime ? "passes":"fails" ));
537 fd = read_field(pamh, fd, &buffer, &from, &to);
538 if (buffer && buffer[0]) {
539 pam_syslog(pamh, LOG_ERR,
540 "%s: poorly terminated rule #%d", PAM_TIME_CONF, count);
544 if (good && !intime) {
546 * for security parse whole file.. also need to ensure
547 * that the buffer is free()'d and the file is closed.
549 retval = PAM_PERM_DENIED;
558 /* --- public account management functions --- */
561 pam_sm_acct_mgmt(pam_handle_t *pamh, int flags UNUSED,
562 int argc UNUSED, const char **argv UNUSED)
564 const void *service=NULL, *void_tty=NULL;
566 const char *user=NULL;
568 /* set service name */
570 if (pam_get_item(pamh, PAM_SERVICE, &service)
571 != PAM_SUCCESS || service == NULL) {
572 pam_syslog(pamh, LOG_ERR, "cannot find the current service name");
578 if (pam_get_user(pamh, &user, NULL) != PAM_SUCCESS || user == NULL
580 pam_syslog(pamh, LOG_ERR, "can not get the username");
581 return PAM_USER_UNKNOWN;
586 if (pam_get_item(pamh, PAM_TTY, &void_tty) != PAM_SUCCESS
587 || void_tty == NULL) {
588 D(("PAM_TTY not set, probing stdin"));
589 tty = ttyname(STDIN_FILENO);
593 if (pam_set_item(pamh, PAM_TTY, tty) != PAM_SUCCESS) {
594 pam_syslog(pamh, LOG_ERR, "couldn't set tty name");
601 if (tty[0] == '/') { /* full path */
604 if ((t = strchr(tty, '/')) != NULL) {
609 /* good, now we have the service name, the user and the terminal name */
611 D(("service=%s", service));
612 D(("user=%s", user));
615 return check_account(pamh, service, tty, user);
618 /* end of module definition */
622 /* static module data */
624 struct pam_module _pam_time_modstruct = {