Mercurial > pidgin
diff libgaim/dnssrv.c @ 14192:60b1bc8dbf37
[gaim-migrate @ 16863]
Renamed 'core' to 'libgaim'
committer: Tailor Script <tailor@pidgin.im>
author | Evan Schoenberg <evan.s@dreskin.net> |
---|---|
date | Sat, 19 Aug 2006 01:50:10 +0000 |
parents | |
children | 9ad313800b19 |
line wrap: on
line diff
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/libgaim/dnssrv.c Sat Aug 19 01:50:10 2006 +0000 @@ -0,0 +1,358 @@ +/** + * @file dnssrv.c + * + * gaim + * + * Copyright (C) 2005 Thomas Butter <butter@uni-mannheim.de> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include "internal.h" + +#ifndef _WIN32 +#include <resolv.h> +#include <arpa/nameser.h> +#ifdef HAVE_ARPA_NAMESER_COMPAT_H +#include <arpa/nameser_compat.h> +#endif +#ifndef T_SRV +#define T_SRV 33 +#endif +#else +#include <windns.h> +/* Missing from the mingw headers */ +#ifndef DNS_TYPE_SRV +# define DNS_TYPE_SRV 33 +#endif +#endif + +#include "dnssrv.h" +#include "eventloop.h" +#include "debug.h" + +#ifndef _WIN32 +typedef union { + HEADER hdr; + u_char buf[1024]; +} queryans; +#else +static DNS_STATUS WINAPI (*MyDnsQuery_UTF8) ( + PCSTR lpstrName, WORD wType, DWORD fOptions, + PIP4_ARRAY aipServers, PDNS_RECORD* ppQueryResultsSet, + PVOID* pReserved) = NULL; +static void WINAPI (*MyDnsRecordListFree) (PDNS_RECORD pRecordList, + DNS_FREE_TYPE FreeType) = NULL; +#endif + +struct resdata { + GaimSRVCallback cb; + gpointer extradata; +#ifndef _WIN32 + guint handle; +#else + char *query; + char *errmsg; + GSList *results; +#endif +}; + +static gint responsecompare(gconstpointer ar, gconstpointer br) { + GaimSrvResponse *a = (GaimSrvResponse*)ar; + GaimSrvResponse *b = (GaimSrvResponse*)br; + + if(a->pref == b->pref) { + if(a->weight == b->weight) + return 0; + if(a->weight < b->weight) + return -1; + return 1; + } + if(a->pref < b->pref) + return -1; + return 1; +} +#ifndef _WIN32 +static void resolve(int in, int out) { + GList *ret = NULL; + GaimSrvResponse *srvres; + queryans answer; + int size; + int qdcount; + int ancount; + guchar *end; + guchar *cp; + gchar name[256]; + guint16 type, dlen, pref, weight, port; + gchar query[256]; + + if(read(in, query, 256) <= 0) { + _exit(0); + } + size = res_query( query, C_IN, T_SRV, (u_char*)&answer, sizeof( answer)); + + qdcount = ntohs(answer.hdr.qdcount); + ancount = ntohs(answer.hdr.ancount); + + + cp = (guchar*)&answer + sizeof(HEADER); + end = (guchar*)&answer + size; + + /* skip over unwanted stuff */ + while (qdcount-- > 0 && cp < end) { + size = dn_expand( (unsigned char*)&answer, end, cp, name, 256); + if(size < 0) goto end; + cp += size + QFIXEDSZ; + } + + while (ancount-- > 0 && cp < end) { + size = dn_expand((unsigned char*)&answer, end, cp, name, 256); + if(size < 0) + goto end; + + cp += size; + + GETSHORT(type,cp); + + /* skip ttl and class since we already know it */ + cp += 6; + + GETSHORT(dlen,cp); + + if (type == T_SRV) { + GETSHORT(pref,cp); + + GETSHORT(weight,cp); + + GETSHORT(port,cp); + + size = dn_expand( (unsigned char*)&answer, end, cp, name, 256); + if(size < 0 ) + goto end; + + cp += size; + + srvres = g_new0(GaimSrvResponse, 1); + strcpy(srvres->hostname, name); + srvres->pref = pref; + srvres->port = port; + srvres->weight = weight; + + ret = g_list_insert_sorted(ret, srvres, responsecompare); + } else { + cp += dlen; + } + } +end: size = g_list_length(ret); + write(out, &size, sizeof(int)); + while(g_list_first(ret)) { + write(out, g_list_first(ret)->data, sizeof(GaimSrvResponse)); + g_free(g_list_first(ret)->data); + ret = g_list_remove(ret, g_list_first(ret)->data); + } + + /* Should the resolver be reused? + * There is most likely only 1 SRV queries per prpl... + */ + _exit(0); +} + +static void resolved(gpointer data, gint source, GaimInputCondition cond) { + int size; + struct resdata *rdata = (struct resdata*)data; + GaimSrvResponse *res; + GaimSrvResponse *tmp; + int i; + GaimSRVCallback cb = rdata->cb; + + read(source, &size, sizeof(int)); + gaim_debug_info("srv","found %d SRV entries\n", size); + tmp = res = g_new0(GaimSrvResponse, size); + i = size; + while(i) { + read(source, tmp++, sizeof(GaimSrvResponse)); + i--; + } + cb(res, size, rdata->extradata); + gaim_input_remove(rdata->handle); + g_free(rdata); +} + +#else /* _WIN32 */ + +/** The Jabber Server code was inspiration for parts of this. */ + +static gboolean res_main_thread_cb(gpointer data) { + GaimSrvResponse *srvres = NULL; + int size = 0; + struct resdata *rdata = data; + + if (rdata->errmsg != NULL) { + gaim_debug_error("srv", rdata->errmsg); + g_free(rdata->errmsg); + } else { + GaimSrvResponse *srvres_tmp; + GSList *lst = rdata->results; + + size = g_slist_length(rdata->results); + + srvres_tmp = srvres = g_new0(GaimSrvResponse, size); + while (lst) { + memcpy(srvres_tmp++, lst->data, sizeof(GaimSrvResponse)); + g_free(lst->data); + lst = g_slist_remove(lst, lst->data); + } + + rdata->results = lst; + } + + gaim_debug_info("srv", "found %d SRV entries\n", size); + + rdata->cb(srvres, size, rdata->extradata); + + g_free(rdata->query); + g_free(rdata); + + return FALSE; +} + +static gpointer res_thread(gpointer data) { + PDNS_RECORD dr = NULL; + int type = DNS_TYPE_SRV; + DNS_STATUS ds; + struct resdata *rdata = data; + + ds = MyDnsQuery_UTF8(rdata->query, type, DNS_QUERY_STANDARD, NULL, &dr, NULL); + if (ds != ERROR_SUCCESS) { + gchar *msg = g_win32_error_message(ds); + rdata->errmsg = g_strdup_printf("Couldn't look up SRV record. %s (%lu).\n", msg, ds); + g_free(msg); + } else { + PDNS_RECORD dr_tmp; + GSList *lst = NULL; + DNS_SRV_DATA *srv_data; + GaimSrvResponse *srvres; + + for (dr_tmp = dr; dr_tmp != NULL; dr_tmp = dr_tmp->pNext) { + /* Discard any incorrect entries. I'm not sure if this is necessary */ + if (dr_tmp->wType != type || strcmp(dr_tmp->pName, rdata->query) != 0) { + continue; + } + + srv_data = &dr_tmp->Data.SRV; + srvres = g_new0(GaimSrvResponse, 1); + strncpy(srvres->hostname, srv_data->pNameTarget, 255); + srvres->hostname[255] = '\0'; + srvres->pref = srv_data->wPriority; + srvres->port = srv_data->wPort; + srvres->weight = srv_data->wWeight; + + lst = g_slist_insert_sorted(lst, srvres, responsecompare); + } + + MyDnsRecordListFree(dr, DnsFreeRecordList); + rdata->results = lst; + } + + /* back to main thread */ + g_idle_add(res_main_thread_cb, rdata); + + g_thread_exit(NULL); + return NULL; +} + +#endif + +/* + * TODO: It would be really good if this returned some sort of handle + * that we could use to cancel the DNS query. As it is now, + * each callback has to check to make sure gc is still valid. + * And that is ugly. + */ +void gaim_srv_resolve(const char *protocol, const char *transport, const char *domain, GaimSRVCallback cb, gpointer extradata) { + char *query = g_strdup_printf("_%s._%s.%s",protocol, transport, domain); + struct resdata *rdata; +#ifndef _WIN32 + int in[2], out[2]; + int pid; + gaim_debug_info("srv","querying SRV record for %s\n", query); + if(pipe(in) || pipe(out)) { + gaim_debug_error("srv", "Could not create pipe\n"); + g_free(query); + cb(NULL, 0, extradata); + return; + } + + pid = fork(); + + if(pid == -1) { + gaim_debug_error("srv","Could not create process!\n"); + cb(NULL, 0, extradata); + g_free(query); + return; + } + /* Child */ + if( pid == 0 ) { + close(out[0]); + close(in[1]); + resolve(in[0], out[1]); + } + + close(out[1]); + close(in[0]); + + if(write(in[1], query, strlen(query)+1)<0) { + gaim_debug_error("srv", "Could not write to SRV resolver\n"); + } + rdata = g_new0(struct resdata,1); + rdata->cb = cb; + rdata->extradata = extradata; + rdata->handle = gaim_input_add(out[0], GAIM_INPUT_READ, resolved, rdata); + + g_free(query); +#else + GError* err = NULL; + + static gboolean initialized = FALSE; + + gaim_debug_info("srv","querying SRV record for %s\n", query); + + if (!initialized) { + MyDnsQuery_UTF8 = (void*) wgaim_find_and_loadproc("dnsapi.dll", "DnsQuery_UTF8"); + MyDnsRecordListFree = (void*) wgaim_find_and_loadproc( + "dnsapi.dll", "DnsRecordListFree"); + initialized = TRUE; + } + + if (!MyDnsQuery_UTF8 || !MyDnsRecordListFree) { + gaim_debug_error("srv", "System missing DNS API (Requires W2K+)\n"); + g_free(query); + cb(NULL, 0, extradata); + return; + } + + rdata = g_new0(struct resdata, 1); + rdata->cb = cb; + rdata->query = query; + rdata->extradata = extradata; + + if (!g_thread_create(res_thread, rdata, FALSE, &err)) { + rdata->errmsg = g_strdup_printf("SRV thread create failure: %s\n", err ? err->message : ""); + g_error_free(err); + res_main_thread_cb(rdata); + } +#endif +} +