Mercurial > pidgin
comparison libpurple/dnsquery.c @ 15373:5fe8042783c1
Rename gtk/ and libgaim/ to pidgin/ and libpurple/
author | Sean Egan <seanegan@gmail.com> |
---|---|
date | Sat, 20 Jan 2007 02:32:10 +0000 |
parents | |
children | 61b42cf81aa4 |
comparison
equal
deleted
inserted
replaced
15372:f79e0f4df793 | 15373:5fe8042783c1 |
---|---|
1 /** | |
2 * @file dnsquery.c DNS query API | |
3 * @ingroup core | |
4 * | |
5 * gaim | |
6 * | |
7 * Gaim is the legal property of its developers, whose names are too numerous | |
8 * to list here. Please refer to the COPYRIGHT file distributed with this | |
9 * source distribution. | |
10 * | |
11 * This program is free software; you can redistribute it and/or modify | |
12 * it under the terms of the GNU General Public License as published by | |
13 * the Free Software Foundation; either version 2 of the License, or | |
14 * (at your option) any later version. | |
15 * | |
16 * This program is distributed in the hope that it will be useful, | |
17 * but WITHOUT ANY WARRANTY; without even the implied warranty of | |
18 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |
19 * GNU General Public License for more details. | |
20 * | |
21 * You should have received a copy of the GNU General Public License | |
22 * along with this program; if not, write to the Free Software | |
23 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA | |
24 * | |
25 */ | |
26 | |
27 #include "internal.h" | |
28 #include "debug.h" | |
29 #include "dnsquery.h" | |
30 #include "notify.h" | |
31 #include "prefs.h" | |
32 #include "util.h" | |
33 | |
34 /************************************************************************** | |
35 * DNS query API | |
36 **************************************************************************/ | |
37 | |
38 typedef struct _GaimDnsQueryResolverProcess GaimDnsQueryResolverProcess; | |
39 | |
40 struct _GaimDnsQueryData { | |
41 char *hostname; | |
42 int port; | |
43 GaimDnsQueryConnectFunction callback; | |
44 gpointer data; | |
45 guint timeout; | |
46 | |
47 #if defined(__unix__) || defined(__APPLE__) | |
48 GaimDnsQueryResolverProcess *resolver; | |
49 #elif defined _WIN32 /* end __unix__ || __APPLE__ */ | |
50 GThread *resolver; | |
51 GSList *hosts; | |
52 gchar *error_message; | |
53 #endif | |
54 }; | |
55 | |
56 #if defined(__unix__) || defined(__APPLE__) | |
57 | |
58 #define MAX_DNS_CHILDREN 4 | |
59 | |
60 /* | |
61 * This structure keeps a reference to a child resolver process. | |
62 */ | |
63 struct _GaimDnsQueryResolverProcess { | |
64 guint inpa; | |
65 int fd_in, fd_out; | |
66 pid_t dns_pid; | |
67 }; | |
68 | |
69 static GSList *free_dns_children = NULL; | |
70 static GSList *queued_requests = NULL; | |
71 | |
72 static int number_of_dns_children = 0; | |
73 | |
74 /* | |
75 * This is a convenience struct used to pass data to | |
76 * the child resolver process. | |
77 */ | |
78 typedef struct { | |
79 char hostname[512]; | |
80 int port; | |
81 } dns_params_t; | |
82 #endif | |
83 | |
84 static void | |
85 gaim_dnsquery_resolved(GaimDnsQueryData *query_data, GSList *hosts) | |
86 { | |
87 gaim_debug_info("dnsquery", "IP resolved for %s\n", query_data->hostname); | |
88 if (query_data->callback != NULL) | |
89 query_data->callback(hosts, query_data->data, NULL); | |
90 else | |
91 { | |
92 /* | |
93 * Callback is a required parameter, but it can get set to | |
94 * NULL if we cancel a thread-based DNS lookup. So we need | |
95 * to free hosts. | |
96 */ | |
97 while (hosts != NULL) | |
98 { | |
99 hosts = g_slist_remove(hosts, hosts->data); | |
100 g_free(hosts->data); | |
101 hosts = g_slist_remove(hosts, hosts->data); | |
102 } | |
103 } | |
104 | |
105 gaim_dnsquery_destroy(query_data); | |
106 } | |
107 | |
108 static void | |
109 gaim_dnsquery_failed(GaimDnsQueryData *query_data, const gchar *error_message) | |
110 { | |
111 gaim_debug_info("dnsquery", "%s\n", error_message); | |
112 if (query_data->callback != NULL) | |
113 query_data->callback(NULL, query_data->data, error_message); | |
114 gaim_dnsquery_destroy(query_data); | |
115 } | |
116 | |
117 #if defined(__unix__) || defined(__APPLE__) | |
118 | |
119 /* | |
120 * Unix! | |
121 */ | |
122 | |
123 /* | |
124 * Begin the DNS resolver child process functions. | |
125 */ | |
126 #ifdef HAVE_SIGNAL_H | |
127 static void | |
128 trap_gdb_bug() | |
129 { | |
130 const char *message = | |
131 "Gaim's DNS child got a SIGTRAP signal.\n" | |
132 "This can be caused by trying to run gaim inside gdb.\n" | |
133 "There is a known gdb bug which prevents this. Supposedly gaim\n" | |
134 "should have detected you were using gdb and used an ugly hack,\n" | |
135 "check cope_with_gdb_brokenness() in dnsquery.c.\n\n" | |
136 "For more info about this bug, see http://sources.redhat.com/ml/gdb/2001-07/msg00349.html\n"; | |
137 fputs("\n* * *\n",stderr); | |
138 fputs(message,stderr); | |
139 fputs("* * *\n\n",stderr); | |
140 execlp("xmessage","xmessage","-center", message, NULL); | |
141 _exit(1); | |
142 } | |
143 #endif | |
144 | |
145 static void | |
146 gaim_dnsquery_resolver_run(int child_out, int child_in, gboolean show_debug) | |
147 { | |
148 dns_params_t dns_params; | |
149 const size_t zero = 0; | |
150 int rc; | |
151 #ifdef HAVE_GETADDRINFO | |
152 struct addrinfo hints, *res, *tmp; | |
153 char servname[20]; | |
154 #else | |
155 struct sockaddr_in sin; | |
156 const size_t addrlen = sizeof(sin); | |
157 #endif | |
158 | |
159 #ifdef HAVE_SIGNAL_H | |
160 signal(SIGHUP, SIG_DFL); | |
161 signal(SIGINT, SIG_DFL); | |
162 signal(SIGQUIT, SIG_DFL); | |
163 signal(SIGCHLD, SIG_DFL); | |
164 signal(SIGTERM, SIG_DFL); | |
165 signal(SIGTRAP, trap_gdb_bug); | |
166 #endif | |
167 | |
168 /* | |
169 * We resolve 1 host name for each iteration of this | |
170 * while loop. | |
171 * | |
172 * The top half of this reads in the hostname and port | |
173 * number from the socket with our parent. The bottom | |
174 * half of this resolves the IP (blocking) and sends | |
175 * the result back to our parent, when finished. | |
176 */ | |
177 while (1) { | |
178 const char ch = 'Y'; | |
179 fd_set fds; | |
180 struct timeval tv = { .tv_sec = 40 , .tv_usec = 0 }; | |
181 FD_ZERO(&fds); | |
182 FD_SET(child_in, &fds); | |
183 rc = select(child_in + 1, &fds, NULL, NULL, &tv); | |
184 if (!rc) { | |
185 if (show_debug) | |
186 printf("dns[%d]: nobody needs me... =(\n", getpid()); | |
187 break; | |
188 } | |
189 rc = read(child_in, &dns_params, sizeof(dns_params_t)); | |
190 if (rc < 0) { | |
191 perror("read()"); | |
192 break; | |
193 } | |
194 if (rc == 0) { | |
195 if (show_debug) | |
196 printf("dns[%d]: Oops, father has gone, wait for me, wait...!\n", getpid()); | |
197 _exit(0); | |
198 } | |
199 if (dns_params.hostname[0] == '\0') { | |
200 printf("dns[%d]: hostname = \"\" (port = %d)!!!\n", getpid(), dns_params.port); | |
201 _exit(1); | |
202 } | |
203 /* Tell our parent that we read the data successfully */ | |
204 write(child_out, &ch, sizeof(ch)); | |
205 | |
206 /* We have the hostname and port, now resolve the IP */ | |
207 | |
208 #ifdef HAVE_GETADDRINFO | |
209 g_snprintf(servname, sizeof(servname), "%d", dns_params.port); | |
210 memset(&hints, 0, sizeof(hints)); | |
211 | |
212 /* This is only used to convert a service | |
213 * name to a port number. As we know we are | |
214 * passing a number already, we know this | |
215 * value will not be really used by the C | |
216 * library. | |
217 */ | |
218 hints.ai_socktype = SOCK_STREAM; | |
219 rc = getaddrinfo(dns_params.hostname, servname, &hints, &res); | |
220 write(child_out, &rc, sizeof(rc)); | |
221 if (rc != 0) { | |
222 close(child_out); | |
223 if (show_debug) | |
224 printf("dns[%d] Error: getaddrinfo returned %d\n", | |
225 getpid(), rc); | |
226 dns_params.hostname[0] = '\0'; | |
227 continue; | |
228 } | |
229 tmp = res; | |
230 while (res) { | |
231 size_t ai_addrlen = res->ai_addrlen; | |
232 write(child_out, &ai_addrlen, sizeof(ai_addrlen)); | |
233 write(child_out, res->ai_addr, res->ai_addrlen); | |
234 res = res->ai_next; | |
235 } | |
236 freeaddrinfo(tmp); | |
237 write(child_out, &zero, sizeof(zero)); | |
238 #else | |
239 if (!inet_aton(dns_params.hostname, &sin.sin_addr)) { | |
240 struct hostent *hp; | |
241 if (!(hp = gethostbyname(dns_params.hostname))) { | |
242 write(child_out, &h_errno, sizeof(int)); | |
243 close(child_out); | |
244 if (show_debug) | |
245 printf("DNS Error: %d\n", h_errno); | |
246 _exit(0); | |
247 } | |
248 memset(&sin, 0, sizeof(struct sockaddr_in)); | |
249 memcpy(&sin.sin_addr.s_addr, hp->h_addr, hp->h_length); | |
250 sin.sin_family = hp->h_addrtype; | |
251 } else | |
252 sin.sin_family = AF_INET; | |
253 | |
254 sin.sin_port = htons(dns_params.port); | |
255 write(child_out, &zero, sizeof(zero)); | |
256 write(child_out, &addrlen, sizeof(addrlen)); | |
257 write(child_out, &sin, addrlen); | |
258 write(child_out, &zero, sizeof(zero)); | |
259 #endif | |
260 dns_params.hostname[0] = '\0'; | |
261 } | |
262 | |
263 close(child_out); | |
264 close(child_in); | |
265 | |
266 _exit(0); | |
267 } | |
268 /* | |
269 * End the DNS resolver child process functions. | |
270 */ | |
271 | |
272 /* | |
273 * Begin the functions for dealing with the DNS child processes. | |
274 */ | |
275 static void | |
276 cope_with_gdb_brokenness() | |
277 { | |
278 #ifdef __linux__ | |
279 static gboolean already_done = FALSE; | |
280 char s[256], e[512]; | |
281 int n; | |
282 pid_t ppid; | |
283 | |
284 if(already_done) | |
285 return; | |
286 already_done = TRUE; | |
287 ppid = getppid(); | |
288 snprintf(s, sizeof(s), "/proc/%d/exe", ppid); | |
289 n = readlink(s, e, sizeof(e)); | |
290 if(n < 0) | |
291 return; | |
292 | |
293 e[MIN(n,sizeof(e)-1)] = '\0'; | |
294 | |
295 if(strstr(e,"gdb")) { | |
296 gaim_debug_info("dns", | |
297 "Debugger detected, performing useless query...\n"); | |
298 gethostbyname("x.x.x.x.x"); | |
299 } | |
300 #endif | |
301 } | |
302 | |
303 static void | |
304 gaim_dnsquery_resolver_destroy(GaimDnsQueryResolverProcess *resolver) | |
305 { | |
306 g_return_if_fail(resolver != NULL); | |
307 | |
308 /* | |
309 * We might as well attempt to kill our child process. It really | |
310 * doesn't matter if this fails, because children will expire on | |
311 * their own after a few seconds. | |
312 */ | |
313 if (resolver->dns_pid > 0) | |
314 kill(resolver->dns_pid, SIGKILL); | |
315 | |
316 if (resolver->inpa != 0) | |
317 gaim_input_remove(resolver->inpa); | |
318 | |
319 close(resolver->fd_in); | |
320 close(resolver->fd_out); | |
321 | |
322 g_free(resolver); | |
323 | |
324 number_of_dns_children--; | |
325 } | |
326 | |
327 static GaimDnsQueryResolverProcess * | |
328 gaim_dnsquery_resolver_new(gboolean show_debug) | |
329 { | |
330 GaimDnsQueryResolverProcess *resolver; | |
331 int child_out[2], child_in[2]; | |
332 | |
333 /* Create pipes for communicating with the child process */ | |
334 if (pipe(child_out) || pipe(child_in)) { | |
335 gaim_debug_error("dns", | |
336 "Could not create pipes: %s\n", strerror(errno)); | |
337 return NULL; | |
338 } | |
339 | |
340 resolver = g_new(GaimDnsQueryResolverProcess, 1); | |
341 resolver->inpa = 0; | |
342 | |
343 cope_with_gdb_brokenness(); | |
344 | |
345 /* "Go fork and multiply." --Tommy Caldwell (Emily's dad, not the climber) */ | |
346 resolver->dns_pid = fork(); | |
347 | |
348 /* If we are the child process... */ | |
349 if (resolver->dns_pid == 0) { | |
350 /* We should not access the parent's side of the pipes, so close them */ | |
351 close(child_out[0]); | |
352 close(child_in[1]); | |
353 | |
354 gaim_dnsquery_resolver_run(child_out[1], child_in[0], show_debug); | |
355 /* The thread calls _exit() rather than returning, so we never get here */ | |
356 } | |
357 | |
358 /* We should not access the child's side of the pipes, so close them */ | |
359 close(child_out[1]); | |
360 close(child_in[0]); | |
361 if (resolver->dns_pid == -1) { | |
362 gaim_debug_error("dns", | |
363 "Could not create child process for DNS: %s\n", | |
364 strerror(errno)); | |
365 gaim_dnsquery_resolver_destroy(resolver); | |
366 return NULL; | |
367 } | |
368 | |
369 resolver->fd_out = child_out[0]; | |
370 resolver->fd_in = child_in[1]; | |
371 number_of_dns_children++; | |
372 gaim_debug_info("dns", | |
373 "Created new DNS child %d, there are now %d children.\n", | |
374 resolver->dns_pid, number_of_dns_children); | |
375 | |
376 return resolver; | |
377 } | |
378 | |
379 /** | |
380 * @return TRUE if the request was sent succesfully. FALSE | |
381 * if the request could not be sent. This isn't | |
382 * necessarily an error. If the child has expired, | |
383 * for example, we won't be able to send the message. | |
384 */ | |
385 static gboolean | |
386 send_dns_request_to_child(GaimDnsQueryData *query_data, | |
387 GaimDnsQueryResolverProcess *resolver) | |
388 { | |
389 pid_t pid; | |
390 dns_params_t dns_params; | |
391 int rc; | |
392 char ch; | |
393 | |
394 /* This waitpid might return the child's PID if it has recently | |
395 * exited, or it might return an error if it exited "long | |
396 * enough" ago that it has already been reaped; in either | |
397 * instance, we can't use it. */ | |
398 pid = waitpid(resolver->dns_pid, NULL, WNOHANG); | |
399 if (pid > 0) { | |
400 gaim_debug_warning("dns", "DNS child %d no longer exists\n", | |
401 resolver->dns_pid); | |
402 gaim_dnsquery_resolver_destroy(resolver); | |
403 return FALSE; | |
404 } else if (pid < 0) { | |
405 gaim_debug_warning("dns", "Wait for DNS child %d failed: %s\n", | |
406 resolver->dns_pid, strerror(errno)); | |
407 gaim_dnsquery_resolver_destroy(resolver); | |
408 return FALSE; | |
409 } | |
410 | |
411 /* Copy the hostname and port into a single data structure */ | |
412 strncpy(dns_params.hostname, query_data->hostname, sizeof(dns_params.hostname) - 1); | |
413 dns_params.hostname[sizeof(dns_params.hostname) - 1] = '\0'; | |
414 dns_params.port = query_data->port; | |
415 | |
416 /* Send the data structure to the child */ | |
417 rc = write(resolver->fd_in, &dns_params, sizeof(dns_params)); | |
418 if (rc < 0) { | |
419 gaim_debug_error("dns", "Unable to write to DNS child %d: %d\n", | |
420 resolver->dns_pid, strerror(errno)); | |
421 gaim_dnsquery_resolver_destroy(resolver); | |
422 return FALSE; | |
423 } | |
424 | |
425 g_return_val_if_fail(rc == sizeof(dns_params), -1); | |
426 | |
427 /* Did you hear me? (This avoids some race conditions) */ | |
428 rc = read(resolver->fd_out, &ch, sizeof(ch)); | |
429 if (rc != 1 || ch != 'Y') | |
430 { | |
431 gaim_debug_warning("dns", | |
432 "DNS child %d not responding. Killing it!\n", | |
433 resolver->dns_pid); | |
434 gaim_dnsquery_resolver_destroy(resolver); | |
435 return FALSE; | |
436 } | |
437 | |
438 gaim_debug_info("dns", | |
439 "Successfully sent DNS request to child %d\n", | |
440 resolver->dns_pid); | |
441 | |
442 query_data->resolver = resolver; | |
443 | |
444 return TRUE; | |
445 } | |
446 | |
447 static void host_resolved(gpointer data, gint source, GaimInputCondition cond); | |
448 | |
449 static void | |
450 handle_next_queued_request() | |
451 { | |
452 GaimDnsQueryData *query_data; | |
453 GaimDnsQueryResolverProcess *resolver; | |
454 | |
455 if (queued_requests == NULL) | |
456 /* No more DNS queries, yay! */ | |
457 return; | |
458 | |
459 query_data = queued_requests->data; | |
460 queued_requests = g_slist_delete_link(queued_requests, queued_requests); | |
461 | |
462 /* | |
463 * If we have any children, attempt to have them perform the DNS | |
464 * query. If we're able to send the query then resolver will be | |
465 * set to the GaimDnsQueryResolverProcess. Otherwise, resolver | |
466 * will be NULL and we'll need to create a new DNS request child. | |
467 */ | |
468 while (free_dns_children != NULL) | |
469 { | |
470 resolver = free_dns_children->data; | |
471 free_dns_children = g_slist_remove(free_dns_children, resolver); | |
472 | |
473 if (send_dns_request_to_child(query_data, resolver)) | |
474 /* We found an acceptable child, yay */ | |
475 break; | |
476 } | |
477 | |
478 /* We need to create a new DNS request child */ | |
479 if (query_data->resolver == NULL) | |
480 { | |
481 if (number_of_dns_children >= MAX_DNS_CHILDREN) | |
482 { | |
483 /* Apparently all our children are busy */ | |
484 queued_requests = g_slist_prepend(queued_requests, query_data); | |
485 return; | |
486 } | |
487 | |
488 resolver = gaim_dnsquery_resolver_new(gaim_debug_is_enabled()); | |
489 if (resolver == NULL) | |
490 { | |
491 gaim_dnsquery_failed(query_data, _("Unable to create new resolver process\n")); | |
492 return; | |
493 } | |
494 if (!send_dns_request_to_child(query_data, resolver)) | |
495 { | |
496 gaim_dnsquery_failed(query_data, _("Unable to send request to resolver process\n")); | |
497 return; | |
498 } | |
499 } | |
500 | |
501 query_data->resolver->inpa = gaim_input_add(query_data->resolver->fd_out, | |
502 GAIM_INPUT_READ, host_resolved, query_data); | |
503 } | |
504 | |
505 /* | |
506 * End the functions for dealing with the DNS child processes. | |
507 */ | |
508 | |
509 static void | |
510 host_resolved(gpointer data, gint source, GaimInputCondition cond) | |
511 { | |
512 GaimDnsQueryData *query_data; | |
513 int rc, err; | |
514 GSList *hosts = NULL; | |
515 struct sockaddr *addr = NULL; | |
516 size_t addrlen; | |
517 char message[1024]; | |
518 | |
519 query_data = data; | |
520 | |
521 gaim_debug_info("dns", "Got response for '%s'\n", query_data->hostname); | |
522 gaim_input_remove(query_data->resolver->inpa); | |
523 query_data->resolver->inpa = 0; | |
524 | |
525 rc = read(query_data->resolver->fd_out, &err, sizeof(err)); | |
526 if ((rc == 4) && (err != 0)) | |
527 { | |
528 #ifdef HAVE_GETADDRINFO | |
529 g_snprintf(message, sizeof(message), _("Error resolving %s:\n%s"), | |
530 query_data->hostname, gai_strerror(err)); | |
531 #else | |
532 g_snprintf(message, sizeof(message), _("Error resolving %s: %d"), | |
533 query_data->hostname, err); | |
534 #endif | |
535 gaim_dnsquery_failed(query_data, message); | |
536 | |
537 } else if (rc > 0) { | |
538 /* Success! */ | |
539 while (rc > 0) { | |
540 rc = read(query_data->resolver->fd_out, &addrlen, sizeof(addrlen)); | |
541 if (rc > 0 && addrlen > 0) { | |
542 addr = g_malloc(addrlen); | |
543 rc = read(query_data->resolver->fd_out, addr, addrlen); | |
544 hosts = g_slist_append(hosts, GINT_TO_POINTER(addrlen)); | |
545 hosts = g_slist_append(hosts, addr); | |
546 } else { | |
547 break; | |
548 } | |
549 } | |
550 /* wait4(resolver->dns_pid, NULL, WNOHANG, NULL); */ | |
551 gaim_dnsquery_resolved(query_data, hosts); | |
552 | |
553 } else if (rc == -1) { | |
554 g_snprintf(message, sizeof(message), _("Error reading from resolver process:\n%s"), strerror(errno)); | |
555 gaim_dnsquery_failed(query_data, message); | |
556 | |
557 } else if (rc == 0) { | |
558 g_snprintf(message, sizeof(message), _("EOF while reading from resolver process")); | |
559 gaim_dnsquery_failed(query_data, message); | |
560 } | |
561 | |
562 handle_next_queued_request(); | |
563 } | |
564 | |
565 static gboolean | |
566 resolve_host(gpointer data) | |
567 { | |
568 GaimDnsQueryData *query_data; | |
569 | |
570 query_data = data; | |
571 query_data->timeout = 0; | |
572 | |
573 handle_next_queued_request(); | |
574 | |
575 return FALSE; | |
576 } | |
577 | |
578 GaimDnsQueryData * | |
579 gaim_dnsquery_a(const char *hostname, int port, | |
580 GaimDnsQueryConnectFunction callback, gpointer data) | |
581 { | |
582 GaimDnsQueryData *query_data; | |
583 | |
584 g_return_val_if_fail(hostname != NULL, NULL); | |
585 g_return_val_if_fail(port != 0, NULL); | |
586 g_return_val_if_fail(callback != NULL, NULL); | |
587 | |
588 query_data = g_new(GaimDnsQueryData, 1); | |
589 query_data->hostname = g_strdup(hostname); | |
590 g_strstrip(query_data->hostname); | |
591 query_data->port = port; | |
592 query_data->callback = callback; | |
593 query_data->data = data; | |
594 query_data->resolver = NULL; | |
595 | |
596 if (strlen(query_data->hostname) == 0) | |
597 { | |
598 gaim_dnsquery_destroy(query_data); | |
599 g_return_val_if_reached(NULL); | |
600 } | |
601 | |
602 queued_requests = g_slist_append(queued_requests, query_data); | |
603 | |
604 gaim_debug_info("dns", "DNS query for '%s' queued\n", query_data->hostname); | |
605 | |
606 query_data->timeout = gaim_timeout_add(0, resolve_host, query_data); | |
607 | |
608 return query_data; | |
609 } | |
610 | |
611 #elif defined _WIN32 /* end __unix__ || __APPLE__ */ | |
612 | |
613 /* | |
614 * Windows! | |
615 */ | |
616 | |
617 static gboolean | |
618 dns_main_thread_cb(gpointer data) | |
619 { | |
620 GaimDnsQueryData *query_data; | |
621 | |
622 query_data = data; | |
623 | |
624 if (query_data->error_message != NULL) | |
625 gaim_dnsquery_failed(query_data, query_data->error_message); | |
626 else | |
627 { | |
628 GSList *hosts; | |
629 | |
630 /* We don't want gaim_dns_query_resolved() to free(hosts) */ | |
631 hosts = query_data->hosts; | |
632 query_data->hosts = NULL; | |
633 gaim_dnsquery_resolved(query_data, hosts); | |
634 } | |
635 | |
636 return FALSE; | |
637 } | |
638 | |
639 static gpointer | |
640 dns_thread(gpointer data) | |
641 { | |
642 GaimDnsQueryData *query_data; | |
643 #ifdef HAVE_GETADDRINFO | |
644 int rc; | |
645 struct addrinfo hints, *res, *tmp; | |
646 char servname[20]; | |
647 #else | |
648 struct sockaddr_in sin; | |
649 struct hostent *hp; | |
650 #endif | |
651 | |
652 query_data = data; | |
653 | |
654 #ifdef HAVE_GETADDRINFO | |
655 g_snprintf(servname, sizeof(servname), "%d", query_data->port); | |
656 memset(&hints,0,sizeof(hints)); | |
657 | |
658 /* | |
659 * This is only used to convert a service | |
660 * name to a port number. As we know we are | |
661 * passing a number already, we know this | |
662 * value will not be really used by the C | |
663 * library. | |
664 */ | |
665 hints.ai_socktype = SOCK_STREAM; | |
666 if ((rc = getaddrinfo(query_data->hostname, servname, &hints, &res)) == 0) { | |
667 tmp = res; | |
668 while(res) { | |
669 query_data->hosts = g_slist_append(query_data->hosts, | |
670 GSIZE_TO_POINTER(res->ai_addrlen)); | |
671 query_data->hosts = g_slist_append(query_data->hosts, | |
672 g_memdup(res->ai_addr, res->ai_addrlen)); | |
673 res = res->ai_next; | |
674 } | |
675 freeaddrinfo(tmp); | |
676 } else { | |
677 query_data->error_message = g_strdup_printf(_("Error resolving %s:\n%s"), query_data->hostname, gai_strerror(rc)); | |
678 } | |
679 #else | |
680 if ((hp = gethostbyname(query_data->hostname))) { | |
681 memset(&sin, 0, sizeof(struct sockaddr_in)); | |
682 memcpy(&sin.sin_addr.s_addr, hp->h_addr, hp->h_length); | |
683 sin.sin_family = hp->h_addrtype; | |
684 sin.sin_port = htons(query_data->port); | |
685 | |
686 query_data->hosts = g_slist_append(query_data->hosts, | |
687 GSIZE_TO_POINTER(sizeof(sin))); | |
688 query_data->hosts = g_slist_append(query_data->hosts, | |
689 g_memdup(&sin, sizeof(sin))); | |
690 } else { | |
691 query_data->error_message = g_strdup_printf(_("Error resolving %s: %d"), query_data->hostname, h_errno); | |
692 } | |
693 #endif | |
694 | |
695 /* back to main thread */ | |
696 g_idle_add(dns_main_thread_cb, query_data); | |
697 | |
698 return 0; | |
699 } | |
700 | |
701 static gboolean | |
702 resolve_host(gpointer data) | |
703 { | |
704 GaimDnsQueryData *query_data; | |
705 struct sockaddr_in sin; | |
706 GError *err = NULL; | |
707 | |
708 query_data = data; | |
709 query_data->timeout = 0; | |
710 | |
711 if (inet_aton(query_data->hostname, &sin.sin_addr)) | |
712 { | |
713 /* | |
714 * The given "hostname" is actually an IP address, so we | |
715 * don't need to do anything. | |
716 */ | |
717 GSList *hosts = NULL; | |
718 sin.sin_family = AF_INET; | |
719 sin.sin_port = htons(query_data->port); | |
720 hosts = g_slist_append(hosts, GINT_TO_POINTER(sizeof(sin))); | |
721 hosts = g_slist_append(hosts, g_memdup(&sin, sizeof(sin))); | |
722 gaim_dnsquery_resolved(query_data, hosts); | |
723 } | |
724 else | |
725 { | |
726 /* | |
727 * Spin off a separate thread to perform the DNS lookup so | |
728 * that we don't block the UI. | |
729 */ | |
730 query_data->resolver = g_thread_create(dns_thread, | |
731 query_data, FALSE, &err); | |
732 if (query_data->resolver == NULL) | |
733 { | |
734 char message[1024]; | |
735 g_snprintf(message, sizeof(message), _("Thread creation failure: %s"), | |
736 err ? err->message : _("Unknown reason")); | |
737 g_error_free(err); | |
738 gaim_dnsquery_failed(query_data, message); | |
739 } | |
740 } | |
741 | |
742 return FALSE; | |
743 } | |
744 | |
745 GaimDnsQueryData * | |
746 gaim_dnsquery_a(const char *hostname, int port, | |
747 GaimDnsQueryConnectFunction callback, gpointer data) | |
748 { | |
749 GaimDnsQueryData *query_data; | |
750 | |
751 g_return_val_if_fail(hostname != NULL, NULL); | |
752 g_return_val_if_fail(port != 0, NULL); | |
753 g_return_val_if_fail(callback != NULL, NULL); | |
754 | |
755 gaim_debug_info("dnsquery", "Performing DNS lookup for %s\n", hostname); | |
756 | |
757 query_data = g_new(GaimDnsQueryData, 1); | |
758 query_data->hostname = g_strdup(hostname); | |
759 g_strstrip(query_data->hostname); | |
760 query_data->port = port; | |
761 query_data->callback = callback; | |
762 query_data->data = data; | |
763 query_data->error_message = NULL; | |
764 query_data->hosts = NULL; | |
765 | |
766 if (strlen(query_data->hostname) == 0) | |
767 { | |
768 gaim_dnsquery_destroy(query_data); | |
769 g_return_val_if_reached(NULL); | |
770 } | |
771 | |
772 /* Don't call the callback before returning */ | |
773 query_data->timeout = gaim_timeout_add(0, resolve_host, query_data); | |
774 | |
775 return query_data; | |
776 } | |
777 | |
778 #else /* not __unix__ or __APPLE__ or _WIN32 */ | |
779 | |
780 /* | |
781 * We weren't able to do anything fancier above, so use the | |
782 * fail-safe name resolution code, which is blocking. | |
783 */ | |
784 | |
785 static gboolean | |
786 resolve_host(gpointer data) | |
787 { | |
788 GaimDnsQueryData *query_data; | |
789 struct sockaddr_in sin; | |
790 GSList *hosts = NULL; | |
791 | |
792 query_data = data; | |
793 query_data->timeout = 0; | |
794 | |
795 if (!inet_aton(query_data->hostname, &sin.sin_addr)) { | |
796 struct hostent *hp; | |
797 if(!(hp = gethostbyname(query_data->hostname))) { | |
798 char message[1024]; | |
799 g_snprintf(message, sizeof(message), _("Error resolving %s: %d"), | |
800 query_data->hostname, h_errno); | |
801 gaim_dnsquery_failed(query_data, message); | |
802 return FALSE; | |
803 } | |
804 memset(&sin, 0, sizeof(struct sockaddr_in)); | |
805 memcpy(&sin.sin_addr.s_addr, hp->h_addr, hp->h_length); | |
806 sin.sin_family = hp->h_addrtype; | |
807 } else | |
808 sin.sin_family = AF_INET; | |
809 sin.sin_port = htons(query_data->port); | |
810 | |
811 hosts = g_slist_append(hosts, GINT_TO_POINTER(sizeof(sin))); | |
812 hosts = g_slist_append(hosts, g_memdup(&sin, sizeof(sin))); | |
813 | |
814 gaim_dnsquery_resolved(query_data, hosts); | |
815 | |
816 return FALSE; | |
817 } | |
818 | |
819 GaimDnsQueryData * | |
820 gaim_dnsquery_a(const char *hostname, int port, | |
821 GaimDnsQueryConnectFunction callback, gpointer data) | |
822 { | |
823 GaimDnsQueryData *query_data; | |
824 | |
825 g_return_val_if_fail(hostname != NULL, NULL); | |
826 g_return_val_if_fail(port != 0, NULL); | |
827 g_return_val_if_fail(callback != NULL, NULL); | |
828 | |
829 query_data = g_new(GaimDnsQueryData, 1); | |
830 query_data->hostname = g_strdup(hostname); | |
831 g_strstrip(query_data->hostname); | |
832 query_data->port = port; | |
833 query_data->callback = callback; | |
834 query_data->data = data; | |
835 | |
836 if (strlen(query_data->hostname) == 0) | |
837 { | |
838 gaim_dnsquery_destroy(query_data); | |
839 g_return_val_if_reached(NULL); | |
840 } | |
841 | |
842 /* Don't call the callback before returning */ | |
843 query_data->timeout = gaim_timeout_add(0, resolve_host, query_data); | |
844 | |
845 return query_data; | |
846 } | |
847 | |
848 #endif /* not __unix__ or __APPLE__ or _WIN32 */ | |
849 | |
850 void | |
851 gaim_dnsquery_destroy(GaimDnsQueryData *query_data) | |
852 { | |
853 #if defined(__unix__) || defined(__APPLE__) | |
854 queued_requests = g_slist_remove(queued_requests, query_data); | |
855 | |
856 if (query_data->resolver != NULL) | |
857 /* | |
858 * Ideally we would tell our resolver child to stop resolving | |
859 * shit and then we would add it back to the free_dns_children | |
860 * linked list. However, it's hard to tell children stuff, | |
861 * they just don't listen. | |
862 */ | |
863 gaim_dnsquery_resolver_destroy(query_data->resolver); | |
864 #elif defined _WIN32 /* end __unix__ || __APPLE__ */ | |
865 if (query_data->resolver != NULL) | |
866 { | |
867 /* | |
868 * It's not really possible to kill a thread. So instead we | |
869 * just set the callback to NULL and let the DNS lookup | |
870 * finish. | |
871 */ | |
872 query_data->callback = NULL; | |
873 return; | |
874 } | |
875 | |
876 while (query_data->hosts != NULL) | |
877 { | |
878 /* Discard the length... */ | |
879 query_data->hosts = g_slist_remove(query_data->hosts, query_data->hosts->data); | |
880 /* Free the address... */ | |
881 g_free(query_data->hosts->data); | |
882 query_data->hosts = g_slist_remove(query_data->hosts, query_data->hosts->data); | |
883 } | |
884 g_free(query_data->error_message); | |
885 #endif | |
886 | |
887 if (query_data->timeout > 0) | |
888 gaim_timeout_remove(query_data->timeout); | |
889 | |
890 g_free(query_data->hostname); | |
891 g_free(query_data); | |
892 } | |
893 | |
894 void | |
895 gaim_dnsquery_init(void) | |
896 { | |
897 } | |
898 | |
899 void | |
900 gaim_dnsquery_uninit(void) | |
901 { | |
902 #if defined(__unix__) || defined(__APPLE__) | |
903 while (free_dns_children != NULL) | |
904 { | |
905 gaim_dnsquery_resolver_destroy(free_dns_children->data); | |
906 free_dns_children = g_slist_remove(free_dns_children, free_dns_children->data); | |
907 } | |
908 #endif | |
909 } |