removed debugging cruft, added README and stub headers for standalone testing
[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 /* WITH_RESOLVER_STATS */
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
83 allocated 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 deadline = time(&now) + IDENT_TIMEOUT;
764
765 fd = socket(ss->ss_family, SOCK_STREAM, 0);
766 if (fd == -1) {
767 LOG("%s: %s\n", "socket", strerror(errno));
768 goto done;
769 }
770
771 /* make non-blocking */
772 if (fcntl(fd, F_SETFL, O_NONBLOCK) == -1) {
773 LOG("%s: %s\n", "fcntl", strerror(errno));
774 goto close_done;
775 }
776
777 /* get the sockaddr length, and the port address */
778 ss_addr_fields_(ss, &sockaddr_len, NULL, NULL, &vport);
779 if (vport == NULL)
780 goto close_done;
781
782 *vport = htons(IDENT_PORT);
783
784 r = connect_nb_(fd, (struct sockaddr *)ss, sockaddr_len, deadline);
785 if (r) {
786 LOG_V("%s: %s\n", "nb_connect_", strerror(errno));
787 goto close_done;
788 }
789
790 /* craft and send our meager request */
791 len = snprintf(req_buf, sizeof req_buf, "%hu,%hu\r\n", port_remote, port_local);
792 r = write_nb_(fd, req_buf, len, deadline);
793 if (r < 0) {
794 LOG_V("%s: %s\n", "nb_write_", strerror(errno));
795 goto close_done;
796 }
797
798 /* read one full line as a response */
799 r = read_line_nb_(fd, buf, sizeof buf, &buf_ptr, deadline);
800 if (r < 0) {
801 LOG_V("%s: %s\n", "nb_read_", strerror(errno));
802 goto close_done;
803 }
804
805 if (buf_ptr == NULL) {
806 LOG_VV("incomplete response\n");
807 goto close_done;
808 }
809
810 *buf_ptr = '\0';
811
812 if (ident_response_parse_(buf, port_remote, port_local))
813 goto close_done;
814
815 /* success */
816 strncpy(out_buf, buf, out_buf_sz);
817 retval = 0;
818
819 close_done:
820 shutdown(fd, 2); /* be ruthless, discard any remaining data */
821 close(fd);
822
823 done:
824 if (retval) {
825 /* no success, just echo the port */
826 snprintf(out_buf, out_buf_sz, "%d", port_remote);
827 }
828
829 return retval;
830 }
831
832
833
834 /*
835 Render the name of an address into the supplied buffer.
836 buf_sz ought to be at least NI_HOSTMAX (1025)
837 */
838 static
839 void
840 host_query_(char *buf, size_t buf_sz, struct sockaddr_storage *ss)
841 {
842 int succeeded = 1;
843 int r;
844
845 if (cache_find_(ss, buf, buf_sz)) {
846 LOG_VVV("found '%s' in cache\n", buf);
847 return;
848 }
849
850 /* not in cache, try to resolve it */
851 r = getnameinfo((struct sockaddr *)ss, sizeof *ss, buf, buf_sz, NULL, 0, NI_NAMEREQD);
852 if (r) {
853 if (r != EAI_NONAME)
854 LOG("%s: %s\n", "getnameinfo", gai_strerror(r));
855
856 succeeded = 0;
857
858 /* not resolvable, render it numerically */
859 r = getnameinfo((struct sockaddr *)ss, sizeof *ss, buf, buf_sz, NULL, 0, NI_NUMERICHOST);
860 if (r) {
861 LOG("%s: %s\n", "getnameinfo", gai_strerror(r));
862 snprintf(buf, buf_sz, "(unknown)");
863 }
864 LOG_VVV("failed to resolve '%s'\n", buf);
865 }
866
867 cache_add_(ss, buf, strlen(buf), time(NULL), succeeded);
868 }
869
870
871
872 /*
873 The main processing loop for each thread.
874 */
875 static
876 void *
877 resolve_thread_(void *data)
878 {
879 size_t id = *(size_t *)data;
880 char buf[BUF_SZ];
881 char hostname_buf[2048]; /* seems silly-large, but is just the next power-of-two up from NI_MAXHOST (1025) */
882 char username_buf[512]; /* identd user id might be this long */
883 char *buf_ptr;
884 struct sockaddr_storage ss;
885 void *vaddr;
886 int port_remote, port_local;
887 sigset_t sigset_maskall;
888 int r;
889
890 LOG_VV("<%zu> thread started\n", id);
891
892 sigfillset(&sigset_maskall);
893 pthread_sigmask(SIG_SETMASK, &sigset_maskall, NULL);
894
895 for (/* */; ! g_want_shutdown_; /* */) {
896 memset(&ss, 0, sizeof ss);
897
898 /*
899 Attempt to fetch a line from stdin.
900 If EOF is encountered, or any error other than EAGAIN,
901 signal global shutdown and bail.
902 */
903 flockfile(stdin);
904 if (g_want_shutdown_) {
905 funlockfile(stdin);
906 break;
907 }
908
909 LOG_VVV("<%zu> awaiting request\n", id);
910
911 if (fgets(buf, sizeof buf, stdin) == NULL) {
912 if (feof_unlocked(stdin)) {
913 g_want_shutdown_ = 1;
914 } else if (ferror_unlocked(stdin)) {
915 if (errno == EINTR) {
916 clearerr_unlocked(stdin);
917 funlockfile(stdin);
918 continue;
919 } else {
920 LOG("<%zu> %s: %s\n", id, "fgets", strerror(errno));
921 g_want_shutdown_ = 1;
922 }
923 } else {
924 LOG("<%zu> %s: %s\n", id, "fgets", "NULL but no error?");
925 g_want_shutdown_ = 1;
926 }
927 }
928
929 LOG_VVV("<%zu> '%s'\n", id, buf);
930
931 funlockfile(stdin);
932 if (g_want_shutdown_)
933 break;
934
935 /* try again if line is empty */
936 if (*buf == '\n' || *buf == '\0')
937 continue;
938
939 /* explicit exit request */
940 if (strncmp(QUIT_COMMAND, buf, strlen(QUIT_COMMAND)) == 0) {
941 g_want_shutdown_ = 1;
942 fclose(stdin);
943 break;
944 }
945
946 #ifdef WITH_RESOLVER_STATS
947 if (strncmp(DUMP_COMMAND, buf, strlen(DUMP_COMMAND)) == 0) {
948 cache_statdump_();
949 continue;
950 }
951 #endif /* WITH_RESOLVER_STATS */
952
953 /* locate the ports, and parse them first */
954 buf_ptr = strchr(buf, '(');
955 if (buf_ptr == NULL) {
956 LOG_V("<%zu> malformed request, no paren: %s", id, buf); /* expect \n in buf */
957 continue;
958 }
959
960 r = sscanf(buf_ptr, "(%d)%d", &port_remote, &port_local);
961 if (r != 2) {
962 LOG_V("<%zu> malformed request, bad ports: %s (%d)", id, buf_ptr, r);
963 continue;
964 }
965
966 if (port_local < 0
967 || port_local > (unsigned short)-1
968 || port_remote < 0
969 || port_remote > (unsigned short)-1) {
970 LOG_V("<%zu> port out of range: %s", id, buf);
971 continue;
972 }
973
974 /* truncate buffer at ports, and parse into an address */
975 *buf_ptr = '\0';
976
977 /* locate the first delimiter to determine address family */
978 buf_ptr = strpbrk(buf, ".:");
979 if (buf_ptr == NULL) {
980 LOG_V("<%zu> could not parse address: %s\n", id, buf);
981 continue;
982 }
983 else if (*buf_ptr == '.') {
984 /* ipv4 */
985 ss.ss_family = AF_INET;
986 vaddr = &((struct sockaddr_in *)&ss)->sin_addr;
987 }
988 #ifdef USE_IPV6
989 else if (*buf_ptr == ':') {
990 /* ipv6 */
991 ss.ss_family = AF_INET6;
992 vaddr = &((struct sockaddr_in6 *)&ss)->sin6_addr;
993 }
994 #endif /* USE_IPV6 */
995 else {
996 LOG_V("<%zu> could not parse address: %s\n", id, buf);
997 continue;
998 }
999
1000 r = inet_pton(ss.ss_family, buf, vaddr);
1001 if (r == -1) {
1002 LOG("%s: %s\n", "inet_pton", strerror(errno));
1003 continue;
1004 } else if (r == 0) {
1005 LOG_V("<%zu> could not parse address: %s\n", id, buf);
1006 continue;
1007 }
1008
1009 LOG_VVV("> %s %d %d\n", buf, port_remote, port_local);
1010
1011 ident_query_(username_buf, sizeof username_buf, &ss, port_remote, port_local);
1012 host_query_(hostname_buf, sizeof hostname_buf, &ss);
1013
1014 flockfile(stdout);
1015 fprintf(stdout, "%s(%d)|%s(%s)\n", buf, port_remote, hostname_buf, username_buf);
1016 fflush(stdout);
1017 funlockfile(stdout);
1018
1019 LOG_VVV("<%zu> '%s(%d)|%s(%s)'\n", id, buf, port_remote, hostname_buf, username_buf);
1020 }
1021
1022 LOG_VV("<%zu> thread exiting\n", id);
1023 return NULL;
1024 }
1025
1026
1027
1028 static
1029 void
1030 set_signal_(int sig, void (*func)(int))
1031 {
1032 #ifdef _POSIX_VERSION
1033 struct sigaction act, oact;
1034
1035 act.sa_handler = func;
1036 sigemptyset(&act.sa_mask);
1037
1038 # ifdef SA_RESTART
1039 act.sa_flags = SA_RESTART;
1040 # else /* SA_RESTART */
1041 act.sa_flags = 0;
1042 # endif /* SA_RESTART */
1043
1044 if (sigaction(sig, &act, &oact)) {
1045 LOG("%s: %s\n", "sigaction", strerror(errno));
1046 }
1047 #else /* _POSIX_VERSION */
1048 if (signal(signo, sighandler) == SIG_ERR) {
1049 LOG("%s: %s\n", "signal", strerror(errno));
1050 }
1051 #endif /* _POSIX_VERSION */
1052 }
1053
1054
1055
1056 /*
1057 Convert a base-10 number in str to a size_t within range specified.
1058 Just a helper for processing command-line arguments.
1059 */
1060 static
1061 int
1062 str_to_sizet_range_(const char *str, size_t *val, size_t min, size_t max)
1063 {
1064 char *end;
1065 long long num;
1066
1067 num = strtoll(str, &end, 10);
1068 if (*str == '\0' || *end != '\0') {
1069 fprintf(stderr, "'%s' is not a valid number\n", str);
1070 return -1;
1071 }
1072 if (num < 0
1073 || (unsigned long long)num < min
1074 || (unsigned long long)num > max) {
1075 fprintf(stderr, "%lld is not between %zu and %zu\n", num, min, max);
1076 return -1;
1077 }
1078
1079 *val = (size_t)num;
1080
1081 return 0;
1082 }
1083
1084 /* same for unsigned int */
1085 static
1086 int
1087 str_to_uint_range_(const char *str, unsigned int *val, unsigned int min, unsigned int max)
1088 {
1089 char *end;
1090 long long num;
1091
1092 num = strtoll(str, &end, 10);
1093 if (*str == '\0' || *end != '\0') {
1094 fprintf(stderr, "'%s' is not a valid number\n", str);
1095 return -1;
1096 }
1097 if (num < 0
1098 || (unsigned long long)num < min
1099 || (unsigned long long)num > max) {
1100 fprintf(stderr, "%lld is not between %u and %u\n", num, min, max);
1101 return -1;
1102 }
1103
1104 *val = (unsigned int)num;
1105
1106 return 0;
1107 }
1108
1109 int
1110 main(int argc, char **argv)
1111 {
1112 struct {
1113 pthread_t pthr;
1114 size_t tid;
1115 } *threads = NULL;
1116 size_t i;
1117 int c;
1118
1119 #ifdef HAVE_GETRLIMIT
1120 struct rlimit lim;
1121
1122 /* close anything not stdio */
1123 getrlimit(RLIMIT_NOFILE, &lim);
1124 for (i = 3; i < lim.rlim_cur; i++) {
1125 if (close(i) == 0)
1126 LOG_VV("cleaned up fd %zu\n", i);
1127 }
1128 #endif /* HAVE_GETRLIMIT */
1129
1130 g_log_stream_ = stderr;
1131
1132 while ( (c = getopt(argc, argv, "a:c:f:j:s:o:vh")) != EOF ) {
1133 switch (c) {
1134 case 'a':
1135 if (str_to_uint_range_(optarg, &g_opts_.age_success, 0, (unsigned int)-1))
1136 exit(EX_USAGE);
1137 break;
1138
1139 case 'c':
1140 if (str_to_sizet_range_(optarg, &g_opts_.capacity, 256, (size_t)-1))
1141 exit(EX_USAGE);
1142 break;
1143
1144 case 'f':
1145 if (str_to_uint_range_(optarg, &g_opts_.age_fail, 0, (unsigned int)-1))
1146 exit(EX_USAGE);
1147 break;
1148
1149 case 'j':
1150 if (str_to_sizet_range_(optarg, &g_opts_.num_threads, 1, (size_t)-1))
1151 exit(EX_USAGE);
1152 break;
1153
1154 case 's':
1155 if (str_to_sizet_range_(optarg, &g_opts_.hash_sz, 1, (size_t)-1))
1156 exit(EX_USAGE);
1157 break;
1158
1159 case 'v':
1160 g_opts_.verbose += 1;
1161 break;
1162
1163 case 'o':
1164 g_opts_.log_filename = optarg;
1165 g_opts_.verbose += 1;
1166 break;
1167
1168 case 'h':
1169 usage_(argv[0], USAGE_FLAG_SHOWFULL);
1170 exit(EX_OK);
1171
1172 default:
1173 usage_(argv[0], 0);
1174 exit(EX_USAGE);
1175 }
1176 }
1177
1178 if (argc - optind != 0) {
1179 usage_(argv[0], 0);
1180 exit(EX_USAGE);
1181 }
1182
1183 if (g_opts_.log_filename) {
1184 g_log_stream_ = fopen(g_opts_.log_filename, "a+");
1185 if (g_log_stream_ == NULL) {
1186 fprintf(stderr, "could not open '%s': %s\n", g_opts_.log_filename, strerror(errno));
1187 exit(EX_OSERR);
1188 }
1189 }
1190
1191 #ifdef LOG_DEBUG
1192 /* forces muck-spawned resolver to log debug messages */
1193 char lf[] = "/tmp/resolver-debug.XXXXXX";
1194 int lf_fd = mkstemp(lf);
1195
1196 if (lf_fd == -1) {
1197 fprintf(stderr, "could not create debug logfile '%s': %s\n", lf, strerror(errno));
1198 exit(EX_OSERR);
1199 }
1200 g_log_stream_ = fdopen(lf_fd, "a+");
1201 if (g_log_stream_ == NULL) {
1202 fprintf(stderr, "%s: %s\n", "fdopen", strerror(errno));
1203 exit(EX_OSERR);
1204 }
1205 g_opts_.verbose = 3;
1206 #endif
1207
1208 g_cache_ = lru_cache_new(g_opts_.hash_sz, g_opts_.capacity, cache_entry_hash_feed_, cache_entry_cmp_);
1209 if (g_cache_ == NULL) {
1210 exit(EX_OSERR);
1211 }
1212
1213 threads = calloc(g_opts_.num_threads, sizeof *threads);
1214 if (threads == NULL) {
1215 LOG("%s: %s\n", "calloc", strerror(errno));
1216 exit(EX_OSERR);
1217 }
1218
1219 set_signal_(SIGPIPE, SIG_IGN);
1220 set_signal_(SIGHUP, SIG_IGN);
1221
1222 for (i = 0; i < g_opts_.num_threads; i++) {
1223 threads[i].tid = i;
1224 c = pthread_create(&threads[i].pthr, NULL, resolve_thread_, (void *)&threads[i].tid);
1225 if (c) {
1226 LOG("%s: %s\n", "pthread_create", strerror(c));
1227 exit(EX_OSERR);
1228 }
1229 }
1230
1231 for (i = 0; i < g_opts_.num_threads; i++) {
1232 void *retval;
1233 c = pthread_join(threads[i].pthr, &retval);
1234 if (c) {
1235 LOG("%s: %s\n", "pthread_join", strerror(c));
1236 }
1237 }
1238
1239 cache_free_();
1240 free(g_cache_);
1241 free(threads);
1242
1243 LOG("Resolver exited.\n");
1244
1245 fclose(g_log_stream_);
1246
1247 exit(EX_OK);
1248 }