| |
1 /** |
| |
2 * @file mdns.c Multicast DNS connection code used by rendezvous. |
| |
3 * |
| |
4 * gaim |
| |
5 * |
| |
6 * Gaim is the legal property of its developers, whose names are too numerous |
| |
7 * to list here. Please refer to the COPYRIGHT file distributed with this |
| |
8 * source distribution. |
| |
9 * |
| |
10 * This program is free software; you can redistribute it and/or modify |
| |
11 * it under the terms of the GNU General Public License as published by |
| |
12 * the Free Software Foundation; either version 2 of the License, or |
| |
13 * (at your option) any later version. |
| |
14 * |
| |
15 * This program is distributed in the hope that it will be useful, |
| |
16 * but WITHOUT ANY WARRANTY; without even the implied warranty of |
| |
17 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
| |
18 * GNU General Public License for more details. |
| |
19 * |
| |
20 * You should have received a copy of the GNU General Public License |
| |
21 * along with this program; if not, write to the Free Software |
| |
22 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA |
| |
23 * |
| |
24 */ |
| |
25 |
| |
26 /* |
| |
27 * If you want to understand this, read RFC1035 and |
| |
28 * draft-cheshire-dnsext-multicastdns.txt |
| |
29 */ |
| |
30 |
| |
31 /* |
| |
32 * XXX - THIS DOESN'T DO BOUNDS CHECKING!!! DON'T USE IT ON AN UNTRUSTED |
| |
33 * NETWORK UNTIL IT DOES!!! THERE ARE POSSIBLE REMOTE ACCESS VIA BUFFER |
| |
34 * OVERFLOW SECURITY HOLES!!! |
| |
35 */ |
| |
36 |
| |
37 #include <errno.h> |
| |
38 #include <fcntl.h> |
| |
39 #include <stdlib.h> |
| |
40 #include <string.h> |
| |
41 #include <unistd.h> |
| |
42 #include <arpa/inet.h> |
| |
43 #include <netinet/in.h> |
| |
44 #include <sys/socket.h> |
| |
45 #include <sys/types.h> |
| |
46 |
| |
47 #include <glib.h> |
| |
48 |
| |
49 #include "debug.h" |
| |
50 |
| |
51 #include "mdns.h" |
| |
52 #include "util.h" |
| |
53 |
| |
54 int |
| |
55 mdns_establish_socket() |
| |
56 { |
| |
57 int fd = -1; |
| |
58 struct sockaddr_in addr; |
| |
59 struct ip_mreq mreq; |
| |
60 unsigned char loop; |
| |
61 unsigned char ttl; |
| |
62 int reuseaddr; |
| |
63 |
| |
64 gaim_debug_info("mdns", "Establishing multicast socket\n"); |
| |
65 |
| |
66 /* What's the difference between AF_INET and PF_INET? */ |
| |
67 if ((fd = socket(AF_INET, SOCK_DGRAM, 0)) < 0) { |
| |
68 gaim_debug_error("mdns", "Unable to create socket: %s\n", strerror(errno)); |
| |
69 return -1; |
| |
70 } |
| |
71 |
| |
72 /* Make the socket non-blocking (although it shouldn't matter) */ |
| |
73 fcntl(fd, F_SETFL, O_NONBLOCK); |
| |
74 |
| |
75 /* Bind the socket to a local IP and port */ |
| |
76 addr.sin_family = AF_INET; |
| |
77 addr.sin_port = htons(5353); |
| |
78 addr.sin_addr.s_addr = INADDR_ANY; |
| |
79 if (bind(fd, (struct sockaddr *)&addr, sizeof(struct sockaddr_in)) < 0) { |
| |
80 gaim_debug_error("mdns", "Unable to bind socket to interface.\n"); |
| |
81 close(fd); |
| |
82 return -1; |
| |
83 } |
| |
84 |
| |
85 /* Ensure loopback is enabled (it should be enabled by default, by let's be sure) */ |
| |
86 loop = 1; |
| |
87 if (setsockopt(fd, IPPROTO_IP, IP_MULTICAST_LOOP, &loop, sizeof(unsigned char)) == -1) { |
| |
88 gaim_debug_error("mdns", "Error calling setsockopt for IP_MULTICAST_LOOP\n"); |
| |
89 } |
| |
90 |
| |
91 /* Set TTL to 255--required by mDNS */ |
| |
92 ttl = 255; |
| |
93 if (setsockopt(fd, IPPROTO_IP, IP_MULTICAST_TTL, &ttl, sizeof(unsigned char)) == -1) { |
| |
94 gaim_debug_error("mdns", "Error calling setsockopt for IP_MULTICAST_TTL\n"); |
| |
95 close(fd); |
| |
96 return -1; |
| |
97 } |
| |
98 |
| |
99 /* Join the .local multicast group */ |
| |
100 mreq.imr_multiaddr.s_addr = inet_addr("224.0.0.251"); |
| |
101 mreq.imr_interface.s_addr = htonl(INADDR_ANY); |
| |
102 if (setsockopt(fd, IPPROTO_IP, IP_ADD_MEMBERSHIP, &mreq, sizeof(struct ip_mreq)) == -1) { |
| |
103 gaim_debug_error("mdns", "Error calling setsockopt for IP_ADD_MEMBERSHIP\n"); |
| |
104 close(fd); |
| |
105 return -1; |
| |
106 } |
| |
107 |
| |
108 /* Make the local IP re-usable */ |
| |
109 reuseaddr = 1; |
| |
110 if (setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &reuseaddr, sizeof(int)) == -1) { |
| |
111 gaim_debug_error("mdns", "Error calling setsockopt for SO_REUSEADDR: %s\n", strerror(errno)); |
| |
112 } |
| |
113 |
| |
114 return fd; |
| |
115 } |
| |
116 |
| |
117 int |
| |
118 mdns_query(int fd, const char *domain) |
| |
119 { |
| |
120 struct sockaddr_in addr; |
| |
121 unsigned int querylen; |
| |
122 unsigned char *query; |
| |
123 char *b, *c; |
| |
124 int i, n; |
| |
125 |
| |
126 if (strlen(domain) > 255) { |
| |
127 return -EINVAL; |
| |
128 } |
| |
129 |
| |
130 /* |
| |
131 * Build the outgoing query packet. It is made of the header with a |
| |
132 * query made up of the given domain. The header is 12 bytes. |
| |
133 */ |
| |
134 querylen = 12 + strlen(domain) + 2 + 4; |
| |
135 if (!(query = (unsigned char *)g_malloc(querylen))) { |
| |
136 return -ENOMEM; |
| |
137 } |
| |
138 |
| |
139 /* The header section */ |
| |
140 util_put32(&query[0], 0); /* The first 32 bits of the header are all 0's in mDNS */ |
| |
141 util_put16(&query[4], 1); /* QDCOUNT */ |
| |
142 util_put16(&query[6], 0); /* ANCOUNT */ |
| |
143 util_put16(&query[8], 0); /* NSCOUNT */ |
| |
144 util_put16(&query[10], 0); /* ARCOUNT */ |
| |
145 |
| |
146 /* The question section */ |
| |
147 i = 12; /* Destination in query */ |
| |
148 b = (char *)domain; |
| |
149 while ((c = strchr(b, '.'))) { |
| |
150 i += util_put8(&query[i], c - b); /* Length of domain-name segment */ |
| |
151 memcpy(&query[i], b, c - b); /* Domain-name segment */ |
| |
152 i += c - b; /* Increment the destination pointer */ |
| |
153 b = c + 1; |
| |
154 } |
| |
155 i += util_put8(&query[i], strlen(b)); /* Length of domain-name segment */ |
| |
156 strcpy(&query[i], b); /* Domain-name segment */ |
| |
157 i += strlen(b) + 1; /* Increment the destination pointer */ |
| |
158 i += util_put16(&query[i], 0x000c); /* QTYPE */ |
| |
159 i += util_put16(&query[i], 0x8001); /* QCLASS */ |
| |
160 |
| |
161 /* Actually send the DNS query */ |
| |
162 addr.sin_family = AF_INET; |
| |
163 addr.sin_port = htons(5353); |
| |
164 addr.sin_addr.s_addr = inet_addr("224.0.0.251"); |
| |
165 n = sendto(fd, query, querylen, 0, (struct sockaddr *)&addr, sizeof(struct sockaddr_in)); |
| |
166 g_free(query); |
| |
167 |
| |
168 if (n == -1) { |
| |
169 gaim_debug_error("mdns", "Error sending packet: %d\n", errno); |
| |
170 return -1; |
| |
171 } else if (n != querylen) { |
| |
172 gaim_debug_error("mdns", "Only sent %d of %d bytes of query.\n", n, querylen); |
| |
173 return -1; |
| |
174 } |
| |
175 |
| |
176 return 0; |
| |
177 } |
| |
178 |
| |
179 /* |
| |
180 * XXX - Needs bounds checking! |
| |
181 * |
| |
182 * Read in a domain name from the given buffer starting at the given |
| |
183 * offset. This handles using domain name compression to jump around |
| |
184 * the data buffer, if needed. |
| |
185 * |
| |
186 * @return A null-terminated string representation of the domain name. |
| |
187 * This should be g_free'd when no longer needed. |
| |
188 */ |
| |
189 static gchar * |
| |
190 mdns_read_name(const char *data, int datalen, int dataoffset) |
| |
191 { |
| |
192 GString *ret = g_string_new(""); |
| |
193 unsigned char tmp; |
| |
194 |
| |
195 while ((tmp = util_get8(&data[dataoffset])) != 0) { |
| |
196 dataoffset++; |
| |
197 |
| |
198 if ((tmp & 0xc0) == 0) { /* First two bits are 00 */ |
| |
199 if (*ret->str) |
| |
200 g_string_append_c(ret, '.'); |
| |
201 g_string_append_len(ret, &data[dataoffset], tmp); |
| |
202 dataoffset += tmp; |
| |
203 |
| |
204 } else if ((tmp & 0x40) == 0) { /* First two bits are 10 */ |
| |
205 /* Reserved for future use */ |
| |
206 |
| |
207 } else if ((tmp & 0x80) == 1) { /* First two bits are 01 */ |
| |
208 /* Reserved for future use */ |
| |
209 |
| |
210 } else { /* First two bits are 11 */ |
| |
211 /* Jump to another position in the data */ |
| |
212 dataoffset = util_get8(&data[dataoffset]); |
| |
213 |
| |
214 } |
| |
215 } |
| |
216 |
| |
217 return g_string_free(ret, FALSE); |
| |
218 } |
| |
219 |
| |
220 /* |
| |
221 * XXX - Needs bounds checking! |
| |
222 * |
| |
223 * Determine how many bytes long a portion of the domain name is |
| |
224 * at the given offset. This does NOT jump around the data array |
| |
225 * in the case of domain name compression. |
| |
226 * |
| |
227 * @return The length of the portion of the domain name. |
| |
228 */ |
| |
229 static int |
| |
230 mdns_read_name_len(const char *data, int datalen, int dataoffset) |
| |
231 { |
| |
232 int startoffset = dataoffset; |
| |
233 unsigned char tmp; |
| |
234 |
| |
235 while ((tmp = util_get8(&data[dataoffset++])) != 0) { |
| |
236 |
| |
237 if ((tmp & 0xc0) == 0) { /* First two bits are 00 */ |
| |
238 dataoffset += tmp; |
| |
239 |
| |
240 } else if ((tmp & 0x40) == 0) { /* First two bits are 10 */ |
| |
241 /* Reserved for future use */ |
| |
242 |
| |
243 } else if ((tmp & 0x80) == 1) { /* First two bits are 01 */ |
| |
244 /* Reserved for future use */ |
| |
245 |
| |
246 } else { /* First two bits are 11 */ |
| |
247 /* End of this portion of the domain name */ |
| |
248 dataoffset++; |
| |
249 break; |
| |
250 |
| |
251 } |
| |
252 } |
| |
253 |
| |
254 return dataoffset - startoffset; |
| |
255 } |
| |
256 |
| |
257 /* |
| |
258 * XXX - Needs bounds checking! |
| |
259 * |
| |
260 */ |
| |
261 static Question * |
| |
262 mdns_read_questions(int numquestions, const char *data, int datalen, int *offset) |
| |
263 { |
| |
264 Question *ret; |
| |
265 int i; |
| |
266 |
| |
267 ret = (Question *)g_malloc0(numquestions * sizeof(Question)); |
| |
268 for (i = 0; i < numquestions; i++) { |
| |
269 ret[i].name = mdns_read_name(data, 0, *offset); |
| |
270 *offset += mdns_read_name_len(data, 0, *offset); |
| |
271 ret[i].type = util_get16(&data[*offset]); /* QTYPE */ |
| |
272 *offset += 2; |
| |
273 ret[i].class = util_get16(&data[*offset]); /* QCLASS */ |
| |
274 *offset += 2; |
| |
275 } |
| |
276 |
| |
277 return ret; |
| |
278 } |
| |
279 |
| |
280 /* |
| |
281 * Read in a chunk of data, probably a buddy icon. |
| |
282 * |
| |
283 */ |
| |
284 static unsigned char * |
| |
285 mdns_read_rr_rdata_null(const char *data, int datalen, int offset, unsigned short rdlength) |
| |
286 { |
| |
287 unsigned char *ret = NULL; |
| |
288 |
| |
289 if (offset + rdlength > datalen) |
| |
290 return NULL; |
| |
291 |
| |
292 ret = (unsigned char *)g_malloc(rdlength); |
| |
293 memcpy(ret, &data[offset], rdlength); |
| |
294 |
| |
295 return ret; |
| |
296 } |
| |
297 |
| |
298 /* |
| |
299 * XXX - Needs bounds checking! |
| |
300 * |
| |
301 */ |
| |
302 static char * |
| |
303 mdns_read_rr_rdata_ptr(const char *data, int datalen, int offset) |
| |
304 { |
| |
305 char *ret = NULL; |
| |
306 |
| |
307 ret = mdns_read_name(data, datalen, offset); |
| |
308 |
| |
309 return ret; |
| |
310 } |
| |
311 |
| |
312 /* |
| |
313 * |
| |
314 * |
| |
315 */ |
| |
316 static GHashTable * |
| |
317 mdns_read_rr_rdata_txt(const char *data, int datalen, int offset, unsigned short rdlength) |
| |
318 { |
| |
319 GHashTable *ret = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, g_free); |
| |
320 int endoffset = offset + rdlength; |
| |
321 unsigned char tmp; |
| |
322 char buf[256], *key, *value; |
| |
323 |
| |
324 while (offset < endoffset) { |
| |
325 /* Read in the length of the next name/value pair */ |
| |
326 tmp = util_get8(&data[offset]); |
| |
327 offset++; |
| |
328 |
| |
329 /* Ensure packet is valid */ |
| |
330 if (offset + tmp > endoffset) |
| |
331 break; |
| |
332 |
| |
333 /* Read in the next name/value pair */ |
| |
334 strncpy(buf, &data[offset], tmp); |
| |
335 offset += tmp; |
| |
336 |
| |
337 if (buf[0] == '=') { |
| |
338 /* Name/value pairs beginning with = are silently ignored */ |
| |
339 continue; |
| |
340 } |
| |
341 |
| |
342 /* The value is a substring of buf, starting just after the = */ |
| |
343 buf[tmp] = '\0'; |
| |
344 value = strchr(buf, '='); |
| |
345 if (value != NULL) { |
| |
346 value[0] = '\0'; |
| |
347 value++; |
| |
348 } |
| |
349 |
| |
350 /* Make the key all lowercase */ |
| |
351 key = g_utf8_strdown(buf, -1); |
| |
352 if (!g_hash_table_lookup(ret, key)) |
| |
353 g_hash_table_insert(ret, key, g_strdup(value)); |
| |
354 else |
| |
355 g_free(key); |
| |
356 } |
| |
357 |
| |
358 return ret; |
| |
359 } |
| |
360 |
| |
361 /* |
| |
362 * XXX - Needs bounds checking! |
| |
363 * |
| |
364 */ |
| |
365 static ResourceRecord * |
| |
366 mdns_read_rr(int numrecords, const char *data, int datalen, int *offset) |
| |
367 { |
| |
368 ResourceRecord *ret; |
| |
369 int i; |
| |
370 |
| |
371 ret = (ResourceRecord *)g_malloc0(numrecords * sizeof(ResourceRecord)); |
| |
372 for (i = 0; i < numrecords; i++) { |
| |
373 ret[i].name = mdns_read_name(data, 0, *offset); /* NAME */ |
| |
374 *offset += mdns_read_name_len(data, 0, *offset); |
| |
375 ret[i].type = util_get16(&data[*offset]); /* TYPE */ |
| |
376 *offset += 2; |
| |
377 ret[i].class = util_get16(&data[*offset]); /* CLASS */ |
| |
378 *offset += 2; |
| |
379 ret[i].ttl = util_get32(&data[*offset]); /* TTL */ |
| |
380 *offset += 4; |
| |
381 ret[i].rdlength = util_get16(&data[*offset]); /* RDLENGTH */ |
| |
382 *offset += 2; |
| |
383 |
| |
384 /* RDATA */ |
| |
385 switch (ret[i].type) { |
| |
386 case RENDEZVOUS_RRTYPE_NULL: |
| |
387 ret[i].rdata = mdns_read_rr_rdata_null(data, datalen, *offset, ret[i].rdlength); |
| |
388 break; |
| |
389 |
| |
390 case RENDEZVOUS_RRTYPE_PTR: |
| |
391 ret[i].rdata = mdns_read_rr_rdata_ptr(data, datalen, *offset); |
| |
392 break; |
| |
393 |
| |
394 case RENDEZVOUS_RRTYPE_TXT: |
| |
395 ret[i].rdata = mdns_read_rr_rdata_txt(data, datalen, *offset, ret[i].rdlength); |
| |
396 break; |
| |
397 |
| |
398 default: |
| |
399 ret[i].rdata = NULL; |
| |
400 break; |
| |
401 } |
| |
402 *offset += ret[i].rdlength; |
| |
403 } |
| |
404 |
| |
405 return ret; |
| |
406 } |
| |
407 |
| |
408 /* |
| |
409 * XXX - Needs bounds checking! |
| |
410 * |
| |
411 */ |
| |
412 DNSPacket * |
| |
413 mdns_read(int fd) |
| |
414 { |
| |
415 DNSPacket *ret = NULL; |
| |
416 int i; /* Current position in datagram */ |
| |
417 //char data[512]; |
| |
418 char data[10096]; |
| |
419 int datalen; |
| |
420 struct sockaddr_in addr; |
| |
421 socklen_t addrlen; |
| |
422 |
| |
423 /* Read in an mDNS packet */ |
| |
424 addrlen = sizeof(struct sockaddr_in); |
| |
425 if ((datalen = recvfrom(fd, data, sizeof(data), 0, (struct sockaddr *)&addr, &addrlen)) == -1) { |
| |
426 gaim_debug_error("mdns", "Error reading packet: %d\n", errno); |
| |
427 return NULL; |
| |
428 } |
| |
429 |
| |
430 ret = (DNSPacket *)g_malloc0(sizeof(DNSPacket)); |
| |
431 |
| |
432 /* Parse the incoming packet, starting from 0 */ |
| |
433 i = 0; |
| |
434 |
| |
435 /* The header section */ |
| |
436 ret->header.id = util_get16(&data[i]); /* ID */ |
| |
437 i += 2; |
| |
438 |
| |
439 /* For the flags, some bits must be 0 and some must be 1, the rest are ignored */ |
| |
440 ret->header.flags = util_get16(&data[i]); /* Flags (QR, OPCODE, AA, TC, RD, RA, Z, AD, CD, and RCODE */ |
| |
441 i += 2; |
| |
442 if ((ret->header.flags & 0x8000) == 0) { |
| |
443 /* QR should be 1 */ |
| |
444 g_free(ret); |
| |
445 return NULL; |
| |
446 } |
| |
447 if ((ret->header.flags & 0x7800) != 0) { |
| |
448 /* OPCODE should be all 0's */ |
| |
449 g_free(ret); |
| |
450 return NULL; |
| |
451 } |
| |
452 |
| |
453 /* Read in the number of other things in the packet */ |
| |
454 ret->header.numquestions = util_get16(&data[i]); |
| |
455 i += 2; |
| |
456 ret->header.numanswers = util_get16(&data[i]); |
| |
457 i += 2; |
| |
458 ret->header.numauthority = util_get16(&data[i]); |
| |
459 i += 2; |
| |
460 ret->header.numadditional = util_get16(&data[i]); |
| |
461 i += 2; |
| |
462 |
| |
463 /* Read in all the questions */ |
| |
464 ret->questions = mdns_read_questions(ret->header.numquestions, data, datalen, &i); |
| |
465 |
| |
466 /* Read in all resource records */ |
| |
467 ret->answers = mdns_read_rr(ret->header.numanswers, data, datalen, &i); |
| |
468 |
| |
469 /* Read in all authority records */ |
| |
470 ret->authority = mdns_read_rr(ret->header.numauthority, data, datalen, &i); |
| |
471 |
| |
472 /* Read in all additional records */ |
| |
473 ret->additional = mdns_read_rr(ret->header.numadditional, data, datalen, &i); |
| |
474 |
| |
475 /* We should be at the end of the packet */ |
| |
476 if (i != datalen) { |
| |
477 gaim_debug_error("mdns", "Finished parsing before end of DNS packet! Only parsed %d of %d bytes.", i, datalen); |
| |
478 g_free(ret); |
| |
479 return NULL; |
| |
480 } |
| |
481 |
| |
482 return ret; |
| |
483 } |
| |
484 |
| |
485 /** |
| |
486 * Free the rdata associated with a given resource record. |
| |
487 */ |
| |
488 static void |
| |
489 mdns_free_rr_rdata(unsigned short type, void *rdata) |
| |
490 { |
| |
491 switch (type) { |
| |
492 case RENDEZVOUS_RRTYPE_NULL: |
| |
493 case RENDEZVOUS_RRTYPE_PTR: |
| |
494 g_free(rdata); |
| |
495 break; |
| |
496 |
| |
497 case RENDEZVOUS_RRTYPE_TXT: |
| |
498 g_hash_table_destroy(rdata); |
| |
499 break; |
| |
500 } |
| |
501 } |
| |
502 |
| |
503 /** |
| |
504 * Free a given question |
| |
505 */ |
| |
506 static void |
| |
507 mdns_free_q(Question *q) |
| |
508 { |
| |
509 g_free(q->name); |
| |
510 } |
| |
511 |
| |
512 /** |
| |
513 * Free a given resource record. |
| |
514 */ |
| |
515 static void |
| |
516 mdns_free_rr(ResourceRecord *rr) |
| |
517 { |
| |
518 g_free(rr->name); |
| |
519 mdns_free_rr_rdata(rr->type, rr->rdata); |
| |
520 } |
| |
521 |
| |
522 void |
| |
523 mdns_free(DNSPacket *dns) |
| |
524 { |
| |
525 int i; |
| |
526 |
| |
527 for (i = 0; i < dns->header.numquestions; i++) |
| |
528 mdns_free_q(&dns->questions[i]); |
| |
529 for (i = 0; i < dns->header.numanswers; i++) |
| |
530 mdns_free_rr(&dns->answers[i]); |
| |
531 for (i = 0; i < dns->header.numauthority; i++) |
| |
532 mdns_free_rr(&dns->authority[i]); |
| |
533 for (i = 0; i < dns->header.numadditional; i++) |
| |
534 mdns_free_rr(&dns->additional[i]); |
| |
535 |
| |
536 g_free(dns->questions); |
| |
537 g_free(dns->answers); |
| |
538 g_free(dns->authority); |
| |
539 g_free(dns->additional); |
| |
540 g_free(dns); |
| |
541 } |