1/*	$OpenBSD: socks.c,v 1.15 2005/05/24 20:13:28 avsm Exp $	*/
2
3/*
4 * Copyright (c) 1999 Niklas Hallqvist.  All rights reserved.
5 * Copyright (c) 2004, 2005 Damien Miller.  All rights reserved.
6 *
7 * Redistribution and use in source and binary forms, with or without
8 * modification, are permitted provided that the following conditions
9 * are met:
10 * 1. Redistributions of source code must retain the above copyright
11 *    notice, this list of conditions and the following disclaimer.
12 * 2. Redistributions in binary form must reproduce the above copyright
13 *    notice, this list of conditions and the following disclaimer in the
14 *    documentation and/or other materials provided with the distribution.
15 *
16 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
17 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
18 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
19 * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
20 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
21 * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
22 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
23 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
24 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
25 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
26 */
27
28#include <sys/types.h>
29#include <sys/socket.h>
30#include <netinet/in.h>
31#include <arpa/inet.h>
32
33#include <err.h>
34#include <errno.h>
35#include <netdb.h>
36#include <stdio.h>
37#include <stdlib.h>
38#include <string.h>
39#include <unistd.h>
40#include "atomicio.h"
41
42#define SOCKS_PORT	"1080"
43#define HTTP_PROXY_PORT	"3128"
44#define HTTP_MAXHDRS	64
45#define SOCKS_V5	5
46#define SOCKS_V4	4
47#define SOCKS_NOAUTH	0
48#define SOCKS_NOMETHOD	0xff
49#define SOCKS_CONNECT	1
50#define SOCKS_IPV4	1
51#define SOCKS_DOMAIN	3
52#define SOCKS_IPV6	4
53
54int	remote_connect(const char *, const char *, struct addrinfo);
55int	socks_connect(const char *host, const char *port, struct addrinfo hints,
56	    const char *proxyhost, const char *proxyport, struct addrinfo proxyhints,
57	    int socksv);
58
59static int
60decode_addrport(const char *h, const char *p, struct sockaddr *addr,
61    socklen_t addrlen, int v4only, int numeric)
62{
63	int r;
64	struct addrinfo hints, *res;
65
66	bzero(&hints, sizeof(hints));
67	hints.ai_family = v4only ? PF_INET : PF_UNSPEC;
68	hints.ai_flags = numeric ? AI_NUMERICHOST : 0;
69	hints.ai_socktype = SOCK_STREAM;
70	r = getaddrinfo(h, p, &hints, &res);
71	/* Don't fatal when attempting to convert a numeric address */
72	if (r != 0) {
73		if (!numeric) {
74			errx(1, "getaddrinfo(\"%.64s\", \"%.64s\"): %s", h, p,
75			    gai_strerror(r));
76		}
77		return (-1);
78	}
79	if (addrlen < res->ai_addrlen) {
80		freeaddrinfo(res);
81		errx(1, "internal error: addrlen < res->ai_addrlen");
82	}
83	memcpy(addr, res->ai_addr, res->ai_addrlen);
84	freeaddrinfo(res);
85	return (0);
86}
87
88static int
89proxy_read_line(int fd, char *buf, size_t bufsz)
90{
91	size_t off;
92
93	for(off = 0;;) {
94		if (off >= bufsz)
95			errx(1, "proxy read too long");
96		if (atomicio(read, fd, buf + off, 1) != 1)
97			err(1, "proxy read");
98		/* Skip CR */
99		if (buf[off] == '\r')
100			continue;
101		if (buf[off] == '\n') {
102			buf[off] = '\0';
103			break;
104		}
105		off++;
106	}
107	return (off);
108}
109
110int
111socks_connect(const char *host, const char *port,
112    struct addrinfo hints __attribute__ ((__unused__)),
113    const char *proxyhost, const char *proxyport, struct addrinfo proxyhints,
114    int socksv)
115{
116	int proxyfd, r;
117	size_t hlen, wlen;
118	unsigned char buf[1024];
119	size_t cnt;
120	struct sockaddr_storage addr;
121	struct sockaddr_in *in4 = (struct sockaddr_in *)&addr;
122	struct sockaddr_in6 *in6 = (struct sockaddr_in6 *)&addr;
123	in_port_t serverport;
124
125	if (proxyport == NULL)
126		proxyport = (socksv == -1) ? HTTP_PROXY_PORT : SOCKS_PORT;
127
128	proxyfd = remote_connect(proxyhost, proxyport, proxyhints);
129
130	if (proxyfd < 0)
131		return (-1);
132
133	/* Abuse API to lookup port */
134	if (decode_addrport("0.0.0.0", port, (struct sockaddr *)&addr,
135	    sizeof(addr), 1, 1) == -1)
136		errx(1, "unknown port \"%.64s\"", port);
137	serverport = in4->sin_port;
138
139	if (socksv == 5) {
140		if (decode_addrport(host, port, (struct sockaddr *)&addr,
141		    sizeof(addr), 0, 1) == -1)
142			addr.ss_family = 0; /* used in switch below */
143
144		/* Version 5, one method: no authentication */
145		buf[0] = SOCKS_V5;
146		buf[1] = 1;
147		buf[2] = SOCKS_NOAUTH;
148		cnt = atomicio(vwrite, proxyfd, buf, 3);
149		if (cnt != 3)
150			err(1, "write failed (%lu/3)", cnt);
151
152		cnt = atomicio(read, proxyfd, buf, 2);
153		if (cnt != 2)
154			err(1, "read failed (%lu/3)", cnt);
155
156		if (buf[1] == SOCKS_NOMETHOD)
157			errx(1, "authentication method negotiation failed");
158
159		switch (addr.ss_family) {
160		case 0:
161			/* Version 5, connect: domain name */
162
163			/* Max domain name length is 255 bytes */
164			hlen = strlen(host);
165			if (hlen > 255)
166				errx(1, "host name too long for SOCKS5");
167			buf[0] = SOCKS_V5;
168			buf[1] = SOCKS_CONNECT;
169			buf[2] = 0;
170			buf[3] = SOCKS_DOMAIN;
171			buf[4] = hlen;
172			memcpy(buf + 5, host, hlen);
173			memcpy(buf + 5 + hlen, &serverport, sizeof serverport);
174			wlen = 7 + hlen;
175			break;
176		case AF_INET:
177			/* Version 5, connect: IPv4 address */
178			buf[0] = SOCKS_V5;
179			buf[1] = SOCKS_CONNECT;
180			buf[2] = 0;
181			buf[3] = SOCKS_IPV4;
182			memcpy(buf + 4, &in4->sin_addr, sizeof in4->sin_addr);
183			memcpy(buf + 8, &in4->sin_port, sizeof in4->sin_port);
184			wlen = 10;
185			break;
186		case AF_INET6:
187			/* Version 5, connect: IPv6 address */
188			buf[0] = SOCKS_V5;
189			buf[1] = SOCKS_CONNECT;
190			buf[2] = 0;
191			buf[3] = SOCKS_IPV6;
192			memcpy(buf + 4, &in6->sin6_addr, sizeof in6->sin6_addr);
193			memcpy(buf + 20, &in6->sin6_port,
194			    sizeof in6->sin6_port);
195			wlen = 22;
196			break;
197		default:
198			errx(1, "internal error: silly AF");
199		}
200
201		cnt = atomicio(vwrite, proxyfd, buf, wlen);
202		if (cnt != wlen)
203			err(1, "write failed (%lu/%lu)", cnt, wlen);
204
205		cnt = atomicio(read, proxyfd, buf, 10);
206		if (cnt != 10)
207			err(1, "read failed (%lu/10)", cnt);
208		if (buf[1] != 0)
209			errx(1, "connection failed, SOCKS error %d", buf[1]);
210	} else if (socksv == 4) {
211		/* This will exit on lookup failure */
212		decode_addrport(host, port, (struct sockaddr *)&addr,
213		    sizeof(addr), 1, 0);
214
215		/* Version 4 */
216		buf[0] = SOCKS_V4;
217		buf[1] = SOCKS_CONNECT;	/* connect */
218		memcpy(buf + 2, &in4->sin_port, sizeof in4->sin_port);
219		memcpy(buf + 4, &in4->sin_addr, sizeof in4->sin_addr);
220		buf[8] = 0;	/* empty username */
221		wlen = 9;
222
223		cnt = atomicio(vwrite, proxyfd, buf, wlen);
224		if (cnt != wlen)
225			err(1, "write failed (%lu/%lu)", cnt, wlen);
226
227		cnt = atomicio(read, proxyfd, buf, 8);
228		if (cnt != 8)
229			err(1, "read failed (%lu/8)", cnt);
230		if (buf[1] != 90)
231			errx(1, "connection failed, SOCKS error %d", buf[1]);
232	} else if (socksv == -1) {
233		/* HTTP proxy CONNECT */
234
235		/* Disallow bad chars in hostname */
236		if (strcspn(host, "\r\n\t []:") != strlen(host))
237			errx(1, "Invalid hostname");
238
239		/* Try to be sane about numeric IPv6 addresses */
240		if (strchr(host, ':') != NULL) {
241			r = snprintf((char *)buf, sizeof(buf),
242			    "CONNECT [%s]:%d HTTP/1.0\r\n\r\n",
243			    host, ntohs(serverport));
244		} else {
245			r = snprintf((char *)buf, sizeof(buf),
246			    "CONNECT %s:%d HTTP/1.0\r\n\r\n",
247			    host, ntohs(serverport));
248		}
249		if (r == -1 || (size_t)r >= sizeof(buf))
250			errx(1, "hostname too long");
251		r = strlen((char *)buf);
252
253		cnt = atomicio(vwrite, proxyfd, buf, r);
254		if (cnt != r)
255			err(1, "write failed (%lu/%d)", cnt, r);
256
257		/* Read reply */
258		for (r = 0; r < HTTP_MAXHDRS; r++) {
259			proxy_read_line(proxyfd, (char *)buf, sizeof(buf));
260			if (r == 0 && strncmp((char *)buf, "HTTP/1.0 200 ", 12) != 0)
261				errx(1, "Proxy error: \"%s\"", buf);
262			/* Discard headers until we hit an empty line */
263			if (*buf == '\0')
264				break;
265		}
266	} else
267		errx(1, "Unknown proxy protocol %d", socksv);
268
269	return (proxyfd);
270}
271