6 * Written by Andrew Morgan <morgan@linux.kernel.org> 1996/7/6
9 const static char rcsid[] =
11 "Version 0.5 for Linux-PAM\n"
12 "Copyright (c) Andrew G. Morgan 1996 <morgan@linux.kernel.org>\n";
27 #include <sys/types.h>
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 */
35 typedef enum { FALSE, TRUE } boolean;
36 typedef enum { AND, OR } operator;
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.
47 #include <security/pam_modules.h>
48 #include <security/_pam_macros.h>
50 /* --- static functions for checking whether the user should be let in --- */
52 static void _log_err(const char *format, ... )
56 va_start(args, format);
57 openlog("pam_group", LOG_CONS|LOG_PID, LOG_AUTH);
58 vsyslog(LOG_CRIT, format, args);
63 static void shift_bytes(char *mem, int from, int by)
71 static int read_field(int fd, char **buf, int *from, int *to)
76 *buf = (char *) malloc(PAM_GROUP_BUFLEN);
78 _log_err("out of memory");
82 fd = open(PAM_GROUP_CONF, O_RDONLY);
85 /* do we have a file open ? return error */
87 if (fd < 0 && *to <= 0) {
88 _log_err( PAM_GROUP_CONF " not opened");
89 memset(*buf, 0, PAM_GROUP_BUFLEN);
94 /* check if there was a newline last time */
96 if ((*to > *from) && (*to > 0)
97 && ((*buf)[*from] == '\0')) { /* previous line ended */
103 /* ready for more data: first shift the buffer's remaining data */
106 shift_bytes(*buf, *from, *to);
110 while (fd >= 0 && *to < PAM_GROUP_BUFLEN) {
113 /* now try to fill the remainder of the buffer */
115 i = read(fd, *to + *buf, PAM_GROUP_BUFLEN - *to);
117 _log_err("error reading " PAM_GROUP_CONF);
121 fd = -1; /* end of file reached */
126 * contract the buffer. Delete any comments, and replace all
127 * multiple spaces with single commas
132 D(("buffer=<%s>",*buf));
135 if ((*buf)[i] == ',') {
138 for (j=++i; j<*to && (*buf)[j] == ','; ++j);
140 shift_bytes(i + (*buf), j-i, (*to) - j);
147 for (j=i; j < *to && (c = (*buf)[j]) != '\n'; ++j);
149 (*buf)[*to = ++i] = '\0';
150 } else if (c == '\n') {
151 shift_bytes(i + (*buf), j-i, (*to) - j);
155 _log_err("internal error in " __FILE__
156 " at line %d", __LINE__ );
161 if ((*buf)[i+1] == '\n') {
162 shift_bytes(i + *buf, 2, *to - (i+2));
169 if ((*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 );
185 /* now return the next field (set the from/to markers) */
189 for (i=0; i<*to; ++i) {
192 case '\n': /* end of the line/file */
196 case FIELD_SEPARATOR: /* end of the field */
203 (*buf)[*from] = '\0';
207 D(("[end of text]"));
213 /* read a member from a field */
215 static int logic_member(const char *string, int *at)
243 if (isalpha(c) || c == '*' || isdigit(c) || c == '_'
244 || c == '-' || c == '.') {
258 typedef enum { VAL, OP } expect;
260 static boolean logic_field(const void *me, const char *x, int rule,
261 boolean (*agrees)(const void *, const char *
264 boolean left=FALSE, right, not=FALSE;
269 while ((l = logic_member(x,&at))) {
275 else if (isalpha(c) || c == '*') {
276 right = not ^ agrees(me, x+at, l, rule);
283 _log_err("garbled syntax; expected name (rule #%d)", rule);
295 _log_err("garbled syntax; expected & or | (rule #%d)"
297 D(("%c at %d",c,at));
308 static boolean is_same(const void *A, const char *b, int len, int rule)
314 for (i=0; len > 0; ++i, --len) {
317 return (!--len || !strncmp(b+i,a+strlen(a)-len,len));
326 int day; /* array of 7 bits, one set for today */
327 int minute; /* integer, hour*100+minute for now */
333 } static const days[11] = {
347 static TIME time_now(void)
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;
358 D(("day: 0%o, time: %.4d", this.day, this.minute));
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)
366 int marked_day, time_start, time_end;
371 D(("checking: 0%o/%.4d vs. %s", at->day, at->minute, times));
374 /* this should not happen */
375 _log_err("internal error: " __FILE__ " line %d", __LINE__);
379 if (times[j] == '!') {
386 for (marked_day = 0; len > 0 && isalpha(times[j]); --len) {
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;
398 if (this_day == -1) {
399 _log_err("bad day specified (rule #%d)", rule);
402 marked_day ^= this_day;
404 if (marked_day == 0) {
405 _log_err("no day specified");
408 D(("day range = 0%o", marked_day));
411 for (i=0; len > 0 && i < 4 && isdigit(times[i+j]); ++i, --len) {
413 time_start += times[i+j]-'0'; /* is this portable? */
417 if (times[j] == '-') {
419 for (i=1; len > 0 && i < 5 && isdigit(times[i+j]); ++i, --len) {
421 time_end += times[i+j]-'0'; /* is this portable? */
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);
432 D(("times(%d to %d)", time_start,time_end));
433 D(("marked_day = 0%o", marked_day));
435 /* compare with the actual time now */
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"));
444 } else { /* spans two days */
445 if ((at->day & marked_day) && (at->minute >= time_start)) {
446 D(("caught on first day"));
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"));
462 static int find_member(const char *string, int *at)
490 if (isalpha(c) || isdigit(c) || c == '_' || c == '*'
506 #define blk_size(len) (((len-1 + GROUP_BLK)/GROUP_BLK)*GROUP_BLK)
508 static int mkgrplist(char *buf, gid_t **list, int len)
513 blks = blk_size(len);
514 D(("cf. blks=%d and len=%d", blks,len));
516 while ((l = find_member(buf,&at))) {
522 D(("allocating new block"));
523 tmp = (gid_t *) realloc((*list)
524 , sizeof(gid_t) * (blks += GROUP_BLK));
528 _log_err("out of memory for group list");
535 /* '\0' terminate the entry */
537 edge = (buf[at+l]) ? 1:0;
539 D(("found group: %s",buf+at));
541 /* this is where we convert a group name to a gid_t */
545 const struct pwdb *pw=NULL;
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));
552 const struct pwdb_entry *pwe=NULL;
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 */
561 _log_err("%s group entry is bad; %s"
562 , pwdb_strerror(retval));
564 pw = NULL; /* break link - cached for later use */
569 const struct group *grp;
571 grp = getgrnam(buf+at);
573 _log_err("bad group: %s", buf+at);
575 D(("group %s exists", buf+at));
576 (*list)[len++] = grp->gr_gid;
581 /* next entry along */
585 D(("returning with [%p/len=%d]->%p",list,len,*list));
590 static int check_account(const char *service, const char *tty
593 int from=0,to=0,fd=-1;
597 int retval=PAM_SUCCESS;
602 * first we get the current list of groups - the application
603 * will have previously done an initgroups(), or equivalent.
606 D(("counting supplementary groups"));
607 no_grps = getgroups(0, NULL); /* find the current number of groups */
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);
615 for (z=0; z<no_grps; ++z) {
616 D(("gid[%d]=%d", z, grps[z]));
621 D(("no supplementary groups known"));
626 here_and_now = time_now(); /* find current time */
628 /* parse the rules in the configuration file */
632 /* here we get the service name field */
634 fd = read_field(fd,&buffer,&from,&to);
635 if (!buffer || !buffer[0]) {
636 /* empty line .. ? */
640 D(("working on rule #%d",count));
642 good = logic_field(service, buffer, count, is_same);
643 D(("with service: %s", good ? "passes":"fails" ));
645 /* here we get the terminal name field */
647 fd = read_field(fd,&buffer,&from,&to);
648 if (!buffer || !buffer[0]) {
649 _log_err(PAM_GROUP_CONF "; no tty entry #%d", count);
652 good &= logic_field(tty, buffer, count, is_same);
653 D(("with tty: %s", good ? "passes":"fails" ));
655 /* here we get the username field */
657 fd = read_field(fd,&buffer,&from,&to);
658 if (!buffer || !buffer[0]) {
659 _log_err(PAM_GROUP_CONF "; no user entry #%d", count);
662 good &= logic_field(user, buffer, count, is_same);
663 D(("with user: %s", good ? "passes":"fails" ));
665 /* here we get the time field */
667 fd = read_field(fd,&buffer,&from,&to);
668 if (!buffer || !buffer[0]) {
669 _log_err(PAM_GROUP_CONF "; no time entry #%d", count);
673 good &= logic_field(&here_and_now, buffer, count, check_time);
674 D(("with time: %s", good ? "passes":"fails" ));
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"
684 * so we have a list of groups, we need to turn it into
685 * something to send to setgroups(2)
689 D(("adding %s to gid list", buffer));
690 good = mkgrplist(buffer, &grps, no_grps);
698 /* check the line is terminated correctly */
700 fd = read_field(fd,&buffer,&from,&to);
701 if (buffer && buffer[0]) {
702 _log_err(PAM_GROUP_CONF "; poorly terminated rule #%d", count);
706 D(("rule #%d passed, added %d groups", count, good));
707 } else if (good < 0) {
708 retval = PAM_BUF_ERR;
710 D(("rule #%d failed", count));
715 /* now set the groups for the user */
719 D(("trying to set %d groups", no_grps));
721 for (err=0; err<no_grps; ++err) {
722 D(("gid[%d]=%d", err, grps[err]));
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)"
729 retval = PAM_CRED_ERR;
733 if (grps) { /* tidy up */
734 memset(grps, 0, sizeof(gid_t) * blk_size(no_grps));
742 /* --- public authentication management functions --- */
744 PAM_EXTERN int pam_sm_authenticate(pam_handle_t *pamh, int flags
745 , int argc, const char **argv)
750 PAM_EXTERN int pam_sm_setcred(pam_handle_t *pamh, int flags
751 , int argc, const char **argv)
753 const char *service=NULL, *tty=NULL;
754 const char *user=NULL;
758 /* only interested in establishing credentials */
761 if (!(setting & PAM_ESTABLISH_CRED)) {
762 D(("ignoring call - not for establishing credentials"));
763 return PAM_SUCCESS; /* don't fail because of this */
766 /* set service name */
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");
776 if (pam_get_user(pamh, &user, NULL) != PAM_SUCCESS || user == NULL
778 _log_err("cannot determine the user's name");
779 return PAM_USER_UNKNOWN;
784 if (pam_get_item(pamh, PAM_TTY, (const void **)&tty) != PAM_SUCCESS
786 D(("PAM_TTY not set, probing stdin"));
787 tty = ttyname(STDIN_FILENO);
789 _log_err("couldn't get the tty name");
792 if (pam_set_item(pamh, PAM_TTY, tty) != PAM_SUCCESS) {
793 _log_err("couldn't set tty name");
798 if (strncmp("/dev/",tty,5) == 0) { /* strip leading /dev/ */
802 /* good, now we have the service name, the user and the terminal name */
804 D(("service=%s", service));
805 D(("user=%s", user));
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 */
816 D(("failed to initialize pwdb; %s", pwdb_strerror(retval)));
817 _log_err("unable to initialize libpwdb");
821 #else /* WANT_PWDB */
822 retval = check_account(service,tty,user); /* get groups */
823 #endif /* WANT_PWDB */
828 /* end of module definition */
832 /* static module data */
834 struct pam_module _pam_group_modstruct = {