#include "first.h" #include "server.h" #include "buffer.h" #include "burl.h" #include "network.h" #include "log.h" #include "rand.h" #include "chunk.h" #include "http_auth.h" #include "http_vhostdb.h" #include "fdevent.h" #include "connections.h" #include "sock_addr.h" #include "stat_cache.h" #include "configfile.h" #include "plugin.h" #include "joblist.h" #include "network_write.h" #ifdef HAVE_VERSIONSTAMP_H # include "versionstamp.h" #else # define REPO_VERSION "" #endif #define PACKAGE_DESC PACKAGE_NAME "/" PACKAGE_VERSION REPO_VERSION #include #include #include #include #include #include #include #include #include #include #include #include #include #ifdef HAVE_GETOPT_H # include #endif #ifdef HAVE_VALGRIND_VALGRIND_H # include #endif #ifdef HAVE_SYS_WAIT_H # include #endif #ifdef HAVE_PWD_H # include # include #endif #ifdef HAVE_SYSLOG_H # include #endif #ifdef HAVE_SYS_RESOURCE_H # include #endif #ifdef HAVE_SYS_PRCTL_H # include #endif #include "sys-crypto.h" #ifdef USE_OPENSSL_CRYPTO #define USE_SSL #define TEXT_SSL " (ssl)" #else #define TEXT_SSL #endif #ifndef __sgi /* IRIX doesn't like the alarm based time() optimization */ /* #define USE_ALARM */ #endif static int oneshot_fd = 0; static volatile int pid_fd = -2; static server_socket_array graceful_sockets; static server_socket_array inherited_sockets; static volatile sig_atomic_t graceful_restart = 0; static volatile sig_atomic_t graceful_shutdown = 0; static volatile sig_atomic_t srv_shutdown = 0; static volatile sig_atomic_t handle_sig_child = 0; static volatile sig_atomic_t handle_sig_alarm = 1; static volatile sig_atomic_t handle_sig_hup = 0; #if defined(HAVE_SIGACTION) && defined(SA_SIGINFO) static volatile siginfo_t last_sigterm_info; static volatile siginfo_t last_sighup_info; static void sigaction_handler(int sig, siginfo_t *si, void *context) { static const siginfo_t empty_siginfo; UNUSED(context); if (!si) *(const siginfo_t **)&si = &empty_siginfo; switch (sig) { case SIGTERM: srv_shutdown = 1; last_sigterm_info = *si; break; case SIGUSR1: if (!graceful_shutdown) { graceful_restart = 1; graceful_shutdown = 1; last_sigterm_info = *si; } break; case SIGINT: if (graceful_shutdown) { if (2 == graceful_restart) graceful_restart = 1; else srv_shutdown = 1; } else { graceful_shutdown = 1; } last_sigterm_info = *si; break; case SIGALRM: handle_sig_alarm = 1; break; case SIGHUP: handle_sig_hup = 1; last_sighup_info = *si; break; case SIGCHLD: handle_sig_child = 1; break; } } #elif defined(HAVE_SIGNAL) || defined(HAVE_SIGACTION) static void signal_handler(int sig) { switch (sig) { case SIGTERM: srv_shutdown = 1; break; case SIGUSR1: if (!graceful_shutdown) { graceful_restart = 1; graceful_shutdown = 1; } break; case SIGINT: if (graceful_shutdown) { if (2 == graceful_restart) graceful_restart = 1; else srv_shutdown = 1; } else { graceful_shutdown = 1; } break; case SIGALRM: handle_sig_alarm = 1; break; case SIGHUP: handle_sig_hup = 1; break; case SIGCHLD: handle_sig_child = 1; break; } } #endif #ifdef HAVE_FORK static int daemonize(void) { int pipefd[2]; pid_t pid; #ifdef SIGTTOU signal(SIGTTOU, SIG_IGN); #endif #ifdef SIGTTIN signal(SIGTTIN, SIG_IGN); #endif #ifdef SIGTSTP signal(SIGTSTP, SIG_IGN); #endif if (pipe(pipefd) < 0) exit(-1); if (0 > (pid = fork())) exit(-1); if (0 < pid) { char buf; ssize_t bytes; close(pipefd[1]); /* parent waits for grandchild to be ready */ do { bytes = read(pipefd[0], &buf, sizeof(buf)); } while (bytes < 0 && EINTR == errno); close(pipefd[0]); if (bytes <= 0) { /* closed fd (without writing) == failure in grandchild */ fputs("daemonized server failed to start; check error log for details\n", stderr); exit(-1); } exit(0); } close(pipefd[0]); if (-1 == setsid()) exit(0); signal(SIGHUP, SIG_IGN); if (0 != fork()) exit(0); if (0 != chdir("/")) exit(0); fdevent_setfd_cloexec(pipefd[1]); return pipefd[1]; } #endif static server *server_init(void) { int i; server *srv = calloc(1, sizeof(*srv)); force_assert(srv); #define CLEAN(x) \ srv->x = buffer_init(); CLEAN(response_header); CLEAN(parse_full_path); CLEAN(ts_debug_str); CLEAN(ts_date_str); CLEAN(errorlog_buf); CLEAN(response_range); CLEAN(tmp_buf); srv->empty_string = buffer_init_string(""); CLEAN(cond_check_buf); CLEAN(srvconf.errorlog_file); CLEAN(srvconf.breakagelog_file); CLEAN(srvconf.groupname); CLEAN(srvconf.username); CLEAN(srvconf.changeroot); CLEAN(srvconf.bindhost); CLEAN(srvconf.event_handler); CLEAN(srvconf.pid_file); CLEAN(srvconf.syslog_facility); CLEAN(tmp_chunk_len); #undef CLEAN #define CLEAN(x) \ srv->x = array_init(); CLEAN(config_context); CLEAN(config_touched); CLEAN(status); #undef CLEAN for (i = 0; i < FILE_CACHE_MAX; i++) { srv->mtime_cache[i].mtime = (time_t)-1; srv->mtime_cache[i].str = buffer_init(); } li_rand_reseed(); srv->cur_ts = time(NULL); srv->startup_ts = srv->cur_ts; srv->conns = calloc(1, sizeof(*srv->conns)); force_assert(srv->conns); srv->joblist = calloc(1, sizeof(*srv->joblist)); force_assert(srv->joblist); srv->fdwaitqueue = calloc(1, sizeof(*srv->fdwaitqueue)); force_assert(srv->fdwaitqueue); srv->srvconf.modules = array_init(); srv->srvconf.modules_dir = buffer_init_string(LIBRARY_DIR); srv->srvconf.network_backend = buffer_init(); srv->srvconf.upload_tempdirs = array_init(); srv->srvconf.reject_expect_100_with_417 = 1; srv->srvconf.xattr_name = buffer_init_string("Content-Type"); srv->srvconf.http_header_strict = 1; srv->srvconf.http_host_strict = 1; /*(implies http_host_normalize)*/ srv->srvconf.http_host_normalize = 0; #if 0 srv->srvconf.http_url_normalize = HTTP_PARSEOPT_URL_NORMALIZE | HTTP_PARSEOPT_URL_NORMALIZE_UNRESERVED | HTTP_PARSEOPT_URL_NORMALIZE_CTRLS_REJECT | HTTP_PARSEOPT_URL_NORMALIZE_PATH_BACKSLASH_TRANS | HTTP_PARSEOPT_URL_NORMALIZE_PATH_2F_DECODE | HTTP_PARSEOPT_URL_NORMALIZE_PATH_DOTSEG_REMOVE; #endif srv->srvconf.http_url_normalize = 0; /* temporary; change in future */ srv->srvconf.high_precision_timestamps = 0; srv->srvconf.max_request_field_size = 8192; srv->srvconf.loadavg[0] = 0.0; srv->srvconf.loadavg[1] = 0.0; srv->srvconf.loadavg[2] = 0.0; srv->srvconf.compat_module_load = 1; srv->srvconf.systemd_socket_activation = 0; /* use syslog */ srv->errorlog_fd = STDERR_FILENO; srv->errorlog_mode = ERRORLOG_FD; srv->split_vals = array_init(); srv->request_env = plugins_call_handle_request_env; return srv; } static void server_free(server *srv) { size_t i; for (i = 0; i < FILE_CACHE_MAX; i++) { buffer_free(srv->mtime_cache[i].str); } if (oneshot_fd > 0) { close(oneshot_fd); } #define CLEAN(x) \ buffer_free(srv->x); CLEAN(response_header); CLEAN(parse_full_path); CLEAN(ts_debug_str); CLEAN(ts_date_str); CLEAN(errorlog_buf); CLEAN(response_range); CLEAN(tmp_buf); CLEAN(empty_string); CLEAN(cond_check_buf); CLEAN(srvconf.errorlog_file); CLEAN(srvconf.breakagelog_file); CLEAN(srvconf.groupname); CLEAN(srvconf.username); CLEAN(srvconf.changeroot); CLEAN(srvconf.bindhost); CLEAN(srvconf.event_handler); CLEAN(srvconf.pid_file); CLEAN(srvconf.modules_dir); CLEAN(srvconf.network_backend); CLEAN(srvconf.xattr_name); CLEAN(srvconf.syslog_facility); CLEAN(tmp_chunk_len); #undef CLEAN #if 0 fdevent_unregister(srv->ev, srv->fd); #endif fdevent_free(srv->ev); free(srv->conns); if (srv->config_storage) { for (i = 0; i < srv->config_context->used; i++) { specific_config *s = srv->config_storage[i]; if (!s) continue; buffer_free(s->document_root); buffer_free(s->server_name); buffer_free(s->server_tag); buffer_free(s->error_handler); buffer_free(s->error_handler_404); buffer_free(s->errorfile_prefix); buffer_free(s->socket_perms); array_free(s->mimetypes); free(s); } free(srv->config_storage); srv->config_storage = NULL; } #define CLEAN(x) \ array_free(srv->x); CLEAN(config_context); CLEAN(config_touched); CLEAN(status); CLEAN(srvconf.upload_tempdirs); #undef CLEAN joblist_free(srv, srv->joblist); fdwaitqueue_free(srv, srv->fdwaitqueue); if (srv->stat_cache) { stat_cache_free(srv->stat_cache); } array_free(srv->srvconf.modules); array_free(srv->split_vals); li_rand_cleanup(); chunkqueue_chunk_pool_free(); free(srv); } static void remove_pid_file(server *srv) { if (pid_fd <= -2) return; if (!buffer_string_is_empty(srv->srvconf.pid_file) && 0 <= pid_fd) { if (0 != ftruncate(pid_fd, 0)) { log_error_write(srv, __FILE__, __LINE__, "sbds", "ftruncate failed for:", srv->srvconf.pid_file, errno, strerror(errno)); } } if (0 <= pid_fd) { close(pid_fd); pid_fd = -1; } if (!buffer_string_is_empty(srv->srvconf.pid_file) && buffer_string_is_empty(srv->srvconf.changeroot)) { if (0 != unlink(srv->srvconf.pid_file->ptr)) { if (errno != EACCES && errno != EPERM) { log_error_write(srv, __FILE__, __LINE__, "sbds", "unlink failed for:", srv->srvconf.pid_file, errno, strerror(errno)); } } } } static server_socket * server_oneshot_getsock(server *srv, sock_addr *cnt_addr) { server_socket *srv_socket, *srv_socket_wild = NULL; size_t i; for (i = 0; i < srv->srv_sockets.used; ++i) { srv_socket = srv->srv_sockets.ptr[i]; if (!sock_addr_is_port_eq(&srv_socket->addr,cnt_addr)) continue; if (sock_addr_is_addr_eq(&srv_socket->addr,cnt_addr)) return srv_socket; if (NULL != srv_socket_wild) continue; if (sock_addr_is_addr_wildcard(&srv_socket->addr)) { srv_socket_wild = srv_socket; } } if (NULL != srv_socket_wild) { return srv_socket_wild; } else if (srv->srv_sockets.used) { return srv->srv_sockets.ptr[0]; } else { log_error_write(srv, __FILE__, __LINE__, "s", "no sockets configured"); return NULL; } } static int server_oneshot_init(server *srv, int fd) { /* Note: does not work with netcat due to requirement that fd be socket. * STDOUT_FILENO was not saved earlier in startup, and that is to where * netcat expects output to be sent. Since lighttpd expects connections * to be sockets, con->fd is where output is sent; separate fds are not * stored for input and output, but netcat has different fds for stdin * and * stdout. To support netcat, would additionally need to avoid * S_ISSOCK(), getsockname(), and getpeername() below, reconstructing * addresses from environment variables: * NCAT_LOCAL_ADDR NCAT_LOCAL_PORT * NCAT_REMOTE_ADDR NCAT_REMOTE_PORT * NCAT_PROTO */ connection *con; server_socket *srv_socket; sock_addr cnt_addr; socklen_t cnt_len; struct stat st; if (0 != fstat(fd, &st)) { log_error_write(srv, __FILE__, __LINE__, "ss", "fstat:", strerror(errno)); return 0; } if (!S_ISSOCK(st.st_mode)) { /* require that fd is a socket * (modules might expect STDIN_FILENO and STDOUT_FILENO opened to /dev/null) */ log_error_write(srv, __FILE__, __LINE__, "s", "lighttpd -1 stdin is not a socket"); return 0; } cnt_len = sizeof(cnt_addr); if (0 != getsockname(fd, (struct sockaddr *)&cnt_addr, &cnt_len)) { log_error_write(srv, __FILE__, __LINE__, "ss", "getsockname:", strerror(errno)); return 0; } srv_socket = server_oneshot_getsock(srv, &cnt_addr); if (NULL == srv_socket) return 0; cnt_len = sizeof(cnt_addr); if (0 != getpeername(fd, (struct sockaddr *)&cnt_addr, &cnt_len)) { log_error_write(srv, __FILE__, __LINE__, "ss", "getpeername:", strerror(errno)); return 0; } /*(must set flags; fd did not pass through fdevent accept() logic)*/ if (-1 == fdevent_fcntl_set_nb_cloexec(srv->ev, fd)) { log_error_write(srv, __FILE__, __LINE__, "ss", "fcntl:", strerror(errno)); return 0; } if (sock_addr_get_family(&cnt_addr) != AF_UNIX) { network_accept_tcp_nagle_disable(fd); } con = connection_accepted(srv, srv_socket, &cnt_addr, fd); if (NULL == con) return 0; connection_state_machine(srv, con); return 1; } static void show_version (void) { char *b = PACKAGE_DESC TEXT_SSL \ " - a light and fast webserver\n" #ifdef NONREPRODUCIBLE_BUILD "Build-Date: " __DATE__ " " __TIME__ "\n"; #endif ; write_all(STDOUT_FILENO, b, strlen(b)); } static void show_features (void) { static const char features[] = "\nFeatures:\n\n" #ifdef HAVE_IPV6 "\t+ IPv6 support\n" #else "\t- IPv6 support\n" #endif #if defined HAVE_ZLIB_H && defined HAVE_LIBZ "\t+ zlib support\n" #else "\t- zlib support\n" #endif #if defined HAVE_BZLIB_H && defined HAVE_LIBBZ2 "\t+ bzip2 support\n" #else "\t- bzip2 support\n" #endif #if defined(HAVE_CRYPT) || defined(HAVE_CRYPT_R) || defined(HAVE_LIBCRYPT) "\t+ crypt support\n" #else "\t- crypt support\n" #endif #ifdef USE_SSL "\t+ SSL support\n" #else "\t- SSL support\n" #endif #ifdef HAVE_LIBPCRE "\t+ PCRE support\n" #else "\t- PCRE support\n" #endif #ifdef HAVE_MYSQL "\t+ MySQL support\n" #else "\t- MySQL support\n" #endif #ifdef HAVE_PGSQL "\t+ PgSQL support\n" #else "\t- PgSQL support\n" #endif #ifdef HAVE_DBI "\t+ DBI support\n" #else "\t- DBI support\n" #endif #ifdef HAVE_KRB5 "\t+ Kerberos support\n" #else "\t- Kerberos support\n" #endif #if defined(HAVE_LDAP_H) && defined(HAVE_LBER_H) && defined(HAVE_LIBLDAP) && defined(HAVE_LIBLBER) "\t+ LDAP support\n" #else "\t- LDAP support\n" #endif #ifdef HAVE_PAM "\t+ PAM support\n" #else "\t- PAM support\n" #endif #ifdef USE_MEMCACHED "\t+ memcached support\n" #else "\t- memcached support\n" #endif #ifdef HAVE_FAM_H "\t+ FAM support\n" #else "\t- FAM support\n" #endif #ifdef HAVE_LUA_H "\t+ LUA support\n" #else "\t- LUA support\n" #endif #ifdef HAVE_LIBXML_H "\t+ xml support\n" #else "\t- xml support\n" #endif #ifdef HAVE_SQLITE3_H "\t+ SQLite support\n" #else "\t- SQLite support\n" #endif #ifdef HAVE_GDBM_H "\t+ GDBM support\n" #else "\t- GDBM support\n" #endif ; show_version(); printf("%s%s%s\n", fdevent_show_event_handlers(), network_write_show_handlers(), features); } static void show_help (void) { char *b = PACKAGE_DESC TEXT_SSL #ifdef NONREPRODUCIBLE_BUILD " ("__DATE__ " " __TIME__ ")" #endif " - a light and fast webserver\n" \ "usage:\n" \ " -f filename of the config-file\n" \ " -m module directory (default: "LIBRARY_DIR")\n" \ " -i graceful shutdown after of inactivity\n" \ " -1 process single (one) request on stdin socket, then exit\n" \ " -p print the parsed config-file in internal form, and exit\n" \ " -t test config-file syntax, then exit\n" \ " -tt test config-file syntax, load and init modules, then exit\n" \ " -D don't go to background (default: go to background)\n" \ " -v show version\n" \ " -V show compile-time features\n" \ " -h show this help\n" \ "\n" ; write_all(STDOUT_FILENO, b, strlen(b)); } /** * open the errorlog * * we have 4 possibilities: * - stderr (default) * - syslog * - logfile * - pipe * */ static int log_error_open(server *srv) { int errfd; #ifdef HAVE_SYSLOG_H /* perhaps someone wants to use syslog() */ int facility = -1; if (!buffer_string_is_empty(srv->srvconf.syslog_facility)) { static const struct facility_name_st { const char *name; int val; } facility_names[] = { { "auth", LOG_AUTH } #ifdef LOG_AUTHPRIV ,{ "authpriv", LOG_AUTHPRIV } #endif #ifdef LOG_CRON ,{ "cron", LOG_CRON } #endif ,{ "daemon", LOG_DAEMON } #ifdef LOG_FTP ,{ "ftp", LOG_FTP } #endif #ifdef LOG_KERN ,{ "kern", LOG_KERN } #endif #ifdef LOG_LPR ,{ "lpr", LOG_LPR } #endif #ifdef LOG_MAIL ,{ "mail", LOG_MAIL } #endif #ifdef LOG_NEWS ,{ "news", LOG_NEWS } #endif ,{ "security", LOG_AUTH } /* DEPRECATED */ #ifdef LOG_SYSLOG ,{ "syslog", LOG_SYSLOG } #endif #ifdef LOG_USER ,{ "user", LOG_USER } #endif #ifdef LOG_UUCP ,{ "uucp", LOG_UUCP } #endif ,{ "local0", LOG_LOCAL0 } ,{ "local1", LOG_LOCAL1 } ,{ "local2", LOG_LOCAL2 } ,{ "local3", LOG_LOCAL3 } ,{ "local4", LOG_LOCAL4 } ,{ "local5", LOG_LOCAL5 } ,{ "local6", LOG_LOCAL6 } ,{ "local7", LOG_LOCAL7 } }; unsigned int i; for (i = 0; i < sizeof(facility_names)/sizeof(facility_names[0]); ++i) { const struct facility_name_st *f = facility_names+i; if (0 == strcmp(srv->srvconf.syslog_facility->ptr, f->name)) { facility = f->val; break; } } if (-1 == facility) { log_error_write(srv, __FILE__, __LINE__, "SBS", "unrecognized server.syslog-facility: \"", srv->srvconf.syslog_facility, "\"; defaulting to \"daemon\" facility"); } } openlog("lighttpd", LOG_CONS|LOG_PID, -1==facility ? LOG_DAEMON : facility); #endif srv->errorlog_mode = ERRORLOG_FD; srv->errorlog_fd = STDERR_FILENO; if (srv->srvconf.errorlog_use_syslog) { srv->errorlog_mode = ERRORLOG_SYSLOG; } else if (!buffer_string_is_empty(srv->srvconf.errorlog_file)) { const char *logfile = srv->srvconf.errorlog_file->ptr; int fd = fdevent_open_logger(logfile); if (-1 == fd) { log_error_write(srv, __FILE__, __LINE__, "SSSS", "opening errorlog '", logfile, "' failed: ", strerror(errno)); return -1; } srv->errorlog_fd = fd; srv->errorlog_mode = logfile[0] == '|' ? ERRORLOG_PIPE : ERRORLOG_FILE; } if (srv->errorlog_mode == ERRORLOG_FD && !srv->srvconf.dont_daemonize) { /* We can only log to stderr in dont-daemonize mode; * if we do daemonize and no errorlog file is specified, * we log into /dev/null */ srv->errorlog_fd = -1; } if (!buffer_string_is_empty(srv->srvconf.breakagelog_file)) { const char *logfile = srv->srvconf.breakagelog_file->ptr; if (srv->errorlog_mode == ERRORLOG_FD) { srv->errorlog_fd = dup(STDERR_FILENO); fdevent_setfd_cloexec(srv->errorlog_fd); } if (-1 == (errfd = fdevent_open_logger(logfile))) { log_error_write(srv, __FILE__, __LINE__, "SSSS", "opening errorlog '", logfile, "' failed: ", strerror(errno)); return -1; } if (*logfile == '|') fdevent_breakagelog_logger_pipe(errfd); } else if (!srv->srvconf.dont_daemonize) { /* move STDERR_FILENO to /dev/null */ if (-1 == (errfd = fdevent_open_devnull())) { log_error_write(srv, __FILE__, __LINE__, "ss", "opening /dev/null failed:", strerror(errno)); return -1; } } else { /*(leave STDERR_FILENO as-is)*/ errfd = -1; } if (0 != fdevent_set_stdin_stdout_stderr(-1, -1, errfd)) { log_error_write(srv, __FILE__, __LINE__, "ss", "setting stderr failed:", strerror(errno)); #ifdef FD_CLOEXEC if (-1 != errfd) close(errfd); #endif return -1; } #ifdef FD_CLOEXEC if (-1 != errfd) close(errfd); #endif return 0; } /** * cycle the errorlog * */ static int log_error_cycle(server *srv) { /* cycle only if the error log is a file */ if (srv->errorlog_mode == ERRORLOG_FILE) { const char *logfile = srv->srvconf.errorlog_file->ptr; if (-1 == fdevent_cycle_logger(logfile, &srv->errorlog_fd)) { /* write to old log */ log_error_write(srv, __FILE__, __LINE__, "SSSS", "cycling errorlog '", logfile, "' failed: ", strerror(errno)); } } return 0; } static int log_error_close(server *srv) { switch(srv->errorlog_mode) { case ERRORLOG_PIPE: case ERRORLOG_FILE: case ERRORLOG_FD: if (-1 != srv->errorlog_fd) { /* don't close STDERR */ /* fdevent_close_logger_pipes() closes ERRORLOG_PIPE */ if (STDERR_FILENO != srv->errorlog_fd && srv->errorlog_mode != ERRORLOG_PIPE) { close(srv->errorlog_fd); } srv->errorlog_fd = -1; } break; case ERRORLOG_SYSLOG: #ifdef HAVE_SYSLOG_H closelog(); #endif break; } return 0; } static void server_sockets_save (server *srv) { /* graceful_restart */ memcpy(&graceful_sockets, &srv->srv_sockets, sizeof(server_socket_array)); memset(&srv->srv_sockets, 0, sizeof(server_socket_array)); memcpy(&inherited_sockets, &srv->srv_sockets_inherited, sizeof(server_socket_array)); memset(&srv->srv_sockets_inherited, 0, sizeof(server_socket_array)); } static void server_sockets_restore (server *srv) { /* graceful_restart */ memcpy(&srv->srv_sockets, &graceful_sockets, sizeof(server_socket_array)); memset(&graceful_sockets, 0, sizeof(server_socket_array)); memcpy(&srv->srv_sockets_inherited, &inherited_sockets, sizeof(server_socket_array)); memset(&inherited_sockets, 0, sizeof(server_socket_array)); } static int server_sockets_set_nb_cloexec (server *srv) { if (srv->sockets_disabled) return 0; /* lighttpd -1 (one-shot mode) */ for (size_t i = 0; i < srv->srv_sockets.used; ++i) { server_socket *srv_socket = srv->srv_sockets.ptr[i]; if (-1 == fdevent_fcntl_set_nb_cloexec_sock(srv->ev, srv_socket->fd)) { log_error_write(srv, __FILE__, __LINE__, "ss", "fcntl failed:", strerror(errno)); return -1; } } return 0; } static void server_sockets_set_event (server *srv, int event) { for (size_t i = 0; i < srv->srv_sockets.used; ++i) { server_socket *srv_socket = srv->srv_sockets.ptr[i]; fdevent_event_set(srv->ev,&(srv_socket->fde_ndx),srv_socket->fd,event); } } static void server_sockets_unregister (server *srv) { for (size_t i = 0; i < srv->srv_sockets.used; ++i) network_unregister_sock(srv, srv->srv_sockets.ptr[i]); } static void server_sockets_close (server *srv) { /* closing socket right away will make it possible for the next lighttpd * to take over (old-style graceful restart), but only if backends * (e.g. fastcgi, scgi, etc) are independent from lighttpd, rather * than started by lighttpd via "bin-path") */ for (size_t i = 0; i < srv->srv_sockets.used; ++i) { server_socket *srv_socket = srv->srv_sockets.ptr[i]; if (-1 == srv_socket->fd) continue; network_unregister_sock(srv, srv_socket); close(srv_socket->fd); srv_socket->fd = -1; /* network_close() will cleanup after us */ } } static void server_graceful_shutdown_maint (server *srv) { connections *conns = srv->conns; for (size_t ndx = 0; ndx < conns->used; ++ndx) { connection * const con = conns->ptr[ndx]; int changed = 0; if (con->state == CON_STATE_CLOSE) { /* reduce remaining linger timeout to be * (from zero) *up to* one more second, but no more */ if (HTTP_LINGER_TIMEOUT > 1) con->close_timeout_ts -= (HTTP_LINGER_TIMEOUT - 1); if (srv->cur_ts - con->close_timeout_ts > HTTP_LINGER_TIMEOUT) changed = 1; } else if (con->state == CON_STATE_READ && con->request_count > 1 && chunkqueue_is_empty(con->read_queue)) { /* close connections in keep-alive waiting for next request */ connection_set_state(srv, con, CON_STATE_ERROR); changed = 1; } con->keep_alive = 0; /* disable keep-alive */ con->conf.kbytes_per_second = 0; /* disable rate limit */ con->conf.global_kbytes_per_second = 0; /* disable rate limit */ if (con->traffic_limit_reached) { con->traffic_limit_reached = 0; changed = 1; } if (changed) { connection_state_machine(srv, con); } } } static void server_graceful_state (server *srv) { if (!srv_shutdown) server_graceful_shutdown_maint(srv); if (!oneshot_fd) { if (0==srv->srv_sockets.used || -1 == srv->srv_sockets.ptr[0]->fde_ndx) return; } log_error_write(srv, __FILE__, __LINE__, "s", "[note] graceful shutdown started"); /* no graceful restart if chroot()ed, if oneshot mode, or if idle timeout */ if (!buffer_string_is_empty(srv->srvconf.changeroot) || oneshot_fd || 2 == graceful_shutdown) graceful_restart = 0; if (graceful_restart) { server_sockets_unregister(srv); if (pid_fd > 0) pid_fd = -pid_fd; /*(flag to skip removing pid file)*/ } else { server_sockets_close(srv); remove_pid_file(srv); buffer_clear(srv->srvconf.pid_file); /*(prevent more removal attempts)*/ } } static int server_main (server * const srv, int argc, char **argv) { int print_config = 0; int test_config = 0; int i_am_root = 0; int o; #ifdef HAVE_FORK int num_childs = 0; #endif size_t i; time_t idle_limit = 0, last_active_ts = time(NULL); #ifdef HAVE_SIGACTION struct sigaction act; #endif #ifdef HAVE_FORK int parent_pipe_fd = -1; #endif int stdin_fd = -1; #ifdef HAVE_GETUID i_am_root = (0 == getuid()); #endif /* initialize globals (including file-scoped static globals) */ oneshot_fd = 0; srv_shutdown = 0; graceful_shutdown = 0; handle_sig_alarm = 1; handle_sig_hup = 0; chunkqueue_set_tempdirs_default_reset(); http_auth_dumbdata_reset(); http_vhostdb_dumbdata_reset(); /*graceful_restart = 0;*//*(reset below to avoid further daemonizing)*/ /*(intentionally preserved)*/ /*memset(graceful_sockets, 0, sizeof(graceful_sockets));*/ /*memset(inherited_sockets, 0, sizeof(inherited_sockets));*/ /*pid_fd = -1;*/ srv->srvconf.port = 0; srv->srvconf.dont_daemonize = 0; srv->srvconf.preflight_check = 0; while(-1 != (o = getopt(argc, argv, "f:m:i:hvVD1pt"))) { switch(o) { case 'f': if (srv->config_storage) { log_error_write(srv, __FILE__, __LINE__, "s", "Can only read one config file. Use the include command to use multiple config files."); return -1; } if (config_read(srv, optarg)) { return -1; } break; case 'm': buffer_copy_string(srv->srvconf.modules_dir, optarg); break; case 'i': { char *endptr; long timeout = strtol(optarg, &endptr, 0); if (!*optarg || *endptr || timeout < 0) { log_error_write(srv, __FILE__, __LINE__, "ss", "Invalid idle timeout value:", optarg); return -1; } idle_limit = (time_t)timeout; break; } case 'p': print_config = 1; break; case 't': ++test_config; break; case '1': if (0 == oneshot_fd) oneshot_fd = dup(STDIN_FILENO); break; case 'D': srv->srvconf.dont_daemonize = 1; break; case 'v': show_version(); return 0; case 'V': show_features(); return 0; case 'h': show_help(); return 0; default: show_help(); return -1; } } #ifdef __CYGWIN__ if (!srv->config_storage && NULL != getenv("NSSM_SERVICE_NAME")) { char *dir = getenv("NSSM_SERVICE_DIR"); if (NULL != dir && 0 != chdir(dir)) { log_error_write(srv, __FILE__, __LINE__, "sss", "chdir failed:", dir, strerror(errno)); return -1; } srv->srvconf.dont_daemonize = 1; buffer_copy_string_len(srv->srvconf.modules_dir, CONST_STR_LEN("modules")); if (config_read(srv, "conf/lighttpd.conf")) return -1; } #endif if (!srv->config_storage) { log_error_write(srv, __FILE__, __LINE__, "s", "No configuration available. Try using -f option."); return -1; } if (print_config) { data_unset *dc = srv->config_context->data[0]; if (dc) { dc->fn->print(dc, 0); fprintf(stdout, "\n"); } else { /* shouldn't happend */ fprintf(stderr, "global config not found\n"); } } if (test_config) { buffer_clear(srv->srvconf.pid_file); if (1 == test_config) { printf("Syntax OK\n"); } else { /*(test_config > 1)*/ test_config = 0; srv->srvconf.preflight_check = 1; srv->srvconf.dont_daemonize = 1; } } if (test_config || print_config) { return 0; } if (oneshot_fd) { if (oneshot_fd <= STDERR_FILENO) { log_error_write(srv, __FILE__, __LINE__, "s", "Invalid fds at startup with lighttpd -1"); return -1; } graceful_shutdown = 1; srv->sockets_disabled = 1; srv->srvconf.dont_daemonize = 1; buffer_clear(srv->srvconf.pid_file); if (srv->srvconf.max_worker) { srv->srvconf.max_worker = 0; log_error_write(srv, __FILE__, __LINE__, "s", "server one-shot command line option disables server.max-worker config file option."); } } if (buffer_is_equal_string(srv->srvconf.bindhost, CONST_STR_LEN("/dev/stdin"))) { stdin_fd = dup(STDIN_FILENO); if (stdin_fd <= STDERR_FILENO) { log_error_write(srv, __FILE__, __LINE__, "s", "Invalid fds at startup"); return -1; } } /* close stdin and stdout, as they are not needed */ { struct stat st; int devnull; int errfd; do { /* coverity[overwrite_var : FALSE] */ devnull = fdevent_open_devnull(); #ifdef __COVERITY__ __coverity_escape__(devnull); #endif } while (-1 != devnull && devnull <= STDERR_FILENO); if (-1 == devnull) { log_error_write(srv, __FILE__, __LINE__, "ss", "opening /dev/null failed:", strerror(errno)); return -1; } errfd = (0 == fstat(STDERR_FILENO, &st)) ? -1 : devnull; if (0 != fdevent_set_stdin_stdout_stderr(devnull, devnull, errfd)) { log_error_write(srv, __FILE__, __LINE__, "ss", "setting default fds failed:", strerror(errno)); #ifdef FD_CLOEXEC if (-1 != errfd) close(errfd); if (devnull != errfd) close(devnull); #endif return -1; } #ifdef FD_CLOEXEC if (-1 != errfd) close(errfd); if (devnull != errfd) close(devnull); #endif } if (0 != config_set_defaults(srv)) { log_error_write(srv, __FILE__, __LINE__, "s", "setting default values failed"); return -1; } /* check document-root */ if (buffer_string_is_empty(srv->config_storage[0]->document_root)) { log_error_write(srv, __FILE__, __LINE__, "s", "document-root is not set\n"); return -1; } if (plugins_load(srv)) { log_error_write(srv, __FILE__, __LINE__, "s", "loading plugins finally failed"); return -1; } if (HANDLER_GO_ON != plugins_call_init(srv)) { log_error_write(srv, __FILE__, __LINE__, "s", "Initialization of plugins failed. Going down."); return -1; } /* mod_indexfile should be listed in server.modules prior to dynamic handlers */ i = 0; for (buffer *pname = NULL; i < srv->plugins.used; ++i) { plugin *p = ((plugin **)srv->plugins.ptr)[i]; if (buffer_is_equal_string(p->name, CONST_STR_LEN("indexfile"))) { if (pname) { log_error_write(srv, __FILE__, __LINE__, "SB", "Warning: mod_indexfile should be listed in server.modules prior to mod_", pname); } break; } if (p->handle_subrequest_start && p->handle_subrequest) { if (!pname) pname = p->name; } } /* open pid file BEFORE chroot */ if (-2 == pid_fd) pid_fd = -1; /*(initial startup state)*/ if (-1 == pid_fd && !buffer_string_is_empty(srv->srvconf.pid_file)) { if (-1 == (pid_fd = fdevent_open_cloexec(srv->srvconf.pid_file->ptr, O_WRONLY | O_CREAT | O_EXCL | O_TRUNC, S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH))) { struct stat st; if (errno != EEXIST) { log_error_write(srv, __FILE__, __LINE__, "sbs", "opening pid-file failed:", srv->srvconf.pid_file, strerror(errno)); return -1; } if (0 != stat(srv->srvconf.pid_file->ptr, &st)) { log_error_write(srv, __FILE__, __LINE__, "sbs", "stating existing pid-file failed:", srv->srvconf.pid_file, strerror(errno)); } if (!S_ISREG(st.st_mode)) { log_error_write(srv, __FILE__, __LINE__, "sb", "pid-file exists and isn't regular file:", srv->srvconf.pid_file); return -1; } if (-1 == (pid_fd = fdevent_open_cloexec(srv->srvconf.pid_file->ptr, O_WRONLY | O_CREAT | O_TRUNC, S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH))) { log_error_write(srv, __FILE__, __LINE__, "sbs", "opening pid-file failed:", srv->srvconf.pid_file, strerror(errno)); return -1; } } } { #ifdef HAVE_GETRLIMIT struct rlimit rlim; int use_rlimit = 1; #ifdef HAVE_VALGRIND_VALGRIND_H if (RUNNING_ON_VALGRIND) use_rlimit = 0; #endif if (0 != getrlimit(RLIMIT_NOFILE, &rlim)) { log_error_write(srv, __FILE__, __LINE__, "ss", "couldn't get 'max filedescriptors'", strerror(errno)); return -1; } /** * if we are not root can can't increase the fd-limit above rlim_max, but we can reduce it */ if (use_rlimit && srv->srvconf.max_fds && (i_am_root || srv->srvconf.max_fds <= rlim.rlim_max)) { /* set rlimits */ rlim.rlim_cur = srv->srvconf.max_fds; if (i_am_root) rlim.rlim_max = srv->srvconf.max_fds; if (0 != setrlimit(RLIMIT_NOFILE, &rlim)) { log_error_write(srv, __FILE__, __LINE__, "ss", "couldn't set 'max filedescriptors'", strerror(errno)); return -1; } } srv->max_fds = rlim.rlim_cur; /*(default upper limit of 4k if server.max-fds not specified)*/ if (i_am_root && 0 == srv->srvconf.max_fds && rlim.rlim_cur > 4096) srv->max_fds = 4096; /* set core file rlimit, if enable_cores is set */ if (use_rlimit && srv->srvconf.enable_cores && getrlimit(RLIMIT_CORE, &rlim) == 0) { rlim.rlim_cur = rlim.rlim_max; setrlimit(RLIMIT_CORE, &rlim); } #endif } /* we need root-perms for port < 1024 */ if (0 != network_init(srv, stdin_fd)) { return -1; } if (i_am_root) { #ifdef HAVE_PWD_H /* set user and group */ struct group *grp = NULL; struct passwd *pwd = NULL; if (!buffer_string_is_empty(srv->srvconf.groupname)) { if (NULL == (grp = getgrnam(srv->srvconf.groupname->ptr))) { log_error_write(srv, __FILE__, __LINE__, "sb", "can't find groupname", srv->srvconf.groupname); return -1; } } if (!buffer_string_is_empty(srv->srvconf.username)) { if (NULL == (pwd = getpwnam(srv->srvconf.username->ptr))) { log_error_write(srv, __FILE__, __LINE__, "sb", "can't find username", srv->srvconf.username); return -1; } if (pwd->pw_uid == 0) { log_error_write(srv, __FILE__, __LINE__, "s", "I will not set uid to 0\n"); return -1; } if (NULL == grp && NULL == (grp = getgrgid(pwd->pw_gid))) { log_error_write(srv, __FILE__, __LINE__, "sd", "can't find group id", pwd->pw_gid); return -1; } } if (NULL != grp) { if (grp->gr_gid == 0) { log_error_write(srv, __FILE__, __LINE__, "s", "I will not set gid to 0\n"); return -1; } } /* * Change group before chroot, when we have access * to /etc/group * */ if (NULL != grp) { if (-1 == setgid(grp->gr_gid)) { log_error_write(srv, __FILE__, __LINE__, "ss", "setgid failed: ", strerror(errno)); return -1; } if (-1 == setgroups(0, NULL)) { log_error_write(srv, __FILE__, __LINE__, "ss", "setgroups failed: ", strerror(errno)); return -1; } if (!buffer_string_is_empty(srv->srvconf.username)) { initgroups(srv->srvconf.username->ptr, grp->gr_gid); } } #endif #ifdef HAVE_CHROOT if (!buffer_string_is_empty(srv->srvconf.changeroot)) { tzset(); if (-1 == chroot(srv->srvconf.changeroot->ptr)) { log_error_write(srv, __FILE__, __LINE__, "ss", "chroot failed: ", strerror(errno)); return -1; } if (-1 == chdir("/")) { log_error_write(srv, __FILE__, __LINE__, "ss", "chdir failed: ", strerror(errno)); return -1; } } #endif #ifdef HAVE_PWD_H /* drop root privs */ if (NULL != pwd) { if (-1 == setuid(pwd->pw_uid)) { log_error_write(srv, __FILE__, __LINE__, "ss", "setuid failed: ", strerror(errno)); return -1; } } #endif #if defined(HAVE_SYS_PRCTL_H) && defined(PR_SET_DUMPABLE) /** * on IRIX 6.5.30 they have prctl() but no DUMPABLE */ if (srv->srvconf.enable_cores) { prctl(PR_SET_DUMPABLE, 1, 0, 0, 0); } #endif } /* set max-conns */ if (srv->srvconf.max_conns > srv->max_fds/2) { /* we can't have more connections than max-fds/2 */ log_error_write(srv, __FILE__, __LINE__, "sdd", "can't have more connections than fds/2: ", srv->srvconf.max_conns, srv->max_fds); srv->max_conns = srv->max_fds/2; } else if (srv->srvconf.max_conns) { /* otherwise respect the wishes of the user */ srv->max_conns = srv->srvconf.max_conns; } else { /* or use the default: we really don't want to hit max-fds */ srv->max_conns = srv->max_fds/3; } #ifdef HAVE_FORK /* network is up, let's daemonize ourself */ if (0 == srv->srvconf.dont_daemonize && 0 == graceful_restart) { parent_pipe_fd = daemonize(); } #endif graceful_restart = 0;/*(reset here after avoiding further daemonizing)*/ if (0 == oneshot_fd) graceful_shutdown = 0; #ifdef HAVE_SIGACTION memset(&act, 0, sizeof(act)); act.sa_handler = SIG_IGN; sigaction(SIGPIPE, &act, NULL); # if defined(SA_SIGINFO) last_sighup_info.si_uid = 0, last_sighup_info.si_pid = 0; last_sigterm_info.si_uid = 0, last_sigterm_info.si_pid = 0; act.sa_sigaction = sigaction_handler; sigemptyset(&act.sa_mask); act.sa_flags = SA_SIGINFO; # else act.sa_handler = signal_handler; sigemptyset(&act.sa_mask); act.sa_flags = 0; # endif sigaction(SIGINT, &act, NULL); sigaction(SIGTERM, &act, NULL); sigaction(SIGHUP, &act, NULL); sigaction(SIGALRM, &act, NULL); sigaction(SIGUSR1, &act, NULL); /* it should be safe to restart syscalls after SIGCHLD */ act.sa_flags |= SA_RESTART | SA_NOCLDSTOP; sigaction(SIGCHLD, &act, NULL); #elif defined(HAVE_SIGNAL) /* ignore the SIGPIPE from sendfile() */ signal(SIGPIPE, SIG_IGN); signal(SIGALRM, signal_handler); signal(SIGTERM, signal_handler); signal(SIGHUP, signal_handler); signal(SIGCHLD, signal_handler); signal(SIGINT, signal_handler); signal(SIGUSR1, signal_handler); #endif srv->gid = getgid(); srv->uid = getuid(); srv->pid = getpid(); /* write pid file */ if (pid_fd > 2) { buffer_copy_int(srv->tmp_buf, srv->pid); buffer_append_string_len(srv->tmp_buf, CONST_STR_LEN("\n")); if (-1 == write_all(pid_fd, CONST_BUF_LEN(srv->tmp_buf))) { log_error_write(srv, __FILE__, __LINE__, "ss", "Couldn't write pid file:", strerror(errno)); close(pid_fd); pid_fd = -1; return -1; } } else if (pid_fd < -2) { pid_fd = -pid_fd; } /* Close stderr ASAP in the child process to make sure that nothing * is being written to that fd which may not be valid anymore. */ if (!srv->srvconf.preflight_check) { if (-1 == log_error_open(srv)) { log_error_write(srv, __FILE__, __LINE__, "s", "Opening errorlog failed. Going down."); return -1; } log_error_write(srv, __FILE__, __LINE__, "s", "server started (" PACKAGE_DESC ")"); } if (buffer_is_empty(srv->config_storage[0]->server_tag)) { buffer_copy_string_len(srv->config_storage[0]->server_tag, CONST_STR_LEN(PACKAGE_DESC)); } if (HANDLER_GO_ON != plugins_call_set_defaults(srv)) { log_error_write(srv, __FILE__, __LINE__, "s", "Configuration of plugins failed. Going down."); return -1; } /* settings might be enabled during module config set defaults */ srv->config_storage[0]->high_precision_timestamps = srv->srvconf.high_precision_timestamps; /* dump unused config-keys */ for (i = 0; i < srv->config_context->used; i++) { array *config = ((data_config *)srv->config_context->data[i])->value; size_t j; for (j = 0; config && j < config->used; j++) { data_unset *du = config->data[j]; /* all var.* is known as user defined variable */ if (strncmp(du->key->ptr, "var.", sizeof("var.") - 1) == 0) { continue; } if (NULL == array_get_element_klen(srv->config_touched, CONST_BUF_LEN(du->key))) { log_error_write(srv, __FILE__, __LINE__, "sbs", "WARNING: unknown config-key:", du->key, "(ignored)"); } } } if (srv->config_unsupported) { log_error_write(srv, __FILE__, __LINE__, "s", "Configuration contains unsupported keys. Going down."); } if (srv->config_deprecated) { log_error_write(srv, __FILE__, __LINE__, "s", "Configuration contains deprecated keys. Going down."); } if (srv->config_unsupported || srv->config_deprecated) { return -1; } if (srv->srvconf.preflight_check) { /*printf("Preflight OK");*//*(stdout reopened to /dev/null)*/ return 0; } #ifdef HAVE_FORK /** * notify daemonize-grandparent of successful startup * do this before any further forking is done (workers) */ if (0 == srv->srvconf.dont_daemonize && -1 != parent_pipe_fd) { if (0 > write(parent_pipe_fd, "", 1)) return -1; close(parent_pipe_fd); } if (idle_limit && srv->srvconf.max_worker) { srv->srvconf.max_worker = 0; log_error_write(srv, __FILE__, __LINE__, "s", "server idle time limit command line option disables server.max-worker config file option."); } /* start watcher and workers */ num_childs = srv->srvconf.max_worker; if (num_childs > 0) { pid_t pids[num_childs]; pid_t pid; const int npids = num_childs; int child = 0; unsigned int timer = 0; for (int n = 0; n < npids; ++n) pids[n] = -1; while (!child && !srv_shutdown && !graceful_shutdown) { if (num_childs > 0) { switch ((pid = fork())) { case -1: return -1; case 0: child = 1; alarm(0); break; default: num_childs--; for (int n = 0; n < npids; ++n) { if (-1 == pids[n]) { pids[n] = pid; break; } } break; } } else { int status; if (-1 != (pid = wait(&status))) { srv->cur_ts = time(NULL); if (plugins_call_handle_waitpid(srv, pid, status) != HANDLER_GO_ON) { if (!timer) alarm((timer = 5)); continue; } switch (fdevent_reaped_logger_pipe(pid)) { default: break; case -1: if (!timer) alarm((timer = 5)); /* fall through */ case 1: continue; } /** * check if one of our workers went away */ for (int n = 0; n < npids; ++n) { if (pid == pids[n]) { pids[n] = -1; num_childs++; break; } } } else { switch (errno) { case EINTR: srv->cur_ts = time(NULL); /** * if we receive a SIGHUP we have to close our logs ourself as we don't * have the mainloop who can help us here */ if (handle_sig_hup) { handle_sig_hup = 0; log_error_cycle(srv); /* forward SIGHUP to workers */ for (int n = 0; n < npids; ++n) { if (pids[n] > 0) kill(pids[n], SIGHUP); } } if (handle_sig_alarm) { handle_sig_alarm = 0; timer = 0; plugins_call_handle_trigger(srv); fdevent_restart_logger_pipes(srv->cur_ts); } break; default: break; } } } } /** * for the parent this is the exit-point */ if (!child) { /** * kill all children too */ if (graceful_shutdown || graceful_restart) { /* flag to ignore one SIGINT if graceful_restart */ if (graceful_restart) graceful_restart = 2; kill(0, SIGINT); server_graceful_state(srv); } else if (srv_shutdown) { kill(0, SIGTERM); } return 0; } /* ignore SIGUSR1 in workers; only parent directs graceful restart */ #ifdef HAVE_SIGACTION { struct sigaction actignore; memset(&actignore, 0, sizeof(actignore)); actignore.sa_handler = SIG_IGN; sigaction(SIGUSR1, &actignore, NULL); } #elif defined(HAVE_SIGNAL) signal(SIGUSR1, SIG_IGN); #endif /** * make sure workers do not muck with pid-file */ if (0 <= pid_fd) { close(pid_fd); pid_fd = -1; } buffer_clear(srv->srvconf.pid_file); fdevent_clr_logger_pipe_pids(); srv->pid = getpid(); li_rand_reseed(); } #endif if (NULL == (srv->ev = fdevent_init(srv))) { log_error_write(srv, __FILE__, __LINE__, "s", "fdevent_init failed"); return -1; } /* libev backend overwrites our SIGCHLD handler and calls waitpid on SIGCHLD; we want our own SIGCHLD handling. */ #ifdef HAVE_SIGACTION sigaction(SIGCHLD, &act, NULL); #elif defined(HAVE_SIGNAL) signal(SIGCHLD, signal_handler); #endif /* * kqueue() is called here, select resets its internals, * all server sockets get their handlers * * */ if (0 != network_register_fdevents(srv)) { return -1; } /* might fail if user is using fam (not gamin) and famd isn't running */ if (NULL == (srv->stat_cache = stat_cache_init(srv))) { log_error_write(srv, __FILE__, __LINE__, "s", "stat-cache could not be setup, dieing."); return -1; } #ifdef USE_ALARM { /* setup periodic timer (1 second) */ struct itimerval interval; interval.it_interval.tv_sec = 1; interval.it_interval.tv_usec = 0; interval.it_value.tv_sec = 1; interval.it_value.tv_usec = 0; if (setitimer(ITIMER_REAL, &interval, NULL)) { log_error_write(srv, __FILE__, __LINE__, "s", "setting timer failed"); return -1; } } #endif /* get the current number of FDs */ { int fd = fdevent_open_devnull(); if (fd >= 0) { srv->cur_fds = fd; close(fd); } } if (0 != server_sockets_set_nb_cloexec(srv)) { return -1; } if (oneshot_fd && server_oneshot_init(srv, oneshot_fd)) { oneshot_fd = -1; } /* main-loop */ while (!srv_shutdown) { int n; size_t ndx; time_t min_ts; if (handle_sig_hup) { handler_t r; /* reset notification */ handle_sig_hup = 0; /* cycle logfiles */ switch(r = plugins_call_handle_sighup(srv)) { case HANDLER_GO_ON: break; default: log_error_write(srv, __FILE__, __LINE__, "sd", "sighup-handler return with an error", r); break; } if (-1 == log_error_cycle(srv)) { log_error_write(srv, __FILE__, __LINE__, "s", "cycling errorlog failed, dying"); return -1; } else { #ifdef HAVE_SIGACTION log_error_write(srv, __FILE__, __LINE__, "sdsd", "logfiles cycled UID =", last_sighup_info.si_uid, "PID =", last_sighup_info.si_pid); #else log_error_write(srv, __FILE__, __LINE__, "s", "logfiles cycled"); #endif } } if (handle_sig_alarm) { /* a new second */ #ifdef USE_ALARM /* reset notification */ handle_sig_alarm = 0; #endif /* get current time */ min_ts = time(NULL); if (min_ts != srv->cur_ts) { #ifdef DEBUG_CONNECTION_STATES int cs = 0; #endif connections *conns = srv->conns; handler_t r; switch(r = plugins_call_handle_trigger(srv)) { case HANDLER_GO_ON: break; case HANDLER_ERROR: log_error_write(srv, __FILE__, __LINE__, "s", "one of the triggers failed"); break; default: log_error_write(srv, __FILE__, __LINE__, "d", r); break; } srv->cur_ts = min_ts; /* check idle time limit, if enabled */ if (idle_limit && idle_limit < min_ts - last_active_ts && !graceful_shutdown) { log_error_write(srv, __FILE__, __LINE__, "sDs", "[note] idle timeout", (int)idle_limit, "s exceeded, initiating graceful shutdown"); graceful_shutdown = 2; /* value 2 indicates idle timeout */ if (graceful_restart) { graceful_restart = 0; if (pid_fd < -2) pid_fd = -pid_fd; server_sockets_close(srv); } } #ifdef HAVE_GETLOADAVG /* refresh loadavg data every 30 seconds */ if (srv->srvconf.loadts + 30 < min_ts) { if (-1 != getloadavg(srv->srvconf.loadavg, 3)) { srv->srvconf.loadts = min_ts; } } #endif /* free excess chunkqueue buffers every 64 seconds */ if (0 == (min_ts & 0x3f)) chunkqueue_chunk_pool_clear(); /* cleanup stat-cache */ stat_cache_trigger_cleanup(srv); /* reset global/aggregate rate limit counters */ for (i = 0; i < srv->config_context->used; ++i) { srv->config_storage[i]->global_bytes_per_second_cnt = 0; } /* if graceful_shutdown, accelerate cleanup of recently completed request/responses */ if (graceful_shutdown && !srv_shutdown) server_graceful_shutdown_maint(srv); /** * check all connections for timeouts * */ for (ndx = 0; ndx < conns->used; ndx++) { connection * const con = conns->ptr[ndx]; const int waitevents = fdevent_event_get_interest(srv->ev, con->fd); int changed = 0; int t_diff; if (con->state == CON_STATE_CLOSE) { if (srv->cur_ts - con->close_timeout_ts > HTTP_LINGER_TIMEOUT) { changed = 1; } } else if (waitevents & FDEVENT_IN) { if (con->request_count == 1 || con->state != CON_STATE_READ) { /* e.g. CON_STATE_READ_POST || CON_STATE_WRITE */ if (srv->cur_ts - con->read_idle_ts > con->conf.max_read_idle) { /* time - out */ if (con->conf.log_request_handling) { log_error_write(srv, __FILE__, __LINE__, "sd", "connection closed - read timeout:", con->fd); } connection_set_state(srv, con, CON_STATE_ERROR); changed = 1; } } else { if (srv->cur_ts - con->read_idle_ts > con->keep_alive_idle) { /* time - out */ if (con->conf.log_request_handling) { log_error_write(srv, __FILE__, __LINE__, "sd", "connection closed - keep-alive timeout:", con->fd); } connection_set_state(srv, con, CON_STATE_ERROR); changed = 1; } } } /* max_write_idle timeout currently functions as backend timeout, * too, after response has been started. * future: have separate backend timeout, and then change this * to check for write interest before checking for timeout */ /*if (waitevents & FDEVENT_OUT)*/ if ((con->state == CON_STATE_WRITE) && (con->write_request_ts != 0)) { #if 0 if (srv->cur_ts - con->write_request_ts > 60) { log_error_write(srv, __FILE__, __LINE__, "sdd", "connection closed - pre-write-request-timeout:", con->fd, srv->cur_ts - con->write_request_ts); } #endif if (srv->cur_ts - con->write_request_ts > con->conf.max_write_idle) { /* time - out */ if (con->conf.log_timeouts) { log_error_write(srv, __FILE__, __LINE__, "sbsbsosds", "NOTE: a request from", con->dst_addr_buf, "for", con->request.uri, "timed out after writing", con->bytes_written, "bytes. We waited", (int)con->conf.max_write_idle, "seconds. If this a problem increase server.max-write-idle"); } connection_set_state(srv, con, CON_STATE_ERROR); changed = 1; } } /* we don't like div by zero */ if (0 == (t_diff = srv->cur_ts - con->connection_start)) t_diff = 1; if (con->traffic_limit_reached && (con->conf.kbytes_per_second == 0 || ((con->bytes_written / t_diff) < con->conf.kbytes_per_second * 1024))) { /* enable connection again */ con->traffic_limit_reached = 0; changed = 1; } con->bytes_written_cur_second = 0; if (changed) { connection_state_machine(srv, con); } #if DEBUG_CONNECTION_STATES if (cs == 0) { fprintf(stderr, "connection-state: "); cs = 1; } fprintf(stderr, "c[%d,%d]: %s ", con->fd, con->fcgi.fd, connection_get_state(con->state)); #endif } #ifdef DEBUG_CONNECTION_STATES if (cs == 1) fprintf(stderr, "\n"); #endif } } if (handle_sig_child) { pid_t pid; handle_sig_child = 0; do { int status; pid = waitpid(-1, &status, WNOHANG); if (pid > 0) { if (plugins_call_handle_waitpid(srv, pid, status) != HANDLER_GO_ON) { continue; } if (0 == srv->srvconf.max_worker) { /* check piped-loggers and restart, even if shutting down */ if (fdevent_waitpid_logger_pipe_pid(pid, srv->cur_ts)) { continue; } } } } while (pid > 0 || (-1 == pid && errno == EINTR)); } if (graceful_shutdown) { server_graceful_state(srv); srv->sockets_disabled = 1; } else if (srv->sockets_disabled) { /* our server sockets are disabled, why ? */ if ((srv->cur_fds + srv->want_fds < srv->max_fds * 8 / 10) && /* we have enough unused fds */ (srv->conns->used <= srv->max_conns * 9 / 10)) { server_sockets_set_event(srv, FDEVENT_IN); log_error_write(srv, __FILE__, __LINE__, "s", "[note] sockets enabled again"); srv->sockets_disabled = 0; } } else { if ((srv->cur_fds + srv->want_fds > srv->max_fds * 9 / 10) || /* out of fds */ (srv->conns->used >= srv->max_conns)) { /* out of connections */ /* disable server-fds */ server_sockets_set_event(srv, 0); if (srv->conns->used >= srv->max_conns) { log_error_write(srv, __FILE__, __LINE__, "s", "[note] sockets disabled, connection limit reached"); } else { log_error_write(srv, __FILE__, __LINE__, "s", "[note] sockets disabled, out-of-fds"); } srv->sockets_disabled = 1; } } if (graceful_shutdown && srv->conns->used == 0) { /* we are in graceful shutdown phase and all connections are closed * we are ready to terminate without harming anyone */ srv_shutdown = 1; break; } /* we still have some fds to share */ if (srv->want_fds) { /* check the fdwaitqueue for waiting fds */ int free_fds = srv->max_fds - srv->cur_fds - 16; connection *con; for (; free_fds > 0 && NULL != (con = fdwaitqueue_unshift(srv, srv->fdwaitqueue)); free_fds--) { connection_state_machine(srv, con); srv->want_fds--; } } if ((n = fdevent_poll(srv->ev, 1000)) > 0) { /* n is the number of events */ int fd; int revents; int fd_ndx; last_active_ts = srv->cur_ts; fd_ndx = -1; do { fdevent_handler handler; void *context; fd_ndx = fdevent_event_next_fdndx (srv->ev, fd_ndx); if (-1 == fd_ndx) break; /* not all fdevent handlers know how many fds got an event */ revents = fdevent_event_get_revent (srv->ev, fd_ndx); fd = fdevent_event_get_fd (srv->ev, fd_ndx); handler = fdevent_get_handler(srv->ev, fd); context = fdevent_get_context(srv->ev, fd); if (NULL != handler) { (*handler)(srv, context, revents); } } while (--n > 0); } else if (n < 0 && errno != EINTR) { log_error_write(srv, __FILE__, __LINE__, "ss", "fdevent_poll failed:", strerror(errno)); } if (n >= 0) fdevent_sched_run(srv, srv->ev); for (ndx = 0; ndx < srv->joblist->used; ndx++) { connection *con = srv->joblist->ptr[ndx]; connection_state_machine(srv, con); } srv->joblist->used = 0; } if (graceful_shutdown || graceful_restart) { server_graceful_state(srv); } if (2 == graceful_shutdown) { /* value 2 indicates idle timeout */ log_error_write(srv, __FILE__, __LINE__, "s", "server stopped after idle timeout"); } else { #ifdef HAVE_SIGACTION log_error_write(srv, __FILE__, __LINE__, "sdsd", "server stopped by UID =", last_sigterm_info.si_uid, "PID =", last_sigterm_info.si_pid); #else log_error_write(srv, __FILE__, __LINE__, "s", "server stopped"); #endif } return 0; } int main (int argc, char **argv) { int rc; #ifdef HAVE_GETUID #ifndef HAVE_ISSETUGID #define issetugid() (geteuid() != getuid() || getegid() != getgid()) #endif if (0 != getuid() && issetugid()) { /*check as early as possible in main()*/ fprintf(stderr, "Are you nuts ? Don't apply a SUID bit to this binary\n"); return -1; } #endif /* for nice %b handling in strftime() */ setlocale(LC_TIME, "C"); do { server * const srv = server_init(); if (graceful_restart) { server_sockets_restore(srv); optind = 1; } rc = server_main(srv, argc, argv); /* clean-up */ remove_pid_file(srv); log_error_close(srv); fdevent_close_logger_pipes(); if (graceful_restart) server_sockets_save(srv); else network_close(srv); connections_free(srv); plugins_free(srv); server_free(srv); if (0 != rc || !graceful_restart) break; /* wait for all children to exit before graceful restart */ while (waitpid(-1, NULL, 0) > 0) ; } while (graceful_restart); return rc; }