* @file upnp.c UPnP Implementation * Purple is the legal property of its developers, whose names are too numerous * to list here. Please refer to the COPYRIGHT file distributed with this * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA /*************************************************************** ****************************************************************/ #define DEFAULT_HTTP_PORT 80 #define DISCOVERY_TIMEOUT 1000 /* limit UPnP-triggered http downloads to 128k */ #define MAX_UPNP_DOWNLOAD (128 * 1024) /*************************************************************** ** Discovery/Description Defines * ****************************************************************/ #define NUM_UDP_ATTEMPTS 2 /* Address and port of an SSDP request used for discovery */ #define HTTPMU_HOST_ADDRESS "239.255.255.250" #define HTTPMU_HOST_PORT 1900 #define SEARCH_REQUEST_DEVICE "urn:schemas-upnp-org:service:%s" #define SEARCH_REQUEST_STRING \ "M-SEARCH * HTTP/1.1\r\n" \ "HOST: 239.255.255.250:1900\r\n" \ "MAN: \"ssdp:discover\"\r\n" \ "ST: urn:schemas-upnp-org:service:%s\r\n" \ #define WAN_IP_CONN_SERVICE "WANIPConnection:1" #define WAN_PPP_CONN_SERVICE "WANPPPConnection:1" /****************************************************************** *******************************************************************/ #define HTTP_HEADER_ACTION \ "POST /%s HTTP/1.1\r\n" \ "SOAPACTION: \"urn:schemas-upnp-org:service:%s#%s\"\r\n" \ "CONTENT-TYPE: text/xml ; charset=\"utf-8\"\r\n" \ "CONTENT-LENGTH: %" G_GSIZE_FORMAT "\r\n\r\n" "<?xml version=\"1.0\" encoding=\"utf-8\"?>\r\n" \ "<s:Envelope xmlns:s=\"http://schemas.xmlsoap.org/soap/envelope/\" " \ "s:encodingStyle=\"http://schemas.xmlsoap.org/soap/encoding/\">\r\n" \
"<u:%s xmlns:u=\"urn:schemas-upnp-org:service:%s\">\r\n" \
#define PORT_MAPPING_LEASE_TIME "0" #define PORT_MAPPING_DESCRIPTION "PURPLE_UPNP_PORT_FORWARD" #define ADD_PORT_MAPPING_PARAMS \ "<NewRemoteHost></NewRemoteHost>\r\n" \ "<NewExternalPort>%i</NewExternalPort>\r\n" \ "<NewProtocol>%s</NewProtocol>\r\n" \ "<NewInternalPort>%i</NewInternalPort>\r\n" \ "<NewInternalClient>%s</NewInternalClient>\r\n" \ "<NewEnabled>1</NewEnabled>\r\n" \ "<NewPortMappingDescription>" \ PORT_MAPPING_DESCRIPTION \ "</NewPortMappingDescription>\r\n" \ PORT_MAPPING_LEASE_TIME \ "</NewLeaseDuration>\r\n" #define DELETE_PORT_MAPPING_PARAMS \ "<NewRemoteHost></NewRemoteHost>\r\n" \ "<NewExternalPort>%i</NewExternalPort>\r\n" \ "<NewProtocol>%s</NewProtocol>\r\n" PURPLE_UPNP_STATUS_UNDISCOVERED = -1, PURPLE_UPNP_STATUS_UNABLE_TO_DISCOVER, PURPLE_UPNP_STATUS_DISCOVERING, PURPLE_UPNP_STATUS_DISCOVERED guint inpa; /* purple_input_add handle */ guint tima; /* purple_timeout_add handle */ struct sockaddr_in server; struct _UPnPMappingAddRemove guint tima; /* purple_timeout_add handle */ PurpleUtilFetchUrlData *gfud; static PurpleUPnPControlInfo control_info = { PURPLE_UPNP_STATUS_UNDISCOVERED, NULL, "\0", "\0", "\0", 0}; static GSList *discovery_callbacks = NULL; static void purple_upnp_discover_send_broadcast(UPnPDiscoveryData *dd); static void lookup_public_ip(void); static void lookup_internal_ip(void); fire_ar_cb_async_and_free(gpointer data) UPnPMappingAddRemove *ar = data; ar->cb(ar->success, ar->cb_data); fire_discovery_callbacks(gboolean success) while(discovery_callbacks) { PurpleUPnPCallback cb = discovery_callbacks->data; discovery_callbacks = g_slist_delete_link(discovery_callbacks, discovery_callbacks); data = discovery_callbacks->data; discovery_callbacks = g_slist_delete_link(discovery_callbacks, discovery_callbacks); purple_upnp_compare_device(const xmlnode* device, const gchar* deviceType) xmlnode* deviceTypeNode = xmlnode_get_child(device, "deviceType"); if(deviceTypeNode == NULL) { tmp = xmlnode_get_data(deviceTypeNode); ret = !g_ascii_strcasecmp(tmp, deviceType); purple_upnp_compare_service(const xmlnode* service, const gchar* serviceType) xmlnode* serviceTypeNode; serviceTypeNode = xmlnode_get_child(service, "serviceType"); if(serviceTypeNode == NULL) { tmp = xmlnode_get_data(serviceTypeNode); ret = !g_ascii_strcasecmp(tmp, serviceType); purple_upnp_parse_description_response(const gchar* httpResponse, gsize len, const gchar* httpURL, const gchar* serviceType) gchar *xmlRoot, *baseURL, *controlURL, *service; xmlnode *xmlRootNode, *serviceTypeNode, *controlURLNode, *baseURLNode; /* make sure we have a valid http response */ if(g_strstr_len(httpResponse, len, HTTP_OK) == NULL) { purple_debug_error("upnp", "parse_description_response(): Failed In HTTP_OK\n"); /* find the root of the xml document */ if((xmlRoot = g_strstr_len(httpResponse, len, "<root")) == NULL) { purple_debug_error("upnp", "parse_description_response(): Failed finding root\n"); /* create the xml root node */ if((xmlRootNode = xmlnode_from_str(xmlRoot, len - (xmlRoot - httpResponse))) == NULL) { purple_debug_error("upnp", "parse_description_response(): Could not parse xml root node\n"); /* get the baseURL of the device */ if((baseURLNode = xmlnode_get_child(xmlRootNode, "URLBase")) != NULL) { baseURL = xmlnode_get_data(baseURLNode); /* fixes upnp-descriptions with empty urlbase-element */ baseURL = g_strdup(httpURL); /* get the serviceType child that has the service type as its data */ /* get urn:schemas-upnp-org:device:InternetGatewayDevice:1 and its devicelist */ serviceTypeNode = xmlnode_get_child(xmlRootNode, "device"); while(!purple_upnp_compare_device(serviceTypeNode, "urn:schemas-upnp-org:device:InternetGatewayDevice:1") && serviceTypeNode != NULL) { serviceTypeNode = xmlnode_get_next_twin(serviceTypeNode); if(serviceTypeNode == NULL) { purple_debug_error("upnp", "parse_description_response(): could not get serviceTypeNode 1\n"); xmlnode_free(xmlRootNode); serviceTypeNode = xmlnode_get_child(serviceTypeNode, "deviceList"); if(serviceTypeNode == NULL) { purple_debug_error("upnp", "parse_description_response(): could not get serviceTypeNode 2\n"); xmlnode_free(xmlRootNode); /* get urn:schemas-upnp-org:device:WANDevice:1 and its devicelist */ serviceTypeNode = xmlnode_get_child(serviceTypeNode, "device"); while(!purple_upnp_compare_device(serviceTypeNode, "urn:schemas-upnp-org:device:WANDevice:1") && serviceTypeNode != NULL) { serviceTypeNode = xmlnode_get_next_twin(serviceTypeNode); if(serviceTypeNode == NULL) { purple_debug_error("upnp", "parse_description_response(): could not get serviceTypeNode 3\n"); xmlnode_free(xmlRootNode); serviceTypeNode = xmlnode_get_child(serviceTypeNode, "deviceList"); if(serviceTypeNode == NULL) { purple_debug_error("upnp", "parse_description_response(): could not get serviceTypeNode 4\n"); xmlnode_free(xmlRootNode); /* get urn:schemas-upnp-org:device:WANConnectionDevice:1 and its servicelist */ serviceTypeNode = xmlnode_get_child(serviceTypeNode, "device"); while(serviceTypeNode && !purple_upnp_compare_device(serviceTypeNode, "urn:schemas-upnp-org:device:WANConnectionDevice:1")) { serviceTypeNode = xmlnode_get_next_twin(serviceTypeNode); if(serviceTypeNode == NULL) { purple_debug_error("upnp", "parse_description_response(): could not get serviceTypeNode 5\n"); xmlnode_free(xmlRootNode); serviceTypeNode = xmlnode_get_child(serviceTypeNode, "serviceList"); if(serviceTypeNode == NULL) { purple_debug_error("upnp", "parse_description_response(): could not get serviceTypeNode 6\n"); xmlnode_free(xmlRootNode); /* get the serviceType variable passed to this function */ service = g_strdup_printf(SEARCH_REQUEST_DEVICE, serviceType); serviceTypeNode = xmlnode_get_child(serviceTypeNode, "service"); while(!purple_upnp_compare_service(serviceTypeNode, service) && serviceTypeNode != NULL) { serviceTypeNode = xmlnode_get_next_twin(serviceTypeNode); if(serviceTypeNode == NULL) { purple_debug_error("upnp", "parse_description_response(): could not get serviceTypeNode 7\n"); xmlnode_free(xmlRootNode); /* get the controlURL of the service */ if((controlURLNode = xmlnode_get_child(serviceTypeNode, "controlURL")) == NULL) { purple_debug_error("upnp", "parse_description_response(): Could not find controlURL\n"); xmlnode_free(xmlRootNode); tmp = xmlnode_get_data(controlURLNode); if(baseURL && !purple_str_has_prefix(tmp, "http://") && !purple_str_has_prefix(tmp, "HTTP://")) { /* Handle absolute paths in a relative URL. This probably const char *path, *start = strstr(baseURL, "://"); start = start ? start + 3 : baseURL; path = strchr(start, '/'); length = path ? path - baseURL : strlen(baseURL); controlURL = g_strdup_printf("%.*s%s", (int)length, baseURL, tmp); controlURL = g_strdup_printf("%s%s", baseURL, tmp); xmlnode_free(xmlRootNode); upnp_parse_description_cb(PurpleUtilFetchUrlData *url_data, gpointer user_data, const gchar *httpResponse, gsize len, const gchar *error_message) UPnPDiscoveryData *dd = user_data; gchar *control_url = NULL; control_url = purple_upnp_parse_description_response( httpResponse, len, dd->full_url, dd->service_type); if(control_url == NULL) { purple_debug_error("upnp", "purple_upnp_parse_description(): control URL is NULL\n"); control_info.status = control_url ? PURPLE_UPNP_STATUS_DISCOVERED : PURPLE_UPNP_STATUS_UNABLE_TO_DISCOVER; control_info.lookup_time = time(NULL); control_info.control_url = control_url; g_strlcpy(control_info.service_type, dd->service_type, sizeof(control_info.service_type)); fire_discovery_callbacks(control_url != NULL); /* Look up the public and internal IPs */ if(control_url != NULL) { purple_input_remove(dd->inpa); purple_timeout_remove(dd->tima); purple_upnp_parse_description(const gchar* descriptionURL, UPnPDiscoveryData *dd) gchar* descriptionXMLAddress; gchar* descriptionAddress; /* parse the 4 above variables out of the descriptionURL example description URL: http://192.168.1.1:5678/rootDesc.xml */ /* parse the url into address, port, path variables */ if(!purple_url_parse(descriptionURL, &descriptionAddress, &port, &descriptionXMLAddress, NULL, NULL)) { if(port == 0 || port == -1) { port = DEFAULT_HTTP_PORT; GET /rootDesc.xml HTTP/1.1\r\nHost: 192.168.1.1:5678\r\n\r\n */ httpRequest = g_strdup_printf( descriptionXMLAddress, descriptionAddress, port); g_free(descriptionXMLAddress); dd->full_url = g_strdup_printf("http://%s:%d", descriptionAddress, port); g_free(descriptionAddress); /* Remove the timeout because everything it is waiting for has * successfully completed */ purple_timeout_remove(dd->tima); purple_util_fetch_url_request_len(descriptionURL, TRUE, NULL, TRUE, httpRequest, TRUE, MAX_UPNP_DOWNLOAD, upnp_parse_description_cb, dd); purple_upnp_parse_discover_response(const gchar* buf, unsigned int buf_len, if(g_strstr_len(buf, buf_len, HTTP_OK) == NULL) { purple_debug_error("upnp", "parse_discover_response(): Failed In HTTP_OK\n"); if((startDescURL = g_strstr_len(buf, buf_len, "http://")) == NULL) { purple_debug_error("upnp", "parse_discover_response(): Failed In finding http://\n"); endDescURL = g_strstr_len(startDescURL, buf_len - (startDescURL - buf), endDescURL = g_strstr_len(startDescURL, buf_len - (startDescURL - buf), "\n"); purple_debug_error("upnp", "parse_discover_response(): Failed In endDescURL\n"); /* XXX: I'm not sure how this could ever happen */ if(endDescURL == startDescURL) { purple_debug_error("upnp", "parse_discover_response(): endDescURL == startDescURL\n"); descURL = g_strndup(startDescURL, endDescURL - startDescURL); purple_upnp_parse_description(descURL, dd); purple_upnp_discover_timeout(gpointer data) UPnPDiscoveryData* dd = data; purple_input_remove(dd->inpa); purple_timeout_remove(dd->tima); if (dd->retry_count < NUM_UDP_ATTEMPTS) { /* TODO: We probably shouldn't be incrementing retry_count in two places */ purple_upnp_discover_send_broadcast(dd); control_info.status = PURPLE_UPNP_STATUS_UNABLE_TO_DISCOVER; control_info.lookup_time = time(NULL); control_info.service_type[0] = '\0'; g_free(control_info.control_url); control_info.control_url = NULL; fire_discovery_callbacks(FALSE); purple_upnp_discover_udp_read(gpointer data, gint sock, PurpleInputCondition cond) UPnPDiscoveryData *dd = data; } else if(errno != EINTR) { /* We'll either get called again, or time out */ } while (errno == EINTR); purple_input_remove(dd->inpa); /* parse the response, and see if it was a success */ purple_upnp_parse_discover_response(buf, len, dd); /* We'll either time out or continue successfully */ purple_upnp_discover_send_broadcast(UPnPDiscoveryData *dd) gchar *sendMessage = NULL; /* because we are sending over UDP, if there is a failure we should retry the send NUM_UDP_ATTEMPTS times. Also, try different requests for WANIPConnection and WANPPPConnection*/ for(; dd->retry_count < NUM_UDP_ATTEMPTS; dd->retry_count++) { if((dd->retry_count % 2) == 0) { g_strlcpy(dd->service_type, WAN_IP_CONN_SERVICE, sizeof(dd->service_type)); g_strlcpy(dd->service_type, WAN_PPP_CONN_SERVICE, sizeof(dd->service_type)); sendMessage = g_strdup_printf(SEARCH_REQUEST_STRING, dd->service_type); totalSize = strlen(sendMessage); if(sendto(dd->fd, sendMessage, totalSize, 0, (struct sockaddr*) &(dd->server), sizeof(struct sockaddr_in) } while (errno == EINTR || errno == EAGAIN); dd->tima = purple_timeout_add(DISCOVERY_TIMEOUT, purple_upnp_discover_timeout, dd); dd->inpa = purple_input_add(dd->fd, PURPLE_INPUT_READ, purple_upnp_discover_udp_read, dd); /* We have already done all our retries. Make sure that the callback * doesn't get called before the original function returns */ dd->tima = purple_timeout_add(10, purple_upnp_discover_timeout, dd); purple_upnp_discover(PurpleUPnPCallback cb, gpointer cb_data) /* Socket Setup Variables */ /* UDP RECEIVE VARIABLES */ if (control_info.status == PURPLE_UPNP_STATUS_DISCOVERING) { discovery_callbacks = g_slist_append( discovery_callbacks, cb); discovery_callbacks = g_slist_append( discovery_callbacks, cb_data); dd = g_new0(UPnPDiscoveryData, 1); discovery_callbacks = g_slist_append(discovery_callbacks, cb); discovery_callbacks = g_slist_append(discovery_callbacks, dd->fd = sock = socket(AF_INET, SOCK_DGRAM, 0); purple_debug_error("upnp", "purple_upnp_discover(): Failed In sock creation\n"); /* Short circuit the retry attempts */ dd->retry_count = NUM_UDP_ATTEMPTS; dd->tima = purple_timeout_add(10, purple_upnp_discover_timeout, dd); /* TODO: Non-blocking! */ if((hp = gethostbyname(HTTPMU_HOST_ADDRESS)) == NULL) { purple_debug_error("upnp", "purple_upnp_discover(): Failed In gethostbyname\n"); /* Short circuit the retry attempts */ dd->retry_count = NUM_UDP_ATTEMPTS; dd->tima = purple_timeout_add(10, purple_upnp_discover_timeout, dd); memset(&(dd->server), 0, sizeof(struct sockaddr)); dd->server.sin_family = AF_INET; memcpy(&(dd->server.sin_addr), hp->h_addr_list[0], hp->h_length); dd->server.sin_port = htons(HTTPMU_HOST_PORT); control_info.status = PURPLE_UPNP_STATUS_DISCOVERING; purple_upnp_discover_send_broadcast(dd); static PurpleUtilFetchUrlData* purple_upnp_generate_action_message_and_send(const gchar* actionName, const gchar* actionParams, PurpleUtilFetchUrlCallback cb, PurpleUtilFetchUrlData* gfud; /* parse the url into address, port, path variables */ if(!purple_url_parse(control_info.control_url, &addressOfControl, &port, &pathOfControl, NULL, NULL)) { purple_debug_error("upnp", "generate_action_message_and_send(): Failed In Parse URL\n"); /* XXX: This should probably be async */ cb(NULL, cb_data, NULL, 0, NULL); if(port == 0 || port == -1) { port = DEFAULT_HTTP_PORT; /* set the soap message */ soapMessage = g_strdup_printf(SOAP_ACTION, actionName, control_info.service_type, actionParams, actionName); /* set the HTTP Header, and append the body to it */ totalSendMessage = g_strdup_printf(HTTP_HEADER_ACTION "%s", pathOfControl, addressOfControl, port, control_info.service_type, actionName, strlen(soapMessage), soapMessage); gfud = purple_util_fetch_url_request_len(control_info.control_url, FALSE, NULL, TRUE, totalSendMessage, TRUE, MAX_UPNP_DOWNLOAD, cb, cb_data); g_free(totalSendMessage); g_free(addressOfControl); purple_upnp_get_public_ip() if (control_info.status == PURPLE_UPNP_STATUS_DISCOVERED && strlen(control_info.publicip) > 0) return control_info.publicip; /* Trigger another UPnP discovery if 5 minutes have elapsed since the * last one, and it wasn't successful */ if (control_info.status < PURPLE_UPNP_STATUS_DISCOVERING && (time(NULL) - control_info.lookup_time) > 300) purple_upnp_discover(NULL, NULL); looked_up_public_ip_cb(PurpleUtilFetchUrlData *url_data, gpointer user_data, const gchar *httpResponse, gsize len, const gchar *error_message) if ((error_message != NULL) || (httpResponse == NULL)) /* extract the ip, or see if there is an error */ if((temp = g_strstr_len(httpResponse, len, "<NewExternalIPAddress")) == NULL) { purple_debug_error("upnp", "looked_up_public_ip_cb(): Failed Finding <NewExternalIPAddress\n"); if(!(temp = g_strstr_len(temp, len - (temp - httpResponse), ">"))) { purple_debug_error("upnp", "looked_up_public_ip_cb(): Failed In Finding >\n"); if(!(temp2 = g_strstr_len(temp, len - (temp - httpResponse), "<"))) { purple_debug_error("upnp", "looked_up_public_ip_cb(): Failed In Finding <\n"); g_strlcpy(control_info.publicip, temp + 1, sizeof(control_info.publicip)); purple_debug_info("upnp", "NAT Returned IP: %s\n", control_info.publicip); purple_upnp_generate_action_message_and_send("GetExternalIPAddress", "", looked_up_public_ip_cb, NULL); /* TODO: This could be exported */ purple_upnp_get_internal_ip(void) if (control_info.status == PURPLE_UPNP_STATUS_DISCOVERED && strlen(control_info.internalip) > 0) return control_info.internalip; /* Trigger another UPnP discovery if 5 minutes have elapsed since the * last one, and it wasn't successful */ if (control_info.status < PURPLE_UPNP_STATUS_DISCOVERING && (time(NULL) - control_info.lookup_time) > 300) purple_upnp_discover(NULL, NULL); looked_up_internal_ip_cb(gpointer data, gint source, const gchar *error_message) g_strlcpy(control_info.internalip, purple_network_get_local_system_ip(source), sizeof(control_info.internalip)); purple_debug_info("upnp", "Local IP: %s\n", control_info.internalip); purple_debug_error("upnp", "Unable to look up local IP\n"); if(!purple_url_parse(control_info.control_url, &addressOfControl, &port, purple_debug_error("upnp", "lookup_internal_ip(): Failed In Parse URL\n"); if(port == 0 || port == -1) { port = DEFAULT_HTTP_PORT; if(purple_proxy_connect(NULL, NULL, addressOfControl, port, looked_up_internal_ip_cb, NULL) == NULL) purple_debug_error("upnp", "Get Local IP Connect Failed: Address: %s @@@ Port %d\n", g_free(addressOfControl); done_port_mapping_cb(PurpleUtilFetchUrlData *url_data, gpointer user_data, const gchar *httpResponse, gsize len, const gchar *error_message) UPnPMappingAddRemove *ar = user_data; /* determine if port mapping was a success */ if ((error_message != NULL) || (httpResponse == NULL) || (g_strstr_len(httpResponse, len, HTTP_OK) == NULL)) purple_debug_error("upnp", "purple_upnp_set_port_mapping(): Failed HTTP_OK\n%s\n", httpResponse ? httpResponse : "(null)"); purple_debug_info("upnp", "Successfully completed port mapping operation\n"); ar->tima = purple_timeout_add(0, fire_ar_cb_async_and_free, ar); do_port_mapping_cb(gboolean has_control_mapping, gpointer data) UPnPMappingAddRemove *ar = data; if (has_control_mapping) { const gchar *internal_ip; /* get the internal IP */ if(!(internal_ip = purple_upnp_get_internal_ip())) { purple_debug_error("upnp", "purple_upnp_set_port_mapping(): couldn't get local ip\n"); ar->tima = purple_timeout_add(0, fire_ar_cb_async_and_free, ar); strncpy(action_name, "AddPortMapping", action_params = g_strdup_printf( ar->portmap, ar->protocol, ar->portmap, strncpy(action_name, "DeletePortMapping", sizeof(action_name)); action_params = g_strdup_printf( DELETE_PORT_MAPPING_PARAMS, ar->portmap, ar->protocol); ar->gfud = purple_upnp_generate_action_message_and_send(action_name, action_params, done_port_mapping_cb, ar); ar->tima = purple_timeout_add(0, fire_ar_cb_async_and_free, ar); fire_port_mapping_failure_cb(gpointer data) UPnPMappingAddRemove *ar = data; do_port_mapping_cb(FALSE, data); void purple_upnp_cancel_port_mapping(UPnPMappingAddRemove *ar) /* Remove ar from discovery_callbacks if present; it was inserted after a cb. * The same cb may be in the list multiple times, so be careful to remove * the one associated with ar. */ if (next && (next->data == ar)) { discovery_callbacks = g_slist_delete_link(discovery_callbacks, next); discovery_callbacks = g_slist_delete_link(discovery_callbacks, l); purple_timeout_remove(ar->tima); purple_util_fetch_url_cancel(ar->gfud); purple_upnp_set_port_mapping(unsigned short portmap, const gchar* protocol, PurpleUPnPCallback cb, gpointer cb_data) UPnPMappingAddRemove *ar; ar = g_new0(UPnPMappingAddRemove, 1); g_strlcpy(ar->protocol, protocol, sizeof(ar->protocol)); /* If we're waiting for a discovery, add to the callbacks list */ if(control_info.status == PURPLE_UPNP_STATUS_DISCOVERING) { /* TODO: This will fail because when this cb is triggered, * the internal IP lookup won't be complete */ discovery_callbacks = g_slist_append( discovery_callbacks, do_port_mapping_cb); discovery_callbacks = g_slist_append( discovery_callbacks, ar); /* If we haven't had a successful UPnP discovery, check if 5 minutes has * elapsed since the last try, try again */ if(control_info.status == PURPLE_UPNP_STATUS_UNDISCOVERED || (control_info.status == PURPLE_UPNP_STATUS_UNABLE_TO_DISCOVER && (time(NULL) - control_info.lookup_time) > 300)) { purple_upnp_discover(do_port_mapping_cb, ar); } else if(control_info.status == PURPLE_UPNP_STATUS_UNABLE_TO_DISCOVER) { /* Asynchronously trigger a failed response */ ar->tima = purple_timeout_add(10, fire_port_mapping_failure_cb, ar); /* No need to do anything if nobody expects a response*/ do_port_mapping_cb(TRUE, ar); purple_upnp_remove_port_mapping(unsigned short portmap, const char* protocol, PurpleUPnPCallback cb, gpointer cb_data) UPnPMappingAddRemove *ar; ar = g_new0(UPnPMappingAddRemove, 1); g_strlcpy(ar->protocol, protocol, sizeof(ar->protocol)); /* If we're waiting for a discovery, add to the callbacks list */ if(control_info.status == PURPLE_UPNP_STATUS_DISCOVERING) { discovery_callbacks = g_slist_append( discovery_callbacks, do_port_mapping_cb); discovery_callbacks = g_slist_append( discovery_callbacks, ar); /* If we haven't had a successful UPnP discovery, check if 5 minutes has * elapsed since the last try, try again */ if(control_info.status == PURPLE_UPNP_STATUS_UNDISCOVERED || (control_info.status == PURPLE_UPNP_STATUS_UNABLE_TO_DISCOVER && (time(NULL) - control_info.lookup_time) > 300)) { purple_upnp_discover(do_port_mapping_cb, ar); } else if(control_info.status == PURPLE_UPNP_STATUS_UNABLE_TO_DISCOVER) { /* Asynchronously trigger a failed response */ ar->tima = purple_timeout_add(10, fire_port_mapping_failure_cb, ar); /* No need to do anything if nobody expects a response*/ do_port_mapping_cb(TRUE, ar); purple_upnp_network_config_changed_cb(void *data) /* Reset the control_info to default values */ control_info.status = PURPLE_UPNP_STATUS_UNDISCOVERED; g_free(control_info.control_url); control_info.control_url = NULL; control_info.service_type[0] = '\0'; control_info.publicip[0] = '\0'; control_info.internalip[0] = '\0'; control_info.lookup_time = 0; purple_upnp_get_handle(void) purple_signal_connect(purple_network_get_handle(), "network-configuration-changed", purple_upnp_get_handle(), PURPLE_CALLBACK(purple_upnp_network_config_changed_cb),