]> granicus.if.org Git - linux-pam/blob - modules/pam_access/pam_access.c
Relevant BUGIDs:
[linux-pam] / modules / pam_access / pam_access.c
1 /* pam_access module */
2
3 /*
4  * Written by Alexei Nogin <alexei@nogin.dnttm.ru> 1997/06/15
5  * (I took login_access from logdaemon-5.6 and converted it to PAM
6  * using parts of pam_time code.)
7  *
8  ************************************************************************
9  * Copyright message from logdaemon-5.6 (original file name DISCLAIMER)
10  ************************************************************************
11  * Copyright 1995 by Wietse Venema. All rights reserved. Individual files
12  * may be covered by other copyrights (as noted in the file itself.)
13  *
14  * This material was originally written and compiled by Wietse Venema at
15  * Eindhoven University of Technology, The Netherlands, in 1990, 1991,
16  * 1992, 1993, 1994 and 1995.
17  *
18  * Redistribution and use in source and binary forms are permitted
19  * provided that this entire copyright notice is duplicated in all such
20  * copies.
21  *
22  * This software is provided "as is" and without any expressed or implied
23  * warranties, including, without limitation, the implied warranties of
24  * merchantibility and fitness for any particular purpose.
25  *************************************************************************
26  */
27
28 #include "config.h"
29
30 #include <stdio.h>
31 #include <stdlib.h>
32 #include <unistd.h>
33
34 #include <stdarg.h>
35 #include <syslog.h>
36 #include <string.h>
37 #include <sys/types.h>
38 #include <sys/stat.h>
39 #include <pwd.h>
40 #include <grp.h>
41 #include <errno.h>
42 #include <ctype.h>
43 #include <sys/utsname.h>
44 #include <arpa/inet.h>
45 #include <netdb.h>
46 #include <sys/socket.h>
47 #ifdef HAVE_RPCSVC_YPCLNT_H
48 #include <rpcsvc/ypclnt.h>
49 #endif
50 #ifdef HAVE_LIBAUDIT
51 #include <libaudit.h>
52 #endif
53
54 /*
55  * here, we make definitions for the externally accessible functions
56  * in this file (these definitions are required for static modules
57  * but strongly encouraged generally) they are used to instruct the
58  * modules include file to define their prototypes.
59  */
60
61 #define PAM_SM_AUTH
62 #define PAM_SM_ACCOUNT
63 #define PAM_SM_SESSION
64 #define PAM_SM_PASSWORD
65
66 #include <security/_pam_macros.h>
67 #include <security/pam_modules.h>
68 #include <security/pam_modutil.h>
69 #include <security/pam_ext.h>
70
71 /* login_access.c from logdaemon-5.6 with several changes by A.Nogin: */
72
73  /*
74   * This module implements a simple but effective form of login access
75   * control based on login names and on host (or domain) names, internet
76   * addresses (or network numbers), or on terminal line names in case of
77   * non-networked logins. Diagnostics are reported through syslog(3).
78   *
79   * Author: Wietse Venema, Eindhoven University of Technology, The Netherlands.
80   */
81
82 #if !defined(MAXHOSTNAMELEN) || (MAXHOSTNAMELEN < 64)
83 #undef MAXHOSTNAMELEN
84 #define MAXHOSTNAMELEN 256
85 #endif
86
87  /* Delimiters for fields and for lists of users, ttys or hosts. */
88
89
90 #define ALL             2
91 #define YES             1
92 #define NO              0
93
94  /*
95   * A structure to bundle up all login-related information to keep the
96   * functional interfaces as generic as possible.
97   */
98 struct login_info {
99     const struct passwd *user;
100     const char *from;
101     const char *config_file;
102     const char *hostname;
103     int debug;                          /* Print debugging messages. */
104     int only_new_group_syntax;          /* Only allow group entries of the form "(xyz)" */
105     int noaudit;                        /* Do not audit denials */
106     const char *fs;                     /* field separator */
107     const char *sep;                    /* list-element separator */
108     int from_remote_host;               /* If PAM_RHOST was used for from */
109 };
110
111 /* Parse module config arguments */
112
113 static int
114 parse_args(pam_handle_t *pamh, struct login_info *loginfo,
115            int argc, const char **argv)
116 {
117     int i;
118
119     loginfo->noaudit = NO;
120     loginfo->debug = NO;
121     loginfo->only_new_group_syntax = NO;
122     loginfo->fs = ":";
123     loginfo->sep = ", \t";
124     for (i=0; i<argc; ++i) {
125         if (!strncmp("fieldsep=", argv[i], 9)) {
126
127             /* the admin wants to override the default field separators */
128             loginfo->fs = argv[i]+9;
129
130         } else if (!strncmp("listsep=", argv[i], 8)) {
131
132             /* the admin wants to override the default list separators */
133             loginfo->sep = argv[i]+8;
134
135         } else if (!strncmp("accessfile=", argv[i], 11)) {
136             FILE *fp = fopen(11 + argv[i], "r");
137
138             if (fp) {
139                 loginfo->config_file = 11 + argv[i];
140                 fclose(fp);
141             } else {
142                 pam_syslog(pamh, LOG_ERR,
143                            "failed to open accessfile=[%s]: %m", 11 + argv[i]);
144                 return 0;
145             }
146
147         } else if (strcmp (argv[i], "debug") == 0) {
148             loginfo->debug = YES;
149         } else if (strcmp (argv[i], "nodefgroup") == 0) {
150             loginfo->only_new_group_syntax = YES;
151         } else if (strcmp (argv[i], "noaudit") == 0) {
152             loginfo->noaudit = YES;
153         } else {
154             pam_syslog(pamh, LOG_ERR, "unrecognized option [%s]", argv[i]);
155         }
156     }
157
158     return 1;  /* OK */
159 }
160
161 /* --- static functions for checking whether the user should be let in --- */
162
163 typedef int match_func (pam_handle_t *, char *, struct login_info *);
164
165 static int list_match (pam_handle_t *, char *, char *, struct login_info *,
166                        match_func *);
167 static int user_match (pam_handle_t *, char *, struct login_info *);
168 static int group_match (pam_handle_t *, const char *, const char *, int);
169 static int from_match (pam_handle_t *, char *, struct login_info *);
170 static int string_match (pam_handle_t *, const char *, const char *, int);
171 static int network_netmask_match (pam_handle_t *, const char *, const char *, int);
172
173
174 /* isipaddr - find out if string provided is an IP address or not */
175
176 static int
177 isipaddr (const char *string, int *addr_type,
178           struct sockaddr_storage *addr)
179 {
180   struct sockaddr_storage local_addr;
181   int is_ip;
182
183   /* We use struct sockaddr_storage addr because
184    * struct in_addr/in6_addr is an integral part
185    * of struct sockaddr and we doesn't want to
186    * use its value.
187    */
188
189   if (addr == NULL)
190     addr = &local_addr;
191
192   memset(addr, 0, sizeof(struct sockaddr_storage));
193
194   /* first ipv4 */
195   if (inet_pton(AF_INET, string, addr) > 0)
196     {
197       if (addr_type != NULL)
198         *addr_type = AF_INET;
199
200       is_ip = YES;
201     }
202   else if (inet_pton(AF_INET6, string, addr) > 0)
203     { /* then ipv6 */
204       if (addr_type != NULL) {
205         *addr_type = AF_INET6;
206       }
207       is_ip = YES;
208     }
209   else
210     is_ip = NO;
211
212   return is_ip;
213 }
214
215
216 /* are_addresses_equal - translate IP address strings to real IP
217  * addresses and compare them to find out if they are equal.
218  * If netmask was provided it will be used to focus comparation to
219  * relevant bits.
220  */
221 static int
222 are_addresses_equal (const char *ipaddr0, const char *ipaddr1,
223                      const char *netmask)
224 {
225   struct sockaddr_storage addr0;
226   struct sockaddr_storage addr1;
227   int addr_type0 = 0;
228   int addr_type1 = 0;
229
230   if (isipaddr (ipaddr0, &addr_type0, &addr0) == NO)
231     return NO;
232
233   if (isipaddr (ipaddr1, &addr_type1, &addr1) == NO)
234     return NO;
235
236   if (addr_type0 != addr_type1)
237     /* different address types */
238     return NO;
239
240   if (netmask != NULL) {
241     /* Got a netmask, so normalize addresses? */
242     struct sockaddr_storage nmask;
243     unsigned char *byte_a, *byte_nm;
244
245     memset(&nmask, 0, sizeof(struct sockaddr_storage));
246     if (inet_pton(addr_type0, netmask, (void *)&nmask) > 0) {
247       unsigned int i;
248       byte_a = (unsigned char *)(&addr0);
249       byte_nm = (unsigned char *)(&nmask);
250       for (i=0; i<sizeof(struct sockaddr_storage); i++) {
251         byte_a[i] = byte_a[i] & byte_nm[i];
252       }
253
254       byte_a = (unsigned char *)(&addr1);
255       byte_nm = (unsigned char *)(&nmask);
256       for (i=0; i<sizeof(struct sockaddr_storage); i++) {
257         byte_a[i] = byte_a[i] & byte_nm[i];
258       }
259     }
260   }
261
262
263   /* Are the two addresses equal? */
264   if (memcmp((void *)&addr0, (void *)&addr1,
265               sizeof(struct sockaddr_storage)) == 0) {
266     return(YES);
267   }
268
269   return(NO);
270 }
271
272 static char *
273 number_to_netmask (long netmask, int addr_type,
274                    char *ipaddr_buf, size_t ipaddr_buf_len)
275 {
276   /* We use struct sockaddr_storage addr because
277    * struct in_addr/in6_addr is an integral part
278    * of struct sockaddr and we doesn't want to
279    * use its value.
280    */
281   struct sockaddr_storage nmask;
282   unsigned char *byte_nm;
283   const char *ipaddr_dst = NULL;
284   int i, ip_bytes;
285
286   if (netmask == 0) {
287     /* mask 0 is the same like no mask */
288     return(NULL);
289   }
290
291   memset(&nmask, 0, sizeof(struct sockaddr_storage));
292   if (addr_type == AF_INET6) {
293     /* ipv6 address mask */
294     ip_bytes = 16;
295   } else {
296     /* default might be an ipv4 address mask */
297     addr_type = AF_INET;
298     ip_bytes = 4;
299   }
300
301   byte_nm = (unsigned char *)(&nmask);
302   /* translate number to mask */
303   for (i=0; i<ip_bytes; i++) {
304     if (netmask >= 8) {
305       byte_nm[i] = 0xff;
306       netmask -= 8;
307     } else
308     if (netmask > 0) {
309       byte_nm[i] = 0xff << (8 - netmask);
310       break;
311     } else
312     if (netmask <= 0) {
313       break;
314     }
315   }
316
317   /* now generate netmask address string */
318   ipaddr_dst = inet_ntop(addr_type, &nmask, ipaddr_buf, ipaddr_buf_len);
319   if (ipaddr_dst == ipaddr_buf) {
320     return (ipaddr_buf);
321   }
322
323   return (NULL);
324 }
325
326 /* login_access - match username/group and host/tty with access control file */
327
328 static int
329 login_access (pam_handle_t *pamh, struct login_info *item)
330 {
331     FILE   *fp;
332     char    line[BUFSIZ];
333     char   *perm;               /* becomes permission field */
334     char   *users;              /* becomes list of login names */
335     char   *froms;              /* becomes list of terminals or hosts */
336     int     match = NO;
337     int     nonall_match = NO;
338     int     end;
339     int     lineno = 0;         /* for diagnostics */
340     char   *sptr;
341
342     if (item->debug)
343       pam_syslog (pamh, LOG_DEBUG,
344                   "login_access: user=%s, from=%s, file=%s",
345                   item->user->pw_name,
346                   item->from, item->config_file);
347
348     /*
349      * Process the table one line at a time and stop at the first match.
350      * Blank lines and lines that begin with a '#' character are ignored.
351      * Non-comment lines are broken at the ':' character. All fields are
352      * mandatory. The first field should be a "+" or "-" character. A
353      * non-existing table means no access control.
354      */
355
356     if ((fp = fopen(item->config_file, "r"))!=NULL) {
357         while (!match && fgets(line, sizeof(line), fp)) {
358             lineno++;
359             if (line[end = strlen(line) - 1] != '\n') {
360                 pam_syslog(pamh, LOG_ERR,
361                            "%s: line %d: missing newline or line too long",
362                            item->config_file, lineno);
363                 continue;
364             }
365             if (line[0] == '#')
366                 continue;                       /* comment line */
367             while (end > 0 && isspace(line[end - 1]))
368                 end--;
369             line[end] = 0;                      /* strip trailing whitespace */
370             if (line[0] == 0)                   /* skip blank lines */
371                 continue;
372
373             /* Allow field seperator in last field of froms */
374             if (!(perm = strtok_r(line, item->fs, &sptr))
375                 || !(users = strtok_r(NULL, item->fs, &sptr))
376                 || !(froms = strtok_r(NULL, "\n", &sptr))) {
377                 pam_syslog(pamh, LOG_ERR, "%s: line %d: bad field count",
378                            item->config_file, lineno);
379                 continue;
380             }
381             if (perm[0] != '+' && perm[0] != '-') {
382                 pam_syslog(pamh, LOG_ERR, "%s: line %d: bad first field",
383                            item->config_file, lineno);
384                 continue;
385             }
386             if (item->debug)
387               pam_syslog (pamh, LOG_DEBUG,
388                           "line %d: %s : %s : %s", lineno, perm, users, froms);
389             match = list_match(pamh, users, NULL, item, user_match);
390             if (item->debug)
391               pam_syslog (pamh, LOG_DEBUG, "user_match=%d, \"%s\"",
392                           match, item->user->pw_name);
393             if (match) {
394                 match = list_match(pamh, froms, NULL, item, from_match);
395                 if (!match && perm[0] == '+') {
396                     nonall_match = YES;
397                 }
398                 if (item->debug)
399                     pam_syslog (pamh, LOG_DEBUG,
400                           "from_match=%d, \"%s\"", match, item->from);
401             }
402         }
403         (void) fclose(fp);
404     } else if (errno == ENOENT) {
405         /* This is no error.  */
406         pam_syslog(pamh, LOG_WARNING, "warning: cannot open %s: %m",
407                    item->config_file);
408     } else {
409         pam_syslog(pamh, LOG_ERR, "cannot open %s: %m", item->config_file);
410         return NO;
411     }
412 #ifdef HAVE_LIBAUDIT
413     if (!item->noaudit && line[0] == '-' && (match == YES || (match == ALL &&
414         nonall_match == YES))) {
415         pam_modutil_audit_write(pamh, AUDIT_ANOM_LOGIN_LOCATION,
416             "pam_access", 0);
417     }
418 #endif
419     return (match == NO || (line[0] == '+'));
420 }
421
422
423 /* list_match - match an item against a list of tokens with exceptions */
424
425 static int
426 list_match(pam_handle_t *pamh, char *list, char *sptr,
427            struct login_info *item, match_func *match_fn)
428 {
429     char   *tok;
430     int     match = NO;
431
432     if (item->debug && list != NULL)
433       pam_syslog (pamh, LOG_DEBUG,
434                   "list_match: list=%s, item=%s", list, item->user->pw_name);
435
436     /*
437      * Process tokens one at a time. We have exhausted all possible matches
438      * when we reach an "EXCEPT" token or the end of the list. If we do find
439      * a match, look for an "EXCEPT" list and recurse to determine whether
440      * the match is affected by any exceptions.
441      */
442
443     for (tok = strtok_r(list, item->sep, &sptr); tok != 0;
444          tok = strtok_r(NULL, item->sep, &sptr)) {
445         if (strcasecmp(tok, "EXCEPT") == 0)     /* EXCEPT: give up */
446             break;
447         if ((match = (*match_fn) (pamh, tok, item)))    /* YES */
448             break;
449     }
450     /* Process exceptions to matches. */
451
452     if (match != NO) {
453         while ((tok = strtok_r(NULL, item->sep, &sptr)) && strcasecmp(tok, "EXCEPT"))
454              /* VOID */ ;
455         if (tok == 0)
456             return match;
457         if (list_match(pamh, NULL, sptr, item, match_fn) == NO)
458             return YES; /* drop special meaning of ALL */
459     }
460     return (NO);
461 }
462
463 /* netgroup_match - match group against machine or user */
464
465 static int
466 netgroup_match (pam_handle_t *pamh, const char *netgroup,
467                 const char *machine, const char *user, int debug)
468 {
469   int retval;
470   char *mydomain = NULL;
471
472 #ifdef HAVE_YP_GET_DEFAUTL_DOMAIN
473   yp_get_default_domain(&mydomain);
474 #elif defined(HAVE_GETDOMAINNAME)
475   char domainname_res[256];
476
477   if (getdomainname (domainname_res, sizeof (domainname_res)) == 0)
478     {
479       if (strcmp (domainname_res, "(none)") == 0)
480         {
481           /* If domainname is not set, some systems will return "(none)" */
482           domainname_res[0] = '\0';
483         }
484       mydomain = domainname_res;
485     }
486 #endif
487
488 #ifdef HAVE_INNETGR
489   retval = innetgr (netgroup, machine, user, mydomain);
490 #else
491   retval = 0;
492   pam_syslog (pamh, LOG_ERR, "pam_access does not have netgroup support");
493 #endif
494   if (debug == YES)
495     pam_syslog (pamh, LOG_DEBUG,
496                 "netgroup_match: %d (netgroup=%s, machine=%s, user=%s, domain=%s)",
497                 retval, netgroup ? netgroup : "NULL",
498                 machine ? machine : "NULL",
499                 user ? user : "NULL", mydomain ? mydomain : "NULL");
500   return retval;
501 }
502
503 /* user_match - match a username against one token */
504
505 static int
506 user_match (pam_handle_t *pamh, char *tok, struct login_info *item)
507 {
508     char   *string = item->user->pw_name;
509     struct login_info fake_item;
510     char   *at;
511     int    rv;
512
513     if (item->debug)
514       pam_syslog (pamh, LOG_DEBUG,
515                   "user_match: tok=%s, item=%s", tok, string);
516
517     /*
518      * If a token has the magic value "ALL" the match always succeeds.
519      * Otherwise, return YES if the token fully matches the username, if the
520      * token is a group that contains the username, or if the token is the
521      * name of the user's primary group.
522      */
523
524     if ((at = strchr(tok + 1, '@')) != 0) {     /* split user@host pattern */
525         if (item->hostname == NULL)
526             return NO;
527         fake_item.from = item->hostname;
528         *at = 0;
529         return (user_match (pamh, tok, item) &&
530                 from_match (pamh, at + 1, &fake_item));
531     } else if (tok[0] == '@') {                 /* netgroup */
532         if (item->hostname == NULL)
533             return NO;
534         return (netgroup_match (pamh, tok + 1, item->hostname, string, item->debug));
535     } else if (tok[0] == '(' && tok[strlen(tok) - 1] == ')')
536       return (group_match (pamh, tok, string, item->debug));
537     else if ((rv=string_match (pamh, tok, string, item->debug)) != NO) /* ALL or exact match */
538       return rv;
539     else if (item->only_new_group_syntax == NO &&
540              pam_modutil_user_in_group_nam_nam (pamh,
541                                                 item->user->pw_name, tok))
542       /* try group membership */
543       return YES;
544
545     return NO;
546 }
547
548
549 /* group_match - match a username against token named group */
550
551 static int
552 group_match (pam_handle_t *pamh, const char *tok, const char* usr,
553     int debug)
554 {
555     char grptok[BUFSIZ];
556
557     if (debug)
558         pam_syslog (pamh, LOG_DEBUG,
559                     "group_match: grp=%s, user=%s", grptok, usr);
560
561     if (strlen(tok) < 3)
562         return NO;
563
564     /* token is recieved under the format '(...)' */
565     memset(grptok, 0, BUFSIZ);
566     strncpy(grptok, tok + 1, strlen(tok) - 2);
567
568     if (pam_modutil_user_in_group_nam_nam(pamh, usr, grptok))
569         return YES;
570
571   return NO;
572 }
573
574
575 /* from_match - match a host or tty against a list of tokens */
576
577 static int
578 from_match (pam_handle_t *pamh UNUSED, char *tok, struct login_info *item)
579 {
580     const char *string = item->from;
581     int        tok_len;
582     int        str_len;
583     int        rv;
584
585     if (item->debug)
586       pam_syslog (pamh, LOG_DEBUG,
587                   "from_match: tok=%s, item=%s", tok, string);
588
589     /*
590      * If a token has the magic value "ALL" the match always succeeds. Return
591      * YES if the token fully matches the string. If the token is a domain
592      * name, return YES if it matches the last fields of the string. If the
593      * token has the magic value "LOCAL", return YES if the from field was
594      * not taken by PAM_RHOST. If the token is a network number, return YES
595      * if it matches the head of the string.
596      */
597
598     if (string == NULL) {
599         return NO;
600     } else if (tok[0] == '@') {                 /* netgroup */
601         return (netgroup_match (pamh, tok + 1, string, (char *) 0, item->debug));
602     } else if ((rv = string_match(pamh, tok, string, item->debug)) != NO) {
603         /* ALL or exact match */
604         return rv;
605     } else if (tok[0] == '.') {                 /* domain: match last fields */
606         if ((str_len = strlen(string)) > (tok_len = strlen(tok))
607             && strcasecmp(tok, string + str_len - tok_len) == 0)
608             return (YES);
609     } else if (strcasecmp(tok, "LOCAL") == 0) { /* local: no PAM_RHOSTS */
610         if (item->from_remote_host == 0)
611             return (YES);
612     } else if (tok[(tok_len = strlen(tok)) - 1] == '.') {
613       struct addrinfo *res;
614       struct addrinfo hint;
615
616       memset (&hint, '\0', sizeof (hint));
617       hint.ai_flags = AI_CANONNAME;
618       hint.ai_family = AF_INET;
619
620       if (getaddrinfo (string, NULL, &hint, &res) != 0)
621         return NO;
622       else
623         {
624           struct addrinfo *runp = res;
625
626           while (runp != NULL)
627             {
628               char buf[INET_ADDRSTRLEN+2];
629
630               if (runp->ai_family == AF_INET)
631                 {
632                   inet_ntop (runp->ai_family,
633                              &((struct sockaddr_in *) runp->ai_addr)->sin_addr,
634                              buf, sizeof (buf));
635
636                   strcat (buf, ".");
637
638                   if (strncmp(tok, buf, tok_len) == 0)
639                     {
640                       freeaddrinfo (res);
641                       return YES;
642                     }
643                 }
644               runp = runp->ai_next;
645             }
646           freeaddrinfo (res);
647         }
648     } else {
649       /* Assume network/netmask with a IP of a host.  */
650       if (network_netmask_match(pamh, tok, string, item->debug))
651         return YES;
652     }
653
654     return NO;
655 }
656
657 /* string_match - match a string against one token */
658
659 static int
660 string_match (pam_handle_t *pamh, const char *tok, const char *string,
661     int debug)
662 {
663
664     if (debug)
665         pam_syslog (pamh, LOG_DEBUG,
666                     "string_match: tok=%s, item=%s", tok, string);
667
668     /*
669      * If the token has the magic value "ALL" the match always succeeds.
670      * Otherwise, return YES if the token fully matches the string.
671          * "NONE" token matches NULL string.
672      */
673
674     if (strcasecmp(tok, "ALL") == 0) {          /* all: always matches */
675         return (ALL);
676     } else if (string != NULL) {
677         if (strcasecmp(tok, string) == 0) {     /* try exact match */
678             return (YES);
679         }
680     } else if (strcasecmp(tok, "NONE") == 0) {
681         return (YES);
682     }
683     return (NO);
684 }
685
686
687 /* network_netmask_match - match a string against one token
688  * where string is a hostname or ip (v4,v6) address and tok
689  * represents either a single ip (v4,v6) address or a network/netmask
690  */
691 static int
692 network_netmask_match (pam_handle_t *pamh,
693                        const char *tok, const char *string, int debug)
694 {
695     char *netmask_ptr;
696     char netmask_string[MAXHOSTNAMELEN + 1];
697     int addr_type;
698
699     if (debug)
700     pam_syslog (pamh, LOG_DEBUG,
701                 "network_netmask_match: tok=%s, item=%s", tok, string);
702     /* OK, check if tok is of type addr/mask */
703     if ((netmask_ptr = strchr(tok, '/')) != NULL)
704       {
705         long netmask = 0;
706
707         /* YES */
708         *netmask_ptr = 0;
709         netmask_ptr++;
710
711         if (isipaddr(tok, &addr_type, NULL) == NO)
712           { /* no netaddr */
713             return NO;
714           }
715
716         /* check netmask */
717         if (isipaddr(netmask_ptr, NULL, NULL) == NO)
718           { /* netmask as integre value */
719             char *endptr = NULL;
720             netmask = strtol(netmask_ptr, &endptr, 0);
721             if ((endptr == NULL) || (*endptr != '\0'))
722                 { /* invalid netmask value */
723                   return NO;
724                 }
725             if ((netmask < 0) || (netmask >= 128))
726                 { /* netmask value out of range */
727                   return NO;
728                 }
729
730             netmask_ptr = number_to_netmask(netmask, addr_type,
731                 netmask_string, MAXHOSTNAMELEN);
732           }
733         }
734     else
735         /* NO, then check if it is only an addr */
736         if (isipaddr(tok, NULL, NULL) != YES)
737           {
738             return NO;
739           }
740
741     if (isipaddr(string, NULL, NULL) != YES)
742       {
743         /* Assume network/netmask with a name of a host.  */
744         struct addrinfo *res;
745         struct addrinfo hint;
746
747         memset (&hint, '\0', sizeof (hint));
748         hint.ai_flags = AI_CANONNAME;
749         hint.ai_family = AF_UNSPEC;
750
751         if (getaddrinfo (string, NULL, &hint, &res) != 0)
752             return NO;
753         else
754           {
755             struct addrinfo *runp = res;
756
757             while (runp != NULL)
758               {
759                 char buf[INET6_ADDRSTRLEN];
760
761                 inet_ntop (runp->ai_family,
762                         runp->ai_family == AF_INET
763                         ? (void *) &((struct sockaddr_in *) runp->ai_addr)->sin_addr
764                         : (void *) &((struct sockaddr_in6 *) runp->ai_addr)->sin6_addr,
765                         buf, sizeof (buf));
766
767                 if (are_addresses_equal(buf, tok, netmask_ptr))
768                   {
769                     freeaddrinfo (res);
770                     return YES;
771                   }
772                 runp = runp->ai_next;
773               }
774             freeaddrinfo (res);
775           }
776       }
777     else
778       return (are_addresses_equal(string, tok, netmask_ptr));
779
780   return NO;
781 }
782
783
784 /* --- public PAM management functions --- */
785
786 PAM_EXTERN int
787 pam_sm_authenticate (pam_handle_t *pamh, int flags UNUSED,
788                      int argc, const char **argv)
789 {
790     struct login_info loginfo;
791     const char *user=NULL;
792     const void *void_from=NULL;
793     const char *from;
794     struct passwd *user_pw;
795     char hostname[MAXHOSTNAMELEN + 1];
796
797
798     /* set username */
799
800     if (pam_get_user(pamh, &user, NULL) != PAM_SUCCESS || user == NULL
801         || *user == '\0') {
802         pam_syslog(pamh, LOG_ERR, "cannot determine the user's name");
803         return PAM_USER_UNKNOWN;
804     }
805
806     if ((user_pw=pam_modutil_getpwnam(pamh, user))==NULL)
807       return (PAM_USER_UNKNOWN);
808
809     /*
810      * Bundle up the arguments to avoid unnecessary clumsiness later on.
811      */
812     loginfo.user = user_pw;
813     loginfo.config_file = PAM_ACCESS_CONFIG;
814
815     /* parse the argument list */
816
817     if (!parse_args(pamh, &loginfo, argc, argv)) {
818         pam_syslog(pamh, LOG_ERR, "failed to parse the module arguments");
819         return PAM_ABORT;
820     }
821
822     /* remote host name */
823
824     if (pam_get_item(pamh, PAM_RHOST, &void_from)
825         != PAM_SUCCESS) {
826         pam_syslog(pamh, LOG_ERR, "cannot find the remote host name");
827         return PAM_ABORT;
828     }
829     from = void_from;
830
831     if ((from==NULL) || (*from=='\0')) {
832
833         /* local login, set tty name */
834
835         loginfo.from_remote_host = 0;
836
837         if (pam_get_item(pamh, PAM_TTY, &void_from) != PAM_SUCCESS
838             || void_from == NULL) {
839             D(("PAM_TTY not set, probing stdin"));
840             from = ttyname(STDIN_FILENO);
841             if (from != NULL) {
842                 if (pam_set_item(pamh, PAM_TTY, from) != PAM_SUCCESS)
843                     pam_syslog(pamh, LOG_WARNING, "couldn't set tty name");
844             } else {
845               if (pam_get_item(pamh, PAM_SERVICE, &void_from) != PAM_SUCCESS
846                   || void_from == NULL) {
847                 pam_syslog (pamh, LOG_ERR,
848                      "cannot determine remote host, tty or service name");
849                 return PAM_ABORT;
850               }
851               from = void_from;
852               if (loginfo.debug)
853                 pam_syslog (pamh, LOG_DEBUG,
854                             "cannot determine tty or remote hostname, using service %s",
855                             from);
856             }
857         }
858         else
859           from = void_from;
860
861         if (from[0] == '/') {   /* full path, remove device path.  */
862             const char *f;
863             from++;
864             if ((f = strchr(from, '/')) != NULL) {
865                 from = f + 1;
866             }
867         }
868     }
869     else
870       loginfo.from_remote_host = 1;
871
872     loginfo.from = from;
873
874     hostname[sizeof(hostname)-1] = '\0';
875     if (gethostname(hostname, sizeof(hostname)-1) == 0)
876         loginfo.hostname = hostname;
877     else {
878         pam_syslog (pamh, LOG_ERR, "gethostname failed: %m");
879         loginfo.hostname = NULL;
880     }
881
882     if (login_access(pamh, &loginfo)) {
883         return (PAM_SUCCESS);
884     } else {
885         pam_syslog(pamh, LOG_ERR,
886                    "access denied for user `%s' from `%s'",user,from);
887         return (PAM_PERM_DENIED);
888     }
889 }
890
891 PAM_EXTERN int
892 pam_sm_setcred (pam_handle_t *pamh UNUSED, int flags UNUSED,
893                 int argc UNUSED, const char **argv UNUSED)
894 {
895   return PAM_IGNORE;
896 }
897
898 PAM_EXTERN int
899 pam_sm_acct_mgmt (pam_handle_t *pamh, int flags,
900                   int argc, const char **argv)
901 {
902   return pam_sm_authenticate (pamh, flags, argc, argv);
903 }
904
905 PAM_EXTERN int
906 pam_sm_open_session(pam_handle_t *pamh, int flags,
907                     int argc, const char **argv)
908 {
909   return pam_sm_authenticate(pamh, flags, argc, argv);
910 }
911
912 PAM_EXTERN int
913 pam_sm_close_session(pam_handle_t *pamh, int flags,
914                      int argc, const char **argv)
915 {
916   return pam_sm_authenticate(pamh, flags, argc, argv);
917 }
918
919 PAM_EXTERN int
920 pam_sm_chauthtok(pam_handle_t *pamh, int flags,
921                  int argc, const char **argv)
922 {
923   return pam_sm_authenticate(pamh, flags, argc, argv);
924 }
925
926 /* end of module definition */
927
928 #ifdef PAM_STATIC
929
930 /* static module data */
931
932 struct pam_module _pam_access_modstruct = {
933     "pam_access",
934     pam_sm_authenticate,
935     pam_sm_setcred,
936     pam_sm_acct_mgmt,
937     pam_sm_open_session,
938     pam_sm_close_session,
939     pam_sm_chauthtok
940 };
941 #endif