Fri, 10 Jun 2022 20:42:36 -0500
fix memory leak when using purple accounts
Testing Done:
ran test_account_manager and test_notification (from /r/1502 where I first encountered that leak) in valgrind -> no more leak and no new invalid read/write
Reviewed at https://reviews.imfreedom.org/r/1503/
/* purple * * 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 * source distribution. * * 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 */ #include <gio/gio.h> #include <libsoup/soup.h> #include "internal.h" #include "upnp.h" #include "glibcompat.h" #include "soupcompat.h" #include "debug.h" #include "eventloop.h" #include "network.h" #include "proxy.h" #include "purple-gio.h" #include "signals.h" #include "util.h" #include "xmlnode.h" /*************************************************************** ** General Defines * ****************************************************************/ #define HTTP_OK "200 OK" #define DEFAULT_HTTP_PORT 80 #define DISCOVERY_TIMEOUT 1 /* 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" \ "MX: 2\r\n" \ "HOST: 239.255.255.250:1900\r\n" \ "MAN: \"ssdp:discover\"\r\n" \ "ST: urn:schemas-upnp-org:service:%s\r\n" \ "\r\n" #define WAN_IP_CONN_SERVICE "WANIPConnection:1" #define WAN_PPP_CONN_SERVICE "WANPPPConnection:1" /****************************************************************** ** Action Defines * *******************************************************************/ #define SOAP_ACTION \ "<?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" \ "<s:Body>\r\n" \ "<u:%s xmlns:u=\"urn:schemas-upnp-org:service:%s\">\r\n" \ "%s" \ "</u:%s>\r\n" \ "</s:Body>\r\n" \ "</s:Envelope>" #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" \ "<NewLeaseDuration>" \ 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" typedef enum { PURPLE_UPNP_STATUS_UNDISCOVERED = -1, PURPLE_UPNP_STATUS_UNABLE_TO_DISCOVER, PURPLE_UPNP_STATUS_DISCOVERING, PURPLE_UPNP_STATUS_DISCOVERED } PurpleUPnPStatus; typedef struct { PurpleUPnPStatus status; gchar* control_url; gchar service_type[20]; char publicip[16]; char internalip[16]; gint64 lookup_time; } PurpleUPnPControlInfo; typedef struct { guint inpa; /* purple_input_add handle */ guint tima; /* g_timeout_add handle */ GSocket *socket; GSocketAddress *server; gchar service_type[20]; int retry_count; gchar *full_url; } UPnPDiscoveryData; struct _PurpleUPnPMappingAddRemove { unsigned short portmap; gchar protocol[4]; gboolean add; PurpleUPnPCallback cb; gpointer cb_data; gboolean success; guint tima; /* g_timeout_add handle */ SoupMessage *msg; }; static PurpleUPnPControlInfo control_info = { PURPLE_UPNP_STATUS_UNDISCOVERED, NULL, "\0", "\0", "\0", 0}; static SoupSession *session = NULL; 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); static gboolean fire_ar_cb_async_and_free(gpointer data) { PurpleUPnPMappingAddRemove *ar = data; if (ar) { if (ar->cb) ar->cb(ar->success, ar->cb_data); g_free(ar); } return FALSE; } static void fire_discovery_callbacks(gboolean success) { while(discovery_callbacks) { gpointer data; 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); cb(success, data); } } static gboolean purple_upnp_compare_device(const PurpleXmlNode* device, const gchar* deviceType) { PurpleXmlNode* deviceTypeNode = purple_xmlnode_get_child(device, "deviceType"); char *tmp; gboolean ret; if(deviceTypeNode == NULL) { return FALSE; } tmp = purple_xmlnode_get_data(deviceTypeNode); ret = !g_ascii_strcasecmp(tmp, deviceType); g_free(tmp); return ret; } static gboolean purple_upnp_compare_service(const PurpleXmlNode* service, const gchar* serviceType) { PurpleXmlNode* serviceTypeNode; char *tmp; gboolean ret; if(service == NULL) { return FALSE; } serviceTypeNode = purple_xmlnode_get_child(service, "serviceType"); if(serviceTypeNode == NULL) { return FALSE; } tmp = purple_xmlnode_get_data(serviceTypeNode); ret = !g_ascii_strcasecmp(tmp, serviceType); g_free(tmp); return ret; } static gchar* purple_upnp_parse_description_response(const gchar* httpResponse, gsize len, const gchar* httpURL, const gchar* serviceType) { gchar *baseURL, *controlURL, *service; PurpleXmlNode *xmlRootNode, *serviceTypeNode, *controlURLNode, *baseURLNode; char *tmp; /* create the xml root node */ if ((xmlRootNode = purple_xmlnode_from_str(httpResponse, len)) == NULL) { purple_debug_error("upnp", "parse_description_response(): Could not parse xml root node\n"); return NULL; } /* get the baseURL of the device */ baseURL = NULL; if((baseURLNode = purple_xmlnode_get_child(xmlRootNode, "URLBase")) != NULL) { baseURL = purple_xmlnode_get_data(baseURLNode); } /* fixes upnp-descriptions with empty urlbase-element */ if(baseURL == NULL){ 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 = purple_xmlnode_get_child(xmlRootNode, "device"); while(!purple_upnp_compare_device(serviceTypeNode, "urn:schemas-upnp-org:device:InternetGatewayDevice:1") && serviceTypeNode != NULL) { serviceTypeNode = purple_xmlnode_get_next_twin(serviceTypeNode); } if(serviceTypeNode == NULL) { purple_debug_error("upnp", "parse_description_response(): could not get serviceTypeNode 1\n"); g_free(baseURL); purple_xmlnode_free(xmlRootNode); return NULL; } serviceTypeNode = purple_xmlnode_get_child(serviceTypeNode, "deviceList"); if(serviceTypeNode == NULL) { purple_debug_error("upnp", "parse_description_response(): could not get serviceTypeNode 2\n"); g_free(baseURL); purple_xmlnode_free(xmlRootNode); return NULL; } /* get urn:schemas-upnp-org:device:WANDevice:1 and its devicelist */ serviceTypeNode = purple_xmlnode_get_child(serviceTypeNode, "device"); while(!purple_upnp_compare_device(serviceTypeNode, "urn:schemas-upnp-org:device:WANDevice:1") && serviceTypeNode != NULL) { serviceTypeNode = purple_xmlnode_get_next_twin(serviceTypeNode); } if(serviceTypeNode == NULL) { purple_debug_error("upnp", "parse_description_response(): could not get serviceTypeNode 3\n"); g_free(baseURL); purple_xmlnode_free(xmlRootNode); return NULL; } serviceTypeNode = purple_xmlnode_get_child(serviceTypeNode, "deviceList"); if(serviceTypeNode == NULL) { purple_debug_error("upnp", "parse_description_response(): could not get serviceTypeNode 4\n"); g_free(baseURL); purple_xmlnode_free(xmlRootNode); return NULL; } /* get urn:schemas-upnp-org:device:WANConnectionDevice:1 and its servicelist */ serviceTypeNode = purple_xmlnode_get_child(serviceTypeNode, "device"); while(serviceTypeNode && !purple_upnp_compare_device(serviceTypeNode, "urn:schemas-upnp-org:device:WANConnectionDevice:1")) { serviceTypeNode = purple_xmlnode_get_next_twin(serviceTypeNode); } if(serviceTypeNode == NULL) { purple_debug_error("upnp", "parse_description_response(): could not get serviceTypeNode 5\n"); g_free(baseURL); purple_xmlnode_free(xmlRootNode); return NULL; } serviceTypeNode = purple_xmlnode_get_child(serviceTypeNode, "serviceList"); if(serviceTypeNode == NULL) { purple_debug_error("upnp", "parse_description_response(): could not get serviceTypeNode 6\n"); g_free(baseURL); purple_xmlnode_free(xmlRootNode); return NULL; } /* get the serviceType variable passed to this function */ service = g_strdup_printf(SEARCH_REQUEST_DEVICE, serviceType); serviceTypeNode = purple_xmlnode_get_child(serviceTypeNode, "service"); while(!purple_upnp_compare_service(serviceTypeNode, service) && serviceTypeNode != NULL) { serviceTypeNode = purple_xmlnode_get_next_twin(serviceTypeNode); } g_free(service); if(serviceTypeNode == NULL) { purple_debug_error("upnp", "parse_description_response(): could not get serviceTypeNode 7\n"); g_free(baseURL); purple_xmlnode_free(xmlRootNode); return NULL; } /* get the controlURL of the service */ if((controlURLNode = purple_xmlnode_get_child(serviceTypeNode, "controlURL")) == NULL) { purple_debug_error("upnp", "parse_description_response(): Could not find controlURL\n"); g_free(baseURL); purple_xmlnode_free(xmlRootNode); return NULL; } tmp = purple_xmlnode_get_data(controlURLNode); if (baseURL && !g_str_has_prefix(tmp, "http://") && !g_str_has_prefix(tmp, "HTTP://")) { /* Handle absolute paths in a relative URL. This probably * belongs in util.c. */ if (tmp[0] == '/') { size_t length; const char *path, *start = strstr(baseURL, "://"); start = start ? start + 3 : baseURL; path = strchr(start, '/'); length = path ? (gsize)(path - baseURL) : strlen(baseURL); controlURL = g_strdup_printf("%.*s%s", (int)length, baseURL, tmp); } else { controlURL = g_strdup_printf("%s%s", baseURL, tmp); } g_free(tmp); } else { controlURL = tmp; } g_free(baseURL); purple_xmlnode_free(xmlRootNode); return controlURL; } static void upnp_parse_description_cb(G_GNUC_UNUSED SoupSession *session, SoupMessage *msg, gpointer _dd) { UPnPDiscoveryData *dd = _dd; gchar *control_url = NULL; if (msg && SOUP_STATUS_IS_SUCCESSFUL(soup_message_get_status(msg))) { control_url = purple_upnp_parse_description_response( msg->response_body->data, msg->response_body->length, dd->full_url, dd->service_type); } g_free(dd->full_url); 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 = g_get_monotonic_time(); 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) { lookup_public_ip(); lookup_internal_ip(); } if (dd->inpa > 0) { g_source_remove(dd->inpa); dd->inpa = 0; } if (dd->tima > 0) { g_source_remove(dd->tima); dd->tima = 0; } g_clear_object(&dd->socket); g_clear_object(&dd->server); g_free(dd); } static void purple_upnp_parse_description(const gchar* descriptionURL, UPnPDiscoveryData *dd) { SoupMessage *msg; gchar *host; gint port; /* Remove the timeout because everything it is waiting for has * successfully completed */ g_source_remove(dd->tima); dd->tima = 0; /* Extract base url out of the descriptionURL. * Example description URL: http://192.168.1.1:5678/rootDesc.xml */ if (!g_uri_split_network(descriptionURL, G_URI_FLAGS_NONE, NULL, &host, &port, NULL)) { upnp_parse_description_cb(NULL, NULL, dd); return; } dd->full_url = g_strdup_printf("http://%s:%d", host, port); g_free(host); msg = soup_message_new("GET", descriptionURL); // purple_http_request_set_max_len(msg, MAX_UPNP_DOWNLOAD); soup_session_queue_message(session, msg, upnp_parse_description_cb, dd); } static void purple_upnp_parse_discover_response(const gchar* buf, unsigned int buf_len, UPnPDiscoveryData *dd) { gchar* startDescURL; gchar* endDescURL; gchar* descURL; if(g_strstr_len(buf, buf_len, HTTP_OK) == NULL) { purple_debug_error("upnp", "parse_discover_response(): Failed In HTTP_OK\n"); return; } if((startDescURL = g_strstr_len(buf, buf_len, "http://")) == NULL) { purple_debug_error("upnp", "parse_discover_response(): Failed In finding http://\n"); return; } endDescURL = g_strstr_len(startDescURL, buf_len - (startDescURL - buf), "\r"); if(endDescURL == NULL) { endDescURL = g_strstr_len(startDescURL, buf_len - (startDescURL - buf), "\n"); if(endDescURL == NULL) { purple_debug_error("upnp", "parse_discover_response(): Failed In endDescURL\n"); return; } } /* XXX: I'm not sure how this could ever happen */ if(endDescURL == startDescURL) { purple_debug_error("upnp", "parse_discover_response(): endDescURL == startDescURL\n"); return; } descURL = g_strndup(startDescURL, endDescURL - startDescURL); purple_upnp_parse_description(descURL, dd); g_free(descURL); } static gboolean purple_upnp_discover_timeout(gpointer data) { UPnPDiscoveryData* dd = data; if (dd->inpa > 0) { g_source_remove(dd->inpa); dd->inpa = 0; } if (dd->tima > 0) { g_source_remove(dd->tima); dd->tima = 0; } if (dd->retry_count < NUM_UDP_ATTEMPTS) { /* TODO: We probably shouldn't be incrementing retry_count in two places */ dd->retry_count++; purple_upnp_discover_send_broadcast(dd); } else { control_info.status = PURPLE_UPNP_STATUS_UNABLE_TO_DISCOVER; control_info.lookup_time = g_get_monotonic_time(); control_info.service_type[0] = '\0'; g_free(control_info.control_url); control_info.control_url = NULL; fire_discovery_callbacks(FALSE); g_clear_object(&dd->socket); g_clear_object(&dd->server); g_free(dd); } return FALSE; } static void purple_upnp_discover_udp_read(GSocket *socket, GIOCondition condition, gpointer data) { UPnPDiscoveryData *dd = data; gchar buf[65536]; gssize len; len = g_socket_receive(dd->socket, buf, sizeof(buf) - 1, NULL, NULL); if (len >= 0) { buf[len] = '\0'; } else { /* We'll either get called again, or time out */ return; } g_source_remove(dd->inpa); dd->inpa = 0; /* 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 */ } static void purple_upnp_discover_send_broadcast(UPnPDiscoveryData *dd) { gchar *sendMessage = NULL; size_t totalSize; gboolean sentSuccess; GError *error = 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++) { sentSuccess = FALSE; if((dd->retry_count % 2) == 0) { g_strlcpy(dd->service_type, WAN_IP_CONN_SERVICE, sizeof(dd->service_type)); } else { 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); do { gssize sent; g_clear_error(&error); sent = g_socket_send_to(dd->socket, dd->server, sendMessage, totalSize, NULL, &error); if (sent >= 0 && (gsize)sent == totalSize) { sentSuccess = TRUE; break; } } while (error != NULL && error->code == G_IO_ERROR_WOULD_BLOCK); g_clear_error(&error); g_free(sendMessage); if(sentSuccess) { GSource *source; source = g_socket_create_source(dd->socket, G_IO_IN, NULL); g_source_set_callback(source, G_SOURCE_FUNC(purple_upnp_discover_udp_read), dd, NULL); dd->inpa = g_source_attach(source, NULL); g_source_unref(source); dd->tima = g_timeout_add_seconds(DISCOVERY_TIMEOUT, purple_upnp_discover_timeout, dd); return; } } /* We have already done all our retries. Make sure that the callback * doesn't get called before the original function returns */ dd->tima = g_timeout_add(10, purple_upnp_discover_timeout, dd); } void purple_upnp_discover(PurpleUPnPCallback cb, gpointer cb_data) { /* Socket Setup Variables */ GSocket *socket; GError *error = NULL; /* UDP RECEIVE VARIABLES */ UPnPDiscoveryData *dd; if (control_info.status == PURPLE_UPNP_STATUS_DISCOVERING) { if (cb) { discovery_callbacks = g_slist_append( discovery_callbacks, cb); discovery_callbacks = g_slist_append( discovery_callbacks, cb_data); } return; } dd = g_new0(UPnPDiscoveryData, 1); if (cb) { discovery_callbacks = g_slist_append(discovery_callbacks, cb); discovery_callbacks = g_slist_append(discovery_callbacks, cb_data); } /* Set up the sockets */ dd->socket = socket = g_socket_new(G_SOCKET_FAMILY_IPV4, G_SOCKET_TYPE_DATAGRAM, G_SOCKET_PROTOCOL_DEFAULT, &error); if (socket == NULL) { purple_debug_error( "upnp", "purple_upnp_discover(): Failed in sock creation: %s", error->message); g_error_free(error); /* Short circuit the retry attempts */ dd->retry_count = NUM_UDP_ATTEMPTS; dd->tima = g_timeout_add(10, purple_upnp_discover_timeout, dd); return; } dd->server = g_inet_socket_address_new_from_string(HTTPMU_HOST_ADDRESS, HTTPMU_HOST_PORT); control_info.status = PURPLE_UPNP_STATUS_DISCOVERING; purple_upnp_discover_send_broadcast(dd); } static SoupMessage * purple_upnp_generate_action_message_and_send(const gchar *actionName, const gchar *actionParams, SoupSessionCallback cb, gpointer cb_data) { SoupMessage *msg; gchar *action; gchar* soapMessage; /* set the soap message */ soapMessage = g_strdup_printf(SOAP_ACTION, actionName, control_info.service_type, actionParams, actionName); msg = soup_message_new("POST", control_info.control_url); // purple_http_request_set_max_len(msg, MAX_UPNP_DOWNLOAD); action = g_strdup_printf("\"urn:schemas-upnp-org:service:%s#%s\"", control_info.service_type, actionName); soup_message_headers_replace(soup_message_get_request_headers(msg), "SOAPAction", action); g_free(action); soup_message_set_request(msg, "text/xml; charset=utf-8", SOUP_MEMORY_TAKE, soapMessage, strlen(soapMessage)); soup_session_queue_message(session, msg, cb, cb_data); return msg; } const gchar * purple_upnp_get_public_ip() { if (control_info.status == PURPLE_UPNP_STATUS_DISCOVERED && *control_info.publicip) 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 && (g_get_monotonic_time() - control_info.lookup_time) > 300 * G_USEC_PER_SEC) { purple_upnp_discover(NULL, NULL); } return NULL; } static void looked_up_public_ip_cb(G_GNUC_UNUSED SoupSession *session, SoupMessage *msg, gpointer user_data) { gchar* temp, *temp2; const gchar *got_data; size_t got_len; if (!SOUP_STATUS_IS_SUCCESSFUL(soup_message_get_status(msg))) { return; } /* extract the ip, or see if there is an error */ got_data = msg->response_body->data; got_len = msg->response_body->length; if((temp = g_strstr_len(got_data, got_len, "<NewExternalIPAddress")) == NULL) { purple_debug_error("upnp", "looked_up_public_ip_cb(): Failed Finding <NewExternalIPAddress\n"); return; } if(!(temp = g_strstr_len(temp, got_len - (temp - got_data), ">"))) { purple_debug_error("upnp", "looked_up_public_ip_cb(): Failed In Finding >\n"); return; } if(!(temp2 = g_strstr_len(temp, got_len - (temp - got_data), "<"))) { purple_debug_error("upnp", "looked_up_public_ip_cb(): Failed In Finding <\n"); return; } *temp2 = '\0'; g_strlcpy(control_info.publicip, temp + 1, sizeof(control_info.publicip)); purple_debug_info("upnp", "NAT Returned IP: %s\n", control_info.publicip); } static void lookup_public_ip() { purple_upnp_generate_action_message_and_send("GetExternalIPAddress", "", looked_up_public_ip_cb, NULL); } /* TODO: This could be exported */ static const gchar * purple_upnp_get_internal_ip(void) { if (control_info.status == PURPLE_UPNP_STATUS_DISCOVERED && *control_info.internalip) 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 && (g_get_monotonic_time() - control_info.lookup_time) > 300 * G_USEC_PER_SEC) { purple_upnp_discover(NULL, NULL); } return NULL; } static void looked_up_internal_ip_cb(GObject *source, GAsyncResult *result, G_GNUC_UNUSED gpointer user_data) { GSocketConnection *conn; GSocketAddress *addr; GInetSocketAddress *inetsockaddr; GError *error = NULL; conn = g_socket_client_connect_to_host_finish(G_SOCKET_CLIENT(source), result, &error); if (conn == NULL) { purple_debug_error("upnp", "Unable to look up local IP: %s", error->message); g_clear_error(&error); return; } g_strlcpy(control_info.internalip, "0.0.0.0", sizeof(control_info.internalip)); addr = g_socket_connection_get_local_address(conn, &error); if ((inetsockaddr = G_INET_SOCKET_ADDRESS(addr)) != NULL) { GInetAddress *inetaddr = g_inet_socket_address_get_address(inetsockaddr); if (g_inet_address_get_family(inetaddr) == G_SOCKET_FAMILY_IPV4 && !g_inet_address_get_is_loopback(inetaddr)) { gchar *ip = g_inet_address_to_string(inetaddr); g_strlcpy(control_info.internalip, ip, sizeof(control_info.internalip)); g_free(ip); } } else { purple_debug_error( "upnp", "Unable to get local address of connection: %s", error ? error->message : "unknown socket address type"); g_clear_error(&error); } g_object_unref(addr); purple_debug_info("upnp", "Local IP: %s", control_info.internalip); g_object_unref(conn); } static void lookup_internal_ip() { gchar *host; gint port; GSocketClient *client; GError *error = NULL; if (!g_uri_split_network(control_info.control_url, G_URI_FLAGS_NONE, NULL, &host, &port, &error)) { purple_debug_error("upnp", "lookup_internal_ip(): Failed In Parse URL: %s", error->message); return; } client = purple_gio_socket_client_new(NULL, &error); if (client == NULL) { purple_debug_error("upnp", "Get Local IP Connect to %s:%d Failed: %s", host, port, error->message); g_clear_error(&error); g_free(host); return; } purple_debug_info("upnp", "Attempting connection to %s:%u\n", host, port); g_socket_client_connect_to_host_async(client, host, port, NULL, looked_up_internal_ip_cb, NULL); g_object_unref(client); g_free(host); } static void done_port_mapping_cb(G_GNUC_UNUSED SoupSession *session, SoupMessage *msg, gpointer user_data) { PurpleUPnPMappingAddRemove *ar = user_data; gboolean success = TRUE; /* determine if port mapping was a success */ if (!SOUP_STATUS_IS_SUCCESSFUL(soup_message_get_status(msg))) { purple_debug_error("upnp", "purple_upnp_set_port_mapping(): Failed HTTP_OK: %s", soup_message_get_reason_phrase(msg)); success = FALSE; } else { purple_debug_info("upnp", "Successfully completed port mapping operation"); } ar->success = success; ar->tima = g_timeout_add(0, fire_ar_cb_async_and_free, ar); } static void do_port_mapping_cb(gboolean has_control_mapping, gpointer data) { PurpleUPnPMappingAddRemove *ar = data; if (has_control_mapping) { gchar action_name[25]; gchar *action_params; if(ar->add) { 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->success = FALSE; ar->tima = g_timeout_add(0, fire_ar_cb_async_and_free, ar); return; } strncpy(action_name, "AddPortMapping", sizeof(action_name)); action_params = g_strdup_printf( ADD_PORT_MAPPING_PARAMS, ar->portmap, ar->protocol, ar->portmap, internal_ip); } else { strncpy(action_name, "DeletePortMapping", sizeof(action_name)); action_params = g_strdup_printf( DELETE_PORT_MAPPING_PARAMS, ar->portmap, ar->protocol); } ar->msg = purple_upnp_generate_action_message_and_send( action_name, action_params, done_port_mapping_cb, ar); g_free(action_params); return; } ar->success = FALSE; ar->tima = g_timeout_add(0, fire_ar_cb_async_and_free, ar); } static gboolean fire_port_mapping_failure_cb(gpointer data) { PurpleUPnPMappingAddRemove *ar = data; ar->tima = 0; do_port_mapping_cb(FALSE, data); return FALSE; } void purple_upnp_cancel_port_mapping(PurpleUPnPMappingAddRemove *ar) { GSList *l; /* 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. */ l = discovery_callbacks; while (l) { GSList *next = l->next; if (next && (next->data == ar)) { discovery_callbacks = g_slist_delete_link(discovery_callbacks, next); next = l->next; discovery_callbacks = g_slist_delete_link(discovery_callbacks, l); } l = next; } if (ar->tima > 0) g_source_remove(ar->tima); soup_session_cancel_message(session, ar->msg, SOUP_STATUS_CANCELLED); g_free(ar); } PurpleUPnPMappingAddRemove * purple_upnp_set_port_mapping(unsigned short portmap, const gchar* protocol, PurpleUPnPCallback cb, gpointer cb_data) { PurpleUPnPMappingAddRemove *ar; ar = g_new0(PurpleUPnPMappingAddRemove, 1); ar->cb = cb; ar->cb_data = cb_data; ar->add = TRUE; ar->portmap = portmap; 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); return ar; } if (control_info.status == PURPLE_UPNP_STATUS_UNDISCOVERED) { purple_upnp_discover(do_port_mapping_cb, ar); return ar; } else if (control_info.status == PURPLE_UPNP_STATUS_UNABLE_TO_DISCOVER) { if (g_get_monotonic_time() - control_info.lookup_time > 300 * G_USEC_PER_SEC) { /* If we haven't had a successful UPnP discovery, check if 5 minutes * has elapsed since the last try, try again */ purple_upnp_discover(do_port_mapping_cb, ar); } else if (cb) { /* Asynchronously trigger a failed response */ ar->tima = g_timeout_add(10, fire_port_mapping_failure_cb, ar); } else { /* No need to do anything if nobody expects a response*/ g_free(ar); ar = NULL; } return ar; } do_port_mapping_cb(TRUE, ar); return ar; } PurpleUPnPMappingAddRemove * purple_upnp_remove_port_mapping(unsigned short portmap, const char* protocol, PurpleUPnPCallback cb, gpointer cb_data) { PurpleUPnPMappingAddRemove *ar; ar = g_new0(PurpleUPnPMappingAddRemove, 1); ar->cb = cb; ar->cb_data = cb_data; ar->add = FALSE; ar->portmap = portmap; 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); return ar; } if (control_info.status == PURPLE_UPNP_STATUS_UNDISCOVERED) { purple_upnp_discover(do_port_mapping_cb, ar); return ar; } else if (control_info.status == PURPLE_UPNP_STATUS_UNABLE_TO_DISCOVER) { if (g_get_monotonic_time() - control_info.lookup_time > 300 * G_USEC_PER_SEC) { /* If we haven't had a successful UPnP discovery, check if 5 minutes * has elapsed since the last try, try again */ purple_upnp_discover(do_port_mapping_cb, ar); } else if (cb) { /* Asynchronously trigger a failed response */ ar->tima = g_timeout_add(10, fire_port_mapping_failure_cb, ar); } else { /* No need to do anything if nobody expects a response*/ g_free(ar); ar = NULL; } return ar; } do_port_mapping_cb(TRUE, ar); return ar; } static void purple_upnp_network_config_changed_cb(GNetworkMonitor *monitor, gboolean available, gpointer 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; } void purple_upnp_init() { session = soup_session_new(); g_signal_connect(g_network_monitor_get_default(), "network-changed", G_CALLBACK(purple_upnp_network_config_changed_cb), NULL); } void purple_upnp_uninit(void) { soup_session_abort(session); g_clear_object(&session); }