1 /* $OpenBSD: asr.c,v 1.30 2013/06/01 15:02:01 eric Exp $ */
3 * Copyright (c) 2010-2012 Eric Faurot <eric@openbsd.org>
5 * Permission to use, copy, modify, and distribute this software for any
6 * purpose with or without fee is hereby granted, provided that the above
7 * copyright notice and this permission notice appear in all copies.
9 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
10 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
11 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
12 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
13 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
14 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
15 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
18 #include <sys/types.h>
20 #include <netinet/in.h>
21 #include <arpa/inet.h>
22 #include <arpa/nameser.h>
36 #include "asr_private.h"
38 #ifndef ASR_OPT_THREADSAFE
39 #define ASR_OPT_THREADSAFE 1
41 #ifndef ASR_OPT_HOSTALIASES
42 #define ASR_OPT_HOSTALIASES 1
44 #ifndef ASR_OPT_ENVOPTS
45 #define ASR_OPT_ENVOPTS 1
47 #ifndef ASR_OPT_RELOADCONF
48 #define ASR_OPT_RELOADCONF 1
50 #ifndef ASR_OPT_ALTCONF
51 #define ASR_OPT_ALTCONF 1
54 #if ASR_OPT_THREADSAFE
55 #include "thread_private.h"
58 #define DEFAULT_CONFFILE "/etc/resolv.conf"
59 #define DEFAULT_HOSTFILE "/etc/hosts"
60 #define DEFAULT_CONF "lookup file\n"
61 #define DEFAULT_LOOKUP "lookup bind file"
63 #define RELOAD_DELAY 15 /* seconds */
65 static void asr_check_reload(struct asr
*);
66 static struct asr_ctx
*asr_ctx_create(void);
67 static void asr_ctx_ref(struct asr_ctx
*);
68 static void asr_ctx_free(struct asr_ctx
*);
69 static int asr_ctx_add_searchdomain(struct asr_ctx
*, const char *);
70 static int asr_ctx_from_file(struct asr_ctx
*, const char *);
71 static int asr_ctx_from_string(struct asr_ctx
*, const char *);
72 static int asr_ctx_parse(struct asr_ctx
*, const char *);
73 static int asr_parse_nameserver(struct sockaddr
*, const char *);
74 static int asr_ndots(const char *);
75 static void pass0(char **, int, struct asr_ctx
*);
76 static int strsplit(char *, char **, int);
78 static void asr_ctx_envopts(struct asr_ctx
*);
80 #if ASR_OPT_THREADSAFE
81 static void *__THREAD_NAME(_asr
);
83 # define _THREAD_PRIVATE(a, b, c) (c)
86 static struct asr
*_asr
= NULL
;
88 /* Allocate and configure an async "resolver". */
90 async_resolver(const char *conf
)
97 if (getenv("ASR_DEBUG"))
103 if ((asr
= calloc(1, sizeof(*asr
))) == NULL
)
107 /* If not setuid/setgid, allow to use an alternate config. */
108 if (conf
== NULL
&& !issetugid())
109 conf
= getenv("ASR_CONFIG");
113 conf
= DEFAULT_CONFFILE
;
115 if (conf
[0] == '!') {
116 /* Use the rest of the string as config file */
117 if ((asr
->a_ctx
= asr_ctx_create()) == NULL
)
119 if (asr_ctx_from_string(asr
->a_ctx
, conf
+ 1) == -1)
122 /* Use the given config file */
123 asr
->a_path
= strdup(conf
);
124 if (asr
->a_path
== NULL
)
126 asr_check_reload(asr
);
127 if (asr
->a_ctx
== NULL
) {
128 if ((asr
->a_ctx
= asr_ctx_create()) == NULL
)
130 if (asr_ctx_from_string(asr
->a_ctx
, DEFAULT_CONF
) == -1)
133 asr_ctx_envopts(asr
->a_ctx
);
139 asr_dump_config(asr_debug
, asr
);
146 asr_ctx_free(asr
->a_ctx
);
155 * Free the "asr" async resolver (or the thread-local resolver if NULL).
156 * Drop the reference to the current context.
159 async_resolver_done(struct asr
*asr
)
164 priv
= _THREAD_PRIVATE(_asr
, _asr
, &_asr
);
171 asr_ctx_unref(asr
->a_ctx
);
177 * Cancel an async query.
180 async_abort(struct async
*as
)
186 * Resume the "as" async query resolution. Return one of ASYNC_COND,
187 * ASYNC_YIELD or ASYNC_DONE and put query-specific return values in
188 * the user-allocated memory at "ar".
191 async_run(struct async
*as
, struct async_res
*ar
)
193 int r
, saved_errno
= errno
;
195 DPRINT("asr: async_run(%p, %p) %s ctx=[%p]\n", as
, ar
,
196 asr_querystr(as
->as_type
), as
->as_ctx
);
197 r
= as
->as_run(as
, ar
);
199 DPRINT("asr: async_run(%p, %p) -> %s", as
, ar
, asr_transitionstr(r
));
203 DPRINT(" fd=%i timeout=%i", ar
->ar_fd
, ar
->ar_timeout
);
214 * Same as above, but run in a loop that handles the fd conditions result.
217 async_run_sync(struct async
*as
, struct async_res
*ar
)
219 struct pollfd fds
[1];
220 int r
, saved_errno
= errno
;
222 while ((r
= async_run(as
, ar
)) == ASYNC_COND
) {
223 fds
[0].fd
= ar
->ar_fd
;
224 fds
[0].events
= (ar
->ar_cond
== ASYNC_READ
) ? POLLIN
: POLLOUT
;
226 r
= poll(fds
, 1, ar
->ar_timeout
);
227 if (r
== -1 && errno
== EINTR
)
230 * Otherwise, just ignore the error and let async_run()
241 * Create a new async request of the given "type" on the async context "ac".
242 * Take a reference on it so it does not gets deleted while the async query
246 async_new(struct asr_ctx
*ac
, int type
)
250 DPRINT("asr: async_new(ctx=%p) type=%i refcount=%i\n", ac
, type
,
251 ac
? ac
->ac_refcount
: 0);
252 if (ac
== NULL
|| (as
= calloc(1, sizeof(*as
))) == NULL
)
255 ac
->ac_refcount
+= 1;
259 as
->as_state
= ASR_STATE_INIT
;
265 * Free an async query and unref the associated context.
268 async_free(struct async
*as
)
270 DPRINT("asr: async_free(%p)\n", as
);
271 switch (as
->as_type
) {
275 if (as
->as
.dns
.obuf
&& !(as
->as
.dns
.flags
& ASYNC_EXTOBUF
))
276 free(as
->as
.dns
.obuf
);
278 free(as
->as
.dns
.ibuf
);
279 if (as
->as
.dns
.dname
)
280 free(as
->as
.dns
.dname
);
284 if (as
->as
.search
.subq
)
285 async_free(as
->as
.search
.subq
);
286 if (as
->as
.search
.name
)
287 free(as
->as
.search
.name
);
290 case ASR_GETRRSETBYNAME
:
291 if (as
->as
.rrset
.subq
)
292 async_free(as
->as
.rrset
.subq
);
293 if (as
->as
.rrset
.name
)
294 free(as
->as
.rrset
.name
);
297 case ASR_GETHOSTBYNAME
:
298 case ASR_GETHOSTBYADDR
:
299 if (as
->as
.hostnamadr
.subq
)
300 async_free(as
->as
.hostnamadr
.subq
);
301 if (as
->as
.hostnamadr
.name
)
302 free(as
->as
.hostnamadr
.name
);
305 case ASR_GETNETBYNAME
:
306 case ASR_GETNETBYADDR
:
307 if (as
->as
.netnamadr
.subq
)
308 async_free(as
->as
.netnamadr
.subq
);
309 if (as
->as
.netnamadr
.name
)
310 free(as
->as
.netnamadr
.name
);
313 case ASR_GETADDRINFO
:
315 async_free(as
->as
.ai
.subq
);
316 if (as
->as
.ai
.aifirst
)
317 freeaddrinfo(as
->as
.ai
.aifirst
);
318 if (as
->as
.ai
.hostname
)
319 free(as
->as
.ai
.hostname
);
320 if (as
->as
.ai
.servname
)
321 free(as
->as
.ai
.servname
);
323 free(as
->as
.ai
.fqdn
);
326 case ASR_GETNAMEINFO
:
328 async_free(as
->as
.ni
.subq
);
332 asr_ctx_unref(as
->as_ctx
);
337 * Get a context from the given resolver. This takes a new reference to
338 * the returned context, which *must* be explicitely dropped when done
339 * using this context.
342 asr_use_resolver(struct asr
*asr
)
347 DPRINT("using thread-local resolver\n");
348 priv
= _THREAD_PRIVATE(_asr
, _asr
, &_asr
);
350 DPRINT("setting up thread-local resolver\n");
351 *priv
= async_resolver(NULL
);
356 asr_check_reload(asr
);
357 asr_ctx_ref(asr
->a_ctx
);
364 asr_ctx_ref(struct asr_ctx
*ac
)
366 DPRINT("asr: asr_ctx_ref(ctx=%p) refcount=%i\n", ac
, ac
->ac_refcount
);
367 ac
->ac_refcount
+= 1;
371 * Drop a reference to an async context, freeing it if the reference
375 asr_ctx_unref(struct asr_ctx
*ac
)
377 DPRINT("asr: asr_ctx_unref(ctx=%p) refcount=%i\n", ac
,
378 ac
? ac
->ac_refcount
: 0);
381 if (--ac
->ac_refcount
)
388 asr_ctx_free(struct asr_ctx
*ac
)
394 for (i
= 0; i
< ASR_MAXNS
; i
++)
396 for (i
= 0; i
< ASR_MAXDOM
; i
++)
403 * Reload the configuration file if it has changed on disk.
406 asr_check_reload(struct asr
*asr
)
409 #if ASR_OPT_RELOADCONF
414 if (asr
->a_path
== NULL
)
417 #if ASR_OPT_RELOADCONF
418 if (clock_gettime(CLOCK_MONOTONIC
, &ts
) == -1)
421 if ((ts
.tv_sec
- asr
->a_rtime
) < RELOAD_DELAY
&& asr
->a_rtime
!= 0)
423 asr
->a_rtime
= ts
.tv_sec
;
425 DPRINT("asr: checking for update of \"%s\"\n", asr
->a_path
);
426 if (stat(asr
->a_path
, &st
) == -1 ||
427 asr
->a_mtime
== st
.st_mtime
||
428 (ac
= asr_ctx_create()) == NULL
)
430 asr
->a_mtime
= st
.st_mtime
;
432 if ((ac
= asr_ctx_create()) == NULL
)
436 DPRINT("asr: reloading config file\n");
437 if (asr_ctx_from_file(ac
, asr
->a_path
) == -1) {
446 asr_ctx_unref(asr
->a_ctx
);
451 * Construct a fully-qualified domain name for the given name and domain.
452 * If "name" ends with a '.' it is considered as a FQDN by itself.
453 * Otherwise, the domain, which must be a FQDN, is appended to "name" (it
454 * may have a leading dot which would be ignored). If the domain is null,
455 * then "." is used. Return the length of the constructed FQDN or (0) on
459 asr_make_fqdn(const char *name
, const char *domain
, char *buf
, size_t buflen
)
465 else if ((len
= strlen(domain
)) == 0)
467 else if (domain
[len
-1] != '.')
472 if (strlcpy(buf
, domain
, buflen
) >= buflen
)
474 } else if (name
[len
- 1] != '.') {
475 if (domain
[0] == '.')
477 if (strlcpy(buf
, name
, buflen
) >= buflen
||
478 strlcat(buf
, ".", buflen
) >= buflen
||
479 strlcat(buf
, domain
, buflen
) >= buflen
)
482 if (strlcpy(buf
, name
, buflen
) >= buflen
)
486 return (strlen(buf
));
490 * Count the dots in a string.
493 asr_ndots(const char *s
)
505 * Allocate a new empty context.
507 static struct asr_ctx
*
512 if ((ac
= calloc(1, sizeof(*ac
))) == NULL
)
515 ac
->ac_options
= RES_RECURSE
| RES_DEFNAMES
| RES_DNSRCH
;
518 ac
->ac_family
[0] = AF_INET
;
519 ac
->ac_family
[1] = AF_INET6
;
520 ac
->ac_family
[2] = -1;
522 ac
->ac_hostfile
= DEFAULT_HOSTFILE
;
525 ac
->ac_nstimeout
= 5;
526 ac
->ac_nsretries
= 4;
532 * Add a search domain to the async context.
535 asr_ctx_add_searchdomain(struct asr_ctx
*ac
, const char *domain
)
539 if (ac
->ac_domcount
== ASR_MAXDOM
)
542 if (asr_make_fqdn(domain
, NULL
, buf
, sizeof(buf
)) == 0)
545 if ((ac
->ac_dom
[ac
->ac_domcount
] = strdup(buf
)) == NULL
)
548 ac
->ac_domcount
+= 1;
554 strsplit(char *line
, char **tokens
, int ntokens
)
559 for (cp
= line
, tp
= tokens
, ntok
= 0;
560 ntok
< ntokens
&& (*tp
= strsep(&cp
, " \t")) != NULL
; )
570 * Pass on a split config line.
573 pass0(char **tok
, int n
, struct asr_ctx
*ac
)
577 struct sockaddr_storage ss
;
579 if (!strcmp(tok
[0], "nameserver")) {
580 if (ac
->ac_nscount
== ASR_MAXNS
)
584 if (asr_parse_nameserver((struct sockaddr
*)&ss
, tok
[1]))
586 if ((ac
->ac_ns
[ac
->ac_nscount
] = calloc(1, ss
.ss_len
)) == NULL
)
588 memmove(ac
->ac_ns
[ac
->ac_nscount
], &ss
, ss
.ss_len
);
591 } else if (!strcmp(tok
[0], "domain")) {
596 ac
->ac_domain
= strdup(tok
[1]);
598 } else if (!strcmp(tok
[0], "lookup")) {
599 /* ensure that each lookup is only given once */
600 for (i
= 1; i
< n
; i
++)
601 for (j
= i
+ 1; j
< n
; j
++)
602 if (!strcmp(tok
[i
], tok
[j
]))
605 for (i
= 1; i
< n
&& ac
->ac_dbcount
< ASR_MAXDB
; i
++) {
606 if (!strcmp(tok
[i
], "yp"))
607 ac
->ac_db
[ac
->ac_dbcount
++] = ASR_DB_YP
;
608 else if (!strcmp(tok
[i
], "bind"))
609 ac
->ac_db
[ac
->ac_dbcount
++] = ASR_DB_DNS
;
610 else if (!strcmp(tok
[i
], "file"))
611 ac
->ac_db
[ac
->ac_dbcount
++] = ASR_DB_FILE
;
613 } else if (!strcmp(tok
[0], "search")) {
614 /* resolv.conf says the last line wins */
615 for (i
= 0; i
< ASR_MAXDOM
; i
++)
618 for (i
= 1; i
< n
; i
++)
619 asr_ctx_add_searchdomain(ac
, tok
[i
]);
621 } else if (!strcmp(tok
[0], "family")) {
624 for (i
= 1; i
< n
; i
++)
625 if (strcmp(tok
[i
], "inet4") && strcmp(tok
[i
], "inet6"))
627 for (i
= 1; i
< n
; i
++)
628 ac
->ac_family
[i
- 1] = strcmp(tok
[i
], "inet4") ? \
630 ac
->ac_family
[i
- 1] = -1;
632 } else if (!strcmp(tok
[0], "options")) {
633 for (i
= 1; i
< n
; i
++) {
634 if (!strcmp(tok
[i
], "tcp"))
635 ac
->ac_options
|= RES_USEVC
;
636 else if ((!strncmp(tok
[i
], "ndots:", 6))) {
638 d
= strtonum(tok
[i
] + 6, 1, 16, &e
);
647 * Setup an async context with the config specified in the string "str".
650 asr_ctx_from_string(struct asr_ctx
*ac
, const char *str
)
654 asr_ctx_parse(ac
, str
);
656 if (ac
->ac_dbcount
== 0) {
657 /* No lookup directive */
658 asr_ctx_parse(ac
, DEFAULT_LOOKUP
);
661 if (ac
->ac_nscount
== 0)
662 asr_ctx_parse(ac
, "nameserver 127.0.0.1");
664 if (ac
->ac_domain
== NULL
)
665 if (gethostname(buf
, sizeof buf
) == 0) {
666 ch
= strchr(buf
, '.');
668 ac
->ac_domain
= strdup(ch
+ 1);
669 else /* Assume root. see resolv.conf(5) */
670 ac
->ac_domain
= strdup("");
673 /* If no search domain was specified, use the local subdomains */
674 if (ac
->ac_domcount
== 0)
675 for (ch
= ac
->ac_domain
; ch
; ) {
676 asr_ctx_add_searchdomain(ac
, ch
);
677 ch
= strchr(ch
, '.');
678 if (ch
&& asr_ndots(++ch
) == 0)
686 * Setup the "ac" async context from the file at location "path".
689 asr_ctx_from_file(struct asr_ctx
*ac
, const char *path
)
695 cf
= fopen(path
, "r");
699 r
= fread(buf
, 1, sizeof buf
- 1, cf
);
701 DPRINT("asr: config file too long: \"%s\"\n", path
);
709 return asr_ctx_from_string(ac
, buf
);
713 * Parse lines in the configuration string. For each one, split it into
714 * tokens and pass them to "pass0" for processing.
717 asr_ctx_parse(struct asr_ctx
*ac
, const char *str
)
727 len
= strcspn(line
, "\n\0");
728 if (len
< sizeof buf
) {
729 memmove(buf
, line
, len
);
736 buf
[strcspn(buf
, ";#")] = '\0';
737 if ((ntok
= strsplit(buf
, tok
, 10)) == 0)
740 pass0(tok
, ntok
, ac
);
748 * Check for environment variables altering the configuration as described
749 * in resolv.conf(5). Altough not documented there, this feature is disabled
750 * for setuid/setgid programs.
753 asr_ctx_envopts(struct asr_ctx
*ac
)
759 ac
->ac_options
|= RES_NOALIASES
;
763 if ((e
= getenv("RES_OPTIONS")) != NULL
) {
764 strlcpy(buf
, "options ", sizeof buf
);
765 strlcat(buf
, e
, sizeof buf
);
766 s
= strlcat(buf
, "\n", sizeof buf
);
767 s
= strlcat(buf
, "\n", sizeof buf
);
769 asr_ctx_parse(ac
, buf
);
772 if ((e
= getenv("LOCALDOMAIN")) != NULL
) {
773 strlcpy(buf
, "search ", sizeof buf
);
774 strlcat(buf
, e
, sizeof buf
);
775 s
= strlcat(buf
, "\n", sizeof buf
);
777 asr_ctx_parse(ac
, buf
);
783 * Parse a resolv.conf(5) nameserver string into a sockaddr.
786 asr_parse_nameserver(struct sockaddr
*sa
, const char *s
)
791 in_port_t portno
= 53;
794 strlcpy(buf
, s
+ 1, sizeof buf
);
796 port
= strchr(buf
, ']');
806 portno
= strtonum(port
, 1, USHRT_MAX
, &estr
);
811 if (sockaddr_from_str(sa
, PF_UNSPEC
, s
) == -1)
814 if (sa
->sa_family
== PF_INET
)
815 ((struct sockaddr_in
*)sa
)->sin_port
= htons(portno
);
816 else if (sa
->sa_family
== PF_INET6
)
817 ((struct sockaddr_in6
*)sa
)->sin6_port
= htons(portno
);
823 * Turn a (uncompressed) DNS domain name into a regular nul-terminated string
824 * where labels are separated by dots. The result is put into the "buf" buffer,
825 * truncated if it exceeds "max" chars. The function returns "buf".
828 asr_strdname(const char *_dname
, char *buf
, size_t max
)
830 const unsigned char *dname
= _dname
;
832 size_t left
, n
, count
;
834 if (_dname
[0] == 0) {
835 strlcpy(buf
, ".", max
);
841 for (n
= 0; dname
[0] && left
; n
+= dname
[0]) {
842 count
= (dname
[0] < (left
- 1)) ? dname
[0] : (left
- 1);
843 memmove(buf
, dname
+ 1, count
);
844 dname
+= dname
[0] + 1;
858 * Read and split the next line from the given namedb file.
859 * Return -1 on error, or put the result in the "tokens" array of
860 * size "ntoken" and returns the number of token on the line.
863 asr_parse_namedb_line(FILE *file
, char **tokens
, int ntoken
)
870 if ((buf
= fgetln(file
, &len
)) == NULL
)
873 if (buf
[len
- 1] == '\n')
877 buf
[strcspn(buf
, "#")] = '\0';
878 if ((ntok
= strsplit(buf
, tokens
, ntoken
)) == 0)
885 * Update the async context so that it uses the next configured DB.
886 * Return 0 on success, or -1 if no more DBs is available.
889 asr_iter_db(struct async
*as
)
891 if (as
->as_db_idx
>= as
->as_ctx
->ac_dbcount
) {
892 DPRINT("asr_iter_db: done\n");
897 DPRINT("asr_iter_db: %i\n", as
->as_db_idx
);
903 * Check if the hostname "name" is a user-defined alias as per hostname(7).
904 * If so, copies the result in the buffer "abuf" of size "abufsz" and
905 * return "abuf". Otherwise return NULL.
908 asr_hostalias(struct asr_ctx
*ac
, const char *name
, char *abuf
, size_t abufsz
)
910 #if ASR_OPT_HOSTALIASES
913 char *file
, *buf
, *tokens
[2];
916 if (ac
->ac_options
& RES_NOALIASES
||
917 asr_ndots(name
) != 0 ||
919 (file
= getenv("HOSTALIASES")) == NULL
||
920 (fp
= fopen(file
, "r")) == NULL
)
923 DPRINT("asr: looking up aliases in \"%s\"\n", file
);
925 while ((buf
= fgetln(fp
, &len
)) != NULL
) {
926 if (buf
[len
- 1] == '\n')
929 if ((ntok
= strsplit(buf
, tokens
, 2)) != 2)
931 if (!strcasecmp(tokens
[0], name
)) {
932 if (strlcpy(abuf
, tokens
[1], abufsz
) > abufsz
)
934 DPRINT("asr: found alias \"%s\"\n", abuf
);