]> granicus.if.org Git - linux-pam/blob - modules/pam_time/pam_time.c
Relevant BUGIDs: 115055
[linux-pam] / modules / pam_time / pam_time.c
1 /* pam_time module */
2
3 /*
4  * $Id$
5  *
6  * Written by Andrew Morgan <morgan@linux.kernel.org> 1996/6/22
7  * (File syntax and much other inspiration from the shadow package
8  * shadow-960129)
9  */
10
11 const static char rcsid[] =
12 "$Id$;\n"
13 "\t\tVersion 0.22 for Linux-PAM\n"
14 "Copyright (C) Andrew G. Morgan 1996 <morgan@linux.kernel.org>\n";
15
16 #include <security/_pam_aconf.h>
17
18 #include <sys/file.h>
19 #include <stdio.h>
20 #include <stdlib.h>
21 #include <ctype.h>
22 #include <unistd.h>
23 #include <stdarg.h>
24 #include <time.h>
25 #include <syslog.h>
26 #include <string.h>
27 #include <sys/types.h>
28 #include <sys/stat.h>
29 #include <fcntl.h>
30
31 #ifdef DEFAULT_CONF_FILE
32 # define PAM_TIME_CONF         DEFAULT_CONF_FILE /* from external define */
33 #else
34 # define PAM_TIME_CONF         "/etc/security/time.conf"
35 #endif
36 #define PAM_TIME_BUFLEN        1000
37 #define FIELD_SEPARATOR        ';'   /* this is new as of .02 */
38
39 typedef enum { FALSE, TRUE } boolean;
40 typedef enum { AND, OR } operator;
41
42 /*
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.
47  */
48
49 #define PAM_SM_ACCOUNT
50
51 #include <security/_pam_macros.h>
52 #include <security/pam_modules.h>
53
54 /* --- static functions for checking whether the user should be let in --- */
55
56 static void _log_err(const char *format, ... )
57 {
58     va_list args;
59
60     va_start(args, format);
61     openlog("pam_time", LOG_CONS|LOG_PID, LOG_AUTH);
62     vsyslog(LOG_CRIT, format, args);
63     va_end(args);
64     closelog();
65 }
66
67 static void shift_bytes(char *mem, int from, int by)
68 {
69     while (by-- > 0) {
70         *mem = mem[from];
71         ++mem;
72     }
73 }
74
75 static int read_field(int fd, char **buf, int *from, int *to)
76 {
77     /* is buf set ? */
78
79     if (! *buf) {
80         *buf = (char *) malloc(PAM_TIME_BUFLEN);
81         if (! *buf) {
82             _log_err("out of memory");
83             D(("no memory"));
84             return -1;
85         }
86         *from = *to = 0;
87         fd = open(PAM_TIME_CONF, O_RDONLY);
88     }
89
90     /* do we have a file open ? return error */
91
92     if (fd < 0 && *to <= 0) {
93         _log_err( PAM_TIME_CONF " not opened");
94         memset(*buf, 0, PAM_TIME_BUFLEN);
95         _pam_drop(*buf);
96         return -1;
97     }
98
99     /* check if there was a newline last time */
100
101     if ((*to > *from) && (*to > 0)
102         && ((*buf)[*from] == '\0')) { /* previous line ended */
103         (*from)++;
104         (*buf)[0] = '\0';
105         return fd;
106     }
107
108     /* ready for more data: first shift the buffer's remaining data */
109
110     *to -= *from;
111     shift_bytes(*buf, *from, *to);
112     *from = 0;
113     (*buf)[*to] = '\0';
114
115     while (fd >= 0 && *to < PAM_TIME_BUFLEN) {
116         int i;
117
118         /* now try to fill the remainder of the buffer */
119
120         i = read(fd, *to + *buf, PAM_TIME_BUFLEN - *to);
121         if (i < 0) {
122             _log_err("error reading " PAM_TIME_CONF);
123             return -1;
124         } else if (!i) {
125             close(fd);
126             fd = -1;          /* end of file reached */
127         } else
128             *to += i;
129     
130         /*
131          * contract the buffer. Delete any comments, and replace all
132          * multiple spaces with single commas
133          */
134
135         i = 0;
136 #ifdef DEBUG_DUMP
137         D(("buffer=<%s>",*buf));
138 #endif
139         while (i < *to) {
140             if ((*buf)[i] == ',') {
141                 int j;
142
143                 for (j=++i; j<*to && (*buf)[j] == ','; ++j);
144                 if (j!=i) {
145                     shift_bytes(i + (*buf), j-i, (*to) - j);
146                     *to -= j-i;
147                 }
148             }
149             switch ((*buf)[i]) {
150                 int j,c;
151             case '#':
152                 for (j=i; j < *to && (c = (*buf)[j]) != '\n'; ++j);
153                 if (j >= *to) {
154                     (*buf)[*to = ++i] = '\0';
155                 } else if (c == '\n') {
156                     shift_bytes(i + (*buf), j-i, (*to) - j);
157                     *to -= j-i;
158                     ++i;
159                 } else {
160                     _log_err("internal error in " __FILE__
161                              " at line %d", __LINE__ );
162                     return -1;
163                 }
164                 break;
165             case '\\':
166                 if ((*buf)[i+1] == '\n') {
167                     shift_bytes(i + *buf, 2, *to - (i+2));
168                     *to -= 2;
169                 }
170                 break;
171             case '!':
172             case ' ':
173             case '\t':
174                 if ((*buf)[i] != '!')
175                     (*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 );
180                 *to -= j-i;
181                 break;
182             default:
183                 ++i;
184             }
185         }
186     }
187
188     (*buf)[*to] = '\0';
189
190     /* now return the next field (set the from/to markers) */
191     {
192         int i;
193
194         for (i=0; i<*to; ++i) {
195             switch ((*buf)[i]) {
196             case '#':
197             case '\n':               /* end of the line/file */
198                 (*buf)[i] = '\0';
199                 *from = i;
200                 return fd;
201             case FIELD_SEPARATOR:    /* end of the field */
202                 (*buf)[i] = '\0';
203             *from = ++i;
204             return fd;
205             }
206         }
207         *from = i;
208         (*buf)[*from] = '\0';
209     }
210
211     if (*to <= 0) {
212         D(("[end of text]"));
213         *buf = NULL;
214     }
215
216     return fd;
217 }
218
219 /* read a member from a field */
220
221 static int logic_member(const char *string, int *at)
222 {
223      int len,c,to;
224      int done=0;
225      int token=0;
226
227      len=0;
228      to=*at;
229      do {
230           c = string[to++];
231
232           switch (c) {
233
234           case '\0':
235                --to;
236                done = 1;
237                break;
238
239           case '&':
240           case '|':
241           case '!':
242                if (token) {
243                     --to;
244                }
245                done = 1;
246                break;
247
248           default:
249                if (isalpha(c) || c == '*' || isdigit(c) || c == '_'
250                     || c == '-' || c == '.') {
251                     token = 1;
252                } else if (token) {
253                     --to;
254                     done = 1;
255                } else {
256                     ++*at;
257                }
258           }
259      } while (!done);
260
261      return to - *at;
262 }
263
264 typedef enum { VAL, OP } expect;
265
266 static boolean logic_field(const void *me, const char *x, int rule,
267                            boolean (*agrees)(const void *, const char *
268                                              , int, int))
269 {
270      boolean left=FALSE, right, not=FALSE;
271      operator oper=OR;
272      int at=0, l;
273      expect next=VAL;
274
275      while ((l = logic_member(x,&at))) {
276           int c = x[at];
277
278           if (next == VAL) {
279                if (c == '!')
280                     not = !not;
281                else if (isalpha(c) || c == '*') {
282                     right = not ^ agrees(me, x+at, l, rule);
283                     if (oper == AND)
284                          left &= right;
285                     else
286                          left |= right;
287                     next = OP;
288                } else {
289                     _log_err("garbled syntax; expected name (rule #%d)", rule);
290                     return FALSE;
291                }
292           } else {   /* OP */
293                switch (c) {
294                case '&':
295                     oper = AND;
296                     break;
297                case '|':
298                     oper = OR;
299                     break;
300                default:
301                     _log_err("garbled syntax; expected & or | (rule #%d)"
302                              , rule);
303                     D(("%c at %d",c,at));
304                     return FALSE;
305                }
306                next = VAL;
307           }
308           at += l;
309      }
310
311      return left;
312 }
313
314 static boolean is_same(const void *A, const char *b, int len, int rule)
315 {
316      int i;
317      const char *a;
318
319      a = A;
320      for (i=0; len > 0; ++i, --len) {
321           if (b[i] != a[i]) {
322                if (b[i++] == '*') {
323                     return (!--len || !strncmp(b+i,a+strlen(a)-len,len));
324                } else
325                     return FALSE;
326           }
327      }
328      return ( !len );
329 }
330
331 typedef struct {
332      int day;             /* array of 7 bits, one set for today */
333      int minute;            /* integer, hour*100+minute for now */
334 } TIME;
335
336 struct day {
337      const char *d;
338      int bit;
339 } static const days[11] = {
340      { "su", 01 },
341      { "mo", 02 },
342      { "tu", 04 },
343      { "we", 010 },
344      { "th", 020 },
345      { "fr", 040 },
346      { "sa", 0100 },
347      { "wk", 076 },
348      { "wd", 0101 },
349      { "al", 0177 },
350      { NULL, 0 }
351 };
352
353 static TIME time_now(void)
354 {
355      struct tm *local;
356      time_t the_time;
357      TIME this;
358
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;
363
364      D(("day: 0%o, time: %.4d", this.day, this.minute));
365      return this;
366 }
367
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)
370 {
371      boolean not,pass;
372      int marked_day, time_start, time_end;
373      const TIME *at;
374      int i,j=0;
375
376      at = AT;
377      D(("chcking: 0%o/%.4d vs. %s", at->day, at->minute, times));
378
379      if (times == NULL) {
380           /* this should not happen */
381           _log_err("internal error: " __FILE__ " line %d", __LINE__);
382           return FALSE;
383      }
384
385      if (times[j] == '!') {
386           ++j;
387           not = TRUE;
388      } else {
389           not = FALSE;
390      }
391
392      for (marked_day = 0; len > 0 && isalpha(times[j]); --len) {
393           int this_day=-1;
394
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;
400                     break;
401                }
402           }
403           j += 2;
404           if (this_day == -1) {
405                _log_err("bad day specified (rule #%d)", rule);
406                return FALSE;
407           }
408           marked_day ^= this_day;
409      }
410      if (marked_day == 0) {
411           _log_err("no day specified");
412           return FALSE;
413      }
414      D(("day range = 0%o", marked_day));
415
416      time_start = 0;
417      for (i=0; len > 0 && i < 4 && isdigit(times[i+j]); ++i, --len) {
418           time_start *= 10;
419           time_start += times[i+j]-'0';        /* is this portable? */
420      }
421      j += i;
422
423      if (times[j] == '-') {
424           time_end = 0;
425           for (i=1; len > 0 && i < 5 && isdigit(times[i+j]); ++i, --len) {
426                time_end *= 10;
427                time_end += times[i+j]-'0';    /* is this portable */
428           }
429           j += i;
430      } else
431           time_end = -1;
432
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);
436           return TRUE;
437      }
438      D(("times(%d to %d)", time_start,time_end));
439      D(("marked_day = 0%o", marked_day));
440
441      /* compare with the actual time now */
442
443      pass = FALSE;
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"));
448                pass = TRUE;
449           }
450      } else {                                    /* spans two days */
451           if ((at->day & marked_day) && (at->minute >= time_start)) {
452                D(("caught on first day"));
453                pass = TRUE;
454           } else {
455                marked_day <<= 1;
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"));
460                     pass = TRUE;
461                }
462           }
463      }
464
465      return (not ^ pass);
466 }
467
468 static int check_account(const char *service
469                          , const char *tty, const char *user)
470 {
471      int from=0,to=0,fd=-1;
472      char *buffer=NULL;
473      int count=0;
474      TIME here_and_now;
475      int retval=PAM_SUCCESS;
476
477      here_and_now = time_now();                     /* find current time */
478      do {
479           boolean good=TRUE,intime;
480
481           /* here we get the service name field */
482
483           fd = read_field(fd,&buffer,&from,&to);
484
485           if (!buffer || !buffer[0]) {
486                /* empty line .. ? */
487                continue;
488           }
489           ++count;
490
491           good = logic_field(service, buffer, count, is_same);
492           D(("with service: %s", good ? "passes":"fails" ));
493
494           /* here we get the terminal name field */
495
496           fd = read_field(fd,&buffer,&from,&to);
497           if (!buffer || !buffer[0]) {
498                _log_err(PAM_TIME_CONF "; no tty entry #%d", count);
499                continue;
500           }
501           good &= logic_field(tty, buffer, count, is_same);
502           D(("with tty: %s", good ? "passes":"fails" ));
503
504           /* here we get the username field */
505
506           fd = read_field(fd,&buffer,&from,&to);
507           if (!buffer || !buffer[0]) {
508                _log_err(PAM_TIME_CONF "; no user entry #%d", count);
509                continue;
510           }
511           good &= logic_field(user, buffer, count, is_same);
512           D(("with user: %s", good ? "passes":"fails" ));
513
514           /* here we get the time field */
515
516           fd = read_field(fd,&buffer,&from,&to);
517           if (!buffer || !buffer[0]) {
518                _log_err(PAM_TIME_CONF "; no time entry #%d", count);
519                continue;
520           }
521
522           intime = logic_field(&here_and_now, buffer, count, check_time);
523           D(("with time: %s", intime ? "passes":"fails" ));
524
525           fd = read_field(fd,&buffer,&from,&to);
526           if (buffer && buffer[0]) {
527                _log_err(PAM_TIME_CONF "; poorly terminated rule #%d", count);
528                continue;
529           }
530
531           if (good && !intime) {
532                /*
533                 * for security parse whole file..  also need to ensure
534                 * that the buffer is free()'d and the file is closed.
535                 */
536                retval = PAM_PERM_DENIED;
537           } else {
538                D(("rule passed"));
539           }
540      } while (buffer);
541
542      return retval;
543 }
544
545 /* --- public account management functions --- */
546
547 PAM_EXTERN int pam_sm_acct_mgmt(pam_handle_t *pamh,int flags,int argc
548                      ,const char **argv)
549 {
550     const char *service=NULL, *tty=NULL;
551     const char *user=NULL;
552
553     /* set service name */
554
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");
558         return PAM_ABORT;
559     }
560
561     /* set username */
562
563     if (pam_get_user(pamh, &user, NULL) != PAM_SUCCESS || user == NULL
564         || *user == '\0') {
565         _log_err("cannot determine the user's name");
566         return PAM_USER_UNKNOWN;
567     }
568
569     /* set tty name */
570
571     if (pam_get_item(pamh, PAM_TTY, (const void **)&tty) != PAM_SUCCESS
572         || tty == NULL) {
573         D(("PAM_TTY not set, probing stdin"));
574         tty = ttyname(STDIN_FILENO);
575         if (tty == NULL) {
576             _log_err("couldn't get the tty name");
577             return PAM_ABORT;
578         }
579         if (pam_set_item(pamh, PAM_TTY, tty) != PAM_SUCCESS) {
580             _log_err("couldn't set tty name");
581             return PAM_ABORT;
582         }
583     }
584
585     if (strncmp("/dev/",tty,5) == 0) {          /* strip leading /dev/ */
586         tty += 5;
587     }
588
589     /* good, now we have the service name, the user and the terminal name */
590
591     D(("service=%s", service));
592     D(("user=%s", user));
593     D(("tty=%s", tty));
594
595     return check_account(service,tty,user);
596 }
597
598 /* end of module definition */
599
600 #ifdef PAM_STATIC
601
602 /* static module data */
603
604 struct pam_module _pam_time_modstruct = {
605     "pam_time",
606     NULL,
607     NULL,
608     pam_sm_acct_mgmt,
609     NULL,
610     NULL,
611     NULL
612 };
613 #endif