diff options
author | Andrew G. Morgan <morgan@kernel.org> | 2000-06-20 22:10:38 +0000 |
---|---|---|
committer | Andrew G. Morgan <morgan@kernel.org> | 2000-06-20 22:10:38 +0000 |
commit | ea488580c42e8918445a945484de3c8a5addc761 (patch) | |
tree | c992f3ba699caafedfadc16af38e6359c3c24698 /modules/pam_group | |
download | pam-ea488580c42e8918445a945484de3c8a5addc761.tar.gz pam-ea488580c42e8918445a945484de3c8a5addc761.tar.bz2 pam-ea488580c42e8918445a945484de3c8a5addc761.zip |
Initial revision
Diffstat (limited to 'modules/pam_group')
-rw-r--r-- | modules/pam_group/.cvsignore | 1 | ||||
-rw-r--r-- | modules/pam_group/Makefile | 98 | ||||
-rw-r--r-- | modules/pam_group/group.conf | 60 | ||||
-rwxr-xr-x | modules/pam_group/install_conf | 46 | ||||
-rw-r--r-- | modules/pam_group/pam_group.c | 843 |
5 files changed, 1048 insertions, 0 deletions
diff --git a/modules/pam_group/.cvsignore b/modules/pam_group/.cvsignore new file mode 100644 index 00000000..380a834a --- /dev/null +++ b/modules/pam_group/.cvsignore @@ -0,0 +1 @@ +dynamic diff --git a/modules/pam_group/Makefile b/modules/pam_group/Makefile new file mode 100644 index 00000000..b61a57de --- /dev/null +++ b/modules/pam_group/Makefile @@ -0,0 +1,98 @@ +# +# $Id$ +# +# This Makefile controls a build process of $(TITLE) module for +# Linux-PAM. You should not modify this Makefile (unless you know +# what you are doing!). +# +# Created by Andrew Morgan <morgan@linux.kernel.org> 1996/6/11 +# + +TITLE=pam_group +CONFD=$(CONFIGED)/security +export CONFD +CONFILE=$(CONFD)/group.conf +export CONFILE + +# + +LIBSRC = $(TITLE).c +LIBOBJ = $(TITLE).o +LIBOBJD = $(addprefix dynamic/,$(LIBOBJ)) +LIBOBJS = $(addprefix static/,$(LIBOBJ)) + +DEFS=-DCONFILE=\"$(CONFILE)\" + +CFLAGS += $(DEFS) + +dynamic/%.o : %.c + $(CC) $(CFLAGS) $(DYNAMIC) $(CPPFLAGS) $(TARGET_ARCH) -c $< -o $@ + +static/%.o : %.c + $(CC) $(CFLAGS) $(STATIC) $(CPPFLAGS) $(TARGET_ARCH) -c $< -o $@ + + +ifdef DYNAMIC +LIBSHARED = $(TITLE).so +endif +ifdef STATIC +LIBSTATIC = lib$(TITLE).o +endif + +####################### don't edit below ####################### + +dummy: + @echo "**** This is not a top-level Makefile " + exit + +all: dirs $(LIBSHARED) $(LIBSTATIC) register + +dirs: +ifdef DYNAMIC + $(MKDIR) ./dynamic +endif +ifdef STATIC + $(MKDIR) ./static +endif + +register: +ifdef STATIC + ( cd .. ; ./register_static $(TITLE) $(TITLE)/$(LIBSTATIC) ) +endif + +ifdef DYNAMIC +$(LIBOBJD): $(LIBSRC) + +$(LIBSHARED): $(LIBOBJD) + $(LD_D) -o $@ $(LIBOBJD) $(ELIBS) +endif + +ifdef STATIC +$(LIBOBJS): $(LIBSRC) + +$(LIBSTATIC): $(LIBOBJS) + $(LD) -r -o $@ $(LIBOBJS) $(ELIBS) +endif + +install: all +ifdef DYNAMIC + $(MKDIR) $(FAKEROOT)$(SECUREDIR) + $(INSTALL) -m $(SHLIBMODE) $(LIBSHARED) $(FAKEROOT)$(SECUREDIR) +endif + $(MKDIR) $(FAKEROOT)$(SCONFIGED) + bash -f ./install_conf + +remove: + rm -f $(FAKEROOT)$(SECUREDIR)/$(TITLE).so + rm -f $(FAKEROOT)$(CONFILE) + +clean: + rm -f $(LIBOBJD) $(LIBOBJS) core *~ + rm -f ./.ignore_age + +extraclean: clean + rm -f *.a *.o *.so *.bak + +.c.o: + $(CC) $(CFLAGS) -c $< + diff --git a/modules/pam_group/group.conf b/modules/pam_group/group.conf new file mode 100644 index 00000000..bdd76adb --- /dev/null +++ b/modules/pam_group/group.conf @@ -0,0 +1,60 @@ +## +## Note, to get this to work as it is currently typed you need +## +## 1. to run an application as root +## 2. add the following groups to the /etc/group file: +## floppy, games, sound +## +# +# *** Please note that giving group membership on a session basis is +# *** NOT inherently secure. If a user can create an executable that +# *** is setgid a group that they are infrequently given membership +# *** of, they can basically obtain group membership any time they +# *** like. Example: games are alowed between the hours of 6pm and 6am +# *** user joe logs in at 7pm writes a small C-program toplay.c that +# *** invokes their favorite shell, compiles it and does +# *** "chgrp games toplay; chmod g+s toplay". They are basically able +# *** to play games any time... You have been warned. AGM +# +# this is an example configuration file for the pam_group module. Its +# syntax is based on that of the pam_time module and (at some point in +# the distant past was inspired by the 'shadow' package) +# +# the syntax of the lines is as follows: +# +# services;ttys;users;times;groups +# +# white space is ignored and lines maybe extended with '\\n' (escaped +# newlines). From reading these comments, it is clear that +# text following a '#' is ignored to the end of the line. +# +# the first four fields are described in the pam_time directory. +# The only difference for these is how the time field is interpretted: +# it is used to indicate "when" these groups are to be given to the user. +# +# groups +# The (comma or space separated) list of groups that the user +# inherits membership of. These groups are added if the previous +# fields are satisfied by the user's request +# + +# +# Here is a simple example: running 'xsh' on tty* (any ttyXXX device), +# the user 'us' is given access to the floppy (through membership of +# the floppy group) +# + +#xsh;tty*&!ttyp*;us;Al0000-2400;floppy + +# +# another example: running 'xsh' on tty* (any ttyXXX device), +# the user 'sword' is given access to games (through membership of +# the floppy group) after work hours +# + +#xsh; tty* ;sword;!Wk0900-1800;games, sound +#xsh; tty* ;*;Al0900-1800;floppy + +# +# End of group.conf file +# diff --git a/modules/pam_group/install_conf b/modules/pam_group/install_conf new file mode 100755 index 00000000..03bb7edb --- /dev/null +++ b/modules/pam_group/install_conf @@ -0,0 +1,46 @@ +#!/bin/bash + +CONFILE=$FAKEROOT"$CONFILE" +IGNORE_AGE=./.ignore_age +QUIET_INSTALL=../../.quiet_install +CONF=./group.conf +MODULE=pam_group + +echo + +if [ -f "$QUIET_INSTALL" ]; then + if [ ! -f "$CONFILE" ]; then + yes="y" + else + yes="skip" + fi +elif [ -f "$IGNORE_AGE" ]; then + echo "you don't want to be bothered with the age of your $CONFILE file" + yes="n" +elif [ ! -f "$CONFILE" ] || [ "$CONF" -nt "$CONFILE" ]; then + if [ -f "$CONFILE" ]; then + echo "An older $MODULE configuration file already exists ($CONFILE)" + echo "Do you wish to copy the $CONF file in this distribution" + echo "to $CONFILE ? (y/n) [skip] " + read yes + else + yes="y" + fi +else + yes="skip" +fi + +if [ "$yes" = "y" ]; then + mkdir -p $FAKEROOT$CONFD + echo " copying $CONF to $CONFILE" + cp $CONF $CONFILE +else + echo " Skipping $CONF installation" + if [ "$yes" = "n" ]; then + touch "$IGNORE_AGE" + fi +fi + +echo + +exit 0 diff --git a/modules/pam_group/pam_group.c b/modules/pam_group/pam_group.c new file mode 100644 index 00000000..81953f76 --- /dev/null +++ b/modules/pam_group/pam_group.c @@ -0,0 +1,843 @@ +/* pam_group module */ + +/* + * $Id$ + * + * Written by Andrew Morgan <morgan@linux.kernel.org> 1996/7/6 + */ + +const static char rcsid[] = +"$Id$;\n" +"Version 0.5 for Linux-PAM\n" +"Copyright (c) Andrew G. Morgan 1996 <morgan@linux.kernel.org>\n"; + +#define _BSD_SOURCE + +#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> + +#define PAM_GROUP_CONF CONFILE /* from external define */ +#define PAM_GROUP_BUFLEN 1000 +#define FIELD_SEPARATOR ';' /* this is new as of .02 */ + +typedef enum { FALSE, TRUE } boolean; +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 <security/pam_modules.h> +#include <security/_pam_macros.h> + +/* --- static functions for checking whether the user should be let in --- */ + +static void _log_err(const char *format, ... ) +{ + va_list args; + + va_start(args, format); + openlog("pam_group", LOG_CONS|LOG_PID, LOG_AUTH); + vsyslog(LOG_CRIT, format, args); + va_end(args); + closelog(); +} + +static void shift_bytes(char *mem, int from, int by) +{ + while (by-- > 0) { + *mem = mem[from]; + ++mem; + } +} + +static int read_field(int fd, char **buf, int *from, int *to) +{ + /* is buf set ? */ + + if (! *buf) { + *buf = (char *) malloc(PAM_GROUP_BUFLEN); + if (! *buf) { + _log_err("out of memory"); + return -1; + } + *from = *to = 0; + fd = open(PAM_GROUP_CONF, O_RDONLY); + } + + /* do we have a file open ? return error */ + + if (fd < 0 && *to <= 0) { + _log_err( PAM_GROUP_CONF " not opened"); + memset(*buf, 0, PAM_GROUP_BUFLEN); + _pam_drop(*buf); + return -1; + } + + /* check if there was a newline last time */ + + if ((*to > *from) && (*to > 0) + && ((*buf)[*from] == '\0')) { /* previous line ended */ + (*from)++; + (*buf)[0] = '\0'; + return fd; + } + + /* ready for more data: first shift the buffer's remaining data */ + + *to -= *from; + shift_bytes(*buf, *from, *to); + *from = 0; + (*buf)[*to] = '\0'; + + while (fd >= 0 && *to < PAM_GROUP_BUFLEN) { + int i; + + /* now try to fill the remainder of the buffer */ + + i = read(fd, *to + *buf, PAM_GROUP_BUFLEN - *to); + if (i < 0) { + _log_err("error reading " PAM_GROUP_CONF); + return -1; + } else if (!i) { + close(fd); + fd = -1; /* end of file reached */ + } else + *to += i; + + /* + * contract the buffer. Delete any comments, and replace all + * multiple spaces with single commas + */ + + i = 0; +#ifdef DEBUG_DUMP + D(("buffer=<%s>",*buf)); +#endif + while (i < *to) { + if ((*buf)[i] == ',') { + int j; + + for (j=++i; j<*to && (*buf)[j] == ','; ++j); + if (j!=i) { + shift_bytes(i + (*buf), j-i, (*to) - j); + *to -= j-i; + } + } + switch ((*buf)[i]) { + int j,c; + case '#': + for (j=i; j < *to && (c = (*buf)[j]) != '\n'; ++j); + if (j >= *to) { + (*buf)[*to = ++i] = '\0'; + } else if (c == '\n') { + shift_bytes(i + (*buf), j-i, (*to) - j); + *to -= j-i; + ++i; + } else { + _log_err("internal error in " __FILE__ + " at line %d", __LINE__ ); + return -1; + } + break; + case '\\': + if ((*buf)[i+1] == '\n') { + shift_bytes(i + *buf, 2, *to - (i+2)); + *to -= 2; + } + break; + case '!': + case ' ': + case '\t': + if ((*buf)[i] != '!') + (*buf)[i] = ','; + /* delete any trailing spaces */ + for (j=++i; j < *to && ( (c = (*buf)[j]) == ' ' + || c == '\t' ); ++j); + shift_bytes(i + *buf, j-i, (*to)-j ); + *to -= j-i; + break; + default: + ++i; + } + } + } + + (*buf)[*to] = '\0'; + + /* now return the next field (set the from/to markers) */ + { + int i; + + for (i=0; i<*to; ++i) { + switch ((*buf)[i]) { + case '#': + case '\n': /* end of the line/file */ + (*buf)[i] = '\0'; + *from = i; + return fd; + case FIELD_SEPARATOR: /* end of the field */ + (*buf)[i] = '\0'; + *from = ++i; + return fd; + } + } + *from = i; + (*buf)[*from] = '\0'; + } + + if (*to <= 0) { + D(("[end of text]")); + *buf = NULL; + } + return fd; +} + +/* read a member from a field */ + +static int logic_member(const char *string, int *at) +{ + int len,c,to; + int done=0; + int token=0; + + len=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 == '.') { + token = 1; + } else if (token) { + --to; + done = 1; + } else { + ++*at; + } + } + } while (!done); + + return to - *at; +} + +typedef enum { VAL, OP } expect; + +static boolean logic_field(const void *me, const char *x, int rule, + boolean (*agrees)(const void *, const char * + , int, int)) +{ + boolean 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 == '*') { + right = not ^ agrees(me, x+at, l, rule); + if (oper == AND) + left &= right; + else + left |= right; + next = OP; + } else { + _log_err("garbled syntax; expected name (rule #%d)", rule); + return FALSE; + } + } else { /* OP */ + switch (c) { + case '&': + oper = AND; + break; + case '|': + oper = OR; + break; + default: + _log_err("garbled syntax; expected & or | (rule #%d)" + , rule); + D(("%c at %d",c,at)); + return FALSE; + } + next = VAL; + } + at += l; + } + + return left; +} + +static boolean is_same(const void *A, const char *b, int len, int rule) +{ + 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; + } + } + return ( !len ); +} + +typedef struct { + int day; /* array of 7 bits, one set for today */ + int minute; /* integer, hour*100+minute for now */ +} TIME; + +struct day { + const char *d; + int bit; +} static 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 boolean check_time(const void *AT, const char *times, int len, int rule) +{ + boolean 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 */ + _log_err("internal error: " __FILE__ " line %d", __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) { + _log_err("bad day specified (rule #%d)", rule); + return FALSE; + } + marked_day ^= this_day; + } + if (marked_day == 0) { + _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) { + _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 len,c,to; + int done=0; + int token=0; + + len=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(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 { + _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 */ +#ifdef WANT_PWDB + { + int retval; + const struct pwdb *pw=NULL; + + retval = pwdb_locate("group", PWDB_DEFAULT, buf+at + , PWDB_ID_UNKNOWN, &pw); + if (retval != PWDB_SUCCESS) { + _log_err("bad group: %s; %s", buf+at, pwdb_strerror(retval)); + } else { + const struct pwdb_entry *pwe=NULL; + + D(("group %s exists", buf+at)); + retval = pwdb_get_entry(pw, "gid", &pwe); + if (retval == PWDB_SUCCESS) { + D(("gid = %d [%p]",* (const gid_t *) pwe->value,list)); + (*list)[len++] = * (const gid_t *) pwe->value; + pwdb_entry_delete(&pwe); /* tidy up */ + } else { + _log_err("%s group entry is bad; %s" + , pwdb_strerror(retval)); + } + pw = NULL; /* break link - cached for later use */ + } + } +#else + { + const struct group *grp; + + grp = getgrnam(buf+at); + if (grp == NULL) { + _log_err("bad group: %s", buf+at); + } else { + D(("group %s exists", buf+at)); + (*list)[len++] = grp->gr_gid; + } + } +#endif + + /* next entry along */ + + at += l + edge; + } + D(("returning with [%p/len=%d]->%p",list,len,*list)); + return len; +} + + +static int check_account(const char *service, const char *tty + , const char *user) +{ + int from=0,to=0,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))); + (void) getgroups(no_grps, grps); +#ifdef 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(fd,&buffer,&from,&to); + if (!buffer || !buffer[0]) { + /* empty line .. ? */ + continue; + } + ++count; + D(("working on rule #%d",count)); + + good = logic_field(service, buffer, count, is_same); + D(("with service: %s", good ? "passes":"fails" )); + + /* here we get the terminal name field */ + + fd = read_field(fd,&buffer,&from,&to); + if (!buffer || !buffer[0]) { + _log_err(PAM_GROUP_CONF "; no tty entry #%d", count); + continue; + } + good &= logic_field(tty, buffer, count, is_same); + D(("with tty: %s", good ? "passes":"fails" )); + + /* here we get the username field */ + + fd = read_field(fd,&buffer,&from,&to); + if (!buffer || !buffer[0]) { + _log_err(PAM_GROUP_CONF "; no user entry #%d", count); + continue; + } + good &= logic_field(user, buffer, count, is_same); + D(("with user: %s", good ? "passes":"fails" )); + + /* here we get the time field */ + + fd = read_field(fd,&buffer,&from,&to); + if (!buffer || !buffer[0]) { + _log_err(PAM_GROUP_CONF "; no time entry #%d", count); + continue; + } + + good &= logic_field(&here_and_now, buffer, count, check_time); + D(("with time: %s", good ? "passes":"fails" )); + + fd = read_field(fd,&buffer,&from,&to); + if (!buffer || !buffer[0]) { + _log_err(PAM_GROUP_CONF "; no listed groups for rule #%d" + , 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(buffer, &grps, no_grps); + if (good < 0) { + no_grps = 0; + } else { + no_grps = good; + } + } + + /* check the line is terminated correctly */ + + fd = read_field(fd,&buffer,&from,&to); + if (buffer && buffer[0]) { + _log_err(PAM_GROUP_CONF "; poorly terminated rule #%d", count); + } + + 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 (buffer); + + /* now set the groups for the user */ + + if (no_grps > 0) { + int err; + D(("trying to set %d groups", no_grps)); +#ifdef DEBUG + for (err=0; err<no_grps; ++err) { + D(("gid[%d]=%d", err, grps[err])); + } +#endif + if ((err = setgroups(no_grps, grps))) { + D(("but couldn't set groups %d", err)); + _log_err("unable to set the group membership for user (err=%d)" + , err); + 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 --- */ + +PAM_EXTERN int pam_sm_authenticate(pam_handle_t *pamh, int flags + , int argc, const char **argv) +{ + return PAM_IGNORE; +} + +PAM_EXTERN int pam_sm_setcred(pam_handle_t *pamh, int flags + , int argc, const char **argv) +{ + const char *service=NULL, *tty=NULL; + const char *user=NULL; + int retval; + unsigned setting; + + /* only interested in establishing credentials */ + + setting = flags; + if (!(setting & PAM_ESTABLISH_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, (const void **)&service) + != PAM_SUCCESS || service == NULL) { + _log_err("cannot find the current service name"); + return PAM_ABORT; + } + + /* set username */ + + if (pam_get_user(pamh, &user, NULL) != PAM_SUCCESS || user == NULL + || *user == '\0') { + _log_err("cannot determine the user's name"); + return PAM_USER_UNKNOWN; + } + + /* set tty name */ + + if (pam_get_item(pamh, PAM_TTY, (const void **)&tty) != PAM_SUCCESS + || tty == NULL) { + D(("PAM_TTY not set, probing stdin")); + tty = ttyname(STDIN_FILENO); + if (tty == NULL) { + _log_err("couldn't get the tty name"); + return PAM_ABORT; + } + if (pam_set_item(pamh, PAM_TTY, tty) != PAM_SUCCESS) { + _log_err("couldn't set tty name"); + return PAM_ABORT; + } + } + + if (strncmp("/dev/",tty,5) == 0) { /* strip leading /dev/ */ + tty += 5; + } + + /* 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)); + +#ifdef WANT_PWDB + + /* We initialize the pwdb library and check the account */ + retval = pwdb_start(); /* initialize */ + if (retval == PWDB_SUCCESS) { + retval = check_account(service,tty,user); /* get groups */ + (void) pwdb_end(); /* tidy up */ + } else { + D(("failed to initialize pwdb; %s", pwdb_strerror(retval))); + _log_err("unable to initialize libpwdb"); + retval = PAM_ABORT; + } + +#else /* WANT_PWDB */ + retval = check_account(service,tty,user); /* get groups */ +#endif /* WANT_PWDB */ + + return retval; +} + +/* end of module definition */ + +#ifdef PAM_STATIC + +/* static module data */ + +struct pam_module _pam_group_modstruct = { + "pam_group", + pam_sm_authenticate, + pam_sm_setcred, + NULL, + NULL, + NULL, + NULL +}; +#endif |