static char *service_path(char *root, char *path) { int len_root; int len_path; size_t size; char *new_path; /* If no root/prefix exists */ if (!root) { return mk_string_dup(path); } /* Root prefix is set, check if 'path' is absolute or not */ if (*path == '/') { /* Absolute path never uses the prefix */ return mk_string_dup(path); } /* Concatenare root and path */ len_root = strlen(root); len_path = strlen(path); size = (len_root + len_path + 2); new_path = mk_mem_alloc(size); if (!new_path) { return NULL; } snprintf(new_path, size, "%s/%s", root, path); return new_path; }
/* Creates a web service instance */ struct duda_service *duda_service_create(struct duda *d, char *root, char *log, char *data, char *html, char *service) { int ret; void *handle; struct duda_service *ds; struct duda_api_objects *api; if (!d) { return NULL; } /* Check access to root, log, data and html directories */ if (root) { ret = validate_dir(root); if (ret != 0) { return NULL; } } if (log) { ret = validate_dir(log); if (ret != 0) { return NULL; } } if (data) { ret = validate_dir(log); if (ret != 0) { return NULL; } } if (html) { ret = validate_dir(html); if (ret != 0) { return NULL; } } /* Validate the web service file */ handle = dlopen(service, RTLD_LAZY); if (!handle) { fprintf(stderr, "Error opening web service file '%s'\n", service); return NULL; } /* Create web service context */ ds = mk_mem_alloc_z(sizeof(struct duda_service)); if (!ds) { dlclose(handle); return NULL; } /* Root prefix path */ if (root) { ds->path_root = mk_string_dup(root); } /* * If root prefix path is set, for all incoming paths that are not * absolute, we prefix the path */ if (log) { ds->path_log = service_path(root, log); } if (data) { ds->path_data = service_path(root, data); } if (html) { ds->path_html = service_path(root, html); } if (service) { ds->path_service = service_path(root, service); } ds->dl_handle = handle; mk_list_add(&ds->_head, &d->services); /* Initialize references for API objects */ mk_list_init(&ds->router_list); api = duda_api_create(); map_internals(ds, api); return ds; }
static int mk_http_request_prepare(struct mk_http_session *cs, struct mk_http_request *sr) { int status = 0; char *temp; struct mk_list *hosts = &mk_config->hosts; struct mk_list *alias; struct mk_http_header *header; /* * Process URI, if it contains ASCII encoded strings like '%20', * it will return a new memory buffer with the decoded string, otherwise * it returns NULL */ temp = mk_utils_url_decode(sr->uri); if (temp) { sr->uri_processed.data = temp; sr->uri_processed.len = strlen(temp); } else { sr->uri_processed.data = sr->uri.data; sr->uri_processed.len = sr->uri.len; } /* Always assign the default vhost' */ sr->host_conf = mk_list_entry_first(hosts, struct host, _head); sr->user_home = MK_FALSE; /* Valid request URI? */ if (sr->uri_processed.data[0] != '/') { mk_http_error(MK_CLIENT_BAD_REQUEST, cs, sr); return MK_EXIT_OK; } /* Check if we have a Host header: Hostname ; port */ mk_http_point_header(&sr->host, &cs->parser, MK_HEADER_HOST); /* Header: Connection */ mk_http_point_header(&sr->connection, &cs->parser, MK_HEADER_CONNECTION); /* Header: Range */ mk_http_point_header(&sr->range, &cs->parser, MK_HEADER_RANGE); /* Header: If-Modified-Since */ mk_http_point_header(&sr->if_modified_since, &cs->parser, MK_HEADER_IF_MODIFIED_SINCE); /* HTTP/1.1 needs Host header */ if (!sr->host.data && sr->protocol == MK_HTTP_PROTOCOL_11) { mk_http_error(MK_CLIENT_BAD_REQUEST, cs, sr); return MK_EXIT_OK; } /* Should we close the session after this request ? */ mk_http_keepalive_check(cs, sr); /* Content Length */ header = &cs->parser.headers[MK_HEADER_CONTENT_LENGTH]; if (header->type == MK_HEADER_CONTENT_LENGTH) { sr->_content_length.data = header->val.data; sr->_content_length.len = header->val.len; } else { sr->_content_length.data = NULL; } /* Assign the first node alias */ alias = &sr->host_conf->server_names; sr->host_alias = mk_list_entry_first(alias, struct host_alias, _head); if (sr->host.data) { /* Set the given port */ if (cs->parser.header_host_port > 0) { sr->port = cs->parser.header_host_port; } /* Match the virtual host */ mk_vhost_get(sr->host, &sr->host_conf, &sr->host_alias); /* Check if this virtual host have some redirection */ if (sr->host_conf->header_redirect.data) { mk_header_set_http_status(sr, MK_REDIR_MOVED); sr->headers.location = mk_string_dup(sr->host_conf->header_redirect.data); sr->headers.content_length = 0; sr->headers.location = NULL; mk_header_prepare(cs, sr); return 0; } } /* Is requesting an user home directory ? */ if (mk_config->user_dir && sr->uri_processed.len > 2 && sr->uri_processed.data[1] == MK_USER_HOME) { if (mk_user_init(cs, sr) != 0) { mk_http_error(MK_CLIENT_NOT_FOUND, cs, sr); return MK_EXIT_ABORT; } } /* Plugins Stage 20 */ int ret; ret = mk_plugin_stage_run_20(cs, sr); if (ret == MK_PLUGIN_RET_CLOSE_CONX) { MK_TRACE("STAGE 20 requested close conexion"); return MK_EXIT_ABORT; } /* Normal HTTP process */ status = mk_http_init(cs, sr); MK_TRACE("[FD %i] HTTP Init returning %i", cs->socket, status); return status; }
int mk_http_init(struct client_session *cs, struct session_request *sr) { int ret; int bytes = 0; struct mimetype *mime; MK_TRACE("HTTP Protocol Init"); /* Request to root path of the virtualhost in question */ if (sr->uri_processed.len == 1 && sr->uri_processed.data[0] == '/') { sr->real_path.data = sr->host_conf->documentroot.data; sr->real_path.len = sr->host_conf->documentroot.len; } /* Compose real path */ if (sr->user_home == MK_FALSE) { int len; len = sr->host_conf->documentroot.len + sr->uri_processed.len; if (len < MK_PATH_BASE) { memcpy(sr->real_path_static, sr->host_conf->documentroot.data, sr->host_conf->documentroot.len); memcpy(sr->real_path_static + sr->host_conf->documentroot.len, sr->uri_processed.data, sr->uri_processed.len); sr->real_path_static[len] = '\0'; sr->real_path.data = sr->real_path_static; sr->real_path.len = len; } else { ret = mk_buffer_cat(&sr->real_path, sr->host_conf->documentroot.data, sr->host_conf->documentroot.len, sr->uri_processed.data, sr->uri_processed.len); if (ret < 0) { MK_TRACE("Error composing real path"); return EXIT_ERROR; } } } /* Check backward directory request */ if (memmem(sr->uri_processed.data, sr->uri_processed.len, MK_HTTP_DIRECTORY_BACKWARD, sizeof(MK_HTTP_DIRECTORY_BACKWARD) - 1)) { return mk_request_error(MK_CLIENT_FORBIDDEN, cs, sr); } if (mk_file_get_info(sr->real_path.data, &sr->file_info) != 0) { /* if the requested resource doesn't exist, * check if some plugin would like to handle it */ MK_TRACE("No file, look for handler plugin"); ret = mk_plugin_stage_run(MK_PLUGIN_STAGE_30, cs->socket, NULL, cs, sr); if (ret == MK_PLUGIN_RET_CLOSE_CONX) { if (sr->headers.status > 0) { return mk_request_error(sr->headers.status, cs, sr); } else { return mk_request_error(MK_CLIENT_FORBIDDEN, cs, sr); } } else if (ret == MK_PLUGIN_RET_CONTINUE) { return MK_PLUGIN_RET_CONTINUE; } else if (ret == MK_PLUGIN_RET_END) { return EXIT_NORMAL; } if (sr->file_info.exists == MK_FALSE) { return mk_request_error(MK_CLIENT_NOT_FOUND, cs, sr); } else if (sr->stage30_blocked == MK_FALSE) { return mk_request_error(MK_CLIENT_FORBIDDEN, cs, sr); } } /* is it a valid directory ? */ if (sr->file_info.is_directory == MK_TRUE) { /* Send redirect header if end slash is not found */ if (mk_http_directory_redirect_check(cs, sr) == -1) { MK_TRACE("Directory Redirect"); /* Redirect has been sent */ return -1; } /* looking for an index file */ mk_ptr_t index_file; char tmppath[MK_MAX_PATH]; index_file = mk_request_index(sr->real_path.data, tmppath, MK_MAX_PATH); if (index_file.data) { if (sr->real_path.data != sr->real_path_static) { mk_ptr_t_free(&sr->real_path); sr->real_path = index_file; sr->real_path.data = mk_string_dup(index_file.data); } /* If it's static and it still fits */ else if (index_file.len < MK_PATH_BASE) { memcpy(sr->real_path_static, index_file.data, index_file.len); sr->real_path_static[index_file.len] = '\0'; sr->real_path.len = index_file.len; } /* It was static, but didn't fit */ else { sr->real_path = index_file; sr->real_path.data = mk_string_dup(index_file.data); } mk_file_get_info(sr->real_path.data, &sr->file_info); } } /* Check symbolic link file */ if (sr->file_info.is_link == MK_TRUE) { if (config->symlink == MK_FALSE) { return mk_request_error(MK_CLIENT_FORBIDDEN, cs, sr); } else { int n; char linked_file[MK_MAX_PATH]; n = readlink(sr->real_path.data, linked_file, MK_MAX_PATH); if (n < 0) { return mk_request_error(MK_CLIENT_FORBIDDEN, cs, sr); } } } /* Plugin Stage 30: look for handlers for this request */ if (sr->stage30_blocked == MK_FALSE) { ret = mk_plugin_stage_run(MK_PLUGIN_STAGE_30, cs->socket, NULL, cs, sr); MK_TRACE("[FD %i] STAGE_30 returned %i", cs->socket, ret); switch (ret) { case MK_PLUGIN_RET_CONTINUE: return MK_PLUGIN_RET_CONTINUE; case MK_PLUGIN_RET_CLOSE_CONX: if (sr->headers.status > 0) { return mk_request_error(sr->headers.status, cs, sr); } else { return mk_request_error(MK_CLIENT_FORBIDDEN, cs, sr); } case MK_PLUGIN_RET_END: return EXIT_NORMAL; } } /* * Monkey listens for PUT and DELETE methods in addition to GET, POST and * HEAD, but it does not care about them, so if any plugin did not worked * on it, Monkey will return error 501 (501 Not Implemented). */ if (sr->method == MK_HTTP_METHOD_PUT || sr->method == MK_HTTP_METHOD_DELETE || sr->method == MK_HTTP_METHOD_UNKNOWN) { return mk_request_error(MK_SERVER_NOT_IMPLEMENTED, cs, sr); } /* counter connections */ sr->headers.pconnections_left = (int) (config->max_keep_alive_request - cs->counter_connections); /* Set default value */ mk_header_set_http_status(sr, MK_HTTP_OK); sr->headers.location = NULL; sr->headers.content_length = 0; /* * For OPTIONS method, we let the plugin handle it and * return without any content. */ if (sr->method == MK_HTTP_METHOD_OPTIONS) { sr->headers.allow_methods.data = MK_HTTP_METHOD_AVAILABLE; sr->headers.allow_methods.len = strlen(MK_HTTP_METHOD_AVAILABLE); mk_ptr_t_reset(&sr->headers.content_type); mk_header_send(cs->socket, cs, sr); return EXIT_NORMAL; } else { mk_ptr_t_reset(&sr->headers.allow_methods); } /* read permissions and check file */ if (sr->file_info.read_access == MK_FALSE) { return mk_request_error(MK_CLIENT_FORBIDDEN, cs, sr); } /* Matching MimeType */ mime = mk_mimetype_find(&sr->real_path); if (!mime) { mime = mimetype_default; } if (sr->file_info.is_directory == MK_TRUE) { return mk_request_error(MK_CLIENT_FORBIDDEN, cs, sr); } /* get file size */ if (sr->file_info.size < 0) { return mk_request_error(MK_CLIENT_NOT_FOUND, cs, sr); } sr->headers.last_modified = sr->file_info.last_modification; if (sr->if_modified_since.data && sr->method == MK_HTTP_METHOD_GET) { time_t date_client; /* Date sent by client */ time_t date_file_server; /* Date server file */ date_client = mk_utils_gmt2utime(sr->if_modified_since.data); date_file_server = sr->file_info.last_modification; if (date_file_server <= date_client && date_client > 0 && date_client <= log_current_utime) { mk_header_set_http_status(sr, MK_NOT_MODIFIED); mk_header_send(cs->socket, cs, sr); return EXIT_NORMAL; } } /* Object size for log and response headers */ sr->headers.content_length = sr->file_info.size; sr->headers.real_length = sr->file_info.size; /* Open file */ if (mk_likely(sr->file_info.size > 0)) { sr->fd_file = mk_vhost_open(sr); if (sr->fd_file == -1) { MK_TRACE("open() failed"); return mk_request_error(MK_CLIENT_FORBIDDEN, cs, sr); } sr->bytes_to_send = sr->file_info.size; } /* Process methods */ if (sr->method == MK_HTTP_METHOD_GET || sr->method == MK_HTTP_METHOD_HEAD) { sr->headers.content_type = mime->type; /* HTTP Ranges */ if (sr->range.data != NULL && config->resume == MK_TRUE) { if (mk_http_range_parse(sr) < 0) { sr->headers.ranges[0] = -1; sr->headers.ranges[1] = -1; return mk_request_error(MK_CLIENT_BAD_REQUEST, cs, sr); } if (sr->headers.ranges[0] >= 0 || sr->headers.ranges[1] >= 0) { mk_header_set_http_status(sr, MK_HTTP_PARTIAL); } /* Calc bytes to send & offset */ if (mk_http_range_set(sr, sr->file_info.size) != 0) { sr->headers.content_length = -1; sr->headers.ranges[0] = -1; sr->headers.ranges[1] = -1; return mk_request_error(MK_CLIENT_REQUESTED_RANGE_NOT_SATISF, cs, sr); } } } else { /* without content-type */ mk_ptr_t_reset(&sr->headers.content_type); } /* Send headers */ mk_header_send(cs->socket, cs, sr); if (mk_unlikely(sr->headers.content_length == 0)) { return 0; } /* Send file content */ if (sr->method == MK_HTTP_METHOD_GET || sr->method == MK_HTTP_METHOD_POST) { bytes = mk_http_send_file(cs, sr); } return bytes; }
/* MAIN */ int main(int argc, char **argv) { int opt; char *port_override = NULL; int workers_override = -1; int run_daemon = 0; int balancing_mode = MK_FALSE; int allow_shared_sockets = MK_FALSE; char *one_shot = NULL; char *pid_file = NULL; char *transport_layer = NULL; char *path_config = NULL; char *server_conf_file = NULL; char *plugin_load_conf_file = NULL; char *sites_conf_dir = NULL; char *plugins_conf_dir = NULL; char *mimes_conf_file = NULL; static const struct option long_opts[] = { { "configdir", required_argument, NULL, 'c' }, { "serverconf", required_argument, NULL, 's' }, { "build", no_argument, NULL, 'b' }, { "daemon", no_argument, NULL, 'D' }, { "pid-file", required_argument, NULL, 'I' }, { "port", required_argument, NULL, 'p' }, { "one-shot", required_argument, NULL, 'o' }, { "transport", required_argument, NULL, 't' }, { "workers", required_argument, NULL, 'w' }, { "version", no_argument, NULL, 'v' }, { "help", no_argument, NULL, 'h' }, { "mimes-conf-file", required_argument, NULL, 'm' }, { "plugin-load-conf-file", required_argument, NULL, 'l' }, { "plugins-conf-dir", required_argument, NULL, 'P' }, { "sites-conf-dir", required_argument, NULL, 'S' }, { "balancing-mode", no_argument, NULL, 'B' }, { "allow-shared-sockets", no_argument, NULL, 'T' }, { NULL, 0, NULL, 0 } }; while ((opt = getopt_long(argc, argv, "bDI:Svhp:o:t:w:c:s:m:l:P:S:BT", long_opts, NULL)) != -1) { switch (opt) { case 'b': mk_build_info(); exit(EXIT_SUCCESS); case 'v': mk_version(); exit(EXIT_SUCCESS); case 'h': mk_help(EXIT_SUCCESS); case 'D': run_daemon = 1; break; case 'I': pid_file = optarg; break; case 'p': port_override = optarg; break; case 'o': one_shot = optarg; break; case 't': transport_layer = mk_string_dup(optarg); break; case 'w': workers_override = atoi(optarg); break; case 'c': path_config = optarg; break; case 's': server_conf_file = optarg; break; case 'm': mimes_conf_file = optarg; break; case 'P': plugins_conf_dir = optarg; break; case 'S': sites_conf_dir = optarg; break; case 'B': balancing_mode = MK_TRUE; break; case 'T': allow_shared_sockets = MK_TRUE; break; case 'l': plugin_load_conf_file = optarg; break; case '?': mk_help(EXIT_FAILURE); } } /* setup basic configurations */ mk_config = mk_config_init(); /* Init Kernel version data */ mk_kernel_init(); mk_kernel_features(); /* set configuration path */ if (!path_config) { mk_config->path_config = MK_PATH_CONF; } else { mk_config->path_config = path_config; } /* set target configuration file for the server */ if (!server_conf_file) { mk_config->server_conf_file = MK_DEFAULT_CONFIG_FILE; } else { mk_config->server_conf_file = server_conf_file; } if (!pid_file) { mk_config->pid_file_path = NULL; } else { mk_config->pid_file_path = pid_file; } if (run_daemon) { mk_config->is_daemon = MK_TRUE; } else { mk_config->is_daemon = MK_FALSE; } if (!mimes_conf_file) { mk_config->mimes_conf_file = MK_DEFAULT_MIMES_CONF_FILE; } else { mk_config->mimes_conf_file = mimes_conf_file; } if (!plugin_load_conf_file) { mk_config->plugin_load_conf_file = MK_DEFAULT_PLUGIN_LOAD_CONF_FILE; } else { mk_config->plugin_load_conf_file = plugin_load_conf_file; } if (!sites_conf_dir) { mk_config->sites_conf_dir = MK_DEFAULT_SITES_CONF_DIR; } else { mk_config->sites_conf_dir = sites_conf_dir; } if (!plugins_conf_dir) { mk_config->plugins_conf_dir = MK_DEFAULT_PLUGINS_CONF_DIR; } else { mk_config->plugins_conf_dir = plugins_conf_dir; } /* Override some configuration */ mk_config->one_shot = one_shot; mk_config->port_override = port_override; mk_config->transport_layer = transport_layer; #ifdef TRACE monkey_init_time = time(NULL); MK_TRACE("Monkey TRACE is enabled"); env_trace_filter = getenv("MK_TRACE_FILTER"); pthread_mutex_init(&mutex_trace, (pthread_mutexattr_t *) NULL); #endif pthread_mutex_init(&mutex_port_init, (pthread_mutexattr_t *) NULL); mk_version(); mk_signal_init(); #ifdef LINUX_TRACE mk_info("Linux Trace enabled"); #endif /* Override number of thread workers */ if (workers_override >= 0) { mk_config->workers = workers_override; } else { mk_config->workers = -1; } /* Core and Scheduler setup */ mk_config_start_configure(); mk_sched_init(); if (balancing_mode == MK_TRUE) { mk_config->scheduler_mode = MK_SCHEDULER_FAIR_BALANCING; } /* Clock init that must happen before starting threads */ mk_clock_sequential_init(); /* Load plugins */ mk_plugin_api_init(); mk_plugin_load_all(); /* Running Monkey as daemon */ if (mk_config->is_daemon == MK_TRUE) { mk_utils_set_daemon(); } /* Register PID of Monkey */ mk_utils_register_pid(); /* Workers: logger and clock */ mk_clock_tid = mk_utils_worker_spawn((void *) mk_clock_worker_init, NULL); /* Init thread keys */ mk_thread_keys_init(); /* Configuration sanity check */ mk_config_sanity_check(); if (mk_config->scheduler_mode == MK_SCHEDULER_REUSEPORT && mk_config_listen_check_busy() == MK_TRUE && allow_shared_sockets == MK_FALSE) { mk_warn("Some Listen interface is busy, re-try using -T. Aborting."); exit(EXIT_FAILURE); } /* Invoke Plugin PRCTX hooks */ mk_plugin_core_process(); /* Launch monkey http workers */ MK_TLS_INIT(); mk_server_launch_workers(); /* Print server details */ mk_details(); /* Change process owner */ mk_user_set_uidgid(); /* Server loop, let's listen for incomming clients */ mk_server_loop(); mk_mem_free(mk_config); return 0; }