1 /*
2  * Claws Mail -- a GTK+ based, lightweight, and fast e-mail client
3  * Copyright (C) 2018 the Claws Mail team
4  *
5  * This program is free software; you can redistribute it and/or modify
6  * it under the terms of the GNU General Public License as published by
7  * the Free Software Foundation; either version 3 of the License, or
8  * (at your option) any later version.
9  *
10  * This program is distributed in the hope that it will be useful,
11  * but WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13  * GNU General Public License for more details.
14  *
15  * You should have received a copy of the GNU General Public License
16  * along with this program. If not, see <http://www.gnu.org/licenses/>.
17  */
18 
19 #ifdef HAVE_CONFIG_H
20 #  include "config.h"
21 #endif
22 
23 #include <glib.h>
24 
25 #ifdef G_OS_WIN32
26 #  include <winsock2.h>
27 #  include <ws2tcpip.h>
28 #  include <stdint.h>
29 #else
30 #  include <sys/socket.h>
31 #  include <netinet/in.h>
32 #  include <netinet/ip.h>
33 #endif
34 
35 #include "proxy.h"
36 #include "socket.h"
37 #include "utils.h"
38 
39 gint socks4_connect(SockInfo *sock, const gchar *hostname, gushort port);
40 gint socks5_connect(SockInfo *sock, const gchar *hostname, gushort port,
41 		const gchar *proxy_name, const gchar *proxy_pass);
42 
proxy_connect(SockInfo * sock,const gchar * hostname,gushort port,ProxyInfo * proxy_info)43 gint proxy_connect(SockInfo *sock, const gchar *hostname, gushort port,
44 		   ProxyInfo *proxy_info)
45 {
46 	gint ret;
47 
48 	g_return_val_if_fail(sock != NULL, -1);
49 	g_return_val_if_fail(hostname != NULL, -1);
50 	g_return_val_if_fail(proxy_info != NULL, -1);
51 
52 	debug_print("proxy_connect: connect to %s:%u via %s:%u\n",
53 		    hostname, port,
54 		    proxy_info->proxy_host, proxy_info->proxy_port);
55 
56 	if (proxy_info->proxy_type == PROXY_SOCKS5) {
57 		ret = socks5_connect(sock, hostname, port,
58 				      proxy_info->use_proxy_auth ? proxy_info->proxy_name : NULL,
59 				      proxy_info->use_proxy_auth ? proxy_info->proxy_pass : NULL);
60 		/* Scrub the password before returning */
61 		if (proxy_info->proxy_pass != NULL) {
62 			memset(proxy_info->proxy_pass, 0, strlen(proxy_info->proxy_pass));
63 			g_free(proxy_info->proxy_pass);
64 			proxy_info->proxy_pass = NULL;
65 		}
66 		return ret;
67 	} else if (proxy_info->proxy_type == PROXY_SOCKS4) {
68 		return socks4_connect(sock, hostname, port);
69 	} else {
70 		g_warning("proxy_connect: unknown SOCKS type: %d\n",
71 			  proxy_info->proxy_type);
72 	}
73 
74 	return -1;
75 }
76 
socks4_connect(SockInfo * sock,const gchar * hostname,gushort port)77 gint socks4_connect(SockInfo *sock, const gchar *hostname, gushort port)
78 {
79 	guchar socks_req[1024];
80 	struct addrinfo hints, *res, *ai;
81 	gboolean got_address = FALSE;
82 	int s;
83 
84 	g_return_val_if_fail(sock != NULL, -1);
85 	g_return_val_if_fail(hostname != NULL, -1);
86 
87 	debug_print("socks4_connect: connect to %s:%u\n", hostname, port);
88 
89 	socks_req[0] = 4;
90 	socks_req[1] = 1;
91 	*((gushort *)(socks_req + 2)) = htons(port);
92 
93 	/* lookup */
94 	memset(&hints, 0, sizeof(struct addrinfo));
95 	hints.ai_family = AF_INET; /* SOCKS4 only supports IPv4 addresses */
96 
97 	s = getaddrinfo(hostname, NULL, &hints, &res);
98 	if (s != 0) {
99 		fprintf(stderr, "getaddrinfo for '%s' failed: %s\n",
100 				hostname, gai_strerror(s));
101 		return -1;
102 	}
103 
104 	for (ai = res; ai != NULL; ai = ai->ai_next) {
105 		uint32_t addr;
106 
107 		if (ai->ai_family != AF_INET)
108 			continue;
109 
110 		addr = ((struct sockaddr_in *)ai->ai_addr)->sin_addr.s_addr;
111 		memcpy(socks_req + 4, &addr, 4);
112 		got_address = TRUE;
113 		break;
114 	}
115 
116 	if (res != NULL)
117 		freeaddrinfo(res);
118 
119 	if (!got_address) {
120 		g_warning("socks4_connect: could not get valid IPv4 address for '%s'", hostname);
121 		return -1;
122 	}
123 
124 	debug_print("got a valid IPv4 address, continuing\n");
125 
126 	/* userid (empty) */
127 	socks_req[8] = 0;
128 
129 	if (sock_write_all(sock, (gchar *)socks_req, 9) != 9) {
130 		g_warning("socks4_connect: SOCKS4 initial request write failed");
131 		return -1;
132 	}
133 
134 	if (sock_read(sock, (gchar *)socks_req, 8) != 8) {
135 		g_warning("socks4_connect: SOCKS4 response read failed");
136 		return -1;
137 	}
138 	if (socks_req[0] != 0) {
139 		g_warning("socks4_connect: SOCKS4 response has invalid version");
140 		return -1;
141 	}
142 	if (socks_req[1] != 90) {
143 		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]);
144 		return -1;
145 	}
146 
147 	/* replace sock->hostname with endpoint */
148 	if (sock->hostname != hostname) {
149 		g_free(sock->hostname);
150 		sock->hostname = g_strdup(hostname);
151 		sock->port = port;
152 	}
153 
154 	debug_print("socks4_connect: SOCKS4 connection to %s:%u successful.\n", hostname, port);
155 
156 	return 0;
157 }
158 
socks5_connect(SockInfo * sock,const gchar * hostname,gushort port,const gchar * proxy_name,const gchar * proxy_pass)159 gint socks5_connect(SockInfo *sock, const gchar *hostname, gushort port,
160 		    const gchar *proxy_name, const gchar *proxy_pass)
161 {
162 	guchar socks_req[1024];
163 	size_t len;
164 	size_t size;
165 
166 	g_return_val_if_fail(sock != NULL, -1);
167 	g_return_val_if_fail(hostname != NULL, -1);
168 
169 	debug_print("socks5_connect: connect to %s:%u\n", hostname, port);
170 
171 	len = strlen(hostname);
172 	if (len > 255) {
173 		g_warning("socks5_connect: hostname too long");
174 		return -1;
175 	}
176 
177 	socks_req[0] = 5;
178 	socks_req[1] = proxy_name ? 2 : 1;
179 	socks_req[2] = 0;
180 	socks_req[3] = 2;
181 
182 	if (sock_write_all(sock, (gchar *)socks_req, 2 + socks_req[1]) != 2 + socks_req[1]) {
183 		g_warning("socks5_connect: SOCKS5 initial request write failed");
184 		return -1;
185 	}
186 
187 	if (sock_read(sock, (gchar *)socks_req, 2) != 2) {
188 		g_warning("socks5_connect: SOCKS5 response read failed");
189 		return -1;
190 	}
191 	if (socks_req[0] != 5) {
192 		g_warning("socks5_connect: SOCKS5 response has invalid version");
193 		return -1;
194 	}
195 	if (socks_req[1] == 2) {
196 		/* auth */
197 		size_t userlen, passlen;
198 		gint reqlen;
199 
200 		if (proxy_name && proxy_pass) {
201 			debug_print("socks5_connect: auth using username '%s'\n", proxy_name);
202 			userlen = strlen(proxy_name);
203 			passlen = strlen(proxy_pass);
204 		} else
205 			userlen = passlen = 0;
206 
207 		socks_req[0] = 1;
208 		socks_req[1] = (guchar)userlen;
209 		if (proxy_name && userlen > 0)
210 			memcpy(socks_req + 2, proxy_name, userlen);
211 		socks_req[2 + userlen] = (guchar)passlen;
212 		if (proxy_pass && passlen > 0)
213 			memcpy(socks_req + 2 + userlen + 1, proxy_pass, passlen);
214 
215 		reqlen = 2 + userlen + 1 + passlen;
216 		if (sock_write_all(sock, (gchar *)socks_req, reqlen) != reqlen) {
217 			memset(socks_req, 0, reqlen);
218 			g_warning("socks5_connect: SOCKS5 auth write failed");
219 			return -1;
220 		}
221 		memset(socks_req, 0, reqlen);
222 		if (sock_read(sock, (gchar *)socks_req, 2) != 2) {
223 			g_warning("socks5_connect: SOCKS5 auth response read failed");
224 			return -1;
225 		}
226 		if (socks_req[1] != 0) {
227 			g_warning("socks5_connect: SOCKS5 authentication failed: user: %s (%u %u)", proxy_name ? proxy_name : "(none)", socks_req[0], socks_req[1]);
228 			return -1;
229 		}
230 	} else if (socks_req[1] != 0) {
231 		g_warning("socks5_connect: SOCKS5 reply (%u) error", socks_req[1]);
232 		return -1;
233 	}
234 
235 	socks_req[0] = 5;
236 	socks_req[1] = 1;
237 	socks_req[2] = 0;
238 
239 	socks_req[3] = 3;
240 	socks_req[4] = (guchar)len;
241 	memcpy(socks_req + 5, hostname, len);
242 	*((gushort *)(socks_req + 5 + len)) = htons(port);
243 
244 	if (sock_write_all(sock, (gchar *)socks_req, 5 + len + 2) != 5 + len + 2) {
245 		g_warning("socks5_connect: SOCKS5 connect request write failed");
246 		return -1;
247 	}
248 
249 	if (sock_read(sock, (gchar *)socks_req, 10) != 10) {
250 		g_warning("socks5_connect: SOCKS5 connect request response read failed");
251 		return -1;
252 	}
253 	if (socks_req[0] != 5) {
254 		g_warning("socks5_connect: SOCKS5 response has invalid version");
255 		return -1;
256 	}
257 	if (socks_req[1] != 0) {
258 		g_warning("socks5_connect: SOCKS5 connection to %s:%u failed. (%u)",
259 				hostname, port, socks_req[1]);
260 		return -1;
261 	}
262 
263 	size = 10;
264 	if (socks_req[3] == 3)
265 		size = 5 + socks_req[4] + 2;
266 	else if (socks_req[3] == 4)
267 		size = 4 + 16 + 2;
268 	if (size > 10) {
269 		size -= 10;
270 		if (sock_read(sock, (gchar *)socks_req + 10, size) != size) {
271 			g_warning("socks5_connect: SOCKS5 connect request response read failed");
272 			return -1;
273 		}
274 	}
275 
276 	/* replace sock->hostname with endpoint */
277 	if (sock->hostname != hostname) {
278 		g_free(sock->hostname);
279 		sock->hostname = g_strdup(hostname);
280 		sock->port = port;
281 	}
282 
283 	debug_print("socks5_connect: SOCKS5 connection to %s:%u successful.\n", hostname, port);
284 
285 	return 0;
286 }
287