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