/* * wee-network.c - network functions * * Copyright (C) 2003-2013 Sebastien Helleu * Copyright (C) 2005-2010 Emmanuel Bouthenot * Copyright (C) 2010 Gu1ll4um3r0m41n * 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 . */ #ifdef HAVE_CONFIG_H #include "config.h" #endif #include #include #include #include #include #include #include #include #include #include #include #include #if defined(__OpenBSD__) #include #endif #ifdef HAVE_GNUTLS #include #endif #include "weechat.h" #include "wee-network.h" #include "wee-hook.h" #include "wee-config.h" #include "wee-proxy.h" #include "wee-string.h" #include "../plugins/plugin.h" int network_init_ok = 0; #ifdef HAVE_GNUTLS gnutls_certificate_credentials_t gnutls_xcred; /* GnuTLS client credentials */ #endif /* * 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 } /* * Initializes network. */ void network_init () { #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 /* for gnutls >= 2.9.10 */ gnutls_certificate_set_verify_function (gnutls_xcred, &hook_connect_gnutls_verify_certificates); #endif #if LIBGNUTLS_VERSION_NUMBER >= 0x020b00 /* for gnutls >= 2.11.0 */ gnutls_certificate_set_retrieve_function (gnutls_xcred, &hook_connect_gnutls_set_certificates); #else /* for gnutls < 2.11.0 */ gnutls_certificate_client_set_retrieve_function (gnutls_xcred, &hook_connect_gnutls_set_certificates); #endif } #endif /* HAVE_GNUTLS */ if (!weechat_no_gcrypt) { gcry_check_version (GCRYPT_VERSION); gcry_control (GCRYCTL_DISABLE_SECMEM, 0); gcry_control (GCRYCTL_INITIALIZATION_FINISHED, 0); } network_init_ok = 1; } /* * Ends network. */ void network_end () { if (network_init_ok) { #ifdef HAVE_GNUTLS if (!weechat_no_gnutls) { gnutls_certificate_free_credentials (gnutls_xcred); gnutls_global_deinit(); } #endif network_init_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]; int length; if (CONFIG_STRING(proxy->options[PROXY_OPTION_USERNAME]) && CONFIG_STRING(proxy->options[PROXY_OPTION_USERNAME])[0]) { /* authentication */ snprintf (authbuf, sizeof (authbuf), "%s:%s", CONFIG_STRING(proxy->options[PROXY_OPTION_USERNAME]), (CONFIG_STRING(proxy->options[PROXY_OPTION_PASSWORD])) ? CONFIG_STRING(proxy->options[PROXY_OPTION_PASSWORD]) : ""); string_encode_base64 (authbuf, strlen (authbuf), authbuf_base64); 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; 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: http://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]; int length; 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, CONFIG_STRING(proxy->options[PROXY_OPTION_USERNAME]), sizeof (socks4.user) - 1); 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; 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_len = strlen (CONFIG_STRING(proxy->options[PROXY_OPTION_USERNAME])); password_len = strlen (CONFIG_STRING(proxy->options[PROXY_OPTION_PASSWORD])); /* make username/password buffer */ buffer[0] = 1; buffer[1] = (unsigned char) username_len; memcpy(buffer + 2, CONFIG_STRING(proxy->options[PROXY_OPTION_USERNAME]), username_len); buffer[2 + username_len] = (unsigned char) password_len; memcpy (buffer + 3 + username_len, CONFIG_STRING(proxy->options[PROXY_OPTION_PASSWORD]), password_len); 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) { fd_set write_fds; 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) { FD_ZERO (&write_fds); FD_SET (sock, &write_fds); ready = select (sock + 1, NULL, &write_fds, NULL, NULL); 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: * 1: OK * 0: error */ int network_connect_to (const char *proxy, int sock, unsigned long address, int port) { struct t_proxy *ptr_proxy; struct sockaddr_in addr; struct hostent *hostent; char *ip4; ptr_proxy = NULL; if (proxy && proxy[0]) { ptr_proxy = proxy_search (proxy); if (!ptr_proxy) return 0; } if (ptr_proxy) { memset (&addr, 0, sizeof (addr)); addr.sin_addr.s_addr = htonl (address); ip4 = inet_ntoa(addr.sin_addr); memset (&addr, 0, sizeof (addr)); addr.sin_port = htons (CONFIG_INTEGER(ptr_proxy->options[PROXY_OPTION_PORT])); addr.sin_family = AF_INET; hostent = gethostbyname (CONFIG_STRING(ptr_proxy->options[PROXY_OPTION_ADDRESS])); if (!hostent) return 0; memcpy(&(addr.sin_addr), *(hostent->h_addr_list), sizeof(struct in_addr)); if (!network_connect (sock, (struct sockaddr *) &addr, sizeof (addr))) return 0; if (!network_pass_proxy (proxy, sock, ip4, port)) return 0; } else { memset (&addr, 0, sizeof (addr)); addr.sin_port = htons (port); addr.sin_family = AF_INET; addr.sin_addr.s_addr = htonl (address); if (!network_connect (sock, (struct sockaddr *) &addr, sizeof (addr))) return 0; } 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; #ifdef HOOK_CONNECT_MAX_SOCKETS int j; #else struct msghdr msg; struct cmsghdr *cmsg; char msg_buf[CMSG_SPACE(sizeof (sock))]; struct iovec iov[1]; char iov_data[1] = { 0 }; #endif /* * 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 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 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]; #ifdef HOOK_CONNECT_MAX_SOCKETS /* 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; #else /* create a socket */ sock = socket (ptr_res->ai_family, ptr_res->ai_socktype, ptr_res->ai_protocol); #endif 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 */ #ifndef HOOK_CONNECT_MAX_SOCKETS 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/OSX) */ 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; #endif } 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 (void *arg_hook_connect, int remaining_calls) { struct t_hook *hook_connect; /* make C compiler happy */ (void) remaining_calls; hook_connect = (struct t_hook *)arg_hook_connect; HOOK_CONNECT(hook_connect, hook_child_timer) = NULL; (void) (HOOK_CONNECT(hook_connect, callback)) (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 (void *arg_hook_connect, int fd) { struct t_hook *hook_connect; int rc, direction, flags; /* make C compiler happy */ (void) fd; hook_connect = (struct t_hook *)arg_hook_connect; 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) { (void) (HOOK_CONNECT(hook_connect, callback)) (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 /* * 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_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 unhook (HOOK_CONNECT(hook_connect, handshake_hook_fd)); (void) (HOOK_CONNECT(hook_connect, callback)) (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 /* * Timer callback for timeout of handshake. */ #ifdef HAVE_GNUTLS int network_connect_gnutls_handshake_timer_cb (void *arg_hook_connect, int remaining_calls) { struct t_hook *hook_connect; /* make C compiler happy */ (void) remaining_calls; hook_connect = (struct t_hook *)arg_hook_connect; HOOK_CONNECT(hook_connect, handshake_hook_timer) = NULL; (void) (HOOK_CONNECT(hook_connect, callback)) (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 /* * Reads connection progress from child process. */ int network_connect_child_read_cb (void *arg_hook_connect, 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 int sock; #ifdef HOOK_CONNECT_MAX_SOCKETS int i; #else struct msghdr msg; struct cmsghdr *cmsg; char msg_buf[CMSG_SPACE(sizeof (sock))]; struct iovec iov[1]; char iov_data[1]; #endif /* make C compiler happy */ (void) fd; hook_connect = (struct t_hook *)arg_hook_connect; 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; } } } } #ifndef HOOK_CONNECT_MAX_SOCKETS /* 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/OSX) */ 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; } #endif 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); 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); return WEECHAT_RC_OK; } else if (rc != GNUTLS_E_SUCCESS) { (void) (HOOK_CONNECT(hook_connect, callback)) (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 /* * 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_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 } #endif } 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_data, buffer[0] - '0', 0, sock, cb_error, cb_ip_address); unhook (hook_connect); } else { (void) (HOOK_CONNECT(hook_connect, callback)) (hook_connect->callback_data, WEECHAT_HOOK_CONNECT_MEMORY_ERROR, 0, sock, cb_error, cb_ip_address); 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]; #ifdef HOOK_CONNECT_MAX_SOCKETS int i; #else int child_socket[2]; #endif #ifdef HAVE_GNUTLS int rc; const char *pos_error; #endif 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_data, WEECHAT_HOOK_CONNECT_GNUTLS_INIT_ERROR, 0, -1, NULL, 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_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 /* create pipe for child process */ if (pipe (child_pipe) < 0) { (void) (HOOK_CONNECT(hook_connect, callback)) (hook_connect->callback_data, WEECHAT_HOOK_CONNECT_MEMORY_ERROR, 0, -1, NULL, NULL); unhook (hook_connect); return; } HOOK_CONNECT(hook_connect, child_read) = child_pipe[0]; HOOK_CONNECT(hook_connect, child_write) = child_pipe[1]; #ifndef HOOK_CONNECT_MAX_SOCKETS /* create socket for child process */ if (socketpair (AF_LOCAL, SOCK_DGRAM, 0, child_socket) < 0) { (void) (HOOK_CONNECT(hook_connect, callback)) (hook_connect->callback_data, WEECHAT_HOOK_CONNECT_MEMORY_ERROR, 0, -1, NULL, 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); } #endif switch (pid = fork ()) { /* fork failed */ case -1: (void) (HOOK_CONNECT(hook_connect, callback)) (hook_connect->callback_data, WEECHAT_HOOK_CONNECT_MEMORY_ERROR, 0, -1, NULL, NULL); unhook (hook_connect); return; /* child process */ case 0: setuid (getuid ()); close (HOOK_CONNECT(hook_connect, child_read)); #ifndef HOOK_CONNECT_MAX_SOCKETS close (HOOK_CONNECT(hook_connect, child_recv)); #endif 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; #ifndef HOOK_CONNECT_MAX_SOCKETS close (HOOK_CONNECT(hook_connect, child_send)); HOOK_CONNECT(hook_connect, child_send) = -1; #endif 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); 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); }