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