2 * @file upnp.c UPnP Implementation
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
31 #include "eventloop.h"
38 /***************************************************************
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" \
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" \
66 #define WAN_IP_CONN_SERVICE "WANIPConnection:1"
67 #define WAN_PPP_CONN_SERVICE "WANPPPConnection:1"
69 /******************************************************************
71 *******************************************************************/
72 #define HTTP_HEADER_ACTION \
73 "POST /%s HTTP/1.1\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"
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" \
84 "<u:%s xmlns:u=\"urn:schemas-upnp-org:service:%s\">\r\n" \
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"
113 PURPLE_UPNP_STATUS_UNDISCOVERED
= -1,
114 PURPLE_UPNP_STATUS_UNABLE_TO_DISCOVER
,
115 PURPLE_UPNP_STATUS_DISCOVERING
,
116 PURPLE_UPNP_STATUS_DISCOVERED
120 PurpleUPnPStatus status
;
122 gchar service_type
[20];
126 } PurpleUPnPControlInfo
;
129 guint inpa
; /* purple_input_add handle */
130 guint tima
; /* purple_timeout_add handle */
132 struct sockaddr_in server
;
133 gchar service_type
[25];
138 struct _UPnPMappingAddRemove
140 unsigned short portmap
;
143 PurpleUPnPCallback cb
;
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);
160 fire_discovery_callbacks(gboolean success
)
162 while(discovery_callbacks
) {
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
);
173 purple_upnp_compare_device(const xmlnode
* device
, const gchar
* deviceType
)
175 xmlnode
* deviceTypeNode
= xmlnode_get_child(device
, "deviceType");
179 if(deviceTypeNode
== NULL
) {
183 tmp
= xmlnode_get_data(deviceTypeNode
);
184 ret
= !g_ascii_strcasecmp(tmp
, deviceType
);
191 purple_upnp_compare_service(const xmlnode
* service
, const gchar
* serviceType
)
193 xmlnode
* serviceTypeNode
;
197 if(service
== NULL
) {
201 serviceTypeNode
= xmlnode_get_child(service
, "serviceType");
203 if(serviceTypeNode
== NULL
) {
207 tmp
= xmlnode_get_data(serviceTypeNode
);
208 ret
= !g_ascii_strcasecmp(tmp
, serviceType
);
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
;
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");
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");
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");
244 /* get the baseURL of the device */
245 if((baseURLNode
= xmlnode_get_child(xmlRootNode
, "URLBase")) != NULL
) {
246 baseURL
= xmlnode_get_data(baseURLNode
);
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");
264 xmlnode_free(xmlRootNode
);
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");
272 xmlnode_free(xmlRootNode
);
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");
287 xmlnode_free(xmlRootNode
);
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");
295 xmlnode_free(xmlRootNode
);
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");
309 xmlnode_free(xmlRootNode
);
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");
317 xmlnode_free(xmlRootNode
);
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
);
330 if(serviceTypeNode
== NULL
) {
331 purple_debug_error("upnp",
332 "parse_description_response(): could not get serviceTypeNode 7\n");
334 xmlnode_free(xmlRootNode
);
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");
344 xmlnode_free(xmlRootNode
);
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. */
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
);
361 controlURL
= g_strdup_printf("%s%s", baseURL
, tmp
);
368 xmlnode_free(xmlRootNode
);
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
;
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
) {
403 lookup_internal_ip();
410 purple_upnp_parse_description(const gchar
* descriptionURL
, UPnPDiscoveryData
*dd
)
413 gchar
* descriptionXMLAddress
;
414 gchar
* descriptionAddress
;
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
)) {
425 if(port
== 0 || port
== -1) {
426 port
= DEFAULT_HTTP_PORT
;
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
);
448 purple_util_fetch_url_request_len(descriptionURL
, TRUE
, NULL
, TRUE
, httpRequest
,
449 TRUE
, MAX_UPNP_DOWNLOAD
, upnp_parse_description_cb
, dd
);
456 purple_upnp_parse_discover_response(const gchar
* buf
, unsigned int buf_len
,
457 UPnPDiscoveryData
*dd
)
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");
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");
475 endDescURL
= g_strstr_len(startDescURL
, buf_len
- (startDescURL
- buf
),
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");
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");
494 descURL
= g_strndup(startDescURL
, endDescURL
- startDescURL
);
496 purple_upnp_parse_description(descURL
, dd
);
503 purple_upnp_discover_timeout(gpointer data
)
505 UPnPDiscoveryData
* dd
= data
;
508 purple_input_remove(dd
->inpa
);
512 if (dd
->retry_count
< NUM_UDP_ATTEMPTS
) {
513 /* TODO: We probably shouldn't be incrementing retry_count in two places */
515 purple_upnp_discover_send_broadcast(dd
);
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
);
535 purple_upnp_discover_udp_read(gpointer data
, gint sock
, PurpleInputCondition cond
)
538 UPnPDiscoveryData
*dd
= data
;
542 len
= recv(dd
->fd
, buf
,
548 } else if(errno
!= EINTR
) {
549 /* We'll either get called again, or time out */
552 } while (errno
== EINTR
);
554 purple_input_remove(dd
->inpa
);
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 */
567 purple_upnp_discover_send_broadcast(UPnPDiscoveryData
*dd
)
569 gchar
*sendMessage
= NULL
;
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
++) {
579 if((dd
->retry_count
% 2) == 0) {
580 strncpy(dd
->service_type
, WAN_IP_CONN_SERVICE
, sizeof(dd
->service_type
));
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
);
590 if(sendto(dd
->fd
, sendMessage
, totalSize
, 0,
591 (struct sockaddr
*) &(dd
->server
),
592 sizeof(struct sockaddr_in
)
597 } while (errno
== EINTR
|| errno
== EAGAIN
);
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
);
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
);
617 purple_upnp_discover(PurpleUPnPCallback cb
, gpointer cb_data
)
619 /* Socket Setup Variables */
623 /* UDP RECEIVE VARIABLES */
624 UPnPDiscoveryData
*dd
;
626 if (control_info
.status
== PURPLE_UPNP_STATUS_DISCOVERING
) {
628 discovery_callbacks
= g_slist_append(
629 discovery_callbacks
, cb
);
630 discovery_callbacks
= g_slist_append(
631 discovery_callbacks
, cb_data
);
636 dd
= g_new0(UPnPDiscoveryData
, 1);
638 discovery_callbacks
= g_slist_append(discovery_callbacks
, cb
);
639 discovery_callbacks
= g_slist_append(discovery_callbacks
,
643 /* Set up the sockets */
644 sock
= socket(AF_INET
, SOCK_DGRAM
, 0);
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
);
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
);
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
,
681 PurpleUtilFetchUrlData
* gfud
;
683 gchar
* totalSendMessage
;
684 gchar
* pathOfControl
;
685 gchar
* addressOfControl
;
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 */
695 cb(NULL
, cb_data
, NULL
, 0, 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
);
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
);
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
);
741 looked_up_public_ip_cb(PurpleUtilFetchUrlData
*url_data
, gpointer user_data
,
742 const gchar
*httpResponse
, gsize len
, const gchar
*error_message
)
746 if ((error_message
!= NULL
) || (httpResponse
== NULL
))
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");
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");
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");
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
);
777 purple_upnp_generate_action_message_and_send("GetExternalIPAddress", "",
778 looked_up_public_ip_cb
, NULL
);
781 /* TODO: This could be exported */
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
);
800 looked_up_internal_ip_cb(gpointer data
, gint source
, const gchar
*error_message
)
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
);
810 purple_debug_info("upnp", "Unable to look up local IP\n");
817 gchar
* addressOfControl
;
820 if(!purple_url_parse(control_info
.control_url
, &addressOfControl
, &port
,
822 purple_debug_error("upnp",
823 "lookup_internal_ip(): Failed In Parse URL\n");
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
);
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)");
857 purple_debug_info("upnp", "Successfully completed port mapping operation\n");
860 ar
->cb(success
, ar
->cb_data
);
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
;
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");
880 ar
->cb(FALSE
, ar
->cb_data
);
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
,
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
);
906 ar
->cb(FALSE
, ar
->cb_data
);
911 fire_port_mapping_failure_cb(gpointer data
)
913 do_port_mapping_cb(FALSE
, data
);
917 void purple_upnp_cancel_port_mapping(UPnPMappingAddRemove
*ar
)
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
;
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
);
935 purple_timeout_remove(ar
->tima
);
938 purple_util_fetch_url_cancel(ar
->gfud
);
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);
951 ar
->cb_data
= cb_data
;
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
);
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
);
974 } else if(control_info
.status
== PURPLE_UPNP_STATUS_UNABLE_TO_DISCOVER
) {
976 /* Asynchronously trigger a failed response */
977 ar
->tima
= purple_timeout_add(10, fire_port_mapping_failure_cb
, ar
);
979 /* No need to do anything if nobody expects a response*/
986 do_port_mapping_cb(TRUE
, 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);
998 ar
->cb_data
= cb_data
;
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
);
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
);
1019 } else if(control_info
.status
== PURPLE_UPNP_STATUS_UNABLE_TO_DISCOVER
) {
1021 /* Asynchronously trigger a failed response */
1022 ar
->tima
= purple_timeout_add(10, fire_port_mapping_failure_cb
, ar
);
1024 /* No need to do anything if nobody expects a response*/
1031 do_port_mapping_cb(TRUE
, ar
);
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;
1049 purple_upnp_get_handle(void)
1059 purple_signal_connect(purple_network_get_handle(), "network-configuration-changed",
1060 purple_upnp_get_handle(), PURPLE_CALLBACK(purple_upnp_network_config_changed_cb
),