d05cc330 |
1 | /**/ |
2 | |
3 | #include "internal.h" |
4 | |
168e26fe |
5 | typedef enum { |
6 | rcode_noerror, |
7 | rcode_formaterror, |
54ed1d64 |
8 | rcode_servfail, |
168e26fe |
9 | rcode_nxdomain, |
10 | rcode_notimp, |
11 | rcode_refused |
12 | } dns_rcode; |
13 | |
54ed1d64 |
14 | #define GETIL_B(cb) (dgram[*(cb)++]) |
15 | #define GET_B(cb,tv) ((tv)= GETIL_B((cb))) |
16 | #define GET_W(cb,tv) ((tv)=0, (tv)|=(GETIL_B((cb))<<8), (tv)|=GETIL_B(cb), (tv)) |
17 | |
18 | static void vbuf__append_quoted1035(vbuf *vb, const byte *buf, int len) { |
19 | char qbuf[10]; |
20 | int i; |
21 | |
22 | while (len) { |
23 | qbuf[0]= 0; |
24 | for (i=0; i<len; i++) { |
25 | ch= buf[i]; |
26 | if (ch == '.' || ch == '"' || ch == '(' || ch == ')' || |
27 | ch == '@' || ch == ';' || ch == '$') { |
28 | sprintf(qbuf,"\\%c",ch); |
29 | break; |
30 | } else if (ch <= ' ' || ch >= 127) { |
31 | sprintf(qbuf,"\\%03o",ch); |
32 | break; |
33 | } |
34 | } |
35 | if (!adns__vbuf_append(vb,buf,i) || !adns__vbuf_append(vb,qbuf,strlen(qbuf))) |
36 | return adns_s_nolocalmem; |
37 | buf+= i; len-= i; |
38 | } |
39 | } |
40 | |
41 | static adns_status get_domain_perm(adns_state ads, adns_query qu, int serv, |
42 | const byte *dgram, int dglen, |
43 | int *cbyte_io, int max, char **domain_r) { |
44 | /* Returns 0 for OK (*domain_r set) or truncated (*domain_r null) |
45 | * or any other adns_s_* value. |
46 | */ |
47 | int cbyte, sused, lablen; |
48 | |
49 | /* If we follow a pointer we set cbyte_io to 0 to indicate that |
50 | * we've lost our original starting and ending points; we don't |
51 | * put the end of the pointed-to thing into the original *cbyte_io. |
52 | */ |
53 | cbyte= *cbyte_io; |
54 | sused= qu->ans.used; |
55 | *domain_r= 0; |
56 | for (;;) { |
57 | if (cbyte>=max) goto x_truncated; |
58 | lablen= GET_B(cbyte); |
59 | if (!lablen) break; |
60 | if (lablen&0x0c000) { |
61 | if ((lablen&0x0c000) != 0x0c0000) return adns_s_unknownreply; |
62 | if (cbyte_io) { *cbyte_io= cbyte; cbyte_io= 0; } |
63 | cbyte= (lablen&0x3fff) + DNS_HDR_SIZE; |
64 | max= dglen; |
65 | continue; |
66 | } |
67 | if (cbyte+lablen>=max) bgoto x_truncated; |
68 | if (qu->ans.used != sused) |
69 | if (!adns__vbuf_append(&qu->ans,".",1)) return adns_s_nolocalmem; |
70 | if (qu->flags & adns_qf_anyquote) { |
71 | if (!vbuf__append_quoted1035(&qu->ans,dgram+cbyte,lablen)) |
72 | return adns_s_nolocalmem; |
73 | } else { |
74 | if (!ctype_isalpha(dgram[cbyte])) return adns_s_invaliddomain; |
75 | for (i= cbyte+1; i<cbyte+lablen; i++) { |
76 | ch= dgram[cbyte]; |
77 | if (ch != '-' && !ctype_isalpha(ch) && !ctype_isdigit(ch)) |
78 | return adns_s_invaliddomain; |
79 | } |
80 | if (!adns__vbuf_append(&qu->ans,dgram+cbyte,lablen)) |
81 | return adns_s_nolocalmem; |
82 | } |
83 | } |
84 | if (cbyte_io) *cbyte_io= cbyte; |
85 | if (!adns__vbuf_append(&qu->ans,"",1)) return adns_s_nolocalmem; |
86 | *domain_r= qu->ans.buf+sused; |
87 | return adns_s_ok; |
88 | |
89 | x_truncated: |
90 | return cbyte_io ? -1 : adns_s_serverfaulty; |
91 | } |
92 | |
93 | static adns_status get_domain_temp(adns_state ads, adns_query qu, int serv, |
94 | const byte *dgram, int dglen, |
95 | int *cbyte_io, int max, char **domain_r) { |
96 | int sused; |
97 | adns_status st; |
98 | |
99 | sused= qu->ans.used; |
100 | st= get_domain_perm(ads,qu,serv,dgram,dglen,cbyte_io,max,domain_r); |
101 | qu->ans.used= sused; |
102 | return st; |
103 | } |
104 | |
105 | /* fixme: sensible comparison of owners */ |
106 | |
107 | static adns_status get_rr_temp(adns_state ads, adns_query qu, int serv, |
108 | const byte *dgram, int dglen, |
109 | int *cbyte_io, |
110 | int *type_r, int *class_r, int *rdlen_r, int *rdstart_r, |
111 | char **owner_r) { |
112 | int cbyte, tmp, rdlen; |
113 | |
114 | cbyte= *cbyte_io; |
115 | st= get_domain_temp(ads,qu,serv,dgram,dglen,&cbyte,dglen,owner_r); |
116 | if (st) return st; |
117 | |
118 | if (cbyte+10>len) goto x_truncated; |
119 | GET_W(cbyte,tmp); if (type_r) *type_r= tmp; |
120 | GET_W(cbyte,tmp); if (class_r) *class_r= tmp; |
121 | cbyte+= 4; /* we skip the TTL */ |
122 | GET_W(cbyte,rdlen); if (rdlen_r) *rdlen_r= tmp; |
123 | if (rdstart_r) *rdstart_r= cbyte; |
124 | cbyte+= rdlen; |
125 | if (cbyte>dglen) goto x_truncated; |
126 | *cbyte_io= cbyte; |
127 | return adns_s_ok; |
128 | |
129 | x_truncated: |
130 | *owner_r= 0; return 0;; |
131 | } |
132 | |
133 | void adns__procdgram(adns_state ads, const byte *dgram, int dglen, int serv) { |
134 | int cbyte, anstart, rrstart, lablen, wantedrrs, get_t; |
135 | |
136 | cbyte= 0; |
168e26fe |
137 | |
54ed1d64 |
138 | if (dglen<DNS_HDR_SIZE) { |
168e26fe |
139 | adns__diag(ads,serv,"received datagram too short for message header (%d)",len); |
140 | return; |
141 | } |
54ed1d64 |
142 | GET_W(cbyte,id); |
143 | GET_B(cbyte,f1); |
144 | GET_B(cbyte,f2); |
145 | GET_W(cbyte,qdcount); |
146 | GET_W(cbyte,ancount); |
147 | GET_W(cbyte,nscount); |
148 | GET_W(cbyte,arcount); |
149 | assert(cbyte == DNS_HDR_SIZE); |
168e26fe |
150 | |
151 | if (f1&0x80) { |
152 | adns__diag(ads,serv,"server sent us a query, not a response"); |
153 | return; |
154 | } |
155 | if (f1&0x70) { |
156 | adns__diag(ads,serv,"server sent us unknown opcode %d (wanted 0=QUERY)", |
157 | (f1>>4)&0x70); |
158 | return; |
159 | } |
160 | if (!qdcount) { |
161 | adns__diag(ads,serv,"server sent reply without quoting our question"); |
162 | return; |
163 | } else if (qdcount>1) { |
164 | adns__diag(ads,serv,"server claimed to answer %d questions with one message", |
165 | qdcount); |
166 | return; |
167 | } |
168 | for (qu= ads->timew; qu= nqu; qu++) { |
169 | nqu= qu->next; |
170 | if (qu->id != id) continue; |
171 | if (len < qu->querylen) continue; |
54ed1d64 |
172 | if (memcmp(qu->querymsg+DNSHDRSIZE,dgram+DNSHDRSIZE,qu->querylen-DNSHDRSIZE)) |
173 | continue; |
168e26fe |
174 | break; |
175 | } |
54ed1d64 |
176 | anstart= qu->querylen; |
168e26fe |
177 | if (!qu) { |
178 | adns__debug(ads,serv,"reply not found (id=%02x)",id); |
179 | return; |
180 | } |
168e26fe |
181 | if (!(f1&0x01)) { |
182 | adns__diag(ads,serv,"server thinks we didn't ask for recursive lookup"); |
183 | adns__query_fail(ads,qu,adns_s_serverfaulty); |
184 | return; |
185 | } |
54ed1d64 |
186 | |
187 | rcode= (f1&0x0f); |
188 | switch (rcode) { |
189 | case rcode_noerror: |
190 | case rcode_nxdomain: |
168e26fe |
191 | break; |
54ed1d64 |
192 | case rcode_formaterror: |
193 | adns__warn(ads,serv,"server cannot understand our query (Format Error)"); |
168e26fe |
194 | adns__query_fail(ads,qu,adns_s_serverfaulty); |
195 | return; |
54ed1d64 |
196 | case rcode_servfail; |
168e26fe |
197 | adns__query_fail(ads,qu,adns_s_serverfailure); |
198 | return; |
54ed1d64 |
199 | case rcode_notimp: |
200 | adns__warn(ads,serv,"server claims not to implement our query"); |
201 | adns__query_fail(ads,qu,adns_s_notimplemented); |
202 | return; |
203 | case rcode_refused: |
204 | adns__warn(ads,serv,"server refused our query"); |
205 | adns__query_fail(ads,qu,adns_s_refused); |
206 | return; |
207 | default: |
208 | adns__warn(ads,serv,"server gave unknown response code %d",rcode); |
209 | adns__query_fail(ads,qu,adns_s_reasonunknown); |
210 | return; |
211 | } |
212 | |
213 | /* Now, take a look at the answer section, and see if it is complete. |
214 | * If it has any CNAMEs we stuff them in the answer. |
215 | */ |
216 | wantedrrs= 0; |
217 | for (rri= 0; rri<ancount; rri++) { |
218 | rrstart= cbyte; |
219 | st= get_rr_temp(ads,qu,serv, |
220 | dgram,dglen, |
221 | &cbyte, |
222 | &rrtype,&rrclass,&rdlength,&cowner); |
223 | if (st) adns__query_fail(ads,qu,st); |
224 | if (rrclass != DNS_CLASS_IN) { |
225 | adns__diag(ads,serv,"ignoring RR with wrong class %d (expected IN=%d)", |
226 | rrclass,DNS_CLASS_IN); |
227 | continue; |
228 | } |
229 | if (strcmp_quoted1035(cowner, qu->cname ? qu->cname : qu->owner)) { |
230 | adns__debug(ads,serv,"ignoring answer RR with irrelevant owner \"%s\"",cowner); |
231 | continue; |
232 | } |
233 | if (!qu->cname && |
234 | (qu->type & adns__rrt_typemask) != adns_cname && |
235 | rrtype == adns_cname) { /* Ignore second and subsequent CNAMEs */ |
236 | qu->cname= get_domain_perm(ads,qu,dgram,len,rdstart,rdstart+rdlength); |
237 | /* If we find the answer section truncated after this point we restart |
238 | * the query at the CNAME; otherwise we can use it as-is. |
239 | */ |
240 | } else if (rrtype == (qu->type & adns__rrt_typemask)) { |
241 | wantedrrs++; |
242 | } else { |
243 | adns__debug(ads,serv,"ignoring answer RR with irrelevant type %d",rrtype); |
244 | } |
245 | } |
246 | |
247 | /* If we got here then the answer section is intact. */ |
248 | nsstart= cbyte; |
249 | |
250 | if (!wantedrrs) { |
251 | /* Oops, NODATA or NXDOMAIN or perhaps a referral (which would be a problem) */ |
252 | |
253 | if (rcode == rcode_nxdomain) { |
254 | adnns__query_finish(ads,qu,adns_s_nxdomain); |
255 | return; |
256 | } |
257 | |
258 | /* RFC2308: NODATA has _either_ a SOA _or_ _no_ NS records in authority section */ |
259 | for (rri= 0; rri<nscount; rri++) { |
260 | |
261 | } |
262 | } else { |
263 | |
264 | |
265 | if (!(f2&0x80)) { |
266 | adns__diag(ads,serv,"server is not willing to do recursive lookups for us"); |
267 | adns__query_fail(ads,qu,adns_s_norecurse); |
268 | return; |
269 | } |
270 | |
271 | |
272 | ) { |
273 | if (type |
274 | if (cbyte+lab |
275 | if (anstart > dgend) { truncated(ads,qu,f1); return; } |
276 | } |
277 | for |
278 | |
279 | /* Look for CNAMEs in the answer section */ |
280 | |
281 | } |
282 | |
283 | |
284 | adns__diag(ads,serv,"server refused our query"); |
285 | |
286 | case rcode_ |
287 | |
288 | case 0: /* NOERROR |
289 | break; |
290 | case 1: /* Format error */ |
168e26fe |
291 | case 3: /* Name Error */ |
292 | |
293 | qr= f1&0x80; |
294 | |
295 | |
d05cc330 |
296 | adns__diag(ads,serv,"received datagram size %d",len); |
168e26fe |
297 | |
d05cc330 |
298 | } |