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
28 #include <arpa/nameser.h>
30 #ifdef HAVE_ARPA_NAMESER_COMPAT_H
31 #include <arpa/nameser_compat.h>
38 /* Missing from the mingw headers */
40 # define DNS_TYPE_SRV 33
45 #include "eventloop.h"
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
;
62 struct _PurpleSrvQueryData
{
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
)
86 if(a
->weight
< b
->weight
)
97 G_GNUC_NORETURN
static void
98 resolve(int in
, int out
)
101 PurpleSrvResponse
*srvres
;
109 guint16 type
, dlen
, pref
, weight
, port
;
113 purple_restore_default_signal_handlers();
116 if (read(in
, query
, 256) <= 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);
146 /* skip ttl and class since we already know it */
158 size
= dn_expand( (unsigned char*)&answer
, end
, cp
, name
, 256);
164 srvres
= g_new0(PurpleSrvResponse
, 1);
165 strcpy(srvres
->hostname
, name
);
168 srvres
->weight
= weight
;
170 ret
= g_list_insert_sorted(ret
, srvres
, responsecompare
);
177 size
= g_list_length(ret
);
178 write(out
, &size
, sizeof(int));
181 write(out
, ret
->data
, sizeof(PurpleSrvResponse
));
183 ret
= g_list_remove(ret
, ret
->data
);
193 resolved(gpointer data
, gint source
, PurpleInputCondition cond
)
196 PurpleSrvQueryData
*query_data
= (PurpleSrvQueryData
*)data
;
197 PurpleSrvResponse
*res
;
198 PurpleSrvResponse
*tmp
;
200 PurpleSrvCallback cb
= query_data
->cb
;
203 if (read(source
, &size
, sizeof(int)) == sizeof(int))
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
));
221 purple_debug_info("dnssrv","found 0 SRV entries; errno is %i\n", errno
);
226 cb(res
, size
, query_data
->extradata
);
227 waitpid(query_data
->pid
, &status
, 0);
229 purple_srv_cancel(query_data
);
234 /** The Jabber Server code was inspiration for parts of this. */
237 res_main_thread_cb(gpointer data
)
239 PurpleSrvResponse
*srvres
= NULL
;
241 PurpleSrvQueryData
*query_data
= data
;
243 if(query_data
->error_message
!= NULL
)
244 purple_debug_error("dnssrv", query_data
->error_message
);
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
);
255 memcpy(srvres_tmp
++, lst
->data
, sizeof(PurpleSrvResponse
));
257 lst
= g_slist_remove(lst
, lst
->data
);
260 query_data
->results
= NULL
;
262 purple_debug_info("dnssrv", "found %d SRV entries\n", size
);
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
);
277 res_thread(gpointer data
)
279 PDNS_RECORD dr
= NULL
;
280 int type
= DNS_TYPE_SRV
;
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
);
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) {
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
);
327 purple_srv_resolve(const char *protocol
, const char *transport
, const char *domain
, PurpleSrvCallback cb
, gpointer extradata
)
330 PurpleSrvQueryData
*query_data
;
336 static gboolean initialized
= FALSE
;
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
);
349 if(pipe(in
) || pipe(out
)) {
350 purple_debug_error("dnssrv", "Could not create pipe\n");
352 cb(NULL
, 0, extradata
);
358 purple_debug_error("dnssrv", "Could not create process!\n");
359 cb(NULL
, 0, extradata
);
371 resolve(in
[0], out
[1]);
372 /* resolve() does not return */
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);
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
);
394 MyDnsQuery_UTF8
= (void*) wpurple_find_and_loadproc("dnsapi.dll", "DnsQuery_UTF8");
395 MyDnsRecordListFree
= (void*) wpurple_find_and_loadproc(
396 "dnsapi.dll", "DnsRecordListFree");
400 query_data
= g_new0(PurpleSrvQueryData
, 1);
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");
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
: "");
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
);
426 purple_srv_cancel(PurpleSrvQueryData
*query_data
)
428 if (query_data
->handle
> 0)
429 purple_input_remove(query_data
->handle
);
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
438 query_data
->cb
= NULL
;
441 g_free(query_data
->query
);
442 g_free(query_data
->error_message
);
444 close(query_data
->fd_out
);
445 close(query_data
->fd_in
);