123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815 |
- /*
- * pam_group module
- *
- * Written by Andrew Morgan <morgan@linux.kernel.org> 1996/7/6
- * Field parsing rewritten by Tomas Mraz <tm@t8m.info>
- */
- #include "config.h"
- #include <sys/file.h>
- #include <stdio.h>
- #include <stdlib.h>
- #include <ctype.h>
- #include <unistd.h>
- #include <stdarg.h>
- #include <time.h>
- #include <syslog.h>
- #include <string.h>
- #include <grp.h>
- #include <sys/types.h>
- #include <sys/stat.h>
- #include <fcntl.h>
- #include <netdb.h>
- #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;
- #include <security/pam_modules.h>
- #include <security/_pam_macros.h>
- #include <security/pam_modutil.h>
- #include <security/pam_ext.h>
- /* --- 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;
- }
- /* fallthrough */
- 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;
- not = FALSE;
- }
- 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<no_grps; ++z) {
- D(("gid[%d]=%d", z, grps[z]));
- }
- }
- #endif
- } else {
- D(("no supplementary groups known"));
- no_grps = 0;
- grps = NULL;
- }
- here_and_now = time_now(); /* find current time */
- /* parse the rules in the configuration file */
- do {
- int good=TRUE;
- /* here we get the service name field */
- fd = read_field(pamh, fd, &buffer, &from, &state);
- if (!buffer || !buffer[0]) {
- /* empty line .. ? */
- continue;
- }
- ++count;
- D(("working on rule #%d",count));
- if (state != STATE_FIELD) {
- pam_syslog(pamh, LOG_ERR,
- "%s: malformed rule #%d", PAM_GROUP_CONF, count);
- continue;
- }
- good = logic_field(pamh,service, buffer, count, is_same);
- D(("with service: %s", good ? "passes":"fails" ));
- /* here we get the terminal name field */
- fd = read_field(pamh, fd, &buffer, &from, &state);
- if (state != STATE_FIELD) {
- pam_syslog(pamh, LOG_ERR,
- "%s: malformed rule #%d", PAM_GROUP_CONF, count);
- continue;
- }
- good &= logic_field(pamh,tty, buffer, count, is_same);
- D(("with tty: %s", good ? "passes":"fails" ));
- /* here we get the username field */
- fd = read_field(pamh, fd, &buffer, &from, &state);
- if (state != STATE_FIELD) {
- pam_syslog(pamh, LOG_ERR,
- "%s: malformed rule #%d", PAM_GROUP_CONF, count);
- continue;
- }
- /* If buffer starts with @, we are using netgroups */
- if (buffer[0] == '@')
- #ifdef HAVE_INNETGR
- good &= innetgr (&buffer[1], NULL, user, NULL);
- #else
- pam_syslog (pamh, LOG_ERR, "pam_group does not have netgroup support");
- #endif
- /* otherwise, if the buffer starts with %, it's a UNIX group */
- else if (buffer[0] == '%')
- good &= pam_modutil_user_in_group_nam_nam(pamh, user, &buffer[1]);
- else
- good &= logic_field(pamh,user, buffer, count, is_same);
- D(("with user: %s", good ? "passes":"fails" ));
- /* here we get the time field */
- fd = read_field(pamh, fd, &buffer, &from, &state);
- if (state != STATE_FIELD) {
- pam_syslog(pamh, LOG_ERR,
- "%s: malformed rule #%d", PAM_GROUP_CONF, count);
- continue;
- }
- good &= logic_field(pamh,&here_and_now, buffer, count, check_time);
- D(("with time: %s", good ? "passes":"fails" ));
- fd = read_field(pamh, fd, &buffer, &from, &state);
- if (state == STATE_FIELD) {
- pam_syslog(pamh, LOG_ERR,
- "%s: poorly terminated rule #%d", PAM_GROUP_CONF, count);
- continue;
- }
- /*
- * so we have a list of groups, we need to turn it into
- * something to send to setgroups(2)
- */
- if (good) {
- D(("adding %s to gid list", buffer));
- good = mkgrplist(pamh, buffer, &grps, no_grps);
- if (good < 0) {
- no_grps = 0;
- } else {
- no_grps = good;
- }
- }
- if (good > 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<no_grps; ++err) {
- D(("gid[%d]=%d", err, grps[err]));
- }
- #endif
- if (setgroups(no_grps, grps) != 0) {
- D(("but couldn't set groups %m"));
- pam_syslog(pamh, LOG_ERR,
- "unable to set the group membership for user: %m");
- retval = PAM_CRED_ERR;
- }
- }
- if (grps) { /* tidy up */
- memset(grps, 0, sizeof(gid_t) * blk_size(no_grps));
- _pam_drop(grps);
- no_grps = 0;
- }
- return retval;
- }
- /* --- public authentication management functions --- */
- int
- pam_sm_authenticate (pam_handle_t *pamh UNUSED, int flags UNUSED,
- int argc UNUSED, const char **argv UNUSED)
- {
- return PAM_IGNORE;
- }
- int
- pam_sm_setcred (pam_handle_t *pamh, int flags,
- int argc UNUSED, const char **argv UNUSED)
- {
- const void *service=NULL, *void_tty=NULL;
- const char *user=NULL;
- const char *tty;
- int retval;
- unsigned setting;
- /* only interested in establishing credentials */
- setting = flags;
- if (!(setting & (PAM_ESTABLISH_CRED | PAM_REINITIALIZE_CRED))) {
- D(("ignoring call - not for establishing credentials"));
- return PAM_SUCCESS; /* don't fail because of this */
- }
- /* set service name */
- if (pam_get_item(pamh, PAM_SERVICE, &service)
- != PAM_SUCCESS || service == NULL) {
- pam_syslog(pamh, LOG_ERR, "cannot find the current service name");
- return PAM_ABORT;
- }
- /* set username */
- if (pam_get_user(pamh, &user, NULL) != PAM_SUCCESS || *user == '\0') {
- pam_syslog(pamh, LOG_NOTICE, "cannot determine user name");
- return PAM_USER_UNKNOWN;
- }
- /* set tty name */
- if (pam_get_item(pamh, PAM_TTY, &void_tty) != PAM_SUCCESS
- || void_tty == NULL) {
- D(("PAM_TTY not set, probing stdin"));
- tty = ttyname(STDIN_FILENO);
- if (tty == NULL) {
- tty = "";
- }
- if (pam_set_item(pamh, PAM_TTY, tty) != PAM_SUCCESS) {
- pam_syslog(pamh, LOG_ERR, "couldn't set tty name");
- return PAM_ABORT;
- }
- }
- else
- tty = (const char *) void_tty;
- if (tty[0] == '/') { /* full path */
- const char *t;
- tty++;
- if ((t = strchr(tty, '/')) != NULL) {
- tty = t + 1;
- }
- }
- /* good, now we have the service name, the user and the terminal name */
- D(("service=%s", service));
- D(("user=%s", user));
- D(("tty=%s", tty));
- retval = check_account(pamh,service,tty,user); /* get groups */
- return retval;
- }
- /* end of module definition */
|