--- src/network.c 2011-05-01 23:28:59.000000000 +0200 +++ src/network.c 2011-05-08 09:45:04.000000000 +0200 @@ -119,10 +119,10 @@ int retval, retry; CONTEXT *context, *prev; int min_timeout; - int nfds, i; + unsigned int nfds, i; time_t now; short *signal_revents; - static int max_nfds=0; + static unsigned int max_nfds=0; static struct pollfd *ufds=NULL; time(&now); @@ -216,51 +216,56 @@ } int s_poll_wait(s_poll_set *fds, int sec, int msec) { - /* FIXME: msec parameter is currently ignored with UCONTEXT threads */ CONTEXT *context; /* current context */ static CONTEXT *to_free=NULL; /* delayed memory deallocation */ + /* FIXME: msec parameter is currently ignored with UCONTEXT threads */ + (void)msec; /* skip warning about unused parameter */ + /* remove the current context from ready queue */ context=ready_head; ready_head=ready_head->next; if(!ready_head) /* the queue is empty */ ready_tail=NULL; + /* it it safe to s_log() after new ready_head is set */ + + /* it's illegal to deallocate the stack of the current context */ + if(to_free) { /* a delayed deallocation is scheduled */ + s_log(LOG_DEBUG, "Releasing context %ld", to_free->id); + free(to_free->stack); + free(to_free); + to_free=NULL; + } + /* manage the current thread */ if(fds) { /* something to wait for -> swap the context */ context->fds=fds; /* set file descriptors to wait for */ context->finish=sec<0 ? -1 : time(NULL)+sec; - /* move (append) the current context to the waiting queue */ + + /* append the current context to the waiting queue */ context->next=NULL; if(waiting_tail) waiting_tail->next=context; waiting_tail=context; if(!waiting_head) waiting_head=context; - while(!ready_head) /* no context ready */ - scan_waiting_queue(); + } else { /* nothing to wait for -> drop the context */ + to_free=context; /* schedule for delayed deallocation */ + } + + while(!ready_head) /* wait until there is a thread to switch to */ + scan_waiting_queue(); + + /* switch threads */ + if(fds) { /* swap the current context */ if(context->id!=ready_head->id) { s_log(LOG_DEBUG, "Context swap: %ld -> %ld", context->id, ready_head->id); swapcontext(&context->context, &ready_head->context); s_log(LOG_DEBUG, "Current context: %ld", ready_head->id); - if(to_free) { - s_log(LOG_DEBUG, "Releasing context %ld", to_free->id); - free(to_free->stack); - free(to_free); - to_free=NULL; - } } return ready_head->ready; - } else { /* nothing to wait for -> drop the context */ - /* it's illegal to deallocate the stack of the current context */ - if(to_free) { - s_log(LOG_DEBUG, "Releasing context %ld", to_free->id); - free(to_free->stack); - free(to_free); - } - to_free=context; - while(!ready_head) /* no context ready */ - scan_waiting_queue(); + } else { /* drop the current context */ s_log(LOG_DEBUG, "Context set: %ld (dropped) -> %ld", context->id, ready_head->id); setcontext(&ready_head->context); @@ -701,6 +706,10 @@ longjmp(c->err, 1); /* error */ } line=str_realloc(line, ptr+1); + if(!line) { + s_log(LOG_CRIT, "Memory allocation failed"); + longjmp(c->err, 1); /* error */ + } switch(readsocket(fd, line+ptr, 1)) { case -1: /* error */ sockerror("readsocket (fdgetline)"); --- src/sthreads.c 2011-05-02 23:58:45.000000000 +0200 +++ src/sthreads.c 2011-05-10 11:45:18.000000000 +0200 @@ -47,10 +47,12 @@ /* no need for critical sections */ void enter_critical_section(SECTION_CODE i) { + (void)i; /* skip warning about unused parameter */ /* empty */ } void leave_critical_section(SECTION_CODE i) { + (void)i; /* skip warning about unused parameter */ /* empty */ } @@ -76,7 +78,6 @@ /* first context on the ready list is the active context */ CONTEXT *ready_head=NULL, *ready_tail=NULL; /* ready to execute */ CONTEXT *waiting_head=NULL, *waiting_tail=NULL; /* waiting on poll() */ -int next_id=1; unsigned long stunnel_process_id(void) { return (unsigned long)getpid(); @@ -86,63 +87,48 @@ return ready_head ? ready_head->id : 0; } -static CONTEXT *new_context(int stack_size) { +static CONTEXT *new_context(void) { + static int next_id=1; CONTEXT *context; /* allocate and fill the CONTEXT structure */ - context=str_alloc(sizeof(CONTEXT)); + context=calloc(1, sizeof(CONTEXT)); if(!context) { s_log(LOG_ERR, "Unable to allocate CONTEXT structure"); return NULL; } - context->stack=str_alloc(stack_size); - if(!context->stack) { - s_log(LOG_ERR, "Unable to allocate CONTEXT stack"); - return NULL; - } context->id=next_id++; context->fds=NULL; context->ready=0; - /* some manuals claim that initialization of context structure is required */ - if(getcontext(&context->context)<0) { - str_free(context->stack); - str_free(context); - ioerror("getcontext"); - return NULL; - } - context->context.uc_link=NULL; /* it should never happen */ -#if defined(__sgi) || ARGC==2 /* obsolete ss_sp semantics */ - context->context.uc_stack.ss_sp=context->stack+stack_size-8; -#else - context->context.uc_stack.ss_sp=context->stack; -#endif - context->context.uc_stack.ss_size=stack_size; - context->context.uc_stack.ss_flags=0; - /* attach to the tail of the ready queue */ + /* append to the tail of the ready queue */ context->next=NULL; if(ready_tail) ready_tail->next=context; ready_tail=context; if(!ready_head) ready_head=context; + return context; } void sthreads_init(void) { /* create the first (listening) context and put it in the running queue */ - if(!new_context(DEFAULT_STACK_SIZE)) { + if(!new_context()) { s_log(LOG_ERR, "Unable create the listening context"); die(1); } + /* no need to initialize ucontext_t structure here + it will be initialied with swapcontext() call */ } int create_client(int ls, int s, CLI *arg, void *(*cli)(void *)) { CONTEXT *context; (void)ls; /* this parameter is only used with USE_FORK */ + s_log(LOG_DEBUG, "Creating a new context"); - context=new_context(arg->opt->stack_size); + context=new_context(); if(!context) { if(arg) free(arg); @@ -150,6 +136,38 @@ closesocket(s); return -1; } + + /* initialize context_t structure */ + if(getcontext(&context->context)<0) { + free(context); + if(arg) + free(arg); + if(s>=0) + closesocket(s); + ioerror("getcontext"); + return -1; + } + context->context.uc_link=NULL; /* stunnel does not use uc_link */ + + /* create stack */ + context->stack=calloc(1, arg->opt->stack_size); + if(!context->stack) { + free(context); + if(arg) + free(arg); + if(s>=0) + closesocket(s); + s_log(LOG_ERR, "Unable to allocate stack"); + return -1; + } +#if defined(__sgi) || ARGC==2 /* obsolete ss_sp semantics */ + context->context.uc_stack.ss_sp=context->stack+arg->opt->stack_size-8; +#else + context->context.uc_stack.ss_sp=context->stack; +#endif + context->context.uc_stack.ss_size=arg->opt->stack_size; + context->context.uc_stack.ss_flags=0; + s_log(LOG_DEBUG, "Context %ld created", context->id); makecontext(&context->context, (void(*)(void))cli, ARGC, arg); return 0; @@ -298,7 +316,7 @@ /* Disabled on OS X due to strange problems on Mac OS X 10.5 it seems to restore signal mask somewhere (I couldn't find where) effectively blocking signals after first accepted connection */ - sigset_t sigset; + sigset_t new_set, old_set; #endif /* HAVE_PTHREAD_SIGMASK && !__APPLE__*/ (void)ls; /* this parameter is only used with USE_FORK */ @@ -306,9 +324,8 @@ #if defined(HAVE_PTHREAD_SIGMASK) && !defined(__APPLE__) /* the idea is that only the main thread handles all the signals with * posix threads; signals are blocked for any other thread */ - sigfillset(&sigset); - pthread_sigmask(SIG_SETMASK, &sigset, NULL); /* block signals */ - sigemptyset(&sigset); /* prepare for unblocking */ + sigfillset(&new_set); + pthread_sigmask(SIG_SETMASK, &new_set, &old_set); /* block signals */ #endif /* HAVE_PTHREAD_SIGMASK && !__APPLE__*/ pthread_attr_init(&pth_attr); pthread_attr_setdetachstate(&pth_attr, PTHREAD_CREATE_DETACHED); @@ -316,7 +333,7 @@ error=pthread_create(&thread, &pth_attr, cli, arg); pthread_attr_destroy(&pth_attr); #if defined(HAVE_PTHREAD_SIGMASK) && !defined(__APPLE__) - pthread_sigmask(SIG_SETMASK, &sigset, NULL); /* unblock signals */ + pthread_sigmask(SIG_SETMASK, &old_set, NULL); /* unblock signals */ #endif /* HAVE_PTHREAD_SIGMASK && !__APPLE__*/ if(error) { --- src/client.c 2011-05-02 18:12:53.000000000 +0200 +++ src/client.c 2011-06-17 15:19:44.000000000 +0200 @@ -106,10 +106,8 @@ if(!c->opt->option.retry) break; sleep(1); /* FIXME: not a good idea in ucontext threading */ -#ifndef USE_UCONTEXT str_stats(); str_cleanup(); -#endif } } else run_client(c); @@ -119,18 +117,17 @@ #ifdef DEBUG_STACK_SIZE stack_info(0); /* display computed value */ #endif +#ifdef USE_UCONTEXT + s_log(LOG_DEBUG, "Context %ld closed", ready_head->id); +#endif str_stats(); -#if defined(USE_WIN32) && !defined(_WIN32_WCE) str_cleanup(); + /* s_log() is not allowed after str_cleanup() */ +#if defined(USE_WIN32) && !defined(_WIN32_WCE) _endthread(); #endif #ifdef USE_UCONTEXT - s_log(LOG_DEBUG, "Context %ld closed", ready_head->id); - /* no str_cleanup() here, as all contexts share the same CPU thread */ s_poll_wait(NULL, 0, 0); /* wait on poll() */ - s_log(LOG_ERR, "INTERNAL ERROR: failed to drop context"); -#else - str_cleanup(); #endif return NULL; } @@ -283,6 +280,15 @@ SSL_set_session_id_context(c->ssl, (unsigned char *)sid_ctx, strlen(sid_ctx)); if(c->opt->option.client) { +#ifndef OPENSSL_NO_TLSEXT + if(c->opt->host_name) { + s_log(LOG_DEBUG, "SNI host name: %s", c->opt->host_name); + if(!SSL_set_tlsext_host_name(c->ssl, c->opt->host_name)) { + 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); @@ -314,9 +320,9 @@ while(1) { #if OPENSSL_VERSION_NUMBER<0x1000002f - /* this critical section is a crude workaround for CVE-2010-3864 */ - /* see http://www.securityfocus.com/bid/44884 for details */ - /* NOTE: this critical section also covers callbacks (e.g. OCSP) */ + /* this critical section is a crude workaround for CVE-2010-3864 * + * see http://www.securityfocus.com/bid/44884 for details * + * NOTE: this critical section also covers callbacks (e.g. OCSP) */ enter_critical_section(CRIT_SSL); #endif /* OpenSSL version < 1.0.0b */ if(c->opt->option.client) @@ -821,22 +827,26 @@ 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'; @@ -846,9 +856,11 @@ safestring(user); s_log(LOG_WARNING, "Connection from %s REFUSED by IDENT (user %s)", c->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) @@ -878,8 +890,8 @@ execname_l=str2tstr(c->opt->execname); execargs_l=str2tstr(c->opt->execargs); CreateProcess(execname_l, execargs_l, NULL, NULL, TRUE, 0, NULL, NULL, &si, &pi); - free(execname_l); - free(execargs_l); + str_free(execname_l); + str_free(execargs_l); closesocket(fd[1]); CloseHandle(pi.hProcess); @@ -916,6 +928,7 @@ 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); @@ -969,9 +982,10 @@ s=s_socket(AF_INET, SOCK_STREAM, 0, 1, "socket#1"); if(s<0) longjmp(c->err, 1); - fd[1]=s_socket(AF_INET, SOCK_STREAM, 0, 1, "socket#2"); - if(fd[1]<0) + c->fd=s_socket(AF_INET, SOCK_STREAM, 0, 1, "socket#2"); + if(c->fd<0) longjmp(c->err, 1); + addrlen=sizeof addr; memset(&addr, 0, addrlen); addr.in.sin_family=AF_INET; @@ -979,22 +993,30 @@ addr.in.sin_port=htons(0); /* dynamic port allocation */ if(bind(s, &addr.sa, addrlen)) log_error(LOG_DEBUG, get_last_socket_error(), "bind#1"); - if(bind(fd[1], &addr.sa, addrlen)) + if(bind(c->fd, &addr.sa, addrlen)) log_error(LOG_DEBUG, get_last_socket_error(), "bind#2"); + if(listen(s, 1)) { + closesocket(s); sockerror("listen"); longjmp(c->err, 1); } if(getsockname(s, &addr.sa, &addrlen)) { + closesocket(s); sockerror("getsockname"); longjmp(c->err, 1); } - if(connect(fd[1], &addr.sa, addrlen)) { - sockerror("connect"); + if(connect_blocking(c, &addr, addr_len(addr))) { + closesocket(s); longjmp(c->err, 1); } - if((fd[0]=s_accept(s, &addr.sa, &addrlen, 1, "accept"))<0) + fd[0]=s_accept(s, &addr.sa, &addrlen, 1, "accept"); + if(fd[0]<0) { + closesocket(s); longjmp(c->err, 1); + } + fd[1]=c->fd; + c->fd=-1; closesocket(s); /* don't care about the result */ #else if(s_socketpair(AF_UNIX, SOCK_STREAM, 0, fd, 1, "socketpair")) --- src/str.c 2011-05-02 19:14:53.000000000 +0200 +++ src/str.c 2011-05-08 08:37:57.000000000 +0200 @@ -38,17 +38,128 @@ #include "common.h" #include "prototypes.h" -typedef struct str_struct { - struct str_struct *prev, *next; +#ifndef va_copy +#ifdef __va_copy +#define va_copy(dst, src) __va_copy((dst), (src)) +#else /* __va_copy */ +#define va_copy(dst, src) memcpy(&(dst), &(src), sizeof(va_list)) +#endif /* __va_copy */ +#endif /* va_copy */ + +typedef struct alloc_list { + struct alloc_list *prev, *next; size_t size; unsigned int magic; -} STR; -static void str_set(STR *); -static STR *str_get(); +} ALLOC_LIST; + +static void set_alloc_head(ALLOC_LIST *); +static ALLOC_LIST *get_alloc_head(); + +char *str_dup(const char *str) { + char *retval; + + retval=str_alloc(strlen(str)+1); + if(retval) + strcpy(retval, str); + return retval; +} + +char *str_printf(const char *format, ...) { + char *txt; + va_list arglist; + + va_start(arglist, format); + txt=str_vprintf(format, arglist); + va_end(arglist); + return txt; +} + +char *str_vprintf(const char *format, va_list start_ap) { + int n, size=64; + char *p, *np; + va_list ap; + + p=str_alloc(size); + if(!p) + return NULL; + for(;;) { + va_copy(ap, start_ap); + n=vsnprintf(p, size, format, ap); + if(n>-1 && n-1) /* glibc 2.1 */ + size=n+1; /* precisely what is needed */ + else /* glibc 2.0, WIN32, etc. */ + size*=2; /* twice the old size */ + np=str_realloc(p, size); + if(!np) { + str_free(p); + return NULL; + } + p=np; /* LOL */ + } +} + +#ifdef USE_UCONTEXT + +static ALLOC_LIST *alloc_tls=NULL; + +void str_init() { +} + +static void set_alloc_head(ALLOC_LIST *alloc_head) { + if(ready_head) + ready_head->tls=alloc_head; + else /* ucontext threads not initialized */ + alloc_tls=alloc_head; +} + +static ALLOC_LIST *get_alloc_head() { + if(ready_head) + return ready_head->tls; + else /* ucontext threads not initialized */ + return alloc_tls; +} + +#endif /* USE_UCONTEXT */ + +#ifdef USE_FORK + +static ALLOC_LIST *alloc_tls=NULL; + +void str_init() { +} + +static void set_alloc_head(ALLOC_LIST *alloc_head) { + alloc_tls=alloc_head; +} + +static ALLOC_LIST *get_alloc_head() { + return alloc_tls; +} + +#endif /* USE_FORK */ + +#ifdef USE_PTHREAD + +static pthread_key_t pthread_key; + +void str_init() { + pthread_key_create(&pthread_key, NULL); +} + +static void set_alloc_head(ALLOC_LIST *alloc_head) { + pthread_setspecific(pthread_key, alloc_head); +} + +static ALLOC_LIST *get_alloc_head() { + return pthread_getspecific(pthread_key); +} + +#endif /* USE_PTHREAD */ #ifdef USE_WIN32 -/* __thread does not work in mingw32 due to a bug in GCC */ static DWORD tls_index; void str_init() { @@ -59,97 +170,81 @@ } } -static void str_set(STR *str) { - if(!TlsSetValue(tls_index, str)) { +static void set_alloc_head(ALLOC_LIST *alloc_head) { + if(!TlsSetValue(tls_index, alloc_head)) { s_log(LOG_ERR, "TlsSetValue failed"); die(1); } } -static STR *str_get() { - STR *str; +static ALLOC_LIST *get_alloc_head() { + ALLOC_LIST *alloc_head; - str=TlsGetValue(tls_index); - if(!str && GetLastError()!=ERROR_SUCCESS) { + alloc_head=TlsGetValue(tls_index); + if(!alloc_head && GetLastError()!=ERROR_SUCCESS) { s_log(LOG_ERR, "TlsGetValue failed"); die(1); } - return str; -} - -#else - -/* gcc Thread-Local Storage */ -static __thread STR *root_str=NULL; - -void str_init() { - if(root_str) - s_log(LOG_WARNING, "str_init: Non-empty allocation list"); + return alloc_head; } -static void str_set(STR *str) { - root_str=str; -} - -static STR *str_get() { - return root_str; -} - -#endif +#endif /* USE_WIN32 */ void str_cleanup() { - STR *str, *tmp; + ALLOC_LIST *alloc_head, *tmp; - str=str_get(); - while(str) { - tmp=str; - str=tmp->next; + alloc_head=get_alloc_head(); + while(alloc_head) { + tmp=alloc_head; + alloc_head=tmp->next; free(tmp); } - str_set(NULL); + set_alloc_head(NULL); } void str_stats() { - STR *tmp; + ALLOC_LIST *tmp; int blocks=0, bytes=0; - for(tmp=str_get(); tmp; tmp=tmp->next) { + for(tmp=get_alloc_head(); tmp; tmp=tmp->next) { ++blocks; bytes+=tmp->size; } - s_log(LOG_DEBUG, "str_stats: %d blocks, %d bytes", blocks, bytes); + s_log(LOG_DEBUG, "str_stats: %d block(s), %d byte(s)", blocks, bytes); } void *str_alloc(size_t size) { - STR *str, *tmp; + ALLOC_LIST *alloc_head, *tmp; if(size>=1024*1024) /* huge allocations are not allowed */ return NULL; - tmp=calloc(1, sizeof(STR)+size); + tmp=calloc(1, sizeof(ALLOC_LIST)+size); if(!tmp) return NULL; - str=str_get(); + alloc_head=get_alloc_head(); tmp->prev=NULL; - tmp->next=str; + tmp->next=alloc_head; tmp->size=size; tmp->magic=0xdeadbeef; - if(str) - str->prev=tmp; - str_set(tmp); + if(alloc_head) + alloc_head->prev=tmp; + set_alloc_head(tmp); return tmp+1; } void *str_realloc(void *ptr, size_t size) { - STR *oldtmp, *tmp; + ALLOC_LIST *old_tmp, *tmp; if(!ptr) return str_alloc(size); - oldtmp=(STR *)ptr-1; - if(oldtmp->magic!=0xdeadbeef) { /* not allocated by str_alloc() */ + old_tmp=(ALLOC_LIST *)ptr-1; + if(old_tmp->magic!=0xdeadbeef) { /* not allocated by str_alloc() */ s_log(LOG_CRIT, "INTERNAL ERROR: str_realloc: Bad magic"); die(1); } - tmp=realloc(oldtmp, sizeof(STR)+size); + if(size>=1024*1024) /* huge allocations are not allowed */ + return NULL; + tmp=realloc(old_tmp, sizeof(ALLOC_LIST)+size); if(!tmp) return NULL; /* refresh all possibly invalidated pointers */ @@ -158,17 +253,17 @@ if(tmp->prev) tmp->prev->next=tmp; tmp->size=size; - if(str_get()==oldtmp) - str_set(tmp); + if(get_alloc_head()==old_tmp) + set_alloc_head(tmp); return tmp+1; } void str_free(void *ptr) { - STR *tmp; + ALLOC_LIST *tmp; if(!ptr) /* do not attempt to free null pointers */ return; - tmp=(STR *)ptr-1; + tmp=(ALLOC_LIST *)ptr-1; if(tmp->magic!=0xdeadbeef) { /* not allocated by str_alloc() */ s_log(LOG_CRIT, "INTERNAL ERROR: str_free: Bad magic"); die(1); @@ -178,54 +273,9 @@ tmp->next->prev=tmp->prev; if(tmp->prev) tmp->prev->next=tmp->next; - if(str_get()==tmp) - str_set(tmp->next); + if(get_alloc_head()==tmp) + set_alloc_head(tmp->next); free(tmp); } -char *str_dup(const char *str) { - char *retval; - - retval=str_alloc(strlen(str)+1); - if(retval) - strcpy(retval, str); - return retval; -} - -char *str_vprintf(const char *format, va_list start_ap) { - int n, size=64; - char *p, *np; - va_list ap; - - p=str_alloc(size); - if(!p) - return NULL; - for(;;) { - va_copy(ap, start_ap); - n=vsnprintf(p, size, format, ap); - if(n>-1 && n-1) /* glibc 2.1 */ - size=n+1; /* precisely what is needed */ - else /* glibc 2.0 */ - size*=2; /* twice the old size */ - np=str_realloc(p, size); - if(!np) { - str_free(p); - return NULL; - } - p=np; /* LOL */ - } -} - -char *str_printf(const char *format, ...) { - char *txt; - va_list arglist; - - va_start(arglist, format); - txt=str_vprintf(format, arglist); - va_end(arglist); - return txt; -} - /* end of str.c */