pidgin/pidgin
Clone
Summary
Browse
Changes
Graph
Rework the way we open links with firefox
release-2.x.y
2021-02-13, Gary Kramlich
cfb55052d83a
Rework the way we open links with firefox
Testing Done:
Compiled locally.
Bugs closed: PIDGIN-16589
Reviewed at https://reviews.imfreedom.org/r/503/
/**
* @file network.c Network Implementation
* @ingroup core
*/
/* 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"
#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"
#include
"dnsquery.h"
#ifdef USE_IDN
#include
<idna.h>
#endif
/*
* 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
#ifdef HAVE_NETWORKMANAGER
#include
<dbus/dbus-glib.h>
#include
<NetworkManager.h>
#if !defined(NM_CHECK_VERSION)
#define NM_CHECK_VERSION(x,y,z) 0
#endif
static
DBusGConnection
*
nm_conn
=
NULL
;
static
DBusGProxy
*
nm_proxy
=
NULL
;
static
DBusGProxy
*
dbus_proxy
=
NULL
;
static
NMState
nm_state
=
NM_STATE_UNKNOWN
;
static
gboolean
have_nm_state
=
FALSE
;
#elif defined _WIN32
static
int
current_network_count
;
/* Mutex for the other global vars */
static
GStaticMutex
mutex
=
G_STATIC_MUTEX_INIT
;
static
gboolean
network_initialized
=
FALSE
;
static
HANDLE
network_change_handle
=
NULL
;
static
int
(
WSAAPI
*
MyWSANSPIoctl
)
(
HANDLE
hLookup
,
DWORD
dwControlCode
,
LPVOID
lpvInBuffer
,
DWORD
cbInBuffer
,
LPVOID
lpvOutBuffer
,
DWORD
cbOutBuffer
,
LPDWORD
lpcbBytesReturned
,
LPWSACOMPLETION
lpCompletion
)
=
NULL
;
#endif
struct
_PurpleNetworkListenData
{
int
listenfd
;
int
socket_type
;
gboolean
retry
;
gboolean
adding
;
PurpleNetworkListenCallback
cb
;
gpointer
cb_data
;
UPnPMappingAddRemove
*
mapping_data
;
int
timer
;
};
#ifdef HAVE_NETWORKMANAGER
static
NMState
nm_get_network_state
(
void
);
#endif
#if defined(HAVE_NETWORKMANAGER) || defined(_WIN32)
static
gboolean
force_online
;
#endif
/* 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
;
const
unsigned
char
*
purple_network_ip_atoi
(
const
char
*
ip
)
{
static
unsigned
char
ret
[
4
];
gchar
*
delimiter
=
"."
;
gchar
**
split
;
int
i
;
g_return_val_if_fail
(
ip
!=
NULL
,
NULL
);
split
=
g_strsplit
(
ip
,
delimiter
,
4
);
for
(
i
=
0
;
split
[
i
]
!=
NULL
;
i
++
)
ret
[
i
]
=
atoi
(
split
[
i
]);
g_strfreev
(
split
);
/* i should always be 4 */
if
(
i
!=
4
)
return
NULL
;
return
ret
;
}
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
)
{
char
buffer
[
1024
];
static
char
ip
[
16
];
char
*
tmp
;
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
=
(
struct
ifreq
*
)
buffer
;
ioctl
(
source
,
SIOCGIFCONF
,
&
ifc
);
if
(
fd
<
0
&&
source
>=
0
)
close
(
source
);
tmp
=
buffer
;
while
(
tmp
<
buffer
+
ifc
.
ifc_len
)
{
ifr
=
(
struct
ifreq
*
)
tmp
;
tmp
+=
HX_SIZE_OF_IFREQ
(
*
ifr
);
if
(
ifr
->
ifr_addr
.
sa_family
==
AF_INET
)
{
sinptr
=
(
struct
sockaddr_in
*
)
&
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"
;
}
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
;
if
((
family
!=
AF_INET
&&
family
!=
AF_INET6
)
||
ifa
->
ifa_flags
&
IFF_LOOPBACK
)
continue
;
if
(
family
==
AF_INET
)
tmp
=
inet_ntop
(
family
,
&
((
struct
sockaddr_in
*
)
ifa
->
ifa_addr
)
->
sin_addr
,
host
,
sizeof
(
host
));
else
{
struct
sockaddr_in6
*
sockaddr
=
(
struct
sockaddr_in6
*
)
ifa
->
ifa_addr
;
/* 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
(
&
sockaddr
->
sin6_addr
))
tmp
=
inet_ntop
(
family
,
&
sockaddr
->
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
);
char
buffer
[
1024
];
char
*
tmp
;
struct
ifconf
ifc
;
struct
ifreq
*
ifr
;
ifc
.
ifc_len
=
sizeof
(
buffer
);
ifc
.
ifc_req
=
(
struct
ifreq
*
)
buffer
;
ioctl
(
source
,
SIOCGIFCONF
,
&
ifc
);
close
(
source
);
tmp
=
buffer
;
while
(
tmp
<
buffer
+
ifc
.
ifc_len
)
{
char
dst
[
INET_ADDRSTRLEN
];
ifr
=
(
struct
ifreq
*
)
tmp
;
tmp
+=
HX_SIZE_OF_IFREQ
(
*
ifr
);
if
(
ifr
->
ifr_addr
.
sa_family
==
AF_INET
)
{
struct
sockaddr_in
*
sinptr
=
(
struct
sockaddr_in
*
)
&
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 */
}
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_ip_atoi
(
ip
)
!=
NULL
))
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
);
}
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
gboolean
listen_map_external
=
TRUE
;
void
purple_network_listen_map_external
(
gboolean
map_external
)
{
listen_map_external
=
map_external
;
}
static
PurpleNetworkListenData
*
purple_network_do_listen
(
unsigned
short
port
,
int
socket_family
,
int
socket_type
,
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
)
||
!
listen_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
=
purple_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
=
purple_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_family
(
unsigned
short
port
,
int
socket_family
,
int
socket_type
,
PurpleNetworkListenCallback
cb
,
gpointer
cb_data
)
{
g_return_val_if_fail
(
port
!=
0
,
NULL
);
return
purple_network_do_listen
(
port
,
socket_family
,
socket_type
,
cb
,
cb_data
);
}
PurpleNetworkListenData
*
purple_network_listen
(
unsigned
short
port
,
int
socket_type
,
PurpleNetworkListenCallback
cb
,
gpointer
cb_data
)
{
return
purple_network_listen_family
(
port
,
AF_UNSPEC
,
socket_type
,
cb
,
cb_data
);
}
PurpleNetworkListenData
*
purple_network_listen_range_family
(
unsigned
short
start
,
unsigned
short
end
,
int
socket_family
,
int
socket_type
,
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
,
cb
,
cb_data
);
if
(
ret
!=
NULL
)
break
;
}
return
ret
;
}
PurpleNetworkListenData
*
purple_network_listen_range
(
unsigned
short
start
,
unsigned
short
end
,
int
socket_type
,
PurpleNetworkListenCallback
cb
,
gpointer
cb_data
)
{
return
purple_network_listen_range_family
(
start
,
end
,
AF_UNSPEC
,
socket_type
,
cb
,
cb_data
);
}
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
)
purple_timeout_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
);
}
#ifdef _WIN32
#ifndef NS_NLA
#define NS_NLA 15
#endif
static
gint
wpurple_get_connected_network_count
(
void
)
{
gint
net_cnt
=
0
;
WSAQUERYSET
qs
;
HANDLE
h
;
gint
retval
;
int
errorid
;
memset
(
&
qs
,
0
,
sizeof
(
WSAQUERYSET
));
qs
.
dwSize
=
sizeof
(
WSAQUERYSET
);
qs
.
dwNameSpace
=
NS_NLA
;
retval
=
WSALookupServiceBeginA
(
&
qs
,
LUP_RETURN_ALL
,
&
h
);
if
(
retval
!=
ERROR_SUCCESS
)
{
gchar
*
msg
;
errorid
=
WSAGetLastError
();
msg
=
g_win32_error_message
(
errorid
);
purple_debug_warning
(
"network"
,
"Couldn't retrieve NLA SP lookup handle. "
"NLA service is probably not running. Message: %s (%d).
\n
"
,
msg
,
errorid
);
g_free
(
msg
);
return
-1
;
}
else
{
gchar
*
buf
=
NULL
;
WSAQUERYSET
*
res
=
(
LPWSAQUERYSET
)
buf
;
DWORD
current_size
=
0
;
int
iteration_count
=
0
;
while
(
iteration_count
++
<
100
)
{
DWORD
size
=
current_size
;
retval
=
WSALookupServiceNextA
(
h
,
0
,
&
size
,
res
);
if
(
retval
==
ERROR_SUCCESS
)
{
net_cnt
++
;
purple_debug_info
(
"network"
,
"found network '%s'
\n
"
,
res
->
lpszServiceInstanceName
?
res
->
lpszServiceInstanceName
:
"(NULL)"
);
}
else
{
errorid
=
WSAGetLastError
();
if
(
errorid
==
WSAEFAULT
)
{
if
(
size
==
0
||
size
>
102400
)
{
purple_debug_warning
(
"network"
,
"Got unexpected NLA buffer size %"
G_GUINT32_FORMAT
".
\n
"
,
(
guint32
)
size
);
break
;
}
buf
=
g_realloc
(
buf
,
size
);
res
=
(
LPWSAQUERYSET
)
buf
;
current_size
=
size
;
}
else
{
break
;
}
}
}
g_free
(
buf
);
if
(
!
(
errorid
==
WSA_E_NO_MORE
||
errorid
==
WSAENOMORE
))
{
gchar
*
msg
=
g_win32_error_message
(
errorid
);
purple_debug_error
(
"network"
,
"got unexpected NLA response %s (%d)
\n
"
,
msg
,
errorid
);
g_free
(
msg
);
net_cnt
=
-1
;
}
retval
=
WSALookupServiceEnd
(
h
);
}
return
net_cnt
;
}
static
gboolean
wpurple_network_change_thread_cb
(
gpointer
data
)
{
gint
new_count
;
PurpleConnectionUiOps
*
ui_ops
=
purple_connections_get_ui_ops
();
new_count
=
wpurple_get_connected_network_count
();
if
(
new_count
<
0
)
return
FALSE
;
purple_debug_info
(
"network"
,
"Received Network Change Notification. Current network count is %d, previous count was %d.
\n
"
,
new_count
,
current_network_count
);
purple_signal_emit
(
purple_network_get_handle
(),
"network-configuration-changed"
,
NULL
);
if
(
new_count
>
0
&&
ui_ops
!=
NULL
&&
ui_ops
->
network_connected
!=
NULL
)
{
ui_ops
->
network_connected
();
}
else
if
(
new_count
==
0
&&
current_network_count
>
0
&&
ui_ops
!=
NULL
&&
ui_ops
->
network_disconnected
!=
NULL
)
{
ui_ops
->
network_disconnected
();
}
current_network_count
=
new_count
;
return
FALSE
;
}
static
gboolean
_print_debug_msg
(
gpointer
data
)
{
gchar
*
msg
=
data
;
purple_debug_warning
(
"network"
,
"%s"
,
msg
);
g_free
(
msg
);
return
FALSE
;
}
static
gpointer
wpurple_network_change_thread
(
gpointer
data
)
{
WSAQUERYSET
qs
;
WSAEVENT
*
nla_event
;
time_t
last_trigger
=
time
(
NULL
)
-
31
;
gchar
*
buf
=
NULL
;
WSAQUERYSET
*
res
=
(
LPWSAQUERYSET
)
buf
;
DWORD
current_size
=
0
;
if
((
nla_event
=
WSACreateEvent
())
==
WSA_INVALID_EVENT
)
{
int
errorid
=
WSAGetLastError
();
gchar
*
msg
=
g_win32_error_message
(
errorid
);
purple_timeout_add
(
0
,
_print_debug_msg
,
g_strdup_printf
(
"Couldn't create WSA event. "
"Message: %s (%d).
\n
"
,
msg
,
errorid
));
g_free
(
msg
);
g_thread_exit
(
NULL
);
return
NULL
;
}
while
(
TRUE
)
{
int
retval
;
int
iteration_count
;
DWORD
retLen
=
0
;
WSACOMPLETION
completion
;
WSAOVERLAPPED
overlapped
;
g_static_mutex_lock
(
&
mutex
);
if
(
network_initialized
==
FALSE
)
{
/* purple_network_uninit has been called */
WSACloseEvent
(
nla_event
);
g_static_mutex_unlock
(
&
mutex
);
g_thread_exit
(
NULL
);
return
NULL
;
}
if
(
network_change_handle
==
NULL
)
{
memset
(
&
qs
,
0
,
sizeof
(
WSAQUERYSET
));
qs
.
dwSize
=
sizeof
(
WSAQUERYSET
);
qs
.
dwNameSpace
=
NS_NLA
;
if
(
WSALookupServiceBeginA
(
&
qs
,
0
,
&
network_change_handle
)
==
SOCKET_ERROR
)
{
int
errorid
=
WSAGetLastError
();
gchar
*
msg
=
g_win32_error_message
(
errorid
);
purple_timeout_add
(
0
,
_print_debug_msg
,
g_strdup_printf
(
"Couldn't retrieve NLA SP lookup handle. "
"NLA service is probably not running. Message: %s (%d).
\n
"
,
msg
,
errorid
));
g_free
(
msg
);
WSACloseEvent
(
nla_event
);
g_static_mutex_unlock
(
&
mutex
);
g_thread_exit
(
NULL
);
return
NULL
;
}
}
g_static_mutex_unlock
(
&
mutex
);
memset
(
&
completion
,
0
,
sizeof
(
WSACOMPLETION
));
completion
.
Type
=
NSP_NOTIFY_EVENT
;
overlapped
.
hEvent
=
nla_event
;
completion
.
Parameters
.
Event
.
lpOverlapped
=
&
overlapped
;
if
(
MyWSANSPIoctl
(
network_change_handle
,
SIO_NSP_NOTIFY_CHANGE
,
NULL
,
0
,
NULL
,
0
,
&
retLen
,
&
completion
)
==
SOCKET_ERROR
)
{
int
errorid
=
WSAGetLastError
();
if
(
errorid
==
WSA_INVALID_HANDLE
)
{
purple_timeout_add
(
0
,
_print_debug_msg
,
g_strdup
(
"Invalid NLA handle; resetting.
\n
"
));
g_static_mutex_lock
(
&
mutex
);
retval
=
WSALookupServiceEnd
(
network_change_handle
);
network_change_handle
=
NULL
;
g_static_mutex_unlock
(
&
mutex
);
continue
;
/* WSA_IO_PENDING indicates successful async notification will happen */
}
else
if
(
errorid
!=
WSA_IO_PENDING
)
{
gchar
*
msg
=
g_win32_error_message
(
errorid
);
purple_timeout_add
(
0
,
_print_debug_msg
,
g_strdup_printf
(
"Unable to wait for changes. Message: %s (%d).
\n
"
,
msg
,
errorid
));
g_free
(
msg
);
}
}
/* Make sure at least 30 seconds have elapsed since the last
* notification so we don't peg the cpu if this keeps changing. */
if
((
time
(
NULL
)
-
last_trigger
)
<
30
)
Sleep
(
30000
);
/* This will block until NLA notifies us */
retval
=
WaitForSingleObjectEx
(
nla_event
,
WSA_INFINITE
,
TRUE
);
last_trigger
=
time
(
NULL
);
g_static_mutex_lock
(
&
mutex
);
if
(
network_initialized
==
FALSE
)
{
/* Time to die */
WSACloseEvent
(
nla_event
);
g_static_mutex_unlock
(
&
mutex
);
g_thread_exit
(
NULL
);
return
NULL
;
}
iteration_count
=
0
;
while
(
iteration_count
++
<
100
)
{
DWORD
size
=
current_size
;
retval
=
WSALookupServiceNextA
(
network_change_handle
,
0
,
&
size
,
res
);
if
(
retval
==
ERROR_SUCCESS
)
{
/*purple_timeout_add(0, _print_debug_msg,
g_strdup_printf("thread found network '%s'\n",
res->lpszServiceInstanceName ? res->lpszServiceInstanceName : "(NULL)"));*/
}
else
{
int
errorid
=
WSAGetLastError
();
if
(
errorid
==
WSAEFAULT
)
{
if
(
size
==
0
||
size
>
102400
)
{
purple_timeout_add
(
0
,
_print_debug_msg
,
g_strdup_printf
(
"Thread got unexpected NLA buffer size %"
G_GUINT32_FORMAT
".
\n
"
,
(
guint32
)
size
));
break
;
}
buf
=
g_realloc
(
buf
,
size
);
res
=
(
LPWSAQUERYSET
)
buf
;
current_size
=
size
;
}
else
{
break
;
}
}
}
g_free
(
buf
);
buf
=
NULL
;
current_size
=
0
;
WSAResetEvent
(
nla_event
);
g_static_mutex_unlock
(
&
mutex
);
purple_timeout_add
(
0
,
wpurple_network_change_thread_cb
,
NULL
);
}
g_thread_exit
(
NULL
);
return
NULL
;
}
#endif
gboolean
purple_network_is_available
(
void
)
{
#ifdef HAVE_NETWORKMANAGER
if
(
force_online
)
return
TRUE
;
if
(
!
have_nm_state
)
{
have_nm_state
=
TRUE
;
nm_state
=
nm_get_network_state
();
if
(
nm_state
==
NM_STATE_UNKNOWN
)
purple_debug_warning
(
"network"
,
"NetworkManager not active. Assuming connection exists.
\n
"
);
}
switch
(
nm_state
)
{
case
NM_STATE_UNKNOWN
:
#if NM_CHECK_VERSION(0,8,992)
case
NM_STATE_CONNECTED_LOCAL
:
case
NM_STATE_CONNECTED_SITE
:
case
NM_STATE_CONNECTED_GLOBAL
:
#else
case
NM_STATE_CONNECTED
:
#endif
return
TRUE
;
default
:
break
;
}
return
FALSE
;
#elif defined _WIN32
return
(
current_network_count
>
0
||
force_online
);
#else
return
TRUE
;
#endif
}
void
purple_network_force_online
()
{
#if defined(HAVE_NETWORKMANAGER) || defined(_WIN32)
force_online
=
TRUE
;
#endif
}
#ifdef HAVE_NETWORKMANAGER
static
void
nm_update_state
(
NMState
state
)
{
NMState
prev
=
nm_state
;
PurpleConnectionUiOps
*
ui_ops
=
purple_connections_get_ui_ops
();
have_nm_state
=
TRUE
;
nm_state
=
state
;
purple_signal_emit
(
purple_network_get_handle
(),
"network-configuration-changed"
,
NULL
);
switch
(
state
)
{
#if NM_CHECK_VERSION(0,8,992)
case
NM_STATE_CONNECTED_LOCAL
:
case
NM_STATE_CONNECTED_SITE
:
case
NM_STATE_CONNECTED_GLOBAL
:
#else
case
NM_STATE_CONNECTED
:
#endif
/* Call res_init in case DNS servers have changed */
res_init
();
/* update STUN IP in case we it changed (theoretically we could
have gone from IPv4 to IPv6, f.ex. or we were previously
offline */
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"
));
if
(
ui_ops
!=
NULL
&&
ui_ops
->
network_connected
!=
NULL
)
ui_ops
->
network_connected
();
break
;
case
NM_STATE_ASLEEP
:
case
NM_STATE_CONNECTING
:
case
NM_STATE_DISCONNECTED
:
#if NM_CHECK_VERSION(0,8,992)
case
NM_STATE_DISCONNECTING
:
#endif
#if NM_CHECK_VERSION(1,0,0)
if
(
prev
!=
NM_STATE_CONNECTED_GLOBAL
&&
prev
!=
NM_STATE_UNKNOWN
)
break
;
#else
if
(
prev
!=
NM_STATE_CONNECTED
&&
prev
!=
NM_STATE_UNKNOWN
)
break
;
#endif
if
(
ui_ops
!=
NULL
&&
ui_ops
->
network_disconnected
!=
NULL
)
ui_ops
->
network_disconnected
();
break
;
case
NM_STATE_UNKNOWN
:
default
:
break
;
}
}
static
void
nm_state_change_cb
(
DBusGProxy
*
proxy
,
NMState
state
,
gpointer
user_data
)
{
purple_debug_info
(
"network"
,
"Got StateChange from NetworkManager: %d.
\n
"
,
state
);
nm_update_state
(
state
);
}
static
NMState
nm_get_network_state
(
void
)
{
GError
*
err
=
NULL
;
NMState
state
=
NM_STATE_UNKNOWN
;
if
(
!
nm_proxy
)
return
NM_STATE_UNKNOWN
;
if
(
!
dbus_g_proxy_call
(
nm_proxy
,
"state"
,
&
err
,
G_TYPE_INVALID
,
G_TYPE_UINT
,
&
state
,
G_TYPE_INVALID
))
{
g_error_free
(
err
);
return
NM_STATE_UNKNOWN
;
}
return
state
;
}
static
void
nm_dbus_name_owner_changed_cb
(
DBusGProxy
*
proxy
,
char
*
service
,
char
*
old_owner
,
char
*
new_owner
,
gpointer
user_data
)
{
if
(
purple_strequal
(
service
,
NM_DBUS_SERVICE
))
{
gboolean
old_owner_good
=
old_owner
&&
(
old_owner
[
0
]
!=
'\0'
);
gboolean
new_owner_good
=
new_owner
&&
(
new_owner
[
0
]
!=
'\0'
);
purple_debug_info
(
"network"
,
"Got NameOwnerChanged signal, service = '%s', old_owner = '%s', new_owner = '%s'
\n
"
,
service
,
old_owner
,
new_owner
);
if
(
!
old_owner_good
&&
new_owner_good
)
{
/* Equivalent to old ServiceCreated signal */
purple_debug_info
(
"network"
,
"NetworkManager has started.
\n
"
);
nm_update_state
(
nm_get_network_state
());
}
else
if
(
old_owner_good
&&
!
new_owner_good
)
{
/* Equivalent to old ServiceDeleted signal */
purple_debug_info
(
"network"
,
"NetworkManager has gone away.
\n
"
);
nm_update_state
(
NM_STATE_UNKNOWN
);
}
}
}
#endif
static
void
purple_network_ip_lookup_cb
(
GSList
*
hosts
,
gpointer
data
,
const
char
*
error_message
)
{
const
gchar
**
ip
=
(
const
gchar
**
)
data
;
if
(
error_message
)
{
purple_debug_error
(
"network"
,
"lookup of IP address failed: %s
\n
"
,
error_message
);
g_slist_free
(
hosts
);
return
;
}
if
(
hosts
&&
g_slist_next
(
hosts
))
{
struct
sockaddr
*
addr
=
g_slist_next
(
hosts
)
->
data
;
char
dst
[
INET6_ADDRSTRLEN
];
if
(
addr
->
sa_family
==
AF_INET6
)
{
inet_ntop
(
addr
->
sa_family
,
&
((
struct
sockaddr_in6
*
)
addr
)
->
sin6_addr
,
dst
,
sizeof
(
dst
));
}
else
{
inet_ntop
(
addr
->
sa_family
,
&
((
struct
sockaddr_in
*
)
addr
)
->
sin_addr
,
dst
,
sizeof
(
dst
));
}
*
ip
=
g_strdup
(
dst
);
purple_debug_info
(
"network"
,
"set IP address: %s
\n
"
,
*
ip
);
}
while
(
hosts
!=
NULL
)
{
hosts
=
g_slist_delete_link
(
hosts
,
hosts
);
/* Free the address */
g_free
(
hosts
->
data
);
hosts
=
g_slist_delete_link
(
hosts
,
hosts
);
}
}
void
purple_network_set_stun_server
(
const
gchar
*
stun_server
)
{
if
(
stun_server
&&
stun_server
[
0
]
!=
'\0'
)
{
if
(
purple_network_is_available
())
{
purple_debug_info
(
"network"
,
"running DNS query for STUN server
\n
"
);
purple_dnsquery_a_account
(
NULL
,
stun_server
,
3478
,
purple_network_ip_lookup_cb
,
&
stun_ip
);
}
else
{
purple_debug_info
(
"network"
,
"network is unavailable, don't try to update STUN IP"
);
}
}
else
if
(
stun_ip
)
{
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
())
{
purple_debug_info
(
"network"
,
"running DNS query for TURN server
\n
"
);
purple_dnsquery_a_account
(
NULL
,
turn_server
,
purple_prefs_get_int
(
"/purple/network/turn_port"
),
purple_network_ip_lookup_cb
,
&
turn_ip
);
}
else
{
purple_debug_info
(
"network"
,
"network is unavailable, don't try to update TURN IP"
);
}
}
else
if
(
turn_ip
)
{
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
);
}
}
}
int
purple_network_convert_idn_to_ascii
(
const
gchar
*
in
,
gchar
**
out
)
{
#ifdef USE_IDN
char
*
tmp
;
int
ret
;
g_return_val_if_fail
(
out
!=
NULL
,
-1
);
ret
=
idna_to_ascii_8z
(
in
,
&
tmp
,
IDNA_USE_STD3_ASCII_RULES
);
if
(
ret
!=
IDNA_SUCCESS
)
{
*
out
=
NULL
;
return
ret
;
}
*
out
=
g_strdup
(
tmp
);
/* This *MUST* be freed with free, not g_free */
free
(
tmp
);
return
0
;
#else
g_return_val_if_fail
(
out
!=
NULL
,
-1
);
*
out
=
g_strdup
(
in
);
return
0
;
#endif
}
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
)
{
#ifdef HAVE_NETWORKMANAGER
GError
*
error
=
NULL
;
#endif
#ifdef _WIN32
GError
*
err
=
NULL
;
gint
cnt
=
wpurple_get_connected_network_count
();
network_initialized
=
TRUE
;
if
(
cnt
<
0
)
/* Assume there is a network */
current_network_count
=
1
;
/* Don't listen for network changes if we can't tell anyway */
else
{
current_network_count
=
cnt
;
if
((
MyWSANSPIoctl
=
(
void
*
)
wpurple_find_and_loadproc
(
"ws2_32.dll"
,
"WSANSPIoctl"
)))
{
if
(
!
g_thread_create
(
wpurple_network_change_thread
,
NULL
,
FALSE
,
&
err
))
purple_debug_error
(
"network"
,
"Couldn't create Network Monitor thread: %s
\n
"
,
err
?
err
->
message
:
""
);
}
}
#endif
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
);
#ifdef HAVE_NETWORKMANAGER
nm_conn
=
dbus_g_bus_get
(
DBUS_BUS_SYSTEM
,
&
error
);
if
(
!
nm_conn
)
{
purple_debug_warning
(
"network"
,
"Error connecting to DBus System service: %s.
\n
"
,
error
->
message
);
}
else
{
nm_proxy
=
dbus_g_proxy_new_for_name
(
nm_conn
,
NM_DBUS_SERVICE
,
NM_DBUS_PATH
,
NM_DBUS_INTERFACE
);
/* NM 0.6 signal */
dbus_g_proxy_add_signal
(
nm_proxy
,
"StateChange"
,
G_TYPE_UINT
,
G_TYPE_INVALID
);
dbus_g_proxy_connect_signal
(
nm_proxy
,
"StateChange"
,
G_CALLBACK
(
nm_state_change_cb
),
NULL
,
NULL
);
/* NM 0.7 and later signal */
dbus_g_proxy_add_signal
(
nm_proxy
,
"StateChanged"
,
G_TYPE_UINT
,
G_TYPE_INVALID
);
dbus_g_proxy_connect_signal
(
nm_proxy
,
"StateChanged"
,
G_CALLBACK
(
nm_state_change_cb
),
NULL
,
NULL
);
dbus_proxy
=
dbus_g_proxy_new_for_name
(
nm_conn
,
DBUS_SERVICE_DBUS
,
DBUS_PATH_DBUS
,
DBUS_INTERFACE_DBUS
);
dbus_g_proxy_add_signal
(
dbus_proxy
,
"NameOwnerChanged"
,
G_TYPE_STRING
,
G_TYPE_STRING
,
G_TYPE_STRING
,
G_TYPE_INVALID
);
dbus_g_proxy_connect_signal
(
dbus_proxy
,
"NameOwnerChanged"
,
G_CALLBACK
(
nm_dbus_name_owner_changed_cb
),
NULL
,
NULL
);
}
#endif
purple_signal_register
(
purple_network_get_handle
(),
"network-configuration-changed"
,
purple_marshal_VOID
,
NULL
,
0
);
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
)
{
#ifdef HAVE_NETWORKMANAGER
if
(
nm_proxy
)
{
dbus_g_proxy_disconnect_signal
(
nm_proxy
,
"StateChange"
,
G_CALLBACK
(
nm_state_change_cb
),
NULL
);
dbus_g_proxy_disconnect_signal
(
nm_proxy
,
"StateChanged"
,
G_CALLBACK
(
nm_state_change_cb
),
NULL
);
g_object_unref
(
G_OBJECT
(
nm_proxy
));
}
if
(
dbus_proxy
)
{
dbus_g_proxy_disconnect_signal
(
dbus_proxy
,
"NameOwnerChanged"
,
G_CALLBACK
(
nm_dbus_name_owner_changed_cb
),
NULL
);
g_object_unref
(
G_OBJECT
(
dbus_proxy
));
}
if
(
nm_conn
)
dbus_g_connection_unref
(
nm_conn
);
#endif
#ifdef _WIN32
g_static_mutex_lock
(
&
mutex
);
network_initialized
=
FALSE
;
if
(
network_change_handle
!=
NULL
)
{
int
retval
;
/* Trigger the NLA thread to stop waiting for network changes. Not
* doing this can cause hangs on WSACleanup. */
purple_debug_warning
(
"network"
,
"Terminating the NLA thread
\n
"
);
if
((
retval
=
WSALookupServiceEnd
(
network_change_handle
))
==
SOCKET_ERROR
)
{
int
errorid
=
WSAGetLastError
();
gchar
*
msg
=
g_win32_error_message
(
errorid
);
purple_debug_warning
(
"network"
,
"Unable to kill NLA thread. Message: %s (%d).
\n
"
,
msg
,
errorid
);
g_free
(
msg
);
}
network_change_handle
=
NULL
;
}
g_static_mutex_unlock
(
&
mutex
);
#endif
purple_signal_unregister
(
purple_network_get_handle
(),
"network-configuration-changed"
);
if
(
stun_ip
)
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... */
}