4
* $Id: pam_group.c,v 1.7 2004/01/12 06:47:14 hartmans Exp $
6
* Written by Andrew Morgan <morgan@linux.kernel.org> 1996/7/6
9
const static char rcsid[] =
10
"$Id: pam_group.c,v 1.7 2004/01/12 06:47:14 hartmans Exp $;\n"
11
"Version 0.5 for Linux-PAM\n"
12
"Copyright (c) Andrew G. Morgan 1996 <morgan@linux.kernel.org>\n";
27
#include <sys/types.h>
31
#ifdef DEFAULT_CONF_FILE
32
# define PAM_GROUP_CONF DEFAULT_CONF_FILE /* from external define */
34
# define PAM_GROUP_CONF "/etc/security/group.conf"
36
#define PAM_GROUP_BUFLEN 1000
37
#define FIELD_SEPARATOR ';' /* this is new as of .02 */
46
typedef enum { FALSE, TRUE } boolean;
47
typedef enum { AND, OR } operator;
50
* here, we make definitions for the externally accessible functions
51
* in this file (these definitions are required for static modules
52
* but strongly encouraged generally) they are used to instruct the
53
* modules include file to define their prototypes.
58
#include <security/pam_modules.h>
59
#include <security/_pam_macros.h>
61
/* --- static functions for checking whether the user should be let in --- */
63
static void _log_err(const char *format, ... )
67
va_start(args, format);
68
openlog("pam_group", LOG_CONS|LOG_PID, LOG_AUTH);
69
vsyslog(LOG_CRIT, format, args);
74
static void shift_bytes(char *mem, int from, int by)
82
static int read_field(int fd, char **buf, int *from, int *to)
87
*buf = (char *) malloc(PAM_GROUP_BUFLEN);
89
_log_err("out of memory");
93
fd = open(PAM_GROUP_CONF, O_RDONLY);
96
/* do we have a file open ? return error */
98
if (fd < 0 && *to <= 0) {
99
_log_err( PAM_GROUP_CONF " not opened");
100
memset(*buf, 0, PAM_GROUP_BUFLEN);
105
/* check if there was a newline last time */
107
if ((*to > *from) && (*to > 0)
108
&& ((*buf)[*from] == '\0')) { /* previous line ended */
114
/* ready for more data: first shift the buffer's remaining data */
117
shift_bytes(*buf, *from, *to);
121
while (fd >= 0 && *to < PAM_GROUP_BUFLEN) {
124
/* now try to fill the remainder of the buffer */
126
i = read(fd, *to + *buf, PAM_GROUP_BUFLEN - *to);
128
_log_err("error reading " PAM_GROUP_CONF);
132
fd = -1; /* end of file reached */
137
* contract the buffer. Delete any comments, and replace all
138
* multiple spaces with single commas
143
D(("buffer=<%s>",*buf));
147
if ((*buf)[i] == ',') {
148
for (j=++i; j<*to && (*buf)[j] == ','; ++j);
150
shift_bytes(i + (*buf), j-i, (*to) - j);
156
for (j=i; j < *to && (c = (*buf)[j]) != '\n'; ++j);
158
(*buf)[*to = ++i] = '\0';
159
} else if (c == '\n') {
160
shift_bytes(i + (*buf), j-i, (*to) - j);
164
_log_err("internal error in " __FILE__
165
" at line %d", __LINE__ );
170
if ((*buf)[i+1] == '\n') {
171
shift_bytes(i + *buf, 2, *to - (i+2));
174
++i; /* we don't escape non-newline characters */
180
if ((*buf)[i] != '!')
182
/* delete any trailing spaces */
183
for (j=++i; j < *to && ( (c = (*buf)[j]) == ' '
184
|| c == '\t' ); ++j);
185
shift_bytes(i + *buf, j-i, (*to)-j );
196
/* now return the next field (set the from/to markers) */
200
for (i=0; i<*to; ++i) {
203
case '\n': /* end of the line/file */
207
case FIELD_SEPARATOR: /* end of the field */
214
(*buf)[*from] = '\0';
218
D(("[end of text]"));
224
/* read a member from a field */
226
static int logic_member(const char *string, int *at)
254
if (isalpha(c) || c == '*' || isdigit(c) || c == '_'
255
|| c == '-' || c == '.' || c == '/'
270
typedef enum { VAL, OP } expect;
272
static boolean logic_field(const void *me, const char *x, int rule,
273
boolean (*agrees)(const void *, const char *
276
boolean left=FALSE, right, not=FALSE;
281
while ((l = logic_member(x,&at))) {
287
else if (isalnum(c) || c == '*' || c == ':' || c == '/') {
288
right = not ^ agrees(me, x+at, l, rule);
295
_log_err("garbled syntax; expected name (rule #%d)", rule);
307
_log_err("garbled syntax; expected & or | (rule #%d)"
309
D(("%c at %d",c,at));
320
static boolean is_same(const void *A, const char *b, int len, int rule)
326
for (i=0; len > 0; ++i, --len) {
329
return (!--len || !strncmp(b+i,a+strlen(a)-len,len));
338
int day; /* array of 7 bits, one set for today */
339
int minute; /* integer, hour*100+minute for now */
345
} static const days[11] = {
359
static TIME time_now(void)
365
the_time = time((time_t *)0); /* get the current time */
366
local = localtime(&the_time);
367
this.day = days[local->tm_wday].bit;
368
this.minute = local->tm_hour*100 + local->tm_min;
370
D(("day: 0%o, time: %.4d", this.day, this.minute));
374
/* take the current date and see if the range "date" passes it */
375
static boolean check_time(const void *AT, const char *times, int len, int rule)
378
int marked_day, time_start, time_end;
383
D(("checking: 0%o/%.4d vs. %s", at->day, at->minute, times));
386
/* this should not happen */
387
_log_err("internal error: " __FILE__ " line %d", __LINE__);
391
if (times[j] == '!') {
398
for (marked_day = 0; len > 0 && isalpha(times[j]); --len) {
401
D(("%c%c ?", times[j], times[j+1]));
402
for (i=0; days[i].d != NULL; ++i) {
403
if (tolower(times[j]) == days[i].d[0]
404
&& tolower(times[j+1]) == days[i].d[1] ) {
405
this_day = days[i].bit;
410
if (this_day == -1) {
411
_log_err("bad day specified (rule #%d)", rule);
414
marked_day ^= this_day;
416
if (marked_day == 0) {
417
_log_err("no day specified");
420
D(("day range = 0%o", marked_day));
423
for (i=0; len > 0 && i < 4 && isdigit(times[i+j]); ++i, --len) {
425
time_start += times[i+j]-'0'; /* is this portable? */
429
if (times[j] == '-') {
431
for (i=1; len > 0 && i < 5 && isdigit(times[i+j]); ++i, --len) {
433
time_end += times[i+j]-'0'; /* is this portable? */
439
D(("i=%d, time_end=%d, times[j]='%c'", i, time_end, times[j]));
440
if (i != 5 || time_end == -1) {
441
_log_err("no/bad times specified (rule #%d)", rule);
444
D(("times(%d to %d)", time_start,time_end));
445
D(("marked_day = 0%o", marked_day));
447
/* compare with the actual time now */
450
if (time_start < time_end) { /* start < end ? --> same day */
451
if ((at->day & marked_day) && (at->minute >= time_start)
452
&& (at->minute < time_end)) {
453
D(("time is listed"));
456
} else { /* spans two days */
457
if ((at->day & marked_day) && (at->minute >= time_start)) {
458
D(("caught on first day"));
462
marked_day |= (marked_day & 0200) ? 1:0;
463
D(("next day = 0%o", marked_day));
464
if ((at->day & marked_day) && (at->minute <= time_end)) {
465
D(("caught on second day"));
474
static int find_member(const char *string, int *at)
502
if (isalpha(c) || isdigit(c) || c == '_' || c == '*'
518
#define blk_size(len) (((len-1 + GROUP_BLK)/GROUP_BLK)*GROUP_BLK)
520
static int mkgrplist(char *buf, gid_t **list, int len)
525
blks = blk_size(len);
526
D(("cf. blks=%d and len=%d", blks,len));
528
while ((l = find_member(buf,&at))) {
534
D(("allocating new block"));
535
tmp = (gid_t *) realloc((*list)
536
, sizeof(gid_t) * (blks += GROUP_BLK));
540
_log_err("out of memory for group list");
547
/* '\0' terminate the entry */
549
edge = (buf[at+l]) ? 1:0;
551
D(("found group: %s",buf+at));
553
/* this is where we convert a group name to a gid_t */
557
const struct pwdb *pw=NULL;
559
retval = pwdb_locate("group", PWDB_DEFAULT, buf+at
560
, PWDB_ID_UNKNOWN, &pw);
561
if (retval != PWDB_SUCCESS) {
562
_log_err("bad group: %s; %s", buf+at, pwdb_strerror(retval));
564
const struct pwdb_entry *pwe=NULL;
566
D(("group %s exists", buf+at));
567
retval = pwdb_get_entry(pw, "gid", &pwe);
568
if (retval == PWDB_SUCCESS) {
569
D(("gid = %d [%p]",* (const gid_t *) pwe->value,list));
570
(*list)[len++] = * (const gid_t *) pwe->value;
571
pwdb_entry_delete(&pwe); /* tidy up */
573
_log_err("%s group entry is bad; %s"
574
, pwdb_strerror(retval));
576
pw = NULL; /* break link - cached for later use */
581
const struct group *grp;
583
grp = getgrnam(buf+at);
585
_log_err("bad group: %s", buf+at);
587
D(("group %s exists", buf+at));
588
(*list)[len++] = grp->gr_gid;
593
/* next entry along */
597
D(("returning with [%p/len=%d]->%p",list,len,*list));
602
static int check_account(const char *service, const char *tty
605
int from=0,to=0,fd=-1;
609
int retval=PAM_SUCCESS;
614
* first we get the current list of groups - the application
615
* will have previously done an initgroups(), or equivalent.
618
D(("counting supplementary groups"));
619
no_grps = getgroups(0, NULL); /* find the current number of groups */
621
grps = calloc( blk_size(no_grps) , sizeof(gid_t) );
622
D(("copying current list into grps [%d big]",blk_size(no_grps)));
623
(void) getgroups(no_grps, grps);
627
for (z=0; z<no_grps; ++z) {
628
D(("gid[%d]=%d", z, grps[z]));
633
D(("no supplementary groups known"));
638
here_and_now = time_now(); /* find current time */
640
/* parse the rules in the configuration file */
644
/* here we get the service name field */
646
fd = read_field(fd,&buffer,&from,&to);
647
if (!buffer || !buffer[0]) {
648
/* empty line .. ? */
652
D(("working on rule #%d",count));
654
good = logic_field(service, buffer, count, is_same);
655
D(("with service: %s", good ? "passes":"fails" ));
657
/* here we get the terminal name field */
659
fd = read_field(fd,&buffer,&from,&to);
660
if (!buffer || !buffer[0]) {
661
_log_err(PAM_GROUP_CONF "; no tty entry #%d", count);
664
good &= logic_field(tty, buffer, count, is_same);
665
D(("with tty: %s", good ? "passes":"fails" ));
667
/* here we get the username field */
669
fd = read_field(fd,&buffer,&from,&to);
670
if (!buffer || !buffer[0]) {
671
_log_err(PAM_GROUP_CONF "; no user entry #%d", count);
674
good &= logic_field(user, buffer, count, is_same);
675
D(("with user: %s", good ? "passes":"fails" ));
677
/* here we get the time field */
679
fd = read_field(fd,&buffer,&from,&to);
680
if (!buffer || !buffer[0]) {
681
_log_err(PAM_GROUP_CONF "; no time entry #%d", count);
685
good &= logic_field(&here_and_now, buffer, count, check_time);
686
D(("with time: %s", good ? "passes":"fails" ));
688
fd = read_field(fd,&buffer,&from,&to);
689
if (!buffer || !buffer[0]) {
690
_log_err(PAM_GROUP_CONF "; no listed groups for rule #%d"
696
* so we have a list of groups, we need to turn it into
697
* something to send to setgroups(2)
701
D(("adding %s to gid list", buffer));
702
good = mkgrplist(buffer, &grps, no_grps);
710
/* check the line is terminated correctly */
712
fd = read_field(fd,&buffer,&from,&to);
713
if (buffer && buffer[0]) {
714
_log_err(PAM_GROUP_CONF "; poorly terminated rule #%d", count);
718
D(("rule #%d passed, added %d groups", count, good));
719
} else if (good < 0) {
720
retval = PAM_BUF_ERR;
722
D(("rule #%d failed", count));
727
/* now set the groups for the user */
731
D(("trying to set %d groups", no_grps));
733
for (err=0; err<no_grps; ++err) {
734
D(("gid[%d]=%d", err, grps[err]));
737
if ((err = setgroups(no_grps, grps))) {
738
D(("but couldn't set groups %d", err));
739
_log_err("unable to set the group membership for user (err=%d)"
741
retval = PAM_CRED_ERR;
745
if (grps) { /* tidy up */
746
memset(grps, 0, sizeof(gid_t) * blk_size(no_grps));
754
/* --- public authentication management functions --- */
756
PAM_EXTERN int pam_sm_authenticate(pam_handle_t *pamh, int flags
757
, int argc, const char **argv)
762
PAM_EXTERN int pam_sm_setcred(pam_handle_t *pamh, int flags
763
, int argc, const char **argv)
765
const char *service=NULL, *tty=NULL;
766
const char *user=NULL;
770
/* only interested in establishing credentials */
771
/* PAM docs say that an empty flag is to be treated as PAM_ESTABLISH_CRED.
772
Some people just pass PAM_SILENT, so cope with it, too. */
775
if (!((setting & PAM_ESTABLISH_CRED) || (setting & PAM_REINITIALIZE_CRED)
776
|| (setting == 0) || (setting == PAM_SILENT) )) {
777
D(("ignoring call - not for establishing credentials"));
778
return PAM_SUCCESS; /* don't fail because of this */
781
/* set service name */
783
if (pam_get_item(pamh, PAM_SERVICE, (const void **)&service)
784
!= PAM_SUCCESS || service == NULL) {
785
_log_err("cannot find the current service name");
791
if (pam_get_user(pamh, &user, NULL) != PAM_SUCCESS || user == NULL
793
_log_err("cannot determine the user's name");
794
return PAM_USER_UNKNOWN;
799
if (pam_get_item(pamh, PAM_TTY, (const void **)&tty) != PAM_SUCCESS
801
D(("PAM_TTY not set, probing stdin"));
802
tty = ttyname(STDIN_FILENO);
804
_log_err("couldn't get the tty name");
807
if (pam_set_item(pamh, PAM_TTY, tty) != PAM_SUCCESS) {
808
_log_err("couldn't set tty name");
813
if (strncmp("/dev/",tty,5) == 0) { /* strip leading /dev/ */
817
/* good, now we have the service name, the user and the terminal name */
819
D(("service=%s", service));
820
D(("user=%s", user));
825
/* We initialize the pwdb library and check the account */
826
retval = pwdb_start(); /* initialize */
827
if (retval == PWDB_SUCCESS) {
828
retval = check_account(service,tty,user); /* get groups */
829
(void) pwdb_end(); /* tidy up */
831
D(("failed to initialize pwdb; %s", pwdb_strerror(retval)));
832
_log_err("unable to initialize libpwdb");
836
#else /* WANT_PWDB */
837
retval = check_account(service,tty,user); /* get groups */
838
#endif /* WANT_PWDB */
843
/* end of module definition */
847
/* static module data */
849
struct pam_module _pam_group_modstruct = {