MFC:
[dragonfly.git] / lib / libskey / skeyaccess.c
blob20b25e09ed109263e90d3881162490f9b3635d9c
1 /*
2 * Figure out if UNIX passwords are permitted for any combination of user
3 * name, group member, terminal port, host_name or network:
5 * Programmatic interface: skeyaccess(user, port, host, addr)
7 * All arguments are null-terminated strings. Specify a null character pointer
8 * where information is not available.
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.
14 * When compiled with -DPERMIT_CONSOLE always permits UNIX passwords with
15 * console logins, no matter what the configuration file says.
17 * To build a stand-alone test version, compile with -DTEST and run it off an
18 * skey.access file in the current directory:
20 * Command-line interface: ./skeyaccess user port [host_or_ip_addr]
22 * Errors are reported via syslogd.
24 * Author: Wietse Venema, Eindhoven University of Technology.
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 $
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>
45 #include "pathnames.h"
48 * Token input with one-deep pushback.
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);
60 * Various forms of token matching.
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 *);
73 #define MAX_ADDR 32
74 #define PERMIT 1
75 #define DENY 0
77 #ifndef CONSOLE
78 #define CONSOLE "console"
79 #endif
80 #ifndef VTY_PREFIX
81 #define VTY_PREFIX "ttyv"
82 #endif
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 */
91 static int _skeyaccess (FILE *, struct login_info *);
92 int skeyaccess (char *, char *, char *, char *);
94 /* skeyaccess - find out if UNIX passwords are permitted */
96 int skeyaccess(user, port, host, addr)
97 char *user;
98 char *port;
99 char *host;
100 char *addr;
102 FILE *fp;
103 struct login_info login_info;
104 int result;
107 * Assume no restriction on the use of UNIX passwords when the s/key
108 * acces table does not exist.
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);
118 * Bundle up the arguments in a structure so we won't have to drag around
119 * boring long argument lists.
121 * Look up the host address when only the name is given. We try to reject
122 * addresses that belong to someone else.
124 login_info.user = user;
125 login_info.port = port;
127 if (host != NULL && !is_internet_addr(host)) {
128 login_info.host_name = host;
129 } else {
130 login_info.host_name = NULL;
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);
141 } else {
142 login_info.internet_addr = NULL;
146 * Print what we think the user wants us to do.
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];
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");
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);
173 /* _skeyaccess - find out if UNIX passwords are permitted */
175 static int _skeyaccess(fp, login_info)
176 FILE *fp;
177 struct login_info *login_info;
179 char buf[BUFSIZ];
180 char *tok;
181 int match;
182 int permission=DENY;
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
190 return (1);
191 #endif
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.
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 */
210 * Process all conditions in this entry until we find one that fails.
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;
234 return (match ? permission : DENY);
237 /* translate IPv4 mapped IPv6 address to IPv4 address */
239 static void
240 ai_unmapped(struct addrinfo *ai)
242 struct sockaddr_in6 *sin6;
243 struct sockaddr_in *sin4;
244 u_int32_t addr;
245 int port;
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);
264 /* match_internet_addr - match internet network address */
266 static int match_internet_addr(login_info)
267 struct login_info *login_info;
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;
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;
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.
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;
325 if (match)
326 return (1);
327 break;
330 return (0);
333 /* match_group - match username against group */
335 static int match_group(login_info)
336 struct login_info *login_info;
338 struct passwd *passwd;
339 struct group *group;
340 char *tok;
341 char **memp;
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);
351 return (0); /* XXX endgrent() */
354 /* match_token - get and match token */
356 static int match_token(str)
357 char *str;
359 char *tok;
361 return (str && (tok = need_token()) && strcasecmp(str, tok) == 0);
364 /* first_token - read line and return first token */
366 static char *first_token(buf, len, fp)
367 char *buf;
368 int len;
369 FILE *fp;
371 char *cp;
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')
386 if (cp != NULL)
387 return (cp);
391 /* unget_token - push back last token */
393 static void unget_token(cp)
394 char *cp;
396 prev_token = cp;
399 /* get_token - retrieve next token from buffer */
401 static char *get_token()
403 char *cp;
405 if ( (cp = prev_token) ) {
406 prev_token = 0;
407 } else {
408 while ((cp = strsep(&line_pointer, " \t")) != NULL && *cp == '\0')
411 return (cp);
414 /* need_token - complain if next token is not available */
416 static char *need_token()
418 char *cp;
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);
426 /* need_internet_addr - complain if next token is not an internet address */
428 static char *need_internet_addr()
430 char *cp;
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);
445 /* is_internet_addr - determine if string is a dotted quad decimal address */
447 static int is_internet_addr(str)
448 char *str;
450 struct addrinfo *res;
452 if ((res = convert_internet_addr(str)) != NULL)
453 freeaddrinfo(res);
454 return (res != NULL);
458 * Nuke addrinfo entry from list.
459 * XXX: Depending on the implementation of KAME's getaddrinfo(3).
461 static void
462 nuke_ai(struct addrinfo **aip)
464 struct addrinfo *ai;
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);
474 free(ai);
477 /* lookup_internet_addr - look up internet addresses with extreme prejudice */
479 static struct addrinfo *
480 lookup_internet_addr(char *host)
482 struct addrinfo hints, *res0, *res, **resp;
483 char hname[NI_MAXHOST], haddr[NI_MAXHOST];
484 int error;
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);
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.
502 #define NEQ(x,y) (strcasecmp((x),(y)) != 0)
503 #define NEQ3(x,y,n) (strncasecmp((x),(y), (n)) != 0)
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;
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;
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;
531 resp = &res->ai_next;
533 return (res0);
536 /* convert_internet_addr - convert string to internet address */
538 static struct addrinfo *convert_internet_addr(string)
539 char *string;
541 struct addrinfo hints, *res;
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);
552 #ifdef TEST
554 main(argc, argv)
555 int argc;
556 char **argv;
558 struct addrinfo hints, *res;
559 char host[MAXHOSTNAMELEN + 1];
560 int verdict;
561 char *user;
562 char *port;
564 if (argc != 3 && argc != 4) {
565 fprintf(stderr, "usage: %s user port [host_or_ip_address]\n", argv[0]);
566 exit(0);
568 if (_PATH_SKEYACCESS[0] != '/')
569 printf("Warning: this program uses control file: %s\n", _PATH_SKEYACCESS);
570 openlog("login", LOG_PID, LOG_AUTH);
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;
589 verdict = skeyaccess(user, port, argv[3] ? host : (char *) 0, (char *) 0);
590 printf("UNIX passwords %spermitted\n", verdict ? "" : "NOT ");
591 return (0);
594 #endif