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