]> granicus.if.org Git - linux-pam/blob - modules/pam_rhosts/pam_rhosts_auth.c
Relevant BUGIDs: patch 476936
[linux-pam] / modules / pam_rhosts / pam_rhosts_auth.c
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.
11  *
12  * Redistribution and use in source and binary forms, with or without
13  * modification, are permitted provided that the following conditions
14  * are met:
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.
27  *
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
38  * SUCH DAMAGE.
39  */
40
41 #include <security/_pam_aconf.h>
42
43 #define USER_RHOSTS_FILE "/.rhosts"     /* prefixed by user's home dir */
44
45 #ifdef linux
46 #include <endian.h>
47 #endif
48
49 #ifdef HAVE_SYS_FSUID_H
50 #include <sys/fsuid.h>
51 #endif /* HAVE_SYS_FSUID_H */
52
53 #include <sys/types.h>
54 #include <sys/uio.h>
55 #include <string.h>
56 #include <unistd.h>
57 #include <stdlib.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 *);
63
64 #include <stdio.h>
65 #include <errno.h>
66 #include <sys/time.h>
67 #include <arpa/inet.h>
68
69 #ifndef MAXDNAME
70 #define MAXDNAME  256
71 #endif
72
73 #include <stdarg.h>
74 #include <ctype.h>
75
76 #include <net/if.h>
77 #ifdef linux
78 # include <linux/sockios.h>
79 # ifndef __USE_MISC
80 #  define __USE_MISC
81 #  include <sys/fsuid.h>
82 # endif /* __USE_MISC */
83 #endif
84
85 #include <pwd.h>
86 #include <grp.h>
87 #include <sys/file.h>
88 #include <sys/signal.h>
89 #include <sys/stat.h>
90 #include <stdint.h>
91 #include <syslog.h>
92 #ifndef _PATH_HEQUIV
93 #define _PATH_HEQUIV "/etc/hosts.equiv"
94 #endif /* _PATH_HEQUIV */
95
96 #define PAM_SM_AUTH  /* only defines this management group */
97
98 #include <security/pam_modules.h>
99 #include <security/_pam_macros.h>
100 #include <security/_pam_modutil.h>
101
102 /* Use the C99 type; older platforms will need this to be typedef'ed
103    elsewhere */
104 #define U32 uint32_t
105
106
107 /*
108  * Options for this module
109  */
110
111 struct _options {
112     int  opt_no_hosts_equiv;
113     int  opt_hosts_equiv_rootok;
114     int  opt_no_rhosts;
115     int  opt_debug;
116     int  opt_nowarn;
117     int  opt_disallow_null_authtok;
118     int  opt_silent;
119     int  opt_promiscuous;
120     int  opt_suppress;
121     int  opt_private_group;
122     int  opt_no_uid_check;
123     const char *superuser;
124     const char *last_error;
125 };
126
127 /* logging */
128 static void _pam_log(int err, const char *format, ...)
129 {
130     va_list args;
131
132     va_start(args, format);
133     openlog("pam_rhosts_auth", LOG_CONS|LOG_PID, LOG_AUTH);
134     vsyslog(err, format, args);
135     va_end(args);
136     closelog();
137 }
138
139 static void set_option (struct _options *opts, const char *arg)
140 {
141     if (strcmp(arg, "no_hosts_equiv") == 0) {
142         opts->opt_no_hosts_equiv = 1;
143         return;
144     }
145
146     if (strcmp(arg, "hosts_equiv_rootok") == 0) {
147         opts->opt_hosts_equiv_rootok = 1;
148         return;
149     }
150
151     if (strcmp(arg, "no_rhosts") == 0) {
152         opts->opt_no_rhosts = 1;
153         return;
154     }
155
156     if (strcmp(arg, "debug") == 0) {
157         D(("debugging enabled"));
158         opts->opt_debug = 1;
159         return;
160     }
161
162     if (strcmp(arg, "no_warn") == 0) {
163         opts->opt_nowarn = 1;
164         return;
165     }
166
167     if (strcmp(arg, "promiscuous") == 0) {
168         opts->opt_promiscuous = 1;   /* used to permit '+' in ...hosts file */
169         return;
170     }
171
172     if (strcmp(arg, "suppress") == 0) {
173         opts->opt_suppress = 1; /* used to suppress failure warning message */
174         return;
175     }
176
177     if (strcmp(arg, "privategroup") == 0) {
178         opts->opt_private_group = 1; /* used to permit group write on .rhosts
179                                         file if group has same name as owner */
180         return;
181     }
182
183     if (strcmp(arg, "no_uid_check") == 0) {
184         opts->opt_no_uid_check = 1;  /* NIS optimization */
185         return;
186     }
187
188     if (strcmp(arg, "superuser=") == 0) {
189         opts->superuser = arg+sizeof("superuser=")-1;
190         return;
191     }
192     /*
193      * All other options are ignored at the present time.
194      */
195     _pam_log(LOG_WARNING, "unrecognized option '%s'", arg);
196 }
197
198 static void set_parameters (struct _options *opts, int flags,
199                             int argc, const char **argv)
200 {
201     opts->opt_silent                = flags & PAM_SILENT;
202     opts->opt_disallow_null_authtok = flags & PAM_DISALLOW_NULL_AUTHTOK;
203
204     while (argc-- > 0) {
205         set_option (opts, *argv);
206         ++argv;
207     }
208 }
209
210 /*
211  * Obtain the name of the remote host. Currently, this is simply by
212  * requesting the contents of the PAM_RHOST item.
213  */
214
215 static int pam_get_rhost(pam_handle_t *pamh, const char **rhost
216                          , const char *prompt)
217 {
218     int retval;
219     const char   *current;
220
221     retval = pam_get_item (pamh, PAM_RHOST, (const void **)&current);
222     if (retval != PAM_SUCCESS)
223         return retval;
224
225     if (current == NULL) {
226         return PAM_AUTH_ERR;
227     }
228     *rhost = current;
229
230     return retval;        /* pass on any error from conversation */
231 }
232
233 /*
234  * Obtain the name of the remote user. Currently, this is simply by
235  * requesting the contents of the PAM_RUSER item.
236  */
237
238 static int pam_get_ruser(pam_handle_t *pamh, const char **ruser,
239                          const char *prompt)
240 {
241     int retval;
242     const char   *current;
243
244     retval = pam_get_item (pamh, PAM_RUSER, (const void **)&current);
245     if (retval != PAM_SUCCESS) {
246         return retval;
247     }
248
249     if (current == NULL) {
250         return PAM_AUTH_ERR;
251     }
252     *ruser = current;
253
254     return retval;        /* pass on any error from conversation */
255 }
256
257 /*
258  * Returns 1 if positive match, 0 if no match, -1 if negative match.
259  */
260
261 static int
262 __icheckhost (pam_handle_t *pamh, struct _options *opts, U32 raddr
263               , register char *lhost, const char *rhost)
264 {
265     struct hostent *hp;
266     U32 laddr;
267     int negate=1;    /* Multiply return with this to get -1 instead of 1 */
268     char **pp, *user;
269
270     /* Check nis netgroup.  We assume that pam has done all needed
271        paranoia checking before we are handed the rhost */
272     if (strncmp("+@",lhost,2) == 0)
273       return(innetgr(&lhost[2],rhost,NULL,NULL));
274
275     if (strncmp("-@",lhost,2) == 0)
276       return(-innetgr(&lhost[2],rhost,NULL,NULL));
277
278     /* -host */
279     if (strncmp("-",lhost,1) == 0) {
280         negate=-1;
281         lhost++;
282     } else if (strcmp("+",lhost) == 0) {
283         (void) pam_get_item(pamh, PAM_USER, (const void **)&user);
284         D(("user %s has a `+' host entry", user));
285         if (opts->opt_promiscuous)
286             return (1);                     /* asking for trouble, but ok.. */
287         /* If not promiscuous: handle as negative */
288         return (-1);
289     } else if (strncmp("+",lhost,1) == 0) {
290         /* '+hostname' is supposed to be equivalent to 'hostname' */
291         lhost++;
292     }
293
294
295     /* Try for raw ip address first. */
296     if (isdigit(*lhost) && (long)(laddr = inet_addr(lhost)) != -1)
297         return (negate*(! (raddr ^ laddr)));
298
299     /* Better be a hostname. */
300     hp = gethostbyname(lhost);
301     if (hp == NULL)
302         return (0);
303     
304     /* Spin through ip addresses. */
305     for (pp = hp->h_addr_list; *pp; ++pp)
306         if (!memcmp (&raddr, *pp, sizeof (U32)))
307             return (negate);
308
309     /* No match. */
310     return (0);
311 }
312
313 /* Returns 1 on positive match, 0 on no match, -1 on negative match */
314
315 static int __icheckuser(pam_handle_t *pamh, struct _options *opts
316                         , const char *luser, const char *ruser
317                         , const char *rhost)
318 {
319     /*
320       luser is user entry from .rhosts/hosts.equiv file
321       ruser is user id on remote host
322       rhost is the remote host name
323       */
324     char *user;
325
326     /* [-+]@netgroup */
327     if (strncmp("+@",luser,2) == 0)
328         return (innetgr(&luser[2],NULL,ruser,NULL));
329
330     if (strncmp("-@",luser,2) == 0)
331         return (-innetgr(&luser[2],NULL,ruser,NULL));
332
333     /* -user */
334     if (strncmp("-",luser,1) == 0)
335         return(-(strcmp(&luser[1],ruser) == 0));
336
337     /* + */
338     if (strcmp("+",luser) == 0) {
339         (void) pam_get_item(pamh, PAM_USER, (const void **)&user);
340         _pam_log(LOG_WARNING, "user %s has a `+' user entry", user);
341         if (opts->opt_promiscuous)
342             return(1);
343         /* If not promiscuous we handle it as a negative match */
344         return(-1);
345     }
346
347     /* simple string match */
348     return (strcmp(ruser, luser) == 0);
349 }
350
351 /*
352  * Returns 1 for blank lines (or only comment lines) and 0 otherwise
353  */
354
355 static int __isempty(char *p)
356 {
357     while (*p && isspace(*p)) {
358         ++p;
359     }
360
361     return (*p == '\0' || *p == '#') ? 1:0 ;
362 }
363
364 /*
365  * Returns 0 if positive match, 1 if _not_ ok.
366  */
367
368 static int
369 __ivaliduser (pam_handle_t *pamh, struct _options *opts,
370               FILE *hostf, U32 raddr,
371               const char *luser, const char *ruser, const char *rhost)
372 {
373     register const char *user;
374     register char *p;
375     int hcheck, ucheck;
376     char buf[MAXHOSTNAMELEN + 128];                       /* host + login */
377
378     buf[sizeof (buf)-1] = '\0';                         /* terminate line */
379
380     while (fgets(buf, sizeof(buf), hostf) != NULL) {   /* hostf file line */
381         p = buf;                              /* from beginning of file.. */
382
383         /* Skip empty or comment lines */
384         if (__isempty(p)) {
385             continue;
386         }
387
388         /* Skip lines that are too long. */
389         if (strchr(p, '\n') == NULL) {
390             int ch = getc(hostf);
391
392             while (ch != '\n' && ch != EOF)
393                 ch = getc(hostf);
394             continue;
395         }
396
397         /*
398          * If there is a hostname at the start of the line.  Set it to
399          * lower case. A leading ' ' or '\t' indicates no hostname
400          */
401
402         for (;*p && !isspace(*p); ++p) {
403             *p = tolower(*p);
404         }
405
406         /*
407          * next we want to find the permitted name for the remote user
408          */
409
410         if (*p == ' ' || *p == '\t') {
411
412             /* <nul> terminate hostname and skip spaces */
413             for (*p++='\0'; *p && isspace(*p); ++p);
414
415             user = p;                   /* this is the user's name */
416             while (*p && !isspace(*p))
417                 ++p;                    /* find end of user's name */
418         } else 
419             user = p;
420
421         *p = '\0';              /* <nul> terminate username (+host?) */
422
423         /* buf -> host(?) ; user -> username(?) */
424
425         /* First check host part */
426         hcheck=__icheckhost(pamh, opts, raddr, buf, rhost);
427
428         if (hcheck<0)
429             return(1);
430
431         if (hcheck) {
432             /* Then check user part */
433             if (! (*user))
434                 user = luser;
435
436             ucheck=__icheckuser(pamh, opts, user, ruser, rhost);
437
438             /* Positive 'host user' match? */
439             if (ucheck>0)
440                 return(0);
441
442             /* Negative 'host -user' match? */
443             if (ucheck<0)
444                 return(1);
445
446             /* Neither, go on looking for match */
447         }
448     }
449
450     return (1);
451 }
452
453 /*
454  * New .rhosts strategy: We are passed an ip address. We spin through
455  * hosts.equiv and .rhosts looking for a match. When the .rhosts only
456  * has ip addresses, we don't have to trust a nameserver.  When it
457  * contains hostnames, we spin through the list of addresses the nameserver
458  * gives us and look for a match.
459  *
460  * Returns 0 if ok, -1 if not ok.
461  */
462
463 static int
464 pam_iruserok(pam_handle_t *pamh,
465          struct _options *opts, U32 raddr, int superuser,
466          const char *ruser, const char *luser, const char *rhost)
467 {
468     const char *cp;
469     struct stat sbuf;
470     struct passwd *pwd;
471     FILE *hostf;
472     uid_t uid;
473     int answer;
474     char pbuf[MAXPATHLEN];               /* potential buffer overrun */
475
476     if ((!superuser||opts->opt_hosts_equiv_rootok) && !opts->opt_no_hosts_equiv ) {
477
478         /* try to open system hosts.equiv file */
479         hostf = fopen (_PATH_HEQUIV, "r");
480         if (hostf) {
481             answer = __ivaliduser(pamh, opts, hostf, raddr, luser
482                                   , ruser, rhost);
483             (void) fclose(hostf);
484             if (answer == 0)
485                 return 0;      /* remote host is equivalent to localhost */
486         } /* else {
487             No hosts.equiv file on system.
488         } */
489     }
490     
491     if ( opts->opt_no_rhosts )
492         return 1;
493
494     /*
495      * Identify user's local .rhosts file
496      */
497
498     pwd = _pammodutil_getpwnam(pamh, luser);
499     if (pwd == NULL) {
500         /* 
501          * luser is assumed to be valid because of an earlier check for uid = 0
502          * we don't log this error twice. However, this shouldn't happen !
503          * --cristiang 
504          */
505         return(1);
506     }
507
508     /* check for buffer overrun */
509     if (strlen(pwd->pw_dir) + sizeof(USER_RHOSTS_FILE) + 2 >= MAXPATHLEN) {
510         if (opts->opt_debug)
511             _pam_log(LOG_DEBUG,"home directory for `%s' is too long", luser);
512         return 1;                               /* to dangerous to try */
513     }
514
515     (void) strcpy(pbuf, pwd->pw_dir);
516     (void) strcat(pbuf, USER_RHOSTS_FILE);
517
518     /*
519      * Change effective uid while _reading_ .rhosts. (not just
520      * opening).  If root and reading an NFS mounted file system,
521      * can't read files that are 0600 as .rhosts files should be.
522      */
523
524     /* We are root, this will not fail */
525 #ifdef linux
526     /* If we are on linux the better way is setfsuid */
527     uid = setfsuid(pwd->pw_uid);
528     hostf = fopen(pbuf, "r");
529 #else
530     uid = geteuid();
531     (void) seteuid(pwd->pw_uid);
532     hostf = fopen(pbuf, "r");
533 #endif
534
535     if (hostf == NULL) {
536         if (opts->opt_debug)
537             _pam_log(LOG_DEBUG,"Could not open %s file",pbuf);
538         answer = 1;
539         goto exit_function;
540     }
541
542     /*
543      * If not a regular file, or is owned by someone other than
544      * user or root or if writeable by anyone but the owner, quit.
545      */
546
547     cp = NULL;
548     if (lstat(pbuf, &sbuf) < 0 || !S_ISREG(sbuf.st_mode))
549         cp = ".rhosts not regular file";
550     else if (fstat(fileno(hostf), &sbuf) < 0)
551         cp = ".rhosts fstat failed";
552     else if (sbuf.st_uid && sbuf.st_uid != pwd->pw_uid)
553         cp = "bad .rhosts owner";
554     else if (sbuf.st_mode & S_IWOTH)
555         cp = ".rhosts writable by other!";
556     else if (sbuf.st_mode & S_IWGRP) {
557
558         /* private group caveat */
559         if (opts->opt_private_group) {
560             struct group *grp = getgrgid(sbuf.st_gid);
561
562             if (NULL == grp || NULL == grp->gr_name
563                 || strcmp(luser,grp->gr_name)) {
564                 cp = ".rhosts writable by public group";
565             } else if (grp->gr_mem) {
566                 int gcount;
567
568                 /* require at most one member (luser) of this group */
569                 for (gcount=0; grp->gr_mem[gcount]; ++gcount) {
570                     if (strcmp(grp->gr_mem[gcount], luser)) {
571                         gcount = -1;
572                         break;
573                     }
574                 }
575                 if (gcount < 0) {
576                     cp = ".rhosts writable by other members of group";
577                 }
578             }
579         } else {
580             cp = ".rhosts writable by group";
581         }
582
583     } /* It is _NOT_ safe to append an else here...  Do so prior to
584        * S_IWGRP check */
585
586     /* If there were any problems, quit. */
587     if (cp) {
588         opts->last_error = cp;
589         answer = 1;
590         goto exit_function;
591     }
592
593     answer = __ivaliduser (pamh, opts, hostf, raddr, luser, ruser, rhost);
594
595 exit_function:
596     /*
597      * Go here to exit after the fsuid/euid has been adjusted so that
598      * they are reset before we exit.
599      */
600
601 #ifdef linux
602     setfsuid(uid);
603 #else
604     (void)seteuid(uid);
605 #endif
606
607     if (hostf != NULL)
608         (void) fclose(hostf);
609
610     return answer;
611 }
612
613 static int
614 pam_ruserok (pam_handle_t *pamh,
615              struct _options *opts, const char *rhost, int superuser,
616              const char *ruser, const char *luser)
617 {
618     struct hostent *hp;
619     int answer = 1;                             /* default to failure */
620     U32 *addrs;
621     int n, i;
622
623     opts->last_error = (char *) 0;
624     hp               = gethostbyname(rhost);         /* identify host */
625
626     if (hp != NULL) {
627         /* First of all check the address length */
628         if (hp->h_length != 4) {
629             _pam_log(LOG_ALERT, "pam_rhosts module can't work with not IPv4 "
630                      "addresses");
631             return 1;                                    /* not allowed */
632         }
633
634         /* loop though address list */
635         for (n = 0; hp->h_addr_list[n]; n++);
636         D(("rhosts: %d addresses", n));
637
638         if (n) {
639             addrs = calloc (n, hp->h_length);
640             for (i = 0; i < n; i++)
641                 memcpy (addrs+i, hp->h_addr_list[i], hp->h_length);
642
643             for (i = 0; i < n && answer; i++) {
644                 D(("rhosts: address %d is %04x", i, addrs[i]));
645                 answer = pam_iruserok(pamh, opts, addrs[i], superuser,
646                                       ruser, luser, rhost);
647                          /* answer == 0 means success */
648             }
649
650             free (addrs);
651         }
652     }
653
654     return answer;
655 }
656
657 /*
658  * Internal function to do authentication
659  */
660
661 static int _pam_auth_rhosts (pam_handle_t *pamh,
662                              int flags, 
663                              int argc,
664                              const char **argv) 
665 {
666     int retval;
667     const char *luser = NULL;
668     const char *ruser = NULL, *rhost = NULL;
669     struct _options opts;
670     int as_root = 0;
671
672     /*
673      * Look at the options and set the flags accordingly.
674      */
675     memset (&opts, 0, sizeof (opts));
676     set_parameters (&opts, flags, argc, argv);
677     /*
678      * Obtain the parameters for the various items
679      */
680     for (;;) {                         /* abuse loop to avoid goto */
681
682         /* get the remotehost */
683         D(("getting rhost"));
684         retval = pam_get_rhost(pamh, &rhost, NULL);
685         (void) pam_set_item(pamh, PAM_RHOST, rhost);
686         if (retval != PAM_SUCCESS) {
687             if (opts.opt_debug) {
688                 _pam_log(LOG_DEBUG, "could not get the remote host name");
689             }
690             break;
691         }
692
693         /* get the remote user */
694         D(("getting ruser"));
695         retval = pam_get_ruser(pamh, &ruser, NULL);
696         (void) pam_set_item(pamh, PAM_RUSER, ruser);
697         if (retval != PAM_SUCCESS) {
698             if (opts.opt_debug)
699                 _pam_log(LOG_DEBUG, "could not get the remote username");
700             break;
701         }
702
703         /* get the local user */
704         D(("getting user"));
705         retval = pam_get_user(pamh, &luser, NULL);
706         if (retval != PAM_SUCCESS) {
707             if (opts.opt_debug)
708                 _pam_log(LOG_DEBUG, "could not determine name of local user");
709             break;
710         }
711
712         if (opts.superuser && !strcmp(opts.superuser, luser)) {
713             as_root = 1;
714         }
715
716         /* check if the luser uid == 0... --cristiang */
717         if (! opts.opt_no_uid_check) {
718             struct passwd *luser_pwd;
719
720             luser_pwd = _pammodutil_getpwnam(pamh, luser);
721             if (luser_pwd == NULL) {
722                 if (opts.opt_debug)
723                     _pam_log(LOG_DEBUG, "user '%s' unknown to this system",
724                              luser);
725                 retval = PAM_AUTH_ERR;
726                 break;
727             }
728             if (luser_pwd->pw_uid == 0)
729                 as_root = 1;
730             luser_pwd = NULL;                                   /* forget */
731         }
732 /*
733  * Validate the account information.
734  */
735         if (pam_ruserok (pamh, &opts, rhost, as_root, ruser, luser) != 0) {
736             if ( !opts.opt_suppress ) {
737                 _pam_log(LOG_WARNING, "denied to %s@%s as %s: %s",
738                          ruser, rhost, luser, (opts.last_error==NULL) ?
739                          "access not allowed":opts.last_error);
740             }
741             retval = PAM_AUTH_ERR;
742         } else {
743             _pam_log(LOG_NOTICE, "allowed to %s@%s as %s",
744                      ruser, rhost, luser);
745         }
746         break;
747     }
748
749     return retval;
750 }
751
752 /* --- authentication management functions --- */
753
754 PAM_EXTERN
755 int pam_sm_authenticate (pam_handle_t *pamh, 
756                          int flags,
757                          int argc, 
758                          const char **argv)
759 {
760     int retval;
761
762     if (sizeof(U32) != 4) {
763         _pam_log (LOG_ALERT, "pam_rhosts module can\'t work on this hardware "
764                   "(yet)");
765         return PAM_AUTH_ERR;
766     }
767     sethostent(1);
768     retval = _pam_auth_rhosts (pamh, flags, argc, argv);
769     endhostent();
770     return retval;
771 }
772
773 PAM_EXTERN
774 int pam_sm_setcred(pam_handle_t *pamh,int flags,int argc,
775                    const char **argv)
776 {
777     return PAM_SUCCESS;
778 }
779
780 /* end of module definition */
781
782
783 #ifdef PAM_STATIC
784
785 /* static module data */
786
787 struct pam_module _pam_rhosts_auth_modstruct = {
788     "pam_rhosts_auth",
789     pam_sm_authenticate,
790     pam_sm_setcred,
791     NULL,
792     NULL,
793     NULL,
794     NULL,
795 };
796
797 #endif