/* * stunnel TLS offloading and load-balancing proxy * Copyright (C) 1998-2017 Michal Trojnara * * This program is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the * Free Software Foundation; either version 2 of the License, or (at your * option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. * See the GNU General Public License for more details. * * You should have received a copy of the GNU General Public License along * with this program; if not, see . * * Linking stunnel statically or dynamically with other modules is making * a combined work based on stunnel. Thus, the terms and conditions of * the GNU General Public License cover the whole combination. * * In addition, as a special exception, the copyright holder of stunnel * gives you permission to combine stunnel with free software programs or * libraries that are released under the GNU LGPL and with code included * in the standard release of OpenSSL under the OpenSSL License (or * modified versions of such code, with unchanged license). You may copy * and distribute such a system following the terms of the GNU GPL for * stunnel and the licenses of the other code concerned. * * Note that people who make modified versions of stunnel are not obligated * to grant this special exception for their modified versions; it is their * choice whether to do so. The GNU General Public License gives permission * to release a modified version without this exception; this exception * also makes it possible to release a modified version which carries * forward this exception. */ #ifdef USE_OS2 #define INCL_DOSPROCESS #include #endif #include "common.h" #include "prototypes.h" /**************************************** thread ID callbacks */ #ifdef USE_UCONTEXT unsigned long stunnel_process_id(void) { return (unsigned long)getpid(); } unsigned long stunnel_thread_id(void) { return ready_head ? ready_head->id : 0; } #endif /* USE_UCONTEXT */ #ifdef USE_FORK unsigned long stunnel_process_id(void) { return (unsigned long)getpid(); } unsigned long stunnel_thread_id(void) { return 0L; } #endif /* USE_FORK */ #ifdef USE_PTHREAD unsigned long stunnel_process_id(void) { return (unsigned long)getpid(); } unsigned long stunnel_thread_id(void) { #if defined(SYS_gettid) && defined(__linux__) return (unsigned long)syscall(SYS_gettid); #else return (unsigned long)pthread_self(); #endif } #endif /* USE_PTHREAD */ #ifdef USE_WIN32 unsigned long stunnel_process_id(void) { return GetCurrentProcessId() & 0x00ffffff; } unsigned long stunnel_thread_id(void) { return GetCurrentThreadId() & 0x00ffffff; } #endif /* USE_WIN32 */ #if OPENSSL_VERSION_NUMBER>=0x10000000L && OPENSSL_VERSION_NUMBER<0x10100004L NOEXPORT void threadid_func(CRYPTO_THREADID *tid) { CRYPTO_THREADID_set_numeric(tid, stunnel_thread_id()); } #endif void thread_id_init(void) { #if OPENSSL_VERSION_NUMBER>=0x10000000L && OPENSSL_VERSION_NUMBER<0x10100000L CRYPTO_THREADID_set_callback(threadid_func); #endif #if OPENSSL_VERSION_NUMBER<0x10000000L || !defined(OPENSSL_NO_DEPRECATED) CRYPTO_set_id_callback(stunnel_thread_id); #endif } /**************************************** locking */ #ifdef USE_PTHREAD void stunnel_rwlock_init_debug(struct CRYPTO_dynlock_value *lock, const char *file, int line) { pthread_rwlock_init(&lock->rwlock, NULL); lock->init_file=file; lock->init_line=line; } void stunnel_read_lock_debug(struct CRYPTO_dynlock_value *lock, const char *file, int line) { pthread_rwlock_rdlock(&lock->rwlock); lock->read_lock_file=file; lock->read_lock_line=line; } void stunnel_write_lock_debug(struct CRYPTO_dynlock_value *lock, const char *file, int line) { pthread_rwlock_wrlock(&lock->rwlock); lock->write_lock_file=file; lock->write_lock_line=line; } void stunnel_read_unlock_debug(struct CRYPTO_dynlock_value *lock, const char *file, int line) { pthread_rwlock_unlock(&lock->rwlock); lock->read_unlock_file=file; lock->read_unlock_line=line; } void stunnel_write_unlock_debug(struct CRYPTO_dynlock_value *lock, const char *file, int line) { pthread_rwlock_unlock(&lock->rwlock); lock->write_unlock_file=file; lock->write_unlock_line=line; } void stunnel_rwlock_destroy_debug(struct CRYPTO_dynlock_value *lock, const char *file, int line) { pthread_rwlock_destroy(&lock->rwlock); lock->destroy_file=file; lock->destroy_line=line; str_free(lock); } #endif /* USE_PTHREAD */ #ifdef USE_WIN32 /* Slim Reader/Writer (SRW) Lock would be better than CRITICAL_SECTION, * but it is unsupported on Windows XP (and earlier versions of Windows): * https://msdn.microsoft.com/en-us/library/windows/desktop/aa904937%28v=vs.85%29.aspx */ void stunnel_rwlock_init_debug(struct CRYPTO_dynlock_value *lock, const char *file, int line) { InitializeCriticalSection(&lock->critical_section); lock->init_file=file; lock->init_line=line; } void stunnel_read_lock_debug(struct CRYPTO_dynlock_value *lock, const char *file, int line) { EnterCriticalSection(&lock->critical_section); lock->read_lock_file=file; lock->read_lock_line=line; } void stunnel_write_lock_debug(struct CRYPTO_dynlock_value *lock, const char *file, int line) { EnterCriticalSection(&lock->critical_section); lock->write_lock_file=file; lock->write_lock_line=line; } void stunnel_read_unlock_debug(struct CRYPTO_dynlock_value *lock, const char *file, int line) { LeaveCriticalSection(&lock->critical_section); lock->read_unlock_file=file; lock->read_unlock_line=line; } void stunnel_write_unlock_debug(struct CRYPTO_dynlock_value *lock, const char *file, int line) { LeaveCriticalSection(&lock->critical_section); lock->write_unlock_file=file; lock->write_unlock_line=line; } void stunnel_rwlock_destroy_debug(struct CRYPTO_dynlock_value *lock, const char *file, int line) { DeleteCriticalSection(&lock->critical_section); lock->destroy_file=file; lock->destroy_line=line; str_free(lock); } #endif /* USE_WIN32 */ #if defined(USE_PTHREAD) || defined(USE_WIN32) struct CRYPTO_dynlock_value stunnel_locks[STUNNEL_LOCKS]; #if OPENSSL_VERSION_NUMBER<0x10100004L #define CRYPTO_THREAD_lock_new() CRYPTO_get_new_dynlockid() #endif #if OPENSSL_VERSION_NUMBER<0x10100004L static struct CRYPTO_dynlock_value *lock_cs; NOEXPORT struct CRYPTO_dynlock_value *dyn_create_function(const char *file, int line) { struct CRYPTO_dynlock_value *lock; lock=str_alloc_detached(sizeof(struct CRYPTO_dynlock_value)); stunnel_rwlock_init_debug(lock, file, line); return lock; } NOEXPORT void dyn_lock_function(int mode, struct CRYPTO_dynlock_value *lock, const char *file, int line) { if(mode&CRYPTO_LOCK) { /* either CRYPTO_READ or CRYPTO_WRITE (but not both) are needed */ if(!(mode&CRYPTO_READ)==!(mode&CRYPTO_WRITE)) fatal("Invalid locking mode"); if(mode&CRYPTO_WRITE) stunnel_write_lock_debug(lock, file, line); else stunnel_read_lock_debug(lock, file, line); } else stunnel_write_unlock_debug(lock, file, line); } NOEXPORT void dyn_destroy_function(struct CRYPTO_dynlock_value *lock, const char *file, int line) { stunnel_rwlock_destroy_debug(lock, file, line); str_free(lock); } NOEXPORT void locking_callback(int mode, int type, const char *file, int line) { dyn_lock_function(mode, lock_cs+type, file, line); } #endif /* OPENSSL_VERSION_NUMBER<0x10100004L */ void locking_init(void) { size_t i; #if OPENSSL_VERSION_NUMBER<0x10100004L size_t num; #endif /* initialize stunnel critical sections */ for(i=0; iid=next_id++; context->fds=NULL; context->ready=0; /* 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; } int sthreads_init(void) { thread_id_init(); /* create the first (listening) context and put it in the running queue */ if(!new_context()) { s_log(LOG_ERR, "Cannot create the listening context"); return 1; } /* update tls for newly allocated ready_head */ ui_tls=tls_alloc(NULL, ui_tls, "ui"); /* no need to initialize ucontext_t structure here it will be initialied with swapcontext() call */ return 0; } int create_client(SOCKET ls, SOCKET 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(); if(!context) { str_free(arg); if(s>=0) closesocket(s); return -1; } /* initialize context_t structure */ if(getcontext(&context->context)<0) { str_free(context); str_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=str_alloc_detached(arg->opt->stack_size); #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; makecontext(&context->context, (void(*)(void))cli, ARGC, arg); s_log(LOG_DEBUG, "New context created"); return 0; } #endif /* USE_UCONTEXT */ #ifdef USE_FORK int sthreads_init(void) { thread_id_init(); return 0; } NOEXPORT void null_handler(int sig) { (void)sig; /* squash the unused parameter warning */ signal(SIGCHLD, null_handler); } int create_client(SOCKET ls, SOCKET s, CLI *arg, void *(*cli)(void *)) { switch(fork()) { case -1: /* error */ str_free(arg); if(s>=0) closesocket(s); return -1; case 0: /* child */ if(ls>=0) closesocket(ls); signal(SIGCHLD, null_handler); cli(arg); _exit(0); default: /* parent */ str_free(arg); if(s>=0) closesocket(s); } return 0; } #endif /* USE_FORK */ #ifdef USE_PTHREAD int sthreads_init(void) { thread_id_init(); locking_init(); return 0; } int create_client(SOCKET ls, SOCKET s, CLI *arg, void *(*cli)(void *)) { pthread_t thread; pthread_attr_t pth_attr; int error; #if defined(HAVE_PTHREAD_SIGMASK) && !defined(__APPLE__) /* 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 new_set, old_set; #endif /* HAVE_PTHREAD_SIGMASK && !__APPLE__*/ (void)ls; /* this parameter is only used with USE_FORK */ #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(&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); pthread_attr_setstacksize(&pth_attr, arg->opt->stack_size); error=pthread_create(&thread, &pth_attr, cli, arg); pthread_attr_destroy(&pth_attr); #if defined(HAVE_PTHREAD_SIGMASK) && !defined(__APPLE__) pthread_sigmask(SIG_SETMASK, &old_set, NULL); /* unblock signals */ #endif /* HAVE_PTHREAD_SIGMASK && !__APPLE__*/ if(error) { errno=error; ioerror("pthread_create"); str_free(arg); if(s>=0) closesocket(s); return -1; } return 0; } #endif /* USE_PTHREAD */ #ifdef USE_WIN32 int sthreads_init(void) { thread_id_init(); locking_init(); return 0; } int create_client(SOCKET ls, SOCKET s, CLI *arg, void *(*cli)(void *)) { (void)ls; /* this parameter is only used with USE_FORK */ s_log(LOG_DEBUG, "Creating a new thread"); if((long)_beginthread((void(*)(void *))cli, (unsigned)arg->opt->stack_size, arg)==-1) { ioerror("_beginthread"); str_free(arg); if(s!=INVALID_SOCKET) closesocket(s); return -1; } s_log(LOG_DEBUG, "New thread created"); return 0; } #endif /* USE_WIN32 */ #ifdef USE_OS2 int sthreads_init(void) { return 0; } unsigned long stunnel_process_id(void) { PTIB ptib=NULL; DosGetInfoBlocks(&ptib, NULL); return (unsigned long)ptib->tib_ordinal; } unsigned long stunnel_thread_id(void) { PPIB ppib=NULL; DosGetInfoBlocks(NULL, &ppib); return (unsigned long)ppib->pib_ulpid; } int create_client(SOCKET ls, SOCKET s, CLI *arg, void *(*cli)(void *)) { (void)ls; /* this parameter is only used with USE_FORK */ s_log(LOG_DEBUG, "Creating a new thread"); if((long)_beginthread((void(*)(void *))cli, NULL, arg->opt->stack_size, arg)==-1L) { ioerror("_beginthread"); str_free(arg); if(s>=0) closesocket(s); return -1; } s_log(LOG_DEBUG, "New thread created"); return 0; } #endif /* USE_OS2 */ #ifdef _WIN32_WCE long _beginthread(void (*start_address)(void *), int stack_size, void *arglist) { DWORD thread_id; HANDLE handle; handle=CreateThread(NULL, stack_size, (LPTHREAD_START_ROUTINE)start_address, arglist, STACK_SIZE_PARAM_IS_A_RESERVATION, &thread_id); if(!handle) return -1L; CloseHandle(handle); return 0; } void _endthread(void) { ExitThread(0); } #endif /* _WIN32_WCE */ #ifdef DEBUG_STACK_SIZE #define STACK_RESERVE (STACK_SIZE/8) #define VERIFY_AREA ((STACK_SIZE-STACK_RESERVE)/sizeof(uint32_t)) #define TEST_VALUE 0xdeadbeef /* some heuristic to determine the usage of client stack size */ void stack_info(int init) { /* 1-initialize, 0-display */ uint32_t table[VERIFY_AREA]; int i, num; static int min_num=VERIFY_AREA; if(init) { for(i=0; inum) /* use the higher value */ num=i; if(num<64) { s_log(LOG_NOTICE, "STACK_RESERVE is too high"); return; } if(num