/*
* Claws Mail -- a GTK based, lightweight, and fast e-mail client
* Copyright (C) 2018 the Claws Mail team
*
* 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 3 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, see .
*/
#ifdef HAVE_CONFIG_H
# include "config.h"
#endif
#include
#ifdef G_OS_WIN32
# include
# include
# include
#else
# include
# include
# include
#endif
#include "proxy.h"
#include "socket.h"
#include "utils.h"
gint socks4_connect(SockInfo *sock, const gchar *hostname, gushort port);
gint socks5_connect(SockInfo *sock, const gchar *hostname, gushort port,
const gchar *proxy_name, const gchar *proxy_pass);
gint proxy_connect(SockInfo *sock, const gchar *hostname, gushort port,
ProxyInfo *proxy_info)
{
gint ret;
g_return_val_if_fail(sock != NULL, -1);
g_return_val_if_fail(hostname != NULL, -1);
g_return_val_if_fail(proxy_info != NULL, -1);
debug_print("proxy_connect: connect to %s:%u via %s:%u\n",
hostname, port,
proxy_info->proxy_host, proxy_info->proxy_port);
if (proxy_info->proxy_type == PROXY_SOCKS5) {
ret = socks5_connect(sock, hostname, port,
proxy_info->use_proxy_auth ? proxy_info->proxy_name : NULL,
proxy_info->use_proxy_auth ? proxy_info->proxy_pass : NULL);
/* Scrub the password before returning */
if (proxy_info->proxy_pass != NULL) {
memset(proxy_info->proxy_pass, 0, strlen(proxy_info->proxy_pass));
g_free(proxy_info->proxy_pass);
proxy_info->proxy_pass = NULL;
}
return ret;
} else if (proxy_info->proxy_type == PROXY_SOCKS4) {
return socks4_connect(sock, hostname, port);
} else {
g_warning("proxy_connect: unknown SOCKS type: %d",
proxy_info->proxy_type);
}
return -1;
}
gint socks4_connect(SockInfo *sock, const gchar *hostname, gushort port)
{
guchar socks_req[1024];
struct addrinfo hints, *res, *ai;
gboolean got_address = FALSE;
int s;
g_return_val_if_fail(sock != NULL, -1);
g_return_val_if_fail(hostname != NULL, -1);
debug_print("socks4_connect: connect to %s:%u\n", hostname, port);
socks_req[0] = 4;
socks_req[1] = 1;
*((gushort *)(socks_req + 2)) = htons(port);
/* lookup */
memset(&hints, 0, sizeof(struct addrinfo));
hints.ai_family = AF_INET; /* SOCKS4 only supports IPv4 addresses */
s = getaddrinfo(hostname, NULL, &hints, &res);
if (s != 0) {
fprintf(stderr, "getaddrinfo for '%s' failed: %s\n",
hostname, gai_strerror(s));
return -1;
}
for (ai = res; ai != NULL; ai = ai->ai_next) {
uint32_t addr;
if (ai->ai_family != AF_INET)
continue;
addr = ((struct sockaddr_in *)ai->ai_addr)->sin_addr.s_addr;
memcpy(socks_req + 4, &addr, 4);
got_address = TRUE;
break;
}
if (res != NULL)
freeaddrinfo(res);
if (!got_address) {
g_warning("socks4_connect: could not get valid IPv4 address for '%s'", hostname);
return -1;
}
debug_print("got a valid IPv4 address, continuing\n");
/* userid (empty) */
socks_req[8] = 0;
if (sock_write_all(sock, (gchar *)socks_req, 9) != 9) {
g_warning("socks4_connect: SOCKS4 initial request write failed");
return -1;
}
if (sock_read(sock, (gchar *)socks_req, 8) != 8) {
g_warning("socks4_connect: SOCKS4 response read failed");
return -1;
}
if (socks_req[0] != 0) {
g_warning("socks4_connect: SOCKS4 response has invalid version");
return -1;
}
if (socks_req[1] != 90) {
g_warning("socks4_connect: SOCKS4 connection to %u.%u.%u.%u:%u failed. (%u)", socks_req[4], socks_req[5], socks_req[6], socks_req[7], ntohs(*(gushort *)(socks_req + 2)), socks_req[1]);
return -1;
}
/* replace sock->hostname with endpoint */
if (sock->hostname != hostname) {
g_free(sock->hostname);
sock->hostname = g_strdup(hostname);
sock->port = port;
}
debug_print("socks4_connect: SOCKS4 connection to %s:%u successful.\n", hostname, port);
return 0;
}
gint socks5_connect(SockInfo *sock, const gchar *hostname, gushort port,
const gchar *proxy_name, const gchar *proxy_pass)
{
guchar socks_req[1024];
size_t len;
size_t size;
g_return_val_if_fail(sock != NULL, -1);
g_return_val_if_fail(hostname != NULL, -1);
debug_print("socks5_connect: connect to %s:%u\n", hostname, port);
len = strlen(hostname);
if (len > 255) {
g_warning("socks5_connect: hostname too long");
return -1;
}
socks_req[0] = 5;
socks_req[1] = proxy_name ? 2 : 1;
socks_req[2] = 0;
socks_req[3] = 2;
if (sock_write_all(sock, (gchar *)socks_req, 2 + socks_req[1]) != 2 + socks_req[1]) {
g_warning("socks5_connect: SOCKS5 initial request write failed");
return -1;
}
if (sock_read(sock, (gchar *)socks_req, 2) != 2) {
g_warning("socks5_connect: SOCKS5 response read failed");
return -1;
}
if (socks_req[0] != 5) {
g_warning("socks5_connect: SOCKS5 response has invalid version");
return -1;
}
if (socks_req[1] == 2) {
/* auth */
size_t userlen, passlen;
gint reqlen;
if (proxy_name && proxy_pass) {
debug_print("socks5_connect: auth using username '%s'\n", proxy_name);
userlen = strlen(proxy_name);
passlen = strlen(proxy_pass);
} else
userlen = passlen = 0;
socks_req[0] = 1;
socks_req[1] = (guchar)userlen;
if (proxy_name && userlen > 0)
memcpy(socks_req + 2, proxy_name, userlen);
socks_req[2 + userlen] = (guchar)passlen;
if (proxy_pass && passlen > 0)
memcpy(socks_req + 2 + userlen + 1, proxy_pass, passlen);
reqlen = 2 + userlen + 1 + passlen;
if (sock_write_all(sock, (gchar *)socks_req, reqlen) != reqlen) {
memset(socks_req, 0, reqlen);
g_warning("socks5_connect: SOCKS5 auth write failed");
return -1;
}
memset(socks_req, 0, reqlen);
if (sock_read(sock, (gchar *)socks_req, 2) != 2) {
g_warning("socks5_connect: SOCKS5 auth response read failed");
return -1;
}
if (socks_req[1] != 0) {
g_warning("socks5_connect: SOCKS5 authentication failed: user: %s (%u %u)", proxy_name ? proxy_name : "(none)", socks_req[0], socks_req[1]);
return -1;
}
} else if (socks_req[1] != 0) {
g_warning("socks5_connect: SOCKS5 reply (%u) error", socks_req[1]);
return -1;
}
socks_req[0] = 5;
socks_req[1] = 1;
socks_req[2] = 0;
socks_req[3] = 3;
socks_req[4] = (guchar)len;
memcpy(socks_req + 5, hostname, len);
*((gushort *)(socks_req + 5 + len)) = htons(port);
if (sock_write_all(sock, (gchar *)socks_req, 5 + len + 2) != 5 + len + 2) {
g_warning("socks5_connect: SOCKS5 connect request write failed");
return -1;
}
if (sock_read(sock, (gchar *)socks_req, 10) != 10) {
g_warning("socks5_connect: SOCKS5 connect request response read failed");
return -1;
}
if (socks_req[0] != 5) {
g_warning("socks5_connect: SOCKS5 response has invalid version");
return -1;
}
if (socks_req[1] != 0) {
g_warning("socks5_connect: SOCKS5 connection to %s:%u failed. (%u)",
hostname, port, socks_req[1]);
return -1;
}
size = 10;
if (socks_req[3] == 3)
size = 5 + socks_req[4] + 2;
else if (socks_req[3] == 4)
size = 4 + 16 + 2;
if (size > 10) {
size -= 10;
if (sock_read(sock, (gchar *)socks_req + 10, size) != size) {
g_warning("socks5_connect: SOCKS5 connect request response read failed");
return -1;
}
}
/* replace sock->hostname with endpoint */
if (sock->hostname != hostname) {
g_free(sock->hostname);
sock->hostname = g_strdup(hostname);
sock->port = port;
}
debug_print("socks5_connect: SOCKS5 connection to %s:%u successful.\n", hostname, port);
return 0;
}