7bde533c8f0837c0a3e65980798a025d97ef0d7d
[claws.git] / src / common / proxy.c
1 /*
2  * Claws Mail -- a GTK+ based, lightweight, and fast e-mail client
3  * Copyright (C) 1999-2014 Hiroyuki Yamamoto
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 #endif
29
30 #include <sys/socket.h>
31 #include <netinet/in.h>
32 #include <netinet/ip.h>
33 #include "proxy.h"
34 #include "socket.h"
35 #include "utils.h"
36
37 gint socks4_connect(SockInfo *sock, const gchar *hostname, gushort port);
38 gint socks5_connect(SockInfo *sock, const gchar *hostname, gushort port,
39                 const gchar *proxy_name, const gchar *proxy_pass);
40
41 gint proxy_connect(SockInfo *sock, const gchar *hostname, gushort port,
42                    ProxyInfo *proxy_info)
43 {
44         gint ret;
45
46         g_return_val_if_fail(sock != NULL, -1);
47         g_return_val_if_fail(hostname != NULL, -1);
48         g_return_val_if_fail(proxy_info != NULL, -1);
49
50         debug_print("proxy_connect: connect to %s:%u via %s:%u\n",
51                     hostname, port,
52                     proxy_info->proxy_host, proxy_info->proxy_port);
53
54         if (proxy_info->proxy_type == PROXY_SOCKS5) {
55                 ret = socks5_connect(sock, hostname, port,
56                                       proxy_info->use_proxy_auth ? proxy_info->proxy_name : NULL,
57                                       proxy_info->use_proxy_auth ? proxy_info->proxy_pass : NULL);
58                 /* Scrub the password before returning */
59                 if (proxy_info->proxy_pass != NULL) {
60                         memset(proxy_info->proxy_pass, 0, strlen(proxy_info->proxy_pass));
61                         g_free(proxy_info->proxy_pass);
62                 }
63                 return ret;
64         } else if (proxy_info->proxy_type == PROXY_SOCKS4) {
65                 return socks4_connect(sock, hostname, port);
66         } else {
67                 g_warning("proxy_connect: unknown SOCKS type: %d\n",
68                           proxy_info->proxy_type);
69         }
70
71         return -1;
72 }
73
74 gint socks4_connect(SockInfo *sock, const gchar *hostname, gushort port)
75 {
76         guchar socks_req[1024];
77         struct addrinfo hints, *res, *ai;
78         gboolean got_address = FALSE;
79         int s;
80
81         g_return_val_if_fail(sock != NULL, -1);
82         g_return_val_if_fail(hostname != NULL, -1);
83
84         debug_print("socks4_connect: connect to %s:%u\n", hostname, port);
85
86         socks_req[0] = 4;
87         socks_req[1] = 1;
88         *((gushort *)(socks_req + 2)) = htons(port);
89
90         /* lookup */
91         memset(&hints, 0, sizeof(struct addrinfo));
92         hints.ai_family = AF_INET; /* SOCKS4 only supports IPv4 addresses */
93
94         s = getaddrinfo(hostname, NULL, &hints, &res);
95         if (s != 0) {
96                 fprintf(stderr, "getaddrinfo for '%s' failed: %s\n",
97                                 hostname, gai_strerror(s));
98                 return -1;
99         }
100
101         for (ai = res; ai != NULL; ai = ai->ai_next) {
102                 uint32_t addr;
103
104                 if (ai->ai_family != AF_INET)
105                         continue;
106
107                 addr = ((struct sockaddr_in *)ai->ai_addr)->sin_addr.s_addr;
108                 memcpy(socks_req + 4, &addr, 4);
109                 got_address = TRUE;
110                 break;
111         }
112
113         if (res != NULL)
114                 freeaddrinfo(res);
115
116         if (!got_address) {
117                 g_warning("socks4_connect: could not get valid IPv4 address for '%s'", hostname);
118                 return -1;
119         }
120
121         debug_print("got a valid IPv4 address, continuing\n");
122
123         /* userid (empty) */
124         socks_req[8] = 0;
125
126         if (sock_write_all(sock, (gchar *)socks_req, 9) != 9) {
127                 g_warning("socks4_connect: SOCKS4 initial request write failed");
128                 return -1;
129         }
130
131         if (sock_read(sock, (gchar *)socks_req, 8) != 8) {
132                 g_warning("socks4_connect: SOCKS4 response read failed");
133                 return -1;
134         }
135         if (socks_req[0] != 0) {
136                 g_warning("socks4_connect: SOCKS4 response has invalid version");
137                 return -1;
138         }
139         if (socks_req[1] != 90) {
140                 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]);
141                 return -1;
142         }
143
144         /* replace sock->hostname with endpoint */
145         if (sock->hostname != hostname) {
146                 g_free(sock->hostname);
147                 sock->hostname = g_strdup(hostname);
148                 sock->port = port;
149         }
150
151         debug_print("socks4_connect: SOCKS4 connection to %s:%u successful.\n", hostname, port);
152
153         return 0;
154 }
155
156 gint socks5_connect(SockInfo *sock, const gchar *hostname, gushort port,
157                     const gchar *proxy_name, const gchar *proxy_pass)
158 {
159         guchar socks_req[1024];
160         size_t len;
161         size_t size;
162
163         g_return_val_if_fail(sock != NULL, -1);
164         g_return_val_if_fail(hostname != NULL, -1);
165
166         debug_print("socks5_connect: connect to %s:%u\n", hostname, port);
167
168         len = strlen(hostname);
169         if (len > 255) {
170                 g_warning("socks5_connect: hostname too long");
171                 return -1;
172         }
173
174         socks_req[0] = 5;
175         socks_req[1] = proxy_name ? 2 : 1;
176         socks_req[2] = 0;
177         socks_req[3] = 2;
178
179         if (sock_write_all(sock, (gchar *)socks_req, 2 + socks_req[1]) != 2 + socks_req[1]) {
180                 g_warning("socks5_connect: SOCKS5 initial request write failed");
181                 return -1;
182         }
183
184         if (sock_read(sock, (gchar *)socks_req, 2) != 2) {
185                 g_warning("socks5_connect: SOCKS5 response read failed");
186                 return -1;
187         }
188         if (socks_req[0] != 5) {
189                 g_warning("socks5_connect: SOCKS5 response has invalid version");
190                 return -1;
191         }
192         if (socks_req[1] == 2) {
193                 /* auth */
194                 size_t userlen, passlen;
195                 gint reqlen;
196
197                 if (proxy_name && proxy_pass) {
198                         debug_print("socks5_connect: auth using username '%s'\n", proxy_name);
199                         userlen = strlen(proxy_name);
200                         passlen = strlen(proxy_pass);
201                 } else
202                         userlen = passlen = 0;
203
204                 socks_req[0] = 1;
205                 socks_req[1] = (guchar)userlen;
206                 if (proxy_name && userlen > 0)
207                         memcpy(socks_req + 2, proxy_name, userlen);
208                 socks_req[2 + userlen] = (guchar)passlen;
209                 if (proxy_pass && passlen > 0)
210                         memcpy(socks_req + 2 + userlen + 1, proxy_pass, passlen);
211
212                 reqlen = 2 + userlen + 1 + passlen;
213                 if (sock_write_all(sock, (gchar *)socks_req, reqlen) != reqlen) {
214                         memset(socks_req, 0, reqlen);
215                         g_warning("socks5_connect: SOCKS5 auth write failed");
216                         return -1;
217                 }
218                 memset(socks_req, 0, reqlen);
219                 if (sock_read(sock, (gchar *)socks_req, 2) != 2) {
220                         g_warning("socks5_connect: SOCKS5 auth response read failed");
221                         return -1;
222                 }
223                 if (socks_req[1] != 0) {
224                         g_warning("socks5_connect: SOCKS5 authentication failed: user: %s (%u %u)", proxy_name ? proxy_name : "(none)", socks_req[0], socks_req[1]);
225                         return -1;
226                 }
227         } else if (socks_req[1] != 0) {
228                 g_warning("socks5_connect: SOCKS5 reply (%u) error", socks_req[1]);
229                 return -1;
230         }
231
232         socks_req[0] = 5;
233         socks_req[1] = 1;
234         socks_req[2] = 0;
235
236         socks_req[3] = 3;
237         socks_req[4] = (guchar)len;
238         memcpy(socks_req + 5, hostname, len);
239         *((gushort *)(socks_req + 5 + len)) = htons(port);
240
241         if (sock_write_all(sock, (gchar *)socks_req, 5 + len + 2) != 5 + len + 2) {
242                 g_warning("socks5_connect: SOCKS5 connect request write failed");
243                 return -1;
244         }
245
246         if (sock_read(sock, (gchar *)socks_req, 10) != 10) {
247                 g_warning("socks5_connect: SOCKS5 connect request response read failed");
248                 return -1;
249         }
250         if (socks_req[0] != 5) {
251                 g_warning("socks5_connect: SOCKS5 response has invalid version");
252                 return -1;
253         }
254         if (socks_req[1] != 0) {
255                 if (socks_req[3] == 1) { /* IPv4 address */
256                         g_warning("socks5_connect: SOCKS5 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 + 8)), socks_req[1]);
257                 } else if (socks_req[3] == 3) { /* Domain name */
258                         gint hnlen = socks_req[4];
259                         gchar *hn = malloc(hnlen + 1);
260                         hn[hnlen + 1] = '\0';
261                         memcpy(hn, &socks_req[5], hnlen);
262                         g_warning("socks5_connect: SOCKS5 connection to %s:%u failed. (%u)",
263                                         hn, ntohs(*(gushort *)(socks_req + 5 + hnlen)), socks_req[1]);
264                         g_free(hn);
265                 } else if (socks_req[3] == 4) { /* IPv6 address */
266                         gint hnlen = 16;
267                         gchar *hn = malloc(hnlen + 1);
268                         hn[hnlen + 1] = '\0';
269                         memcpy(hn, &socks_req[4], hnlen);
270                         g_warning("socks5_connect: SOCKS5 connection to IPv6 %s:%u failed. (%u)",
271                                         hn, ntohs(*(gushort *)(socks_req + 5 + hnlen)), socks_req[1]);
272                         g_free(hn);
273                 }
274                 return -1;
275         }
276
277         size = 10;
278         if (socks_req[3] == 3)
279                 size = 5 + socks_req[4] + 2;
280         else if (socks_req[3] == 4)
281                 size = 4 + 16 + 2;
282         if (size > 10) {
283                 size -= 10;
284                 if (sock_read(sock, (gchar *)socks_req + 10, size) != size) {
285                         g_warning("socks5_connect: SOCKS5 connect request response read failed");
286                         return -1;
287                 }
288         }
289
290         /* replace sock->hostname with endpoint */
291         if (sock->hostname != hostname) {
292                 g_free(sock->hostname);
293                 sock->hostname = g_strdup(hostname);
294                 sock->port = port;
295         }
296
297         debug_print("socks5_connect: SOCKS5 connection to %s:%u successful.\n", hostname, port);
298
299         return 0;
300 }