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