MHD debug messages switched off
[oweals/gnunet.git] / src / upnp / upnp.c
1 /**
2  * @file upnp/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   struct GNUNET_NETWORK_Handle *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, struct GNUNET_NETWORK_Handle *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 = GNUNET_IO_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 */