521b1c29787cad8c1f13a9905b7d81c374b6513e
[gnupg.git] / util / srv.c
1 /* srv.c - DNS SRV code
2  * Copyright (C) 2003, 2005, 2006, 2007, 2009 Free Software Foundation, Inc.
3  *
4  * This file is part of GNUPG.
5  *
6  * GNUPG is free software; you can redistribute it and/or modify
7  * it under the terms of the GNU General Public License as published by
8  * the Free Software Foundation; either version 3 of the License, or
9  * (at your option) any later version.
10  *
11  * GNUPG is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14  * GNU General Public License for more details.
15  *
16  * You should have received a copy of the GNU General Public License
17  * along with this program; if not, see <http://www.gnu.org/licenses/>.
18  */
19
20 #include <config.h>
21 #include <sys/types.h>
22 #ifdef _WIN32
23 #include <windows.h>
24 #else
25 #include <netinet/in.h>
26 #include <arpa/nameser.h>
27 #include <arpa/inet.h>
28 #include <resolv.h>
29 #endif
30 #include <unistd.h>
31 #include <stdlib.h>
32 #include <string.h>
33 #include <time.h>
34 #include "srv.h"
35
36 /* Not every installation has gotten around to supporting SRVs
37    yet... */
38 #ifndef T_SRV
39 #define T_SRV 33
40 #endif
41
42 static int
43 priosort(const void *a,const void *b)
44 {
45   const struct srventry *sa=a,*sb=b;
46   if(sa->priority>sb->priority)
47     return 1;
48   else if(sa->priority<sb->priority)
49     return -1;
50   else
51     return 0;
52 }
53
54 int
55 getsrv(const char *name,struct srventry **list)
56 {
57   unsigned char answer[2048];
58   int r,srvcount=0;
59   unsigned char *pt,*emsg;
60   u16 count,dlen;
61
62   *list=NULL;
63
64   r=res_query(name,C_IN,T_SRV,answer,2048);
65   if(r<sizeof(HEADER) || r>2048)
66     return -1;
67
68   if((((HEADER *)answer)->rcode)==NOERROR &&
69      (count=ntohs(((HEADER *)answer)->ancount)))
70     {
71       int i,rc;
72
73       emsg=&answer[r];
74       pt=&answer[sizeof(HEADER)];
75
76       /* Skip over the query */
77
78       rc=dn_skipname(pt,emsg);
79       if(rc==-1)
80         goto fail;
81
82       pt+=rc+QFIXEDSZ;
83
84       while(count-->0 && pt<emsg)
85         {
86           struct srventry *srv=NULL;
87           u16 type,class;
88
89           srv=realloc(*list,(srvcount+1)*sizeof(struct srventry));
90           if(!srv)
91             goto fail;
92
93           *list=srv;
94           memset(&(*list)[srvcount],0,sizeof(struct srventry));
95           srv=&(*list)[srvcount];
96           srvcount++;
97
98           rc=dn_skipname(pt,emsg); /* the name we just queried for */
99           if(rc==-1)
100             goto fail;
101           pt+=rc;
102
103           /* Truncated message? */
104           if((emsg-pt)<16)
105             goto fail;
106
107           type=*pt++ << 8;
108           type|=*pt++;
109           /* We asked for SRV and got something else !? */
110           if(type!=T_SRV)
111             goto fail;
112
113           class=*pt++ << 8;
114           class|=*pt++;
115           /* We asked for IN and got something else !? */
116           if(class!=C_IN)
117             goto fail;
118
119           pt+=4; /* ttl */
120           dlen=*pt++ << 8;
121           dlen|=*pt++;
122           srv->priority=*pt++ << 8;
123           srv->priority|=*pt++;
124           srv->weight=*pt++ << 8;
125           srv->weight|=*pt++;
126           srv->port=*pt++ << 8;
127           srv->port|=*pt++;
128
129           /* Get the name.  2782 doesn't allow name compression, but
130              dn_expand still works to pull the name out of the
131              packet. */
132           rc=dn_expand(answer,emsg,pt,srv->target,MAXDNAME);
133           if(rc==1 && srv->target[0]==0) /* "." */
134             goto noanswer;
135           if(rc==-1)
136             goto fail;
137           pt+=rc;
138           /* Corrupt packet? */
139           if(dlen!=rc+6)
140             goto fail;
141
142 #if 0
143           printf("count=%d\n",srvcount);
144           printf("priority=%d\n",srv->priority);
145           printf("weight=%d\n",srv->weight);
146           printf("port=%d\n",srv->port);
147           printf("target=%s\n",srv->target);
148 #endif
149         }
150
151       /* Now we have an array of all the srv records. */
152
153       /* Order by priority */
154       qsort(*list,srvcount,sizeof(struct srventry),priosort);
155
156       /* For each priority, move the zero-weighted items first. */
157       for(i=0;i<srvcount;i++)
158         {
159           int j;
160
161           for(j=i;j<srvcount && (*list)[i].priority==(*list)[j].priority;j++)
162             {
163               if((*list)[j].weight==0)
164                 {
165                   /* Swap j with i */
166                   if(j!=i)
167                     {
168                       struct srventry temp;
169
170                       memcpy(&temp,&(*list)[j],sizeof(struct srventry));
171                       memcpy(&(*list)[j],&(*list)[i],sizeof(struct srventry));
172                       memcpy(&(*list)[i],&temp,sizeof(struct srventry));
173                     }
174
175                   break;
176                 }
177             }
178         }
179
180       /* Run the RFC-2782 weighting algorithm.  We don't need very
181          high quality randomness for this, so regular libc srand/rand
182          is sufficient. */
183       srand(time(NULL)*getpid());
184
185       for(i=0;i<srvcount;i++)
186         {
187           int j;
188           float prio_count=0,chose;
189
190           for(j=i;j<srvcount && (*list)[i].priority==(*list)[j].priority;j++)
191             {
192               prio_count+=(*list)[j].weight;
193               (*list)[j].run_count=prio_count;
194             }
195
196           chose=prio_count*rand()/RAND_MAX;
197
198           for(j=i;j<srvcount && (*list)[i].priority==(*list)[j].priority;j++)
199             {
200               if(chose<=(*list)[j].run_count)
201                 {
202                   /* Swap j with i */
203                   if(j!=i)
204                     {
205                       struct srventry temp;
206
207                       memcpy(&temp,&(*list)[j],sizeof(struct srventry));
208                       memcpy(&(*list)[j],&(*list)[i],sizeof(struct srventry));
209                       memcpy(&(*list)[i],&temp,sizeof(struct srventry));
210                     }
211                   break;
212                 }
213             }
214         }
215     }
216   
217   return srvcount;
218
219  noanswer:
220   free(*list);
221   *list=NULL;
222   return 0;
223
224  fail:
225   free(*list);
226   *list=NULL;
227   return -1;
228 }
229
230 #ifdef TEST
231 int
232 main(int argc,char *argv[])
233 {
234   struct srventry *srv;
235   int rc,i;
236
237   rc=getsrv("_hkp._tcp.wwwkeys.pgp.net",&srv);
238   printf("Count=%d\n\n",rc);
239   for(i=0;i<rc;i++)
240     {
241       printf("priority=%d\n",srv[i].priority);
242       printf("weight=%d\n",srv[i].weight);
243       printf("port=%d\n",srv[i].port);
244       printf("target=%s\n",srv[i].target);
245       printf("\n");
246     }
247
248   free(srv);
249
250   return 0;
251 }
252 #endif /* TEST */
253
254 /*
255 Local Variables:
256 compile-command: "cc -DTEST -I.. -I../include -Wall -g -o srv srv.c -lresolv libutil.a"
257 End:
258 */