pidgin/pidgin

1896a80ff8e3
Route GLib debug logging directly to the Finch debug window

Instead of flowing through purple debug, this merges some bits of the existing GLib log handler, and the purple debug printer.

Testing Done:
Open the Debug window an see some `GLib-*` outputs.

Reviewed at https://reviews.imfreedom.org/r/1057/
/* 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);
}