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
[20];
138 struct _UPnPMappingAddRemove
140 unsigned short portmap
;
143 PurpleUPnPCallback cb
;
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);
161 fire_ar_cb_async_and_free(gpointer data
)
163 UPnPMappingAddRemove
*ar
= data
;
166 ar
->cb(ar
->success
, ar
->cb_data
);
174 fire_discovery_callbacks(gboolean success
)
176 while(discovery_callbacks
) {
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
);
187 purple_upnp_compare_device(const xmlnode
* device
, const gchar
* deviceType
)
189 xmlnode
* deviceTypeNode
= xmlnode_get_child(device
, "deviceType");
193 if(deviceTypeNode
== NULL
) {
197 tmp
= xmlnode_get_data(deviceTypeNode
);
198 ret
= !g_ascii_strcasecmp(tmp
, deviceType
);
205 purple_upnp_compare_service(const xmlnode
* service
, const gchar
* serviceType
)
207 xmlnode
* serviceTypeNode
;
211 if(service
== NULL
) {
215 serviceTypeNode
= xmlnode_get_child(service
, "serviceType");
217 if(serviceTypeNode
== NULL
) {
221 tmp
= xmlnode_get_data(serviceTypeNode
);
222 ret
= !g_ascii_strcasecmp(tmp
, serviceType
);
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
;
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");
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");
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");
258 /* get the baseURL of the device */
259 if((baseURLNode
= xmlnode_get_child(xmlRootNode
, "URLBase")) != NULL
) {
260 baseURL
= xmlnode_get_data(baseURLNode
);
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");
278 xmlnode_free(xmlRootNode
);
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");
286 xmlnode_free(xmlRootNode
);
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");
301 xmlnode_free(xmlRootNode
);
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");
309 xmlnode_free(xmlRootNode
);
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");
323 xmlnode_free(xmlRootNode
);
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");
331 xmlnode_free(xmlRootNode
);
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
);
344 if(serviceTypeNode
== NULL
) {
345 purple_debug_error("upnp",
346 "parse_description_response(): could not get serviceTypeNode 7\n");
348 xmlnode_free(xmlRootNode
);
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");
358 xmlnode_free(xmlRootNode
);
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. */
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
);
375 controlURL
= g_strdup_printf("%s%s", baseURL
, tmp
);
382 xmlnode_free(xmlRootNode
);
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
;
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
) {
417 lookup_internal_ip();
421 purple_input_remove(dd
->inpa
);
423 purple_timeout_remove(dd
->tima
);
429 purple_upnp_parse_description(const gchar
* descriptionURL
, UPnPDiscoveryData
*dd
)
432 gchar
* descriptionXMLAddress
;
433 gchar
* descriptionAddress
;
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
)) {
444 if(port
== 0 || port
== -1) {
445 port
= DEFAULT_HTTP_PORT
;
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
);
467 purple_util_fetch_url_request_len(descriptionURL
, TRUE
, NULL
, TRUE
, httpRequest
,
468 TRUE
, MAX_UPNP_DOWNLOAD
, upnp_parse_description_cb
, dd
);
475 purple_upnp_parse_discover_response(const gchar
* buf
, unsigned int buf_len
,
476 UPnPDiscoveryData
*dd
)
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");
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");
494 endDescURL
= g_strstr_len(startDescURL
, buf_len
- (startDescURL
- buf
),
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");
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");
513 descURL
= g_strndup(startDescURL
, endDescURL
- startDescURL
);
515 purple_upnp_parse_description(descURL
, dd
);
522 purple_upnp_discover_timeout(gpointer data
)
524 UPnPDiscoveryData
* dd
= data
;
527 purple_input_remove(dd
->inpa
);
529 purple_timeout_remove(dd
->tima
);
533 if (dd
->retry_count
< NUM_UDP_ATTEMPTS
) {
534 /* TODO: We probably shouldn't be incrementing retry_count in two places */
536 purple_upnp_discover_send_broadcast(dd
);
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
);
556 purple_upnp_discover_udp_read(gpointer data
, gint sock
, PurpleInputCondition cond
)
559 UPnPDiscoveryData
*dd
= data
;
563 len
= recv(dd
->fd
, buf
,
569 } else if(errno
!= EINTR
) {
570 /* We'll either get called again, or time out */
573 } while (errno
== EINTR
);
575 purple_input_remove(dd
->inpa
);
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 */
588 purple_upnp_discover_send_broadcast(UPnPDiscoveryData
*dd
)
590 gchar
*sendMessage
= NULL
;
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
++) {
600 if((dd
->retry_count
% 2) == 0) {
601 strncpy(dd
->service_type
, WAN_IP_CONN_SERVICE
, sizeof(dd
->service_type
));
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
);
611 if(sendto(dd
->fd
, sendMessage
, totalSize
, 0,
612 (struct sockaddr
*) &(dd
->server
),
613 sizeof(struct sockaddr_in
)
618 } while (errno
== EINTR
|| errno
== EAGAIN
);
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
);
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
);
638 purple_upnp_discover(PurpleUPnPCallback cb
, gpointer cb_data
)
640 /* Socket Setup Variables */
644 /* UDP RECEIVE VARIABLES */
645 UPnPDiscoveryData
*dd
;
647 if (control_info
.status
== PURPLE_UPNP_STATUS_DISCOVERING
) {
649 discovery_callbacks
= g_slist_append(
650 discovery_callbacks
, cb
);
651 discovery_callbacks
= g_slist_append(
652 discovery_callbacks
, cb_data
);
657 dd
= g_new0(UPnPDiscoveryData
, 1);
659 discovery_callbacks
= g_slist_append(discovery_callbacks
, cb
);
660 discovery_callbacks
= g_slist_append(discovery_callbacks
,
664 /* Set up the sockets */
665 dd
->fd
= sock
= socket(AF_INET
, SOCK_DGRAM
, 0);
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
);
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
);
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
,
700 PurpleUtilFetchUrlData
* gfud
;
702 gchar
* totalSendMessage
;
703 gchar
* pathOfControl
;
704 gchar
* addressOfControl
;
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 */
714 cb(NULL
, cb_data
, NULL
, 0, 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
);
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
);
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
);
760 looked_up_public_ip_cb(PurpleUtilFetchUrlData
*url_data
, gpointer user_data
,
761 const gchar
*httpResponse
, gsize len
, const gchar
*error_message
)
765 if ((error_message
!= NULL
) || (httpResponse
== NULL
))
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");
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");
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");
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
);
796 purple_upnp_generate_action_message_and_send("GetExternalIPAddress", "",
797 looked_up_public_ip_cb
, NULL
);
800 /* TODO: This could be exported */
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
);
819 looked_up_internal_ip_cb(gpointer data
, gint source
, const gchar
*error_message
)
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
);
829 purple_debug_error("upnp", "Unable to look up local IP\n");
836 gchar
* addressOfControl
;
839 if(!purple_url_parse(control_info
.control_url
, &addressOfControl
, &port
,
841 purple_debug_error("upnp",
842 "lookup_internal_ip(): Failed In Parse URL\n");
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
);
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)");
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
);
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
;
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");
897 ar
->tima
= purple_timeout_add(0, fire_ar_cb_async_and_free
, ar
);
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
,
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
);
921 ar
->tima
= purple_timeout_add(0, fire_ar_cb_async_and_free
, ar
);
925 fire_port_mapping_failure_cb(gpointer data
)
927 UPnPMappingAddRemove
*ar
= data
;
930 do_port_mapping_cb(FALSE
, data
);
934 void purple_upnp_cancel_port_mapping(UPnPMappingAddRemove
*ar
)
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
;
944 GSList
*next
= l
->next
;
946 if (next
&& (next
->data
== ar
)) {
947 discovery_callbacks
= g_slist_delete_link(discovery_callbacks
, next
);
949 discovery_callbacks
= g_slist_delete_link(discovery_callbacks
, l
);
956 purple_timeout_remove(ar
->tima
);
959 purple_util_fetch_url_cancel(ar
->gfud
);
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);
972 ar
->cb_data
= cb_data
;
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
);
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
);
995 } else if(control_info
.status
== PURPLE_UPNP_STATUS_UNABLE_TO_DISCOVER
) {
997 /* Asynchronously trigger a failed response */
998 ar
->tima
= purple_timeout_add(10, fire_port_mapping_failure_cb
, ar
);
1000 /* No need to do anything if nobody expects a response*/
1007 do_port_mapping_cb(TRUE
, 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);
1019 ar
->cb_data
= cb_data
;
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
);
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
);
1040 } else if(control_info
.status
== PURPLE_UPNP_STATUS_UNABLE_TO_DISCOVER
) {
1042 /* Asynchronously trigger a failed response */
1043 ar
->tima
= purple_timeout_add(10, fire_port_mapping_failure_cb
, ar
);
1045 /* No need to do anything if nobody expects a response*/
1052 do_port_mapping_cb(TRUE
, ar
);
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;
1070 purple_upnp_get_handle(void)
1080 purple_signal_connect(purple_network_get_handle(), "network-configuration-changed",
1081 purple_upnp_get_handle(), PURPLE_CALLBACK(purple_upnp_network_config_changed_cb
),