/src/pidgin/libpurple/dnsquery.c
Line | Count | Source |
1 | | /** |
2 | | * @file dnsquery.c DNS query API |
3 | | * @ingroup core |
4 | | */ |
5 | | |
6 | | /* purple |
7 | | * |
8 | | * Purple is the legal property of its developers, whose names are too numerous |
9 | | * to list here. Please refer to the COPYRIGHT file distributed with this |
10 | | * source distribution. |
11 | | * |
12 | | * This program is free software; you can redistribute it and/or modify |
13 | | * it under the terms of the GNU General Public License as published by |
14 | | * the Free Software Foundation; either version 2 of the License, or |
15 | | * (at your option) any later version. |
16 | | * |
17 | | * This program is distributed in the hope that it will be useful, |
18 | | * but WITHOUT ANY WARRANTY; without even the implied warranty of |
19 | | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
20 | | * GNU General Public License for more details. |
21 | | * |
22 | | * You should have received a copy of the GNU General Public License |
23 | | * along with this program; if not, write to the Free Software |
24 | | * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA |
25 | | * |
26 | | */ |
27 | | #define _PURPLE_DNSQUERY_C_ |
28 | | |
29 | | #include "internal.h" |
30 | | #include "debug.h" |
31 | | #include "dnsquery.h" |
32 | | #include "glibcompat.h" |
33 | | #include "network.h" |
34 | | #include "notify.h" |
35 | | #include "prefs.h" |
36 | | #include "util.h" |
37 | | |
38 | | #ifndef _WIN32 |
39 | | #include <resolv.h> |
40 | | #endif |
41 | | |
42 | 0 | #define MAX_ADDR_RESPONSE_LEN 1048576 |
43 | | |
44 | | #if (defined(__APPLE__) || defined (__unix__)) && !defined(__osf__) |
45 | | #define PURPLE_DNSQUERY_USE_FORK |
46 | | #endif |
47 | | /************************************************************************** |
48 | | * DNS query API |
49 | | **************************************************************************/ |
50 | | |
51 | | static PurpleDnsQueryUiOps *dns_query_ui_ops = NULL; |
52 | | |
53 | | typedef struct _PurpleDnsQueryResolverProcess PurpleDnsQueryResolverProcess; |
54 | | |
55 | | struct _PurpleDnsQueryData { |
56 | | char *hostname; |
57 | | int port; |
58 | | PurpleDnsQueryConnectFunction callback; |
59 | | gpointer data; |
60 | | guint timeout; |
61 | | PurpleAccount *account; |
62 | | |
63 | | #if defined(PURPLE_DNSQUERY_USE_FORK) |
64 | | PurpleDnsQueryResolverProcess *resolver; |
65 | | #elif defined _WIN32 /* end PURPLE_DNSQUERY_USE_FORK */ |
66 | | GThread *resolver; |
67 | | GSList *hosts; |
68 | | gchar *error_message; |
69 | | #endif |
70 | | }; |
71 | | |
72 | | #if defined(PURPLE_DNSQUERY_USE_FORK) |
73 | | |
74 | 0 | #define MAX_DNS_CHILDREN 4 |
75 | | |
76 | | /* |
77 | | * This structure keeps a reference to a child resolver process. |
78 | | */ |
79 | | struct _PurpleDnsQueryResolverProcess { |
80 | | guint inpa; |
81 | | int fd_in, fd_out; |
82 | | pid_t dns_pid; |
83 | | }; |
84 | | |
85 | | static GSList *free_dns_children = NULL; |
86 | | /* TODO: Make me a GQueue when we require >= glib 2.4 */ |
87 | | static GSList *queued_requests = NULL; |
88 | | |
89 | | static int number_of_dns_children = 0; |
90 | | |
91 | | /* |
92 | | * This is a convenience struct used to pass data to |
93 | | * the child resolver process. |
94 | | */ |
95 | | typedef struct { |
96 | | char hostname[512]; |
97 | | int port; |
98 | | } dns_params_t; |
99 | | #endif /* end PURPLE_DNSQUERY_USE_FORK */ |
100 | | |
101 | | static void |
102 | | purple_dnsquery_resolved(PurpleDnsQueryData *query_data, GSList *hosts) |
103 | 0 | { |
104 | 0 | purple_debug_info("dnsquery", "IP resolved for %s\n", query_data->hostname); |
105 | 0 | if (query_data->callback != NULL) |
106 | 0 | query_data->callback(hosts, query_data->data, NULL); |
107 | 0 | else |
108 | 0 | { |
109 | | /* |
110 | | * Callback is a required parameter, but it can get set to |
111 | | * NULL if we cancel a thread-based DNS lookup. So we need |
112 | | * to free hosts. |
113 | | * |
114 | | * The host's list is pairs of file descriptors and sockaddr_in's. |
115 | | */ |
116 | 0 | while (hosts != NULL) |
117 | 0 | { |
118 | 0 | hosts = g_slist_delete_link(hosts, hosts); |
119 | 0 | g_free(hosts->data); |
120 | 0 | hosts = g_slist_delete_link(hosts, hosts); |
121 | 0 | } |
122 | 0 | } |
123 | |
|
124 | 0 | #ifdef PURPLE_DNSQUERY_USE_FORK |
125 | | /* |
126 | | * Add the resolver to the list of available resolvers, and set it |
127 | | * to NULL so that it doesn't get destroyed along with the query_data |
128 | | */ |
129 | 0 | if (query_data->resolver) |
130 | 0 | { |
131 | 0 | free_dns_children = g_slist_prepend(free_dns_children, query_data->resolver); |
132 | 0 | query_data->resolver = NULL; |
133 | 0 | } |
134 | 0 | #endif /* PURPLE_DNSQUERY_USE_FORK */ |
135 | |
|
136 | 0 | purple_dnsquery_destroy(query_data); |
137 | 0 | } |
138 | | |
139 | | static void |
140 | | purple_dnsquery_failed(PurpleDnsQueryData *query_data, const gchar *error_message) |
141 | 0 | { |
142 | 0 | purple_debug_error("dnsquery", "%s\n", error_message); |
143 | 0 | if (query_data->callback != NULL) |
144 | 0 | query_data->callback(NULL, query_data->data, error_message); |
145 | 0 | purple_dnsquery_destroy(query_data); |
146 | 0 | } |
147 | | |
148 | | static gboolean |
149 | | purple_dnsquery_ui_resolve(PurpleDnsQueryData *query_data) |
150 | 0 | { |
151 | 0 | PurpleDnsQueryUiOps *ops = purple_dnsquery_get_ui_ops(); |
152 | |
|
153 | 0 | if (ops && ops->resolve_host) |
154 | 0 | return ops->resolve_host(query_data, purple_dnsquery_resolved, purple_dnsquery_failed); |
155 | | |
156 | 0 | return FALSE; |
157 | 0 | } |
158 | | |
159 | | static gboolean |
160 | | resolve_ip(PurpleDnsQueryData *query_data) |
161 | 0 | { |
162 | 0 | #if defined(HAVE_GETADDRINFO) && defined(AI_NUMERICHOST) |
163 | 0 | struct addrinfo hints, *res; |
164 | 0 | char servname[20]; |
165 | |
|
166 | 0 | g_snprintf(servname, sizeof(servname), "%d", query_data->port); |
167 | 0 | memset(&hints, 0, sizeof(hints)); |
168 | 0 | hints.ai_family = AF_UNSPEC; |
169 | 0 | hints.ai_flags |= AI_NUMERICHOST; |
170 | |
|
171 | 0 | if (0 == getaddrinfo(query_data->hostname, servname, &hints, &res)) |
172 | 0 | { |
173 | 0 | GSList *hosts = NULL; |
174 | 0 | hosts = g_slist_append(hosts, GINT_TO_POINTER(res->ai_addrlen)); |
175 | 0 | hosts = g_slist_append(hosts, g_memdup2(res->ai_addr, res->ai_addrlen)); |
176 | 0 | purple_dnsquery_resolved(query_data, hosts); |
177 | |
|
178 | 0 | freeaddrinfo(res); |
179 | 0 | return TRUE; |
180 | 0 | } |
181 | | #else /* defined(HAVE_GETADDRINFO) && defined(AI_NUMERICHOST) */ |
182 | | struct sockaddr_in sin; |
183 | | if (inet_aton(query_data->hostname, &sin.sin_addr)) |
184 | | { |
185 | | /* |
186 | | * The given "hostname" is actually an IP address, so we |
187 | | * don't need to do anything. |
188 | | */ |
189 | | GSList *hosts = NULL; |
190 | | sin.sin_family = AF_INET; |
191 | | sin.sin_port = htons(query_data->port); |
192 | | hosts = g_slist_append(hosts, GINT_TO_POINTER(sizeof(sin))); |
193 | | hosts = g_slist_append(hosts, g_memdup2(&sin, sizeof(sin))); |
194 | | purple_dnsquery_resolved(query_data, hosts); |
195 | | |
196 | | return TRUE; |
197 | | } |
198 | | #endif |
199 | | |
200 | 0 | return FALSE; |
201 | 0 | } |
202 | | |
203 | | #ifdef USE_IDN |
204 | | static gboolean |
205 | | dns_str_is_ascii(const char *name) |
206 | | { |
207 | | guchar *c; |
208 | | for (c = (guchar *)name; c && *c; ++c) { |
209 | | if (*c > 0x7f) |
210 | | return FALSE; |
211 | | } |
212 | | |
213 | | return TRUE; |
214 | | } |
215 | | #endif |
216 | | |
217 | | #if defined(PURPLE_DNSQUERY_USE_FORK) |
218 | | |
219 | | /* |
220 | | * Unix! |
221 | | */ |
222 | | |
223 | | /* |
224 | | * Begin the DNS resolver child process functions. |
225 | | */ |
226 | | #ifdef HAVE_SIGNAL_H |
227 | | G_GNUC_NORETURN static void |
228 | | trap_gdb_bug(int sig) |
229 | 0 | { |
230 | 0 | const char *message = |
231 | 0 | "Purple's DNS child got a SIGTRAP signal.\n" |
232 | 0 | "This can be caused by trying to run purple inside gdb.\n" |
233 | 0 | "There is a known gdb bug which prevents this. Supposedly purple\n" |
234 | 0 | "should have detected you were using gdb and used an ugly hack,\n" |
235 | 0 | "check cope_with_gdb_brokenness() in dnsquery.c.\n\n" |
236 | 0 | "For more info about this bug, see http://sources.redhat.com/ml/gdb/2001-07/msg00349.html\n"; |
237 | 0 | fputs("\n* * *\n",stderr); |
238 | 0 | fputs(message,stderr); |
239 | 0 | fputs("* * *\n\n",stderr); |
240 | 0 | execlp("xmessage","xmessage","-center", message, NULL); |
241 | 0 | _exit(1); |
242 | 0 | } |
243 | | #endif |
244 | | |
245 | | static void |
246 | | write_to_parent(int fd, const void *buf, size_t count) |
247 | 0 | { |
248 | 0 | ssize_t written; |
249 | |
|
250 | 0 | written = write(fd, buf, count); |
251 | 0 | if (written < 0 || (gsize)written != count) { |
252 | 0 | if (written < 0) |
253 | 0 | fprintf(stderr, "dns[%d]: Error writing data to " |
254 | 0 | "parent: %s\n", getpid(), strerror(errno)); |
255 | 0 | else |
256 | 0 | fprintf(stderr, "dns[%d]: Error: Tried to write %" |
257 | 0 | G_GSIZE_FORMAT " bytes to parent but instead " |
258 | 0 | "wrote %" G_GSIZE_FORMAT " bytes\n", |
259 | 0 | getpid(), count, written); |
260 | 0 | } |
261 | 0 | } |
262 | | |
263 | | G_GNUC_NORETURN static void |
264 | | purple_dnsquery_resolver_run(int child_out, int child_in, gboolean show_debug) |
265 | 0 | { |
266 | 0 | dns_params_t dns_params; |
267 | 0 | const size_t zero = 0; |
268 | 0 | int rc; |
269 | 0 | #ifdef HAVE_GETADDRINFO |
270 | 0 | struct addrinfo hints, *res, *tmp; |
271 | 0 | char servname[20]; |
272 | | #else |
273 | | struct sockaddr_in sin; |
274 | | const size_t addrlen = sizeof(sin); |
275 | | #endif |
276 | 0 | char *hostname; |
277 | |
|
278 | 0 | #ifdef HAVE_SIGNAL_H |
279 | 0 | purple_restore_default_signal_handlers(); |
280 | 0 | signal(SIGTRAP, trap_gdb_bug); |
281 | 0 | #endif |
282 | | |
283 | | /* |
284 | | * We resolve 1 host name for each iteration of this |
285 | | * while loop. |
286 | | * |
287 | | * The top half of this reads in the hostname and port |
288 | | * number from the socket with our parent. The bottom |
289 | | * half of this resolves the IP (blocking) and sends |
290 | | * the result back to our parent, when finished. |
291 | | */ |
292 | 0 | while (1) { |
293 | 0 | fd_set fds; |
294 | 0 | struct timeval tv = { .tv_sec = 20, .tv_usec = 0 }; |
295 | 0 | FD_ZERO(&fds); |
296 | 0 | FD_SET(child_in, &fds); |
297 | 0 | rc = select(child_in + 1, &fds, NULL, NULL, &tv); |
298 | 0 | if (!rc) { |
299 | 0 | if (show_debug) |
300 | 0 | printf("dns[%d]: nobody needs me... =(\n", getpid()); |
301 | 0 | break; |
302 | 0 | } |
303 | 0 | rc = read(child_in, &dns_params, sizeof(dns_params_t)); |
304 | 0 | if (rc < 0) { |
305 | 0 | fprintf(stderr, "dns[%d]: Error: Could not read dns_params: " |
306 | 0 | "%s\n", getpid(), strerror(errno)); |
307 | 0 | break; |
308 | 0 | } |
309 | 0 | if (rc == 0) { |
310 | 0 | if (show_debug) |
311 | 0 | printf("dns[%d]: Oops, father has gone, wait for me, wait...!\n", getpid()); |
312 | 0 | _exit(0); |
313 | 0 | } |
314 | 0 | if (dns_params.hostname[0] == '\0') { |
315 | 0 | fprintf(stderr, "dns[%d]: Error: Parent requested resolution " |
316 | 0 | "of an empty hostname (port = %d)!!!\n", getpid(), |
317 | 0 | dns_params.port); |
318 | 0 | _exit(1); |
319 | 0 | } |
320 | | |
321 | | #ifdef USE_IDN |
322 | | if (!dns_str_is_ascii(dns_params.hostname)) { |
323 | | rc = purple_network_convert_idn_to_ascii(dns_params.hostname, &hostname); |
324 | | if (rc != 0) { |
325 | | write_to_parent(child_out, &rc, sizeof(rc)); |
326 | | if (show_debug) |
327 | | fprintf(stderr, "dns[%d] Error: IDN conversion returned " |
328 | | "%d\n", getpid(), rc); |
329 | | dns_params.hostname[0] = '\0'; |
330 | | break; |
331 | | } |
332 | | } else /* intentional to execute the g_strdup */ |
333 | | #endif |
334 | 0 | hostname = g_strdup(dns_params.hostname); |
335 | | |
336 | | /* We have the hostname and port, now resolve the IP */ |
337 | |
|
338 | 0 | #ifdef HAVE_GETADDRINFO |
339 | 0 | g_snprintf(servname, sizeof(servname), "%d", dns_params.port); |
340 | 0 | memset(&hints, 0, sizeof(hints)); |
341 | | |
342 | | /* This is only used to convert a service |
343 | | * name to a port number. As we know we are |
344 | | * passing a number already, we know this |
345 | | * value will not be really used by the C |
346 | | * library. |
347 | | */ |
348 | 0 | hints.ai_socktype = SOCK_STREAM; |
349 | 0 | #ifdef AI_ADDRCONFIG |
350 | 0 | hints.ai_flags |= AI_ADDRCONFIG; |
351 | 0 | #endif /* AI_ADDRCONFIG */ |
352 | 0 | rc = getaddrinfo(hostname, servname, &hints, &res); |
353 | 0 | write_to_parent(child_out, &rc, sizeof(rc)); |
354 | 0 | if (rc != 0) { |
355 | 0 | if (show_debug) |
356 | 0 | printf("dns[%d] Error: getaddrinfo returned %d\n", |
357 | 0 | getpid(), rc); |
358 | 0 | dns_params.hostname[0] = '\0'; |
359 | 0 | g_free(hostname); |
360 | 0 | hostname = NULL; |
361 | 0 | break; |
362 | 0 | } |
363 | 0 | tmp = res; |
364 | 0 | while (res) { |
365 | 0 | size_t ai_addrlen = res->ai_addrlen; |
366 | 0 | write_to_parent(child_out, &ai_addrlen, sizeof(ai_addrlen)); |
367 | 0 | write_to_parent(child_out, res->ai_addr, res->ai_addrlen); |
368 | 0 | res = res->ai_next; |
369 | 0 | } |
370 | 0 | freeaddrinfo(tmp); |
371 | | #else |
372 | | struct hostent *hp; |
373 | | if (!(hp = gethostbyname(hostname))) { |
374 | | write_to_parent(child_out, &h_errno, sizeof(int)); |
375 | | close(child_out); |
376 | | if (show_debug) |
377 | | printf("DNS Error: %d\n", h_errno); |
378 | | _exit(0); |
379 | | } |
380 | | memset(&sin, 0, sizeof(struct sockaddr_in)); |
381 | | memcpy(&sin.sin_addr.s_addr, hp->h_addr, hp->h_length); |
382 | | sin.sin_family = hp->h_addrtype; |
383 | | |
384 | | sin.sin_port = htons(dns_params.port); |
385 | | rc = 0; |
386 | | write_to_parent(child_out, &rc, sizeof(rc)); |
387 | | write_to_parent(child_out, &addrlen, sizeof(addrlen)); |
388 | | write_to_parent(child_out, &sin, addrlen); |
389 | | #endif |
390 | 0 | write_to_parent(child_out, &zero, sizeof(zero)); |
391 | 0 | dns_params.hostname[0] = '\0'; |
392 | |
|
393 | 0 | g_free(hostname); |
394 | 0 | hostname = NULL; |
395 | 0 | } |
396 | | |
397 | 0 | close(child_out); |
398 | 0 | close(child_in); |
399 | |
|
400 | 0 | _exit(0); |
401 | 0 | } |
402 | | /* |
403 | | * End the DNS resolver child process functions. |
404 | | */ |
405 | | |
406 | | /* |
407 | | * Begin the functions for dealing with the DNS child processes. |
408 | | */ |
409 | | static void |
410 | | cope_with_gdb_brokenness(void) |
411 | 0 | { |
412 | 0 | #ifdef __linux__ |
413 | 0 | static gboolean already_done = FALSE; |
414 | 0 | char s[256], e[512]; |
415 | 0 | int n; |
416 | 0 | pid_t ppid; |
417 | |
|
418 | 0 | if(already_done) |
419 | 0 | return; |
420 | 0 | already_done = TRUE; |
421 | 0 | ppid = getppid(); |
422 | 0 | g_snprintf(s, sizeof(s), "/proc/%d/exe", ppid); |
423 | 0 | n = readlink(s, e, sizeof(e)); |
424 | 0 | if(n < 0) |
425 | 0 | return; |
426 | | |
427 | 0 | e[MIN((gsize)n,sizeof(e)-1)] = '\0'; |
428 | |
|
429 | 0 | if(strstr(e,"gdb")) { |
430 | 0 | purple_debug_info("dns", |
431 | 0 | "Debugger detected, performing useless query...\n"); |
432 | 0 | gethostbyname("x.x.x.x.x"); |
433 | 0 | } |
434 | 0 | #endif |
435 | 0 | } |
436 | | |
437 | | static void |
438 | | purple_dnsquery_resolver_destroy(PurpleDnsQueryResolverProcess *resolver) |
439 | 0 | { |
440 | 0 | g_return_if_fail(resolver != NULL); |
441 | | |
442 | | /* Keep this before the kill() call below. */ |
443 | 0 | if (resolver->inpa != 0) { |
444 | 0 | purple_input_remove(resolver->inpa); |
445 | 0 | resolver->inpa = 0; |
446 | 0 | } |
447 | | |
448 | | /* |
449 | | * We might as well attempt to kill our child process. It really |
450 | | * doesn't matter if this fails, because children will expire on |
451 | | * their own after a few seconds. |
452 | | */ |
453 | 0 | if (resolver->dns_pid > 0) |
454 | 0 | kill(resolver->dns_pid, SIGKILL); |
455 | |
|
456 | 0 | close(resolver->fd_in); |
457 | 0 | close(resolver->fd_out); |
458 | |
|
459 | 0 | g_free(resolver); |
460 | |
|
461 | 0 | number_of_dns_children--; |
462 | 0 | } |
463 | | |
464 | | static PurpleDnsQueryResolverProcess * |
465 | | purple_dnsquery_resolver_new(gboolean show_debug) |
466 | 0 | { |
467 | 0 | PurpleDnsQueryResolverProcess *resolver; |
468 | 0 | int child_out[2], child_in[2]; |
469 | | |
470 | | /* Create pipes for communicating with the child process */ |
471 | 0 | if (pipe(child_out) || pipe(child_in)) { |
472 | 0 | purple_debug_error("dns", |
473 | 0 | "Could not create pipes: %s\n", g_strerror(errno)); |
474 | 0 | return NULL; |
475 | 0 | } |
476 | | |
477 | 0 | resolver = g_new0(PurpleDnsQueryResolverProcess, 1); |
478 | |
|
479 | 0 | cope_with_gdb_brokenness(); |
480 | | |
481 | | /* "Go fork and multiply." --Tommy Caldwell (Emily's dad, not the climber) */ |
482 | 0 | resolver->dns_pid = fork(); |
483 | | |
484 | | /* If we are the child process... */ |
485 | 0 | if (resolver->dns_pid == 0) { |
486 | | /* We should not access the parent's side of the pipes, so close them */ |
487 | 0 | close(child_out[0]); |
488 | 0 | close(child_in[1]); |
489 | |
|
490 | 0 | purple_dnsquery_resolver_run(child_out[1], child_in[0], show_debug); |
491 | | /* The thread calls _exit() rather than returning, so we never get here */ |
492 | 0 | } |
493 | | |
494 | | /* We should not access the child's side of the pipes, so close them */ |
495 | 0 | close(child_out[1]); |
496 | 0 | close(child_in[0]); |
497 | 0 | if (resolver->dns_pid == -1) { |
498 | 0 | purple_debug_error("dns", |
499 | 0 | "Could not create child process for DNS: %s\n", |
500 | 0 | g_strerror(errno)); |
501 | 0 | purple_dnsquery_resolver_destroy(resolver); |
502 | 0 | return NULL; |
503 | 0 | } |
504 | | |
505 | 0 | resolver->fd_out = child_out[0]; |
506 | 0 | resolver->fd_in = child_in[1]; |
507 | 0 | number_of_dns_children++; |
508 | 0 | purple_debug_info("dns", |
509 | 0 | "Created new DNS child %d, there are now %d children.\n", |
510 | 0 | resolver->dns_pid, number_of_dns_children); |
511 | |
|
512 | 0 | return resolver; |
513 | 0 | } |
514 | | |
515 | | /** |
516 | | * @return TRUE if the request was sent succesfully. FALSE |
517 | | * if the request could not be sent. This isn't |
518 | | * necessarily an error. If the child has expired, |
519 | | * for example, we won't be able to send the message. |
520 | | */ |
521 | | static gboolean |
522 | | send_dns_request_to_child(PurpleDnsQueryData *query_data, |
523 | | PurpleDnsQueryResolverProcess *resolver) |
524 | 0 | { |
525 | 0 | pid_t pid; |
526 | 0 | dns_params_t dns_params; |
527 | 0 | ssize_t rc; |
528 | | |
529 | | /* This waitpid might return the child's PID if it has recently |
530 | | * exited, or it might return an error if it exited "long |
531 | | * enough" ago that it has already been reaped; in either |
532 | | * instance, we can't use it. */ |
533 | 0 | pid = waitpid(resolver->dns_pid, NULL, WNOHANG); |
534 | 0 | if (pid > 0) { |
535 | 0 | purple_debug_warning("dns", "DNS child %d no longer exists\n", |
536 | 0 | resolver->dns_pid); |
537 | 0 | purple_dnsquery_resolver_destroy(resolver); |
538 | 0 | return FALSE; |
539 | 0 | } else if (pid < 0) { |
540 | 0 | purple_debug_warning("dns", "Wait for DNS child %d failed: %s\n", |
541 | 0 | resolver->dns_pid, g_strerror(errno)); |
542 | 0 | purple_dnsquery_resolver_destroy(resolver); |
543 | 0 | return FALSE; |
544 | 0 | } |
545 | | |
546 | | /* Copy the hostname and port into a single data structure */ |
547 | 0 | strncpy(dns_params.hostname, query_data->hostname, sizeof(dns_params.hostname) - 1); |
548 | 0 | dns_params.hostname[sizeof(dns_params.hostname) - 1] = '\0'; |
549 | 0 | dns_params.port = query_data->port; |
550 | | |
551 | | /* Send the data structure to the child */ |
552 | 0 | rc = write(resolver->fd_in, &dns_params, sizeof(dns_params)); |
553 | 0 | if (rc < 0) { |
554 | 0 | purple_debug_error("dns", "Unable to write to DNS child %d: %s\n", |
555 | 0 | resolver->dns_pid, g_strerror(errno)); |
556 | 0 | purple_dnsquery_resolver_destroy(resolver); |
557 | 0 | return FALSE; |
558 | 0 | } |
559 | 0 | if ((gsize)rc < sizeof(dns_params)) { |
560 | 0 | purple_debug_error("dns", "Tried to write %" G_GSSIZE_FORMAT |
561 | 0 | " bytes to child but only wrote %" G_GSSIZE_FORMAT "\n", |
562 | 0 | sizeof(dns_params), rc); |
563 | 0 | purple_dnsquery_resolver_destroy(resolver); |
564 | 0 | return FALSE; |
565 | 0 | } |
566 | | |
567 | 0 | purple_debug_info("dns", |
568 | 0 | "Successfully sent DNS request to child %d\n", |
569 | 0 | resolver->dns_pid); |
570 | |
|
571 | 0 | query_data->resolver = resolver; |
572 | |
|
573 | 0 | return TRUE; |
574 | 0 | } |
575 | | |
576 | | static void host_resolved(gpointer data, gint source, PurpleInputCondition cond); |
577 | | |
578 | | static void |
579 | | handle_next_queued_request(void) |
580 | 0 | { |
581 | 0 | PurpleDnsQueryData *query_data; |
582 | 0 | PurpleDnsQueryResolverProcess *resolver; |
583 | |
|
584 | 0 | if (queued_requests == NULL) |
585 | | /* No more DNS queries, yay! */ |
586 | 0 | return; |
587 | | |
588 | 0 | query_data = queued_requests->data; |
589 | 0 | queued_requests = g_slist_delete_link(queued_requests, queued_requests); |
590 | | |
591 | | /* |
592 | | * If we have any children, attempt to have them perform the DNS |
593 | | * query. If we're able to send the query then resolver will be |
594 | | * set to the PurpleDnsQueryResolverProcess. Otherwise, resolver |
595 | | * will be NULL and we'll need to create a new DNS request child. |
596 | | */ |
597 | 0 | while (free_dns_children != NULL) |
598 | 0 | { |
599 | 0 | resolver = free_dns_children->data; |
600 | 0 | free_dns_children = g_slist_remove(free_dns_children, resolver); |
601 | |
|
602 | 0 | if (send_dns_request_to_child(query_data, resolver)) |
603 | | /* We found an acceptable child, yay */ |
604 | 0 | break; |
605 | 0 | } |
606 | | |
607 | | /* We need to create a new DNS request child */ |
608 | 0 | if (query_data->resolver == NULL) |
609 | 0 | { |
610 | 0 | if (number_of_dns_children >= MAX_DNS_CHILDREN) |
611 | 0 | { |
612 | | /* Apparently all our children are busy */ |
613 | 0 | queued_requests = g_slist_prepend(queued_requests, query_data); |
614 | 0 | return; |
615 | 0 | } |
616 | | |
617 | 0 | resolver = purple_dnsquery_resolver_new(purple_debug_is_enabled()); |
618 | 0 | if (resolver == NULL) |
619 | 0 | { |
620 | 0 | purple_dnsquery_failed(query_data, _("Unable to create new resolver process\n")); |
621 | 0 | return; |
622 | 0 | } |
623 | 0 | if (!send_dns_request_to_child(query_data, resolver)) |
624 | 0 | { |
625 | 0 | purple_dnsquery_failed(query_data, _("Unable to send request to resolver process\n")); |
626 | 0 | return; |
627 | 0 | } |
628 | 0 | } |
629 | | |
630 | 0 | query_data->resolver->inpa = purple_input_add(query_data->resolver->fd_out, |
631 | 0 | PURPLE_INPUT_READ, host_resolved, query_data); |
632 | 0 | } |
633 | | |
634 | | /* |
635 | | * End the functions for dealing with the DNS child processes. |
636 | | */ |
637 | | |
638 | | static void |
639 | | host_resolved(gpointer data, gint source, PurpleInputCondition cond) |
640 | 0 | { |
641 | 0 | PurpleDnsQueryData *query_data; |
642 | 0 | int rc, err; |
643 | 0 | GSList *hosts = NULL; |
644 | 0 | struct sockaddr *addr = NULL; |
645 | 0 | size_t addrlen; |
646 | 0 | char message[1024]; |
647 | |
|
648 | 0 | query_data = data; |
649 | |
|
650 | 0 | purple_debug_info("dns", "Got response for '%s'\n", query_data->hostname); |
651 | 0 | purple_input_remove(query_data->resolver->inpa); |
652 | 0 | query_data->resolver->inpa = 0; |
653 | |
|
654 | 0 | rc = read(query_data->resolver->fd_out, &err, sizeof(err)); |
655 | 0 | if ((rc == 4) && (err != 0)) |
656 | 0 | { |
657 | 0 | #ifdef HAVE_GETADDRINFO |
658 | 0 | g_snprintf(message, sizeof(message), _("Error resolving %s:\n%s"), |
659 | 0 | query_data->hostname, purple_gai_strerror(err)); |
660 | | #else |
661 | | g_snprintf(message, sizeof(message), _("Error resolving %s: %d"), |
662 | | query_data->hostname, err); |
663 | | #endif |
664 | | /* Re-read resolv.conf and friends in case DNS servers have changed */ |
665 | 0 | res_init(); |
666 | |
|
667 | 0 | purple_dnsquery_failed(query_data, message); |
668 | 0 | } else if (rc > 0) { |
669 | | /* Success! */ |
670 | 0 | while (rc > 0) { |
671 | 0 | rc = read(query_data->resolver->fd_out, &addrlen, sizeof(addrlen)); |
672 | 0 | if (rc > 0 && addrlen > 0 && addrlen < MAX_ADDR_RESPONSE_LEN) { |
673 | 0 | addr = g_malloc(addrlen); |
674 | 0 | rc = read(query_data->resolver->fd_out, addr, addrlen); |
675 | 0 | hosts = g_slist_append(hosts, GINT_TO_POINTER(addrlen)); |
676 | 0 | hosts = g_slist_append(hosts, addr); |
677 | 0 | } else { |
678 | 0 | break; |
679 | 0 | } |
680 | 0 | } |
681 | | /* wait4(resolver->dns_pid, NULL, WNOHANG, NULL); */ |
682 | 0 | purple_dnsquery_resolved(query_data, hosts); |
683 | |
|
684 | 0 | } else if (rc == -1) { |
685 | 0 | g_snprintf(message, sizeof(message), _("Error reading from resolver process:\n%s"), g_strerror(errno)); |
686 | 0 | purple_dnsquery_failed(query_data, message); |
687 | |
|
688 | 0 | } else if (rc == 0) { |
689 | 0 | g_snprintf(message, sizeof(message), _("Resolver process exited without answering our request")); |
690 | 0 | purple_dnsquery_failed(query_data, message); |
691 | 0 | } |
692 | |
|
693 | 0 | handle_next_queued_request(); |
694 | 0 | } |
695 | | |
696 | | static void |
697 | | resolve_host(PurpleDnsQueryData *query_data) |
698 | 0 | { |
699 | 0 | queued_requests = g_slist_append(queued_requests, query_data); |
700 | |
|
701 | 0 | handle_next_queued_request(); |
702 | 0 | } |
703 | | |
704 | | #elif defined _WIN32 /* end PURPLE_DNSQUERY_USE_FORK */ |
705 | | |
706 | | /* |
707 | | * Windows! |
708 | | */ |
709 | | |
710 | | static gboolean |
711 | | dns_main_thread_cb(gpointer data) |
712 | | { |
713 | | PurpleDnsQueryData *query_data = data; |
714 | | |
715 | | /* We're done, so purple_dnsquery_destroy() shouldn't think it is canceling an in-progress lookup */ |
716 | | query_data->resolver = NULL; |
717 | | |
718 | | if (query_data->error_message != NULL) |
719 | | purple_dnsquery_failed(query_data, query_data->error_message); |
720 | | else |
721 | | { |
722 | | GSList *hosts; |
723 | | |
724 | | /* We don't want purple_dns_query_resolved() to free(hosts) */ |
725 | | hosts = query_data->hosts; |
726 | | query_data->hosts = NULL; |
727 | | purple_dnsquery_resolved(query_data, hosts); |
728 | | } |
729 | | |
730 | | return FALSE; |
731 | | } |
732 | | |
733 | | static gpointer |
734 | | dns_thread(gpointer data) |
735 | | { |
736 | | PurpleDnsQueryData *query_data; |
737 | | #ifdef HAVE_GETADDRINFO |
738 | | int rc; |
739 | | struct addrinfo hints, *res, *tmp; |
740 | | char servname[20]; |
741 | | #else |
742 | | struct sockaddr_in sin; |
743 | | struct hostent *hp; |
744 | | #endif |
745 | | char *hostname; |
746 | | |
747 | | query_data = data; |
748 | | |
749 | | #ifdef USE_IDN |
750 | | if (!dns_str_is_ascii(query_data->hostname)) { |
751 | | rc = purple_network_convert_idn_to_ascii(query_data->hostname, &hostname); |
752 | | if (rc != 0) { |
753 | | query_data->error_message = g_strdup_printf(_("Error converting %s " |
754 | | "to punycode: %d"), query_data->hostname, rc); |
755 | | /* back to main thread */ |
756 | | purple_timeout_add(0, dns_main_thread_cb, query_data); |
757 | | return 0; |
758 | | } |
759 | | } else /* intentional fallthru */ |
760 | | #endif |
761 | | hostname = g_strdup(query_data->hostname); |
762 | | |
763 | | #ifdef HAVE_GETADDRINFO |
764 | | g_snprintf(servname, sizeof(servname), "%d", query_data->port); |
765 | | memset(&hints,0,sizeof(hints)); |
766 | | |
767 | | /* |
768 | | * This is only used to convert a service |
769 | | * name to a port number. As we know we are |
770 | | * passing a number already, we know this |
771 | | * value will not be really used by the C |
772 | | * library. |
773 | | */ |
774 | | hints.ai_socktype = SOCK_STREAM; |
775 | | #ifdef AI_ADDRCONFIG |
776 | | hints.ai_flags |= AI_ADDRCONFIG; |
777 | | #endif /* AI_ADDRCONFIG */ |
778 | | if ((rc = getaddrinfo(hostname, servname, &hints, &res)) == 0) { |
779 | | tmp = res; |
780 | | while(res) { |
781 | | query_data->hosts = g_slist_append(query_data->hosts, |
782 | | GSIZE_TO_POINTER(res->ai_addrlen)); |
783 | | query_data->hosts = g_slist_append(query_data->hosts, |
784 | | g_memdup2(res->ai_addr, res->ai_addrlen)); |
785 | | res = res->ai_next; |
786 | | } |
787 | | freeaddrinfo(tmp); |
788 | | } else { |
789 | | query_data->error_message = g_strdup_printf(_("Error resolving %s:\n%s"), query_data->hostname, purple_gai_strerror(rc)); |
790 | | } |
791 | | #else |
792 | | if ((hp = gethostbyname(hostname))) { |
793 | | memset(&sin, 0, sizeof(struct sockaddr_in)); |
794 | | memcpy(&sin.sin_addr.s_addr, hp->h_addr, hp->h_length); |
795 | | sin.sin_family = hp->h_addrtype; |
796 | | sin.sin_port = htons(query_data->port); |
797 | | |
798 | | query_data->hosts = g_slist_append(query_data->hosts, |
799 | | GSIZE_TO_POINTER(sizeof(sin))); |
800 | | query_data->hosts = g_slist_append(query_data->hosts, |
801 | | g_memdup2(&sin, sizeof(sin))); |
802 | | } else { |
803 | | query_data->error_message = g_strdup_printf(_("Error resolving %s: %d"), query_data->hostname, h_errno); |
804 | | } |
805 | | #endif |
806 | | g_free(hostname); |
807 | | |
808 | | /* back to main thread */ |
809 | | purple_timeout_add(0, dns_main_thread_cb, query_data); |
810 | | |
811 | | return 0; |
812 | | } |
813 | | |
814 | | static void |
815 | | resolve_host(PurpleDnsQueryData *query_data) |
816 | | { |
817 | | GError *err = NULL; |
818 | | |
819 | | /* |
820 | | * Spin off a separate thread to perform the DNS lookup so |
821 | | * that we don't block the UI. |
822 | | */ |
823 | | query_data->resolver = g_thread_create(dns_thread, |
824 | | query_data, FALSE, &err); |
825 | | if (query_data->resolver == NULL) |
826 | | { |
827 | | char message[1024]; |
828 | | g_snprintf(message, sizeof(message), _("Thread creation failure: %s"), |
829 | | (err && err->message) ? err->message : _("Unknown reason")); |
830 | | g_error_free(err); |
831 | | purple_dnsquery_failed(query_data, message); |
832 | | } |
833 | | } |
834 | | |
835 | | #else /* not PURPLE_DNSQUERY_USE_FORK or _WIN32 */ |
836 | | |
837 | | /* |
838 | | * We weren't able to do anything fancier above, so use the |
839 | | * fail-safe name resolution code, which is blocking. |
840 | | */ |
841 | | |
842 | | static void |
843 | | resolve_host(PurpleDnsQueryData *query_data) |
844 | | { |
845 | | struct sockaddr_in sin; |
846 | | GSList *hosts = NULL; |
847 | | struct hostent *hp; |
848 | | gchar *hostname; |
849 | | #ifdef USE_IDN |
850 | | if (!dns_str_is_ascii(query_data->hostname)) { |
851 | | int ret = purple_network_convert_idn_to_ascii(query_data->hostname, |
852 | | &hostname); |
853 | | if (ret != 0) { |
854 | | char message[1024]; |
855 | | g_snprintf(message, sizeof(message), _("Error resolving %s: %d"), |
856 | | query_data->hostname, ret); |
857 | | purple_dnsquery_failed(query_data, message); |
858 | | return; |
859 | | } |
860 | | } else /* fallthrough is intentional to the g_strdup */ |
861 | | #endif |
862 | | hostname = g_strdup(query_data->hostname); |
863 | | |
864 | | if(!(hp = gethostbyname(hostname))) { |
865 | | char message[1024]; |
866 | | g_snprintf(message, sizeof(message), _("Error resolving %s: %d"), |
867 | | query_data->hostname, h_errno); |
868 | | purple_dnsquery_failed(query_data, message); |
869 | | g_free(hostname); |
870 | | return; |
871 | | } |
872 | | memset(&sin, 0, sizeof(struct sockaddr_in)); |
873 | | memcpy(&sin.sin_addr.s_addr, hp->h_addr, hp->h_length); |
874 | | sin.sin_family = hp->h_addrtype; |
875 | | g_free(hostname); |
876 | | sin.sin_port = htons(query_data->port); |
877 | | |
878 | | hosts = g_slist_append(hosts, GINT_TO_POINTER(sizeof(sin))); |
879 | | hosts = g_slist_append(hosts, g_memdup2(&sin, sizeof(sin))); |
880 | | |
881 | | purple_dnsquery_resolved(query_data, hosts); |
882 | | } |
883 | | |
884 | | #endif /* not PURPLE_DNSQUERY_USE_FORK or _WIN32 */ |
885 | | |
886 | | static gboolean |
887 | | initiate_resolving(gpointer data) |
888 | 0 | { |
889 | 0 | PurpleDnsQueryData *query_data; |
890 | 0 | PurpleProxyType proxy_type; |
891 | |
|
892 | 0 | query_data = data; |
893 | 0 | query_data->timeout = 0; |
894 | |
|
895 | 0 | if (resolve_ip(query_data)) |
896 | | /* resolve_ip calls purple_dnsquery_resolved */ |
897 | 0 | return FALSE; |
898 | | |
899 | 0 | proxy_type = purple_proxy_info_get_type( |
900 | 0 | purple_proxy_get_setup(query_data->account)); |
901 | 0 | if (proxy_type == PURPLE_PROXY_TOR) { |
902 | 0 | purple_dnsquery_failed(query_data, |
903 | 0 | _("Aborting DNS lookup in Tor Proxy mode.")); |
904 | 0 | return FALSE; |
905 | 0 | } |
906 | | |
907 | 0 | if (purple_dnsquery_ui_resolve(query_data)) |
908 | | /* The UI is handling the resolve; we're done */ |
909 | 0 | return FALSE; |
910 | | |
911 | 0 | resolve_host(query_data); |
912 | |
|
913 | 0 | return FALSE; |
914 | 0 | } |
915 | | |
916 | | PurpleDnsQueryData * |
917 | | purple_dnsquery_a_account(PurpleAccount *account, const char *hostname, int port, |
918 | | PurpleDnsQueryConnectFunction callback, gpointer data) |
919 | 0 | { |
920 | 0 | PurpleDnsQueryData *query_data; |
921 | |
|
922 | 0 | g_return_val_if_fail(hostname != NULL, NULL); |
923 | 0 | g_return_val_if_fail(port != 0, NULL); |
924 | 0 | g_return_val_if_fail(callback != NULL, NULL); |
925 | | |
926 | 0 | purple_debug_info("dnsquery", "Performing DNS lookup for %s\n", hostname); |
927 | |
|
928 | 0 | query_data = g_new0(PurpleDnsQueryData, 1); |
929 | 0 | query_data->hostname = g_strdup(hostname); |
930 | 0 | g_strstrip(query_data->hostname); |
931 | 0 | query_data->port = port; |
932 | 0 | query_data->callback = callback; |
933 | 0 | query_data->data = data; |
934 | 0 | query_data->account = account; |
935 | |
|
936 | 0 | if (*query_data->hostname == '\0') |
937 | 0 | { |
938 | 0 | purple_dnsquery_destroy(query_data); |
939 | 0 | g_return_val_if_reached(NULL); |
940 | 0 | } |
941 | | |
942 | 0 | query_data->timeout = purple_timeout_add(0, initiate_resolving, query_data); |
943 | |
|
944 | 0 | return query_data; |
945 | 0 | } |
946 | | |
947 | | PurpleDnsQueryData * |
948 | | purple_dnsquery_a(const char *hostname, int port, |
949 | | PurpleDnsQueryConnectFunction callback, gpointer data) |
950 | 0 | { |
951 | 0 | return purple_dnsquery_a_account(NULL, hostname, port, callback, data); |
952 | 0 | } |
953 | | |
954 | | void |
955 | | purple_dnsquery_destroy(PurpleDnsQueryData *query_data) |
956 | 0 | { |
957 | 0 | PurpleDnsQueryUiOps *ops = purple_dnsquery_get_ui_ops(); |
958 | |
|
959 | 0 | if (ops && ops->destroy) |
960 | 0 | ops->destroy(query_data); |
961 | |
|
962 | 0 | #if defined(PURPLE_DNSQUERY_USE_FORK) |
963 | 0 | queued_requests = g_slist_remove(queued_requests, query_data); |
964 | |
|
965 | 0 | if (query_data->resolver != NULL) |
966 | | /* |
967 | | * This is only non-NULL when we're cancelling an in-progress |
968 | | * query. Ideally we would tell our resolver child to stop |
969 | | * resolving shit and then we would add it back to the |
970 | | * free_dns_children linked list. However, it's hard to tell |
971 | | * children stuff, they just don't listen. So we'll just |
972 | | * kill the process and allow a new child to be started if we |
973 | | * have more stuff to resolve. |
974 | | */ |
975 | 0 | purple_dnsquery_resolver_destroy(query_data->resolver); |
976 | | #elif defined _WIN32 /* end PURPLE_DNSQUERY_USE_FORK */ |
977 | | if (query_data->resolver != NULL) |
978 | | { |
979 | | /* |
980 | | * It's not really possible to kill a thread. So instead we |
981 | | * just set the callback to NULL and let the DNS lookup |
982 | | * finish. |
983 | | */ |
984 | | query_data->callback = NULL; |
985 | | return; |
986 | | } |
987 | | |
988 | | while (query_data->hosts != NULL) |
989 | | { |
990 | | /* Discard the length... */ |
991 | | query_data->hosts = g_slist_remove(query_data->hosts, query_data->hosts->data); |
992 | | /* Free the address... */ |
993 | | g_free(query_data->hosts->data); |
994 | | query_data->hosts = g_slist_remove(query_data->hosts, query_data->hosts->data); |
995 | | } |
996 | | g_free(query_data->error_message); |
997 | | #endif /* end _WIN32 */ |
998 | |
|
999 | 0 | if (query_data->timeout > 0) |
1000 | 0 | purple_timeout_remove(query_data->timeout); |
1001 | |
|
1002 | 0 | g_free(query_data->hostname); |
1003 | 0 | g_free(query_data); |
1004 | 0 | } |
1005 | | |
1006 | | char * |
1007 | | purple_dnsquery_get_host(PurpleDnsQueryData *query_data) |
1008 | 0 | { |
1009 | 0 | g_return_val_if_fail(query_data != NULL, NULL); |
1010 | | |
1011 | 0 | return query_data->hostname; |
1012 | 0 | } |
1013 | | |
1014 | | unsigned short |
1015 | | purple_dnsquery_get_port(PurpleDnsQueryData *query_data) |
1016 | 0 | { |
1017 | 0 | g_return_val_if_fail(query_data != NULL, 0); |
1018 | | |
1019 | 0 | return query_data->port; |
1020 | 0 | } |
1021 | | |
1022 | | void |
1023 | | purple_dnsquery_set_ui_ops(PurpleDnsQueryUiOps *ops) |
1024 | 0 | { |
1025 | 0 | dns_query_ui_ops = ops; |
1026 | 0 | } |
1027 | | |
1028 | | PurpleDnsQueryUiOps * |
1029 | | purple_dnsquery_get_ui_ops(void) |
1030 | 0 | { |
1031 | | /* It is perfectly acceptable for dns_query_ui_ops to be NULL; this just |
1032 | | * means that the default platform-specific implementation will be used. |
1033 | | */ |
1034 | 0 | return dns_query_ui_ops; |
1035 | 0 | } |
1036 | | |
1037 | | void |
1038 | | purple_dnsquery_init(void) |
1039 | 0 | { |
1040 | 0 | } |
1041 | | |
1042 | | void |
1043 | | purple_dnsquery_uninit(void) |
1044 | 0 | { |
1045 | 0 | #if defined(PURPLE_DNSQUERY_USE_FORK) |
1046 | 0 | while (free_dns_children != NULL) |
1047 | 0 | { |
1048 | 0 | purple_dnsquery_resolver_destroy(free_dns_children->data); |
1049 | 0 | free_dns_children = g_slist_remove(free_dns_children, free_dns_children->data); |
1050 | 0 | } |
1051 | 0 | #endif /* end PURPLE_DNSQUERY_USE_FORK */ |
1052 | 0 | } |