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
  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
  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
  86 signal_handler(int sig)
  87 {
  88         exit(0);
  89 }
  90 
  91 static int
  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
 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
 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 }