Fix crashes when filenames end up being NULL in some prpls.
[pidgin-git.git] / libpurple / upnp.c
blob9b63072ecef21543c90eb0c6497fff1b80cd99b4
1 /**
2 * @file upnp.c UPnP Implementation
3 * @ingroup core
4 */
6 /* purple
8 * Purple is the legal property of its developers, whose names are too numerous
9 * to list here. Please refer to the COPYRIGHT file distributed with this
10 * source distribution.
12 * This program is free software; you can redistribute it and/or modify
13 * it under the terms of the GNU General Public License as published by
14 * the Free Software Foundation; either version 2 of the License, or
15 * (at your option) any later version.
17 * This program is distributed in the hope that it will be useful,
18 * but WITHOUT ANY WARRANTY; without even the implied warranty of
19 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
20 * GNU General Public License for more details.
22 * You should have received a copy of the GNU General Public License
23 * along with this program; if not, write to the Free Software
24 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA
26 #include "internal.h"
28 #include "upnp.h"
30 #include "debug.h"
31 #include "eventloop.h"
32 #include "network.h"
33 #include "proxy.h"
34 #include "signals.h"
35 #include "util.h"
36 #include "xmlnode.h"
38 /***************************************************************
39 ** General Defines *
40 ****************************************************************/
41 #define HTTP_OK "200 OK"
42 #define DEFAULT_HTTP_PORT 80
43 #define DISCOVERY_TIMEOUT 1000
44 /* limit UPnP-triggered http downloads to 128k */
45 #define MAX_UPNP_DOWNLOAD (128 * 1024)
47 /***************************************************************
48 ** Discovery/Description Defines *
49 ****************************************************************/
50 #define NUM_UDP_ATTEMPTS 2
52 /* Address and port of an SSDP request used for discovery */
53 #define HTTPMU_HOST_ADDRESS "239.255.255.250"
54 #define HTTPMU_HOST_PORT 1900
56 #define SEARCH_REQUEST_DEVICE "urn:schemas-upnp-org:service:%s"
58 #define SEARCH_REQUEST_STRING \
59 "M-SEARCH * HTTP/1.1\r\n" \
60 "MX: 2\r\n" \
61 "HOST: 239.255.255.250:1900\r\n" \
62 "MAN: \"ssdp:discover\"\r\n" \
63 "ST: urn:schemas-upnp-org:service:%s\r\n" \
64 "\r\n"
66 #define WAN_IP_CONN_SERVICE "WANIPConnection:1"
67 #define WAN_PPP_CONN_SERVICE "WANPPPConnection:1"
69 /******************************************************************
70 ** Action Defines *
71 *******************************************************************/
72 #define HTTP_HEADER_ACTION \
73 "POST /%s HTTP/1.1\r\n" \
74 "HOST: %s:%d\r\n" \
75 "SOAPACTION: \"urn:schemas-upnp-org:service:%s#%s\"\r\n" \
76 "CONTENT-TYPE: text/xml ; charset=\"utf-8\"\r\n" \
77 "CONTENT-LENGTH: %" G_GSIZE_FORMAT "\r\n\r\n"
79 #define SOAP_ACTION \
80 "<?xml version=\"1.0\" encoding=\"utf-8\"?>\r\n" \
81 "<s:Envelope xmlns:s=\"http://schemas.xmlsoap.org/soap/envelope/\" " \
82 "s:encodingStyle=\"http://schemas.xmlsoap.org/soap/encoding/\">\r\n" \
83 "<s:Body>\r\n" \
84 "<u:%s xmlns:u=\"urn:schemas-upnp-org:service:%s\">\r\n" \
85 "%s" \
86 "</u:%s>\r\n" \
87 "</s:Body>\r\n" \
88 "</s:Envelope>"
90 #define PORT_MAPPING_LEASE_TIME "0"
91 #define PORT_MAPPING_DESCRIPTION "PURPLE_UPNP_PORT_FORWARD"
93 #define ADD_PORT_MAPPING_PARAMS \
94 "<NewRemoteHost></NewRemoteHost>\r\n" \
95 "<NewExternalPort>%i</NewExternalPort>\r\n" \
96 "<NewProtocol>%s</NewProtocol>\r\n" \
97 "<NewInternalPort>%i</NewInternalPort>\r\n" \
98 "<NewInternalClient>%s</NewInternalClient>\r\n" \
99 "<NewEnabled>1</NewEnabled>\r\n" \
100 "<NewPortMappingDescription>" \
101 PORT_MAPPING_DESCRIPTION \
102 "</NewPortMappingDescription>\r\n" \
103 "<NewLeaseDuration>" \
104 PORT_MAPPING_LEASE_TIME \
105 "</NewLeaseDuration>\r\n"
107 #define DELETE_PORT_MAPPING_PARAMS \
108 "<NewRemoteHost></NewRemoteHost>\r\n" \
109 "<NewExternalPort>%i</NewExternalPort>\r\n" \
110 "<NewProtocol>%s</NewProtocol>\r\n"
112 typedef enum {
113 PURPLE_UPNP_STATUS_UNDISCOVERED = -1,
114 PURPLE_UPNP_STATUS_UNABLE_TO_DISCOVER,
115 PURPLE_UPNP_STATUS_DISCOVERING,
116 PURPLE_UPNP_STATUS_DISCOVERED
117 } PurpleUPnPStatus;
119 typedef struct {
120 PurpleUPnPStatus status;
121 gchar* control_url;
122 gchar service_type[20];
123 char publicip[16];
124 char internalip[16];
125 time_t lookup_time;
126 } PurpleUPnPControlInfo;
128 typedef struct {
129 guint inpa; /* purple_input_add handle */
130 guint tima; /* purple_timeout_add handle */
131 int fd;
132 struct sockaddr_in server;
133 gchar service_type[25];
134 int retry_count;
135 gchar *full_url;
136 } UPnPDiscoveryData;
138 struct _UPnPMappingAddRemove
140 unsigned short portmap;
141 gchar protocol[4];
142 gboolean add;
143 PurpleUPnPCallback cb;
144 gpointer cb_data;
145 guint tima; /* purple_timeout_add handle */
146 PurpleUtilFetchUrlData *gfud;
149 static PurpleUPnPControlInfo control_info = {
150 PURPLE_UPNP_STATUS_UNDISCOVERED,
151 NULL, "\0", "\0", "\0", 0};
153 static GSList *discovery_callbacks = NULL;
155 static void purple_upnp_discover_send_broadcast(UPnPDiscoveryData *dd);
156 static void lookup_public_ip(void);
157 static void lookup_internal_ip(void);
159 static void
160 fire_discovery_callbacks(gboolean success)
162 while(discovery_callbacks) {
163 gpointer data;
164 PurpleUPnPCallback cb = discovery_callbacks->data;
165 discovery_callbacks = g_slist_remove(discovery_callbacks, cb);
166 data = discovery_callbacks->data;
167 discovery_callbacks = g_slist_remove(discovery_callbacks, data);
168 cb(success, data);
172 static gboolean
173 purple_upnp_compare_device(const xmlnode* device, const gchar* deviceType)
175 xmlnode* deviceTypeNode = xmlnode_get_child(device, "deviceType");
176 char *tmp;
177 gboolean ret;
179 if(deviceTypeNode == NULL) {
180 return FALSE;
183 tmp = xmlnode_get_data(deviceTypeNode);
184 ret = !g_ascii_strcasecmp(tmp, deviceType);
185 g_free(tmp);
187 return ret;
190 static gboolean
191 purple_upnp_compare_service(const xmlnode* service, const gchar* serviceType)
193 xmlnode* serviceTypeNode;
194 char *tmp;
195 gboolean ret;
197 if(service == NULL) {
198 return FALSE;
201 serviceTypeNode = xmlnode_get_child(service, "serviceType");
203 if(serviceTypeNode == NULL) {
204 return FALSE;
207 tmp = xmlnode_get_data(serviceTypeNode);
208 ret = !g_ascii_strcasecmp(tmp, serviceType);
209 g_free(tmp);
211 return ret;
214 static gchar*
215 purple_upnp_parse_description_response(const gchar* httpResponse, gsize len,
216 const gchar* httpURL, const gchar* serviceType)
218 gchar *xmlRoot, *baseURL, *controlURL, *service;
219 xmlnode *xmlRootNode, *serviceTypeNode, *controlURLNode, *baseURLNode;
220 char *tmp;
222 /* make sure we have a valid http response */
223 if(g_strstr_len(httpResponse, len, HTTP_OK) == NULL) {
224 purple_debug_error("upnp",
225 "parse_description_response(): Failed In HTTP_OK\n");
226 return NULL;
229 /* find the root of the xml document */
230 if((xmlRoot = g_strstr_len(httpResponse, len, "<root")) == NULL) {
231 purple_debug_error("upnp",
232 "parse_description_response(): Failed finding root\n");
233 return NULL;
236 /* create the xml root node */
237 if((xmlRootNode = xmlnode_from_str(xmlRoot,
238 len - (xmlRoot - httpResponse))) == NULL) {
239 purple_debug_error("upnp",
240 "parse_description_response(): Could not parse xml root node\n");
241 return NULL;
244 /* get the baseURL of the device */
245 if((baseURLNode = xmlnode_get_child(xmlRootNode, "URLBase")) != NULL) {
246 baseURL = xmlnode_get_data(baseURLNode);
247 } else {
248 baseURL = g_strdup(httpURL);
251 /* get the serviceType child that has the service type as its data */
253 /* get urn:schemas-upnp-org:device:InternetGatewayDevice:1 and its devicelist */
254 serviceTypeNode = xmlnode_get_child(xmlRootNode, "device");
255 while(!purple_upnp_compare_device(serviceTypeNode,
256 "urn:schemas-upnp-org:device:InternetGatewayDevice:1") &&
257 serviceTypeNode != NULL) {
258 serviceTypeNode = xmlnode_get_next_twin(serviceTypeNode);
260 if(serviceTypeNode == NULL) {
261 purple_debug_error("upnp",
262 "parse_description_response(): could not get serviceTypeNode 1\n");
263 g_free(baseURL);
264 xmlnode_free(xmlRootNode);
265 return NULL;
267 serviceTypeNode = xmlnode_get_child(serviceTypeNode, "deviceList");
268 if(serviceTypeNode == NULL) {
269 purple_debug_error("upnp",
270 "parse_description_response(): could not get serviceTypeNode 2\n");
271 g_free(baseURL);
272 xmlnode_free(xmlRootNode);
273 return NULL;
276 /* get urn:schemas-upnp-org:device:WANDevice:1 and its devicelist */
277 serviceTypeNode = xmlnode_get_child(serviceTypeNode, "device");
278 while(!purple_upnp_compare_device(serviceTypeNode,
279 "urn:schemas-upnp-org:device:WANDevice:1") &&
280 serviceTypeNode != NULL) {
281 serviceTypeNode = xmlnode_get_next_twin(serviceTypeNode);
283 if(serviceTypeNode == NULL) {
284 purple_debug_error("upnp",
285 "parse_description_response(): could not get serviceTypeNode 3\n");
286 g_free(baseURL);
287 xmlnode_free(xmlRootNode);
288 return NULL;
290 serviceTypeNode = xmlnode_get_child(serviceTypeNode, "deviceList");
291 if(serviceTypeNode == NULL) {
292 purple_debug_error("upnp",
293 "parse_description_response(): could not get serviceTypeNode 4\n");
294 g_free(baseURL);
295 xmlnode_free(xmlRootNode);
296 return NULL;
299 /* get urn:schemas-upnp-org:device:WANConnectionDevice:1 and its servicelist */
300 serviceTypeNode = xmlnode_get_child(serviceTypeNode, "device");
301 while(serviceTypeNode && !purple_upnp_compare_device(serviceTypeNode,
302 "urn:schemas-upnp-org:device:WANConnectionDevice:1")) {
303 serviceTypeNode = xmlnode_get_next_twin(serviceTypeNode);
305 if(serviceTypeNode == NULL) {
306 purple_debug_error("upnp",
307 "parse_description_response(): could not get serviceTypeNode 5\n");
308 g_free(baseURL);
309 xmlnode_free(xmlRootNode);
310 return NULL;
312 serviceTypeNode = xmlnode_get_child(serviceTypeNode, "serviceList");
313 if(serviceTypeNode == NULL) {
314 purple_debug_error("upnp",
315 "parse_description_response(): could not get serviceTypeNode 6\n");
316 g_free(baseURL);
317 xmlnode_free(xmlRootNode);
318 return NULL;
321 /* get the serviceType variable passed to this function */
322 service = g_strdup_printf(SEARCH_REQUEST_DEVICE, serviceType);
323 serviceTypeNode = xmlnode_get_child(serviceTypeNode, "service");
324 while(!purple_upnp_compare_service(serviceTypeNode, service) &&
325 serviceTypeNode != NULL) {
326 serviceTypeNode = xmlnode_get_next_twin(serviceTypeNode);
329 g_free(service);
330 if(serviceTypeNode == NULL) {
331 purple_debug_error("upnp",
332 "parse_description_response(): could not get serviceTypeNode 7\n");
333 g_free(baseURL);
334 xmlnode_free(xmlRootNode);
335 return NULL;
338 /* get the controlURL of the service */
339 if((controlURLNode = xmlnode_get_child(serviceTypeNode,
340 "controlURL")) == NULL) {
341 purple_debug_error("upnp",
342 "parse_description_response(): Could not find controlURL\n");
343 g_free(baseURL);
344 xmlnode_free(xmlRootNode);
345 return NULL;
348 tmp = xmlnode_get_data(controlURLNode);
349 if(baseURL && !purple_str_has_prefix(tmp, "http://") &&
350 !purple_str_has_prefix(tmp, "HTTP://")) {
351 /* Handle absolute paths in a relative URL. This probably
352 * belongs in util.c. */
353 if (tmp[0] == '/') {
354 size_t length;
355 const char *path, *start = strstr(baseURL, "://");
356 start = start ? start + 3 : baseURL;
357 path = strchr(start, '/');
358 length = path ? path - baseURL : strlen(baseURL);
359 controlURL = g_strdup_printf("%.*s%s", (int)length, baseURL, tmp);
360 } else {
361 controlURL = g_strdup_printf("%s%s", baseURL, tmp);
363 g_free(tmp);
364 }else{
365 controlURL = tmp;
367 g_free(baseURL);
368 xmlnode_free(xmlRootNode);
370 return controlURL;
373 static void
374 upnp_parse_description_cb(PurpleUtilFetchUrlData *url_data, gpointer user_data,
375 const gchar *httpResponse, gsize len, const gchar *error_message)
377 UPnPDiscoveryData *dd = user_data;
378 gchar *control_url = NULL;
380 if (len > 0)
381 control_url = purple_upnp_parse_description_response(
382 httpResponse, len, dd->full_url, dd->service_type);
384 g_free(dd->full_url);
386 if(control_url == NULL) {
387 purple_debug_error("upnp",
388 "purple_upnp_parse_description(): control URL is NULL\n");
391 control_info.status = control_url ? PURPLE_UPNP_STATUS_DISCOVERED
392 : PURPLE_UPNP_STATUS_UNABLE_TO_DISCOVER;
393 control_info.lookup_time = time(NULL);
394 control_info.control_url = control_url;
395 strncpy(control_info.service_type, dd->service_type,
396 sizeof(control_info.service_type));
398 fire_discovery_callbacks(control_url != NULL);
400 /* Look up the public and internal IPs */
401 if(control_url != NULL) {
402 lookup_public_ip();
403 lookup_internal_ip();
406 g_free(dd);
409 static void
410 purple_upnp_parse_description(const gchar* descriptionURL, UPnPDiscoveryData *dd)
412 gchar* httpRequest;
413 gchar* descriptionXMLAddress;
414 gchar* descriptionAddress;
415 int port = 0;
417 /* parse the 4 above variables out of the descriptionURL
418 example description URL: http://192.168.1.1:5678/rootDesc.xml */
420 /* parse the url into address, port, path variables */
421 if(!purple_url_parse(descriptionURL, &descriptionAddress,
422 &port, &descriptionXMLAddress, NULL, NULL)) {
423 return;
425 if(port == 0 || port == -1) {
426 port = DEFAULT_HTTP_PORT;
429 /* for example...
430 GET /rootDesc.xml HTTP/1.1\r\nHost: 192.168.1.1:5678\r\n\r\n */
431 httpRequest = g_strdup_printf(
432 "GET /%s HTTP/1.1\r\n"
433 "Connection: close\r\n"
434 "Host: %s:%d\r\n\r\n",
435 descriptionXMLAddress, descriptionAddress, port);
437 g_free(descriptionXMLAddress);
439 dd->full_url = g_strdup_printf("http://%s:%d",
440 descriptionAddress, port);
441 g_free(descriptionAddress);
443 /* Remove the timeout because everything it is waiting for has
444 * successfully completed */
445 purple_timeout_remove(dd->tima);
446 dd->tima = 0;
448 purple_util_fetch_url_request_len(descriptionURL, TRUE, NULL, TRUE, httpRequest,
449 TRUE, MAX_UPNP_DOWNLOAD, upnp_parse_description_cb, dd);
451 g_free(httpRequest);
455 static void
456 purple_upnp_parse_discover_response(const gchar* buf, unsigned int buf_len,
457 UPnPDiscoveryData *dd)
459 gchar* startDescURL;
460 gchar* endDescURL;
461 gchar* descURL;
463 if(g_strstr_len(buf, buf_len, HTTP_OK) == NULL) {
464 purple_debug_error("upnp",
465 "parse_discover_response(): Failed In HTTP_OK\n");
466 return;
469 if((startDescURL = g_strstr_len(buf, buf_len, "http://")) == NULL) {
470 purple_debug_error("upnp",
471 "parse_discover_response(): Failed In finding http://\n");
472 return;
475 endDescURL = g_strstr_len(startDescURL, buf_len - (startDescURL - buf),
476 "\r");
477 if(endDescURL == NULL) {
478 endDescURL = g_strstr_len(startDescURL,
479 buf_len - (startDescURL - buf), "\n");
480 if(endDescURL == NULL) {
481 purple_debug_error("upnp",
482 "parse_discover_response(): Failed In endDescURL\n");
483 return;
487 /* XXX: I'm not sure how this could ever happen */
488 if(endDescURL == startDescURL) {
489 purple_debug_error("upnp",
490 "parse_discover_response(): endDescURL == startDescURL\n");
491 return;
494 descURL = g_strndup(startDescURL, endDescURL - startDescURL);
496 purple_upnp_parse_description(descURL, dd);
498 g_free(descURL);
502 static gboolean
503 purple_upnp_discover_timeout(gpointer data)
505 UPnPDiscoveryData* dd = data;
507 if (dd->inpa)
508 purple_input_remove(dd->inpa);
509 dd->inpa = 0;
510 dd->tima = 0;
512 if (dd->retry_count < NUM_UDP_ATTEMPTS) {
513 /* TODO: We probably shouldn't be incrementing retry_count in two places */
514 dd->retry_count++;
515 purple_upnp_discover_send_broadcast(dd);
516 } else {
517 if (dd->fd)
518 close(dd->fd);
520 control_info.status = PURPLE_UPNP_STATUS_UNABLE_TO_DISCOVER;
521 control_info.lookup_time = time(NULL);
522 control_info.service_type[0] = '\0';
523 g_free(control_info.control_url);
524 control_info.control_url = NULL;
526 fire_discovery_callbacks(FALSE);
528 g_free(dd);
531 return FALSE;
534 static void
535 purple_upnp_discover_udp_read(gpointer data, gint sock, PurpleInputCondition cond)
537 int len;
538 UPnPDiscoveryData *dd = data;
539 gchar buf[65536];
541 do {
542 len = recv(dd->fd, buf,
543 sizeof(buf) - 1, 0);
545 if(len > 0) {
546 buf[len] = '\0';
547 break;
548 } else if(errno != EINTR) {
549 /* We'll either get called again, or time out */
550 return;
552 } while (errno == EINTR);
554 purple_input_remove(dd->inpa);
555 dd->inpa = 0;
557 close(dd->fd);
558 dd->fd = -1;
560 /* parse the response, and see if it was a success */
561 purple_upnp_parse_discover_response(buf, len, dd);
563 /* We'll either time out or continue successfully */
566 static void
567 purple_upnp_discover_send_broadcast(UPnPDiscoveryData *dd)
569 gchar *sendMessage = NULL;
570 size_t totalSize;
571 gboolean sentSuccess;
573 /* because we are sending over UDP, if there is a failure
574 we should retry the send NUM_UDP_ATTEMPTS times. Also,
575 try different requests for WANIPConnection and WANPPPConnection*/
576 for(; dd->retry_count < NUM_UDP_ATTEMPTS; dd->retry_count++) {
577 sentSuccess = FALSE;
579 if((dd->retry_count % 2) == 0) {
580 strncpy(dd->service_type, WAN_IP_CONN_SERVICE, sizeof(dd->service_type));
581 } else {
582 strncpy(dd->service_type, WAN_PPP_CONN_SERVICE, sizeof(dd->service_type));
585 sendMessage = g_strdup_printf(SEARCH_REQUEST_STRING, dd->service_type);
587 totalSize = strlen(sendMessage);
589 do {
590 if(sendto(dd->fd, sendMessage, totalSize, 0,
591 (struct sockaddr*) &(dd->server),
592 sizeof(struct sockaddr_in)
593 ) == totalSize) {
594 sentSuccess = TRUE;
595 break;
597 } while (errno == EINTR || errno == EAGAIN);
599 g_free(sendMessage);
601 if(sentSuccess) {
602 dd->tima = purple_timeout_add(DISCOVERY_TIMEOUT,
603 purple_upnp_discover_timeout, dd);
604 dd->inpa = purple_input_add(dd->fd, PURPLE_INPUT_READ,
605 purple_upnp_discover_udp_read, dd);
607 return;
611 /* We have already done all our retries. Make sure that the callback
612 * doesn't get called before the original function returns */
613 purple_timeout_add(10, purple_upnp_discover_timeout, dd);
616 void
617 purple_upnp_discover(PurpleUPnPCallback cb, gpointer cb_data)
619 /* Socket Setup Variables */
620 int sock;
621 struct hostent* hp;
623 /* UDP RECEIVE VARIABLES */
624 UPnPDiscoveryData *dd;
626 if (control_info.status == PURPLE_UPNP_STATUS_DISCOVERING) {
627 if (cb) {
628 discovery_callbacks = g_slist_append(
629 discovery_callbacks, cb);
630 discovery_callbacks = g_slist_append(
631 discovery_callbacks, cb_data);
633 return;
636 dd = g_new0(UPnPDiscoveryData, 1);
637 if (cb) {
638 discovery_callbacks = g_slist_append(discovery_callbacks, cb);
639 discovery_callbacks = g_slist_append(discovery_callbacks,
640 cb_data);
643 /* Set up the sockets */
644 sock = socket(AF_INET, SOCK_DGRAM, 0);
645 if(sock == -1) {
646 purple_debug_error("upnp",
647 "purple_upnp_discover(): Failed In sock creation\n");
648 /* Short circuit the retry attempts */
649 dd->retry_count = NUM_UDP_ATTEMPTS;
650 purple_timeout_add(10, purple_upnp_discover_timeout, dd);
651 return;
654 dd->fd = sock;
656 /* TODO: Non-blocking! */
657 if((hp = gethostbyname(HTTPMU_HOST_ADDRESS)) == NULL) {
658 purple_debug_error("upnp",
659 "purple_upnp_discover(): Failed In gethostbyname\n");
660 /* Short circuit the retry attempts */
661 dd->retry_count = NUM_UDP_ATTEMPTS;
662 purple_timeout_add(10, purple_upnp_discover_timeout, dd);
663 return;
666 memset(&(dd->server), 0, sizeof(struct sockaddr));
667 dd->server.sin_family = AF_INET;
668 memcpy(&(dd->server.sin_addr), hp->h_addr_list[0], hp->h_length);
669 dd->server.sin_port = htons(HTTPMU_HOST_PORT);
671 control_info.status = PURPLE_UPNP_STATUS_DISCOVERING;
673 purple_upnp_discover_send_broadcast(dd);
676 static PurpleUtilFetchUrlData*
677 purple_upnp_generate_action_message_and_send(const gchar* actionName,
678 const gchar* actionParams, PurpleUtilFetchUrlCallback cb,
679 gpointer cb_data)
681 PurpleUtilFetchUrlData* gfud;
682 gchar* soapMessage;
683 gchar* totalSendMessage;
684 gchar* pathOfControl;
685 gchar* addressOfControl;
686 int port = 0;
688 /* parse the url into address, port, path variables */
689 if(!purple_url_parse(control_info.control_url, &addressOfControl,
690 &port, &pathOfControl, NULL, NULL)) {
691 purple_debug_error("upnp",
692 "generate_action_message_and_send(): Failed In Parse URL\n");
693 /* XXX: This should probably be async */
694 if(cb)
695 cb(NULL, cb_data, NULL, 0, NULL);
696 return NULL;
698 if(port == 0 || port == -1) {
699 port = DEFAULT_HTTP_PORT;
702 /* set the soap message */
703 soapMessage = g_strdup_printf(SOAP_ACTION, actionName,
704 control_info.service_type, actionParams, actionName);
706 /* set the HTTP Header, and append the body to it */
707 totalSendMessage = g_strdup_printf(HTTP_HEADER_ACTION "%s",
708 pathOfControl, addressOfControl, port,
709 control_info.service_type, actionName,
710 strlen(soapMessage), soapMessage);
711 g_free(pathOfControl);
712 g_free(soapMessage);
714 gfud = purple_util_fetch_url_request_len(control_info.control_url, FALSE, NULL, TRUE,
715 totalSendMessage, TRUE, MAX_UPNP_DOWNLOAD, cb, cb_data);
717 g_free(totalSendMessage);
718 g_free(addressOfControl);
720 return gfud;
723 const gchar *
724 purple_upnp_get_public_ip()
726 if (control_info.status == PURPLE_UPNP_STATUS_DISCOVERED
727 && control_info.publicip
728 && strlen(control_info.publicip) > 0)
729 return control_info.publicip;
731 /* Trigger another UPnP discovery if 5 minutes have elapsed since the
732 * last one, and it wasn't successful */
733 if (control_info.status < PURPLE_UPNP_STATUS_DISCOVERING
734 && (time(NULL) - control_info.lookup_time) > 300)
735 purple_upnp_discover(NULL, NULL);
737 return NULL;
740 static void
741 looked_up_public_ip_cb(PurpleUtilFetchUrlData *url_data, gpointer user_data,
742 const gchar *httpResponse, gsize len, const gchar *error_message)
744 gchar* temp, *temp2;
746 if ((error_message != NULL) || (httpResponse == NULL))
747 return;
749 /* extract the ip, or see if there is an error */
750 if((temp = g_strstr_len(httpResponse, len,
751 "<NewExternalIPAddress")) == NULL) {
752 purple_debug_error("upnp",
753 "looked_up_public_ip_cb(): Failed Finding <NewExternalIPAddress\n");
754 return;
756 if(!(temp = g_strstr_len(temp, len - (temp - httpResponse), ">"))) {
757 purple_debug_error("upnp",
758 "looked_up_public_ip_cb(): Failed In Finding >\n");
759 return;
761 if(!(temp2 = g_strstr_len(temp, len - (temp - httpResponse), "<"))) {
762 purple_debug_error("upnp",
763 "looked_up_public_ip_cb(): Failed In Finding <\n");
764 return;
766 *temp2 = '\0';
768 strncpy(control_info.publicip, temp + 1,
769 sizeof(control_info.publicip));
771 purple_debug_info("upnp", "NAT Returned IP: %s\n", control_info.publicip);
774 static void
775 lookup_public_ip()
777 purple_upnp_generate_action_message_and_send("GetExternalIPAddress", "",
778 looked_up_public_ip_cb, NULL);
781 /* TODO: This could be exported */
782 static const gchar *
783 purple_upnp_get_internal_ip(void)
785 if (control_info.status == PURPLE_UPNP_STATUS_DISCOVERED
786 && control_info.internalip
787 && strlen(control_info.internalip) > 0)
788 return control_info.internalip;
790 /* Trigger another UPnP discovery if 5 minutes have elapsed since the
791 * last one, and it wasn't successful */
792 if (control_info.status < PURPLE_UPNP_STATUS_DISCOVERING
793 && (time(NULL) - control_info.lookup_time) > 300)
794 purple_upnp_discover(NULL, NULL);
796 return NULL;
799 static void
800 looked_up_internal_ip_cb(gpointer data, gint source, const gchar *error_message)
802 if (source) {
803 strncpy(control_info.internalip,
804 purple_network_get_local_system_ip(source),
805 sizeof(control_info.internalip));
806 purple_debug_info("upnp", "Local IP: %s\n",
807 control_info.internalip);
808 close(source);
809 } else
810 purple_debug_info("upnp", "Unable to look up local IP\n");
814 static void
815 lookup_internal_ip()
817 gchar* addressOfControl;
818 int port = 0;
820 if(!purple_url_parse(control_info.control_url, &addressOfControl, &port,
821 NULL, NULL, NULL)) {
822 purple_debug_error("upnp",
823 "lookup_internal_ip(): Failed In Parse URL\n");
824 return;
826 if(port == 0 || port == -1) {
827 port = DEFAULT_HTTP_PORT;
830 if(purple_proxy_connect(NULL, NULL, addressOfControl, port,
831 looked_up_internal_ip_cb, NULL) == NULL)
833 purple_debug_error("upnp", "Get Local IP Connect Failed: Address: %s @@@ Port %d\n",
834 addressOfControl, port);
837 g_free(addressOfControl);
840 static void
841 done_port_mapping_cb(PurpleUtilFetchUrlData *url_data, gpointer user_data,
842 const gchar *httpResponse, gsize len, const gchar *error_message)
844 UPnPMappingAddRemove *ar = user_data;
846 gboolean success = TRUE;
848 /* determine if port mapping was a success */
849 if ((error_message != NULL) || (httpResponse == NULL) ||
850 (g_strstr_len(httpResponse, len, HTTP_OK) == NULL))
852 purple_debug_error("upnp",
853 "purple_upnp_set_port_mapping(): Failed HTTP_OK\n%s\n",
854 httpResponse ? httpResponse : "(null)");
855 success = FALSE;
856 } else
857 purple_debug_info("upnp", "Successfully completed port mapping operation\n");
859 if (ar->cb)
860 ar->cb(success, ar->cb_data);
861 g_free(ar);
864 static void
865 do_port_mapping_cb(gboolean has_control_mapping, gpointer data)
867 UPnPMappingAddRemove *ar = data;
869 if (has_control_mapping) {
870 gchar action_name[25];
871 gchar *action_params;
872 if(ar->add) {
873 const gchar *internal_ip;
874 /* get the internal IP */
875 if(!(internal_ip = purple_upnp_get_internal_ip())) {
876 purple_debug_error("upnp",
877 "purple_upnp_set_port_mapping(): couldn't get local ip\n");
878 /* UGLY */
879 if (ar->cb)
880 ar->cb(FALSE, ar->cb_data);
881 g_free(ar);
882 return;
884 strncpy(action_name, "AddPortMapping",
885 sizeof(action_name));
886 action_params = g_strdup_printf(
887 ADD_PORT_MAPPING_PARAMS,
888 ar->portmap, ar->protocol, ar->portmap,
889 internal_ip);
890 } else {
891 strncpy(action_name, "DeletePortMapping", sizeof(action_name));
892 action_params = g_strdup_printf(
893 DELETE_PORT_MAPPING_PARAMS,
894 ar->portmap, ar->protocol);
897 ar->gfud = purple_upnp_generate_action_message_and_send(action_name,
898 action_params, done_port_mapping_cb, ar);
900 g_free(action_params);
901 return;
905 if (ar->cb)
906 ar->cb(FALSE, ar->cb_data);
907 g_free(ar);
910 static gboolean
911 fire_port_mapping_failure_cb(gpointer data)
913 do_port_mapping_cb(FALSE, data);
914 return FALSE;
917 void purple_upnp_cancel_port_mapping(UPnPMappingAddRemove *ar)
919 GSList *l;
921 /* Remove ar from discovery_callbacks if present; it was inserted after a cb.
922 * The same cb may be in the list multple times, so be careful to remove the one assocaited with ar. */
923 l = discovery_callbacks;
924 while (l)
926 if (l->next && (l->next->data == ar)) {
927 discovery_callbacks = g_slist_delete_link(discovery_callbacks, l->next);
928 discovery_callbacks = g_slist_delete_link(discovery_callbacks, l);
931 l = l->next;
934 if (ar->tima > 0)
935 purple_timeout_remove(ar->tima);
937 if (ar->gfud)
938 purple_util_fetch_url_cancel(ar->gfud);
940 g_free(ar);
943 UPnPMappingAddRemove *
944 purple_upnp_set_port_mapping(unsigned short portmap, const gchar* protocol,
945 PurpleUPnPCallback cb, gpointer cb_data)
947 UPnPMappingAddRemove *ar;
949 ar = g_new0(UPnPMappingAddRemove, 1);
950 ar->cb = cb;
951 ar->cb_data = cb_data;
952 ar->add = TRUE;
953 ar->portmap = portmap;
954 strncpy(ar->protocol, protocol, sizeof(ar->protocol));
956 /* If we're waiting for a discovery, add to the callbacks list */
957 if(control_info.status == PURPLE_UPNP_STATUS_DISCOVERING) {
958 /* TODO: This will fail because when this cb is triggered,
959 * the internal IP lookup won't be complete */
960 discovery_callbacks = g_slist_append(
961 discovery_callbacks, do_port_mapping_cb);
962 discovery_callbacks = g_slist_append(
963 discovery_callbacks, ar);
964 return ar;
967 /* If we haven't had a successful UPnP discovery, check if 5 minutes has
968 * elapsed since the last try, try again */
969 if(control_info.status == PURPLE_UPNP_STATUS_UNDISCOVERED ||
970 (control_info.status == PURPLE_UPNP_STATUS_UNABLE_TO_DISCOVER
971 && (time(NULL) - control_info.lookup_time) > 300)) {
972 purple_upnp_discover(do_port_mapping_cb, ar);
973 return ar;
974 } else if(control_info.status == PURPLE_UPNP_STATUS_UNABLE_TO_DISCOVER) {
975 if (cb) {
976 /* Asynchronously trigger a failed response */
977 ar->tima = purple_timeout_add(10, fire_port_mapping_failure_cb, ar);
978 } else {
979 /* No need to do anything if nobody expects a response*/
980 g_free(ar);
981 ar = NULL;
983 return ar;
986 do_port_mapping_cb(TRUE, ar);
987 return ar;
990 UPnPMappingAddRemove *
991 purple_upnp_remove_port_mapping(unsigned short portmap, const char* protocol,
992 PurpleUPnPCallback cb, gpointer cb_data)
994 UPnPMappingAddRemove *ar;
996 ar = g_new0(UPnPMappingAddRemove, 1);
997 ar->cb = cb;
998 ar->cb_data = cb_data;
999 ar->add = FALSE;
1000 ar->portmap = portmap;
1001 strncpy(ar->protocol, protocol, sizeof(ar->protocol));
1003 /* If we're waiting for a discovery, add to the callbacks list */
1004 if(control_info.status == PURPLE_UPNP_STATUS_DISCOVERING) {
1005 discovery_callbacks = g_slist_append(
1006 discovery_callbacks, do_port_mapping_cb);
1007 discovery_callbacks = g_slist_append(
1008 discovery_callbacks, ar);
1009 return ar;
1012 /* If we haven't had a successful UPnP discovery, check if 5 minutes has
1013 * elapsed since the last try, try again */
1014 if(control_info.status == PURPLE_UPNP_STATUS_UNDISCOVERED ||
1015 (control_info.status == PURPLE_UPNP_STATUS_UNABLE_TO_DISCOVER
1016 && (time(NULL) - control_info.lookup_time) > 300)) {
1017 purple_upnp_discover(do_port_mapping_cb, ar);
1018 return ar;
1019 } else if(control_info.status == PURPLE_UPNP_STATUS_UNABLE_TO_DISCOVER) {
1020 if (cb) {
1021 /* Asynchronously trigger a failed response */
1022 ar->tima = purple_timeout_add(10, fire_port_mapping_failure_cb, ar);
1023 } else {
1024 /* No need to do anything if nobody expects a response*/
1025 g_free(ar);
1026 ar = NULL;
1028 return ar;
1031 do_port_mapping_cb(TRUE, ar);
1032 return ar;
1035 static void
1036 purple_upnp_network_config_changed_cb(void *data)
1038 /* Reset the control_info to default values */
1039 control_info.status = PURPLE_UPNP_STATUS_UNDISCOVERED;
1040 g_free(control_info.control_url);
1041 control_info.control_url = NULL;
1042 control_info.service_type[0] = '\0';
1043 control_info.publicip[0] = '\0';
1044 control_info.internalip[0] = '\0';
1045 control_info.lookup_time = 0;
1048 static void*
1049 purple_upnp_get_handle(void)
1051 static int handle;
1053 return &handle;
1056 void
1057 purple_upnp_init()
1059 purple_signal_connect(purple_network_get_handle(), "network-configuration-changed",
1060 purple_upnp_get_handle(), PURPLE_CALLBACK(purple_upnp_network_config_changed_cb),
1061 NULL);