/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ /* Monkey HTTP Server * ================== * Copyright 2001-2017 Eduardo Silva * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #include #include #include #include #include #include #include #include #include #include #include #ifndef _WIN32 #include #include #endif enum { bufsize = 256 }; static struct plugin_stagemap *plg_stagemap; struct plugin_network_io *plg_netiomap; struct mk_plugin *mk_plugin_lookup(char *shortname, struct mk_server *server) { struct mk_list *head; struct mk_plugin *p = NULL; mk_list_foreach(head, &server->plugins) { p = mk_list_entry(head, struct mk_plugin, _head); if (strcmp(p->shortname, shortname) == 0){ return p; } } return NULL; } void *mk_plugin_load_dynamic(const char *path) { void *handle; #ifdef _WIN32 handle = (void *) LoadLibraryA(path); #else handle = dlopen(path, RTLD_LAZY); if (!handle) { mk_warn("dlopen() %s", dlerror()); } #endif return handle; } void *mk_plugin_load_symbol(void *handler, const char *symbol) { void *s; #ifdef _WIN32 s = GetProcAddress((HMODULE)handler, symbol); #else dlerror(); s = dlsym(handler, symbol); if (dlerror() != NULL) { return NULL; } #endif return s; } /* Initialize a plugin, trigger the init_plugin callback */ static int mk_plugin_init(struct plugin_api *api, struct mk_plugin *plugin, struct mk_server *server) { int ret; unsigned long len; char path[1024]; char *conf_dir = NULL; struct file_info f_info; MK_TRACE("Load Plugin: '%s'", plugin->shortname); snprintf(path, 1024, "%s/%s", server->path_conf_root, server->conf_plugins); ret = mk_file_get_info(path, &f_info, MK_FILE_READ); if (ret == -1 || f_info.is_directory == MK_FALSE) { snprintf(path, 1024, "%s", server->conf_plugins); } /* Build plugin configuration path */ mk_string_build(&conf_dir, &len, "%s/%s/", path, plugin->shortname); /* Init plugin */ plugin->api = api; plugin->server_ctx = server; if (plugin->network != NULL) { plugin->network->plugin = plugin; } ret = plugin->init_plugin(plugin, conf_dir); mk_mem_free(conf_dir); return ret; } /* * Load a plugin into Monkey core, 'type' defines if it's a MK_PLUGIN_STATIC or * a MK_PLUGIN_DYNAMIC. 'shortname' is mandatory and 'path' is only used when * MK_PLUGIN_DYNAMIC is set and represents the absolute path of the shared * library. */ struct mk_plugin *mk_plugin_load(int type, const char *shortname, void *data, struct mk_server *server) { char *path; char symbol[64]; void *handler; struct mk_list *head; struct mk_plugin *tmp; struct mk_plugin *plugin = NULL; struct mk_plugin_stage *stage; /* Set main struct name to reference */ if (type == MK_PLUGIN_DYNAMIC) { path = (char *) data; handler = mk_plugin_load_dynamic(path); if (!handler) { return NULL; } snprintf(symbol, sizeof(symbol) - 1, "mk_plugin_%s", shortname); plugin = mk_plugin_load_symbol(handler, symbol); if (!plugin) { mk_warn("Plugin '%s' is not registering properly", path); #ifdef _WIN32 FreeLibrary((HMODULE)handler); #else dlclose(handler); #endif return NULL; } /* Make sure this is not loaded twice (ref #218) */ mk_list_foreach(head, &server->plugins) { tmp = mk_list_entry(head, struct mk_plugin, _head); if (tmp->load_type == MK_PLUGIN_STATIC && strcmp(tmp->name, plugin->name) == 0){ mk_warn("Plugin '%s' have been built-in.", tmp->shortname); #ifdef _WIN32 FreeLibrary((HMODULE)handler); #else dlclose(handler); #endif return NULL; } } plugin->load_type = MK_PLUGIN_DYNAMIC; plugin->handler = handler; plugin->path = mk_string_dup(path); } else if (type == MK_PLUGIN_STATIC) { plugin = (struct mk_plugin *) data; plugin->load_type = MK_PLUGIN_STATIC; } if (!plugin) { return NULL; } /* Validate all callbacks are set */ if (!plugin->shortname || !plugin->name || !plugin->version || !plugin->init_plugin || !plugin->exit_plugin) { mk_warn("Plugin '%s' is not registering all fields properly", shortname); return NULL; } if (plugin->hooks & MK_PLUGIN_NETWORK_LAYER) { mk_bug(!plugin->network); } mk_list_init(&plugin->stage_list); if (plugin->hooks & MK_PLUGIN_STAGE) { struct mk_plugin_stage *st; stage = plugin->stage; if (stage->stage10) { st = mk_mem_alloc(sizeof(struct mk_plugin_stage)); st->stage10 = stage->stage10; st->plugin = plugin; mk_list_add(&st->_head, &server->stage10_handler); mk_list_add(&st->_parent_head, &plugin->stage_list); } if (stage->stage20) { st = mk_mem_alloc(sizeof(struct mk_plugin_stage)); st->stage20 = stage->stage20; st->plugin = plugin; mk_list_add(&st->_head, &server->stage20_handler); mk_list_add(&st->_parent_head, &plugin->stage_list); } if (stage->stage30) { st = mk_mem_alloc(sizeof(struct mk_plugin_stage)); st->stage30 = stage->stage30; st->plugin = plugin; mk_list_add(&st->_head, &server->stage30_handler); mk_list_add(&st->_parent_head, &plugin->stage_list); } if (stage->stage40) { st = mk_mem_alloc(sizeof(struct mk_plugin_stage)); st->stage40 = stage->stage40; st->plugin = plugin; mk_list_add(&st->_head, &server->stage40_handler); mk_list_add(&st->_parent_head, &plugin->stage_list); } if (stage->stage50) { st = mk_mem_alloc(sizeof(struct mk_plugin_stage)); st->stage50 = stage->stage50; st->plugin = plugin; mk_list_add(&st->_head, &server->stage50_handler); mk_list_add(&st->_parent_head, &plugin->stage_list); } } if (type == MK_PLUGIN_DYNAMIC) { /* Add Plugin to the end of the list */ mk_list_add(&plugin->_head, &server->plugins); } return plugin; } void mk_plugin_unregister(struct mk_plugin *p) { mk_mem_free(p->path); mk_list_del(&p->_head); if (p->load_type == MK_PLUGIN_DYNAMIC) { #ifdef _WIN32 FreeLibrary((HMODULE)p->handler); #else dlclose(p->handler); #endif } } void mk_plugin_api_init(struct mk_server *server) { struct plugin_api *api; /* Create an instance of the API */ api = mk_mem_alloc_z(sizeof(struct plugin_api)); #ifndef _WIN32 __builtin_prefetch(api); #endif /* Setup and connections list */ /* FIXME: api->config = server; */ /* API plugins funcions */ /* Error helper */ api->_error = mk_print; /* HTTP callbacks */ api->http_request_end = mk_plugin_http_request_end; api->http_request_error = mk_plugin_http_error; /* Memory callbacks */ api->pointer_set = mk_ptr_set; api->pointer_print = mk_ptr_print; api->pointer_to_buf = mk_ptr_to_buf; api->plugin_load_symbol = mk_plugin_load_symbol; api->mem_alloc = mk_mem_alloc; api->mem_alloc_z = mk_mem_alloc_z; api->mem_realloc = mk_mem_realloc; api->mem_free = mk_mem_free; /* String Callbacks */ api->str_build = mk_string_build; api->str_dup = mk_string_dup; api->str_search = mk_string_search; api->str_search_n = mk_string_search_n; api->str_char_search = mk_string_char_search; api->str_copy_substr = mk_string_copy_substr; api->str_itop = mk_string_itop; api->str_split_line = mk_string_split_line; api->str_split_free = mk_string_split_free; /* File Callbacks */ api->file_to_buffer = mk_file_to_buffer; api->file_get_info = mk_file_get_info; /* HTTP Callbacks */ api->header_prepare = mk_plugin_header_prepare; api->header_add = mk_plugin_header_add; api->header_get = mk_http_header_get; api->header_set_http_status = mk_header_set_http_status; /* Channels / Streams */ api->channel_new = mk_channel_new; api->channel_flush = mk_channel_flush; api->channel_write = mk_channel_write; api->channel_append_stream = mk_channel_append_stream; /* IOV callbacks */ api->iov_create = mk_iov_create; api->iov_realloc = mk_iov_realloc; api->iov_free = mk_iov_free; api->iov_free_marked = mk_iov_free_marked; api->iov_add = mk_iov_add; api->iov_set_entry = mk_iov_set_entry; api->iov_send = mk_iov_send; api->iov_print = mk_iov_print; /* events mechanism */ api->ev_loop_create = mk_event_loop_create; api->ev_add = mk_event_add; api->ev_del = mk_event_del; api->ev_timeout_create = mk_event_timeout_create; api->ev_channel_create = mk_event_channel_create; api->ev_wait = mk_event_wait; api->ev_backend = mk_event_backend; /* Mimetype */ api->mimetype_lookup = mk_mimetype_lookup; /* Socket callbacks */ api->socket_cork_flag = mk_socket_set_cork_flag; api->socket_connect = mk_socket_connect; api->socket_open = mk_socket_open; api->socket_reset = mk_socket_reset; api->socket_set_tcp_fastopen = mk_socket_set_tcp_fastopen; api->socket_set_tcp_reuseport = mk_socket_set_tcp_reuseport; api->socket_set_tcp_nodelay = mk_socket_set_tcp_nodelay; api->socket_set_nonblocking = mk_socket_set_nonblocking; api->socket_create = mk_socket_create; api->socket_ip_str = mk_socket_ip_str; /* Async network */ api->net_conn_create = mk_net_conn_create; /* Config Callbacks */ api->config_create = mk_rconf_create; api->config_open = mk_rconf_open; api->config_free = mk_rconf_free; api->config_section_get = mk_rconf_section_get; api->config_section_get_key = mk_rconf_section_get_key; /* Scheduler and Event callbacks */ api->sched_loop = mk_sched_loop; api->sched_get_connection = mk_sched_get_connection; api->sched_event_free = mk_sched_event_free; api->sched_remove_client = mk_plugin_sched_remove_client; api->sched_worker_info = mk_plugin_sched_get_thread_conf; /* Worker functions */ api->worker_spawn = mk_utils_worker_spawn; api->worker_rename = mk_utils_worker_rename; /* Time functions */ api->time_unix = mk_plugin_time_now_unix; api->time_to_gmt = mk_utils_utime2gmt; api->time_human = mk_plugin_time_now_human; api->stacktrace = (void *) mk_utils_stacktrace; api->kernel_version = mk_kernel_version; api->kernel_features_print = mk_kernel_features_print; api->plugins = &server->plugins; /* handler */ api->handler_param_get = mk_handler_param_get; server->api = api; } void mk_plugin_load_static(struct mk_server *server) { /* Load static plugins */ mk_list_init(&server->plugins); mk_static_plugins(&server->plugins); } void mk_plugin_load_all(struct mk_server *server) { int ret; char *tmp; char *path; char shortname[64]; struct mk_plugin *p; struct mk_rconf *cnf; struct mk_rconf_section *section; struct mk_rconf_entry *entry; struct mk_list *head; struct mk_list *htmp; struct file_info f_info; mk_plugin_load_static(server); mk_list_foreach_safe(head, htmp, &server->plugins) { p = mk_list_entry(head, struct mk_plugin, _head); /* Load the static plugin */ p = mk_plugin_load(MK_PLUGIN_STATIC, p->shortname, (void *) p, server); if (!p) { continue; } ret = mk_plugin_init(server->api, p, server); if (ret == -1) { /* Free plugin, do not register, error initializing */ mk_warn("Plugin initialization failed: %s", p->shortname); mk_plugin_unregister(p); continue; } else if (ret == -2) { /* Do not register, just skip it */ mk_plugin_unregister(p); continue; } } /* In case there are not dynamic plugins */ if (!server->conf_plugin_load) { return; } /* Read configuration file */ path = mk_mem_alloc_z(1024); snprintf(path, 1024, "%s/%s", server->path_conf_root, server->conf_plugin_load); ret = mk_file_get_info(path, &f_info, MK_FILE_READ); if (ret == -1 || f_info.is_file == MK_FALSE) { snprintf(path, 1024, "%s", server->conf_plugin_load); } cnf = mk_rconf_open(path); if (!cnf) { mk_warn("No dynamic plugins loaded."); mk_mem_free(path); return; } /* Read section 'PLUGINS' */ section = mk_rconf_section_get(cnf, "PLUGINS"); if (!section) { exit(EXIT_FAILURE); } /* Read key entries */ mk_list_foreach_safe(head, htmp, §ion->entries) { entry = mk_list_entry(head, struct mk_rconf_entry, _head); if (strcasecmp(entry->key, "Load") == 0) { /* Get plugin 'shortname' */ tmp = memrchr(entry->val, '-', strlen(entry->val)); ++tmp; memset(shortname, '\0', sizeof(shortname) - 1); strncpy(shortname, tmp, strlen(tmp) - 3); /* Load the dynamic plugin */ p = mk_plugin_load(MK_PLUGIN_DYNAMIC, shortname, entry->val, server); if (!p) { mk_warn("Invalid plugin '%s'", entry->val); continue; } ret = mk_plugin_init(server->api, p, server); if (ret < 0) { /* Free plugin, do not register */ MK_TRACE("Unregister plugin '%s'", p->shortname); mk_plugin_unregister(p); continue; } } } /* Look for plugins thread key data */ mk_plugin_preworker_calls(server); mk_vhost_map_handlers(server); mk_mem_free(path); mk_rconf_free(cnf); } static void mk_plugin_exit_stages(struct mk_plugin *p) { struct mk_list *tmp; struct mk_list *head; struct mk_plugin_stage *st; mk_list_foreach_safe(head, tmp, &p->stage_list) { st = mk_list_entry(head, struct mk_plugin_stage, _parent_head); /* remove from direct config->stageN head list */ mk_list_del(&st->_head); /* remove from plugin->stage_lists */ mk_list_del(&st->_parent_head); mk_mem_free(st); } } /* Invoke all plugins 'exit' hook and free resources by the plugin interface */ void mk_plugin_exit_all(struct mk_server *server) { struct mk_plugin *plugin; struct mk_list *head, *tmp; /* Plugins */ mk_list_foreach(head, &server->plugins) { plugin = mk_list_entry(head, struct mk_plugin, _head); plugin->exit_plugin(plugin); } /* Plugin interface it self */ mk_list_foreach_safe(head, tmp, &server->plugins) { plugin = mk_list_entry(head, struct mk_plugin, _head); mk_list_del(&plugin->_head); mk_plugin_exit_stages(plugin); if (plugin->load_type == MK_PLUGIN_DYNAMIC) { mk_mem_free(plugin->path); #ifdef _WIN32 FreeLibrary((HMODULE)plugin->handler); #else dlclose(plugin ->handler); #endif } else if (plugin->load_type == MK_PLUGIN_STATIC) { if (plugin->network != NULL) { mk_mem_free(plugin->network); } mk_mem_free(plugin); } } mk_mem_free(server->api); mk_mem_free(plg_stagemap); } /* * When a worker is exiting, it invokes this function to release any plugin * associated data. */ void mk_plugin_exit_worker() { } /* This function is called by every created worker * for plugins which need to set some data under a thread * context */ void mk_plugin_core_process(struct mk_server *server) { struct mk_plugin *node; struct mk_list *head; mk_list_foreach(head, &server->plugins) { node = mk_list_entry(head, struct mk_plugin, _head); /* Init plugin */ if (node->master_init) { node->master_init(server); } } } /* This function is called by every created worker * for plugins which need to set some data under a thread * context */ void mk_plugin_core_thread(struct mk_server *server) { struct mk_plugin *node; struct mk_list *head; mk_list_foreach(head, &server->plugins) { node = mk_list_entry(head, struct mk_plugin, _head); /* Init plugin thread context */ if (node->worker_init) { node->worker_init(server); } } } /* This function is called by Monkey *outside* of the * thread context for plugins, so here's the right * place to set pthread keys or similar */ void mk_plugin_preworker_calls(struct mk_server *server) { int ret; struct mk_plugin *node; struct mk_list *head; mk_list_foreach(head, &server->plugins) { node = mk_list_entry(head, struct mk_plugin, _head); /* Init pthread keys */ if (node->thread_key) { MK_TRACE("[%s] Set thread key", node->shortname); ret = pthread_key_create(node->thread_key, NULL); if (ret != 0) { mk_err("Plugin Error: could not create key for %s", node->shortname); } } } } int mk_plugin_http_error(int http_status, struct mk_http_session *cs, struct mk_http_request *sr, struct mk_plugin *plugin) { return mk_http_error(http_status, cs, sr, plugin->server_ctx); } int mk_plugin_http_request_end(struct mk_plugin *plugin, struct mk_http_session *cs, int close) { int ret; int con; struct mk_http_request *sr; struct mk_server *server = plugin->server_ctx; MK_TRACE("[FD %i] PLUGIN HTTP REQUEST END", cs->socket); cs->status = MK_REQUEST_STATUS_INCOMPLETE; if (mk_list_is_empty(&cs->request_list) == 0) { MK_TRACE("[FD %i] Tried to end non-existing request.", cs->socket); return -1; } sr = mk_list_entry_last(&cs->request_list, struct mk_http_request, _head); mk_plugin_stage_run_40(cs, sr, server); if (close == MK_TRUE) { cs->close_now = MK_TRUE; } /* Let's check if we should ask to finalize the connection or not */ ret = mk_http_request_end(cs, server); MK_TRACE("[FD %i] HTTP session end = %i", cs->socket, ret); if (ret < 0) { con = mk_sched_event_close(cs->conn, mk_sched_get_thread_conf(), MK_EP_SOCKET_DONE, server); if (con != 0) { return con; } else { return -1; } } return ret; } /* Plugin epoll event handlers * --------------------------- * this functions are called by connection.c functions as mk_conn_read(), * mk_conn_write(),mk_conn_error(), mk_conn_close() and mk_conn_timeout(). * * Return Values: * ------------- * MK_PLUGIN_RET_EVENT_NOT_ME: There's no plugin hook associated */ void mk_plugin_event_bad_return(const char *hook, int ret) { mk_err("[%s] Not allowed return value %i", hook, ret); } int mk_plugin_time_now_unix(struct mk_server *server) { return server->clock_context->log_current_utime; } mk_ptr_t *mk_plugin_time_now_human(struct mk_server *server) { return &server->clock_context->log_current_time; } int mk_plugin_sched_remove_client(int socket, struct mk_server *server) { struct mk_sched_conn *conn; struct mk_sched_worker *sched; MK_TRACE("[FD %i] remove client", socket); sched = mk_sched_get_thread_conf(); conn = mk_sched_get_connection(sched, socket); if (!conn) { return -1; } return mk_sched_remove_client(conn, sched, server); } int mk_plugin_header_prepare(struct mk_plugin *plugin, struct mk_http_session *cs, struct mk_http_request *sr) { return mk_header_prepare(cs, sr, plugin->server_ctx); } int mk_plugin_header_add(struct mk_http_request *sr, char *row, int len) { mk_bug(!sr); if (!sr->headers._extra_rows) { /* * We allocate space for a fixed number of IOV entries: * * MK_PLUGIN_HEADER_EXTRA_ROWS = X * * we use (MK_PLUGIN_HEADER_EXTRA_ROWS * 2) thinking in an ending CRLF */ sr->headers._extra_rows = mk_iov_create(MK_PLUGIN_HEADER_EXTRA_ROWS * 2, 0); mk_bug(!sr->headers._extra_rows); } mk_iov_add(sr->headers._extra_rows, row, len, MK_FALSE); mk_iov_add(sr->headers._extra_rows, mk_iov_crlf.data, mk_iov_crlf.len, MK_FALSE); return 0; } struct mk_sched_worker *mk_plugin_sched_get_thread_conf() { return MK_TLS_GET(mk_tls_sched_worker_node); } struct mk_plugin *mk_plugin_cap(char cap, struct mk_server *server) { struct mk_list *head; struct mk_plugin *plugin; mk_list_foreach(head, &server->plugins) { plugin = mk_list_entry(head, struct mk_plugin, _head); if (plugin->capabilities & cap) { return plugin; } } return NULL; } struct mk_vhost_handler_param *mk_handler_param_get(int id, struct mk_list *params) { int i = 0; struct mk_list *head; mk_list_foreach(head, params) { if (i == id) { return mk_list_entry(head, struct mk_vhost_handler_param, _head); } i++; } return NULL; }