NOEXPORT void ntlm(CLI *c) { char *line, buf[BUFSIZ], *ntlm1_txt, *ntlm2_txt, *ntlm3_txt, *tmpstr; long content_length=0; /* no HTTP content */ /* send Proxy-Authorization (phase 1) */ fd_printf(c, c->remote_fd.fd, "Proxy-Connection: keep-alive"); ntlm1_txt=ntlm1(); if(!ntlm1_txt) { s_log(LOG_ERR, "Proxy-Authenticate: Failed to build NTLM request"); longjmp(c->err, 1); } fd_printf(c, c->remote_fd.fd, "Proxy-Authorization: NTLM %s", ntlm1_txt); str_free(ntlm1_txt); fd_putline(c, c->remote_fd.fd, ""); /* empty line */ line=fd_getline(c, c->remote_fd.fd); /* receive Proxy-Authenticate (phase 2) */ if(!is_prefix(line, "HTTP/1.0 407") && !is_prefix(line, "HTTP/1.1 407")) { s_log(LOG_ERR, "Proxy-Authenticate: NTLM authorization request rejected"); do { /* read all headers */ str_free(line); line=fd_getline(c, c->remote_fd.fd); } while(*line); str_free(line); longjmp(c->err, 1); } ntlm2_txt=NULL; do { /* read all headers */ str_free(line); line=fd_getline(c, c->remote_fd.fd); if(is_prefix(line, "Proxy-Authenticate: NTLM ")) ntlm2_txt=str_dup(line+25); else if(is_prefix(line, "Content-Length: ")) { content_length=strtol(line+16, &tmpstr, 10); if(tmpstr==line+16 || *tmpstr || content_length<0) { s_log(LOG_ERR, "Proxy-Authenticate: Invalid Content-Length"); str_free(line); longjmp(c->err, 1); } } } while(*line); if(!ntlm2_txt) { /* no Proxy-Authenticate: NTLM header */ s_log(LOG_ERR, "Proxy-Authenticate: NTLM header not found"); str_free(line); longjmp(c->err, 1); } /* read and ignore HTTP content (if any) */ while(content_length>0) { s_read(c, c->remote_fd.fd, buf, s_min(content_length, BUFSIZ)); content_length-=s_min(content_length, BUFSIZ); } /* send Proxy-Authorization (phase 3) */ fd_printf(c, c->remote_fd.fd, "CONNECT %s HTTP/1.1", c->opt->protocol_host); fd_printf(c, c->remote_fd.fd, "Host: %s", c->opt->protocol_host); ntlm3_txt=ntlm3(c->opt->protocol_username, c->opt->protocol_password, ntlm2_txt); str_free(ntlm2_txt); if(!ntlm3_txt) { s_log(LOG_ERR, "Proxy-Authenticate: Failed to build NTLM response"); longjmp(c->err, 1); } fd_printf(c, c->remote_fd.fd, "Proxy-Authorization: NTLM %s", ntlm3_txt); str_free(ntlm3_txt); }
NOEXPORT void init_ssl(CLI *c) { int i, err; SSL_SESSION *old_session; int unsafe_openssl; c->ssl=SSL_new(c->opt->ctx); if(!c->ssl) { sslerror("SSL_new"); longjmp(c->err, 1); } SSL_set_ex_data(c->ssl, cli_index, c); /* for callbacks */ if(c->opt->option.client) { #ifndef OPENSSL_NO_TLSEXT if(c->opt->sni) { s_log(LOG_DEBUG, "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, c->remote_fd.fd); SSL_set_connect_state(c->ssl); } else { if(c->local_rfd.fd==c->local_wfd.fd) SSL_set_fd(c->ssl, c->local_rfd.fd); else { /* does it make sense to have SSL on STDIN/STDOUT? */ SSL_set_rfd(c->ssl, c->local_rfd.fd); SSL_set_wfd(c->ssl, c->local_wfd.fd); } SSL_set_accept_state(c->ssl); } /* setup some values for transfer() function */ 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); } 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("init_ssl: s_poll_wait"); longjmp(c->err, 1); case 0: s_log(LOG_INFO, "init_ssl: s_poll_wait:" " TIMEOUTbusy exceeded: sending reset"); longjmp(c->err, 1); case 1: break; /* OK */ default: s_log(LOG_ERR, "init_ssl: 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); } if(SSL_session_reused(c->ssl)) { s_log(LOG_INFO, "SSL %s: previous session reused", c->opt->option.client ? "connected" : "accepted"); } else { /* a new session was negotiated */ new_chain(c); if(c->opt->option.client) { s_log(LOG_INFO, "SSL connected: new session negotiated"); enter_critical_section(CRIT_SESSION); old_session=c->opt->session; c->opt->session=SSL_get1_session(c->ssl); /* store it */ if(old_session) SSL_SESSION_free(old_session); /* release the old one */ leave_critical_section(CRIT_SESSION); } else s_log(LOG_INFO, "SSL accepted: new session negotiated"); print_cipher(c); } }
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<0) longjmp(c->err, 1); memcpy(&ident, &c->peer_addr, c->peer_addr_len); #ifndef _WIN32_WCE s_ent=getservbyname("auth", "tcp"); if(s_ent) { ident.in.sin_port=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=-1; /* 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)) { safestring(user); 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); }
NOEXPORT int connect_local(CLI *c) { /* spawn local process */ char *name, host[40]; int fd[2], pid; X509 *peer; #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 */ 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, NULL, 0, NI_NUMERICHOST)) { /* just don't set these variables if getnameinfo() fails */ putenv(str_printf("REMOTE_HOST=%s", host)); 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=SSL_get_peer_certificate(c->ssl); if(peer) { name=X509_NAME_oneline(X509_get_subject_name(peer), NULL, 0); safestring(name); putenv(str_printf("SSL_CLIENT_DN=%s", name)); name=X509_NAME_oneline(X509_get_issuer_name(peer), NULL, 0); safestring(name); putenv(str_printf("SSL_CLIENT_I_DN=%s", name)); X509_free(peer); } } #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->execname, c->opt->execargs); ioerror(c->opt->execname); /* execvp failed */ _exit(1); default: /* parent */ s_log(LOG_INFO, "Local mode child started (PID=%lu)", c->pid); closesocket(fd[1]); return fd[0]; } }
NOEXPORT void client_run(CLI *c) { int err, rst; #ifndef USE_FORK int num_clients_copy; #endif #ifndef USE_FORK enter_critical_section(CRIT_CLIENTS); ui_clients(++num_clients); leave_critical_section(CRIT_CLIENTS); #endif c->remote_fd.fd=-1; c->fd=-1; c->ssl=NULL; c->sock_bytes=c->ssl_bytes=0; c->fds=s_poll_alloc(); c->connect_addr.num=0; c->connect_addr.addr=NULL; err=setjmp(c->err); if(!err) client_try(c); rst=err==1 && c->opt->option.reset; s_log(LOG_NOTICE, "Connection %s: %d byte(s) sent to SSL, %d byte(s) sent to socket", rst ? "reset" : "closed", c->ssl_bytes, c->sock_bytes); /* cleanup temporary (e.g. IDENT) socket */ if(c->fd>=0) closesocket(c->fd); c->fd=-1; /* cleanup SSL */ if(c->ssl) { /* SSL initialized */ SSL_set_shutdown(c->ssl, SSL_SENT_SHUTDOWN|SSL_RECEIVED_SHUTDOWN); SSL_free(c->ssl); c->ssl=NULL; ERR_remove_state(0); } /* cleanup remote socket */ if(c->remote_fd.fd>=0) { /* 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=-1; } /* cleanup local socket */ if(c->local_rfd.fd>=0) { /* 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=-1; } #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->option.program) /* '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 (%d left)", c->opt->servname, num_clients_copy); #endif /* free remaining memory structures */ if(c->connect_addr.addr) str_free(c->connect_addr.addr); s_poll_free(c->fds); c->fds=NULL; }
static void auth_user(CLI * c, char *accepted_address) { struct servent *s_ent; SOCKADDR_UNION ident; char *line, *type, *system, *user; if (!c->opt->username) return; if (c->peer_addr.sa.sa_family == AF_UNIX) { s_log(LOG_INFO, "IDENT not supported on Unix sockets"); return; } c->fd = s_socket(c->peer_addr.sa.sa_family, SOCK_STREAM, 0, 1, "socket (auth_user)"); if (c->fd < 0) longjmp(c->err, 1); memcpy(&ident, &c->peer_addr, c->peer_addr_len); s_ent = getservbyname("auth", "tcp"); if (s_ent) { ident.in.sin_port = s_ent->s_port; } else { s_log(LOG_WARNING, "Unknown service 'auth': using default 113"); ident.in.sin_port = htons(113); } if (connect_blocking(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 = -1; 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 == ' ') ++user; if (strcmp(user, c->opt->username)) { safestring(user); 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); }
void log_error(int level, int error, const char *txt) { /* generic error */ s_log(level, "%s: %s (%d)", txt, s_strerror(error), error); }
static int init_local(CLI *c) { SOCKADDR_UNION addr; socklen_t addrlen; addrlen=sizeof(SOCKADDR_UNION); if(getpeername(c->local_rfd.fd, &addr.sa, &addrlen)<0) { strcpy(c->accepting_address, "NOT A SOCKET"); c->local_rfd.is_socket=0; c->local_wfd.is_socket=0; /* TODO: It's not always true */ #ifdef USE_WIN32 if(get_last_socket_error()!=ENOTSOCK) { #else if(c->opt->option.transparent || get_last_socket_error()!=ENOTSOCK) { #endif sockerror("getpeerbyname"); return -1; } /* Ignore ENOTSOCK error so 'local' doesn't have to be a socket */ } else { /* success */ /* copy addr to c->peer_addr */ memcpy(&c->peer_addr.addr[0], &addr, sizeof(SOCKADDR_UNION)); c->peer_addr.num=1; s_ntop(c->accepting_address, &c->peer_addr.addr[0]); c->local_rfd.is_socket=1; c->local_wfd.is_socket=1; /* TODO: It's not always true */ /* It's a socket: lets setup options */ if(set_socket_options(c->local_rfd.fd, 1)<0) return -1; if(auth_libwrap(c)<0) return -1; if(auth_user(c)<0) { s_log(LOG_WARNING, "Connection from %s REFUSED by IDENT", c->accepting_address); return -1; } s_log(LOG_NOTICE, "%s connected from %s", c->opt->servname, c->accepting_address); } return 0; /* OK */ } static int init_remote(CLI *c) { int fd; /* create connection to host/service */ if(c->opt->source_addr.num) memcpy(&c->bind_addr, &c->opt->source_addr, sizeof(SOCKADDR_LIST)); #ifndef USE_WIN32 else if(c->opt->option.transparent) memcpy(&c->bind_addr, &c->peer_addr, sizeof(SOCKADDR_LIST)); #endif else { c->bind_addr.num=0; /* don't bind connecting socket */ } /* Setup c->remote_fd, now */ if(c->opt->option.remote) { fd=connect_remote(c); } else /* NOT in remote mode */ fd=connect_local(c); if(fd<0) { s_log(LOG_ERR, "Failed to initialize remote connection"); return -1; } #ifndef USE_WIN32 if(fd>=max_fds) { s_log(LOG_ERR, "Remote file descriptor out of range (%d>=%d)", fd, max_fds); closesocket(fd); return -1; } #endif s_log(LOG_DEBUG, "Remote FD=%d initialized", fd); c->remote_fd.fd=fd; c->remote_fd.is_socket=1; /* Always! */ if(set_socket_options(fd, 2)<0) return -1; return 0; /* OK */ }
static int init_ssl(CLI *c) { int i, err; SSL_SESSION *old_session; if(!(c->ssl=SSL_new(ctx))) { sslerror("SSL_new"); return -1; } #if SSLEAY_VERSION_NUMBER >= 0x0922 SSL_set_session_id_context(c->ssl, sid_ctx, strlen(sid_ctx)); #endif if(options.option.client) { 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, c->remote_fd.fd); SSL_set_connect_state(c->ssl); } else { if(c->local_rfd.fd==c->local_wfd.fd) SSL_set_fd(c->ssl, c->local_rfd.fd); else { /* Does it make sence to have SSL on STDIN/STDOUT? */ SSL_set_rfd(c->ssl, c->local_rfd.fd); SSL_set_wfd(c->ssl, c->local_wfd.fd); } SSL_set_accept_state(c->ssl); } /* Setup some values for transfer() function */ if(options.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); } while(1) { if(options.option.client) i=SSL_connect(c->ssl); else i=SSL_accept(c->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_zero(&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)) { case -1: sockerror("init_ssl: s_poll_wait"); return -1; /* error */ case 0: s_log(LOG_INFO, "init_ssl: s_poll_wait timeout"); return -1; /* timeout */ case 1: break; /* OK */ default: s_log(LOG_ERR, "init_ssl: s_poll_wait unknown result"); return -1; /* error */ } continue; /* ok -> retry */ } if(err==SSL_ERROR_SYSCALL) { switch(get_last_socket_error()) { case EINTR: case EAGAIN: continue; } } if(options.option.client) sslerror("SSL_connect"); else sslerror("SSL_accept"); return -1; } if(SSL_session_reused(c->ssl)) { s_log(LOG_INFO, "SSL %s: previous session reused", options.option.client ? "connected" : "accepted"); } else { /* a new session was negotiated */ if(options.option.client) { s_log(LOG_INFO, "SSL connected: new session negotiated"); enter_critical_section(CRIT_SESSION); old_session=c->opt->session; c->opt->session=SSL_get1_session(c->ssl); /* store it */ if(old_session) SSL_SESSION_free(old_session); /* release the old one */ leave_critical_section(CRIT_SESSION); } else s_log(LOG_INFO, "SSL accepted: new session negotiated"); print_cipher(c); } return 0; /* OK */ }
NOEXPORT int gui_loop() { #ifdef _WIN32_WCE WNDCLASS wc; #else WNDCLASSEX wc; #endif MSG msg; LPTSTR classname=TEXT("stunnel_main_window_class"); /* register the class */ #ifndef _WIN32_WCE wc.cbSize=sizeof wc; #endif wc.style=CS_VREDRAW|CS_HREDRAW; wc.lpfnWndProc=window_proc; wc.cbClsExtra=wc.cbWndExtra=0; wc.hInstance=ghInst; wc.hIcon=LoadIcon(ghInst, MAKEINTRESOURCE(IDI_STUNNEL_MAIN)); wc.hCursor=LoadCursor(NULL, IDC_ARROW); wc.hbrBackground=(HBRUSH)(COLOR_WINDOW+1); wc.lpszMenuName=NULL; wc.lpszClassName=classname; #ifdef _WIN32_WCE RegisterClass(&wc); #else /* load 16x16 icon */ wc.hIconSm=LoadImage(ghInst, MAKEINTRESOURCE(IDI_STUNNEL_MAIN), IMAGE_ICON, GetSystemMetrics(SM_CXSMICON), GetSystemMetrics(SM_CYSMICON), 0); RegisterClassEx(&wc); #endif /* create main window */ #ifdef _WIN32_WCE hwnd=CreateWindow(classname, win32_name, 0, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, NULL, NULL, ghInst, NULL); #else main_menu_handle=LoadMenu(ghInst, MAKEINTRESOURCE(IDM_MAINMENU)); if(main_menu_handle && cmdline.service) { /* block unsafe operations in the service mode */ EnableMenuItem(main_menu_handle, IDM_EDIT_CONFIG, MF_GRAYED); EnableMenuItem(main_menu_handle, IDM_SAVE_LOG, MF_GRAYED); } hwnd=CreateWindow(classname, win32_name, WS_TILEDWINDOW, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, NULL, main_menu_handle, ghInst, NULL); #endif /* auto-reset, non-signaled events */ main_initialized=CreateEvent(NULL, FALSE, FALSE, NULL); config_ready=CreateEvent(NULL, FALSE, FALSE, NULL); /* hwnd needs to be initialized before _beginthread() */ _beginthread(daemon_thread, DEFAULT_STACK_SIZE, NULL); WaitForSingleObject(main_initialized, INFINITE); /* logging subsystem is now available */ /* setup periodic event to trigger update_logs() */ SetTimer(NULL, 0, 1000, timer_proc); /* run callback once per second */ s_log(LOG_DEBUG, "GUI message loop initialized"); for(;;) switch(GetMessage(&msg, NULL, 0, 0)) { case -1: ioerror("GetMessage"); return 0; case 0: s_log(LOG_DEBUG, "GUI message loop terminated"); return (int)msg.wParam; default: TranslateMessage(&msg); DispatchMessage(&msg); } }
NOEXPORT LRESULT CALLBACK window_proc(HWND main_window_handle, UINT message, WPARAM wParam, LPARAM lParam) { POINT pt; RECT rect; PAINTSTRUCT ps; SERVICE_OPTIONS *section; unsigned section_number; LPTSTR txt; #if 0 switch(message) { case WM_CTLCOLORSTATIC: case WM_TIMER: case WM_LOG: break; default: s_log(LOG_DEBUG, "Window message: 0x%x(0x%hx,0x%lx)", message, wParam, lParam); } #endif switch(message) { case WM_CREATE: #ifdef _WIN32_WCE /* create command bar */ command_bar_handle=CommandBar_Create(ghInst, main_window_handle, 1); if(!command_bar_handle) error_box(TEXT("CommandBar_Create")); if(!CommandBar_InsertMenubar(command_bar_handle, ghInst, IDM_MAINMENU, 0)) error_box(TEXT("CommandBar_InsertMenubar")); if(!CommandBar_AddAdornments(command_bar_handle, 0, 0)) error_box(TEXT("CommandBar_AddAdornments")); #endif /* create child edit window */ edit_handle=CreateWindowEx(WS_EX_STATICEDGE, TEXT("EDIT"), NULL, WS_CHILD|WS_VISIBLE|WS_HSCROLL|WS_VSCROLL|ES_MULTILINE|ES_READONLY, 0, 0, 0, 0, main_window_handle, (HMENU)IDE_EDIT, ghInst, NULL); #ifndef _WIN32_WCE SendMessage(edit_handle, WM_SETFONT, (WPARAM)CreateFont(-12, 0, 0, 0, FW_DONTCARE, FALSE, FALSE, FALSE, DEFAULT_CHARSET, OUT_RASTER_PRECIS, CLIP_DEFAULT_PRECIS, PROOF_QUALITY, DEFAULT_PITCH, TEXT("Courier")), MAKELPARAM(FALSE, 0)); /* no need to redraw right now */ #endif /* NOTE: there's no return statement here -> proceeding with resize */ case WM_SIZE: GetClientRect(main_window_handle, &rect); #ifdef _WIN32_WCE MoveWindow(edit_handle, 0, CommandBar_Height(command_bar_handle), rect.right, rect.bottom-CommandBar_Height(command_bar_handle), TRUE); SendMessage(command_bar_handle, TB_AUTOSIZE, 0L, 0L); CommandBar_AlignAdornments(command_bar_handle); #else MoveWindow(edit_handle, 0, 0, rect.right, rect.bottom, TRUE); #endif UpdateWindow(edit_handle); /* CommandBar_Show(command_bar_handle, TRUE); */ return 0; case WM_SETFOCUS: SetFocus(edit_handle); return 0; case WM_PAINT: BeginPaint(hwnd, &ps); EndPaint(hwnd, &ps); break; case WM_CLOSE: ShowWindow(main_window_handle, SW_HIDE); return 0; #ifdef WM_SHOWWINDOW case WM_SHOWWINDOW: visible=(BOOL)wParam; #else /* this works for Pierre Delaage, but not for me... */ case WM_WINDOWPOSCHANGED: visible=IsWindowVisible(main_window_handle); #endif if(tray_menu_handle) CheckMenuItem(tray_menu_handle, IDM_SHOW_LOG, visible ? MF_CHECKED : MF_UNCHECKED); if(visible) update_logs(); #ifdef WM_SHOWWINDOW return 0; #else break; /* proceed to DefWindowProc() */ #endif case WM_DESTROY: #ifdef _WIN32_WCE CommandBar_Destroy(command_bar_handle); #else if(main_menu_handle) { if(!DestroyMenu(main_menu_handle)) ioerror("DestroyMenu"); main_menu_handle=NULL; } #endif tray_delete(); /* remove the taskbark icon if exists */ PostQuitMessage(0); return 0; case WM_COMMAND: if(wParam>=IDM_PEER_MENU && wParam<IDM_PEER_MENU+number_of_sections) { for(section=service_options.next, section_number=0; section && wParam!=IDM_PEER_MENU+section_number; section=section->next, ++section_number) ; if(!section) return 0; if(save_text_file(section->file, section->chain)) return 0; #ifndef _WIN32_WCE if(main_menu_handle) CheckMenuItem(main_menu_handle, (UINT)wParam, MF_CHECKED); #endif if(tray_menu_handle) CheckMenuItem(tray_menu_handle, (UINT)wParam, MF_CHECKED); message_box(section->help, MB_ICONINFORMATION); return 0; } switch(wParam) { case IDM_ABOUT: DialogBox(ghInst, TEXT("AboutBox"), main_window_handle, (DLGPROC)about_proc); break; case IDM_SHOW_LOG: if(visible) { ShowWindow(main_window_handle, SW_HIDE); /* hide window */ } else { ShowWindow(main_window_handle, SW_SHOWNORMAL); /* show window */ SetForegroundWindow(main_window_handle); /* bring on top */ } break; case IDM_CLOSE: ShowWindow(main_window_handle, SW_HIDE); /* hide window */ break; case IDM_EXIT: if(num_clients>=0) /* signal_pipe is active */ signal_post(SIGNAL_TERMINATE); DestroyWindow(main_window_handle); break; case IDM_SAVE_LOG: if(!cmdline.service) /* security */ save_log(); break; case IDM_EDIT_CONFIG: #ifndef _WIN32_WCE if(!cmdline.service) /* security */ edit_config(main_window_handle); #endif break; case IDM_RELOAD_CONFIG: if(num_clients>=0) /* signal_pipe is active */ signal_post(SIGNAL_RELOAD_CONFIG); else SetEvent(config_ready); /* unlock daemon_thread() */ break; case IDM_REOPEN_LOG: signal_post(SIGNAL_REOPEN_LOG); break; case IDM_MANPAGE: #ifndef _WIN32_WCE if(!cmdline.service) /* security */ ShellExecute(main_window_handle, TEXT("open"), TEXT("..\\doc\\stunnel.html"), NULL, NULL, SW_SHOWNORMAL); #endif break; case IDM_HOMEPAGE: #ifndef _WIN32_WCE if(!cmdline.service) /* security */ ShellExecute(main_window_handle, TEXT("open"), TEXT("http://www.stunnel.org/"), NULL, NULL, SW_SHOWNORMAL); #endif break; } return 0; case WM_SYSTRAY: /* a taskbar event */ switch(lParam) { #ifdef _WIN32_WCE case WM_LBUTTONDOWN: /* no right mouse button on Windows CE */ GetWindowRect(GetDesktopWindow(), &rect); /* no cursor position */ pt.x=rect.right; pt.y=rect.bottom-25; #else case WM_RBUTTONDOWN: GetCursorPos(&pt); #endif SetForegroundWindow(main_window_handle); if(tray_menu_handle) TrackPopupMenuEx(GetSubMenu(tray_menu_handle, 0), TPM_BOTTOMALIGN, pt.x, pt.y, main_window_handle, NULL); PostMessage(main_window_handle, WM_NULL, 0, 0); break; #ifndef _WIN32_WCE case WM_LBUTTONDBLCLK: /* switch log window visibility */ if(visible) { ShowWindow(main_window_handle, SW_HIDE); /* hide window */ } else { ShowWindow(main_window_handle, SW_SHOWNORMAL); /* show window */ SetForegroundWindow(main_window_handle); /* bring on top */ } break; #endif } return 0; case WM_VALID_CONFIG: valid_config(); return 0; case WM_INVALID_CONFIG: invalid_config(); return 0; case WM_LOG: txt=(LPTSTR)wParam; win_log(txt); str_free(txt); return 0; case WM_NEW_CHAIN: #ifndef _WIN32_WCE if(main_menu_handle) EnableMenuItem(main_menu_handle, (UINT)(IDM_PEER_MENU+wParam), MF_ENABLED); #endif if(tray_menu_handle) EnableMenuItem(tray_menu_handle, (UINT)(IDM_PEER_MENU+wParam), MF_ENABLED); return 0; case WM_CLIENTS: tray_update((int)wParam); return 0; } return DefWindowProc(main_window_handle, message, wParam, lParam); }
void c_basis::on_process(void* data, int size, mw_net_pipe* pipe) { if (size > 0 && ((char*)data)[0] == 'Q') { s_log()->info("loopbreak(0x%x)\r\n", ctid()); loopbreak(); return; } //1.优先处理重先绑定的 while (true) { CRawTcpConnection* con = pop_rebind(); if (con == NULL) break; con->on_rebind(); } //判断是否有连接关闭 while (true) { int32 fd = get_close(); if (fd == 0) break; MSocket::iterator pos = m_mSocket.find(fd); if (pos != m_mSocket.end()) { pos->second->on_close(EVUTIL_SOCKET_ERROR()); } } //判断是否有新连接到来 accept_fd fd; while ( libevent::get_instance()->g_fd(fd) ) { CRawTcpConnection* pcon = new CRawTcpConnection(fd.fd, fd.remote_ip); fd.sink->on_accept(pcon, fd.port); m_mSocket.insert(make_pair(fd.fd, pcon)); } //判断是否有新的连接 connect_fd cd; while ( libevent::get_instance()->g_cd(cd) ) { cd.sink->asyn_connect(cd.addr, cd.port); } //信息本线程所有连接信号 while (true) { int fd = pop_fd(); if (fd == 0) break; MSocket::iterator pos = m_mSocket.find(fd); if (pos != m_mSocket.end()) { CRawTcpConnection* con = pos->second; pos->second->on_signal(); } } }
int verify_init(SERVICE_OPTIONS *section) { if(section->verify_level<0) return 1; /* no certificate verification */ if(section->verify_level>1 && !section->ca_file && !section->ca_dir) { s_log(LOG_ERR, "Either CApath or CAfile has to be used for authentication"); return 0; } section->revocation_store=X509_STORE_new(); if(!section->revocation_store) { sslerror("X509_STORE_new"); return 0; } if(section->ca_file) { if(!SSL_CTX_load_verify_locations(section->ctx, section->ca_file, NULL)) { s_log(LOG_ERR, "Error loading verify certificates from %s", section->ca_file); sslerror("SSL_CTX_load_verify_locations"); return 0; } /* list of trusted CAs for the client to choose the right cert */ SSL_CTX_set_client_CA_list(section->ctx, SSL_load_client_CA_file(section->ca_file)); s_log(LOG_DEBUG, "Loaded verify certificates from %s", section->ca_file); if(!load_file_lookup(section->revocation_store, section->ca_file)) return 0; } if(section->ca_dir) { if(!SSL_CTX_load_verify_locations(section->ctx, NULL, section->ca_dir)) { s_log(LOG_ERR, "Error setting verify directory to %s", section->ca_dir); sslerror("SSL_CTX_load_verify_locations"); return 0; } s_log(LOG_DEBUG, "Verify directory set to %s", section->ca_dir); add_dir_lookup(section->revocation_store, section->ca_dir); } if(section->crl_file) if(!load_file_lookup(section->revocation_store, section->crl_file)) return 0; if(section->crl_dir) { section->revocation_store->cache=0; /* don't cache CRLs */ add_dir_lookup(section->revocation_store, section->crl_dir); } SSL_CTX_set_verify(section->ctx, section->verify_level==SSL_VERIFY_NONE ? SSL_VERIFY_PEER : section->verify_level, verify_callback); if(section->ca_dir && section->verify_use_only_my) s_log(LOG_NOTICE, "Peer certificate location %s", section->ca_dir); return 1; /* OK */ }
static int ocsp_check(CLI *c, X509_STORE_CTX *callback_ctx) { int error, retval=0; SOCKADDR_UNION addr; X509 *cert; X509 *issuer=NULL; OCSP_CERTID *certID; BIO *bio=NULL; OCSP_REQUEST *request=NULL; OCSP_RESPONSE *response=NULL; OCSP_BASICRESP *basicResponse=NULL; ASN1_GENERALIZEDTIME *revoked_at=NULL, *this_update=NULL, *next_update=NULL; int status, reason; /* connect specified OCSP server (responder) */ if((c->fd= socket(c->opt->ocsp_addr.addr[0].sa.sa_family, SOCK_STREAM, 0))<0) { sockerror("OCSP: socket (auth_user)"); return 0; /* reject connection */ } if(alloc_fd(c->fd)) goto cleanup; memcpy(&addr, &c->opt->ocsp_addr.addr[0], sizeof addr); if(connect_blocking(c, &addr, addr_len(addr))) goto cleanup; s_log(LOG_DEBUG, "OCSP: server connected"); /* get current certificate ID */ cert=X509_STORE_CTX_get_current_cert(callback_ctx); /* get current cert */ if(X509_STORE_CTX_get1_issuer(&issuer, callback_ctx, cert)!=1) { sslerror("OCSP: X509_STORE_CTX_get1_issuer"); goto cleanup; } certID=OCSP_cert_to_id(0, cert, issuer); if(!certID) { sslerror("OCSP: OCSP_cert_to_id"); goto cleanup; } /* build request */ request=OCSP_REQUEST_new(); if(!request) { sslerror("OCSP: OCSP_REQUEST_new"); goto cleanup; } if(!OCSP_request_add0_id(request, certID)) { sslerror("OCSP: OCSP_request_add0_id"); goto cleanup; } OCSP_request_add1_nonce(request, 0, -1); /* send the request and get a response */ /* FIXME: this code won't work with ucontext threading */ /* (blocking sockets are used) */ bio=BIO_new_fd(c->fd, BIO_NOCLOSE); set_nonblock(c->fd, 0); response=OCSP_sendreq_bio(bio, c->opt->ocsp_path, request); set_nonblock(c->fd, 1); if(!response) { sslerror("OCSP: OCSP_sendreq_bio"); goto cleanup; } error=OCSP_response_status(response); if(error!=OCSP_RESPONSE_STATUS_SUCCESSFUL) { s_log(LOG_WARNING, "OCSP: Responder error: %d: %s", error, OCSP_response_status_str(error)); goto cleanup; } s_log(LOG_DEBUG, "OCSP: Response received"); /* verify the response */ basicResponse=OCSP_response_get1_basic(response); if(!basicResponse) { sslerror("OCSP: OCSP_response_get1_basic"); goto cleanup; } if(OCSP_check_nonce(request, basicResponse)<=0) { sslerror("OCSP: OCSP_check_nonce"); goto cleanup; } if(OCSP_basic_verify(basicResponse, NULL, c->opt->revocation_store, c->opt->ocsp_flags)<=0) { sslerror("OCSP: OCSP_basic_verify"); goto cleanup; } if(!OCSP_resp_find_status(basicResponse, certID, &status, &reason, &revoked_at, &this_update, &next_update)) { sslerror("OCSP: OCSP_resp_find_status"); goto cleanup; } s_log(LOG_NOTICE, "OCSP: Status: %d: %s", status, OCSP_cert_status_str(status)); log_time(LOG_INFO, "OCSP: This update", this_update); log_time(LOG_INFO, "OCSP: Next update", next_update); /* check if the response is valid for at least one minute */ if(!OCSP_check_validity(this_update, next_update, 60, -1)) { sslerror("OCSP: OCSP_check_validity"); goto cleanup; } if(status==V_OCSP_CERTSTATUS_REVOKED) { if(reason==-1) s_log(LOG_WARNING, "OCSP: Certificate revoked"); else s_log(LOG_WARNING, "OCSP: Certificate revoked: %d: %s", reason, OCSP_crl_reason_str(reason)); log_time(LOG_NOTICE, "OCSP: Revoked at", revoked_at); goto cleanup; } retval=1; /* accept connection */ cleanup: if(bio) BIO_free_all(bio); if(issuer) X509_free(issuer); if(request) OCSP_REQUEST_free(request); if(response) OCSP_RESPONSE_free(response); if(basicResponse) OCSP_BASICRESP_free(basicResponse); closesocket(c->fd); c->fd=-1; /* avoid double close on cleanup */ return retval; }
void client_main(CLI * c) { s_log(LOG_DEBUG, "Service [%s] started", c->opt->servname); client_run(c); str_free(c); }
/****************************** transfer data */ static int transfer(CLI *c) { int num, err; int check_SSL_pending; enum {CL_OPEN, CL_INIT, CL_RETRY, CL_CLOSED} ssl_closing=CL_OPEN; int watchdog=0; /* a counter to detect an infinite loop */ int stunnel_hdr_is_sent=0; int insert_new_hdr=0; int is_http=-1; // -1:not set, 0:not http header, 1:is http header int space_cnt=0; char* space=NULL; char* first_hdr_end=NULL; c->sock_ptr=c->ssl_ptr=0; sock_rd=sock_wr=ssl_rd=ssl_wr=1; c->sock_bytes=c->ssl_bytes=0; do { /* main loop */ /* set flag to try and read any buffered SSL data * if we made room in the buffer by writing to the socket */ check_SSL_pending=0; /****************************** setup c->fds structure */ s_poll_zero(&c->fds); /* Initialize the structure */ if(sock_rd && c->sock_ptr<BUFFSIZE) /* socket input buffer not full*/ s_poll_add(&c->fds, c->sock_rfd->fd, 1, 0); if((ssl_rd && c->ssl_ptr<BUFFSIZE) || /* SSL input buffer not full */ ((c->sock_ptr || ssl_closing==CL_RETRY) && want_rd)) /* want to SSL_write or SSL_shutdown but read from the * underlying socket needed for the SSL protocol */ s_poll_add(&c->fds, c->ssl_rfd->fd, 1, 0); if(c->ssl_ptr) /* SSL input buffer not empty */ s_poll_add(&c->fds, c->sock_wfd->fd, 0, 1); if(c->sock_ptr || /* socket input buffer not empty */ ssl_closing==CL_INIT /* need to send close_notify */ || ((c->ssl_ptr<BUFFSIZE || ssl_closing==CL_RETRY) && want_wr)) /* want to SSL_read or SSL_shutdown but write to the * underlying socket needed for the SSL protocol */ s_poll_add(&c->fds, c->ssl_wfd->fd, 0, 1); /****************************** wait for an event */ err=s_poll_wait(&c->fds, (sock_rd && ssl_rd) /* both peers open */ || 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); switch(err) { case -1: sockerror("transfer: s_poll_wait"); return -1; case 0: /* timeout */ if((sock_rd && ssl_rd) || c->ssl_ptr || c->sock_ptr) { s_log(LOG_INFO, "s_poll_wait timeout: connection reset"); return -1; } else { /* already closing connection */ s_log(LOG_INFO, "s_poll_wait timeout: connection close"); return 0; /* OK */ } } 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); return -1; } /****************************** send SSL close_notify message */ if(ssl_closing==CL_INIT || (ssl_closing==CL_RETRY && ((want_rd && ssl_can_rd) || (want_wr && ssl_can_wr)))) { switch(SSL_shutdown(c->ssl)) { /* Send close_notify */ case 1: /* the shutdown was successfully completed */ s_log(LOG_INFO, "SSL_shutdown successfully sent close_notify"); ssl_closing=CL_CLOSED; /* done! */ break; case 0: /* the shutdown is not yet finished */ s_log(LOG_DEBUG, "SSL_shutdown retrying"); ssl_closing=CL_RETRY; /* retry next time */ break; case -1: /* a fatal error occurred */ sslerror("SSL_shutdown"); return -1; } } /****************************** write to socket */ if(sock_wr && sock_can_wr) { /* for stunnel to tell web server the remote ip address */ int write_len; char real_remote_addr[IPLEN+2]; char addr_header[64]; char* colon; if(is_http == -1 && !stunnel_hdr_is_sent) { space = c->ssl_buff; for(; space_cnt < 2; space_cnt++) { space = strchr(space, ' '); if(space == NULL) break; else if(space - c->ssl_buff > c->ssl_ptr - 1) { space = NULL; break; } space++; } if(space_cnt == 2) { if(strncmp(space, "HTTP/", strlen("HTTP/"))==0) is_http = 1; else is_http = 0; } } if(is_http == 1 && !stunnel_hdr_is_sent) { first_hdr_end = strstr(c->ssl_buff, "\r\n"); if(first_hdr_end - c->ssl_buff <= c->ssl_ptr - strlen("\r\n")) insert_new_hdr = 1; } if(insert_new_hdr == 1) { first_hdr_end += 2; write_len = (int)first_hdr_end - (int)c->ssl_buff; num = writesocket(c->sock_wfd->fd, c->ssl_buff, write_len); //fprintf(stderr, "1: %d, %d\n", num, write_len); s_ntop(real_remote_addr, &c->peer_addr.addr[0]); colon = strchr(real_remote_addr, ':'); real_remote_addr[(int)colon - (int)real_remote_addr] = '\0'; sprintf(addr_header, "StunnelRemoteIP: %s\r\n", real_remote_addr); writesocket(c->sock_wfd->fd, addr_header, strlen(addr_header)); //fprintf(stderr, "2: %d, %d, %s\n", num, strlen(addr_header), addr_header); write_len = c->ssl_ptr - write_len; num += writesocket(c->sock_wfd->fd, first_hdr_end, write_len); //fprintf(stderr, "3: %d, %d\n", num, write_len); stunnel_hdr_is_sent = 1; insert_new_hdr = 0; } else { num = writesocket(c->sock_wfd->fd, c->ssl_buff, c->ssl_ptr); } switch(num) { case -1: /* error */ if(parse_socket_error("writesocket")) return -1; break; case 0: s_log(LOG_DEBUG, "No data written to the socket: retrying"); break; default: memmove(c->ssl_buff, c->ssl_buff+num, c->ssl_ptr-num); if(c->ssl_ptr==BUFFSIZE) /* buffer was previously full */ check_SSL_pending=1; /* check for data buffered by SSL */ c->ssl_ptr-=num; c->sock_bytes+=num; watchdog=0; /* reset watchdog */ } } /****************************** write to SSL */ if(ssl_wr && c->sock_ptr && ( /* output buffer not empty */ ssl_can_wr || (want_rd && ssl_can_rd) /* SSL_write wants to read from the underlying descriptor */ )) { num=SSL_write(c->ssl, c->sock_buff, c->sock_ptr); switch(err=SSL_get_error(c->ssl, num)) { case SSL_ERROR_NONE: memmove(c->sock_buff, c->sock_buff+num, c->sock_ptr-num); c->sock_ptr-=num; c->ssl_bytes+=num; watchdog=0; /* reset watchdog */ break; case SSL_ERROR_WANT_WRITE: s_log(LOG_DEBUG, "SSL_write returned WANT_WRITE: retrying"); break; case SSL_ERROR_WANT_READ: s_log(LOG_DEBUG, "SSL_write returned WANT_READ: retrying"); break; case SSL_ERROR_WANT_X509_LOOKUP: s_log(LOG_DEBUG, "SSL_write returned WANT_X509_LOOKUP: retrying"); break; case SSL_ERROR_SYSCALL: /* really an error */ if(num && parse_socket_error("SSL_write")) return -1; break; case SSL_ERROR_ZERO_RETURN: /* close_notify received */ s_log(LOG_DEBUG, "SSL closed on SSL_write"); ssl_rd=0; break; case SSL_ERROR_SSL: sslerror("SSL_write"); return -1; default: s_log(LOG_ERR, "SSL_write/SSL_get_error returned %d", err); return -1; } } /****************************** read from socket */ if(sock_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("readsocket")) return -1; break; case 0: /* close */ s_log(LOG_DEBUG, "Socket closed on read"); sock_rd=0; break; default: c->sock_ptr+=num; watchdog=0; /* reset watchdog */ } } /****************************** read from SSL */ if(ssl_rd && c->ssl_ptr<BUFFSIZE && ( /* input buffer not full */ ssl_can_rd || (want_wr && ssl_can_wr) || /* SSL_read wants to write to the underlying descriptor */ (check_SSL_pending && SSL_pending(c->ssl)) /* write made space from full buffer */ )) { num=SSL_read(c->ssl, c->ssl_buff+c->ssl_ptr, BUFFSIZE-c->ssl_ptr); switch(err=SSL_get_error(c->ssl, num)) { case SSL_ERROR_NONE: c->ssl_ptr+=num; watchdog=0; /* reset watchdog */ break; case SSL_ERROR_WANT_WRITE: s_log(LOG_DEBUG, "SSL_read returned WANT_WRITE: retrying"); break; case SSL_ERROR_WANT_READ: s_log(LOG_DEBUG, "SSL_read returned WANT_READ: retrying"); 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) { /* EOF */ if(c->sock_ptr) { s_log(LOG_ERR, "SSL socket closed with %d byte(s) in buffer", c->sock_ptr); return -1; /* reset the socket */ } s_log(LOG_DEBUG, "SSL socket closed on SSL_read"); ssl_rd=ssl_wr=0; /* buggy or SSLv2 peer: no close_notify */ ssl_closing=CL_CLOSED; /* don't try to send it back */ } else if(parse_socket_error("SSL_read")) return -1; break; case SSL_ERROR_ZERO_RETURN: /* close_notify received */ s_log(LOG_DEBUG, "SSL closed on SSL_read"); ssl_rd=0; break; case SSL_ERROR_SSL: sslerror("SSL_read"); return -1; default: s_log(LOG_ERR, "SSL_read/SSL_get_error returned %d", err); return -1; } } /****************************** check write shutdown conditions */ if(sock_wr && !ssl_rd && !c->ssl_ptr) { s_log(LOG_DEBUG, "Socket write shutdown"); sock_wr=0; /* no further write allowed */ shutdown(c->sock_wfd->fd, SHUT_WR); /* send TCP FIN */ } if(ssl_wr && (!sock_rd || SSL_get_shutdown(c->ssl)) && !c->sock_ptr) { s_log(LOG_DEBUG, "SSL write shutdown"); ssl_wr=0; /* no further write allowed */ if(strcmp(SSL_get_version(c->ssl), "SSLv2")) { /* SSLv3, TLSv1 */ ssl_closing=CL_INIT; /* initiate close_notify */ } else { /* no alerts in SSLv2 including close_notify alert */ shutdown(c->sock_rfd->fd, SHUT_RD); /* notify the kernel */ shutdown(c->sock_wfd->fd, SHUT_WR); /* send TCP FIN */ SSL_set_shutdown(c->ssl, /* notify the OpenSSL library */ SSL_SENT_SHUTDOWN|SSL_RECEIVED_SHUTDOWN); ssl_rd=0; /* no further read allowed */ ssl_closing=CL_CLOSED; /* closed */ } } if(ssl_closing==CL_RETRY) { /* SSL shutdown */ if(!want_rd && !want_wr) { /* close_notify alert was received */ s_log(LOG_DEBUG, "SSL doesn't need to read or write"); ssl_closing=CL_CLOSED; } if(watchdog>5) { s_log(LOG_NOTICE, "Too many retries on SSL shutdown"); ssl_closing=CL_CLOSED; } } /****************************** 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 [email protected]"); s_log(LOG_ERR, "socket open: rd=%s wr=%s, ssl open: rd=%s wr=%s", sock_rd ? "yes" : "no", sock_wr ? "yes" : "no", ssl_rd ? "yes" : "no", ssl_wr ? "yes" : "no"); s_log(LOG_ERR, "socket ready: rd=%s wr=%s, ssl ready: rd=%s wr=%s", sock_can_rd ? "yes" : "no", sock_can_wr ? "yes" : "no", ssl_can_rd ? "yes" : "no", ssl_can_wr ? "yes" : "no"); s_log(LOG_ERR, "ssl want: rd=%s wr=%s", want_rd ? "yes" : "no", want_wr ? "yes" : "no"); s_log(LOG_ERR, "socket input buffer: %d byte(s), " "ssl input buffer: %d byte(s)", c->sock_ptr, c->ssl_ptr); s_log(LOG_ERR, "check_SSL_pending=%d, ssl_closing=%d", check_SSL_pending, ssl_closing); return -1; } } while(sock_wr || ssl_closing!=CL_CLOSED); return 0; /* OK */ }
static void client_run(CLI * c) { int error; c->remote_fd.fd = -1; c->fd = -1; c->ssl = NULL; c->sock_bytes = c->ssl_bytes = 0; c->fds = s_poll_alloc(); c->connect_addr.num = 0; c->connect_addr.addr = NULL; error = setjmp(c->err); if (!error) client_try(c); s_log(LOG_NOTICE, "Connection %s: %d byte(s) sent to SSL, %d byte(s) sent to socket", error == 1 ? "reset" : "closed", c->ssl_bytes, c->sock_bytes); if (c->fd >= 0) closesocket(c->fd); c->fd = -1; if (c->ssl) { SSL_set_shutdown(c->ssl, SSL_SENT_SHUTDOWN | SSL_RECEIVED_SHUTDOWN); SSL_free(c->ssl); c->ssl = NULL; ERR_remove_state(0); } if (c->remote_fd.fd >= 0) { if (error == 1 && c->remote_fd.is_socket) 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 = -1; } if (c->local_rfd.fd >= 0) { if (c->local_rfd.fd == c->local_wfd.fd) { if (error == 1 && 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 { if (error == 1 && c->local_rfd.is_socket) reset(c->local_rfd.fd, "linger (local_rfd)"); if (error == 1 && c->local_wfd.is_socket) reset(c->local_wfd.fd, "linger (local_wfd)"); } c->local_rfd.fd = c->local_wfd.fd = -1; } s_log(LOG_DEBUG, "Service [%s] finished", c->opt->servname); if (c->connect_addr.addr) str_free(c->connect_addr.addr); s_poll_free(c->fds); c->fds = NULL; }
static int connect_local(CLI *c) { /* spawn local process */ #if defined (USE_WIN32) || defined (__vms) s_log(LOG_ERR, "LOCAL MODE NOT SUPPORTED ON WIN32 and OpenVMS PLATFORM"); return -1; #else /* USE_WIN32, __vms */ char env[3][STRLEN], name[STRLEN], *portname; int fd[2], pid; X509 *peer; #ifdef HAVE_PTHREAD_SIGMASK sigset_t newmask; #endif if (c->opt->option.pty) { char tty[STRLEN]; if(pty_allocate(fd, fd+1, tty, STRLEN)) { return -1; } s_log(LOG_DEBUG, "%s allocated", tty); } else { if(make_sockets(fd)) return -1; } pid=fork(); c->pid=(unsigned long)pid; switch(pid) { case -1: /* error */ closesocket(fd[0]); closesocket(fd[1]); ioerror("fork"); return -1; case 0: /* child */ closesocket(fd[0]); dup2(fd[1], 0); dup2(fd[1], 1); if(!options.option.foreground) dup2(fd[1], 2); closesocket(fd[1]); safecopy(env[0], "REMOTE_HOST="); safeconcat(env[0], c->accepting_address); portname=strrchr(env[0], ':'); if(portname) /* strip the port name */ *portname='\0'; putenv(env[0]); if(c->opt->option.transparent) { putenv("LD_PRELOAD=" LIBDIR "/libstunnel.so"); /* For Tru64 _RLD_LIST is used instead */ putenv("_RLD_LIST=" LIBDIR "/libstunnel.so:DEFAULT"); } if(c->ssl) { peer=SSL_get_peer_certificate(c->ssl); if(peer) { safecopy(env[1], "SSL_CLIENT_DN="); X509_NAME_oneline(X509_get_subject_name(peer), name, STRLEN); safestring(name); safeconcat(env[1], name); putenv(env[1]); safecopy(env[2], "SSL_CLIENT_I_DN="); X509_NAME_oneline(X509_get_issuer_name(peer), name, STRLEN); safestring(name); safeconcat(env[2], name); putenv(env[2]); X509_free(peer); } } #ifdef HAVE_PTHREAD_SIGMASK sigemptyset(&newmask); sigprocmask(SIG_SETMASK, &newmask, NULL); #endif execvp(c->opt->execname, c->opt->execargs); ioerror(c->opt->execname); /* execv failed */ _exit(1); default: break; } /* parent */ s_log(LOG_INFO, "Local mode child started (PID=%lu)", c->pid); closesocket(fd[1]); #ifdef FD_CLOEXEC fcntl(fd[0], F_SETFD, FD_CLOEXEC); #endif return fd[0]; #endif /* USE_WIN32,__vms */ }
void s_log(int level, const char *format, ...) { va_list ap; char *text, *stamp, *id; struct LIST *tmp; #ifdef USE_WIN32 DWORD libc_error; #else int libc_error; #endif int socket_error; time_t gmt; struct tm *timeptr; #if defined(HAVE_LOCALTIME_R) && defined(_REENTRANT) struct tm timestruct; #endif TLS_DATA *tls_data; tls_data=tls_get(); if(!tls_data) { tls_data=tls_alloc(NULL, NULL, "log"); s_log(LOG_ERR, "INTERNAL ERROR: Uninitialized TLS at %s, line %d", __FILE__, __LINE__); } /* performance optimization: skip the trivial case early */ if(log_mode==LOG_MODE_CONFIGURED && level>tls_data->opt->log_level) return; libc_error=get_last_error(); socket_error=get_last_socket_error(); /* format the id to be logged */ time(&gmt); #if defined(HAVE_LOCALTIME_R) && defined(_REENTRANT) timeptr=localtime_r(&gmt, ×truct); #else timeptr=localtime(&gmt); #endif stamp=str_printf("%04d.%02d.%02d %02d:%02d:%02d", timeptr->tm_year+1900, timeptr->tm_mon+1, timeptr->tm_mday, timeptr->tm_hour, timeptr->tm_min, timeptr->tm_sec); id=str_printf("LOG%d[%s]", level, tls_data->id); /* format the text to be logged */ va_start(ap, format); text=str_vprintf(format, ap); va_end(ap); safestring(text); CRYPTO_THREAD_read_lock(stunnel_locks[LOCK_LOG_MODE]); if(log_mode==LOG_MODE_BUFFER) { /* save the text to log it later */ CRYPTO_THREAD_write_lock(stunnel_locks[LOCK_LOG_BUFFER]); tmp=str_alloc_detached(sizeof(struct LIST)); tmp->next=NULL; tmp->opt=tls_data->opt; tmp->level=level; tmp->stamp=stamp; str_detach(tmp->stamp); tmp->id=id; str_detach(tmp->id); tmp->text=text; str_detach(tmp->text); if(tail) tail->next=tmp; else head=tmp; tail=tmp; CRYPTO_THREAD_write_unlock(stunnel_locks[LOCK_LOG_BUFFER]); } else { /* ready log the text directly */ log_raw(tls_data->opt, level, stamp, id, text); str_free(stamp); str_free(id); str_free(text); } CRYPTO_THREAD_read_unlock(stunnel_locks[LOCK_LOG_MODE]); set_last_error(libc_error); set_last_socket_error(socket_error); }
void child_status(void) { /* dead libwrap or 'exec' process detected */ int pid, status; #ifdef HAVE_WAIT_FOR_PID while((pid=wait_for_pid(-1, &status, WNOHANG))>0) { #else if((pid=wait(&status))>0) { #endif #ifdef WIFSIGNALED if(WIFSIGNALED(status)) { char *sig_name=signal_name(WTERMSIG(status)); s_log(LOG_INFO, "Child process %d terminated on %s", pid, sig_name); str_free(sig_name); } else { s_log(LOG_INFO, "Child process %d finished with code %d", pid, WEXITSTATUS(status)); } #else s_log(LOG_INFO, "Child process %d finished with status %d", pid, status); #endif } } #endif /* !defined(USE_OS2) */ #endif /* !defined(USE_WIN32) */ /**************************************** main loop accepting connections */ void daemon_loop(void) { while(1) { int temporary_lack_of_resources=0; int num=s_poll_wait(fds, -1, -1); if(num>=0) { SERVICE_OPTIONS *opt; s_log(LOG_DEBUG, "Found %d ready file descriptor(s)", num); if(service_options.log_level>=LOG_DEBUG) /* performance optimization */ s_poll_dump(fds, LOG_DEBUG); if(s_poll_canread(fds, signal_pipe[0])) if(signal_pipe_dispatch()) /* SIGNAL_TERMINATE or error */ break; /* terminate daemon_loop */ for(opt=service_options.next; opt; opt=opt->next) if(opt->option.accept && s_poll_canread(fds, opt->fd)) if(accept_connection(opt)) temporary_lack_of_resources=1; } else { log_error(LOG_NOTICE, get_last_socket_error(), "daemon_loop: s_poll_wait"); temporary_lack_of_resources=1; } if(temporary_lack_of_resources) { s_log(LOG_NOTICE, "Accepting new connections suspended for 1 second"); sleep(1); /* to avoid log trashing */ } } } /* return 1 when a short delay is needed before another try */ NOEXPORT int accept_connection(SERVICE_OPTIONS *opt) { SOCKADDR_UNION addr; char *from_address; SOCKET s; socklen_t addrlen; addrlen=sizeof addr; for(;;) { s=s_accept(opt->fd, &addr.sa, &addrlen, 1, "local socket"); if(s!=INVALID_SOCKET) /* success! */ break; switch(get_last_socket_error()) { case S_EINTR: /* interrupted by a signal */ break; /* retry now */ case S_EMFILE: #ifdef S_ENFILE case S_ENFILE: #endif #ifdef S_ENOBUFS case S_ENOBUFS: #endif #ifdef S_ENOMEM case S_ENOMEM: #endif return 1; /* temporary lack of resources */ default: return 0; /* any other error */ } } from_address=s_ntop(&addr, addrlen); s_log(LOG_DEBUG, "Service [%s] accepted (FD=%ld) from %s", opt->servname, (long)s, from_address); str_free(from_address); #ifdef USE_FORK RAND_add("", 1, 0.0); /* each child needs a unique entropy pool */ #else if(max_clients && num_clients>=max_clients) { s_log(LOG_WARNING, "Connection rejected: too many clients (>=%ld)", max_clients); closesocket(s); return 0; } #endif if(create_client(opt->fd, s, alloc_client_session(opt, s, s), client_thread)) { s_log(LOG_ERR, "Connection rejected: create_client failed"); closesocket(s); return 0; } return 0; }
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 */ }
/* open new ports, update fds */ int bind_ports(void) { SERVICE_OPTIONS *opt; char *local_address; int listening_section; #ifdef HAVE_STRUCT_SOCKADDR_UN struct stat sb; /* buffer for lstat() */ #endif #ifdef USE_LIBWRAP /* execute after options_cmdline() to know service_options.next, * but as early as possible to avoid leaking file descriptors */ /* retry on each bind_ports() in case stunnel.conf was reloaded without "libwrap = no" */ libwrap_init(); #endif /* USE_LIBWRAP */ s_poll_init(fds); s_poll_add(fds, signal_pipe[0], 1, 0); /* allow clean unbind_ports() even though bind_ports() was not fully performed */ for(opt=service_options.next; opt; opt=opt->next) if(opt->option.accept) opt->fd=INVALID_SOCKET; listening_section=0; for(opt=service_options.next; opt; opt=opt->next) { if(opt->option.accept) { if(listening_section<systemd_fds) { opt->fd=(SOCKET)(listen_fds_start+listening_section); s_log(LOG_DEBUG, "Listening file descriptor received from systemd (FD=%ld)", (long)opt->fd); } else { opt->fd=s_socket(opt->local_addr.sa.sa_family, SOCK_STREAM, 0, 1, "accept socket"); if(opt->fd==INVALID_SOCKET) return 1; s_log(LOG_DEBUG, "Listening file descriptor created (FD=%ld)", (long)opt->fd); } if(set_socket_options(opt->fd, 0)<0) { closesocket(opt->fd); opt->fd=INVALID_SOCKET; return 1; } /* local socket can't be unnamed */ local_address=s_ntop(&opt->local_addr, addr_len(&opt->local_addr)); /* we don't bind or listen on a socket inherited from systemd */ if(listening_section>=systemd_fds) { if(bind(opt->fd, &opt->local_addr.sa, addr_len(&opt->local_addr))) { sockerror("bind"); s_log(LOG_ERR, "Error binding service [%s] to %s", opt->servname, local_address); closesocket(opt->fd); opt->fd=INVALID_SOCKET; str_free(local_address); return 1; } if(listen(opt->fd, SOMAXCONN)) { sockerror("listen"); closesocket(opt->fd); opt->fd=INVALID_SOCKET; str_free(local_address); return 1; } } #ifdef HAVE_STRUCT_SOCKADDR_UN /* chown the UNIX socket, errors are ignored */ if(opt->local_addr.sa.sa_family==AF_UNIX && (opt->uid || opt->gid)) { /* fchown() does *not* work on UNIX sockets */ if(!lchown(opt->local_addr.un.sun_path, opt->uid, opt->gid)) s_log(LOG_DEBUG, "Socket chown succeeded: %s, UID=%u, GID=%u", opt->local_addr.un.sun_path, (unsigned)opt->uid, (unsigned)opt->gid); else if(lstat(opt->local_addr.un.sun_path, &sb)) sockerror(opt->local_addr.un.sun_path); else if(sb.st_uid==opt->uid && sb.st_gid==opt->gid) s_log(LOG_DEBUG, "Socket chown unneeded: %s, UID=%u, GID=%u", opt->local_addr.un.sun_path, (unsigned)opt->uid, (unsigned)opt->gid); else s_log(LOG_ERR, "Socket chown failed: %s, UID=%u, GID=%u", opt->local_addr.un.sun_path, (unsigned)opt->uid, (unsigned)opt->gid); } #endif s_poll_add(fds, opt->fd, 1, 0); s_log(LOG_DEBUG, "Service [%s] (FD=%ld) bound to %s", opt->servname, (long)opt->fd, local_address); str_free(local_address); ++listening_section; } else if(opt->exec_name && opt->connect_addr.names) { /* create exec+connect services */ /* FIXME: needs to be delayed on reload with opt->option.retry set */ create_client(INVALID_SOCKET, INVALID_SOCKET, alloc_client_session(opt, INVALID_SOCKET, INVALID_SOCKET), client_thread); } } if(listening_section<systemd_fds) { s_log(LOG_ERR, "Too many listening file descriptors received from systemd, got %d", systemd_fds); return 1; } return 0; /* OK */ }
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 int signal_pipe_dispatch(void) { static int sig; static size_t ptr=0; ssize_t num; char *sig_name; s_log(LOG_DEBUG, "Dispatching signals from the signal pipe"); for(;;) { num=readsocket(signal_pipe[0], (char *)&sig+ptr, sizeof sig-ptr); if(num==-1 && get_last_socket_error()==S_EWOULDBLOCK) { s_log(LOG_DEBUG, "Signal pipe is empty"); return 0; } if(num==-1 || num==0) { if(num) sockerror("signal pipe read"); else s_log(LOG_ERR, "Signal pipe closed"); s_poll_remove(fds, signal_pipe[0]); closesocket(signal_pipe[0]); closesocket(signal_pipe[1]); if(signal_pipe_init()) { s_log(LOG_ERR, "Signal pipe reinitialization failed; terminating"); return 1; } s_poll_add(fds, signal_pipe[0], 1, 0); s_log(LOG_ERR, "Signal pipe reinitialized"); return 0; } ptr+=(size_t)num; if(ptr<sizeof sig) { s_log(LOG_DEBUG, "Incomplete signal pipe read (ptr=%ld)", (long)ptr); return 0; } ptr=0; switch(sig) { #ifndef USE_WIN32 case SIGCHLD: s_log(LOG_DEBUG, "Processing SIGCHLD"); #ifdef USE_FORK client_status(); /* report status of client process */ #else /* USE_UCONTEXT || USE_PTHREAD */ child_status(); /* report status of libwrap or 'exec' process */ #endif /* defined USE_FORK */ break; #endif /* !defind USE_WIN32 */ case SIGNAL_RELOAD_CONFIG: s_log(LOG_DEBUG, "Processing SIGNAL_RELOAD_CONFIG"); if(options_parse(CONF_RELOAD)) { s_log(LOG_ERR, "Failed to reload the configuration file"); } else { unbind_ports(); log_close(); options_apply(); log_open(); ui_config_reloaded(); if(bind_ports()) { /* FIXME: handle the error */ } } break; case SIGNAL_REOPEN_LOG: s_log(LOG_DEBUG, "Processing SIGNAL_REOPEN_LOG"); log_close(); log_open(); s_log(LOG_NOTICE, "Log file reopened"); break; case SIGNAL_TERMINATE: s_log(LOG_DEBUG, "Processing SIGNAL_TERMINATE"); s_log(LOG_NOTICE, "Terminated"); return 1; default: sig_name=signal_name(sig); s_log(LOG_ERR, "Received %s; terminating", sig_name); str_free(sig_name); return 1; } } }
NOEXPORT void init_local(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, 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, 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); }
static void init_ssl(CLI * c) { int i, err; SSL_SESSION *old_session; c->ssl = SSL_new(c->opt->ctx); if (!c->ssl) { sslerror("SSL_new"); longjmp(c->err, 1); } SSL_set_ex_data(c->ssl, cli_index, c); if (c->opt->option.client) { if (c->opt->session) { SSL_set_session(c->ssl, c->opt->session); } SSL_set_fd(c->ssl, c->remote_fd.fd); SSL_set_connect_state(c->ssl); } else { if (c->local_rfd.fd == c->local_wfd.fd) SSL_set_fd(c->ssl, c->local_rfd.fd); else { SSL_set_rfd(c->ssl, c->local_rfd.fd); SSL_set_wfd(c->ssl, c->local_wfd.fd); } SSL_set_accept_state(c->ssl); } 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); } while (1) { if (c->opt->option.client) i = SSL_connect(c->ssl); else i = SSL_accept(c->ssl); err = SSL_get_error(c->ssl, i); if (err == SSL_ERROR_NONE) break; 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("init_ssl: s_poll_wait"); longjmp(c->err, 1); case 0: s_log(LOG_INFO, "init_ssl: s_poll_wait:" " TIMEOUTbusy exceeded: sending reset"); longjmp(c->err, 1); case 1: break; default: s_log(LOG_ERR, "init_ssl: s_poll_wait: unknown result"); longjmp(c->err, 1); } continue; } 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); } if (SSL_session_reused(c->ssl)) { s_log(LOG_INFO, "SSL %s: previous session reused", c->opt->option.client ? "connected" : "accepted"); } else { if (c->opt->option.client) { s_log(LOG_INFO, "SSL connected: new session negotiated"); old_session = c->opt->session; c->opt->session = SSL_get1_session(c->ssl); if (old_session) SSL_SESSION_free(old_session); } else s_log(LOG_INFO, "SSL accepted: new session negotiated"); print_cipher(c); } }
/****************************** transfer data */ NOEXPORT void transfer(CLI *c) { int watchdog=0; /* a counter to detect an infinite loop */ int num, 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; 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); /* 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 */ } } /****************************** check for errors on sockets */ err=s_poll_error(c->fds, c->sock_rfd->fd); if(err && err!=S_EWOULDBLOCK && err!=S_EAGAIN) { s_log(LOG_NOTICE, "Read socket error: %s (%d)", s_strerror(err), err); longjmp(c->err, 1); } if(c->sock_wfd->fd!=c->sock_rfd->fd) { /* performance optimization */ err=s_poll_error(c->fds, c->sock_wfd->fd); if(err && err!=S_EWOULDBLOCK && err!=S_EAGAIN) { s_log(LOG_NOTICE, "Write socket error: %s (%d)", s_strerror(err), err); longjmp(c->err, 1); } } err=s_poll_error(c->fds, c->ssl_rfd->fd); if(err && err!=S_EWOULDBLOCK && err!=S_EAGAIN) { s_log(LOG_NOTICE, "SSL socket error: %s (%d)", s_strerror(err), err); longjmp(c->err, 1); } if(c->ssl_wfd->fd!=c->ssl_rfd->fd) { /* performance optimization */ err=s_poll_error(c->fds, c->ssl_wfd->fd); if(err && err!=S_EWOULDBLOCK && err!=S_EAGAIN) { s_log(LOG_NOTICE, "SSL socket error: %s (%d)", s_strerror(err), err); longjmp(c->err, 1); } } /****************************** check for hangup conditions */ if(s_poll_hup(c->fds, c->sock_rfd->fd)) { s_log(LOG_INFO, "Read socket closed (hangup)"); sock_open_rd=0; } if(s_poll_hup(c->fds, c->sock_wfd->fd)) { if(c->ssl_ptr) { s_log(LOG_ERR, "Write socket closed (hangup) with %d unsent byte(s)", c->ssl_ptr); longjmp(c->err, 1); /* reset the socket */ } s_log(LOG_INFO, "Write socket closed (hangup)"); sock_open_wr=0; } if(s_poll_hup(c->fds, c->ssl_rfd->fd) || s_poll_hup(c->fds, c->ssl_wfd->fd)) { /* hangup -> 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 (hangup) with %d unsent byte(s)", c->sock_ptr); longjmp(c->err, 1); /* reset the socket */ } s_log(LOG_INFO, "SSL socket closed (hangup)"); SSL_set_shutdown(c->ssl, SSL_SENT_SHUTDOWN|SSL_RECEIVED_SHUTDOWN); } /****************************** 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); 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, 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); } } /****************************** 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+=num; watchdog=0; /* reset watchdog */ } } /****************************** 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-num); c->ssl_ptr-=num; c->sock_bytes+=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; /****************************** 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, BUFFSIZE-c->ssl_ptr); switch(err=SSL_get_error(c->ssl, num)) { case SSL_ERROR_NONE: if(num==0) s_log(LOG_DEBUG, "SSL_read returned 0"); c->ssl_ptr+=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: /* is it possible? */ s_log(LOG_DEBUG, "SSL_read returned WANT_READ: retrying"); 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 %d 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); } } /****************************** 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, c->sock_ptr); switch(err=SSL_get_error(c->ssl, 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-num); c->sock_ptr-=num; c->ssl_bytes+=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 %d 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); } } /****************************** 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 [email protected]"); 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: %d byte(s), " "ssl input buffer: %d 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); }
static void transfer(CLI * c) { int watchdog = 0; int num, err; int sock_open_rd = 1, sock_open_wr = 1; int shutdown_wants_read = 0, shutdown_wants_write = 0; int read_wants_read, read_wants_write = 0; int write_wants_read = 0, write_wants_write; int sock_can_rd, sock_can_wr, ssl_can_rd, ssl_can_wr; c->sock_ptr = c->ssl_ptr = 0; do { 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; s_poll_init(c->fds); if (sock_open_rd) s_poll_add(c->fds, c->sock_rfd->fd, c->sock_ptr < BUFFSIZE, 0); if (sock_open_wr) s_poll_add(c->fds, c->sock_wfd->fd, 0, c->ssl_ptr); if (read_wants_read || write_wants_read || shutdown_wants_read) s_poll_add(c->fds, c->ssl_rfd->fd, 1, 0); if (read_wants_write || write_wants_write || shutdown_wants_write) s_poll_add(c->fds, c->ssl_wfd->fd, 0, 1); err = s_poll_wait(c->fds, (sock_open_rd && !(SSL_get_shutdown(c->ssl) & SSL_RECEIVED_SHUTDOWN)) || c->ssl_ptr || c->sock_ptr ? c->opt->timeout_idle : c->opt->timeout_close, 0); switch (err) { case -1: sockerror("transfer: s_poll_wait"); longjmp(c->err, 1); case 0: 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 { s_log(LOG_ERR, "transfer: s_poll_wait:" " TIMEOUTclose exceeded: closing"); return; } } err = s_poll_error(c->fds, c->sock_rfd); if (err) { s_log(LOG_NOTICE, "Error detected on socket (read) file descriptor: %s (%d)", s_strerror(err), err); longjmp(c->err, 1); } if (c->sock_wfd->fd != c->sock_rfd->fd) { err = s_poll_error(c->fds, c->sock_wfd); if (err) { s_log(LOG_NOTICE, "Error detected on socket write file descriptor: %s (%d)", s_strerror(err), err); longjmp(c->err, 1); } } err = s_poll_error(c->fds, c->ssl_rfd); if (err) { s_log(LOG_NOTICE, "Error detected on SSL (read) file descriptor: %s (%d)", s_strerror(err), err); longjmp(c->err, 1); } if (c->ssl_wfd->fd != c->ssl_rfd->fd) { err = s_poll_error(c->fds, c->ssl_wfd); if (err) { s_log(LOG_NOTICE, "Error detected on SSL write file descriptor: %s (%d)", s_strerror(err), err); longjmp(c->err, 1); } } 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); 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); longjmp(c->err, 1); } if (shutdown_wants_read || shutdown_wants_write) { num = SSL_shutdown(c->ssl); if (num < 0) err = SSL_get_error(c->ssl, num); else err = SSL_ERROR_NONE; switch (err) { case SSL_ERROR_NONE: s_log(LOG_INFO, "SSL_shutdown successfully sent close_notify alert"); shutdown_wants_read = shutdown_wants_write = 0; break; case SSL_ERROR_SYSCALL: if (parse_socket_error(c, "SSL_shutdown")) break; 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: sslerror("SSL_shutdown"); longjmp(c->err, 1); default: s_log(LOG_ERR, "SSL_shutdown/SSL_get_error returned %d", err); longjmp(c->err, 1); } } 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; case 0: s_log(LOG_DEBUG, "Socket closed on read"); sock_open_rd = 0; break; default: c->sock_ptr += num; watchdog = 0; } } if (sock_open_wr && sock_can_wr) { num = writesocket(c->sock_wfd->fd, c->ssl_buff, c->ssl_ptr); switch (num) { case -1: if (parse_socket_error(c, "writesocket")) break; case 0: s_log(LOG_DEBUG, "Socket closed on write"); sock_open_rd = sock_open_wr = 0; break; default: memmove(c->ssl_buff, c->ssl_buff + num, c->ssl_ptr - num); c->ssl_ptr -= num; c->sock_bytes += num; watchdog = 0; } } 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; if ((read_wants_read && (ssl_can_rd || SSL_pending(c->ssl))) || (read_wants_write && ssl_can_wr)) { read_wants_write = 0; num = SSL_read(c->ssl, c->ssl_buff + c->ssl_ptr, BUFFSIZE - c->ssl_ptr); switch (err = SSL_get_error(c->ssl, num)) { case SSL_ERROR_NONE: if (num == 0) s_log(LOG_DEBUG, "SSL_read returned 0"); c->ssl_ptr += num; watchdog = 0; 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: 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; if (c->sock_ptr) { s_log(LOG_ERR, "SSL socket closed on SSL_read with %d unsent byte(s)", c->sock_ptr); longjmp(c->err, 1); } s_log(LOG_DEBUG, "SSL socket closed on SSL_read"); SSL_set_shutdown(c->ssl, SSL_SENT_SHUTDOWN | SSL_RECEIVED_SHUTDOWN); break; case SSL_ERROR_ZERO_RETURN: s_log(LOG_DEBUG, "SSL closed on 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); } } if ((write_wants_read && ssl_can_rd) || (write_wants_write && ssl_can_wr)) { write_wants_read = 0; num = SSL_write(c->ssl, c->sock_buff, c->sock_ptr); switch (err = SSL_get_error(c->ssl, 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 - num); c->sock_ptr -= num; c->ssl_bytes += num; watchdog = 0; break; case SSL_ERROR_WANT_WRITE: 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: if (num && parse_socket_error(c, "SSL_write")) break; if (c->sock_ptr) { s_log(LOG_ERR, "SSL socket closed on SSL_write with %d unsent byte(s)", c->sock_ptr); longjmp(c->err, 1); } s_log(LOG_DEBUG, "SSL socket closed on SSL_write"); SSL_set_shutdown(c->ssl, SSL_SENT_SHUTDOWN | SSL_RECEIVED_SHUTDOWN); break; case SSL_ERROR_ZERO_RETURN: s_log(LOG_DEBUG, "SSL closed on 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); } } if (sock_open_wr && SSL_get_shutdown(c->ssl) & SSL_RECEIVED_SHUTDOWN && !c->ssl_ptr) { sock_open_wr = 0; if (!c->sock_wfd->is_socket) { s_log(LOG_DEBUG, "Closing the socket file descriptor"); sock_open_rd = 0; } else if (!shutdown(c->sock_wfd->fd, SHUT_WR)) { s_log(LOG_DEBUG, "Sent socket write shutdown"); } else { s_log(LOG_DEBUG, "Failed to send socket write shutdown"); sock_open_rd = 0; } } if (!(SSL_get_shutdown(c->ssl) & SSL_SENT_SHUTDOWN) && !sock_open_rd && !c->sock_ptr) { if (SSL_version(c->ssl) != SSL2_VERSION) { s_log(LOG_DEBUG, "Sending close_notify alert"); shutdown_wants_write = 1; } else { s_log(LOG_DEBUG, "Closing SSLv2 socket"); if (c->ssl_rfd->is_socket) shutdown(c->ssl_rfd->fd, SHUT_RD); if (c->ssl_wfd->is_socket) shutdown(c->ssl_wfd->fd, SHUT_WR); SSL_set_shutdown(c->ssl, SSL_SENT_SHUTDOWN | SSL_RECEIVED_SHUTDOWN); } } if (++watchdog > 100) { s_log(LOG_ERR, "transfer() loop executes not transferring any data"); wifisec_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: %d byte(s), " "ssl input buffer: %d 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); }
/* based on the BSD-style licensed code of mod_ssl */ NOEXPORT int crl_check(X509_STORE_CTX *callback_ctx) { SSL *ssl; CLI *c; X509_STORE_CTX store_ctx; X509_OBJECT obj; X509_NAME *subject; X509_NAME *issuer; X509 *cert; X509_CRL *crl; X509_REVOKED *revoked; EVP_PKEY *pubkey; long serial; int i, n, rc; char *cp; ASN1_TIME *last_update=NULL, *next_update=NULL; ssl=X509_STORE_CTX_get_ex_data(callback_ctx, SSL_get_ex_data_X509_STORE_CTX_idx()); c=SSL_get_ex_data(ssl, cli_index); cert=X509_STORE_CTX_get_current_cert(callback_ctx); subject=X509_get_subject_name(cert); issuer=X509_get_issuer_name(cert); /* try to retrieve a CRL corresponding to the _subject_ of * the current certificate in order to verify it's integrity */ memset((char *)&obj, 0, sizeof obj); X509_STORE_CTX_init(&store_ctx, c->opt->revocation_store, NULL, NULL); rc=X509_STORE_get_by_subject(&store_ctx, X509_LU_CRL, subject, &obj); X509_STORE_CTX_cleanup(&store_ctx); crl=obj.data.crl; if(rc>0 && crl) { cp=X509_NAME2text(subject); s_log(LOG_INFO, "CRL: issuer: %s", cp); str_free(cp); last_update=X509_CRL_get_lastUpdate(crl); next_update=X509_CRL_get_nextUpdate(crl); log_time(LOG_INFO, "CRL: last update", last_update); log_time(LOG_INFO, "CRL: next update", next_update); /* verify the signature on this CRL */ pubkey=X509_get_pubkey(cert); if(X509_CRL_verify(crl, pubkey)<=0) { s_log(LOG_WARNING, "CRL: Invalid signature"); X509_STORE_CTX_set_error(callback_ctx, X509_V_ERR_CRL_SIGNATURE_FAILURE); X509_OBJECT_free_contents(&obj); if(pubkey) EVP_PKEY_free(pubkey); return 0; /* reject */ } if(pubkey) EVP_PKEY_free(pubkey); /* check date of CRL to make sure it's not expired */ if(!next_update) { s_log(LOG_WARNING, "CRL: Invalid nextUpdate field"); X509_STORE_CTX_set_error(callback_ctx, X509_V_ERR_ERROR_IN_CRL_NEXT_UPDATE_FIELD); X509_OBJECT_free_contents(&obj); return 0; /* reject */ } if(X509_cmp_current_time(next_update)<0) { s_log(LOG_WARNING, "CRL: CRL Expired - revoking all certificates"); X509_STORE_CTX_set_error(callback_ctx, X509_V_ERR_CRL_HAS_EXPIRED); X509_OBJECT_free_contents(&obj); return 0; /* reject */ } X509_OBJECT_free_contents(&obj); } /* try to retrieve a CRL corresponding to the _issuer_ of * the current certificate in order to check for revocation */ memset((char *)&obj, 0, sizeof obj); X509_STORE_CTX_init(&store_ctx, c->opt->revocation_store, NULL, NULL); rc=X509_STORE_get_by_subject(&store_ctx, X509_LU_CRL, issuer, &obj); X509_STORE_CTX_cleanup(&store_ctx); crl=obj.data.crl; if(rc>0 && crl) { /* check if the current certificate is revoked by this CRL */ n=sk_X509_REVOKED_num(X509_CRL_get_REVOKED(crl)); for(i=0; i<n; i++) { revoked=sk_X509_REVOKED_value(X509_CRL_get_REVOKED(crl), i); if(ASN1_INTEGER_cmp(revoked->serialNumber, X509_get_serialNumber(cert))==0) { serial=ASN1_INTEGER_get(revoked->serialNumber); cp=X509_NAME2text(issuer); s_log(LOG_WARNING, "CRL: Certificate with serial %ld (0x%lX) " "revoked per CRL from issuer %s", serial, serial, cp); str_free(cp); X509_STORE_CTX_set_error(callback_ctx, X509_V_ERR_CERT_REVOKED); X509_OBJECT_free_contents(&obj); return 0; /* reject */ } } X509_OBJECT_free_contents(&obj); } return 1; /* accept */ }
NOEXPORT void imap_server(CLI *c) { char *line, *id, *tail, *capa; s_poll_init(c->fds); s_poll_add(c->fds, c->local_rfd.fd, 1, 0); switch(s_poll_wait(c->fds, 0, 200)) { case 0: /* fd not ready to read */ s_log(LOG_DEBUG, "RFC 2595 detected"); break; case 1: /* fd ready to read */ s_log(LOG_DEBUG, "RFC 2595 not detected"); return; /* return if RFC 2595 is not used */ default: /* -1 */ sockerror("RFC2595 (s_poll_wait)"); longjmp(c->err, 1); } /* process server welcome and send it to client */ line=fd_getline(c, c->remote_fd.fd); if(!is_prefix(line, "* OK")) { s_log(LOG_ERR, "Unknown server welcome"); str_free(line); longjmp(c->err, 1); } capa=strstr(line, "CAPABILITY"); if(!capa) capa=strstr(line, "capability"); if(capa) *capa='K'; /* disable CAPABILITY within greeting */ fd_printf(c, c->local_wfd.fd, "%s (stunnel)", line); id=str_dup(""); while(1) { /* process client commands */ str_free(line); line=fd_getline(c, c->local_rfd.fd); /* split line into id and tail */ str_free(id); id=str_dup(line); tail=strchr(id, ' '); if(!tail) break; *tail++='\0'; if(is_prefix(tail, "STARTTLS")) { fd_printf(c, c->local_wfd.fd, "%s OK Begin TLS negotiation now", id); str_free(line); str_free(id); return; /* success */ } else if(is_prefix(tail, "CAPABILITY")) { fd_putline(c, c->remote_fd.fd, line); /* send it to server */ str_free(line); line=fd_getline(c, c->remote_fd.fd); /* get the capabilites */ if(*line=='*') { /* * append STARTTLS * should also add LOGINDISABLED, but can't because * of Mozilla bug #324138/#312009 * LOGIN would fail as "unexpected command", anyway */ fd_printf(c, c->local_wfd.fd, "%s STARTTLS", line); str_free(line); line=fd_getline(c, c->remote_fd.fd); /* next line */ } fd_putline(c, c->local_wfd.fd, line); /* forward to the client */ tail=strchr(line, ' '); if(!tail || !is_prefix(tail+1, "OK")) { /* not OK? */ fd_putline(c, c->local_wfd.fd, "* BYE unexpected server response"); s_log(LOG_ERR, "Unexpected server response: %s", line); break; } } else if(is_prefix(tail, "LOGOUT")) { fd_putline(c, c->local_wfd.fd, "* BYE server terminating"); fd_printf(c, c->local_wfd.fd, "%s OK LOGOUT completed", id); break; } else { fd_putline(c, c->local_wfd.fd, "* BYE stunnel: unexpected command"); fd_printf(c, c->local_wfd.fd, "%s BAD %s unexpected", id, tail); s_log(LOG_ERR, "Unexpected client command %s", tail); break; } } /* clean server shutdown */ str_free(id); fd_putline(c, c->remote_fd.fd, "stunnel LOGOUT"); str_free(line); line=fd_getline(c, c->remote_fd.fd); if(*line=='*') { str_free(line); line=fd_getline(c, c->remote_fd.fd); } str_free(line); longjmp(c->err, 2); /* don't reset */ }