chiark / gitweb /
0bcc3fae08186bd4dd4692fb3a9b284d64591aae
[adns.git] / src / reply.c
1 /**/
2
3 #include "internal.h"
4
5 static void cname_recurse(adns_state ads, adns_query qu, adns_queryflags xflags) {
6   abort(); /* FIXME */
7 }
8     
9 void adns__procdgram(adns_state ads, const byte *dgram, int dglen,
10                      int serv, struct timeval now) {
11   int cbyte, rrstart, wantedrrs, rri, foundsoa, foundns;
12   int id, f1, f2, qdcount, ancount, nscount, arcount, flg_ra, flg_rd, flg_tc, opcode;
13   int rrtype, rrclass, rdlength, rdstart, ownermatched, ownerstart;
14   int anstart, nsstart, arstart;
15   int currentrrs;
16   adns_query qu, nqu;
17   dns_rcode rcode;
18   adns_status st;
19   vbuf vb;
20 #error init and free vb properly
21   
22   if (dglen<DNS_HDRSIZE) {
23     adns__diag(ads,serv,"received datagram too short for message header (%d)",dglen);
24     return;
25   }
26   GET_W(cbyte,id);
27   GET_B(cbyte,f1);
28   GET_B(cbyte,f2);
29   GET_W(cbyte,qdcount);
30   GET_W(cbyte,ancount);
31   GET_W(cbyte,nscount);
32   GET_W(cbyte,arcount);
33   assert(cbyte == DNS_HDRSIZE);
34
35   flg_qr= f1&0x80;
36   opcode= (f1&0x78)>>3;
37   flg_tc= f1&0x20;
38   flg_rd= f1&0x01;
39   flg_ra= f2&0x80;
40   rcode= (f1&0x0f);
41
42   if (flg_qr) {
43     adns__diag(ads,serv,"server sent us a query, not a response");
44     return;
45   }
46   if (opcode) {
47     adns__diag(ads,serv,"server sent us unknown opcode %d (wanted 0=QUERY)",opcode);
48     return;
49   }
50   if (!qdcount) {
51     adns__diag(ads,serv,"server sent reply without quoting our question");
52     return;
53   } else if (qdcount>1) {
54     adns__diag(ads,serv,"server claimed to answer %d questions with one message",
55                qdcount);
56     return;
57   }
58   for (qu= ads->timew.head; qu; qu= nqu) {
59     nqu= qu->next;
60     if (qu->id != id) continue;
61     if (dglen < qu->querylen) continue;
62     if (memcmp(qu->querymsg+DNS_HDRSIZE,dgram+DNS_HDRSIZE,qu->querylen-DNS_HDRSIZE))
63       continue;
64     break;
65   }
66   assert(qu->cnameoff == -1);
67   anstart= qu->querylen;
68   if (!qu) {
69     adns__debug(ads,serv,"reply not found (id=%02x)",id);
70     return;
71   }
72
73   LIST_UNLINK(ads->timew,qu);
74   /* We're definitely going to do something with this query now */
75   
76   switch (rcode) {
77   case rcode_noerror:
78   case rcode_nxdomain:
79     break;
80   case rcode_formaterror:
81     adns__warn(ads,serv,qu,"server cannot understand our query (Format Error)");
82     adns__query_fail(ads,qu,adns_s_serverfaulty);
83     return;
84   case rcode_servfail:
85     adns__query_fail(ads,qu,adns_s_servfail);
86     return;
87   case rcode_notimp:
88     adns__warn(ads,serv,qu,"server claims not to implement our query");
89     adns__query_fail(ads,qu,adns_s_notimplemented);
90     return;
91   case rcode_refused:
92     adns__warn(ads,serv,qu,"server refused our query");
93     adns__query_fail(ads,qu,adns_s_refused);
94     return;
95   default:
96     adns__warn(ads,serv,qu,"server gave unknown response code %d",rcode);
97     adns__query_fail(ads,qu,adns_s_reasonunknown);
98     return;
99   }
100
101   /* Now, take a look at the answer section, and see if it is complete.
102    * If it has any CNAMEs we stuff them in the answer.
103    */
104   wantedrrs= 0;
105   for (rri= 0; rri<ancount; rri++) {
106     rrstart= cbyte;
107     if (qu->cname_dgram >= 0) {
108       st= adns__findrr(ads,serv, dgram,dglen,&cbyte,
109                        &rrtype,&rrclass,&rdlength,&rdstart,
110                        qu->cname_dgram,qu->cname_dglen,qu->cname_begin, &ownermatched);
111     } else {
112       st= adns__findrr(ads,serv, dgram,dglen,&cbyte,
113                        &rrtype,&rrclass,&rdlength,&rdstart,
114                        qu->querymsg,qu->querylen,DNS_HDRSIZE, &ownermatched);
115     }
116     if (st) adns__query_fail(ads,qu,st);
117     if (rrtype == -1) goto x_truncated;
118
119     if (rrclass != DNS_CLASS_IN) {
120       adns__diag(ads,serv,qu,"ignoring answer RR with wrong class %d (expected IN=%d)",
121                  rrclass,DNS_CLASS_IN);
122       continue;
123     }
124     if (!ownermatched) {
125       if (ads->iflags & adns_if_debug) {
126         adns__debug(ads,serv,qu,"ignoring RR with an unexpected owner %s",
127                     adns__diag_domain(ads,serv,&vb,qu->flags,
128                                       dgram,dglen,rrstart,dglen));
129       }
130       continue;
131     }
132     if (rrtype == adns_r_cname &&
133         (qu->typei->type & adns__rrt_typemask) != adns_r_cname) {
134       if (!qu->cname_str) { /* Ignore second and subsequent CNAMEs */
135         qu->cname_begin= rdstart;
136         qu->cname_dgram= dgram;
137         qu->cname_dglen= dglen;
138         st= adns__parse_domain(ads,serv,&vb,qu->flags,
139                                dgram,dglen, &rdstart,rdstart+rdlength);
140         if (!vb.used) goto x_truncated;
141         if (st) { adns__query_fail(ads,qu,st); return; }
142         qu->cname_str= adns__vbuf_extractstring(&vb);
143         /* If we find the answer section truncated after this point we restart
144          * the query at the CNAME; if beforehand then we obviously have to use
145          * TCP.  If there is no truncation we can use the whole answer if
146          * it contains the relevant info.
147          */
148       } else {
149         adns__debug(ads,serv,qu,"ignoring duplicate CNAME (%s, as well as %s)",
150                     adns__diag_domain(ads,serv,&vb,qu->flags,
151                                       dgram,dglen, rdstart,rdstart+rdlength),
152                     qu->cname_str);
153       }
154     } else if (rrtype == (qu->typei->type & adns__rrt_typemask)) {
155       wantedrrs++;
156     } else {
157       adns__debug(ads,serv,qu,"ignoring answer RR with irrelevant type %d",rrtype);
158     }
159   }
160
161   /* If we got here then the answer section is intact. */
162   nsstart= cbyte;
163
164   if (!wantedrrs) {
165     /* Oops, NODATA or NXDOMAIN or perhaps a referral (which would be a problem) */
166     
167     if (rcode == rcode_nxdomain) {
168       adns__query_finish(ads,qu,adns_s_nxdomain);
169       return;
170     }
171
172     /* RFC2308: NODATA has _either_ a SOA _or_ _no_ NS records in authority section */
173     foundsoa= 0; foundns= 0;
174     for (rri= 0; rri<nscount; rri++) {
175       rrstart= cbyte;
176       st= adns__findrr(ads,serv, dgram,dglen,&cbyte,
177                        &rrtype,&rrclass,&rdlength,&rdstart,
178                        0,0,0,0);
179       if (st) { adns__query_fail(ads,qu,st); return; }
180       if (rrtype==-1) goto x_truncated;
181       if (rrclass != DNS_CLASS_IN) {
182         adns__diag(ads,serv,qu,
183                    "ignoring authority RR with wrong class %d (expected IN=%d)",
184                    rrclass,DNS_CLASS_IN);
185         continue;
186       }
187       if (rrtype == adns_r_soa_raw) { foundsoa= 1; break; }
188       else if (rrtype == adns_r_ns_raw) { foundns= 1; }
189     }
190
191     if (foundsoa || !foundns) {
192       /* Aha !  A NODATA response, good. */
193       adns__query_finish(ads,qu,adns_s_nodata);
194       return;
195     }
196
197     /* Now what ?  No relevant answers, no SOA, and at least some NS's.
198      * Looks like a referral.  Just one last chance ... if we came across
199      * a CNAME in this datagram then we should probably do our own CNAME
200      * lookup now in the hope that we won't get a referral again.
201      */
202     if (qu->cname_dgram == dgram) { cname_recurse(ads,qu,0); return; }
203
204     /* Bloody hell, I thought we asked for recursion ? */
205     if (flg_rd) {
206       adns__diag(ads,serv,qu,"server thinks we didn't ask for recursive lookup");
207     }
208     if (!flg_ra) {
209       adns__diag(ads,serv,qu,"server is not willing to do recursive lookups for us");
210       adns__query_fail(ads,qu,adns_s_norecurse);
211     } else {
212       adns__diag(ads,serv,qu,"server claims to do recursion, but gave us a referral");
213       adns__query_fail(ads,qu,adns_s_serverfaulty);
214     }
215     return;
216   }
217
218   /* Now, we have some RRs which we wanted. */
219
220   if (!adns__vbuf_ensure(&qu->ansbuf,qu->typei->rrsz*wantedrrs)) {
221     adns__query_fail(ads,qu,adns_s_nolocalmem);
222     return;
223   }
224
225   cbyte= anstart;
226   currentrrs= 0;
227   arstart= -1;
228   qu->ansbuf.used= 0;
229   for (rri=0; rri<ancount; rri++) {
230     if (qu->cname_dgram >= 0) {
231       st= adns__findrr(ads,serv, dgram,dglen,&cbyte,
232                        &rrtype,&rrclass,&rdlength,&rdstart,
233                        qu->cname_dgram,qu->cname_dglen,qu->cname_begin, &ownermatched);
234     } else {
235       st= adns__findrr(ads,serv, dgram,dglen,&cbyte,
236                        &rrtype,&rrclass,&rdlength,&rdstart,
237                        qu->querymsg,qu->querylen,DNS_HDRSIZE, &ownermatched);
238     }
239     assert(!st); assert(rrtype != -1);
240     if (rrclass != DNS_CLASS_IN ||
241         rrtype != (qu->typei->type & adns__rrt_typemask) ||
242         !ownermatched)
243       continue;
244     assert(currentrrs<wantedrrs);
245     qu->ansbuf.used += quj->typei->rrsz;
246     st= qu->typei->parse(ads,qu,serv,&vb,
247                          dgram,dglen, &rdstart,rdstart+rdlength,
248                          (void*)(qu->ansbuf.buf+qu->ansbuf.used));
249     if (st) { adns__query_fail(ads,qu,st); return; }
250     if (rdstart==-1) goto x_truncated;
251   }
252
253   /* This may have generated some child queries ... */
254   if (qu->children.head) {
255     qu->state= query_child;
256     LIST_LINK_TAIL(ads->childw,qu);
257     return;
258   }
259
260   adns__query_finish(ads,qu,adns_s_ok);
261   return;
262
263 x_truncated:
264   if (!flg_tc) {
265     adns__diag(ads,serv,qu,"server sent datagram which points outside itself");
266     adns__query_fail(ads,qu,adns_s_serverfaulty);
267     return;
268   }
269   if (qu->cname_dgram) { cname_recurse(ads,qu,adns_qf_usevc); return; }
270   ans= (adns_answer*)qu->ans.buf;
271   ans->nrrs= 0;
272   qu->ans.used= sizeof(adns_answer);
273   qu->flags |= adns_qf_usevc;
274   adns__query_udp(ads,qu,now);
275 }