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
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
28 #include "eventloop.h"
36 /***************************************************************
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" \
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" \
64 #define WAN_IP_CONN_SERVICE "WANIPConnection:1"
65 #define WAN_PPP_CONN_SERVICE "WANPPPConnection:1"
67 /******************************************************************
69 *******************************************************************/
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" \
75 "<u:%s xmlns:u=\"urn:schemas-upnp-org:service:%s\">\r\n" \
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"
104 PURPLE_UPNP_STATUS_UNDISCOVERED
= -1,
105 PURPLE_UPNP_STATUS_UNABLE_TO_DISCOVER
,
106 PURPLE_UPNP_STATUS_DISCOVERING
,
107 PURPLE_UPNP_STATUS_DISCOVERED
111 PurpleUPnPStatus status
;
113 gchar service_type
[20];
117 } PurpleUPnPControlInfo
;
120 guint inpa
; /* purple_input_add handle */
121 guint tima
; /* g_timeout_add handle */
123 struct sockaddr_in server
;
124 gchar service_type
[20];
129 struct _PurpleUPnPMappingAddRemove
131 unsigned short portmap
;
134 PurpleUPnPCallback cb
;
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);
152 fire_ar_cb_async_and_free(gpointer data
)
154 PurpleUPnPMappingAddRemove
*ar
= data
;
157 ar
->cb(ar
->success
, ar
->cb_data
);
165 fire_discovery_callbacks(gboolean success
)
167 while(discovery_callbacks
) {
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
);
178 purple_upnp_compare_device(const PurpleXmlNode
* device
, const gchar
* deviceType
)
180 PurpleXmlNode
* deviceTypeNode
= purple_xmlnode_get_child(device
, "deviceType");
184 if(deviceTypeNode
== NULL
) {
188 tmp
= purple_xmlnode_get_data(deviceTypeNode
);
189 ret
= !g_ascii_strcasecmp(tmp
, deviceType
);
196 purple_upnp_compare_service(const PurpleXmlNode
* service
, const gchar
* serviceType
)
198 PurpleXmlNode
* serviceTypeNode
;
202 if(service
== NULL
) {
206 serviceTypeNode
= purple_xmlnode_get_child(service
, "serviceType");
208 if(serviceTypeNode
== NULL
) {
212 tmp
= purple_xmlnode_get_data(serviceTypeNode
);
213 ret
= !g_ascii_strcasecmp(tmp
, serviceType
);
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
;
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");
234 /* get the baseURL of the device */
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 */
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");
257 purple_xmlnode_free(xmlRootNode
);
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");
265 purple_xmlnode_free(xmlRootNode
);
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");
280 purple_xmlnode_free(xmlRootNode
);
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");
288 purple_xmlnode_free(xmlRootNode
);
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");
302 purple_xmlnode_free(xmlRootNode
);
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");
310 purple_xmlnode_free(xmlRootNode
);
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
);
323 if(serviceTypeNode
== NULL
) {
324 purple_debug_error("upnp",
325 "parse_description_response(): could not get serviceTypeNode 7\n");
327 purple_xmlnode_free(xmlRootNode
);
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");
337 purple_xmlnode_free(xmlRootNode
);
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. */
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
);
354 controlURL
= g_strdup_printf("%s%s", baseURL
, tmp
);
361 purple_xmlnode_free(xmlRootNode
);
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
;
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
) {
401 lookup_internal_ip();
405 purple_input_remove(dd
->inpa
);
407 g_source_remove(dd
->tima
);
413 purple_upnp_parse_description(const gchar
* descriptionURL
, UPnPDiscoveryData
*dd
)
415 PurpleHttpRequest
*req
;
418 /* Remove the timeout because everything it is waiting for has
419 * successfully completed */
420 g_source_remove(dd
->tima
);
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
);
428 upnp_parse_description_cb(NULL
, NULL
, dd
);
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
);
443 purple_upnp_parse_discover_response(const gchar
* buf
, unsigned int buf_len
,
444 UPnPDiscoveryData
*dd
)
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");
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");
462 endDescURL
= g_strstr_len(startDescURL
, buf_len
- (startDescURL
- buf
),
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");
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");
481 descURL
= g_strndup(startDescURL
, endDescURL
- startDescURL
);
483 purple_upnp_parse_description(descURL
, dd
);
490 purple_upnp_discover_timeout(gpointer data
)
492 UPnPDiscoveryData
* dd
= data
;
495 purple_input_remove(dd
->inpa
);
497 g_source_remove(dd
->tima
);
501 if (dd
->retry_count
< NUM_UDP_ATTEMPTS
) {
502 /* TODO: We probably shouldn't be incrementing retry_count in two places */
504 purple_upnp_discover_send_broadcast(dd
);
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
);
524 purple_upnp_discover_udp_read(gpointer data
, gint sock
, PurpleInputCondition cond
)
527 UPnPDiscoveryData
*dd
= data
;
531 len
= recv(dd
->fd
, buf
,
537 } else if(errno
!= EINTR
) {
538 /* We'll either get called again, or time out */
541 } while (errno
== EINTR
);
543 purple_input_remove(dd
->inpa
);
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 */
556 purple_upnp_discover_send_broadcast(UPnPDiscoveryData
*dd
)
558 gchar
*sendMessage
= NULL
;
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
++) {
568 if((dd
->retry_count
% 2) == 0) {
569 g_strlcpy(dd
->service_type
, WAN_IP_CONN_SERVICE
, sizeof(dd
->service_type
));
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
);
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
) {
586 } while (errno
== EINTR
|| errno
== EAGAIN
);
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
);
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
);
606 purple_upnp_discover(PurpleUPnPCallback cb
, gpointer cb_data
)
608 /* Socket Setup Variables */
612 /* UDP RECEIVE VARIABLES */
613 UPnPDiscoveryData
*dd
;
615 if (control_info
.status
== PURPLE_UPNP_STATUS_DISCOVERING
) {
617 discovery_callbacks
= g_slist_append(
618 discovery_callbacks
, cb
);
619 discovery_callbacks
= g_slist_append(
620 discovery_callbacks
, cb_data
);
625 dd
= g_new0(UPnPDiscoveryData
, 1);
627 discovery_callbacks
= g_slist_append(discovery_callbacks
, cb
);
628 discovery_callbacks
= g_slist_append(discovery_callbacks
,
632 /* Set up the sockets */
633 dd
->fd
= sock
= socket(AF_INET
, SOCK_DGRAM
, 0);
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
);
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
);
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
,
668 PurpleHttpConnection
*hc
;
669 PurpleHttpRequest
*req
;
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
);
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
);
710 looked_up_public_ip_cb(PurpleHttpConnection
*http_conn
,
711 PurpleHttpResponse
*response
, gpointer user_data
)
714 const gchar
*got_data
;
717 if (!purple_http_response_is_successful(response
))
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");
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");
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");
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
);
749 purple_upnp_generate_action_message_and_send("GetExternalIPAddress", "",
750 looked_up_public_ip_cb
, NULL
);
753 /* TODO: This could be exported */
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
);
771 looked_up_internal_ip_cb(gpointer data
, gint source
, const gchar
*error_message
)
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
);
781 purple_debug_error("upnp", "Unable to look up local IP\n");
790 url
= purple_http_url_parse(control_info
.control_url
);
792 purple_debug_error("upnp",
793 "lookup_internal_ip(): Failed In Parse URL\n");
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
,
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
);
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
));
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
);
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
;
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");
847 ar
->tima
= g_timeout_add(0, fire_ar_cb_async_and_free
, ar
);
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
,
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
);
871 ar
->tima
= g_timeout_add(0, fire_ar_cb_async_and_free
, ar
);
875 fire_port_mapping_failure_cb(gpointer data
)
877 PurpleUPnPMappingAddRemove
*ar
= data
;
880 do_port_mapping_cb(FALSE
, data
);
884 void purple_upnp_cancel_port_mapping(PurpleUPnPMappingAddRemove
*ar
)
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
;
894 GSList
*next
= l
->next
;
896 if (next
&& (next
->data
== ar
)) {
897 discovery_callbacks
= g_slist_delete_link(discovery_callbacks
, next
);
899 discovery_callbacks
= g_slist_delete_link(discovery_callbacks
, l
);
906 g_source_remove(ar
->tima
);
908 purple_http_conn_cancel(ar
->hc
);
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);
921 ar
->cb_data
= cb_data
;
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
);
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
);
944 } else if(control_info
.status
== PURPLE_UPNP_STATUS_UNABLE_TO_DISCOVER
) {
946 /* Asynchronously trigger a failed response */
947 ar
->tima
= g_timeout_add(10, fire_port_mapping_failure_cb
, ar
);
949 /* No need to do anything if nobody expects a response*/
956 do_port_mapping_cb(TRUE
, 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);
968 ar
->cb_data
= cb_data
;
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
);
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
);
989 } else if(control_info
.status
== PURPLE_UPNP_STATUS_UNABLE_TO_DISCOVER
) {
991 /* Asynchronously trigger a failed response */
992 ar
->tima
= g_timeout_add(10, fire_port_mapping_failure_cb
, ar
);
994 /* No need to do anything if nobody expects a response*/
1001 do_port_mapping_cb(TRUE
, ar
);
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;
1021 g_signal_connect(g_network_monitor_get_default(),
1023 G_CALLBACK(purple_upnp_network_config_changed_cb
),