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