50907e3ef07b0d5312153bea17c684319f61d8fc
[fb-resolver] / resolver.c
1 #define _REENTRANT
2
3 #if defined(SOLARIS) && !defined(_POSIX_SOURCE)
4 # define _POSIX_SOURCE
5 #endif
6
7 #include "copyright.h"
8 #include "config.h"
9
10 #include <stdlib.h>
11 #include <unistd.h>
12 #include <stdio.h>
13 #include <string.h>
14 #include <signal.h>
15 #include <pthread.h>
16 #include <fcntl.h>
17 #include <sys/select.h>
18 #include <sys/types.h>
19 #include <sys/socket.h>
20 #include <netinet/in.h>
21 #include <arpa/inet.h>
22 #include <netdb.h>
23 #include <time.h>
24 #include <errno.h>
25 #include <sysexits.h>
26 #include <sys/queue.h>
27 #include <assert.h>
28 #ifdef HAVE_GETRLIMIT
29 # include <sys/resource.h>
30 #endif /* HAVE_GETRLIMIT */
31
32 #include "lru_cache.h"
33
34 #if !defined(O_NONBLOCK) || defined(ULTRIX)
35 # ifdef FNDELAY /* SunOS */
36 # define O_NONBLOCK FNDELAY
37 # else
38 # ifdef O_NDELAY /* SysV */
39 # define O_NONBLOCK O_NDELAY
40 # endif /* O_NDELAY */
41 # endif /* FNDELAY */
42 #endif
43
44 #define NUM_THREADS 5 /* default number of threads to start */
45 #define CACHE_CAPACITY 8192 /* default number of addresses to cache */
46 #define CACHE_HASH_SZ 1021 /* default number of hash slots, prime recommended */
47 #define CACHE_AGE_SUCCESS 60 /* default seconds to maintain a cached success */
48 #define CACHE_AGE_FAIL 1800 /* seconds to maintain a cached fail */
49 #define BUF_SZ 1024 /* size of input and output buffers */
50
51 #define IDENT_PORT 113 /* port number of the ident service */
52 #define IDENT_TIMEOUT 60 /* number of seconds to wait for an ident connection */
53
54 #define QUIT_COMMAND "QUIT" /* shut down if this input is encountered */
55 #ifdef WITH_RESOLVER_STATS
56 # define DUMP_COMMAND "DUMP" /* report on internal state */
57 #endif
58
59
60 static
61 struct options {
62 unsigned int verbose;
63 size_t num_threads;
64 size_t capacity;
65 size_t hash_sz;
66 unsigned int age_success;
67 unsigned int age_fail;
68 char *log_filename;
69 } g_opts_ = {
70 0,
71 NUM_THREADS,
72 CACHE_CAPACITY,
73 CACHE_HASH_SZ,
74 CACHE_AGE_SUCCESS,
75 CACHE_AGE_FAIL,
76 NULL
77 };
78
79
80 /*
81 N.B. The hostname in a cache entry is a flexible array!
82 If this is ever updated to c99 style, also update how it's allocated
83 in cache_add_.
84 */
85 struct cache_entry {
86 lru_entry_t lru_; /* must be first */
87
88 struct sockaddr_storage ss;
89 time_t timestamp;
90 int succeeded;
91 char hostname[1];
92 };
93
94 static pthread_rwlock_t g_cache_rwlock_ = PTHREAD_RWLOCK_INITIALIZER;
95 static struct lru_cache *g_cache_ = NULL;
96
97 static volatile unsigned int g_want_shutdown_ = 0;
98
99 static FILE *g_log_stream_ = NULL;
100
101 #define LOG(...) do { fprintf(g_log_stream_, __VA_ARGS__); fflush(g_log_stream_); } while (0)
102 #define LOG_V(...) do { if (g_opts_.verbose > 0) { fprintf(g_log_stream_, __VA_ARGS__); fflush(g_log_stream_); } } while (0)
103 #define LOG_VV(...) do { if (g_opts_.verbose > 1) { fprintf(g_log_stream_, __VA_ARGS__); fflush(g_log_stream_); } } while (0)
104 #define LOG_VVV(...) do { if (g_opts_.verbose > 2) { fprintf(g_log_stream_, __VA_ARGS__); fflush(g_log_stream_); } } while (0)
105
106
107
108 #define USAGE_FLAG_SHOWFULL (1<<0)
109 static
110 void
111 usage_(const char *prog, unsigned int flags)
112 {
113 FILE *f = (flags & USAGE_FLAG_SHOWFULL) ? stdout : stderr;
114 char *x = strrchr(prog, '/');
115
116 if (x && *(x + 1))
117 prog = x + 1;
118
119 if (flags & USAGE_FLAG_SHOWFULL)
120 fprintf(f,
121 "%s -- asynchronous caching hostname and identd lookup tool\n\n"
122
123 "\tReads lines from stdin, in the format of an IP address,\n"
124 "followed by a remote port number in parenthesis, followed by a local\n"
125 "port number, followed by a newline.\n\n"
126
127 "\tFor each valid line read, it will attempt to resolve the\n"
128 "hostname of the IP address, and the identd response from the IP for\n"
129 "the port pairing.\n\n",
130 prog);
131
132 fprintf(f, "Usage: %s [-h] [-v] [-a <cache_age>] [-f <cache_age>] [-j <num_threads>] [-c <cache_capacity>] [-s <hash_slots>] [-o <log_filename>]\n", prog);
133
134 if (flags & USAGE_FLAG_SHOWFULL) {
135 fprintf(f,
136 "Options:\n"
137 "\t -h -- this screen\n"
138 "\t -v -- increase verbosity\n"
139 "\t -o <log_filename> -- write errors and notices to this file (default is to stderr) (implies -v)\n"
140 "\t -j <num_threads> -- use this many threads (default: %zu)\n"
141 "\t -c <cache_capacity> -- cache up to this many addresses (default: %zu)\n"
142 "\t -s <hash_slots> -- use this many bins for hashing (default: %zu)\n"
143 "\t -a <cache_age> -- cache successful queries for this many seconds (default: %u)\n"
144 "\t -f <cache_age> -- cache failed queries for this many seconds (default: %u)\n",
145 g_opts_.num_threads,
146 g_opts_.capacity,
147 g_opts_.hash_sz,
148 g_opts_.age_success,
149 g_opts_.age_fail
150 );
151 }
152 fflush(f);
153 }
154
155
156
157 #define PTHREAD_OR_DIE__(__fn__,...) do { \
158 int r; \
159 r = __fn__(__VA_ARGS__); \
160 if (r) { \
161 LOG("%s: %s\n", #__fn__, strerror(r)); \
162 exit(EX_OSERR); \
163 } \
164 } while (0)
165
166 #define UNLOCK(__l__) PTHREAD_OR_DIE__(pthread_rwlock_unlock, (__l__))
167 #define RDLOCK(__l__) PTHREAD_OR_DIE__(pthread_rwlock_rdlock, (__l__))
168 #define WRLOCK(__l__) PTHREAD_OR_DIE__(pthread_rwlock_wrlock, (__l__))
169
170
171
172 /*
173 Access AF-specific fields inside a generic sockaddr_storage struct.
174 */
175 static
176 void
177 ss_addr_fields_(const struct sockaddr_storage *ss,
178 socklen_t *sockaddr_len, void **vaddr, size_t *addr_len, unsigned short **port)
179 {
180 assert(ss != NULL);
181
182 if (ss->ss_family == AF_INET) {
183 struct sockaddr_in *sa = (struct sockaddr_in *)ss;
184 struct in_addr *a = &sa->sin_addr;
185
186 if (vaddr)
187 *vaddr = &a->s_addr;
188 if (addr_len)
189 *addr_len = sizeof a->s_addr;
190 if (sockaddr_len)
191 *sockaddr_len = sizeof *sa;
192 if (port)
193 *port = &sa->sin_port;
194 } else if (ss->ss_family == AF_INET6) {
195 struct sockaddr_in6 *sa = (struct sockaddr_in6 *)ss;
196 struct in6_addr *a = &sa->sin6_addr;
197
198 if (vaddr)
199 *vaddr = &a->s6_addr;
200 if (addr_len)
201 *addr_len = sizeof a->s6_addr;
202 if (sockaddr_len)
203 *sockaddr_len = sizeof *sa;
204 if (port)
205 *port = &sa->sin6_port;
206 } else {
207 LOG_V("unknown AF %d\n", ss->ss_family);
208
209 if (vaddr)
210 *vaddr = NULL;
211 if (addr_len)
212 *addr_len = 0;
213 if (sockaddr_len)
214 *sockaddr_len = 0;
215 if (port)
216 *port = NULL;
217 }
218 }
219
220
221
222 /*
223 this is the lru_entry_cmp_fn
224 Two entries are equal if their addresses match.
225 */
226 static
227 int
228 cache_entry_cmp_(lru_entry_t *a, lru_entry_t *b)
229 {
230 struct cache_entry *ea = (struct cache_entry *)a,
231 *eb = (struct cache_entry *)b;
232 void *va, *vb;
233 size_t addr_len;
234
235 assert(ea != NULL);
236 assert(eb != NULL);
237
238 /* Keep things simple: different families are not equal. */
239 if (ea->ss.ss_family != eb->ss.ss_family)
240 return -1;
241
242 ss_addr_fields_(&ea->ss, NULL, &va, &addr_len, NULL);
243 if (va == NULL)
244 return -1;
245
246 ss_addr_fields_(&eb->ss, NULL, &vb, NULL, NULL);
247 if (vb == NULL)
248 return -1;
249
250 return memcmp(va, vb, addr_len);
251 }
252
253
254
255 /*
256 this is the lru_hash_feed_fn
257 an entry is hashed by its address
258 */
259 static
260 void
261 cache_entry_hash_feed_(lru_entry_t *e, char **buf, size_t *sz)
262 {
263 struct cache_entry *entry = (struct cache_entry *)e;
264 void *vaddr;
265 size_t addr_len;
266
267 assert(entry != NULL);
268
269 /* just hash the address */
270 ss_addr_fields_(&entry->ss, NULL, &vaddr, &addr_len, NULL);
271 *buf = vaddr;
272 *sz = addr_len;
273 }
274
275
276
277 /*
278 Search the cache for an entry.
279 If it exists, and has not expired, move it to the front of queue.
280 If it has expired, expunge.
281 */
282 static
283 int
284 cache_find_(struct sockaddr_storage *ss, char *buf, size_t buf_sz)
285 {
286 struct cache_entry *entry, match;
287 int write_lock = 0;
288
289 assert(ss != NULL);
290
291 memset(&match, 0, sizeof match);
292 memcpy(&match.ss, ss, sizeof *ss);
293
294 RDLOCK(&g_cache_rwlock_);
295
296 again:
297 entry = (struct cache_entry *)lru_cache_locate(g_cache_, (lru_entry_t *)&match);
298 if (entry == NULL)
299 goto done;
300
301 /*
302 If an entry exists in the cache, it will need to be updated, so
303 exchange the read lock for a write lock, and look it up again on
304 the off chance another thread got rid of it before we could.
305 */
306 if ( ! write_lock) {
307 UNLOCK(&g_cache_rwlock_);
308 WRLOCK(&g_cache_rwlock_);
309 write_lock = 1;
310 goto again;
311 }
312
313 /* Drop the entry if it is too old. */
314 if (time(NULL) > entry->timestamp + (time_t)(entry->succeeded ? g_opts_.age_success : g_opts_.age_fail)) {
315 LOG_VV("expired %s\n", entry->hostname);
316 lru_cache_extract(g_cache_, (lru_entry_t *)entry);
317 free(entry);
318 entry = NULL;
319 goto done;
320 }
321
322 /* Peek at the front of the queue, and only reinsert if this entry is not there already. */
323 if ((lru_entry_t *)entry != g_cache_->newest) {
324 struct cache_entry *removed;
325 lru_cache_extract(g_cache_, (lru_entry_t *)entry);
326 lru_cache_insert(g_cache_, (lru_entry_t *)entry, (lru_entry_t **)&removed);
327 /* No need to check removed, the queue can never overflow here. */
328 }
329
330 if (buf) {
331 strncpy(buf, entry->hostname, buf_sz);
332 if (buf[buf_sz - 1] != '\0')
333 buf[buf_sz - 1] = '\0';
334 }
335
336 done:
337 UNLOCK(&g_cache_rwlock_);
338 return (entry != NULL);
339 }
340
341
342
343 /*
344 Update the cache to include the given data.
345 hostname_len is the strlen of the hostname
346 */
347 static
348 void
349 cache_add_(struct sockaddr_storage *ss, const char *hostname, size_t hostname_len, time_t timestamp, int succeeded)
350 {
351 struct cache_entry *entry, *old, match;
352
353 assert(ss != NULL);
354 assert(hostname != NULL || hostname_len == 0);
355
356 memset(&match, 0, sizeof match);
357 memcpy(&match.ss, ss, sizeof *ss);
358
359 /*
360 When allocating an entry, the size of the struct already includes
361 the extra byte for the nul at the end of the hostname.
362 */
363 entry = malloc(sizeof *entry + hostname_len);
364 if (entry == NULL) {
365 LOG("%s: %s\n", "malloc", strerror(errno));
366 return;
367 }
368
369 entry->timestamp = timestamp;
370 entry->succeeded = succeeded;
371 memcpy(&entry->ss, ss, sizeof *ss);
372 memcpy(&entry->hostname, hostname, hostname_len);
373 entry->hostname[hostname_len] = '\0';
374
375 WRLOCK(&g_cache_rwlock_);
376
377 /* assure we're not duplicating entries */
378 old = (struct cache_entry *)lru_cache_locate(g_cache_, (lru_entry_t *)entry);
379 if (old != NULL) {
380 lru_cache_extract(g_cache_, (lru_entry_t *)old);
381 free(old);
382 }
383
384 lru_cache_insert(g_cache_, (lru_entry_t *)entry, (lru_entry_t **)&old);
385 if (old)
386 free(old);
387
388 UNLOCK(&g_cache_rwlock_);
389 }
390
391
392
393 #ifdef WITH_RESOLVER_STATS
394
395 static
396 void
397 cache_entry_dump_(lru_entry_t *e, size_t idx, void *data)
398 {
399 char buf[BUF_SZ];
400 struct cache_entry *entry = (struct cache_entry *)e;
401 time_t *now = (time_t *)data;
402 int r;
403
404 assert(e != NULL);
405 assert(data != NULL);
406
407 r = getnameinfo((struct sockaddr *)&(entry->ss), sizeof entry->ss, buf, sizeof buf, NULL, 0, NI_NUMERICHOST);
408 if (r) {
409 LOG("%s: %s\n", "getnameinfo", gai_strerror(r));
410 snprintf(buf, sizeof buf, "(unknown)");
411 }
412 fprintf(g_log_stream_, "%05zu: %s -> %s [%ld]\n", idx, buf, entry->hostname, *now - entry->timestamp);
413 }
414
415 static
416 void
417 cache_statdump_(void) {
418 time_t now = time(NULL);
419 size_t i;
420
421 RDLOCK(&g_cache_rwlock_);
422 flockfile(g_log_stream_);
423
424 fprintf(g_log_stream_, "-- cache newest to oldest --\n");
425 lru_cache_foreach(g_cache_, cache_entry_dump_, &now);
426 fprintf(g_log_stream_, "----------------------------\n");
427
428 fprintf(g_log_stream_, "----- hash slot depths -----\n");
429 for (i = 0; i < g_cache_->hash_sz; i++) {
430 if (g_cache_->hash[i].tally) {
431 fprintf(g_log_stream_, "[%zu]: %zu\n", i, g_cache_->hash[i].tally);
432 }
433 }
434 fprintf(g_log_stream_, "----------------------------\n");
435
436 funlockfile(g_log_stream_);
437 UNLOCK(&g_cache_rwlock_);
438 }
439
440 #endif /* WITH_RESOLVER_STATS */
441
442
443
444 /* empty everything */
445 void cache_free_(void)
446 {
447 struct cache_entry *entry;
448
449 while ((entry = (struct cache_entry *)g_cache_->newest)) {
450 lru_cache_extract(g_cache_, g_cache_->newest);
451 free(entry);
452 }
453 }
454
455
456
457 /* connect_nb_
458 connect wrapper, with timeout
459 deadline is the absolute time after which it will give up
460 */
461 static
462 int
463 connect_nb_(int fd, struct sockaddr *sa, socklen_t sa_len, time_t deadline)
464 {
465 fd_set fds;
466 struct timeval timeout_tv;
467 time_t now = time(NULL);
468 socklen_t r_len;
469 int r;
470
471 LOG_VVV("connecting fd %d\n", fd);
472
473 /* attempt to connect, expect EINPROGRESS or immediate success */
474 while ( (r = connect(fd, sa, sa_len)) ) {
475 if (errno == EINPROGRESS)
476 break;
477 if (errno == EINTR)
478 continue;
479
480 LOG("%s: %s\n", "connect", strerror(errno));
481 return -1;
482 }
483
484 /* wait for it to become writable, which indicates connect completion */
485 do {
486 FD_ZERO(&fds);
487 FD_SET(fd, &fds);
488 timeout_tv.tv_sec = deadline - now;
489 timeout_tv.tv_usec = 0;
490
491 r = select(fd + 1, NULL, &fds, NULL, &timeout_tv);
492 if (r < 0) {
493 LOG("%s: %s\n", "select", strerror(errno));
494 if (errno != EINTR && errno != EAGAIN)
495 return -1;
496 }
497 } while (r == 0
498 && time(&now) < deadline);
499 if (now >= deadline) {
500 errno = ETIMEDOUT;
501 LOG("%s: %s\n", "connect", strerror(errno));
502 return -1;
503 }
504
505 /* connect completed, check for errors */
506 r = -1;
507 r_len = sizeof r;
508 if (getsockopt(fd, SOL_SOCKET, SO_ERROR, &r, &r_len)) {
509 LOG("%s: %s\n", "getsockopt", strerror(errno));
510 return -1;
511 }
512 if (r) {
513 errno = r;
514 LOG("%s: %s\n", "connect", strerror(errno));
515 return -1;
516 }
517
518 return 0;
519 }
520
521
522
523 /* write_nb_
524 write a buffer to a socket, with timeout
525 */
526 static
527 ssize_t
528 write_nb_(int fd, const char *buf, size_t buf_len, time_t deadline)
529 {
530 fd_set fds;
531 struct timeval timeout_tv;
532 time_t now = time(NULL);
533 ssize_t w_len, total;
534 int r;
535
536 total = 0;
537 do {
538 LOG_VVV("writing '%*s' to fd %d\n", (int)buf_len, buf, fd);
539
540 FD_ZERO(&fds);
541 FD_SET(fd, &fds);
542 timeout_tv.tv_sec = deadline - now;
543 timeout_tv.tv_usec = 0;
544
545 r = select(fd + 1, NULL, &fds, NULL, &timeout_tv);
546 if (r < 0) {
547 LOG("%s: %s\n", "select", strerror(errno));
548 if (errno == EINTR || errno == EAGAIN)
549 continue;
550 return -1;
551 }
552 if (r == 0)
553 continue;
554
555 if (FD_ISSET(fd, &fds)) {
556 w_len = write(fd, buf, buf_len);
557 if (w_len < 0) {
558 LOG("%s: %s\n", "write", strerror(errno));
559 if (errno == EAGAIN || errno == EWOULDBLOCK || errno == EINTR)
560 continue;
561 return -1;
562 }
563 LOG_VVV("wrote %zd bytes (of %zu) to fd %d\n", w_len, buf_len, fd);
564
565 total += w_len;
566 buf += w_len;
567 buf_len -= w_len;
568 }
569 } while (buf_len > 0
570 && time(&now) < deadline);
571 if (now >= deadline) {
572 errno = ETIMEDOUT;
573 LOG("%s: %s\n", "write", strerror(errno));
574 return -1;
575 }
576
577 LOG_VVV("fd %d wrote %zd bytes\n", fd, total);
578
579 return total;
580 }
581
582
583
584 /* read_line_nb_
585 reads at least a line, with a timeout
586 sets *line_end to the first '\n' or '\r' encountered
587 */
588 static
589 ssize_t
590 read_line_nb_(int fd, char *buf, size_t buf_len, char **line_end, time_t deadline)
591 {
592 fd_set fds;
593 struct timeval timeout_tv;
594 time_t now = time(NULL);
595 ssize_t r_len, total;
596 int r;
597
598 *line_end = NULL;
599 total = 0;
600 do {
601 FD_ZERO(&fds);
602 FD_SET(fd, &fds);
603 timeout_tv.tv_sec = deadline - now;
604 timeout_tv.tv_usec = 0;
605
606 r = select(fd + 1, &fds, NULL, NULL, &timeout_tv);
607 if (r < 0) {
608 LOG("%s: %s\n", "select", strerror(errno));
609 if (errno == EINTR || errno == EAGAIN)
610 continue;
611 return -1;
612 }
613 if (r == 0)
614 continue;
615
616 if (FD_ISSET(fd, &fds)) {
617 r_len = read(fd, buf, buf_len);
618 if (r_len < 0) {
619 LOG("%s: %s\n", "read", strerror(errno));
620 if (errno == EAGAIN || errno == EWOULDBLOCK || errno == EINTR)
621 continue;
622 return -1;
623 }
624 if (r_len == 0) {
625 LOG_VVV("fd %d EOF\n", fd);
626 break;
627 }
628
629 LOG_VVV("read %zu bytes from fd %d\n", r_len, fd);
630
631 total += r_len;
632 while (r_len--) {
633 if (*buf == '\r' || *buf == '\n') {
634 LOG_VVV("fd %d line-length %zu\n", fd, total - r_len);
635 *line_end = buf;
636 break;
637 }
638 buf++;
639 buf_len--;
640 }
641 }
642 } while (*line_end == NULL
643 && buf_len > 0
644 && time(&now) < deadline);
645 if (now >= deadline) {
646 errno = ETIMEDOUT;
647 LOG("%s: %s\n", "read", strerror(errno));
648 return -1;
649 }
650
651 return total;
652 }
653
654
655
656 /*
657 remove trim characters from start and end of a token
658 */
659 static
660 char *
661 strtok_trim_(char *str, const char *sep, const char *trim, char **endp)
662 {
663 char *tok, *ep;
664
665 tok = strtok_r(str, sep, endp);
666 if (tok == NULL)
667 return NULL;
668
669 if (trim) {
670 while (*tok && strchr(trim, *tok)) {
671 tok++;
672 }
673
674 ep = *endp - 1;
675 while (ep > tok && strchr(trim, *ep)) {
676 *ep = '\0';
677 ep--;
678 }
679 }
680
681 return tok;
682 }
683
684
685
686 /*
687 destructively parse response for validity, leaves username in buffer on success
688
689 an ident response is like:
690 ' port , port : USERID : os : userid'
691 or
692 ' port , port : ERROR : error'
693 */
694 static
695 int
696 ident_response_parse_(char *buf, unsigned short port_remote, unsigned short port_local)
697 {
698 char *tok, *last;
699 int port;
700
701 /* first, ensure response prefix matches request */
702
703 /* remote port */
704 if ((tok = strtok_trim_(buf, ",", " \t", &last)) == NULL)
705 return -1;
706 if (sscanf(tok, "%d", &port) != 1)
707 return -1;
708 if (port != port_remote)
709 return -1;
710
711 /* local port */
712 if ((tok = strtok_trim_(NULL, ":", " \t", &last)) == NULL)
713 return -1;
714 if (sscanf(tok, "%d", &port) != 1)
715 return -1;
716 if (port != port_local)
717 return -1;
718
719 /* reply */
720 if ((tok = strtok_trim_(NULL, ":", " \t", &last)) == NULL)
721 return -1;
722
723 LOG_VVV("ident resonse: %s %s\n", tok, last);
724
725 if (strcasecmp(tok, "USERID") != 0)
726 return -1;
727
728 /* os type, ignored */
729 if ((tok = strtok_trim_(NULL, ":", NULL, &last)) == NULL)
730 return -1;
731
732 LOG_VVV("ident user: %s\n", last);
733
734 /* anything remaining is user id */
735 /* move it to the front of the buffer */
736 /* strcpy is safe here because we could not have passed in an unterminated string */
737 strcpy(buf, last);
738
739 return 0;
740 }
741
742
743
744 /*
745 Query a host's identity service for user information.
746 This overwrites ss->sin*_port.
747 */
748 static
749 int
750 ident_query_(char *out_buf, size_t out_buf_sz, struct sockaddr_storage *ss, unsigned short port_remote, unsigned short port_local)
751 {
752 int retval = -1;
753 char req_buf[16];
754 char buf[BUF_SZ];
755 size_t len;
756 char *buf_ptr;
757 int fd;
758 int r;
759 time_t now, deadline;
760 unsigned short *vport;
761 socklen_t sockaddr_len;
762
763 #ifdef DEBUG_PRETEND_IDENT
764 size_t delay = lrand48() % IDENT_TIMEOUT;
765 if (delay < 45) {
766 delay = delay / 5;
767 sleep(delay);
768 } else {
769 delay = 0;
770 }
771 LOG_VVV("pretending ident query took %zu seconds\n", delay);
772 goto done;
773 #endif /* DEBUG_PRETEND_IDENT */
774
775 deadline = time(&now) + IDENT_TIMEOUT;
776
777 fd = socket(ss->ss_family, SOCK_STREAM, 0);
778 if (fd == -1) {
779 LOG("%s: %s\n", "socket", strerror(errno));
780 goto done;
781 }
782
783 /* make non-blocking */
784 if (fcntl(fd, F_SETFL, O_NONBLOCK) == -1) {
785 LOG("%s: %s\n", "fcntl", strerror(errno));
786 goto close_done;
787 }
788
789 /* get the sockaddr length, and the port address */
790 ss_addr_fields_(ss, &sockaddr_len, NULL, NULL, &vport);
791 if (vport == NULL)
792 goto close_done;
793
794 *vport = htons(IDENT_PORT);
795
796 r = connect_nb_(fd, (struct sockaddr *)ss, sockaddr_len, deadline);
797 if (r) {
798 LOG_V("%s: %s\n", "nb_connect_", strerror(errno));
799 goto close_done;
800 }
801
802 /* craft and send our meager request */
803 len = snprintf(req_buf, sizeof req_buf, "%hu,%hu\r\n", port_remote, port_local);
804 r = write_nb_(fd, req_buf, len, deadline);
805 if (r < 0) {
806 LOG_V("%s: %s\n", "nb_write_", strerror(errno));
807 goto close_done;
808 }
809
810 /* read one full line as a response */
811 r = read_line_nb_(fd, buf, sizeof buf, &buf_ptr, deadline);
812 if (r < 0) {
813 LOG_V("%s: %s\n", "nb_read_", strerror(errno));
814 goto close_done;
815 }
816
817 if (buf_ptr == NULL) {
818 LOG_VV("incomplete response\n");
819 goto close_done;
820 }
821
822 *buf_ptr = '\0';
823
824 if (ident_response_parse_(buf, port_remote, port_local))
825 goto close_done;
826
827 /* success */
828 strncpy(out_buf, buf, out_buf_sz);
829 retval = 0;
830
831 close_done:
832 shutdown(fd, 2); /* be ruthless, discard any remaining data */
833 close(fd);
834
835 done:
836 if (retval) {
837 /* no success, just echo the port */
838 snprintf(out_buf, out_buf_sz, "%d", port_remote);
839 }
840
841 return retval;
842 }
843
844
845
846 /*
847 Render the name of an address into the supplied buffer.
848 buf_sz ought to be at least NI_HOSTMAX (1025)
849 */
850 static
851 void
852 host_query_(char *buf, size_t buf_sz, struct sockaddr_storage *ss)
853 {
854 int succeeded = 1;
855 int r;
856
857 if (cache_find_(ss, buf, buf_sz)) {
858 LOG_VVV("found '%s' in cache\n", buf);
859 return;
860 }
861
862 /* not in cache, try to resolve it */
863 r = getnameinfo((struct sockaddr *)ss, sizeof *ss, buf, buf_sz, NULL, 0, NI_NAMEREQD);
864 if (r) {
865 if (r != EAI_NONAME)
866 LOG("%s: %s\n", "getnameinfo", gai_strerror(r));
867
868 succeeded = 0;
869
870 /* not resolvable, render it numerically */
871 r = getnameinfo((struct sockaddr *)ss, sizeof *ss, buf, buf_sz, NULL, 0, NI_NUMERICHOST);
872 if (r) {
873 LOG("%s: %s\n", "getnameinfo", gai_strerror(r));
874 snprintf(buf, buf_sz, "(unknown)");
875 }
876 LOG_VVV("failed to resolve '%s'\n", buf);
877 }
878
879 cache_add_(ss, buf, strlen(buf), time(NULL), succeeded);
880 }
881
882
883
884 /*
885 The main processing loop for each thread.
886 */
887 static
888 void *
889 resolve_thread_(void *data)
890 {
891 size_t id = *(size_t *)data;
892 char buf[BUF_SZ];
893 char hostname_buf[2048]; /* seems silly-large, but is just the next power-of-two up from NI_MAXHOST (1025) */
894 char username_buf[512]; /* identd user id might be this long */
895 char *buf_ptr;
896 struct sockaddr_storage ss;
897 void *vaddr;
898 int port_remote, port_local;
899 sigset_t sigset_maskall;
900 int r;
901
902 LOG_VV("<%zu> thread started\n", id);
903
904 sigfillset(&sigset_maskall);
905 pthread_sigmask(SIG_SETMASK, &sigset_maskall, NULL);
906
907 for (/* */; ! g_want_shutdown_; /* */) {
908 memset(&ss, 0, sizeof ss);
909
910 /*
911 Attempt to fetch a line from stdin.
912 If EOF is encountered, or any error other than EAGAIN,
913 signal global shutdown and bail.
914 */
915 flockfile(stdin);
916 if (g_want_shutdown_) {
917 funlockfile(stdin);
918 break;
919 }
920
921 LOG_VVV("<%zu> awaiting request\n", id);
922
923 if (fgets(buf, sizeof buf, stdin) == NULL) {
924 if (feof_unlocked(stdin)) {
925 g_want_shutdown_ = 1;
926 } else if (ferror_unlocked(stdin)) {
927 if (errno == EINTR) {
928 clearerr_unlocked(stdin);
929 funlockfile(stdin);
930 continue;
931 } else {
932 LOG("<%zu> %s: %s\n", id, "fgets", strerror(errno));
933 g_want_shutdown_ = 1;
934 }
935 } else {
936 LOG("<%zu> %s: %s\n", id, "fgets", "NULL but no error?");
937 g_want_shutdown_ = 1;
938 }
939 }
940
941 LOG_VVV("<%zu> '%s'\n", id, buf);
942
943 funlockfile(stdin);
944 if (g_want_shutdown_)
945 break;
946
947 /* try again if line is empty */
948 if (*buf == '\n' || *buf == '\0')
949 continue;
950
951 /* explicit exit request */
952 if (strncmp(QUIT_COMMAND, buf, strlen(QUIT_COMMAND)) == 0) {
953 g_want_shutdown_ = 1;
954 fclose(stdin);
955 break;
956 }
957
958 #ifdef WITH_RESOLVER_STATS
959 if (strncmp(DUMP_COMMAND, buf, strlen(DUMP_COMMAND)) == 0) {
960 cache_statdump_();
961 continue;
962 }
963 #endif /* WITH_RESOLVER_STATS */
964
965 /* locate the ports, and parse them first */
966 buf_ptr = strchr(buf, '(');
967 if (buf_ptr == NULL) {
968 LOG_V("<%zu> malformed request, no paren: %s", id, buf); /* expect \n in buf */
969 continue;
970 }
971
972 r = sscanf(buf_ptr, "(%d)%d", &port_remote, &port_local);
973 if (r != 2) {
974 LOG_V("<%zu> malformed request, bad ports: %s (%d)", id, buf_ptr, r);
975 continue;
976 }
977
978 if (port_local < 0
979 || port_local > (unsigned short)-1
980 || port_remote < 0
981 || port_remote > (unsigned short)-1) {
982 LOG_V("<%zu> port out of range: %s", id, buf);
983 continue;
984 }
985
986 /* truncate buffer at ports, and parse into an address */
987 *buf_ptr = '\0';
988
989 /* locate the first delimiter to determine address family */
990 buf_ptr = strpbrk(buf, ".:");
991 if (buf_ptr == NULL) {
992 LOG_V("<%zu> could not parse address: %s\n", id, buf);
993 continue;
994 }
995 else if (*buf_ptr == '.') {
996 /* ipv4 */
997 ss.ss_family = AF_INET;
998 vaddr = &((struct sockaddr_in *)&ss)->sin_addr;
999 }
1000 #ifdef USE_IPV6
1001 else if (*buf_ptr == ':') {
1002 /* ipv6 */
1003 ss.ss_family = AF_INET6;
1004 vaddr = &((struct sockaddr_in6 *)&ss)->sin6_addr;
1005 }
1006 #endif /* USE_IPV6 */
1007 else {
1008 LOG_V("<%zu> could not parse address: %s\n", id, buf);
1009 continue;
1010 }
1011
1012 r = inet_pton(ss.ss_family, buf, vaddr);
1013 if (r == -1) {
1014 LOG("%s: %s\n", "inet_pton", strerror(errno));
1015 continue;
1016 } else if (r == 0) {
1017 LOG_V("<%zu> could not parse address: %s\n", id, buf);
1018 continue;
1019 }
1020
1021 LOG_VVV("> %s %d %d\n", buf, port_remote, port_local);
1022
1023 ident_query_(username_buf, sizeof username_buf, &ss, port_remote, port_local);
1024 host_query_(hostname_buf, sizeof hostname_buf, &ss);
1025
1026 flockfile(stdout);
1027 fprintf(stdout, "%s(%d)|%s(%s)\n", buf, port_remote, hostname_buf, username_buf);
1028 fflush(stdout);
1029 funlockfile(stdout);
1030
1031 LOG_VVV("<%zu> '%s(%d)|%s(%s)'\n", id, buf, port_remote, hostname_buf, username_buf);
1032 }
1033
1034 LOG_VV("<%zu> thread exiting\n", id);
1035 return NULL;
1036 }
1037
1038
1039
1040 static
1041 void
1042 set_signal_(int sig, void (*func)(int))
1043 {
1044 #ifdef _POSIX_VERSION
1045 struct sigaction act, oact;
1046
1047 act.sa_handler = func;
1048 sigemptyset(&act.sa_mask);
1049
1050 # ifdef SA_RESTART
1051 act.sa_flags = SA_RESTART;
1052 # else /* SA_RESTART */
1053 act.sa_flags = 0;
1054 # endif /* SA_RESTART */
1055
1056 if (sigaction(sig, &act, &oact)) {
1057 LOG("%s: %s\n", "sigaction", strerror(errno));
1058 }
1059 #else /* _POSIX_VERSION */
1060 if (signal(signo, sighandler) == SIG_ERR) {
1061 LOG("%s: %s\n", "signal", strerror(errno));
1062 }
1063 #endif /* _POSIX_VERSION */
1064 }
1065
1066
1067
1068 /*
1069 Convert a base-10 number in str to a size_t within range specified.
1070 Just a helper for processing command-line arguments.
1071 */
1072 static
1073 int
1074 str_to_sizet_range_(const char *str, size_t *val, size_t min, size_t max)
1075 {
1076 char *end;
1077 long long num;
1078
1079 num = strtoll(str, &end, 10);
1080 if (*str == '\0' || *end != '\0') {
1081 fprintf(stderr, "'%s' is not a valid number\n", str);
1082 return -1;
1083 }
1084 if (num < 0
1085 || (unsigned long long)num < min
1086 || (unsigned long long)num > max) {
1087 fprintf(stderr, "%lld is not between %zu and %zu\n", num, min, max);
1088 return -1;
1089 }
1090
1091 *val = (size_t)num;
1092
1093 return 0;
1094 }
1095
1096 /* same for unsigned int */
1097 static
1098 int
1099 str_to_uint_range_(const char *str, unsigned int *val, unsigned int min, unsigned int max)
1100 {
1101 char *end;
1102 long long num;
1103
1104 num = strtoll(str, &end, 10);
1105 if (*str == '\0' || *end != '\0') {
1106 fprintf(stderr, "'%s' is not a valid number\n", str);
1107 return -1;
1108 }
1109 if (num < 0
1110 || (unsigned long long)num < min
1111 || (unsigned long long)num > max) {
1112 fprintf(stderr, "%lld is not between %u and %u\n", num, min, max);
1113 return -1;
1114 }
1115
1116 *val = (unsigned int)num;
1117
1118 return 0;
1119 }
1120
1121 int
1122 main(int argc, char **argv)
1123 {
1124 struct {
1125 pthread_t pthr;
1126 size_t tid;
1127 } *threads = NULL;
1128 size_t i;
1129 int c;
1130
1131 #ifdef HAVE_GETRLIMIT
1132 struct rlimit lim;
1133
1134 /* close anything not stdio */
1135 getrlimit(RLIMIT_NOFILE, &lim);
1136 for (i = 3; i < lim.rlim_cur; i++) {
1137 if (close(i) == 0)
1138 LOG_VV("cleaned up fd %zu\n", i);
1139 }
1140 #endif /* HAVE_GETRLIMIT */
1141
1142 #ifdef DEBUG_PRETEND_IDENT
1143 srand48(getpid());
1144 #endif
1145
1146 g_log_stream_ = stderr;
1147
1148 while ( (c = getopt(argc, argv, "a:c:f:j:s:o:vh")) != EOF ) {
1149 switch (c) {
1150 case 'a':
1151 if (str_to_uint_range_(optarg, &g_opts_.age_success, 0, (unsigned int)-1))
1152 exit(EX_USAGE);
1153 break;
1154
1155 case 'c':
1156 if (str_to_sizet_range_(optarg, &g_opts_.capacity, 256, (size_t)-1))
1157 exit(EX_USAGE);
1158 break;
1159
1160 case 'f':
1161 if (str_to_uint_range_(optarg, &g_opts_.age_fail, 0, (unsigned int)-1))
1162 exit(EX_USAGE);
1163 break;
1164
1165 case 'j':
1166 if (str_to_sizet_range_(optarg, &g_opts_.num_threads, 1, (size_t)-1))
1167 exit(EX_USAGE);
1168 break;
1169
1170 case 's':
1171 if (str_to_sizet_range_(optarg, &g_opts_.hash_sz, 1, (size_t)-1))
1172 exit(EX_USAGE);
1173 break;
1174
1175 case 'v':
1176 g_opts_.verbose += 1;
1177 break;
1178
1179 case 'o':
1180 g_opts_.log_filename = optarg;
1181 g_opts_.verbose += 1;
1182 break;
1183
1184 case 'h':
1185 usage_(argv[0], USAGE_FLAG_SHOWFULL);
1186 exit(EX_OK);
1187
1188 default:
1189 usage_(argv[0], 0);
1190 exit(EX_USAGE);
1191 }
1192 }
1193
1194 if (argc - optind != 0) {
1195 usage_(argv[0], 0);
1196 exit(EX_USAGE);
1197 }
1198
1199 if (g_opts_.log_filename) {
1200 g_log_stream_ = fopen(g_opts_.log_filename, "a+");
1201 if (g_log_stream_ == NULL) {
1202 fprintf(stderr, "could not open '%s': %s\n", g_opts_.log_filename, strerror(errno));
1203 exit(EX_OSERR);
1204 }
1205 }
1206
1207 #ifdef LOG_DEBUG
1208 char lf[] = "/tmp/resolver-debug.XXXXXX";
1209 int lf_fd = mkstemp(lf);
1210
1211 if (lf_fd == -1) {
1212 fprintf(stderr, "could not create debug logfile '%s': %s\n", lf, strerror(errno));
1213 exit(EX_OSERR);
1214 }
1215 g_log_stream_ = fdopen(lf_fd, "a+");
1216 if (g_log_stream_ == NULL) {
1217 fprintf(stderr, "%s: %s\n", "fdopen", strerror(errno));
1218 exit(EX_OSERR);
1219 }
1220 g_opts_.verbose = 3;
1221 #endif
1222
1223 g_cache_ = lru_cache_new(g_opts_.hash_sz, g_opts_.capacity, cache_entry_hash_feed_, cache_entry_cmp_);
1224 if (g_cache_ == NULL) {
1225 exit(EX_OSERR);
1226 }
1227
1228 threads = calloc(g_opts_.num_threads, sizeof *threads);
1229 if (threads == NULL) {
1230 LOG("%s: %s\n", "calloc", strerror(errno));
1231 exit(EX_OSERR);
1232 }
1233
1234 set_signal_(SIGPIPE, SIG_IGN);
1235 set_signal_(SIGHUP, SIG_IGN);
1236
1237 for (i = 0; i < g_opts_.num_threads; i++) {
1238 threads[i].tid = i;
1239 c = pthread_create(&threads[i].pthr, NULL, resolve_thread_, (void *)&threads[i].tid);
1240 if (c) {
1241 LOG("%s: %s\n", "pthread_create", strerror(c));
1242 exit(EX_OSERR);
1243 }
1244 }
1245
1246 for (i = 0; i < g_opts_.num_threads; i++) {
1247 void *retval;
1248 c = pthread_join(threads[i].pthr, &retval);
1249 if (c) {
1250 LOG("%s: %s\n", "pthread_join", strerror(c));
1251 }
1252 }
1253
1254 cache_free_();
1255 free(g_cache_);
1256 free(threads);
1257
1258 LOG("Resolver exited.\n");
1259
1260 fclose(g_log_stream_);
1261
1262 exit(EX_OK);
1263 }