1
0
mirror of https://github.com/weechat/weechat.git synced 2026-06-25 20:36:38 +02:00
Files
weechat/src/core/wee-network.c
T
Sébastien Helleu 413aa499cc core: add documentation on command line debug options (in --help and man pages)
Command line debug options are now documented:

* --no-dlclose: do not call the function dlclose after plugins are unloaded
* --no-gnutls: do not call the init and deinit functions of GnuTLS library
* --no-gcrypt: do not call the init and deinit functions of Gcrypt library

These options must not be used in production, they're for debug only and can be
used with tools like Valgrind or electric-fence.
2019-03-31 23:53:54 +02:00

1791 lines
56 KiB
C

/*
* wee-network.c - network functions
*
* Copyright (C) 2003-2019 Sébastien Helleu <flashcode@flashtux.org>
* Copyright (C) 2005-2010 Emmanuel Bouthenot <kolter@openics.org>
* Copyright (C) 2010 Gu1ll4um3r0m41n <aeroxteam@gmail.com>
* Copyright (C) 2012 Simon Arlott
*
* This file is part of WeeChat, the extensible chat client.
*
* WeeChat 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 3 of the License, or
* (at your option) any later version.
*
* WeeChat 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 WeeChat. If not, see <https://www.gnu.org/licenses/>.
*/
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
/* _XPG4_2 is needed on SunOS for macros like CMSG_SPACE */
/* __EXTENSIONS__ is needed on SunOS for constants like NI_MAXHOST */
#ifdef __sun
#define _XPG4_2
#define __EXTENSIONS__
#endif
#include <unistd.h>
#include <fcntl.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <poll.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <arpa/nameser.h>
#include <netdb.h>
#include <resolv.h>
#include <errno.h>
#include <gcrypt.h>
#include <sys/time.h>
#if defined(__OpenBSD__)
#include <sys/uio.h>
#endif
#ifdef HAVE_GNUTLS
#include <gnutls/gnutls.h>
#endif
#include "weechat.h"
#include "wee-network.h"
#include "wee-eval.h"
#include "wee-hook.h"
#include "wee-config.h"
#include "wee-proxy.h"
#include "wee-string.h"
#include "../plugins/plugin.h"
int network_init_gnutls_ok = 0;
#ifdef HAVE_GNUTLS
gnutls_certificate_credentials_t gnutls_xcred; /* GnuTLS client credentials */
#endif /* HAVE_GNUTLS */
/*
* Initializes gcrypt.
*/
void
network_init_gcrypt ()
{
if (weechat_no_gcrypt)
return;
gcry_check_version (GCRYPT_VERSION);
gcry_control (GCRYCTL_DISABLE_SECMEM, 0);
gcry_control (GCRYCTL_INITIALIZATION_FINISHED, 0);
}
/*
* Sets trust file with option "gnutls_ca_file".
*/
void
network_set_gnutls_ca_file ()
{
#ifdef HAVE_GNUTLS
char *ca_path, *ca_path2;
if (weechat_no_gnutls)
return;
ca_path = string_expand_home (CONFIG_STRING(config_network_gnutls_ca_file));
if (ca_path)
{
ca_path2 = string_replace (ca_path, "%h", weechat_home);
if (ca_path2)
{
gnutls_certificate_set_x509_trust_file (gnutls_xcred, ca_path2,
GNUTLS_X509_FMT_PEM);
free (ca_path2);
}
free (ca_path);
}
#endif /* HAVE_GNUTLS */
}
/*
* Initializes GnuTLS.
*/
void
network_init_gnutls ()
{
#ifdef HAVE_GNUTLS
if (!weechat_no_gnutls)
{
gnutls_global_init ();
gnutls_certificate_allocate_credentials (&gnutls_xcred);
network_set_gnutls_ca_file ();
#if LIBGNUTLS_VERSION_NUMBER >= 0x02090a /* 2.9.10 */
gnutls_certificate_set_verify_function (gnutls_xcred,
&hook_connect_gnutls_verify_certificates);
#endif /* LIBGNUTLS_VERSION_NUMBER >= 0x02090a */
#if LIBGNUTLS_VERSION_NUMBER >= 0x020b00 /* 2.11.0 */
gnutls_certificate_set_retrieve_function (gnutls_xcred,
&hook_connect_gnutls_set_certificates);
#else
gnutls_certificate_client_set_retrieve_function (gnutls_xcred,
&hook_connect_gnutls_set_certificates);
#endif /* LIBGNUTLS_VERSION_NUMBER >= 0x020b00 */
}
#endif /* HAVE_GNUTLS */
network_init_gnutls_ok = 1;
}
/*
* Ends network.
*/
void
network_end ()
{
if (network_init_gnutls_ok)
{
#ifdef HAVE_GNUTLS
if (!weechat_no_gnutls)
{
gnutls_certificate_free_credentials (gnutls_xcred);
gnutls_global_deinit ();
}
#endif /* HAVE_GNUTLS */
network_init_gnutls_ok = 0;
}
}
/*
* Sends data on a socket with retry.
*
* WARNING: this function is blocking, it must be called only in a forked
* process.
*
* Returns number of bytes sent, -1 if error.
*/
int
network_send_with_retry (int sock, const void *buffer, int length, int flags)
{
int total_sent, num_sent;
total_sent = 0;
num_sent = send (sock, buffer, length, flags);
if (num_sent > 0)
total_sent += num_sent;
while (total_sent < length)
{
if ((num_sent == -1) && (errno != EAGAIN) && (errno != EWOULDBLOCK))
return total_sent;
usleep (100);
num_sent = send (sock, buffer + total_sent, length - total_sent, flags);
if (num_sent > 0)
total_sent += num_sent;
}
return total_sent;
}
/*
* Receives data on a socket with retry.
*
* WARNING: this function is blocking, it must be called only in a forked
* process.
*
* Returns number of bytes received, -1 if error.
*/
int
network_recv_with_retry (int sock, void *buffer, int length, int flags)
{
int total_recv, num_recv;
total_recv = 0;
num_recv = recv (sock, buffer, length, flags);
if (num_recv > 0)
total_recv += num_recv;
while (num_recv == -1)
{
if ((errno != EAGAIN) && (errno != EWOULDBLOCK))
return total_recv;
usleep (100);
num_recv = recv (sock, buffer + total_recv, length - total_recv, flags);
if (num_recv > 0)
total_recv += num_recv;
}
return total_recv;
}
/*
* Establishes a connection and authenticates with a HTTP proxy.
*
* WARNING: this function is blocking, it must be called only in a forked
* process.
*
* Returns:
* 1: OK
* 0: error
*/
int
network_pass_httpproxy (struct t_proxy *proxy, int sock, const char *address,
int port)
{
char buffer[256], authbuf[128], authbuf_base64[512], *username, *password;
int length;
if (CONFIG_STRING(proxy->options[PROXY_OPTION_USERNAME])
&& CONFIG_STRING(proxy->options[PROXY_OPTION_USERNAME])[0])
{
/* authentication */
username = eval_expression (CONFIG_STRING(proxy->options[PROXY_OPTION_USERNAME]),
NULL, NULL, NULL);
if (!username)
return 0;
password = eval_expression (CONFIG_STRING(proxy->options[PROXY_OPTION_PASSWORD]),
NULL, NULL, NULL);
if (!password)
{
free (username);
return 0;
}
snprintf (authbuf, sizeof (authbuf), "%s:%s", username, password);
free (username);
free (password);
if (string_base64_encode (authbuf, strlen (authbuf), authbuf_base64) < 0)
return 0;
length = snprintf (buffer, sizeof (buffer),
"CONNECT %s:%d HTTP/1.0\r\nProxy-Authorization: "
"Basic %s\r\n\r\n",
address, port, authbuf_base64);
}
else
{
/* no authentication */
length = snprintf (buffer, sizeof (buffer),
"CONNECT %s:%d HTTP/1.0\r\n\r\n", address, port);
}
if (network_send_with_retry (sock, buffer, length, 0) != length)
return 0;
/* success result must be like: "HTTP/1.0 200 OK" */
if (network_recv_with_retry (sock, buffer, sizeof (buffer), 0) < 12)
return 0;
if (memcmp (buffer, "HTTP/", 5) || memcmp (buffer + 9, "200", 3))
return 0;
/* connection OK */
return 1;
}
/*
* Resolves a hostname to its IP address (works with IPv4 and IPv6).
*
* Returns:
* 1: OK
* 0: error
*/
int
network_resolve (const char *hostname, char *ip, int *version)
{
char ipbuffer[NI_MAXHOST];
struct addrinfo *res;
if (version != NULL)
*version = 0;
res = NULL;
res_init ();
if (getaddrinfo (hostname, NULL, NULL, &res) != 0)
return 0;
if (!res)
return 0;
if (getnameinfo (res->ai_addr, res->ai_addrlen, ipbuffer, sizeof (ipbuffer),
NULL, 0, NI_NUMERICHOST) != 0)
{
freeaddrinfo (res);
return 0;
}
if ((res->ai_family == AF_INET) && (version != NULL))
*version = 4;
if ((res->ai_family == AF_INET6) && (version != NULL))
*version = 6;
strcpy (ip, ipbuffer);
freeaddrinfo (res);
/* resolution OK */
return 1;
}
/*
* Establishes a connection and authenticates with a socks4 proxy.
*
* The socks4 protocol is explained here: https://en.wikipedia.org/wiki/SOCKS
*
* WARNING: this function is blocking, it must be called only in a forked
* process.
*
* Returns:
* 1: OK
* 0: error
*/
int
network_pass_socks4proxy (struct t_proxy *proxy, int sock, const char *address,
int port)
{
struct t_network_socks4 socks4;
unsigned char buffer[24];
char ip_addr[NI_MAXHOST], *username;
int length;
username = eval_expression (CONFIG_STRING(proxy->options[PROXY_OPTION_USERNAME]),
NULL, NULL, NULL);
if (!username)
return 0;
socks4.version = 4;
socks4.method = 1;
socks4.port = htons (port);
network_resolve (address, ip_addr, NULL);
socks4.address = inet_addr (ip_addr);
strncpy (socks4.user, username, sizeof (socks4.user) - 1);
free (username);
length = 8 + strlen (socks4.user) + 1;
if (network_send_with_retry (sock, (char *) &socks4, length, 0) != length)
return 0;
if (network_recv_with_retry (sock, buffer, sizeof (buffer), 0) < 2)
return 0;
/* connection OK */
if ((buffer[0] == 0) && (buffer[1] == 90))
return 1;
/* connection failed */
return 0;
}
/*
* Establishes a connection and authenticates with a socks5 proxy.
*
* The socks5 protocol is explained in RFC 1928.
* The socks5 authentication with username/pass is explained in RFC 1929.
*
* WARNING: this function is blocking, it must be called only in a forked
* process.
*
* Returns:
* 1: OK
* 0: error
*/
int
network_pass_socks5proxy (struct t_proxy *proxy, int sock, const char *address,
int port)
{
struct t_network_socks5 socks5;
unsigned char buffer[288];
int username_len, password_len, addr_len, addr_buffer_len;
unsigned char *addr_buffer;
char *username, *password;
socks5.version = 5;
socks5.nmethods = 1;
if (CONFIG_STRING(proxy->options[PROXY_OPTION_USERNAME])
&& CONFIG_STRING(proxy->options[PROXY_OPTION_USERNAME])[0])
socks5.method = 2; /* with authentication */
else
socks5.method = 0; /* without authentication */
if (network_send_with_retry (sock, (char *) &socks5, sizeof (socks5), 0) < (int)sizeof (socks5))
return 0;
/* server socks5 must respond with 2 bytes */
if (network_recv_with_retry (sock, buffer, 2, 0) < 2)
return 0;
if (CONFIG_STRING(proxy->options[PROXY_OPTION_USERNAME])
&& CONFIG_STRING(proxy->options[PROXY_OPTION_USERNAME])[0])
{
/*
* with authentication
* -> socks server must respond with :
* - socks version (buffer[0]) = 5 => socks5
* - socks method (buffer[1]) = 2 => authentication
*/
if (buffer[0] != 5 || buffer[1] != 2)
return 0;
/* authentication as in RFC 1929 */
username = eval_expression (CONFIG_STRING(proxy->options[PROXY_OPTION_USERNAME]),
NULL, NULL, NULL);
if (!username)
return 0;
password = eval_expression (CONFIG_STRING(proxy->options[PROXY_OPTION_PASSWORD]),
NULL, NULL, NULL);
if (!password)
{
free (username);
return 0;
}
username_len = strlen (username);
password_len = strlen (password);
/* make username/password buffer */
buffer[0] = 1;
buffer[1] = (unsigned char) username_len;
memcpy (buffer + 2, username, username_len);
buffer[2 + username_len] = (unsigned char) password_len;
memcpy (buffer + 3 + username_len, password, password_len);
free (username);
free (password);
if (network_send_with_retry (sock, buffer, 3 + username_len + password_len, 0) < 3 + username_len + password_len)
return 0;
/* server socks5 must respond with 2 bytes */
if (network_recv_with_retry (sock, buffer, 2, 0) < 2)
return 0;
/* buffer[1] = auth state, must be 0 for success */
if (buffer[1] != 0)
return 0;
}
else
{
/*
* without authentication
* -> socks server must respond with :
* - socks version (buffer[0]) = 5 => socks5
* - socks method (buffer[1]) = 0 => no authentication
*/
if (!((buffer[0] == 5) && (buffer[1] == 0)))
return 0;
}
/* authentication successful then giving address/port to connect */
addr_len = strlen (address);
addr_buffer_len = 4 + 1 + addr_len + 2;
addr_buffer = malloc (addr_buffer_len * sizeof (*addr_buffer));
if (!addr_buffer)
return 0;
addr_buffer[0] = 5; /* version 5 */
addr_buffer[1] = 1; /* command: 1 for connect */
addr_buffer[2] = 0; /* reserved */
addr_buffer[3] = 3; /* address type : ipv4 (1), domainname (3), ipv6 (4) */
addr_buffer[4] = (unsigned char) addr_len;
memcpy (addr_buffer + 5, address, addr_len); /* server address */
*((unsigned short *) (addr_buffer + 5 + addr_len)) = htons (port); /* server port */
if (network_send_with_retry (sock, addr_buffer, addr_buffer_len, 0) < addr_buffer_len)
{
free (addr_buffer);
return 0;
}
free (addr_buffer);
/* dialog with proxy server */
if (network_recv_with_retry (sock, buffer, 4, 0) < 4)
return 0;
if (!((buffer[0] == 5) && (buffer[1] == 0)))
return 0;
/* buffer[3] = address type */
switch (buffer[3])
{
case 1:
/*
* ipv4
* server socks return server bound address and port
* address of 4 bytes and port of 2 bytes (= 6 bytes)
*/
if (network_recv_with_retry (sock, buffer, 6, 0) < 6)
return 0;
break;
case 3:
/*
* domainname
* server socks return server bound address and port
*/
/* read address length */
if (network_recv_with_retry (sock, buffer, 1, 0) < 1)
return 0;
addr_len = buffer[0];
/* read address + port = addr_len + 2 */
if (network_recv_with_retry (sock, buffer, addr_len + 2, 0) < addr_len + 2)
return 0;
break;
case 4:
/*
* ipv6
* server socks return server bound address and port
* address of 16 bytes and port of 2 bytes (= 18 bytes)
*/
if (network_recv_with_retry (sock, buffer, 18, 0) < 18)
return 0;
break;
default:
return 0;
}
/* connection OK */
return 1;
}
/*
* Establishes a connection and authenticates with a proxy.
*
* WARNING: this function is blocking, it must be called only in a forked
* process.
*
* Returns:
* 1: OK
* 0: error
*/
int
network_pass_proxy (const char *proxy, int sock, const char *address, int port)
{
int rc;
struct t_proxy *ptr_proxy;
rc = 0;
ptr_proxy = proxy_search (proxy);
if (ptr_proxy)
{
switch (CONFIG_INTEGER(ptr_proxy->options[PROXY_OPTION_TYPE]))
{
case PROXY_TYPE_HTTP:
rc = network_pass_httpproxy (ptr_proxy, sock, address, port);
break;
case PROXY_TYPE_SOCKS4:
rc = network_pass_socks4proxy (ptr_proxy, sock, address, port);
break;
case PROXY_TYPE_SOCKS5:
rc = network_pass_socks5proxy (ptr_proxy, sock, address, port);
break;
}
}
return rc;
}
/*
* Connects to a remote host and wait for connection if socket is non blocking.
*
* WARNING: this function is blocking, it must be called only in a forked
* process.
*
* Returns:
* 1: OK
* 0: error
*/
int
network_connect (int sock, const struct sockaddr *addr, socklen_t addrlen)
{
struct pollfd poll_fd;
int ready, value;
socklen_t len;
if (connect (sock, addr, addrlen) == 0)
return 1;
if (errno != EINPROGRESS)
return 0;
/*
* for non-blocking sockets, the connect() may fail with EINPROGRESS,
* if this happens, we wait for writability on socket and check
* the option SO_ERROR, which is 0 if connect is OK (see man connect)
*/
while (1)
{
poll_fd.fd = sock;
poll_fd.events = POLLOUT;
poll_fd.revents = 0;
ready = poll (&poll_fd, 1, -1);
if (ready < 0)
break;
if (ready > 0)
{
len = sizeof (value);
if (getsockopt (sock, SOL_SOCKET, SO_ERROR, &value, &len) == 0)
{
return (value == 0) ? 1 : 0;
}
}
}
return 0;
}
/*
* Connects to a remote host.
*
* WARNING: this function is blocking, it must be called only in a forked
* process.
*
* Returns:
* >= 0: connected socket fd
* -1: error
*/
int
network_connect_to (const char *proxy, struct sockaddr *address,
socklen_t address_length)
{
struct t_proxy *ptr_proxy;
struct addrinfo *proxy_addrinfo, hints;
char str_port[16], ip[NI_MAXHOST];
int port, sock;
sock = -1;
proxy_addrinfo = NULL;
if (!address || (address_length == 0))
return -1;
ptr_proxy = NULL;
if (proxy && proxy[0])
{
ptr_proxy = proxy_search (proxy);
if (!ptr_proxy)
return -1;
}
if (ptr_proxy)
{
/* get IP address/port */
if (getnameinfo (address, address_length, ip, sizeof (ip),
str_port, sizeof (str_port),
NI_NUMERICHOST | NI_NUMERICSERV) != 0)
{
goto error;
}
port = atoi (str_port);
/* get sockaddr for proxy */
memset (&hints, 0, sizeof (hints));
hints.ai_family = AF_UNSPEC;
hints.ai_socktype = SOCK_STREAM;
hints.ai_flags = AI_NUMERICSERV;
snprintf (str_port, sizeof (str_port), "%d",
CONFIG_INTEGER(ptr_proxy->options[PROXY_OPTION_PORT]));
res_init ();
if (getaddrinfo (CONFIG_STRING(ptr_proxy->options[PROXY_OPTION_ADDRESS]),
str_port, &hints, &proxy_addrinfo) != 0)
{
goto error;
}
/* connect and pass address to proxy */
sock = socket (proxy_addrinfo->ai_family, SOCK_STREAM, 0);
if (sock == -1)
goto error;
if (!network_connect (sock, proxy_addrinfo->ai_addr,
proxy_addrinfo->ai_addrlen))
goto error;
if (!network_pass_proxy (proxy, sock, ip, port))
goto error;
}
else
{
sock = socket (address->sa_family, SOCK_STREAM, 0);
if (sock == -1)
goto error;
if (!network_connect (sock, address, address_length))
goto error;
}
if (proxy_addrinfo)
freeaddrinfo (proxy_addrinfo);
return sock;
error:
if (sock >= 0)
close (sock);
if (proxy_addrinfo)
freeaddrinfo (proxy_addrinfo);
return -1;
}
/*
* Connects to peer in a child process.
*/
void
network_connect_child (struct t_hook *hook_connect)
{
struct t_proxy *ptr_proxy;
struct addrinfo hints, *res_local, *res_remote, *ptr_res, *ptr_loc;
char port[NI_MAXSERV + 1];
char status_str[2], *ptr_address, *status_with_string;
char remote_address[NI_MAXHOST + 1];
char status_without_string[1 + 5 + 1];
const char *error;
int rc, length, num_written;
int sock, set, flags, j;
struct msghdr msg;
struct cmsghdr *cmsg;
char msg_buf[CMSG_SPACE(sizeof (sock))];
struct iovec iov[1];
char iov_data[1] = { 0 };
/*
* indicates that something is wrong with whichever group of
* servers is being tried first after connecting, so start at
* a different offset to increase the chance of success
*/
int retry, rand_num, i;
int num_groups, tmp_num_groups, num_hosts, tmp_host;
struct addrinfo **res_reorder;
int last_af;
struct timeval tv_time;
res_local = NULL;
res_remote = NULL;
res_reorder = NULL;
port[0] = '\0';
status_str[1] = '\0';
status_with_string = NULL;
ptr_address = NULL;
gettimeofday (&tv_time, NULL);
srand ((tv_time.tv_sec * tv_time.tv_usec) ^ getpid ());
ptr_proxy = NULL;
if (HOOK_CONNECT(hook_connect, proxy)
&& HOOK_CONNECT(hook_connect, proxy)[0])
{
ptr_proxy = proxy_search (HOOK_CONNECT(hook_connect, proxy));
if (!ptr_proxy)
{
/* proxy not found */
snprintf (status_without_string, sizeof (status_without_string),
"%c00000", '0' + WEECHAT_HOOK_CONNECT_PROXY_ERROR);
num_written = write (HOOK_CONNECT(hook_connect, child_write),
status_without_string, strlen (status_without_string));
(void) num_written;
goto end;
}
}
/* get info about peer */
memset (&hints, 0, sizeof (hints));
hints.ai_socktype = SOCK_STREAM;
#ifdef AI_ADDRCONFIG
hints.ai_flags = AI_ADDRCONFIG;
#endif /* AI_ADDRCONFIG */
res_init ();
if (ptr_proxy)
{
hints.ai_family = (CONFIG_BOOLEAN(ptr_proxy->options[PROXY_OPTION_IPV6])) ?
AF_UNSPEC : AF_INET;
snprintf (port, sizeof (port), "%d", CONFIG_INTEGER(ptr_proxy->options[PROXY_OPTION_PORT]));
rc = getaddrinfo (CONFIG_STRING(ptr_proxy->options[PROXY_OPTION_ADDRESS]),
port, &hints, &res_remote);
}
else
{
hints.ai_family = HOOK_CONNECT(hook_connect, ipv6) ? AF_UNSPEC : AF_INET;
snprintf (port, sizeof (port), "%d", HOOK_CONNECT(hook_connect, port));
rc = getaddrinfo (HOOK_CONNECT(hook_connect, address), port, &hints, &res_remote);
}
if (rc != 0)
{
/* address not found */
status_with_string = NULL;
error = gai_strerror (rc);
if (error)
{
length = 1 + 5 + strlen (error) + 1;
status_with_string = malloc (length);
if (status_with_string)
{
snprintf (status_with_string, length, "%c%05d%s",
'0' + WEECHAT_HOOK_CONNECT_ADDRESS_NOT_FOUND,
(int)strlen (error), error);
}
}
if (status_with_string)
{
num_written = write (HOOK_CONNECT(hook_connect, child_write),
status_with_string, strlen (status_with_string));
}
else
{
snprintf (status_without_string, sizeof (status_without_string),
"%c00000", '0' + WEECHAT_HOOK_CONNECT_ADDRESS_NOT_FOUND);
num_written = write (HOOK_CONNECT(hook_connect, child_write),
status_without_string, strlen (status_without_string));
}
(void) num_written;
goto end;
}
if (!res_remote)
{
/* address not found */
snprintf (status_without_string, sizeof (status_without_string),
"%c00000", '0' + WEECHAT_HOOK_CONNECT_ADDRESS_NOT_FOUND);
num_written = write (HOOK_CONNECT(hook_connect, child_write),
status_without_string, strlen (status_without_string));
(void) num_written;
goto end;
}
/* set local hostname/IP if asked by user */
if (HOOK_CONNECT(hook_connect, local_hostname)
&& HOOK_CONNECT(hook_connect, local_hostname[0]))
{
memset (&hints, 0, sizeof (hints));
hints.ai_family = AF_UNSPEC;
hints.ai_socktype = SOCK_STREAM;
#ifdef AI_ADDRCONFIG
hints.ai_flags = AI_ADDRCONFIG;
#endif /* AI_ADDRCONFIG */
rc = getaddrinfo (HOOK_CONNECT(hook_connect, local_hostname),
NULL, &hints, &res_local);
if (rc != 0)
{
/* address not found */
status_with_string = NULL;
error = gai_strerror (rc);
if (error)
{
length = 1 + 5 + strlen (error) + 1;
status_with_string = malloc (length);
if (status_with_string)
{
snprintf (status_with_string, length, "%c%05d%s",
'0' + WEECHAT_HOOK_CONNECT_LOCAL_HOSTNAME_ERROR,
(int)strlen (error), error);
}
}
if (status_with_string)
{
num_written = write (HOOK_CONNECT(hook_connect, child_write),
status_with_string, strlen (status_with_string));
}
else
{
snprintf (status_without_string, sizeof (status_without_string),
"%c00000", '0' + WEECHAT_HOOK_CONNECT_LOCAL_HOSTNAME_ERROR);
num_written = write (HOOK_CONNECT(hook_connect, child_write),
status_without_string, strlen (status_without_string));
}
(void) num_written;
goto end;
}
if (!res_local)
{
/* address not found */
snprintf (status_without_string, sizeof (status_without_string),
"%c00000", '0' + WEECHAT_HOOK_CONNECT_LOCAL_HOSTNAME_ERROR);
num_written = write (HOOK_CONNECT(hook_connect, child_write),
status_without_string, strlen (status_without_string));
(void) num_written;
goto end;
}
}
/* res_local != NULL now indicates that bind() is required */
/*
* count all the groups of hosts by tracking family, e.g.
* 0 = [2001:db8::1, 2001:db8::2,
* 1 = 192.0.2.1, 192.0.2.2,
* 2 = 2002:c000:201::1, 2002:c000:201::2]
*/
last_af = AF_UNSPEC;
num_groups = 0;
num_hosts = 0;
for (ptr_res = res_remote; ptr_res; ptr_res = ptr_res->ai_next)
{
if (ptr_res->ai_family != last_af)
if (last_af != AF_UNSPEC)
num_groups++;
num_hosts++;
last_af = ptr_res->ai_family;
}
if (last_af != AF_UNSPEC)
num_groups++;
res_reorder = malloc (sizeof (*res_reorder) * num_hosts);
if (!res_reorder)
{
snprintf (status_without_string, sizeof (status_without_string),
"%c00000", '0' + WEECHAT_HOOK_CONNECT_MEMORY_ERROR);
num_written = write (HOOK_CONNECT(hook_connect, child_write),
status_without_string, strlen (status_without_string));
(void) num_written;
goto end;
}
/* reorder groups */
retry = HOOK_CONNECT(hook_connect, retry);
if (num_groups > 0)
{
retry %= num_groups;
i = 0;
last_af = AF_UNSPEC;
tmp_num_groups = 0;
tmp_host = i; /* start of current group */
/* top of list */
for (ptr_res = res_remote; ptr_res; ptr_res = ptr_res->ai_next)
{
if (ptr_res->ai_family != last_af)
{
if (last_af != AF_UNSPEC)
tmp_num_groups++;
tmp_host = i;
}
if (tmp_num_groups >= retry)
{
/* shuffle while adding */
rand_num = tmp_host + (rand () % ((i + 1) - tmp_host));
if (rand_num == i)
res_reorder[i++] = ptr_res;
else
{
res_reorder[i++] = res_reorder[rand_num];
res_reorder[rand_num] = ptr_res;
}
}
last_af = ptr_res->ai_family;
}
last_af = AF_UNSPEC;
tmp_num_groups = 0;
tmp_host = i; /* start of current group */
/* remainder of list */
for (ptr_res = res_remote; ptr_res; ptr_res = ptr_res->ai_next)
{
if (ptr_res->ai_family != last_af)
{
if (last_af != AF_UNSPEC)
tmp_num_groups++;
tmp_host = i;
}
if (tmp_num_groups < retry)
{
/* shuffle while adding */
rand_num = tmp_host + (rand () % ((i + 1) - tmp_host));
if (rand_num == i)
res_reorder[i++] = ptr_res;
else
{
res_reorder[i++] = res_reorder[rand_num];
res_reorder[rand_num] = ptr_res;
}
}
else
break;
last_af = ptr_res->ai_family;
}
}
else
{
/* no IP addresses found (all AF_UNSPEC) */
snprintf (status_without_string, sizeof (status_without_string),
"%c00000", '0' + WEECHAT_HOOK_CONNECT_IP_ADDRESS_NOT_FOUND);
num_written = write (HOOK_CONNECT(hook_connect, child_write),
status_without_string, strlen (status_without_string));
(void) num_written;
goto end;
}
status_str[0] = '0' + WEECHAT_HOOK_CONNECT_IP_ADDRESS_NOT_FOUND;
/* try all IP addresses found, stop when connection is OK */
sock = -1;
for (i = 0; i < num_hosts; i++)
{
ptr_res = res_reorder[i];
if (hook_socketpair_ok)
{
/* create a socket */
sock = socket (ptr_res->ai_family,
ptr_res->ai_socktype,
ptr_res->ai_protocol);
}
else
{
/* use pre-created socket pool */
sock = -1;
for (j = 0; j < HOOK_CONNECT_MAX_SOCKETS; j++)
{
if (ptr_res->ai_family == AF_INET)
{
sock = HOOK_CONNECT(hook_connect, sock_v4[j]);
if (sock != -1)
{
HOOK_CONNECT(hook_connect, sock_v4[j]) = -1;
break;
}
}
else if (ptr_res->ai_family == AF_INET6)
{
sock = HOOK_CONNECT(hook_connect, sock_v6[j]);
if (sock != -1)
{
HOOK_CONNECT(hook_connect, sock_v6[j]) = -1;
break;
}
}
}
if (sock < 0)
continue;
}
if (sock < 0)
{
status_str[0] = '0' + WEECHAT_HOOK_CONNECT_SOCKET_ERROR;
continue;
}
/* set SO_REUSEADDR option for socket */
set = 1;
setsockopt (sock, SOL_SOCKET, SO_REUSEADDR, (void *) &set, sizeof (set));
/* set SO_KEEPALIVE option for socket */
set = 1;
setsockopt (sock, SOL_SOCKET, SO_KEEPALIVE, (void *) &set, sizeof (set));
/* set flag O_NONBLOCK on socket */
flags = fcntl (sock, F_GETFL);
if (flags == -1)
flags = 0;
fcntl (sock, F_SETFL, flags | O_NONBLOCK);
if (res_local)
{
rc = -1;
/* bind local hostname/IP if asked by user */
for (ptr_loc = res_local; ptr_loc; ptr_loc = ptr_loc->ai_next)
{
if (ptr_loc->ai_family != ptr_res->ai_family)
continue;
rc = bind (sock, ptr_loc->ai_addr, ptr_loc->ai_addrlen);
if (rc < 0)
continue;
}
if (rc < 0)
{
status_str[0] = '0' + WEECHAT_HOOK_CONNECT_LOCAL_HOSTNAME_ERROR;
close (sock);
sock = -1;
continue;
}
}
/* connect to peer */
if (network_connect (sock, ptr_res->ai_addr, ptr_res->ai_addrlen))
{
status_str[0] = '0' + WEECHAT_HOOK_CONNECT_OK;
rc = getnameinfo (ptr_res->ai_addr, ptr_res->ai_addrlen,
remote_address, sizeof (remote_address),
NULL, 0, NI_NUMERICHOST);
if (rc == 0)
ptr_address = remote_address;
break;
}
else
{
status_str[0] = '0' + WEECHAT_HOOK_CONNECT_CONNECTION_REFUSED;
close (sock);
sock = -1;
}
}
HOOK_CONNECT(hook_connect, sock) = sock;
if (ptr_proxy && status_str[0] == '0' + WEECHAT_HOOK_CONNECT_OK)
{
if (!network_pass_proxy (HOOK_CONNECT(hook_connect, proxy),
HOOK_CONNECT(hook_connect, sock),
HOOK_CONNECT(hook_connect, address),
HOOK_CONNECT(hook_connect, port)))
{
/* proxy fails to connect to peer */
status_str[0] = '0' + WEECHAT_HOOK_CONNECT_PROXY_ERROR;
}
}
if (status_str[0] == '0' + WEECHAT_HOOK_CONNECT_OK)
{
status_with_string = NULL;
if (ptr_address)
{
length = strlen (status_str) + 5 + strlen (ptr_address) + 1;
status_with_string = malloc (length);
if (status_with_string)
{
snprintf (status_with_string, length, "%s%05d%s",
status_str, (int)strlen (ptr_address), ptr_address);
}
}
if (status_with_string)
{
num_written = write (HOOK_CONNECT(hook_connect, child_write),
status_with_string, strlen (status_with_string));
(void) num_written;
}
else
{
snprintf (status_without_string, sizeof (status_without_string),
"%s00000", status_str);
num_written = write (HOOK_CONNECT(hook_connect, child_write),
status_without_string, strlen (status_without_string));
(void) num_written;
}
/* send the socket to the parent process */
if (hook_socketpair_ok)
{
memset (&msg, 0, sizeof (msg));
msg.msg_control = msg_buf;
msg.msg_controllen = sizeof (msg_buf);
/*
* send 1 byte of data
* (not required on Linux, required by BSD/macOS)
*/
memset (iov, 0, sizeof (iov));
iov[0].iov_base = iov_data;
iov[0].iov_len = 1;
msg.msg_iov = iov;
msg.msg_iovlen = 1;
cmsg = CMSG_FIRSTHDR(&msg);
cmsg->cmsg_level = SOL_SOCKET;
cmsg->cmsg_type = SCM_RIGHTS;
cmsg->cmsg_len = CMSG_LEN(sizeof (sock));
memcpy (CMSG_DATA(cmsg), &sock, sizeof (sock));
msg.msg_controllen = cmsg->cmsg_len;
num_written = sendmsg (HOOK_CONNECT(hook_connect, child_send), &msg, 0);
(void) num_written;
}
else
{
num_written = write (HOOK_CONNECT(hook_connect, child_write), &sock, sizeof (sock));
(void) num_written;
}
}
else
{
snprintf (status_without_string, sizeof (status_without_string),
"%s00000", status_str);
num_written = write (HOOK_CONNECT(hook_connect, child_write),
status_without_string, strlen (status_without_string));
(void) num_written;
}
end:
if (status_with_string)
free (status_with_string);
if (res_reorder)
free (res_reorder);
if (res_local)
freeaddrinfo (res_local);
if (res_remote)
freeaddrinfo (res_remote);
}
/*
* Timer callback for timeout of child process.
*/
int
network_connect_child_timer_cb (const void *pointer, void *data,
int remaining_calls)
{
struct t_hook *hook_connect;
/* make C compiler happy */
(void) data;
(void) remaining_calls;
hook_connect = (struct t_hook *)pointer;
HOOK_CONNECT(hook_connect, hook_child_timer) = NULL;
(void) (HOOK_CONNECT(hook_connect, callback))
(hook_connect->callback_pointer,
hook_connect->callback_data,
WEECHAT_HOOK_CONNECT_TIMEOUT,
0, -1, NULL, NULL);
unhook (hook_connect);
return WEECHAT_RC_OK;
}
/*
* Callback for GnuTLS handshake.
*
* This callback is used to not block WeeChat (handshake takes some time to
* finish).
*/
#ifdef HAVE_GNUTLS
int
network_connect_gnutls_handshake_fd_cb (const void *pointer, void *data,
int fd)
{
struct t_hook *hook_connect;
int rc, direction, flags;
/* make C compiler happy */
(void) data;
(void) fd;
hook_connect = (struct t_hook *)pointer;
rc = gnutls_handshake (*HOOK_CONNECT(hook_connect, gnutls_sess));
if ((rc == GNUTLS_E_AGAIN) || (rc == GNUTLS_E_INTERRUPTED))
{
direction = gnutls_record_get_direction (*HOOK_CONNECT(hook_connect, gnutls_sess));
flags = HOOK_FD(HOOK_CONNECT(hook_connect, handshake_hook_fd), flags);
if ((((flags & HOOK_FD_FLAG_READ) == HOOK_FD_FLAG_READ)
&& (direction != 0))
|| (((flags & HOOK_FD_FLAG_WRITE) == HOOK_FD_FLAG_WRITE)
&& (direction != 1)))
{
HOOK_FD(HOOK_CONNECT(hook_connect, handshake_hook_fd), flags) =
(direction) ? HOOK_FD_FLAG_WRITE: HOOK_FD_FLAG_READ;
}
}
else if (rc != GNUTLS_E_SUCCESS)
{
unhook (HOOK_CONNECT(hook_connect, handshake_hook_fd));
(void) (HOOK_CONNECT(hook_connect, callback))
(hook_connect->callback_pointer,
hook_connect->callback_data,
WEECHAT_HOOK_CONNECT_GNUTLS_HANDSHAKE_ERROR, rc,
HOOK_CONNECT(hook_connect, sock),
gnutls_strerror (rc),
HOOK_CONNECT(hook_connect, handshake_ip_address));
unhook (hook_connect);
}
else
{
fcntl (HOOK_CONNECT(hook_connect, sock), F_SETFL,
HOOK_CONNECT(hook_connect, handshake_fd_flags));
#if LIBGNUTLS_VERSION_NUMBER < 0x02090a /* 2.9.10 */
/*
* gnutls only has the gnutls_certificate_set_verify_function()
* function since version 2.9.10. We need to call our verify
* function manually after the handshake for old gnutls versions
*/
if (hook_connect_gnutls_verify_certificates (*HOOK_CONNECT(hook_connect, gnutls_sess)) != 0)
{
unhook (HOOK_CONNECT(hook_connect, handshake_hook_fd));
(void) (HOOK_CONNECT(hook_connect, callback))
(hook_connect->callback_pointer,
hook_connect->callback_data,
WEECHAT_HOOK_CONNECT_GNUTLS_HANDSHAKE_ERROR, rc,
HOOK_CONNECT(hook_connect, sock),
"Error in the certificate.",
HOOK_CONNECT(hook_connect, handshake_ip_address));
unhook (hook_connect);
return WEECHAT_RC_OK;
}
#endif /* LIBGNUTLS_VERSION_NUMBER < 0x02090a */
unhook (HOOK_CONNECT(hook_connect, handshake_hook_fd));
(void) (HOOK_CONNECT(hook_connect, callback))
(hook_connect->callback_pointer,
hook_connect->callback_data,
WEECHAT_HOOK_CONNECT_OK, 0,
HOOK_CONNECT(hook_connect, sock),
NULL, HOOK_CONNECT(hook_connect, handshake_ip_address));
unhook (hook_connect);
}
return WEECHAT_RC_OK;
}
#endif /* HAVE_GNUTLS */
/*
* Timer callback for timeout of handshake.
*/
#ifdef HAVE_GNUTLS
int
network_connect_gnutls_handshake_timer_cb (const void *pointer,
void *data,
int remaining_calls)
{
struct t_hook *hook_connect;
/* make C compiler happy */
(void) data;
(void) remaining_calls;
hook_connect = (struct t_hook *)pointer;
HOOK_CONNECT(hook_connect, handshake_hook_timer) = NULL;
unhook (HOOK_CONNECT(hook_connect, handshake_hook_fd));
(void) (HOOK_CONNECT(hook_connect, callback))
(hook_connect->callback_pointer,
hook_connect->callback_data,
WEECHAT_HOOK_CONNECT_GNUTLS_HANDSHAKE_ERROR,
GNUTLS_E_EXPIRED,
HOOK_CONNECT(hook_connect, sock),
gnutls_strerror (GNUTLS_E_EXPIRED),
HOOK_CONNECT(hook_connect, handshake_ip_address));
unhook (hook_connect);
return WEECHAT_RC_OK;
}
#endif /* HAVE_GNUTLS */
/*
* Reads connection progress from child process.
*/
int
network_connect_child_read_cb (const void *pointer, void *data, int fd)
{
struct t_hook *hook_connect;
char buffer[1], buf_size[6], *cb_error, *cb_ip_address, *error;
int num_read;
long size_msg;
#ifdef HAVE_GNUTLS
int rc, direction;
#endif /* HAVE_GNUTLS */
int sock, i;
struct msghdr msg;
struct cmsghdr *cmsg;
char msg_buf[CMSG_SPACE(sizeof (sock))];
struct iovec iov[1];
char iov_data[1];
/* make C compiler happy */
(void) data;
(void) fd;
hook_connect = (struct t_hook *)pointer;
cb_error = NULL;
cb_ip_address = NULL;
sock = -1;
num_read = read (HOOK_CONNECT(hook_connect, child_read),
buffer, sizeof (buffer));
if (num_read == sizeof (buffer))
{
if (buffer[0] - '0' == WEECHAT_HOOK_CONNECT_OK)
{
/* connection OK, read IP address */
buf_size[5] = '\0';
num_read = read (HOOK_CONNECT(hook_connect, child_read),
buf_size, 5);
if (num_read == 5)
{
error = NULL;
size_msg = strtol (buf_size, &error, 10);
if (error && !error[0] && (size_msg > 0))
{
cb_ip_address = malloc (size_msg + 1);
if (cb_ip_address)
{
num_read = read (HOOK_CONNECT(hook_connect, child_read),
cb_ip_address, size_msg);
if (num_read == size_msg)
cb_ip_address[size_msg] = '\0';
else
{
free (cb_ip_address);
cb_ip_address = NULL;
}
}
}
}
if (hook_socketpair_ok)
{
/* receive the socket from the child process */
memset (&msg, 0, sizeof (msg));
msg.msg_control = msg_buf;
msg.msg_controllen = sizeof (msg_buf);
/*
* recv 1 byte of data
* (not required on Linux, required by BSD/macOS)
*/
memset (iov, 0, sizeof (iov));
iov[0].iov_base = iov_data;
iov[0].iov_len = 1;
msg.msg_iov = iov;
msg.msg_iovlen = 1;
if (recvmsg (HOOK_CONNECT(hook_connect, child_recv), &msg, 0) >= 0)
{
cmsg = CMSG_FIRSTHDR(&msg);
if (cmsg != NULL
&& cmsg->cmsg_level == SOL_SOCKET
&& cmsg->cmsg_type == SCM_RIGHTS
&& cmsg->cmsg_len >= sizeof (sock))
{
memcpy (&sock, CMSG_DATA(cmsg), sizeof (sock));
}
}
}
else
{
num_read = read (HOOK_CONNECT(hook_connect, child_read), &sock, sizeof (sock));
(void) num_read;
/* prevent unhook process from closing the socket */
for (i = 0; i < HOOK_CONNECT_MAX_SOCKETS; i++)
{
if (HOOK_CONNECT(hook_connect, sock_v4[i]) == sock)
HOOK_CONNECT(hook_connect, sock_v4[i]) = -1;
if (HOOK_CONNECT(hook_connect, sock_v6[i]) == sock)
HOOK_CONNECT(hook_connect, sock_v6[i]) = -1;
}
}
HOOK_CONNECT(hook_connect, sock) = sock;
#ifdef HAVE_GNUTLS
if (HOOK_CONNECT(hook_connect, gnutls_sess))
{
/*
* the socket needs to be non-blocking since the call to
* gnutls_handshake can block
*/
HOOK_CONNECT(hook_connect, handshake_fd_flags) =
fcntl (HOOK_CONNECT(hook_connect, sock), F_GETFL);
if (HOOK_CONNECT(hook_connect, handshake_fd_flags) == -1)
HOOK_CONNECT(hook_connect, handshake_fd_flags) = 0;
fcntl (HOOK_CONNECT(hook_connect, sock), F_SETFL,
HOOK_CONNECT(hook_connect, handshake_fd_flags) | O_NONBLOCK);
gnutls_transport_set_ptr (*HOOK_CONNECT(hook_connect, gnutls_sess),
(gnutls_transport_ptr_t) ((ptrdiff_t) HOOK_CONNECT(hook_connect, sock)));
if (HOOK_CONNECT(hook_connect, gnutls_dhkey_size) > 0)
{
gnutls_dh_set_prime_bits (*HOOK_CONNECT(hook_connect, gnutls_sess),
(unsigned int) HOOK_CONNECT(hook_connect, gnutls_dhkey_size));
}
rc = gnutls_handshake (*HOOK_CONNECT(hook_connect, gnutls_sess));
if ((rc == GNUTLS_E_AGAIN) || (rc == GNUTLS_E_INTERRUPTED))
{
/*
* gnutls was unable to proceed with the handshake without
* blocking: non fatal error, we just have to wait for an
* event about handshake
*/
unhook (HOOK_CONNECT(hook_connect, hook_fd));
HOOK_CONNECT(hook_connect, hook_fd) = NULL;
direction = gnutls_record_get_direction (*HOOK_CONNECT(hook_connect, gnutls_sess));
HOOK_CONNECT(hook_connect, handshake_ip_address) = cb_ip_address;
HOOK_CONNECT(hook_connect, handshake_hook_fd) =
hook_fd (hook_connect->plugin,
HOOK_CONNECT(hook_connect, sock),
(!direction ? 1 : 0), (direction ? 1 : 0), 0,
&network_connect_gnutls_handshake_fd_cb,
hook_connect, NULL);
HOOK_CONNECT(hook_connect, handshake_hook_timer) =
hook_timer (hook_connect->plugin,
CONFIG_INTEGER(config_network_gnutls_handshake_timeout) * 1000,
0, 1,
&network_connect_gnutls_handshake_timer_cb,
hook_connect, NULL);
return WEECHAT_RC_OK;
}
else if (rc != GNUTLS_E_SUCCESS)
{
(void) (HOOK_CONNECT(hook_connect, callback))
(hook_connect->callback_pointer,
hook_connect->callback_data,
WEECHAT_HOOK_CONNECT_GNUTLS_HANDSHAKE_ERROR,
rc, sock,
gnutls_strerror (rc),
cb_ip_address);
unhook (hook_connect);
if (cb_ip_address)
free (cb_ip_address);
return WEECHAT_RC_OK;
}
fcntl (HOOK_CONNECT(hook_connect, sock), F_SETFL,
HOOK_CONNECT(hook_connect, handshake_fd_flags));
#if LIBGNUTLS_VERSION_NUMBER < 0x02090a /* 2.9.10 */
/*
* gnutls only has the gnutls_certificate_set_verify_function()
* function since version 2.9.10. We need to call our verify
* function manually after the handshake for old gnutls versions
*/
if (hook_connect_gnutls_verify_certificates (*HOOK_CONNECT(hook_connect, gnutls_sess)) != 0)
{
(void) (HOOK_CONNECT(hook_connect, callback))
(hook_connect->callback_pointer,
hook_connect->callback_data,
WEECHAT_HOOK_CONNECT_GNUTLS_HANDSHAKE_ERROR,
rc, sock,
"Error in the certificate.",
cb_ip_address);
unhook (hook_connect);
if (cb_ip_address)
free (cb_ip_address);
return WEECHAT_RC_OK;
}
#endif /* LIBGNUTLS_VERSION_NUMBER < 0x02090a */
}
#endif /* HAVE_GNUTLS */
}
else
{
/* connection error, read error */
buf_size[5] = '\0';
num_read = read (HOOK_CONNECT(hook_connect, child_read),
buf_size, 5);
if (num_read == 5)
{
error = NULL;
size_msg = strtol (buf_size, &error, 10);
if (error && !error[0] && (size_msg > 0))
{
cb_error = malloc (size_msg + 1);
if (cb_error)
{
num_read = read (HOOK_CONNECT(hook_connect, child_read),
cb_error, size_msg);
if (num_read == size_msg)
cb_error[size_msg] = '\0';
else
{
free (cb_error);
cb_error = NULL;
}
}
}
}
}
(void) (HOOK_CONNECT(hook_connect, callback))
(hook_connect->callback_pointer,
hook_connect->callback_data,
buffer[0] - '0', 0,
sock, cb_error, cb_ip_address);
unhook (hook_connect);
}
else
{
(void) (HOOK_CONNECT(hook_connect, callback))
(hook_connect->callback_pointer,
hook_connect->callback_data,
WEECHAT_HOOK_CONNECT_MEMORY_ERROR,
0, sock, "child_read_cb", NULL);
unhook (hook_connect);
}
if (cb_error)
free (cb_error);
if (cb_ip_address)
free (cb_ip_address);
return WEECHAT_RC_OK;
}
/*
* Connects with fork (called by hook_connect() only!).
*/
void
network_connect_with_fork (struct t_hook *hook_connect)
{
int child_pipe[2], child_socket[2], rc, i;
char str_error[1024];
#ifdef HAVE_GNUTLS
const char *pos_error;
#endif /* HAVE_GNUTLS */
pid_t pid;
#ifdef HAVE_GNUTLS
/* initialize GnuTLS if SSL asked */
if (HOOK_CONNECT(hook_connect, gnutls_sess))
{
if (gnutls_init (HOOK_CONNECT(hook_connect, gnutls_sess), GNUTLS_CLIENT) != GNUTLS_E_SUCCESS)
{
(void) (HOOK_CONNECT(hook_connect, callback))
(hook_connect->callback_pointer,
hook_connect->callback_data,
WEECHAT_HOOK_CONNECT_GNUTLS_INIT_ERROR,
0, -1, NULL, NULL);
unhook (hook_connect);
return;
}
rc = gnutls_server_name_set (*HOOK_CONNECT(hook_connect, gnutls_sess),
GNUTLS_NAME_DNS,
HOOK_CONNECT(hook_connect, address),
strlen (HOOK_CONNECT(hook_connect, address)));
if (rc != GNUTLS_E_SUCCESS)
{
(void) (HOOK_CONNECT(hook_connect, callback))
(hook_connect->callback_pointer,
hook_connect->callback_data,
WEECHAT_HOOK_CONNECT_GNUTLS_INIT_ERROR,
0, -1, _("set server name indication (SNI) failed"), NULL);
unhook (hook_connect);
return;
}
rc = gnutls_priority_set_direct (*HOOK_CONNECT(hook_connect, gnutls_sess),
HOOK_CONNECT(hook_connect, gnutls_priorities),
&pos_error);
if (rc != GNUTLS_E_SUCCESS)
{
(void) (HOOK_CONNECT(hook_connect, callback))
(hook_connect->callback_pointer,
hook_connect->callback_data,
WEECHAT_HOOK_CONNECT_GNUTLS_INIT_ERROR,
0, -1, _("invalid priorities"), NULL);
unhook (hook_connect);
return;
}
gnutls_credentials_set (*HOOK_CONNECT(hook_connect, gnutls_sess),
GNUTLS_CRD_CERTIFICATE,
gnutls_xcred);
gnutls_transport_set_ptr (*HOOK_CONNECT(hook_connect, gnutls_sess),
(gnutls_transport_ptr_t) ((unsigned long) HOOK_CONNECT(hook_connect, sock)));
}
#endif /* HAVE_GNUTLS */
/* create pipe for child process */
if (pipe (child_pipe) < 0)
{
(void) (HOOK_CONNECT(hook_connect, callback))
(hook_connect->callback_pointer,
hook_connect->callback_data,
WEECHAT_HOOK_CONNECT_MEMORY_ERROR,
0, -1, "pipe", NULL);
unhook (hook_connect);
return;
}
HOOK_CONNECT(hook_connect, child_read) = child_pipe[0];
HOOK_CONNECT(hook_connect, child_write) = child_pipe[1];
if (hook_socketpair_ok)
{
/* create socket for child process */
if (socketpair (AF_LOCAL, SOCK_DGRAM, 0, child_socket) < 0)
{
(void) (HOOK_CONNECT(hook_connect, callback))
(hook_connect->callback_pointer,
hook_connect->callback_data,
WEECHAT_HOOK_CONNECT_MEMORY_ERROR,
0, -1, "socketpair", NULL);
unhook (hook_connect);
return;
}
HOOK_CONNECT(hook_connect, child_recv) = child_socket[0];
HOOK_CONNECT(hook_connect, child_send) = child_socket[1];
}
else
{
for (i = 0; i < HOOK_CONNECT_MAX_SOCKETS; i++)
{
HOOK_CONNECT(hook_connect, sock_v4[i]) = socket (AF_INET, SOCK_STREAM, 0);
HOOK_CONNECT(hook_connect, sock_v6[i]) = socket (AF_INET6, SOCK_STREAM, 0);
}
}
switch (pid = fork ())
{
/* fork failed */
case -1:
snprintf (str_error, sizeof (str_error),
"fork error: %s",
strerror (errno));
(void) (HOOK_CONNECT(hook_connect, callback))
(hook_connect->callback_pointer,
hook_connect->callback_data,
WEECHAT_HOOK_CONNECT_MEMORY_ERROR,
0, -1, str_error, NULL);
unhook (hook_connect);
return;
/* child process */
case 0:
rc = setuid (getuid ());
(void) rc;
close (HOOK_CONNECT(hook_connect, child_read));
if (hook_socketpair_ok)
close (HOOK_CONNECT(hook_connect, child_recv));
network_connect_child (hook_connect);
_exit (EXIT_SUCCESS);
}
/* parent process */
HOOK_CONNECT(hook_connect, child_pid) = pid;
close (HOOK_CONNECT(hook_connect, child_write));
HOOK_CONNECT(hook_connect, child_write) = -1;
if (hook_socketpair_ok)
{
close (HOOK_CONNECT(hook_connect, child_send));
HOOK_CONNECT(hook_connect, child_send) = -1;
}
HOOK_CONNECT(hook_connect, hook_child_timer) = hook_timer (hook_connect->plugin,
CONFIG_INTEGER(config_network_connection_timeout) * 1000,
0, 1,
&network_connect_child_timer_cb,
hook_connect,
NULL);
HOOK_CONNECT(hook_connect, hook_fd) = hook_fd (hook_connect->plugin,
HOOK_CONNECT(hook_connect, child_read),
1, 0, 0,
&network_connect_child_read_cb,
hook_connect, NULL);
}