Fix dangling pointer in proxy password handling.
[claws.git] / src / common / proxy.c
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
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
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
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 }