Hi,
I've created a patch to Stunnel that reloads all accepting connections from the config file. This will not work for privileged ports in the current implementation. Test at your own risk :)
Feedback welcome.
Best regards, Matthew
diff -urb stunnel-orig/stunnel-4.20/src/stunnel.c stunnel-4.20/src/stunnel.c --- stunnel-orig/stunnel-4.20/src/stunnel.c 2006-11-30 15:47:45.000000000 -0500 +++ stunnel-4.20/src/stunnel.c 2006-12-12 03:34:36.000000000 -0500 @@ -53,12 +53,12 @@ #endif
int volatile num_clients=0; /* Current number of clients */ +int volatile conf_updated=0; /* Conf updated ? */
/* Functions */
#ifndef USE_WIN32 int main(int argc, char* argv[]) { /* execution begins here 8-) */ - main_initialize(argc>1 ? argv[1] : NULL, argc>2 ? argv[2] : NULL);
signal(SIGPIPE, SIG_IGN); /* avoid 'broken pipe' signal */ @@ -78,12 +78,17 @@ } #endif
+char *cfg_arg1 = NULL; +char *cfg_arg2 = NULL; + void main_initialize(char *arg1, char *arg2) { ssl_init(); /* initialize SSL library */ sthreads_init(); /* initialize critical sections & SSL callbacks */ parse_config(arg1, arg2); log_open(); stunnel_info(0); + cfg_arg1 = arg1; + cfg_arg2 = arg2; }
void main_execute(void) { @@ -105,6 +110,7 @@ SOCKADDR_UNION addr; s_poll_set fds; LOCAL_OPTIONS *opt; + int i; get_limits(); s_poll_zero(&fds); #if !defined(USE_WIN32) && !defined(USE_OS2) @@ -173,6 +179,42 @@ accept_connection(opt);
} + if(conf_updated) { + for(i=0; i<fds.nfds; i++) + close(fds.ufds[i].fd); + + /* re-bind local ports */ + for(opt=local_options.next; opt; opt=opt->next) { + if(!opt->option.accept) /* no need to bind this service */ + continue; + memcpy(&addr, &opt->local_addr.addr[0], sizeof(SOCKADDR_UNION)); + if((opt->fd=socket(addr.sa.sa_family, SOCK_STREAM, 0))<0) { + sockerror("local socket"); + exit(1); + } + if(alloc_fd(opt->fd)) + exit(1); + if(set_socket_options(opt->fd, 0)<0) + exit(1); + s_ntop(opt->local_address, &addr); + if(bind(opt->fd, &addr.sa, addr_len(addr))) { + s_log(LOG_ERR, "Error binding %s to %s", + opt->servname, opt->local_address); + sockerror("bind"); + exit(1); + } + s_log(LOG_DEBUG, "%s bound to %s", opt->servname, opt->local_address); + if(listen(opt->fd, 5)) { + sockerror("listen"); + exit(1); + } +#ifdef FD_CLOEXEC + fcntl(opt->fd, F_SETFD, FD_CLOEXEC); /* close socket in child execvp */ +#endif + s_poll_add(&fds, opt->fd, 1, 0); + } + conf_updated = 0; + } } s_log(LOG_ERR, "INTERNAL ERROR: End of infinite loop 8-)"); } @@ -413,9 +455,16 @@ }
static void signal_handler(int sig) { /* signal handler */ + if((sig==SIGHUP) && local_options.next) { /* only in service mode */ + s_log(LOG_NOTICE, "Re-reading configuration"); + parse_config(cfg_arg1, cfg_arg2); + conf_updated = 1; + } + else { s_log(sig==SIGTERM ? LOG_NOTICE : LOG_ERR, "Received signal %d; terminating", sig); exit(3); + } } #endif /* !defined USE_WIN32 */
Here is an updated patch that hopefully resolves the polling issue. I didn't know whether to use the signal_pipe to wake up the main loop (since the other end of the signal_pipe isn't in the same scope), so I just added a timeout (1 second) to the s_poll_wait.
Previous issue was that the configuration didn't get reloaded until the main loop woke up (network activity / signal_pipe).
Regards, Matthew
--- stunnel-orig/stunnel-4.20/src/stunnel.c 2006-11-30 15:47:45.000000000 -0500 +++ stunnel-4.20/src/stunnel.c 2006-12-13 04:48:18.000000000 -0500 @@ -53,6 +53,7 @@ #endif
int volatile num_clients=0; /* Current number of clients */ +int volatile conf_updated=0; /* Conf updated ? */
/* Functions */
@@ -78,12 +79,17 @@ } #endif
+char *cfg_arg1 = NULL; +char *cfg_arg2 = NULL; + void main_initialize(char *arg1, char *arg2) { ssl_init(); /* initialize SSL library */ sthreads_init(); /* initialize critical sections & SSL callbacks */ parse_config(arg1, arg2); log_open(); stunnel_info(0); + cfg_arg1 = arg1; + cfg_arg2 = arg2; }
void main_execute(void) { @@ -105,10 +111,15 @@ SOCKADDR_UNION addr; s_poll_set fds; LOCAL_OPTIONS *opt; + int i; +#if !defined(USE_WIN32) && !defined(USE_OS2) + int signal_pipe; +#endif get_limits(); s_poll_zero(&fds); #if !defined(USE_WIN32) && !defined(USE_OS2) - s_poll_add(&fds, signal_pipe_init(), 1, 0); + signal_pipe = signal_pipe_init(); + s_poll_add(&fds, signal_pipe, 1, 0); #endif if(!local_options.next) { s_log(LOG_ERR, "No connections defined in config file"); @@ -163,7 +174,7 @@ create_client(-1, -1, alloc_client_session(opt, -1, -1), client); } while(1) { - if(s_poll_wait(&fds, -1, -1)<0) { /* non-critical error */ + if(s_poll_wait(&fds, 1, -1)<0) { /* non-critical error */ log_error(LOG_INFO, get_last_socket_error(), "daemon_loop: s_poll_wait"); sleep(1); /* to avoid log trashing */ @@ -173,6 +184,46 @@ accept_connection(opt);
} + if(conf_updated) { + for(i=0; i<fds.nfds; i++) + close(fds.ufds[i].fd); + + s_poll_zero(&fds); +#if !defined(USE_WIN32) && !defined(USE_OS2) + s_poll_add(&fds, signal_pipe, 1, 0); +#endif + /* re-bind local ports */ + for(opt=local_options.next; opt; opt=opt->next) { + if(!opt->option.accept) /* no need to bind this service */ + continue; + memcpy(&addr, &opt->local_addr.addr[0], sizeof(SOCKADDR_UNION)); + if((opt->fd=socket(addr.sa.sa_family, SOCK_STREAM, 0))<0) { + sockerror("local socket"); + exit(1); + } + if(alloc_fd(opt->fd)) + exit(1); + if(set_socket_options(opt->fd, 0)<0) + exit(1); + s_ntop(opt->local_address, &addr); + if(bind(opt->fd, &addr.sa, addr_len(addr))) { + s_log(LOG_ERR, "Error binding %s to %s", + opt->servname, opt->local_address); + sockerror("bind"); + exit(1); + } + s_log(LOG_DEBUG, "%s bound to %s", opt->servname, opt->local_address); + if(listen(opt->fd, 5)) { + sockerror("listen"); + exit(1); + } +#ifdef FD_CLOEXEC + fcntl(opt->fd, F_SETFD, FD_CLOEXEC); /* close socket in child execvp */ +#endif + s_poll_add(&fds, opt->fd, 1, 0); + } + conf_updated = 0; + } } s_log(LOG_ERR, "INTERNAL ERROR: End of infinite loop 8-)"); } @@ -413,9 +464,16 @@ }
static void signal_handler(int sig) { /* signal handler */ + if((sig==SIGHUP) && local_options.next) { /* only in service mode */ + s_log(LOG_NOTICE, "Re-reading configuration"); + parse_config(cfg_arg1, cfg_arg2); + conf_updated = 1; + } + else { s_log(sig==SIGTERM ? LOG_NOTICE : LOG_ERR, "Received signal %d; terminating", sig); exit(3); + } } #endif /* !defined USE_WIN32 */
Sorry for yet another correction. This change is also needed otherwise you'll get 100% cpu usage. The original code was not designed to handle timeouts correctly.
Matt
network.c 2006-12-14 19:02:37.000000000 -0500 @@ -260,7 +260,7 @@ do { /* skip "Interrupted system call" errors */ retry=0; retval=poll(fds->ufds, fds->nfds, sec<0 ? -1 : 1000*sec+msec); - if(sec<0 && retval>0 && s_poll_canread(fds, signal_pipe[0])) { + if(retval>0 && s_poll_canread(fds, signal_pipe[0])) { signal_pipe_empty(); /* no timeout -> main loop */ retry=1; }