]>
Commit | Line | Data |
---|---|---|
189935b1 | 1 | /* |
2 | * A rewrite of Darren Reed's original res.c As there is nothing | |
3 | * left of Darren's original code, this is now licensed by the hybrid group. | |
4 | * (Well, some of the function names are the same, and bits of the structs..) | |
5 | * You can use it where it is useful, free even. Buy us a beer and stuff. | |
6 | * | |
7 | * The authors takes no responsibility for any damage or loss | |
8 | * of property which results from the use of this software. | |
9 | * | |
10 | * July 1999 - Rewrote a bunch of stuff here. Change hostent builder code, | |
11 | * added callbacks and reference counting of returned hostents. | |
12 | * --Bleep (Thomas Helvey <tomh@inxpress.net>) | |
13 | * | |
14 | * This was all needlessly complicated for irc. Simplified. No more hostent | |
15 | * All we really care about is the IP -> hostname mappings. Thats all. | |
16 | * | |
17 | * Apr 28, 2003 --cryogen and Dianora | |
18 | */ | |
19 | /** @file | |
20 | * @brief IRC resolver functions. | |
951d56c0 | 21 | * @version $Id: ircd_res.c,v 1.23.2.5 2007/02/25 15:41:49 entrope Exp $ |
189935b1 | 22 | */ |
23 | ||
24 | #include "client.h" | |
25 | #include "ircd_alloc.h" | |
26 | #include "ircd_log.h" | |
27 | #include "ircd_osdep.h" | |
28 | #include "ircd_reply.h" | |
29 | #include "ircd_string.h" | |
30 | #include "ircd_snprintf.h" | |
31 | #include "ircd.h" | |
32 | #include "numeric.h" | |
33 | #include "fileio.h" /* for fbopen / fbclose / fbputs */ | |
34 | #include "random.h" | |
35 | #include "s_bsd.h" | |
36 | #include "s_debug.h" | |
37 | #include "s_stats.h" | |
38 | #include "send.h" | |
39 | #include "sys.h" | |
40 | #include "res.h" | |
41 | #include "ircd_reslib.h" | |
42 | ||
43 | /* #include <assert.h> -- Now using assert in ircd_log.h */ | |
44 | #include <string.h> | |
45 | #include <sys/time.h> | |
46 | #include <sys/socket.h> | |
47 | #include <time.h> | |
48 | ||
49 | #if (CHAR_BIT != 8) | |
50 | #error this code needs to be able to address individual octets | |
51 | #endif | |
52 | ||
53 | /** IPv4 resolver UDP socket. */ | |
54 | static struct Socket res_socket_v4; | |
55 | /** IPv6 resolver UDP socket. */ | |
56 | static struct Socket res_socket_v6; | |
57 | /** Next DNS lookup timeout. */ | |
58 | static struct Timer res_timeout; | |
951d56c0 | 59 | /** Local address for IPv4 DNS lookups. */ |
60 | struct irc_sockaddr VirtualHost_dns_v4; | |
61 | /** Local address for IPv6 DNS lookups. */ | |
62 | struct irc_sockaddr VirtualHost_dns_v6; | |
189935b1 | 63 | /** Check for whether the resolver has been initialized yet. */ |
64 | #define resolver_started() (request_list.next != NULL) | |
65 | ||
66 | /** Maximum DNS packet length. | |
67 | * RFC says 512, but we add extra for expanded names. | |
68 | */ | |
69 | #define MAXPACKET 1024 | |
70 | #define AR_TTL 600 /**< TTL in seconds for dns cache entries */ | |
71 | ||
72 | /* RFC 1104/1105 wasn't very helpful about what these fields | |
73 | * should be named, so for now, we'll just name them this way. | |
74 | * we probably should look at what named calls them or something. | |
75 | */ | |
76 | /** Size of TYPE field of a DNS RR header. */ | |
77 | #define TYPE_SIZE (size_t)2 | |
78 | /** Size of CLASS field of a DNS RR header. */ | |
79 | #define CLASS_SIZE (size_t)2 | |
80 | /** Size of TTL field of a DNS RR header. */ | |
81 | #define TTL_SIZE (size_t)4 | |
82 | /** Size of RDLENGTH field of a DNS RR header. */ | |
83 | #define RDLENGTH_SIZE (size_t)2 | |
84 | /** Size of fixed-format part of a DNS RR header. */ | |
85 | #define ANSWER_FIXED_SIZE (TYPE_SIZE + CLASS_SIZE + TTL_SIZE + RDLENGTH_SIZE) | |
86 | ||
87 | /** Current request state. */ | |
88 | typedef enum | |
89 | { | |
90 | REQ_IDLE, /**< We're doing not much at all. */ | |
91 | REQ_PTR, /**< Looking up a PTR. */ | |
92 | REQ_A, /**< Looking up an A, possibly because AAAA failed. */ | |
93 | REQ_AAAA, /**< Looking up an AAAA. */ | |
94 | REQ_CNAME, /**< We got a CNAME in response, we better get a real answer next. */ | |
95 | REQ_INT /**< ip6.arpa failed, falling back to ip6.int. */ | |
96 | } request_state; | |
97 | ||
98 | /** Doubly linked list node. */ | |
99 | struct dlink | |
100 | { | |
101 | struct dlink *prev; /**< Previous element in list. */ | |
102 | struct dlink *next; /**< Next element in list. */ | |
103 | }; | |
104 | ||
105 | /** A single resolver request. | |
106 | * (Do not be fooled by the "list" in the name.) | |
107 | */ | |
108 | struct reslist | |
109 | { | |
110 | struct dlink node; /**< Doubly linked list node. */ | |
111 | int id; /**< Request ID (from request header). */ | |
112 | int sent; /**< Number of requests sent. */ | |
113 | request_state state; /**< State the resolver machine is in. */ | |
114 | char type; /**< Current request type. */ | |
115 | char retries; /**< Retry counter. */ | |
116 | char sends; /**< Number of sends (>1 means resent). */ | |
117 | char resend; /**< Send flag; 0 == don't resend. */ | |
118 | time_t sentat; /**< Timestamp we last sent this request. */ | |
119 | time_t timeout; /**< When this request times out. */ | |
120 | struct irc_in_addr addr; /**< Address for this request. */ | |
121 | char *name; /**< Hostname for this request. */ | |
122 | dns_callback_f callback; /**< Callback function on completion. */ | |
123 | void *callback_ctx; /**< Context pointer for callback. */ | |
124 | }; | |
125 | ||
126 | /** Base of request list. */ | |
127 | static struct dlink request_list; | |
128 | ||
129 | static void rem_request(struct reslist *request); | |
130 | static struct reslist *make_request(dns_callback_f callback, void *ctx); | |
131 | static void do_query_name(dns_callback_f callback, void *ctx, | |
132 | const char* name, struct reslist *request, int); | |
133 | static void do_query_number(dns_callback_f callback, void *ctx, | |
134 | const struct irc_in_addr *, | |
135 | struct reslist *request); | |
136 | static void query_name(const char *name, int query_class, int query_type, | |
137 | struct reslist *request); | |
138 | static int send_res_msg(const char *buf, int len, int count); | |
139 | static void resend_query(struct reslist *request); | |
140 | static int proc_answer(struct reslist *request, HEADER *header, char *, char *); | |
141 | static struct reslist *find_id(int id); | |
142 | static void res_readreply(struct Event *ev); | |
143 | static void timeout_resolver(struct Event *notused); | |
144 | ||
145 | extern struct irc_sockaddr irc_nsaddr_list[IRCD_MAXNS]; | |
146 | extern int irc_nscount; | |
147 | extern char irc_domain[HOSTLEN]; | |
148 | ||
951d56c0 | 149 | /** Prepare the resolver library to (optionally) accept a list of |
150 | * DNS servers through add_dns_server(). | |
151 | */ | |
152 | void clear_nameservers(void) | |
153 | { | |
154 | irc_nscount = 0; | |
155 | memset(&VirtualHost_dns_v4, 0, sizeof(VirtualHost_dns_v4)); | |
156 | memset(&VirtualHost_dns_v6, 0, sizeof(VirtualHost_dns_v6)); | |
157 | } | |
158 | ||
189935b1 | 159 | /** Check whether \a inp is a nameserver we use. |
160 | * @param[in] inp Nameserver address. | |
161 | * @return Non-zero if we trust \a inp; zero if not. | |
162 | */ | |
163 | static int | |
164 | res_ourserver(const struct irc_sockaddr *inp) | |
165 | { | |
166 | int ns; | |
167 | ||
168 | for (ns = 0; ns < irc_nscount; ns++) | |
169 | if (!irc_in_addr_cmp(&inp->addr, &irc_nsaddr_list[ns].addr) | |
170 | && inp->port == irc_nsaddr_list[ns].port) | |
171 | return 1; | |
172 | ||
173 | return(0); | |
174 | } | |
175 | ||
176 | /** Start (or re-start) resolver. | |
177 | * This means read resolv.conf, initialize the list of pending | |
178 | * requests, open the resolver socket and initialize its timeout. | |
179 | */ | |
180 | void | |
181 | restart_resolver(void) | |
182 | { | |
951d56c0 | 183 | int need_v4; |
184 | int need_v6; | |
185 | int ns; | |
186 | ||
189935b1 | 187 | irc_res_init(); |
188 | ||
189 | if (!request_list.next) | |
190 | request_list.next = request_list.prev = &request_list; | |
191 | ||
951d56c0 | 192 | /* Check which address family (or families) our nameservers use. */ |
193 | for (need_v4 = need_v6 = ns = 0; ns < irc_nscount; ns++) | |
194 | { | |
195 | if (irc_in_addr_is_ipv4(&irc_nsaddr_list[ns].addr)) | |
196 | need_v4 = 1; | |
197 | else | |
198 | need_v6 = 1; | |
199 | } | |
200 | ||
201 | /* If we need an IPv4 socket, and don't have one, open it. */ | |
202 | if (need_v4 && !s_active(&res_socket_v4)) | |
189935b1 | 203 | { |
951d56c0 | 204 | int fd = os_socket(&VirtualHost_dns_v4, SOCK_DGRAM, "Resolver UDPv4 socket", AF_INET); |
189935b1 | 205 | if (fd >= 0) |
206 | socket_add(&res_socket_v4, res_readreply, NULL, | |
207 | SS_DATAGRAM, SOCK_EVENT_READABLE, fd); | |
208 | } | |
209 | ||
9f8856e9 | 210 | #ifdef AF_INET6 |
951d56c0 | 211 | /* If we need an IPv6 socket, and don't have one, open it. */ |
212 | if (need_v6 && !s_active(&res_socket_v6)) | |
189935b1 | 213 | { |
951d56c0 | 214 | int fd = os_socket(&VirtualHost_dns_v6, SOCK_DGRAM, "Resolver UDPv6 socket", AF_INET6); |
189935b1 | 215 | if (fd >= 0) |
216 | socket_add(&res_socket_v6, res_readreply, NULL, | |
217 | SS_DATAGRAM, SOCK_EVENT_READABLE, fd); | |
218 | } | |
9f8856e9 | 219 | #endif |
189935b1 | 220 | |
221 | if (s_active(&res_socket_v4) || s_active(&res_socket_v6)) | |
222 | timer_init(&res_timeout); | |
223 | } | |
224 | ||
225 | /** Append local domain to hostname if needed. | |
226 | * If \a hname does not contain any '.'s, append #irc_domain to it. | |
227 | * @param[in,out] hname Hostname to check. | |
228 | * @param[in] size Length of \a hname buffer. | |
229 | */ | |
230 | void | |
231 | add_local_domain(char* hname, size_t size) | |
232 | { | |
233 | /* try to fix up unqualified names | |
234 | */ | |
235 | if (strchr(hname, '.') == NULL) | |
236 | { | |
237 | if (irc_domain[0]) | |
238 | { | |
239 | size_t len = strlen(hname); | |
240 | ||
241 | if ((strlen(irc_domain) + len + 2) < size) | |
242 | { | |
243 | hname[len++] = '.'; | |
244 | strcpy(hname + len, irc_domain); | |
245 | } | |
246 | } | |
247 | } | |
248 | } | |
249 | ||
250 | /** Add a node to a doubly linked list. | |
251 | * @param[in,out] node Node to add to list. | |
252 | * @param[in,out] next Add \a node before this one. | |
253 | */ | |
254 | static void | |
255 | add_dlink(struct dlink *node, struct dlink *next) | |
256 | { | |
257 | node->prev = next->prev; | |
258 | node->next = next; | |
259 | node->prev->next = node; | |
260 | node->next->prev = node; | |
261 | } | |
262 | ||
263 | /** Remove a request from the list and free it. | |
264 | * @param[in] request Node to free. | |
265 | */ | |
266 | static void | |
267 | rem_request(struct reslist *request) | |
268 | { | |
269 | /* remove from dlist */ | |
270 | request->node.prev->next = request->node.next; | |
271 | request->node.next->prev = request->node.prev; | |
272 | /* free memory */ | |
273 | MyFree(request->name); | |
274 | MyFree(request); | |
275 | } | |
276 | ||
277 | /** Create a DNS request record for the server. | |
278 | * @param[in] query Callback information for caller. | |
279 | * @return Newly allocated and linked-in reslist. | |
280 | */ | |
281 | static struct reslist * | |
282 | make_request(dns_callback_f callback, void *ctx) | |
283 | { | |
284 | struct reslist *request; | |
285 | ||
286 | if (!resolver_started()) | |
287 | restart_resolver(); | |
288 | ||
289 | request = (struct reslist *)MyMalloc(sizeof(struct reslist)); | |
290 | memset(request, 0, sizeof(struct reslist)); | |
291 | ||
292 | request->state = REQ_IDLE; | |
293 | request->sentat = CurrentTime; | |
294 | request->retries = feature_int(FEAT_IRCD_RES_RETRIES); | |
295 | request->resend = 1; | |
296 | request->timeout = feature_int(FEAT_IRCD_RES_TIMEOUT); | |
297 | memset(&request->addr, 0, sizeof(request->addr)); | |
298 | request->callback = callback; | |
299 | request->callback_ctx = ctx; | |
300 | ||
301 | add_dlink(&request->node, &request_list); | |
302 | return(request); | |
303 | } | |
304 | ||
305 | /** Make sure that a timeout event will happen by the given time. | |
306 | * @param[in] when Latest time for timeout to run. | |
307 | */ | |
308 | static void | |
309 | check_resolver_timeout(time_t when) | |
310 | { | |
311 | if (when > CurrentTime + AR_TTL) | |
312 | when = CurrentTime + AR_TTL; | |
313 | /* TODO after 2.10.12: Rewrite the timer API because there should be | |
314 | * no need for clients to know this kind of implementation detail. */ | |
315 | if (when > t_expire(&res_timeout)) | |
316 | /* do nothing */; | |
317 | else if (t_onqueue(&res_timeout) && !(res_timeout.t_header.gh_flags & GEN_MARKED)) | |
318 | timer_chg(&res_timeout, TT_ABSOLUTE, when); | |
319 | else | |
320 | timer_add(&res_timeout, timeout_resolver, NULL, TT_ABSOLUTE, when); | |
321 | } | |
322 | ||
323 | /** Drop pending DNS lookups which have timed out. | |
324 | * @param[in] ev Timer event data (ignored). | |
325 | */ | |
326 | static void | |
327 | timeout_resolver(struct Event *ev) | |
328 | { | |
329 | struct dlink *ptr, *next_ptr; | |
330 | struct reslist *request; | |
331 | time_t next_time = 0; | |
332 | time_t timeout = 0; | |
333 | ||
334 | if (ev_type(ev) != ET_EXPIRE) | |
335 | return; | |
336 | ||
337 | for (ptr = request_list.next; ptr != &request_list; ptr = next_ptr) | |
338 | { | |
339 | next_ptr = ptr->next; | |
340 | request = (struct reslist*)ptr; | |
341 | timeout = request->sentat + request->timeout; | |
342 | ||
343 | if (CurrentTime >= timeout) | |
344 | { | |
345 | if (--request->retries <= 0) | |
346 | { | |
347 | Debug((DEBUG_DNS, "Request %p out of retries; destroying", request)); | |
348 | (*request->callback)(request->callback_ctx, NULL, NULL); | |
349 | rem_request(request); | |
350 | continue; | |
351 | } | |
352 | else | |
353 | { | |
354 | request->sentat = CurrentTime; | |
355 | request->timeout += request->timeout; | |
356 | resend_query(request); | |
357 | } | |
358 | } | |
359 | ||
360 | if ((next_time == 0) || timeout < next_time) | |
361 | { | |
362 | next_time = timeout; | |
363 | } | |
364 | } | |
365 | ||
366 | if (next_time <= CurrentTime) | |
367 | next_time = CurrentTime + AR_TTL; | |
368 | check_resolver_timeout(next_time); | |
369 | } | |
370 | ||
371 | /** Drop queries that are associated with a particular pointer. | |
372 | * This is used to clean up lookups for clients or conf blocks | |
373 | * that went away. | |
374 | * @param[in] vptr User callback pointer to search for. | |
375 | */ | |
376 | void | |
377 | delete_resolver_queries(const void *vptr) | |
378 | { | |
379 | struct dlink *ptr, *next_ptr; | |
380 | struct reslist *request; | |
381 | ||
382 | if (request_list.next) { | |
383 | for (ptr = request_list.next; ptr != &request_list; ptr = next_ptr) | |
384 | { | |
385 | next_ptr = ptr->next; | |
386 | request = (struct reslist*)ptr; | |
387 | if (vptr == request->callback_ctx) { | |
388 | Debug((DEBUG_DNS, "Removing request %p with vptr %p", request, vptr)); | |
389 | rem_request(request); | |
390 | } | |
391 | } | |
392 | } | |
393 | } | |
394 | ||
395 | /** Send a message to all of our nameservers. | |
396 | * @param[in] msg Message to send. | |
397 | * @param[in] len Length of message. | |
398 | * @param[in] rcount Maximum number of servers to ask. | |
399 | * @return Number of servers that were successfully asked. | |
400 | */ | |
401 | static int | |
402 | send_res_msg(const char *msg, int len, int rcount) | |
403 | { | |
404 | int i; | |
405 | int sent = 0; | |
406 | int max_queries = IRCD_MIN(irc_nscount, rcount); | |
407 | ||
408 | /* RES_PRIMARY option is not implemented | |
409 | * if (res.options & RES_PRIMARY || 0 == max_queries) | |
410 | */ | |
411 | if (max_queries == 0) | |
412 | max_queries = 1; | |
413 | ||
414 | for (i = 0; i < max_queries; i++) { | |
415 | int fd = irc_in_addr_is_ipv4(&irc_nsaddr_list[i].addr) ? s_fd(&res_socket_v4) : s_fd(&res_socket_v6); | |
416 | if (os_sendto_nonb(fd, msg, len, NULL, 0, &irc_nsaddr_list[i]) == IO_SUCCESS) | |
417 | ++sent; | |
418 | } | |
419 | ||
420 | return(sent); | |
421 | } | |
422 | ||
423 | /** Find a DNS request by ID. | |
424 | * @param[in] id Identifier to find. | |
425 | * @return Matching DNS request, or NULL if none are found. | |
426 | */ | |
427 | static struct reslist * | |
428 | find_id(int id) | |
429 | { | |
430 | struct dlink *ptr; | |
431 | struct reslist *request; | |
432 | ||
433 | for (ptr = request_list.next; ptr != &request_list; ptr = ptr->next) | |
434 | { | |
435 | request = (struct reslist*)ptr; | |
436 | ||
437 | if (request->id == id) { | |
438 | Debug((DEBUG_DNS, "find_id(%d) -> %p", id, request)); | |
439 | return(request); | |
440 | } | |
441 | } | |
442 | ||
443 | Debug((DEBUG_DNS, "find_id(%d) -> NULL", id)); | |
444 | return(NULL); | |
445 | } | |
446 | ||
447 | /** Try to look up address for a hostname, trying IPv6 (T_AAAA) first. | |
448 | * @param[in] name Hostname to look up. | |
449 | * @param[in] query Callback information. | |
450 | */ | |
451 | void | |
452 | gethost_byname(const char *name, dns_callback_f callback, void *ctx) | |
453 | { | |
454 | do_query_name(callback, ctx, name, NULL, T_AAAA); | |
455 | } | |
456 | ||
457 | /** Try to look up hostname for an address. | |
458 | * @param[in] addr Address to look up. | |
459 | * @param[in] query Callback information. | |
460 | */ | |
461 | void | |
462 | gethost_byaddr(const struct irc_in_addr *addr, dns_callback_f callback, void *ctx) | |
463 | { | |
464 | do_query_number(callback, ctx, addr, NULL); | |
465 | } | |
466 | ||
467 | /** Send a query to look up the address for a name. | |
468 | * @param[in] query Callback information. | |
469 | * @param[in] name Hostname to look up. | |
470 | * @param[in] request DNS lookup structure (may be NULL). | |
471 | * @param[in] type Preferred request type. | |
472 | */ | |
473 | static void | |
474 | do_query_name(dns_callback_f callback, void *ctx, const char *name, | |
475 | struct reslist *request, int type) | |
476 | { | |
477 | char host_name[HOSTLEN + 1]; | |
478 | ||
479 | ircd_strncpy(host_name, name, HOSTLEN); | |
480 | add_local_domain(host_name, HOSTLEN); | |
481 | ||
482 | if (request == NULL) | |
483 | { | |
484 | request = make_request(callback, ctx); | |
485 | DupString(request->name, host_name); | |
486 | #ifdef IPV6 | |
487 | if (type != T_A) | |
488 | request->state = REQ_AAAA; | |
489 | else | |
490 | #endif | |
491 | request->state = REQ_A; | |
492 | } | |
493 | ||
494 | request->type = type; | |
495 | Debug((DEBUG_DNS, "Requesting DNS %s %s as %p", (request->state == REQ_AAAA ? "AAAA" : "A"), host_name, request)); | |
496 | query_name(host_name, C_IN, type, request); | |
497 | } | |
498 | ||
499 | /** Send a query to look up the name for an address. | |
500 | * @param[in] query Callback information. | |
501 | * @param[in] addr Address to look up. | |
502 | * @param[in] request DNS lookup structure (may be NULL). | |
503 | */ | |
504 | static void | |
505 | do_query_number(dns_callback_f callback, void *ctx, const struct irc_in_addr *addr, | |
506 | struct reslist *request) | |
507 | { | |
508 | char ipbuf[128]; | |
509 | const unsigned char *cp; | |
510 | ||
511 | if (irc_in_addr_is_ipv4(addr)) | |
512 | { | |
513 | cp = (const unsigned char*)&addr->in6_16[6]; | |
514 | ircd_snprintf(NULL, ipbuf, sizeof(ipbuf), "%u.%u.%u.%u.in-addr.arpa.", | |
515 | (unsigned int)(cp[3]), (unsigned int)(cp[2]), | |
516 | (unsigned int)(cp[1]), (unsigned int)(cp[0])); | |
517 | } | |
518 | else | |
519 | { | |
520 | const char *intarpa; | |
521 | ||
522 | if (request != NULL && request->state == REQ_INT) | |
523 | intarpa = "int"; | |
524 | else | |
525 | intarpa = "arpa"; | |
526 | ||
527 | cp = (const unsigned char *)&addr->in6_16[0]; | |
528 | ircd_snprintf(NULL, ipbuf, sizeof(ipbuf), | |
529 | "%x.%x.%x.%x.%x.%x.%x.%x.%x.%x.%x.%x.%x.%x.%x.%x.%x." | |
530 | "%x.%x.%x.%x.%x.%x.%x.%x.%x.%x.%x.%x.%x.%x.%x.ip6.%s.", | |
531 | (unsigned int)(cp[15]&0xf), (unsigned int)(cp[15]>>4), | |
532 | (unsigned int)(cp[14]&0xf), (unsigned int)(cp[14]>>4), | |
533 | (unsigned int)(cp[13]&0xf), (unsigned int)(cp[13]>>4), | |
534 | (unsigned int)(cp[12]&0xf), (unsigned int)(cp[12]>>4), | |
535 | (unsigned int)(cp[11]&0xf), (unsigned int)(cp[11]>>4), | |
536 | (unsigned int)(cp[10]&0xf), (unsigned int)(cp[10]>>4), | |
537 | (unsigned int)(cp[9]&0xf), (unsigned int)(cp[9]>>4), | |
538 | (unsigned int)(cp[8]&0xf), (unsigned int)(cp[8]>>4), | |
539 | (unsigned int)(cp[7]&0xf), (unsigned int)(cp[7]>>4), | |
540 | (unsigned int)(cp[6]&0xf), (unsigned int)(cp[6]>>4), | |
541 | (unsigned int)(cp[5]&0xf), (unsigned int)(cp[5]>>4), | |
542 | (unsigned int)(cp[4]&0xf), (unsigned int)(cp[4]>>4), | |
543 | (unsigned int)(cp[3]&0xf), (unsigned int)(cp[3]>>4), | |
544 | (unsigned int)(cp[2]&0xf), (unsigned int)(cp[2]>>4), | |
545 | (unsigned int)(cp[1]&0xf), (unsigned int)(cp[1]>>4), | |
546 | (unsigned int)(cp[0]&0xf), (unsigned int)(cp[0]>>4), intarpa); | |
547 | } | |
548 | if (request == NULL) | |
549 | { | |
550 | request = make_request(callback, ctx); | |
551 | request->state= REQ_PTR; | |
552 | request->type = T_PTR; | |
553 | memcpy(&request->addr, addr, sizeof(request->addr)); | |
554 | request->name = (char *)MyMalloc(HOSTLEN + 1); | |
555 | } | |
556 | Debug((DEBUG_DNS, "Requesting DNS PTR %s as %p", ipbuf, request)); | |
557 | query_name(ipbuf, C_IN, T_PTR, request); | |
558 | } | |
559 | ||
560 | /** Generate a query based on class, type and name. | |
561 | * @param[in] name Domain name to look up. | |
562 | * @param[in] query_class Query class (see RFC 1035). | |
563 | * @param[in] type Query type (see RFC 1035). | |
564 | * @param[in] request DNS request structure. | |
565 | */ | |
566 | static void | |
567 | query_name(const char *name, int query_class, int type, | |
568 | struct reslist *request) | |
569 | { | |
570 | char buf[MAXPACKET]; | |
571 | int request_len = 0; | |
572 | ||
573 | memset(buf, 0, sizeof(buf)); | |
574 | ||
575 | if ((request_len = irc_res_mkquery(name, query_class, type, | |
576 | (unsigned char *)buf, sizeof(buf))) > 0) | |
577 | { | |
578 | HEADER *header = (HEADER *)buf; | |
579 | ||
580 | /* | |
581 | * generate an unique id | |
582 | * NOTE: we don't have to worry about converting this to and from | |
583 | * network byte order, the nameserver does not interpret this value | |
584 | * and returns it unchanged | |
585 | */ | |
586 | do | |
587 | { | |
588 | header->id = (header->id + ircrandom()) & 0xffff; | |
589 | } while (find_id(header->id)); | |
590 | request->id = header->id; | |
591 | ++request->sends; | |
592 | ||
593 | request->sent += send_res_msg(buf, request_len, request->sends); | |
594 | check_resolver_timeout(request->sentat + request->timeout); | |
595 | } | |
596 | } | |
597 | ||
598 | /** Send a failed DNS lookup request again. | |
599 | * @param[in] request Request to resend. | |
600 | */ | |
601 | static void | |
602 | resend_query(struct reslist *request) | |
603 | { | |
604 | if (request->resend == 0) | |
605 | return; | |
606 | ||
607 | switch(request->type) | |
608 | { | |
609 | case T_PTR: | |
610 | do_query_number(NULL, NULL, &request->addr, request); | |
611 | break; | |
612 | case T_A: | |
613 | do_query_name(NULL, NULL, request->name, request, request->type); | |
614 | break; | |
615 | case T_AAAA: | |
616 | /* didn't work, try A */ | |
617 | if (request->state == REQ_AAAA) | |
618 | do_query_name(NULL, NULL, request->name, request, T_A); | |
619 | default: | |
620 | break; | |
621 | } | |
622 | } | |
623 | ||
624 | /** Process the answer for a lookup request. | |
625 | * @param[in] request DNS request that got an answer. | |
626 | * @param[in] header Header of DNS response. | |
627 | * @param[in] buf DNS response body. | |
628 | * @param[in] eob Pointer to end of DNS response. | |
629 | * @return Number of answers read from \a buf. | |
630 | */ | |
631 | static int | |
632 | proc_answer(struct reslist *request, HEADER* header, char* buf, char* eob) | |
633 | { | |
634 | char hostbuf[HOSTLEN + 100]; /* working buffer */ | |
635 | unsigned char *current; /* current position in buf */ | |
636 | int query_class; /* answer class */ | |
637 | int type; /* answer type */ | |
638 | int n; /* temp count */ | |
639 | int rd_length; | |
640 | ||
641 | current = (unsigned char *)buf + sizeof(HEADER); | |
642 | ||
643 | for (; header->qdcount > 0; --header->qdcount) | |
644 | { | |
645 | if ((n = irc_dn_skipname(current, (unsigned char *)eob)) < 0) | |
646 | break; | |
647 | ||
648 | current += (size_t) n + QFIXEDSZ; | |
649 | } | |
650 | ||
651 | /* | |
652 | * process each answer sent to us blech. | |
653 | */ | |
654 | while (header->ancount > 0 && (char *)current < eob) | |
655 | { | |
656 | header->ancount--; | |
657 | ||
658 | n = irc_dn_expand((unsigned char *)buf, (unsigned char *)eob, current, | |
659 | hostbuf, sizeof(hostbuf)); | |
660 | ||
661 | if (n < 0) | |
662 | { | |
663 | /* | |
664 | * broken message | |
665 | */ | |
666 | return(0); | |
667 | } | |
668 | else if (n == 0) | |
669 | { | |
670 | /* | |
671 | * no more answers left | |
672 | */ | |
673 | return(0); | |
674 | } | |
675 | ||
676 | hostbuf[HOSTLEN] = '\0'; | |
677 | ||
678 | /* With Address arithmetic you have to be very anal | |
679 | * this code was not working on alpha due to that | |
680 | * (spotted by rodder/jailbird/dianora) | |
681 | */ | |
682 | current += (size_t) n; | |
683 | ||
684 | if (!(((char *)current + ANSWER_FIXED_SIZE) < eob)) | |
685 | break; | |
686 | ||
687 | type = irc_ns_get16(current); | |
688 | current += TYPE_SIZE; | |
689 | ||
690 | query_class = irc_ns_get16(current); | |
691 | current += CLASS_SIZE; | |
692 | ||
693 | current += TTL_SIZE; | |
694 | ||
695 | rd_length = irc_ns_get16(current); | |
696 | current += RDLENGTH_SIZE; | |
697 | ||
698 | /* | |
699 | * Wait to set request->type until we verify this structure | |
700 | */ | |
701 | switch (type) | |
702 | { | |
703 | case T_A: | |
704 | if (request->type != T_A) | |
705 | return(0); | |
706 | ||
707 | /* | |
708 | * check for invalid rd_length or too many addresses | |
709 | */ | |
710 | if (rd_length != sizeof(struct in_addr)) | |
711 | return(0); | |
712 | memset(&request->addr, 0, sizeof(request->addr)); | |
713 | memcpy(&request->addr.in6_16[6], current, sizeof(struct in_addr)); | |
714 | return(1); | |
715 | break; | |
716 | case T_AAAA: | |
717 | if (request->type != T_AAAA) | |
718 | return(0); | |
719 | if (rd_length != sizeof(struct irc_in_addr)) | |
720 | return(0); | |
721 | memcpy(&request->addr, current, sizeof(struct irc_in_addr)); | |
722 | return(1); | |
723 | break; | |
724 | case T_PTR: | |
725 | if (request->type != T_PTR) | |
726 | return(0); | |
727 | n = irc_dn_expand((unsigned char *)buf, (unsigned char *)eob, | |
728 | current, hostbuf, sizeof(hostbuf)); | |
729 | if (n < 0) | |
730 | return(0); /* broken message */ | |
731 | else if (n == 0) | |
732 | return(0); /* no more answers left */ | |
733 | ||
734 | ircd_strncpy(request->name, hostbuf, HOSTLEN); | |
735 | ||
736 | return(1); | |
737 | break; | |
738 | case T_CNAME: /* first check we already haven't started looking | |
739 | into a cname */ | |
189935b1 | 740 | if (request->state == REQ_CNAME) |
741 | { | |
742 | n = irc_dn_expand((unsigned char *)buf, (unsigned char *)eob, | |
743 | current, hostbuf, sizeof(hostbuf)); | |
744 | ||
745 | if (n < 0) | |
746 | return(0); | |
747 | return(1); | |
748 | } | |
749 | ||
750 | request->state = REQ_CNAME; | |
751 | current += rd_length; | |
752 | break; | |
753 | ||
754 | default: | |
755 | /* XXX I'd rather just throw away the entire bogus thing | |
756 | * but its possible its just a broken nameserver with still | |
757 | * valid answers. But lets do some rudimentary logging for now... | |
758 | */ | |
759 | log_write(LS_RESOLVER, L_ERROR, 0, "irc_res.c bogus type %d", type); | |
760 | ||
761 | if ((char*)current + rd_length >= (char*)current) | |
762 | current += rd_length; | |
763 | else | |
764 | return(0); | |
765 | ||
766 | break; | |
767 | } | |
768 | } | |
769 | ||
770 | return(1); | |
771 | } | |
772 | ||
773 | /** Read a DNS reply from the nameserver and process it. | |
774 | * @param[in] ev I/O activity event for resolver socket. | |
775 | */ | |
776 | static void | |
777 | res_readreply(struct Event *ev) | |
778 | { | |
779 | struct irc_sockaddr lsin; | |
780 | struct Socket *sock; | |
781 | char buf[sizeof(HEADER) + MAXPACKET]; | |
782 | HEADER *header; | |
783 | struct reslist *request = NULL; | |
784 | unsigned int rc; | |
785 | int answer_count; | |
786 | ||
787 | assert((ev_socket(ev) == &res_socket_v4) || (ev_socket(ev) == &res_socket_v6)); | |
788 | sock = ev_socket(ev); | |
789 | ||
790 | if (IO_SUCCESS != os_recvfrom_nonb(s_fd(sock), buf, sizeof(buf), &rc, &lsin) | |
791 | || (rc <= sizeof(HEADER))) | |
792 | return; | |
793 | ||
794 | /* | |
795 | * check against possibly fake replies | |
796 | */ | |
797 | if (!res_ourserver(&lsin)) | |
798 | return; | |
799 | ||
800 | /* | |
801 | * convert DNS reply reader from Network byte order to CPU byte order. | |
802 | */ | |
803 | header = (HEADER *)buf; | |
804 | header->ancount = ntohs(header->ancount); | |
805 | header->qdcount = ntohs(header->qdcount); | |
806 | header->nscount = ntohs(header->nscount); | |
807 | header->arcount = ntohs(header->arcount); | |
808 | ||
809 | /* | |
810 | * response for an id which we have already received an answer for | |
811 | * just ignore this response. | |
812 | */ | |
813 | if (0 == (request = find_id(header->id))) | |
814 | return; | |
815 | ||
816 | if ((header->rcode != NO_ERRORS) || (header->ancount == 0)) | |
817 | { | |
9f8856e9 | 818 | if (SERVFAIL == header->rcode || NXDOMAIN == header->rcode) |
052b069e | 819 | { |
820 | /* | |
821 | * If a bad error was returned, we stop here and don't send | |
822 | * send any more (no retries granted). | |
823 | */ | |
824 | Debug((DEBUG_DNS, "Request %p has bad response (state %d type %d rcode %d)", request, request->state, request->type, header->rcode)); | |
825 | (*request->callback)(request->callback_ctx, NULL, NULL); | |
826 | rem_request(request); | |
827 | } | |
189935b1 | 828 | else |
829 | { | |
830 | /* | |
831 | * If we haven't already tried this, and we're looking up AAAA, try A | |
832 | * now | |
833 | */ | |
834 | ||
835 | if (request->state == REQ_AAAA && request->type == T_AAAA) | |
836 | { | |
837 | request->timeout += feature_int(FEAT_IRCD_RES_TIMEOUT); | |
838 | resend_query(request); | |
839 | } | |
840 | else if (request->type == T_PTR && request->state != REQ_INT && | |
841 | !irc_in_addr_is_ipv4(&request->addr)) | |
842 | { | |
843 | request->state = REQ_INT; | |
844 | request->timeout += feature_int(FEAT_IRCD_RES_TIMEOUT); | |
845 | resend_query(request); | |
846 | } | |
189935b1 | 847 | } |
848 | ||
849 | return; | |
850 | } | |
851 | /* | |
852 | * If this fails there was an error decoding the received packet, | |
853 | * try it again and hope it works the next time. | |
854 | */ | |
855 | answer_count = proc_answer(request, header, buf, buf + rc); | |
856 | ||
857 | if (answer_count) | |
858 | { | |
859 | if (request->type == T_PTR) | |
860 | { | |
861 | if (request->name == NULL) | |
862 | { | |
863 | /* | |
864 | * got a PTR response with no name, something bogus is happening | |
865 | * don't bother trying again, the client address doesn't resolve | |
866 | */ | |
867 | Debug((DEBUG_DNS, "Request %p PTR had empty name", request)); | |
868 | (*request->callback)(request->callback_ctx, NULL, NULL); | |
869 | rem_request(request); | |
870 | return; | |
871 | } | |
872 | ||
873 | /* | |
874 | * Lookup the 'authoritative' name that we were given for the | |
875 | * ip#. | |
876 | */ | |
877 | #ifdef IPV6 | |
878 | if (!irc_in_addr_is_ipv4(&request->addr)) | |
879 | do_query_name(request->callback, request->callback_ctx, request->name, NULL, T_AAAA); | |
880 | else | |
881 | #endif | |
882 | do_query_name(request->callback, request->callback_ctx, request->name, NULL, T_A); | |
883 | Debug((DEBUG_DNS, "Request %p switching to forward resolution", request)); | |
884 | rem_request(request); | |
885 | } | |
886 | else | |
887 | { | |
888 | /* | |
889 | * got a name and address response, client resolved | |
890 | */ | |
891 | (*request->callback)(request->callback_ctx, &request->addr, request->name); | |
892 | Debug((DEBUG_DNS, "Request %p got forward resolution", request)); | |
893 | rem_request(request); | |
894 | } | |
895 | } | |
896 | else if (!request->sent) | |
897 | { | |
898 | /* XXX - we got a response for a query we didn't send with a valid id? | |
899 | * this should never happen, bail here and leave the client unresolved | |
900 | */ | |
901 | assert(0); | |
902 | ||
903 | /* XXX don't leak it */ | |
904 | Debug((DEBUG_DNS, "Request %p was unexpected(!)", request)); | |
905 | rem_request(request); | |
906 | } | |
907 | } | |
908 | ||
909 | /** Statistics callback to list DNS servers. | |
910 | * @param[in] source_p Client requesting statistics. | |
911 | * @param[in] sd Stats descriptor for request (ignored). | |
912 | * @param[in] param Extra parameter from user (ignored). | |
913 | */ | |
914 | void | |
915 | report_dns_servers(struct Client *source_p, const struct StatDesc *sd, char *param) | |
916 | { | |
917 | int i; | |
918 | char ipaddr[128]; | |
919 | ||
920 | for (i = 0; i < irc_nscount; i++) | |
921 | { | |
922 | ircd_ntoa_r(ipaddr, &irc_nsaddr_list[i].addr); | |
923 | send_reply(source_p, RPL_STATSALINE, ipaddr); | |
924 | } | |
925 | } | |
926 | ||
927 | /** Report memory usage to a client. | |
928 | * @param[in] sptr Client requesting information. | |
929 | * @return Total memory used by pending requests. | |
930 | */ | |
931 | size_t | |
932 | cres_mem(struct Client* sptr) | |
933 | { | |
934 | struct dlink *dlink; | |
935 | struct reslist *request; | |
936 | size_t request_mem = 0; | |
937 | int request_count = 0; | |
938 | ||
939 | if (request_list.next) { | |
940 | for (dlink = request_list.next; dlink != &request_list; dlink = dlink->next) { | |
941 | request = (struct reslist*)dlink; | |
942 | request_mem += sizeof(*request); | |
943 | if (request->name) | |
944 | request_mem += strlen(request->name) + 1; | |
945 | ++request_count; | |
946 | } | |
947 | } | |
948 | ||
949 | send_reply(sptr, SND_EXPLICIT | RPL_STATSDEBUG, | |
950 | ":Resolver: requests %d(%d)", request_count, request_mem); | |
951 | return request_mem; | |
952 | } |