1241906Sdelphij/*	$OpenBSD: socks.c,v 1.20 2012/03/08 09:56:28 espie Exp $	*/
2141261Sdelphij
3141261Sdelphij/*
4141261Sdelphij * Copyright (c) 1999 Niklas Hallqvist.  All rights reserved.
5158795Sdelphij * Copyright (c) 2004, 2005 Damien Miller.  All rights reserved.
6141261Sdelphij *
7141261Sdelphij * Redistribution and use in source and binary forms, with or without
8141261Sdelphij * modification, are permitted provided that the following conditions
9141261Sdelphij * are met:
10141261Sdelphij * 1. Redistributions of source code must retain the above copyright
11141261Sdelphij *    notice, this list of conditions and the following disclaimer.
12141261Sdelphij * 2. Redistributions in binary form must reproduce the above copyright
13141261Sdelphij *    notice, this list of conditions and the following disclaimer in the
14141261Sdelphij *    documentation and/or other materials provided with the distribution.
15141261Sdelphij *
16141261Sdelphij * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
17141261Sdelphij * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
18141261Sdelphij * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
19141261Sdelphij * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
20141261Sdelphij * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
21141261Sdelphij * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
22141261Sdelphij * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
23141261Sdelphij * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
24141261Sdelphij * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
25141261Sdelphij * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
26141261Sdelphij */
27141261Sdelphij
28141261Sdelphij#include <sys/types.h>
29141261Sdelphij#include <sys/socket.h>
30141261Sdelphij#include <netinet/in.h>
31141261Sdelphij#include <arpa/inet.h>
32141261Sdelphij
33141261Sdelphij#include <err.h>
34141261Sdelphij#include <errno.h>
35141261Sdelphij#include <netdb.h>
36141261Sdelphij#include <stdio.h>
37141261Sdelphij#include <stdlib.h>
38141261Sdelphij#include <string.h>
39141261Sdelphij#include <unistd.h>
40158795Sdelphij#include <resolv.h>
41158795Sdelphij#include <readpassphrase.h>
42158795Sdelphij#include "atomicio.h"
43141261Sdelphij
44141261Sdelphij#define SOCKS_PORT	"1080"
45141261Sdelphij#define HTTP_PROXY_PORT	"3128"
46141261Sdelphij#define HTTP_MAXHDRS	64
47141261Sdelphij#define SOCKS_V5	5
48141261Sdelphij#define SOCKS_V4	4
49141261Sdelphij#define SOCKS_NOAUTH	0
50141261Sdelphij#define SOCKS_NOMETHOD	0xff
51141261Sdelphij#define SOCKS_CONNECT	1
52141261Sdelphij#define SOCKS_IPV4	1
53158795Sdelphij#define SOCKS_DOMAIN	3
54158795Sdelphij#define SOCKS_IPV6	4
55141261Sdelphij
56158795Sdelphijint	remote_connect(const char *, const char *, struct addrinfo);
57158795Sdelphijint	socks_connect(const char *, const char *, struct addrinfo,
58158795Sdelphij	    const char *, const char *, struct addrinfo, int,
59158795Sdelphij	    const char *);
60141261Sdelphij
61158795Sdelphijstatic int
62158795Sdelphijdecode_addrport(const char *h, const char *p, struct sockaddr *addr,
63158795Sdelphij    socklen_t addrlen, int v4only, int numeric)
64141261Sdelphij{
65158795Sdelphij	int r;
66158795Sdelphij	struct addrinfo hints, *res;
67141261Sdelphij
68158795Sdelphij	bzero(&hints, sizeof(hints));
69158795Sdelphij	hints.ai_family = v4only ? PF_INET : PF_UNSPEC;
70158795Sdelphij	hints.ai_flags = numeric ? AI_NUMERICHOST : 0;
71158795Sdelphij	hints.ai_socktype = SOCK_STREAM;
72158795Sdelphij	r = getaddrinfo(h, p, &hints, &res);
73158795Sdelphij	/* Don't fatal when attempting to convert a numeric address */
74158795Sdelphij	if (r != 0) {
75158795Sdelphij		if (!numeric) {
76158795Sdelphij			errx(1, "getaddrinfo(\"%.64s\", \"%.64s\"): %s", h, p,
77158795Sdelphij			    gai_strerror(r));
78158795Sdelphij		}
79158795Sdelphij		return (-1);
80141261Sdelphij	}
81158795Sdelphij	if (addrlen < res->ai_addrlen) {
82158795Sdelphij		freeaddrinfo(res);
83158795Sdelphij		errx(1, "internal error: addrlen < res->ai_addrlen");
84158795Sdelphij	}
85158795Sdelphij	memcpy(addr, res->ai_addr, res->ai_addrlen);
86158795Sdelphij	freeaddrinfo(res);
87158795Sdelphij	return (0);
88141261Sdelphij}
89141261Sdelphij
90141261Sdelphijstatic int
91158795Sdelphijproxy_read_line(int fd, char *buf, size_t bufsz)
92141261Sdelphij{
93158795Sdelphij	size_t off;
94141261Sdelphij
95141261Sdelphij	for(off = 0;;) {
96141261Sdelphij		if (off >= bufsz)
97141261Sdelphij			errx(1, "proxy read too long");
98158795Sdelphij		if (atomicio(read, fd, buf + off, 1) != 1)
99141261Sdelphij			err(1, "proxy read");
100141261Sdelphij		/* Skip CR */
101141261Sdelphij		if (buf[off] == '\r')
102141261Sdelphij			continue;
103141261Sdelphij		if (buf[off] == '\n') {
104141261Sdelphij			buf[off] = '\0';
105141261Sdelphij			break;
106141261Sdelphij		}
107141261Sdelphij		off++;
108141261Sdelphij	}
109141261Sdelphij	return (off);
110141261Sdelphij}
111141261Sdelphij
112158795Sdelphijstatic const char *
113158795Sdelphijgetproxypass(const char *proxyuser, const char *proxyhost)
114158795Sdelphij{
115158795Sdelphij	char prompt[512];
116158795Sdelphij	static char pw[256];
117158795Sdelphij
118158795Sdelphij	snprintf(prompt, sizeof(prompt), "Proxy password for %s@%s: ",
119158795Sdelphij	   proxyuser, proxyhost);
120158795Sdelphij	if (readpassphrase(prompt, pw, sizeof(pw), RPP_REQUIRE_TTY) == NULL)
121158795Sdelphij		errx(1, "Unable to read proxy passphrase");
122158795Sdelphij	return (pw);
123158795Sdelphij}
124158795Sdelphij
125141261Sdelphijint
126158795Sdelphijsocks_connect(const char *host, const char *port,
127158795Sdelphij    struct addrinfo hints __attribute__ ((__unused__)),
128158795Sdelphij    const char *proxyhost, const char *proxyport, struct addrinfo proxyhints,
129158795Sdelphij    int socksv, const char *proxyuser)
130141261Sdelphij{
131158795Sdelphij	int proxyfd, r, authretry = 0;
132158795Sdelphij	size_t hlen, wlen;
133141261Sdelphij	unsigned char buf[1024];
134158795Sdelphij	size_t cnt;
135158795Sdelphij	struct sockaddr_storage addr;
136158795Sdelphij	struct sockaddr_in *in4 = (struct sockaddr_in *)&addr;
137158795Sdelphij	struct sockaddr_in6 *in6 = (struct sockaddr_in6 *)&addr;
138141261Sdelphij	in_port_t serverport;
139158795Sdelphij	const char *proxypass = NULL;
140141261Sdelphij
141141261Sdelphij	if (proxyport == NULL)
142141261Sdelphij		proxyport = (socksv == -1) ? HTTP_PROXY_PORT : SOCKS_PORT;
143141261Sdelphij
144158795Sdelphij	/* Abuse API to lookup port */
145158795Sdelphij	if (decode_addrport("0.0.0.0", port, (struct sockaddr *)&addr,
146158795Sdelphij	    sizeof(addr), 1, 1) == -1)
147158795Sdelphij		errx(1, "unknown port \"%.64s\"", port);
148158795Sdelphij	serverport = in4->sin_port;
149158795Sdelphij
150158795Sdelphij again:
151158795Sdelphij	if (authretry++ > 3)
152158795Sdelphij		errx(1, "Too many authentication failures");
153158795Sdelphij
154141261Sdelphij	proxyfd = remote_connect(proxyhost, proxyport, proxyhints);
155141261Sdelphij
156141261Sdelphij	if (proxyfd < 0)
157158795Sdelphij		return (-1);
158141261Sdelphij
159158795Sdelphij	if (socksv == 5) {
160158795Sdelphij		if (decode_addrport(host, port, (struct sockaddr *)&addr,
161158795Sdelphij		    sizeof(addr), 0, 1) == -1)
162158795Sdelphij			addr.ss_family = 0; /* used in switch below */
163141261Sdelphij
164141261Sdelphij		/* Version 5, one method: no authentication */
165141261Sdelphij		buf[0] = SOCKS_V5;
166141261Sdelphij		buf[1] = 1;
167141261Sdelphij		buf[2] = SOCKS_NOAUTH;
168158795Sdelphij		cnt = atomicio(vwrite, proxyfd, buf, 3);
169141261Sdelphij		if (cnt != 3)
170214047Sdelphij			err(1, "write failed (%zu/3)", cnt);
171141261Sdelphij
172158795Sdelphij		cnt = atomicio(read, proxyfd, buf, 2);
173158795Sdelphij		if (cnt != 2)
174214047Sdelphij			err(1, "read failed (%zu/3)", cnt);
175158795Sdelphij
176141261Sdelphij		if (buf[1] == SOCKS_NOMETHOD)
177158795Sdelphij			errx(1, "authentication method negotiation failed");
178141261Sdelphij
179158795Sdelphij		switch (addr.ss_family) {
180158795Sdelphij		case 0:
181158795Sdelphij			/* Version 5, connect: domain name */
182141261Sdelphij
183158795Sdelphij			/* Max domain name length is 255 bytes */
184158795Sdelphij			hlen = strlen(host);
185158795Sdelphij			if (hlen > 255)
186158795Sdelphij				errx(1, "host name too long for SOCKS5");
187158795Sdelphij			buf[0] = SOCKS_V5;
188158795Sdelphij			buf[1] = SOCKS_CONNECT;
189158795Sdelphij			buf[2] = 0;
190158795Sdelphij			buf[3] = SOCKS_DOMAIN;
191158795Sdelphij			buf[4] = hlen;
192158795Sdelphij			memcpy(buf + 5, host, hlen);
193158795Sdelphij			memcpy(buf + 5 + hlen, &serverport, sizeof serverport);
194158795Sdelphij			wlen = 7 + hlen;
195158795Sdelphij			break;
196158795Sdelphij		case AF_INET:
197158795Sdelphij			/* Version 5, connect: IPv4 address */
198158795Sdelphij			buf[0] = SOCKS_V5;
199158795Sdelphij			buf[1] = SOCKS_CONNECT;
200158795Sdelphij			buf[2] = 0;
201158795Sdelphij			buf[3] = SOCKS_IPV4;
202158795Sdelphij			memcpy(buf + 4, &in4->sin_addr, sizeof in4->sin_addr);
203158795Sdelphij			memcpy(buf + 8, &in4->sin_port, sizeof in4->sin_port);
204158795Sdelphij			wlen = 10;
205158795Sdelphij			break;
206158795Sdelphij		case AF_INET6:
207158795Sdelphij			/* Version 5, connect: IPv6 address */
208158795Sdelphij			buf[0] = SOCKS_V5;
209158795Sdelphij			buf[1] = SOCKS_CONNECT;
210158795Sdelphij			buf[2] = 0;
211158795Sdelphij			buf[3] = SOCKS_IPV6;
212158795Sdelphij			memcpy(buf + 4, &in6->sin6_addr, sizeof in6->sin6_addr);
213158795Sdelphij			memcpy(buf + 20, &in6->sin6_port,
214158795Sdelphij			    sizeof in6->sin6_port);
215158795Sdelphij			wlen = 22;
216158795Sdelphij			break;
217158795Sdelphij		default:
218158795Sdelphij			errx(1, "internal error: silly AF");
219158795Sdelphij		}
220141261Sdelphij
221158795Sdelphij		cnt = atomicio(vwrite, proxyfd, buf, wlen);
222158795Sdelphij		if (cnt != wlen)
223214047Sdelphij			err(1, "write failed (%zu/%zu)", cnt, wlen);
224158795Sdelphij
225221793Sdelphij		cnt = atomicio(read, proxyfd, buf, 4);
226221793Sdelphij		if (cnt != 4)
227221793Sdelphij			err(1, "read failed (%zu/4)", cnt);
228141261Sdelphij		if (buf[1] != 0)
229158795Sdelphij			errx(1, "connection failed, SOCKS error %d", buf[1]);
230221793Sdelphij		switch (buf[3]) {
231221793Sdelphij		case SOCKS_IPV4:
232221793Sdelphij			cnt = atomicio(read, proxyfd, buf + 4, 6);
233221793Sdelphij			if (cnt != 6)
234241906Sdelphij				err(1, "read failed (%zu/6)", cnt);
235221793Sdelphij			break;
236221793Sdelphij		case SOCKS_IPV6:
237221793Sdelphij			cnt = atomicio(read, proxyfd, buf + 4, 18);
238221793Sdelphij			if (cnt != 18)
239241906Sdelphij				err(1, "read failed (%zu/18)", cnt);
240221793Sdelphij			break;
241221793Sdelphij		default:
242221793Sdelphij			errx(1, "connection failed, unsupported address type");
243221793Sdelphij		}
244141261Sdelphij	} else if (socksv == 4) {
245158795Sdelphij		/* This will exit on lookup failure */
246158795Sdelphij		decode_addrport(host, port, (struct sockaddr *)&addr,
247158795Sdelphij		    sizeof(addr), 1, 0);
248158795Sdelphij
249141261Sdelphij		/* Version 4 */
250141261Sdelphij		buf[0] = SOCKS_V4;
251141261Sdelphij		buf[1] = SOCKS_CONNECT;	/* connect */
252158795Sdelphij		memcpy(buf + 2, &in4->sin_port, sizeof in4->sin_port);
253158795Sdelphij		memcpy(buf + 4, &in4->sin_addr, sizeof in4->sin_addr);
254141261Sdelphij		buf[8] = 0;	/* empty username */
255158795Sdelphij		wlen = 9;
256141261Sdelphij
257158795Sdelphij		cnt = atomicio(vwrite, proxyfd, buf, wlen);
258158795Sdelphij		if (cnt != wlen)
259214047Sdelphij			err(1, "write failed (%zu/%zu)", cnt, wlen);
260141261Sdelphij
261158795Sdelphij		cnt = atomicio(read, proxyfd, buf, 8);
262141261Sdelphij		if (cnt != 8)
263214047Sdelphij			err(1, "read failed (%zu/8)", cnt);
264141261Sdelphij		if (buf[1] != 90)
265158795Sdelphij			errx(1, "connection failed, SOCKS error %d", buf[1]);
266141261Sdelphij	} else if (socksv == -1) {
267141261Sdelphij		/* HTTP proxy CONNECT */
268141261Sdelphij
269141261Sdelphij		/* Disallow bad chars in hostname */
270141261Sdelphij		if (strcspn(host, "\r\n\t []:") != strlen(host))
271158795Sdelphij			errx(1, "Invalid hostname");
272141261Sdelphij
273141261Sdelphij		/* Try to be sane about numeric IPv6 addresses */
274141261Sdelphij		if (strchr(host, ':') != NULL) {
275141261Sdelphij			r = snprintf(buf, sizeof(buf),
276158795Sdelphij			    "CONNECT [%s]:%d HTTP/1.0\r\n",
277141261Sdelphij			    host, ntohs(serverport));
278141261Sdelphij		} else {
279141261Sdelphij			r = snprintf(buf, sizeof(buf),
280158795Sdelphij			    "CONNECT %s:%d HTTP/1.0\r\n",
281141261Sdelphij			    host, ntohs(serverport));
282141261Sdelphij		}
283158795Sdelphij		if (r == -1 || (size_t)r >= sizeof(buf))
284158795Sdelphij			errx(1, "hostname too long");
285141261Sdelphij		r = strlen(buf);
286141261Sdelphij
287158795Sdelphij		cnt = atomicio(vwrite, proxyfd, buf, r);
288141261Sdelphij		if (cnt != r)
289214047Sdelphij			err(1, "write failed (%zu/%d)", cnt, r);
290141261Sdelphij
291158795Sdelphij		if (authretry > 1) {
292158795Sdelphij			char resp[1024];
293158795Sdelphij
294158795Sdelphij			proxypass = getproxypass(proxyuser, proxyhost);
295158795Sdelphij			r = snprintf(buf, sizeof(buf), "%s:%s",
296158795Sdelphij			    proxyuser, proxypass);
297158795Sdelphij			if (r == -1 || (size_t)r >= sizeof(buf) ||
298158795Sdelphij			    b64_ntop(buf, strlen(buf), resp,
299158795Sdelphij			    sizeof(resp)) == -1)
300158795Sdelphij				errx(1, "Proxy username/password too long");
301158795Sdelphij			r = snprintf(buf, sizeof(buf), "Proxy-Authorization: "
302158795Sdelphij			    "Basic %s\r\n", resp);
303158795Sdelphij			if (r == -1 || (size_t)r >= sizeof(buf))
304158795Sdelphij				errx(1, "Proxy auth response too long");
305158795Sdelphij			r = strlen(buf);
306158795Sdelphij			if ((cnt = atomicio(vwrite, proxyfd, buf, r)) != r)
307214047Sdelphij				err(1, "write failed (%zu/%d)", cnt, r);
308158795Sdelphij		}
309158795Sdelphij
310158795Sdelphij		/* Terminate headers */
311158795Sdelphij		if ((r = atomicio(vwrite, proxyfd, "\r\n", 2)) != 2)
312158795Sdelphij			err(1, "write failed (2/%d)", r);
313158795Sdelphij
314158795Sdelphij		/* Read status reply */
315158795Sdelphij		proxy_read_line(proxyfd, buf, sizeof(buf));
316158795Sdelphij		if (proxyuser != NULL &&
317158795Sdelphij		    strncmp(buf, "HTTP/1.0 407 ", 12) == 0) {
318158795Sdelphij			if (authretry > 1) {
319158795Sdelphij				fprintf(stderr, "Proxy authentication "
320158795Sdelphij				    "failed\n");
321158795Sdelphij			}
322158795Sdelphij			close(proxyfd);
323158795Sdelphij			goto again;
324167961Sdelphij		} else if (strncmp(buf, "HTTP/1.0 200 ", 12) != 0 &&
325167961Sdelphij		    strncmp(buf, "HTTP/1.1 200 ", 12) != 0)
326158795Sdelphij			errx(1, "Proxy error: \"%s\"", buf);
327158795Sdelphij
328158795Sdelphij		/* Headers continue until we hit an empty line */
329141261Sdelphij		for (r = 0; r < HTTP_MAXHDRS; r++) {
330141261Sdelphij			proxy_read_line(proxyfd, buf, sizeof(buf));
331141261Sdelphij			if (*buf == '\0')
332141261Sdelphij				break;
333141261Sdelphij		}
334158795Sdelphij		if (*buf != '\0')
335158795Sdelphij			errx(1, "Too many proxy headers received");
336141261Sdelphij	} else
337158795Sdelphij		errx(1, "Unknown proxy protocol %d", socksv);
338141261Sdelphij
339158795Sdelphij	return (proxyfd);
340141261Sdelphij}
341