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
43 #define USER_RHOSTS_FILE "/.rhosts" /* prefixed by user's home dir */
50 #include <sys/fsuid.h>
51 #endif /* NEED_FSUID_H */
53 #include <sys/types.h>
58 #include <sys/param.h>
59 #include <sys/socket.h>
60 #include <netinet/in.h>
61 #include <netdb.h> /* This is supposed(?) to contain the following */
62 int innetgr(const char *, const char *, const char *,const char *);
67 #include <arpa/inet.h>
78 # include <linux/sockios.h>
81 # include <sys/fsuid.h>
82 # endif /* __USE_MISC */
88 #include <sys/signal.h>
92 #define _PATH_HEQUIV "/etc/hosts.equiv"
93 #endif /* _PATH_HEQUIV */
95 #define PAM_SM_AUTH /* only defines this management group */
97 #include <security/pam_modules.h>
98 #include <security/_pam_macros.h>
100 /* to the best of my knowledge, all modern UNIX boxes have 32 bit integers */
101 #define U32 unsigned int
105 * Options for this module
109 int opt_no_hosts_equiv;
110 int opt_hosts_equiv_rootok;
114 int opt_disallow_null_authtok;
118 int opt_private_group;
119 int opt_no_uid_check;
120 const char *superuser;
121 const char *last_error;
125 static void _pam_log(int err, const char *format, ...)
129 va_start(args, format);
130 openlog("pam_rhosts_auth", LOG_CONS|LOG_PID, LOG_AUTH);
131 vsyslog(err, format, args);
136 static void set_option (struct _options *opts, const char *arg)
138 if (strcmp(arg, "no_hosts_equiv") == 0) {
139 opts->opt_no_hosts_equiv = 1;
143 if (strcmp(arg, "hosts_equiv_rootok") == 0) {
144 opts->opt_hosts_equiv_rootok = 1;
148 if (strcmp(arg, "no_rhosts") == 0) {
149 opts->opt_no_rhosts = 1;
153 if (strcmp(arg, "debug") == 0) {
154 D(("debugging enabled"));
159 if (strcmp(arg, "no_warn") == 0) {
160 opts->opt_nowarn = 1;
164 if (strcmp(arg, "promiscuous") == 0) {
165 opts->opt_promiscuous = 1; /* used to permit '+' in ...hosts file */
169 if (strcmp(arg, "suppress") == 0) {
170 opts->opt_suppress = 1; /* used to suppress failure warning message */
174 if (strcmp(arg, "privategroup") == 0) {
175 opts->opt_private_group = 1; /* used to permit group write on .rhosts
176 file if group has same name as owner */
180 if (strcmp(arg, "no_uid_check") == 0) {
181 opts->opt_no_uid_check = 1; /* NIS optimization */
185 if (strcmp(arg, "superuser=") == 0) {
186 opts->superuser = arg+sizeof("superuser=")-1;
190 * All other options are ignored at the present time.
192 _pam_log(LOG_WARNING, "unrecognized option '%s'", arg);
195 static void set_parameters (struct _options *opts, int flags,
196 int argc, const char **argv)
198 opts->opt_silent = flags & PAM_SILENT;
199 opts->opt_disallow_null_authtok = flags & PAM_DISALLOW_NULL_AUTHTOK;
202 set_option (opts, *argv);
208 * Obtain the name of the remote host. Currently, this is simply by
209 * requesting the contents of the PAM_RHOST item.
212 static int pam_get_rhost(pam_handle_t *pamh, const char **rhost
213 , const char *prompt)
218 retval = pam_get_item (pamh, PAM_RHOST, (const void **)¤t);
219 if (retval != PAM_SUCCESS)
222 if (current == NULL) {
227 return retval; /* pass on any error from conversation */
231 * Obtain the name of the remote user. Currently, this is simply by
232 * requesting the contents of the PAM_RUSER item.
235 static int pam_get_ruser(pam_handle_t *pamh, const char **ruser
236 , const char *prompt)
241 retval = pam_get_item (pamh, PAM_RUSER, (const void **)¤t);
242 if (retval != PAM_SUCCESS)
245 if (current == NULL) {
250 return retval; /* pass on any error from conversation */
254 * Returns 1 if positive match, 0 if no match, -1 if negative match.
258 __icheckhost (pam_handle_t *pamh, struct _options *opts, U32 raddr
259 , register char *lhost, const char *rhost)
263 int negate=1; /* Multiply return with this to get -1 instead of 1 */
266 /* Check nis netgroup. We assume that pam has done all needed
267 paranoia checking before we are handed the rhost */
268 if (strncmp("+@",lhost,2) == 0)
269 return(innetgr(&lhost[2],rhost,NULL,NULL));
271 if (strncmp("-@",lhost,2) == 0)
272 return(-innetgr(&lhost[2],rhost,NULL,NULL));
275 if (strncmp("-",lhost,1) == 0) {
278 } else if (strcmp("+",lhost) == 0) {
279 (void) pam_get_item(pamh, PAM_USER, (const void **)&user);
280 D(("user %s has a `+' host entry", user));
281 if (opts->opt_promiscuous)
282 return (1); /* asking for trouble, but ok.. */
283 /* If not promiscuous: handle as negative */
287 /* Try for raw ip address first. */
288 if (isdigit(*lhost) && (long)(laddr = inet_addr(lhost)) != -1)
289 return (negate*(! (raddr ^ laddr)));
291 /* Better be a hostname. */
292 hp = gethostbyname(lhost);
296 /* Spin through ip addresses. */
297 for (pp = hp->h_addr_list; *pp; ++pp)
298 if (!memcmp (&raddr, *pp, sizeof (U32)))
305 /* Returns 1 on positive match, 0 on no match, -1 on negative match */
307 static int __icheckuser(pam_handle_t *pamh, struct _options *opts
308 , const char *luser, const char *ruser
312 luser is user entry from .rhosts/hosts.equiv file
313 ruser is user id on remote host
314 rhost is the remote host name
319 if (strncmp("+@",luser,2) == 0)
320 return (innetgr(&luser[2],NULL,ruser,NULL));
322 if (strncmp("-@",luser,2) == 0)
323 return (-innetgr(&luser[2],NULL,ruser,NULL));
326 if (strncmp("-",luser,1) == 0)
327 return(-(strcmp(&luser[1],ruser) == 0));
330 if (strcmp("+",luser) == 0) {
331 (void) pam_get_item(pamh, PAM_USER, (const void **)&user);
332 _pam_log(LOG_WARNING, "user %s has a `+' user entry", user);
333 if (opts->opt_promiscuous)
335 /* If not promiscuous we handle it as a negative match */
339 /* simple string match */
340 return (strcmp(ruser, luser) == 0);
344 * Returns 1 for blank lines (or only comment lines) and 0 otherwise
347 static int __isempty(char *p)
349 while (*p && isspace(*p)) {
353 return (*p == '\0' || *p == '#') ? 1:0 ;
357 * Returns 0 if positive match, 1 if _not_ ok.
361 __ivaliduser (pam_handle_t *pamh, struct _options *opts,
362 FILE *hostf, U32 raddr,
363 const char *luser, const char *ruser, const char *rhost)
365 register const char *user;
368 char buf[MAXHOSTNAMELEN + 128]; /* host + login */
370 buf[sizeof (buf)-1] = '\0'; /* terminate line */
372 while (fgets(buf, sizeof(buf), hostf) != NULL) { /* hostf file line */
373 p = buf; /* from beginning of file.. */
375 /* Skip empty or comment lines */
380 /* Skip lines that are too long. */
381 if (strchr(p, '\n') == NULL) {
382 int ch = getc(hostf);
384 while (ch != '\n' && ch != EOF)
390 * If there is a hostname at the start of the line. Set it to
391 * lower case. A leading ' ' or '\t' indicates no hostname
394 for (;*p && !isspace(*p); ++p) {
399 * next we want to find the permitted name for the remote user
402 if (*p == ' ' || *p == '\t') {
404 /* <nul> terminate hostname and skip spaces */
405 for (*p++='\0'; *p && isspace(*p); ++p);
407 user = p; /* this is the user's name */
408 while (*p && !isspace(*p))
409 ++p; /* find end of user's name */
413 *p = '\0'; /* <nul> terminate username (+host?) */
415 /* buf -> host(?) ; user -> username(?) */
417 /* First check host part */
418 hcheck=__icheckhost(pamh, opts, raddr, buf, rhost);
424 /* Then check user part */
428 ucheck=__icheckuser(pamh, opts, user, ruser, rhost);
430 /* Positive 'host user' match? */
434 /* Negative 'host -user' match? */
438 /* Neither, go on looking for match */
446 * New .rhosts strategy: We are passed an ip address. We spin through
447 * hosts.equiv and .rhosts looking for a match. When the .rhosts only
448 * has ip addresses, we don't have to trust a nameserver. When it
449 * contains hostnames, we spin through the list of addresses the nameserver
450 * gives us and look for a match.
452 * Returns 0 if ok, -1 if not ok.
456 pam_iruserok(pam_handle_t *pamh,
457 struct _options *opts, U32 raddr, int superuser,
458 const char *ruser, const char *luser, const char *rhost)
466 char pbuf[MAXPATHLEN]; /* potential buffer overrun */
468 if ((!superuser||opts->opt_hosts_equiv_rootok) && !opts->opt_no_hosts_equiv ) {
470 /* try to open system hosts.equiv file */
471 hostf = fopen (_PATH_HEQUIV, "r");
473 answer = __ivaliduser(pamh, opts, hostf, raddr, luser
475 (void) fclose(hostf);
477 return 0; /* remote host is equivalent to localhost */
479 No hosts.equiv file on system.
483 if ( opts->opt_no_rhosts )
487 * Identify user's local .rhosts file
490 pwd = getpwnam(luser);
493 * luser is assumed to be valid because of an earlier check for uid = 0
494 * we don't log this error twice. However, this shouldn't happen !
500 /* check for buffer overrun */
501 if (strlen(pwd->pw_dir) + sizeof(USER_RHOSTS_FILE) + 2 >= MAXPATHLEN) {
503 _pam_log(LOG_DEBUG,"home directory for `%s' is too long", luser);
504 return 1; /* to dangerous to try */
507 (void) strcpy(pbuf, pwd->pw_dir);
508 (void) strcat(pbuf, USER_RHOSTS_FILE);
511 * Change effective uid while _reading_ .rhosts. (not just
512 * opening). If root and reading an NFS mounted file system,
513 * can't read files that are 0600 as .rhosts files should be.
516 /* We are root, this will not fail */
518 /* If we are on linux the better way is setfsuid */
519 uid = setfsuid(pwd->pw_uid);
520 hostf = fopen(pbuf, "r");
523 (void) seteuid(pwd->pw_uid);
524 hostf = fopen(pbuf, "r");
529 _pam_log(LOG_DEBUG,"Could not open %s file",pbuf);
535 * If not a regular file, or is owned by someone other than
536 * user or root or if writeable by anyone but the owner, quit.
540 if (lstat(pbuf, &sbuf) < 0 || !S_ISREG(sbuf.st_mode))
541 cp = ".rhosts not regular file";
542 else if (fstat(fileno(hostf), &sbuf) < 0)
543 cp = ".rhosts fstat failed";
544 else if (sbuf.st_uid && sbuf.st_uid != pwd->pw_uid)
545 cp = "bad .rhosts owner";
546 else if (sbuf.st_mode & S_IWOTH)
547 cp = ".rhosts writable by other!";
548 else if (sbuf.st_mode & S_IWGRP) {
550 /* private group caveat */
551 if (opts->opt_private_group) {
552 struct group *grp = getgrgid(sbuf.st_gid);
554 if (NULL == grp || NULL == grp->gr_name
555 || strcmp(luser,grp->gr_name)) {
556 cp = ".rhosts writable by public group";
557 } else if (grp->gr_mem) {
560 /* require at most one member (luser) of this group */
561 for (gcount=0; grp->gr_mem[gcount]; ++gcount) {
562 if (strcmp(grp->gr_mem[gcount], luser)) {
568 cp = ".rhosts writable by other members of group";
572 cp = ".rhosts writable by group";
575 } /* It is _NOT_ safe to append an else here... Do so prior to
578 /* If there were any problems, quit. */
580 opts->last_error = cp;
585 answer = __ivaliduser (pamh, opts, hostf, raddr, luser, ruser, rhost);
589 * Go here to exit after the fsuid/euid has been adjusted so that
590 * they are reset before we exit.
600 (void) fclose(hostf);
606 pam_ruserok (pam_handle_t *pamh,
607 struct _options *opts, const char *rhost, int superuser,
608 const char *ruser, const char *luser)
611 int answer = 1; /* default to failure */
615 opts->last_error = (char *) 0;
616 hp = gethostbyname(rhost); /* identify host */
619 /* First of all check the address length */
620 if (hp->h_length != 4) {
621 _pam_log(LOG_ALERT, "pam_rhosts module can't work with not IPv4 "
623 return 1; /* not allowed */
626 /* loop though address list */
627 for (n = 0; hp->h_addr_list[n]; n++);
628 D(("rhosts: %d addresses", n));
631 addrs = calloc (n, hp->h_length);
632 for (i = 0; i < n; i++)
633 memcpy (addrs+i, hp->h_addr_list[i], hp->h_length);
635 for (i = 0; i < n && answer; i++) {
636 D(("rhosts: address %d is %04x", i, addrs[i]));
637 answer = pam_iruserok(pamh, opts, addrs[i], superuser,
638 ruser, luser, rhost);
639 /* answer == 0 means success */
650 * Internal function to do authentication
653 static int _pam_auth_rhosts (pam_handle_t *pamh,
660 const char *ruser,*rhost;
661 struct _options opts;
664 * Look at the options and set the flags accordingly.
666 memset (&opts, 0, sizeof (opts));
667 set_parameters (&opts, flags, argc, argv);
669 * Obtain the parameters for the various items
671 for (;;) { /* abuse loop to avoid goto */
673 /* get the remotehost */
674 retval = pam_get_rhost(pamh, &rhost, NULL);
675 (void) pam_set_item(pamh, PAM_RHOST, rhost);
676 if (retval != PAM_SUCCESS) {
677 if (opts.opt_debug) {
678 _pam_log(LOG_DEBUG, "could not get the remote host name");
683 /* get the remote user */
684 retval = pam_get_ruser(pamh, &ruser, NULL);
685 (void) pam_set_item(pamh, PAM_RUSER, ruser);
686 if (retval != PAM_SUCCESS) {
688 _pam_log(LOG_DEBUG, "could not get the remote username");
692 /* get the local user */
693 retval = pam_get_user(pamh, &luser, NULL);
695 if (retval != PAM_SUCCESS) {
697 _pam_log(LOG_DEBUG, "could not determine name of local user");
701 if (opts.superuser && !strcmp(opts.superuser, luser)) {
705 /* check if the luser uid == 0... --cristiang */
706 if (! opts.opt_no_uid_check) {
707 struct passwd *luser_pwd;
709 luser_pwd = getpwnam(luser);
710 if (luser_pwd == NULL) {
712 _pam_log(LOG_DEBUG, "user '%s' unknown to this system",
714 retval = PAM_AUTH_ERR;
717 if (luser_pwd->pw_uid == 0)
719 luser_pwd = NULL; /* forget */
722 * Validate the account information.
724 if (pam_ruserok (pamh, &opts, rhost, as_root, ruser, luser) != 0) {
725 if ( !opts.opt_suppress ) {
726 _pam_log(LOG_WARNING, "denied to %s@%s as %s: %s",
727 ruser, rhost, luser, (opts.last_error==NULL) ?
728 "access not allowed":opts.last_error);
730 retval = PAM_AUTH_ERR;
732 _pam_log(LOG_NOTICE, "allowed to %s@%s as %s",
733 ruser, rhost, luser);
741 /* --- authentication management functions --- */
744 int pam_sm_authenticate (pam_handle_t *pamh,
751 if (sizeof(U32) != 4) {
752 _pam_log (LOG_ALERT, "pam_rhosts module can\'t work on this hardware "
757 retval = _pam_auth_rhosts (pamh, flags, argc, argv);
763 int pam_sm_setcred(pam_handle_t *pamh,int flags,int argc,
769 /* end of module definition */
774 /* static module data */
776 struct pam_module _pam_rhosts_auth_modstruct = {
790 * Revision 1.1 2000/06/20 22:11:56 agmorgan
793 * Revision 1.4 1999/11/08 05:46:58 morgan
796 * Revision 1.3 1999/10/09 05:12:49 morgan
797 * added hosts_equiv_rootok support
799 * Revision 1.2 1998/12/14 05:47:58 morgan
800 * added a couple of options: specify the name of root, and don't do the
803 * Revision 1.1.1.1 1998/07/12 05:17:16 morgan
804 * Linux PAM sources pre-0.66
806 * Revision 1.12 1997/09/27 14:34:01 morgan
807 * fixed comment and renamed iruserok to pam_iruserok.
809 * Revision 1.11 1997/04/05 06:26:39 morgan
810 * fairly major fixes and enhancements (see CHANGELOG for 0.57 release)
812 * Revision 1.10 1997/02/09 02:09:30 morgan
813 * - implementation of 'debug' argument (Cristian Gafton)
814 * - we check for uid=0 accounts instead of hardcoded 'root' (Cristian Gafton)
816 * Revision 1.9 1996/12/01 03:09:47 morgan
817 * *** empty log message ***
819 * Revision 1.8 1996/11/12 06:08:59 morgan
820 * Oliver Crow's "rootok" patch plus a little clean up of set_option
823 * Revision 1.7 1996/11/10 20:15:56 morgan
824 * cross platform support
826 * Revision 1.6 1996/08/09 05:46:29 morgan
827 * removed code for manually setting the remote username etc..