6 * Written by Andrew Morgan <morgan@linux.kernel.org> 1996/6/22
7 * (File syntax and much other inspiration from the shadow package
11 const static char rcsid[] =
13 "\t\tVersion 0.22 for Linux-PAM\n"
14 "Copyright (C) Andrew G. Morgan 1996 <morgan@linux.kernel.org>\n";
16 #include <security/_pam_aconf.h>
27 #include <sys/types.h>
31 #ifdef DEFAULT_CONF_FILE
32 # define PAM_TIME_CONF DEFAULT_CONF_FILE /* from external define */
34 # define PAM_TIME_CONF "/etc/security/time.conf"
36 #define PAM_TIME_BUFLEN 1000
37 #define FIELD_SEPARATOR ';' /* this is new as of .02 */
39 typedef enum { FALSE, TRUE } boolean;
40 typedef enum { AND, OR } operator;
43 * here, we make definitions for the externally accessible functions
44 * in this file (these definitions are required for static modules
45 * but strongly encouraged generally) they are used to instruct the
46 * modules include file to define their prototypes.
49 #define PAM_SM_ACCOUNT
51 #include <security/_pam_macros.h>
52 #include <security/pam_modules.h>
54 /* --- static functions for checking whether the user should be let in --- */
56 static void _log_err(const char *format, ... )
60 va_start(args, format);
61 openlog("pam_time", LOG_CONS|LOG_PID, LOG_AUTH);
62 vsyslog(LOG_CRIT, format, args);
67 static void shift_bytes(char *mem, int from, int by)
75 static int read_field(int fd, char **buf, int *from, int *to)
80 *buf = (char *) malloc(PAM_TIME_BUFLEN);
82 _log_err("out of memory");
87 fd = open(PAM_TIME_CONF, O_RDONLY);
90 /* do we have a file open ? return error */
92 if (fd < 0 && *to <= 0) {
93 _log_err( PAM_TIME_CONF " not opened");
94 memset(*buf, 0, PAM_TIME_BUFLEN);
99 /* check if there was a newline last time */
101 if ((*to > *from) && (*to > 0)
102 && ((*buf)[*from] == '\0')) { /* previous line ended */
108 /* ready for more data: first shift the buffer's remaining data */
111 shift_bytes(*buf, *from, *to);
115 while (fd >= 0 && *to < PAM_TIME_BUFLEN) {
118 /* now try to fill the remainder of the buffer */
120 i = read(fd, *to + *buf, PAM_TIME_BUFLEN - *to);
122 _log_err("error reading " PAM_TIME_CONF);
126 fd = -1; /* end of file reached */
131 * contract the buffer. Delete any comments, and replace all
132 * multiple spaces with single commas
137 D(("buffer=<%s>",*buf));
140 if ((*buf)[i] == ',') {
143 for (j=++i; j<*to && (*buf)[j] == ','; ++j);
145 shift_bytes(i + (*buf), j-i, (*to) - j);
152 for (j=i; j < *to && (c = (*buf)[j]) != '\n'; ++j);
154 (*buf)[*to = ++i] = '\0';
155 } else if (c == '\n') {
156 shift_bytes(i + (*buf), j-i, (*to) - j);
160 _log_err("internal error in " __FILE__
161 " at line %d", __LINE__ );
166 if ((*buf)[i+1] == '\n') {
167 shift_bytes(i + *buf, 2, *to - (i+2));
174 if ((*buf)[i] != '!')
176 /* delete any trailing spaces */
177 for (j=++i; j < *to && ( (c = (*buf)[j]) == ' '
178 || c == '\t' ); ++j);
179 shift_bytes(i + *buf, j-i, (*to)-j );
190 /* now return the next field (set the from/to markers) */
194 for (i=0; i<*to; ++i) {
197 case '\n': /* end of the line/file */
201 case FIELD_SEPARATOR: /* end of the field */
208 (*buf)[*from] = '\0';
212 D(("[end of text]"));
219 /* read a member from a field */
221 static int logic_member(const char *string, int *at)
249 if (isalpha(c) || c == '*' || isdigit(c) || c == '_'
250 || c == '-' || c == '.') {
264 typedef enum { VAL, OP } expect;
266 static boolean logic_field(const void *me, const char *x, int rule,
267 boolean (*agrees)(const void *, const char *
270 boolean left=FALSE, right, not=FALSE;
275 while ((l = logic_member(x,&at))) {
281 else if (isalpha(c) || c == '*') {
282 right = not ^ agrees(me, x+at, l, rule);
289 _log_err("garbled syntax; expected name (rule #%d)", rule);
301 _log_err("garbled syntax; expected & or | (rule #%d)"
303 D(("%c at %d",c,at));
314 static boolean is_same(const void *A, const char *b, int len, int rule)
320 for (i=0; len > 0; ++i, --len) {
323 return (!--len || !strncmp(b+i,a+strlen(a)-len,len));
332 int day; /* array of 7 bits, one set for today */
333 int minute; /* integer, hour*100+minute for now */
339 } static const days[11] = {
353 static TIME time_now(void)
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 */
369 static boolean check_time(const void *AT, const char *times, int len, int rule)
372 int marked_day, time_start, time_end;
377 D(("chcking: 0%o/%.4d vs. %s", at->day, at->minute, times));
380 /* this should not happen */
381 _log_err("internal error: " __FILE__ " line %d", __LINE__);
385 if (times[j] == '!') {
392 for (marked_day = 0; len > 0 && isalpha(times[j]); --len) {
395 D(("%c%c ?", times[j], times[j+1]));
396 for (i=0; days[i].d != NULL; ++i) {
397 if (tolower(times[j]) == days[i].d[0]
398 && tolower(times[j+1]) == days[i].d[1] ) {
399 this_day = days[i].bit;
404 if (this_day == -1) {
405 _log_err("bad day specified (rule #%d)", rule);
408 marked_day ^= this_day;
410 if (marked_day == 0) {
411 _log_err("no day specified");
414 D(("day range = 0%o", marked_day));
417 for (i=0; len > 0 && i < 4 && isdigit(times[i+j]); ++i, --len) {
419 time_start += times[i+j]-'0'; /* is this portable? */
423 if (times[j] == '-') {
425 for (i=1; len > 0 && i < 5 && isdigit(times[i+j]); ++i, --len) {
427 time_end += times[i+j]-'0'; /* is this portable */
433 D(("i=%d, time_end=%d, times[j]='%c'", i, time_end, times[j]));
434 if (i != 5 || time_end == -1) {
435 _log_err("no/bad times specified (rule #%d)", rule);
438 D(("times(%d to %d)", time_start,time_end));
439 D(("marked_day = 0%o", marked_day));
441 /* compare with the actual time now */
444 if (time_start < time_end) { /* start < end ? --> same day */
445 if ((at->day & marked_day) && (at->minute >= time_start)
446 && (at->minute < time_end)) {
447 D(("time is listed"));
450 } else { /* spans two days */
451 if ((at->day & marked_day) && (at->minute >= time_start)) {
452 D(("caught on first day"));
456 marked_day |= (marked_day & 0200) ? 1:0;
457 D(("next day = 0%o", marked_day));
458 if ((at->day & marked_day) && (at->minute <= time_end)) {
459 D(("caught on second day"));
468 static int check_account(const char *service
469 , const char *tty, const char *user)
471 int from=0,to=0,fd=-1;
475 int retval=PAM_SUCCESS;
477 here_and_now = time_now(); /* find current time */
479 boolean good=TRUE,intime;
481 /* here we get the service name field */
483 fd = read_field(fd,&buffer,&from,&to);
485 if (!buffer || !buffer[0]) {
486 /* empty line .. ? */
491 good = logic_field(service, buffer, count, is_same);
492 D(("with service: %s", good ? "passes":"fails" ));
494 /* here we get the terminal name field */
496 fd = read_field(fd,&buffer,&from,&to);
497 if (!buffer || !buffer[0]) {
498 _log_err(PAM_TIME_CONF "; no tty entry #%d", count);
501 good &= logic_field(tty, buffer, count, is_same);
502 D(("with tty: %s", good ? "passes":"fails" ));
504 /* here we get the username field */
506 fd = read_field(fd,&buffer,&from,&to);
507 if (!buffer || !buffer[0]) {
508 _log_err(PAM_TIME_CONF "; no user entry #%d", count);
511 good &= logic_field(user, buffer, count, is_same);
512 D(("with user: %s", good ? "passes":"fails" ));
514 /* here we get the time field */
516 fd = read_field(fd,&buffer,&from,&to);
517 if (!buffer || !buffer[0]) {
518 _log_err(PAM_TIME_CONF "; no time entry #%d", count);
522 intime = logic_field(&here_and_now, buffer, count, check_time);
523 D(("with time: %s", intime ? "passes":"fails" ));
525 fd = read_field(fd,&buffer,&from,&to);
526 if (buffer && buffer[0]) {
527 _log_err(PAM_TIME_CONF "; poorly terminated rule #%d", count);
531 if (good && !intime) {
533 * for security parse whole file.. also need to ensure
534 * that the buffer is free()'d and the file is closed.
536 retval = PAM_PERM_DENIED;
545 /* --- public account management functions --- */
547 PAM_EXTERN int pam_sm_acct_mgmt(pam_handle_t *pamh,int flags,int argc
550 const char *service=NULL, *tty=NULL;
551 const char *user=NULL;
553 /* set service name */
555 if (pam_get_item(pamh, PAM_SERVICE, (const void **)&service)
556 != PAM_SUCCESS || service == NULL) {
557 _log_err("cannot find the current service name");
563 if (pam_get_user(pamh, &user, NULL) != PAM_SUCCESS || user == NULL
565 _log_err("cannot determine the user's name");
566 return PAM_USER_UNKNOWN;
571 if (pam_get_item(pamh, PAM_TTY, (const void **)&tty) != PAM_SUCCESS
573 D(("PAM_TTY not set, probing stdin"));
574 tty = ttyname(STDIN_FILENO);
576 _log_err("couldn't get the tty name");
579 if (pam_set_item(pamh, PAM_TTY, tty) != PAM_SUCCESS) {
580 _log_err("couldn't set tty name");
585 if (strncmp("/dev/",tty,5) == 0) { /* strip leading /dev/ */
589 /* good, now we have the service name, the user and the terminal name */
591 D(("service=%s", service));
592 D(("user=%s", user));
595 return check_account(service,tty,user);
598 /* end of module definition */
602 /* static module data */
604 struct pam_module _pam_time_modstruct = {