1 | /* $NetBSD: identd.c,v 1.36 2016/12/10 22:08:13 christos Exp $ */ |
2 | |
3 | /* |
4 | * identd.c - TCP/IP Ident protocol server. |
5 | * |
6 | * This software is in the public domain. |
7 | * Written by Peter Postma <peter@NetBSD.org> |
8 | */ |
9 | |
10 | #include <sys/cdefs.h> |
11 | __RCSID("$NetBSD: identd.c,v 1.36 2016/12/10 22:08:13 christos Exp $" ); |
12 | |
13 | #include <sys/param.h> |
14 | #include <sys/socket.h> |
15 | #include <sys/stat.h> |
16 | #include <sys/sysctl.h> |
17 | |
18 | #include <netinet/in.h> |
19 | #include <netinet/ip_var.h> |
20 | #include <netinet/tcp.h> |
21 | #include <netinet/tcp_timer.h> |
22 | #include <netinet/tcp_var.h> |
23 | |
24 | #include <arpa/inet.h> |
25 | |
26 | #include <ctype.h> |
27 | #include <err.h> |
28 | #include <errno.h> |
29 | #include <fcntl.h> |
30 | #include <grp.h> |
31 | #include <netdb.h> |
32 | #include <poll.h> |
33 | #include <pwd.h> |
34 | #include <signal.h> |
35 | #include <stdarg.h> |
36 | #include <stdio.h> |
37 | #include <stdlib.h> |
38 | #include <string.h> |
39 | #include <syslog.h> |
40 | #include <unistd.h> |
41 | |
42 | #include "identd.h" |
43 | |
44 | #define OPSYS_NAME "UNIX" |
45 | #define IDENT_SERVICE "auth" |
46 | #define TIMEOUT 30 /* seconds */ |
47 | |
48 | static int idhandle(int, const char *, const char *, const char *, |
49 | const char *, struct sockaddr *, int); |
50 | static void idparse(int, int, int, const char *, const char *, const char *); |
51 | static void iderror(int, int, int, const char *); |
52 | static const char *gethost(struct sockaddr *); |
53 | static int *socketsetup(const char *, const char *, int); |
54 | static int ident_getuid(struct sockaddr_storage *, socklen_t, |
55 | struct sockaddr *, uid_t *); |
56 | static int sysctl_getuid(struct sockaddr_storage *, socklen_t, uid_t *); |
57 | static int sysctl_proxy_getuid(struct sockaddr_storage *, |
58 | struct sockaddr *, uid_t *); |
59 | static int forward(int, struct sockaddr *, int, int, int); |
60 | static int check_noident(const char *); |
61 | static int check_userident(const char *, char *, size_t); |
62 | static void random_string(char *, size_t); |
63 | static int change_format(const char *, struct passwd *, char *, size_t); |
64 | __dead static void timeout_handler(int); |
65 | __dead static void fatal(const char *); |
66 | __dead static void die(const char *, ...) __printflike(1, 2); |
67 | |
68 | static int bflag, dflag, eflag, fflag, iflag, Iflag; |
69 | static int lflag, Lflag, nflag, Nflag, rflag; |
70 | |
71 | /* NAT lookup function pointer. */ |
72 | typedef int (*nat_lookup_t)(const struct sockaddr_storage *, |
73 | struct sockaddr_storage *, in_port_t *); |
74 | |
75 | static nat_lookup_t nat_lookup; |
76 | |
77 | /* Packet filters. */ |
78 | static const struct { |
79 | const char *name; |
80 | nat_lookup_t fn; |
81 | } filters[] = { |
82 | #ifdef WITH_PF |
83 | { "pf" , pf_natlookup }, |
84 | #endif |
85 | #ifdef WITH_IPF |
86 | { "ipfilter" , ipf_natlookup }, |
87 | #endif |
88 | #ifdef WITH_NPF |
89 | { "npf" , npf_natlookup }, |
90 | #endif |
91 | { NULL, NULL } |
92 | }; |
93 | |
94 | int |
95 | main(int argc, char *argv[]) |
96 | { |
97 | int IPv4or6, ch, error, i, *socks, timeout; |
98 | const char *filter, *osname, *portno, *proxy; |
99 | char *address, *charset, *fmt, *p; |
100 | char user[LOGIN_NAME_MAX]; |
101 | struct addrinfo *ai, hints; |
102 | struct sockaddr *proxy_addr; |
103 | struct group *grp; |
104 | struct passwd *pw; |
105 | gid_t gid; |
106 | uid_t uid; |
107 | |
108 | socks = NULL; |
109 | IPv4or6 = AF_UNSPEC; |
110 | osname = OPSYS_NAME; |
111 | portno = IDENT_SERVICE; |
112 | timeout = TIMEOUT; |
113 | nat_lookup = NULL; |
114 | proxy_addr = NULL; |
115 | filter = proxy = NULL; |
116 | address = charset = fmt = NULL; |
117 | uid = gid = 0; |
118 | bflag = dflag = eflag = fflag = iflag = Iflag = 0; |
119 | lflag = Lflag = nflag = Nflag = rflag = 0; |
120 | |
121 | /* Started from a tty? then run as daemon. */ |
122 | if (isatty(STDIN_FILENO)) |
123 | bflag = 1; |
124 | |
125 | /* Parse command line arguments. */ |
126 | while ((ch = getopt(argc, argv, |
127 | "46a:bcdeF:f:g:IiL:lm:Nno:P:p:rt:u:" )) != -1) { |
128 | switch (ch) { |
129 | case '4': |
130 | IPv4or6 = AF_INET; |
131 | break; |
132 | case '6': |
133 | IPv4or6 = AF_INET6; |
134 | break; |
135 | case 'a': |
136 | address = optarg; |
137 | break; |
138 | case 'b': |
139 | bflag = 1; |
140 | break; |
141 | case 'c': |
142 | charset = optarg; |
143 | break; |
144 | case 'd': |
145 | dflag++; |
146 | break; |
147 | case 'e': |
148 | eflag = 1; |
149 | break; |
150 | case 'F': |
151 | fmt = optarg; |
152 | break; |
153 | case 'f': |
154 | fflag = 1; |
155 | (void)strlcpy(user, optarg, sizeof(user)); |
156 | break; |
157 | case 'g': |
158 | gid = (gid_t)strtol(optarg, &p, 0); |
159 | if (*p != '\0') { |
160 | if ((grp = getgrnam(optarg)) != NULL) |
161 | gid = grp->gr_gid; |
162 | else |
163 | die("No such group `%s'" , optarg); |
164 | } |
165 | break; |
166 | case 'I': |
167 | Iflag = 1; |
168 | /* FALLTHROUGH */ |
169 | case 'i': |
170 | iflag = 1; |
171 | break; |
172 | case 'L': |
173 | Lflag = 1; |
174 | (void)strlcpy(user, optarg, sizeof(user)); |
175 | break; |
176 | case 'l': |
177 | if (!lflag) |
178 | openlog("identd" , LOG_PID, LOG_DAEMON); |
179 | lflag = 1; |
180 | break; |
181 | case 'm': |
182 | filter = optarg; |
183 | break; |
184 | case 'N': |
185 | Nflag = 1; |
186 | break; |
187 | case 'n': |
188 | nflag = 1; |
189 | break; |
190 | case 'o': |
191 | osname = optarg; |
192 | break; |
193 | case 'P': |
194 | proxy = optarg; |
195 | break; |
196 | case 'p': |
197 | portno = optarg; |
198 | break; |
199 | case 'r': |
200 | rflag = 1; |
201 | break; |
202 | case 't': |
203 | timeout = (int)strtol(optarg, &p, 0); |
204 | if (*p != '\0' || timeout < 1) |
205 | die("Invalid timeout value `%s'" , optarg); |
206 | break; |
207 | case 'u': |
208 | uid = (uid_t)strtol(optarg, &p, 0); |
209 | if (*p != '\0') { |
210 | if ((pw = getpwnam(optarg)) != NULL) { |
211 | uid = pw->pw_uid; |
212 | gid = pw->pw_gid; |
213 | } else |
214 | die("No such user `%s'" , optarg); |
215 | } |
216 | break; |
217 | default: |
218 | exit(EXIT_FAILURE); |
219 | } |
220 | } |
221 | |
222 | /* Verify proxy address, if enabled. */ |
223 | if (proxy != NULL) { |
224 | (void)memset(&hints, 0, sizeof(hints)); |
225 | hints.ai_family = IPv4or6; |
226 | hints.ai_socktype = SOCK_STREAM; |
227 | error = getaddrinfo(proxy, NULL, &hints, &ai); |
228 | if (error != 0) |
229 | die("Bad proxy `%s': %s" , proxy, gai_strerror(error)); |
230 | if (ai->ai_next != NULL) |
231 | die("Bad proxy `%s': resolves to multiple addresses" , |
232 | proxy); |
233 | proxy_addr = ai->ai_addr; |
234 | } |
235 | |
236 | /* Verify filter, if enabled. */ |
237 | if (filter != NULL) { |
238 | for (i = 0; filters[i].name != NULL; i++) { |
239 | if (strcasecmp(filter, filters[i].name) == 0) { |
240 | nat_lookup = filters[i].fn; |
241 | break; |
242 | } |
243 | } |
244 | if (nat_lookup == NULL) |
245 | die("Packet filter `%s' is not supported" , filter); |
246 | } |
247 | |
248 | /* Setup sockets when running in the background. */ |
249 | if (bflag) |
250 | socks = socketsetup(address, portno, IPv4or6); |
251 | |
252 | /* Switch to another uid/gid? */ |
253 | if (gid && setgid(gid) == -1) |
254 | die("Failed to set GID to `%d': %s" , gid, strerror(errno)); |
255 | if (uid && setuid(uid) == -1) |
256 | die("Failed to set UID to `%d': %s" , uid, strerror(errno)); |
257 | |
258 | /* |
259 | * When running as daemon: daemonize, setup pollfds and go into |
260 | * the mainloop. Otherwise, just read the input from stdin and |
261 | * let inetd handle the sockets. |
262 | */ |
263 | if (bflag) { |
264 | int fd, nfds, rv; |
265 | struct pollfd *rfds; |
266 | |
267 | if (!dflag && daemon(0, 0) < 0) |
268 | die("daemon: %s" , strerror(errno)); |
269 | |
270 | rfds = malloc(*socks * sizeof(struct pollfd)); |
271 | if (rfds == NULL) |
272 | fatal("malloc" ); |
273 | nfds = *socks; |
274 | for (i = 0; i < nfds; i++) { |
275 | rfds[i].fd = socks[i+1]; |
276 | rfds[i].events = POLLIN; |
277 | rfds[i].revents = 0; |
278 | } |
279 | /* Mainloop for daemon. */ |
280 | for (;;) { |
281 | rv = poll(rfds, nfds, INFTIM); |
282 | if (rv < 0) { |
283 | if (errno == EINTR) |
284 | continue; |
285 | fatal("poll" ); |
286 | } |
287 | for (i = 0; i < nfds; i++) { |
288 | if (rfds[i].revents & POLLIN) { |
289 | fd = accept(rfds[i].fd, NULL, NULL); |
290 | if (fd < 0) { |
291 | maybe_syslog(LOG_ERR, |
292 | "accept: %m" ); |
293 | continue; |
294 | } |
295 | switch (fork()) { |
296 | case -1: /* error */ |
297 | maybe_syslog(LOG_ERR, |
298 | "fork: %m" ); |
299 | (void)sleep(1); |
300 | break; |
301 | case 0: /* child */ |
302 | (void)idhandle(fd, charset, |
303 | fmt, osname, user, |
304 | proxy_addr, timeout); |
305 | _exit(EXIT_SUCCESS); |
306 | default: /* parent */ |
307 | (void)signal(SIGCHLD, SIG_IGN); |
308 | (void)close(fd); |
309 | } |
310 | } |
311 | } |
312 | } |
313 | } else |
314 | (void)idhandle(STDIN_FILENO, charset, fmt, osname, user, |
315 | proxy_addr, timeout); |
316 | |
317 | return 0; |
318 | } |
319 | |
320 | /* |
321 | * Handle a request on the ident port. Returns 0 on success or 1 on |
322 | * failure. The return values are currently ignored. |
323 | */ |
324 | static int |
325 | idhandle(int fd, const char *charset, const char *fmt, const char *osname, |
326 | const char *user, struct sockaddr *proxy, int timeout) |
327 | { |
328 | struct sockaddr_storage ss[2]; |
329 | char userbuf[LOGIN_NAME_MAX]; /* actual user name (or numeric uid) */ |
330 | char idbuf[LOGIN_NAME_MAX]; /* name to be used in response */ |
331 | char buf[BUFSIZ], *p; |
332 | struct passwd *pw; |
333 | int lport, fport; |
334 | socklen_t len; |
335 | uid_t uid; |
336 | ssize_t n; |
337 | size_t qlen; |
338 | |
339 | lport = fport = 0; |
340 | |
341 | (void)strlcpy(idbuf, user, sizeof(idbuf)); |
342 | (void)signal(SIGALRM, timeout_handler); |
343 | (void)alarm(timeout); |
344 | |
345 | /* Get foreign internet address. */ |
346 | len = sizeof(ss[0]); |
347 | if (getpeername(fd, (struct sockaddr *)&ss[0], &len) < 0) |
348 | fatal("getpeername" ); |
349 | |
350 | maybe_syslog(LOG_INFO, "Connection from %s" , |
351 | gethost((struct sockaddr *)&ss[0])); |
352 | |
353 | /* Get local internet address. */ |
354 | len = sizeof(ss[1]); |
355 | if (getsockname(fd, (struct sockaddr *)&ss[1], &len) < 0) |
356 | fatal("getsockname" ); |
357 | |
358 | /* Be sure to have the same address families. */ |
359 | if (ss[0].ss_family != ss[1].ss_family) { |
360 | maybe_syslog(LOG_ERR, "Different foreign/local address family" ); |
361 | return 1; |
362 | } |
363 | |
364 | /* Receive data from the client. */ |
365 | qlen = 0; |
366 | for (;;) { |
367 | if ((n = recv(fd, &buf[qlen], sizeof(buf) - qlen, 0)) < 0) { |
368 | fatal("recv" ); |
369 | } else if (n == 0) { |
370 | maybe_syslog(LOG_NOTICE, "recv: EOF" ); |
371 | iderror(fd, 0, 0, "UNKNOWN-ERROR" ); |
372 | return 1; |
373 | } |
374 | /* |
375 | * 1413 is not clear on what to do if data follows the first |
376 | * CRLF before we respond. We do not consider the query |
377 | * complete until we get a CRLF _at the end of the buffer_. |
378 | */ |
379 | qlen += n; |
380 | if (qlen >= sizeof(buf)) { |
381 | maybe_syslog(LOG_NOTICE, "recv: message too long" ); |
382 | exit(0); |
383 | } |
384 | if ((qlen >= 2) && (buf[qlen - 2] == '\r') && |
385 | (buf[qlen - 1] == '\n')) |
386 | break; |
387 | } |
388 | buf[qlen - 2] = '\0'; |
389 | |
390 | /* Get local and remote ports from the received data. */ |
391 | p = buf; |
392 | while (*p != '\0' && isspace((unsigned char)*p)) |
393 | p++; |
394 | if ((p = strtok(p, " \t," )) != NULL) { |
395 | lport = atoi(p); |
396 | if ((p = strtok(NULL, " \t," )) != NULL) |
397 | fport = atoi(p); |
398 | } |
399 | |
400 | /* Are the ports valid? */ |
401 | if (lport < 1 || lport > 65535 || fport < 1 || fport > 65535) { |
402 | maybe_syslog(LOG_NOTICE, "Invalid port(s): %d, %d from %s" , |
403 | lport, fport, gethost((struct sockaddr *)&ss[0])); |
404 | iderror(fd, 0, 0, eflag ? "UNKNOWN-ERROR" : "INVALID-PORT" ); |
405 | return 1; |
406 | } |
407 | |
408 | /* If there is a 'lie' user enabled, then handle it now and stop. */ |
409 | if (Lflag) { |
410 | maybe_syslog(LOG_NOTICE, "Lying with name %s to %s" , |
411 | idbuf, gethost((struct sockaddr *)&ss[0])); |
412 | idparse(fd, lport, fport, charset, osname, idbuf); |
413 | return 0; |
414 | } |
415 | |
416 | /* Protocol dependent stuff. */ |
417 | switch (ss[0].ss_family) { |
418 | case AF_INET: |
419 | satosin(&ss[0])->sin_port = htons(fport); |
420 | satosin(&ss[1])->sin_port = htons(lport); |
421 | break; |
422 | case AF_INET6: |
423 | satosin6(&ss[0])->sin6_port = htons(fport); |
424 | satosin6(&ss[1])->sin6_port = htons(lport); |
425 | break; |
426 | default: |
427 | maybe_syslog(LOG_ERR, "Unsupported protocol (no. %d)" , |
428 | ss[0].ss_family); |
429 | return 1; |
430 | } |
431 | |
432 | /* Try to get the UID of the connection owner using sysctl. */ |
433 | if (ident_getuid(ss, sizeof(ss), proxy, &uid) == -1) { |
434 | /* Lookup failed, try to forward if enabled. */ |
435 | if (nat_lookup != NULL) { |
436 | struct sockaddr_storage nat_addr; |
437 | in_port_t nat_lport; |
438 | |
439 | (void)memset(&nat_addr, 0, sizeof(nat_addr)); |
440 | if ((*nat_lookup)(ss, &nat_addr, &nat_lport) && |
441 | forward(fd, (struct sockaddr *)&nat_addr, |
442 | nat_lport, fport, lport)) { |
443 | maybe_syslog(LOG_INFO, |
444 | "Succesfully forwarded the request to %s" , |
445 | gethost((struct sockaddr *)&nat_addr)); |
446 | return 0; |
447 | } |
448 | } |
449 | /* Fall back to a default name? */ |
450 | if (fflag) { |
451 | maybe_syslog(LOG_NOTICE, "Using fallback name %s to %s" , |
452 | idbuf, gethost((struct sockaddr *)&ss[0])); |
453 | idparse(fd, lport, fport, charset, osname, idbuf); |
454 | return 0; |
455 | } |
456 | maybe_syslog(LOG_ERR, "Lookup failed, returning error to %s" , |
457 | gethost((struct sockaddr *)&ss[0])); |
458 | iderror(fd, lport, fport, eflag ? "UNKNOWN-ERROR" : "NO-USER" ); |
459 | return 1; |
460 | } |
461 | |
462 | /* Fill in userbuf with user name if possible, else numeric UID. */ |
463 | if ((pw = getpwuid(uid)) == NULL) { |
464 | maybe_syslog(LOG_ERR, "Couldn't map uid (%u) to name" , uid); |
465 | (void)snprintf(userbuf, sizeof(userbuf), "%u" , uid); |
466 | } else { |
467 | maybe_syslog(LOG_INFO, "Successful lookup: %d, %d: %s for %s" , |
468 | lport, fport, pw->pw_name, |
469 | gethost((struct sockaddr *)&ss[0])); |
470 | (void)strlcpy(userbuf, pw->pw_name, sizeof(userbuf)); |
471 | } |
472 | |
473 | /* No ident enabled? */ |
474 | if (Nflag && pw && check_noident(pw->pw_dir)) { |
475 | maybe_syslog(LOG_NOTICE, "Returning HIDDEN-USER for user %s" |
476 | " to %s" , pw->pw_name, gethost((struct sockaddr *)&ss[0])); |
477 | iderror(fd, lport, fport, "HIDDEN-USER" ); |
478 | return 1; |
479 | } |
480 | |
481 | /* User ident enabled? */ |
482 | if (iflag && pw && check_userident(pw->pw_dir, idbuf, sizeof(idbuf))) { |
483 | if (!Iflag) { |
484 | if ((strspn(idbuf, "0123456789" ) && |
485 | getpwuid(atoi(idbuf)) != NULL) || |
486 | (getpwnam(idbuf) != NULL)) { |
487 | maybe_syslog(LOG_NOTICE, |
488 | "Ignoring user-specified '%s' for user %s" , |
489 | idbuf, userbuf); |
490 | (void)strlcpy(idbuf, userbuf, sizeof(idbuf)); |
491 | } |
492 | } |
493 | maybe_syslog(LOG_NOTICE, |
494 | "Returning user-specified '%s' for user %s to %s" , |
495 | idbuf, userbuf, gethost((struct sockaddr *)&ss[0])); |
496 | idparse(fd, lport, fport, charset, osname, idbuf); |
497 | return 0; |
498 | } |
499 | |
500 | /* Send a random message? */ |
501 | if (rflag) { |
502 | /* Random number or string? */ |
503 | if (nflag) |
504 | (void)snprintf(idbuf, sizeof(idbuf), "%u" , |
505 | (unsigned int)(arc4random() % 65535)); |
506 | else |
507 | random_string(idbuf, sizeof(idbuf)); |
508 | |
509 | maybe_syslog(LOG_NOTICE, |
510 | "Returning random '%s' for user %s to %s" , |
511 | idbuf, userbuf, gethost((struct sockaddr *)&ss[0])); |
512 | idparse(fd, lport, fport, charset, osname, idbuf); |
513 | return 0; |
514 | } |
515 | |
516 | /* Return numberic user ID? */ |
517 | if (nflag) |
518 | (void)snprintf(idbuf, sizeof(idbuf), "%u" , uid); |
519 | else |
520 | (void)strlcpy(idbuf, userbuf, sizeof(idbuf)); |
521 | |
522 | /* |
523 | * Change the output format? Note that 512 is the maximum |
524 | * size of the result according to RFC 1413. |
525 | */ |
526 | if (fmt && change_format(fmt, pw, buf, 512 + 1)) |
527 | idparse(fd, lport, fport, charset, osname, buf); |
528 | else |
529 | idparse(fd, lport, fport, charset, osname, idbuf); |
530 | |
531 | return 0; |
532 | } |
533 | |
534 | /* Send/parse the ident result. */ |
535 | static void |
536 | idparse(int fd, int lport, int fport, const char *charset, const char *osname, |
537 | const char *user) |
538 | { |
539 | char *p; |
540 | |
541 | if (asprintf(&p, "%d,%d:USERID:%s%s%s:%s\r\n" , lport, fport, |
542 | osname, charset ? "," : "" , charset ? charset : "" , user) < 0) |
543 | fatal("asprintf" ); |
544 | if (send(fd, p, strlen(p), 0) < 0) { |
545 | free(p); |
546 | fatal("send" ); |
547 | } |
548 | free(p); |
549 | } |
550 | |
551 | /* Return a specified ident error. */ |
552 | static void |
553 | iderror(int fd, int lport, int fport, const char *error) |
554 | { |
555 | char *p; |
556 | |
557 | if (asprintf(&p, "%d,%d:ERROR:%s\r\n" , lport, fport, error) < 0) |
558 | fatal("asprintf" ); |
559 | if (send(fd, p, strlen(p), 0) < 0) { |
560 | free(p); |
561 | fatal("send" ); |
562 | } |
563 | free(p); |
564 | } |
565 | |
566 | /* Return the IP address of the connecting host. */ |
567 | static const char * |
568 | gethost(struct sockaddr *sa) |
569 | { |
570 | static char host[NI_MAXHOST]; |
571 | |
572 | if (getnameinfo(sa, sa->sa_len, host, sizeof(host), |
573 | NULL, 0, NI_NUMERICHOST) == 0) |
574 | return host; |
575 | |
576 | return "UNKNOWN" ; |
577 | } |
578 | |
579 | /* Setup sockets, for daemon mode. */ |
580 | static int * |
581 | socketsetup(const char *address, const char *port, int af) |
582 | { |
583 | struct addrinfo hints, *res, *res0; |
584 | int error, maxs, *s, *socks; |
585 | const char *cause = NULL; |
586 | socklen_t y = 1; |
587 | |
588 | (void)memset(&hints, 0, sizeof(hints)); |
589 | hints.ai_flags = AI_PASSIVE; |
590 | hints.ai_family = af; |
591 | hints.ai_socktype = SOCK_STREAM; |
592 | error = getaddrinfo(address, port, &hints, &res0); |
593 | if (error) { |
594 | die("getaddrinfo: %s" , gai_strerror(error)); |
595 | /* NOTREACHED */ |
596 | } |
597 | |
598 | /* Count max number of sockets we may open. */ |
599 | for (maxs = 0, res = res0; res != NULL; res = res->ai_next) |
600 | maxs++; |
601 | |
602 | socks = malloc((maxs + 1) * sizeof(int)); |
603 | if (socks == NULL) { |
604 | die("malloc: %s" , strerror(errno)); |
605 | /* NOTREACHED */ |
606 | } |
607 | |
608 | *socks = 0; |
609 | s = socks + 1; |
610 | for (res = res0; res != NULL; res = res->ai_next) { |
611 | *s = socket(res->ai_family, res->ai_socktype, res->ai_protocol); |
612 | if (*s < 0) { |
613 | cause = "socket" ; |
614 | continue; |
615 | } |
616 | (void)setsockopt(*s, SOL_SOCKET, SO_REUSEADDR, &y, sizeof(y)); |
617 | if (bind(*s, res->ai_addr, res->ai_addrlen) < 0) { |
618 | cause = "bind" ; |
619 | (void)close(*s); |
620 | continue; |
621 | } |
622 | if (listen(*s, 5) < 0) { |
623 | cause = "listen" ; |
624 | (void)close(*s); |
625 | continue; |
626 | } |
627 | *socks = *socks + 1; |
628 | s++; |
629 | } |
630 | |
631 | if (*socks == 0) { |
632 | free(socks); |
633 | die("%s: %s" , cause, strerror(errno)); |
634 | /* NOTREACHED */ |
635 | } |
636 | if (res0) |
637 | freeaddrinfo(res0); |
638 | |
639 | return socks; |
640 | } |
641 | |
642 | /* UID lookup wrapper. */ |
643 | static int |
644 | ident_getuid(struct sockaddr_storage *ss, socklen_t len, |
645 | struct sockaddr *proxy, uid_t *uid) |
646 | { |
647 | int rc; |
648 | |
649 | rc = sysctl_getuid(ss, len, uid); |
650 | if (rc == -1 && proxy != NULL) |
651 | rc = sysctl_proxy_getuid(ss, proxy, uid); |
652 | |
653 | return rc; |
654 | } |
655 | |
656 | /* Try to get the UID of the connection owner using sysctl. */ |
657 | static int |
658 | sysctl_getuid(struct sockaddr_storage *ss, socklen_t len, uid_t *uid) |
659 | { |
660 | int mib[4]; |
661 | uid_t myuid; |
662 | size_t uidlen; |
663 | |
664 | uidlen = sizeof(myuid); |
665 | |
666 | mib[0] = CTL_NET; |
667 | mib[1] = ss->ss_family; |
668 | mib[2] = IPPROTO_TCP; |
669 | mib[3] = TCPCTL_IDENT; |
670 | |
671 | if (sysctl(mib, sizeof(mib)/ sizeof(int), &myuid, &uidlen, ss, len) < 0) |
672 | return -1; |
673 | *uid = myuid; |
674 | |
675 | return 0; |
676 | } |
677 | |
678 | /* Try to get the UID of the connection owner using sysctl (proxy version). */ |
679 | static int |
680 | sysctl_proxy_getuid(struct sockaddr_storage *ss, struct sockaddr *proxy, |
681 | uid_t *uid) |
682 | { |
683 | struct sockaddr_storage new[2]; |
684 | int rc, name[CTL_MAXNAME]; |
685 | size_t i; |
686 | struct kinfo_pcb *kp; |
687 | size_t sz, len; |
688 | const char *list; |
689 | |
690 | rc = -1; |
691 | sz = CTL_MAXNAME; |
692 | list = NULL; |
693 | |
694 | /* Retrieve a list of sockets. */ |
695 | switch (ss[0].ss_family) { |
696 | case AF_INET: |
697 | /* We only accept queries from the proxy. */ |
698 | if (in_hosteq(satosin(&ss[0])->sin_addr, |
699 | satosin(proxy)->sin_addr)) |
700 | list = "net.inet.tcp.pcblist" ; |
701 | break; |
702 | case AF_INET6: |
703 | /* We only accept queries from the proxy. */ |
704 | if (IN6_ARE_ADDR_EQUAL(&satosin6(&ss[0])->sin6_addr, |
705 | &satosin6(proxy)->sin6_addr)) |
706 | list = "net.inet6.tcp.pcblist" ; |
707 | break; |
708 | default: |
709 | maybe_syslog(LOG_ERR, "Unsupported protocol for proxy (no. %d)" , |
710 | ss[0].ss_family); |
711 | } |
712 | if (list != NULL) |
713 | rc = sysctlnametomib(list, &name[0], &sz); |
714 | if (rc == -1) |
715 | return -1; |
716 | len = sz; |
717 | |
718 | name[len++] = PCB_ALL; |
719 | name[len++] = 0; |
720 | name[len++] = sizeof(struct kinfo_pcb); |
721 | name[len++] = INT_MAX; |
722 | |
723 | kp = NULL; |
724 | sz = 0; |
725 | do { |
726 | rc = sysctl(&name[0], len, kp, &sz, NULL, 0); |
727 | if (rc == -1 && errno != ENOMEM) |
728 | return -1; |
729 | if (kp == NULL) { |
730 | kp = malloc(sz); |
731 | rc = -1; |
732 | } |
733 | if (kp == NULL) |
734 | return -1; |
735 | } while (rc == -1); |
736 | |
737 | rc = -1; |
738 | /* |
739 | * Walk through the list of sockets and try to find a match. |
740 | * We don't know who has sent the query (we only know that the |
741 | * proxy has forwarded to us) so just try to match the ports and |
742 | * the local address. |
743 | */ |
744 | for (i = 0; i < sz / sizeof(struct kinfo_pcb); i++) { |
745 | switch (ss[0].ss_family) { |
746 | case AF_INET: |
747 | /* Foreign and local ports must match. */ |
748 | if (satosin(&ss[0])->sin_port != |
749 | satosin(&kp[i].ki_src)->sin_port) |
750 | continue; |
751 | if (satosin(&ss[1])->sin_port != |
752 | satosin(&kp[i].ki_dst)->sin_port) |
753 | continue; |
754 | /* Foreign address may not match proxy address. */ |
755 | if (in_hosteq(satosin(proxy)->sin_addr, |
756 | satosin(&kp[i].ki_dst)->sin_addr)) |
757 | continue; |
758 | /* Local addresses must match. */ |
759 | if (!in_hosteq(satosin(&ss[1])->sin_addr, |
760 | satosin(&kp[i].ki_src)->sin_addr)) |
761 | continue; |
762 | break; |
763 | case AF_INET6: |
764 | /* Foreign and local ports must match. */ |
765 | if (satosin6(&ss[0])->sin6_port != |
766 | satosin6(&kp[i].ki_src)->sin6_port) |
767 | continue; |
768 | if (satosin6(&ss[1])->sin6_port != |
769 | satosin6(&kp[i].ki_dst)->sin6_port) |
770 | continue; |
771 | /* Foreign address may not match proxy address. */ |
772 | if (IN6_ARE_ADDR_EQUAL(&satosin6(proxy)->sin6_addr, |
773 | &satosin6(&kp[i].ki_dst)->sin6_addr)) |
774 | continue; |
775 | /* Local addresses must match. */ |
776 | if (!IN6_ARE_ADDR_EQUAL(&satosin6(&ss[1])->sin6_addr, |
777 | &satosin6(&kp[i].ki_src)->sin6_addr)) |
778 | continue; |
779 | break; |
780 | } |
781 | |
782 | /* |
783 | * We have found the foreign address, copy it to a new |
784 | * struct and retrieve the UID of the connection owner. |
785 | */ |
786 | (void)memcpy(&new[0], &kp[i].ki_dst, kp[i].ki_dst.sa_len); |
787 | (void)memcpy(&new[1], &kp[i].ki_src, kp[i].ki_src.sa_len); |
788 | |
789 | rc = sysctl_getuid(new, sizeof(new), uid); |
790 | |
791 | /* Done. */ |
792 | break; |
793 | } |
794 | |
795 | free(kp); |
796 | return rc; |
797 | } |
798 | |
799 | /* Forward ident queries. Returns 1 when succesful, or zero if not. */ |
800 | static int |
801 | forward(int fd, struct sockaddr *nat_addr, int nat_lport, int fport, int lport) |
802 | { |
803 | char buf[BUFSIZ], reply[BUFSIZ], *p; |
804 | ssize_t n; |
805 | int sock; |
806 | |
807 | /* Connect to the NAT host. */ |
808 | sock = socket(nat_addr->sa_family, SOCK_STREAM, 0); |
809 | if (sock < 0) { |
810 | maybe_syslog(LOG_ERR, "socket: %m" ); |
811 | return 0; |
812 | } |
813 | if (connect(sock, nat_addr, nat_addr->sa_len) < 0) { |
814 | maybe_syslog(LOG_ERR, "Can't connect to %s: %m" , |
815 | gethost(nat_addr)); |
816 | (void)close(sock); |
817 | return 0; |
818 | } |
819 | |
820 | /* |
821 | * Send the ident query to the NAT host, but use as local port |
822 | * the port of the NAT host. |
823 | */ |
824 | (void)snprintf(buf, sizeof(buf), "%d , %d\r\n" , fport, nat_lport); |
825 | if (send(sock, buf, strlen(buf), 0) < 0) { |
826 | maybe_syslog(LOG_ERR, "send: %m" ); |
827 | (void)close(sock); |
828 | return 0; |
829 | } |
830 | |
831 | /* Read the reply from the NAT host. */ |
832 | if ((n = recv(sock, reply, sizeof(reply) - 1, 0)) < 0) { |
833 | maybe_syslog(LOG_ERR, "recv: %m" ); |
834 | (void)close(sock); |
835 | return 0; |
836 | } else if (n == 0) { |
837 | maybe_syslog(LOG_NOTICE, "recv: EOF" ); |
838 | (void)close(sock); |
839 | return 0; |
840 | } |
841 | reply[n] = '\0'; |
842 | if (dflag) |
843 | maybe_syslog(LOG_ERR, "Replied %s" , reply); |
844 | (void)close(sock); |
845 | |
846 | /* Extract everything after the port specs from the ident reply. */ |
847 | for (p = reply; *p != '\0' && *p != ':'; p++) |
848 | continue; |
849 | if (*p == '\0' || *++p == '\0') { |
850 | maybe_syslog(LOG_ERR, "Malformed ident reply from %s" , |
851 | gethost(nat_addr)); |
852 | return 0; |
853 | } |
854 | /* Build reply for the requesting host, use the original local port. */ |
855 | (void)snprintf(buf, sizeof(buf), "%d,%d:%s" , lport, fport, p); |
856 | |
857 | /* Send the reply from the NAT host back to the requesting host. */ |
858 | if (send(fd, buf, strlen(buf), 0) < 0) { |
859 | maybe_syslog(LOG_ERR, "send: %m" ); |
860 | return 0; |
861 | } |
862 | |
863 | return 1; |
864 | } |
865 | |
866 | /* Check if a .noident file exists in the user home directory. */ |
867 | static int |
868 | check_noident(const char *homedir) |
869 | { |
870 | struct stat sb; |
871 | char *path; |
872 | int ret; |
873 | |
874 | if (homedir == NULL) |
875 | return 0; |
876 | if (asprintf(&path, "%s/.noident" , homedir) < 0) |
877 | return 0; |
878 | ret = stat(path, &sb); |
879 | |
880 | free(path); |
881 | return (ret == 0); |
882 | } |
883 | |
884 | /* |
885 | * Check if a .ident file exists in the user home directory and |
886 | * return the contents of that file. |
887 | */ |
888 | static int |
889 | check_userident(const char *homedir, char *username, size_t len) |
890 | { |
891 | struct stat sb; |
892 | char *path, *p; |
893 | ssize_t n; |
894 | int fd; |
895 | |
896 | if (len == 0 || homedir == NULL) |
897 | return 0; |
898 | if (asprintf(&path, "%s/.ident" , homedir) < 0) |
899 | return 0; |
900 | if ((fd = open(path, O_RDONLY|O_NONBLOCK|O_NOFOLLOW, 0)) < 0) { |
901 | free(path); |
902 | return 0; |
903 | } |
904 | if (fstat(fd, &sb) < 0 || !S_ISREG(sb.st_mode)) { |
905 | (void)close(fd); |
906 | free(path); |
907 | return 0; |
908 | } |
909 | if ((n = read(fd, username, len - 1)) < 1) { |
910 | (void)close(fd); |
911 | free(path); |
912 | return 0; |
913 | } |
914 | username[n] = '\0'; |
915 | |
916 | if ((p = strpbrk(username, "\r\n" )) != NULL) |
917 | *p = '\0'; |
918 | |
919 | (void)close(fd); |
920 | free(path); |
921 | return 1; |
922 | } |
923 | |
924 | /* Generate a random string. */ |
925 | static void |
926 | random_string(char *str, size_t len) |
927 | { |
928 | static const char chars[] = "abcdefghijklmnopqrstuvwxyz1234567890" ; |
929 | char *p; |
930 | |
931 | if (len == 0) |
932 | return; |
933 | for (p = str; len > 1; len--) |
934 | *p++ = chars[arc4random() % (sizeof(chars) - 1)]; |
935 | *p = '\0'; |
936 | } |
937 | |
938 | /* Change the output format. */ |
939 | static int |
940 | change_format(const char *format, struct passwd *pw, char *dest, size_t len) |
941 | { |
942 | struct group *gr; |
943 | const char *cp; |
944 | char **gmp; |
945 | size_t bp; |
946 | |
947 | if (len == 0 || ((gr = getgrgid(pw->pw_gid)) == NULL)) |
948 | return 0; |
949 | |
950 | for (bp = 0, cp = format; *cp != '\0' && bp < len - 1; cp++) { |
951 | if (*cp != '%') { |
952 | dest[bp++] = *cp; |
953 | continue; |
954 | } |
955 | if (*++cp == '\0') |
956 | break; |
957 | switch (*cp) { |
958 | case 'u': |
959 | (void)snprintf(&dest[bp], len - bp, "%s" , pw->pw_name); |
960 | break; |
961 | case 'U': |
962 | (void)snprintf(&dest[bp], len - bp, "%d" , pw->pw_uid); |
963 | break; |
964 | case 'g': |
965 | (void)snprintf(&dest[bp], len - bp, "%s" , gr->gr_name); |
966 | break; |
967 | case 'G': |
968 | (void)snprintf(&dest[bp], len - bp, "%d" , gr->gr_gid); |
969 | break; |
970 | case 'l': |
971 | (void)snprintf(&dest[bp], len - bp, "%s" , gr->gr_name); |
972 | bp += strlen(&dest[bp]); |
973 | if (bp >= len) |
974 | break; |
975 | setgrent(); |
976 | while ((gr = getgrent()) != NULL) { |
977 | if (gr->gr_gid == pw->pw_gid) |
978 | continue; |
979 | for (gmp = gr->gr_mem; *gmp && **gmp; gmp++) { |
980 | if (strcmp(*gmp, pw->pw_name) == 0) { |
981 | (void)snprintf(&dest[bp], |
982 | len - bp, ",%s" , |
983 | gr->gr_name); |
984 | bp += strlen(&dest[bp]); |
985 | break; |
986 | } |
987 | } |
988 | if (bp >= len) |
989 | break; |
990 | } |
991 | endgrent(); |
992 | break; |
993 | case 'L': |
994 | (void)snprintf(&dest[bp], len - bp, "%u" , gr->gr_gid); |
995 | bp += strlen(&dest[bp]); |
996 | if (bp >= len) |
997 | break; |
998 | setgrent(); |
999 | while ((gr = getgrent()) != NULL) { |
1000 | if (gr->gr_gid == pw->pw_gid) |
1001 | continue; |
1002 | for (gmp = gr->gr_mem; *gmp && **gmp; gmp++) { |
1003 | if (strcmp(*gmp, pw->pw_name) == 0) { |
1004 | (void)snprintf(&dest[bp], |
1005 | len - bp, ",%u" , |
1006 | gr->gr_gid); |
1007 | bp += strlen(&dest[bp]); |
1008 | break; |
1009 | } |
1010 | } |
1011 | if (bp >= len) |
1012 | break; |
1013 | } |
1014 | endgrent(); |
1015 | break; |
1016 | default: |
1017 | dest[bp] = *cp; |
1018 | dest[bp+1] = '\0'; |
1019 | break; |
1020 | } |
1021 | bp += strlen(&dest[bp]); |
1022 | } |
1023 | dest[bp] = '\0'; |
1024 | |
1025 | return 1; |
1026 | } |
1027 | |
1028 | /* Just exit when we caught SIGALRM. */ |
1029 | static void |
1030 | timeout_handler(int __unused s) |
1031 | { |
1032 | maybe_syslog(LOG_INFO, "Timeout for request, closing connection..." ); |
1033 | exit(EXIT_FAILURE); |
1034 | } |
1035 | |
1036 | /* Report error message string through syslog and quit. */ |
1037 | static void |
1038 | fatal(const char *func) |
1039 | { |
1040 | maybe_syslog(LOG_ERR, "%s: %m" , func); |
1041 | exit(EXIT_FAILURE); |
1042 | } |
1043 | |
1044 | /* |
1045 | * Report an error through syslog and/or stderr and quit. Only used when |
1046 | * running identd in the background and when it isn't a daemon yet. |
1047 | */ |
1048 | static void |
1049 | die(const char *message, ...) |
1050 | { |
1051 | va_list ap; |
1052 | |
1053 | if (bflag) { |
1054 | va_start(ap, message); |
1055 | vwarnx(message, ap); |
1056 | va_end(ap); |
1057 | } |
1058 | |
1059 | if (lflag) { |
1060 | va_start(ap, message); |
1061 | vsyslog(LOG_ERR, message, ap); |
1062 | va_end(ap); |
1063 | } |
1064 | |
1065 | exit(EXIT_FAILURE); |
1066 | } |
1067 | |
1068 | /* Log using syslog, but only if enabled with the -l flag. */ |
1069 | void |
1070 | maybe_syslog(int priority, const char *message, ...) |
1071 | { |
1072 | va_list ap; |
1073 | |
1074 | if (lflag) { |
1075 | va_start(ap, message); |
1076 | vsyslog(priority, message, ap); |
1077 | va_end(ap); |
1078 | } |
1079 | } |
1080 | |