libpurple/dnssrv.c

changeset 37468
2a1934a1e457
parent 37436
1f30661ee575
parent 37467
37d053fc8907
child 37469
f238b0a5ee69
equal deleted inserted replaced
37436:1f30661ee575 37468:2a1934a1e457
1 /* purple
2 *
3 * Copyright (C) 2005 Thomas Butter <butter@uni-mannheim.de>
4 *
5 * This program is free software; you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation; either version 2 of the License, or
8 * (at your option) any later version.
9 *
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
14 *
15 * You should have received a copy of the GNU General Public License
16 * along with this program; if not, write to the Free Software
17 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA
18 */
19 #define _PURPLE_DNSSRV_C_
20
21 #include "internal.h"
22 #include "util.h"
23
24 #ifndef _WIN32
25 #include <arpa/nameser.h>
26 #include <resolv.h>
27 #ifdef HAVE_ARPA_NAMESER_COMPAT_H
28 #include <arpa/nameser_compat.h>
29 #endif
30 #else /* WIN32 */
31 #include <windns.h>
32 /* Missing from the mingw headers */
33 #ifndef DNS_TYPE_SRV
34 # define DNS_TYPE_SRV PurpleDnsTypeSrv
35 #endif
36 #ifndef DNS_TYPE_TXT
37 # define DNS_TYPE_TXT PurpleDnsTypeTxt
38 #endif
39 #endif
40
41 #ifndef T_SRV
42 #define T_SRV PurpleDnsTypeSrv
43 #endif
44 #ifndef T_TXT
45 #define T_TXT PurpleDnsTypeTxt
46 #endif
47
48 #define MAX_ADDR_RESPONSE_LEN 1048576
49
50 #include "debug.h"
51 #include "dnssrv.h"
52 #include "eventloop.h"
53 #include "network.h"
54
55 static PurpleSrvTxtQueryUiOps *srv_txt_query_ui_ops = NULL;
56
57 #ifndef _WIN32
58 typedef union {
59 HEADER hdr;
60 u_char buf[1024];
61 } queryans;
62 #endif
63
64 enum PurpleDnsType {
65 PurpleDnsTypeTxt = 16,
66 PurpleDnsTypeSrv = 33
67 };
68
69 struct _PurpleSrvTxtQueryData {
70 union {
71 PurpleSrvCallback srv;
72 PurpleTxtCallback txt;
73 } cb;
74
75 gpointer extradata;
76 guint handle;
77 int type;
78 char *query;
79 #ifdef _WIN32
80 GThread *resolver;
81 char *error_message;
82 GList *results;
83 #else
84 int fd_in, fd_out;
85 pid_t pid;
86 #endif
87 };
88
89 typedef struct _PurpleSrvInternalQuery {
90 int type;
91 char query[256];
92 } PurpleSrvInternalQuery;
93
94 typedef struct _PurpleSrvResponseContainer {
95 PurpleSrvResponse *response;
96 int sum;
97 } PurpleSrvResponseContainer;
98
99 static gboolean purple_srv_txt_query_ui_resolve(PurpleSrvTxtQueryData *query_data);
100
101 /*
102 * Sort by priority, then by weight. Strictly numerically--no
103 * randomness. Technically we only need to sort by pref and then
104 * make sure any records with weight 0 are at the beginning of
105 * their group, but it's just as easy to sort by weight.
106 */
107 static gint
108 responsecompare(gconstpointer ar, gconstpointer br)
109 {
110 PurpleSrvResponse *a = (PurpleSrvResponse*)ar;
111 PurpleSrvResponse *b = (PurpleSrvResponse*)br;
112
113 if(a->pref == b->pref) {
114 if(a->weight == b->weight)
115 return 0;
116 if(a->weight < b->weight)
117 return -1;
118 return 1;
119 }
120 if(a->pref < b->pref)
121 return -1;
122 return 1;
123 }
124
125 /*
126 * select_random_response:
127 * @list: The list of PurpleSrvResponseContainer. This function
128 * removes a node from this list and returns the new list.
129 * @container_ptr: The PurpleSrvResponseContainer that was chosen
130 * will be returned here.
131 *
132 * Iterate over a list of PurpleSrvResponseContainer making the sum
133 * the running total of the sums. Select a random integer in the range
134 * (1, sum+1), then find the first element greater than or equal to the
135 * number selected. From RFC 2782.
136 */
137 static GList *
138 select_random_response(GList *list, PurpleSrvResponseContainer **container_ptr)
139 {
140 GList *cur;
141 size_t runningtotal;
142 int r;
143
144 g_return_val_if_fail(list != NULL, NULL);
145
146 runningtotal = 0;
147 cur = list;
148
149 while (cur) {
150 PurpleSrvResponseContainer *container = cur->data;
151 runningtotal += container->response->weight;
152 container->sum = runningtotal;
153 cur = cur->next;
154 }
155
156 /*
157 * If the running total is greater than 0, pick a number between
158 * 1 and the runningtotal inclusive. (This is not precisely what
159 * the RFC algorithm describes, but we wish to deal with integers
160 * and avoid floats. This is functionally equivalent.)
161 * If running total is 0, then choose r = 0.
162 */
163 r = runningtotal ? g_random_int_range(1, runningtotal + 1) : 0;
164 cur = list;
165 while (r > ((PurpleSrvResponseContainer *)cur->data)->sum) {
166 if (G_UNLIKELY(!cur->next))
167 break;
168 cur = cur->next;
169 }
170
171 /* Set the return parameter and remove cur from the list */
172 *container_ptr = cur->data;
173 return g_list_delete_link(list, cur);
174 }
175
176 /*
177 * Reorder a GList of PurpleSrvResponses that have the same priority
178 * (aka "pref").
179 */
180 static void
181 srv_reorder(GList *list, int num)
182 {
183 int i;
184 GList *cur, *container_list = NULL;
185 PurpleSrvResponseContainer *container;
186
187 if (num < 2)
188 /* Nothing to sort */
189 return;
190
191 g_return_if_fail(list != NULL);
192
193 /* First build a list of container structs */
194 for (i = 0, cur = list; i < num; i++, cur = cur->next) {
195 container = g_new(PurpleSrvResponseContainer, 1);
196 container->response = cur->data;
197 container_list = g_list_prepend(container_list, container);
198 }
199 container_list = g_list_reverse(container_list);
200
201 /*
202 * Re-order the list that was passed in as a parameter. We leave
203 * the list nodes in place, but replace their data pointers.
204 */
205 cur = list;
206 while (container_list) {
207 g_return_if_fail(cur);
208 container_list = select_random_response(container_list, &container);
209 cur->data = container->response;
210 g_free(container);
211 cur = cur->next;
212 }
213 }
214
215 /*
216 * purple_srv_sort:
217 * @list: The original list, resorted
218 *
219 * Sorts a GList of PurpleSrvResponses according to the
220 * algorithm described in RFC 2782.
221 *
222 * Returns: GList of PurpleSrvResponse's
223 */
224 static GList *
225 purple_srv_sort(GList *list)
226 {
227 int pref, count;
228 GList *cur, *start;
229
230 if (!list || !list->next) {
231 /* Nothing to sort */
232 return list;
233 }
234
235 list = g_list_sort(list, responsecompare);
236
237 start = cur = list;
238 count = 1;
239 while (cur) {
240 PurpleSrvResponse *next_response;
241
242 g_return_val_if_fail(cur->data, list);
243
244 pref = ((PurpleSrvResponse *)cur->data)->pref;
245 next_response = cur->next ? cur->next->data : NULL;
246 if (!next_response || next_response->pref != pref) {
247 /*
248 * The 'count' records starting at 'start' all have the same
249 * priority. Sort them by weight.
250 */
251 srv_reorder(start, count);
252 start = cur->next;
253 count = 0;
254 }
255 count++;
256 cur = cur->next;
257 }
258
259 return list;
260 }
261
262 static PurpleSrvTxtQueryData *
263 query_data_new(int type, gchar *query, gpointer extradata)
264 {
265 PurpleSrvTxtQueryData *query_data = g_new0(PurpleSrvTxtQueryData, 1);
266 query_data->type = type;
267 query_data->extradata = extradata;
268 query_data->query = query;
269 #ifndef _WIN32
270 query_data->fd_in = -1;
271 query_data->fd_out = -1;
272 #endif
273 return query_data;
274 }
275
276 void
277 purple_srv_txt_query_destroy(PurpleSrvTxtQueryData *query_data)
278 {
279 PurpleSrvTxtQueryUiOps *ops = purple_srv_txt_query_get_ui_ops();
280
281 if (ops && ops->destroy)
282 ops->destroy(query_data);
283
284 if (query_data->handle > 0)
285 purple_input_remove(query_data->handle);
286 #ifdef _WIN32
287 if (query_data->resolver != NULL)
288 {
289 /*
290 * It's not really possible to kill a thread. So instead we
291 * just set the callback to NULL and let the DNS lookup
292 * finish.
293 */
294 query_data->cb.srv = NULL;
295 return;
296 }
297 g_free(query_data->error_message);
298 #else
299 if (query_data->fd_out != -1)
300 close(query_data->fd_out);
301 if (query_data->fd_in != -1)
302 close(query_data->fd_in);
303 #endif
304 g_free(query_data->query);
305 g_free(query_data);
306 }
307
308 #ifdef USE_IDN
309 static gboolean
310 dns_str_is_ascii(const char *name)
311 {
312 guchar *c;
313 for (c = (guchar *)name; c && *c; ++c) {
314 if (*c > 0x7f)
315 return FALSE;
316 }
317
318 return TRUE;
319 }
320 #endif
321
322 #ifndef _WIN32
323 static void
324 write_to_parent(int in, int out, gconstpointer data, gsize size)
325 {
326 const guchar *buf = data;
327 gssize w;
328
329 do {
330 w = write(out, buf, size);
331 if (w > 0) {
332 buf += w;
333 size -= w;
334 } else if (w < 0 && errno == EINTR) {
335 /* Let's try some more; */
336 w = 1;
337 }
338 } while (size > 0 && w > 0);
339
340 if (size != 0) {
341 /* An error occurred */
342 close(out);
343 close(in);
344 _exit(0);
345 }
346 }
347
348 /* Read size bytes to data. Dies if an error occurs. */
349 static void
350 read_from_parent(int in, int out, gpointer data, gsize size)
351 {
352 guchar *buf = data;
353 gssize r;
354
355 do {
356 r = read(in, data, size);
357 if (r > 0) {
358 buf += r;
359 size -= r;
360 } else if (r < 0 && errno == EINTR) {
361 /* Let's try some more; */
362 r = 1;
363 }
364 } while (size > 0 && r > 0);
365
366 if (size != 0) {
367 /* An error occurred */
368 close(out);
369 close(in);
370 _exit(0);
371 }
372 }
373
374
375 G_GNUC_NORETURN static void
376 resolve(int in, int out)
377 {
378 GList *ret = NULL;
379 PurpleSrvResponse *srvres;
380 PurpleTxtResponse *txtres;
381 queryans answer;
382 int size, qdcount, ancount;
383 guchar *end, *cp;
384 gchar name[256];
385 guint16 type, dlen, pref, weight, port;
386 PurpleSrvInternalQuery query;
387
388 #ifdef HAVE_SIGNAL_H
389 purple_restore_default_signal_handlers();
390 #endif
391
392 read_from_parent(in, out, &query, sizeof(query));
393
394 size = res_query( query.query, C_IN, query.type, (u_char*)&answer, sizeof( answer));
395 if (size == -1) {
396 write_to_parent(in, out, &(query.type), sizeof(query.type));
397 write_to_parent(in, out, &size, sizeof(size));
398 close(out);
399 close(in);
400 _exit(0);
401 }
402
403 qdcount = ntohs(answer.hdr.qdcount);
404 ancount = ntohs(answer.hdr.ancount);
405 cp = (guchar*)&answer + sizeof(HEADER);
406 end = (guchar*)&answer + size;
407
408 /* skip over unwanted stuff */
409 while (qdcount-- > 0 && cp < end) {
410 size = dn_expand( (unsigned char*)&answer, end, cp, name, 256);
411 if(size < 0) goto end;
412 cp += size + QFIXEDSZ;
413 }
414
415 while (ancount-- > 0 && cp < end) {
416 size = dn_expand((unsigned char*)&answer, end, cp, name, 256);
417 if(size < 0)
418 goto end;
419 cp += size;
420 GETSHORT(type,cp);
421
422 /* skip ttl and class since we already know it */
423 cp += 6;
424
425 GETSHORT(dlen,cp);
426 if (type == T_SRV) {
427 GETSHORT(pref,cp);
428
429 GETSHORT(weight,cp);
430
431 GETSHORT(port,cp);
432
433 size = dn_expand( (unsigned char*)&answer, end, cp, name, 256);
434 if(size < 0 )
435 goto end;
436
437 cp += size;
438
439 srvres = g_new0(PurpleSrvResponse, 1);
440 if (strlen(name) > sizeof(srvres->hostname) - 1) {
441 purple_debug_error("dnssrv", "hostname is "
442 "longer than available buffer ('%s', %"
443 G_GSIZE_FORMAT " bytes)!",
444 name, strlen(name));
445 }
446 g_strlcpy(srvres->hostname, name, sizeof(srvres->hostname));
447 srvres->pref = pref;
448 srvres->port = port;
449 srvres->weight = weight;
450
451 ret = g_list_prepend(ret, srvres);
452 } else if (type == T_TXT) {
453 txtres = g_new0(PurpleTxtResponse, 1);
454 txtres->content = g_strndup((gchar*)(++cp), dlen-1);
455 ret = g_list_append(ret, txtres);
456 cp += dlen - 1;
457 } else {
458 cp += dlen;
459 }
460 }
461
462 end:
463 size = g_list_length(ret);
464
465 if (query.type == T_SRV)
466 ret = purple_srv_sort(ret);
467
468 write_to_parent(in, out, &(query.type), sizeof(query.type));
469 write_to_parent(in, out, &size, sizeof(size));
470 while (ret != NULL)
471 {
472 if (query.type == T_SRV)
473 write_to_parent(in, out, ret->data, sizeof(PurpleSrvResponse));
474 if (query.type == T_TXT) {
475 PurpleTxtResponse *response = ret->data;
476 gsize l = strlen(response->content) + 1 /* null byte */;
477 write_to_parent(in, out, &l, sizeof(l));
478 write_to_parent(in, out, response->content, l);
479 }
480
481 g_free(ret->data);
482 ret = g_list_remove(ret, ret->data);
483 }
484
485 close(out);
486 close(in);
487
488 _exit(0);
489 }
490
491 static void
492 resolved(gpointer data, gint source, PurpleInputCondition cond)
493 {
494 int size;
495 int type;
496 PurpleSrvTxtQueryData *query_data = (PurpleSrvTxtQueryData*)data;
497 int i;
498 int status;
499
500 if (read(source, &type, sizeof(type)) == sizeof(type)) {
501 if (read(source, &size, sizeof(size)) == sizeof(size)) {
502 if (size < -1 || size > MAX_ADDR_RESPONSE_LEN) {
503 purple_debug_warning("dnssrv", "res_query returned invalid number\n");
504 size = 0;
505 }
506 if (size == -1 || size == 0) {
507 if (size == -1) {
508 purple_debug_warning("dnssrv", "res_query returned an error\n");
509 /* Re-read resolv.conf and friends in case DNS servers have changed */
510 res_init();
511 } else
512 purple_debug_info("dnssrv", "Found 0 entries, errno is %i\n", errno);
513
514 if (type == T_SRV) {
515 PurpleSrvCallback cb = query_data->cb.srv;
516 cb(NULL, 0, query_data->extradata);
517 } else if (type == T_TXT) {
518 PurpleTxtCallback cb = query_data->cb.txt;
519 cb(NULL, query_data->extradata);
520 } else {
521 purple_debug_error("dnssrv", "type unknown of DNS result entry; errno is %i\n", errno);
522 }
523
524 } else if (size) {
525 if (type == T_SRV) {
526 PurpleSrvResponse *res;
527 PurpleSrvResponse *tmp;
528 PurpleSrvCallback cb = query_data->cb.srv;
529 ssize_t red;
530 purple_debug_info("dnssrv","found %d SRV entries\n", size);
531 tmp = res = g_new0(PurpleSrvResponse, size);
532 for (i = 0; i < size; i++) {
533 red = read(source, tmp++, sizeof(PurpleSrvResponse));
534 if (red != sizeof(PurpleSrvResponse)) {
535 purple_debug_error("dnssrv","unable to read srv "
536 "response: %s\n", g_strerror(errno));
537 size = 0;
538 g_free(res);
539 res = NULL;
540 }
541 }
542
543 cb(res, size, query_data->extradata);
544 } else if (type == T_TXT) {
545 GList *responses = NULL;
546 PurpleTxtResponse *res;
547 PurpleTxtCallback cb = query_data->cb.txt;
548 ssize_t red;
549 purple_debug_info("dnssrv","found %d TXT entries\n", size);
550 for (i = 0; i < size; i++) {
551 gsize len;
552
553 red = read(source, &len, sizeof(len));
554 if (red != sizeof(len)) {
555 purple_debug_error("dnssrv","unable to read txt "
556 "response length: %s\n", g_strerror(errno));
557 size = 0;
558 g_list_foreach(responses, (GFunc)purple_txt_response_destroy, NULL);
559 g_list_free(responses);
560 responses = NULL;
561 break;
562 }
563 if (len > MAX_ADDR_RESPONSE_LEN) {
564 purple_debug_error("dnssrv", "we've read invalid number\n");
565 size = 0;
566 g_list_foreach(responses, (GFunc)purple_txt_response_destroy, NULL);
567 g_list_free(responses);
568 responses = NULL;
569 break;
570 }
571
572 res = g_new0(PurpleTxtResponse, 1);
573 res->content = g_new0(gchar, len);
574
575 red = read(source, res->content, len);
576 if (red < 0 || (gsize)red != len) {
577 purple_debug_error("dnssrv","unable to read txt "
578 "response: %s\n", g_strerror(errno));
579 size = 0;
580 purple_txt_response_destroy(res);
581 g_list_foreach(responses, (GFunc)purple_txt_response_destroy, NULL);
582 g_list_free(responses);
583 responses = NULL;
584 break;
585 }
586 responses = g_list_prepend(responses, res);
587 }
588
589 responses = g_list_reverse(responses);
590 cb(responses, query_data->extradata);
591 } else {
592 purple_debug_error("dnssrv", "type unknown of DNS result entry; errno is %i\n", errno);
593 }
594 }
595 }
596 }
597
598 waitpid(query_data->pid, &status, 0);
599 purple_srv_txt_query_destroy(query_data);
600 }
601
602 #else /* _WIN32 */
603
604 /* The Jabber Server code was inspiration for parts of this. */
605
606 static gboolean
607 res_main_thread_cb(gpointer data)
608 {
609 PurpleSrvResponse *srvres = NULL;
610 PurpleSrvTxtQueryData *query_data = data;
611 if(query_data->error_message != NULL) {
612 purple_debug_error("dnssrv", "%s", query_data->error_message);
613 if (query_data->type == DNS_TYPE_SRV) {
614 if (query_data->cb.srv)
615 query_data->cb.srv(srvres, 0, query_data->extradata);
616 } else if (query_data->type == DNS_TYPE_TXT) {
617 if (query_data->cb.txt)
618 query_data->cb.txt(NULL, query_data->extradata);
619 }
620 } else {
621 if (query_data->type == DNS_TYPE_SRV) {
622 PurpleSrvResponse *srvres_tmp = NULL;
623 GList *lst = query_data->results;
624 int size = g_list_length(lst);
625
626 if(query_data->cb.srv && size > 0)
627 srvres_tmp = srvres = g_new0(PurpleSrvResponse, size);
628 while (lst) {
629 PurpleSrvResponse *lstdata = lst->data;
630 lst = g_list_delete_link(lst, lst);
631
632 if(query_data->cb.srv)
633 memcpy(srvres_tmp++, lstdata, sizeof(PurpleSrvResponse));
634 g_free(lstdata);
635 }
636
637 query_data->results = NULL;
638
639 purple_debug_info("dnssrv", "found %d SRV entries\n", size);
640
641 if(query_data->cb.srv) query_data->cb.srv(srvres, size, query_data->extradata);
642 } else if (query_data->type == DNS_TYPE_TXT) {
643 GList *lst = query_data->results;
644
645 purple_debug_info("dnssrv", "found %d TXT entries\n", g_list_length(lst));
646
647 if (query_data->cb.txt) {
648 query_data->results = NULL;
649 query_data->cb.txt(lst, query_data->extradata);
650 }
651 } else {
652 purple_debug_error("dnssrv", "unknown query type");
653 }
654 }
655
656 query_data->resolver = NULL;
657 query_data->handle = 0;
658
659 purple_srv_txt_query_destroy(query_data);
660
661 return FALSE;
662 }
663
664 static gpointer
665 res_thread(gpointer data)
666 {
667 PDNS_RECORD dr = NULL;
668 int type;
669 DNS_STATUS ds;
670 PurpleSrvTxtQueryData *query_data = data;
671 type = query_data->type;
672 ds = DnsQuery_UTF8(query_data->query, type, DNS_QUERY_STANDARD, NULL, &dr, NULL);
673 if (ds != ERROR_SUCCESS) {
674 gchar *msg = g_win32_error_message(ds);
675 if (type == DNS_TYPE_SRV) {
676 query_data->error_message = g_strdup_printf("Couldn't look up SRV record. %s (%lu).\n", msg, ds);
677 } else if (type == DNS_TYPE_TXT) {
678 query_data->error_message = g_strdup_printf("Couldn't look up TXT record. %s (%lu).\n", msg, ds);
679 }
680 g_free(msg);
681 } else {
682 if (type == DNS_TYPE_SRV) {
683 PDNS_RECORD dr_tmp;
684 GList *lst = NULL;
685 DNS_SRV_DATA *srv_data;
686 PurpleSrvResponse *srvres;
687
688 for (dr_tmp = dr; dr_tmp != NULL; dr_tmp = dr_tmp->pNext) {
689 /* Discard any incorrect entries. I'm not sure if this is necessary */
690 if (dr_tmp->wType != type || strcmp(dr_tmp->pName, query_data->query) != 0) {
691 continue;
692 }
693
694 srv_data = &dr_tmp->Data.SRV;
695 srvres = g_new0(PurpleSrvResponse, 1);
696 strncpy(srvres->hostname, srv_data->pNameTarget, 255);
697 srvres->hostname[255] = '\0';
698 srvres->pref = srv_data->wPriority;
699 srvres->port = srv_data->wPort;
700 srvres->weight = srv_data->wWeight;
701
702 lst = g_list_prepend(lst, srvres);
703 }
704
705 DnsRecordListFree(dr, DnsFreeRecordList);
706 query_data->results = purple_srv_sort(lst);
707 } else if (type == DNS_TYPE_TXT) {
708 PDNS_RECORD dr_tmp;
709 GList *lst = NULL;
710 DNS_TXT_DATA *txt_data;
711 PurpleTxtResponse *txtres;
712
713 for (dr_tmp = dr; dr_tmp != NULL; dr_tmp = dr_tmp->pNext) {
714 GString *s;
715 int i;
716
717 /* Discard any incorrect entries. I'm not sure if this is necessary */
718 if (dr_tmp->wType != type || strcmp(dr_tmp->pName, query_data->query) != 0) {
719 continue;
720 }
721
722 txt_data = &dr_tmp->Data.TXT;
723 txtres = g_new0(PurpleTxtResponse, 1);
724
725 s = g_string_new("");
726 for (i = 0; i < (int)txt_data->dwStringCount; ++i)
727 s = g_string_append(s, txt_data->pStringArray[i]);
728 txtres->content = g_string_free(s, FALSE);
729
730 lst = g_list_append(lst, txtres);
731 }
732
733 DnsRecordListFree(dr, DnsFreeRecordList);
734 query_data->results = lst;
735 } else {
736
737 }
738 }
739
740 /* back to main thread */
741 /* Note: this should *not* be attached to query_data->handle - it will cause leakage */
742 purple_timeout_add(0, res_main_thread_cb, query_data);
743
744 g_thread_exit(NULL);
745 return NULL;
746 }
747
748 #endif
749
750 PurpleSrvTxtQueryData *
751 purple_srv_resolve(PurpleAccount *account, const char *protocol,
752 const char *transport, const char *domain, PurpleSrvCallback cb,
753 gpointer extradata)
754 {
755 char *query;
756 char *hostname;
757 PurpleSrvTxtQueryData *query_data;
758 PurpleProxyType proxy_type;
759 #ifndef _WIN32
760 PurpleSrvInternalQuery internal_query;
761 int in[2], out[2];
762 int pid;
763 #else
764 GError* err = NULL;
765 #endif
766
767 if (!protocol || !*protocol || !transport || !*transport || !domain || !*domain) {
768 purple_debug_error("dnssrv", "Wrong arguments\n");
769 cb(NULL, 0, extradata);
770 g_return_val_if_reached(NULL);
771 }
772
773 proxy_type = purple_proxy_info_get_proxy_type(
774 purple_proxy_get_setup(account));
775 if (proxy_type == PURPLE_PROXY_TOR) {
776 purple_debug_info("dnssrv", "Aborting SRV lookup in Tor Proxy mode.\n");
777 cb(NULL, 0, extradata);
778 return NULL;
779 }
780
781 #ifdef USE_IDN
782 if (!dns_str_is_ascii(domain)) {
783 int ret = purple_network_convert_idn_to_ascii(domain, &hostname);
784 if (ret != 0) {
785 purple_debug_error("dnssrv", "IDNA ToASCII failed\n");
786 cb(NULL, 0, extradata);
787 return NULL;
788 }
789 } else /* Fallthru is intentional */
790 #endif
791 hostname = g_strdup(domain);
792
793 query = g_strdup_printf("_%s._%s.%s", protocol, transport, hostname);
794 purple_debug_info("dnssrv","querying SRV record for %s: %s\n", domain,
795 query);
796 g_free(hostname);
797
798 query_data = query_data_new(PurpleDnsTypeSrv, query, extradata);
799 query_data->cb.srv = cb;
800
801 if (purple_srv_txt_query_ui_resolve(query_data))
802 {
803 return query_data;
804 }
805
806 #ifndef _WIN32
807 if(pipe(in) || pipe(out)) {
808 purple_debug_error("dnssrv", "Could not create pipe\n");
809 g_free(query);
810 g_free(query_data);
811 cb(NULL, 0, extradata);
812 return NULL;
813 }
814
815 /*
816 * TODO: We should put a cap on the number of forked processes that we
817 * allow at any given time. If we get too many requests they
818 * should be put into a queue and handled later. (This is what
819 * we do for A record lookups.)
820 */
821 pid = fork();
822 if (pid == -1) {
823 purple_debug_error("dnssrv", "Could not create process!\n");
824 g_free(query);
825 g_free(query_data);
826 cb(NULL, 0, extradata);
827 return NULL;
828 }
829
830 /* Child */
831 if (pid == 0)
832 {
833 g_free(query);
834 g_free(query_data);
835
836 close(out[0]);
837 close(in[1]);
838 resolve(in[0], out[1]);
839 /* resolve() does not return */
840 }
841
842 close(out[1]);
843 close(in[0]);
844
845 internal_query.type = T_SRV;
846 strncpy(internal_query.query, query, 255);
847 internal_query.query[255] = '\0';
848
849 if (write(in[1], &internal_query, sizeof(internal_query)) < 0)
850 purple_debug_error("dnssrv", "Could not write to SRV resolver\n");
851
852 query_data->pid = pid;
853 query_data->fd_out = out[0];
854 query_data->fd_in = in[1];
855 query_data->handle = purple_input_add(out[0], PURPLE_INPUT_READ, resolved, query_data);
856
857 return query_data;
858 #else
859 query_data->resolver = g_thread_try_new("dnssrv srv resolver", res_thread, query_data, &err);
860 if (query_data->resolver == NULL) {
861 query_data->error_message = g_strdup_printf("SRV thread create failure: %s\n", (err && err->message) ? err->message : "");
862 g_error_free(err);
863 }
864 else
865 g_thread_unref(query_data->resolver);
866
867 /* The query isn't going to happen, so finish the SRV lookup now.
868 * Asynchronously call the callback since stuff may not expect
869 * the callback to be called before this returns */
870 if (query_data->error_message != NULL)
871 query_data->handle = purple_timeout_add(0, res_main_thread_cb, query_data);
872
873 return query_data;
874 #endif
875 }
876
877 PurpleSrvTxtQueryData *purple_txt_resolve(PurpleAccount *account,
878 const char *owner, const char *domain, PurpleTxtCallback cb,
879 gpointer extradata)
880 {
881 char *query;
882 char *hostname;
883 PurpleSrvTxtQueryData *query_data;
884 PurpleProxyType proxy_type;
885 #ifndef _WIN32
886 PurpleSrvInternalQuery internal_query;
887 int in[2], out[2];
888 int pid;
889 #else
890 GError* err = NULL;
891 #endif
892
893 proxy_type = purple_proxy_info_get_proxy_type(
894 purple_proxy_get_setup(account));
895 if (proxy_type == PURPLE_PROXY_TOR) {
896 purple_debug_info("dnssrv", "Aborting TXT lookup in Tor Proxy mode.\n");
897 cb(NULL, extradata);
898 return NULL;
899 }
900
901 #ifdef USE_IDN
902 if (!dns_str_is_ascii(domain)) {
903 int ret = purple_network_convert_idn_to_ascii(domain, &hostname);
904 if (ret != 0) {
905 purple_debug_error("dnssrv", "IDNA ToASCII failed\n");
906 cb(NULL, extradata);
907 return NULL;
908 }
909 } else /* fallthru is intentional */
910 #endif
911 hostname = g_strdup(domain);
912
913 query = g_strdup_printf("%s.%s", owner, hostname);
914 purple_debug_info("dnssrv","querying TXT record for %s: %s\n", domain,
915 query);
916 g_free(hostname);
917
918 query_data = query_data_new(PurpleDnsTypeTxt, query, extradata);
919 query_data->cb.txt = cb;
920
921 if (purple_srv_txt_query_ui_resolve(query_data)) {
922 /* query intentionally not freed
923 */
924 return query_data;
925 }
926
927 #ifndef _WIN32
928 if(pipe(in) || pipe(out)) {
929 purple_debug_error("dnssrv", "Could not create pipe\n");
930 g_free(query);
931 g_free(query_data);
932 cb(NULL, extradata);
933 return NULL;
934 }
935
936 /*
937 * TODO: We should put a cap on the number of forked processes that we
938 * allow at any given time. If we get too many requests they
939 * should be put into a queue and handled later. (This is what
940 * we do for A record lookups.)
941 */
942 pid = fork();
943 if (pid == -1) {
944 purple_debug_error("dnssrv", "Could not create process!\n");
945 g_free(query);
946 g_free(query_data);
947 cb(NULL, extradata);
948 return NULL;
949 }
950
951 /* Child */
952 if (pid == 0)
953 {
954 g_free(query);
955 g_free(query_data);
956
957 close(out[0]);
958 close(in[1]);
959 resolve(in[0], out[1]);
960 /* resolve() does not return */
961 }
962
963 close(out[1]);
964 close(in[0]);
965
966 internal_query.type = T_TXT;
967 strncpy(internal_query.query, query, 255);
968 internal_query.query[255] = '\0';
969
970 if (write(in[1], &internal_query, sizeof(internal_query)) < 0)
971 purple_debug_error("dnssrv", "Could not write to TXT resolver\n");
972
973 query_data->pid = pid;
974 query_data->fd_out = out[0];
975 query_data->fd_in = in[1];
976 query_data->handle = purple_input_add(out[0], PURPLE_INPUT_READ, resolved, query_data);
977
978 return query_data;
979 #else
980 query_data->resolver = g_thread_try_new("dnssrv srv resolver", res_thread, query_data, &err);
981 if (query_data->resolver == NULL) {
982 query_data->error_message = g_strdup_printf("TXT thread create failure: %s\n", (err && err->message) ? err->message : "");
983 g_error_free(err);
984 }
985 else
986 g_thread_unref(query_data->resolver);
987
988 /* The query isn't going to happen, so finish the TXT lookup now.
989 * Asynchronously call the callback since stuff may not expect
990 * the callback to be called before this returns */
991 if (query_data->error_message != NULL)
992 query_data->handle = purple_timeout_add(0, res_main_thread_cb, query_data);
993
994 return query_data;
995 #endif
996 }
997
998 const gchar *
999 purple_txt_response_get_content(PurpleTxtResponse *resp)
1000 {
1001 g_return_val_if_fail(resp != NULL, NULL);
1002
1003 return resp->content;
1004 }
1005
1006 void purple_txt_response_destroy(PurpleTxtResponse *resp)
1007 {
1008 g_return_if_fail(resp != NULL);
1009
1010 g_free(resp->content);
1011 g_free(resp);
1012 }
1013
1014 /*
1015 * Only used as the callback for the ui ops.
1016 */
1017 static void
1018 purple_srv_query_resolved(PurpleSrvTxtQueryData *query_data, GList *records)
1019 {
1020 GList *l;
1021 PurpleSrvResponse *records_array;
1022 int i = 0, length;
1023
1024 g_return_if_fail(records != NULL);
1025
1026 if (query_data->cb.srv == NULL) {
1027 purple_srv_txt_query_destroy(query_data);
1028
1029 while (records) {
1030 g_free(records->data);
1031 records = g_list_delete_link(records, records);
1032 }
1033 return;
1034 }
1035
1036 records = purple_srv_sort(records);
1037 length = g_list_length(records);
1038
1039 purple_debug_info("dnssrv", "SRV records resolved for %s, count: %d\n",
1040 query_data->query, length);
1041
1042 records_array = g_new(PurpleSrvResponse, length);
1043 for (l = records; l; l = l->next, i++) {
1044 records_array[i] = *(PurpleSrvResponse *)l->data;
1045 }
1046
1047 query_data->cb.srv(records_array, length, query_data->extradata);
1048
1049 purple_srv_txt_query_destroy(query_data);
1050
1051 while (records) {
1052 g_free(records->data);
1053 records = g_list_delete_link(records, records);
1054 }
1055 }
1056
1057 /*
1058 * Only used as the callback for the ui ops.
1059 */
1060 static void
1061 purple_txt_query_resolved(PurpleSrvTxtQueryData *query_data, GList *entries)
1062 {
1063 g_return_if_fail(entries != NULL);
1064
1065 purple_debug_info("dnssrv", "TXT entries resolved for %s, count: %d\n", query_data->query, g_list_length(entries));
1066
1067 /* the callback should g_free the entries.
1068 */
1069 if (query_data->cb.txt != NULL)
1070 query_data->cb.txt(entries, query_data->extradata);
1071 else {
1072 while (entries) {
1073 g_free(entries->data);
1074 entries = g_list_delete_link(entries, entries);
1075 }
1076 }
1077
1078 purple_srv_txt_query_destroy(query_data);
1079 }
1080
1081 static void
1082 purple_srv_query_failed(PurpleSrvTxtQueryData *query_data, const gchar *error_message)
1083 {
1084 purple_debug_error("dnssrv", "%s\n", error_message);
1085
1086 if (query_data->cb.srv != NULL)
1087 query_data->cb.srv(NULL, 0, query_data->extradata);
1088
1089 purple_srv_txt_query_destroy(query_data);
1090 }
1091
1092 static gboolean
1093 purple_srv_txt_query_ui_resolve(PurpleSrvTxtQueryData *query_data)
1094 {
1095 PurpleSrvTxtQueryUiOps *ops = purple_srv_txt_query_get_ui_ops();
1096
1097 if (ops && ops->resolve)
1098 return ops->resolve(query_data, (query_data->type == T_SRV ? purple_srv_query_resolved : purple_txt_query_resolved), purple_srv_query_failed);
1099
1100 return FALSE;
1101 }
1102
1103 void
1104 purple_srv_txt_query_set_ui_ops(PurpleSrvTxtQueryUiOps *ops)
1105 {
1106 srv_txt_query_ui_ops = ops;
1107 }
1108
1109 PurpleSrvTxtQueryUiOps *
1110 purple_srv_txt_query_get_ui_ops(void)
1111 {
1112 /* It is perfectly acceptable for srv_txt_query_ui_ops to be NULL; this just
1113 * means that the default platform-specific implementation will be used.
1114 */
1115 return srv_txt_query_ui_ops;
1116 }
1117
1118 char *
1119 purple_srv_txt_query_get_query(PurpleSrvTxtQueryData *query_data)
1120 {
1121 g_return_val_if_fail(query_data != NULL, NULL);
1122
1123 return query_data->query;
1124 }
1125
1126
1127 int
1128 purple_srv_txt_query_get_query_type(PurpleSrvTxtQueryData *query_data)
1129 {
1130 g_return_val_if_fail(query_data != NULL, 0);
1131
1132 return query_data->type;
1133 }
1134
1135 /**************************************************************************
1136 * GBoxed code
1137 **************************************************************************/
1138 static PurpleSrvTxtQueryUiOps *
1139 purple_srv_txt_query_ui_ops_copy(PurpleSrvTxtQueryUiOps *ops)
1140 {
1141 PurpleSrvTxtQueryUiOps *ops_new;
1142
1143 g_return_val_if_fail(ops != NULL, NULL);
1144
1145 ops_new = g_new(PurpleSrvTxtQueryUiOps, 1);
1146 *ops_new = *ops;
1147
1148 return ops_new;
1149 }
1150
1151 GType
1152 purple_srv_txt_query_ui_ops_get_type(void)
1153 {
1154 static GType type = 0;
1155
1156 if (type == 0) {
1157 type = g_boxed_type_register_static("PurpleSrvTxtQueryUiOps",
1158 (GBoxedCopyFunc)purple_srv_txt_query_ui_ops_copy,
1159 (GBoxedFreeFunc)g_free);
1160 }
1161
1162 return type;
1163 }

mercurial