1 /*
2 * CDDL HEADER START
3 *
4 * The contents of this file are subject to the terms of the
5 * Common Development and Distribution License (the "License").
6 * You may not use this file except in compliance with the License.
7 *
8 * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
9 * or http://www.opensolaris.org/os/licensing.
10 * See the License for the specific language governing permissions
11 * and limitations under the License.
12 *
13 * When distributing Covered Code, include this CDDL HEADER in each
14 * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
15 * If applicable, add the following below this CDDL HEADER, with the
16 * fields enclosed by brackets "[]" replaced with your own identifying
17 * information: Portions Copyright [yyyy] [name of copyright owner]
18 *
19 * CDDL HEADER END
20 *
21 * Copyright 2007 Sun Microsystems, Inc. All rights reserved.
22 * Use is subject to license terms.
23 */
24
25 #pragma ident "%Z%%M% %I% %E% SMI"
26
27 /*
28 * A SOCKS client that let's users 'ssh' to the
29 * outside of the firewall by opening up a connection
30 * through the SOCKS server. Supports only SOCKS v5.
31 */
32
33 #include <stdio.h>
34 #include <stdlib.h>
35 #include <string.h>
36 #include <netdb.h>
37 #include <strings.h>
38 #include <unistd.h>
39 #include <inttypes.h>
40 #include <errno.h>
41 #include <poll.h>
42 #include <signal.h>
43 #include <locale.h>
44 #include <libintl.h>
45 #include <netinet/in.h>
46 #include <sys/types.h>
47 #include <sys/socket.h>
48 #include <arpa/inet.h>
49 #include <sys/time.h>
50 #include <sys/stropts.h>
51 #include <sys/stat.h>
52 #include <sys/varargs.h>
53 #include "proxy-io.h"
54
55 #define DEFAULT_SOCKS5_PORT "1080"
56
57 static int debug_flag = 0;
58
59 static void
usage(void)60 usage(void)
61 {
62 (void) fprintf(stderr, gettext("Usage: ssh-socks5-proxy-connect "
63 "[-h socks5_proxy_host] [-p socks5_proxy_port] \n"
64 "remote_host remote_port\n"));
65 exit(1);
66 }
67
68 /* PRINTFLIKE1 */
69 static void
debug(const char * format,...)70 debug(const char *format, ...)
71 {
72 char fmtbuf[BUFFER_SIZ];
73 va_list args;
74
75 if (debug_flag == 0) {
76 return;
77 }
78 va_start(args, format);
79 (void) snprintf(fmtbuf, sizeof (fmtbuf),
80 "ssh-socks5-proxy: %s\n", format);
81 (void) vfprintf(stderr, fmtbuf, args);
82 va_end(args);
83 }
84
85 static void
signal_handler(int sig)86 signal_handler(int sig)
87 {
88 exit(0);
89 }
90
91 static int
do_version_exchange(int sockfd)92 do_version_exchange(int sockfd)
93 {
94 char buffer[3], recv_buf[2];
95
96 buffer[0] = 0x05; /* VER */
97 buffer[1] = 0x01; /* NMETHODS */
98 buffer[2] = 0x00; /* METHODS */
99
100 if (write(sockfd, &buffer, sizeof (buffer)) < 0) {
101 perror("write");
102 return (0);
103 }
104
105 if (read(sockfd, &recv_buf, sizeof (recv_buf)) == -1) {
106 perror("read");
107 return (0);
108 }
109
110 /*
111 * No need to check the server's version as per
112 * the protocol spec. Check the method supported
113 * by the server. Currently if the server does not
114 * support NO AUTH, we disconnect.
115 */
116 if (recv_buf[1] != 0x00) {
117 debug("Unsupported Authentication Method");
118 return (0);
119 }
120
121 /* Return success. */
122 return (1);
123 }
124
125 static void
send_request(int sockfd,const char * ssh_host,uchar_t ssh_host_len,uint16_t * ssh_port)126 send_request(
127 int sockfd,
128 const char *ssh_host,
129 uchar_t ssh_host_len,
130 uint16_t *ssh_port)
131 {
132 int failure = 1;
133 char *buffer, *temp, recv_buf[BUFFER_SIZ];
134 uchar_t version = 0x05, cmd = 0x01, rsv = 0x00, atyp = 0x03;
135
136 buffer = malloc(strlen(ssh_host) + 7);
137
138 temp = buffer;
139
140 /* Assemble the request packet */
141 (void) memcpy(temp, &version, sizeof (version));
142 temp += sizeof (version);
143 (void) memcpy(temp, &cmd, sizeof (cmd));
144 temp += sizeof (cmd);
145 (void) memcpy(temp, &rsv, sizeof (rsv));
146 temp += sizeof (rsv);
147 (void) memcpy(temp, &atyp, sizeof (atyp));
148 temp += sizeof (atyp);
149 (void) memcpy(temp, &ssh_host_len, sizeof (ssh_host_len));
150 temp += sizeof (ssh_host_len);
151 (void) memcpy(temp, ssh_host, strlen(ssh_host));
152 temp += strlen(ssh_host);
153 (void) memcpy(temp, ssh_port, sizeof (*ssh_port));
154 temp += sizeof (*ssh_port);
155
156 if (write(sockfd, buffer, temp - buffer) == -1) {
157 perror("write");
158 exit(1);
159 }
160
161 /*
162 * The maximum size of the protocol message we are waiting for is 10
163 * bytes -- VER[1], REP[1], RSV[1], ATYP[1], BND.ADDR[4] and
164 * BND.PORT[2]; see RFC 1928, section "6. Replies" for more details.
165 * Everything else is already a part of the data we are supposed to
166 * deliver to the requester. We know that BND.ADDR is exactly 4 bytes
167 * since as you can see below, we accept only ATYP == 1 which specifies
168 * that the IPv4 address is in a binary format.
169 */
170 if (read(sockfd, &recv_buf, 10) == -1) {
171 perror("read");
172 exit(1);
173 }
174
175 /* temp now points to the recieve buffer. */
176 temp = recv_buf;
177
178 /* Check the server's version. */
179 if (*temp++ != 0x05) {
180 (void) fprintf(stderr, gettext("Unsupported SOCKS version: %x\n"),
181 recv_buf[0]);
182 exit(1);
183 }
184
185 /* Check server's reply */
186 switch (*temp++) {
187 case 0x00:
188 failure = 0;
189 debug("CONNECT command Succeeded.");
190 break;
191 case 0x01:
192 debug("General SOCKS server failure.");
193 break;
194 case 0x02:
195 debug("Connection not allowed by ruleset.");
196 break;
197 case 0x03:
198 debug("Network Unreachable.");
199 break;
200 case 0x04:
201 debug("Host unreachable.");
202 break;
203 case 0x05:
204 debug("Connection refused.");
205 break;
206 case 0x06:
207 debug("TTL expired.");
208 break;
209 case 0x07:
210 debug("Command not supported");
211 break;
212 case 0x08:
213 debug("Address type not supported.");
214 break;
215 default:
216 (void) fprintf(stderr, gettext("ssh-socks5-proxy: "
217 "SOCKS Server reply not understood\n"));
218 }
219
220 if (failure == 1) {
221 exit(1);
222 }
223
224 /* Parse the rest of the packet */
225
226 /* Ignore RSV */
227 temp++;
228
229 /* Check ATYP */
230 if (*temp != 0x01) {
231 (void) fprintf(stderr, gettext("ssh-socks5-proxy: "
232 "Address type not supported: %u\n"), *temp);
233 exit(1);
234 }
235
236 free(buffer);
237 }
238
239 int
main(int argc,char ** argv)240 main(int argc, char **argv)
241 {
242 extern char *optarg;
243 extern int optind;
244 int retval, err_code, sock;
245 uint16_t ssh_port;
246 uchar_t ssh_host_len;
247 char *socks_server = NULL, *socks_port = NULL;
248 char *ssh_host;
249 struct addrinfo hints, *ai;
250 struct pollfd fds[2];
251
252 /* Initialization for variables, set locale and textdomain */
253
254 (void) setlocale(LC_ALL, "");
255
256 #if !defined(TEXT_DOMAIN) /* Should be defined by cc -D */
257 #define TEXT_DOMAIN "SYS_TEST" /* Use this only if it weren't */
258 #endif
259 (void) textdomain(TEXT_DOMAIN);
260
261 /* Set up the signal handler */
262 (void) signal(SIGINT, signal_handler);
263 (void) signal(SIGPIPE, signal_handler);
264 (void) signal(SIGPOLL, signal_handler);
265
266 while ((retval = getopt(argc, argv, "dp:h:")) != -1) {
267 switch (retval) {
268 case 'h':
269 socks_server = optarg;
270 break;
271 case 'p':
272 socks_port = optarg;
273 break;
274 case 'd':
275 debug_flag = 1;
276 break;
277 default:
278 break;
279 }
280 }
281
282 if (optind != argc - 2) {
283 usage();
284 }
285
286 ssh_host = argv[optind++];
287 ssh_host_len = (uchar_t)strlen(ssh_host);
288 ssh_port = htons(atoi(argv[optind]));
289
290 /*
291 * If the name and/or port number of the
292 * socks server were not passed on the
293 * command line, try the user's environment.
294 */
295 if (socks_server == NULL) {
296 if ((socks_server = getenv("SOCKS5_SERVER")) == NULL) {
297 (void) fprintf(stderr, gettext("ssh-socks5-proxy: "
298 "SOCKS5 SERVER not specified\n"));
299 exit(1);
300 }
301 }
302 if (socks_port == NULL) {
303 if ((socks_port = getenv("SOCKS5_PORT")) == NULL) {
304 socks_port = DEFAULT_SOCKS5_PORT;
305 }
306 }
307
308 debug("SOCKS5_SERVER = %s", socks_server);
309 debug("SOCKS5_PORT = %s", socks_port);
310
311 bzero(&hints, sizeof (struct addrinfo));
312 hints.ai_family = PF_UNSPEC;
313 hints.ai_socktype = SOCK_STREAM;
314
315 if ((err_code = getaddrinfo(socks_server, socks_port, &hints, &ai))
316 != 0) {
317 (void) fprintf(stderr, "%s: %s\n", socks_server,
318 gai_strerror(err_code));
319 exit(1);
320 }
321
322 if ((sock = socket(ai->ai_family, SOCK_STREAM, 0)) < 0) {
323 perror("socket");
324 exit(1);
325 }
326
327 /* Connect to the SOCKS server */
328 if (connect(sock, ai->ai_addr, ai->ai_addrlen) == 0) {
329 debug("Connected to the SOCKS server");
330 /* Do the SOCKS v5 communication with the server. */
331 if (do_version_exchange(sock) > 0) {
332 debug("Done version exchange");
333 send_request(sock, ssh_host, ssh_host_len, &ssh_port);
334 } else {
335 (void) fprintf(stderr, gettext("ssh-socks5-proxy: Client and "
336 "Server versions differ.\n"));
337 (void) close(sock);
338 exit(1);
339 }
340 } else {
341 perror("connect");
342 (void) close(sock);
343 exit(1);
344 }
345
346 fds[0].fd = STDIN_FILENO; /* Poll stdin for data. */
347 fds[1].fd = sock; /* Poll the socket for data. */
348 fds[0].events = fds[1].events = POLLIN;
349
350 for (;;) {
351 if (poll(fds, 2, INFTIM) == -1) {
352 perror("poll");
353 (void) close(sock);
354 exit(1);
355 }
356
357 /* Data arrived on stdin, write it to the socket */
358 if (fds[0].revents & POLLIN) {
359 if (proxy_read_write_loop(STDIN_FILENO, sock) == 0) {
360 (void) close(sock);
361 exit(1);
362 }
363 } else if (fds[0].revents & (POLLERR | POLLHUP | POLLNVAL)) {
364 (void) close(sock);
365 exit(1);
366 }
367
368 /* Data arrived on the socket, write it to stdout */
369 if (fds[1].revents & POLLIN) {
370 if (proxy_read_write_loop(sock, STDOUT_FILENO) == 0) {
371 (void) close(sock);
372 exit(1);
373 }
374 } else if (fds[1].revents & (POLLERR | POLLHUP | POLLNVAL)) {
375 (void) close(sock);
376 exit(1);
377 }
378 }
379
380 /* NOTREACHED */
381 return (0);
382 }
383