Initial import from FreeBSD RELENG_4:
[dragonfly.git] / lib / libskey / skeyaccess.c
1  /*
2   * Figure out if UNIX passwords are permitted for any combination of user
3   * name, group member, terminal port, host_name or network:
4   *
5   * Programmatic interface: skeyaccess(user, port, host, addr)
6   *
7   * All arguments are null-terminated strings. Specify a null character pointer
8   * where information is not available.
9   *
10   * When no address information is given this code performs the host (internet)
11   * address lookup itself. It rejects addresses that appear to belong to
12   * someone else.
13   *
14   * When compiled with -DPERMIT_CONSOLE always permits UNIX passwords with
15   * console logins, no matter what the configuration file says.
16   *
17   * To build a stand-alone test version, compile with -DTEST and run it off an
18   * skey.access file in the current directory:
19   *
20   * Command-line interface: ./skeyaccess user port [host_or_ip_addr]
21   *
22   * Errors are reported via syslogd.
23   *
24   * Author: Wietse Venema, Eindhoven University of Technology.
25   *
26   * $FreeBSD: src/lib/libskey/skeyaccess.c,v 1.9.6.2 2002/08/12 19:42:24 iedowse Exp $
27   */
28
29 #include <sys/types.h>
30 #include <sys/socket.h>
31 #include <netinet/in.h>
32 #include <string.h>
33 #include <netdb.h>
34 #include <arpa/inet.h>
35 #include <stdio.h>
36 #include <grp.h>
37 #include <pwd.h>
38 #include <ctype.h>
39 #include <syslog.h>
40 #include <unistd.h>
41 #include <stdlib.h>
42 #include <sys/param.h>
43
44 #include "pathnames.h"
45
46  /*
47   * Token input with one-deep pushback.
48   */
49 static char *prev_token = 0;            /* push-back buffer */
50 static char *line_pointer = NULL;
51 static char *first_token __P((char *, int, FILE *));
52 static int line_number;
53 static void unget_token __P((char *));
54 static char *get_token __P((void));
55 static char *need_token __P((void));
56 static char *need_internet_addr __P((void));
57
58  /*
59   * Various forms of token matching.
60   */
61 #define match_host_name(l)      match_token((l)->host_name)
62 #define match_port(l)           match_token((l)->port)
63 #define match_user(l)           match_token((l)->user)
64 struct login_info;
65 static int match_internet_addr __P((struct login_info *));
66 static int match_group __P((struct login_info *));
67 static int match_token __P((char *));
68 static int is_internet_addr __P((char *));
69 static struct addrinfo *convert_internet_addr __P((char *));
70 static struct addrinfo *lookup_internet_addr __P((char *));
71
72 #define MAX_ADDR        32
73 #define PERMIT          1
74 #define DENY            0
75
76 #ifndef CONSOLE
77 #define CONSOLE         "console"
78 #endif
79 #ifndef VTY_PREFIX
80 #define VTY_PREFIX      "ttyv"
81 #endif
82
83 struct login_info {
84     char   *host_name;                  /* host name */
85     struct addrinfo *internet_addr;     /* addrinfo chain */
86     char   *user;                       /* user name */
87     char   *port;                       /* login port */
88 };
89
90 static int _skeyaccess __P((FILE *, struct login_info *));
91 int skeyaccess __P((char *, char *, char *, char *));
92
93 /* skeyaccess - find out if UNIX passwords are permitted */
94
95 int     skeyaccess(user, port, host, addr)
96 char   *user;
97 char   *port;
98 char   *host;
99 char   *addr;
100 {
101     FILE   *fp;
102     struct login_info login_info;
103     int     result;
104
105     /*
106      * Assume no restriction on the use of UNIX passwords when the s/key
107      * acces table does not exist.
108      */
109     if ((fp = fopen(_PATH_SKEYACCESS, "r")) == 0) {
110 #ifdef TEST
111         fprintf(stderr, "No file %s, thus no access control\n", _PATH_SKEYACCESS);
112 #endif
113         return (PERMIT);
114     }
115
116     /*
117      * Bundle up the arguments in a structure so we won't have to drag around
118      * boring long argument lists.
119      *
120      * Look up the host address when only the name is given. We try to reject
121      * addresses that belong to someone else.
122      */
123     login_info.user = user;
124     login_info.port = port;
125
126     if (host != NULL && !is_internet_addr(host)) {
127         login_info.host_name = host;
128     } else {
129         login_info.host_name = NULL;
130     }
131
132     if (addr != NULL && is_internet_addr(addr)) {
133         login_info.internet_addr = convert_internet_addr(addr);
134     } else if (host != NULL) {
135         if (is_internet_addr(host)) {
136             login_info.internet_addr = convert_internet_addr(host);
137         } else {
138             login_info.internet_addr = lookup_internet_addr(host);
139         }
140     } else {
141         login_info.internet_addr = NULL;
142     }
143
144     /*
145      * Print what we think the user wants us to do.
146      */
147 #ifdef TEST
148     printf("port: %s\n", login_info.port);
149     printf("user: %s\n", login_info.user);
150     printf("host: %s\n", login_info.host_name ? login_info.host_name : "none");
151     printf("addr: ");
152     if (login_info.internet_addr == NULL) {
153         printf("none\n");
154     } else {
155         struct addrinfo *res;
156         char haddr[NI_MAXHOST];
157
158         for (res = login_info.internet_addr; res; res = res->ai_next) {
159             getnameinfo(res->ai_addr, res->ai_addrlen, haddr, sizeof(haddr),
160                         NULL, 0, NI_NUMERICHOST | NI_WITHSCOPEID);
161             printf("%s%s", haddr, res->ai_next ? " " : "\n");
162         }
163     }
164 #endif
165     result = _skeyaccess(fp, &login_info);
166     fclose(fp);
167     if (login_info.internet_addr)
168         freeaddrinfo(login_info.internet_addr);
169     return (result);
170 }
171
172 /* _skeyaccess - find out if UNIX passwords are permitted */
173
174 static int _skeyaccess(fp, login_info)
175 FILE   *fp;
176 struct login_info *login_info;
177 {
178     char    buf[BUFSIZ];
179     char   *tok;
180     int     match;
181     int     permission=DENY;
182
183 #ifdef PERMIT_CONSOLE
184     if (login_info->port != 0 &&
185         (strcmp(login_info->port, CONSOLE) == 0 ||
186          strncmp(login_info->port, VTY_PREFIX, sizeof(VTY_PREFIX) - 1) == 0
187         )
188        )
189         return (1);
190 #endif
191
192     /*
193      * Scan the s/key access table until we find an entry that matches. If no
194      * match is found, assume that UNIX passwords are disallowed.
195      */
196     match = 0;
197     while (match == 0 && (tok = first_token(buf, sizeof(buf), fp))) {
198         if (strncasecmp(tok, "permit", 4) == 0) {
199             permission = PERMIT;
200         } else if (strncasecmp(tok, "deny", 4) == 0) {
201             permission = DENY;
202         } else {
203             syslog(LOG_ERR, "%s: line %d: bad permission: %s",
204                    _PATH_SKEYACCESS, line_number, tok);
205             continue;                           /* error */
206         }
207
208         /*
209          * Process all conditions in this entry until we find one that fails.
210          */
211         match = 1;
212         while (match != 0 && (tok = get_token())) {
213             if (strcasecmp(tok, "hostname") == 0) {
214                 match = match_host_name(login_info);
215             } else if (strcasecmp(tok, "port") == 0) {
216                 match = match_port(login_info);
217             } else if (strcasecmp(tok, "user") == 0) {
218                 match = match_user(login_info);
219             } else if (strcasecmp(tok, "group") == 0) {
220                 match = match_group(login_info);
221             } else if (strcasecmp(tok, "internet") == 0) {
222                 match = match_internet_addr(login_info);
223             } else if (is_internet_addr(tok)) {
224                 unget_token(tok);
225                 match = match_internet_addr(login_info);
226             } else {
227                 syslog(LOG_ERR, "%s: line %d: bad condition: %s",
228                        _PATH_SKEYACCESS, line_number, tok);
229                 match = 0;
230             }
231         }
232     }
233     return (match ? permission : DENY);
234 }
235
236 /* translate IPv4 mapped IPv6 address to IPv4 address */
237
238 static void
239 ai_unmapped(struct addrinfo *ai)
240 {
241     struct sockaddr_in6 *sin6;
242     struct sockaddr_in *sin4;
243     u_int32_t addr;
244     int port;
245
246     if (ai->ai_family != AF_INET6)
247         return;
248     sin6 = (struct sockaddr_in6 *)ai->ai_addr;
249     if (!IN6_IS_ADDR_V4MAPPED(&sin6->sin6_addr))
250         return;
251     sin4 = (struct sockaddr_in *)ai->ai_addr;
252     addr = *(u_int32_t *)&sin6->sin6_addr.s6_addr[12];
253     port = sin6->sin6_port;
254     memset(sin4, 0, sizeof(struct sockaddr_in));
255     sin4->sin_addr.s_addr = addr;
256     sin4->sin_port = port;
257     sin4->sin_family = AF_INET;
258     sin4->sin_len = sizeof(struct sockaddr_in);
259     ai->ai_family = AF_INET;
260     ai->ai_addrlen = sizeof(struct sockaddr_in);
261 }
262
263 /* match_internet_addr - match internet network address */
264
265 static int match_internet_addr(login_info)
266 struct login_info *login_info;
267 {
268     char *tok;
269     struct addrinfo *res;
270     struct sockaddr_storage pattern, mask;
271     struct sockaddr_in *addr4, *pattern4, *mask4;
272     struct sockaddr_in6 *addr6, *pattern6, *mask6;
273     int i, match;
274
275     if (login_info->internet_addr == NULL)
276         return (0);
277     if ((tok = need_internet_addr()) == 0)
278         return (0);
279     if ((res = convert_internet_addr(tok)) == NULL)
280         return (0);
281     memcpy(&pattern, res->ai_addr, res->ai_addrlen);
282     freeaddrinfo(res);
283     if ((tok = need_internet_addr()) == 0)
284         return (0);
285     if ((res = convert_internet_addr(tok)) == NULL)
286         return (0);
287     memcpy(&mask, res->ai_addr, res->ai_addrlen);
288     freeaddrinfo(res);
289     if (pattern.ss_family != mask.ss_family)
290         return (0);
291     mask4 = (struct sockaddr_in *)&mask;
292     pattern4 = (struct sockaddr_in *)&pattern;
293     mask6 = (struct sockaddr_in6 *)&mask;
294     pattern6 = (struct sockaddr_in6 *)&pattern;
295
296     /*
297      * See if any of the addresses matches a pattern in the control file. We
298      * have already tried to drop addresses that belong to someone else.
299      */
300
301     for (res = login_info->internet_addr; res; res = res->ai_next) {
302         ai_unmapped(res);
303         if (res->ai_family != pattern.ss_family)
304             continue;
305         switch (res->ai_family) {
306         case AF_INET:
307             addr4 = (struct sockaddr_in *)res->ai_addr;
308             if (addr4->sin_addr.s_addr != INADDR_NONE &&
309                 (addr4->sin_addr.s_addr & mask4->sin_addr.s_addr) == pattern4->sin_addr.s_addr)
310                 return (1);
311             break;
312         case AF_INET6:
313             addr6 = (struct sockaddr_in6 *)res->ai_addr;
314             if (pattern6->sin6_scope_id != 0 &&
315                 addr6->sin6_scope_id != pattern6->sin6_scope_id)
316                 break;
317             match = 1;
318             for (i = 0; i < 16; ++i) {
319                 if ((addr6->sin6_addr.s6_addr[i] & mask6->sin6_addr.s6_addr[i]) != pattern6->sin6_addr.s6_addr[i]) {
320                     match = 0;
321                     break;
322                 }
323             }
324             if (match)
325                 return (1);
326             break;
327         }
328     }
329     return (0);
330 }
331
332 /* match_group - match username against group */
333
334 static int match_group(login_info)
335 struct login_info *login_info;
336 {
337     struct passwd *passwd;
338     struct group *group;
339     char   *tok;
340     char  **memp;
341
342     if ((tok = need_token()) &&
343         (passwd = getpwnam(login_info->user)) && (group = getgrnam(tok))) {
344         if (passwd->pw_gid == (gid_t)group->gr_gid)
345             return (1);
346         for (memp = group->gr_mem; *memp; memp++)
347             if (strcmp(login_info->user, *memp) == 0)
348                 return (1);
349     }
350     return (0);                                 /* XXX endgrent() */
351 }
352
353 /* match_token - get and match token */
354
355 static int match_token(str)
356 char   *str;
357 {
358     char   *tok;
359
360     return (str && (tok = need_token()) && strcasecmp(str, tok) == 0);
361 }
362
363 /* first_token - read line and return first token */
364
365 static char *first_token(buf, len, fp)
366 char   *buf;
367 int     len;
368 FILE   *fp;
369 {
370     char   *cp;
371
372     prev_token = 0;
373     for (;;) {
374         if (fgets(buf, len, fp) == 0)
375             return (0);
376         line_number++;
377         buf[strcspn(buf, "\r\n#")] = 0;
378 #ifdef TEST
379         if (buf[0])
380             printf("rule: %s\n", buf);
381 #endif
382         line_pointer = buf;
383         while ((cp = strsep(&line_pointer, " \t")) != NULL && *cp == '\0')
384                 ;
385         if (cp != NULL)
386             return (cp);
387     }
388 }
389
390 /* unget_token - push back last token */
391
392 static void unget_token(cp)
393 char   *cp;
394 {
395     prev_token = cp;
396 }
397
398 /* get_token - retrieve next token from buffer */
399
400 static char *get_token()
401 {
402     char   *cp;
403
404     if ( (cp = prev_token) ) {
405         prev_token = 0;
406     } else {
407         while ((cp = strsep(&line_pointer, " \t")) != NULL && *cp == '\0')
408                 ;
409     }
410     return (cp);
411 }
412
413 /* need_token - complain if next token is not available */
414
415 static char *need_token()
416 {
417     char   *cp;
418
419     if ((cp = get_token()) == 0)
420         syslog(LOG_ERR, "%s: line %d: premature end of rule",
421                _PATH_SKEYACCESS, line_number);
422     return (cp);
423 }
424
425 /* need_internet_addr - complain if next token is not an internet address */
426
427 static char *need_internet_addr()
428 {
429     char   *cp;
430
431     if ((cp = get_token()) == 0) {
432         syslog(LOG_ERR, "%s: line %d: internet address expected",
433                _PATH_SKEYACCESS, line_number);
434         return (0);
435     } else if (!is_internet_addr(cp)) {
436         syslog(LOG_ERR, "%s: line %d: bad internet address: %s",
437                _PATH_SKEYACCESS, line_number, cp);
438         return (0);
439     } else {
440         return (cp);
441     }
442 }
443
444 /* is_internet_addr - determine if string is a dotted quad decimal address */
445
446 static int is_internet_addr(str)
447 char   *str;
448 {
449     struct addrinfo *res;
450
451     if ((res = convert_internet_addr(str)) != NULL)
452         freeaddrinfo(res);
453     return (res != NULL);
454 }
455
456 /*
457  * Nuke addrinfo entry from list.
458  * XXX: Depending on the implementation of KAME's getaddrinfo(3).
459  */
460 static void nuke_ai(aip)
461 struct addrinfo **aip;
462 {
463     struct addrinfo *ai;
464
465     ai = *aip;
466     *aip = ai->ai_next;
467     if (ai->ai_canonname) {
468         if (ai->ai_next && !ai->ai_next->ai_canonname)
469             ai->ai_next->ai_canonname = ai->ai_canonname;
470         else
471             free(ai->ai_canonname);
472     }
473     free(ai);
474 }
475
476 /* lookup_internet_addr - look up internet addresses with extreme prejudice */
477
478 static struct addrinfo *lookup_internet_addr(host)
479 char   *host;
480 {
481     struct addrinfo hints, *res0, *res, **resp;
482     char hname[NI_MAXHOST], haddr[NI_MAXHOST];
483     int error;
484
485     memset(&hints, 0, sizeof(hints));
486     hints.ai_family = PF_UNSPEC;
487     hints.ai_socktype = SOCK_STREAM;
488     hints.ai_flags = AI_PASSIVE | AI_CANONNAME;
489     if (getaddrinfo(host, NULL, &hints, &res0) != 0)
490         return (NULL);
491     if (res0->ai_canonname == NULL) {
492         freeaddrinfo(res0);
493         return (NULL);
494     }
495
496     /*
497      * Wipe addresses that appear to belong to someone else. We will get
498      * false alarms when when the hostname comes from DNS, while its
499      * addresses are listed under different names in local databases.
500      */
501 #define NEQ(x,y)        (strcasecmp((x),(y)) != 0)
502 #define NEQ3(x,y,n)     (strncasecmp((x),(y), (n)) != 0)
503
504     resp = &res0;
505     while ((res = *resp) != NULL) {
506         if (res->ai_family != AF_INET && res->ai_family != AF_INET6) {
507             nuke_ai(resp);
508             continue;
509         }
510         error = getnameinfo(res->ai_addr, res->ai_addrlen,
511                             hname, sizeof(hname),
512                             NULL, 0, NI_NAMEREQD | NI_WITHSCOPEID);
513         if (error) {
514             getnameinfo(res->ai_addr, res->ai_addrlen, haddr, sizeof(haddr),
515                         NULL, 0, NI_NUMERICHOST | NI_WITHSCOPEID);
516             syslog(LOG_ERR, "address %s not registered for host %s",
517                    haddr, res0->ai_canonname);
518             nuke_ai(resp);
519             continue;
520         }
521         if (NEQ(res0->ai_canonname, hname) &&
522             NEQ3(res0->ai_canonname, "localhost.", 10)) {
523             getnameinfo(res->ai_addr, res->ai_addrlen, haddr, sizeof(haddr),
524                         NULL, 0, NI_NUMERICHOST | NI_WITHSCOPEID);
525             syslog(LOG_ERR, "address %s registered for host %s and %s",
526                    haddr, hname, res0->ai_canonname);
527             nuke_ai(resp);
528             continue;
529         }
530         resp = &res->ai_next;
531     }
532     return (res0);
533 }
534
535 /* convert_internet_addr - convert string to internet address */
536
537 static struct addrinfo *convert_internet_addr(string)
538 char   *string;
539 {
540     struct addrinfo hints, *res;
541
542     memset(&hints, 0, sizeof(hints));
543     hints.ai_family = PF_UNSPEC;
544     hints.ai_socktype = SOCK_STREAM;
545     hints.ai_flags = AI_PASSIVE | AI_NUMERICHOST;
546     if (getaddrinfo(string, NULL, &hints, &res) != 0)
547         return (NULL);
548     return (res);
549 }
550
551 #ifdef TEST
552
553 main(argc, argv)
554 int     argc;
555 char  **argv;
556 {
557     struct addrinfo hints, *res;
558     char    host[MAXHOSTNAMELEN + 1];
559     int     verdict;
560     char   *user;
561     char   *port;
562
563     if (argc != 3 && argc != 4) {
564         fprintf(stderr, "usage: %s user port [host_or_ip_address]\n", argv[0]);
565         exit(0);
566     }
567     if (_PATH_SKEYACCESS[0] != '/')
568         printf("Warning: this program uses control file: %s\n", _PATH_SKEYACCESS);
569     openlog("login", LOG_PID, LOG_AUTH);
570
571     user = argv[1];
572     port = argv[2];
573     if (argv[3]) {
574         memset(&hints, 0, sizeof(hints));
575         hints.ai_family = PF_UNSPEC;
576         hints.ai_socktype = SOCK_STREAM;
577         hints.ai_flags = AI_PASSIVE | AI_CANONNAME;
578         if (getaddrinfo(argv[3], NULL, &hints, &res) == 0) {
579             if (res->ai_canonname == NULL)
580                 strncpy(host, argv[3], MAXHOSTNAMELEN);
581             else
582                 strncpy(host, res->ai_canonname, MAXHOSTNAMELEN);
583             freeaddrinfo(res);
584         } else
585             strncpy(host, argv[3], MAXHOSTNAMELEN);
586         host[MAXHOSTNAMELEN] = 0;
587     }
588     verdict = skeyaccess(user, port, argv[3] ? host : (char *) 0, (char *) 0);
589     printf("UNIX passwords %spermitted\n", verdict ? "" : "NOT ");
590     return (0);
591 }
592
593 #endif