| /* |
| * stunnel TLS offloading and load-balancing proxy |
| * Copyright (C) 1998-2015 Michal Trojnara <Michal.Trojnara@mirt.net> |
| * |
| * 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, see <http://www.gnu.org/licenses>. |
| * |
| * Linking stunnel statically or dynamically with other modules is making |
| * a combined work based on stunnel. Thus, the terms and conditions of |
| * the GNU General Public License cover the whole combination. |
| * |
| * In addition, as a special exception, the copyright holder of stunnel |
| * gives you permission to combine stunnel with free software programs or |
| * libraries that are released under the GNU LGPL and with code included |
| * in the standard release of OpenSSL under the OpenSSL License (or |
| * modified versions of such code, with unchanged license). You may copy |
| * and distribute such a system following the terms of the GNU GPL for |
| * stunnel and the licenses of the other code concerned. |
| * |
| * Note that people who make modified versions of stunnel are not obligated |
| * to grant this special exception for their modified versions; it is their |
| * choice whether to do so. The GNU General Public License gives permission |
| * to release a modified version without this exception; this exception |
| * also makes it possible to release a modified version which carries |
| * forward this exception. |
| */ |
| |
| #include "common.h" |
| #include "prototypes.h" |
| |
| #ifndef SHUT_RD |
| #define SHUT_RD 0 |
| #endif |
| #ifndef SHUT_WR |
| #define SHUT_WR 1 |
| #endif |
| #ifndef SHUT_RDWR |
| #define SHUT_RDWR 2 |
| #endif |
| |
| NOEXPORT void client_try(CLI *); |
| NOEXPORT void client_run(CLI *); |
| NOEXPORT void local_start(CLI *); |
| NOEXPORT void remote_start(CLI *); |
| NOEXPORT void ssl_start(CLI *); |
| NOEXPORT void new_chain(CLI *); |
| NOEXPORT void transfer(CLI *); |
| NOEXPORT int parse_socket_error(CLI *, const char *); |
| |
| NOEXPORT void print_cipher(CLI *); |
| NOEXPORT void auth_user(CLI *, char *); |
| NOEXPORT SOCKET connect_local(CLI *); |
| NOEXPORT SOCKET connect_remote(CLI *); |
| NOEXPORT void connect_cache(SSL_SESSION *, SOCKADDR_UNION *); |
| NOEXPORT unsigned connect_index(CLI *); |
| NOEXPORT void setup_connect_addr(CLI *); |
| NOEXPORT void local_bind(CLI *c); |
| NOEXPORT void print_bound_address(CLI *); |
| NOEXPORT void reset(SOCKET, char *); |
| |
| /* allocate local data structure for the new thread */ |
| CLI *alloc_client_session(SERVICE_OPTIONS *opt, SOCKET rfd, SOCKET wfd) { |
| CLI *c; |
| |
| c=str_alloc_detached(sizeof(CLI)); |
| c->opt=opt; |
| c->local_rfd.fd=rfd; |
| c->local_wfd.fd=wfd; |
| c->redirect=REDIRECT_OFF; |
| return c; |
| } |
| |
| void *client_thread(void *arg) { |
| CLI *c=arg; |
| |
| c->tls=NULL; /* do not reuse */ |
| tls_alloc(c, NULL, NULL); |
| #ifdef DEBUG_STACK_SIZE |
| stack_info(1); /* initialize */ |
| #endif |
| client_main(c); |
| #ifdef DEBUG_STACK_SIZE |
| stack_info(0); /* display computed value */ |
| #endif |
| str_stats(); /* client thread allocation tracking */ |
| tls_cleanup(); |
| /* s_log() is not allowed after tls_cleanup() */ |
| #if defined(USE_WIN32) && !defined(_WIN32_WCE) |
| _endthread(); |
| #endif |
| #ifdef USE_UCONTEXT |
| s_poll_wait(NULL, 0, 0); /* wait on poll() */ |
| #endif |
| return NULL; |
| } |
| |
| void client_main(CLI *c) { |
| s_log(LOG_DEBUG, "Service [%s] started", c->opt->servname); |
| if(c->opt->exec_name && c->opt->connect_addr.names) { |
| /* exec+connect options specified together |
| * -> spawn a local program instead of stdio */ |
| for(;;) { |
| SERVICE_OPTIONS *opt=c->opt; |
| memset(c, 0, sizeof(CLI)); /* connect_local needs clean c */ |
| c->opt=opt; |
| if(!setjmp(c->err)) |
| c->local_rfd.fd=c->local_wfd.fd=connect_local(c); |
| else |
| break; |
| client_run(c); |
| if(!c->opt->option.retry) |
| break; |
| sleep(1); /* FIXME: not a good idea in ucontext threading */ |
| s_poll_free(c->fds); |
| c->fds=NULL; |
| str_stats(); /* client thread allocation tracking */ |
| /* c allocation is detached, so it is safe to call str_stats() */ |
| if(service_options.next) /* no tls_cleanup() in inetd mode */ |
| tls_cleanup(); |
| } |
| } else |
| client_run(c); |
| str_free(c); |
| } |
| |
| #ifdef __GNUC__ |
| #pragma GCC diagnostic push |
| #pragma GCC diagnostic ignored "-Wformat" |
| #pragma GCC diagnostic ignored "-Wformat-extra-args" |
| #endif /* __GNUC__ */ |
| NOEXPORT void client_run(CLI *c) { |
| int err, rst; |
| #ifndef USE_FORK |
| long num_clients_copy; |
| #endif |
| |
| #ifndef USE_FORK |
| enter_critical_section(CRIT_CLIENTS); |
| ui_clients(++num_clients); |
| leave_critical_section(CRIT_CLIENTS); |
| #endif |
| |
| /* initialize the client context */ |
| c->remote_fd.fd=INVALID_SOCKET; |
| c->fd=INVALID_SOCKET; |
| c->ssl=NULL; |
| c->sock_bytes=c->ssl_bytes=0; |
| if(c->opt->option.client) { |
| c->sock_rfd=&(c->local_rfd); |
| c->sock_wfd=&(c->local_wfd); |
| c->ssl_rfd=c->ssl_wfd=&(c->remote_fd); |
| } else { |
| c->sock_rfd=c->sock_wfd=&(c->remote_fd); |
| c->ssl_rfd=&(c->local_rfd); |
| c->ssl_wfd=&(c->local_wfd); |
| } |
| c->fds=s_poll_alloc(); |
| addrlist_clear(&c->connect_addr, 0); |
| |
| /* try to process the request */ |
| err=setjmp(c->err); |
| if(!err) |
| client_try(c); |
| rst=err==1 && c->opt->option.reset; |
| s_log(LOG_NOTICE, |
| "Connection %s: %llu byte(s) sent to SSL, %llu byte(s) sent to socket", |
| rst ? "reset" : "closed", |
| (unsigned long long)c->ssl_bytes, (unsigned long long)c->sock_bytes); |
| |
| /* cleanup temporary (e.g. IDENT) socket */ |
| if(c->fd!=INVALID_SOCKET) |
| closesocket(c->fd); |
| c->fd=INVALID_SOCKET; |
| |
| /* cleanup the SSL context */ |
| if(c->ssl) { /* SSL initialized */ |
| SSL_set_shutdown(c->ssl, SSL_SENT_SHUTDOWN|SSL_RECEIVED_SHUTDOWN); |
| SSL_free(c->ssl); |
| c->ssl=NULL; |
| #if OPENSSL_VERSION_NUMBER>=0x10000000L |
| ERR_remove_thread_state(NULL); |
| #else /* OpenSSL version < 1.0.0 */ |
| ERR_remove_state(0); |
| #endif /* OpenSSL version >= 1.0.0 */ |
| } |
| |
| /* cleanup the remote socket */ |
| if(c->remote_fd.fd!=INVALID_SOCKET) { /* remote socket initialized */ |
| if(rst && c->remote_fd.is_socket) /* reset */ |
| reset(c->remote_fd.fd, "linger (remote)"); |
| closesocket(c->remote_fd.fd); |
| s_log(LOG_DEBUG, "Remote socket (FD=%d) closed", c->remote_fd.fd); |
| c->remote_fd.fd=INVALID_SOCKET; |
| } |
| |
| /* cleanup the local socket */ |
| if(c->local_rfd.fd!=INVALID_SOCKET) { /* local socket initialized */ |
| if(c->local_rfd.fd==c->local_wfd.fd) { |
| if(rst && c->local_rfd.is_socket) |
| reset(c->local_rfd.fd, "linger (local)"); |
| closesocket(c->local_rfd.fd); |
| s_log(LOG_DEBUG, "Local socket (FD=%d) closed", c->local_rfd.fd); |
| } else { /* stdin/stdout */ |
| if(rst && c->local_rfd.is_socket) |
| reset(c->local_rfd.fd, "linger (local_rfd)"); |
| if(rst && c->local_wfd.is_socket) |
| reset(c->local_wfd.fd, "linger (local_wfd)"); |
| } |
| c->local_rfd.fd=c->local_wfd.fd=INVALID_SOCKET; |
| } |
| |
| #ifdef USE_FORK |
| /* display child return code if it managed to arrive on time */ |
| /* otherwise it will be retrieved by the init process and ignored */ |
| if(c->opt->exec_name) /* 'exec' specified */ |
| child_status(); /* null SIGCHLD handler was used */ |
| s_log(LOG_DEBUG, "Service [%s] finished", c->opt->servname); |
| #else |
| enter_critical_section(CRIT_CLIENTS); |
| ui_clients(--num_clients); |
| num_clients_copy=num_clients; /* to move s_log() away from CRIT_CLIENTS */ |
| leave_critical_section(CRIT_CLIENTS); |
| s_log(LOG_DEBUG, "Service [%s] finished (%ld left)", |
| c->opt->servname, num_clients_copy); |
| #endif |
| |
| /* free the client context */ |
| str_free(c->connect_addr.addr); |
| s_poll_free(c->fds); |
| c->fds=NULL; |
| } |
| #ifdef __GNUC__ |
| #pragma GCC diagnostic pop |
| #endif /* __GNUC__ */ |
| |
| NOEXPORT void client_try(CLI *c) { |
| local_start(c); |
| protocol(c, c->opt, PROTOCOL_EARLY); |
| if(c->opt->option.connect_before_ssl) { |
| remote_start(c); |
| protocol(c, c->opt, PROTOCOL_MIDDLE); |
| ssl_start(c); |
| } else { |
| ssl_start(c); |
| protocol(c, c->opt, PROTOCOL_MIDDLE); |
| remote_start(c); |
| } |
| protocol(c, c->opt, PROTOCOL_LATE); |
| transfer(c); |
| } |
| |
| NOEXPORT void local_start(CLI *c) { |
| SOCKADDR_UNION addr; |
| socklen_t addr_len; |
| char *accepted_address; |
| |
| /* check if local_rfd is a socket and get peer address */ |
| addr_len=sizeof(SOCKADDR_UNION); |
| c->local_rfd.is_socket=!getpeername(c->local_rfd.fd, &addr.sa, &addr_len); |
| if(c->local_rfd.is_socket) { |
| memcpy(&c->peer_addr.sa, &addr.sa, (size_t)addr_len); |
| c->peer_addr_len=addr_len; |
| if(set_socket_options(c->local_rfd.fd, 1)) |
| s_log(LOG_WARNING, "Failed to set local socket options"); |
| } else { |
| if(get_last_socket_error()!=S_ENOTSOCK) { |
| sockerror("getpeerbyname (local_rfd)"); |
| longjmp(c->err, 1); |
| } |
| } |
| |
| /* check if local_wfd is a socket and get peer address */ |
| if(c->local_rfd.fd==c->local_wfd.fd) { |
| c->local_wfd.is_socket=c->local_rfd.is_socket; |
| } else { |
| addr_len=sizeof(SOCKADDR_UNION); |
| c->local_wfd.is_socket=!getpeername(c->local_wfd.fd, &addr.sa, &addr_len); |
| if(c->local_wfd.is_socket) { |
| if(!c->local_rfd.is_socket) { /* already retrieved */ |
| memcpy(&c->peer_addr.sa, &addr.sa, (size_t)addr_len); |
| c->peer_addr_len=addr_len; |
| } |
| if(set_socket_options(c->local_wfd.fd, 1)) |
| s_log(LOG_WARNING, "Failed to set local socket options"); |
| } else { |
| if(get_last_socket_error()!=S_ENOTSOCK) { |
| sockerror("getpeerbyname (local_wfd)"); |
| longjmp(c->err, 1); |
| } |
| } |
| } |
| |
| /* neither of local descriptors is a socket */ |
| if(!c->local_rfd.is_socket && !c->local_wfd.is_socket) { |
| #ifndef USE_WIN32 |
| if(c->opt->option.transparent_src) { |
| s_log(LOG_ERR, "Transparent source needs a socket"); |
| longjmp(c->err, 1); |
| } |
| #endif |
| s_log(LOG_NOTICE, "Service [%s] accepted connection", c->opt->servname); |
| return; |
| } |
| |
| /* authenticate based on retrieved IP address of the client */ |
| accepted_address=s_ntop(&c->peer_addr, c->peer_addr_len); |
| #ifdef USE_LIBWRAP |
| libwrap_auth(c, accepted_address); |
| #endif /* USE_LIBWRAP */ |
| auth_user(c, accepted_address); |
| s_log(LOG_NOTICE, "Service [%s] accepted connection from %s", |
| c->opt->servname, accepted_address); |
| str_free(accepted_address); |
| } |
| |
| NOEXPORT void remote_start(CLI *c) { |
| /* where to bind connecting socket */ |
| if(c->opt->option.local) /* outgoing interface */ |
| c->bind_addr=&c->opt->source_addr; |
| #ifndef USE_WIN32 |
| else if(c->opt->option.transparent_src) |
| c->bind_addr=&c->peer_addr; |
| #endif |
| else |
| c->bind_addr=NULL; /* don't bind */ |
| |
| /* setup c->remote_fd, now */ |
| if(c->opt->exec_name && !c->opt->connect_addr.names) |
| c->remote_fd.fd=connect_local(c); /* not for exec+connect targets */ |
| else |
| c->remote_fd.fd=connect_remote(c); |
| |
| c->remote_fd.is_socket=1; /* always! */ |
| s_log(LOG_DEBUG, "Remote socket (FD=%d) initialized", c->remote_fd.fd); |
| if(set_socket_options(c->remote_fd.fd, 2)) |
| s_log(LOG_WARNING, "Failed to set remote socket options"); |
| } |
| |
| NOEXPORT void ssl_start(CLI *c) { |
| int i, err; |
| SSL_SESSION *old_session; |
| int unsafe_openssl; |
| X509 *peer_cert; |
| |
| c->ssl=SSL_new(c->opt->ctx); |
| if(!c->ssl) { |
| sslerror("SSL_new"); |
| longjmp(c->err, 1); |
| } |
| SSL_set_ex_data(c->ssl, index_cli, c); /* for callbacks */ |
| if(c->opt->option.client) { |
| #ifndef OPENSSL_NO_TLSEXT |
| if(c->opt->sni) { |
| s_log(LOG_INFO, "SNI: sending servername: %s", c->opt->sni); |
| if(!SSL_set_tlsext_host_name(c->ssl, c->opt->sni)) { |
| sslerror("SSL_set_tlsext_host_name"); |
| longjmp(c->err, 1); |
| } |
| } |
| #endif |
| if(c->opt->session) { |
| enter_critical_section(CRIT_SESSION); |
| SSL_set_session(c->ssl, c->opt->session); |
| leave_critical_section(CRIT_SESSION); |
| } |
| SSL_set_fd(c->ssl, (int)c->remote_fd.fd); |
| SSL_set_connect_state(c->ssl); |
| } else { /* SSL server */ |
| if(c->local_rfd.fd==c->local_wfd.fd) |
| SSL_set_fd(c->ssl, (int)c->local_rfd.fd); |
| else { |
| /* does it make sense to have SSL on STDIN/STDOUT? */ |
| SSL_set_rfd(c->ssl, (int)c->local_rfd.fd); |
| SSL_set_wfd(c->ssl, (int)c->local_wfd.fd); |
| } |
| SSL_set_accept_state(c->ssl); |
| } |
| |
| unsafe_openssl=SSLeay()<0x0090810fL || |
| (SSLeay()>=0x10000000L && SSLeay()<0x1000002fL); |
| while(1) { |
| /* critical section for OpenSSL version < 0.9.8p or 1.x.x < 1.0.0b * |
| * this critical section is a crude workaround for CVE-2010-3864 * |
| * see http://www.securityfocus.com/bid/44884 for details * |
| * alternative solution is to disable internal session caching * |
| * NOTE: this critical section also covers callbacks (e.g. OCSP) */ |
| if(unsafe_openssl) |
| enter_critical_section(CRIT_SSL); |
| |
| if(c->opt->option.client) |
| i=SSL_connect(c->ssl); |
| else |
| i=SSL_accept(c->ssl); |
| |
| if(unsafe_openssl) |
| leave_critical_section(CRIT_SSL); |
| |
| err=SSL_get_error(c->ssl, i); |
| if(err==SSL_ERROR_NONE) |
| break; /* ok -> done */ |
| if(err==SSL_ERROR_WANT_READ || err==SSL_ERROR_WANT_WRITE) { |
| s_poll_init(c->fds); |
| s_poll_add(c->fds, c->ssl_rfd->fd, |
| err==SSL_ERROR_WANT_READ, |
| err==SSL_ERROR_WANT_WRITE); |
| switch(s_poll_wait(c->fds, c->opt->timeout_busy, 0)) { |
| case -1: |
| sockerror("ssl_start: s_poll_wait"); |
| longjmp(c->err, 1); |
| case 0: |
| s_log(LOG_INFO, "ssl_start: s_poll_wait:" |
| " TIMEOUTbusy exceeded: sending reset"); |
| longjmp(c->err, 1); |
| case 1: |
| break; /* OK */ |
| default: |
| s_log(LOG_ERR, "ssl_start: s_poll_wait: unknown result"); |
| longjmp(c->err, 1); |
| } |
| continue; /* ok -> retry */ |
| } |
| if(err==SSL_ERROR_SYSCALL) { |
| switch(get_last_socket_error()) { |
| case S_EINTR: |
| case S_EWOULDBLOCK: |
| #if S_EAGAIN!=S_EWOULDBLOCK |
| case S_EAGAIN: |
| #endif |
| continue; |
| } |
| } |
| if(c->opt->option.client) |
| sslerror("SSL_connect"); |
| else |
| sslerror("SSL_accept"); |
| longjmp(c->err, 1); |
| } |
| s_log(LOG_INFO, "SSL %s: %s", |
| c->opt->option.client ? "connected" : "accepted", |
| SSL_session_reused(c->ssl) ? |
| "previous session reused" : "new session negotiated"); |
| if(SSL_session_reused(c->ssl)) { |
| c->redirect=(uintptr_t)SSL_SESSION_get_ex_data(SSL_get_session(c->ssl), |
| index_redirect); |
| if(c->opt->redirect_addr.names && !c->redirect) { |
| s_log(LOG_ERR, "No application data found in the reused session"); |
| longjmp(c->err, 1); |
| } |
| } else { /* a new session was negotiated */ |
| new_chain(c); |
| peer_cert=SSL_get_peer_certificate(c->ssl); |
| if(peer_cert) /* c->redirect was set by the callback */ |
| X509_free(peer_cert); |
| else if(c->opt->redirect_addr.names) |
| c->redirect=REDIRECT_ON; |
| SSL_SESSION_set_ex_data(SSL_get_session(c->ssl), |
| index_redirect, (void *)c->redirect); |
| if(c->opt->option.client) { |
| enter_critical_section(CRIT_SESSION); |
| old_session=c->opt->session; |
| c->opt->session=SSL_get1_session(c->ssl); /* store it */ |
| leave_critical_section(CRIT_SESSION); |
| if(old_session) |
| SSL_SESSION_free(old_session); /* release the old one */ |
| } else { /* SSL server */ |
| SSL_CTX_add_session(c->opt->ctx, SSL_get_session(c->ssl)); |
| } |
| print_cipher(c); |
| } |
| } |
| |
| NOEXPORT void new_chain(CLI *c) { |
| BIO *bio; |
| int i, len; |
| X509 *peer_cert; |
| STACK_OF(X509) *sk; |
| char *chain; |
| |
| if(c->opt->chain) /* already cached */ |
| return; /* this race condition is safe to ignore */ |
| bio=BIO_new(BIO_s_mem()); |
| if(!bio) |
| return; |
| sk=SSL_get_peer_cert_chain(c->ssl); |
| for(i=0; sk && i<sk_X509_num(sk); i++) { |
| peer_cert=sk_X509_value(sk, i); |
| PEM_write_bio_X509(bio, peer_cert); |
| } |
| if(!sk || !c->opt->option.client) { |
| peer_cert=SSL_get_peer_certificate(c->ssl); |
| if(peer_cert) { |
| PEM_write_bio_X509(bio, peer_cert); |
| X509_free(peer_cert); |
| } |
| } |
| len=BIO_pending(bio); |
| if(len<=0) { |
| s_log(LOG_INFO, "No peer certificate received"); |
| BIO_free(bio); |
| return; |
| } |
| /* prevent automatic deallocation of the cached value */ |
| chain=str_alloc_detached((size_t)len+1); |
| len=BIO_read(bio, chain, len); |
| if(len<0) { |
| s_log(LOG_ERR, "BIO_read failed"); |
| BIO_free(bio); |
| str_free(chain); |
| return; |
| } |
| chain[len]='\0'; |
| BIO_free(bio); |
| c->opt->chain=chain; /* this race condition is safe to ignore */ |
| ui_new_chain(c->opt->section_number); |
| s_log(LOG_DEBUG, "Peer certificate was cached (%d bytes)", len); |
| } |
| |
| /****************************** transfer data */ |
| NOEXPORT void transfer(CLI *c) { |
| int watchdog=0; /* a counter to detect an infinite loop */ |
| ssize_t num; |
| int err; |
| /* logical channels (not file descriptors!) open for read or write */ |
| int sock_open_rd=1, sock_open_wr=1; |
| /* awaited conditions on SSL file descriptors */ |
| int shutdown_wants_read=0, shutdown_wants_write=0; |
| int read_wants_read=0, read_wants_write=0; |
| int write_wants_read=0, write_wants_write=0; |
| /* actual conditions on file descriptors */ |
| int sock_can_rd, sock_can_wr, ssl_can_rd, ssl_can_wr; |
| #ifdef USE_WIN32 |
| unsigned long bytes; |
| #else |
| int bytes; |
| #endif |
| |
| c->sock_ptr=c->ssl_ptr=0; |
| |
| do { /* main loop of client data transfer */ |
| /****************************** initialize *_wants_* */ |
| read_wants_read|=!(SSL_get_shutdown(c->ssl)&SSL_RECEIVED_SHUTDOWN) |
| && c->ssl_ptr<BUFFSIZE && !read_wants_write; |
| write_wants_write|=!(SSL_get_shutdown(c->ssl)&SSL_SENT_SHUTDOWN) |
| && c->sock_ptr && !write_wants_read; |
| |
| /****************************** setup c->fds structure */ |
| s_poll_init(c->fds); /* initialize the structure */ |
| /* for plain socket open data strem = open file descriptor */ |
| /* make sure to add each open socket to receive exceptions! */ |
| if(sock_open_rd) /* only poll if the read file descriptor is open */ |
| s_poll_add(c->fds, c->sock_rfd->fd, c->sock_ptr<BUFFSIZE, 0); |
| if(sock_open_wr) /* only poll if the write file descriptor is open */ |
| s_poll_add(c->fds, c->sock_wfd->fd, 0, c->ssl_ptr>0); |
| /* poll SSL file descriptors unless SSL shutdown was completed */ |
| if(SSL_get_shutdown(c->ssl)!= |
| (SSL_SENT_SHUTDOWN|SSL_RECEIVED_SHUTDOWN)) { |
| s_poll_add(c->fds, c->ssl_rfd->fd, |
| read_wants_read || write_wants_read || shutdown_wants_read, 0); |
| s_poll_add(c->fds, c->ssl_wfd->fd, 0, |
| read_wants_write || write_wants_write || shutdown_wants_write); |
| } |
| |
| /****************************** wait for an event */ |
| err=s_poll_wait(c->fds, |
| (sock_open_rd && /* both peers open */ |
| !(SSL_get_shutdown(c->ssl)&SSL_RECEIVED_SHUTDOWN)) || |
| c->ssl_ptr /* data buffered to write to socket */ || |
| c->sock_ptr /* data buffered to write to SSL */ ? |
| c->opt->timeout_idle : c->opt->timeout_close, 0); |
| switch(err) { |
| case -1: |
| sockerror("transfer: s_poll_wait"); |
| longjmp(c->err, 1); |
| case 0: /* timeout */ |
| if((sock_open_rd && |
| !(SSL_get_shutdown(c->ssl)&SSL_RECEIVED_SHUTDOWN)) || |
| c->ssl_ptr || c->sock_ptr) { |
| s_log(LOG_INFO, "transfer: s_poll_wait:" |
| " TIMEOUTidle exceeded: sending reset"); |
| longjmp(c->err, 1); |
| } else { /* already closing connection */ |
| s_log(LOG_ERR, "transfer: s_poll_wait:" |
| " TIMEOUTclose exceeded: closing"); |
| return; /* OK */ |
| } |
| } |
| |
| /****************************** retrieve results from c->fds */ |
| sock_can_rd=s_poll_canread(c->fds, c->sock_rfd->fd); |
| sock_can_wr=s_poll_canwrite(c->fds, c->sock_wfd->fd); |
| ssl_can_rd=s_poll_canread(c->fds, c->ssl_rfd->fd); |
| ssl_can_wr=s_poll_canwrite(c->fds, c->ssl_wfd->fd); |
| |
| /****************************** checks for internal failures */ |
| /* please report any internal errors to stunnel-users mailing list */ |
| if(!(sock_can_rd || sock_can_wr || ssl_can_rd || ssl_can_wr)) { |
| s_log(LOG_ERR, "INTERNAL ERROR: " |
| "s_poll_wait returned %d, but no descriptor is ready", err); |
| s_poll_dump(c->fds, LOG_ERR); |
| longjmp(c->err, 1); |
| } |
| |
| if(c->reneg_state==RENEG_DETECTED && !c->opt->option.renegotiation) { |
| s_log(LOG_ERR, "Aborting due to renegotiation request"); |
| longjmp(c->err, 1); |
| } |
| |
| /****************************** send SSL close_notify alert */ |
| if(shutdown_wants_read || shutdown_wants_write) { |
| num=SSL_shutdown(c->ssl); /* send close_notify alert */ |
| if(num<0) /* -1 - not completed */ |
| err=SSL_get_error(c->ssl, (int)num); |
| else /* 0 or 1 - success */ |
| err=SSL_ERROR_NONE; |
| switch(err) { |
| case SSL_ERROR_NONE: /* the shutdown was successfully completed */ |
| s_log(LOG_INFO, "SSL_shutdown successfully sent close_notify alert"); |
| shutdown_wants_read=shutdown_wants_write=0; |
| break; |
| case SSL_ERROR_SYSCALL: /* socket error */ |
| if(parse_socket_error(c, "SSL_shutdown")) |
| break; /* a non-critical error: retry */ |
| SSL_set_shutdown(c->ssl, SSL_SENT_SHUTDOWN|SSL_RECEIVED_SHUTDOWN); |
| shutdown_wants_read=shutdown_wants_write=0; |
| break; |
| case SSL_ERROR_WANT_WRITE: |
| s_log(LOG_DEBUG, "SSL_shutdown returned WANT_WRITE: retrying"); |
| shutdown_wants_read=0; |
| shutdown_wants_write=1; |
| break; |
| case SSL_ERROR_WANT_READ: |
| s_log(LOG_DEBUG, "SSL_shutdown returned WANT_READ: retrying"); |
| shutdown_wants_read=1; |
| shutdown_wants_write=0; |
| break; |
| case SSL_ERROR_SSL: /* SSL error */ |
| sslerror("SSL_shutdown"); |
| longjmp(c->err, 1); |
| default: |
| s_log(LOG_ERR, "SSL_shutdown/SSL_get_error returned %d", err); |
| longjmp(c->err, 1); |
| } |
| } |
| |
| /****************************** write to socket */ |
| if(sock_open_wr && sock_can_wr) { |
| num=writesocket(c->sock_wfd->fd, c->ssl_buff, c->ssl_ptr); |
| switch(num) { |
| case -1: /* error */ |
| if(parse_socket_error(c, "writesocket")) |
| break; /* a non-critical error: retry */ |
| sock_open_rd=sock_open_wr=0; |
| break; |
| default: |
| memmove(c->ssl_buff, c->ssl_buff+num, c->ssl_ptr-(size_t)num); |
| c->ssl_ptr-=(size_t)num; |
| memset(c->ssl_buff+c->ssl_ptr, 0, (size_t)num); /* paranoia */ |
| c->sock_bytes+=(size_t)num; |
| watchdog=0; /* reset watchdog */ |
| } |
| } |
| |
| /****************************** read from socket */ |
| if(sock_open_rd && sock_can_rd) { |
| num=readsocket(c->sock_rfd->fd, |
| c->sock_buff+c->sock_ptr, BUFFSIZE-c->sock_ptr); |
| switch(num) { |
| case -1: |
| if(parse_socket_error(c, "readsocket")) |
| break; /* a non-critical error: retry */ |
| sock_open_rd=sock_open_wr=0; |
| break; |
| case 0: /* close */ |
| s_log(LOG_INFO, "Read socket closed (readsocket)"); |
| sock_open_rd=0; |
| break; |
| default: |
| c->sock_ptr+=(size_t)num; |
| watchdog=0; /* reset watchdog */ |
| } |
| } |
| |
| /****************************** update *_wants_* based on new *_ptr */ |
| /* this update is also required for SSL_pending() to be used */ |
| read_wants_read|=!(SSL_get_shutdown(c->ssl)&SSL_RECEIVED_SHUTDOWN) |
| && c->ssl_ptr<BUFFSIZE && !read_wants_write; |
| write_wants_write|=!(SSL_get_shutdown(c->ssl)&SSL_SENT_SHUTDOWN) |
| && c->sock_ptr && !write_wants_read; |
| |
| /****************************** write to SSL */ |
| if((write_wants_read && ssl_can_rd) || |
| (write_wants_write && ssl_can_wr)) { |
| write_wants_read=0; |
| write_wants_write=0; |
| num=SSL_write(c->ssl, c->sock_buff, (int)(c->sock_ptr)); |
| switch(err=SSL_get_error(c->ssl, (int)num)) { |
| case SSL_ERROR_NONE: |
| if(num==0) |
| s_log(LOG_DEBUG, "SSL_write returned 0"); |
| memmove(c->sock_buff, c->sock_buff+num, |
| c->sock_ptr-(size_t)num); |
| c->sock_ptr-=(size_t)num; |
| memset(c->sock_buff+c->sock_ptr, 0, (size_t)num); /* paranoia */ |
| c->ssl_bytes+=(size_t)num; |
| watchdog=0; /* reset watchdog */ |
| break; |
| case SSL_ERROR_WANT_WRITE: /* buffered data? */ |
| s_log(LOG_DEBUG, "SSL_write returned WANT_WRITE: retrying"); |
| write_wants_write=1; |
| break; |
| case SSL_ERROR_WANT_READ: |
| s_log(LOG_DEBUG, "SSL_write returned WANT_READ: retrying"); |
| write_wants_read=1; |
| break; |
| case SSL_ERROR_WANT_X509_LOOKUP: |
| s_log(LOG_DEBUG, |
| "SSL_write returned WANT_X509_LOOKUP: retrying"); |
| break; |
| case SSL_ERROR_SYSCALL: /* socket error */ |
| if(num && parse_socket_error(c, "SSL_write")) |
| break; /* a non-critical error: retry */ |
| /* EOF -> buggy (e.g. Microsoft) peer: |
| * SSL socket closed without close_notify alert */ |
| if(c->sock_ptr) { /* TODO: what about buffered data? */ |
| s_log(LOG_ERR, |
| "SSL socket closed (SSL_write) with %ld unsent byte(s)", |
| c->sock_ptr); |
| longjmp(c->err, 1); /* reset the socket */ |
| } |
| s_log(LOG_INFO, "SSL socket closed (SSL_write)"); |
| SSL_set_shutdown(c->ssl, SSL_SENT_SHUTDOWN|SSL_RECEIVED_SHUTDOWN); |
| break; |
| case SSL_ERROR_ZERO_RETURN: /* close_notify alert received */ |
| s_log(LOG_INFO, "SSL closed (SSL_write)"); |
| if(SSL_version(c->ssl)==SSL2_VERSION) |
| SSL_set_shutdown(c->ssl, SSL_SENT_SHUTDOWN|SSL_RECEIVED_SHUTDOWN); |
| break; |
| case SSL_ERROR_SSL: |
| sslerror("SSL_write"); |
| longjmp(c->err, 1); |
| default: |
| s_log(LOG_ERR, "SSL_write/SSL_get_error returned %d", err); |
| longjmp(c->err, 1); |
| } |
| } |
| |
| /****************************** read from SSL */ |
| if((read_wants_read && (ssl_can_rd || SSL_pending(c->ssl))) || |
| /* it may be possible to read some pending data after |
| * writesocket() above made some room in c->ssl_buff */ |
| (read_wants_write && ssl_can_wr)) { |
| read_wants_read=0; |
| read_wants_write=0; |
| num=SSL_read(c->ssl, c->ssl_buff+c->ssl_ptr, (int)(BUFFSIZE-c->ssl_ptr)); |
| switch(err=SSL_get_error(c->ssl, (int)num)) { |
| case SSL_ERROR_NONE: |
| if(num==0) |
| s_log(LOG_DEBUG, "SSL_read returned 0"); |
| c->ssl_ptr+=(size_t)num; |
| watchdog=0; /* reset watchdog */ |
| break; |
| case SSL_ERROR_WANT_WRITE: |
| s_log(LOG_DEBUG, "SSL_read returned WANT_WRITE: retrying"); |
| read_wants_write=1; |
| break; |
| case SSL_ERROR_WANT_READ: /* happens quite often */ |
| #if 0 |
| s_log(LOG_DEBUG, "SSL_read returned WANT_READ: retrying"); |
| #endif |
| read_wants_read=1; |
| break; |
| case SSL_ERROR_WANT_X509_LOOKUP: |
| s_log(LOG_DEBUG, |
| "SSL_read returned WANT_X509_LOOKUP: retrying"); |
| break; |
| case SSL_ERROR_SYSCALL: |
| if(num && parse_socket_error(c, "SSL_read")) |
| break; /* a non-critical error: retry */ |
| /* EOF -> buggy (e.g. Microsoft) peer: |
| * SSL socket closed without close_notify alert */ |
| if(c->sock_ptr || write_wants_write) { |
| s_log(LOG_ERR, |
| "SSL socket closed (SSL_read) with %ld unsent byte(s)", |
| c->sock_ptr); |
| longjmp(c->err, 1); /* reset the socket */ |
| } |
| s_log(LOG_INFO, "SSL socket closed (SSL_read)"); |
| SSL_set_shutdown(c->ssl, |
| SSL_SENT_SHUTDOWN|SSL_RECEIVED_SHUTDOWN); |
| break; |
| case SSL_ERROR_ZERO_RETURN: /* close_notify alert received */ |
| s_log(LOG_INFO, "SSL closed (SSL_read)"); |
| if(SSL_version(c->ssl)==SSL2_VERSION) |
| SSL_set_shutdown(c->ssl, |
| SSL_SENT_SHUTDOWN|SSL_RECEIVED_SHUTDOWN); |
| break; |
| case SSL_ERROR_SSL: |
| sslerror("SSL_read"); |
| longjmp(c->err, 1); |
| default: |
| s_log(LOG_ERR, "SSL_read/SSL_get_error returned %d", err); |
| longjmp(c->err, 1); |
| } |
| } |
| |
| /****************************** check for hangup conditions */ |
| /* http://marc.info/?l=linux-man&m=128002066306087 */ |
| /* readsocket() must be the last sock_rfd operation before FIONREAD */ |
| if(sock_open_rd && s_poll_rdhup(c->fds, c->sock_rfd->fd) && |
| (ioctlsocket(c->sock_rfd->fd, FIONREAD, &bytes) || !bytes)) { |
| s_log(LOG_INFO, "Read socket closed (read hangup)"); |
| sock_open_rd=0; |
| } |
| if(sock_open_wr && s_poll_hup(c->fds, c->sock_wfd->fd)) { |
| if(c->ssl_ptr) { |
| s_log(LOG_ERR, |
| "Write socket closed (write hangup) with %ld unsent byte(s)", |
| c->ssl_ptr); |
| longjmp(c->err, 1); /* reset the socket */ |
| } |
| s_log(LOG_INFO, "Write socket closed (write hangup)"); |
| sock_open_wr=0; |
| } |
| /* SSL_read() must be the last ssl_rfd operation before FIONREAD */ |
| if(!(SSL_get_shutdown(c->ssl)&SSL_RECEIVED_SHUTDOWN) && |
| s_poll_rdhup(c->fds, c->ssl_rfd->fd) && |
| (ioctlsocket(c->ssl_rfd->fd, FIONREAD, &bytes) || !bytes)) { |
| /* hangup -> buggy (e.g. Microsoft) peer: |
| * SSL socket closed without close_notify alert */ |
| s_log(LOG_INFO, "SSL socket closed (read hangup)"); |
| SSL_set_shutdown(c->ssl, |
| SSL_get_shutdown(c->ssl)|SSL_RECEIVED_SHUTDOWN); |
| } |
| if(!(SSL_get_shutdown(c->ssl)&SSL_SENT_SHUTDOWN) && |
| s_poll_hup(c->fds, c->ssl_wfd->fd)) { |
| if(c->sock_ptr || write_wants_write) { |
| s_log(LOG_ERR, |
| "SSL socket closed (write hangup) with %ld unsent byte(s)", |
| c->sock_ptr); |
| longjmp(c->err, 1); /* reset the socket */ |
| } |
| s_log(LOG_INFO, "SSL socket closed (write hangup)"); |
| SSL_set_shutdown(c->ssl, |
| SSL_get_shutdown(c->ssl)|SSL_SENT_SHUTDOWN); |
| } |
| |
| /****************************** check write shutdown conditions */ |
| if(sock_open_wr && SSL_get_shutdown(c->ssl)&SSL_RECEIVED_SHUTDOWN && |
| !c->ssl_ptr) { |
| sock_open_wr=0; /* no further write allowed */ |
| if(!c->sock_wfd->is_socket) { |
| s_log(LOG_DEBUG, "Closing the file descriptor"); |
| sock_open_rd=0; /* file descriptor is ready to be closed */ |
| } else if(!shutdown(c->sock_wfd->fd, SHUT_WR)) { /* send TCP FIN */ |
| s_log(LOG_DEBUG, "Sent socket write shutdown"); |
| } else { |
| s_log(LOG_DEBUG, "Failed to send socket write shutdown"); |
| sock_open_rd=0; /* file descriptor is ready to be closed */ |
| } |
| } |
| if(!(SSL_get_shutdown(c->ssl)&SSL_SENT_SHUTDOWN) && !sock_open_rd && |
| !c->sock_ptr && !write_wants_write) { |
| if(SSL_version(c->ssl)!=SSL2_VERSION) { |
| s_log(LOG_DEBUG, "Sending close_notify alert"); |
| shutdown_wants_write=1; |
| } else { /* no alerts in SSLv2, including the close_notify alert */ |
| s_log(LOG_DEBUG, "Closing SSLv2 socket"); |
| if(c->ssl_rfd->is_socket) |
| shutdown(c->ssl_rfd->fd, SHUT_RD); /* notify the kernel */ |
| if(c->ssl_wfd->is_socket) |
| shutdown(c->ssl_wfd->fd, SHUT_WR); /* send TCP FIN */ |
| /* notify the OpenSSL library */ |
| SSL_set_shutdown(c->ssl, SSL_SENT_SHUTDOWN|SSL_RECEIVED_SHUTDOWN); |
| } |
| } |
| |
| /****************************** check watchdog */ |
| if(++watchdog>100) { /* loop executes without transferring any data */ |
| s_log(LOG_ERR, |
| "transfer() loop executes not transferring any data"); |
| s_log(LOG_ERR, |
| "please report the problem to Michal.Trojnara@mirt.net"); |
| stunnel_info(LOG_ERR); |
| s_log(LOG_ERR, "protocol=%s, SSL_pending=%d", |
| SSL_get_version(c->ssl), SSL_pending(c->ssl)); |
| s_log(LOG_ERR, "sock_open_rd=%s, sock_open_wr=%s", |
| sock_open_rd ? "Y" : "n", sock_open_wr ? "Y" : "n"); |
| s_log(LOG_ERR, "SSL_RECEIVED_SHUTDOWN=%s, SSL_SENT_SHUTDOWN=%s", |
| SSL_get_shutdown(c->ssl)&SSL_RECEIVED_SHUTDOWN ? "Y" : "n", |
| SSL_get_shutdown(c->ssl)&SSL_SENT_SHUTDOWN ? "Y" : "n"); |
| s_log(LOG_ERR, "sock_can_rd=%s, sock_can_wr=%s", |
| sock_can_rd ? "Y" : "n", sock_can_wr ? "Y" : "n"); |
| s_log(LOG_ERR, "ssl_can_rd=%s, ssl_can_wr=%s", |
| ssl_can_rd ? "Y" : "n", ssl_can_wr ? "Y" : "n"); |
| s_log(LOG_ERR, "read_wants_read=%s, read_wants_write=%s", |
| read_wants_read ? "Y" : "n", read_wants_write ? "Y" : "n"); |
| s_log(LOG_ERR, "write_wants_read=%s, write_wants_write=%s", |
| write_wants_read ? "Y" : "n", write_wants_write ? "Y" : "n"); |
| s_log(LOG_ERR, "shutdown_wants_read=%s, shutdown_wants_write=%s", |
| shutdown_wants_read ? "Y" : "n", |
| shutdown_wants_write ? "Y" : "n"); |
| s_log(LOG_ERR, "socket input buffer: %ld byte(s), " |
| "ssl input buffer: %ld byte(s)", c->sock_ptr, c->ssl_ptr); |
| longjmp(c->err, 1); |
| } |
| |
| } while(sock_open_wr || !(SSL_get_shutdown(c->ssl)&SSL_SENT_SHUTDOWN) || |
| shutdown_wants_read || shutdown_wants_write); |
| } |
| |
| /* returns 0 on close and 1 on non-critical errors */ |
| NOEXPORT int parse_socket_error(CLI *c, const char *text) { |
| switch(get_last_socket_error()) { |
| /* http://tangentsoft.net/wskfaq/articles/bsd-compatibility.html */ |
| case 0: /* close on read, or close on write on WIN32 */ |
| #ifndef USE_WIN32 |
| case EPIPE: /* close on write on Unix */ |
| #endif |
| case S_ECONNABORTED: |
| s_log(LOG_INFO, "%s: Socket is closed", text); |
| return 0; |
| case S_EINTR: |
| s_log(LOG_DEBUG, "%s: Interrupted by a signal: retrying", text); |
| return 1; |
| case S_EWOULDBLOCK: |
| s_log(LOG_NOTICE, "%s: Would block: retrying", text); |
| sleep(1); /* Microsoft bug KB177346 */ |
| return 1; |
| #if S_EAGAIN!=S_EWOULDBLOCK |
| case S_EAGAIN: |
| s_log(LOG_DEBUG, |
| "%s: Temporary lack of resources: retrying", text); |
| return 1; |
| #endif |
| #ifdef USE_WIN32 |
| case S_ECONNRESET: |
| /* dying "exec" processes on Win32 cause reset instead of close */ |
| if(c->opt->exec_name) { |
| s_log(LOG_INFO, "%s: Socket is closed (exec)", text); |
| return 0; |
| } |
| #endif |
| default: |
| sockerror(text); |
| longjmp(c->err, 1); |
| return -1; /* some C compilers require a return value */ |
| } |
| } |
| |
| NOEXPORT void print_cipher(CLI *c) { /* print negotiated cipher */ |
| SSL_CIPHER *cipher; |
| #ifndef OPENSSL_NO_COMP |
| const COMP_METHOD *compression, *expansion; |
| #endif |
| |
| if(c->opt->log_level<LOG_INFO) /* performance optimization */ |
| return; |
| cipher=(SSL_CIPHER *)SSL_get_current_cipher(c->ssl); |
| s_log(LOG_INFO, "Negotiated %s ciphersuite %s (%d-bit encryption)", |
| SSL_get_version(c->ssl), SSL_CIPHER_get_name(cipher), |
| SSL_CIPHER_get_bits(cipher, NULL)); |
| |
| #ifndef OPENSSL_NO_COMP |
| compression=SSL_get_current_compression(c->ssl); |
| expansion=SSL_get_current_expansion(c->ssl); |
| s_log(compression||expansion ? LOG_INFO : LOG_DEBUG, |
| "Compression: %s, expansion: %s", |
| compression ? SSL_COMP_get_name(compression) : "null", |
| expansion ? SSL_COMP_get_name(expansion) : "null"); |
| #endif |
| } |
| |
| NOEXPORT void auth_user(CLI *c, char *accepted_address) { |
| #ifndef _WIN32_WCE |
| struct servent *s_ent; /* structure for getservbyname */ |
| #endif |
| SOCKADDR_UNION ident; /* IDENT socket name */ |
| char *line, *type, *system, *user; |
| |
| if(!c->opt->username) |
| return; /* -u option not specified */ |
| #ifdef HAVE_STRUCT_SOCKADDR_UN |
| if(c->peer_addr.sa.sa_family==AF_UNIX) { |
| s_log(LOG_INFO, "IDENT not supported on Unix sockets"); |
| return; |
| } |
| #endif |
| c->fd=s_socket(c->peer_addr.sa.sa_family, SOCK_STREAM, |
| 0, 1, "socket (auth_user)"); |
| if(c->fd==INVALID_SOCKET) |
| longjmp(c->err, 1); |
| memcpy(&ident, &c->peer_addr, (size_t)c->peer_addr_len); |
| #ifndef _WIN32_WCE |
| s_ent=getservbyname("auth", "tcp"); |
| if(s_ent) { |
| ident.in.sin_port=(u_short)s_ent->s_port; |
| } else |
| #endif |
| { |
| s_log(LOG_WARNING, "Unknown service 'auth': using default 113"); |
| ident.in.sin_port=htons(113); |
| } |
| if(s_connect(c, &ident, addr_len(&ident))) |
| longjmp(c->err, 1); |
| s_log(LOG_DEBUG, "IDENT server connected"); |
| fd_printf(c, c->fd, "%u , %u", |
| ntohs(c->peer_addr.in.sin_port), |
| ntohs(c->opt->local_addr.in.sin_port)); |
| line=fd_getline(c, c->fd); |
| closesocket(c->fd); |
| c->fd=INVALID_SOCKET; /* avoid double close on cleanup */ |
| type=strchr(line, ':'); |
| if(!type) { |
| s_log(LOG_ERR, "Malformed IDENT response"); |
| str_free(line); |
| longjmp(c->err, 1); |
| } |
| *type++='\0'; |
| system=strchr(type, ':'); |
| if(!system) { |
| s_log(LOG_ERR, "Malformed IDENT response"); |
| str_free(line); |
| longjmp(c->err, 1); |
| } |
| *system++='\0'; |
| if(strcmp(type, " USERID ")) { |
| s_log(LOG_ERR, "Incorrect INETD response type"); |
| str_free(line); |
| longjmp(c->err, 1); |
| } |
| user=strchr(system, ':'); |
| if(!user) { |
| s_log(LOG_ERR, "Malformed IDENT response"); |
| str_free(line); |
| longjmp(c->err, 1); |
| } |
| *user++='\0'; |
| while(*user==' ') /* skip leading spaces */ |
| ++user; |
| if(strcmp(user, c->opt->username)) { |
| s_log(LOG_WARNING, "Connection from %s REFUSED by IDENT (user \"%s\")", |
| accepted_address, user); |
| str_free(line); |
| longjmp(c->err, 1); |
| } |
| s_log(LOG_INFO, "IDENT authentication passed"); |
| str_free(line); |
| } |
| |
| #if defined(_WIN32_WCE) || defined(__vms) |
| |
| NOEXPORT int connect_local(CLI *c) { /* spawn local process */ |
| s_log(LOG_ERR, "Local mode is not supported on this platform"); |
| longjmp(c->err, 1); |
| return -1; /* some C compilers require a return value */ |
| } |
| |
| #elif defined(USE_WIN32) |
| |
| NOEXPORT SOCKET connect_local(CLI *c) { /* spawn local process */ |
| SOCKET fd[2]; |
| STARTUPINFO si; |
| PROCESS_INFORMATION pi; |
| LPTSTR name, args; |
| |
| if(make_sockets(fd)) |
| longjmp(c->err, 1); |
| memset(&si, 0, sizeof si); |
| si.cb=sizeof si; |
| si.dwFlags=STARTF_USESHOWWINDOW|STARTF_USESTDHANDLES; |
| si.wShowWindow=SW_HIDE; |
| si.hStdInput=si.hStdOutput=si.hStdError=(HANDLE)fd[1]; |
| memset(&pi, 0, sizeof pi); |
| |
| name=str2tstr(c->opt->exec_name); |
| args=str2tstr(c->opt->exec_args); |
| CreateProcess(name, args, NULL, NULL, TRUE, 0, NULL, NULL, &si, &pi); |
| str_free(name); |
| str_free(args); |
| |
| closesocket(fd[1]); |
| CloseHandle(pi.hProcess); |
| CloseHandle(pi.hThread); |
| return fd[0]; |
| } |
| |
| #else /* standard Unix version */ |
| |
| NOEXPORT SOCKET connect_local(CLI *c) { /* spawn local process */ |
| char *name, host[40], port[6]; |
| int fd[2], pid; |
| X509 *peer_cert; |
| #ifdef HAVE_PTHREAD_SIGMASK |
| sigset_t newmask; |
| #endif |
| |
| if(c->opt->option.pty) { |
| char tty[64]; |
| |
| if(pty_allocate(fd, fd+1, tty)) |
| longjmp(c->err, 1); |
| s_log(LOG_DEBUG, "TTY=%s allocated", tty); |
| } else |
| if(make_sockets(fd)) |
| longjmp(c->err, 1); |
| |
| pid=fork(); |
| c->pid=(unsigned long)pid; |
| switch(pid) { |
| case -1: /* error */ |
| closesocket(fd[0]); |
| closesocket(fd[1]); |
| ioerror("fork"); |
| longjmp(c->err, 1); |
| case 0: /* child */ |
| tls_alloc(NULL, c->tls, NULL); /* reuse thread-local storage */ |
| closesocket(fd[0]); |
| set_nonblock(fd[1], 0); /* switch back to blocking mode */ |
| /* dup2() does not copy FD_CLOEXEC flag */ |
| dup2(fd[1], 0); |
| dup2(fd[1], 1); |
| if(!global_options.option.foreground) |
| dup2(fd[1], 2); |
| closesocket(fd[1]); /* not really needed due to FD_CLOEXEC */ |
| |
| if(!getnameinfo(&c->peer_addr.sa, c->peer_addr_len, |
| host, 40, port, 6, NI_NUMERICHOST|NI_NUMERICSERV)) { |
| /* just don't set these variables if getnameinfo() fails */ |
| putenv(str_printf("REMOTE_HOST=%s", host)); |
| putenv(str_printf("REMOTE_PORT=%s", port)); |
| if(c->opt->option.transparent_src) { |
| #ifndef LIBDIR |
| #define LIBDIR "." |
| #endif |
| #ifdef MACH64 |
| putenv("LD_PRELOAD_32=" LIBDIR "/libstunnel.so"); |
| putenv("LD_PRELOAD_64=" LIBDIR "/" MACH64 "/libstunnel.so"); |
| #elif __osf /* for Tru64 _RLD_LIST is used instead */ |
| putenv("_RLD_LIST=" LIBDIR "/libstunnel.so:DEFAULT"); |
| #else |
| putenv("LD_PRELOAD=" LIBDIR "/libstunnel.so"); |
| #endif |
| } |
| } |
| |
| if(c->ssl) { |
| peer_cert=SSL_get_peer_certificate(c->ssl); |
| if(peer_cert) { |
| name=X509_NAME2text(X509_get_subject_name(peer_cert)); |
| putenv(str_printf("SSL_CLIENT_DN=%s", name)); |
| name=X509_NAME2text(X509_get_issuer_name(peer_cert)); |
| putenv(str_printf("SSL_CLIENT_I_DN=%s", name)); |
| X509_free(peer_cert); |
| } |
| } |
| #ifdef HAVE_PTHREAD_SIGMASK |
| sigemptyset(&newmask); |
| sigprocmask(SIG_SETMASK, &newmask, NULL); |
| #endif |
| signal(SIGCHLD, SIG_DFL); |
| signal(SIGHUP, SIG_DFL); |
| signal(SIGUSR1, SIG_DFL); |
| signal(SIGPIPE, SIG_DFL); |
| signal(SIGTERM, SIG_DFL); |
| signal(SIGQUIT, SIG_DFL); |
| signal(SIGINT, SIG_DFL); |
| execvp(c->opt->exec_name, c->opt->exec_args); |
| ioerror(c->opt->exec_name); /* execvp failed */ |
| _exit(1); |
| default: /* parent */ |
| s_log(LOG_INFO, "Local mode child started (PID=%lu)", c->pid); |
| closesocket(fd[1]); |
| return fd[0]; |
| } |
| } |
| |
| #endif /* not USE_WIN32 or __vms */ |
| |
| /* connect remote host */ |
| NOEXPORT SOCKET connect_remote(CLI *c) { |
| SOCKET fd; |
| unsigned ind_start, ind_try, ind_cur; |
| |
| setup_connect_addr(c); |
| switch(c->connect_addr.num) { |
| case 0: |
| s_log(LOG_ERR, "No remote host resolved"); |
| longjmp(c->err, 1); |
| case 1: |
| ind_start=0; |
| break; |
| default: |
| ind_start=connect_index(c); |
| } |
| |
| /* try to connect each host from the list */ |
| for(ind_try=0; ind_try<c->connect_addr.num; ind_try++) { |
| ind_cur=(ind_start+ind_try)%c->connect_addr.num; |
| c->fd=s_socket(c->connect_addr.addr[ind_cur].sa.sa_family, |
| SOCK_STREAM, 0, 1, "remote socket"); |
| if(c->fd==INVALID_SOCKET) |
| longjmp(c->err, 1); |
| |
| local_bind(c); /* explicit local bind or transparent proxy */ |
| |
| if(s_connect(c, &c->connect_addr.addr[ind_cur], |
| addr_len(&c->connect_addr.addr[ind_cur]))) { |
| closesocket(c->fd); |
| c->fd=INVALID_SOCKET; |
| continue; /* next IP */ |
| } |
| if(c->ssl) |
| connect_cache(SSL_get_session(c->ssl), |
| &c->connect_addr.addr[ind_cur]); |
| print_bound_address(c); |
| fd=c->fd; |
| c->fd=INVALID_SOCKET; |
| return fd; /* success! */ |
| } |
| longjmp(c->err, 1); |
| return INVALID_SOCKET; /* some C compilers require a return value */ |
| } |
| |
| NOEXPORT void connect_cache(SSL_SESSION *sess, SOCKADDR_UNION *cur_addr) { |
| SOCKADDR_UNION *old_addr, *new_addr; |
| socklen_t len; |
| char *addr_txt; |
| |
| /* make a copy of the address, so it may work with delayed resolver */ |
| len=addr_len(cur_addr); |
| new_addr=str_alloc_detached((size_t)len); |
| memcpy(new_addr, cur_addr, (size_t)len); |
| |
| addr_txt=s_ntop(cur_addr, len); |
| s_log(LOG_INFO, "persistence: %s cached", addr_txt); |
| str_free(addr_txt); |
| |
| enter_critical_section(CRIT_ADDR); |
| old_addr=SSL_SESSION_get_ex_data(sess, index_addr); |
| SSL_SESSION_set_ex_data(sess, index_addr, new_addr); |
| leave_critical_section(CRIT_ADDR); |
| str_free(old_addr); /* NULL pointers are ignored */ |
| } |
| |
| NOEXPORT unsigned connect_index(CLI *c) { |
| unsigned i; |
| SOCKADDR_UNION addr, *ptr; |
| socklen_t len; |
| char *addr_txt; |
| |
| if(c->ssl && SSL_session_reused(c->ssl)) { |
| enter_critical_section(CRIT_ADDR); |
| ptr=SSL_SESSION_get_ex_data(SSL_get_session(c->ssl), index_addr); |
| if(ptr) { |
| len=addr_len(ptr); |
| memcpy(&addr, ptr, (size_t)len); |
| leave_critical_section(CRIT_ADDR); |
| /* address was copied, ptr itself is no longer valid */ |
| for(i=0; i<c->connect_addr.num; ++i) { |
| if(addr_len(&c->connect_addr.addr[i])==len && |
| !memcmp(&c->connect_addr.addr[i], |
| &addr, (size_t)len)) { |
| addr_txt=s_ntop(&addr, len); |
| s_log(LOG_INFO, "persistence: %s reused", addr_txt); |
| str_free(addr_txt); |
| return i; |
| } |
| } |
| addr_txt=s_ntop(&addr, len); |
| s_log(LOG_INFO, "persistence: %s not available", addr_txt); |
| str_free(addr_txt); |
| } else { |
| leave_critical_section(CRIT_ADDR); |
| s_log(LOG_NOTICE, "persistence: No cached address found"); |
| } |
| } |
| |
| if(c->opt->failover==FAILOVER_RR) { |
| /* the race condition here can be safely ignored */ |
| i=*c->connect_addr.rr_ptr; |
| *c->connect_addr.rr_ptr=(i+1)%c->connect_addr.num; |
| s_log(LOG_INFO, "failover: round-robin, starting at entry #%d", i); |
| } else { |
| i=0; |
| s_log(LOG_INFO, "failover: priority, starting at entry #0"); |
| } |
| return i; |
| } |
| |
| NOEXPORT void setup_connect_addr(CLI *c) { |
| #ifdef SO_ORIGINAL_DST |
| socklen_t addrlen=sizeof(SOCKADDR_UNION); |
| #endif /* SO_ORIGINAL_DST */ |
| |
| /* process "redirect" first */ |
| if(c->redirect==REDIRECT_ON) { |
| s_log(LOG_NOTICE, "Redirecting connection"); |
| /* c->connect_addr.addr may be allocated in protocol negotiations */ |
| str_free(c->connect_addr.addr); |
| addrlist_dup(&c->connect_addr, &c->opt->redirect_addr); |
| return; |
| } |
| |
| /* check if the address was already set in protocol negotiations */ |
| /* used by the following protocols: CONNECT, SOCKS */ |
| if(c->connect_addr.num) |
| return; |
| |
| /* transparent destination */ |
| #ifdef SO_ORIGINAL_DST |
| if(c->opt->option.transparent_dst) { |
| c->connect_addr.num=1; |
| c->connect_addr.addr=str_alloc(sizeof(SOCKADDR_UNION)); |
| if(getsockopt(c->local_rfd.fd, SOL_IP, SO_ORIGINAL_DST, |
| c->connect_addr.addr, &addrlen)) { |
| sockerror("setsockopt SO_ORIGINAL_DST"); |
| longjmp(c->err, 1); |
| } |
| return; |
| } |
| #endif /* SO_ORIGINAL_DST */ |
| |
| /* default "connect" target */ |
| addrlist_dup(&c->connect_addr, &c->opt->connect_addr); |
| } |
| |
| NOEXPORT void local_bind(CLI *c) { |
| #ifndef USE_WIN32 |
| int on; |
| |
| on=1; |
| #endif |
| if(!c->bind_addr) |
| return; |
| #if defined(USE_WIN32) |
| /* do nothing */ |
| #elif defined(__linux__) |
| /* non-local bind on Linux */ |
| if(c->opt->option.transparent_src) { |
| if(setsockopt(c->fd, SOL_IP, IP_TRANSPARENT, &on, sizeof on)) { |
| sockerror("setsockopt IP_TRANSPARENT"); |
| if(setsockopt(c->fd, SOL_IP, IP_FREEBIND, &on, sizeof on)) |
| sockerror("setsockopt IP_FREEBIND"); |
| else |
| s_log(LOG_INFO, "IP_FREEBIND socket option set"); |
| } else |
| s_log(LOG_INFO, "IP_TRANSPARENT socket option set"); |
| /* ignore the error to retain Linux 2.2 compatibility */ |
| /* the error will be handled by bind(), anyway */ |
| } |
| #elif defined(IP_BINDANY) && defined(IPV6_BINDANY) |
| /* non-local bind on FreeBSD */ |
| if(c->opt->option.transparent_src) { |
| if(c->bind_addr->sa.sa_family==AF_INET) { /* IPv4 */ |
| if(setsockopt(c->fd, IPPROTO_IP, IP_BINDANY, &on, sizeof on)) { |
| sockerror("setsockopt IP_BINDANY"); |
| longjmp(c->err, 1); |
| } |
| } else { /* IPv6 */ |
| if(setsockopt(c->fd, IPPROTO_IPV6, IPV6_BINDANY, &on, sizeof on)) { |
| sockerror("setsockopt IPV6_BINDANY"); |
| longjmp(c->err, 1); |
| } |
| } |
| } |
| #else |
| /* unsupported platform */ |
| if(c->opt->option.transparent_src) { |
| s_log(LOG_ERR, "Transparent proxy in remote mode is not supported" |
| " on this platform"); |
| longjmp(c->err, 1); |
| } |
| #endif |
| |
| if(ntohs(c->bind_addr->in.sin_port)>=1024) { /* security check */ |
| /* this is currently only possible with transparent_src */ |
| if(!bind(c->fd, &c->bind_addr->sa, addr_len(c->bind_addr))) { |
| s_log(LOG_INFO, "local_bind succeeded on the original port"); |
| return; /* success */ |
| } |
| if(get_last_socket_error()!=S_EADDRINUSE) { |
| sockerror("local_bind (original port)"); |
| longjmp(c->err, 1); |
| } |
| } |
| |
| c->bind_addr->in.sin_port=htons(0); /* retry with ephemeral port */ |
| if(!bind(c->fd, &c->bind_addr->sa, addr_len(c->bind_addr))) { |
| s_log(LOG_INFO, "local_bind succeeded on an ephemeral port"); |
| return; /* success */ |
| } |
| sockerror("local_bind (ephemeral port)"); |
| longjmp(c->err, 1); |
| } |
| |
| NOEXPORT void print_bound_address(CLI *c) { |
| char *txt; |
| SOCKADDR_UNION addr; |
| socklen_t addrlen=sizeof addr; |
| |
| if(c->opt->log_level<LOG_NOTICE) /* performance optimization */ |
| return; |
| memset(&addr, 0, (size_t)addrlen); |
| if(getsockname(c->fd, (struct sockaddr *)&addr, &addrlen)) { |
| sockerror("getsockname"); |
| return; |
| } |
| txt=s_ntop(&addr, addrlen); |
| s_log(LOG_NOTICE,"Service [%s] connected remote server from %s", |
| c->opt->servname, txt); |
| str_free(txt); |
| } |
| |
| NOEXPORT void reset(SOCKET fd, char *txt) { /* set lingering on a socket */ |
| struct linger l; |
| |
| l.l_onoff=1; |
| l.l_linger=0; |
| if(setsockopt(fd, SOL_SOCKET, SO_LINGER, (void *)&l, sizeof l)) |
| log_error(LOG_DEBUG, get_last_socket_error(), txt); |
| } |
| |
| /* end of client.c */ |