| /*********************************************************************** |
| * connect.c -- Make socket connection using SOCKS4/5 and HTTP tunnel. |
| * |
| * Copyright (c) 2000-2006 Shun-ichi Goto |
| * Copyright (c) 2002, J. Grant (English Corrections) |
| * |
| * This program is free software; you can redistribute it and/or |
| * modify it under the terms of the GNU General Public License |
| * as published by the Free Software Foundation; either version 2 |
| * of the License, or (at your option) any later version. |
| * |
| * This program is distributed in the hope that it will be useful, |
| * but WITHOUT ANY WARRANTY; without even the implied warranty of |
| * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
| * GNU General Public License for more details. |
| * |
| * You should have received a copy of the GNU General Public License |
| * along with this program; if not, write to the Free Software |
| * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. |
| * |
| * --------------------------------------------------------- |
| * PROJECT: My Test Program |
| * AUTHOR: Shun-ichi GOTO <gotoh@taiyo.co.jp> |
| * CREATE: Wed Jun 21, 2000 |
| * REVISION: $Revision: 100 $ |
| * --------------------------------------------------------- |
| * |
| * Getting Source |
| * ============== |
| * |
| * Recent version of 'connect.c' is available from |
| * http://www.taiyo.co.jp/~gotoh/ssh/connect.c |
| * |
| * Related tool, ssh-askpass.exe (alternative ssh-askpass on UNIX) |
| * is available: |
| * http://www.taiyo.co.jp/~gotoh/ssh/ssh-askpass.exe.gz |
| * |
| * See more detail: |
| * http://www.taiyo.co.jp/~gotoh/ssh/connect.html |
| * |
| * How To Compile |
| * ============== |
| * |
| * On UNIX environment: |
| * $ gcc connect.c -o connect |
| * |
| * On SOLARIS: |
| * $ gcc -o connect -lresolv -lsocket -lnsl connect.c |
| * |
| * on Win32 environment: |
| * $ cl connect.c wsock32.lib advapi32.lib |
| * or |
| * $ bcc32 connect.c wsock32.lib advapi32.lib |
| * or |
| * $ gcc connect.c -o connect |
| * |
| * on Mac OS X environment: |
| * $ gcc connect.c -o connect -lresolv |
| * or |
| * $ gcc connect.c -o connect -DBIND_8_COMPAT=1 |
| * |
| * How To Use |
| * ========== |
| * |
| * You can specify proxy method in an environment variable or in a |
| * command line option. |
| * |
| * usage: connect [-dnhst45] [-R resolve] [-p local-port] [-w sec] |
| * [-H [user@]proxy-server[:port]] |
| * [-S [user@]socks-server[:port]] |
| * [-T proxy-server[:port]] |
| * [-c telnet proxy command] |
| * host port |
| * |
| * "host" and "port" is for the target hostname and port-number to |
| * connect to. |
| * |
| * The -H option specifys a hostname and port number of the http proxy |
| * server to relay. If port is omitted, 80 is used. You can specify this |
| * value in the environment variable HTTP_PROXY and pass the -h option |
| * to use it. |
| * |
| * The -S option specifys the hostname and port number of the SOCKS |
| * server to relay. Like -H, port number can be omitted and the default |
| * is 1080. You can also specify this value pair in the environment |
| * variable SOCKS5_SERVER and give the -s option to use it. |
| * |
| * The '-4' and the '-5' options are for specifying SOCKS relaying and |
| * indicates protocol version to use. It is valid only when used with |
| * '-s' or '-S'. Default is '-5' (protocol version 5) |
| * |
| * The '-R' option is for specifying method to resolve the |
| * hostname. Three keywords ("local", "remote", "both") or dot-notation |
| * IP address are acceptable. The keyword "both" means, "Try local |
| * first, then remote". If a dot-notation IP address is specified, use |
| * this host as nameserver. The default is "remote" for SOCKS5 or |
| * "local" for others. On SOCKS4 protocol, remote resolving method |
| * ("remote" and "both") requires protocol 4a supported server. |
| * |
| * The '-p' option will forward a local TCP port instead of using the |
| * standard input and output. |
| * |
| * The '-P' option is same to '-p' except keep remote session. The |
| * program repeats waiting the port with holding remote session without |
| * disconnecting. To disconnect the remote session, send EOF to stdin or |
| * kill the program. |
| * |
| * The '-w' option specifys timeout seconds for making connection with |
| * TARGET host. |
| * |
| * The '-d' option is used for debug. If you fail to connect, use this |
| * and check request to and response from server. |
| * |
| * You can omit the "port" argument when program name is special format |
| * containing port number itself. For example, |
| * $ ln -s connect connect-25 |
| * means this connect-25 command is spcifying port number 25 already |
| * so you need not 2nd argument (and ignored if specified). |
| * |
| * To use proxy, this example is for SOCKS5 connection to connect to |
| * 'host' at port 25 via SOCKS5 server on 'firewall' host. |
| * $ connect -S firewall host 25 |
| * or |
| * $ SOCKS5_SERVER=firewall; export SOCKS5_SERVER |
| * $ connect -s host 25 |
| * |
| * For a HTTP-PROXY connection: |
| * $ connect -H proxy-server:8080 host 25 |
| * or |
| * $ HTTP_PROXY=proxy-server:8080; export HTTP_PROXY |
| * $ connect -h host 25 |
| * To forward a local port, for example to use ssh: |
| * $ connect -p 5550 -H proxy-server:8080 host 22 |
| * ($ ssh -l user -p 5550 localhost ) |
| * |
| * TIPS |
| * ==== |
| * |
| * Connect.c doesn't have any configuration to specify the SOCKS server. |
| * If you are a mobile user, this limitation might bother you. However, |
| * You can compile connect.c and link with other standard SOCKS library |
| * like the NEC SOCKS5 library or Dante. This means connect.c is |
| * socksified and uses a configration file like to other SOCKSified |
| * network commands and you can switch configuration file any time |
| * (ex. when ppp startup) that brings you switching of SOCKS server for |
| * connect.c in same way with other commands. For this case, you can |
| * write ~/.ssh/config like this: |
| * |
| * ProxyCommand connect -n %h %p |
| * |
| * SOCKS5 authentication |
| * ===================== |
| * |
| * Only USER/PASS authentication is supported. |
| * |
| * Proxy authentication |
| * ==================== |
| * |
| * Only BASIC scheme is supported. |
| * |
| * Authentication informations |
| * =========================== |
| * |
| * User name for authentication is specifed by an environment variable |
| * or system login name. And password is specified from environment |
| * variable or external program (specified in $SSH_ASKPASS) or tty. |
| * |
| * Following environment variable is used for specifying user name. |
| * SOCKS: $SOCKS5_USER, $LOGNAME, $USER |
| * HTTP Proxy: $HTTP_PROXY_USER, $LOGNAME, $USER |
| * |
| * ssh-askpass support |
| * =================== |
| * |
| * You can use ssh-askpass (came from OpenSSH or else) to specify |
| * password on graphical environment (X-Window or MS Windows). To use |
| * this, set program name to environment variable SSH_ASKPASS. On UNIX, |
| * X-Window must be required, so $DISPLAY environment variable is also |
| * needed. On Win32 environment, $DISPLAY is not mentioned. |
| * |
| * Related Informations |
| * ==================== |
| * |
| * SOCKS5 -- RFC 1928, RFC 1929, RFC 1961 |
| * NEC SOCKS Reference Implementation is available from: |
| * http://www.socks.nec.com |
| * DeleGate version 5 or earlier can be SOCKS4 server, |
| * and version 6 can be SOCKS5 and SOCKS4 server. |
| * and version 7.7.0 or later can be SOCKS5 and SOCKS4a server. |
| * http://www.delegate.org/delegate/ |
| * |
| * HTTP-Proxy -- |
| * Many http proxy servers supports this, but https should |
| * be allowed as configuration on your host. |
| * For example on DeleGate, you should add "https" to the |
| * "REMITTABLE" parameter to allow HTTP-Proxy like this: |
| * delegated -Pxxxx ...... REMITTABLE="+,https" ... |
| * |
| * Hypertext Transfer Protocol -- HTTP/1.1 -- RFC 2616 |
| * HTTP Authentication: Basic and Digest Access Authentication -- RFC 2617 |
| * For proxy authentication, refer these documents. |
| * |
| ***********************************************************************/ |
| |
| #include <stdio.h> |
| #include <stdlib.h> |
| #include <string.h> |
| #include <ctype.h> |
| #include <memory.h> |
| #include <errno.h> |
| #include <assert.h> |
| #include <sys/types.h> |
| #include <stdarg.h> |
| #include <fcntl.h> |
| #include <signal.h> |
| |
| #ifdef __CYGWIN32__ |
| #undef _WIN32 |
| #endif |
| |
| #ifdef _WIN32 |
| #include <windows.h> |
| #include <winsock.h> |
| #include <sys/stat.h> |
| #include <io.h> |
| #include <conio.h> |
| #else /* !_WIN32 */ |
| #include <unistd.h> |
| #include <pwd.h> |
| #include <termios.h> |
| #include <sys/time.h> |
| #ifndef __hpux |
| #include <sys/select.h> |
| #endif /* __hpux */ |
| #include <sys/socket.h> |
| #include <netinet/in.h> |
| #include <arpa/inet.h> |
| #include <netdb.h> |
| #if !defined(_WIN32) && !defined(__CYGWIN32__) |
| #define WITH_RESOLVER 1 |
| #include <arpa/nameser.h> |
| #include <resolv.h> |
| #else /* not ( not _WIN32 && not __CYGWIN32__) */ |
| #undef WITH_RESOLVER |
| #endif /* not ( not _WIN32 && not __CYGWIN32__) */ |
| #endif /* !_WIN32 */ |
| |
| #ifdef _WIN32 |
| #define ECONNRESET WSAECONNRESET |
| #endif /* _WI32 */ |
| |
| |
| |
| #ifndef LINT |
| static char *vcid = "$Id: connect.c 100 2007-07-03 10:48:26Z gotoh $"; |
| #endif |
| |
| /* Microsoft Visual C/C++ has _snprintf() and _vsnprintf() */ |
| #ifdef _MSC_VER |
| #define snprintf _snprintf |
| #define vsnprintf _vsnprintf |
| #endif |
| |
| /* consider Borland C */ |
| #ifdef __BORLANDC__ |
| #define _kbhit kbhit |
| #define _setmode setmode |
| #endif |
| |
| /* help message. |
| Win32 environment does not support -R option (vc and cygwin) |
| Win32 native compilers does not support -w option, yet (vc) |
| */ |
| static char *usage = "usage: %s [-dnhst45] [-p local-port]" |
| #ifdef _WIN32 |
| #ifdef __CYGWIN32__ |
| "[-w timeout] \n" /* cygwin cannot -R */ |
| #else /* not __CYGWIN32__ */ |
| " \n" /* VC cannot -w nor -R */ |
| #endif /* not __CYGWIN32__ */ |
| #else /* not _WIN32 */ |
| /* help message for UNIX */ |
| "[-R resolve] [-w timeout] \n" |
| #endif /* not _WIN32 */ |
| " [-H proxy-server[:port]] [-S [user@]socks-server[:port]] \n" |
| " [-T proxy-server[:port]]\n" |
| " [-c telnet-proxy-command]\n" |
| " host port\n"; |
| |
| /* name of this program */ |
| char *progname = NULL; |
| char *progdesc = "connect --- simple relaying command via proxy."; |
| char *rcs_revstr = "$Revision: 100 $"; |
| char *revstr = NULL; |
| int major_version = 1; |
| int minor_version = 0; |
| |
| /* set of character for strspn() */ |
| const char *digits = "0123456789"; |
| const char *dotdigits = "0123456789."; |
| |
| /* options */ |
| int f_debug = 0; |
| |
| /* report flag to hide secure information */ |
| int f_report = 1; |
| |
| int connect_timeout = 0; |
| |
| /* local input type */ |
| #define LOCAL_STDIO 0 |
| #define LOCAL_SOCKET 1 |
| char *local_type_names[] = { "stdio", "socket" }; |
| int local_type = LOCAL_STDIO; |
| u_short local_port = 0; /* option 'p' */ |
| int f_hold_session = 0; /* option 'P' */ |
| |
| char *telnet_command = "telnet %h %p"; |
| |
| /* utiity types, pair holder of number and string */ |
| typedef struct { |
| int num; |
| const char *str; |
| } LOOKUP_ITEM; |
| |
| /* relay method, server and port */ |
| #define METHOD_UNDECIDED 0 |
| #define METHOD_DIRECT 1 |
| #define METHOD_SOCKS 2 |
| #define METHOD_HTTP 3 |
| #define METHOD_TELNET 4 |
| char *method_names[] = { "UNDECIDED", "DIRECT", "SOCKS", "HTTP", "TELNET" }; |
| |
| int relay_method = METHOD_UNDECIDED; /* relaying method */ |
| char *relay_host = NULL; /* hostname of relay server */ |
| u_short relay_port = 0; /* port of relay server */ |
| char *relay_user = NULL; /* user name for auth */ |
| |
| /* destination target host and port */ |
| char *dest_host = NULL; |
| struct sockaddr_in dest_addr; |
| u_short dest_port = 0; |
| |
| /* informations for SOCKS */ |
| #define SOCKS5_REP_SUCCEEDED 0x00 /* succeeded */ |
| #define SOCKS5_REP_FAIL 0x01 /* general SOCKS serer failure */ |
| #define SOCKS5_REP_NALLOWED 0x02 /* connection not allowed by ruleset */ |
| #define SOCKS5_REP_NUNREACH 0x03 /* Network unreachable */ |
| #define SOCKS5_REP_HUNREACH 0x04 /* Host unreachable */ |
| #define SOCKS5_REP_REFUSED 0x05 /* connection refused */ |
| #define SOCKS5_REP_EXPIRED 0x06 /* TTL expired */ |
| #define SOCKS5_REP_CNOTSUP 0x07 /* Command not supported */ |
| #define SOCKS5_REP_ANOTSUP 0x08 /* Address not supported */ |
| #define SOCKS5_REP_INVADDR 0x09 /* Inalid address */ |
| |
| LOOKUP_ITEM socks5_rep_names[] = { |
| { SOCKS5_REP_SUCCEEDED, "succeeded"}, |
| { SOCKS5_REP_FAIL, "general SOCKS server failure"}, |
| { SOCKS5_REP_NALLOWED, "connection not allowed by ruleset"}, |
| { SOCKS5_REP_NUNREACH, "Network unreachable"}, |
| { SOCKS5_REP_HUNREACH, "Host unreachable"}, |
| { SOCKS5_REP_REFUSED, "connection refused"}, |
| { SOCKS5_REP_EXPIRED, "TTL expired"}, |
| { SOCKS5_REP_CNOTSUP, "Command not supported"}, |
| { SOCKS5_REP_ANOTSUP, "Address not supported"}, |
| { SOCKS5_REP_INVADDR, "Invalid address"}, |
| { -1, NULL } |
| }; |
| |
| /* SOCKS5 authentication methods */ |
| #define SOCKS5_AUTH_REJECT 0xFF /* No acceptable auth method */ |
| #define SOCKS5_AUTH_NOAUTH 0x00 /* without authentication */ |
| #define SOCKS5_AUTH_GSSAPI 0x01 /* GSSAPI */ |
| #define SOCKS5_AUTH_USERPASS 0x02 /* User/Password */ |
| #define SOCKS5_AUTH_CHAP 0x03 /* Challenge-Handshake Auth Proto. */ |
| #define SOCKS5_AUTH_EAP 0x05 /* Extensible Authentication Proto. */ |
| #define SOCKS5_AUTH_MAF 0x08 /* Multi-Authentication Framework */ |
| |
| #define SOCKS4_REP_SUCCEEDED 90 /* rquest granted (succeeded) */ |
| #define SOCKS4_REP_REJECTED 91 /* request rejected or failed */ |
| #define SOCKS4_REP_IDENT_FAIL 92 /* cannot connect identd */ |
| #define SOCKS4_REP_USERID 93 /* user id not matched */ |
| |
| LOOKUP_ITEM socks4_rep_names[] = { |
| { SOCKS4_REP_SUCCEEDED, "request granted (succeeded)"}, |
| { SOCKS4_REP_REJECTED, "request rejected or failed"}, |
| { SOCKS4_REP_IDENT_FAIL, "cannot connect identd"}, |
| { SOCKS4_REP_USERID, "user id not matched"}, |
| { -1, NULL } |
| }; |
| |
| #define RESOLVE_UNKNOWN 0 |
| #define RESOLVE_LOCAL 1 |
| #define RESOLVE_REMOTE 2 |
| #define RESOLVE_BOTH 3 |
| char *resolve_names[] = { "UNKNOWN", "LOCAL", "REMOTE", "BOTH" }; |
| |
| int socks_version = 5; /* SOCKS protocol version */ |
| int socks_resolve = RESOLVE_UNKNOWN; |
| struct sockaddr_in socks_ns; |
| char *socks5_auth = NULL; |
| |
| /* Environment variable names */ |
| #define ENV_SOCKS_SERVER "SOCKS_SERVER" /* SOCKS server */ |
| #define ENV_SOCKS5_SERVER "SOCKS5_SERVER" |
| #define ENV_SOCKS4_SERVER "SOCKS4_SERVER" |
| |
| #define ENV_SOCKS_RESOLVE "SOCKS_RESOLVE" /* resolve method */ |
| #define ENV_SOCKS5_RESOLVE "SOCKS5_RESOLVE" |
| #define ENV_SOCKS4_RESOLVE "SOCKS4_RESOLVE" |
| |
| #define ENV_SOCKS5_USER "SOCKS5_USER" /* auth user for SOCKS5 */ |
| #define ENV_SOCKS4_USER "SOCKS4_USER" /* auth user for SOCKS4 */ |
| #define ENV_SOCKS_USER "SOCKS_USER" /* auth user for SOCKS */ |
| #define ENV_SOCKS5_PASSWD "SOCKS5_PASSWD" /* auth password for SOCKS5 */ |
| #define ENV_SOCKS5_PASSWORD "SOCKS5_PASSWORD" /* old style */ |
| |
| #define ENV_HTTP_PROXY "HTTP_PROXY" /* common env var */ |
| #define ENV_HTTP_PROXY_USER "HTTP_PROXY_USER" /* auth user */ |
| #define ENV_HTTP_PROXY_PASSWORD "HTTP_PROXY_PASSWORD" /* auth password */ |
| |
| #define ENV_TELNET_PROXY "TELNET_PROXY" /* common env var */ |
| |
| #define ENV_CONNECT_USER "CONNECT_USER" /* default auth user name */ |
| #define ENV_CONNECT_PASSWORD "CONNECT_PASSWORD" /* default auth password */ |
| |
| #define ENV_SOCKS_DIRECT "SOCKS_DIRECT" /* addr-list for non-proxy */ |
| #define ENV_SOCKS5_DIRECT "SOCKS5_DIRECT" |
| #define ENV_SOCKS4_DIRECT "SOCKS4_DIRECT" |
| #define ENV_HTTP_DIRECT "HTTP_DIRECT" |
| #define ENV_CONNECT_DIRECT "CONNECT_DIRECT" |
| |
| #define ENV_SOCKS5_AUTH "SOCKS5_AUTH" |
| #define ENV_SSH_ASKPASS "SSH_ASKPASS" /* askpass program */ |
| |
| /* Prefix string of HTTP_PROXY */ |
| #define HTTP_PROXY_PREFIX "http://" |
| #define PROXY_AUTH_NONE 0 |
| #define PROXY_AUTH_BASIC 1 |
| #define PROXY_AUTH_DIGEST 2 |
| int proxy_auth_type = PROXY_AUTH_NONE; |
| |
| /* reason of end repeating */ |
| #define REASON_UNK -2 |
| #define REASON_ERROR -1 |
| #define REASON_CLOSED_BY_LOCAL 0 |
| #define REASON_CLOSED_BY_REMOTE 1 |
| |
| /* return value of relay start function. */ |
| #define START_ERROR -1 |
| #define START_OK 0 |
| #define START_RETRY 1 |
| |
| /* socket related definitions */ |
| #ifndef _WIN32 |
| #define SOCKET int |
| #endif |
| #ifndef SOCKET_ERROR |
| #define SOCKET_ERROR -1 |
| #endif |
| |
| #ifdef _WIN32 |
| #define socket_errno() WSAGetLastError() |
| #else /* !_WIN32 */ |
| #define closesocket close |
| #define socket_errno() (errno) |
| #endif /* !_WIN32 */ |
| |
| #ifdef _WIN32 |
| #define popen _popen |
| #endif /* WIN32 */ |
| |
| /* packet operation macro */ |
| #define PUT_BYTE(ptr,data) (*(unsigned char*)ptr = data) |
| |
| /* debug message output */ |
| void |
| debug( const char *fmt, ... ) |
| { |
| va_list args; |
| if ( f_debug ) { |
| va_start( args, fmt ); |
| fprintf(stderr, "DEBUG: "); |
| vfprintf( stderr, fmt, args ); |
| va_end( args ); |
| } |
| } |
| |
| void |
| debug_( const char *fmt, ... ) /* without prefix */ |
| { |
| va_list args; |
| if ( f_debug ) { |
| va_start( args, fmt ); |
| vfprintf( stderr, fmt, args ); |
| va_end( args ); |
| } |
| } |
| |
| /* error message output */ |
| void |
| error( const char *fmt, ... ) |
| { |
| va_list args; |
| va_start( args, fmt ); |
| fprintf(stderr, "ERROR: "); |
| vfprintf( stderr, fmt, args ); |
| va_end( args ); |
| } |
| |
| void |
| fatal( const char *fmt, ... ) |
| { |
| va_list args; |
| va_start( args, fmt ); |
| fprintf(stderr, "FATAL: "); |
| vfprintf( stderr, fmt, args ); |
| va_end( args ); |
| exit (EXIT_FAILURE); |
| } |
| |
| |
| void * |
| xmalloc (size_t size) |
| { |
| void *ret = malloc(size); |
| if (ret == NULL) |
| fatal("Cannot allocate memory: %d bytes.\n", size); |
| return ret; |
| } |
| |
| char * |
| downcase( char *str ) |
| { |
| char *buf = str; |
| while ( *buf ) { |
| if ( isupper(*buf) ) |
| *buf += 'a'-'A'; |
| buf++; |
| } |
| return str; /* return converted arg */ |
| } |
| |
| char * |
| expand_host_and_port (const char *fmt, const char *host, int port) |
| { |
| const char *src; |
| char *buf, *dst, *ptr; |
| size_t len = strlen(fmt) + strlen(host) + 20; |
| buf = xmalloc (len); |
| dst = buf; |
| src = fmt; |
| |
| while (*src) { |
| if (*src == '%') { |
| switch (src[1]) { |
| case 'h': |
| strcpy (dst, host); |
| src += 2; |
| break; |
| case 'p': |
| snprintf (dst, len, "%d", port); |
| src += 2; |
| break; |
| default: |
| src ++; |
| break; |
| } |
| dst = buf + strlen (buf); |
| } else if (*src == '\\') { |
| switch (src[1]) { |
| case 'r': /* CR */ |
| *dst++ = '\r'; |
| src += 2; |
| break; |
| case 'n': /* LF */ |
| *dst++ = '\n'; |
| src += 2; |
| break; |
| case 't': /* TAB */ |
| *dst++ = '\t'; |
| src += 2; |
| break; |
| default: |
| src ++; |
| break; |
| } |
| } else { |
| /* usual */ |
| *dst++ = *src++; |
| } |
| *dst = '\0'; |
| } |
| assert (strlen(buf) < len); |
| return buf; |
| } |
| |
| |
| int |
| lookup_resolve( const char *str ) |
| { |
| char *buf = strdup( str ); |
| int ret; |
| |
| downcase( buf ); |
| if ( strcmp( buf, "both" ) == 0 ) |
| ret = RESOLVE_BOTH; |
| else if ( strcmp( buf, "remote" ) == 0 ) |
| ret = RESOLVE_REMOTE; |
| else if ( strcmp( buf, "local" ) == 0 ) |
| ret = RESOLVE_LOCAL; |
| else if ( strspn(buf, dotdigits) == strlen(buf) ) { |
| #ifndef WITH_RESOLVER |
| fatal("Sorry, you can't specify to resolve the hostname with the -R option on Win32 environment."); |
| #endif /* not WITH_RESOLVER */ |
| ret = RESOLVE_LOCAL; /* this case is also 'local' */ |
| socks_ns.sin_addr.s_addr = inet_addr(buf); |
| socks_ns.sin_family = AF_INET; |
| } |
| else |
| ret = RESOLVE_UNKNOWN; |
| free(buf); |
| return ret; |
| } |
| |
| char * |
| getusername(void) |
| { |
| #ifdef _WIN32 |
| static char buf[1024]; |
| DWORD size = sizeof(buf); |
| buf[0] = '\0'; |
| GetUserName( buf, &size); |
| return buf; |
| #else /* not _WIN32 */ |
| struct passwd *pw = getpwuid(getuid()); |
| if ( pw == NULL ) |
| fatal("getpwuid() failed for uid: %d\n", getuid()); |
| return pw->pw_name; |
| #endif /* not _WIN32 */ |
| } |
| |
| /* expect |
| check STR is begin with substr with case-ignored comparison. |
| Return 1 if matched, otherwise 0. |
| */ |
| int |
| expect( char *str, char *substr) |
| { |
| int len = strlen(substr); |
| while ( 0 < len-- ) { |
| if ( toupper(*str) != toupper(*substr) ) |
| return 0; /* not matched */ |
| str++, substr++; |
| } |
| return 1; /* good, matched */ |
| } |
| |
| |
| /** PARAMETER operation **/ |
| #define PARAMETER_FILE "/etc/connectrc" |
| #define PARAMETER_DOTFILE ".connectrc" |
| typedef struct { |
| char* name; |
| char* value; |
| } PARAMETER_ITEM; |
| PARAMETER_ITEM parameter_table[] = { |
| { ENV_SOCKS_SERVER, NULL }, |
| { ENV_SOCKS5_SERVER, NULL }, |
| { ENV_SOCKS4_SERVER, NULL }, |
| { ENV_SOCKS_RESOLVE, NULL }, |
| { ENV_SOCKS5_RESOLVE, NULL }, |
| { ENV_SOCKS4_RESOLVE, NULL }, |
| { ENV_SOCKS5_USER, NULL }, |
| { ENV_SOCKS5_PASSWD, NULL }, |
| { ENV_SOCKS5_PASSWORD, NULL }, |
| { ENV_HTTP_PROXY, NULL }, |
| { ENV_HTTP_PROXY_USER, NULL }, |
| { ENV_HTTP_PROXY_PASSWORD, NULL }, |
| { ENV_CONNECT_USER, NULL }, |
| { ENV_CONNECT_PASSWORD, NULL }, |
| { ENV_SSH_ASKPASS, NULL }, |
| { ENV_SOCKS5_DIRECT, NULL }, |
| { ENV_SOCKS4_DIRECT, NULL }, |
| { ENV_SOCKS_DIRECT, NULL }, |
| { ENV_HTTP_DIRECT, NULL }, |
| { ENV_CONNECT_DIRECT, NULL }, |
| { ENV_SOCKS5_AUTH, NULL }, |
| { NULL, NULL } |
| }; |
| |
| PARAMETER_ITEM* |
| find_parameter_item(const char* name) |
| { |
| int i; |
| for( i = 0; parameter_table[i].name != NULL; i++ ){ |
| if ( strcmp(name, parameter_table[i].name) == 0 ) |
| return ¶meter_table[i]; |
| } |
| return NULL; |
| } |
| |
| void |
| read_parameter_file_1(const char* name) |
| { |
| FILE* f; |
| int line; |
| char lbuf[1025]; |
| f = fopen(name, "r"); |
| if( f ){ |
| debug("Reading parameter file(%s)\n", name); |
| for ( line = 1; fgets(lbuf, 1024, f); line++ ) { |
| char *p, *q, *param, *value; |
| p = strchr(lbuf, '\n'); |
| if ( p == NULL ) |
| fatal("%s:%d: buffer overflow\n", name, line); |
| *p = '\0'; |
| p = strchr(lbuf, '#'); |
| if ( p ) |
| *p = '\0'; |
| for ( p = lbuf; *p; p++ ) |
| if( *p != ' ' && *p != '\t' ) break; |
| if ( *p == '\0' ) continue; |
| param = p; |
| p = strchr(p, '='); |
| if ( p == NULL ) { |
| error("%s:%d: missing equal sign\n", name, line); |
| continue; |
| } |
| for ( q = p - 1; q >= lbuf; q-- ) |
| if ( *q != ' ' && *q != '\t' ) break; |
| *++q = '\0'; |
| for ( ++p; *p; p++ ) |
| if ( *p != ' ' && *p != '\t' ) break; |
| value = p; |
| for ( ; *p; p++ ); |
| for ( p--; p >= lbuf; p-- ) |
| if ( *p != ' ' && *p != '\t' ) break; |
| *++p = '\0'; |
| if ( param && value ) { |
| PARAMETER_ITEM *item; |
| item = find_parameter_item(param); |
| if ( item == NULL ) { |
| error("%s:%d: unknown parameter `%s'\n", name, line, param); |
| continue; |
| } |
| item->value = strdup(value); |
| debug("Parameter `%s' is set to `%s'\n", param, value); |
| } |
| } |
| } |
| } |
| |
| void |
| read_parameter_file(void) |
| { |
| #if !defined(_WIN32) || defined(cygwin) |
| char *name; |
| struct passwd *pw; |
| #endif |
| |
| read_parameter_file_1(PARAMETER_FILE); |
| #if !defined(_WIN32) || defined(cygwin) |
| pw = getpwuid(getuid()); |
| if ( pw == NULL ) |
| fatal("getpwuid() failed for uid: %d\n", getuid()); |
| name = xmalloc(strlen(pw->pw_dir) + strlen(PARAMETER_DOTFILE) + 2); |
| strcpy(name, pw->pw_dir); |
| strcat(name, "/" PARAMETER_DOTFILE); |
| read_parameter_file_1(name); |
| free(name); |
| #endif /* _WIN32 */ |
| } |
| |
| char* |
| getparam(const char* name) |
| { |
| char *value = getenv(name); |
| if ( value == NULL ){ |
| PARAMETER_ITEM *item = find_parameter_item(name); |
| if ( item != NULL ) |
| value = item->value; |
| } |
| return value; |
| } |
| |
| |
| /** DIRECT connection **/ |
| #define MAX_DIRECT_ADDR_LIST 256 |
| |
| struct ADDRPAIR { |
| struct in_addr addr; |
| struct in_addr mask; |
| char *name; |
| int negative; |
| }; |
| |
| struct ADDRPAIR direct_addr_list[MAX_DIRECT_ADDR_LIST]; |
| int n_direct_addr_list = 0; |
| |
| void |
| mask_addr (void *addr, void *mask, int addrlen) |
| { |
| char *a, *m; |
| a = addr; |
| m = mask; |
| while ( 0 < addrlen-- ) |
| *a++ &= *m++; |
| } |
| |
| int |
| add_direct_addr (struct in_addr *addr, struct in_addr *mask, int negative) |
| { |
| struct in_addr iaddr; |
| char *s; |
| if ( MAX_DIRECT_ADDR_LIST <= n_direct_addr_list ) { |
| error("direct address table is full!\n"); |
| return -1; |
| } |
| iaddr = *addr; |
| mask_addr(&iaddr, mask, sizeof(iaddr)); |
| s = strdup(inet_ntoa(iaddr)); |
| debug("adding direct addr entry: %s%s/%s\n", |
| negative? "!": "", s, inet_ntoa(*mask)); |
| free(s); |
| memcpy( &direct_addr_list[n_direct_addr_list].addr, |
| &iaddr, sizeof(iaddr)); |
| memcpy( &direct_addr_list[n_direct_addr_list].mask, |
| mask, sizeof(*mask)); |
| direct_addr_list[n_direct_addr_list].name = NULL; |
| direct_addr_list[n_direct_addr_list].negative = negative; |
| n_direct_addr_list++; |
| return 0; |
| } |
| |
| |
| /* add domain/host name entry to direct name table */ |
| int |
| add_direct_host( const char *name, int negative) |
| { |
| if ( MAX_DIRECT_ADDR_LIST <= n_direct_addr_list ) { |
| error("direct address table is full!\n"); |
| return -1; |
| } |
| if (*name == '*') |
| name++; |
| if (*name == '.') |
| name++; |
| debug("adding direct name entry: %s%s\n", negative? "!": "", name); |
| direct_addr_list[n_direct_addr_list].name = downcase(strdup(name)); |
| direct_addr_list[n_direct_addr_list].negative = negative; |
| n_direct_addr_list++; |
| return 0; |
| } |
| |
| |
| int |
| parse_addr_pair (const char *str, struct in_addr *addr, struct in_addr *mask) |
| { |
| /* NOTE: */ |
| /* Assume already be splitted by separator |
| and formatted as folowing: |
| 1) 12.34.56.789/255.255.255.0 |
| 2) 12.34.56.789/24 |
| 3) 12.34.56. |
| All above generates same addr/mask pair 12.34.56.0 and 255.255.255.0 |
| */ |
| const char *ptr; |
| u_char *dsta, *dstm; |
| int i, n; |
| |
| assert( str != NULL ); |
| addr->s_addr = 0; |
| mask->s_addr = 0; |
| ptr = str; |
| dsta = (u_char*)&addr->s_addr; |
| dstm = (u_char*)&mask->s_addr; |
| for (i=0; i<4; i++ ) { |
| if ( *ptr == '\0' ) |
| break; /* case of format #3 */ |
| if ( !isdigit(*ptr) ) |
| return -1; /* format error: */ |
| *dsta++ = atoi( ptr ); |
| *dstm++ = 255; /* automatic mask for format #3 */ |
| while ( isdigit(*ptr) ) /* skip digits */ |
| ptr++; |
| if ( *ptr == '.' ) |
| ptr++; |
| else |
| break; |
| } |
| /* At this point, *ptr points '/' or EOS ('\0') */ |
| if ( *ptr == '\0' ) |
| return 0; /* complete as format #3 */ |
| if ( *ptr != '/' ) |
| return -1; /* format error */ |
| /* Now parse mask for format #1 or #2 */ |
| ptr++; |
| mask->s_addr = 0; /* clear automatic mask */ |
| |
| if ( strchr( ptr, '.') ) { |
| /* case of format #1 */ |
| dstm = (u_char*)&mask->s_addr; |
| for (i=0; i<4; i++) { |
| if ( !isdigit(*ptr) ) |
| return -1; /* format error: */ |
| *dstm++ = atoi(ptr); |
| while ( isdigit(*ptr) ) /* skip digits */ |
| ptr++; |
| if ( *ptr == '.' ) |
| ptr++; |
| else |
| break; /* from for loop */ |
| } |
| /* complete as format #1 */ |
| } else { |
| /* case of format #2 */ |
| if ( !isdigit(*ptr) ) |
| return -1; /* format error: */ |
| n = atoi(ptr); |
| if ( n<0 || 32<n) |
| return -1; /* format error */ |
| mask->s_addr = (n==0)? 0: htonl(((u_long)0xFFFFFFFF)<<(32-n)); |
| /* complete as format #1 */ |
| } |
| return 0; |
| } |
| |
| void |
| initialize_direct_addr (void) |
| { |
| int negative; |
| int n_entries; |
| char *env = NULL, *beg, *next, *envkey = NULL; |
| struct in_addr addr, mask; |
| |
| if ( relay_method == METHOD_SOCKS ){ |
| if ( socks_version == 5 ) |
| envkey = ENV_SOCKS5_DIRECT; |
| else |
| envkey = ENV_SOCKS4_DIRECT; |
| env = getparam(envkey); |
| if ( env == NULL ) |
| env = getparam(ENV_SOCKS_DIRECT); |
| } else if ( relay_method == METHOD_HTTP ){ |
| env = getparam(ENV_HTTP_DIRECT); |
| } |
| |
| if ( env == NULL ) |
| env = getparam(ENV_CONNECT_DIRECT); |
| |
| if ( env == NULL ) |
| return; /* no entry */ |
| debug("making direct addr list from: '%s'\n", env); |
| env = strdup( env ); /* reallocate to modify */ |
| beg = next = env; |
| n_entries = 0; |
| do { |
| if ( MAX_DIRECT_ADDR_LIST <= n_entries ) { |
| error("too many entries in %s", envkey); |
| break; /* from do loop */ |
| } |
| next = strchr( beg, ','); |
| if ( next != NULL ) |
| *next++ = '\0'; |
| addr.s_addr = 0; |
| mask.s_addr = 0; |
| if (*beg == '!') { |
| negative = 1; |
| beg++; |
| } else |
| negative = 0; |
| if ( !parse_addr_pair( beg, &addr, &mask ) ) { |
| add_direct_addr( &addr, &mask, negative ); |
| } else { |
| add_direct_host( beg, negative ); |
| } |
| if ( next != NULL ) |
| beg = next; |
| } while ( next != NULL ); |
| |
| free( env ); |
| return; |
| } |
| |
| int |
| cmp_addr (void *addr1, void *addr2, int addrlen) |
| { |
| return memcmp( addr1, addr2, addrlen ); |
| } |
| |
| int |
| is_direct_address (const struct in_addr addr) |
| { |
| int i, neg; |
| struct in_addr iaddr; |
| |
| /* Note: assume IPV4 address !! */ |
| for (i=0; i<n_direct_addr_list; i++ ) { |
| if (direct_addr_list[i].name != NULL) |
| continue; /* it's name entry */ |
| neg = direct_addr_list[i].negative; |
| iaddr = addr; |
| mask_addr( &iaddr, &direct_addr_list[i].mask, |
| sizeof(struct in_addr)); |
| if (cmp_addr(&iaddr, &direct_addr_list[i].addr, |
| sizeof(struct in_addr)) == 0) { |
| char *a, *m; |
| a = strdup(inet_ntoa(direct_addr_list[i].addr)); |
| m = strdup(inet_ntoa(direct_addr_list[i].mask)); |
| debug("match with: %s/%s%s\n", a, m, neg? " (negative)": ""); |
| free(a); |
| free(m); |
| return !neg? 1: 0; |
| } |
| } |
| debug("not matched, addr to be relayed: %s\n", inet_ntoa(addr)); |
| return 0; /* not direct */ |
| } |
| |
| |
| /* check s1 is ends with s2. |
| return 1 if exact match or domain part match. |
| return 0 if s1 is shorter than s2 or partial match. |
| For example, |
| ends_with("bar.com", "bar.com") => 1 (exact match) |
| ends_with("foo.bar.com", "bar.com") => 1 (domain match) |
| ends_with("foo.beebar.com", "bar.com") => 0 (partial match) |
| ends_with("bar", "bar.com") => 0 (shorter) |
| */ |
| domain_match(const char *s1, const char *s2) |
| { |
| int len1, len2; |
| const char *tail1, *tail2; |
| len1 = strlen(s1); |
| len2 = strlen(s2); |
| if (len1 < len2 || len1 == 0 || len2 == 0) |
| return 0; /* not match */ |
| tail1 = s1 + len1; |
| tail2 = s2 + len2; |
| while (0 < len1 && 0 < len2) { |
| if (*--tail1 != *--tail2) |
| break; /* not match */ |
| len1--, len2--; |
| } |
| if (len2 != 0) |
| return 0; /* not match */ |
| /* Now exact match, domain match or partial match. |
| Return true if exact or domain match. |
| Or continue checking. */ |
| if (tail1 == s1 || tail1[-1] == '.') |
| return 1; /* match! */ |
| return 0; /* not match */ |
| } |
| |
| /* Check given NAME is ends with one of |
| registered direct name entry. |
| Return 1 if matched, or 0. |
| */ |
| int |
| is_direct_name (const char *name) |
| { |
| int len, i; |
| const char *tail; |
| debug("checking %s is for direct?\n", name); |
| name = downcase(strdup(name)); |
| len = strlen(name); |
| if (len < 1) |
| return 0; /* false */ |
| tail = &name[len]; |
| for (i=0; i<n_direct_addr_list; i++ ) { |
| int dlen, neg; |
| const char *dname; |
| const char *n, *d; |
| dname = direct_addr_list[i].name; |
| if (dname == NULL) |
| continue; /* it's addr/mask entry */ |
| neg = direct_addr_list[i].negative; |
| if (domain_match(name, dname)) { |
| debug("match with: %s%s\n", dname, neg? " (negative)": ""); |
| if (neg) { |
| return 0; /* not direct */ |
| } else { |
| return 1; /* direct*/ |
| } |
| } |
| } |
| return 0; /* not matched */ |
| } |
| |
| /* check to connect to HOST directyly? |
| return 1 if to be direct, 0 for else. */ |
| int |
| check_direct(const char *host) |
| { |
| struct in_addr addr; |
| addr.s_addr = inet_addr(host); |
| if (addr.s_addr != INADDR_NONE) { |
| /* case of IP address */ |
| if (is_direct_address(addr)) { |
| debug("%s is for direct.\n", host); |
| return 1; /* true */ |
| } |
| } else { |
| /* case of hostname */ |
| if (is_direct_name(host)) { |
| debug("%s is for direct.\n", host); |
| return 1; /* true */ |
| } |
| } |
| debug("%s is for not direct.\n", host); |
| return 0; /* false */ |
| } |
| |
| |
| /** TTY operation **/ |
| |
| int intr_flag = 0; |
| |
| #ifndef _WIN32 |
| void |
| intr_handler(int sig) |
| { |
| intr_flag = 1; |
| } |
| |
| void |
| tty_change_echo(int fd, int enable) |
| { |
| static struct termios ntio, otio; /* new/old termios */ |
| static sigset_t nset, oset; /* new/old sigset */ |
| static struct sigaction nsa, osa; /* new/old sigaction */ |
| static int disabled = 0; |
| |
| if ( disabled && enable ) { |
| /* enable echo */ |
| tcsetattr(fd, TCSANOW, &otio); |
| disabled = 0; |
| /* resotore sigaction */ |
| sigprocmask(SIG_SETMASK, &oset, NULL); |
| sigaction(SIGINT, &osa, NULL); |
| if ( intr_flag != 0 ) { |
| /* re-generate signal */ |
| kill(getpid(), SIGINT); |
| sigemptyset(&nset); |
| sigsuspend(&nset); |
| intr_flag = 0; |
| } |
| } else if (!disabled && !enable) { |
| /* set SIGINTR handler and break syscall on singal */ |
| sigemptyset(&nset); |
| sigaddset(&nset, SIGTSTP); |
| sigprocmask(SIG_BLOCK, &nset, &oset); |
| intr_flag = 0; |
| memset(&nsa, 0, sizeof(nsa)); |
| nsa.sa_handler = intr_handler; |
| sigaction(SIGINT, &nsa, &osa); |
| /* disable echo */ |
| if (tcgetattr(fd, &otio) == 0 && (otio.c_lflag & ECHO)) { |
| disabled = 1; |
| ntio = otio; |
| ntio.c_lflag &= ~(ECHO | ECHOE | ECHOK | ECHONL); |
| (void) tcsetattr(fd, TCSANOW, &ntio); |
| } |
| } |
| |
| return; |
| } |
| |
| #define TTY_NAME "/dev/tty" |
| int |
| tty_readpass( const char *prompt, char *buf, size_t size ) |
| { |
| int tty, ret = 0; |
| |
| tty = open(TTY_NAME, O_RDWR); |
| if ( tty < 0 ) { |
| error("Unable to open %s\n", TTY_NAME); |
| return -1; /* can't open tty */ |
| } |
| if ( size <= 0 ) |
| return -1; /* no room */ |
| write(tty, prompt, strlen(prompt)); |
| buf[0] = '\0'; |
| tty_change_echo(tty, 0); /* disable echo */ |
| ret = read(tty,buf, size-1); |
| tty_change_echo(tty, 1); /* restore */ |
| write(tty, "\n", 1); /* new line */ |
| close(tty); |
| if ( strchr(buf,'\n') == NULL ) |
| return -1; |
| if ( 0 < ret ) |
| buf[ret] = '\0'; |
| return ret; |
| } |
| |
| #else /* _WIN32 */ |
| |
| BOOL __stdcall |
| w32_intr_handler(DWORD dwCtrlType) |
| { |
| if ( dwCtrlType == CTRL_C_EVENT ) { |
| intr_flag = 1; |
| return TRUE; |
| } else { |
| return FALSE; |
| } |
| } |
| |
| #define tty_readpass w32_tty_readpass |
| int |
| w32_tty_readpass( const char *prompt, char *buf, size_t size ) |
| { |
| HANDLE in = CreateFile("CONIN$", GENERIC_READ|GENERIC_WRITE, |
| 0, NULL, OPEN_EXISTING, 0, NULL); |
| HANDLE out = CreateFile("CONOUT$", GENERIC_WRITE, |
| 0, NULL, OPEN_EXISTING, 0, NULL); |
| DWORD mode; |
| DWORD ret, bytes; |
| |
| if (in == INVALID_HANDLE_VALUE || out == INVALID_HANDLE_VALUE) |
| fatal("Cannot open console. (errno=%d)", GetLastError()); |
| |
| WriteFile(out, prompt, strlen(prompt), &bytes, 0); |
| SetConsoleCtrlHandler(w32_intr_handler, TRUE ); /* add handler */ |
| GetConsoleMode(in, &mode); |
| SetConsoleMode(in, mode&~ENABLE_ECHO_INPUT); /* disable echo */ |
| ret = ReadFile(in, buf, size, &bytes, 0); |
| SetConsoleMode(in, mode); /* enable echo */ |
| SetConsoleCtrlHandler( w32_intr_handler, FALSE ); /* remove handler */ |
| if ( intr_flag ) |
| GenerateConsoleCtrlEvent(CTRL_C_EVENT, 0); /* re-signal */ |
| WriteFile(out,"\n", 1, &bytes, 0); |
| CloseHandle(in); |
| CloseHandle(out); |
| return ret; |
| } |
| |
| #endif /* _WIN32 */ |
| |
| /*** User / Password ***/ |
| |
| /* SOCKS5 and HTTP Proxy authentication may requires username and |
| password. We ll give it via environment variable or tty. |
| Username and password for authentication are decided by |
| following rules: |
| |
| Username is taken from |
| 1) server location spec (i.e. user@host:port) |
| 2) environment variables (see tables.1) |
| 3) system account name currently logged in. |
| |
| Table.1 Order of environment variables for username |
| |
| | SOCKS v5 | SOCKS v4 | HTTP proxy | |
| --+-------------+-------------+-----------------+ |
| 1 | SOCKS45_USER | SOCKS4_USER | HTTP_PROXY_USER | |
| --+-------------+-------------+ | |
| 2 | SOCKS_USER | | |
| --+---------------------------+-----------------+ |
| 3 | CONNECT_USER | |
| --+---------------------------------------------+ |
| |
| Password is taken from |
| 1) by environment variables (see table.2) |
| 2) by entering from tty. |
| |
| Table.2 Order of environment variables for password |
| |
| | SOCKS v5 | HTTP proxy | |
| --+-----------------+---------------------+ |
| 1 | SOCKS5_PASSWD | | |
| --+-----------------+ HTTP_PROXY_PASSWORD | |
| 2 | SOCKS5_PASSWORD | | |
| --+-----------------+---------------------+ |
| 3 | CONNECT_PASSWORD | |
| --+---------------------------------------+ |
| |
| Note: SOCKS5_PASSWD which is added in rev. 1.79 |
| to share value with NEC SOCKS implementation. |
| */ |
| |
| char * |
| determine_relay_user () |
| { |
| char *user = NULL; |
| /* get username from environment variable, or system. */ |
| if (relay_method == METHOD_SOCKS) { |
| if (user == NULL && socks_version == 5) |
| user = getparam (ENV_SOCKS5_USER); |
| if (user == NULL && socks_version == 4) |
| user = getparam (ENV_SOCKS4_USER); |
| if (user == NULL) |
| user = getparam (ENV_SOCKS_USER); |
| } else if (relay_method == METHOD_HTTP) { |
| if (user == NULL) |
| user = getparam (ENV_HTTP_PROXY_USER); |
| } |
| if (user == NULL) |
| user = getparam (ENV_CONNECT_USER); |
| /* determine relay user by system call if not yet. */ |
| if (user == NULL) |
| user = getusername(); |
| return user; |
| } |
| |
| char * |
| determine_relay_password () |
| { |
| char *pass = NULL; |
| if (pass == NULL && relay_method == METHOD_HTTP) |
| pass = getparam(ENV_HTTP_PROXY_PASSWORD); |
| if (pass == NULL && relay_method == METHOD_SOCKS) |
| pass = getparam(ENV_SOCKS5_PASSWD); |
| if (pass == NULL && relay_method == METHOD_SOCKS) |
| pass = getparam(ENV_SOCKS5_PASSWORD); |
| if (pass == NULL) |
| pass = getparam(ENV_CONNECT_PASSWORD); |
| return pass; |
| } |
| |
| |
| /*** network operations ***/ |
| |
| |
| /* set_relay() |
| Determine relay informations: |
| method, host, port, and username. |
| 1st arg, METHOD should be METHOD_xxx. |
| 2nd arg, SPEC is hostname or hostname:port or user@hostame:port. |
| hostname is domain name or dot notation. |
| If port is omitted, use 80 for METHOD_HTTP method, |
| use 1080 for METHOD_SOCKS method. |
| Username is also able to given by 3rd. format. |
| 2nd argument SPEC can be NULL. if NULL, use environment variable. |
| */ |
| int |
| set_relay( int method, char *spec ) |
| { |
| char *buf, *sep, *resolve; |
| |
| relay_method = method; |
| |
| read_parameter_file(); |
| initialize_direct_addr(); |
| if (n_direct_addr_list == 0) { |
| debug ("No direct address are specified.\n"); |
| } else { |
| debug ("%d direct address entries.\n", n_direct_addr_list); |
| } |
| |
| switch ( method ) { |
| case METHOD_DIRECT: |
| return -1; /* nothing to do */ |
| |
| case METHOD_SOCKS: |
| if ( spec == NULL ) { |
| switch ( socks_version ) { |
| case 5: |
| spec = getparam(ENV_SOCKS5_SERVER); |
| break; |
| case 4: |
| spec = getparam(ENV_SOCKS4_SERVER); |
| break; |
| } |
| } |
| if ( spec == NULL ) |
| spec = getparam(ENV_SOCKS_SERVER); |
| |
| if ( spec == NULL ) |
| fatal("Failed to determine SOCKS server.\n"); |
| relay_port = 1080; /* set default first */ |
| |
| /* determine resolve method */ |
| if ( socks_resolve == RESOLVE_UNKNOWN ) { |
| if ( ((socks_version == 5) && |
| ((resolve = getparam(ENV_SOCKS5_RESOLVE)) != NULL)) || |
| ((socks_version == 4) && |
| ((resolve = getparam(ENV_SOCKS4_RESOLVE)) != NULL)) || |
| ((resolve = getparam(ENV_SOCKS_RESOLVE)) != NULL) ) { |
| socks_resolve = lookup_resolve( resolve ); |
| if ( socks_resolve == RESOLVE_UNKNOWN ) |
| fatal("Invalid resolve method: %s\n", resolve); |
| } else { |
| /* default */ |
| if ( socks_version == 5 ) |
| socks_resolve = RESOLVE_REMOTE; |
| else |
| socks_resolve = RESOLVE_LOCAL; |
| } |
| } |
| break; |
| |
| case METHOD_HTTP: |
| if ( spec == NULL ) |
| spec = getparam(ENV_HTTP_PROXY); |
| if ( spec == NULL ) |
| fatal("You must specify http proxy server\n"); |
| relay_port = 80; /* set default first */ |
| break; |
| case METHOD_TELNET: |
| if ( spec == NULL ) |
| spec = getparam(ENV_TELNET_PROXY); |
| if ( spec == NULL ) |
| fatal("You must specify telnet proxy server\n"); |
| relay_port = 23; /* set default first */ |
| } |
| |
| if (expect( spec, HTTP_PROXY_PREFIX)) { |
| /* URL format like: "http://server:port/" */ |
| /* extract server:port part */ |
| buf = strdup( spec + strlen(HTTP_PROXY_PREFIX)); |
| buf[strcspn(buf, "/")] = '\0'; |
| } else { |
| /* assume spec is aready "server:port" format */ |
| buf = strdup( spec ); |
| } |
| spec = buf; |
| |
| /* check username in spec */ |
| sep = strchr( spec, '@' ); |
| if ( sep != NULL ) { |
| *sep = '\0'; |
| relay_user = strdup( spec ); |
| spec = sep +1; |
| } |
| if (relay_user == NULL) |
| relay_user = determine_relay_user(); |
| |
| /* split out hostname and port number from spec */ |
| sep = strchr(spec,':'); |
| if ( sep == NULL ) { |
| /* hostname only, port is already set as default */ |
| relay_host = strdup( spec ); |
| } else { |
| /* hostname and port */ |
| relay_port = atoi(sep+1); |
| *sep = '\0'; |
| relay_host = strdup( spec ); |
| } |
| free(buf); |
| return 0; |
| } |
| |
| |
| u_short |
| resolve_port( const char *service ) |
| { |
| int port; |
| if ( service[strspn (service, digits)] == '\0' ) { |
| /* all digits, port number */ |
| port = atoi(service); |
| } else { |
| /* treat as service name */ |
| struct servent *ent; |
| ent = getservbyname( service, NULL ); |
| if ( ent == NULL ) { |
| debug("Unknown service, '%s'\n", service); |
| port = 0; |
| } else { |
| port = ntohs(ent->s_port); |
| debug("service: %s => %d\n", service, port); |
| } |
| } |
| return (u_short)port; |
| } |
| |
| void |
| make_revstr(void) |
| { |
| char *ptr; |
| size_t len; |
| ptr = strstr(rcs_revstr, ": "); |
| if (!ptr) { |
| revstr = strdup("unknown"); |
| return; |
| } |
| ptr += 2; |
| /* assume subversion's keyword expansion like "Revision: 96". */ |
| minor_version = atoi(ptr); |
| revstr = xmalloc(20); |
| snprintf(revstr, 20, "%d.%d", major_version, minor_version); |
| } |
| |
| int |
| getarg( int argc, char **argv ) |
| { |
| int err = 0; |
| char *ptr, *server = (char*)NULL; |
| int method = METHOD_DIRECT; |
| |
| progname = *argv; |
| argc--, argv++; |
| |
| /* check optinos */ |
| while ( (0 < argc) && (**argv == '-') ) { |
| ptr = *argv + 1; |
| while ( *ptr ) { |
| switch ( *ptr ) { |
| case 's': /* use SOCKS */ |
| method = METHOD_SOCKS; |
| break; |
| |
| case 'n': /* no proxy */ |
| method = METHOD_DIRECT; |
| break; |
| |
| case 'h': /* use http-proxy */ |
| method = METHOD_HTTP; |
| break; |
| case 't': |
| method = METHOD_TELNET; |
| break; |
| |
| case 'S': /* specify SOCKS server */ |
| if ( 1 < argc ) { |
| argv++, argc--; |
| method = METHOD_SOCKS; |
| server = *argv; |
| } else { |
| error("option '-%c' needs argument.\n", *ptr); |
| err++; |
| } |
| break; |
| |
| case 'H': /* specify http-proxy server */ |
| if ( 1 < argc ) { |
| argv++, argc--; |
| method = METHOD_HTTP; |
| server = *argv; |
| } else { |
| error("option '-%c' needs argument.\n", *ptr); |
| err++; |
| } |
| break; |
| case 'T': /* specify telnet proxy server */ |
| if ( 1 < argc ) { |
| argv++, argc--; |
| method = METHOD_TELNET; |
| server = *argv; |
| } else { |
| error("option '-%c' needs argument.\n", *ptr); |
| err++; |
| } |
| break; |
| |
| case 'c': |
| if (1 < argc) { |
| argv++, argc--; |
| telnet_command = *argv; |
| } else { |
| error("option '%c' needs argument.\n", *ptr); |
| err++; |
| } |
| break; |
| |
| case 'P': |
| f_hold_session = 1; |
| /* without break */ |
| case 'p': /* specify port to forward */ |
| if ( 1 < argc ) { |
| argv++, argc--; |
| local_type = LOCAL_SOCKET; |
| local_port = resolve_port(*argv); |
| } else { |
| error("option '-%c' needs argument.\n", *ptr); |
| err++; |
| } |
| break; |
| |
| #ifndef _WIN32 |
| case 'w': |
| if ( 1 < argc ) { |
| argv++, argc--; |
| connect_timeout = atoi(*argv); |
| } else { |
| error("option '-%c' needs argument.\n", *ptr); |
| err++; |
| } |
| break; |
| #endif /* not _WIN32 */ |
| |
| case '4': |
| socks_version = 4; |
| break; |
| |
| case '5': |
| socks_version = 5; |
| break; |
| |
| case 'a': |
| if ( 1 < argc ) { |
| argv++, argc--; |
| socks5_auth = *argv; |
| } else { |
| error("option '-%c' needs argument.\n", *ptr); |
| err++; |
| } |
| break; |
| |
| case 'R': /* specify resolve method */ |
| if ( 1 < argc ) { |
| argv++, argc--; |
| socks_resolve = lookup_resolve( *argv ); |
| } else { |
| error("option '-%c' needs argument.\n", *ptr); |
| err++; |
| } |
| break; |
| |
| case 'V': /* print version */ |
| fprintf(stderr, "%s\nVersion %s\n", progdesc, revstr); |
| exit(0); |
| |
| case 'd': /* debug mode */ |
| f_debug++; |
| break; |
| |
| default: |
| error("unknown option '-%c'\n", *ptr); |
| err++; |
| } |
| ptr++; |
| } |
| argc--, argv++; |
| } |
| |
| /* check error */ |
| if ( 0 < err ) |
| goto quit; |
| |
| set_relay( method, server ); |
| |
| /* check destination HOST (MUST) */ |
| if ( argc == 0 ) { |
| fprintf(stderr, "%s\nVersion %s\n", progdesc, revstr); |
| fprintf(stderr, usage, progname); |
| exit(0); |
| } |
| dest_host = argv[0]; |
| /* decide port or service name from programname or argument */ |
| if ( ((ptr=strrchr( progname, '/' )) != NULL) || |
| ((ptr=strchr( progname, '\\')) != NULL) ) |
| ptr++; |
| else |
| ptr = progname; |
| if ( dest_port == 0 ) { |
| /* accept only if -P is not specified. */ |
| if ( 1 < argc ) { |
| /* get port number from argument (prior to progname) */ |
| /* NOTE: This way is for cvs ext method. */ |
| dest_port = resolve_port(argv[1]); |
| } else if ( strncmp( ptr, "connect-", 8) == 0 ) { |
| /* decide port number from program name */ |
| char *str = strdup( ptr+8 ); |
| str[strcspn( str, "." )] = '\0'; |
| dest_port = resolve_port(str); |
| free(str); |
| } |
| } |
| /* check port number */ |
| if ( dest_port <= 0 ) { |
| error( "You must specify the destination port correctly.\n"); |
| err++; |
| goto quit; |
| } |
| if ( (relay_method != METHOD_DIRECT) && (relay_port <= 0) ) { |
| error("Invalid relay port: %d\n", dest_port); |
| err++; |
| goto quit; |
| } |
| |
| quit: |
| /* report for debugging */ |
| debug("relay_method = %s (%d)\n", |
| method_names[relay_method], relay_method); |
| if ( relay_method != METHOD_DIRECT ) { |
| debug("relay_host=%s\n", relay_host); |
| debug("relay_port=%d\n", relay_port); |
| debug("relay_user=%s\n", relay_user); |
| } |
| if ( relay_method == METHOD_SOCKS ) { |
| debug("socks_version=%d\n", socks_version); |
| debug("socks_resolve=%s (%d)\n", |
| resolve_names[socks_resolve], socks_resolve); |
| } |
| debug("local_type=%s\n", local_type_names[local_type]); |
| if ( local_type == LOCAL_SOCKET ) { |
| debug("local_port=%d\n", local_port); |
| if (f_hold_session) |
| debug (" with holding remote session.\n"); |
| } |
| debug("dest_host=%s\n", dest_host); |
| debug("dest_port=%d\n", dest_port); |
| if ( 0 < err ) { |
| fprintf(stderr, usage, progname); |
| exit(1); |
| } |
| return 0; |
| } |
| |
| #ifndef _WIN32 |
| /* Time-out feature is not allowed for Win32 native compilers. */ |
| /* MSVC and Borland C cannot but Cygwin and UNIXes can. */ |
| |
| /* timeout signal hander */ |
| void |
| sig_timeout(void) |
| { |
| signal( SIGALRM, SIG_IGN ); |
| alarm( 0 ); |
| error( "timed out\n" ); |
| exit(1); |
| } |
| |
| /* set timeout param = seconds, 0 clears */ |
| void |
| set_timeout(int timeout) |
| { |
| /* This feature is allowed for UNIX or cygwin environments, currently */ |
| if ( timeout == 0 ) { |
| debug( "clearing timeout\n" ); |
| signal( SIGALRM, SIG_IGN ); |
| alarm( 0 ); |
| } else { |
| debug( "setting timeout: %d seconds\n", timeout ); |
| signal(SIGALRM, (void *)sig_timeout); |
| alarm( timeout ); |
| } |
| } |
| #endif |
| |
| #if !defined(_WIN32) && !defined(__CYGWIN32__) |
| void |
| switch_ns (struct sockaddr_in *ns) |
| { |
| res_init(); |
| memcpy (&_res.nsaddr_list[0], ns, sizeof(*ns)); |
| _res.nscount = 1; |
| debug("Using nameserver at %s\n", inet_ntoa(ns->sin_addr)); |
| } |
| #endif /* !_WIN32 && !__CYGWIN32__ */ |
| |
| /* TODO: IPv6 |
| TODO: fallback if askpass execution failed. |
| */ |
| |
| int |
| local_resolve (const char *host, struct sockaddr_in *addr) |
| { |
| struct hostent *ent; |
| if ( strspn(host, dotdigits) == strlen(host) ) { |
| /* given by IPv4 address */ |
| addr->sin_family = AF_INET; |
| addr->sin_addr.s_addr = inet_addr(host); |
| } else { |
| debug("resolving host by name: %s\n", host); |
| ent = gethostbyname (host); |
| if ( ent ) { |
| memcpy (&addr->sin_addr, ent->h_addr, ent->h_length); |
| addr->sin_family = ent->h_addrtype; |
| debug("resolved: %s (%s)\n", |
| host, inet_ntoa(addr->sin_addr)); |
| } else { |
| debug("failed to resolve locally.\n"); |
| return -1; /* failed */ |
| } |
| } |
| return 0; /* good */ |
| } |
| |
| int |
| open_connection( const char *host, u_short port ) |
| { |
| SOCKET s; |
| struct sockaddr_in saddr; |
| |
| /* resolve address of proxy or direct target */ |
| if (local_resolve (host, &saddr) < 0) { |
| error("can't resolve hostname: %s\n", host); |
| return SOCKET_ERROR; |
| } |
| saddr.sin_port = htons(port); |
| |
| debug("connecting to %s:%u\n", inet_ntoa(saddr.sin_addr), port); |
| s = socket( AF_INET, SOCK_STREAM, 0 ); |
| if ( connect( s, (struct sockaddr *)&saddr, sizeof(saddr)) |
| == SOCKET_ERROR) { |
| debug( "connect() failed.\n"); |
| return SOCKET_ERROR; |
| } |
| return s; |
| } |
| |
| void |
| report_text( char *prefix, char *buf ) |
| { |
| static char work[1024]; |
| char *tmp; |
| |
| if ( !f_debug ) |
| return; |
| if ( !f_report ) |
| return; /* don't report */ |
| debug("%s \"", prefix); |
| while ( *buf ) { |
| memset( work, 0, sizeof(work)); |
| tmp = work; |
| while ( *buf && ((tmp-work) < (int)sizeof(work)-5) ) { |
| switch ( *buf ) { |
| case '\t': *tmp++ = '\\'; *tmp++ = 't'; break; |
| case '\r': *tmp++ = '\\'; *tmp++ = 'r'; break; |
| case '\n': *tmp++ = '\\'; *tmp++ = 'n'; break; |
| case '\\': *tmp++ = '\\'; *tmp++ = '\\'; break; |
| default: |
| if ( isprint(*buf) ) { |
| *tmp++ = *buf; |
| } else { |
| int consumed = tmp - work; |
| snprintf( tmp, sizeof(work)-consumed, |
| "\\x%02X", (unsigned char)*buf); |
| tmp += strlen(tmp); |
| } |
| } |
| buf++; |
| *tmp = '\0'; |
| } |
| debug_("%s", work); |
| } |
| |
| debug_("\"\n"); |
| } |
| |
| |
| void |
| report_bytes( char *prefix, char *buf, int len ) |
| { |
| if ( ! f_debug ) |
| return; |
| debug( "%s", prefix ); |
| while ( 0 < len ) { |
| fprintf( stderr, " %02x", *(unsigned char *)buf); |
| buf++; |
| len--; |
| } |
| fprintf(stderr, "\n"); |
| return; |
| } |
| |
| int |
| atomic_out( SOCKET s, char *buf, int size ) |
| { |
| int ret, len; |
| |
| assert( buf != NULL ); |
| assert( 0<=size ); |
| /* do atomic out */ |
| ret = 0; |
| while ( 0 < size ) { |
| len = send( s, buf+ret, size, 0 ); |
| if ( len == -1 ) |
| fatal("atomic_out() failed to send(), %d\n", socket_errno()); |
| ret += len; |
| size -= len; |
| } |
| if (!f_report) { |
| debug("atomic_out() [some bytes]\n"); |
| debug(">>> xx xx xx xx ...\n"); |
| } else { |
| debug("atomic_out() [%d bytes]\n", ret); |
| report_bytes(">>>", buf, ret); |
| } |
| return ret; |
| } |
| |
| int |
| atomic_in( SOCKET s, char *buf, int size ) |
| { |
| int ret, len; |
| |
| assert( buf != NULL ); |
| assert( 0<=size ); |
| |
| /* do atomic in */ |
| ret = 0; |
| while ( 0 < size ) { |
| len = recv( s, buf+ret, size, 0 ); |
| if ( len == -1 ) { |
| fatal("atomic_in() failed to recv(), %d\n", socket_errno()); |
| } else if ( len == 0 ) { |
| fatal( "Connection closed by peer.\n"); |
| } |
| ret += len; |
| size -= len; |
| } |
| if (!f_report) { |
| debug("atomic_in() [some bytes]\n"); |
| debug("<<< xx xx xx xx ...\n"); |
| } else { |
| debug("atomic_in() [%d bytes]\n", ret); |
| report_bytes("<<<", buf, ret); |
| } |
| return ret; |
| } |
| |
| int |
| line_input( SOCKET s, char *buf, int size ) |
| { |
| char *dst = buf; |
| if ( size == 0 ) |
| return 0; /* no error */ |
| size--; |
| while ( 0 < size ) { |
| switch ( recv( s, dst, 1, 0) ) { /* recv one-by-one */ |
| case SOCKET_ERROR: |
| error("recv() error\n"); |
| return -1; /* error */ |
| case 0: |
| size = 0; /* end of stream */ |
| break; |
| default: |
| /* continue reading until last 1 char is EOL? */ |
| if ( *dst == '\n' ) { |
| /* finished */ |
| size = 0; |
| } else { |
| /* more... */ |
| size--; |
| } |
| dst++; |
| } |
| } |
| *dst = '\0'; |
| report_text( "<<<", buf); |
| return 0; |
| } |
| |
| /* cut_token() |
| Span token in given string STR until char in DELIM is appeared. |
| Then replace contiguous DELIMS with '\0' for string termination |
| and returns next pointer. |
| If no next token, return NULL. |
| */ |
| char * |
| cut_token( char *str, char *delim) |
| { |
| char *ptr = str + strcspn(str, delim); |
| char *end = ptr + strspn(ptr, delim); |
| if ( ptr == str ) |
| return NULL; |
| while ( ptr < end ) |
| *ptr++ = '\0'; |
| return ptr; |
| } |
| |
| const char * |
| lookup(int num, LOOKUP_ITEM *items) |
| { |
| int i = 0; |
| while (0 <= items[i].num) { |
| if (items[i].num == num) |
| return items[i].str; |
| i++; |
| } |
| return "(unknown)"; |
| } |
| |
| /* readpass() |
| password input routine |
| Use ssh-askpass (same mechanism to OpenSSH) |
| */ |
| char * |
| readpass( const char* prompt, ...) |
| { |
| static char buf[1000]; /* XXX, don't be fix length */ |
| va_list args; |
| va_start(args, prompt); |
| vsnprintf(buf, sizeof(buf), prompt, args); |
| va_end(args); |
| |
| if ( getparam(ENV_SSH_ASKPASS) |
| #if !defined(_WIN32) && !defined(__CYGWIN32__) |
| && getenv("DISPLAY") |
| #endif /* not _WIN32 && not __CYGWIN32__ */ |
| ) { |
| /* use ssh-askpass to get password */ |
| FILE *fp; |
| char *askpass = getparam(ENV_SSH_ASKPASS), *cmd; |
| int cmd_size = strlen(askpass) +1 +1 +strlen(buf) +1 +1; |
| cmd = xmalloc(cmd_size); |
| snprintf(cmd, cmd_size, "%s \"%s\"", askpass, buf); |
| fp = popen(cmd, "r"); |
| free(cmd); |
| if ( fp == NULL ) |
| return NULL; /* fail */ |
| buf[0] = '\0'; |
| if (fgets(buf, sizeof(buf), fp) == NULL) |
| return NULL; /* fail */ |
| fclose(fp); |
| } else { |
| tty_readpass( buf, buf, sizeof(buf)); |
| } |
| buf[strcspn(buf, "\r\n")] = '\0'; |
| return buf; |
| } |
| |
| static int |
| socks5_do_auth_userpass( int s ) |
| { |
| unsigned char buf[1024], *ptr; |
| char *pass = NULL; |
| int len; |
| |
| /* do User/Password authentication. */ |
| /* This feature requires username and password from |
| command line argument or environment variable, |
| or terminal. */ |
| if (relay_user == NULL) |
| fatal("cannot determine user name.\n"); |
| |
| /* get password from environment variable if exists. */ |
| if ((pass=determine_relay_password()) == NULL && |
| (pass=readpass("Enter SOCKS5 password for %s@%s: ", |
| relay_user, relay_host)) == NULL) |
| fatal("Cannot get password for user: %s\n", relay_user); |
| |
| /* make authentication packet */ |
| ptr = buf; |
| PUT_BYTE( ptr++, 1 ); /* subnegotiation ver.: 1 */ |
| len = strlen( relay_user ); /* ULEN and UNAME */ |
| PUT_BYTE( ptr++, len ); |
| strcpy( ptr, relay_user ); |
| ptr += len; |
| len = strlen( pass ); /* PLEN and PASSWD */ |
| PUT_BYTE( ptr++, strlen(pass)); |
| strcpy( ptr, pass ); |
| ptr += len; |
| memset (pass, 0, strlen(pass)); /* erase password */ |
| |
| /* send it and get answer */ |
| f_report = 0; |
| atomic_out( s, buf, ptr-buf ); |
| f_report = 1; |
| atomic_in( s, buf, 2 ); |
| |
| /* check status */ |
| if ( buf[1] == 0 ) |
| return 0; /* success */ |
| else |
| return -1; /* fail */ |
| } |
| |
| static const char * |
| socks5_getauthname( int auth ) |
| { |
| switch ( auth ) { |
| case SOCKS5_AUTH_REJECT: return "REJECTED"; |
| case SOCKS5_AUTH_NOAUTH: return "NO-AUTH"; |
| case SOCKS5_AUTH_GSSAPI: return "GSSAPI"; |
| case SOCKS5_AUTH_USERPASS: return "USERPASS"; |
| case SOCKS5_AUTH_CHAP: return "CHAP"; |
| case SOCKS5_AUTH_EAP: return "EAP"; |
| case SOCKS5_AUTH_MAF: return "MAF"; |
| default: return "(unknown)"; |
| } |
| } |
| |
| typedef struct { |
| char* name; |
| unsigned char auth; |
| } AUTH_METHOD_ITEM; |
| |
| AUTH_METHOD_ITEM socks5_auth_table[] = { |
| { "none", SOCKS5_AUTH_NOAUTH }, |
| { "gssapi", SOCKS5_AUTH_GSSAPI }, |
| { "userpass", SOCKS5_AUTH_USERPASS }, |
| { "chap", SOCKS5_AUTH_CHAP }, |
| { NULL, -1 }, |
| }; |
| |
| int |
| socks5_auth_parse_1(char *start, char *end){ |
| int i, len; |
| for ( ; *start; start++ ) |
| if ( *start != ' ' && *start != '\t') break; |
| for ( end--; end >= start; end-- ) { |
| if ( *end != ' ' && *end != '\t'){ |
| end++; |
| break; |
| } |
| } |
| len = end - start; |
| for ( i = 0; socks5_auth_table[i].name != NULL; i++ ){ |
| if ( strncmp(start, socks5_auth_table[i].name, len) == 0) { |
| return socks5_auth_table[i].auth; |
| } |
| } |
| fatal("Unknown auth method: %s\n", start); |
| return -1; |
| } |
| |
| int |
| socks5_auth_parse(char *start, unsigned char *auth_list, int max_auth){ |
| char *end; |
| int i = 0; |
| while ( i < max_auth ) { |
| end = strchr(start, ','); |
| if (*start && end) { |
| auth_list[i++] = socks5_auth_parse_1(start, end); |
| start = ++end; |
| } else { |
| break; |
| } |
| } |
| if ( *start && ( i < max_auth ) ){ |
| for( end = start; *end; end++ ); |
| auth_list[i++] = socks5_auth_parse_1(start, end); |
| } else { |
| fatal("Too much auth method.\n"); |
| } |
| return i; |
| } |
| |
| /* begin SOCKS5 relaying |
| And no authentication is supported. |
| */ |
| int |
| begin_socks5_relay( SOCKET s ) |
| { |
| unsigned char buf[256], *ptr, *env = socks5_auth; |
| unsigned char n_auth = 0; unsigned char auth_list[10], auth_method; |
| int len, auth_result, i; |
| |
| debug( "begin_socks_relay()\n"); |
| |
| /* request authentication */ |
| ptr = buf; |
| PUT_BYTE( ptr++, 5); /* SOCKS version (5) */ |
| |
| if ( env == NULL ) |
| env = getparam(ENV_SOCKS5_AUTH); |
| if ( env == NULL ) { |
| /* add no-auth authentication */ |
| auth_list[n_auth++] = SOCKS5_AUTH_NOAUTH; |
| /* add user/pass authentication */ |
| auth_list[n_auth++] = SOCKS5_AUTH_USERPASS; |
| } else { |
| n_auth = socks5_auth_parse(env, auth_list, 10); |
| } |
| PUT_BYTE( ptr++, n_auth); /* num auth */ |
| for (i=0; i<n_auth; i++) { |
| debug("available auth method[%d] = %s (0x%02x)\n", |
| i, socks5_getauthname(auth_list[i]), auth_list[i]); |
| PUT_BYTE( ptr++, auth_list[i]); /* authentications */ |
| } |
| atomic_out( s, buf, ptr-buf ); /* send requst */ |
| atomic_in( s, buf, 2 ); /* recv response */ |
| if ( (buf[0] != 5) || /* ver5 response */ |
| (buf[1] == 0xFF) ) { /* check auth method */ |
| error("No auth method accepted.\n"); |
| return -1; |
| } |
| auth_method = buf[1]; |
| |
| debug("auth method: %s\n", socks5_getauthname(auth_method)); |
| |
| switch ( auth_method ) { |
| case SOCKS5_AUTH_REJECT: |
| error("No acceptable authentication method\n"); |
| return -1; /* fail */ |
| |
| case SOCKS5_AUTH_NOAUTH: |
| /* nothing to do */ |
| auth_result = 0; |
| break; |
| |
| case SOCKS5_AUTH_USERPASS: |
| auth_result = socks5_do_auth_userpass(s); |
| break; |
| |
| default: |
| error("Unsupported authentication method: %s\n", |
| socks5_getauthname( auth_method )); |
| return -1; /* fail */ |
| } |
| if ( auth_result != 0 ) { |
| error("Authentication failed.\n"); |
| return -1; |
| } |
| /* request to connect */ |
| ptr = buf; |
| PUT_BYTE( ptr++, 5); /* SOCKS version (5) */ |
| PUT_BYTE( ptr++, 1); /* CMD: CONNECT */ |
| PUT_BYTE( ptr++, 0); /* FLG: 0 */ |
| if ( dest_addr.sin_addr.s_addr == 0 ) { |
| /* resolved by SOCKS server */ |
| PUT_BYTE( ptr++, 3); /* ATYP: DOMAINNAME */ |
| len = strlen(dest_host); |
| PUT_BYTE( ptr++, len); /* DST.ADDR (len) */ |
| memcpy( ptr, dest_host, len ); /* (hostname) */ |
| ptr += len; |
| } else { |
| /* resolved localy */ |
| PUT_BYTE( ptr++, 1 ); /* ATYP: IPv4 */ |
| memcpy( ptr, &dest_addr.sin_addr.s_addr, sizeof(dest_addr.sin_addr)); |
| ptr += sizeof(dest_addr.sin_addr); |
| } |
| PUT_BYTE( ptr++, dest_port>>8); /* DST.PORT */ |
| PUT_BYTE( ptr++, dest_port&0xFF); |
| atomic_out( s, buf, ptr-buf); /* send request */ |
| atomic_in( s, buf, 4 ); /* recv response */ |
| if ( (buf[1] != SOCKS5_REP_SUCCEEDED) ) { /* check reply code */ |
| error("Got error response from SOCKS server: %d (%s).\n", |
| buf[1], lookup(buf[1], socks5_rep_names)); |
| return -1; |
| } |
| ptr = buf + 4; |
| switch ( buf[3] ) { /* case by ATYP */ |
| case 1: /* IP v4 ADDR*/ |
| atomic_in( s, ptr, 4+2 ); /* recv IPv4 addr and port */ |
| break; |
| case 3: /* DOMAINNAME */ |
| atomic_in( s, ptr, 1 ); /* recv name and port */ |
| atomic_in( s, ptr+1, *(unsigned char*)ptr + 2); |
| break; |
| case 4: /* IP v6 ADDR */ |
| atomic_in( s, ptr, 16+2 ); /* recv IPv6 addr and port */ |
| break; |
| } |
| |
| /* Conguraturation, connected via SOCKS5 server! */ |
| return 0; |
| } |
| |
| /* begin SOCKS protocol 4 relaying |
| And no authentication is supported. |
| |
| There's SOCKS protocol version 4 and 4a. Protocol version |
| 4a has capability to resolve hostname by SOCKS server, so |
| we don't need resolving IP address of destination host on |
| local machine. |
| |
| Environment variable SOCKS_RESOLVE directs how to resolve |
| IP addess. There's 3 keywords allowed; "local", "remote" |
| and "both" (case insensitive). Keyword "local" means taht |
| target host name is resolved by localhost resolver |
| (usualy with gethostbyname()), "remote" means by remote |
| SOCKS server, "both" means to try resolving by localhost |
| then remote. |
| |
| SOCKS4 protocol and authentication of SOCKS5 protocol |
| requires user name on connect request. |
| User name is determined by following method. |
| |
| 1. If server spec has user@hostname:port format then |
| user part is used for this SOCKS server. |
| |
| 2. Get user name from environment variable LOGNAME, USER |
| (in this order). |
| |
| */ |
| int |
| begin_socks4_relay( SOCKET s ) |
| { |
| unsigned char buf[256], *ptr; |
| |
| debug( "begin_socks_relay()\n"); |
| |
| /* make connect request packet |
| protocol v4: |
| VN:1, CD:1, PORT:2, ADDR:4, USER:n, NULL:1 |
| protocol v4a: |
| VN:1, CD:1, PORT:2, DUMMY:4, USER:n, NULL:1, HOSTNAME:n, NULL:1 |
| */ |
| ptr = buf; |
| PUT_BYTE( ptr++, 4); /* protocol version (4) */ |
| PUT_BYTE( ptr++, 1); /* CONNECT command */ |
| PUT_BYTE( ptr++, dest_port>>8); /* destination Port */ |
| PUT_BYTE( ptr++, dest_port&0xFF); |
| /* destination IP */ |
| memcpy(ptr, &dest_addr.sin_addr, sizeof(dest_addr.sin_addr)); |
| ptr += sizeof(dest_addr.sin_addr); |
| if ( dest_addr.sin_addr.s_addr == 0 ) |
| *(ptr-1) = 1; /* fake, protocol 4a */ |
| /* username */ |
| if (relay_user == NULL) |
| fatal( "Cannot determine user name.\n"); |
| strcpy( ptr, relay_user ); |
| ptr += strlen( relay_user ) +1; |
| /* destination host name (for protocol 4a) */ |
| if ( (socks_version == 4) && (dest_addr.sin_addr.s_addr == 0)) { |
| strcpy( ptr, dest_host ); |
| ptr += strlen( dest_host ) +1; |
| } |
| /* send command and get response |
| response is: VN:1, CD:1, PORT:2, ADDR:4 */ |
| atomic_out( s, buf, ptr-buf); /* send request */ |
| atomic_in( s, buf, 8 ); /* recv response */ |
| if ( (buf[1] != SOCKS4_REP_SUCCEEDED) ) { /* check reply code */ |
| error("Got error response: %d: '%s'.\n", |
| buf[1], lookup(buf[1], socks4_rep_names)); |
| return -1; /* failed */ |
| } |
| |
| /* Conguraturation, connected via SOCKS4 server! */ |
| return 0; |
| } |
| |
| int |
| sendf(SOCKET s, const char *fmt,...) |
| { |
| static char buf[10240]; /* xxx, enough? */ |
| |
| va_list args; |
| va_start( args, fmt ); |
| vsnprintf( buf, sizeof(buf), fmt, args ); |
| va_end( args ); |
| |
| report_text(">>>", buf); |
| if ( send(s, buf, strlen(buf), 0) == SOCKET_ERROR ) { |
| debug("failed to send http request. errno=%d\n", socket_errno()); |
| return -1; |
| } |
| return 0; |
| } |
| |
| const char *base64_table = |
| "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; |
| |
| char * |
| make_base64_string(const char *str) |
| { |
| static char *buf; |
| unsigned char *src; |
| char *dst; |
| int bits, data, src_len, dst_len; |
| /* make base64 string */ |
| src_len = strlen(str); |
| dst_len = (src_len+2)/3*4; |
| buf = xmalloc(dst_len+1); |
| bits = data = 0; |
| src = (unsigned char *)str; |
| dst = (unsigned char *)buf; |
| while ( dst_len-- ) { |
| if ( bits < 6 ) { |
| data = (data << 8) | *src; |
| bits += 8; |
| if ( *src != 0 ) |
| src++; |
| } |
| *dst++ = base64_table[0x3F & (data >> (bits-6))]; |
| bits -= 6; |
| } |
| *dst = '\0'; |
| /* fix-up tail padding */ |
| switch ( src_len%3 ) { |
| case 1: |
| *--dst = '='; |
| case 2: |
| *--dst = '='; |
| } |
| return buf; |
| } |
| |
| |
| int |
| basic_auth (SOCKET s) |
| { |
| char *userpass; |
| char *cred; |
| const char *user = relay_user; |
| char *pass = NULL; |
| int len, ret; |
| |
| /* Get username/password for authentication */ |
| if (user == NULL) |
| fatal("Cannot decide username for proxy authentication."); |
| if ((pass = determine_relay_password ()) == NULL && |
| (pass = readpass("Enter proxy authentication password for %s@%s: ", |
| relay_user, relay_host)) == NULL) |
| fatal("Cannot decide password for proxy authentication."); |
| |
| len = strlen(user)+strlen(pass)+1; |
| userpass = xmalloc(len+1); |
| snprintf(userpass, len+1, "%s:%s", user, pass); |
| memset (pass, 0, strlen(pass)); |
| cred = make_base64_string(userpass); |
| memset (userpass, 0, len); |
| |
| f_report = 0; /* don't report for security */ |
| ret = sendf(s, "Proxy-Authorization: Basic %s\r\n", cred); |
| f_report = 1; |
| report_text(">>>", "Proxy-Authorization: Basic xxxxx\r\n"); |
| |
| memset(cred, 0, strlen(cred)); |
| free(cred); |
| |
| return ret; |
| } |
| |
| /* begin relaying via HTTP proxy |
| Directs CONNECT method to proxy server to connect to |
| destination host (and port). It may not be allowed on your |
| proxy server. |
| */ |
| int |
| begin_http_relay( SOCKET s ) |
| { |
| char buf[1024]; |
| int result; |
| char *auth_what; |
| |
| debug("begin_http_relay()\n"); |
| |
| if (sendf(s,"CONNECT %s:%d HTTP/1.0\r\n", dest_host, dest_port) < 0) |
| return START_ERROR; |
| if (proxy_auth_type == PROXY_AUTH_BASIC && basic_auth (s) < 0) |
| return START_ERROR; |
| if (sendf(s,"\r\n") < 0) |
| return START_ERROR; |
| |
| /* get response */ |
| if ( line_input(s, buf, sizeof(buf)) < 0 ) { |
| debug("failed to read http response.\n"); |
| return START_ERROR; |
| } |
| |
| /* check status */ |
| if (!strchr(buf, ' ')) { |
| error ("Unexpected http response: '%s'.\n", buf); |
| return START_ERROR; |
| } |
| result = atoi(strchr(buf,' ')); |
| |
| switch ( result ) { |
| case 200: |
| /* Conguraturation, connected via http proxy server! */ |
| debug("connected, start user session.\n"); |
| break; |
| case 302: /* redirect */ |
| do { |
| if (line_input(s, buf, sizeof(buf))) |
| break; |
| downcase(buf); |
| if (expect(buf, "Location: ")) { |
| relay_host = cut_token(buf, "//"); |
| cut_token(buf, "/"); |
| relay_port = atoi(cut_token(buf, ":")); |
| } |
| } while (strcmp(buf,"\r\n") != 0); |
| return START_RETRY; |
| |
| /* We handle both 401 and 407 codes here: 401 is WWW-Authenticate, which |
| * not strictly the correct response, but some proxies do send this (e.g. |
| * Symantec's Raptor firewall) */ |
| case 401: /* WWW-Auth required */ |
| case 407: /* Proxy-Auth required */ |
| /** NOTE: As easy implementation, we support only BASIC scheme |
| and ignore realm. */ |
| /* If proxy_auth_type is PROXY_AUTH_BASIC and get |
| this result code, authentication was failed. */ |
| if (proxy_auth_type != PROXY_AUTH_NONE) { |
| error("Authentication failed.\n"); |
| return START_ERROR; |
| } |
| auth_what = (result == 401) ? "WWW-Authenticate:" : "Proxy-Authenticate:"; |
| do { |
| if ( line_input(s, buf, sizeof(buf)) ) { |
| break; |
| } |
| downcase(buf); |
| if (expect(buf, auth_what)) { |
| /* parse type and realm */ |
| char *scheme, *realm; |
| scheme = cut_token(buf, " "); |
| realm = cut_token(scheme, " "); |
| if ( scheme == NULL || realm == NULL ) { |
| debug("Invalid format of %s field.", auth_what); |
| return START_ERROR; /* fail */ |
| } |
| /* check supported auth type */ |
| if (expect(scheme, "basic")) { |
| proxy_auth_type = PROXY_AUTH_BASIC; |
| } else { |
| debug("Unsupported authentication type: %s", scheme); |
| } |
| } |
| } while (strcmp(buf,"\r\n") != 0); |
| if ( proxy_auth_type == PROXY_AUTH_NONE ) { |
| debug("Can't find %s in response header.", auth_what); |
| return START_ERROR; |
| } else { |
| return START_RETRY; |
| } |
| |
| default: |
| /* Not allowed */ |
| debug("http proxy is not allowed.\n"); |
| return START_ERROR; |
| } |
| /* skip to end of response header */ |
| do { |
| if ( line_input(s, buf, sizeof(buf) ) ) { |
| debug("Can't skip response headers\n"); |
| return START_ERROR; |
| } |
| } while ( strcmp(buf,"\r\n") != 0 ); |
| |
| return START_OK; |
| } |
| |
| /* begin relaying via TELNET proxy. |
| Sends string specified by telnet_command (-c option) with |
| replacing host name and port number to the socket. */ |
| int |
| begin_telnet_relay( SOCKET s ) |
| { |
| char buf[1024]; |
| char *cmd; |
| char *good_phrase = "connected to"; |
| char *bad_phrase_list[] = { |
| " failed", " refused", " rejected", " closed" |
| }; |
| char sep = ' '; |
| int i; |
| |
| debug("begin_telnet_relay()\n"); |
| |
| /* report phrase */ |
| debug("good phrase: '%s'\n", good_phrase); |
| debug("bad phrases"); |
| sep = ':'; |
| for (i=0; i< (sizeof(bad_phrase_list) / sizeof(char*)); i++) { |
| debug_("%c '%s'", sep, bad_phrase_list[i]); |
| sep = ','; |
| } |
| debug_("\n"); |
| |
| /* make request string with replacing %h by destination hostname |
| and %p by port number, etc. */ |
| cmd = expand_host_and_port(telnet_command, dest_host, dest_port); |
| |
| /* Sorry, we send request string now without waiting a prompt. */ |
| if (sendf(s, "%s\r\n", cmd) < 0) { |
| free(cmd); |
| return START_ERROR; |
| } |
| free(cmd); |
| |
| /* Process answer from proxy until good or bad phrase is detected. We |
| assume that the good phrase should be appeared only in the final |
| line of proxy responses. Bad keywods in the line causes operation |
| fail. First checks a good phrase, then checks bad phrases. |
| If no match, continue reading line from proxy. */ |
| while (!line_input(s, buf, sizeof(buf)) && buf[0] != '\0') { |
| downcase(buf); |
| /* first, check good phrase */ |
| if (strstr(buf, good_phrase)) { |
| debug("good phrase is detected: '%s'\n", good_phrase); |
| return START_OK; |
| } |
| /* then, check bad phrase */ |
| for (i=0; i<(sizeof(bad_phrase_list)/sizeof(char*)); i++) { |
| if (strstr(buf, bad_phrase_list[i]) != NULL) { |
| debug("bad phrase is detected: '%s'\n", bad_phrase_list[i]); |
| return START_ERROR; |
| } |
| } |
| } |
| debug("error reading from telnet proxy\n"); |
| |
| return START_ERROR; |
| } |
| |
| |
| #ifdef _WIN32 |
| /* ddatalen() |
| Returns 1 if data is available, otherwise return 0 |
| */ |
| int |
| stdindatalen (void) |
| { |
| DWORD len = 0; |
| struct stat st; |
| fstat( 0, &st ); |
| if ( st.st_mode & _S_IFIFO ) { |
| /* in case of PIPE */ |
| if ( !PeekNamedPipe( GetStdHandle(STD_INPUT_HANDLE), |
| NULL, 0, NULL, &len, NULL) ) { |
| if ( GetLastError() == ERROR_BROKEN_PIPE ) { |
| /* PIPE source is closed */ |
| /* read() will detects EOF */ |
| len = 1; |
| } else { |
| fatal("PeekNamedPipe() failed, errno=%d\n", |
| GetLastError()); |
| } |
| } |
| } else if ( st.st_mode & _S_IFREG ) { |
| /* in case of regular file (redirected) */ |
| len = 1; /* always data ready */ |
| } else if ( _kbhit() ) { |
| /* in case of console */ |
| len = 1; |
| } |
| return len; |
| } |
| #endif /* _WIN32 */ |
| |
| /* relay byte from stdin to socket and fro socket to stdout. |
| returns reason of termination */ |
| int |
| do_repeater( SOCKET local_in, SOCKET local_out, SOCKET remote ) |
| { |
| /** vars for local input data **/ |
| char lbuf[1024]; /* local input buffer */ |
| int lbuf_len; /* available data in lbuf */ |
| int f_local; /* read local input more? */ |
| /** vars for remote input data **/ |
| char rbuf[1024]; /* remote input buffer */ |
| int rbuf_len; /* available data in rbuf */ |
| int f_remote; /* read remote input more? */ |
| int close_reason = REASON_UNK; /* reason of end repeating */ |
| /** other variables **/ |
| int nfds, len; |
| fd_set ifds, ofds; |
| struct timeval *tmo; |
| #ifdef _WIN32 |
| struct timeval win32_tmo; |
| #endif /* _WIN32 */ |
| |
| /* repeater between stdin/out and socket */ |
| nfds = ((local_in<remote)? remote: local_in) +1; |
| f_local = 1; /* yes, read from local */ |
| f_remote = 1; /* yes, read from remote */ |
| lbuf_len = 0; |
| rbuf_len = 0; |
| |
| while ( f_local || f_remote ) { |
| FD_ZERO(&ifds ); |
| FD_ZERO(&ofds ); |
| tmo = NULL; |
| |
| /** prepare for reading local input **/ |
| if ( f_local && (lbuf_len < (int)sizeof(lbuf)) ) { |
| #ifdef _WIN32 |
| if ( local_type != LOCAL_SOCKET ) { |
| /* select() on Winsock is not accept standard handle. |
| So use select() with short timeout and checking data |
| in stdin by another method. */ |
| win32_tmo.tv_sec = 0; |
| win32_tmo.tv_usec = 10*1000; /* 10 ms */ |
| tmo = &win32_tmo; |
| } else |
| #endif /* !_WIN32 */ |
| FD_SET( local_in, &ifds ); |
| } |
| |
| /** prepare for reading remote input **/ |
| if ( f_remote && (rbuf_len < (int)sizeof(rbuf)) ) { |
| FD_SET( remote, &ifds ); |
| } |
| |
| /* FD_SET( local_out, ofds ); */ |
| /* FD_SET( remote, ofds ); */ |
| |
| if ( select( nfds, &ifds, &ofds, (fd_set*)NULL, tmo ) == -1 ) { |
| /* some error */ |
| error( "select() failed, %d\n", socket_errno()); |
| return REASON_ERROR; |
| } |
| #ifdef _WIN32 |
| /* fake ifds if local is stdio handle because |
| select() of Winsock does not accept stdio |
| handle. */ |
| if (f_local && (local_type!=LOCAL_SOCKET) && (0<stdindatalen())) |
| FD_SET(0,&ifds); /* data ready */ |
| #endif |
| |
| /* remote => local */ |
| if ( FD_ISSET(remote, &ifds) && (rbuf_len < (int)sizeof(rbuf)) ) { |
| len = recv( remote, rbuf + rbuf_len, sizeof(rbuf)-rbuf_len, 0); |
| if ( len == 0 || (len == -1 && socket_errno() == ECONNRESET)) { |
| debug("connection %s by peer\n", |
| (len==0)? "closed": "reset"); |
| close_reason = REASON_CLOSED_BY_REMOTE; |
| f_remote = 0; /* no more read from socket */ |
| f_local = 0; |
| } else if ( len == -1 ) { |
| /* error */ |
| fatal("recv() failed, %d\n", socket_errno()); |
| } else { |
| debug("recv %d bytes\n", len); |
| if ( 1 < f_debug ) /* more verbose */ |
| report_bytes( "<<<", rbuf+rbuf_len, len); |
| rbuf_len += len; |
| } |
| } |
| |
| /* local => remote */ |
| if ( FD_ISSET(local_in, &ifds) && (lbuf_len < (int)sizeof(lbuf)) ) { |
| if (local_type == LOCAL_SOCKET) |
| len = recv(local_in, lbuf + lbuf_len, |
| sizeof(lbuf)-lbuf_len, 0); |
| else |
| len = read(local_in, lbuf + lbuf_len, sizeof(lbuf)-lbuf_len); |
| if ( len == 0 ) { |
| /* stdin is EOF */ |
| debug("local input is EOF\n"); |
| if (!f_hold_session) |
| shutdown(remote, 1); /* no-more writing */ |
| f_local = 0; |
| close_reason = REASON_CLOSED_BY_LOCAL; |
| } else if ( len == -1 ) { |
| /* error on reading from stdin */ |
| if (f_hold_session) { |
| debug ("failed to read from local\n"); |
| f_local = 0; |
| close_reason = REASON_CLOSED_BY_LOCAL; |
| } else |
| fatal("recv() failed, errno = %d\n", errno); |
| } else { |
| /* repeat */ |
| lbuf_len += len; |
| } |
| } |
| |
| /* flush data in buffer to socket */ |
| if ( 0 < lbuf_len ) { |
| len = send(remote, lbuf, lbuf_len, 0); |
| if ( len == -1 ) { |
| fatal("send() failed, %d\n", socket_errno()); |
| } else if ( 0 < len ) { |
| if ( 1 < f_debug ) /* more verbose */ |
| report_bytes( ">>>", lbuf, len); |
| /* move data on to top of buffer */ |
| debug("sent %d bytes\n", len); |
| lbuf_len -= len; |
| if ( 0 < lbuf_len ) |
| memcpy( lbuf, lbuf+len, lbuf_len ); |
| assert( 0 <= lbuf_len ); |
| } |
| } |
| |
| /* flush data in buffer to local output */ |
| if ( 0 < rbuf_len ) { |
| if (local_type == LOCAL_SOCKET) |
| len = send( local_out, rbuf, rbuf_len, 0); |
| else |
| len = write( local_out, rbuf, rbuf_len); |
| if ( len == -1 ) { |
| fatal("output (local) failed, errno=%d\n", errno); |
| } |
| rbuf_len -= len; |
| if ( len < rbuf_len ) |
| memcpy( rbuf, rbuf+len, rbuf_len ); |
| assert( 0 <= rbuf_len ); |
| } |
| if (f_local == 0 && f_hold_session) { |
| debug ("closing local port without disconnecting from remote\n"); |
| f_remote = 0; |
| shutdown (local_out, 2); |
| close (local_out); |
| break; |
| } |
| } |
| |
| return close_reason; |
| } |
| |
| int |
| accept_connection (u_short port) |
| { |
| static int sock = -1; |
| int connection; |
| struct sockaddr_in name; |
| struct sockaddr client; |
| int socklen; |
| fd_set ifds; |
| int nfds; |
| int sockopt; |
| |
| /* Create the socket. */ |
| debug("Creating source port to forward.\n"); |
| sock = socket (PF_INET, SOCK_STREAM, 0); |
| if (sock < 0) |
| fatal("socket() failed, errno=%d\n", socket_errno()); |
| sockopt = 1; |
| setsockopt (sock, SOL_SOCKET, SO_REUSEADDR, |
| (void*)&sockopt, sizeof(sockopt)); |
| |
| /* Give the socket a name. */ |
| name.sin_family = AF_INET; |
| name.sin_port = htons (port); |
| name.sin_addr.s_addr = htonl (INADDR_ANY); |
| if (bind (sock, (struct sockaddr *) &name, sizeof (name)) < 0) |
| fatal ("bind() failed, errno=%d\n", socket_errno()); |
| |
| if (listen( sock, 1) < 0) |
| fatal ("listen() failed, errno=%d\n", socket_errno()); |
| |
| /* wait for new connection with watching EOF of stdin. */ |
| debug ("waiting new connection at port %d (socket=%d)\n", port, sock); |
| nfds = sock + 1; |
| do { |
| int n; |
| struct timeval *ptmo = NULL; |
| #ifdef _WIN32 |
| struct timeval tmo; |
| tmo.tv_sec = 0; |
| tmo.tv_usec = 100*1000; /* On Windows, 100ms timeout */ |
| ptmo = &tmo; |
| #endif /* _WIN32 */ |
| FD_ZERO (&ifds); |
| FD_SET ((SOCKET)sock, &ifds); |
| #ifndef _WIN32 |
| FD_SET (0, &ifds); /* watch stdin */ |
| #endif |
| n = select (nfds, &ifds, NULL, NULL, ptmo); |
| if (n == -1) { |
| fatal ("select() failed, %d\n", socket_errno()); |
| exit (1); |
| } |
| #ifdef _WIN32 |
| if (0 < stdindatalen()) { |
| FD_SET (0, &ifds); /* fake */ |
| n++; |
| } |
| #endif |
| if (0 < n) { |
| if (FD_ISSET(0, &ifds) && (getchar() <= 0)) { |
| /* EOF */ |
| debug ("Give-up waiting port because stdin is closed."); |
| exit(0); |
| } |
| if (FD_ISSET(sock, &ifds)) |
| break; /* socket is stimulated */ |
| } |
| } while (1); |
| socklen = sizeof(client); |
| connection = accept( sock, &client, &socklen); |
| if ( connection < 0 ) |
| fatal ("accept() failed, errno=%d\n", socket_errno()); |
| return connection; |
| } |
| |
| |
| |
| /** Main of program **/ |
| int |
| main( int argc, char **argv ) |
| { |
| int ret; |
| int remote; /* socket */ |
| int local_in; /* Local input */ |
| int local_out; /* Local output */ |
| int reason; |
| #ifdef _WIN32 |
| WSADATA wsadata; |
| WSAStartup( 0x101, &wsadata); |
| #endif /* _WIN32 */ |
| |
| /* initialization */ |
| make_revstr(); |
| getarg( argc, argv ); |
| debug("Program is $Revision: 100 $\n"); |
| |
| /* Open local_in and local_out if forwarding a port */ |
| if ( local_type == LOCAL_SOCKET ) { |
| /* Relay between local port and destination */ |
| local_in = local_out = accept_connection( local_port ); |
| } else { |
| /* Relay between stdin/stdout and desteination */ |
| local_in = 0; |
| local_out = 1; |
| #ifdef _WIN32 |
| _setmode(local_in, O_BINARY); |
| _setmode(local_out, O_BINARY); |
| #endif |
| } |
| |
| retry: |
| #ifndef _WIN32 |
| if (0 < connect_timeout) |
| set_timeout (connect_timeout); |
| #endif /* not _WIN32 */ |
| |
| if (check_direct(dest_host)) |
| relay_method = METHOD_DIRECT; |
| /* make connection */ |
| if ( relay_method == METHOD_DIRECT ) { |
| remote = open_connection (dest_host, dest_port); |
| if ( remote == SOCKET_ERROR ) |
| fatal( "Unable to connect to destination host, errno=%d\n", |
| socket_errno()); |
| } else { |
| remote = open_connection (relay_host, relay_port); |
| if ( remote == SOCKET_ERROR ) |
| fatal( "Unable to connect to relay host, errno=%d\n", |
| socket_errno()); |
| } |
| |
| /** resolve destination host (SOCKS) **/ |
| #if !defined(_WIN32) && !defined(__CYGWIN32__) |
| if (socks_ns.sin_addr.s_addr != 0) |
| switch_ns (&socks_ns); |
| #endif /* not _WIN32 && not __CYGWIN32__ */ |
| if (relay_method == METHOD_SOCKS && |
| socks_resolve == RESOLVE_LOCAL && |
| local_resolve (dest_host, &dest_addr) < 0) { |
| fatal("Unknown host: %s", dest_host); |
| } |
| |
| /** relay negociation **/ |
| switch ( relay_method ) { |
| case METHOD_SOCKS: |
| if ( ((socks_version == 5) && (begin_socks5_relay(remote) < 0)) || |
| ((socks_version == 4) && (begin_socks4_relay(remote) < 0)) ) |
| fatal( "failed to begin relaying via SOCKS.\n"); |
| break; |
| |
| case METHOD_HTTP: |
| ret = begin_http_relay(remote); |
| switch (ret) { |
| case START_ERROR: |
| close (remote); |
| fatal("failed to begin relaying via HTTP.\n"); |
| case START_OK: |
| break; |
| case START_RETRY: |
| /* retry with authentication */ |
| close (remote); |
| goto retry; |
| } |
| break; |
| case METHOD_TELNET: |
| if (begin_telnet_relay(remote) < 0) |
| fatal("failed to begin relaying via telnet.\n"); |
| break; |
| } |
| debug("connected\n"); |
| |
| #ifndef _WIN32 |
| if (0 < connect_timeout) |
| set_timeout (0); |
| #endif /* not _WIN32 */ |
| |
| /* main loop */ |
| debug ("start relaying.\n"); |
| do_repeater: |
| reason = do_repeater(local_in, local_out, remote); |
| debug ("relaying done.\n"); |
| if (local_type == LOCAL_SOCKET && |
| reason == REASON_CLOSED_BY_LOCAL && |
| f_hold_session) { |
| /* re-wait at local port without closing remote session */ |
| debug ("re-waiting at local port %d\n", local_port); |
| local_in = local_out = accept_connection( local_port ); |
| debug ("re-start relaying\n"); |
| goto do_repeater; |
| } |
| closesocket(remote); |
| if ( local_type == LOCAL_SOCKET) |
| closesocket(local_in); |
| #ifdef _WIN32 |
| WSACleanup(); |
| #endif /* _WIN32 */ |
| debug ("that's all, bye.\n"); |
| |
| return 0; |
| } |
| |
| /* ------------------------------------------------------------ |
| Local Variables: |
| compile-command: "cc connect.c -o connect" |
| tab-width: 8 |
| fill-column: 74 |
| comment-column: 48 |
| End: |
| ------------------------------------------------------------ */ |
| |
| /*** end of connect.c ***/ |