pidgin/pidgin

Merged in default (pull request #661)

2019-12-20, Gary Kramlich
2a80cd63e914
Merged in default (pull request #661)

Add metadata to Glade ui files.

Approved-by: Gary Kramlich
/* 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 "internal.h"
#include <gio/gio.h>
#ifndef _WIN32
#include <arpa/nameser.h>
#include <resolv.h>
#include <netinet/in.h>
#include <net/if.h>
#include <sys/ioctl.h>
#ifdef HAVE_GETIFADDRS
#include <ifaddrs.h>
#endif
#else
#include <nspapi.h>
#endif
/* Solaris */
#if defined (__SVR4) && defined (__sun)
#include <sys/sockio.h>
#endif
#include "debug.h"
#include "account.h"
#include "nat-pmp.h"
#include "network.h"
#include "prefs.h"
#include "stun.h"
#include "upnp.h"
/*
* Calling sizeof(struct ifreq) isn't always correct on
* Mac OS X (and maybe others).
*/
#ifdef _SIZEOF_ADDR_IFREQ
# define HX_SIZE_OF_IFREQ(a) _SIZEOF_ADDR_IFREQ(a)
#else
# define HX_SIZE_OF_IFREQ(a) sizeof(a)
#endif
struct _PurpleNetworkListenData {
int listenfd;
int socket_type;
gboolean retry;
gboolean adding;
PurpleNetworkListenCallback cb;
gpointer cb_data;
PurpleUPnPMappingAddRemove *mapping_data;
int timer;
};
static gboolean force_online = FALSE;
/* Cached IP addresses for STUN and TURN servers (set globally in prefs) */
static gchar *stun_ip = NULL;
static gchar *turn_ip = NULL;
/* Keep track of port mappings done with UPnP and NAT-PMP */
static GHashTable *upnp_port_mappings = NULL;
static GHashTable *nat_pmp_port_mappings = NULL;
void
purple_network_set_public_ip(const char *ip)
{
g_return_if_fail(ip != NULL);
/* XXX - Ensure the IP address is valid */
purple_prefs_set_string("/purple/network/public_ip", ip);
}
const char *
purple_network_get_public_ip(void)
{
return purple_prefs_get_string("/purple/network/public_ip");
}
const char *
purple_network_get_local_system_ip(int fd)
{
struct ifreq buffer[100];
guchar *it, *it_end;
static char ip[16];
struct ifconf ifc;
struct ifreq *ifr;
struct sockaddr_in *sinptr;
guint32 lhost = htonl((127 << 24) + 1); /* 127.0.0.1 */
long unsigned int add;
int source = fd;
if (fd < 0)
source = socket(PF_INET,SOCK_STREAM, 0);
ifc.ifc_len = sizeof(buffer);
ifc.ifc_req = buffer;
ioctl(source, SIOCGIFCONF, &ifc);
if (fd < 0 && source >= 0)
close(source);
it = (guchar*)buffer;
it_end = it + ifc.ifc_len;
while (it < it_end) {
/* in this case "it" is:
* a) (struct ifreq)-aligned
* b) not aligned, because of OS quirks (see
* _SIZEOF_ADDR_IFREQ), so the OS should deal with it.
*/
ifr = (struct ifreq *)(gpointer)it;
it += HX_SIZE_OF_IFREQ(*ifr);
if (ifr->ifr_addr.sa_family == AF_INET)
{
sinptr = (struct sockaddr_in *)(gpointer)&ifr->ifr_addr;
if (sinptr->sin_addr.s_addr != lhost)
{
add = ntohl(sinptr->sin_addr.s_addr);
g_snprintf(ip, 16, "%lu.%lu.%lu.%lu",
((add >> 24) & 255),
((add >> 16) & 255),
((add >> 8) & 255),
add & 255);
return ip;
}
}
}
return "0.0.0.0";
}
static gchar *
purple_network_get_local_system_ip_from_gio(GSocketConnection *sockconn)
{
GSocketAddress *addr;
GInetSocketAddress *inetsockaddr;
gchar *ip;
addr = g_socket_connection_get_local_address(sockconn, NULL);
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)) {
ip = g_inet_address_to_string(inetaddr);
g_object_unref(addr);
return ip;
}
}
g_object_unref(addr);
return g_strdup("0.0.0.0");
}
GList *
purple_network_get_all_local_system_ips(void)
{
#if defined(HAVE_GETIFADDRS) && defined(HAVE_INET_NTOP)
GList *result = NULL;
struct ifaddrs *start, *ifa;
int ret;
ret = getifaddrs(&start);
if (ret < 0) {
purple_debug_warning("network",
"getifaddrs() failed: %s\n", g_strerror(errno));
return NULL;
}
for (ifa = start; ifa; ifa = ifa->ifa_next) {
int family = ifa->ifa_addr ? ifa->ifa_addr->sa_family : AF_UNSPEC;
char host[INET6_ADDRSTRLEN];
const char *tmp = NULL;
common_sockaddr_t *addr =
(common_sockaddr_t *)(gpointer)ifa->ifa_addr;
if ((family != AF_INET && family != AF_INET6) || ifa->ifa_flags & IFF_LOOPBACK)
continue;
if (family == AF_INET)
tmp = inet_ntop(family, &addr->in.sin_addr, host, sizeof(host));
else {
/* Peer-peer link-local communication is a big TODO. I am not sure
* how communicating link-local addresses is supposed to work, and
* it seems like it would require attempting the cartesian product
* of the local and remote interfaces to see if any match (eww).
*/
if (!IN6_IS_ADDR_LINKLOCAL(&addr->in6.sin6_addr))
tmp = inet_ntop(family, &addr->in6.sin6_addr, host, sizeof(host));
}
if (tmp != NULL)
result = g_list_prepend(result, g_strdup(tmp));
}
freeifaddrs(start);
return g_list_reverse(result);
#else /* HAVE_GETIFADDRS && HAVE_INET_NTOP */
GList *result = NULL;
int source = socket(PF_INET,SOCK_STREAM, 0);
struct ifreq buffer[100];
guchar *it, *it_end;
struct ifconf ifc;
struct ifreq *ifr;
ifc.ifc_len = sizeof(buffer);
ifc.ifc_req = buffer;
ioctl(source, SIOCGIFCONF, &ifc);
close(source);
it = (guchar*)buffer;
it_end = it + ifc.ifc_len;
while (it < it_end) {
char dst[INET_ADDRSTRLEN];
/* alignment: see purple_network_get_local_system_ip */
ifr = (struct ifreq *)(gpointer)it;
it += HX_SIZE_OF_IFREQ(*ifr);
if (ifr->ifr_addr.sa_family == AF_INET) {
struct sockaddr_in *sinptr =
(struct sockaddr_in *)(gpointer)&ifr->ifr_addr;
inet_ntop(AF_INET, &sinptr->sin_addr, dst,
sizeof(dst));
purple_debug_info("network",
"found local i/f with address %s on IPv4\n", dst);
if (!purple_strequal(dst, "127.0.0.1")) {
result = g_list_append(result, g_strdup(dst));
}
}
}
return result;
#endif /* HAVE_GETIFADDRS && HAVE_INET_NTOP */
}
/*
* purple_network_is_ipv4:
* @hostname: The hostname to be verified.
*
* Checks, if specified hostname is valid ipv4 address.
*
* Returns: TRUE, if the hostname is valid.
*/
static gboolean
purple_network_is_ipv4(const gchar *hostname)
{
g_return_val_if_fail(hostname != NULL, FALSE);
/* We don't accept ipv6 here. */
if (strchr(hostname, ':') != NULL)
return FALSE;
return g_hostname_is_ip_address(hostname);
}
const char *
purple_network_get_my_ip(int fd)
{
const char *ip = NULL;
PurpleStunNatDiscovery *stun;
/* Check if the user specified an IP manually */
if (!purple_prefs_get_bool("/purple/network/auto_ip")) {
ip = purple_network_get_public_ip();
/* Make sure the IP address entered by the user is valid */
if ((ip != NULL) && (purple_network_is_ipv4(ip)))
return ip;
} else {
/* Check if STUN discovery was already done */
stun = purple_stun_discover(NULL);
if ((stun != NULL) && (stun->status == PURPLE_STUN_STATUS_DISCOVERED))
return stun->publicip;
/* Attempt to get the IP from a NAT device using UPnP */
ip = purple_upnp_get_public_ip();
if (ip != NULL)
return ip;
/* Attempt to get the IP from a NAT device using NAT-PMP */
ip = purple_pmp_get_public_ip();
if (ip != NULL)
return ip;
}
/* Just fetch the IP of the local system */
return purple_network_get_local_system_ip(fd);
}
gchar *
purple_network_get_my_ip_from_gio(GSocketConnection *sockconn)
{
const gchar *ip = NULL;
PurpleStunNatDiscovery *stun;
/* Check if the user specified an IP manually */
if (!purple_prefs_get_bool("/purple/network/auto_ip")) {
ip = purple_network_get_public_ip();
/* Make sure the IP address entered by the user is valid */
if ((ip != NULL) && (purple_network_is_ipv4(ip))) {
return g_strdup(ip);
}
} else {
/* Check if STUN discovery was already done */
stun = purple_stun_discover(NULL);
if ((stun != NULL) && (stun->status == PURPLE_STUN_STATUS_DISCOVERED)) {
return g_strdup(stun->publicip);
}
/* Attempt to get the IP from a NAT device using UPnP */
ip = purple_upnp_get_public_ip();
if (ip != NULL) {
return g_strdup(ip);
}
/* Attempt to get the IP from a NAT device using NAT-PMP */
ip = purple_pmp_get_public_ip();
if (ip != NULL) {
return g_strdup(ip);
}
}
/* Just fetch the IP of the local system */
return purple_network_get_local_system_ip_from_gio(sockconn);
}
static void
purple_network_set_upnp_port_mapping_cb(gboolean success, gpointer data)
{
PurpleNetworkListenData *listen_data;
listen_data = data;
/* TODO: Once we're keeping track of upnp requests... */
/* listen_data->pnp_data = NULL; */
if (!success) {
purple_debug_warning("network", "Couldn't create UPnP mapping\n");
if (listen_data->retry) {
listen_data->retry = FALSE;
listen_data->adding = FALSE;
listen_data->mapping_data = purple_upnp_remove_port_mapping(
purple_network_get_port_from_fd(listen_data->listenfd),
(listen_data->socket_type == SOCK_STREAM) ? "TCP" : "UDP",
purple_network_set_upnp_port_mapping_cb, listen_data);
return;
}
} else if (!listen_data->adding) {
/* We've tried successfully to remove the port mapping.
* Try to add it again */
listen_data->adding = TRUE;
listen_data->mapping_data = purple_upnp_set_port_mapping(
purple_network_get_port_from_fd(listen_data->listenfd),
(listen_data->socket_type == SOCK_STREAM) ? "TCP" : "UDP",
purple_network_set_upnp_port_mapping_cb, listen_data);
return;
}
if (success) {
/* add port mapping to hash table */
gint key = purple_network_get_port_from_fd(listen_data->listenfd);
gint value = listen_data->socket_type;
g_hash_table_insert(upnp_port_mappings, GINT_TO_POINTER(key), GINT_TO_POINTER(value));
}
if (listen_data->cb)
listen_data->cb(listen_data->listenfd, listen_data->cb_data);
/* Clear the UPnP mapping data, since it's complete and purple_network_listen_cancel() will try to cancel
* it otherwise. */
listen_data->mapping_data = NULL;
purple_network_listen_cancel(listen_data);
}
static gboolean
purple_network_finish_pmp_map_cb(gpointer data)
{
PurpleNetworkListenData *listen_data;
gint key;
gint value;
listen_data = data;
listen_data->timer = 0;
/* add port mapping to hash table */
key = purple_network_get_port_from_fd(listen_data->listenfd);
value = listen_data->socket_type;
g_hash_table_insert(nat_pmp_port_mappings, GINT_TO_POINTER(key), GINT_TO_POINTER(value));
if (listen_data->cb)
listen_data->cb(listen_data->listenfd, listen_data->cb_data);
purple_network_listen_cancel(listen_data);
return FALSE;
}
static PurpleNetworkListenData *
purple_network_do_listen(unsigned short port, int socket_family, int socket_type, gboolean map_external,
PurpleNetworkListenCallback cb, gpointer cb_data)
{
int listenfd = -1;
const int on = 1;
PurpleNetworkListenData *listen_data;
unsigned short actual_port;
#ifdef HAVE_GETADDRINFO
int errnum;
struct addrinfo hints, *res, *next;
char serv[6];
/*
* Get a list of addresses on this machine.
*/
g_snprintf(serv, sizeof(serv), "%hu", port);
memset(&hints, 0, sizeof(struct addrinfo));
hints.ai_flags = AI_PASSIVE;
hints.ai_family = socket_family;
hints.ai_socktype = socket_type;
errnum = getaddrinfo(NULL /* any IP */, serv, &hints, &res);
if (errnum != 0) {
#ifndef _WIN32
purple_debug_warning("network", "getaddrinfo: %s\n", purple_gai_strerror(errnum));
if (errnum == EAI_SYSTEM)
purple_debug_warning("network", "getaddrinfo: system error: %s\n", g_strerror(errno));
#else
purple_debug_warning("network", "getaddrinfo: Error Code = %d\n", errnum);
#endif
return NULL;
}
/*
* Go through the list of addresses and attempt to listen on
* one of them.
* XXX - Try IPv6 addresses first?
*/
for (next = res; next != NULL; next = next->ai_next) {
listenfd = socket(next->ai_family, next->ai_socktype, next->ai_protocol);
if (listenfd < 0)
continue;
if (setsockopt(listenfd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on)) != 0)
purple_debug_warning("network", "setsockopt(SO_REUSEADDR): %s\n", g_strerror(errno));
if (bind(listenfd, next->ai_addr, next->ai_addrlen) == 0)
break; /* success */
/* XXX - It is unclear to me (datallah) whether we need to be
using a new socket each time */
close(listenfd);
}
freeaddrinfo(res);
if (next == NULL)
return NULL;
#else
struct sockaddr_in sockin;
if (socket_family != AF_INET && socket_family != AF_UNSPEC) {
purple_debug_warning("network", "Address family %d only "
"supported when built with getaddrinfo() "
"support\n", socket_family);
return NULL;
}
if ((listenfd = socket(AF_INET, socket_type, 0)) < 0) {
purple_debug_warning("network", "socket: %s\n", g_strerror(errno));
return NULL;
}
if (setsockopt(listenfd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on)) != 0)
purple_debug_warning("network", "setsockopt: %s\n", g_strerror(errno));
memset(&sockin, 0, sizeof(struct sockaddr_in));
sockin.sin_family = PF_INET;
sockin.sin_port = htons(port);
if (bind(listenfd, (struct sockaddr *)&sockin, sizeof(struct sockaddr_in)) != 0) {
purple_debug_warning("network", "bind: %s\n", g_strerror(errno));
close(listenfd);
return NULL;
}
#endif
if (socket_type == SOCK_STREAM && listen(listenfd, 4) != 0) {
purple_debug_warning("network", "listen: %s\n", g_strerror(errno));
close(listenfd);
return NULL;
}
_purple_network_set_common_socket_flags(listenfd);
actual_port = purple_network_get_port_from_fd(listenfd);
purple_debug_info("network", "Listening on port: %hu\n", actual_port);
listen_data = g_new0(PurpleNetworkListenData, 1);
listen_data->listenfd = listenfd;
listen_data->adding = TRUE;
listen_data->retry = TRUE;
listen_data->cb = cb;
listen_data->cb_data = cb_data;
listen_data->socket_type = socket_type;
if (!purple_socket_speaks_ipv4(listenfd) || !map_external ||
!purple_prefs_get_bool("/purple/network/map_ports"))
{
purple_debug_info("network", "Skipping external port mapping.\n");
/* The pmp_map_cb does what we want to do */
listen_data->timer = g_timeout_add(0, purple_network_finish_pmp_map_cb, listen_data);
}
/* Attempt a NAT-PMP Mapping, which will return immediately */
else if (purple_pmp_create_map(((socket_type == SOCK_STREAM) ? PURPLE_PMP_TYPE_TCP : PURPLE_PMP_TYPE_UDP),
actual_port, actual_port, PURPLE_PMP_LIFETIME))
{
purple_debug_info("network", "Created NAT-PMP mapping on port %i\n", actual_port);
/* We want to return listen_data now, and on the next run loop trigger the cb and destroy listen_data */
listen_data->timer = g_timeout_add(0, purple_network_finish_pmp_map_cb, listen_data);
}
else
{
/* Attempt a UPnP Mapping */
listen_data->mapping_data = purple_upnp_set_port_mapping(
actual_port,
(socket_type == SOCK_STREAM) ? "TCP" : "UDP",
purple_network_set_upnp_port_mapping_cb, listen_data);
}
return listen_data;
}
PurpleNetworkListenData *
purple_network_listen(unsigned short port, int socket_family, int socket_type,
gboolean map_external, PurpleNetworkListenCallback cb,
gpointer cb_data)
{
g_return_val_if_fail(port != 0, NULL);
return purple_network_do_listen(port, socket_family, socket_type, map_external,
cb, cb_data);
}
PurpleNetworkListenData *
purple_network_listen_range(unsigned short start, unsigned short end,
int socket_family, int socket_type, gboolean map_external,
PurpleNetworkListenCallback cb,
gpointer cb_data)
{
PurpleNetworkListenData *ret = NULL;
if (purple_prefs_get_bool("/purple/network/ports_range_use")) {
start = purple_prefs_get_int("/purple/network/ports_range_start");
end = purple_prefs_get_int("/purple/network/ports_range_end");
} else {
if (end < start)
end = start;
}
for (; start <= end; start++) {
ret = purple_network_do_listen(start, AF_UNSPEC, socket_type, map_external, cb, cb_data);
if (ret != NULL)
break;
}
return ret;
}
void purple_network_listen_cancel(PurpleNetworkListenData *listen_data)
{
if (listen_data->mapping_data != NULL)
purple_upnp_cancel_port_mapping(listen_data->mapping_data);
if (listen_data->timer > 0)
g_source_remove(listen_data->timer);
g_free(listen_data);
}
unsigned short
purple_network_get_port_from_fd(int fd)
{
struct sockaddr_in addr;
socklen_t len;
g_return_val_if_fail(fd >= 0, 0);
len = sizeof(addr);
if (getsockname(fd, (struct sockaddr *) &addr, &len) == -1) {
purple_debug_warning("network", "getsockname: %s\n", g_strerror(errno));
return 0;
}
return ntohs(addr.sin_port);
}
gboolean
purple_network_is_available(void)
{
if(force_online) {
return TRUE;
}
return g_network_monitor_get_network_available(g_network_monitor_get_default());
}
void
purple_network_force_online()
{
force_online = TRUE;
}
static void
purple_network_ip_lookup_cb(GObject *sender, GAsyncResult *result, gpointer data) {
GError *error = NULL;
GList *addresses = NULL;
GInetAddress *address = NULL;
const gchar **ip_address = (const gchar **)data;
addresses = g_resolver_lookup_by_name_finish(G_RESOLVER(sender),
result, &error);
if(error) {
purple_debug_info("network", "lookup of IP address failed: %s\n", error->message);
g_error_free(error);
return;
}
address = G_INET_ADDRESS(addresses->data);
*ip_address = g_inet_address_to_string(address);
g_resolver_free_addresses(addresses);
}
void
purple_network_set_stun_server(const gchar *stun_server)
{
if (stun_server && stun_server[0] != '\0') {
if (purple_network_is_available()) {
GResolver *resolver = g_resolver_get_default();
g_resolver_lookup_by_name_async(resolver,
stun_server,
NULL,
purple_network_ip_lookup_cb,
&stun_ip);
g_object_unref(resolver);
} else {
purple_debug_info("network",
"network is unavailable, don't try to update STUN IP");
}
} else {
g_free(stun_ip);
stun_ip = NULL;
}
}
void
purple_network_set_turn_server(const gchar *turn_server)
{
if (turn_server && turn_server[0] != '\0') {
if (purple_network_is_available()) {
GResolver *resolver = g_resolver_get_default();
g_resolver_lookup_by_name_async(resolver,
turn_server,
NULL,
purple_network_ip_lookup_cb,
&turn_server);
g_object_unref(resolver);
} else {
purple_debug_info("network",
"network is unavailable, don't try to update TURN IP");
}
} else {
g_free(turn_ip);
turn_ip = NULL;
}
}
const gchar *
purple_network_get_stun_ip(void)
{
return stun_ip;
}
const gchar *
purple_network_get_turn_ip(void)
{
return turn_ip;
}
void *
purple_network_get_handle(void)
{
static int handle;
return &handle;
}
static void
purple_network_upnp_mapping_remove_cb(gboolean sucess, gpointer data)
{
purple_debug_info("network", "done removing UPnP port mapping\n");
}
/* the reason for these functions to have these signatures is to be able to
use them for g_hash_table_foreach to clean remaining port mappings, which is
not yet done */
static void
purple_network_upnp_mapping_remove(gpointer key, gpointer value,
gpointer user_data)
{
gint port = GPOINTER_TO_INT(key);
gint protocol = GPOINTER_TO_INT(value);
purple_debug_info("network", "removing UPnP port mapping for port %d\n",
port);
purple_upnp_remove_port_mapping(port,
protocol == SOCK_STREAM ? "TCP" : "UDP",
purple_network_upnp_mapping_remove_cb, NULL);
g_hash_table_remove(upnp_port_mappings, GINT_TO_POINTER(port));
}
static void
purple_network_nat_pmp_mapping_remove(gpointer key, gpointer value,
gpointer user_data)
{
gint port = GPOINTER_TO_INT(key);
gint protocol = GPOINTER_TO_INT(value);
purple_debug_info("network", "removing NAT-PMP port mapping for port %d\n",
port);
purple_pmp_destroy_map(
protocol == SOCK_STREAM ? PURPLE_PMP_TYPE_TCP : PURPLE_PMP_TYPE_UDP,
port);
g_hash_table_remove(nat_pmp_port_mappings, GINT_TO_POINTER(port));
}
void
purple_network_remove_port_mapping(gint fd)
{
int port = purple_network_get_port_from_fd(fd);
gint protocol = GPOINTER_TO_INT(g_hash_table_lookup(upnp_port_mappings, GINT_TO_POINTER(port)));
if (protocol) {
purple_network_upnp_mapping_remove(GINT_TO_POINTER(port), GINT_TO_POINTER(protocol), NULL);
} else {
protocol = GPOINTER_TO_INT(g_hash_table_lookup(nat_pmp_port_mappings, GINT_TO_POINTER(port)));
if (protocol) {
purple_network_nat_pmp_mapping_remove(GINT_TO_POINTER(port), GINT_TO_POINTER(protocol), NULL);
}
}
}
gboolean
_purple_network_set_common_socket_flags(int fd)
{
int flags;
gboolean succ = TRUE;
g_return_val_if_fail(fd >= 0, FALSE);
flags = fcntl(fd, F_GETFL);
if (fcntl(fd, F_SETFL, flags | O_NONBLOCK) != 0) {
purple_debug_warning("network",
"Couldn't set O_NONBLOCK flag\n");
succ = FALSE;
}
#ifndef _WIN32
if (fcntl(fd, F_SETFD, FD_CLOEXEC) != 0) {
purple_debug_warning("network",
"Couldn't set FD_CLOEXEC flag\n");
succ = FALSE;
}
#endif
return succ;
}
void
purple_network_init(void)
{
purple_prefs_add_none ("/purple/network");
purple_prefs_add_string("/purple/network/stun_server", "");
purple_prefs_add_string("/purple/network/turn_server", "");
purple_prefs_add_int ("/purple/network/turn_port", 3478);
purple_prefs_add_int ("/purple/network/turn_port_tcp", 3478);
purple_prefs_add_string("/purple/network/turn_username", "");
purple_prefs_add_string("/purple/network/turn_password", "");
purple_prefs_add_bool ("/purple/network/auto_ip", TRUE);
purple_prefs_add_string("/purple/network/public_ip", "");
purple_prefs_add_bool ("/purple/network/map_ports", TRUE);
purple_prefs_add_bool ("/purple/network/ports_range_use", FALSE);
purple_prefs_add_int ("/purple/network/ports_range_start", 1024);
purple_prefs_add_int ("/purple/network/ports_range_end", 2048);
if(purple_prefs_get_bool("/purple/network/map_ports") || purple_prefs_get_bool("/purple/network/auto_ip"))
purple_upnp_discover(NULL, NULL);
purple_pmp_init();
purple_upnp_init();
purple_network_set_stun_server(
purple_prefs_get_string("/purple/network/stun_server"));
purple_network_set_turn_server(
purple_prefs_get_string("/purple/network/turn_server"));
upnp_port_mappings = g_hash_table_new(g_direct_hash, g_direct_equal);
nat_pmp_port_mappings = g_hash_table_new(g_direct_hash, g_direct_equal);
}
void
purple_network_uninit(void)
{
g_free(stun_ip);
g_hash_table_destroy(upnp_port_mappings);
g_hash_table_destroy(nat_pmp_port_mappings);
/* TODO: clean up remaining port mappings, note calling
purple_upnp_remove_port_mapping from here doesn't quite work... */
purple_upnp_uninit();
}