summaryrefslogtreecommitdiff
path: root/modules/pam_group
diff options
context:
space:
mode:
authorAndrew G. Morgan <morgan@kernel.org>2000-06-20 22:10:38 +0000
committerAndrew G. Morgan <morgan@kernel.org>2000-06-20 22:10:38 +0000
commitea488580c42e8918445a945484de3c8a5addc761 (patch)
treec992f3ba699caafedfadc16af38e6359c3c24698 /modules/pam_group
Initial revision
Diffstat (limited to 'modules/pam_group')
-rw-r--r--modules/pam_group/.cvsignore1
-rw-r--r--modules/pam_group/Makefile98
-rw-r--r--modules/pam_group/group.conf60
-rwxr-xr-xmodules/pam_group/install_conf46
-rw-r--r--modules/pam_group/pam_group.c843
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