6 * Written by Andrew Morgan <morgan@linux.kernel.org> 1996/7/6
9 static const char rcsid[] =
11 "Version 0.5 for Linux-PAM\n"
12 "Copyright (c) Andrew G. Morgan 1996 <morgan@linux.kernel.org>\n";
29 #include <sys/types.h>
33 #ifdef DEFAULT_CONF_FILE
34 # define PAM_GROUP_CONF DEFAULT_CONF_FILE /* from external define */
36 # define PAM_GROUP_CONF "/etc/security/group.conf"
38 #define PAM_GROUP_BUFLEN 1000
39 #define FIELD_SEPARATOR ';' /* this is new as of .02 */
48 typedef enum { FALSE, TRUE } boolean;
49 typedef enum { AND, OR } operator;
52 * here, we make definitions for the externally accessible functions
53 * in this file (these definitions are required for static modules
54 * but strongly encouraged generally) they are used to instruct the
55 * modules include file to define their prototypes.
60 #include <security/pam_modules.h>
61 #include <security/_pam_macros.h>
62 #include <security/_pam_modutil.h>
64 /* --- static functions for checking whether the user should be let in --- */
66 static void _log_err(const char *format, ... )
70 va_start(args, format);
71 openlog("pam_group", LOG_CONS|LOG_PID, LOG_AUTH);
72 vsyslog(LOG_CRIT, format, args);
77 static void shift_bytes(char *mem, int from, int by)
85 /* This function should initially be called with buf = NULL. If
86 * an error occurs, the file descriptor is closed. Subsequent
87 * calls with a closed descriptor will cause buf to be deallocated.
88 * Therefore, always check buf after calling this to see if an error
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);
142 fd = -1; /* end of file reached */
147 * contract the buffer. Delete any comments, and replace all
148 * multiple spaces with single commas
153 D(("buffer=<%s>",*buf));
156 if ((*buf)[i] == ',') {
159 for (j=++i; j<*to && (*buf)[j] == ','; ++j);
161 shift_bytes(i + (*buf), j-i, (*to) - j);
168 for (j=i; j < *to && (c = (*buf)[j]) != '\n'; ++j);
170 (*buf)[*to = ++i] = '\0';
171 } else if (c == '\n') {
172 shift_bytes(i + (*buf), j-i, (*to) - j);
176 _log_err("internal error in " __FILE__
177 " at line %d", __LINE__ );
183 if ((*buf)[i+1] == '\n') {
184 shift_bytes(i + *buf, 2, *to - (i+2));
187 ++i; /* we don't escape non-newline characters */
193 if ((*buf)[i] != '!')
195 /* delete any trailing spaces */
196 for (j=++i; j < *to && ( (c = (*buf)[j]) == ' '
197 || c == '\t' ); ++j);
198 shift_bytes(i + *buf, j-i, (*to)-j );
209 /* now return the next field (set the from/to markers) */
213 for (i=0; i<*to; ++i) {
216 case '\n': /* end of the line/file */
220 case FIELD_SEPARATOR: /* end of the field */
227 (*buf)[*from] = '\0';
231 D(("[end of text]"));
237 /* read a member from a field */
239 static int logic_member(const char *string, int *at)
267 if (isalpha(c) || c == '*' || isdigit(c) || c == '_'
268 || c == '-' || c == '.' || c == '/') {
282 typedef enum { VAL, OP } expect;
284 static boolean logic_field(const void *me, const char *x, int rule,
285 boolean (*agrees)(const void *, const char *
288 boolean left=FALSE, right, not=FALSE;
293 while ((l = logic_member(x,&at))) {
299 else if (isalpha(c) || c == '*') {
300 right = not ^ agrees(me, x+at, l, rule);
307 _log_err("garbled syntax; expected name (rule #%d)", rule);
319 _log_err("garbled syntax; expected & or | (rule #%d)"
321 D(("%c at %d",c,at));
332 static boolean is_same(const void *A, const char *b, int len, int rule)
338 for (i=0; len > 0; ++i, --len) {
341 return (!--len || !strncmp(b+i,a+strlen(a)-len,len));
350 int day; /* array of 7 bits, one set for today */
351 int minute; /* integer, hour*100+minute for now */
357 } static const days[11] = {
371 static TIME time_now(void)
377 the_time = time((time_t *)0); /* get the current time */
378 local = localtime(&the_time);
379 this.day = days[local->tm_wday].bit;
380 this.minute = local->tm_hour*100 + local->tm_min;
382 D(("day: 0%o, time: %.4d", this.day, this.minute));
386 /* take the current date and see if the range "date" passes it */
387 static boolean check_time(const void *AT, const char *times, int len, int rule)
390 int marked_day, time_start, time_end;
395 D(("checking: 0%o/%.4d vs. %s", at->day, at->minute, times));
398 /* this should not happen */
399 _log_err("internal error: " __FILE__ " line %d", __LINE__);
403 if (times[j] == '!') {
410 for (marked_day = 0; len > 0 && isalpha(times[j]); --len) {
413 D(("%c%c ?", times[j], times[j+1]));
414 for (i=0; days[i].d != NULL; ++i) {
415 if (tolower(times[j]) == days[i].d[0]
416 && tolower(times[j+1]) == days[i].d[1] ) {
417 this_day = days[i].bit;
422 if (this_day == -1) {
423 _log_err("bad day specified (rule #%d)", rule);
426 marked_day ^= this_day;
428 if (marked_day == 0) {
429 _log_err("no day specified");
432 D(("day range = 0%o", marked_day));
435 for (i=0; len > 0 && i < 4 && isdigit(times[i+j]); ++i, --len) {
437 time_start += times[i+j]-'0'; /* is this portable? */
441 if (times[j] == '-') {
443 for (i=1; len > 0 && i < 5 && isdigit(times[i+j]); ++i, --len) {
445 time_end += times[i+j]-'0'; /* is this portable? */
451 D(("i=%d, time_end=%d, times[j]='%c'", i, time_end, times[j]));
452 if (i != 5 || time_end == -1) {
453 _log_err("no/bad times specified (rule #%d)", rule);
456 D(("times(%d to %d)", time_start,time_end));
457 D(("marked_day = 0%o", marked_day));
459 /* compare with the actual time now */
462 if (time_start < time_end) { /* start < end ? --> same day */
463 if ((at->day & marked_day) && (at->minute >= time_start)
464 && (at->minute < time_end)) {
465 D(("time is listed"));
468 } else { /* spans two days */
469 if ((at->day & marked_day) && (at->minute >= time_start)) {
470 D(("caught on first day"));
474 marked_day |= (marked_day & 0200) ? 1:0;
475 D(("next day = 0%o", marked_day));
476 if ((at->day & marked_day) && (at->minute <= time_end)) {
477 D(("caught on second day"));
486 static int find_member(const char *string, int *at)
514 if (isalpha(c) || isdigit(c) || c == '_' || c == '*'
530 #define blk_size(len) (((len-1 + GROUP_BLK)/GROUP_BLK)*GROUP_BLK)
532 static int mkgrplist(pam_handle_t *pamh, char *buf, gid_t **list, int len)
537 blks = blk_size(len);
538 D(("cf. blks=%d and len=%d", blks,len));
540 while ((l = find_member(buf,&at))) {
546 D(("allocating new block"));
547 tmp = (gid_t *) realloc((*list)
548 , sizeof(gid_t) * (blks += GROUP_BLK));
552 _log_err("out of memory for group list");
559 /* '\0' terminate the entry */
561 edge = (buf[at+l]) ? 1:0;
563 D(("found group: %s",buf+at));
565 /* this is where we convert a group name to a gid_t */
569 const struct pwdb *pw=NULL;
571 retval = pwdb_locate("group", PWDB_DEFAULT, buf+at
572 , PWDB_ID_UNKNOWN, &pw);
573 if (retval != PWDB_SUCCESS) {
574 _log_err("bad group: %s; %s", buf+at, pwdb_strerror(retval));
576 const struct pwdb_entry *pwe=NULL;
578 D(("group %s exists", buf+at));
579 retval = pwdb_get_entry(pw, "gid", &pwe);
580 if (retval == PWDB_SUCCESS) {
581 D(("gid = %d [%p]",* (const gid_t *) pwe->value,list));
582 (*list)[len++] = * (const gid_t *) pwe->value;
583 pwdb_entry_delete(&pwe); /* tidy up */
585 _log_err("%s group entry is bad; %s"
586 , pwdb_strerror(retval));
588 pw = NULL; /* break link - cached for later use */
593 const struct group *grp;
595 grp = _pammodutil_getgrnam(pamh, buf+at);
597 _log_err("bad group: %s", buf+at);
599 D(("group %s exists", buf+at));
600 (*list)[len++] = grp->gr_gid;
605 /* next entry along */
609 D(("returning with [%p/len=%d]->%p",list,len,*list));
614 static int check_account(pam_handle_t *pamh, const char *service,
615 const char *tty, const char *user)
617 int from=0,to=0,fd=-1;
621 int retval=PAM_SUCCESS;
626 * first we get the current list of groups - the application
627 * will have previously done an initgroups(), or equivalent.
630 D(("counting supplementary groups"));
631 no_grps = getgroups(0, NULL); /* find the current number of groups */
633 grps = calloc( blk_size(no_grps) , sizeof(gid_t) );
634 D(("copying current list into grps [%d big]",blk_size(no_grps)));
635 if (getgroups(no_grps, grps) < 0) {
636 D(("getgroups call failed"));
643 for (z=0; z<no_grps; ++z) {
644 D(("gid[%d]=%d", z, grps[z]));
649 D(("no supplementary groups known"));
654 here_and_now = time_now(); /* find current time */
656 /* parse the rules in the configuration file */
660 /* here we get the service name field */
662 fd = read_field(fd,&buffer,&from,&to);
663 if (!buffer || !buffer[0]) {
664 /* empty line .. ? */
668 D(("working on rule #%d",count));
670 good = logic_field(service, buffer, count, is_same);
671 D(("with service: %s", good ? "passes":"fails" ));
673 /* here we get the terminal name field */
675 fd = read_field(fd,&buffer,&from,&to);
676 if (!buffer || !buffer[0]) {
677 _log_err(PAM_GROUP_CONF "; no tty entry #%d", count);
680 good &= logic_field(tty, buffer, count, is_same);
681 D(("with tty: %s", good ? "passes":"fails" ));
683 /* here we get the username field */
685 fd = read_field(fd,&buffer,&from,&to);
686 if (!buffer || !buffer[0]) {
687 _log_err(PAM_GROUP_CONF "; no user entry #%d", count);
690 good &= logic_field(user, buffer, count, is_same);
691 D(("with user: %s", good ? "passes":"fails" ));
693 /* here we get the time field */
695 fd = read_field(fd,&buffer,&from,&to);
696 if (!buffer || !buffer[0]) {
697 _log_err(PAM_GROUP_CONF "; no time entry #%d", count);
701 good &= logic_field(&here_and_now, buffer, count, check_time);
702 D(("with time: %s", good ? "passes":"fails" ));
704 fd = read_field(fd,&buffer,&from,&to);
705 if (!buffer || !buffer[0]) {
706 _log_err(PAM_GROUP_CONF "; no listed groups for rule #%d"
712 * so we have a list of groups, we need to turn it into
713 * something to send to setgroups(2)
717 D(("adding %s to gid list", buffer));
718 good = mkgrplist(pamh, buffer, &grps, no_grps);
726 /* check the line is terminated correctly */
728 fd = read_field(fd,&buffer,&from,&to);
729 if (buffer && buffer[0]) {
730 _log_err(PAM_GROUP_CONF "; poorly terminated rule #%d", count);
734 D(("rule #%d passed, added %d groups", count, good));
735 } else if (good < 0) {
736 retval = PAM_BUF_ERR;
738 D(("rule #%d failed", count));
743 /* now set the groups for the user */
747 D(("trying to set %d groups", no_grps));
749 for (err=0; err<no_grps; ++err) {
750 D(("gid[%d]=%d", err, grps[err]));
753 if ((err = setgroups(no_grps, grps))) {
754 D(("but couldn't set groups %d", err));
755 _log_err("unable to set the group membership for user (err=%d)"
757 retval = PAM_CRED_ERR;
761 if (grps) { /* tidy up */
762 memset(grps, 0, sizeof(gid_t) * blk_size(no_grps));
770 /* --- public authentication management functions --- */
772 PAM_EXTERN int pam_sm_authenticate(pam_handle_t *pamh, int flags
773 , int argc, const char **argv)
778 PAM_EXTERN int pam_sm_setcred(pam_handle_t *pamh, int flags
779 , int argc, const char **argv)
781 const void *service=NULL, *void_tty=NULL;
782 const char *user=NULL;
787 /* only interested in establishing credentials */
790 if (!(setting & (PAM_ESTABLISH_CRED | PAM_REINITIALIZE_CRED))) {
791 D(("ignoring call - not for establishing credentials"));
792 return PAM_SUCCESS; /* don't fail because of this */
795 /* set service name */
797 if (pam_get_item(pamh, PAM_SERVICE, &service)
798 != PAM_SUCCESS || service == NULL) {
799 _log_err("cannot find the current service name");
805 if (pam_get_user(pamh, &user, NULL) != PAM_SUCCESS || user == NULL
807 _log_err("cannot determine the user's name");
808 return PAM_USER_UNKNOWN;
813 if (pam_get_item(pamh, PAM_TTY, &void_tty) != PAM_SUCCESS
814 || void_tty == NULL) {
815 D(("PAM_TTY not set, probing stdin"));
816 tty = ttyname(STDIN_FILENO);
818 _log_err("couldn't get the tty name");
821 if (pam_set_item(pamh, PAM_TTY, tty) != PAM_SUCCESS) {
822 _log_err("couldn't set tty name");
827 tty = (const char *) void_tty;
829 if (strncmp("/dev/",tty,5) == 0) { /* strip leading /dev/ */
833 /* good, now we have the service name, the user and the terminal name */
835 D(("service=%s", service));
836 D(("user=%s", user));
841 /* We initialize the pwdb library and check the account */
842 retval = pwdb_start(); /* initialize */
843 if (retval == PWDB_SUCCESS) {
844 retval = check_account(pamh, service,tty,user); /* get groups */
845 (void) pwdb_end(); /* tidy up */
847 D(("failed to initialize pwdb; %s", pwdb_strerror(retval)));
848 _log_err("unable to initialize libpwdb");
852 #else /* WANT_PWDB */
853 retval = check_account(pamh,service,tty,user); /* get groups */
854 #endif /* WANT_PWDB */
859 /* end of module definition */
863 /* static module data */
865 struct pam_module _pam_group_modstruct = {