pidgin/pidgin

b5eccef8404e
Avoid manual handling of icons in request field list items

* Use `PurpleKeyValuePair` as field list items
* Replace `icons` list with `has_icons` flag, thus fixing memleak
* Replace `purple_request_field_list_get_icons` with `purple_request_field_list_has_icons`

Testing Done:
Compile and run.

Reviewed at https://reviews.imfreedom.org/r/563/
/* 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 "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(msg->status_code)) {
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;
SoupURI *uri;
/* 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
*/
uri = soup_uri_new(descriptionURL);
if (!uri) {
upnp_parse_description_cb(NULL, NULL, dd);
return;
}
dd->full_url = g_strdup_printf("http://%s:%d", uri->host, uri->port);
soup_uri_free(uri);
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(msg->request_headers, "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(msg->status_code)) {
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()
{
SoupURI *uri;
GSocketClient *client;
GError *error = NULL;
uri = soup_uri_new(control_info.control_url);
if (!uri) {
purple_debug_error("upnp",
"lookup_internal_ip(): Failed In Parse URL\n");
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",
uri->host, uri->port, error->message);
g_clear_error(&error);
soup_uri_free(uri);
return;
}
purple_debug_info("upnp", "Attempting connection to %s:%u\n", uri->host,
uri->port);
g_socket_client_connect_to_host_async(client, uri->host, uri->port, NULL,
looked_up_internal_ip_cb, NULL);
g_object_unref(client);
soup_uri_free(uri);
}
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(msg->status_code)) {
purple_debug_error("upnp",
"purple_upnp_set_port_mapping(): Failed HTTP_OK: %s",
msg->reason_phrase);
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);
}