Mercurial > pidgin
annotate libgaim/upnp.c @ 15268:132db06150ca
[gaim-migrate @ 18058]
* Nathan and I decided that there's no reason for having the if (value != old_value) checks in the setters; it's a convention which makes sense in my 'native' Objective-C but is silly here.
* Fixed a return in g_return_val_if_fail(): gaim_notify_user_info_entry_get_type() returns a GaimNotifyUserInfoEntryType, not a pointer.
committer: Tailor Script <tailor@pidgin.im>
author | Evan Schoenberg <evan.s@dreskin.net> |
---|---|
date | Tue, 26 Dec 2006 19:22:03 +0000 |
parents | 192c66c0c2cb |
children | 96abd13cc0b3 |
rev | line source |
---|---|
14192 | 1 /** |
2 * @file upnp.c UPnP Implementation | |
3 * @ingroup core | |
4 * | |
5 * gaim | |
6 * | |
7 * Gaim is the legal property of its developers, whose names are too numerous | |
8 * to list here. Please refer to the COPYRIGHT file distributed with this | |
9 * source distribution. | |
10 * | |
11 * This program is free software; you can redistribute it and/or modify | |
12 * it under the terms of the GNU General Public License as published by | |
13 * the Free Software Foundation; either version 2 of the License, or | |
14 * (at your option) any later version. | |
15 * | |
16 * This program is distributed in the hope that it will be useful, | |
17 * but WITHOUT ANY WARRANTY; without even the implied warranty of | |
18 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |
19 * GNU General Public License for more details. | |
20 * | |
21 * You should have received a copy of the GNU General Public License | |
22 * along with this program; if not, write to the Free Software | |
23 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA | |
24 */ | |
25 #include "internal.h" | |
26 | |
27 #include "debug.h" | |
28 #include "util.h" | |
29 #include "proxy.h" | |
30 #include "xmlnode.h" | |
31 #include "network.h" | |
32 #include "eventloop.h" | |
33 #include "upnp.h" | |
34 | |
35 | |
36 /*************************************************************** | |
37 ** General Defines * | |
38 ****************************************************************/ | |
39 #define HTTP_OK "200 OK" | |
40 #define DEFAULT_HTTP_PORT 80 | |
41 #define DISCOVERY_TIMEOUT 1000 | |
42 | |
43 /*************************************************************** | |
44 ** Discovery/Description Defines * | |
45 ****************************************************************/ | |
46 #define NUM_UDP_ATTEMPTS 2 | |
47 | |
48 /* Address and port of an SSDP request used for discovery */ | |
49 #define HTTPMU_HOST_ADDRESS "239.255.255.250" | |
50 #define HTTPMU_HOST_PORT 1900 | |
51 | |
52 #define SEARCH_REQUEST_DEVICE "urn:schemas-upnp-org:service:%s" | |
53 | |
54 #define SEARCH_REQUEST_STRING \ | |
55 "M-SEARCH * HTTP/1.1\r\n" \ | |
56 "MX: 2\r\n" \ | |
57 "HOST: 239.255.255.250:1900\r\n" \ | |
58 "MAN: \"ssdp:discover\"\r\n" \ | |
59 "ST: urn:schemas-upnp-org:service:%s\r\n" \ | |
60 "\r\n" | |
61 | |
62 #define WAN_IP_CONN_SERVICE "WANIPConnection:1" | |
63 #define WAN_PPP_CONN_SERVICE "WANPPPConnection:1" | |
64 | |
65 /****************************************************************** | |
66 ** Action Defines * | |
67 *******************************************************************/ | |
68 #define HTTP_HEADER_ACTION \ | |
69 "POST /%s HTTP/1.1\r\n" \ | |
70 "HOST: %s:%d\r\n" \ | |
71 "SOAPACTION: \"urn:schemas-upnp-org:service:%s#%s\"\r\n" \ | |
72 "CONTENT-TYPE: text/xml ; charset=\"utf-8\"\r\n" \ | |
73 "CONTENT-LENGTH: %" G_GSIZE_FORMAT "\r\n\r\n" | |
74 | |
75 #define SOAP_ACTION \ | |
76 "<?xml version=\"1.0\" encoding=\"utf-8\"?>\r\n" \ | |
77 "<s:Envelope xmlns:s=\"http://schemas.xmlsoap.org/soap/envelope/\" " \ | |
78 "s:encodingStyle=\"http://schemas.xmlsoap.org/soap/encoding/\">\r\n" \ | |
79 "<s:Body>\r\n" \ | |
80 "<u:%s xmlns:u=\"urn:schemas-upnp-org:service:%s\">\r\n" \ | |
81 "%s" \ | |
82 "</u:%s>\r\n" \ | |
83 "</s:Body>\r\n" \ | |
84 "</s:Envelope>" | |
85 | |
86 #define PORT_MAPPING_LEASE_TIME "0" | |
87 #define PORT_MAPPING_DESCRIPTION "GAIM_UPNP_PORT_FORWARD" | |
88 | |
89 #define ADD_PORT_MAPPING_PARAMS \ | |
90 "<NewRemoteHost></NewRemoteHost>\r\n" \ | |
91 "<NewExternalPort>%i</NewExternalPort>\r\n" \ | |
92 "<NewProtocol>%s</NewProtocol>\r\n" \ | |
93 "<NewInternalPort>%i</NewInternalPort>\r\n" \ | |
94 "<NewInternalClient>%s</NewInternalClient>\r\n" \ | |
95 "<NewEnabled>1</NewEnabled>\r\n" \ | |
96 "<NewPortMappingDescription>" \ | |
97 PORT_MAPPING_DESCRIPTION \ | |
98 "</NewPortMappingDescription>\r\n" \ | |
99 "<NewLeaseDuration>" \ | |
100 PORT_MAPPING_LEASE_TIME \ | |
101 "</NewLeaseDuration>\r\n" | |
102 | |
103 #define DELETE_PORT_MAPPING_PARAMS \ | |
104 "<NewRemoteHost></NewRemoteHost>\r\n" \ | |
105 "<NewExternalPort>%i</NewExternalPort>\r\n" \ | |
106 "<NewProtocol>%s</NewProtocol>\r\n" | |
107 | |
108 typedef enum { | |
109 GAIM_UPNP_STATUS_UNDISCOVERED = -1, | |
110 GAIM_UPNP_STATUS_UNABLE_TO_DISCOVER, | |
111 GAIM_UPNP_STATUS_DISCOVERING, | |
112 GAIM_UPNP_STATUS_DISCOVERED | |
113 } GaimUPnPStatus; | |
114 | |
115 typedef struct { | |
116 GaimUPnPStatus status; | |
117 gchar* control_url; | |
118 gchar service_type[20]; | |
119 char publicip[16]; | |
120 char internalip[16]; | |
121 time_t lookup_time; | |
122 } GaimUPnPControlInfo; | |
123 | |
124 typedef struct { | |
125 guint inpa; /* gaim_input_add handle */ | |
126 guint tima; /* gaim_timeout_add handle */ | |
127 int fd; | |
128 struct sockaddr_in server; | |
129 gchar service_type[25]; | |
130 int retry_count; | |
131 gchar *full_url; | |
132 } UPnPDiscoveryData; | |
133 | |
134 typedef struct { | |
135 unsigned short portmap; | |
136 gchar protocol[4]; | |
137 gboolean add; | |
138 GaimUPnPCallback cb; | |
139 gpointer cb_data; | |
140 } UPnPMappingAddRemove; | |
141 | |
142 static GaimUPnPControlInfo control_info = { | |
143 GAIM_UPNP_STATUS_UNDISCOVERED, | |
144 NULL, "\0", "\0", "\0", 0}; | |
145 | |
146 static GSList *discovery_callbacks = NULL; | |
147 | |
148 static void gaim_upnp_discover_send_broadcast(UPnPDiscoveryData *dd); | |
149 static void lookup_public_ip(void); | |
150 static void lookup_internal_ip(void); | |
151 | |
152 static void | |
153 fire_discovery_callbacks(gboolean success) | |
154 { | |
155 while(discovery_callbacks) { | |
156 gpointer data; | |
157 GaimUPnPCallback cb = discovery_callbacks->data; | |
158 discovery_callbacks = g_slist_remove(discovery_callbacks, cb); | |
159 data = discovery_callbacks->data; | |
160 discovery_callbacks = g_slist_remove(discovery_callbacks, data); | |
161 cb(success, data); | |
162 } | |
163 } | |
164 | |
165 static gboolean | |
166 gaim_upnp_compare_device(const xmlnode* device, const gchar* deviceType) | |
167 { | |
168 xmlnode* deviceTypeNode = xmlnode_get_child(device, "deviceType"); | |
169 char *tmp; | |
170 gboolean ret; | |
171 | |
172 if(deviceTypeNode == NULL) { | |
173 return FALSE; | |
174 } | |
175 | |
176 tmp = xmlnode_get_data(deviceTypeNode); | |
177 ret = !g_ascii_strcasecmp(tmp, deviceType); | |
178 g_free(tmp); | |
179 | |
180 return ret; | |
181 } | |
182 | |
183 static gboolean | |
184 gaim_upnp_compare_service(const xmlnode* service, const gchar* serviceType) | |
185 { | |
186 xmlnode* serviceTypeNode; | |
187 char *tmp; | |
188 gboolean ret; | |
189 | |
190 if(service == NULL) { | |
191 return FALSE; | |
192 } | |
193 | |
194 serviceTypeNode = xmlnode_get_child(service, "serviceType"); | |
195 | |
196 if(serviceTypeNode == NULL) { | |
197 return FALSE; | |
198 } | |
199 | |
200 tmp = xmlnode_get_data(serviceTypeNode); | |
201 ret = !g_ascii_strcasecmp(tmp, serviceType); | |
202 g_free(tmp); | |
203 | |
204 return ret; | |
205 } | |
206 | |
207 static gchar* | |
208 gaim_upnp_parse_description_response(const gchar* httpResponse, gsize len, | |
209 const gchar* httpURL, const gchar* serviceType) | |
210 { | |
211 gchar *xmlRoot, *baseURL, *controlURL, *service; | |
212 xmlnode *xmlRootNode, *serviceTypeNode, *controlURLNode, *baseURLNode; | |
213 char *tmp; | |
214 | |
215 /* make sure we have a valid http response */ | |
216 if(g_strstr_len(httpResponse, len, HTTP_OK) == NULL) { | |
217 gaim_debug_error("upnp", | |
14837 | 218 "parse_description_response(): Failed In HTTP_OK\n"); |
14192 | 219 return NULL; |
220 } | |
221 | |
222 /* find the root of the xml document */ | |
223 if((xmlRoot = g_strstr_len(httpResponse, len, "<root")) == NULL) { | |
224 gaim_debug_error("upnp", | |
14837 | 225 "parse_description_response(): Failed finding root\n"); |
14192 | 226 return NULL; |
227 } | |
228 | |
229 /* create the xml root node */ | |
230 if((xmlRootNode = xmlnode_from_str(xmlRoot, | |
231 len - (xmlRoot - httpResponse))) == NULL) { | |
232 gaim_debug_error("upnp", | |
14837 | 233 "parse_description_response(): Could not parse xml root node\n"); |
14192 | 234 return NULL; |
235 } | |
236 | |
237 /* get the baseURL of the device */ | |
238 if((baseURLNode = xmlnode_get_child(xmlRootNode, "URLBase")) != NULL) { | |
239 baseURL = xmlnode_get_data(baseURLNode); | |
240 } else { | |
241 baseURL = g_strdup(httpURL); | |
242 } | |
243 | |
244 /* get the serviceType child that has the service type as its data */ | |
245 | |
246 /* get urn:schemas-upnp-org:device:InternetGatewayDevice:1 and its devicelist */ | |
247 serviceTypeNode = xmlnode_get_child(xmlRootNode, "device"); | |
248 while(!gaim_upnp_compare_device(serviceTypeNode, | |
249 "urn:schemas-upnp-org:device:InternetGatewayDevice:1") && | |
250 serviceTypeNode != NULL) { | |
251 serviceTypeNode = xmlnode_get_next_twin(serviceTypeNode); | |
252 } | |
253 if(serviceTypeNode == NULL) { | |
254 gaim_debug_error("upnp", | |
14837 | 255 "parse_description_response(): could not get serviceTypeNode 1\n"); |
14192 | 256 g_free(baseURL); |
257 xmlnode_free(xmlRootNode); | |
258 return NULL; | |
259 } | |
260 serviceTypeNode = xmlnode_get_child(serviceTypeNode, "deviceList"); | |
261 if(serviceTypeNode == NULL) { | |
262 gaim_debug_error("upnp", | |
14837 | 263 "parse_description_response(): could not get serviceTypeNode 2\n"); |
14192 | 264 g_free(baseURL); |
265 xmlnode_free(xmlRootNode); | |
266 return NULL; | |
267 } | |
268 | |
269 /* get urn:schemas-upnp-org:device:WANDevice:1 and its devicelist */ | |
270 serviceTypeNode = xmlnode_get_child(serviceTypeNode, "device"); | |
271 while(!gaim_upnp_compare_device(serviceTypeNode, | |
272 "urn:schemas-upnp-org:device:WANDevice:1") && | |
273 serviceTypeNode != NULL) { | |
274 serviceTypeNode = xmlnode_get_next_twin(serviceTypeNode); | |
275 } | |
276 if(serviceTypeNode == NULL) { | |
277 gaim_debug_error("upnp", | |
14837 | 278 "parse_description_response(): could not get serviceTypeNode 3\n"); |
14192 | 279 g_free(baseURL); |
280 xmlnode_free(xmlRootNode); | |
281 return NULL; | |
282 } | |
283 serviceTypeNode = xmlnode_get_child(serviceTypeNode, "deviceList"); | |
284 if(serviceTypeNode == NULL) { | |
285 gaim_debug_error("upnp", | |
14837 | 286 "parse_description_response(): could not get serviceTypeNode 4\n"); |
14192 | 287 g_free(baseURL); |
288 xmlnode_free(xmlRootNode); | |
289 return NULL; | |
290 } | |
291 | |
292 /* get urn:schemas-upnp-org:device:WANConnectionDevice:1 and its servicelist */ | |
293 serviceTypeNode = xmlnode_get_child(serviceTypeNode, "device"); | |
294 while(serviceTypeNode && !gaim_upnp_compare_device(serviceTypeNode, | |
295 "urn:schemas-upnp-org:device:WANConnectionDevice:1")) { | |
296 serviceTypeNode = xmlnode_get_next_twin(serviceTypeNode); | |
297 } | |
298 if(serviceTypeNode == NULL) { | |
299 gaim_debug_error("upnp", | |
14837 | 300 "parse_description_response(): could not get serviceTypeNode 5\n"); |
14192 | 301 g_free(baseURL); |
302 xmlnode_free(xmlRootNode); | |
303 return NULL; | |
304 } | |
305 serviceTypeNode = xmlnode_get_child(serviceTypeNode, "serviceList"); | |
306 if(serviceTypeNode == NULL) { | |
307 gaim_debug_error("upnp", | |
14837 | 308 "parse_description_response(): could not get serviceTypeNode 6\n"); |
14192 | 309 g_free(baseURL); |
310 xmlnode_free(xmlRootNode); | |
311 return NULL; | |
312 } | |
313 | |
314 /* get the serviceType variable passed to this function */ | |
315 service = g_strdup_printf(SEARCH_REQUEST_DEVICE, serviceType); | |
316 serviceTypeNode = xmlnode_get_child(serviceTypeNode, "service"); | |
317 while(!gaim_upnp_compare_service(serviceTypeNode, service) && | |
318 serviceTypeNode != NULL) { | |
319 serviceTypeNode = xmlnode_get_next_twin(serviceTypeNode); | |
320 } | |
321 | |
322 g_free(service); | |
323 if(serviceTypeNode == NULL) { | |
324 gaim_debug_error("upnp", | |
14837 | 325 "parse_description_response(): could not get serviceTypeNode 7\n"); |
14192 | 326 g_free(baseURL); |
327 xmlnode_free(xmlRootNode); | |
328 return NULL; | |
329 } | |
330 | |
331 /* get the controlURL of the service */ | |
332 if((controlURLNode = xmlnode_get_child(serviceTypeNode, | |
333 "controlURL")) == NULL) { | |
334 gaim_debug_error("upnp", | |
14837 | 335 "parse_description_response(): Could not find controlURL\n"); |
14192 | 336 g_free(baseURL); |
337 xmlnode_free(xmlRootNode); | |
338 return NULL; | |
339 } | |
340 | |
341 tmp = xmlnode_get_data(controlURLNode); | |
15228
192c66c0c2cb
[gaim-migrate @ 18018]
Daniel Atallah <daniel.atallah@gmail.com>
parents:
14837
diff
changeset
|
342 if(baseURL && !gaim_str_has_prefix(tmp, "http://") && |
14192 | 343 !gaim_str_has_prefix(tmp, "HTTP://")) { |
344 controlURL = g_strdup_printf("%s%s", baseURL, tmp); | |
345 g_free(tmp); | |
346 }else{ | |
347 controlURL = tmp; | |
348 } | |
349 g_free(baseURL); | |
350 xmlnode_free(xmlRootNode); | |
351 | |
352 return controlURL; | |
353 } | |
354 | |
355 static void | |
14354 | 356 upnp_parse_description_cb(GaimUtilFetchUrlData *url_data, gpointer user_data, |
357 const gchar *httpResponse, gsize len, const gchar *error_message) | |
14192 | 358 { |
14354 | 359 UPnPDiscoveryData *dd = user_data; |
14192 | 360 gchar *control_url = NULL; |
361 | |
362 if (len > 0) | |
363 control_url = gaim_upnp_parse_description_response( | |
364 httpResponse, len, dd->full_url, dd->service_type); | |
365 | |
366 g_free(dd->full_url); | |
367 | |
368 if(control_url == NULL) { | |
369 gaim_debug_error("upnp", | |
14837 | 370 "gaim_upnp_parse_description(): control URL is NULL\n"); |
14192 | 371 } |
372 | |
373 control_info.status = control_url ? GAIM_UPNP_STATUS_DISCOVERED | |
374 : GAIM_UPNP_STATUS_UNABLE_TO_DISCOVER; | |
375 control_info.lookup_time = time(NULL); | |
376 control_info.control_url = control_url; | |
377 strncpy(control_info.service_type, dd->service_type, | |
378 sizeof(control_info.service_type)); | |
379 | |
380 fire_discovery_callbacks(control_url != NULL); | |
381 | |
382 /* Look up the public and internal IPs */ | |
383 if(control_url != NULL) { | |
384 lookup_public_ip(); | |
385 lookup_internal_ip(); | |
386 } | |
387 | |
388 g_free(dd); | |
389 } | |
390 | |
391 static void | |
392 gaim_upnp_parse_description(const gchar* descriptionURL, UPnPDiscoveryData *dd) | |
393 { | |
394 gchar* httpRequest; | |
395 gchar* descriptionXMLAddress; | |
396 gchar* descriptionAddress; | |
397 int port = 0; | |
398 | |
399 /* parse the 4 above variables out of the descriptionURL | |
400 example description URL: http://192.168.1.1:5678/rootDesc.xml */ | |
401 | |
402 /* parse the url into address, port, path variables */ | |
403 if(!gaim_url_parse(descriptionURL, &descriptionAddress, | |
404 &port, &descriptionXMLAddress, NULL, NULL)) { | |
405 return; | |
406 } | |
407 if(port == 0 || port == -1) { | |
408 port = DEFAULT_HTTP_PORT; | |
409 } | |
410 | |
411 /* for example... | |
412 GET /rootDesc.xml HTTP/1.1\r\nHost: 192.168.1.1:5678\r\n\r\n */ | |
413 httpRequest = g_strdup_printf( | |
414 "GET /%s HTTP/1.1\r\n" | |
415 "Connection: close\r\n" | |
416 "Host: %s:%d\r\n\r\n", | |
417 descriptionXMLAddress, descriptionAddress, port); | |
418 | |
419 g_free(descriptionXMLAddress); | |
420 | |
421 dd->full_url = g_strdup_printf("http://%s:%d", | |
422 descriptionAddress, port); | |
423 g_free(descriptionAddress); | |
424 | |
425 /* Remove the timeout because everything it is waiting for has | |
426 * successfully completed */ | |
427 gaim_timeout_remove(dd->tima); | |
428 dd->tima = 0; | |
429 | |
14354 | 430 gaim_util_fetch_url_request(descriptionURL, TRUE, NULL, TRUE, httpRequest, |
14192 | 431 TRUE, upnp_parse_description_cb, dd); |
432 | |
433 g_free(httpRequest); | |
434 | |
435 } | |
436 | |
437 static void | |
438 gaim_upnp_parse_discover_response(const gchar* buf, unsigned int buf_len, | |
439 UPnPDiscoveryData *dd) | |
440 { | |
441 gchar* startDescURL; | |
442 gchar* endDescURL; | |
443 gchar* descURL; | |
444 | |
445 if(g_strstr_len(buf, buf_len, HTTP_OK) == NULL) { | |
446 gaim_debug_error("upnp", | |
14837 | 447 "parse_discover_response(): Failed In HTTP_OK\n"); |
14192 | 448 return; |
449 } | |
450 | |
451 if((startDescURL = g_strstr_len(buf, buf_len, "http://")) == NULL) { | |
452 gaim_debug_error("upnp", | |
14837 | 453 "parse_discover_response(): Failed In finding http://\n"); |
14192 | 454 return; |
455 } | |
456 | |
457 endDescURL = g_strstr_len(startDescURL, buf_len - (startDescURL - buf), | |
458 "\r"); | |
459 if(endDescURL == NULL) { | |
460 endDescURL = g_strstr_len(startDescURL, | |
461 buf_len - (startDescURL - buf), "\n"); | |
462 if(endDescURL == NULL) { | |
463 gaim_debug_error("upnp", | |
14837 | 464 "parse_discover_response(): Failed In endDescURL\n"); |
14192 | 465 return; |
466 } | |
467 } | |
468 | |
469 /* XXX: I'm not sure how this could ever happen */ | |
470 if(endDescURL == startDescURL) { | |
471 gaim_debug_error("upnp", | |
14837 | 472 "parse_discover_response(): endDescURL == startDescURL\n"); |
14192 | 473 return; |
474 } | |
475 | |
476 descURL = g_strndup(startDescURL, endDescURL - startDescURL); | |
477 | |
478 gaim_upnp_parse_description(descURL, dd); | |
479 | |
480 g_free(descURL); | |
481 | |
482 } | |
483 | |
484 static gboolean | |
485 gaim_upnp_discover_timeout(gpointer data) | |
486 { | |
487 UPnPDiscoveryData* dd = data; | |
488 | |
489 if (dd->inpa) | |
490 gaim_input_remove(dd->inpa); | |
491 dd->inpa = 0; | |
492 dd->tima = 0; | |
493 | |
494 if (dd->retry_count < NUM_UDP_ATTEMPTS) { | |
14837 | 495 /* TODO: We probably shouldn't be incrementing retry_count in two places */ |
14192 | 496 dd->retry_count++; |
497 gaim_upnp_discover_send_broadcast(dd); | |
498 } else { | |
499 if (dd->fd) | |
500 close(dd->fd); | |
501 | |
502 control_info.status = GAIM_UPNP_STATUS_UNABLE_TO_DISCOVER; | |
503 control_info.lookup_time = time(NULL); | |
504 control_info.service_type[0] = '\0'; | |
505 g_free(control_info.control_url); | |
506 control_info.control_url = NULL; | |
507 | |
508 fire_discovery_callbacks(FALSE); | |
509 | |
510 g_free(dd); | |
511 } | |
512 | |
513 return FALSE; | |
514 } | |
515 | |
516 static void | |
517 gaim_upnp_discover_udp_read(gpointer data, gint sock, GaimInputCondition cond) | |
518 { | |
519 int len; | |
520 UPnPDiscoveryData *dd = data; | |
521 gchar buf[65536]; | |
522 | |
523 do { | |
524 len = recv(dd->fd, buf, | |
525 sizeof(buf) - 1, 0); | |
526 | |
527 if(len > 0) { | |
528 buf[len] = '\0'; | |
529 break; | |
530 } else if(errno != EINTR) { | |
531 /* We'll either get called again, or time out */ | |
532 return; | |
533 } | |
534 } while (errno == EINTR); | |
535 | |
536 gaim_input_remove(dd->inpa); | |
537 dd->inpa = 0; | |
538 | |
539 close(dd->fd); | |
540 dd->fd = 0; | |
541 | |
542 /* parse the response, and see if it was a success */ | |
543 gaim_upnp_parse_discover_response(buf, len, dd); | |
544 | |
545 /* We'll either time out or continue successfully */ | |
546 } | |
547 | |
14354 | 548 static void |
14192 | 549 gaim_upnp_discover_send_broadcast(UPnPDiscoveryData *dd) |
550 { | |
551 gchar *sendMessage = NULL; | |
552 gsize totalSize; | |
553 gboolean sentSuccess; | |
554 | |
555 /* because we are sending over UDP, if there is a failure | |
556 we should retry the send NUM_UDP_ATTEMPTS times. Also, | |
557 try different requests for WANIPConnection and WANPPPConnection*/ | |
558 for(; dd->retry_count < NUM_UDP_ATTEMPTS; dd->retry_count++) { | |
559 sentSuccess = FALSE; | |
560 | |
561 if((dd->retry_count % 2) == 0) { | |
562 strncpy(dd->service_type, WAN_IP_CONN_SERVICE, sizeof(dd->service_type)); | |
563 } else { | |
564 strncpy(dd->service_type, WAN_PPP_CONN_SERVICE, sizeof(dd->service_type)); | |
565 } | |
566 | |
567 sendMessage = g_strdup_printf(SEARCH_REQUEST_STRING, dd->service_type); | |
568 | |
569 totalSize = strlen(sendMessage); | |
570 | |
571 do { | |
572 if(sendto(dd->fd, sendMessage, totalSize, 0, | |
573 (struct sockaddr*) &(dd->server), | |
574 sizeof(struct sockaddr_in) | |
575 ) == totalSize) { | |
576 sentSuccess = TRUE; | |
577 break; | |
578 } | |
579 } while (errno == EINTR || errno == EAGAIN); | |
580 | |
581 g_free(sendMessage); | |
582 | |
583 if(sentSuccess) { | |
584 dd->tima = gaim_timeout_add(DISCOVERY_TIMEOUT, | |
585 gaim_upnp_discover_timeout, dd); | |
586 dd->inpa = gaim_input_add(dd->fd, GAIM_INPUT_READ, | |
587 gaim_upnp_discover_udp_read, dd); | |
588 | |
589 return; | |
590 } | |
591 } | |
592 | |
593 /* We have already done all our retries. Make sure that the callback | |
594 * doesn't get called before the original function returns */ | |
595 gaim_timeout_add(10, gaim_upnp_discover_timeout, dd); | |
596 } | |
597 | |
598 void | |
599 gaim_upnp_discover(GaimUPnPCallback cb, gpointer cb_data) | |
600 { | |
601 /* Socket Setup Variables */ | |
602 int sock; | |
603 struct hostent* hp; | |
604 | |
605 /* UDP RECEIVE VARIABLES */ | |
606 UPnPDiscoveryData *dd; | |
607 | |
608 if (control_info.status == GAIM_UPNP_STATUS_DISCOVERING) { | |
609 if (cb) { | |
610 discovery_callbacks = g_slist_append( | |
611 discovery_callbacks, cb); | |
612 discovery_callbacks = g_slist_append( | |
613 discovery_callbacks, cb_data); | |
614 } | |
615 return; | |
616 } | |
617 | |
618 dd = g_new0(UPnPDiscoveryData, 1); | |
619 if (cb) { | |
620 discovery_callbacks = g_slist_append(discovery_callbacks, cb); | |
621 discovery_callbacks = g_slist_append(discovery_callbacks, | |
622 cb_data); | |
623 } | |
624 | |
625 /* Set up the sockets */ | |
626 sock = socket(AF_INET, SOCK_DGRAM, 0); | |
627 if(sock == -1) { | |
628 gaim_debug_error("upnp", | |
14837 | 629 "gaim_upnp_discover(): Failed In sock creation\n"); |
14192 | 630 /* Short circuit the retry attempts */ |
631 dd->retry_count = NUM_UDP_ATTEMPTS; | |
632 gaim_timeout_add(10, gaim_upnp_discover_timeout, dd); | |
633 return; | |
634 } | |
635 | |
636 dd->fd = sock; | |
637 | |
14837 | 638 /* TODO: Non-blocking! */ |
14192 | 639 if((hp = gethostbyname(HTTPMU_HOST_ADDRESS)) == NULL) { |
640 gaim_debug_error("upnp", | |
14837 | 641 "gaim_upnp_discover(): Failed In gethostbyname\n"); |
14192 | 642 /* Short circuit the retry attempts */ |
643 dd->retry_count = NUM_UDP_ATTEMPTS; | |
644 gaim_timeout_add(10, gaim_upnp_discover_timeout, dd); | |
645 return; | |
646 } | |
647 | |
648 memset(&(dd->server), 0, sizeof(struct sockaddr)); | |
649 dd->server.sin_family = AF_INET; | |
650 memcpy(&(dd->server.sin_addr), hp->h_addr_list[0], hp->h_length); | |
651 dd->server.sin_port = htons(HTTPMU_HOST_PORT); | |
652 | |
653 control_info.status = GAIM_UPNP_STATUS_DISCOVERING; | |
654 | |
655 gaim_upnp_discover_send_broadcast(dd); | |
656 } | |
657 | |
658 static void | |
659 gaim_upnp_generate_action_message_and_send(const gchar* actionName, | |
14354 | 660 const gchar* actionParams, GaimUtilFetchUrlCallback cb, |
14192 | 661 gpointer cb_data) |
662 { | |
663 | |
664 gchar* soapMessage; | |
665 gchar* totalSendMessage; | |
666 gchar* pathOfControl; | |
667 gchar* addressOfControl; | |
668 int port = 0; | |
669 | |
670 /* parse the url into address, port, path variables */ | |
671 if(!gaim_url_parse(control_info.control_url, &addressOfControl, | |
672 &port, &pathOfControl, NULL, NULL)) { | |
673 gaim_debug_error("upnp", | |
14837 | 674 "generate_action_message_and_send(): Failed In Parse URL\n"); |
14192 | 675 /* XXX: This should probably be async */ |
676 if(cb) | |
14354 | 677 cb(NULL, cb_data, NULL, 0, NULL); |
14192 | 678 } |
679 if(port == 0 || port == -1) { | |
680 port = DEFAULT_HTTP_PORT; | |
681 } | |
682 | |
683 /* set the soap message */ | |
684 soapMessage = g_strdup_printf(SOAP_ACTION, actionName, | |
685 control_info.service_type, actionParams, actionName); | |
686 | |
687 /* set the HTTP Header, and append the body to it */ | |
688 totalSendMessage = g_strdup_printf(HTTP_HEADER_ACTION "%s", | |
689 pathOfControl, addressOfControl, port, | |
690 control_info.service_type, actionName, | |
691 strlen(soapMessage), soapMessage); | |
692 g_free(pathOfControl); | |
693 g_free(soapMessage); | |
694 | |
14354 | 695 gaim_util_fetch_url_request(control_info.control_url, FALSE, NULL, TRUE, |
14192 | 696 totalSendMessage, TRUE, cb, cb_data); |
697 | |
698 g_free(totalSendMessage); | |
699 g_free(addressOfControl); | |
700 } | |
701 | |
702 const gchar * | |
703 gaim_upnp_get_public_ip() | |
704 { | |
705 if (control_info.status == GAIM_UPNP_STATUS_DISCOVERED | |
706 && control_info.publicip | |
707 && strlen(control_info.publicip) > 0) | |
708 return control_info.publicip; | |
709 | |
710 /* Trigger another UPnP discovery if 5 minutes have elapsed since the | |
711 * last one, and it wasn't successful */ | |
712 if (control_info.status < GAIM_UPNP_STATUS_DISCOVERING | |
713 && (time(NULL) - control_info.lookup_time) > 300) | |
714 gaim_upnp_discover(NULL, NULL); | |
715 | |
716 return NULL; | |
717 } | |
718 | |
719 static void | |
14354 | 720 looked_up_public_ip_cb(GaimUtilFetchUrlData *url_data, gpointer user_data, |
721 const gchar *httpResponse, gsize len, const gchar *error_message) | |
14192 | 722 { |
723 gchar* temp, *temp2; | |
724 | |
14354 | 725 if ((error_message != NULL) || (httpResponse == NULL)) |
14192 | 726 return; |
727 | |
728 /* extract the ip, or see if there is an error */ | |
729 if((temp = g_strstr_len(httpResponse, len, | |
730 "<NewExternalIPAddress")) == NULL) { | |
731 gaim_debug_error("upnp", | |
14837 | 732 "looked_up_public_ip_cb(): Failed Finding <NewExternalIPAddress\n"); |
14192 | 733 return; |
734 } | |
735 if(!(temp = g_strstr_len(temp, len - (temp - httpResponse), ">"))) { | |
736 gaim_debug_error("upnp", | |
14837 | 737 "looked_up_public_ip_cb(): Failed In Finding >\n"); |
14192 | 738 return; |
739 } | |
740 if(!(temp2 = g_strstr_len(temp, len - (temp - httpResponse), "<"))) { | |
741 gaim_debug_error("upnp", | |
14837 | 742 "looked_up_public_ip_cb(): Failed In Finding <\n"); |
14192 | 743 return; |
744 } | |
745 *temp2 = '\0'; | |
746 | |
747 strncpy(control_info.publicip, temp + 1, | |
748 sizeof(control_info.publicip)); | |
749 | |
750 gaim_debug_info("upnp", "NAT Returned IP: %s\n", control_info.publicip); | |
751 } | |
752 | |
14354 | 753 static void |
14192 | 754 lookup_public_ip() |
755 { | |
756 gaim_upnp_generate_action_message_and_send("GetExternalIPAddress", "", | |
757 looked_up_public_ip_cb, NULL); | |
758 } | |
759 | |
760 /* TODO: This could be exported */ | |
761 static const gchar * | |
762 gaim_upnp_get_internal_ip() | |
763 { | |
764 if (control_info.status == GAIM_UPNP_STATUS_DISCOVERED | |
765 && control_info.internalip | |
766 && strlen(control_info.internalip) > 0) | |
767 return control_info.internalip; | |
768 | |
769 /* Trigger another UPnP discovery if 5 minutes have elapsed since the | |
770 * last one, and it wasn't successful */ | |
771 if (control_info.status < GAIM_UPNP_STATUS_DISCOVERING | |
772 && (time(NULL) - control_info.lookup_time) > 300) | |
773 gaim_upnp_discover(NULL, NULL); | |
774 | |
775 return NULL; | |
776 } | |
777 | |
778 static void | |
779 looked_up_internal_ip_cb(gpointer data, gint source, const gchar *error_message) | |
780 { | |
781 if (source) { | |
782 strncpy(control_info.internalip, | |
783 gaim_network_get_local_system_ip(source), | |
784 sizeof(control_info.internalip)); | |
785 gaim_debug_info("upnp", "Local IP: %s\n", | |
786 control_info.internalip); | |
787 close(source); | |
788 } else | |
789 gaim_debug_info("upnp", "Unable to look up local IP\n"); | |
790 | |
791 } | |
792 | |
14354 | 793 static void |
14192 | 794 lookup_internal_ip() |
795 { | |
796 gchar* addressOfControl; | |
797 int port = 0; | |
798 | |
799 if(!gaim_url_parse(control_info.control_url, &addressOfControl, &port, | |
800 NULL, NULL, NULL)) { | |
801 gaim_debug_error("upnp", | |
14837 | 802 "lookup_internal_ip(): Failed In Parse URL\n"); |
14192 | 803 return; |
804 } | |
805 if(port == 0 || port == -1) { | |
806 port = DEFAULT_HTTP_PORT; | |
807 } | |
808 | |
14837 | 809 if(gaim_proxy_connect(NULL, NULL, addressOfControl, port, |
14192 | 810 looked_up_internal_ip_cb, NULL) == NULL) |
811 { | |
812 gaim_debug_error("upnp", "Get Local IP Connect Failed: Address: %s @@@ Port %d\n", | |
813 addressOfControl, port); | |
814 } | |
815 | |
816 g_free(addressOfControl); | |
817 } | |
818 | |
819 static void | |
14354 | 820 done_port_mapping_cb(GaimUtilFetchUrlData *url_data, gpointer user_data, |
821 const gchar *httpResponse, gsize len, const gchar *error_message) | |
14192 | 822 { |
14354 | 823 UPnPMappingAddRemove *ar = user_data; |
14192 | 824 |
825 gboolean success = TRUE; | |
826 | |
827 /* determine if port mapping was a success */ | |
14354 | 828 if ((error_message != NULL) || (httpResponse == NULL) || |
829 (g_strstr_len(httpResponse, len, HTTP_OK) == NULL)) | |
830 { | |
14192 | 831 gaim_debug_error("upnp", |
14837 | 832 "gaim_upnp_set_port_mapping(): Failed HTTP_OK\n%s\n", |
14192 | 833 httpResponse ? httpResponse : "(null)"); |
834 success = FALSE; | |
835 } else | |
836 gaim_debug_info("upnp", "Successfully completed port mapping operation\n"); | |
837 | |
838 if (ar->cb) | |
839 ar->cb(success, ar->cb_data); | |
840 g_free(ar); | |
841 } | |
842 | |
843 static void | |
844 do_port_mapping_cb(gboolean has_control_mapping, gpointer data) | |
845 { | |
846 UPnPMappingAddRemove *ar = data; | |
847 | |
848 if (has_control_mapping) { | |
849 gchar action_name[25]; | |
850 gchar *action_params; | |
851 if(ar->add) { | |
852 const gchar *internal_ip; | |
853 /* get the internal IP */ | |
854 if(!(internal_ip = gaim_upnp_get_internal_ip())) { | |
855 gaim_debug_error("upnp", | |
14837 | 856 "gaim_upnp_set_port_mapping(): couldn't get local ip\n"); |
14192 | 857 /* UGLY */ |
858 if (ar->cb) | |
859 ar->cb(FALSE, ar->cb_data); | |
860 g_free(ar); | |
861 return; | |
862 } | |
863 strncpy(action_name, "AddPortMapping", | |
864 sizeof(action_name)); | |
865 action_params = g_strdup_printf( | |
866 ADD_PORT_MAPPING_PARAMS, | |
867 ar->portmap, ar->protocol, ar->portmap, | |
868 internal_ip); | |
869 } else { | |
870 strncpy(action_name, "DeletePortMapping", sizeof(action_name)); | |
871 action_params = g_strdup_printf( | |
872 DELETE_PORT_MAPPING_PARAMS, | |
873 ar->portmap, ar->protocol); | |
874 } | |
875 | |
876 gaim_upnp_generate_action_message_and_send(action_name, | |
877 action_params, done_port_mapping_cb, ar); | |
878 | |
879 g_free(action_params); | |
880 return; | |
881 } | |
882 | |
883 | |
884 if (ar->cb) | |
885 ar->cb(FALSE, ar->cb_data); | |
886 g_free(ar); | |
887 } | |
888 | |
889 static gboolean | |
890 fire_port_mapping_failure_cb(gpointer data) | |
891 { | |
892 do_port_mapping_cb(FALSE, data); | |
893 return FALSE; | |
894 } | |
895 | |
896 void | |
897 gaim_upnp_set_port_mapping(unsigned short portmap, const gchar* protocol, | |
898 GaimUPnPCallback cb, gpointer cb_data) | |
899 { | |
900 UPnPMappingAddRemove *ar; | |
901 | |
902 ar = g_new0(UPnPMappingAddRemove, 1); | |
903 ar->cb = cb; | |
904 ar->cb_data = cb_data; | |
905 ar->add = TRUE; | |
906 ar->portmap = portmap; | |
907 strncpy(ar->protocol, protocol, sizeof(ar->protocol)); | |
908 | |
909 /* If we're waiting for a discovery, add to the callbacks list */ | |
910 if(control_info.status == GAIM_UPNP_STATUS_DISCOVERING) { | |
911 /* TODO: This will fail because when this cb is triggered, | |
912 * the internal IP lookup won't be complete */ | |
913 discovery_callbacks = g_slist_append( | |
914 discovery_callbacks, do_port_mapping_cb); | |
915 discovery_callbacks = g_slist_append( | |
916 discovery_callbacks, ar); | |
917 return; | |
918 } | |
919 | |
920 /* If we haven't had a successful UPnP discovery, check if 5 minutes has | |
921 * elapsed since the last try, try again */ | |
922 if(control_info.status == GAIM_UPNP_STATUS_UNDISCOVERED || | |
923 (control_info.status == GAIM_UPNP_STATUS_UNABLE_TO_DISCOVER | |
924 && (time(NULL) - control_info.lookup_time) > 300)) { | |
925 gaim_upnp_discover(do_port_mapping_cb, ar); | |
926 return; | |
927 } else if(control_info.status == GAIM_UPNP_STATUS_UNABLE_TO_DISCOVER) { | |
928 if (cb) { | |
929 /* Asynchronously trigger a failed response */ | |
930 gaim_timeout_add(10, fire_port_mapping_failure_cb, ar); | |
931 } else { | |
932 /* No need to do anything if nobody expects a response*/ | |
933 g_free(ar); | |
934 } | |
935 return; | |
936 } | |
937 | |
938 do_port_mapping_cb(TRUE, ar); | |
939 } | |
940 | |
941 void | |
942 gaim_upnp_remove_port_mapping(unsigned short portmap, const char* protocol, | |
943 GaimUPnPCallback cb, gpointer cb_data) | |
944 { | |
945 UPnPMappingAddRemove *ar; | |
946 | |
947 ar = g_new0(UPnPMappingAddRemove, 1); | |
948 ar->cb = cb; | |
949 ar->cb_data = cb_data; | |
950 ar->add = FALSE; | |
951 ar->portmap = portmap; | |
952 strncpy(ar->protocol, protocol, sizeof(ar->protocol)); | |
953 | |
954 /* If we're waiting for a discovery, add to the callbacks list */ | |
955 if(control_info.status == GAIM_UPNP_STATUS_DISCOVERING) { | |
956 discovery_callbacks = g_slist_append( | |
957 discovery_callbacks, do_port_mapping_cb); | |
958 discovery_callbacks = g_slist_append( | |
959 discovery_callbacks, ar); | |
960 return; | |
961 } | |
962 | |
963 /* If we haven't had a successful UPnP discovery, check if 5 minutes has | |
964 * elapsed since the last try, try again */ | |
965 if(control_info.status == GAIM_UPNP_STATUS_UNDISCOVERED || | |
966 (control_info.status == GAIM_UPNP_STATUS_UNABLE_TO_DISCOVER | |
967 && (time(NULL) - control_info.lookup_time) > 300)) { | |
968 gaim_upnp_discover(do_port_mapping_cb, ar); | |
969 return; | |
970 } else if(control_info.status == GAIM_UPNP_STATUS_UNABLE_TO_DISCOVER) { | |
971 if (cb) { | |
972 /* Asynchronously trigger a failed response */ | |
973 gaim_timeout_add(10, fire_port_mapping_failure_cb, ar); | |
974 } else { | |
975 /* No need to do anything if nobody expects a response*/ | |
976 g_free(ar); | |
977 } | |
978 return; | |
979 } | |
980 | |
981 do_port_mapping_cb(TRUE, ar); | |
982 } |