]> granicus.if.org Git - linux-pam/blob - modules/pam_group/pam_group.c
Relevant BUGIDs: 111648
[linux-pam] / modules / pam_group / pam_group.c
1 /* pam_group module */
2
3 /*
4  * $Id$
5  *
6  * Written by Andrew Morgan <morgan@linux.kernel.org> 1996/7/6
7  */
8
9 const static char rcsid[] =
10 "$Id$;\n"
11 "Version 0.5 for Linux-PAM\n"
12 "Copyright (c) Andrew G. Morgan 1996 <morgan@linux.kernel.org>\n";
13
14 #define _BSD_SOURCE
15
16 #include <sys/file.h>
17 #include <stdio.h>
18 #include <stdlib.h>
19 #include <ctype.h>
20 #include <unistd.h>
21 #include <stdarg.h>
22 #include <time.h>
23 #include <syslog.h>
24 #include <string.h>
25
26 #include <grp.h>
27 #include <sys/types.h>
28 #include <sys/stat.h>
29 #include <fcntl.h>
30
31 #define PAM_GROUP_CONF          CONFILE /* from external define */
32 #define PAM_GROUP_BUFLEN        1000
33 #define FIELD_SEPARATOR         ';'   /* this is new as of .02 */
34
35 typedef enum { FALSE, TRUE } boolean;
36 typedef enum { AND, OR } operator;
37
38 /*
39  * here, we make definitions for the externally accessible functions
40  * in this file (these definitions are required for static modules
41  * but strongly encouraged generally) they are used to instruct the
42  * modules include file to define their prototypes.
43  */
44
45 #define PAM_SM_AUTH
46
47 #include <security/pam_modules.h>
48 #include <security/_pam_macros.h>
49
50 /* --- static functions for checking whether the user should be let in --- */
51
52 static void _log_err(const char *format, ... )
53 {
54     va_list args;
55
56     va_start(args, format);
57     openlog("pam_group", LOG_CONS|LOG_PID, LOG_AUTH);
58     vsyslog(LOG_CRIT, format, args);
59     va_end(args);
60     closelog();
61 }
62
63 static void shift_bytes(char *mem, int from, int by)
64 {
65     while (by-- > 0) {
66         *mem = mem[from];
67         ++mem;
68     }
69 }
70
71 static int read_field(int fd, char **buf, int *from, int *to)
72 {
73     /* is buf set ? */
74
75     if (! *buf) {
76         *buf = (char *) malloc(PAM_GROUP_BUFLEN);
77         if (! *buf) {
78             _log_err("out of memory");
79             return -1;
80         }
81         *from = *to = 0;
82         fd = open(PAM_GROUP_CONF, O_RDONLY);
83     }
84
85     /* do we have a file open ? return error */
86
87     if (fd < 0 && *to <= 0) {
88         _log_err( PAM_GROUP_CONF " not opened");
89         memset(*buf, 0, PAM_GROUP_BUFLEN);
90         _pam_drop(*buf);
91         return -1;
92     }
93
94     /* check if there was a newline last time */
95
96     if ((*to > *from) && (*to > 0)
97         && ((*buf)[*from] == '\0')) {   /* previous line ended */
98         (*from)++;
99         (*buf)[0] = '\0';
100         return fd;
101     }
102
103     /* ready for more data: first shift the buffer's remaining data */
104
105     *to -= *from;
106     shift_bytes(*buf, *from, *to);
107     *from = 0;
108     (*buf)[*to] = '\0';
109
110     while (fd >= 0 && *to < PAM_GROUP_BUFLEN) {
111         int i;
112
113         /* now try to fill the remainder of the buffer */
114
115         i = read(fd, *to + *buf, PAM_GROUP_BUFLEN - *to);
116         if (i < 0) {
117             _log_err("error reading " PAM_GROUP_CONF);
118             return -1;
119         } else if (!i) {
120             close(fd);
121             fd = -1;          /* end of file reached */
122         } else
123             *to += i;
124     
125         /*
126          * contract the buffer. Delete any comments, and replace all
127          * multiple spaces with single commas
128          */
129
130         i = 0;
131 #ifdef DEBUG_DUMP
132         D(("buffer=<%s>",*buf));
133 #endif
134         while (i < *to) {
135             if ((*buf)[i] == ',') {
136                 int j;
137
138                 for (j=++i; j<*to && (*buf)[j] == ','; ++j);
139                 if (j!=i) {
140                     shift_bytes(i + (*buf), j-i, (*to) - j);
141                     *to -= j-i;
142                 }
143             }
144             switch ((*buf)[i]) {
145                 int j,c;
146             case '#':
147                 for (j=i; j < *to && (c = (*buf)[j]) != '\n'; ++j);
148                 if (j >= *to) {
149                     (*buf)[*to = ++i] = '\0';
150                 } else if (c == '\n') {
151                     shift_bytes(i + (*buf), j-i, (*to) - j);
152                     *to -= j-i;
153                     ++i;
154                 } else {
155                     _log_err("internal error in " __FILE__
156                              " at line %d", __LINE__ );
157                     return -1;
158                 }
159                 break;
160             case '\\':
161                 if ((*buf)[i+1] == '\n') {
162                     shift_bytes(i + *buf, 2, *to - (i+2));
163                     *to -= 2;
164                 }
165                 break;
166             case '!':
167             case ' ':
168             case '\t':
169                 if ((*buf)[i] != '!')
170                     (*buf)[i] = ',';
171                 /* delete any trailing spaces */
172                 for (j=++i; j < *to && ( (c = (*buf)[j]) == ' '
173                                          || c == '\t' ); ++j);
174                 shift_bytes(i + *buf, j-i, (*to)-j );
175                 *to -= j-i;
176                 break;
177             default:
178                 ++i;
179             }
180         }
181     }
182
183     (*buf)[*to] = '\0';
184
185     /* now return the next field (set the from/to markers) */
186     {
187         int i;
188
189         for (i=0; i<*to; ++i) {
190             switch ((*buf)[i]) {
191             case '#':
192             case '\n':               /* end of the line/file */
193                 (*buf)[i] = '\0';
194                 *from = i;
195                 return fd;
196             case FIELD_SEPARATOR:    /* end of the field */
197                 (*buf)[i] = '\0';
198             *from = ++i;
199             return fd;
200             }
201         }
202         *from = i;
203         (*buf)[*from] = '\0';
204     }
205
206     if (*to <= 0) {
207         D(("[end of text]"));
208         *buf = NULL;
209     }
210     return fd;
211 }
212
213 /* read a member from a field */
214
215 static int logic_member(const char *string, int *at)
216 {
217      int len,c,to;
218      int done=0;
219      int token=0;
220
221      len=0;
222      to=*at;
223      do {
224           c = string[to++];
225
226           switch (c) {
227
228           case '\0':
229                --to;
230                done = 1;
231                break;
232
233           case '&':
234           case '|':
235           case '!':
236                if (token) {
237                     --to;
238                }
239                done = 1;
240                break;
241
242           default:
243                if (isalpha(c) || c == '*' || isdigit(c) || c == '_'
244                     || c == '-' || c == '.') {
245                     token = 1;
246                } else if (token) {
247                     --to;
248                     done = 1;
249                } else {
250                     ++*at;
251                }
252           }
253      } while (!done);
254
255      return to - *at;
256 }
257
258 typedef enum { VAL, OP } expect;
259
260 static boolean logic_field(const void *me, const char *x, int rule,
261                            boolean (*agrees)(const void *, const char *
262                                              , int, int))
263 {
264      boolean left=FALSE, right, not=FALSE;
265      operator oper=OR;
266      int at=0, l;
267      expect next=VAL;
268
269      while ((l = logic_member(x,&at))) {
270           int c = x[at];
271
272           if (next == VAL) {
273                if (c == '!')
274                     not = !not;
275                else if (isalpha(c) || c == '*') {
276                     right = not ^ agrees(me, x+at, l, rule);
277                     if (oper == AND)
278                          left &= right;
279                     else
280                          left |= right;
281                     next = OP;
282                } else {
283                     _log_err("garbled syntax; expected name (rule #%d)", rule);
284                     return FALSE;
285                }
286           } else {   /* OP */
287                switch (c) {
288                case '&':
289                     oper = AND;
290                     break;
291                case '|':
292                     oper = OR;
293                     break;
294                default:
295                     _log_err("garbled syntax; expected & or | (rule #%d)"
296                              , rule);
297                     D(("%c at %d",c,at));
298                     return FALSE;
299                }
300                next = VAL;
301           }
302           at += l;
303      }
304
305      return left;
306 }
307
308 static boolean is_same(const void *A, const char *b, int len, int rule)
309 {
310      int i;
311      const char *a;
312
313      a = A;
314      for (i=0; len > 0; ++i, --len) {
315           if (b[i] != a[i]) {
316                if (b[i++] == '*') {
317                     return (!--len || !strncmp(b+i,a+strlen(a)-len,len));
318                } else
319                     return FALSE;
320           }
321      }
322      return ( !len );
323 }
324
325 typedef struct {
326      int day;             /* array of 7 bits, one set for today */
327      int minute;            /* integer, hour*100+minute for now */
328 } TIME;
329
330 struct day {
331      const char *d;
332      int bit;
333 } static const days[11] = {
334      { "su", 01 },
335      { "mo", 02 },
336      { "tu", 04 },
337      { "we", 010 },
338      { "th", 020 },
339      { "fr", 040 },
340      { "sa", 0100 },
341      { "wk", 076 },
342      { "wd", 0101 },
343      { "al", 0177 },
344      { NULL, 0 }
345 };
346
347 static TIME time_now(void)
348 {
349      struct tm *local;
350      time_t the_time;
351      TIME this;
352
353      the_time = time((time_t *)0);                /* get the current time */
354      local = localtime(&the_time);
355      this.day = days[local->tm_wday].bit;
356      this.minute = local->tm_hour*100 + local->tm_min;
357
358      D(("day: 0%o, time: %.4d", this.day, this.minute));
359      return this;
360 }
361
362 /* take the current date and see if the range "date" passes it */
363 static boolean check_time(const void *AT, const char *times, int len, int rule)
364 {
365      boolean not,pass;
366      int marked_day, time_start, time_end;
367      const TIME *at;
368      int i,j=0;
369
370      at = AT;
371      D(("checking: 0%o/%.4d vs. %s", at->day, at->minute, times));
372
373      if (times == NULL) {
374           /* this should not happen */
375           _log_err("internal error: " __FILE__ " line %d", __LINE__);
376           return FALSE;
377      }
378
379      if (times[j] == '!') {
380           ++j;
381           not = TRUE;
382      } else {
383           not = FALSE;
384      }
385
386      for (marked_day = 0; len > 0 && isalpha(times[j]); --len) {
387           int this_day=-1;
388
389           D(("%c%c ?", times[j], times[j+1]));
390           for (i=0; days[i].d != NULL; ++i) {
391                if (tolower(times[j]) == days[i].d[0]
392                    && tolower(times[j+1]) == days[i].d[1] ) {
393                     this_day = days[i].bit;
394                     break;
395                }
396           }
397           j += 2;
398           if (this_day == -1) {
399                _log_err("bad day specified (rule #%d)", rule);
400                return FALSE;
401           }
402           marked_day ^= this_day;
403      }
404      if (marked_day == 0) {
405           _log_err("no day specified");
406           return FALSE;
407      }
408      D(("day range = 0%o", marked_day));
409
410      time_start = 0;
411      for (i=0; len > 0 && i < 4 && isdigit(times[i+j]); ++i, --len) {
412           time_start *= 10;
413           time_start += times[i+j]-'0';       /* is this portable? */
414      }
415      j += i;
416
417      if (times[j] == '-') {
418           time_end = 0;
419           for (i=1; len > 0 && i < 5 && isdigit(times[i+j]); ++i, --len) {
420                time_end *= 10;
421                time_end += times[i+j]-'0';    /* is this portable? */
422           }
423           j += i;
424      } else
425           time_end = -1;
426
427      D(("i=%d, time_end=%d, times[j]='%c'", i, time_end, times[j]));
428      if (i != 5 || time_end == -1) {
429           _log_err("no/bad times specified (rule #%d)", rule);
430           return TRUE;
431      }
432      D(("times(%d to %d)", time_start,time_end));
433      D(("marked_day = 0%o", marked_day));
434
435      /* compare with the actual time now */
436
437      pass = FALSE;
438      if (time_start < time_end) {    /* start < end ? --> same day */
439           if ((at->day & marked_day) && (at->minute >= time_start)
440               && (at->minute < time_end)) {
441                D(("time is listed"));
442                pass = TRUE;
443           }
444      } else {                                    /* spans two days */
445           if ((at->day & marked_day) && (at->minute >= time_start)) {
446                D(("caught on first day"));
447                pass = TRUE;
448           } else {
449                marked_day <<= 1;
450                marked_day |= (marked_day & 0200) ? 1:0;
451                D(("next day = 0%o", marked_day));
452                if ((at->day & marked_day) && (at->minute <= time_end)) {
453                     D(("caught on second day"));
454                     pass = TRUE;
455                }
456           }
457      }
458
459      return (not ^ pass);
460 }
461
462 static int find_member(const char *string, int *at)
463 {
464      int len,c,to;
465      int done=0;
466      int token=0;
467
468      len=0;
469      to=*at;
470      do {
471           c = string[to++];
472
473           switch (c) {
474
475           case '\0':
476                --to;
477                done = 1;
478                break;
479
480           case '&':
481           case '|':
482           case '!':
483                if (token) {
484                     --to;
485                }
486                done = 1;
487                break;
488
489           default:
490                if (isalpha(c) || isdigit(c) || c == '_' || c == '*'
491                     || c == '-') {
492                     token = 1;
493                } else if (token) {
494                     --to;
495                     done = 1;
496                } else {
497                     ++*at;
498                }
499           }
500      } while (!done);
501
502      return to - *at;
503 }
504
505 #define GROUP_BLK 10
506 #define blk_size(len) (((len-1 + GROUP_BLK)/GROUP_BLK)*GROUP_BLK)
507
508 static int mkgrplist(char *buf, gid_t **list, int len)
509 {
510      int l,at=0;
511      int blks;
512
513      blks = blk_size(len);
514      D(("cf. blks=%d and len=%d", blks,len));
515
516      while ((l = find_member(buf,&at))) {
517           int edge;
518
519           if (len >= blks) {
520                gid_t *tmp;
521
522                D(("allocating new block"));
523                tmp = (gid_t *) realloc((*list)
524                                        , sizeof(gid_t) * (blks += GROUP_BLK));
525                if (tmp != NULL) {
526                     (*list) = tmp;
527                } else {
528                     _log_err("out of memory for group list");
529                     free(*list);
530                     (*list) = NULL;
531                     return -1;
532                }
533           }
534
535           /* '\0' terminate the entry */
536
537           edge = (buf[at+l]) ? 1:0;
538           buf[at+l] = '\0';
539           D(("found group: %s",buf+at));
540
541           /* this is where we convert a group name to a gid_t */
542 #ifdef WANT_PWDB
543           {
544               int retval;
545               const struct pwdb *pw=NULL;
546
547               retval = pwdb_locate("group", PWDB_DEFAULT, buf+at
548                                    , PWDB_ID_UNKNOWN, &pw);
549               if (retval != PWDB_SUCCESS) {
550                   _log_err("bad group: %s; %s", buf+at, pwdb_strerror(retval));
551               } else {
552                   const struct pwdb_entry *pwe=NULL;
553
554                   D(("group %s exists", buf+at));
555                   retval = pwdb_get_entry(pw, "gid", &pwe);
556                   if (retval == PWDB_SUCCESS) {
557                       D(("gid = %d [%p]",* (const gid_t *) pwe->value,list));
558                       (*list)[len++] = * (const gid_t *) pwe->value;
559                       pwdb_entry_delete(&pwe);                  /* tidy up */
560                   } else {
561                       _log_err("%s group entry is bad; %s"
562                                , pwdb_strerror(retval));
563                   }
564                   pw = NULL;          /* break link - cached for later use */
565               }
566           }
567 #else
568           {
569               const struct group *grp;
570
571               grp = getgrnam(buf+at);
572               if (grp == NULL) {
573                   _log_err("bad group: %s", buf+at);
574               } else {
575                   D(("group %s exists", buf+at));
576                   (*list)[len++] = grp->gr_gid;
577               }
578           }
579 #endif
580
581           /* next entry along */
582
583           at += l + edge;
584      }
585      D(("returning with [%p/len=%d]->%p",list,len,*list));
586      return len;
587 }
588
589
590 static int check_account(const char *service, const char *tty
591      , const char *user)
592 {
593     int from=0,to=0,fd=-1;
594     char *buffer=NULL;
595     int count=0;
596     TIME here_and_now;
597     int retval=PAM_SUCCESS;
598     gid_t *grps;
599     int no_grps;
600
601     /*
602      * first we get the current list of groups - the application
603      * will have previously done an initgroups(), or equivalent.
604      */
605
606     D(("counting supplementary groups"));
607     no_grps = getgroups(0, NULL);      /* find the current number of groups */
608     if (no_grps > 0) {
609         grps = calloc( blk_size(no_grps) , sizeof(gid_t) );
610         D(("copying current list into grps [%d big]",blk_size(no_grps)));
611         (void) getgroups(no_grps, grps);
612 #ifdef DEBUG
613         {
614             int z;
615             for (z=0; z<no_grps; ++z) {
616                 D(("gid[%d]=%d", z, grps[z]));
617             }
618         }
619 #endif
620     } else {
621         D(("no supplementary groups known"));
622         no_grps = 0;
623         grps = NULL;
624     }
625
626     here_and_now = time_now();                         /* find current time */
627
628     /* parse the rules in the configuration file */
629     do {
630         int good=TRUE;
631
632         /* here we get the service name field */
633
634         fd = read_field(fd,&buffer,&from,&to);
635         if (!buffer || !buffer[0]) {
636             /* empty line .. ? */
637             continue;
638         }
639         ++count;
640         D(("working on rule #%d",count));
641
642         good = logic_field(service, buffer, count, is_same);
643         D(("with service: %s", good ? "passes":"fails" ));
644
645         /* here we get the terminal name field */
646
647         fd = read_field(fd,&buffer,&from,&to);
648         if (!buffer || !buffer[0]) {
649             _log_err(PAM_GROUP_CONF "; no tty entry #%d", count);
650             continue;
651         }
652         good &= logic_field(tty, buffer, count, is_same);
653         D(("with tty: %s", good ? "passes":"fails" ));
654
655         /* here we get the username field */
656
657         fd = read_field(fd,&buffer,&from,&to);
658         if (!buffer || !buffer[0]) {
659             _log_err(PAM_GROUP_CONF "; no user entry #%d", count);
660             continue;
661         }
662         good &= logic_field(user, buffer, count, is_same);
663         D(("with user: %s", good ? "passes":"fails" ));
664
665         /* here we get the time field */
666
667         fd = read_field(fd,&buffer,&from,&to);
668         if (!buffer || !buffer[0]) {
669             _log_err(PAM_GROUP_CONF "; no time entry #%d", count);
670             continue;
671         }
672
673         good &= logic_field(&here_and_now, buffer, count, check_time);
674         D(("with time: %s", good ? "passes":"fails" ));
675
676         fd = read_field(fd,&buffer,&from,&to);
677         if (!buffer || !buffer[0]) {
678             _log_err(PAM_GROUP_CONF "; no listed groups for rule #%d"
679                      , count);
680             continue;
681         }
682
683         /*
684          * so we have a list of groups, we need to turn it into
685          * something to send to setgroups(2)
686          */
687
688         if (good) {
689             D(("adding %s to gid list", buffer));
690             good = mkgrplist(buffer, &grps, no_grps);
691             if (good < 0) {
692                 no_grps = 0;
693             } else {
694                 no_grps = good;
695             }
696         }
697
698         /* check the line is terminated correctly */
699
700         fd = read_field(fd,&buffer,&from,&to);
701         if (buffer && buffer[0]) {
702             _log_err(PAM_GROUP_CONF "; poorly terminated rule #%d", count);
703         }
704
705         if (good > 0) {
706             D(("rule #%d passed, added %d groups", count, good));
707         } else if (good < 0) {
708             retval = PAM_BUF_ERR;
709         } else {
710             D(("rule #%d failed", count));
711         }
712
713     } while (buffer);
714
715     /* now set the groups for the user */
716
717     if (no_grps > 0) {
718         int err;
719         D(("trying to set %d groups", no_grps));
720 #ifdef DEBUG
721         for (err=0; err<no_grps; ++err) {
722             D(("gid[%d]=%d", err, grps[err]));
723         }
724 #endif
725         if ((err = setgroups(no_grps, grps))) {
726             D(("but couldn't set groups %d", err));
727             _log_err("unable to set the group membership for user (err=%d)"
728                      , err);
729             retval = PAM_CRED_ERR;
730         }
731     }
732
733     if (grps) {                                          /* tidy up */
734         memset(grps, 0, sizeof(gid_t) * blk_size(no_grps));
735         _pam_drop(grps);
736         no_grps = 0;
737     }
738
739     return retval;
740 }
741
742 /* --- public authentication management functions --- */
743
744 PAM_EXTERN int pam_sm_authenticate(pam_handle_t *pamh, int flags
745                                    , int argc, const char **argv)
746 {
747     return PAM_IGNORE;
748 }
749
750 PAM_EXTERN int pam_sm_setcred(pam_handle_t *pamh, int flags
751                               , int argc, const char **argv)
752 {
753     const char *service=NULL, *tty=NULL;
754     const char *user=NULL;
755     int retval;
756     unsigned setting;
757
758     /* only interested in establishing credentials */
759
760     setting = flags;
761     if (!(setting & PAM_ESTABLISH_CRED)) {
762         D(("ignoring call - not for establishing credentials"));
763         return PAM_SUCCESS;            /* don't fail because of this */
764     }
765
766     /* set service name */
767
768     if (pam_get_item(pamh, PAM_SERVICE, (const void **)&service)
769         != PAM_SUCCESS || service == NULL) {
770         _log_err("cannot find the current service name");
771         return PAM_ABORT;
772     }
773
774     /* set username */
775
776     if (pam_get_user(pamh, &user, NULL) != PAM_SUCCESS || user == NULL
777         || *user == '\0') {
778         _log_err("cannot determine the user's name");
779         return PAM_USER_UNKNOWN;
780     }
781
782     /* set tty name */
783
784     if (pam_get_item(pamh, PAM_TTY, (const void **)&tty) != PAM_SUCCESS
785         || tty == NULL) {
786         D(("PAM_TTY not set, probing stdin"));
787         tty = ttyname(STDIN_FILENO);
788         if (tty == NULL) {
789             _log_err("couldn't get the tty name");
790             return PAM_ABORT;
791         }
792         if (pam_set_item(pamh, PAM_TTY, tty) != PAM_SUCCESS) {
793             _log_err("couldn't set tty name");
794             return PAM_ABORT;
795         }
796     }
797
798     if (strncmp("/dev/",tty,5) == 0) {          /* strip leading /dev/ */
799         tty += 5;
800     }
801
802     /* good, now we have the service name, the user and the terminal name */
803
804     D(("service=%s", service));
805     D(("user=%s", user));
806     D(("tty=%s", tty));
807
808 #ifdef WANT_PWDB
809
810     /* We initialize the pwdb library and check the account */
811     retval = pwdb_start();                             /* initialize */
812     if (retval == PWDB_SUCCESS) {
813         retval = check_account(service,tty,user);      /* get groups */
814         (void) pwdb_end();                                /* tidy up */
815     } else {
816         D(("failed to initialize pwdb; %s", pwdb_strerror(retval)));
817         _log_err("unable to initialize libpwdb");
818         retval = PAM_ABORT;
819     }
820
821 #else /* WANT_PWDB */
822     retval = check_account(service,tty,user);          /* get groups */
823 #endif /* WANT_PWDB */
824
825     return retval;
826 }
827
828 /* end of module definition */
829
830 #ifdef PAM_STATIC
831
832 /* static module data */
833
834 struct pam_module _pam_group_modstruct = {
835     "pam_group",
836     pam_sm_authenticate,
837     pam_sm_setcred,
838     NULL,
839     NULL,
840     NULL,
841     NULL
842 };
843 #endif