Merged default into xdg-dirs
[pidgin-git.git] / libpurple / upnp.c
blobb9f08fa4f5a1b07d8a0dfc63a3381b473c662ecf
1 /* purple
3 * Purple is the legal property of its developers, whose names are too numerous
4 * to list here. Please refer to the COPYRIGHT file distributed with this
5 * source distribution.
7 * This program is free software; you can redistribute it and/or modify
8 * it under the terms of the GNU General Public License as published by
9 * the Free Software Foundation; either version 2 of the License, or
10 * (at your option) any later version.
12 * This program is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 * GNU General Public License for more details.
17 * You should have received a copy of the GNU General Public License
18 * along with this program; if not, write to the Free Software
19 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA
21 #include <gio/gio.h>
23 #include "internal.h"
25 #include "upnp.h"
27 #include "debug.h"
28 #include "eventloop.h"
29 #include "http.h"
30 #include "network.h"
31 #include "proxy.h"
32 #include "signals.h"
33 #include "util.h"
34 #include "xmlnode.h"
36 /***************************************************************
37 ** General Defines *
38 ****************************************************************/
39 #define HTTP_OK "200 OK"
40 #define DEFAULT_HTTP_PORT 80
41 #define DISCOVERY_TIMEOUT 1000
42 /* limit UPnP-triggered http downloads to 128k */
43 #define MAX_UPNP_DOWNLOAD (128 * 1024)
45 /***************************************************************
46 ** Discovery/Description Defines *
47 ****************************************************************/
48 #define NUM_UDP_ATTEMPTS 2
50 /* Address and port of an SSDP request used for discovery */
51 #define HTTPMU_HOST_ADDRESS "239.255.255.250"
52 #define HTTPMU_HOST_PORT 1900
54 #define SEARCH_REQUEST_DEVICE "urn:schemas-upnp-org:service:%s"
56 #define SEARCH_REQUEST_STRING \
57 "M-SEARCH * HTTP/1.1\r\n" \
58 "MX: 2\r\n" \
59 "HOST: 239.255.255.250:1900\r\n" \
60 "MAN: \"ssdp:discover\"\r\n" \
61 "ST: urn:schemas-upnp-org:service:%s\r\n" \
62 "\r\n"
64 #define WAN_IP_CONN_SERVICE "WANIPConnection:1"
65 #define WAN_PPP_CONN_SERVICE "WANPPPConnection:1"
67 /******************************************************************
68 ** Action Defines *
69 *******************************************************************/
70 #define SOAP_ACTION \
71 "<?xml version=\"1.0\" encoding=\"utf-8\"?>\r\n" \
72 "<s:Envelope xmlns:s=\"http://schemas.xmlsoap.org/soap/envelope/\" " \
73 "s:encodingStyle=\"http://schemas.xmlsoap.org/soap/encoding/\">\r\n" \
74 "<s:Body>\r\n" \
75 "<u:%s xmlns:u=\"urn:schemas-upnp-org:service:%s\">\r\n" \
76 "%s" \
77 "</u:%s>\r\n" \
78 "</s:Body>\r\n" \
79 "</s:Envelope>"
81 #define PORT_MAPPING_LEASE_TIME "0"
82 #define PORT_MAPPING_DESCRIPTION "PURPLE_UPNP_PORT_FORWARD"
84 #define ADD_PORT_MAPPING_PARAMS \
85 "<NewRemoteHost></NewRemoteHost>\r\n" \
86 "<NewExternalPort>%i</NewExternalPort>\r\n" \
87 "<NewProtocol>%s</NewProtocol>\r\n" \
88 "<NewInternalPort>%i</NewInternalPort>\r\n" \
89 "<NewInternalClient>%s</NewInternalClient>\r\n" \
90 "<NewEnabled>1</NewEnabled>\r\n" \
91 "<NewPortMappingDescription>" \
92 PORT_MAPPING_DESCRIPTION \
93 "</NewPortMappingDescription>\r\n" \
94 "<NewLeaseDuration>" \
95 PORT_MAPPING_LEASE_TIME \
96 "</NewLeaseDuration>\r\n"
98 #define DELETE_PORT_MAPPING_PARAMS \
99 "<NewRemoteHost></NewRemoteHost>\r\n" \
100 "<NewExternalPort>%i</NewExternalPort>\r\n" \
101 "<NewProtocol>%s</NewProtocol>\r\n"
103 typedef enum {
104 PURPLE_UPNP_STATUS_UNDISCOVERED = -1,
105 PURPLE_UPNP_STATUS_UNABLE_TO_DISCOVER,
106 PURPLE_UPNP_STATUS_DISCOVERING,
107 PURPLE_UPNP_STATUS_DISCOVERED
108 } PurpleUPnPStatus;
110 typedef struct {
111 PurpleUPnPStatus status;
112 gchar* control_url;
113 gchar service_type[20];
114 char publicip[16];
115 char internalip[16];
116 time_t lookup_time;
117 } PurpleUPnPControlInfo;
119 typedef struct {
120 guint inpa; /* purple_input_add handle */
121 guint tima; /* g_timeout_add handle */
122 int fd;
123 struct sockaddr_in server;
124 gchar service_type[20];
125 int retry_count;
126 gchar *full_url;
127 } UPnPDiscoveryData;
129 struct _PurpleUPnPMappingAddRemove
131 unsigned short portmap;
132 gchar protocol[4];
133 gboolean add;
134 PurpleUPnPCallback cb;
135 gpointer cb_data;
136 gboolean success;
137 guint tima; /* g_timeout_add handle */
138 PurpleHttpConnection *hc;
141 static PurpleUPnPControlInfo control_info = {
142 PURPLE_UPNP_STATUS_UNDISCOVERED,
143 NULL, "\0", "\0", "\0", 0};
145 static GSList *discovery_callbacks = NULL;
147 static void purple_upnp_discover_send_broadcast(UPnPDiscoveryData *dd);
148 static void lookup_public_ip(void);
149 static void lookup_internal_ip(void);
151 static gboolean
152 fire_ar_cb_async_and_free(gpointer data)
154 PurpleUPnPMappingAddRemove *ar = data;
155 if (ar) {
156 if (ar->cb)
157 ar->cb(ar->success, ar->cb_data);
158 g_free(ar);
161 return FALSE;
164 static void
165 fire_discovery_callbacks(gboolean success)
167 while(discovery_callbacks) {
168 gpointer data;
169 PurpleUPnPCallback cb = discovery_callbacks->data;
170 discovery_callbacks = g_slist_delete_link(discovery_callbacks, discovery_callbacks);
171 data = discovery_callbacks->data;
172 discovery_callbacks = g_slist_delete_link(discovery_callbacks, discovery_callbacks);
173 cb(success, data);
177 static gboolean
178 purple_upnp_compare_device(const PurpleXmlNode* device, const gchar* deviceType)
180 PurpleXmlNode* deviceTypeNode = purple_xmlnode_get_child(device, "deviceType");
181 char *tmp;
182 gboolean ret;
184 if(deviceTypeNode == NULL) {
185 return FALSE;
188 tmp = purple_xmlnode_get_data(deviceTypeNode);
189 ret = !g_ascii_strcasecmp(tmp, deviceType);
190 g_free(tmp);
192 return ret;
195 static gboolean
196 purple_upnp_compare_service(const PurpleXmlNode* service, const gchar* serviceType)
198 PurpleXmlNode* serviceTypeNode;
199 char *tmp;
200 gboolean ret;
202 if(service == NULL) {
203 return FALSE;
206 serviceTypeNode = purple_xmlnode_get_child(service, "serviceType");
208 if(serviceTypeNode == NULL) {
209 return FALSE;
212 tmp = purple_xmlnode_get_data(serviceTypeNode);
213 ret = !g_ascii_strcasecmp(tmp, serviceType);
214 g_free(tmp);
216 return ret;
219 static gchar*
220 purple_upnp_parse_description_response(const gchar* httpResponse, gsize len,
221 const gchar* httpURL, const gchar* serviceType)
223 gchar *baseURL, *controlURL, *service;
224 PurpleXmlNode *xmlRootNode, *serviceTypeNode, *controlURLNode, *baseURLNode;
225 char *tmp;
227 /* create the xml root node */
228 if ((xmlRootNode = purple_xmlnode_from_str(httpResponse, len)) == NULL) {
229 purple_debug_error("upnp",
230 "parse_description_response(): Could not parse xml root node\n");
231 return NULL;
234 /* get the baseURL of the device */
235 baseURL = NULL;
236 if((baseURLNode = purple_xmlnode_get_child(xmlRootNode, "URLBase")) != NULL) {
237 baseURL = purple_xmlnode_get_data(baseURLNode);
239 /* fixes upnp-descriptions with empty urlbase-element */
240 if(baseURL == NULL){
241 baseURL = g_strdup(httpURL);
244 /* get the serviceType child that has the service type as its data */
246 /* get urn:schemas-upnp-org:device:InternetGatewayDevice:1 and its devicelist */
247 serviceTypeNode = purple_xmlnode_get_child(xmlRootNode, "device");
248 while(!purple_upnp_compare_device(serviceTypeNode,
249 "urn:schemas-upnp-org:device:InternetGatewayDevice:1") &&
250 serviceTypeNode != NULL) {
251 serviceTypeNode = purple_xmlnode_get_next_twin(serviceTypeNode);
253 if(serviceTypeNode == NULL) {
254 purple_debug_error("upnp",
255 "parse_description_response(): could not get serviceTypeNode 1\n");
256 g_free(baseURL);
257 purple_xmlnode_free(xmlRootNode);
258 return NULL;
260 serviceTypeNode = purple_xmlnode_get_child(serviceTypeNode, "deviceList");
261 if(serviceTypeNode == NULL) {
262 purple_debug_error("upnp",
263 "parse_description_response(): could not get serviceTypeNode 2\n");
264 g_free(baseURL);
265 purple_xmlnode_free(xmlRootNode);
266 return NULL;
269 /* get urn:schemas-upnp-org:device:WANDevice:1 and its devicelist */
270 serviceTypeNode = purple_xmlnode_get_child(serviceTypeNode, "device");
271 while(!purple_upnp_compare_device(serviceTypeNode,
272 "urn:schemas-upnp-org:device:WANDevice:1") &&
273 serviceTypeNode != NULL) {
274 serviceTypeNode = purple_xmlnode_get_next_twin(serviceTypeNode);
276 if(serviceTypeNode == NULL) {
277 purple_debug_error("upnp",
278 "parse_description_response(): could not get serviceTypeNode 3\n");
279 g_free(baseURL);
280 purple_xmlnode_free(xmlRootNode);
281 return NULL;
283 serviceTypeNode = purple_xmlnode_get_child(serviceTypeNode, "deviceList");
284 if(serviceTypeNode == NULL) {
285 purple_debug_error("upnp",
286 "parse_description_response(): could not get serviceTypeNode 4\n");
287 g_free(baseURL);
288 purple_xmlnode_free(xmlRootNode);
289 return NULL;
292 /* get urn:schemas-upnp-org:device:WANConnectionDevice:1 and its servicelist */
293 serviceTypeNode = purple_xmlnode_get_child(serviceTypeNode, "device");
294 while(serviceTypeNode && !purple_upnp_compare_device(serviceTypeNode,
295 "urn:schemas-upnp-org:device:WANConnectionDevice:1")) {
296 serviceTypeNode = purple_xmlnode_get_next_twin(serviceTypeNode);
298 if(serviceTypeNode == NULL) {
299 purple_debug_error("upnp",
300 "parse_description_response(): could not get serviceTypeNode 5\n");
301 g_free(baseURL);
302 purple_xmlnode_free(xmlRootNode);
303 return NULL;
305 serviceTypeNode = purple_xmlnode_get_child(serviceTypeNode, "serviceList");
306 if(serviceTypeNode == NULL) {
307 purple_debug_error("upnp",
308 "parse_description_response(): could not get serviceTypeNode 6\n");
309 g_free(baseURL);
310 purple_xmlnode_free(xmlRootNode);
311 return NULL;
314 /* get the serviceType variable passed to this function */
315 service = g_strdup_printf(SEARCH_REQUEST_DEVICE, serviceType);
316 serviceTypeNode = purple_xmlnode_get_child(serviceTypeNode, "service");
317 while(!purple_upnp_compare_service(serviceTypeNode, service) &&
318 serviceTypeNode != NULL) {
319 serviceTypeNode = purple_xmlnode_get_next_twin(serviceTypeNode);
322 g_free(service);
323 if(serviceTypeNode == NULL) {
324 purple_debug_error("upnp",
325 "parse_description_response(): could not get serviceTypeNode 7\n");
326 g_free(baseURL);
327 purple_xmlnode_free(xmlRootNode);
328 return NULL;
331 /* get the controlURL of the service */
332 if((controlURLNode = purple_xmlnode_get_child(serviceTypeNode,
333 "controlURL")) == NULL) {
334 purple_debug_error("upnp",
335 "parse_description_response(): Could not find controlURL\n");
336 g_free(baseURL);
337 purple_xmlnode_free(xmlRootNode);
338 return NULL;
341 tmp = purple_xmlnode_get_data(controlURLNode);
342 if(baseURL && !purple_str_has_prefix(tmp, "http://") &&
343 !purple_str_has_prefix(tmp, "HTTP://")) {
344 /* Handle absolute paths in a relative URL. This probably
345 * belongs in util.c. */
346 if (tmp[0] == '/') {
347 size_t length;
348 const char *path, *start = strstr(baseURL, "://");
349 start = start ? start + 3 : baseURL;
350 path = strchr(start, '/');
351 length = path ? (gsize)(path - baseURL) : strlen(baseURL);
352 controlURL = g_strdup_printf("%.*s%s", (int)length, baseURL, tmp);
353 } else {
354 controlURL = g_strdup_printf("%s%s", baseURL, tmp);
356 g_free(tmp);
357 }else{
358 controlURL = tmp;
360 g_free(baseURL);
361 purple_xmlnode_free(xmlRootNode);
363 return controlURL;
366 static void
367 upnp_parse_description_cb(PurpleHttpConnection *http_conn,
368 PurpleHttpResponse *response, gpointer _dd)
370 UPnPDiscoveryData *dd = _dd;
371 gchar *control_url = NULL;
373 if (response && purple_http_response_is_successful(response)) {
374 const gchar *got_data;
375 size_t got_len;
377 got_data = purple_http_response_get_data(response, &got_len);
378 control_url = purple_upnp_parse_description_response(
379 got_data, got_len, dd->full_url, dd->service_type);
382 g_free(dd->full_url);
384 if(control_url == NULL) {
385 purple_debug_error("upnp",
386 "purple_upnp_parse_description(): control URL is NULL\n");
389 control_info.status = control_url ? PURPLE_UPNP_STATUS_DISCOVERED
390 : PURPLE_UPNP_STATUS_UNABLE_TO_DISCOVER;
391 control_info.lookup_time = time(NULL);
392 control_info.control_url = control_url;
393 g_strlcpy(control_info.service_type, dd->service_type,
394 sizeof(control_info.service_type));
396 fire_discovery_callbacks(control_url != NULL);
398 /* Look up the public and internal IPs */
399 if(control_url != NULL) {
400 lookup_public_ip();
401 lookup_internal_ip();
404 if (dd->inpa > 0)
405 purple_input_remove(dd->inpa);
406 if (dd->tima > 0)
407 g_source_remove(dd->tima);
409 g_free(dd);
412 static void
413 purple_upnp_parse_description(const gchar* descriptionURL, UPnPDiscoveryData *dd)
415 PurpleHttpRequest *req;
416 PurpleHttpURL *url;
418 /* Remove the timeout because everything it is waiting for has
419 * successfully completed */
420 g_source_remove(dd->tima);
421 dd->tima = 0;
423 /* Extract base url out of the descriptionURL.
424 * Example description URL: http://192.168.1.1:5678/rootDesc.xml
426 url = purple_http_url_parse(descriptionURL);
427 if (!url) {
428 upnp_parse_description_cb(NULL, NULL, dd);
429 return;
431 dd->full_url = g_strdup_printf("http://%s:%d",
432 purple_http_url_get_host(url),
433 purple_http_url_get_port(url));
434 purple_http_url_free(url);
436 req = purple_http_request_new(descriptionURL);
437 purple_http_request_set_max_len(req, MAX_UPNP_DOWNLOAD);
438 purple_http_request(NULL, req, upnp_parse_description_cb, dd);
439 purple_http_request_unref(req);
442 static void
443 purple_upnp_parse_discover_response(const gchar* buf, unsigned int buf_len,
444 UPnPDiscoveryData *dd)
446 gchar* startDescURL;
447 gchar* endDescURL;
448 gchar* descURL;
450 if(g_strstr_len(buf, buf_len, HTTP_OK) == NULL) {
451 purple_debug_error("upnp",
452 "parse_discover_response(): Failed In HTTP_OK\n");
453 return;
456 if((startDescURL = g_strstr_len(buf, buf_len, "http://")) == NULL) {
457 purple_debug_error("upnp",
458 "parse_discover_response(): Failed In finding http://\n");
459 return;
462 endDescURL = g_strstr_len(startDescURL, buf_len - (startDescURL - buf),
463 "\r");
464 if(endDescURL == NULL) {
465 endDescURL = g_strstr_len(startDescURL,
466 buf_len - (startDescURL - buf), "\n");
467 if(endDescURL == NULL) {
468 purple_debug_error("upnp",
469 "parse_discover_response(): Failed In endDescURL\n");
470 return;
474 /* XXX: I'm not sure how this could ever happen */
475 if(endDescURL == startDescURL) {
476 purple_debug_error("upnp",
477 "parse_discover_response(): endDescURL == startDescURL\n");
478 return;
481 descURL = g_strndup(startDescURL, endDescURL - startDescURL);
483 purple_upnp_parse_description(descURL, dd);
485 g_free(descURL);
489 static gboolean
490 purple_upnp_discover_timeout(gpointer data)
492 UPnPDiscoveryData* dd = data;
494 if (dd->inpa)
495 purple_input_remove(dd->inpa);
496 if (dd->tima > 0)
497 g_source_remove(dd->tima);
498 dd->inpa = 0;
499 dd->tima = 0;
501 if (dd->retry_count < NUM_UDP_ATTEMPTS) {
502 /* TODO: We probably shouldn't be incrementing retry_count in two places */
503 dd->retry_count++;
504 purple_upnp_discover_send_broadcast(dd);
505 } else {
506 if (dd->fd != -1)
507 close(dd->fd);
509 control_info.status = PURPLE_UPNP_STATUS_UNABLE_TO_DISCOVER;
510 control_info.lookup_time = time(NULL);
511 control_info.service_type[0] = '\0';
512 g_free(control_info.control_url);
513 control_info.control_url = NULL;
515 fire_discovery_callbacks(FALSE);
517 g_free(dd);
520 return FALSE;
523 static void
524 purple_upnp_discover_udp_read(gpointer data, gint sock, PurpleInputCondition cond)
526 int len;
527 UPnPDiscoveryData *dd = data;
528 gchar buf[65536];
530 do {
531 len = recv(dd->fd, buf,
532 sizeof(buf) - 1, 0);
534 if(len >= 0) {
535 buf[len] = '\0';
536 break;
537 } else if(errno != EINTR) {
538 /* We'll either get called again, or time out */
539 return;
541 } while (errno == EINTR);
543 purple_input_remove(dd->inpa);
544 dd->inpa = 0;
546 close(dd->fd);
547 dd->fd = -1;
549 /* parse the response, and see if it was a success */
550 purple_upnp_parse_discover_response(buf, len, dd);
552 /* We'll either time out or continue successfully */
555 static void
556 purple_upnp_discover_send_broadcast(UPnPDiscoveryData *dd)
558 gchar *sendMessage = NULL;
559 size_t totalSize;
560 gboolean sentSuccess;
562 /* because we are sending over UDP, if there is a failure
563 we should retry the send NUM_UDP_ATTEMPTS times. Also,
564 try different requests for WANIPConnection and WANPPPConnection*/
565 for(; dd->retry_count < NUM_UDP_ATTEMPTS; dd->retry_count++) {
566 sentSuccess = FALSE;
568 if((dd->retry_count % 2) == 0) {
569 g_strlcpy(dd->service_type, WAN_IP_CONN_SERVICE, sizeof(dd->service_type));
570 } else {
571 g_strlcpy(dd->service_type, WAN_PPP_CONN_SERVICE, sizeof(dd->service_type));
574 sendMessage = g_strdup_printf(SEARCH_REQUEST_STRING, dd->service_type);
576 totalSize = strlen(sendMessage);
578 do {
579 gssize sent = sendto(dd->fd, sendMessage, totalSize, 0,
580 (struct sockaddr*) &(dd->server),
581 sizeof(struct sockaddr_in));
582 if (sent >= 0 && (gsize)sent == totalSize) {
583 sentSuccess = TRUE;
584 break;
586 } while (errno == EINTR || errno == EAGAIN);
588 g_free(sendMessage);
590 if(sentSuccess) {
591 dd->tima = g_timeout_add(DISCOVERY_TIMEOUT,
592 purple_upnp_discover_timeout, dd);
593 dd->inpa = purple_input_add(dd->fd, PURPLE_INPUT_READ,
594 purple_upnp_discover_udp_read, dd);
596 return;
600 /* We have already done all our retries. Make sure that the callback
601 * doesn't get called before the original function returns */
602 dd->tima = g_timeout_add(10, purple_upnp_discover_timeout, dd);
605 void
606 purple_upnp_discover(PurpleUPnPCallback cb, gpointer cb_data)
608 /* Socket Setup Variables */
609 int sock;
610 struct hostent* hp;
612 /* UDP RECEIVE VARIABLES */
613 UPnPDiscoveryData *dd;
615 if (control_info.status == PURPLE_UPNP_STATUS_DISCOVERING) {
616 if (cb) {
617 discovery_callbacks = g_slist_append(
618 discovery_callbacks, cb);
619 discovery_callbacks = g_slist_append(
620 discovery_callbacks, cb_data);
622 return;
625 dd = g_new0(UPnPDiscoveryData, 1);
626 if (cb) {
627 discovery_callbacks = g_slist_append(discovery_callbacks, cb);
628 discovery_callbacks = g_slist_append(discovery_callbacks,
629 cb_data);
632 /* Set up the sockets */
633 dd->fd = sock = socket(AF_INET, SOCK_DGRAM, 0);
634 if(sock == -1) {
635 purple_debug_error("upnp",
636 "purple_upnp_discover(): Failed In sock creation\n");
637 /* Short circuit the retry attempts */
638 dd->retry_count = NUM_UDP_ATTEMPTS;
639 dd->tima = g_timeout_add(10, purple_upnp_discover_timeout, dd);
640 return;
643 /* TODO: Non-blocking! */
644 if((hp = gethostbyname(HTTPMU_HOST_ADDRESS)) == NULL) {
645 purple_debug_error("upnp",
646 "purple_upnp_discover(): Failed In gethostbyname\n");
647 /* Short circuit the retry attempts */
648 dd->retry_count = NUM_UDP_ATTEMPTS;
649 dd->tima = g_timeout_add(10, purple_upnp_discover_timeout, dd);
650 return;
653 memset(&(dd->server), 0, sizeof(struct sockaddr));
654 dd->server.sin_family = AF_INET;
655 memcpy(&(dd->server.sin_addr), hp->h_addr_list[0], hp->h_length);
656 dd->server.sin_port = htons(HTTPMU_HOST_PORT);
658 control_info.status = PURPLE_UPNP_STATUS_DISCOVERING;
660 purple_upnp_discover_send_broadcast(dd);
663 static PurpleHttpConnection*
664 purple_upnp_generate_action_message_and_send(const gchar* actionName,
665 const gchar* actionParams, PurpleHttpCallback cb,
666 gpointer cb_data)
668 PurpleHttpConnection *hc;
669 PurpleHttpRequest *req;
670 gchar* soapMessage;
672 /* set the soap message */
673 soapMessage = g_strdup_printf(SOAP_ACTION, actionName,
674 control_info.service_type, actionParams, actionName);
676 req = purple_http_request_new(control_info.control_url);
677 purple_http_request_set_max_len(req, MAX_UPNP_DOWNLOAD);
678 purple_http_request_set_method(req, "POST");
679 purple_http_request_header_set_printf(req, "SOAPAction",
680 "\"urn:schemas-upnp-org:service:%s#%s\"",
681 control_info.service_type, actionName);
682 purple_http_request_header_set(req, "Content-Type",
683 "text/xml; charset=utf-8");
684 purple_http_request_set_contents(req, soapMessage, -1);
685 hc = purple_http_request(NULL, req, cb, cb_data);
686 purple_http_request_unref(req);
688 g_free(soapMessage);
690 return hc;
693 const gchar *
694 purple_upnp_get_public_ip()
696 if (control_info.status == PURPLE_UPNP_STATUS_DISCOVERED
697 && *control_info.publicip)
698 return control_info.publicip;
700 /* Trigger another UPnP discovery if 5 minutes have elapsed since the
701 * last one, and it wasn't successful */
702 if (control_info.status < PURPLE_UPNP_STATUS_DISCOVERING
703 && (time(NULL) - control_info.lookup_time) > 300)
704 purple_upnp_discover(NULL, NULL);
706 return NULL;
709 static void
710 looked_up_public_ip_cb(PurpleHttpConnection *http_conn,
711 PurpleHttpResponse *response, gpointer user_data)
713 gchar* temp, *temp2;
714 const gchar *got_data;
715 size_t got_len;
717 if (!purple_http_response_is_successful(response))
718 return;
720 /* extract the ip, or see if there is an error */
721 got_data = purple_http_response_get_data(response, &got_len);
722 if((temp = g_strstr_len(got_data, got_len,
723 "<NewExternalIPAddress")) == NULL) {
724 purple_debug_error("upnp",
725 "looked_up_public_ip_cb(): Failed Finding <NewExternalIPAddress\n");
726 return;
728 if(!(temp = g_strstr_len(temp, got_len - (temp - got_data), ">"))) {
729 purple_debug_error("upnp",
730 "looked_up_public_ip_cb(): Failed In Finding >\n");
731 return;
733 if(!(temp2 = g_strstr_len(temp, got_len - (temp - got_data), "<"))) {
734 purple_debug_error("upnp",
735 "looked_up_public_ip_cb(): Failed In Finding <\n");
736 return;
738 *temp2 = '\0';
740 g_strlcpy(control_info.publicip, temp + 1,
741 sizeof(control_info.publicip));
743 purple_debug_info("upnp", "NAT Returned IP: %s\n", control_info.publicip);
746 static void
747 lookup_public_ip()
749 purple_upnp_generate_action_message_and_send("GetExternalIPAddress", "",
750 looked_up_public_ip_cb, NULL);
753 /* TODO: This could be exported */
754 static const gchar *
755 purple_upnp_get_internal_ip(void)
757 if (control_info.status == PURPLE_UPNP_STATUS_DISCOVERED
758 && *control_info.internalip)
759 return control_info.internalip;
761 /* Trigger another UPnP discovery if 5 minutes have elapsed since the
762 * last one, and it wasn't successful */
763 if (control_info.status < PURPLE_UPNP_STATUS_DISCOVERING
764 && (time(NULL) - control_info.lookup_time) > 300)
765 purple_upnp_discover(NULL, NULL);
767 return NULL;
770 static void
771 looked_up_internal_ip_cb(gpointer data, gint source, const gchar *error_message)
773 if (source != -1) {
774 g_strlcpy(control_info.internalip,
775 purple_network_get_local_system_ip(source),
776 sizeof(control_info.internalip));
777 purple_debug_info("upnp", "Local IP: %s\n",
778 control_info.internalip);
779 close(source);
780 } else
781 purple_debug_error("upnp", "Unable to look up local IP\n");
785 static void
786 lookup_internal_ip()
788 PurpleHttpURL *url;
790 url = purple_http_url_parse(control_info.control_url);
791 if (!url) {
792 purple_debug_error("upnp",
793 "lookup_internal_ip(): Failed In Parse URL\n");
794 return;
797 if(purple_proxy_connect(NULL, NULL, purple_http_url_get_host(url),
798 purple_http_url_get_port(url), looked_up_internal_ip_cb,
799 NULL) == NULL)
801 purple_debug_error("upnp", "Get Local IP Connect Failed: "
802 "Address: %s @@@ Port %d\n",
803 purple_http_url_get_host(url),
804 purple_http_url_get_port(url));
807 purple_http_url_free(url);
810 static void
811 done_port_mapping_cb(PurpleHttpConnection *http_conn,
812 PurpleHttpResponse *response, gpointer user_data)
814 PurpleUPnPMappingAddRemove *ar = user_data;
816 gboolean success = TRUE;
818 /* determine if port mapping was a success */
819 if (!purple_http_response_is_successful(response))
821 purple_debug_error("upnp",
822 "purple_upnp_set_port_mapping(): Failed HTTP_OK\n%s\n",
823 purple_http_response_get_error(response));
824 success = FALSE;
825 } else
826 purple_debug_info("upnp", "Successfully completed port mapping operation\n");
828 ar->success = success;
829 ar->tima = g_timeout_add(0, fire_ar_cb_async_and_free, ar);
832 static void
833 do_port_mapping_cb(gboolean has_control_mapping, gpointer data)
835 PurpleUPnPMappingAddRemove *ar = data;
837 if (has_control_mapping) {
838 gchar action_name[25];
839 gchar *action_params;
840 if(ar->add) {
841 const gchar *internal_ip;
842 /* get the internal IP */
843 if(!(internal_ip = purple_upnp_get_internal_ip())) {
844 purple_debug_error("upnp",
845 "purple_upnp_set_port_mapping(): couldn't get local ip\n");
846 ar->success = FALSE;
847 ar->tima = g_timeout_add(0, fire_ar_cb_async_and_free, ar);
848 return;
850 strncpy(action_name, "AddPortMapping",
851 sizeof(action_name));
852 action_params = g_strdup_printf(
853 ADD_PORT_MAPPING_PARAMS,
854 ar->portmap, ar->protocol, ar->portmap,
855 internal_ip);
856 } else {
857 strncpy(action_name, "DeletePortMapping", sizeof(action_name));
858 action_params = g_strdup_printf(
859 DELETE_PORT_MAPPING_PARAMS,
860 ar->portmap, ar->protocol);
863 ar->hc = purple_upnp_generate_action_message_and_send(
864 action_name, action_params, done_port_mapping_cb, ar);
866 g_free(action_params);
867 return;
870 ar->success = FALSE;
871 ar->tima = g_timeout_add(0, fire_ar_cb_async_and_free, ar);
874 static gboolean
875 fire_port_mapping_failure_cb(gpointer data)
877 PurpleUPnPMappingAddRemove *ar = data;
879 ar->tima = 0;
880 do_port_mapping_cb(FALSE, data);
881 return FALSE;
884 void purple_upnp_cancel_port_mapping(PurpleUPnPMappingAddRemove *ar)
886 GSList *l;
888 /* Remove ar from discovery_callbacks if present; it was inserted after a cb.
889 * The same cb may be in the list multiple times, so be careful to remove
890 * the one associated with ar. */
891 l = discovery_callbacks;
892 while (l)
894 GSList *next = l->next;
896 if (next && (next->data == ar)) {
897 discovery_callbacks = g_slist_delete_link(discovery_callbacks, next);
898 next = l->next;
899 discovery_callbacks = g_slist_delete_link(discovery_callbacks, l);
902 l = next;
905 if (ar->tima > 0)
906 g_source_remove(ar->tima);
908 purple_http_conn_cancel(ar->hc);
910 g_free(ar);
913 PurpleUPnPMappingAddRemove *
914 purple_upnp_set_port_mapping(unsigned short portmap, const gchar* protocol,
915 PurpleUPnPCallback cb, gpointer cb_data)
917 PurpleUPnPMappingAddRemove *ar;
919 ar = g_new0(PurpleUPnPMappingAddRemove, 1);
920 ar->cb = cb;
921 ar->cb_data = cb_data;
922 ar->add = TRUE;
923 ar->portmap = portmap;
924 g_strlcpy(ar->protocol, protocol, sizeof(ar->protocol));
926 /* If we're waiting for a discovery, add to the callbacks list */
927 if(control_info.status == PURPLE_UPNP_STATUS_DISCOVERING) {
928 /* TODO: This will fail because when this cb is triggered,
929 * the internal IP lookup won't be complete */
930 discovery_callbacks = g_slist_append(
931 discovery_callbacks, do_port_mapping_cb);
932 discovery_callbacks = g_slist_append(
933 discovery_callbacks, ar);
934 return ar;
937 /* If we haven't had a successful UPnP discovery, check if 5 minutes has
938 * elapsed since the last try, try again */
939 if(control_info.status == PURPLE_UPNP_STATUS_UNDISCOVERED ||
940 (control_info.status == PURPLE_UPNP_STATUS_UNABLE_TO_DISCOVER
941 && (time(NULL) - control_info.lookup_time) > 300)) {
942 purple_upnp_discover(do_port_mapping_cb, ar);
943 return ar;
944 } else if(control_info.status == PURPLE_UPNP_STATUS_UNABLE_TO_DISCOVER) {
945 if (cb) {
946 /* Asynchronously trigger a failed response */
947 ar->tima = g_timeout_add(10, fire_port_mapping_failure_cb, ar);
948 } else {
949 /* No need to do anything if nobody expects a response*/
950 g_free(ar);
951 ar = NULL;
953 return ar;
956 do_port_mapping_cb(TRUE, ar);
957 return ar;
960 PurpleUPnPMappingAddRemove *
961 purple_upnp_remove_port_mapping(unsigned short portmap, const char* protocol,
962 PurpleUPnPCallback cb, gpointer cb_data)
964 PurpleUPnPMappingAddRemove *ar;
966 ar = g_new0(PurpleUPnPMappingAddRemove, 1);
967 ar->cb = cb;
968 ar->cb_data = cb_data;
969 ar->add = FALSE;
970 ar->portmap = portmap;
971 g_strlcpy(ar->protocol, protocol, sizeof(ar->protocol));
973 /* If we're waiting for a discovery, add to the callbacks list */
974 if(control_info.status == PURPLE_UPNP_STATUS_DISCOVERING) {
975 discovery_callbacks = g_slist_append(
976 discovery_callbacks, do_port_mapping_cb);
977 discovery_callbacks = g_slist_append(
978 discovery_callbacks, ar);
979 return ar;
982 /* If we haven't had a successful UPnP discovery, check if 5 minutes has
983 * elapsed since the last try, try again */
984 if(control_info.status == PURPLE_UPNP_STATUS_UNDISCOVERED ||
985 (control_info.status == PURPLE_UPNP_STATUS_UNABLE_TO_DISCOVER
986 && (time(NULL) - control_info.lookup_time) > 300)) {
987 purple_upnp_discover(do_port_mapping_cb, ar);
988 return ar;
989 } else if(control_info.status == PURPLE_UPNP_STATUS_UNABLE_TO_DISCOVER) {
990 if (cb) {
991 /* Asynchronously trigger a failed response */
992 ar->tima = g_timeout_add(10, fire_port_mapping_failure_cb, ar);
993 } else {
994 /* No need to do anything if nobody expects a response*/
995 g_free(ar);
996 ar = NULL;
998 return ar;
1001 do_port_mapping_cb(TRUE, ar);
1002 return ar;
1005 static void
1006 purple_upnp_network_config_changed_cb(GNetworkMonitor *monitor, gboolean available, gpointer data)
1008 /* Reset the control_info to default values */
1009 control_info.status = PURPLE_UPNP_STATUS_UNDISCOVERED;
1010 g_free(control_info.control_url);
1011 control_info.control_url = NULL;
1012 control_info.service_type[0] = '\0';
1013 control_info.publicip[0] = '\0';
1014 control_info.internalip[0] = '\0';
1015 control_info.lookup_time = 0;
1018 void
1019 purple_upnp_init()
1021 g_signal_connect(g_network_monitor_get_default(),
1022 "network-changed",
1023 G_CALLBACK(purple_upnp_network_config_changed_cb),
1024 NULL);