Mercurial > pidgin
comparison src/protocols/napster/mdns.c @ 8486:538f4d0faf1d
[gaim-migrate @ 9221]
Don't use this, it's doesn't work, it's buggy, and it has security
problems. I just don't want to pull a Sean Egan and delete everthing
I have so far.
committer: Tailor Script <tailor@pidgin.im>
author | Mark Doliner <mark@kingant.net> |
---|---|
date | Tue, 23 Mar 2004 03:39:06 +0000 |
parents | |
children |
comparison
equal
deleted
inserted
replaced
8485:db4a125edd7b | 8486:538f4d0faf1d |
---|---|
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 } |