1
/*----------------------------------------------------------------------
2
* Modified for Linux-PAM by Al Longyear <longyear@netcom.com> 96/5/5
3
* Modifications, Cristian Gafton 97/2/8
4
* Modifications, Peter Allgeyer 97/3
5
* Modifications (netgroups and fixes), Nicolai Langfeldt 97/3/21
6
* Security fix: 97/10/2 - gethostbyname called repeatedly without care
7
* Modification (added privategroup option) Andrew <morgan@transmeta.com>
8
*----------------------------------------------------------------------
9
* Copyright (c) 1983, 1993, 1994
10
* The Regents of the University of California. All rights reserved.
12
* Redistribution and use in source and binary forms, with or without
13
* modification, are permitted provided that the following conditions
15
* 1. Redistributions of source code must retain the above copyright
16
* notice, this list of conditions and the following disclaimer.
17
* 2. Redistributions in binary form must reproduce the above copyright
18
* notice, this list of conditions and the following disclaimer in the
19
* documentation and/or other materials provided with the distribution.
20
* 3. All advertising materials mentioning features or use of this software
21
* must display the following acknowledgement:
22
* This product includes software developed by the University of
23
* California, Berkeley and its contributors.
24
* 4. Neither the name of the University nor the names of its contributors
25
* may be used to endorse or promote products derived from this software
26
* without specific prior written permission.
28
* THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
29
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
30
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
31
* ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
32
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
33
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
34
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
35
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
36
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
37
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
41
#include <security/_pam_aconf.h>
43
#define USER_RHOSTS_FILE "/.rhosts" /* prefixed by user's home dir */
49
#ifdef HAVE_SYS_FSUID_H
50
#include <sys/fsuid.h>
51
#endif /* HAVE_SYS_FSUID_H */
57
#include <sys/types.h>
62
#include <sys/param.h>
63
#include <sys/socket.h>
64
#include <netinet/in.h>
65
#include <netdb.h> /* This is supposed(?) to contain the following */
66
int innetgr(const char *, const char *, const char *,const char *);
71
#include <arpa/inet.h>
82
# include <linux/sockios.h>
85
# include <sys/fsuid.h>
86
# endif /* __USE_MISC */
92
#include <sys/signal.h>
96
#define _PATH_HEQUIV "/etc/hosts.equiv"
97
#endif /* _PATH_HEQUIV */
99
#define PAM_SM_AUTH /* only defines this management group */
101
#include <security/pam_modules.h>
102
#include <security/_pam_macros.h>
103
#include <security/_pam_modutil.h>
105
#ifdef _ISOC9X_SOURCE
106
#include <inttypes.h>
109
/* to the best of my knowledge, all modern UNIX boxes have 32 bit integers */
110
#define U32 unsigned int
111
#endif /* _ISOC9X_SOURCE */
115
* Options for this module
119
int opt_no_hosts_equiv;
120
int opt_hosts_equiv_rootok;
124
int opt_disallow_null_authtok;
128
int opt_private_group;
129
int opt_no_uid_check;
130
const char *superuser;
131
const char *last_error;
135
static void _pam_log(int err, const char *format, ...)
139
va_start(args, format);
140
openlog("pam_rhosts_auth", LOG_CONS|LOG_PID, LOG_AUTH);
141
vsyslog(err, format, args);
146
static void set_option (struct _options *opts, const char *arg)
148
if (strcmp(arg, "no_hosts_equiv") == 0) {
149
opts->opt_no_hosts_equiv = 1;
153
if (strcmp(arg, "hosts_equiv_rootok") == 0) {
154
opts->opt_hosts_equiv_rootok = 1;
158
if (strcmp(arg, "no_rhosts") == 0) {
159
opts->opt_no_rhosts = 1;
163
if (strcmp(arg, "debug") == 0) {
164
D(("debugging enabled"));
169
if (strcmp(arg, "no_warn") == 0) {
170
opts->opt_nowarn = 1;
174
if (strcmp(arg, "promiscuous") == 0) {
175
opts->opt_promiscuous = 1; /* used to permit '+' in ...hosts file */
179
if (strcmp(arg, "suppress") == 0) {
180
opts->opt_suppress = 1; /* used to suppress failure warning message */
184
if (strcmp(arg, "privategroup") == 0) {
185
opts->opt_private_group = 1; /* used to permit group write on .rhosts
186
file if group has same name as owner */
190
if (strcmp(arg, "no_uid_check") == 0) {
191
opts->opt_no_uid_check = 1; /* NIS optimization */
195
if (strcmp(arg, "superuser=") == 0) {
196
opts->superuser = arg+sizeof("superuser=")-1;
200
* All other options are ignored at the present time.
202
_pam_log(LOG_WARNING, "unrecognized option '%s'", arg);
205
static void set_parameters (struct _options *opts, int flags,
206
int argc, const char **argv)
208
opts->opt_silent = flags & PAM_SILENT;
209
opts->opt_disallow_null_authtok = flags & PAM_DISALLOW_NULL_AUTHTOK;
212
set_option (opts, *argv);
218
* Obtain the name of the remote host. Currently, this is simply by
219
* requesting the contents of the PAM_RHOST item.
222
static int pam_get_rhost(pam_handle_t *pamh, const char **rhost
223
, const char *prompt)
228
retval = pam_get_item (pamh, PAM_RHOST, (const void **)¤t);
229
if (retval != PAM_SUCCESS)
232
if (current == NULL) {
237
return retval; /* pass on any error from conversation */
241
* Obtain the name of the remote user. Currently, this is simply by
242
* requesting the contents of the PAM_RUSER item.
245
static int pam_get_ruser(pam_handle_t *pamh, const char **ruser,
251
retval = pam_get_item (pamh, PAM_RUSER, (const void **)¤t);
252
if (retval != PAM_SUCCESS) {
256
if (current == NULL) {
261
return retval; /* pass on any error from conversation */
265
* Returns 1 if positive match, 0 if no match, -1 if negative match.
269
__icheckhost (pam_handle_t *pamh, struct _options *opts, U32 raddr
270
, register char *lhost, const char *rhost)
274
int negate=1; /* Multiply return with this to get -1 instead of 1 */
277
/* Check nis netgroup. We assume that pam has done all needed
278
paranoia checking before we are handed the rhost */
279
if (strncmp("+@",lhost,2) == 0)
280
return(innetgr(&lhost[2],rhost,NULL,NULL));
282
if (strncmp("-@",lhost,2) == 0)
283
return(-innetgr(&lhost[2],rhost,NULL,NULL));
286
if (strncmp("-",lhost,1) == 0) {
289
} else if (strcmp("+",lhost) == 0) {
290
(void) pam_get_item(pamh, PAM_USER, (const void **)&user);
291
D(("user %s has a `+' host entry", user));
292
if (opts->opt_promiscuous)
293
return (1); /* asking for trouble, but ok.. */
294
/* If not promiscuous: handle as negative */
296
} else if (strncmp("+",lhost,1) == 0) {
297
/* '+hostname' is supposed to be equivalent to 'hostname' */
302
/* Try for raw ip address first. */
303
if (isdigit(*lhost) && (long)(laddr = inet_addr(lhost)) != -1)
304
return (negate*(! (raddr ^ laddr)));
306
/* Better be a hostname. */
307
hp = gethostbyname(lhost);
311
/* Spin through ip addresses. */
312
for (pp = hp->h_addr_list; *pp; ++pp)
313
if (!memcmp (&raddr, *pp, sizeof (U32)))
320
/* Returns 1 on positive match, 0 on no match, -1 on negative match */
322
static int __icheckuser(pam_handle_t *pamh, struct _options *opts
323
, const char *luser, const char *ruser
327
luser is user entry from .rhosts/hosts.equiv file
328
ruser is user id on remote host
329
rhost is the remote host name
334
if (strncmp("+@",luser,2) == 0)
335
return (innetgr(&luser[2],NULL,ruser,NULL));
337
if (strncmp("-@",luser,2) == 0)
338
return (-innetgr(&luser[2],NULL,ruser,NULL));
341
if (strncmp("-",luser,1) == 0)
342
return(-(strcmp(&luser[1],ruser) == 0));
345
if (strcmp("+",luser) == 0) {
346
(void) pam_get_item(pamh, PAM_USER, (const void **)&user);
347
_pam_log(LOG_WARNING, "user %s has a `+' user entry", user);
348
if (opts->opt_promiscuous)
350
/* If not promiscuous we handle it as a negative match */
354
/* simple string match */
355
return (strcmp(ruser, luser) == 0);
359
* Returns 1 for blank lines (or only comment lines) and 0 otherwise
362
static int __isempty(char *p)
364
while (*p && isspace(*p)) {
368
return (*p == '\0' || *p == '#') ? 1:0 ;
372
* Returns 0 if positive match, 1 if _not_ ok.
376
__ivaliduser (pam_handle_t *pamh, struct _options *opts,
377
FILE *hostf, U32 raddr,
378
const char *luser, const char *ruser, const char *rhost)
380
register const char *user;
383
#ifndef MAXHOSTNAMELEN
386
/* XXX definitely should check for getline if should be portable */
387
while (getline(&buf,&buflen,hostf)) {
389
char buf[MAXHOSTNAMELEN + 128]; /* host + login */
391
buf[sizeof (buf)-1] = '\0'; /* terminate line */
393
while (fgets(buf, sizeof(buf), hostf) != NULL) { /* hostf file line */
395
p = buf; /* from beginning of file.. */
397
/* Skip empty or comment lines */
402
/* Skip lines that are too long. */
403
if (strchr(p, '\n') == NULL) {
404
int ch = getc(hostf);
406
while (ch != '\n' && ch != EOF)
412
* If there is a hostname at the start of the line. Set it to
413
* lower case. A leading ' ' or '\t' indicates no hostname
416
for (;*p && !isspace(*p); ++p) {
421
* next we want to find the permitted name for the remote user
424
if (*p == ' ' || *p == '\t') {
426
/* <nul> terminate hostname and skip spaces */
427
for (*p++='\0'; *p && isspace(*p); ++p);
429
user = p; /* this is the user's name */
430
while (*p && !isspace(*p))
431
++p; /* find end of user's name */
435
*p = '\0'; /* <nul> terminate username (+host?) */
437
/* buf -> host(?) ; user -> username(?) */
439
/* First check host part */
440
hcheck=__icheckhost(pamh, opts, raddr, buf, rhost);
443
#ifndef MAXHOSTNAMELEN
450
/* Then check user part */
454
ucheck=__icheckuser(pamh, opts, user, ruser, rhost);
456
/* Positive 'host user' match? */
458
#ifndef MAXHOSTNAMELEN
464
/* Negative 'host -user' match? */
466
#ifndef MAXHOSTNAMELEN
472
/* Neither, go on looking for match */
475
#ifndef MAXHOSTNAMELEN
483
* New .rhosts strategy: We are passed an ip address. We spin through
484
* hosts.equiv and .rhosts looking for a match. When the .rhosts only
485
* has ip addresses, we don't have to trust a nameserver. When it
486
* contains hostnames, we spin through the list of addresses the nameserver
487
* gives us and look for a match.
489
* Returns 0 if ok, -1 if not ok.
493
pam_iruserok(pam_handle_t *pamh,
494
struct _options *opts, U32 raddr, int superuser,
495
const char *ruser, const char *luser, const char *rhost)
507
char pbuf[MAXPATHLEN]; /* potential buffer overrun */
510
if ((!superuser||opts->opt_hosts_equiv_rootok) && !opts->opt_no_hosts_equiv ) {
512
/* try to open system hosts.equiv file */
513
hostf = fopen (_PATH_HEQUIV, "r");
515
answer = __ivaliduser(pamh, opts, hostf, raddr, luser
517
(void) fclose(hostf);
519
return 0; /* remote host is equivalent to localhost */
521
No hosts.equiv file on system.
525
if ( opts->opt_no_rhosts )
529
* Identify user's local .rhosts file
532
pwd = _pammodutil_getpwnam(pamh, luser);
535
* luser is assumed to be valid because of an earlier check for uid = 0
536
* we don't log this error twice. However, this shouldn't happen !
543
pblen=strlen(pwd->pw_dir) + sizeof(USER_RHOSTS_FILE) + 2;
544
if(!(pbuf=malloc(pblen))){
546
opts->last_error="Memory allocation failed.";
550
/* check for buffer overrun */
551
if (strlen(pwd->pw_dir) + sizeof(USER_RHOSTS_FILE) + 2 >= MAXPATHLEN) {
553
_pam_log(LOG_DEBUG,"home directory for `%s' is too long", luser);
554
return 1; /* to dangerous to try */
558
(void) strcpy(pbuf, pwd->pw_dir);
559
(void) strcat(pbuf, USER_RHOSTS_FILE);
562
* Change effective uid while _reading_ .rhosts. (not just
563
* opening). If root and reading an NFS mounted file system,
564
* can't read files that are 0600 as .rhosts files should be.
567
/* We are root, this will not fail */
569
/* If we are on linux the better way is setfsuid */
570
uid = setfsuid(pwd->pw_uid);
571
hostf = fopen(pbuf, "r");
574
(void) seteuid(pwd->pw_uid);
575
hostf = fopen(pbuf, "r");
580
_pam_log(LOG_DEBUG,"Could not open %s file",pbuf);
586
* If not a regular file, or is owned by someone other than
587
* user or root or if writeable by anyone but the owner, quit.
591
if (lstat(pbuf, &sbuf) < 0 || !S_ISREG(sbuf.st_mode))
592
cp = ".rhosts not regular file";
593
else if (fstat(fileno(hostf), &sbuf) < 0)
594
cp = ".rhosts fstat failed";
595
else if (sbuf.st_uid && sbuf.st_uid != pwd->pw_uid)
596
cp = "bad .rhosts owner";
597
else if (sbuf.st_mode & S_IWOTH)
598
cp = ".rhosts writable by other!";
599
else if (sbuf.st_mode & S_IWGRP) {
601
/* private group caveat */
602
if (opts->opt_private_group) {
603
struct group *grp = getgrgid(sbuf.st_gid);
605
if (NULL == grp || NULL == grp->gr_name
606
|| strcmp(luser,grp->gr_name)) {
607
cp = ".rhosts writable by public group";
608
} else if (grp->gr_mem) {
611
/* require at most one member (luser) of this group */
612
for (gcount=0; grp->gr_mem[gcount]; ++gcount) {
613
if (strcmp(grp->gr_mem[gcount], luser)) {
619
cp = ".rhosts writable by other members of group";
623
cp = ".rhosts writable by group";
626
} /* It is _NOT_ safe to append an else here... Do so prior to
629
/* If there were any problems, quit. */
631
opts->last_error = cp;
636
answer = __ivaliduser (pamh, opts, hostf, raddr, luser, ruser, rhost);
640
* Go here to exit after the fsuid/euid has been adjusted so that
641
* they are reset before we exit.
654
(void) fclose(hostf);
660
pam_ruserok (pam_handle_t *pamh,
661
struct _options *opts, const char *rhost, int superuser,
662
const char *ruser, const char *luser)
665
int answer = 1; /* default to failure */
669
opts->last_error = (char *) 0;
670
hp = gethostbyname(rhost); /* identify host */
673
/* First of all check the address length */
674
if (hp->h_length != 4) {
675
_pam_log(LOG_ALERT, "pam_rhosts module can't work with not IPv4 "
677
return 1; /* not allowed */
680
/* loop though address list */
681
for (n = 0; hp->h_addr_list[n]; n++);
682
D(("rhosts: %d addresses", n));
685
addrs = calloc (n, hp->h_length);
686
for (i = 0; i < n; i++)
687
memcpy (addrs+i, hp->h_addr_list[i], hp->h_length);
689
for (i = 0; i < n && answer; i++) {
690
D(("rhosts: address %d is %04x", i, addrs[i]));
691
answer = pam_iruserok(pamh, opts, addrs[i], superuser,
692
ruser, luser, rhost);
693
/* answer == 0 means success */
704
* Internal function to do authentication
707
static int _pam_auth_rhosts (pam_handle_t *pamh,
713
const char *luser = NULL;
714
const char *ruser = NULL, *rhost = NULL;
715
struct _options opts;
719
* Look at the options and set the flags accordingly.
721
memset (&opts, 0, sizeof (opts));
722
set_parameters (&opts, flags, argc, argv);
724
* Obtain the parameters for the various items
726
for (;;) { /* abuse loop to avoid goto */
728
/* get the remotehost */
729
D(("getting rhost"));
730
retval = pam_get_rhost(pamh, &rhost, NULL);
731
(void) pam_set_item(pamh, PAM_RHOST, rhost);
732
if (retval != PAM_SUCCESS) {
733
if (opts.opt_debug) {
734
_pam_log(LOG_DEBUG, "could not get the remote host name");
739
/* get the remote user */
740
D(("getting ruser"));
741
retval = pam_get_ruser(pamh, &ruser, NULL);
742
(void) pam_set_item(pamh, PAM_RUSER, ruser);
743
if (retval != PAM_SUCCESS) {
745
_pam_log(LOG_DEBUG, "could not get the remote username");
749
/* get the local user */
751
retval = pam_get_user(pamh, &luser, NULL);
752
if (retval != PAM_SUCCESS) {
754
_pam_log(LOG_DEBUG, "could not determine name of local user");
758
if (opts.superuser && !strcmp(opts.superuser, luser)) {
762
/* check if the luser uid == 0... --cristiang */
763
if (! opts.opt_no_uid_check) {
764
struct passwd *luser_pwd;
766
luser_pwd = _pammodutil_getpwnam(pamh, luser);
767
if (luser_pwd == NULL) {
769
_pam_log(LOG_DEBUG, "user '%s' unknown to this system",
771
retval = PAM_AUTH_ERR;
774
if (luser_pwd->pw_uid == 0)
776
luser_pwd = NULL; /* forget */
779
* Validate the account information.
781
if (pam_ruserok (pamh, &opts, rhost, as_root, ruser, luser) != 0) {
782
if ( !opts.opt_suppress ) {
783
_pam_log(LOG_WARNING, "denied to %s@%s as %s: %s",
784
ruser, rhost, luser, (opts.last_error==NULL) ?
785
"access not allowed":opts.last_error);
787
retval = PAM_AUTH_ERR;
789
_pam_log(LOG_NOTICE, "allowed to %s@%s as %s",
790
ruser, rhost, luser);
798
/* --- authentication management functions --- */
801
int pam_sm_authenticate (pam_handle_t *pamh,
808
if (sizeof(U32) != 4) {
809
_pam_log (LOG_ALERT, "pam_rhosts module can\'t work on this hardware "
814
retval = _pam_auth_rhosts (pamh, flags, argc, argv);
820
int pam_sm_setcred(pam_handle_t *pamh,int flags,int argc,
826
/* end of module definition */
831
/* static module data */
833
struct pam_module _pam_rhosts_auth_modstruct = {