Imported Upstream version 1.1.6
[platform/upstream/pam.git] / 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  * Field parsing rewritten by Tomas Mraz <tm@t8m.info>
8  */
9
10 #include "config.h"
11
12 #include <sys/file.h>
13 #include <stdio.h>
14 #include <stdlib.h>
15 #include <ctype.h>
16 #include <unistd.h>
17 #include <stdarg.h>
18 #include <time.h>
19 #include <syslog.h>
20 #include <string.h>
21 #include <sys/types.h>
22 #include <sys/stat.h>
23 #include <fcntl.h>
24 #include <netdb.h>
25
26 #ifdef HAVE_LIBAUDIT
27 #include <libaudit.h>
28 #endif
29
30 #define PAM_TIME_BUFLEN        1000
31 #define FIELD_SEPARATOR        ';'   /* this is new as of .02 */
32
33 #define PAM_DEBUG_ARG       0x0001
34 #define PAM_NO_AUDIT        0x0002
35
36 #ifndef TRUE
37 # define TRUE 1
38 #endif
39 #ifndef FALSE
40 # define FALSE 0
41 #endif
42
43 typedef enum { AND, OR } operator;
44
45 /*
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.
50  */
51
52 #define PAM_SM_ACCOUNT
53
54 #include <security/_pam_macros.h>
55 #include <security/pam_modules.h>
56 #include <security/pam_ext.h>
57 #include <security/pam_modutil.h>
58
59 static int
60 _pam_parse (const pam_handle_t *pamh, int argc, const char **argv)
61 {
62     int ctrl = 0;
63
64     /* step through arguments */
65     for (; argc-- > 0; ++argv) {
66
67         /* generic options */
68
69         if (!strcmp(*argv, "debug")) {
70             ctrl |= PAM_DEBUG_ARG;
71         } else if (!strcmp(*argv, "noaudit")) {
72             ctrl |= PAM_NO_AUDIT;
73         } else {
74             pam_syslog(pamh, LOG_ERR, "unknown option: %s", *argv);
75         }
76     }
77
78     return ctrl;
79 }
80
81 /* --- static functions for checking whether the user should be let in --- */
82
83 static char *
84 shift_buf(char *mem, int from)
85 {
86     char *start = mem;
87     while ((*mem = mem[from]) != '\0')
88         ++mem;
89     memset(mem, '\0', PAM_TIME_BUFLEN - (mem - start));
90     return mem;
91 }
92
93 static void
94 trim_spaces(char *buf, char *from)
95 {
96     while (from > buf) {
97         --from;
98         if (*from == ' ')
99             *from = '\0';
100         else
101             break;
102     }
103 }
104
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 */
109
110 static int
111 read_field(const pam_handle_t *pamh, int fd, char **buf, int *from, int *state)
112 {
113     char *to;
114     char *src;
115     int i;
116     char c;
117     int onspace;
118
119     /* is buf set ? */
120     if (! *buf) {
121         *buf = (char *) calloc(1, PAM_TIME_BUFLEN+1);
122         if (! *buf) {
123             pam_syslog(pamh, LOG_ERR, "out of memory");
124             D(("no memory"));
125             *state = STATE_EOF;
126             return -1;
127         }
128         *from = 0;
129         *state = STATE_NL;
130         fd = open(PAM_TIME_CONF, O_RDONLY);
131         if (fd < 0) {
132             pam_syslog(pamh, LOG_ERR, "error opening %s: %m", PAM_TIME_CONF);
133             _pam_drop(*buf);
134             *state = STATE_EOF;
135             return -1;
136         }
137     }
138
139
140     if (*from > 0)
141         to = shift_buf(*buf, *from);
142     else
143         to = *buf;
144
145     while (fd != -1 && to - *buf < PAM_TIME_BUFLEN) {
146         i = pam_modutil_read(fd, to, PAM_TIME_BUFLEN - (to - *buf));
147         if (i < 0) {
148             pam_syslog(pamh, LOG_ERR, "error reading %s: %m", PAM_TIME_CONF);
149             close(fd);
150             memset(*buf, 0, PAM_TIME_BUFLEN);
151             _pam_drop(*buf);
152             *state = STATE_EOF;
153             return -1;
154         } else if (!i) {
155             close(fd);
156             fd = -1;          /* end of file reached */
157         }
158
159         to += i;
160     }
161
162     if (to == *buf) {
163         /* nothing previously in buf, nothing read */
164         _pam_drop(*buf);
165         *state = STATE_EOF;
166         return -1;
167     }
168
169     memset(to, '\0', PAM_TIME_BUFLEN - (to - *buf));
170
171     to = *buf;
172     onspace = 1; /* delete any leading spaces */
173
174     for (src = to; (c=*src) != '\0'; ++src) {
175         if (*state == STATE_COMMENT && c != '\n') {
176                 continue;
177         }
178
179         switch (c) {
180             case '\n':
181                 *state = STATE_NL;
182                 *to = '\0';
183                 *from = (src - *buf) + 1;
184                 trim_spaces(*buf, to);
185                 return fd;
186
187             case '\t':
188             case ' ':
189                 if (!onspace) {
190                     onspace = 1;
191                     *to++ = ' ';
192                 }
193                 break;
194
195             case '!':
196                 onspace = 1; /* ignore following spaces */
197                 *to++ = '!';
198                 break;
199
200             case '#':
201                 *state = STATE_COMMENT;
202                 break;
203
204             case FIELD_SEPARATOR:
205                 *state = STATE_FIELD;
206                 *to = '\0';
207                 *from = (src - *buf) + 1;
208                 trim_spaces(*buf, to);
209                 return fd;
210
211             case '\\':
212                 if (src[1] == '\n') {
213                     ++src; /* skip it */
214                     break;
215                 }
216             default:
217                 *to++ = c;
218                 onspace = 0;
219         }
220         if (src > to)
221             *src = '\0'; /* clearing */
222     }
223
224     if (*state != STATE_COMMENT) {
225         *state = STATE_COMMENT;
226         pam_syslog(pamh, LOG_ERR, "field too long - ignored");
227         **buf = '\0';
228     } else {
229         *to = '\0';
230         trim_spaces(*buf, to);
231     }
232
233     *from = 0;
234     return fd;
235 }
236
237 /* read a member from a field */
238
239 static int
240 logic_member(const char *string, int *at)
241 {
242      int c,to;
243      int done=0;
244      int token=0;
245
246      to=*at;
247      do {
248           c = string[to++];
249
250           switch (c) {
251
252           case '\0':
253                --to;
254                done = 1;
255                break;
256
257           case '&':
258           case '|':
259           case '!':
260                if (token) {
261                     --to;
262                }
263                done = 1;
264                break;
265
266           default:
267                if (isalpha(c) || c == '*' || isdigit(c) || c == '_'
268                     || c == '-' || c == '.' || c == '/' || c == ':') {
269                     token = 1;
270                } else if (token) {
271                     --to;
272                     done = 1;
273                } else {
274                     ++*at;
275                }
276           }
277      } while (!done);
278
279      return to - *at;
280 }
281
282 typedef enum { VAL, OP } expect;
283
284 static int
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))
288 {
289      int left=FALSE, right, not=FALSE;
290      operator oper=OR;
291      int at=0, l;
292      expect next=VAL;
293
294      while ((l = logic_member(x,&at))) {
295           int c = x[at];
296
297           if (next == VAL) {
298                if (c == '!')
299                     not = !not;
300                else if (isalpha(c) || c == '*' || isdigit(c) || c == '_'
301                     || c == '-' || c == '.' || c == '/' || c == ':') {
302                     right = not ^ agrees(pamh, me, x+at, l, rule);
303                     if (oper == AND)
304                          left &= right;
305                     else
306                          left |= right;
307                     next = OP;
308                } else {
309                     pam_syslog(pamh, LOG_ERR,
310                                "garbled syntax; expected name (rule #%d)",
311                                rule);
312                     return FALSE;
313                }
314           } else {   /* OP */
315                switch (c) {
316                case '&':
317                     oper = AND;
318                     break;
319                case '|':
320                     oper = OR;
321                     break;
322                default:
323                     pam_syslog(pamh, LOG_ERR,
324                                "garbled syntax; expected & or | (rule #%d)",
325                                rule);
326                     D(("%c at %d",c,at));
327                     return FALSE;
328                }
329                next = VAL;
330           }
331           at += l;
332      }
333
334      return left;
335 }
336
337 static int
338 is_same(pam_handle_t *pamh UNUSED, const void *A, const char *b,
339         int len, int rule UNUSED)
340 {
341      int i;
342      const char *a;
343
344      a = A;
345      for (i=0; len > 0; ++i, --len) {
346           if (b[i] != a[i]) {
347                if (b[i++] == '*') {
348                     return (!--len || !strncmp(b+i,a+strlen(a)-len,len));
349                } else
350                     return FALSE;
351           }
352      }
353
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. */
357      if (a[i] != '\0')
358           return FALSE;
359
360      return ( !len );
361 }
362
363 typedef struct {
364      int day;             /* array of 7 bits, one set for today */
365      int minute;            /* integer, hour*100+minute for now */
366 } TIME;
367
368 static struct day {
369      const char *d;
370      int bit;
371 } const days[11] = {
372      { "su", 01 },
373      { "mo", 02 },
374      { "tu", 04 },
375      { "we", 010 },
376      { "th", 020 },
377      { "fr", 040 },
378      { "sa", 0100 },
379      { "wk", 076 },
380      { "wd", 0101 },
381      { "al", 0177 },
382      { NULL, 0 }
383 };
384
385 static TIME
386 time_now(void)
387 {
388      struct tm *local;
389      time_t the_time;
390      TIME this;
391
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;
396
397      D(("day: 0%o, time: %.4d", this.day, this.minute));
398      return this;
399 }
400
401 /* take the current date and see if the range "date" passes it */
402 static int
403 check_time(pam_handle_t *pamh, const void *AT, const char *times,
404            int len, int rule)
405 {
406      int not,pass;
407      int marked_day, time_start, time_end;
408      const TIME *at;
409      int i,j=0;
410
411      at = AT;
412      D(("chcking: 0%o/%.4d vs. %s", at->day, at->minute, times));
413
414      if (times == NULL) {
415           /* this should not happen */
416           pam_syslog(pamh, LOG_CRIT,
417                      "internal error in file %s at line %d",
418                      __FILE__, __LINE__);
419           return FALSE;
420      }
421
422      if (times[j] == '!') {
423           ++j;
424           not = TRUE;
425      } else {
426           not = FALSE;
427      }
428
429      for (marked_day = 0; len > 0 && isalpha(times[j]); --len) {
430           int this_day=-1;
431
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;
437                     break;
438                }
439           }
440           j += 2;
441           if (this_day == -1) {
442                pam_syslog(pamh, LOG_ERR, "bad day specified (rule #%d)", rule);
443                return FALSE;
444           }
445           marked_day ^= this_day;
446      }
447      if (marked_day == 0) {
448           pam_syslog(pamh, LOG_ERR, "no day specified");
449           return FALSE;
450      }
451      D(("day range = 0%o", marked_day));
452
453      time_start = 0;
454      for (i=0; len > 0 && i < 4 && isdigit(times[i+j]); ++i, --len) {
455           time_start *= 10;
456           time_start += times[i+j]-'0';        /* is this portable? */
457      }
458      j += i;
459
460      if (times[j] == '-') {
461           time_end = 0;
462           for (i=1; len > 0 && i < 5 && isdigit(times[i+j]); ++i, --len) {
463                time_end *= 10;
464                time_end += times[i+j]-'0';    /* is this portable */
465           }
466           j += i;
467      } else
468           time_end = -1;
469
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);
473           return TRUE;
474      }
475      D(("times(%d to %d)", time_start,time_end));
476      D(("marked_day = 0%o", marked_day));
477
478      /* compare with the actual time now */
479
480      pass = FALSE;
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"));
485                pass = TRUE;
486           }
487      } else {                                    /* spans two days */
488           if ((at->day & marked_day) && (at->minute >= time_start)) {
489                D(("caught on first day"));
490                pass = TRUE;
491           } else {
492                marked_day <<= 1;
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"));
497                     pass = TRUE;
498                }
499           }
500      }
501
502      return (not ^ pass);
503 }
504
505 static int
506 check_account(pam_handle_t *pamh, const char *service,
507               const char *tty, const char *user)
508 {
509      int from=0, state=STATE_NL, fd=-1;
510      char *buffer=NULL;
511      int count=0;
512      TIME here_and_now;
513      int retval=PAM_SUCCESS;
514
515      here_and_now = time_now();                     /* find current time */
516      do {
517           int good=TRUE,intime;
518
519           /* here we get the service name field */
520
521           fd = read_field(pamh, fd, &buffer, &from, &state);
522           if (!buffer || !buffer[0]) {
523                /* empty line .. ? */
524                continue;
525           }
526           ++count;
527
528           if (state != STATE_FIELD) {
529                pam_syslog(pamh, LOG_ERR,
530                           "%s: malformed rule #%d", PAM_TIME_CONF, count);
531                continue;
532           }
533
534           good = logic_field(pamh, service, buffer, count, is_same);
535           D(("with service: %s", good ? "passes":"fails" ));
536
537           /* here we get the terminal name field */
538
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);
543                continue;
544           }
545           good &= logic_field(pamh, tty, buffer, count, is_same);
546           D(("with tty: %s", good ? "passes":"fails" ));
547
548           /* here we get the username field */
549
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);
554                continue;
555           }
556           /* If buffer starts with @, we are using netgroups */
557           if (buffer[0] == '@')
558             good &= innetgr (&buffer[1], NULL, user, NULL);
559           else
560             good &= logic_field(pamh, user, buffer, count, is_same);
561           D(("with user: %s", good ? "passes":"fails" ));
562
563           /* here we get the time field */
564
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);
569                continue;
570           }
571
572           intime = logic_field(pamh, &here_and_now, buffer, count, check_time);
573           D(("with time: %s", intime ? "passes":"fails" ));
574
575           if (good && !intime) {
576                /*
577                 * for security parse whole file..  also need to ensure
578                 * that the buffer is free()'d and the file is closed.
579                 */
580                retval = PAM_PERM_DENIED;
581           } else {
582                D(("rule passed"));
583           }
584      } while (state != STATE_EOF);
585
586      return retval;
587 }
588
589 /* --- public account management functions --- */
590
591 PAM_EXTERN int
592 pam_sm_acct_mgmt(pam_handle_t *pamh, int flags UNUSED,
593                  int argc, const char **argv)
594 {
595     const void *service=NULL, *void_tty=NULL;
596     const char *tty;
597     const char *user=NULL;
598     int ctrl;
599     int rv;
600
601     ctrl = _pam_parse(pamh, argc, argv);
602
603     /* set service name */
604
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");
608         return PAM_ABORT;
609     }
610
611     /* set username */
612
613     if (pam_get_user(pamh, &user, NULL) != PAM_SUCCESS || user == NULL
614         || *user == '\0') {
615         pam_syslog(pamh, LOG_ERR, "can not get the username");
616         return PAM_USER_UNKNOWN;
617     }
618
619     /* set tty name */
620
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);
625         if (tty == NULL) {
626             tty = "";
627         }
628         if (pam_set_item(pamh, PAM_TTY, tty) != PAM_SUCCESS) {
629             pam_syslog(pamh, LOG_ERR, "couldn't set tty name");
630             return PAM_ABORT;
631         }
632     }
633     else
634       tty = void_tty;
635
636     if (tty[0] == '/') {   /* full path */
637         const char *t;
638         tty++;
639         if ((t = strchr(tty, '/')) != NULL) {
640             tty = t + 1;
641         }
642     }
643
644     /* good, now we have the service name, the user and the terminal name */
645
646     D(("service=%s", service));
647     D(("user=%s", user));
648     D(("tty=%s", tty));
649
650     rv = check_account(pamh, service, tty, user);
651     if (rv != PAM_SUCCESS) {
652 #ifdef HAVE_LIBAUDIT
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 */
656         }
657 #endif
658         if (ctrl & PAM_DEBUG_ARG) {
659             pam_syslog(pamh, LOG_DEBUG, "user %s rejected", user);
660         }
661     }
662     return rv;
663 }
664
665 /* end of module definition */
666
667 #ifdef PAM_STATIC
668
669 /* static module data */
670
671 struct pam_module _pam_time_modstruct = {
672     "pam_time",
673     NULL,
674     NULL,
675     pam_sm_acct_mgmt,
676     NULL,
677     NULL,
678     NULL
679 };
680 #endif