Fix crashes when filenames end up being NULL in some prpls.
[pidgin-git.git] / libpurple / dnssrv.c
blob6fce86cc3d86bdcca18f30814abca720d11a215c
1 /**
2 * @file dnssrv.c
3 */
5 /* purple
7 * Copyright (C) 2005 Thomas Butter <butter@uni-mannheim.de>
9 * This program is free software; you can redistribute it and/or modify
10 * it under the terms of the GNU General Public License as published by
11 * the Free Software Foundation; either version 2 of the License, or
12 * (at your option) any later version.
14 * This program is distributed in the hope that it will be useful,
15 * but WITHOUT ANY WARRANTY; without even the implied warranty of
16 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17 * GNU General Public License for more details.
19 * You should have received a copy of the GNU General Public License
20 * along with this program; if not, write to the Free Software
21 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA
24 #include "internal.h"
25 #include "util.h"
27 #ifndef _WIN32
28 #include <arpa/nameser.h>
29 #include <resolv.h>
30 #ifdef HAVE_ARPA_NAMESER_COMPAT_H
31 #include <arpa/nameser_compat.h>
32 #endif
33 #ifndef T_SRV
34 #define T_SRV 33
35 #endif
36 #else
37 #include <windns.h>
38 /* Missing from the mingw headers */
39 #ifndef DNS_TYPE_SRV
40 # define DNS_TYPE_SRV 33
41 #endif
42 #endif
44 #include "dnssrv.h"
45 #include "eventloop.h"
46 #include "debug.h"
48 #ifndef _WIN32
49 typedef union {
50 HEADER hdr;
51 u_char buf[1024];
52 } queryans;
53 #else
54 static DNS_STATUS (WINAPI *MyDnsQuery_UTF8) (
55 PCSTR lpstrName, WORD wType, DWORD fOptions,
56 PIP4_ARRAY aipServers, PDNS_RECORD* ppQueryResultsSet,
57 PVOID* pReserved) = NULL;
58 static void (WINAPI *MyDnsRecordListFree) (PDNS_RECORD pRecordList,
59 DNS_FREE_TYPE FreeType) = NULL;
60 #endif
62 struct _PurpleSrvQueryData {
63 PurpleSrvCallback cb;
64 gpointer extradata;
65 guint handle;
66 #ifdef _WIN32
67 GThread *resolver;
68 char *query;
69 char *error_message;
70 GSList *results;
71 #else
72 int fd_in, fd_out;
73 pid_t pid;
74 #endif
77 static gint
78 responsecompare(gconstpointer ar, gconstpointer br)
80 PurpleSrvResponse *a = (PurpleSrvResponse*)ar;
81 PurpleSrvResponse *b = (PurpleSrvResponse*)br;
83 if(a->pref == b->pref) {
84 if(a->weight == b->weight)
85 return 0;
86 if(a->weight < b->weight)
87 return -1;
88 return 1;
90 if(a->pref < b->pref)
91 return -1;
92 return 1;
95 #ifndef _WIN32
97 G_GNUC_NORETURN static void
98 resolve(int in, int out)
100 GList *ret = NULL;
101 PurpleSrvResponse *srvres;
102 queryans answer;
103 int size;
104 int qdcount;
105 int ancount;
106 guchar *end;
107 guchar *cp;
108 gchar name[256];
109 guint16 type, dlen, pref, weight, port;
110 gchar query[256];
112 #ifdef HAVE_SIGNAL_H
113 purple_restore_default_signal_handlers();
114 #endif
116 if (read(in, query, 256) <= 0) {
117 close(out);
118 close(in);
119 _exit(0);
122 size = res_query( query, C_IN, T_SRV, (u_char*)&answer, sizeof( answer));
124 qdcount = ntohs(answer.hdr.qdcount);
125 ancount = ntohs(answer.hdr.ancount);
127 cp = (guchar*)&answer + sizeof(HEADER);
128 end = (guchar*)&answer + size;
130 /* skip over unwanted stuff */
131 while (qdcount-- > 0 && cp < end) {
132 size = dn_expand( (unsigned char*)&answer, end, cp, name, 256);
133 if(size < 0) goto end;
134 cp += size + QFIXEDSZ;
137 while (ancount-- > 0 && cp < end) {
138 size = dn_expand((unsigned char*)&answer, end, cp, name, 256);
139 if(size < 0)
140 goto end;
142 cp += size;
144 GETSHORT(type,cp);
146 /* skip ttl and class since we already know it */
147 cp += 6;
149 GETSHORT(dlen,cp);
151 if (type == T_SRV) {
152 GETSHORT(pref,cp);
154 GETSHORT(weight,cp);
156 GETSHORT(port,cp);
158 size = dn_expand( (unsigned char*)&answer, end, cp, name, 256);
159 if(size < 0 )
160 goto end;
162 cp += size;
164 srvres = g_new0(PurpleSrvResponse, 1);
165 strcpy(srvres->hostname, name);
166 srvres->pref = pref;
167 srvres->port = port;
168 srvres->weight = weight;
170 ret = g_list_insert_sorted(ret, srvres, responsecompare);
171 } else {
172 cp += dlen;
176 end:
177 size = g_list_length(ret);
178 write(out, &size, sizeof(int));
179 while (ret != NULL)
181 write(out, ret->data, sizeof(PurpleSrvResponse));
182 g_free(ret->data);
183 ret = g_list_remove(ret, ret->data);
186 close(out);
187 close(in);
189 _exit(0);
192 static void
193 resolved(gpointer data, gint source, PurpleInputCondition cond)
195 int size;
196 PurpleSrvQueryData *query_data = (PurpleSrvQueryData*)data;
197 PurpleSrvResponse *res;
198 PurpleSrvResponse *tmp;
199 int i;
200 PurpleSrvCallback cb = query_data->cb;
201 int status;
203 if (read(source, &size, sizeof(int)) == sizeof(int))
205 ssize_t red;
206 purple_debug_info("dnssrv","found %d SRV entries\n", size);
207 tmp = res = g_new0(PurpleSrvResponse, size);
208 for (i = 0; i < size; i++) {
209 red = read(source, tmp++, sizeof(PurpleSrvResponse));
210 if (red != sizeof(PurpleSrvResponse)) {
211 purple_debug_error("dnssrv","unable to read srv "
212 "response: %s\n", g_strerror(errno));
213 size = 0;
214 g_free(res);
215 res = NULL;
219 else
221 purple_debug_info("dnssrv","found 0 SRV entries; errno is %i\n", errno);
222 size = 0;
223 res = NULL;
226 cb(res, size, query_data->extradata);
227 waitpid(query_data->pid, &status, 0);
229 purple_srv_cancel(query_data);
232 #else /* _WIN32 */
234 /** The Jabber Server code was inspiration for parts of this. */
236 static gboolean
237 res_main_thread_cb(gpointer data)
239 PurpleSrvResponse *srvres = NULL;
240 int size = 0;
241 PurpleSrvQueryData *query_data = data;
243 if(query_data->error_message != NULL)
244 purple_debug_error("dnssrv", query_data->error_message);
245 else {
246 PurpleSrvResponse *srvres_tmp = NULL;
247 GSList *lst = query_data->results;
249 size = g_slist_length(lst);
251 if(query_data->cb && size > 0)
252 srvres_tmp = srvres = g_new0(PurpleSrvResponse, size);
253 while (lst) {
254 if(query_data->cb)
255 memcpy(srvres_tmp++, lst->data, sizeof(PurpleSrvResponse));
256 g_free(lst->data);
257 lst = g_slist_remove(lst, lst->data);
260 query_data->results = NULL;
262 purple_debug_info("dnssrv", "found %d SRV entries\n", size);
265 if(query_data->cb)
266 query_data->cb(srvres, size, query_data->extradata);
268 query_data->resolver = NULL;
269 query_data->handle = 0;
271 purple_srv_cancel(query_data);
273 return FALSE;
276 static gpointer
277 res_thread(gpointer data)
279 PDNS_RECORD dr = NULL;
280 int type = DNS_TYPE_SRV;
281 DNS_STATUS ds;
282 PurpleSrvQueryData *query_data = data;
284 ds = MyDnsQuery_UTF8(query_data->query, type, DNS_QUERY_STANDARD, NULL, &dr, NULL);
285 if (ds != ERROR_SUCCESS) {
286 gchar *msg = g_win32_error_message(ds);
287 query_data->error_message = g_strdup_printf("Couldn't look up SRV record. %s (%lu).\n", msg, ds);
288 g_free(msg);
289 } else {
290 PDNS_RECORD dr_tmp;
291 GSList *lst = NULL;
292 DNS_SRV_DATA *srv_data;
293 PurpleSrvResponse *srvres;
295 for (dr_tmp = dr; dr_tmp != NULL; dr_tmp = dr_tmp->pNext) {
296 /* Discard any incorrect entries. I'm not sure if this is necessary */
297 if (dr_tmp->wType != type || strcmp(dr_tmp->pName, query_data->query) != 0) {
298 continue;
301 srv_data = &dr_tmp->Data.SRV;
302 srvres = g_new0(PurpleSrvResponse, 1);
303 strncpy(srvres->hostname, srv_data->pNameTarget, 255);
304 srvres->hostname[255] = '\0';
305 srvres->pref = srv_data->wPriority;
306 srvres->port = srv_data->wPort;
307 srvres->weight = srv_data->wWeight;
309 lst = g_slist_insert_sorted(lst, srvres, responsecompare);
312 MyDnsRecordListFree(dr, DnsFreeRecordList);
313 query_data->results = lst;
316 /* back to main thread */
317 /* Note: this should *not* be attached to query_data->handle - it will cause leakage */
318 purple_timeout_add(0, res_main_thread_cb, query_data);
320 g_thread_exit(NULL);
321 return NULL;
324 #endif
326 PurpleSrvQueryData *
327 purple_srv_resolve(const char *protocol, const char *transport, const char *domain, PurpleSrvCallback cb, gpointer extradata)
329 char *query;
330 PurpleSrvQueryData *query_data;
331 #ifndef _WIN32
332 int in[2], out[2];
333 int pid;
334 #else
335 GError* err = NULL;
336 static gboolean initialized = FALSE;
337 #endif
339 if (!protocol || !*protocol || !transport || !*transport || !domain || !*domain) {
340 purple_debug_error("dnssrv", "Wrong arguments\n");
341 cb(NULL, 0, extradata);
342 g_return_val_if_reached(NULL);
345 query = g_strdup_printf("_%s._%s.%s", protocol, transport, domain);
346 purple_debug_info("dnssrv","querying SRV record for %s\n", query);
348 #ifndef _WIN32
349 if(pipe(in) || pipe(out)) {
350 purple_debug_error("dnssrv", "Could not create pipe\n");
351 g_free(query);
352 cb(NULL, 0, extradata);
353 return NULL;
356 pid = fork();
357 if (pid == -1) {
358 purple_debug_error("dnssrv", "Could not create process!\n");
359 cb(NULL, 0, extradata);
360 g_free(query);
361 return NULL;
364 /* Child */
365 if (pid == 0)
367 g_free(query);
369 close(out[0]);
370 close(in[1]);
371 resolve(in[0], out[1]);
372 /* resolve() does not return */
375 close(out[1]);
376 close(in[0]);
378 if (write(in[1], query, strlen(query)+1) < 0)
379 purple_debug_error("dnssrv", "Could not write to SRV resolver\n");
381 query_data = g_new0(PurpleSrvQueryData, 1);
382 query_data->cb = cb;
383 query_data->extradata = extradata;
384 query_data->pid = pid;
385 query_data->fd_out = out[0];
386 query_data->fd_in = in[1];
387 query_data->handle = purple_input_add(out[0], PURPLE_INPUT_READ, resolved, query_data);
389 g_free(query);
391 return query_data;
392 #else
393 if (!initialized) {
394 MyDnsQuery_UTF8 = (void*) wpurple_find_and_loadproc("dnsapi.dll", "DnsQuery_UTF8");
395 MyDnsRecordListFree = (void*) wpurple_find_and_loadproc(
396 "dnsapi.dll", "DnsRecordListFree");
397 initialized = TRUE;
400 query_data = g_new0(PurpleSrvQueryData, 1);
401 query_data->cb = cb;
402 query_data->query = query;
403 query_data->extradata = extradata;
405 if (!MyDnsQuery_UTF8 || !MyDnsRecordListFree)
406 query_data->error_message = g_strdup("System missing DNS API (Requires W2K+)\n");
407 else {
408 query_data->resolver = g_thread_create(res_thread, query_data, FALSE, &err);
409 if (query_data->resolver == NULL) {
410 query_data->error_message = g_strdup_printf("SRV thread create failure: %s\n", (err && err->message) ? err->message : "");
411 g_error_free(err);
415 /* The query isn't going to happen, so finish the SRV lookup now.
416 * Asynchronously call the callback since stuff may not expect
417 * the callback to be called before this returns */
418 if (query_data->error_message != NULL)
419 query_data->handle = purple_timeout_add(0, res_main_thread_cb, query_data);
421 return query_data;
422 #endif
425 void
426 purple_srv_cancel(PurpleSrvQueryData *query_data)
428 if (query_data->handle > 0)
429 purple_input_remove(query_data->handle);
430 #ifdef _WIN32
431 if (query_data->resolver != NULL)
434 * It's not really possible to kill a thread. So instead we
435 * just set the callback to NULL and let the DNS lookup
436 * finish.
438 query_data->cb = NULL;
439 return;
441 g_free(query_data->query);
442 g_free(query_data->error_message);
443 #else
444 close(query_data->fd_out);
445 close(query_data->fd_in);
446 #endif
447 g_free(query_data);