/* * Copyright (C) Igor Sysoev * Copyright (C) 2007 Manlio Perillo (manlio.perillo@gmail.com) * Copyright (C) 2010 Phusion * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY AUTHOR AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL AUTHOR OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include "ngx_http_passenger_module.h" #include "Configuration.h" #include "ContentHandler.h" #define HELPER_SERVER_MAX_SHUTDOWN_TIME 5 #define HELPER_SERVER_PASSWORD_SIZE 64 static int first_start = 1; ngx_str_t passenger_schema_string; ngx_str_t passenger_placeholder_upstream_address; CachedFileStat *passenger_stat_cache; AgentsStarter *passenger_agents_starter = NULL; ngx_cycle_t *passenger_current_cycle; /* HISTORIC NOTE: We used to register passenger_content_handler as a default content handler, instead of setting ngx_http_core_loc_conf_t->handler. However, if ngx_http_read_client_request_body (and thus passenger_content_handler) returns NGX_AGAIN, then Nginx will pass the not-fully-receive file upload data to the upstream handler even though it shouldn't. Is this an Nginx bug? In any case, setting ngx_http_core_loc_conf_t->handler fixed the problem. static ngx_int_t register_content_handler(ngx_conf_t *cf) { ngx_http_handler_pt *h; ngx_http_core_main_conf_t *cmcf; cmcf = ngx_http_conf_get_module_main_conf(cf, ngx_http_core_module); h = ngx_array_push(&cmcf->phases[NGX_HTTP_CONTENT_PHASE].handlers); if (h == NULL) { return NGX_ERROR; } *h = passenger_content_handler; return NGX_OK; } */ static void ignore_sigpipe() { struct sigaction action; action.sa_handler = SIG_IGN; action.sa_flags = 0; sigemptyset(&action.sa_mask); sigaction(SIGPIPE, &action, NULL); } char * ngx_str_null_terminate(ngx_str_t *str) { char *result = malloc(str->len + 1); memcpy(result, str->data, str->len); result[str->len] = '\0'; return result; } /** * Save the Nginx master process's PID into a file under the server instance directory. * * A bug/limitation in Nginx doesn't allow us to create the server instance dir * *after* Nginx has daemonized, so the server instance dir's filename contains Nginx's * PID before daemonization. Normally PhusionPassenger::AdminTools::ServerInstance (used * by e.g. passenger-status) will think that the server instance dir is stale because the * PID in the filename doesn't exist. This PID file tells AdminTools::ServerInstance * what the actual PID is. */ static ngx_int_t save_master_process_pid(ngx_cycle_t *cycle) { u_char filename[NGX_MAX_PATH]; u_char *last; FILE *f; last = ngx_snprintf(filename, sizeof(filename) - 1, "%s/control_process.pid", agents_starter_get_server_instance_dir(passenger_agents_starter)); *last = (u_char) '\0'; f = fopen((const char *) filename, "w"); if (f != NULL) { fprintf(f, "%ld", (long) getppid()); fclose(f); } else { ngx_log_error(NGX_LOG_ALERT, cycle->log, ngx_errno, "could not create %s", filename); } return NGX_OK; } /** * This function is called after forking and just before exec()ing the helper server. */ static void starting_helper_server_after_fork(void *arg) { ngx_cycle_t *cycle = (void *) arg; char *log_filename; FILE *log_file; /* At this point, stdout and stderr may still point to the console. * Make sure that they're both redirected to the log file. */ log_file = NULL; if (cycle->new_log.file->name.len > 0) { log_filename = ngx_str_null_terminate(&cycle->new_log.file->name); log_file = fopen((const char *) log_filename, "a"); if (log_file == NULL) { ngx_log_error(NGX_LOG_ALERT, cycle->log, ngx_errno, "could not open the error log file for writing"); } free(log_filename); } else if (cycle->log != NULL && cycle->log->file->name.len > 0) { log_filename = ngx_str_null_terminate(&cycle->log->file->name); log_file = fopen((const char *) log_filename, "a"); if (log_file == NULL) { ngx_log_error(NGX_LOG_ALERT, cycle->log, ngx_errno, "could not open the error log file for writing"); } free(log_filename); } if (log_file == NULL) { /* If the log file cannot be opened then we redirect stdout * and stderr to /dev/null, because if the user disconnects * from the console on which Nginx is started, then on Linux * any writes to stdout or stderr will result in an EIO error. */ log_file = fopen("/dev/null", "w"); } if (log_file != NULL) { dup2(fileno(log_file), 1); dup2(fileno(log_file), 2); fclose(log_file); } /* Set SERVER_SOFTWARE so that application processes know what web * server they're running on during startup. */ setenv("SERVER_SOFTWARE", NGINX_VER, 1); } static ngx_int_t create_file(ngx_cycle_t *cycle, const u_char *filename, const u_char *contents, size_t len) { FILE *f; int ret; size_t total_written = 0, written; f = fopen((const char *) filename, "w"); if (f != NULL) { /* We must do something with these return values because * otherwise on some platforms it will cause a compiler * warning. */ do { ret = fchmod(fileno(f), S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH); } while (ret == -1 && errno == EINTR); do { written = fwrite(contents + total_written, 1, len - total_written, f); total_written += written; } while (total_written < len); fclose(f); return NGX_OK; } else { ngx_log_error(NGX_LOG_ALERT, cycle->log, ngx_errno, "could not create %s", filename); return NGX_ERROR; } } /** * Start the helper server and save its runtime information into various variables. * * @pre The helper server isn't already started. * @pre The Nginx configuration has been loaded. */ static ngx_int_t start_helper_server(ngx_cycle_t *cycle) { ngx_core_conf_t *core_conf; ngx_int_t ret, result; ngx_uint_t i; ngx_str_t *prestart_uris; char **prestart_uris_ary = NULL; u_char filename[NGX_MAX_PATH], *last; char *debug_log_file = NULL; char *default_user = NULL; char *default_group = NULL; char *passenger_root = NULL; char *ruby = NULL; char *analytics_log_dir; char *analytics_log_user; char *analytics_log_group; char *analytics_log_permissions; char *union_station_gateway_address; char *union_station_gateway_cert; char *error_message = NULL; core_conf = (ngx_core_conf_t *) ngx_get_conf(cycle->conf_ctx, ngx_core_module); result = NGX_OK; /* Create null-terminated versions of some strings. */ debug_log_file = ngx_str_null_terminate(&passenger_main_conf.debug_log_file); default_user = ngx_str_null_terminate(&passenger_main_conf.default_user); default_group = ngx_str_null_terminate(&passenger_main_conf.default_group); passenger_root = ngx_str_null_terminate(&passenger_main_conf.root_dir); ruby = ngx_str_null_terminate(&passenger_main_conf.ruby); analytics_log_dir = ngx_str_null_terminate(&passenger_main_conf.analytics_log_dir); analytics_log_user = ngx_str_null_terminate(&passenger_main_conf.analytics_log_user); analytics_log_group = ngx_str_null_terminate(&passenger_main_conf.analytics_log_group); analytics_log_permissions = ngx_str_null_terminate(&passenger_main_conf.analytics_log_permissions); union_station_gateway_address = ngx_str_null_terminate(&passenger_main_conf.union_station_gateway_address); union_station_gateway_cert = ngx_str_null_terminate(&passenger_main_conf.union_station_gateway_cert); prestart_uris = (ngx_str_t *) passenger_main_conf.prestart_uris->elts; prestart_uris_ary = calloc(sizeof(char *), passenger_main_conf.prestart_uris->nelts); for (i = 0; i < passenger_main_conf.prestart_uris->nelts; i++) { prestart_uris_ary[i] = malloc(prestart_uris[i].len + 1); if (prestart_uris_ary[i] == NULL) { ngx_log_error(NGX_LOG_ALERT, cycle->log, ENOMEM, "Cannot allocate memory"); result = NGX_ERROR; goto cleanup; } memcpy(prestart_uris_ary[i], prestart_uris[i].data, prestart_uris[i].len); prestart_uris_ary[i][prestart_uris[i].len] = '\0'; } ret = agents_starter_start(passenger_agents_starter, passenger_main_conf.log_level, debug_log_file, getpid(), "", passenger_main_conf.user_switching, default_user, default_group, core_conf->user, core_conf->group, passenger_root, ruby, passenger_main_conf.max_pool_size, passenger_main_conf.max_instances_per_app, passenger_main_conf.pool_idle_time, "", analytics_log_dir, analytics_log_user, analytics_log_group, analytics_log_permissions, union_station_gateway_address, passenger_main_conf.union_station_gateway_port, union_station_gateway_cert, (const char **) prestart_uris_ary, passenger_main_conf.prestart_uris->nelts, starting_helper_server_after_fork, cycle, &error_message); if (!ret) { ngx_log_error(NGX_LOG_ALERT, cycle->log, ngx_errno, "%s", error_message); result = NGX_ERROR; goto cleanup; } /* Create the file passenger_temp_dir + "/control_process.pid" * and make it writable by the worker processes. This is because * save_master_process_pid is run after Nginx has lowered privileges. */ last = ngx_snprintf(filename, sizeof(filename) - 1, "%s/control_process.pid", agents_starter_get_server_instance_dir(passenger_agents_starter)); *last = (u_char) '\0'; if (create_file(cycle, filename, (const u_char *) "", 0) != NGX_OK) { result = NGX_ERROR; goto cleanup; } do { ret = chown((const char *) filename, (uid_t) core_conf->user, (gid_t) -1); } while (ret == -1 && errno == EINTR); if (ret == -1) { result = NGX_ERROR; goto cleanup; } /* Create various other info files. */ last = ngx_snprintf(filename, sizeof(filename) - 1, "%s/web_server.txt", agents_starter_get_generation_dir(passenger_agents_starter)); *last = (u_char) '\0'; if (create_file(cycle, filename, (const u_char *) NGINX_VER, strlen(NGINX_VER)) != NGX_OK) { result = NGX_ERROR; goto cleanup; } last = ngx_snprintf(filename, sizeof(filename) - 1, "%s/config_files.txt", agents_starter_get_generation_dir(passenger_agents_starter)); *last = (u_char) '\0'; if (create_file(cycle, filename, cycle->conf_file.data, cycle->conf_file.len) != NGX_OK) { result = NGX_ERROR; goto cleanup; } last = ngx_snprintf(filename, sizeof(filename) - 1, "%s/analytics_log_dir.txt", agents_starter_get_generation_dir(passenger_agents_starter)); *last = (u_char) '\0'; if (create_file(cycle, filename, passenger_main_conf.analytics_log_dir.data, passenger_main_conf.analytics_log_dir.len) != NGX_OK) { result = NGX_ERROR; goto cleanup; } cleanup: free(debug_log_file); free(default_user); free(default_group); free(passenger_root); free(ruby); free(analytics_log_dir); free(analytics_log_user); free(analytics_log_group); free(analytics_log_permissions); free(union_station_gateway_address); free(union_station_gateway_cert); free(error_message); if (prestart_uris_ary != NULL) { for (i = 0; i < passenger_main_conf.prestart_uris->nelts; i++) { free(prestart_uris_ary[i]); } free(prestart_uris_ary); } if (result == NGX_ERROR && passenger_main_conf.abort_on_startup_error) { exit(1); } return result; } /** * Shutdown the helper server, if there's one running. */ static void shutdown_helper_server() { if (passenger_agents_starter != NULL) { agents_starter_free(passenger_agents_starter); passenger_agents_starter = NULL; } } /** * Called when: * - Nginx is started, before the configuration is loaded and before daemonization. * - Nginx is restarted, before the configuration is reloaded. */ static ngx_int_t pre_config_init(ngx_conf_t *cf) { char *error_message; shutdown_helper_server(); ngx_memzero(&passenger_main_conf, sizeof(passenger_main_conf_t)); passenger_schema_string.data = (u_char *) "passenger:"; passenger_schema_string.len = sizeof("passenger:") - 1; passenger_placeholder_upstream_address.data = (u_char *) "unix:/passenger_helper_server"; passenger_placeholder_upstream_address.len = sizeof("unix:/passenger_helper_server") - 1; passenger_stat_cache = cached_file_stat_new(1024); passenger_agents_starter = agents_starter_new(AS_NGINX, &error_message); if (passenger_agents_starter == NULL) { ngx_log_error(NGX_LOG_ALERT, cf->log, ngx_errno, "%s", error_message); free(error_message); return NGX_ERROR; } return NGX_OK; } /** * Called when: * - Nginx is started, before daemonization and after the configuration has loaded. * - Nginx is restarted, after the configuration has reloaded. */ static ngx_int_t init_module(ngx_cycle_t *cycle) { if (passenger_main_conf.root_dir.len != 0) { if (first_start) { /* Ignore SIGPIPE now so that, if the helper server fails to start, * Nginx doesn't get killed by the default SIGPIPE handler upon * writing the password to the helper server. */ ignore_sigpipe(); first_start = 0; } if (start_helper_server(cycle) != NGX_OK) { passenger_main_conf.root_dir.len = 0; return NGX_OK; } passenger_current_cycle = cycle; } return NGX_OK; } /** * Called when an Nginx worker process is started. This happens after init_module * is called. * * If 'master_process' is turned off, then there is only one single Nginx process * in total, and this process also acts as the worker process. In this case * init_worker_process is only called when Nginx is started, but not when it's restarted. */ static ngx_int_t init_worker_process(ngx_cycle_t *cycle) { ngx_core_conf_t *core_conf; if (passenger_main_conf.root_dir.len != 0) { save_master_process_pid(cycle); core_conf = (ngx_core_conf_t *) ngx_get_conf(cycle->conf_ctx, ngx_core_module); if (core_conf->master) { agents_starter_detach(passenger_agents_starter); } } return NGX_OK; } /** * Called when Nginx exits. Not called when Nginx is restarted. */ static void exit_master(ngx_cycle_t *cycle) { shutdown_helper_server(); } static ngx_http_module_t passenger_module_ctx = { pre_config_init, /* preconfiguration */ /* register_content_handler */ NULL, /* postconfiguration */ passenger_create_main_conf, /* create main configuration */ passenger_init_main_conf, /* init main configuration */ NULL, /* create server configuration */ NULL, /* merge server configuration */ passenger_create_loc_conf, /* create location configuration */ passenger_merge_loc_conf /* merge location configuration */ }; ngx_module_t ngx_http_passenger_module = { NGX_MODULE_V1, &passenger_module_ctx, /* module context */ (ngx_command_t *) passenger_commands, /* module directives */ NGX_HTTP_MODULE, /* module type */ NULL, /* init master */ init_module, /* init module */ init_worker_process, /* init process */ NULL, /* init thread */ NULL, /* exit thread */ NULL, /* exit process */ exit_master, /* exit master */ NGX_MODULE_V1_PADDING };