Open an explorer.exe window at the location of the file when clicking
[pidgin-git.git] / libpurple / upnp.c
bloba0ad9af7fb706e62203d555e5b740ba709d79348
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[20];
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 gboolean success;
146 guint tima; /* purple_timeout_add handle */
147 PurpleUtilFetchUrlData *gfud;
150 static PurpleUPnPControlInfo control_info = {
151 PURPLE_UPNP_STATUS_UNDISCOVERED,
152 NULL, "\0", "\0", "\0", 0};
154 static GSList *discovery_callbacks = NULL;
156 static void purple_upnp_discover_send_broadcast(UPnPDiscoveryData *dd);
157 static void lookup_public_ip(void);
158 static void lookup_internal_ip(void);
160 static gboolean
161 fire_ar_cb_async_and_free(gpointer data)
163 UPnPMappingAddRemove *ar = data;
164 if (ar) {
165 if (ar->cb)
166 ar->cb(ar->success, ar->cb_data);
167 g_free(ar);
170 return FALSE;
173 static void
174 fire_discovery_callbacks(gboolean success)
176 while(discovery_callbacks) {
177 gpointer data;
178 PurpleUPnPCallback cb = discovery_callbacks->data;
179 discovery_callbacks = g_slist_delete_link(discovery_callbacks, discovery_callbacks);
180 data = discovery_callbacks->data;
181 discovery_callbacks = g_slist_delete_link(discovery_callbacks, discovery_callbacks);
182 cb(success, data);
186 static gboolean
187 purple_upnp_compare_device(const xmlnode* device, const gchar* deviceType)
189 xmlnode* deviceTypeNode = xmlnode_get_child(device, "deviceType");
190 char *tmp;
191 gboolean ret;
193 if(deviceTypeNode == NULL) {
194 return FALSE;
197 tmp = xmlnode_get_data(deviceTypeNode);
198 ret = !g_ascii_strcasecmp(tmp, deviceType);
199 g_free(tmp);
201 return ret;
204 static gboolean
205 purple_upnp_compare_service(const xmlnode* service, const gchar* serviceType)
207 xmlnode* serviceTypeNode;
208 char *tmp;
209 gboolean ret;
211 if(service == NULL) {
212 return FALSE;
215 serviceTypeNode = xmlnode_get_child(service, "serviceType");
217 if(serviceTypeNode == NULL) {
218 return FALSE;
221 tmp = xmlnode_get_data(serviceTypeNode);
222 ret = !g_ascii_strcasecmp(tmp, serviceType);
223 g_free(tmp);
225 return ret;
228 static gchar*
229 purple_upnp_parse_description_response(const gchar* httpResponse, gsize len,
230 const gchar* httpURL, const gchar* serviceType)
232 gchar *xmlRoot, *baseURL, *controlURL, *service;
233 xmlnode *xmlRootNode, *serviceTypeNode, *controlURLNode, *baseURLNode;
234 char *tmp;
236 /* make sure we have a valid http response */
237 if(g_strstr_len(httpResponse, len, HTTP_OK) == NULL) {
238 purple_debug_error("upnp",
239 "parse_description_response(): Failed In HTTP_OK\n");
240 return NULL;
243 /* find the root of the xml document */
244 if((xmlRoot = g_strstr_len(httpResponse, len, "<root")) == NULL) {
245 purple_debug_error("upnp",
246 "parse_description_response(): Failed finding root\n");
247 return NULL;
250 /* create the xml root node */
251 if((xmlRootNode = xmlnode_from_str(xmlRoot,
252 len - (xmlRoot - httpResponse))) == NULL) {
253 purple_debug_error("upnp",
254 "parse_description_response(): Could not parse xml root node\n");
255 return NULL;
258 /* get the baseURL of the device */
259 if((baseURLNode = xmlnode_get_child(xmlRootNode, "URLBase")) != NULL) {
260 baseURL = xmlnode_get_data(baseURLNode);
261 } else {
262 baseURL = g_strdup(httpURL);
265 /* get the serviceType child that has the service type as its data */
267 /* get urn:schemas-upnp-org:device:InternetGatewayDevice:1 and its devicelist */
268 serviceTypeNode = xmlnode_get_child(xmlRootNode, "device");
269 while(!purple_upnp_compare_device(serviceTypeNode,
270 "urn:schemas-upnp-org:device:InternetGatewayDevice:1") &&
271 serviceTypeNode != NULL) {
272 serviceTypeNode = xmlnode_get_next_twin(serviceTypeNode);
274 if(serviceTypeNode == NULL) {
275 purple_debug_error("upnp",
276 "parse_description_response(): could not get serviceTypeNode 1\n");
277 g_free(baseURL);
278 xmlnode_free(xmlRootNode);
279 return NULL;
281 serviceTypeNode = xmlnode_get_child(serviceTypeNode, "deviceList");
282 if(serviceTypeNode == NULL) {
283 purple_debug_error("upnp",
284 "parse_description_response(): could not get serviceTypeNode 2\n");
285 g_free(baseURL);
286 xmlnode_free(xmlRootNode);
287 return NULL;
290 /* get urn:schemas-upnp-org:device:WANDevice:1 and its devicelist */
291 serviceTypeNode = xmlnode_get_child(serviceTypeNode, "device");
292 while(!purple_upnp_compare_device(serviceTypeNode,
293 "urn:schemas-upnp-org:device:WANDevice:1") &&
294 serviceTypeNode != NULL) {
295 serviceTypeNode = xmlnode_get_next_twin(serviceTypeNode);
297 if(serviceTypeNode == NULL) {
298 purple_debug_error("upnp",
299 "parse_description_response(): could not get serviceTypeNode 3\n");
300 g_free(baseURL);
301 xmlnode_free(xmlRootNode);
302 return NULL;
304 serviceTypeNode = xmlnode_get_child(serviceTypeNode, "deviceList");
305 if(serviceTypeNode == NULL) {
306 purple_debug_error("upnp",
307 "parse_description_response(): could not get serviceTypeNode 4\n");
308 g_free(baseURL);
309 xmlnode_free(xmlRootNode);
310 return NULL;
313 /* get urn:schemas-upnp-org:device:WANConnectionDevice:1 and its servicelist */
314 serviceTypeNode = xmlnode_get_child(serviceTypeNode, "device");
315 while(serviceTypeNode && !purple_upnp_compare_device(serviceTypeNode,
316 "urn:schemas-upnp-org:device:WANConnectionDevice:1")) {
317 serviceTypeNode = xmlnode_get_next_twin(serviceTypeNode);
319 if(serviceTypeNode == NULL) {
320 purple_debug_error("upnp",
321 "parse_description_response(): could not get serviceTypeNode 5\n");
322 g_free(baseURL);
323 xmlnode_free(xmlRootNode);
324 return NULL;
326 serviceTypeNode = xmlnode_get_child(serviceTypeNode, "serviceList");
327 if(serviceTypeNode == NULL) {
328 purple_debug_error("upnp",
329 "parse_description_response(): could not get serviceTypeNode 6\n");
330 g_free(baseURL);
331 xmlnode_free(xmlRootNode);
332 return NULL;
335 /* get the serviceType variable passed to this function */
336 service = g_strdup_printf(SEARCH_REQUEST_DEVICE, serviceType);
337 serviceTypeNode = xmlnode_get_child(serviceTypeNode, "service");
338 while(!purple_upnp_compare_service(serviceTypeNode, service) &&
339 serviceTypeNode != NULL) {
340 serviceTypeNode = xmlnode_get_next_twin(serviceTypeNode);
343 g_free(service);
344 if(serviceTypeNode == NULL) {
345 purple_debug_error("upnp",
346 "parse_description_response(): could not get serviceTypeNode 7\n");
347 g_free(baseURL);
348 xmlnode_free(xmlRootNode);
349 return NULL;
352 /* get the controlURL of the service */
353 if((controlURLNode = xmlnode_get_child(serviceTypeNode,
354 "controlURL")) == NULL) {
355 purple_debug_error("upnp",
356 "parse_description_response(): Could not find controlURL\n");
357 g_free(baseURL);
358 xmlnode_free(xmlRootNode);
359 return NULL;
362 tmp = xmlnode_get_data(controlURLNode);
363 if(baseURL && !purple_str_has_prefix(tmp, "http://") &&
364 !purple_str_has_prefix(tmp, "HTTP://")) {
365 /* Handle absolute paths in a relative URL. This probably
366 * belongs in util.c. */
367 if (tmp[0] == '/') {
368 size_t length;
369 const char *path, *start = strstr(baseURL, "://");
370 start = start ? start + 3 : baseURL;
371 path = strchr(start, '/');
372 length = path ? path - baseURL : strlen(baseURL);
373 controlURL = g_strdup_printf("%.*s%s", (int)length, baseURL, tmp);
374 } else {
375 controlURL = g_strdup_printf("%s%s", baseURL, tmp);
377 g_free(tmp);
378 }else{
379 controlURL = tmp;
381 g_free(baseURL);
382 xmlnode_free(xmlRootNode);
384 return controlURL;
387 static void
388 upnp_parse_description_cb(PurpleUtilFetchUrlData *url_data, gpointer user_data,
389 const gchar *httpResponse, gsize len, const gchar *error_message)
391 UPnPDiscoveryData *dd = user_data;
392 gchar *control_url = NULL;
394 if (len > 0)
395 control_url = purple_upnp_parse_description_response(
396 httpResponse, len, dd->full_url, dd->service_type);
398 g_free(dd->full_url);
400 if(control_url == NULL) {
401 purple_debug_error("upnp",
402 "purple_upnp_parse_description(): control URL is NULL\n");
405 control_info.status = control_url ? PURPLE_UPNP_STATUS_DISCOVERED
406 : PURPLE_UPNP_STATUS_UNABLE_TO_DISCOVER;
407 control_info.lookup_time = time(NULL);
408 control_info.control_url = control_url;
409 strncpy(control_info.service_type, dd->service_type,
410 sizeof(control_info.service_type));
412 fire_discovery_callbacks(control_url != NULL);
414 /* Look up the public and internal IPs */
415 if(control_url != NULL) {
416 lookup_public_ip();
417 lookup_internal_ip();
420 if (dd->inpa > 0)
421 purple_input_remove(dd->inpa);
422 if (dd->tima > 0)
423 purple_timeout_remove(dd->tima);
425 g_free(dd);
428 static void
429 purple_upnp_parse_description(const gchar* descriptionURL, UPnPDiscoveryData *dd)
431 gchar* httpRequest;
432 gchar* descriptionXMLAddress;
433 gchar* descriptionAddress;
434 int port = 0;
436 /* parse the 4 above variables out of the descriptionURL
437 example description URL: http://192.168.1.1:5678/rootDesc.xml */
439 /* parse the url into address, port, path variables */
440 if(!purple_url_parse(descriptionURL, &descriptionAddress,
441 &port, &descriptionXMLAddress, NULL, NULL)) {
442 return;
444 if(port == 0 || port == -1) {
445 port = DEFAULT_HTTP_PORT;
448 /* for example...
449 GET /rootDesc.xml HTTP/1.1\r\nHost: 192.168.1.1:5678\r\n\r\n */
450 httpRequest = g_strdup_printf(
451 "GET /%s HTTP/1.1\r\n"
452 "Connection: close\r\n"
453 "Host: %s:%d\r\n\r\n",
454 descriptionXMLAddress, descriptionAddress, port);
456 g_free(descriptionXMLAddress);
458 dd->full_url = g_strdup_printf("http://%s:%d",
459 descriptionAddress, port);
460 g_free(descriptionAddress);
462 /* Remove the timeout because everything it is waiting for has
463 * successfully completed */
464 purple_timeout_remove(dd->tima);
465 dd->tima = 0;
467 purple_util_fetch_url_request_len(descriptionURL, TRUE, NULL, TRUE, httpRequest,
468 TRUE, MAX_UPNP_DOWNLOAD, upnp_parse_description_cb, dd);
470 g_free(httpRequest);
474 static void
475 purple_upnp_parse_discover_response(const gchar* buf, unsigned int buf_len,
476 UPnPDiscoveryData *dd)
478 gchar* startDescURL;
479 gchar* endDescURL;
480 gchar* descURL;
482 if(g_strstr_len(buf, buf_len, HTTP_OK) == NULL) {
483 purple_debug_error("upnp",
484 "parse_discover_response(): Failed In HTTP_OK\n");
485 return;
488 if((startDescURL = g_strstr_len(buf, buf_len, "http://")) == NULL) {
489 purple_debug_error("upnp",
490 "parse_discover_response(): Failed In finding http://\n");
491 return;
494 endDescURL = g_strstr_len(startDescURL, buf_len - (startDescURL - buf),
495 "\r");
496 if(endDescURL == NULL) {
497 endDescURL = g_strstr_len(startDescURL,
498 buf_len - (startDescURL - buf), "\n");
499 if(endDescURL == NULL) {
500 purple_debug_error("upnp",
501 "parse_discover_response(): Failed In endDescURL\n");
502 return;
506 /* XXX: I'm not sure how this could ever happen */
507 if(endDescURL == startDescURL) {
508 purple_debug_error("upnp",
509 "parse_discover_response(): endDescURL == startDescURL\n");
510 return;
513 descURL = g_strndup(startDescURL, endDescURL - startDescURL);
515 purple_upnp_parse_description(descURL, dd);
517 g_free(descURL);
521 static gboolean
522 purple_upnp_discover_timeout(gpointer data)
524 UPnPDiscoveryData* dd = data;
526 if (dd->inpa)
527 purple_input_remove(dd->inpa);
528 if (dd->tima > 0)
529 purple_timeout_remove(dd->tima);
530 dd->inpa = 0;
531 dd->tima = 0;
533 if (dd->retry_count < NUM_UDP_ATTEMPTS) {
534 /* TODO: We probably shouldn't be incrementing retry_count in two places */
535 dd->retry_count++;
536 purple_upnp_discover_send_broadcast(dd);
537 } else {
538 if (dd->fd != -1)
539 close(dd->fd);
541 control_info.status = PURPLE_UPNP_STATUS_UNABLE_TO_DISCOVER;
542 control_info.lookup_time = time(NULL);
543 control_info.service_type[0] = '\0';
544 g_free(control_info.control_url);
545 control_info.control_url = NULL;
547 fire_discovery_callbacks(FALSE);
549 g_free(dd);
552 return FALSE;
555 static void
556 purple_upnp_discover_udp_read(gpointer data, gint sock, PurpleInputCondition cond)
558 int len;
559 UPnPDiscoveryData *dd = data;
560 gchar buf[65536];
562 do {
563 len = recv(dd->fd, buf,
564 sizeof(buf) - 1, 0);
566 if(len >= 0) {
567 buf[len] = '\0';
568 break;
569 } else if(errno != EINTR) {
570 /* We'll either get called again, or time out */
571 return;
573 } while (errno == EINTR);
575 purple_input_remove(dd->inpa);
576 dd->inpa = 0;
578 close(dd->fd);
579 dd->fd = -1;
581 /* parse the response, and see if it was a success */
582 purple_upnp_parse_discover_response(buf, len, dd);
584 /* We'll either time out or continue successfully */
587 static void
588 purple_upnp_discover_send_broadcast(UPnPDiscoveryData *dd)
590 gchar *sendMessage = NULL;
591 size_t totalSize;
592 gboolean sentSuccess;
594 /* because we are sending over UDP, if there is a failure
595 we should retry the send NUM_UDP_ATTEMPTS times. Also,
596 try different requests for WANIPConnection and WANPPPConnection*/
597 for(; dd->retry_count < NUM_UDP_ATTEMPTS; dd->retry_count++) {
598 sentSuccess = FALSE;
600 if((dd->retry_count % 2) == 0) {
601 strncpy(dd->service_type, WAN_IP_CONN_SERVICE, sizeof(dd->service_type));
602 } else {
603 strncpy(dd->service_type, WAN_PPP_CONN_SERVICE, sizeof(dd->service_type));
606 sendMessage = g_strdup_printf(SEARCH_REQUEST_STRING, dd->service_type);
608 totalSize = strlen(sendMessage);
610 do {
611 if(sendto(dd->fd, sendMessage, totalSize, 0,
612 (struct sockaddr*) &(dd->server),
613 sizeof(struct sockaddr_in)
614 ) == totalSize) {
615 sentSuccess = TRUE;
616 break;
618 } while (errno == EINTR || errno == EAGAIN);
620 g_free(sendMessage);
622 if(sentSuccess) {
623 dd->tima = purple_timeout_add(DISCOVERY_TIMEOUT,
624 purple_upnp_discover_timeout, dd);
625 dd->inpa = purple_input_add(dd->fd, PURPLE_INPUT_READ,
626 purple_upnp_discover_udp_read, dd);
628 return;
632 /* We have already done all our retries. Make sure that the callback
633 * doesn't get called before the original function returns */
634 dd->tima = purple_timeout_add(10, purple_upnp_discover_timeout, dd);
637 void
638 purple_upnp_discover(PurpleUPnPCallback cb, gpointer cb_data)
640 /* Socket Setup Variables */
641 int sock;
642 struct hostent* hp;
644 /* UDP RECEIVE VARIABLES */
645 UPnPDiscoveryData *dd;
647 if (control_info.status == PURPLE_UPNP_STATUS_DISCOVERING) {
648 if (cb) {
649 discovery_callbacks = g_slist_append(
650 discovery_callbacks, cb);
651 discovery_callbacks = g_slist_append(
652 discovery_callbacks, cb_data);
654 return;
657 dd = g_new0(UPnPDiscoveryData, 1);
658 if (cb) {
659 discovery_callbacks = g_slist_append(discovery_callbacks, cb);
660 discovery_callbacks = g_slist_append(discovery_callbacks,
661 cb_data);
664 /* Set up the sockets */
665 dd->fd = sock = socket(AF_INET, SOCK_DGRAM, 0);
666 if(sock == -1) {
667 purple_debug_error("upnp",
668 "purple_upnp_discover(): Failed In sock creation\n");
669 /* Short circuit the retry attempts */
670 dd->retry_count = NUM_UDP_ATTEMPTS;
671 dd->tima = purple_timeout_add(10, purple_upnp_discover_timeout, dd);
672 return;
675 /* TODO: Non-blocking! */
676 if((hp = gethostbyname(HTTPMU_HOST_ADDRESS)) == NULL) {
677 purple_debug_error("upnp",
678 "purple_upnp_discover(): Failed In gethostbyname\n");
679 /* Short circuit the retry attempts */
680 dd->retry_count = NUM_UDP_ATTEMPTS;
681 dd->tima = purple_timeout_add(10, purple_upnp_discover_timeout, dd);
682 return;
685 memset(&(dd->server), 0, sizeof(struct sockaddr));
686 dd->server.sin_family = AF_INET;
687 memcpy(&(dd->server.sin_addr), hp->h_addr_list[0], hp->h_length);
688 dd->server.sin_port = htons(HTTPMU_HOST_PORT);
690 control_info.status = PURPLE_UPNP_STATUS_DISCOVERING;
692 purple_upnp_discover_send_broadcast(dd);
695 static PurpleUtilFetchUrlData*
696 purple_upnp_generate_action_message_and_send(const gchar* actionName,
697 const gchar* actionParams, PurpleUtilFetchUrlCallback cb,
698 gpointer cb_data)
700 PurpleUtilFetchUrlData* gfud;
701 gchar* soapMessage;
702 gchar* totalSendMessage;
703 gchar* pathOfControl;
704 gchar* addressOfControl;
705 int port = 0;
707 /* parse the url into address, port, path variables */
708 if(!purple_url_parse(control_info.control_url, &addressOfControl,
709 &port, &pathOfControl, NULL, NULL)) {
710 purple_debug_error("upnp",
711 "generate_action_message_and_send(): Failed In Parse URL\n");
712 /* XXX: This should probably be async */
713 if(cb)
714 cb(NULL, cb_data, NULL, 0, NULL);
715 return NULL;
717 if(port == 0 || port == -1) {
718 port = DEFAULT_HTTP_PORT;
721 /* set the soap message */
722 soapMessage = g_strdup_printf(SOAP_ACTION, actionName,
723 control_info.service_type, actionParams, actionName);
725 /* set the HTTP Header, and append the body to it */
726 totalSendMessage = g_strdup_printf(HTTP_HEADER_ACTION "%s",
727 pathOfControl, addressOfControl, port,
728 control_info.service_type, actionName,
729 strlen(soapMessage), soapMessage);
730 g_free(pathOfControl);
731 g_free(soapMessage);
733 gfud = purple_util_fetch_url_request_len(control_info.control_url, FALSE, NULL, TRUE,
734 totalSendMessage, TRUE, MAX_UPNP_DOWNLOAD, cb, cb_data);
736 g_free(totalSendMessage);
737 g_free(addressOfControl);
739 return gfud;
742 const gchar *
743 purple_upnp_get_public_ip()
745 if (control_info.status == PURPLE_UPNP_STATUS_DISCOVERED
746 && control_info.publicip
747 && strlen(control_info.publicip) > 0)
748 return control_info.publicip;
750 /* Trigger another UPnP discovery if 5 minutes have elapsed since the
751 * last one, and it wasn't successful */
752 if (control_info.status < PURPLE_UPNP_STATUS_DISCOVERING
753 && (time(NULL) - control_info.lookup_time) > 300)
754 purple_upnp_discover(NULL, NULL);
756 return NULL;
759 static void
760 looked_up_public_ip_cb(PurpleUtilFetchUrlData *url_data, gpointer user_data,
761 const gchar *httpResponse, gsize len, const gchar *error_message)
763 gchar* temp, *temp2;
765 if ((error_message != NULL) || (httpResponse == NULL))
766 return;
768 /* extract the ip, or see if there is an error */
769 if((temp = g_strstr_len(httpResponse, len,
770 "<NewExternalIPAddress")) == NULL) {
771 purple_debug_error("upnp",
772 "looked_up_public_ip_cb(): Failed Finding <NewExternalIPAddress\n");
773 return;
775 if(!(temp = g_strstr_len(temp, len - (temp - httpResponse), ">"))) {
776 purple_debug_error("upnp",
777 "looked_up_public_ip_cb(): Failed In Finding >\n");
778 return;
780 if(!(temp2 = g_strstr_len(temp, len - (temp - httpResponse), "<"))) {
781 purple_debug_error("upnp",
782 "looked_up_public_ip_cb(): Failed In Finding <\n");
783 return;
785 *temp2 = '\0';
787 strncpy(control_info.publicip, temp + 1,
788 sizeof(control_info.publicip));
790 purple_debug_info("upnp", "NAT Returned IP: %s\n", control_info.publicip);
793 static void
794 lookup_public_ip()
796 purple_upnp_generate_action_message_and_send("GetExternalIPAddress", "",
797 looked_up_public_ip_cb, NULL);
800 /* TODO: This could be exported */
801 static const gchar *
802 purple_upnp_get_internal_ip(void)
804 if (control_info.status == PURPLE_UPNP_STATUS_DISCOVERED
805 && control_info.internalip
806 && strlen(control_info.internalip) > 0)
807 return control_info.internalip;
809 /* Trigger another UPnP discovery if 5 minutes have elapsed since the
810 * last one, and it wasn't successful */
811 if (control_info.status < PURPLE_UPNP_STATUS_DISCOVERING
812 && (time(NULL) - control_info.lookup_time) > 300)
813 purple_upnp_discover(NULL, NULL);
815 return NULL;
818 static void
819 looked_up_internal_ip_cb(gpointer data, gint source, const gchar *error_message)
821 if (source != -1) {
822 strncpy(control_info.internalip,
823 purple_network_get_local_system_ip(source),
824 sizeof(control_info.internalip));
825 purple_debug_info("upnp", "Local IP: %s\n",
826 control_info.internalip);
827 close(source);
828 } else
829 purple_debug_error("upnp", "Unable to look up local IP\n");
833 static void
834 lookup_internal_ip()
836 gchar* addressOfControl;
837 int port = 0;
839 if(!purple_url_parse(control_info.control_url, &addressOfControl, &port,
840 NULL, NULL, NULL)) {
841 purple_debug_error("upnp",
842 "lookup_internal_ip(): Failed In Parse URL\n");
843 return;
845 if(port == 0 || port == -1) {
846 port = DEFAULT_HTTP_PORT;
849 if(purple_proxy_connect(NULL, NULL, addressOfControl, port,
850 looked_up_internal_ip_cb, NULL) == NULL)
852 purple_debug_error("upnp", "Get Local IP Connect Failed: Address: %s @@@ Port %d\n",
853 addressOfControl, port);
856 g_free(addressOfControl);
859 static void
860 done_port_mapping_cb(PurpleUtilFetchUrlData *url_data, gpointer user_data,
861 const gchar *httpResponse, gsize len, const gchar *error_message)
863 UPnPMappingAddRemove *ar = user_data;
865 gboolean success = TRUE;
867 /* determine if port mapping was a success */
868 if ((error_message != NULL) || (httpResponse == NULL) ||
869 (g_strstr_len(httpResponse, len, HTTP_OK) == NULL))
871 purple_debug_error("upnp",
872 "purple_upnp_set_port_mapping(): Failed HTTP_OK\n%s\n",
873 httpResponse ? httpResponse : "(null)");
874 success = FALSE;
875 } else
876 purple_debug_info("upnp", "Successfully completed port mapping operation\n");
878 ar->success = success;
879 ar->tima = purple_timeout_add(0, fire_ar_cb_async_and_free, ar);
882 static void
883 do_port_mapping_cb(gboolean has_control_mapping, gpointer data)
885 UPnPMappingAddRemove *ar = data;
887 if (has_control_mapping) {
888 gchar action_name[25];
889 gchar *action_params;
890 if(ar->add) {
891 const gchar *internal_ip;
892 /* get the internal IP */
893 if(!(internal_ip = purple_upnp_get_internal_ip())) {
894 purple_debug_error("upnp",
895 "purple_upnp_set_port_mapping(): couldn't get local ip\n");
896 ar->success = FALSE;
897 ar->tima = purple_timeout_add(0, fire_ar_cb_async_and_free, ar);
898 return;
900 strncpy(action_name, "AddPortMapping",
901 sizeof(action_name));
902 action_params = g_strdup_printf(
903 ADD_PORT_MAPPING_PARAMS,
904 ar->portmap, ar->protocol, ar->portmap,
905 internal_ip);
906 } else {
907 strncpy(action_name, "DeletePortMapping", sizeof(action_name));
908 action_params = g_strdup_printf(
909 DELETE_PORT_MAPPING_PARAMS,
910 ar->portmap, ar->protocol);
913 ar->gfud = purple_upnp_generate_action_message_and_send(action_name,
914 action_params, done_port_mapping_cb, ar);
916 g_free(action_params);
917 return;
920 ar->success = FALSE;
921 ar->tima = purple_timeout_add(0, fire_ar_cb_async_and_free, ar);
924 static gboolean
925 fire_port_mapping_failure_cb(gpointer data)
927 UPnPMappingAddRemove *ar = data;
929 ar->tima = 0;
930 do_port_mapping_cb(FALSE, data);
931 return FALSE;
934 void purple_upnp_cancel_port_mapping(UPnPMappingAddRemove *ar)
936 GSList *l;
938 /* Remove ar from discovery_callbacks if present; it was inserted after a cb.
939 * The same cb may be in the list multiple times, so be careful to remove
940 * the one associated with ar. */
941 l = discovery_callbacks;
942 while (l)
944 GSList *next = l->next;
946 if (next && (next->data == ar)) {
947 discovery_callbacks = g_slist_delete_link(discovery_callbacks, next);
948 next = l->next;
949 discovery_callbacks = g_slist_delete_link(discovery_callbacks, l);
952 l = next;
955 if (ar->tima > 0)
956 purple_timeout_remove(ar->tima);
958 if (ar->gfud)
959 purple_util_fetch_url_cancel(ar->gfud);
961 g_free(ar);
964 UPnPMappingAddRemove *
965 purple_upnp_set_port_mapping(unsigned short portmap, const gchar* protocol,
966 PurpleUPnPCallback cb, gpointer cb_data)
968 UPnPMappingAddRemove *ar;
970 ar = g_new0(UPnPMappingAddRemove, 1);
971 ar->cb = cb;
972 ar->cb_data = cb_data;
973 ar->add = TRUE;
974 ar->portmap = portmap;
975 strncpy(ar->protocol, protocol, sizeof(ar->protocol));
977 /* If we're waiting for a discovery, add to the callbacks list */
978 if(control_info.status == PURPLE_UPNP_STATUS_DISCOVERING) {
979 /* TODO: This will fail because when this cb is triggered,
980 * the internal IP lookup won't be complete */
981 discovery_callbacks = g_slist_append(
982 discovery_callbacks, do_port_mapping_cb);
983 discovery_callbacks = g_slist_append(
984 discovery_callbacks, ar);
985 return ar;
988 /* If we haven't had a successful UPnP discovery, check if 5 minutes has
989 * elapsed since the last try, try again */
990 if(control_info.status == PURPLE_UPNP_STATUS_UNDISCOVERED ||
991 (control_info.status == PURPLE_UPNP_STATUS_UNABLE_TO_DISCOVER
992 && (time(NULL) - control_info.lookup_time) > 300)) {
993 purple_upnp_discover(do_port_mapping_cb, ar);
994 return ar;
995 } else if(control_info.status == PURPLE_UPNP_STATUS_UNABLE_TO_DISCOVER) {
996 if (cb) {
997 /* Asynchronously trigger a failed response */
998 ar->tima = purple_timeout_add(10, fire_port_mapping_failure_cb, ar);
999 } else {
1000 /* No need to do anything if nobody expects a response*/
1001 g_free(ar);
1002 ar = NULL;
1004 return ar;
1007 do_port_mapping_cb(TRUE, ar);
1008 return ar;
1011 UPnPMappingAddRemove *
1012 purple_upnp_remove_port_mapping(unsigned short portmap, const char* protocol,
1013 PurpleUPnPCallback cb, gpointer cb_data)
1015 UPnPMappingAddRemove *ar;
1017 ar = g_new0(UPnPMappingAddRemove, 1);
1018 ar->cb = cb;
1019 ar->cb_data = cb_data;
1020 ar->add = FALSE;
1021 ar->portmap = portmap;
1022 strncpy(ar->protocol, protocol, sizeof(ar->protocol));
1024 /* If we're waiting for a discovery, add to the callbacks list */
1025 if(control_info.status == PURPLE_UPNP_STATUS_DISCOVERING) {
1026 discovery_callbacks = g_slist_append(
1027 discovery_callbacks, do_port_mapping_cb);
1028 discovery_callbacks = g_slist_append(
1029 discovery_callbacks, ar);
1030 return ar;
1033 /* If we haven't had a successful UPnP discovery, check if 5 minutes has
1034 * elapsed since the last try, try again */
1035 if(control_info.status == PURPLE_UPNP_STATUS_UNDISCOVERED ||
1036 (control_info.status == PURPLE_UPNP_STATUS_UNABLE_TO_DISCOVER
1037 && (time(NULL) - control_info.lookup_time) > 300)) {
1038 purple_upnp_discover(do_port_mapping_cb, ar);
1039 return ar;
1040 } else if(control_info.status == PURPLE_UPNP_STATUS_UNABLE_TO_DISCOVER) {
1041 if (cb) {
1042 /* Asynchronously trigger a failed response */
1043 ar->tima = purple_timeout_add(10, fire_port_mapping_failure_cb, ar);
1044 } else {
1045 /* No need to do anything if nobody expects a response*/
1046 g_free(ar);
1047 ar = NULL;
1049 return ar;
1052 do_port_mapping_cb(TRUE, ar);
1053 return ar;
1056 static void
1057 purple_upnp_network_config_changed_cb(void *data)
1059 /* Reset the control_info to default values */
1060 control_info.status = PURPLE_UPNP_STATUS_UNDISCOVERED;
1061 g_free(control_info.control_url);
1062 control_info.control_url = NULL;
1063 control_info.service_type[0] = '\0';
1064 control_info.publicip[0] = '\0';
1065 control_info.internalip[0] = '\0';
1066 control_info.lookup_time = 0;
1069 static void*
1070 purple_upnp_get_handle(void)
1072 static int handle;
1074 return &handle;
1077 void
1078 purple_upnp_init()
1080 purple_signal_connect(purple_network_get_handle(), "network-configuration-changed",
1081 purple_upnp_get_handle(), PURPLE_CALLBACK(purple_upnp_network_config_changed_cb),
1082 NULL);