/* pam_group module */ /* * Written by Andrew Morgan 1996/7/6 * Field parsing rewritten by Tomas Mraz */ #include "config.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #define PAM_GROUP_BUFLEN 1000 #define FIELD_SEPARATOR ';' /* this is new as of .02 */ #ifndef TRUE # define TRUE 1 #endif #ifndef FALSE # define FALSE 0 #endif typedef enum { AND, OR } operator; /* * here, we make definitions for the externally accessible functions * in this file (these definitions are required for static modules * but strongly encouraged generally) they are used to instruct the * modules include file to define their prototypes. */ #define PAM_SM_AUTH #include #include #include #include /* --- static functions for checking whether the user should be let in --- */ static char * shift_buf(char *mem, int from) { char *start = mem; while ((*mem = mem[from]) != '\0') ++mem; memset(mem, '\0', PAM_GROUP_BUFLEN - (mem - start)); return mem; } static void trim_spaces(char *buf, char *from) { while (from > buf) { --from; if (*from == ' ') *from = '\0'; else break; } } #define STATE_NL 0 /* new line starting */ #define STATE_COMMENT 1 /* inside comment */ #define STATE_FIELD 2 /* field following */ #define STATE_EOF 3 /* end of file or error */ static int read_field(const pam_handle_t *pamh, int fd, char **buf, int *from, int *state) { char *to; char *src; int i; char c; int onspace; /* is buf set ? */ if (! *buf) { *buf = (char *) calloc(1, PAM_GROUP_BUFLEN+1); if (! *buf) { pam_syslog(pamh, LOG_CRIT, "out of memory"); D(("no memory")); *state = STATE_EOF; return -1; } *from = 0; *state = STATE_NL; fd = open(PAM_GROUP_CONF, O_RDONLY); if (fd < 0) { pam_syslog(pamh, LOG_ERR, "error opening %s: %m", PAM_GROUP_CONF); _pam_drop(*buf); *state = STATE_EOF; return -1; } } if (*from > 0) to = shift_buf(*buf, *from); else to = *buf; while (fd != -1 && to - *buf < PAM_GROUP_BUFLEN) { i = pam_modutil_read(fd, to, PAM_GROUP_BUFLEN - (to - *buf)); if (i < 0) { pam_syslog(pamh, LOG_ERR, "error reading %s: %m", PAM_GROUP_CONF); close(fd); memset(*buf, 0, PAM_GROUP_BUFLEN); _pam_drop(*buf); *state = STATE_EOF; return -1; } else if (!i) { close(fd); fd = -1; /* end of file reached */ } to += i; } if (to == *buf) { /* nothing previously in buf, nothing read */ _pam_drop(*buf); *state = STATE_EOF; return -1; } memset(to, '\0', PAM_GROUP_BUFLEN - (to - *buf)); to = *buf; onspace = 1; /* delete any leading spaces */ for (src = to; (c=*src) != '\0'; ++src) { if (*state == STATE_COMMENT && c != '\n') { continue; } switch (c) { case '\n': *state = STATE_NL; *to = '\0'; *from = (src - *buf) + 1; trim_spaces(*buf, to); return fd; case '\t': case ' ': if (!onspace) { onspace = 1; *to++ = ' '; } break; case '!': onspace = 1; /* ignore following spaces */ *to++ = '!'; break; case '#': *state = STATE_COMMENT; break; case FIELD_SEPARATOR: *state = STATE_FIELD; *to = '\0'; *from = (src - *buf) + 1; trim_spaces(*buf, to); return fd; case '\\': if (src[1] == '\n') { ++src; /* skip it */ break; } default: *to++ = c; onspace = 0; } if (src > to) *src = '\0'; /* clearing */ } if (*state != STATE_COMMENT) { *state = STATE_COMMENT; pam_syslog(pamh, LOG_ERR, "field too long - ignored"); **buf = '\0'; } else { *to = '\0'; trim_spaces(*buf, to); } *from = 0; return fd; } /* read a member from a field */ static int logic_member(const char *string, int *at) { int c,to; int done=0; int token=0; to=*at; do { c = string[to++]; switch (c) { case '\0': --to; done = 1; break; case '&': case '|': case '!': if (token) { --to; } done = 1; break; default: if (isalpha(c) || c == '*' || isdigit(c) || c == '_' || c == '-' || c == '.' || c == '/' || c == ':') { token = 1; } else if (token) { --to; done = 1; } else { ++*at; } } } while (!done); return to - *at; } typedef enum { VAL, OP } expect; static int logic_field (const pam_handle_t *pamh, const void *me, const char *x, int rule, int (*agrees)(const pam_handle_t *pamh, const void *, const char *, int, int)) { int left=FALSE, right, not=FALSE; operator oper=OR; int at=0, l; expect next=VAL; while ((l = logic_member(x,&at))) { int c = x[at]; if (next == VAL) { if (c == '!') not = !not; else if (isalpha(c) || c == '*' || isdigit(c) || c == '_' || c == '-' || c == '.' || c == '/' || c == ':') { right = not ^ agrees(pamh, me, x+at, l, rule); if (oper == AND) left &= right; else left |= right; next = OP; } else { pam_syslog(pamh, LOG_ERR, "garbled syntax; expected name (rule #%d)", rule); return FALSE; } } else { /* OP */ switch (c) { case '&': oper = AND; break; case '|': oper = OR; break; default: pam_syslog(pamh, LOG_ERR, "garbled syntax; expected & or | (rule #%d)", rule); D(("%c at %d",c,at)); return FALSE; } next = VAL; } at += l; } return left; } static int is_same (const pam_handle_t *pamh UNUSED, const void *A, const char *b, int len, int rule UNUSED) { int i; const char *a; a = A; for (i=0; len > 0; ++i, --len) { if (b[i] != a[i]) { if (b[i++] == '*') { return (!--len || !strncmp(b+i,a+strlen(a)-len,len)); } else return FALSE; } } /* Ok, we know that b is a substring from A and does not contain wildcards, but now the length of both strings must be the same, too. In this case it means, a[i] has to be the end of the string. */ if (a[i] != '\0') return FALSE; return ( !len ); } typedef struct { int day; /* array of 7 bits, one set for today */ int minute; /* integer, hour*100+minute for now */ } TIME; static struct day { const char *d; int bit; } const days[11] = { { "su", 01 }, { "mo", 02 }, { "tu", 04 }, { "we", 010 }, { "th", 020 }, { "fr", 040 }, { "sa", 0100 }, { "wk", 076 }, { "wd", 0101 }, { "al", 0177 }, { NULL, 0 } }; static TIME time_now(void) { struct tm *local; time_t the_time; TIME this; the_time = time((time_t *)0); /* get the current time */ local = localtime(&the_time); this.day = days[local->tm_wday].bit; this.minute = local->tm_hour*100 + local->tm_min; D(("day: 0%o, time: %.4d", this.day, this.minute)); return this; } /* take the current date and see if the range "date" passes it */ static int check_time (const pam_handle_t *pamh, const void *AT, const char *times, int len, int rule) { int not,pass; int marked_day, time_start, time_end; const TIME *at; int i,j=0; at = AT; D(("checking: 0%o/%.4d vs. %s", at->day, at->minute, times)); if (times == NULL) { /* this should not happen */ pam_syslog(pamh, LOG_CRIT, "internal error in file %s at line %d", __FILE__, __LINE__); return FALSE; } if (times[j] == '!') { ++j; not = TRUE; } else { not = FALSE; } for (marked_day = 0; len > 0 && isalpha(times[j]); --len) { int this_day=-1; D(("%c%c ?", times[j], times[j+1])); for (i=0; days[i].d != NULL; ++i) { if (tolower(times[j]) == days[i].d[0] && tolower(times[j+1]) == days[i].d[1] ) { this_day = days[i].bit; break; } } j += 2; if (this_day == -1) { pam_syslog(pamh, LOG_ERR, "bad day specified (rule #%d)", rule); return FALSE; } marked_day ^= this_day; } if (marked_day == 0) { pam_syslog(pamh, LOG_ERR, "no day specified"); return FALSE; } D(("day range = 0%o", marked_day)); time_start = 0; for (i=0; len > 0 && i < 4 && isdigit(times[i+j]); ++i, --len) { time_start *= 10; time_start += times[i+j]-'0'; /* is this portable? */ } j += i; if (times[j] == '-') { time_end = 0; for (i=1; len > 0 && i < 5 && isdigit(times[i+j]); ++i, --len) { time_end *= 10; time_end += times[i+j]-'0'; /* is this portable? */ } j += i; } else time_end = -1; D(("i=%d, time_end=%d, times[j]='%c'", i, time_end, times[j])); if (i != 5 || time_end == -1) { pam_syslog(pamh, LOG_ERR, "no/bad times specified (rule #%d)", rule); return TRUE; } D(("times(%d to %d)", time_start,time_end)); D(("marked_day = 0%o", marked_day)); /* compare with the actual time now */ pass = FALSE; if (time_start < time_end) { /* start < end ? --> same day */ if ((at->day & marked_day) && (at->minute >= time_start) && (at->minute < time_end)) { D(("time is listed")); pass = TRUE; } } else { /* spans two days */ if ((at->day & marked_day) && (at->minute >= time_start)) { D(("caught on first day")); pass = TRUE; } else { marked_day <<= 1; marked_day |= (marked_day & 0200) ? 1:0; D(("next day = 0%o", marked_day)); if ((at->day & marked_day) && (at->minute <= time_end)) { D(("caught on second day")); pass = TRUE; } } } return (not ^ pass); } static int find_member(const char *string, int *at) { int c,to; int done=0; int token=0; to=*at; do { c = string[to++]; switch (c) { case '\0': --to; done = 1; break; case '&': case '|': case '!': if (token) { --to; } done = 1; break; default: if (isalpha(c) || isdigit(c) || c == '_' || c == '*' || c == '-') { token = 1; } else if (token) { --to; done = 1; } else { ++*at; } } } while (!done); return to - *at; } #define GROUP_BLK 10 #define blk_size(len) (((len-1 + GROUP_BLK)/GROUP_BLK)*GROUP_BLK) static int mkgrplist(pam_handle_t *pamh, char *buf, gid_t **list, int len) { int l,at=0; int blks; blks = blk_size(len); D(("cf. blks=%d and len=%d", blks,len)); while ((l = find_member(buf,&at))) { int edge; if (len >= blks) { gid_t *tmp; D(("allocating new block")); tmp = (gid_t *) realloc((*list) , sizeof(gid_t) * (blks += GROUP_BLK)); if (tmp != NULL) { (*list) = tmp; } else { pam_syslog(pamh, LOG_ERR, "out of memory for group list"); free(*list); (*list) = NULL; return -1; } } /* '\0' terminate the entry */ edge = (buf[at+l]) ? 1:0; buf[at+l] = '\0'; D(("found group: %s",buf+at)); /* this is where we convert a group name to a gid_t */ { const struct group *grp; grp = pam_modutil_getgrnam(pamh, buf+at); if (grp == NULL) { pam_syslog(pamh, LOG_ERR, "bad group: %s", buf+at); } else { D(("group %s exists", buf+at)); (*list)[len++] = grp->gr_gid; } } /* next entry along */ at += l + edge; } D(("returning with [%p/len=%d]->%p",list,len,*list)); return len; } static int check_account(pam_handle_t *pamh, const char *service, const char *tty, const char *user) { int from=0, state=STATE_NL, fd=-1; char *buffer=NULL; int count=0; TIME here_and_now; int retval=PAM_SUCCESS; gid_t *grps; int no_grps; /* * first we get the current list of groups - the application * will have previously done an initgroups(), or equivalent. */ D(("counting supplementary groups")); no_grps = getgroups(0, NULL); /* find the current number of groups */ if (no_grps > 0) { grps = calloc( blk_size(no_grps) , sizeof(gid_t) ); D(("copying current list into grps [%d big]",blk_size(no_grps))); if (getgroups(no_grps, grps) < 0) { D(("getgroups call failed")); no_grps = 0; _pam_drop(grps); } #ifdef PAM_DEBUG { int z; for (z=0; z 0) { D(("rule #%d passed, added %d groups", count, good)); } else if (good < 0) { retval = PAM_BUF_ERR; } else { D(("rule #%d failed", count)); } } while (state != STATE_EOF); /* now set the groups for the user */ if (no_grps > 0) { #ifdef PAM_DEBUG int err; #endif D(("trying to set %d groups", no_grps)); #ifdef PAM_DEBUG for (err=0; err