4 * $Id: pam_group.c,v 1.7 1997/02/15 17:31:48 morgan Exp morgan $
6 * Written by Andrew Morgan <morgan@parc.power.net> 1996/7/6
8 * $Log: pam_group.c,v $
9 * Revision 1.7 1997/02/15 17:31:48 morgan
10 * time parsing more robust
12 * Revision 1.6 1997/01/04 21:57:49 morgan
13 * fixed warning about setgroups not being defined
15 * Revision 1.5 1997/01/04 20:26:49 morgan
16 * can be compiled with and without libpwdb. fixed buffer underwriting
17 * pays attention to PAM_CRED flags(!)
19 * Revision 1.4 1996/12/01 02:54:37 morgan
20 * mostly debugging now uses D(())
22 * Revision 1.3 1996/11/10 21:01:22 morgan
23 * compatability and pam_get_user changes
26 const static char rcsid[] =
27 "$Id: pam_group.c,v 1.7 1997/02/15 17:31:48 morgan Exp morgan $;\n"
28 "Version 0.5 for Linux-PAM\n"
29 "Copyright (c) Andrew G. Morgan 1996 <morgan@parc.power.net>\n";
43 #include <sys/types.h>
48 #include <pwdb/pwdb_public.h>
51 #define PAM_GROUP_CONF CONFILE /* from external define */
52 #define PAM_GROUP_BUFLEN 1000
53 #define FIELD_SEPARATOR ';' /* this is new as of .02 */
55 typedef enum { FALSE, TRUE } boolean;
56 typedef enum { AND, OR } operator;
59 * here, we make definitions for the externally accessible functions
60 * in this file (these definitions are required for static modules
61 * but strongly encouraged generally) they are used to instruct the
62 * modules include file to define their prototypes.
67 #include <security/pam_modules.h>
68 #include <security/_pam_macros.h>
70 /* --- static functions for checking whether the user should be let in --- */
72 static void _log_err(const char *format, ... )
76 va_start(args, format);
77 openlog("pam_group", LOG_CONS|LOG_PID, LOG_AUTH);
78 vsyslog(LOG_CRIT, format, args);
83 static void shift_bytes(char *mem, int from, int by)
91 static int read_field(int fd, char **buf, int *from, int *to)
96 *buf = (char *) malloc(PAM_GROUP_BUFLEN);
98 _log_err("out of memory");
102 fd = open(PAM_GROUP_CONF, O_RDONLY);
105 /* do we have a file open ? return error */
107 if (fd < 0 && *to <= 0) {
108 _log_err( PAM_GROUP_CONF " not opened");
109 memset(*buf, 0, PAM_GROUP_BUFLEN);
114 /* check if there was a newline last time */
116 if ((*to > *from) && (*to > 0)
117 && ((*buf)[*from] == '\0')) { /* previous line ended */
123 /* ready for more data: first shift the buffer's remaining data */
126 shift_bytes(*buf, *from, *to);
130 while (fd >= 0 && *to < PAM_GROUP_BUFLEN) {
133 /* now try to fill the remainder of the buffer */
135 i = read(fd, *to + *buf, PAM_GROUP_BUFLEN - *to);
137 _log_err("error reading " PAM_GROUP_CONF);
140 fd = -1; /* end of file reached */
145 * contract the buffer. Delete any comments, and replace all
146 * multiple spaces with single commas
151 D(("buffer=<%s>",*buf));
154 if ((*buf)[i] == ',') {
157 for (j=++i; j<*to && (*buf)[j] == ','; ++j);
159 shift_bytes(i + (*buf), j-i, (*to) - j);
166 for (j=i; j < *to && (c = (*buf)[j]) != '\n'; ++j);
168 (*buf)[*to = ++i] = '\0';
169 } else if (c == '\n') {
170 shift_bytes(i + (*buf), j-i, (*to) - j);
174 _log_err("internal error in " __FILE__
175 " at line %d", __LINE__ );
180 if ((*buf)[i+1] == '\n') {
181 shift_bytes(i + *buf, 2, *to - (i+2));
188 if ((*buf)[i] != '!')
190 /* delete any trailing spaces */
191 for (j=++i; j < *to && ( (c = (*buf)[j]) == ' '
192 || c == '\t' ); ++j);
193 shift_bytes(i + *buf, j-i, (*to)-j );
204 /* now return the next field (set the from/to markers) */
208 for (i=0; i<*to; ++i) {
211 case '\n': /* end of the line/file */
215 case FIELD_SEPARATOR: /* end of the field */
222 (*buf)[*from] = '\0';
226 D(("[end of text]"));
232 /* read a member from a field */
234 static int logic_member(const char *string, int *at)
262 if (isalpha(c) || c == '*' || isdigit(c) || c == '_'
263 || c == '-' || c == '.') {
277 typedef enum { VAL, OP } expect;
279 static boolean logic_field(const void *me, const char *x, int rule,
280 boolean (*agrees)(const void *, const char *
283 boolean left=FALSE, right, not=FALSE;
288 while ((l = logic_member(x,&at))) {
294 else if (isalpha(c) || c == '*') {
295 right = not ^ agrees(me, x+at, l, rule);
302 _log_err("garbled syntax; expected name (rule #%d)", rule);
314 _log_err("garbled syntax; expected & or | (rule #%d)"
316 D(("%c at %d",c,at));
327 static boolean is_same(const void *A, const char *b, int len, int rule)
333 for (i=0; len > 0; ++i, --len) {
336 return (!--len || !strncmp(b+i,a+strlen(a)-len,len));
345 int day; /* array of 7 bits, one set for today */
346 int minute; /* integer, hour*100+minute for now */
352 } static const days[11] = {
366 static TIME time_now(void)
372 the_time = time((time_t *)0); /* get the current time */
373 local = localtime(&the_time);
374 this.day = days[local->tm_wday].bit;
375 this.minute = local->tm_hour*100 + local->tm_min;
377 D(("day: 0%o, time: %.4d", this.day, this.minute));
381 /* take the current date and see if the range "date" passes it */
382 static boolean check_time(const void *AT, const char *times, int len, int rule)
385 int marked_day, time_start, time_end;
390 D(("checking: 0%o/%.4d vs. %s", at->day, at->minute, times));
393 /* this should not happen */
394 _log_err("internal error: " __FILE__ " line %d", __LINE__);
398 if (times[j] == '!') {
405 for (marked_day = 0; len > 0 && isalpha(times[j]); --len) {
408 D(("%c%c ?", times[j], times[j+1]));
409 for (i=0; days[i].d != NULL; ++i) {
410 if (tolower(times[j]) == days[i].d[0]
411 && tolower(times[j+1]) == days[i].d[1] ) {
412 this_day = days[i].bit;
417 if (this_day == -1) {
418 _log_err("bad day specified (rule #%d)", rule);
421 marked_day ^= this_day;
423 if (marked_day == 0) {
424 _log_err("no day specified");
427 D(("day range = 0%o", marked_day));
430 for (i=0; len > 0 && i < 4 && isdigit(times[i+j]); ++i, --len) {
432 time_start += times[i+j]-'0'; /* is this portable? */
436 if (times[j] == '-') {
438 for (i=1; len > 0 && i < 5 && isdigit(times[i+j]); ++i, --len) {
440 time_end += times[i+j]-'0'; /* is this portable? */
446 D(("i=%d, time_end=%d, times[j]='%c'", i, time_end, times[j]));
447 if (i != 5 || time_end == -1) {
448 _log_err("no/bad times specified (rule #%d)", rule);
451 D(("times(%d to %d)", time_start,time_end));
452 D(("marked_day = 0%o", marked_day));
454 /* compare with the actual time now */
457 if (time_start < time_end) { /* start < end ? --> same day */
458 if ((at->day & marked_day) && (at->minute >= time_start)
459 && (at->minute < time_end)) {
460 D(("time is listed"));
463 } else { /* spans two days */
464 if ((at->day & marked_day) && (at->minute >= time_start)) {
465 D(("caught on first day"));
469 marked_day |= (marked_day & 0200) ? 1:0;
470 D(("next day = 0%o", marked_day));
471 if ((at->day & marked_day) && (at->minute <= time_end)) {
472 D(("caught on second day"));
481 static int find_member(const char *string, int *at)
509 if (isalpha(c) || isdigit(c) || c == '_' || c == '*'
525 #define blk_size(len) (((len-1 + GROUP_BLK)/GROUP_BLK)*GROUP_BLK)
527 static int mkgrplist(char *buf, gid_t **list, int len)
532 blks = blk_size(len);
533 D(("cf. blks=%d and len=%d", blks,len));
535 while ((l = find_member(buf,&at))) {
541 D(("allocating new block"));
542 tmp = (gid_t *) realloc((*list)
543 , sizeof(gid_t) * (blks += GROUP_BLK));
547 _log_err("out of memory for group list");
554 /* '\0' terminate the entry */
556 edge = (buf[at+l]) ? 1:0;
558 D(("found group: %s",buf+at));
560 /* this is where we convert a group name to a gid_t */
564 const struct pwdb *pw=NULL;
566 retval = pwdb_locate("group", PWDB_DEFAULT, buf+at
567 , PWDB_ID_UNKNOWN, &pw);
568 if (retval != PWDB_SUCCESS) {
569 _log_err("bad group: %s; %s", buf+at, pwdb_strerror(retval));
571 const struct pwdb_entry *pwe=NULL;
573 D(("group %s exists", buf+at));
574 retval = pwdb_get_entry(pw, "gid", &pwe);
575 if (retval == PWDB_SUCCESS) {
576 D(("gid = %d [%p]",* (const gid_t *) pwe->value,list));
577 (*list)[len++] = * (const gid_t *) pwe->value;
578 pwdb_entry_delete(&pwe); /* tidy up */
580 _log_err("%s group entry is bad; %s"
581 , pwdb_strerror(retval));
583 pw = NULL; /* break link - cached for later use */
588 const struct group *grp;
590 grp = getgrnam(buf+at);
592 _log_err("bad group: %s", buf+at);
594 D(("group %s exists", buf+at));
595 (*list)[len++] = grp->gr_gid;
600 /* next entry along */
604 D(("returning with [%p/len=%d]->%p",list,len,*list));
609 static int check_account(const char *service, const char *tty
612 int from=0,to=0,fd=-1;
616 int retval=PAM_SUCCESS;
621 * first we get the current list of groups - the application
622 * will have previously done an initgroups(), or equivalent.
625 D(("counting supplementary groups"));
626 no_grps = getgroups(0, NULL); /* find the current number of groups */
628 grps = calloc( blk_size(no_grps) , sizeof(gid_t) );
629 D(("copying current list into grps [%d big]",blk_size(no_grps)));
630 (void) getgroups(no_grps, grps);
634 for (z=0; z<no_grps; ++z) {
635 D(("gid[%d]=%d", z, grps[z]));
640 D(("no supplementary groups known"));
645 here_and_now = time_now(); /* find current time */
647 /* parse the rules in the configuration file */
651 /* here we get the service name field */
653 fd = read_field(fd,&buffer,&from,&to);
654 if (!buffer || !buffer[0]) {
655 /* empty line .. ? */
659 D(("working on rule #%d",count));
661 good = logic_field(service, buffer, count, is_same);
662 D(("with service: %s", good ? "passes":"fails" ));
664 /* here we get the terminal name field */
666 fd = read_field(fd,&buffer,&from,&to);
667 if (!buffer || !buffer[0]) {
668 _log_err(PAM_GROUP_CONF "; no tty entry #%d", count);
671 good &= logic_field(tty, buffer, count, is_same);
672 D(("with tty: %s", good ? "passes":"fails" ));
674 /* here we get the username field */
676 fd = read_field(fd,&buffer,&from,&to);
677 if (!buffer || !buffer[0]) {
678 _log_err(PAM_GROUP_CONF "; no user entry #%d", count);
681 good &= logic_field(user, buffer, count, is_same);
682 D(("with user: %s", good ? "passes":"fails" ));
684 /* here we get the time field */
686 fd = read_field(fd,&buffer,&from,&to);
687 if (!buffer || !buffer[0]) {
688 _log_err(PAM_GROUP_CONF "; no time entry #%d", count);
692 good &= logic_field(&here_and_now, buffer, count, check_time);
693 D(("with time: %s", good ? "passes":"fails" ));
695 fd = read_field(fd,&buffer,&from,&to);
696 if (!buffer || !buffer[0]) {
697 _log_err(PAM_GROUP_CONF "; no listed groups for rule #%d"
703 * so we have a list of groups, we need to turn it into
704 * something to send to setgroups(2)
708 D(("adding %s to gid list", buffer));
709 good = mkgrplist(buffer, &grps, no_grps);
717 /* check the line is terminated correctly */
719 fd = read_field(fd,&buffer,&from,&to);
720 if (buffer && buffer[0]) {
721 _log_err(PAM_GROUP_CONF "; poorly terminated rule #%d", count);
725 D(("rule #%d passed, added %d groups", count, good));
726 } else if (good < 0) {
727 retval = PAM_BUF_ERR;
729 D(("rule #%d failed", count));
734 /* now set the groups for the user */
738 D(("trying to set %d groups", no_grps));
740 for (err=0; err<no_grps; ++err) {
741 D(("gid[%d]=%d", err, grps[err]));
744 if ((err = setgroups(no_grps, grps))) {
745 D(("but couldn't set groups %d", err));
746 _log_err("unable to set the group membership for user (err=%d)"
748 retval = PAM_CRED_ERR;
752 if (grps) { /* tidy up */
753 memset(grps, 0, sizeof(gid_t) * blk_size(no_grps));
761 /* --- public authentication management functions --- */
763 PAM_EXTERN int pam_sm_authenticate(pam_handle_t *pamh, int flags
764 , int argc, const char **argv)
769 PAM_EXTERN int pam_sm_setcred(pam_handle_t *pamh, int flags
770 , int argc, const char **argv)
772 const char *service=NULL, *tty=NULL;
773 const char *user=NULL;
777 /* only interested in establishing credentials */
780 if (!(setting & PAM_ESTABLISH_CRED)) {
781 D(("ignoring call - not for establishing credentials"));
782 return PAM_SUCCESS; /* don't fail because of this */
785 /* set service name */
787 if (pam_get_item(pamh, PAM_SERVICE, (const void **)&service)
788 != PAM_SUCCESS || service == NULL) {
789 _log_err("cannot find the current service name");
795 if (pam_get_user(pamh, &user, NULL) != PAM_SUCCESS || user == NULL
797 _log_err("cannot determine the user's name");
798 return PAM_USER_UNKNOWN;
803 if (pam_get_item(pamh, PAM_TTY, (const void **)&tty) != PAM_SUCCESS
805 D(("PAM_TTY not set, probing stdin"));
806 tty = ttyname(STDIN_FILENO);
808 _log_err("couldn't get the tty name");
811 if (pam_set_item(pamh, PAM_TTY, tty) != PAM_SUCCESS) {
812 _log_err("couldn't set tty name");
817 if (strncmp("/dev/",tty,5) == 0) { /* strip leading /dev/ */
821 /* good, now we have the service name, the user and the terminal name */
823 D(("service=%s", service));
824 D(("user=%s", user));
829 /* We initialize the pwdb library and check the account */
830 retval = pwdb_start(); /* initialize */
831 if (retval == PWDB_SUCCESS) {
832 retval = check_account(service,tty,user); /* get groups */
833 (void) pwdb_end(); /* tidy up */
835 D(("failed to initialize pwdb; %s", pwdb_strerror(retval)));
836 _log_err("unable to initialize libpwdb");
840 #else /* WANT_PWDB */
841 retval = check_account(service,tty,user); /* get groups */
842 #endif /* WANT_PWDB */
847 /* end of module definition */
851 /* static module data */
853 struct pam_module _pam_group_modstruct = {