1/*
2   SOCKS proxy support for neon
3   Copyright (C) 2008, Joe Orton <joe@manyfish.co.uk>
4
5   This library is free software; you can redistribute it and/or
6   modify it under the terms of the GNU Library General Public
7   License as published by the Free Software Foundation; either
8   version 2 of the License, or (at your option) any later version.
9
10   This library 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 GNU
13   Library General Public License for more details.
14
15   You should have received a copy of the GNU Library General Public
16   License along with this library; if not, write to the Free
17   Software Foundation, Inc., 59 Temple Place - Suite 330, Boston,
18   MA 02111-1307, USA
19*/
20
21#include "config.h"
22
23#include "ne_internal.h"
24#include "ne_string.h"
25#include "ne_socket.h"
26#include "ne_utils.h"
27
28#include <string.h>
29
30/* SOCKS protocol reference:
31   v4:  http://www.ufasoft.com/doc/socks4_protocol.htm
32   v4a  http://www.smartftp.com/Products/SmartFTP/RFC/socks4a.protocol
33   v5:  http://tools.ietf.org/html/rfc1928
34   ...v5 auth: http://tools.ietf.org/html/rfc1929
35*/
36
37#define V5_REPLY_OK 0
38#define V5_REPLY_FAIL 1
39#define V5_REPLY_DISALLOW 2
40#define V5_REPLY_NET_UNREACH 3
41#define V5_REPLY_HOST_UNREACH 4
42#define V5_REPLY_CONN_REFUSED 5
43#define V5_REPLY_TTL_EXPIRED 6
44#define V5_REPLY_CMD_UNSUPPORTED 7
45#define V5_REPLY_TYPE_UNSUPPORTED 8
46
47#define V5_VERSION   0x05
48#define V5_ADDR_IPV4 0x01
49#define V5_ADDR_FQDN 0x03
50#define V5_ADDR_IPV6 0x04
51
52#define V5_CMD_CONNECT 0x01
53
54#define V5_AUTH_NONE   0x00
55#define V5_AUTH_USER   0x02
56#define V5_AUTH_NOMETH 0xFF
57
58/* Fail with given V5 error code in given context. */
59static int v5fail(ne_socket *sock, unsigned int code, const char *context)
60{
61    const char *err;
62
63    switch (code) {
64    case V5_REPLY_FAIL:
65        err = _("failure");
66        break;
67    case V5_REPLY_DISALLOW:
68        err = _("connection not permitted");
69        break;
70    case V5_REPLY_NET_UNREACH:
71        err = _("network unreachable");
72        break;
73    case V5_REPLY_HOST_UNREACH:
74        err = _("host unreachable");
75        break;
76    case V5_REPLY_TTL_EXPIRED:
77        err = _("TTL expired");
78        break;
79    case V5_REPLY_CMD_UNSUPPORTED:
80        err = _("command not supported");
81        break;
82    case V5_REPLY_TYPE_UNSUPPORTED:
83        err = _("address type not supported");
84        break;
85    default:
86        ne_sock_set_error(sock, _("%s: unrecognized error (%u)"), context, code);
87        return NE_SOCK_ERROR;
88    }
89
90    ne_sock_set_error(sock, "%s: %s", context, err);
91    return NE_SOCK_ERROR;
92}
93
94/* Fail with given error string. */
95static int fail(ne_socket *sock, const char *error)
96{
97    ne_sock_set_error(sock, "%s", error);
98    return NE_SOCK_ERROR;
99}
100
101/* Fail with given NE_SOCK_* error code and given context. */
102static int sofail(ne_socket *sock, ssize_t ret, const char *context)
103{
104    char *err = ne_strdup(ne_sock_error(sock));
105    ne_sock_set_error(sock, "%s: %s", context, err);
106    ne_free(err);
107    return NE_SOCK_ERROR;
108}
109
110/* SOCKSv5 proxy. */
111static int v5_proxy(ne_socket *sock, const ne_inet_addr *addr,
112                    const char *hostname, unsigned int port,
113                    const char *username, const char *password)
114{
115    unsigned char msg[1024], *p;
116    unsigned int len;
117    int ret;
118    ssize_t n;
119
120    p = msg;
121    *p++ = V5_VERSION;
122    *p++ = 2; /* Two supported auth protocols; none and user. */
123    *p++ = V5_AUTH_NONE;
124    *p++ = V5_AUTH_USER;
125
126    ret = ne_sock_fullwrite(sock, (char *)msg, p - msg);
127    if (ret) {
128        return sofail(sock, ret, _("Could not send message to proxy"));
129    }
130
131    n = ne_sock_fullread(sock, (char *)msg, 2);
132    if (n) {
133        return sofail(sock, ret, _("Could not read initial response from proxy"));
134    }
135    else if (msg[0] != V5_VERSION) {
136        return fail(sock, _("Invalid version in proxy response"));
137    }
138
139    /* Authenticate, if necessary. */
140    switch (msg[1]) {
141    case V5_AUTH_NONE:
142        break;
143    case V5_AUTH_USER:
144        p = msg;
145        *p++ = 0x01;
146        len = strlen(username) & 0xff;
147        *p++ = len;
148        memcpy(p, username, len);
149        p += len;
150        len = strlen(password) & 0xff;
151        *p++ = len;
152        memcpy(p, password, len);
153        p += len;
154
155        ret = ne_sock_fullwrite(sock, (char *)msg, p - msg);
156        if (ret) {
157            return sofail(sock, ret, _("Could not send login message"));
158        }
159
160        n = ne_sock_fullread(sock, (char *)msg, 2);
161        if (n) {
162            return sofail(sock, ret, _("Could not read login reply"));
163        }
164        else if (msg[0] != 1) {
165            return fail(sock, _("Invalid version in login reply"));
166        }
167        else if (msg[1] != 0) {
168            return fail(sock, _("Authentication failed"));
169        }
170        break;
171    case V5_AUTH_NOMETH:
172        return fail(sock, _("No acceptable authentication method"));
173    default:
174        return fail(sock, _("Unexpected authentication method chosen"));
175    }
176
177    /* Send the CONNECT command. */
178    p = msg;
179    *p++ = V5_VERSION;
180    *p++ = V5_CMD_CONNECT;
181    *p++ = 0; /* reserved */
182    if (addr) {
183        unsigned char raw[16];
184
185        if (ne_iaddr_typeof(addr) == ne_iaddr_ipv4) {
186            len = 4;
187            *p++ = V5_ADDR_IPV4;
188        }
189        else {
190            len = 16;
191            *p++ = V5_ADDR_IPV6;
192        }
193
194        memcpy(p, ne_iaddr_raw(addr, raw), len);
195        p += len;
196    }
197    else {
198        len = strlen(hostname) & 0xff;
199        *p++ = V5_ADDR_FQDN;
200        *p++ = len;
201        memcpy(p, hostname, len);
202        p += len;
203    }
204
205    *p++ = (port >> 8) & 0xff;
206    *p++ = port & 0xff;
207
208    ret = ne_sock_fullwrite(sock, (char *)msg, p - msg);
209    if (ret) {
210        return sofail(sock, ret, _("Could not send connect request"));
211    }
212
213    n = ne_sock_fullread(sock, (char *)msg, 4);
214    if (n) {
215        return sofail(sock, n, _("Could not read connect reply"));
216    }
217    if (msg[0] != V5_VERSION) {
218        return fail(sock, _("Invalid version in connect reply"));
219    }
220    if (msg[1] != V5_REPLY_OK) {
221        return v5fail(sock, msg[1], _("Could not connect"));
222    }
223
224    switch (msg[3]) {
225    case V5_ADDR_IPV4:
226        len = 4;
227        break;
228    case V5_ADDR_IPV6:
229        len = 16;
230        break;
231    case V5_ADDR_FQDN:
232        n = ne_sock_read(sock, (char *)msg, 1);
233        if (n != 1) {
234            return sofail(sock, n,
235                            _("Could not read FQDN length in connect reply"));
236        }
237        len = msg[0];
238        break;
239    default:
240        return fail(sock, _("Unknown address type in connect reply"));
241    }
242
243    n = ne_sock_fullread(sock, (char *)msg, len + 2);
244    if (n) {
245        return sofail(sock, n, _("Could not read address in connect reply"));
246    }
247
248    return 0;
249}
250
251#define V4_VERSION 0x04
252#define V4_CMD_STREAM 0x01
253
254#define V4_REP_OK      0x5a /* request granted */
255#define V4_REP_FAIL    0x5b /* request rejected or failed */
256#define V4_REP_NOIDENT 0x5c /* request failed, could connect to identd */
257#define V4_REP_IDFAIL  0x5d /* request failed, identd denial */
258
259/* Fail for given SOCKSv4 error code. */
260static int v4fail(ne_socket *sock, unsigned int code, const char *context)
261{
262    const char *err;
263
264    switch (code) {
265    case V4_REP_FAIL:
266        err = _("request rejected or failed");
267        break;
268    case V4_REP_NOIDENT:
269        err = _("could not establish connection to identd");
270        break;
271    case V4_REP_IDFAIL:
272        err = _("rejected due to identd user mismatch");
273        break;
274    default:
275        ne_sock_set_error(sock, _("%s: unrecognized failure (%u)"),
276                          context, code);
277        return NE_SOCK_ERROR;
278    }
279
280    ne_sock_set_error(sock, "%s: %s", context, err);
281    return NE_SOCK_ERROR;
282}
283
284/* SOCKS v4 or v4A proxy. */
285static int v4_proxy(ne_socket *sock, enum ne_sock_sversion vers,
286                    const ne_inet_addr *addr, const char *hostname,
287                    unsigned int port, const char *username)
288{
289    unsigned char msg[1024], raw[16], *p;
290    ssize_t n;
291    int ret;
292
293    p = msg;
294    *p++ = V4_VERSION;
295    *p++ = V4_CMD_STREAM;
296    *p++ = (port >> 8) & 0xff;
297    *p++ = port & 0xff;
298
299    if (vers == NE_SOCK_SOCKSV4A) {
300        /* A bogus address is used to signify use of the hostname,
301         * 0.0.0.X where X != 0. */
302        memcpy(p, "\x00\x00\x00\xff", 4);
303    }
304    else {
305        /* API precondition that addr is IPv4; if it's not this will
306         * just copy out the first four bytes of the v6 address;
307         * garbage in => garbage out. */
308        memcpy(p, ne_iaddr_raw(addr, raw), 4);
309    }
310    p += 4;
311
312    if (username) {
313        unsigned int len = strlen(username) & 0xff;
314        memcpy(p, username, len);
315        p += len;
316    }
317    *p++ = '\0';
318
319    if (vers == NE_SOCK_SOCKSV4A) {
320        unsigned int len = strlen(hostname) & 0xff;
321        memcpy(p, hostname, len);
322        p += len;
323        *p++ = '\0';
324    }
325
326    ret = ne_sock_fullwrite(sock, (char *)msg, p - msg);
327    if (ret) {
328        return sofail(sock, ret, _("Could not send message to proxy"));
329    }
330
331    n = ne_sock_fullread(sock, (char *)msg, 8);
332    if (n) {
333        return sofail(sock, ret, _("Could not read response from proxy"));
334    }
335
336    if (msg[1] != V4_REP_OK) {
337        return v4fail(sock, ret, _("Could not connect"));
338    }
339
340    return 0;
341}
342
343int ne_sock_proxy(ne_socket *sock, enum ne_sock_sversion vers,
344                  const ne_inet_addr *addr, const char *hostname,
345                  unsigned int port,
346                  const char *username, const char *password)
347{
348    if (vers == NE_SOCK_SOCKSV5) {
349        return v5_proxy(sock, addr, hostname, port, username, password);
350    }
351    else {
352        return v4_proxy(sock, vers, addr, hostname, port, username);
353    }
354}
355