diff options
Diffstat (limited to 'src/upnp/upnp.c')
-rw-r--r-- | src/upnp/upnp.c | 721 |
1 files changed, 721 insertions, 0 deletions
diff --git a/src/upnp/upnp.c b/src/upnp/upnp.c new file mode 100644 index 000000000..6dc4338a0 --- /dev/null +++ b/src/upnp/upnp.c | |||
@@ -0,0 +1,721 @@ | |||
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 | |||
26 | #include "platform.h" | ||
27 | #include "upnp_xmlnode.h" | ||
28 | #include "upnp_util.h" | ||
29 | #include "upnp.h" | ||
30 | |||
31 | #include <curl/curl.h> | ||
32 | |||
33 | #define TRUE GNUNET_YES | ||
34 | #define FALSE GNUNET_NO | ||
35 | #define g_return_if_fail(a) if(!(a)) return; | ||
36 | #define g_return_val_if_fail(a, val) if(!(a)) return (val); | ||
37 | |||
38 | #define HTTP_OK "200 OK" | ||
39 | #define NUM_UDP_ATTEMPTS 2 | ||
40 | #define HTTPMU_HOST_ADDRESS "239.255.255.250" | ||
41 | #define HTTPMU_HOST_PORT 1900 | ||
42 | #define SEARCH_REQUEST_DEVICE "urn:schemas-upnp-org:service:%s" | ||
43 | #define SEARCH_REQUEST_STRING \ | ||
44 | "M-SEARCH * HTTP/1.1\r\n" \ | ||
45 | "MX: 2\r\n" \ | ||
46 | "HOST: 239.255.255.250:1900\r\n" \ | ||
47 | "MAN: \"ssdp:discover\"\r\n" \ | ||
48 | "ST: urn:schemas-upnp-org:service:%s\r\n" \ | ||
49 | "\r\n" | ||
50 | #define WAN_IP_CONN_SERVICE "WANIPConnection:1" | ||
51 | #define WAN_PPP_CONN_SERVICE "WANPPPConnection:1" | ||
52 | #define HTTP_POST_SOAP_HEADER \ | ||
53 | "SOAPACTION: \"urn:schemas-upnp-org:service:%s#%s\"" | ||
54 | #define HTTP_POST_SIZE_HEADER "CONTENT-LENGTH: %u" | ||
55 | #define SOAP_ACTION \ | ||
56 | "<?xml version=\"1.0\" encoding=\"utf-8\"?>\r\n" \ | ||
57 | "<s:Envelope xmlns:s=\"http://schemas.xmlsoap.org/soap/envelope/\" " \ | ||
58 | "s:encodingStyle=\"http://schemas.xmlsoap.org/soap/encoding/\">\r\n" \ | ||
59 | "<s:Body>\r\n" \ | ||
60 | "<u:%s xmlns:u=\"urn:schemas-upnp-org:service:%s\">\r\n" \ | ||
61 | "%s" \ | ||
62 | "</u:%s>\r\n" \ | ||
63 | "</s:Body>\r\n" \ | ||
64 | "</s:Envelope>" | ||
65 | #define PORT_MAPPING_LEASE_TIME "0" | ||
66 | #define PORT_MAPPING_DESCRIPTION "GNUNET_UPNP_PORT_FORWARD" | ||
67 | #define ADD_PORT_MAPPING_PARAMS \ | ||
68 | "<NewRemoteHost></NewRemoteHost>\r\n" \ | ||
69 | "<NewExternalPort>%i</NewExternalPort>\r\n" \ | ||
70 | "<NewProtocol>%s</NewProtocol>\r\n" \ | ||
71 | "<NewInternalPort>%i</NewInternalPort>\r\n" \ | ||
72 | "<NewInternalClient>%s</NewInternalClient>\r\n" \ | ||
73 | "<NewEnabled>1</NewEnabled>\r\n" \ | ||
74 | "<NewPortMappingDescription>" \ | ||
75 | PORT_MAPPING_DESCRIPTION \ | ||
76 | "</NewPortMappingDescription>\r\n" \ | ||
77 | "<NewLeaseDuration>" \ | ||
78 | PORT_MAPPING_LEASE_TIME \ | ||
79 | "</NewLeaseDuration>\r\n" | ||
80 | #define DELETE_PORT_MAPPING_PARAMS \ | ||
81 | "<NewRemoteHost></NewRemoteHost>\r\n" \ | ||
82 | "<NewExternalPort>%i</NewExternalPort>\r\n" \ | ||
83 | "<NewProtocol>%s</NewProtocol>\r\n" | ||
84 | |||
85 | typedef enum | ||
86 | { | ||
87 | GAIM_UPNP_STATUS_UNDISCOVERED = -1, | ||
88 | GAIM_UPNP_STATUS_UNABLE_TO_DISCOVER, | ||
89 | GAIM_UPNP_STATUS_DISCOVERING, | ||
90 | GAIM_UPNP_STATUS_DISCOVERED | ||
91 | } GaimUPnPStatus; | ||
92 | |||
93 | typedef struct | ||
94 | { | ||
95 | GaimUPnPStatus status; | ||
96 | char *control_url; | ||
97 | const char *service_type; | ||
98 | char publicip[16]; | ||
99 | } GaimUPnPControlInfo; | ||
100 | |||
101 | typedef struct | ||
102 | { | ||
103 | const char *service_type; | ||
104 | char *full_url; | ||
105 | char *buf; | ||
106 | unsigned int buf_len; | ||
107 | int sock; | ||
108 | } UPnPDiscoveryData; | ||
109 | |||
110 | static GaimUPnPControlInfo control_info = { | ||
111 | GAIM_UPNP_STATUS_UNDISCOVERED, | ||
112 | NULL, | ||
113 | NULL, | ||
114 | "", | ||
115 | }; | ||
116 | |||
117 | /** | ||
118 | * This is the signature used for functions that act as a callback | ||
119 | * to CURL. | ||
120 | */ | ||
121 | typedef size_t (*GaimUtilFetchUrlCallback) (void *url_data, | ||
122 | size_t size, | ||
123 | size_t nmemb, void *user_data); | ||
124 | |||
125 | |||
126 | |||
127 | static char * | ||
128 | g_strstr_len (const char *haystack, int haystack_len, const char *needle) | ||
129 | { | ||
130 | int i; | ||
131 | |||
132 | g_return_val_if_fail (haystack != NULL, NULL); | ||
133 | g_return_val_if_fail (needle != NULL, NULL); | ||
134 | |||
135 | if (haystack_len < 0) | ||
136 | return strstr (haystack, needle); | ||
137 | else | ||
138 | { | ||
139 | const char *p = haystack; | ||
140 | int needle_len = strlen (needle); | ||
141 | const char *end = haystack + haystack_len - needle_len; | ||
142 | |||
143 | if (needle_len == 0) | ||
144 | return (char *) haystack; | ||
145 | |||
146 | while (*p && p <= end) | ||
147 | { | ||
148 | for (i = 0; i < needle_len; i++) | ||
149 | if (p[i] != needle[i]) | ||
150 | goto next; | ||
151 | |||
152 | return (char *) p; | ||
153 | |||
154 | next: | ||
155 | p++; | ||
156 | } | ||
157 | } | ||
158 | |||
159 | return NULL; | ||
160 | } | ||
161 | |||
162 | static int | ||
163 | gaim_upnp_compare_device (const xmlnode * device, const char *deviceType) | ||
164 | { | ||
165 | xmlnode *deviceTypeNode = xmlnode_get_child (device, "deviceType"); | ||
166 | char *tmp; | ||
167 | int ret; | ||
168 | |||
169 | if (deviceTypeNode == NULL) | ||
170 | return FALSE; | ||
171 | tmp = xmlnode_get_data (deviceTypeNode); | ||
172 | ret = !strcasecmp (tmp, deviceType); | ||
173 | GNUNET_free (tmp); | ||
174 | return ret; | ||
175 | } | ||
176 | |||
177 | static int | ||
178 | gaim_upnp_compare_service (const xmlnode * service, const char *serviceType) | ||
179 | { | ||
180 | xmlnode *serviceTypeNode; | ||
181 | char *tmp; | ||
182 | int ret; | ||
183 | |||
184 | if (service == NULL) | ||
185 | return FALSE; | ||
186 | serviceTypeNode = xmlnode_get_child (service, "serviceType"); | ||
187 | if (serviceTypeNode == NULL) | ||
188 | return FALSE; | ||
189 | tmp = xmlnode_get_data (serviceTypeNode); | ||
190 | ret = !strcasecmp (tmp, serviceType); | ||
191 | GNUNET_free (tmp); | ||
192 | return ret; | ||
193 | } | ||
194 | |||
195 | static char * | ||
196 | gaim_upnp_parse_description_response (const char *httpResponse, | ||
197 | size_t len, | ||
198 | const char *httpURL, | ||
199 | const char *serviceType) | ||
200 | { | ||
201 | char *xmlRoot, *baseURL, *controlURL, *service; | ||
202 | xmlnode *xmlRootNode, *serviceTypeNode, *controlURLNode, *baseURLNode; | ||
203 | char *tmp; | ||
204 | |||
205 | /* find the root of the xml document */ | ||
206 | xmlRoot = g_strstr_len (httpResponse, len, "<root"); | ||
207 | if (xmlRoot == NULL) | ||
208 | return NULL; | ||
209 | if (g_strstr_len (httpResponse, len, "</root") == NULL) | ||
210 | return NULL; | ||
211 | |||
212 | /* create the xml root node */ | ||
213 | xmlRootNode = xmlnode_from_str (xmlRoot, len - (xmlRoot - httpResponse)); | ||
214 | if (xmlRootNode == NULL) | ||
215 | return NULL; | ||
216 | |||
217 | /* get the baseURL of the device */ | ||
218 | baseURLNode = xmlnode_get_child (xmlRootNode, "URLBase"); | ||
219 | if (baseURLNode != NULL) | ||
220 | { | ||
221 | baseURL = xmlnode_get_data (baseURLNode); | ||
222 | } | ||
223 | else | ||
224 | { | ||
225 | baseURL = GNUNET_strdup (httpURL); | ||
226 | } | ||
227 | |||
228 | /* get the serviceType child that has the service type as its data */ | ||
229 | /* get urn:schemas-upnp-org:device:InternetGatewayDevice:1 and its devicelist */ | ||
230 | serviceTypeNode = xmlnode_get_child (xmlRootNode, "device"); | ||
231 | while (!gaim_upnp_compare_device (serviceTypeNode, | ||
232 | "urn:schemas-upnp-org:device:InternetGatewayDevice:1") | ||
233 | && serviceTypeNode != NULL) | ||
234 | { | ||
235 | serviceTypeNode = xmlnode_get_next_twin (serviceTypeNode); | ||
236 | } | ||
237 | if (serviceTypeNode == NULL) | ||
238 | { | ||
239 | GNUNET_free (baseURL); | ||
240 | xmlnode_free (xmlRootNode); | ||
241 | return NULL; | ||
242 | } | ||
243 | serviceTypeNode = xmlnode_get_child (serviceTypeNode, "deviceList"); | ||
244 | if (serviceTypeNode == NULL) | ||
245 | { | ||
246 | GNUNET_free (baseURL); | ||
247 | xmlnode_free (xmlRootNode); | ||
248 | return NULL; | ||
249 | } | ||
250 | |||
251 | /* get urn:schemas-upnp-org:device:WANDevice:1 and its devicelist */ | ||
252 | serviceTypeNode = xmlnode_get_child (serviceTypeNode, "device"); | ||
253 | while (!gaim_upnp_compare_device (serviceTypeNode, | ||
254 | "urn:schemas-upnp-org:device:WANDevice:1") | ||
255 | && serviceTypeNode != NULL) | ||
256 | { | ||
257 | serviceTypeNode = xmlnode_get_next_twin (serviceTypeNode); | ||
258 | } | ||
259 | if (serviceTypeNode == NULL) | ||
260 | { | ||
261 | GNUNET_free (baseURL); | ||
262 | xmlnode_free (xmlRootNode); | ||
263 | return NULL; | ||
264 | } | ||
265 | serviceTypeNode = xmlnode_get_child (serviceTypeNode, "deviceList"); | ||
266 | if (serviceTypeNode == NULL) | ||
267 | { | ||
268 | GNUNET_free (baseURL); | ||
269 | xmlnode_free (xmlRootNode); | ||
270 | return NULL; | ||
271 | } | ||
272 | |||
273 | /* get urn:schemas-upnp-org:device:WANConnectionDevice:1 and its servicelist */ | ||
274 | serviceTypeNode = xmlnode_get_child (serviceTypeNode, "device"); | ||
275 | while (serviceTypeNode && !gaim_upnp_compare_device (serviceTypeNode, | ||
276 | "urn:schemas-upnp-org:device:WANConnectionDevice:1")) | ||
277 | { | ||
278 | serviceTypeNode = xmlnode_get_next_twin (serviceTypeNode); | ||
279 | } | ||
280 | if (serviceTypeNode == NULL) | ||
281 | { | ||
282 | GNUNET_free (baseURL); | ||
283 | xmlnode_free (xmlRootNode); | ||
284 | return NULL; | ||
285 | } | ||
286 | serviceTypeNode = xmlnode_get_child (serviceTypeNode, "serviceList"); | ||
287 | if (serviceTypeNode == NULL) | ||
288 | { | ||
289 | GNUNET_free (baseURL); | ||
290 | xmlnode_free (xmlRootNode); | ||
291 | return NULL; | ||
292 | } | ||
293 | |||
294 | /* get the serviceType variable passed to this function */ | ||
295 | service = g_strdup_printf (SEARCH_REQUEST_DEVICE, serviceType); | ||
296 | serviceTypeNode = xmlnode_get_child (serviceTypeNode, "service"); | ||
297 | while (!gaim_upnp_compare_service (serviceTypeNode, service) && | ||
298 | serviceTypeNode != NULL) | ||
299 | { | ||
300 | serviceTypeNode = xmlnode_get_next_twin (serviceTypeNode); | ||
301 | } | ||
302 | |||
303 | GNUNET_free (service); | ||
304 | if (serviceTypeNode == NULL) | ||
305 | { | ||
306 | GNUNET_free (baseURL); | ||
307 | xmlnode_free (xmlRootNode); | ||
308 | return NULL; | ||
309 | } | ||
310 | |||
311 | /* get the controlURL of the service */ | ||
312 | if ((controlURLNode = xmlnode_get_child (serviceTypeNode, | ||
313 | "controlURL")) == NULL) | ||
314 | { | ||
315 | GNUNET_free (baseURL); | ||
316 | xmlnode_free (xmlRootNode); | ||
317 | return NULL; | ||
318 | } | ||
319 | |||
320 | tmp = xmlnode_get_data (controlURLNode); | ||
321 | if (baseURL && !gaim_str_has_prefix (tmp, "http://") && | ||
322 | !gaim_str_has_prefix (tmp, "HTTP://")) | ||
323 | { | ||
324 | if (tmp[0] == '/') | ||
325 | { | ||
326 | size_t len; | ||
327 | const char *end; | ||
328 | /* absolute path */ | ||
329 | end = strstr (&baseURL[strlen ("http://")], "/"); | ||
330 | if (end == NULL) | ||
331 | len = strlen (&baseURL[strlen ("http://")]); | ||
332 | else | ||
333 | len = end - &baseURL[strlen ("http://")]; | ||
334 | controlURL = g_strdup_printf ("http://%.*s%s", | ||
335 | len, | ||
336 | &baseURL[strlen ("http://")], tmp); | ||
337 | } | ||
338 | else | ||
339 | { | ||
340 | controlURL = g_strdup_printf ("%s%s", baseURL, tmp); | ||
341 | } | ||
342 | GNUNET_free (tmp); | ||
343 | } | ||
344 | else | ||
345 | { | ||
346 | controlURL = tmp; | ||
347 | } | ||
348 | GNUNET_free (baseURL); | ||
349 | xmlnode_free (xmlRootNode); | ||
350 | |||
351 | return controlURL; | ||
352 | } | ||
353 | |||
354 | #define CURL_EASY_SETOPT(c, a, b) do { ret = curl_easy_setopt(c, a, b); if (ret != CURLE_OK) GNUNET_log(GNUNET_ERROR_TYPE_WARNING | GNUNET_ERROR_TYPE_BULK, _("%s failed at %s:%d: `%s'\n"), "curl_easy_setopt", __FILE__, __LINE__, curl_easy_strerror(ret)); } while (0); | ||
355 | |||
356 | /** | ||
357 | * Do the generic curl setup. | ||
358 | */ | ||
359 | static int | ||
360 | setup_curl (const char *proxy, CURL * curl) | ||
361 | { | ||
362 | int ret; | ||
363 | |||
364 | CURL_EASY_SETOPT (curl, CURLOPT_FAILONERROR, 1); | ||
365 | if (strlen (proxy) > 0) | ||
366 | CURL_EASY_SETOPT (curl, CURLOPT_PROXY, proxy); | ||
367 | CURL_EASY_SETOPT (curl, CURLOPT_BUFFERSIZE, 1024); /* a bit more than one HELLO */ | ||
368 | CURL_EASY_SETOPT (curl, CURLOPT_CONNECTTIMEOUT, 150L); | ||
369 | /* NOTE: use of CONNECTTIMEOUT without also | ||
370 | setting NOSIGNAL results in really weird | ||
371 | crashes on my system! */ | ||
372 | CURL_EASY_SETOPT (curl, CURLOPT_NOSIGNAL, 1); | ||
373 | return GNUNET_OK; | ||
374 | } | ||
375 | |||
376 | static int | ||
377 | gaim_upnp_generate_action_message_and_send (const char *proxy, | ||
378 | const char *actionName, | ||
379 | const char *actionParams, | ||
380 | GaimUtilFetchUrlCallback cb, | ||
381 | void *cb_data) | ||
382 | { | ||
383 | CURL *curl; | ||
384 | int ret; | ||
385 | char *soapHeader; | ||
386 | char *sizeHeader; | ||
387 | char *soapMessage; | ||
388 | struct curl_slist *headers = NULL; | ||
389 | |||
390 | GNUNET_assert (cb != NULL); | ||
391 | if (0 != curl_global_init (CURL_GLOBAL_WIN32)) | ||
392 | return GNUNET_SYSERR; | ||
393 | /* set the soap message */ | ||
394 | soapMessage = g_strdup_printf (SOAP_ACTION, | ||
395 | actionName, | ||
396 | control_info.service_type, | ||
397 | actionParams, actionName); | ||
398 | soapHeader = g_strdup_printf (HTTP_POST_SOAP_HEADER, | ||
399 | control_info.service_type, actionName); | ||
400 | sizeHeader = g_strdup_printf (HTTP_POST_SIZE_HEADER, strlen (soapMessage)); | ||
401 | curl = curl_easy_init (); | ||
402 | setup_curl (proxy, curl); | ||
403 | CURL_EASY_SETOPT (curl, CURLOPT_URL, control_info.control_url); | ||
404 | CURL_EASY_SETOPT (curl, CURLOPT_WRITEFUNCTION, cb); | ||
405 | CURL_EASY_SETOPT (curl, CURLOPT_WRITEDATA, cb_data); | ||
406 | CURL_EASY_SETOPT (curl, CURLOPT_POST, 1); | ||
407 | headers = curl_slist_append (headers, | ||
408 | "CONTENT-TYPE: text/xml ; charset=\"utf-8\""); | ||
409 | headers = curl_slist_append (headers, soapHeader); | ||
410 | headers = curl_slist_append (headers, sizeHeader); | ||
411 | CURL_EASY_SETOPT (curl, CURLOPT_HTTPHEADER, headers); | ||
412 | CURL_EASY_SETOPT (curl, CURLOPT_POSTFIELDS, soapMessage); | ||
413 | CURL_EASY_SETOPT (curl, CURLOPT_POSTFIELDSIZE, strlen (soapMessage)); | ||
414 | CURL_EASY_SETOPT (curl, CURLOPT_MAXREDIRS, 1L); | ||
415 | CURL_EASY_SETOPT (curl, CURLOPT_CONNECTTIMEOUT, 1L); | ||
416 | CURL_EASY_SETOPT (curl, CURLOPT_TIMEOUT, 2L); | ||
417 | /* NOTE: use of CONNECTTIMEOUT without also | ||
418 | setting NOSIGNAL results in really weird | ||
419 | crashes on my system! */ | ||
420 | CURL_EASY_SETOPT (curl, CURLOPT_NOSIGNAL, 1); | ||
421 | if (ret == CURLE_OK) | ||
422 | ret = curl_easy_perform (curl); | ||
423 | #if 0 | ||
424 | if (ret != CURLE_OK) | ||
425 | GNUNET_log (GNUNET_ERROR_TYPE_ERROR | GNUNET_ERROR_TYPE_BULK, | ||
426 | _ | ||
427 | ("%s failed for url `%s' and post-data `%s' at %s:%d: `%s'\n"), | ||
428 | "curl_easy_perform", control_info.control_url, soapMessage, | ||
429 | __FILE__, __LINE__, curl_easy_strerror (ret)); | ||
430 | #endif | ||
431 | curl_slist_free_all (headers); | ||
432 | curl_easy_cleanup (curl); | ||
433 | curl_global_cleanup (); | ||
434 | GNUNET_free (sizeHeader); | ||
435 | GNUNET_free (soapMessage); | ||
436 | GNUNET_free (soapHeader); | ||
437 | if (ret != CURLE_OK) | ||
438 | return GNUNET_SYSERR; | ||
439 | return GNUNET_OK; | ||
440 | } | ||
441 | |||
442 | |||
443 | static size_t | ||
444 | looked_up_public_ip_cb (void *url_data, | ||
445 | size_t size, size_t nmemb, void *user_data) | ||
446 | { | ||
447 | UPnPDiscoveryData *dd = user_data; | ||
448 | size_t len = size * nmemb; | ||
449 | const char *temp; | ||
450 | const char *temp2; | ||
451 | |||
452 | if (len + dd->buf_len > 1024 * 1024 * 4) | ||
453 | return 0; /* refuse to process - too big! */ | ||
454 | GNUNET_array_grow (dd->buf, dd->buf_len, dd->buf_len + len); | ||
455 | memcpy (&dd->buf[dd->buf_len - len], url_data, len); | ||
456 | if (dd->buf_len == 0) | ||
457 | return len; | ||
458 | /* extract the ip, or see if there is an error */ | ||
459 | if ((temp = g_strstr_len (dd->buf, | ||
460 | dd->buf_len, "<NewExternalIPAddress")) == NULL) | ||
461 | return len; | ||
462 | if (!(temp = g_strstr_len (temp, dd->buf_len - (temp - dd->buf), ">"))) | ||
463 | return len; | ||
464 | if (!(temp2 = g_strstr_len (temp, dd->buf_len - (temp - dd->buf), "<"))) | ||
465 | return len; | ||
466 | memset (control_info.publicip, 0, sizeof (control_info.publicip)); | ||
467 | if (temp2 - temp >= sizeof (control_info.publicip)) | ||
468 | temp2 = temp + sizeof (control_info.publicip) - 1; | ||
469 | memcpy (control_info.publicip, temp + 1, temp2 - (temp + 1)); | ||
470 | GNUNET_log (GNUNET_ERROR_TYPE_INFO | GNUNET_ERROR_TYPE_BULK, | ||
471 | _("upnp: NAT Returned IP: %s\n"), control_info.publicip); | ||
472 | return len; | ||
473 | } | ||
474 | |||
475 | |||
476 | static size_t | ||
477 | ignore_response (void *url_data, size_t size, size_t nmemb, void *user_data) | ||
478 | { | ||
479 | return size * nmemb; | ||
480 | } | ||
481 | |||
482 | /** | ||
483 | * Process downloaded bits of service description. | ||
484 | */ | ||
485 | static size_t | ||
486 | upnp_parse_description_cb (void *httpResponse, | ||
487 | size_t size, size_t nmemb, void *user_data) | ||
488 | { | ||
489 | UPnPDiscoveryData *dd = user_data; | ||
490 | size_t len = size * nmemb; | ||
491 | char *control_url = NULL; | ||
492 | |||
493 | if (len + dd->buf_len > 1024 * 1024 * 4) | ||
494 | return len; /* refuse to process - too big! */ | ||
495 | GNUNET_array_grow (dd->buf, dd->buf_len, dd->buf_len + len); | ||
496 | memcpy (&dd->buf[dd->buf_len - len], httpResponse, len); | ||
497 | if (dd->buf_len > 0) | ||
498 | control_url = gaim_upnp_parse_description_response (dd->buf, | ||
499 | dd->buf_len, | ||
500 | dd->full_url, | ||
501 | dd->service_type); | ||
502 | control_info.status = control_url ? GAIM_UPNP_STATUS_DISCOVERED | ||
503 | : GAIM_UPNP_STATUS_UNABLE_TO_DISCOVER; | ||
504 | GNUNET_free_non_null (control_info.control_url); | ||
505 | control_info.control_url = control_url; | ||
506 | control_info.service_type = dd->service_type; | ||
507 | return len; | ||
508 | } | ||
509 | |||
510 | static int | ||
511 | gaim_upnp_parse_description (char *proxy, UPnPDiscoveryData * dd) | ||
512 | { | ||
513 | CURL *curl; | ||
514 | int ret; | ||
515 | |||
516 | if (0 != curl_global_init (CURL_GLOBAL_WIN32)) | ||
517 | return GNUNET_SYSERR; | ||
518 | curl = curl_easy_init (); | ||
519 | setup_curl (proxy, curl); | ||
520 | ret = CURLE_OK; | ||
521 | CURL_EASY_SETOPT (curl, CURLOPT_URL, dd->full_url); | ||
522 | CURL_EASY_SETOPT (curl, CURLOPT_WRITEFUNCTION, &upnp_parse_description_cb); | ||
523 | CURL_EASY_SETOPT (curl, CURLOPT_WRITEDATA, dd); | ||
524 | CURL_EASY_SETOPT (curl, CURLOPT_MAXREDIRS, 1L); | ||
525 | CURL_EASY_SETOPT (curl, CURLOPT_CONNECTTIMEOUT, 1L); | ||
526 | CURL_EASY_SETOPT (curl, CURLOPT_TIMEOUT, 2L); | ||
527 | |||
528 | /* NOTE: use of CONNECTTIMEOUT without also | ||
529 | setting NOSIGNAL results in really weird | ||
530 | crashes on my system! */ | ||
531 | CURL_EASY_SETOPT (curl, CURLOPT_NOSIGNAL, 1); | ||
532 | ret = curl_easy_perform (curl); | ||
533 | if (ret != CURLE_OK) | ||
534 | GNUNET_log (GNUNET_ERROR_TYPE_ERROR | GNUNET_ERROR_TYPE_BULK, | ||
535 | _("%s failed at %s:%d: `%s'\n"), | ||
536 | "curl_easy_perform", __FILE__, __LINE__, | ||
537 | curl_easy_strerror (ret)); | ||
538 | curl_easy_cleanup (curl); | ||
539 | curl_global_cleanup (); | ||
540 | if (control_info.control_url == NULL) | ||
541 | return GNUNET_SYSERR; | ||
542 | return GNUNET_OK; | ||
543 | } | ||
544 | |||
545 | int | ||
546 | gaim_upnp_discover (struct GNUNET_CONFIGURATION_Handle *cfg, int sock) | ||
547 | { | ||
548 | char *proxy; | ||
549 | socklen_t avail; | ||
550 | struct sockaddr_in server; | ||
551 | int retry_count; | ||
552 | char *sendMessage; | ||
553 | size_t totalSize; | ||
554 | int sentSuccess; | ||
555 | char buf[65536]; | ||
556 | int buf_len; | ||
557 | const char *startDescURL; | ||
558 | const char *endDescURL; | ||
559 | int ret; | ||
560 | UPnPDiscoveryData dd; | ||
561 | struct sockaddr *sa; | ||
562 | |||
563 | memset (&dd, 0, sizeof (UPnPDiscoveryData)); | ||
564 | if (control_info.status == GAIM_UPNP_STATUS_DISCOVERING) | ||
565 | return GNUNET_NO; | ||
566 | dd.sock = sock; | ||
567 | memset (&server, 0, sizeof (struct sockaddr_in)); | ||
568 | server.sin_family = AF_INET; | ||
569 | avail = sizeof (struct sockaddr_in); | ||
570 | sa = (struct sockaddr *) &server; | ||
571 | if (GNUNET_OK != | ||
572 | GNUNET_get_ip_from_hostname (HTTPMU_HOST_ADDRESS, AF_INET, &sa, &avail)) | ||
573 | { | ||
574 | return GNUNET_SYSERR; | ||
575 | } | ||
576 | server.sin_port = htons (HTTPMU_HOST_PORT); | ||
577 | control_info.status = GAIM_UPNP_STATUS_DISCOVERING; | ||
578 | |||
579 | /* because we are sending over UDP, if there is a failure | ||
580 | we should retry the send NUM_UDP_ATTEMPTS times. Also, | ||
581 | try different requests for WANIPConnection and WANPPPConnection */ | ||
582 | for (retry_count = 0; retry_count < NUM_UDP_ATTEMPTS; retry_count++) | ||
583 | { | ||
584 | sentSuccess = FALSE; | ||
585 | if ((retry_count % 2) == 0) | ||
586 | dd.service_type = WAN_IP_CONN_SERVICE; | ||
587 | else | ||
588 | dd.service_type = WAN_PPP_CONN_SERVICE; | ||
589 | sendMessage = g_strdup_printf (SEARCH_REQUEST_STRING, dd.service_type); | ||
590 | totalSize = strlen (sendMessage); | ||
591 | do | ||
592 | { | ||
593 | if (SENDTO (dd.sock, | ||
594 | sendMessage, | ||
595 | totalSize, | ||
596 | 0, | ||
597 | (struct sockaddr *) &server, | ||
598 | sizeof (struct sockaddr_in)) == totalSize) | ||
599 | { | ||
600 | sentSuccess = TRUE; | ||
601 | break; | ||
602 | } | ||
603 | } | ||
604 | while (((errno == EINTR) || (errno == EAGAIN)) && | ||
605 | (GNUNET_shutdown_test () == GNUNET_NO)); | ||
606 | GNUNET_free (sendMessage); | ||
607 | if (sentSuccess) | ||
608 | break; | ||
609 | } | ||
610 | if (sentSuccess == FALSE) | ||
611 | return GNUNET_SYSERR; | ||
612 | |||
613 | /* try to read response */ | ||
614 | do | ||
615 | { | ||
616 | buf_len = recv (dd.sock, buf, sizeof (buf) - 1, 0); | ||
617 | if (buf_len > 0) | ||
618 | { | ||
619 | buf[buf_len] = '\0'; | ||
620 | break; | ||
621 | } | ||
622 | else if (errno != EINTR) | ||
623 | { | ||
624 | continue; | ||
625 | } | ||
626 | } | ||
627 | while ((errno == EINTR) && (GNUNET_shutdown_test () == GNUNET_NO)); | ||
628 | |||
629 | /* parse the response, and see if it was a success */ | ||
630 | if (g_strstr_len (buf, buf_len, HTTP_OK) == NULL) | ||
631 | return GNUNET_SYSERR; | ||
632 | if ((startDescURL = g_strstr_len (buf, buf_len, "http://")) == NULL) | ||
633 | return GNUNET_SYSERR; | ||
634 | |||
635 | endDescURL = g_strstr_len (startDescURL, | ||
636 | buf_len - (startDescURL - buf), "\r"); | ||
637 | if (endDescURL == NULL) | ||
638 | endDescURL = g_strstr_len (startDescURL, | ||
639 | buf_len - (startDescURL - buf), "\n"); | ||
640 | if (endDescURL == NULL) | ||
641 | return GNUNET_SYSERR; | ||
642 | if (endDescURL == startDescURL) | ||
643 | return GNUNET_SYSERR; | ||
644 | dd.full_url = GNUNET_strdup (startDescURL); | ||
645 | dd.full_url[endDescURL - startDescURL] = '\0'; | ||
646 | proxy = NULL; | ||
647 | GNUNET_CONFIGURATION_get_value_string (cfg, | ||
648 | "GNUNETD", "HTTP-PROXY", &proxy); | ||
649 | ret = gaim_upnp_parse_description (proxy, &dd); | ||
650 | GNUNET_free (dd.full_url); | ||
651 | GNUNET_array_grow (dd.buf, dd.buf_len, 0); | ||
652 | if (ret == GNUNET_OK) | ||
653 | { | ||
654 | ret = gaim_upnp_generate_action_message_and_send (proxy, | ||
655 | "GetExternalIPAddress", | ||
656 | "", | ||
657 | looked_up_public_ip_cb, | ||
658 | &dd); | ||
659 | GNUNET_array_grow (dd.buf, dd.buf_len, 0); | ||
660 | } | ||
661 | GNUNET_free (proxy); | ||
662 | return ret; | ||
663 | } | ||
664 | |||
665 | const char * | ||
666 | gaim_upnp_get_public_ip () | ||
667 | { | ||
668 | if ((control_info.status == GAIM_UPNP_STATUS_DISCOVERED) | ||
669 | && (strlen (control_info.publicip) > 0)) | ||
670 | return control_info.publicip; | ||
671 | return NULL; | ||
672 | } | ||
673 | |||
674 | int | ||
675 | gaim_upnp_change_port_mapping (struct GNUNET_CONFIGURATION_Handle *cfg, | ||
676 | int do_add, | ||
677 | unsigned short portmap, const char *protocol) | ||
678 | { | ||
679 | const char *action_name; | ||
680 | char *action_params; | ||
681 | char *internal_ip; | ||
682 | char *proxy; | ||
683 | int ret; | ||
684 | |||
685 | if (control_info.status != GAIM_UPNP_STATUS_DISCOVERED) | ||
686 | return GNUNET_NO; | ||
687 | if (do_add) | ||
688 | { | ||
689 | internal_ip = GNUNET_upnp_get_internal_ip (cfg); | ||
690 | if (internal_ip == NULL) | ||
691 | { | ||
692 | gaim_debug_error ("upnp", | ||
693 | "gaim_upnp_set_port_mapping(): couldn't get local ip\n"); | ||
694 | return GNUNET_NO; | ||
695 | } | ||
696 | action_name = "AddPortMapping"; | ||
697 | action_params = g_strdup_printf (ADD_PORT_MAPPING_PARAMS, | ||
698 | portmap, | ||
699 | protocol, portmap, internal_ip); | ||
700 | GNUNET_free (internal_ip); | ||
701 | } | ||
702 | else | ||
703 | { | ||
704 | action_name = "DeletePortMapping"; | ||
705 | action_params = g_strdup_printf (DELETE_PORT_MAPPING_PARAMS, | ||
706 | portmap, protocol); | ||
707 | } | ||
708 | proxy = NULL; | ||
709 | GNUNET_CONFIGURATION_get_value_string (cfg, | ||
710 | "GNUNETD", "HTTP-PROXY", &proxy); | ||
711 | ret = | ||
712 | gaim_upnp_generate_action_message_and_send (proxy, action_name, | ||
713 | action_params, | ||
714 | &ignore_response, NULL); | ||
715 | |||
716 | GNUNET_free (action_params); | ||
717 | GNUNET_free (proxy); | ||
718 | return ret; | ||
719 | } | ||
720 | |||
721 | /* end of upnp.c */ | ||