libpurple/network.c

Thu, 25 Aug 2022 21:34:53 -0500

author
Gary Kramlich <grim@reaperworld.com>
date
Thu, 25 Aug 2022 21:34:53 -0500
branch
gtk4
changeset 41591
ad6f5ffc2825
parent 41153
5f034a5261c3
child 41682
289867067994
permissions
-rw-r--r--

Port the invite dialog to GTK4

Testing Done:
Opened the dialog via conversations -> invite and made sure it worked as expected.

Reviewed at https://reviews.imfreedom.org/r/1644/

/* 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 "purpleprivate.h"

#include <gio/gio.h>

#ifndef _WIN32
#include <arpa/nameser.h>
#include <resolv.h>
#include <netinet/in.h>
#include <net/if.h>
#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"

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");
}

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");
}

/*
 * 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);
}

void
purple_network_discover_my_ip(void)
{
	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;
		}
	}

	/* Check if STUN discovery was already done */
	stun = purple_stun_discover(NULL);
	if (stun != NULL && stun->status == PURPLE_STUN_STATUS_DISCOVERED) {
		return;
	}

	/* Attempt to get the IP from a NAT device using UPnP */
	ip = purple_upnp_get_public_ip();
	if (ip != NULL) {
		return;
	}

	/* Attempt to get the IP from a NAT device using NAT-PMP */
	ip = purple_pmp_get_public_ip();
	if (ip != NULL) {
		return;
	}
}

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);
}

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_ip);
			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;
}

static void
purple_network_upnp_mapping_remove_cb(gboolean success, 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)
{
	gint port, protocol;
	struct sockaddr_in addr;
	socklen_t len;

	g_return_if_fail(fd >= 0);

	len = sizeof(addr);
	if (getsockname(fd, (struct sockaddr *) &addr, &len) == -1) {
		purple_debug_warning("network", "getsockname: %s", g_strerror(errno));
		port = 0;
	} else {
		port = g_ntohs(addr.sin_port);
	}

	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_free(turn_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();
}

mercurial