diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-05-05 12:08:03 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-05-05 12:08:18 +0000 |
commit | 5da14042f70711ea5cf66e034699730335462f66 (patch) | |
tree | 0f6354ccac934ed87a2d555f45be4c831cf92f4a /src/fluent-bit/lib/monkey/plugins | |
parent | Releasing debian version 1.44.3-2. (diff) | |
download | netdata-5da14042f70711ea5cf66e034699730335462f66.tar.xz netdata-5da14042f70711ea5cf66e034699730335462f66.zip |
Merging upstream version 1.45.3+dfsg.
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'src/fluent-bit/lib/monkey/plugins')
76 files changed, 8965 insertions, 0 deletions
diff --git a/src/fluent-bit/lib/monkey/plugins/CMakeLists.txt b/src/fluent-bit/lib/monkey/plugins/CMakeLists.txt new file mode 100644 index 000000000..a78d83695 --- /dev/null +++ b/src/fluent-bit/lib/monkey/plugins/CMakeLists.txt @@ -0,0 +1,111 @@ +set(static_plugins "" CACHE INTERNAL "static_plugins") +set(CMAKE_POSITION_INDEPENDENT_CODE ON) + +# CHECK_STATIC_PLUGIN: Check if a plugin will be linked statically +macro(CHECK_STATIC_PLUGIN name) + string(REPLACE "," ";" plugins ${MK_STATIC_PLUGINS}) + list(FIND plugins ${name} found) + if(NOT found EQUAL -1) + set(IS_STATIC TRUE) + else() + set(IS_STATIC FALSE) + endif() +endmacro() + +# MONKEY_PLUGIN: Used by plugins to register and create the targets +macro(MONKEY_PLUGIN name src) + CHECK_STATIC_PLUGIN(${name}) + if(IS_STATIC) + add_library(monkey-${name}-static STATIC ${src}) + set_target_properties(monkey-${name}-static PROPERTIES OUTPUT_NAME monkey-${name}) + set_target_properties(monkey-${name}-static PROPERTIES PREFIX "") + else() + if(APPLE) + add_library(monkey-${name}-shared MODULE ${src}) + else() + add_library(monkey-${name}-shared SHARED ${src}) + endif() + set_target_properties(monkey-${name}-shared PROPERTIES OUTPUT_NAME monkey-${name}) + set_target_properties(monkey-${name}-shared PROPERTIES PREFIX "") + + if(NOT MK_LOCAL) + if(CMAKE_INSTALL_LIBDIR) + install(TARGETS monkey-${name}-shared LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR}) + else() + install(TARGETS monkey-${name}-shared LIBRARY DESTINATION lib) + endif() + endif() + endif() +endmacro() + +# MK_BUILD_PLUGIN: This macro determinate if the plugin is enabled through the +# option MK_PLUGIN_NAME defined on the root CMakeLists.txt +macro(MK_BUILD_PLUGIN name) + set(mode "") + string(TOUPPER ${name} NAME) + + # Check if the plugin is enabled + set(option MK_PLUGIN_${NAME}) + if(${option}) + add_subdirectory(${name}) + + # Is this a static plugin ? + CHECK_STATIC_PLUGIN(${name}) + if(IS_STATIC) + # Let Monkey and CMake aware about this is a static plugin. A static plugin + # requires a different handling: link the object and register the plugin + # struct reference on mk_static_plugins.h + set(static_plugins "${static_plugins}monkey-${name}-static;") + set(STATIC_PLUGINS_INIT "${STATIC_PLUGINS_INIT}\n mk_static_plugin_attach(plugins, &mk_plugin_${name});\n") + set(STATIC_PLUGINS_DECL "${STATIC_PLUGINS_DECL}extern struct mk_plugin mk_plugin_${name};\n") + + # append message to stdout + set(mode "[== static ==]") + else() + if(MK_LOCAL) + set(MK_LOAD_PLUGINS "${MK_LOAD_PLUGINS} # Load ${CMAKE_LIBRARY_OUTPUT_DIRECTORY}/monkey-${name}.so\n") + else() + set(MK_LOAD_PLUGINS "${MK_LOAD_PLUGINS} # Load ${CMAKE_INSTALL_FULL_LIBDIR}/monkey-${name}.so\n") + endif() + endif() + message(STATUS "Plugin ${name} enabled ${mode}") + endif() +endmacro() + + +macro(MONKEY_PLUGIN_LINK_LIB target lib) + CHECK_STATIC_PLUGIN(${target}) + if(IS_STATIC) + target_link_libraries(monkey-${target}-static ${lib}) + else() + target_link_libraries(monkey-${target}-shared ${lib}) + endif() +endmacro() + +# Try to configure/build all plugins +MK_BUILD_PLUGIN("auth") +MK_BUILD_PLUGIN("cgi") +MK_BUILD_PLUGIN("cheetah") +MK_BUILD_PLUGIN("dirlisting") +MK_BUILD_PLUGIN("fastcgi") +MK_BUILD_PLUGIN("liana") +MK_BUILD_PLUGIN("logger") +MK_BUILD_PLUGIN("mandril") +MK_BUILD_PLUGIN("tls") +MK_BUILD_PLUGIN("duda") + +# Generate include/monkey/mk_static_plugins.h +configure_file( + "${PROJECT_SOURCE_DIR}/include/monkey/mk_static_plugins.h.in" + "${PROJECT_SOURCE_DIR}/include/monkey/mk_static_plugins.h" + ) + +# Generate conf/plugins.load +if(NOT MK_WITHOUT_CONF) + configure_file( + "${PROJECT_SOURCE_DIR}/conf/plugins.load.in" + "${PROJECT_BINARY_DIR}/conf/plugins.load" + ) +endif() + +set(STATIC_PLUGINS_LIBS "${static_plugins}" PARENT_SCOPE) diff --git a/src/fluent-bit/lib/monkey/plugins/README b/src/fluent-bit/lib/monkey/plugins/README new file mode 100644 index 000000000..0daa023cb --- /dev/null +++ b/src/fluent-bit/lib/monkey/plugins/README @@ -0,0 +1,61 @@ +Monkey Plugins +============== +Plugins are extra features which modifies the Monkey behavior, all of them +are shared libraries which are loaded on runtime. + +Each plugin is loaded per configuration instruction and it will work on a +defined stage or event depending of it's type. + +Please review the file API.txt for more details + + + +MK_PLUGIN_STAGE_10: Server has not yet entered in the server loop, no + listeners yet available +--------------------------------------------------------------------- + Return Values > + + +MK_PLUGIN_STAGE_20: Accepted connection has not been assigned to worker thread +------------------------------------------------------------------------------ + Return Values > + + * MK_PLUGIN_RET_CLOSE_CONX: The connection must be closed. + + +MK_PLUGIN_STAGE_30: HTTP Request received +----------------------------------------- + Return Values > + * MK_PLUGIN_RET_CLOSE_CONX: The connection must be closed. + + +MK_PLUGIN_STAGE_40: Object Handler +----------------------------------------- + Extra functions > + * _mk_plugin_stage_40_loop(): if _mk_plugin_stage_40() has + returned MK_PLUGIN_RET_CONTINUE, the server will wait + for an event and call _mk_plugin_stage_40() until it + returns MK_PLUGIN_RET_END. + + Return Values > + * MK_PLUGIN_RET_END + * MK_PLUGIN_RET_CONTINUE + + Return Values > + * MK_PLUGIN_RET_NOT_ME: Plugin will not handle this request. + + * MK_PLUGIN_RET_END: Plugin has taken some action and + has finished the work, the handler will no take the request + again. + + * MK_PLUGIN_RET_CONTINUE:: Plugin has taken some action and + will continue in the next loop. + + +MK_PLUGIN_STAGE_50: Request ended +----------------------------------------- + + +MK_PLUGIN_STAGE_60: The Connection has been closed +-------------------------------------------------- + diff --git a/src/fluent-bit/lib/monkey/plugins/auth/ABOUT b/src/fluent-bit/lib/monkey/plugins/auth/ABOUT new file mode 100644 index 000000000..66a5384c3 --- /dev/null +++ b/src/fluent-bit/lib/monkey/plugins/auth/ABOUT @@ -0,0 +1,2 @@ +HTTP Basic Authentication +========================= diff --git a/src/fluent-bit/lib/monkey/plugins/auth/CMakeLists.txt b/src/fluent-bit/lib/monkey/plugins/auth/CMakeLists.txt new file mode 100644 index 000000000..a6fd27a06 --- /dev/null +++ b/src/fluent-bit/lib/monkey/plugins/auth/CMakeLists.txt @@ -0,0 +1,10 @@ +set(src + auth.c + base64.c + conf.c + sha1.c + ) + +add_subdirectory(tools) + +MONKEY_PLUGIN(auth "${src}") diff --git a/src/fluent-bit/lib/monkey/plugins/auth/OPTIONAL b/src/fluent-bit/lib/monkey/plugins/auth/OPTIONAL new file mode 100644 index 000000000..e69de29bb --- /dev/null +++ b/src/fluent-bit/lib/monkey/plugins/auth/OPTIONAL diff --git a/src/fluent-bit/lib/monkey/plugins/auth/auth.c b/src/fluent-bit/lib/monkey/plugins/auth/auth.c new file mode 100644 index 000000000..b0f983bfb --- /dev/null +++ b/src/fluent-bit/lib/monkey/plugins/auth/auth.c @@ -0,0 +1,254 @@ +/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ + +/* Monkey HTTP Server + * ================== + * Copyright 2001-2017 Eduardo Silva <eduardo@monkey.io> + * + * 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 <monkey/mk_api.h> + +#include <sys/stat.h> + +#include "auth.h" +#include "conf.h" +#include "sha1.h" +#include "base64.h" + +static int mk_auth_validate_user(struct users_file *users, + const char *credentials, unsigned int len) +{ + int sep; + size_t auth_len; + unsigned char *decoded = NULL; + unsigned char digest[SHA1_DIGEST_LEN]; + struct mk_list *head; + struct user *entry; + + SHA_CTX sha; /* defined in sha1/sha1.h */ + + /* Validate value length */ + if (len <= auth_header_basic.len + 1) { + return -1; + } + + /* Validate 'basic' credential type */ + if (strncmp(credentials, auth_header_basic.data, + auth_header_basic.len) != 0) { + return -1; + } + + /* Decode credentials: incoming credentials comes in base64 encode */ + decoded = base64_decode((unsigned char *) credentials + auth_header_basic.len, + len - auth_header_basic.len, + &auth_len); + if (decoded == NULL) { + PLUGIN_TRACE("Failed to decode credentials."); + goto error; + } + + if (auth_len <= 3) { + goto error; + } + + sep = mk_api->str_search_n((char *) decoded, ":", 1, auth_len); + if (sep == -1 || sep == 0 || (unsigned int) sep == auth_len - 1) { + goto error; + } + + /* Get SHA1 hash */ + SHA1_Init(&sha); + SHA1_Update(&sha, (unsigned char *) decoded + sep + 1, auth_len - (sep + 1)); + SHA1_Final(digest, &sha); + + mk_list_foreach(head, &users->_users) { + entry = mk_list_entry(head, struct user, _head); + /* match user */ + if (strlen(entry->user) != (unsigned int) sep) { + continue; + } + if (strncmp(entry->user, (char *) decoded, sep) != 0) { + continue; + } + + PLUGIN_TRACE("User match '%s'", entry->user); + + /* match password */ + if (memcmp(entry->passwd_decoded, digest, SHA1_DIGEST_LEN) == 0) { + PLUGIN_TRACE("User '%s' matched password", entry->user); + mk_api->mem_free(decoded); + return 0; + } + PLUGIN_TRACE("Invalid password"); + break; + } + + error: + if (decoded) { + mk_api->mem_free(decoded); + } + return -1; +} + +int mk_auth_plugin_init(struct plugin_api **api, char *confdir) +{ + (void) confdir; + + mk_api = *api; + + /* Init and load global users list */ + mk_list_init(&vhosts_list); + mk_list_init(&users_file_list); + mk_auth_conf_init_users_list(); + + /* Set HTTP headers key */ + auth_header_basic.data = MK_AUTH_HEADER_BASIC; + auth_header_basic.len = sizeof(MK_AUTH_HEADER_BASIC) - 1; + + return 0; +} + +int mk_auth_plugin_exit() +{ + return 0; +} + +void mk_auth_worker_init() +{ + char *user; + + /* Init thread buffer for given credentials */ + user = mk_api->mem_alloc(MK_AUTH_CREDENTIALS_LEN - 1); + pthread_setspecific(_mkp_data, (void *) user); +} + +/* Object handler */ +int mk_auth_stage30(struct mk_plugin *plugin, + struct mk_http_session *cs, + struct mk_http_request *sr, + int n_params, + struct mk_list *params) +{ + int val; + short int is_restricted = MK_FALSE; + struct mk_list *vh_head; + struct mk_list *loc_head; + struct vhost *vh_entry = NULL; + struct location *loc_entry; + struct mk_http_header *header; + (void) plugin; + (void) n_params; + (void) params; + + PLUGIN_TRACE("[FD %i] Handler received request"); + + /* Match auth_vhost with global vhost */ + mk_list_foreach(vh_head, &vhosts_list) { + vh_entry = mk_list_entry(vh_head, struct vhost, _head); + if (vh_entry->host == sr->host_conf) { + PLUGIN_TRACE("[FD %i] host matched %s", + cs->socket, + mk_api->config->server_signature); + break; + } + } + + if (!vh_entry) { + return MK_PLUGIN_RET_NOT_ME; + } + + /* Check vhost locations */ + mk_list_foreach(loc_head, &vh_entry->locations) { + loc_entry = mk_list_entry(loc_head, struct location, _head); + if (sr->uri_processed.len < loc_entry->path.len) { + continue; + } + if (strncmp(sr->uri_processed.data, + loc_entry->path.data, loc_entry->path.len) == 0) { + is_restricted = MK_TRUE; + PLUGIN_TRACE("[FD %i] Location matched %s", + cs->socket, + loc_entry->path.data); + break; + } + } + + /* For non-restricted location do not take any action, just returns */ + if (is_restricted == MK_FALSE) { + return MK_PLUGIN_RET_NOT_ME; + } + + /* Check authorization header */ + header = mk_api->header_get(MK_HEADER_AUTHORIZATION, + sr, NULL, 0); + + if (header) { + /* Validate user */ + val = mk_auth_validate_user(loc_entry->users, + header->val.data, header->val.len); + if (val == 0) { + /* user validated, success */ + PLUGIN_TRACE("[FD %i] user validated!", cs->socket); + return MK_PLUGIN_RET_NOT_ME; + } + } + + /* Restricted access: requires auth */ + PLUGIN_TRACE("[FD %i] unauthorized user, credentials required", + cs->socket); + + sr->headers.content_length = 0; + mk_api->header_set_http_status(sr, MK_CLIENT_UNAUTH); + mk_api->header_add(sr, + loc_entry->auth_http_header.data, + loc_entry->auth_http_header.len); + + mk_api->header_prepare(plugin, cs, sr); + return MK_PLUGIN_RET_END; +} + +int mk_auth_stage30_hangup(struct mk_plugin *plugin, + struct mk_http_session *cs, + struct mk_http_request *sr) +{ + (void) plugin; + (void) cs; + (void) sr; + + return 0; +} + +struct mk_plugin_stage mk_plugin_stage_auth = { + .stage30 = &mk_auth_stage30, + .stage30_hangup = &mk_auth_stage30_hangup +}; + +struct mk_plugin mk_plugin_auth = { + /* Identification */ + .shortname = "auth", + .name = "Basic Authentication", + .version = MK_VERSION_STR, + .hooks = MK_PLUGIN_STAGE, + + /* Init / Exit */ + .init_plugin = mk_auth_plugin_init, + .exit_plugin = mk_auth_plugin_exit, + + /* Init Levels */ + .master_init = NULL, + .worker_init = mk_auth_worker_init, + + /* Type */ + .stage = &mk_plugin_stage_auth +}; diff --git a/src/fluent-bit/lib/monkey/plugins/auth/auth.h b/src/fluent-bit/lib/monkey/plugins/auth/auth.h new file mode 100644 index 000000000..f6eebe83e --- /dev/null +++ b/src/fluent-bit/lib/monkey/plugins/auth/auth.h @@ -0,0 +1,106 @@ +/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ + +/* Monkey HTTP Server + * ================== + * Copyright 2001-2017 Eduardo Silva <eduardo@monkey.io> + * + * 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. + */ + +#ifndef MK_AUTH_H +#define MK_AUTH_H + +#include <monkey/mk_api.h> + +/* Header stuff */ +#define MK_AUTH_HEADER_BASIC "Basic " +#define MK_AUTH_HEADER_TITLE "WWW-Authenticate: Basic realm=\"%s\"" + +/* Credentials length */ +#define MK_AUTH_CREDENTIALS_LEN 256 + +/* + * The plugin hold one struct per virtual host and link to the + * locations and users file associated: + * + * +---------------------------------+ + * struct vhost > vhost (1:N) | + * | +---------+----------+ | + * | | | | | + * struct location > location location location | + * | | | | | + * | +----+----+ + | + * | | | | + * struct users > users users | + * +---------------------------------+ + * + */ + +/* List of virtual hosts to handle locations */ +struct mk_list vhosts_list; + +/* main index for locations under a virtualhost */ +struct vhost { + struct mk_vhost *host; + struct mk_list locations; + struct mk_list _head; +}; + +/* + * A location restrict a filesystem path with a list + * of allowed users + */ +struct location { + mk_ptr_t path; + mk_ptr_t title; + mk_ptr_t auth_http_header; + + struct users_file *users; + struct mk_list _head; +}; + +/* Head index for user files list */ +struct mk_list users_file_list; + +/* + * Represents a users file, each entry represents a physical + * file and belongs to a node of the users_file_list list + */ +struct users_file { + time_t last_updated; /* last time this entry was modified */ + char *path; /* file path */ + struct mk_list _users; /* list of users */ + struct mk_list _head; /* head for main mk_list users_file_list */ +}; + +/* + * a list of users, this list belongs to a + * struct location + */ +struct user { + char user[128]; + char passwd_raw[256]; + unsigned char *passwd_decoded; + + struct mk_list _head; +}; + +struct mk_list users_file_list; + +/* Thread key */ +mk_ptr_t auth_header_request; +mk_ptr_t auth_header_basic; + +#define SHA1_DIGEST_LEN 20 + +#endif diff --git a/src/fluent-bit/lib/monkey/plugins/auth/base64.c b/src/fluent-bit/lib/monkey/plugins/auth/base64.c new file mode 100644 index 000000000..e3149fe73 --- /dev/null +++ b/src/fluent-bit/lib/monkey/plugins/auth/base64.c @@ -0,0 +1,172 @@ +/* + * Base64 encoding/decoding (RFC1341) + * Copyright (c) 2005-2011, Jouni Malinen <j@w1.fi> + * + * This software may be distributed under the terms of the BSD license. + * See README for more details. + */ + +#ifdef _FORCE_SYSMALLOC +#undef MALLOC_JEMALLOC +#endif + +#include <monkey/mk_api.h> +#include "base64.h" + +#if defined(MALLOC_JEMALLOC) +#define __mem_alloc mk_api->mem_alloc +#define __mem_free mk_api->mem_free +#else +#define __mem_alloc malloc +#define __mem_free free +#endif + +static const unsigned char base64_table[65] = + "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; + +/** + * base64_encode - Base64 encode + * @src: Data to be encoded + * @len: Length of the data to be encoded + * @out_len: Pointer to output length variable, or %NULL if not used + * Returns: Allocated buffer of out_len bytes of encoded data, + * or %NULL on failure + * + * Caller is responsible for freeing the returned buffer. Returned buffer is + * nul terminated to make it easier to use as a C string. The nul terminator is + * not included in out_len. + */ +unsigned char * base64_encode(const unsigned char *src, size_t len, + size_t *out_len) +{ + unsigned char *out, *pos; + const unsigned char *end, *in; + size_t olen; + int line_len; + + olen = len * 4 / 3 + 4; /* 3-byte blocks to 4-byte */ + olen += olen / 72; /* line feeds */ + olen++; /* nul termination */ + if (olen < len) + return NULL; /* integer overflow */ + if (mk_api != NULL) { + out = __mem_alloc(olen); + } + else { + out = __mem_alloc(olen); + } + + if (out == NULL) + return NULL; + + end = src + len; + in = src; + pos = out; + line_len = 0; + while (end - in >= 3) { + *pos++ = base64_table[in[0] >> 2]; + *pos++ = base64_table[((in[0] & 0x03) << 4) | (in[1] >> 4)]; + *pos++ = base64_table[((in[1] & 0x0f) << 2) | (in[2] >> 6)]; + *pos++ = base64_table[in[2] & 0x3f]; + in += 3; + line_len += 4; + if (line_len >= 72) { + *pos++ = '\n'; + line_len = 0; + } + } + + if (end - in) { + *pos++ = base64_table[in[0] >> 2]; + if (end - in == 1) { + *pos++ = base64_table[(in[0] & 0x03) << 4]; + *pos++ = '='; + } else { + *pos++ = base64_table[((in[0] & 0x03) << 4) | + (in[1] >> 4)]; + *pos++ = base64_table[(in[1] & 0x0f) << 2]; + } + *pos++ = '='; + line_len += 4; + } + + if (line_len) + *pos++ = '\n'; + + *pos = '\0'; + if (out_len) + *out_len = pos - out; + return out; +} + + +/** + * base64_decode - Base64 decode + * @src: Data to be decoded + * @len: Length of the data to be decoded + * @out_len: Pointer to output length variable + * Returns: Allocated buffer of out_len bytes of decoded data, + * or %NULL on failure + * + * Caller is responsible for freeing the returned buffer. + */ +unsigned char * base64_decode(const unsigned char *src, size_t len, + size_t *out_len) +{ + unsigned char dtable[256], *out, *pos, block[4], tmp; + size_t i, count, olen; + int pad = 0; + + memset(dtable, 0x80, 256); + for (i = 0; i < sizeof(base64_table) - 1; i++) + dtable[base64_table[i]] = (unsigned char) i; + dtable['='] = 0; + + count = 0; + for (i = 0; i < len; i++) { + if (dtable[src[i]] != 0x80) + count++; + } + + if (count == 0 || count % 4) + return NULL; + + olen = (count / 4 * 3) + 1; + pos = out = __mem_alloc(olen); + if (out == NULL) + return NULL; + + count = 0; + for (i = 0; i < len; i++) { + tmp = dtable[src[i]]; + if (tmp == 0x80) + continue; + + if (src[i] == '=') + pad++; + block[count] = tmp; + count++; + if (count == 4) { + *pos++ = (block[0] << 2) | (block[1] >> 4); + *pos++ = (block[1] << 4) | (block[2] >> 2); + *pos++ = (block[2] << 6) | block[3]; + count = 0; + if (pad) { + if (pad == 1) + pos--; + else if (pad == 2) + pos -= 2; + else { + /* Invalid padding */ + __mem_free(out); + return NULL; + } + break; + } + } + } + *pos = '\0'; + + *out_len = pos - out; + return out; +} diff --git a/src/fluent-bit/lib/monkey/plugins/auth/base64.h b/src/fluent-bit/lib/monkey/plugins/auth/base64.h new file mode 100644 index 000000000..45001d47f --- /dev/null +++ b/src/fluent-bit/lib/monkey/plugins/auth/base64.h @@ -0,0 +1,11 @@ +#ifndef AUTH_BASE64_H +#define AUTH_BASE64_H + + +unsigned char * base64_encode(const unsigned char *src, size_t len, + size_t *out_len); +unsigned char *base64_decode(const unsigned char *src, size_t len, + size_t *out_len); + + +#endif diff --git a/src/fluent-bit/lib/monkey/plugins/auth/conf.c b/src/fluent-bit/lib/monkey/plugins/auth/conf.c new file mode 100644 index 000000000..6dede1dd4 --- /dev/null +++ b/src/fluent-bit/lib/monkey/plugins/auth/conf.c @@ -0,0 +1,244 @@ + /* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ + +/* Monkey HTTP Server + * ================== + * Copyright 2001-2017 Eduardo Silva <eduardo@monkey.io> + * + * 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 <monkey/mk_api.h> +#include "base64.h" +#include "auth.h" +#include "conf.h" + +/* + * Register a users file into the main list, if the users + * file already exists it just return the node in question, + * otherwise add the node to the list and return the node + * created. + */ +static struct users_file *mk_auth_conf_add_users(char *users_path) +{ + struct file_info finfo; + struct mk_list *head; + struct users_file *entry; + struct user *cred; + int i, sep, len; + int offset = 0; + size_t decoded_len; + char *buf; + + mk_list_foreach(head, &users_file_list) { + entry = mk_list_entry(head, struct users_file, _head); + if (strcmp(entry->path, users_path) == 0) { + return entry; + } + } + + if (mk_api->file_get_info(users_path, &finfo, MK_FILE_READ) != 0) { + mk_warn("Auth: Invalid users file '%s'", users_path); + return NULL; + } + + if (finfo.is_directory == MK_TRUE) { + mk_warn("Auth: Not a credentials file '%s'", users_path); + return NULL; + } + + if (finfo.read_access == MK_FALSE) { + mk_warn("Auth: Could not read file '%s'", users_path); + return NULL; + } + + /* We did not find the path in our list, let's create a new node */ + entry = mk_api->mem_alloc(sizeof(struct users_file)); + entry->last_updated = finfo.last_modification; + entry->path = users_path; + + /* Read file and add users to the list */ + mk_list_init(&entry->_users); + + /* Read credentials file */ + buf = mk_api->file_to_buffer(users_path); + if (!buf) { + mk_warn("Auth: No users loaded '%s'", users_path); + return NULL; + } + + /* Read users list buffer lines */ + len = strlen(buf); + for (i = 0; i < len; i++) { + if (buf[i] == '\n' || (i) == len -1) { + sep = mk_api->str_search(buf + offset, ":", 1); + + if (sep >= (int)sizeof(cred->user)) { + mk_warn("Auth: username too long"); + offset = i + 1; + continue; + } + if (i - offset - sep - 1 - 5 >= (int)sizeof(cred->passwd_raw)) { + mk_warn("Auth: password hash too long"); + offset = i + 1; + continue; + } + + cred = mk_api->mem_alloc(sizeof(struct user)); + + /* Copy username */ + strncpy(cred->user, buf + offset, sep); + cred->user[sep] = '\0'; + + /* Copy raw password */ + offset += sep + 1 + 5; + strncpy(cred->passwd_raw, + buf + offset, + i - (offset)); + cred->passwd_raw[i - offset] = '\0'; + + /* Decode raw password */ + cred->passwd_decoded = base64_decode((unsigned char *)(cred->passwd_raw), + strlen(cred->passwd_raw), + &decoded_len); + + offset = i + 1; + + if (!cred->passwd_decoded) { + mk_warn("Auth: invalid user '%s' in '%s'", + cred->user, users_path); + mk_api->mem_free(cred); + continue; + } + mk_list_add(&cred->_head, &entry->_users); + } + } + mk_api->mem_free(buf); + + /* Link node to global list */ + mk_list_add(&entry->_head, &users_file_list); + + return entry; +} + +/* + * Read all vhost configuration nodes and looks for users files under an [AUTH] + * section, if present, it add that file to the unique list. It parse all user's + * files mentioned to avoid duplicated lists in memory. + */ +int mk_auth_conf_init_users_list() +{ + /* Section data */ + char *location; + char *title; + char *users_path; + /* auth vhost list */ + struct vhost *auth_vhost; + + /* vhost configuration */ + struct mk_list *head_hosts; + struct mk_list *hosts = &mk_api->config->hosts; + struct mk_list *head_sections; + struct mk_vhost *entry_host; + struct mk_rconf_section *section; + + /* vhost [AUTH] locations */ + struct location *loc; + + /* User files list */ + struct users_file *uf; + + PLUGIN_TRACE("Loading user's files"); + + mk_list_foreach(head_hosts, hosts) { + entry_host = mk_list_entry(head_hosts, struct mk_vhost, _head); + if (!entry_host->config) { + continue; + } + + auth_vhost = mk_api->mem_alloc(sizeof(struct vhost)); + auth_vhost->host = entry_host; /* link virtual host entry */ + mk_list_init(&auth_vhost->locations); /* init locations list */ + + /* + * check vhost 'config' and look for [AUTH] sections, we don't use + * mk_config_section_get() because we can have multiple [AUTH] + * sections. + */ + mk_list_foreach(head_sections, &entry_host->config->sections) { + section = mk_list_entry(head_sections, struct mk_rconf_section, _head); + + if (strcasecmp(section->name, "AUTH") == 0) { + location = NULL; + title = NULL; + users_path = NULL; + + /* Get section keys */ + location = mk_api->config_section_get_key(section, + "Location", + MK_RCONF_STR); + title = mk_api->config_section_get_key(section, + "Title", + MK_RCONF_STR); + + users_path = mk_api->config_section_get_key(section, + "Users", + MK_RCONF_STR); + + /* get or create users file entry */ + uf = mk_auth_conf_add_users(users_path); + if (!uf) { + continue; + } + + /* Location node */ + loc = mk_api->mem_alloc(sizeof(struct location)); + mk_api->pointer_set(&loc->path, location); + mk_api->pointer_set(&loc->title, title); + + loc->auth_http_header.data = NULL; + mk_api->str_build(&loc->auth_http_header.data, + &loc->auth_http_header.len, + MK_AUTH_HEADER_TITLE, title); + + loc->users = uf; + + /* Add new location to auth_vhost node */ + mk_list_add(&loc->_head, &auth_vhost->locations); + } + } + + /* Link auth_vhost node to global list vhosts_list */ + mk_list_add(&auth_vhost->_head, &vhosts_list); + } + +#ifdef TRACE + struct mk_list *vh_head, *loc_head; + struct vhost *vh_entry; + struct location *loc_entry; + + mk_list_foreach(vh_head, &vhosts_list) { + vh_entry = mk_list_entry(vh_head, struct vhost, _head); + PLUGIN_TRACE("Auth VHost: %p", vh_entry->host); + + mk_list_foreach(loc_head, &vh_entry->locations) { + loc_entry = mk_list_entry(loc_head, struct location, _head); + PLUGIN_TRACE("---"); + PLUGIN_TRACE(" location: %s", loc_entry->path); + PLUGIN_TRACE(" title : %s", loc_entry->title); + PLUGIN_TRACE(" users : %s", loc_entry->users->path); + } + } +#endif + + return 0; +} diff --git a/src/fluent-bit/lib/monkey/plugins/auth/conf.h b/src/fluent-bit/lib/monkey/plugins/auth/conf.h new file mode 100644 index 000000000..90fd8f79a --- /dev/null +++ b/src/fluent-bit/lib/monkey/plugins/auth/conf.h @@ -0,0 +1,25 @@ +/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ + +/* Monkey HTTP Server + * ================== + * Copyright 2001-2017 Eduardo Silva <eduardo@monkey.io> + * + * 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. + */ + +#ifndef MK_AUTH_CONF_H +#define MK_AUTH_CONF_H + +int mk_auth_conf_init_users_list(); + +#endif diff --git a/src/fluent-bit/lib/monkey/plugins/auth/sha1.c b/src/fluent-bit/lib/monkey/plugins/auth/sha1.c new file mode 100644 index 000000000..53c7946e8 --- /dev/null +++ b/src/fluent-bit/lib/monkey/plugins/auth/sha1.c @@ -0,0 +1,288 @@ +/* + * Code adapted to fill Monkey Project requirements, no big changes + * just a few header files added + */ + +#include <arpa/inet.h> +#include <string.h> + +/* + * SHA1 routine optimized to do word accesses rather than byte accesses, + * and to avoid unnecessary copies into the context array. + * + * This was initially based on the Mozilla SHA1 implementation, although + * none of the original Mozilla code remains. + */ + +#include "sha1.h" + +#if defined(__GNUC__) && (defined(__i386__) || defined(__x86_64__)) + +/* + * Force usage of rol or ror by selecting the one with the smaller constant. + * It _can_ generate slightly smaller code (a constant of 1 is special), but + * perhaps more importantly it's possibly faster on any uarch that does a + * rotate with a loop. + */ + +#define SHA_ASM(op, x, n) ({ unsigned int __res; __asm__(op " %1,%0":"=r" (__res):"i" (n), "0" (x)); __res; }) +#define SHA_ROL(x,n) SHA_ASM("rol", x, n) +#define SHA_ROR(x,n) SHA_ASM("ror", x, n) + +#else + +#define SHA_ROT(X,l,r) (((X) << (l)) | ((X) >> (r))) +#define SHA_ROL(X,n) SHA_ROT(X,n,32-(n)) +#define SHA_ROR(X,n) SHA_ROT(X,32-(n),n) + +#endif + +/* + * If you have 32 registers or more, the compiler can (and should) + * try to change the array[] accesses into registers. However, on + * machines with less than ~25 registers, that won't really work, + * and at least gcc will make an unholy mess of it. + * + * So to avoid that mess which just slows things down, we force + * the stores to memory to actually happen (we might be better off + * with a 'W(t)=(val);asm("":"+m" (W(t))' there instead, as + * suggested by Artur Skawina - that will also make gcc unable to + * try to do the silly "optimize away loads" part because it won't + * see what the value will be). + * + * Ben Herrenschmidt reports that on PPC, the C version comes close + * to the optimized asm with this (ie on PPC you don't want that + * 'volatile', since there are lots of registers). + * + * On ARM we get the best code generation by forcing a full memory barrier + * between each SHA_ROUND, otherwise gcc happily get wild with spilling and + * the stack frame size simply explode and performance goes down the drain. + */ + +#if defined(__i386__) || defined(__x86_64__) + #define setW(x, val) (*(volatile unsigned int *)&W(x) = (val)) +#elif defined(__GNUC__) && defined(__arm__) + #define setW(x, val) do { W(x) = (val); __asm__("":::"memory"); } while (0) +#else + #define setW(x, val) (W(x) = (val)) +#endif + +/* + * Performance might be improved if the CPU architecture is OK with + * unaligned 32-bit loads and a fast ntohl() is available. + * Otherwise fall back to byte loads and shifts which is portable, + * and is faster on architectures with memory alignment issues. + */ + +#if defined(__i386__) || defined(__x86_64__) || \ + defined(_M_IX86) || defined(_M_X64) || \ + defined(__ppc__) || defined(__ppc64__) || \ + defined(__powerpc__) || defined(__powerpc64__) || \ + defined(__s390__) || defined(__s390x__) + +#define get_be32(p) ntohl(*(unsigned int *)(p)) +#define put_be32(p, v) do { *(unsigned int *)(p) = htonl(v); } while (0) + +#else + +#define get_be32(p) ( \ + (*((unsigned char *)(p) + 0) << 24) | \ + (*((unsigned char *)(p) + 1) << 16) | \ + (*((unsigned char *)(p) + 2) << 8) | \ + (*((unsigned char *)(p) + 3) << 0) ) +#define put_be32(p, v) do { \ + unsigned int __v = (v); \ + *((unsigned char *)(p) + 0) = __v >> 24; \ + *((unsigned char *)(p) + 1) = __v >> 16; \ + *((unsigned char *)(p) + 2) = __v >> 8; \ + *((unsigned char *)(p) + 3) = __v >> 0; } while (0) + +#endif + +/* This "rolls" over the 512-bit array */ +#define W(x) (array[(x)&15]) + +/* + * Where do we get the source from? The first 16 iterations get it from + * the input data, the next mix it from the 512-bit array. + */ +#define SHA_SRC(t) get_be32(data + t) +#define SHA_MIX(t) SHA_ROL(W(t+13) ^ W(t+8) ^ W(t+2) ^ W(t), 1) + +#define SHA_ROUND(t, input, fn, constant, A, B, C, D, E) do { \ + unsigned int TEMP = input(t); setW(t, TEMP); \ + E += TEMP + SHA_ROL(A,5) + (fn) + (constant); \ + B = SHA_ROR(B, 2); } while (0) + +#define T_0_15(t, A, B, C, D, E) SHA_ROUND(t, SHA_SRC, (((C^D)&B)^D) , 0x5a827999, A, B, C, D, E ) +#define T_16_19(t, A, B, C, D, E) SHA_ROUND(t, SHA_MIX, (((C^D)&B)^D) , 0x5a827999, A, B, C, D, E ) +#define T_20_39(t, A, B, C, D, E) SHA_ROUND(t, SHA_MIX, (B^C^D) , 0x6ed9eba1, A, B, C, D, E ) +#define T_40_59(t, A, B, C, D, E) SHA_ROUND(t, SHA_MIX, ((B&C)+(D&(B^C))) , 0x8f1bbcdc, A, B, C, D, E ) +#define T_60_79(t, A, B, C, D, E) SHA_ROUND(t, SHA_MIX, (B^C^D) , 0xca62c1d6, A, B, C, D, E ) + +static void blk_SHA1_Block(blk_SHA_CTX *ctx, const unsigned int *data) +{ + unsigned int A,B,C,D,E; + unsigned int array[16]; + + A = ctx->H[0]; + B = ctx->H[1]; + C = ctx->H[2]; + D = ctx->H[3]; + E = ctx->H[4]; + + /* Round 1 - iterations 0-16 take their input from 'data' */ + T_0_15( 0, A, B, C, D, E); + T_0_15( 1, E, A, B, C, D); + T_0_15( 2, D, E, A, B, C); + T_0_15( 3, C, D, E, A, B); + T_0_15( 4, B, C, D, E, A); + T_0_15( 5, A, B, C, D, E); + T_0_15( 6, E, A, B, C, D); + T_0_15( 7, D, E, A, B, C); + T_0_15( 8, C, D, E, A, B); + T_0_15( 9, B, C, D, E, A); + T_0_15(10, A, B, C, D, E); + T_0_15(11, E, A, B, C, D); + T_0_15(12, D, E, A, B, C); + T_0_15(13, C, D, E, A, B); + T_0_15(14, B, C, D, E, A); + T_0_15(15, A, B, C, D, E); + + /* Round 1 - tail. Input from 512-bit mixing array */ + T_16_19(16, E, A, B, C, D); + T_16_19(17, D, E, A, B, C); + T_16_19(18, C, D, E, A, B); + T_16_19(19, B, C, D, E, A); + + /* Round 2 */ + T_20_39(20, A, B, C, D, E); + T_20_39(21, E, A, B, C, D); + T_20_39(22, D, E, A, B, C); + T_20_39(23, C, D, E, A, B); + T_20_39(24, B, C, D, E, A); + T_20_39(25, A, B, C, D, E); + T_20_39(26, E, A, B, C, D); + T_20_39(27, D, E, A, B, C); + T_20_39(28, C, D, E, A, B); + T_20_39(29, B, C, D, E, A); + T_20_39(30, A, B, C, D, E); + T_20_39(31, E, A, B, C, D); + T_20_39(32, D, E, A, B, C); + T_20_39(33, C, D, E, A, B); + T_20_39(34, B, C, D, E, A); + T_20_39(35, A, B, C, D, E); + T_20_39(36, E, A, B, C, D); + T_20_39(37, D, E, A, B, C); + T_20_39(38, C, D, E, A, B); + T_20_39(39, B, C, D, E, A); + + /* Round 3 */ + T_40_59(40, A, B, C, D, E); + T_40_59(41, E, A, B, C, D); + T_40_59(42, D, E, A, B, C); + T_40_59(43, C, D, E, A, B); + T_40_59(44, B, C, D, E, A); + T_40_59(45, A, B, C, D, E); + T_40_59(46, E, A, B, C, D); + T_40_59(47, D, E, A, B, C); + T_40_59(48, C, D, E, A, B); + T_40_59(49, B, C, D, E, A); + T_40_59(50, A, B, C, D, E); + T_40_59(51, E, A, B, C, D); + T_40_59(52, D, E, A, B, C); + T_40_59(53, C, D, E, A, B); + T_40_59(54, B, C, D, E, A); + T_40_59(55, A, B, C, D, E); + T_40_59(56, E, A, B, C, D); + T_40_59(57, D, E, A, B, C); + T_40_59(58, C, D, E, A, B); + T_40_59(59, B, C, D, E, A); + + /* Round 4 */ + T_60_79(60, A, B, C, D, E); + T_60_79(61, E, A, B, C, D); + T_60_79(62, D, E, A, B, C); + T_60_79(63, C, D, E, A, B); + T_60_79(64, B, C, D, E, A); + T_60_79(65, A, B, C, D, E); + T_60_79(66, E, A, B, C, D); + T_60_79(67, D, E, A, B, C); + T_60_79(68, C, D, E, A, B); + T_60_79(69, B, C, D, E, A); + T_60_79(70, A, B, C, D, E); + T_60_79(71, E, A, B, C, D); + T_60_79(72, D, E, A, B, C); + T_60_79(73, C, D, E, A, B); + T_60_79(74, B, C, D, E, A); + T_60_79(75, A, B, C, D, E); + T_60_79(76, E, A, B, C, D); + T_60_79(77, D, E, A, B, C); + T_60_79(78, C, D, E, A, B); + T_60_79(79, B, C, D, E, A); + + ctx->H[0] += A; + ctx->H[1] += B; + ctx->H[2] += C; + ctx->H[3] += D; + ctx->H[4] += E; +} + +void blk_SHA1_Init(blk_SHA_CTX *ctx) +{ + ctx->size = 0; + + /* Initialize H with the magic constants (see FIPS180 for constants) */ + ctx->H[0] = 0x67452301; + ctx->H[1] = 0xefcdab89; + ctx->H[2] = 0x98badcfe; + ctx->H[3] = 0x10325476; + ctx->H[4] = 0xc3d2e1f0; +} + +void blk_SHA1_Update(blk_SHA_CTX *ctx, const void *data, unsigned long len) +{ + unsigned int lenW = ctx->size & 63; + + ctx->size += len; + + /* Read the data into W and process blocks as they get full */ + if (lenW) { + unsigned int left = 64 - lenW; + if (len < left) + left = len; + memcpy(lenW + (char *)ctx->W, data, left); + lenW = (lenW + left) & 63; + len -= left; + data = ((const char *)data + left); + if (lenW) + return; + blk_SHA1_Block(ctx, ctx->W); + } + while (len >= 64) { + blk_SHA1_Block(ctx, data); + data = ((const char *)data + 64); + len -= 64; + } + if (len) + memcpy(ctx->W, data, len); +} + +void blk_SHA1_Final(unsigned char hashout[20], blk_SHA_CTX *ctx) +{ + static const unsigned char pad[64] = { 0x80 }; + unsigned int padlen[2]; + int i; + + /* Pad with a binary 1 (ie 0x80), then zeroes, then length */ + padlen[0] = htonl((uint32_t)(ctx->size >> 29)); + padlen[1] = htonl((uint32_t)(ctx->size << 3)); + + i = ctx->size & 63; + blk_SHA1_Update(ctx, pad, 1+ (63 & (55 - i))); + blk_SHA1_Update(ctx, padlen, 8); + + /* Output hash */ + for (i = 0; i < 5; i++) + put_be32(hashout + i*4, ctx->H[i]); +} diff --git a/src/fluent-bit/lib/monkey/plugins/auth/sha1.h b/src/fluent-bit/lib/monkey/plugins/auth/sha1.h new file mode 100644 index 000000000..4a75ab351 --- /dev/null +++ b/src/fluent-bit/lib/monkey/plugins/auth/sha1.h @@ -0,0 +1,22 @@ +/* + * SHA1 routine optimized to do word accesses rather than byte accesses, + * and to avoid unnecessary copies into the context array. + * + * This was initially based on the Mozilla SHA1 implementation, although + * none of the original Mozilla code remains. + */ + +typedef struct { + unsigned long long size; + unsigned int H[5]; + unsigned int W[16]; +} blk_SHA_CTX; + +void blk_SHA1_Init(blk_SHA_CTX *ctx); +void blk_SHA1_Update(blk_SHA_CTX *ctx, const void *dataIn, unsigned long len); +void blk_SHA1_Final(unsigned char hashout[20], blk_SHA_CTX *ctx); + +#define SHA_CTX blk_SHA_CTX +#define SHA1_Init blk_SHA1_Init +#define SHA1_Update blk_SHA1_Update +#define SHA1_Final blk_SHA1_Final diff --git a/src/fluent-bit/lib/monkey/plugins/auth/tools/CMakeLists.txt b/src/fluent-bit/lib/monkey/plugins/auth/tools/CMakeLists.txt new file mode 100644 index 000000000..63b445cf7 --- /dev/null +++ b/src/fluent-bit/lib/monkey/plugins/auth/tools/CMakeLists.txt @@ -0,0 +1,15 @@ +set(src + ../sha1.c + ../base64.c + mk_passwd.c + ) + +include_directories(../) +add_definitions(-D_FORCE_SYSMALLOC) +add_executable(mk_passwd ${src}) + +if(BUILD_LOCAL) + set(CMAKE_RUNTIME_OUTPUT_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}/") +else() + install(TARGETS mk_passwd RUNTIME DESTINATION ${CMAKE_INSTALL_FULL_SBINDIR}) +endif() diff --git a/src/fluent-bit/lib/monkey/plugins/auth/tools/mk_passwd.c b/src/fluent-bit/lib/monkey/plugins/auth/tools/mk_passwd.c new file mode 100644 index 000000000..6dba1d3f1 --- /dev/null +++ b/src/fluent-bit/lib/monkey/plugins/auth/tools/mk_passwd.c @@ -0,0 +1,209 @@ +/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ + +#ifdef MALLOC_JEMALLOC +#undef MALLOC_JEMALLOC +#endif + +#include <monkey/monkey.h> +#include <monkey/mk_core.h> + +#include <getopt.h> + +#include "sha1.h" +#include "base64.h" + +#define MAX_LINE_LEN 256 + +struct mk_passwd_user { + char *row; + struct mk_list _head; +}; + +/* Store a file as a linked list of its lines */ +static struct mk_list passwd_file; + +/* Load file to memory from disk + * If create_file == MK_TRUE, the file will be rewritten */ +void read_file(char *filename, int create_file) +{ + FILE *filein = fopen(filename, "r"); + char line[MAX_LINE_LEN]; + struct mk_passwd_user *entry; + + mk_list_init(&passwd_file); + + if (filein == NULL && create_file == MK_FALSE) { + printf("Error opening file %s\n", filename); + exit(1); + } + + if (filein == NULL || create_file == MK_TRUE) { + if (filein != NULL) + fclose(filein); + return; + } + + while (fgets(line, MAX_LINE_LEN, filein) != NULL) { + entry = malloc(sizeof(*entry)); + entry->row = strdup(line); + mk_list_add(&entry->_head, &passwd_file); + } + fclose(filein); +} + +/* Store data to disk */ +void dump_file(char *filename) +{ + FILE *fileout = fopen(filename, "w"); + struct mk_list *it, *tmp; + struct mk_passwd_user *entry; + + if (!fileout) { + printf("Error opening: %s", filename); + exit(EXIT_FAILURE); + } + + mk_list_foreach_safe(it, tmp, &passwd_file) { + entry = mk_list_entry(it, struct mk_passwd_user, _head); + fprintf(fileout, "%s", entry->row); + mk_list_del(&entry->_head); + free(entry->row); + free(entry); + } + fclose(fileout); +} + +/* Return sha1 hash of password + * A new line is appended at the hash */ +unsigned char *sha1_hash(const char *password) +{ + + unsigned char sha_hash[20]; + blk_SHA_CTX sha; + + blk_SHA1_Init(&sha); + blk_SHA1_Update(&sha, password, strlen(password)); + blk_SHA1_Final(sha_hash, &sha); + + return base64_encode(sha_hash, 20, NULL); +} + +void update_user(const char *username, const char *password, int create_user) +{ + struct mk_list *it, *tmp; + struct mk_passwd_user *entry; + unsigned char *hash_passwd; + int i; + + mk_list_foreach_safe(it, tmp, &passwd_file) { + entry = mk_list_entry(it, struct mk_passwd_user, _head); + for (i = 0; entry->row[i] != '\0' && entry->row[i] != ':' && username[i] != '\0' && entry->row[i] == username[i]; i++); + if (entry->row[i] != ':' || username[i] != '\0') + continue; + + /* Found a match */ + + /* Delete user */ + if (create_user == MK_FALSE) { + printf("[-] Deleting user %s\n", username); + mk_list_del(&entry->_head); + free(entry->row); + free(entry); + return; + } + + /* Update user */ + printf("[+] Password changed for user %s\n", username); + hash_passwd = sha1_hash(password); + free(entry->row); + entry->row = malloc(512); + snprintf(entry->row, 512, "%s:{SHA1}%s", username, hash_passwd); + free(hash_passwd); + + return; + } + + /* Create user */ + if (create_user == MK_TRUE) { + printf("[+] Adding user %s\n", username); + entry = malloc(sizeof(struct mk_passwd_user)); + entry->row = malloc(512); + hash_passwd = sha1_hash(password); + snprintf(entry->row, 512, "%s:{SHA1}%s", username, hash_passwd); + free(hash_passwd); + + mk_list_add(&entry->_head, &passwd_file); + } +} + +static void print_help(int full_help) +{ + printf("Usage: mk_passwd [-c] [-D] filename username password\n"); + if (full_help == MK_TRUE) { + printf("\nOptions:\n"); + printf(" -h, --help\tshow this help message and exit\n"); + printf(" -c\t\tCreate a new mkpasswd file, overwriting any existing file.\n"); + printf(" -D\t\tRemove the given user from the password file.\n"); + } +} + +int main(int argc, char *argv[]) +{ + int opt; + int create_user = MK_TRUE; + int create_file = MK_FALSE; + int show_help = MK_FALSE; + char *filename = NULL; + char *username = NULL; + char *password = NULL; + + /* Command line options */ + static const struct option long_opts[] = { + {"create", no_argument, NULL, 'c'}, + {"delete_user", no_argument, NULL, 'D'}, + {"help", no_argument, NULL, 'h'}, + }; + + /* Parse options */ + while ((opt = getopt_long(argc, argv, "hbDc", long_opts, NULL)) != -1) { + switch (opt) { + case 'c': + create_file = MK_TRUE; + break; + case 'D': + create_user = MK_FALSE; + break; + case 'h': + show_help = MK_TRUE; + break; + } + } + + /* Retrieve filename, username and password */ + while (optind < argc) { + if (filename == NULL) + filename = argv[optind++]; + else if (username == NULL) + username = argv[optind++]; + else if (password == NULL) + password = argv[optind++]; + } + + if (show_help == MK_TRUE) { + print_help(MK_TRUE); + exit(0); + } + + /* If delete_user option is provided, do not provide a password */ + if ((password != NULL) ^ (create_user == MK_TRUE)) { + print_help(MK_FALSE); + exit(1); + } + + /* Process request */ + read_file(filename, create_file); + update_user(username, password, create_user); + dump_file(filename); + + return 0; +} diff --git a/src/fluent-bit/lib/monkey/plugins/cgi/CMakeLists.txt b/src/fluent-bit/lib/monkey/plugins/cgi/CMakeLists.txt new file mode 100644 index 000000000..121144aa1 --- /dev/null +++ b/src/fluent-bit/lib/monkey/plugins/cgi/CMakeLists.txt @@ -0,0 +1,7 @@ +set(src + cgi.c + event.c + request.c + ) + +MONKEY_PLUGIN(cgi "${src}") diff --git a/src/fluent-bit/lib/monkey/plugins/cgi/cgi.c b/src/fluent-bit/lib/monkey/plugins/cgi/cgi.c new file mode 100644 index 000000000..58c7a647f --- /dev/null +++ b/src/fluent-bit/lib/monkey/plugins/cgi/cgi.c @@ -0,0 +1,501 @@ +/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ + +/* Monkey HTTP Server + * ================== + * Copyright 2001-2017 Eduardo Silva <eduardo@monkey.io> + * Copyright (C) 2012-2013, Lauri Kasanen + * + * 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 <monkey/mk_stream.h> +#include "cgi.h" + +#include <sys/types.h> +#include <sys/wait.h> +#include <sys/time.h> +#include <sys/resource.h> +#include <sys/stat.h> + +void cgi_finish(struct cgi_request *r) +{ + /* + * Unregister & close the CGI child process pipe reader fd from the + * thread event loop, otherwise we may get unexpected notifications. + */ + mk_api->ev_del(mk_api->sched_loop(), (struct mk_event *) r); + close(r->fd); + if (r->chunked && r->active == MK_TRUE) { + PLUGIN_TRACE("CGI sending Chunked EOF"); + channel_write(r, "0\r\n\r\n", 5); + } + + /* Try to kill any child process */ + if (r->child > 0) { + kill(r->child, SIGKILL); + r->child = 0; + } + + /* Invalidte our socket handler */ + requests_by_socket[r->socket] = NULL; + if (r->active == MK_TRUE) { + mk_api->http_request_end(r->plugin, r->cs, r->hangup); + } + cgi_req_del(r); +} + +int swrite(const int fd, const void *buf, const size_t count) +{ + ssize_t pos = count, ret = 0; + + while (pos > 0 && ret >= 0) { + ret = write(fd, buf, pos); + if (ret < 0) { + return ret; + } + + pos -= ret; + buf += ret; + } + return count; +} + +int channel_write(struct cgi_request *r, void *buf, size_t count) +{ + int ret; + + if (r->active == MK_FALSE) { + return -1; + } + + MK_TRACE("channel write: %d bytes", count); + mk_stream_in_raw(&r->sr->stream, + NULL, + buf, count, + NULL, NULL); + + ret = mk_api->channel_flush(r->sr->session->channel); + if (ret & MK_CHANNEL_ERROR) { + r->active = MK_FALSE; + cgi_finish(r); + } + return 0; +} + +static void cgi_write_post(void *p) +{ + const struct post_t * const in = p; + + swrite(in->fd, in->buf, in->len); + close(in->fd); +} + +static int do_cgi(const char *const __restrict__ file, + const char *const __restrict__ url, + struct mk_http_request *sr, + struct mk_http_session *cs, + struct mk_plugin *plugin, + char *interpreter, + char *mimetype) +{ + int ret; + int devnull; + const int socket = cs->socket; + struct file_info finfo; + struct cgi_request *r = NULL; + struct mk_event *event; + char *env[30]; + int writepipe[2], readpipe[2]; + (void) plugin; + + /* Unchanging env vars */ + env[0] = "PATH_INFO="; + env[1] = "GATEWAY_INTERFACE=CGI/1.1"; + env[2] = "REDIRECT_STATUS=200"; + const int env_start = 3; + char *protocol; + unsigned long len; + + /* Dynamic env vars */ + unsigned short envpos = env_start; + + char method[SHORTLEN]; + char *query = NULL; + char request_uri[PATHLEN]; + char script_filename[PATHLEN]; + char script_name[PATHLEN]; + char query_string[PATHLEN]; + char remote_addr[INET6_ADDRSTRLEN+SHORTLEN]; + char tmpaddr[INET6_ADDRSTRLEN], *ptr = tmpaddr; + char remote_port[SHORTLEN]; + char content_length[SHORTLEN]; + char content_type[SHORTLEN]; + char server_software[SHORTLEN]; + char server_protocol[SHORTLEN]; + char http_host[SHORTLEN]; + + /* Check the interpreter exists */ + if (interpreter) { + ret = mk_api->file_get_info(interpreter, &finfo, MK_FILE_EXEC); + if (ret == -1 || + (finfo.is_file == MK_FALSE && finfo.is_link == MK_FALSE) || + finfo.exec_access == MK_FALSE) { + return 500; + } + } + + if (mimetype) { + sr->content_type.data = mimetype; + sr->content_type.len = strlen(mimetype); + } + + snprintf(method, SHORTLEN, "REQUEST_METHOD=%.*s", (int) sr->method_p.len, sr->method_p.data); + env[envpos++] = method; + + snprintf(server_software, SHORTLEN, "SERVER_SOFTWARE=%s", + mk_api->config->server_signature); + env[envpos++] = server_software; + + snprintf(http_host, SHORTLEN, "HTTP_HOST=%.*s", (int) sr->host.len, sr->host.data); + env[envpos++] = http_host; + + if (sr->protocol == MK_HTTP_PROTOCOL_11) + protocol = MK_HTTP_PROTOCOL_11_STR; + else + protocol = MK_HTTP_PROTOCOL_10_STR; + + snprintf(server_protocol, SHORTLEN, "SERVER_PROTOCOL=%s", protocol); + env[envpos++] = server_protocol; + + if (sr->query_string.len) { + query = mk_api->mem_alloc_z(sr->query_string.len + 1); + memcpy(query, sr->query_string.data, sr->query_string.len); + snprintf(request_uri, PATHLEN, "REQUEST_URI=%s?%s", url, query); + } + else { + snprintf(request_uri, PATHLEN, "REQUEST_URI=%s", url); + } + env[envpos++] = request_uri; + + snprintf(script_filename, PATHLEN, "SCRIPT_FILENAME=%s", file); + env[envpos++] = script_filename; + + snprintf(script_name, PATHLEN, "SCRIPT_NAME=%s", url); + env[envpos++] = script_name; + + if (query) { + snprintf(query_string, PATHLEN, "QUERY_STRING=%s", query); + env[envpos++] = query_string; + mk_api->mem_free(query); + } + + if (mk_api->socket_ip_str(socket, &ptr, INET6_ADDRSTRLEN, &len) < 0) + tmpaddr[0] = '\0'; + snprintf(remote_addr, INET6_ADDRSTRLEN+SHORTLEN, "REMOTE_ADDR=%s", tmpaddr); + env[envpos++] = remote_addr; + + snprintf(remote_port, SHORTLEN, "REMOTE_PORT=%ld", sr->port); + env[envpos++] = remote_port; + + if (sr->data.len) { + snprintf(content_length, SHORTLEN, "CONTENT_LENGTH=%lu", sr->data.len); + env[envpos++] = content_length; + } + + if (sr->content_type.len) { + snprintf(content_type, SHORTLEN, "CONTENT_TYPE=%.*s", (int)sr->content_type.len, sr->content_type.data); + env[envpos++] = content_type; + } + + + /* Must be NULL-terminated */ + env[envpos] = NULL; + + /* pipes, from monkey's POV */ + if (pipe(writepipe) || pipe(readpipe)) { + mk_err("Failed to create pipe"); + return 403; + } + + pid_t pid = vfork(); + if (pid < 0) { + mk_err("Failed to fork"); + return 403; + } + + /* Child */ + if (pid == 0) { + close(writepipe[1]); + close(readpipe[0]); + + /* Our stdin is the read end of monkey's writing */ + if (dup2(writepipe[0], 0) < 0) { + mk_err("dup2 failed"); + _exit(1); + } + close(writepipe[0]); + + /* Our stdout is the write end of monkey's reading */ + if (dup2(readpipe[1], 1) < 0) { + mk_err("dup2 failed"); + _exit(1); + } + close(readpipe[1]); + + /* Our stderr goes to /dev/null */ + devnull = open("/dev/null", O_WRONLY); + if (devnull == -1) { + perror("open"); + _exit(1); + } + + if (dup2(devnull, 2) < 0) { + mk_err("dup2 failed"); + _exit(1); + } + close(devnull); + + char *argv[3] = { NULL }; + + char *tmp = mk_api->str_dup(file); + if (chdir(dirname(tmp))) + _exit(1); + + char *tmp2 = mk_api->str_dup(file); + argv[0] = basename(tmp2); + + /* Restore signals for the child */ + signal(SIGPIPE, SIG_DFL); + signal(SIGCHLD, SIG_DFL); + + if (!interpreter) { + execve(file, argv, env); + } + else { + argv[0] = basename(interpreter); + argv[1] = (char *) file; + execve(interpreter, argv, env); + } + /* Exec failed, return */ + _exit(1); + } + + /* Yay me */ + close(writepipe[0]); + close(readpipe[1]); + + /* If we have POST data to write, spawn a thread to do that */ + if (sr->data.len) { + struct post_t p; + pthread_t tid; + + p.fd = writepipe[1]; + p.buf = sr->data.data; + p.len = sr->data.len; + + ret = mk_api->worker_spawn(cgi_write_post, &p, &tid); + if (ret != 0) { + return 403; + } + } + else { + close(writepipe[1]); + } + + r = cgi_req_create(readpipe[0], socket, plugin, sr, cs); + if (!r) { + return 403; + } + r->child = pid; + + /* + * Hang up?: by default Monkey assumes the CGI scripts generate + * content dynamically (no Content-Length header), so for such HTTP/1.0 + * clients we should close the connection as KeepAlive is not supported + * by specification, only on HTTP/1.1 where the Chunked Transfer encoding + * exists. + */ + if (r->sr->protocol >= MK_HTTP_PROTOCOL_11) { + r->hangup = MK_FALSE; + } + + /* Set transfer encoding */ + if (r->sr->protocol >= MK_HTTP_PROTOCOL_11 && + (r->sr->headers.status < MK_REDIR_MULTIPLE || + r->sr->headers.status > MK_REDIR_USE_PROXY)) { + r->sr->headers.transfer_encoding = MK_HEADER_TE_TYPE_CHUNKED; + r->chunked = 1; + } + + /* Register the 'request' context */ + cgi_req_add(r); + + /* Prepare the built-in event structure */ + event = &r->event; + event->fd = readpipe[0]; + event->type = MK_EVENT_CUSTOM; + event->mask = MK_EVENT_EMPTY; + event->data = r; + event->handler = cb_cgi_read; + + /* Register the event into the worker event-loop */ + ret = mk_api->ev_add(mk_api->sched_loop(), + readpipe[0], + MK_EVENT_CUSTOM, MK_EVENT_READ, r); + if (ret != 0) { + return 403; + } + + + /* XXX Fixme: this needs to be atomic */ + requests_by_socket[socket] = r; + return 200; +} + +int mk_cgi_plugin_init(struct plugin_api **api, char *confdir) +{ + struct rlimit lim; + (void) confdir; + + mk_api = *api; + mk_list_init(&cgi_global_matches); + pthread_key_create(&cgi_request_list, NULL); + + /* + * We try to perform some quick lookup over the list of CGI + * instances. We do this with a fixed length array, if you use CGI + * you don't care too much about performance anyways. + */ + getrlimit(RLIMIT_NOFILE, &lim); + requests_by_socket = mk_api->mem_alloc_z(sizeof(struct cgi_request *) * lim.rlim_cur); + + /* Make sure we act good if the child dies */ + signal(SIGPIPE, SIG_IGN); + signal(SIGCHLD, SIG_IGN); + + return 0; +} + +int mk_cgi_plugin_exit() +{ + regfree(&match_regex); + mk_api->mem_free(requests_by_socket); + + return 0; +} + +int mk_cgi_stage30(struct mk_plugin *plugin, + struct mk_http_session *cs, + struct mk_http_request *sr, + int n_params, + struct mk_list *params) +{ + char *interpreter = NULL; + char *mimetype = NULL; + struct mk_vhost_handler_param *param; + (void) plugin; + + const char *const file = sr->real_path.data; + + if (!sr->file_info.is_file) { + return MK_PLUGIN_RET_NOT_ME; + } + + /* start running the CGI */ + if (cgi_req_get(cs->socket)) { + PLUGIN_TRACE("Error, someone tried to retry\n"); + return MK_PLUGIN_RET_CONTINUE; + } + + if (n_params > 0) { + /* Interpreter */ + param = mk_api->handler_param_get(0, params); + if (param) { + interpreter = param->p.data; + } + + /* Mimetype */ + param = mk_api->handler_param_get(0, params); + if (param) { + mimetype = param->p.data; + } + } + + int status = do_cgi(file, sr->uri_processed.data, + sr, cs, plugin, interpreter, mimetype); + + /* These are just for the other plugins, such as logger; bogus data */ + mk_api->header_set_http_status(sr, status); + if (status != 200) { + return MK_PLUGIN_RET_CLOSE_CONX; + } + + sr->headers.cgi = SH_CGI; + return MK_PLUGIN_RET_CONTINUE; +} + +/* + * Invoked everytime a remote client drop the active connection, this + * callback is triggered by the Monkey Scheduler + */ +int mk_cgi_stage30_hangup(struct mk_plugin *plugin, + struct mk_http_session *cs, + struct mk_http_request *sr) +{ + struct cgi_request *r; + (void) sr; + (void) plugin; + + PLUGIN_TRACE("CGI / Parent connection closed (hangup)"); + r = requests_by_socket[cs->socket]; + if (!r) { + return -1; + } + + r->active = MK_FALSE; + cgi_finish(r); + return 0; +} + +void mk_cgi_worker_init() +{ + struct mk_list *list = mk_api->mem_alloc_z(sizeof(struct mk_list)); + + mk_list_init(list); + pthread_setspecific(cgi_request_list, (void *) list); +} + + +struct mk_plugin_stage mk_plugin_stage_cgi = { + .stage30 = &mk_cgi_stage30, + .stage30_hangup = &mk_cgi_stage30_hangup +}; + +struct mk_plugin mk_plugin_cgi = { + /* Identification */ + .shortname = "cgi", + .name = "Common Gateway Interface", + .version = MK_VERSION_STR, + .hooks = MK_PLUGIN_STAGE, + + /* Init / Exit */ + .init_plugin = mk_cgi_plugin_init, + .exit_plugin = mk_cgi_plugin_exit, + + /* Init Levels */ + .master_init = NULL, + .worker_init = mk_cgi_worker_init, + + /* Type */ + .stage = &mk_plugin_stage_cgi +}; diff --git a/src/fluent-bit/lib/monkey/plugins/cgi/cgi.h b/src/fluent-bit/lib/monkey/plugins/cgi/cgi.h new file mode 100644 index 000000000..27123b7ae --- /dev/null +++ b/src/fluent-bit/lib/monkey/plugins/cgi/cgi.h @@ -0,0 +1,133 @@ +/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ + +/* Monkey HTTP Server + * ================== + * Copyright 2001-2017 Eduardo Silva <eduardo@monkey.io> + * Copyright (C) 2012-2013, Lauri Kasanen + * + * 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. + */ + +#ifndef MK_CGI_H +#define MK_CGI_H + +#include <monkey/mk_api.h> + +#include <sys/types.h> +#include <regex.h> +#include <signal.h> +#include <libgen.h> + +enum { + BUFLEN = 4096, + PATHLEN = 1024, + SHORTLEN = 64 +}; + +regex_t match_regex; + +struct cgi_request **requests_by_socket; + +struct post_t { + int fd; + void *buf; + unsigned long len; +}; + +struct cgi_match_t { + regex_t match; + char *bin; + mk_ptr_t content_type; + + struct mk_list _head; +}; + +struct cgi_vhost_t { + struct mk_vhost *host; + struct mk_list matches; +}; + +struct cgi_vhost_t *cgi_vhosts; +struct mk_list cgi_global_matches; + + +struct cgi_request { + /* Built-in reference for the event loop */ + struct mk_event event; + + char in_buf[BUFLEN]; + + struct mk_list _head; + + struct mk_plugin *plugin; + struct mk_http_request *sr; + struct mk_http_session *cs; + + unsigned int in_len; + + int fd; /* Pipe the CGI proc */ + int socket; /* Client connection */ + int hangup; /* Should close connection when done ? */ + int active; /* Active session ? */ + pid_t child; /* child process ID */ + unsigned char status_done; + unsigned char all_headers_done; + unsigned char chunked; +}; + +/* Global list per worker */ +pthread_key_t cgi_request_list; + +extern struct cgi_request **requests_by_socket; + +void cgi_finish(struct cgi_request *r); + +int swrite(const int fd, const void *buf, const size_t count); +int channel_write(struct cgi_request *r, void *buf, size_t count); + +struct cgi_request *cgi_req_create(int fd, int socket, + struct mk_plugin *plugin, + struct mk_http_request *sr, + struct mk_http_session *cs); +void cgi_req_add(struct cgi_request *r); +int cgi_req_del(struct cgi_request *r); + +// Get the CGI request by the client socket +static inline struct cgi_request *cgi_req_get(int socket) +{ + struct cgi_request *r = requests_by_socket[socket]; + return r; +} + +// Get the CGI request by the CGI app's fd +static inline struct cgi_request *cgi_req_get_by_fd(int fd) +{ + struct mk_list *list, *node; + struct cgi_request *r; + + list = pthread_getspecific(cgi_request_list); + if (mk_list_is_empty(list) == 0) + return NULL; + + mk_list_foreach(node, list) { + r = mk_list_entry(node, struct cgi_request, _head); + if (r->fd == fd) + return r; + } + + return NULL; +} + +int cb_cgi_read(void *data); + +#endif diff --git a/src/fluent-bit/lib/monkey/plugins/cgi/event.c b/src/fluent-bit/lib/monkey/plugins/cgi/event.c new file mode 100644 index 000000000..b8d395260 --- /dev/null +++ b/src/fluent-bit/lib/monkey/plugins/cgi/event.c @@ -0,0 +1,170 @@ +/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ + +/* Monkey HTTP Server + * ================== + * Copyright 2001-2017 Eduardo Silva <eduardo@monkey.io> + * Copyright (C) 2012-2013, Lauri Kasanen + * + * 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 "cgi.h" + +/* + * The reason for this function is that some CGI apps + * + * use LFLF and some use CRLFCRLF. + * + * If that app then sends content that has the other break + * in the beginning, monkey can accidentally send part of the + * content as headers. + */ + +static char *getearliestbreak(const char buf[], const unsigned bufsize, + unsigned char * const advance) + { + char * const crend = memmem(buf, bufsize, MK_IOV_CRLFCRLF, + sizeof(MK_IOV_CRLFCRLF) - 1); + char * const lfend = memmem(buf, bufsize, MK_IOV_LFLF, + sizeof(MK_IOV_LFLF) - 1); + + if (!crend && !lfend) + return NULL; + + /* If only one found, return that one */ + if (!crend) { + *advance = 2; + return lfend; + } + if (!lfend) + return crend; + + /* Both found, return the earlier one - the latter one is part of content */ + if (lfend < crend) { + *advance = 2; + return lfend; + } + return crend; +} + +int process_cgi_data(struct cgi_request *r) +{ + int ret; + int len; + int status; + char *buf = r->in_buf; + char *outptr = r->in_buf; + char *end; + char *endl; + unsigned char advance; + + mk_api->socket_cork_flag(r->cs->socket, TCP_CORK_OFF); + if (!r->status_done && r->in_len >= 8) { + if (memcmp(buf, "Status: ", 8) == 0) { + status = atoi(buf + 8); + mk_api->header_set_http_status(r->sr, status); + endl = memchr(buf + 8, '\n', r->in_len - 8); + if (!endl) { + return MK_PLUGIN_RET_EVENT_OWNED; + } + else { + endl++; + outptr = endl; + r->in_len -= endl - buf; + } + } + else if (memcmp(buf, "HTTP", 4) == 0) { + status = atoi(buf + 9); + mk_api->header_set_http_status(r->sr, status); + + endl = memchr(buf + 8, '\n', r->in_len - 8); + if (!endl) { + return MK_PLUGIN_RET_EVENT_OWNED; + } + else { + endl++; + outptr = endl; + r->in_len -= endl - buf; + } + } + mk_api->header_prepare(r->plugin, r->cs, r->sr); + r->status_done = 1; + } + + if (!r->all_headers_done) { + advance = 4; + + /* Write the rest of the headers without chunking */ + end = getearliestbreak(outptr, r->in_len, &advance); + if (!end) { + /* Let's return until we have the headers break */ + return MK_PLUGIN_RET_EVENT_OWNED; + } + end += advance; + len = end - outptr; + channel_write(r, outptr, len); + outptr += len; + r->in_len -= len; + + r->all_headers_done = 1; + if (r->in_len == 0) { + return MK_PLUGIN_RET_EVENT_OWNED; + } + } + + if (r->chunked) { + char tmp[16]; + len = snprintf(tmp, 16, "%x\r\n", r->in_len); + ret = channel_write(r, tmp, len); + if (ret < 0) + return MK_PLUGIN_RET_EVENT_CLOSE; + } + + ret = channel_write(r, outptr, r->in_len); + if (ret < 0) { + return MK_PLUGIN_RET_EVENT_CLOSE; + } + + r->in_len = 0; + if (r->chunked) { + channel_write(r, MK_CRLF, 2); + } + return MK_PLUGIN_RET_EVENT_OWNED; +} + +int cb_cgi_read(void *data) +{ + int n; + struct cgi_request *r = data; + + if (r->active == MK_FALSE) { + return -1; + } + + if ((BUFLEN - r->in_len) < 1) { + PLUGIN_TRACE("CLOSE BY SIZE"); + cgi_finish(r); + return -1; + } + + n = read(r->fd, r->in_buf + r->in_len, BUFLEN - r->in_len); + PLUGIN_TRACE("FD=%i CGI READ=%d", r->fd, n); + if (n <= 0) { + /* It most of cases this means the child process finished */ + cgi_finish(r); + return MK_PLUGIN_RET_EVENT_CLOSE; + } + r->in_len += n; + process_cgi_data(r); + return 0; +} diff --git a/src/fluent-bit/lib/monkey/plugins/cgi/request.c b/src/fluent-bit/lib/monkey/plugins/cgi/request.c new file mode 100644 index 000000000..c51a13831 --- /dev/null +++ b/src/fluent-bit/lib/monkey/plugins/cgi/request.c @@ -0,0 +1,72 @@ +/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ + +/* Monkey HTTP Server + * ================== + * Copyright 2001-2017 Eduardo Silva <eduardo@monkey.io> + * Copyright (C) 2012-2013, Lauri Kasanen + * + * 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 "cgi.h" + +struct cgi_request *cgi_req_create(int fd, int socket, + struct mk_plugin *plugin, + struct mk_http_request *sr, + struct mk_http_session *cs) +{ + struct cgi_request *cgi; + + cgi = mk_api->mem_alloc_z(sizeof(struct cgi_request)); + if (!cgi) { + return NULL; + } + + cgi->fd = fd; + cgi->socket = socket; + cgi->plugin = plugin; + cgi->sr = sr; + cgi->cs = cs; + cgi->hangup = MK_TRUE; + cgi->active = MK_TRUE; + cgi->in_len = 0; + + cgi->event.mask = MK_EVENT_EMPTY; + cgi->event.status = MK_EVENT_NONE; + + return cgi; +} + +void cgi_req_add(struct cgi_request *r) +{ + struct mk_list *list; + + list = pthread_getspecific(cgi_request_list); + mk_list_add(&r->_head, list); +} + +int cgi_req_del(struct cgi_request *r) +{ + PLUGIN_TRACE("Delete request child_fd=%i child_pid=%lu", + r->fd, r->child); + + mk_list_del(&r->_head); + if (r->active == MK_FALSE) { + mk_api->sched_event_free(&r->event); + } + else { + mk_mem_free(r); + } + + return 0; +} diff --git a/src/fluent-bit/lib/monkey/plugins/cheetah/ABOUT b/src/fluent-bit/lib/monkey/plugins/cheetah/ABOUT new file mode 100644 index 000000000..229bf62c2 --- /dev/null +++ b/src/fluent-bit/lib/monkey/plugins/cheetah/ABOUT @@ -0,0 +1,4 @@ +Cheetah! Plugin +=============== +This plugin provides a command line interface for Monkey, +it works like a shell. diff --git a/src/fluent-bit/lib/monkey/plugins/cheetah/CMakeLists.txt b/src/fluent-bit/lib/monkey/plugins/cheetah/CMakeLists.txt new file mode 100644 index 000000000..ca760c337 --- /dev/null +++ b/src/fluent-bit/lib/monkey/plugins/cheetah/CMakeLists.txt @@ -0,0 +1,9 @@ +set(src + cheetah.c + loop.c + cmd.c + cutils.c + ) + +MONKEY_PLUGIN(cheetah "${src}") +add_subdirectory(conf) diff --git a/src/fluent-bit/lib/monkey/plugins/cheetah/OPTIONAL b/src/fluent-bit/lib/monkey/plugins/cheetah/OPTIONAL new file mode 100644 index 000000000..e69de29bb --- /dev/null +++ b/src/fluent-bit/lib/monkey/plugins/cheetah/OPTIONAL diff --git a/src/fluent-bit/lib/monkey/plugins/cheetah/cheetah.c b/src/fluent-bit/lib/monkey/plugins/cheetah/cheetah.c new file mode 100644 index 000000000..489e73422 --- /dev/null +++ b/src/fluent-bit/lib/monkey/plugins/cheetah/cheetah.c @@ -0,0 +1,162 @@ +/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ + +/* Monkey HTTP Server + * ================== + * Copyright 2001-2017 Eduardo Silva <eduardo@monkey.io> + * + * 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. + */ + +/* Monkey Plugin Interface */ +#include <monkey/mk_api.h> + +/* Local header files */ +#include "cmd.h" +#include "cutils.h" +#include "cheetah.h" +#include "loop.h" + +void mk_cheetah_welcome_msg() +{ + CHEETAH_WRITE("\n%s%s***%s Welcome to %sCheetah!%s, the %sMonkey Shell %s:) %s***%s\n", + ANSI_BOLD, ANSI_YELLOW, + ANSI_WHITE, ANSI_GREEN, + ANSI_WHITE, ANSI_RED, ANSI_WHITE, ANSI_YELLOW, ANSI_RESET); + CHEETAH_WRITE("\n << %sType 'help' or '\\h' for help%s >>\n\n", + ANSI_BLUE, ANSI_RESET); + CHEETAH_FLUSH(); +} + +static int mk_cheetah_config(char *path) +{ + unsigned long len; + char *listen = NULL; + char *default_file = NULL; + struct mk_rconf *conf; + struct mk_rconf_section *section; + + /* this variable is defined in cheetah.h and points to + * the FILE *descriptor where to write out the data + */ + cheetah_output = NULL; + + /* read configuration file */ + mk_api->str_build(&default_file, &len, "%scheetah.conf", path); + conf = mk_api->config_open(default_file); + if (!conf) { + return -1; + } + + section = mk_api->config_section_get(conf, "CHEETAH"); + + if (!section) { + CHEETAH_WRITE("\nError, could not find CHEETAH tag"); + return -1; + } + + /* no longer needed */ + mk_api->mem_free(default_file); + + /* Listen directive */ + listen = mk_api->config_section_get_key(section, "Listen", MK_RCONF_STR); + + if (strcasecmp(listen, LISTEN_STDIN_STR) == 0) { + listen_mode = LISTEN_STDIN; + } + else if (strcasecmp(listen, LISTEN_SERVER_STR) == 0) { + listen_mode = LISTEN_SERVER; + } + else { + printf("\nCheetah! Error: Invalid LISTEN value"); + return -1; + } + + /* Cheetah cannot work in STDIN mode if Monkey is working in background */ + if (listen_mode == LISTEN_STDIN && mk_api->config->is_daemon == MK_TRUE) { + printf("\nCheetah!: Forcing SERVER mode as Monkey is running in background\n"); + fflush(stdout); + listen_mode = LISTEN_SERVER; + } + + return 0; +} + +static void mk_cheetah_init(void *args) +{ + struct mk_server *server = args; + + /* Rename worker */ + mk_api->worker_rename("monkey: cheetah"); + + /* Open right FDs for I/O */ + if (listen_mode == LISTEN_STDIN) { + cheetah_input = stdin; + cheetah_output = stdout; + mk_cheetah_loop_stdin(server); + } + else if (listen_mode == LISTEN_SERVER) { + mk_cheetah_loop_server(server); + } +} + +/* This function is called when the plugin is loaded, it must + * return + */ +int mk_cheetah_plugin_init(struct plugin_api **api, char *confdir) +{ + int ret; + mk_api = *api; + init_time = time(NULL); + + ret = mk_cheetah_config(confdir); + return ret; +} + +int mk_cheetah_plugin_exit() +{ + if (listen_mode == LISTEN_SERVER) { + /* Remote named pipe */ + unlink(cheetah_server); + mk_api->mem_free(cheetah_server); + } + + return 0; +} + +int mk_cheetah_master_init(struct mk_server *server) +{ + int ret; + pthread_t tid; + + ret = mk_api->worker_spawn(mk_cheetah_init, server, &tid); + if (ret != 0) { + return -1; + } + + return 0; +} + +struct mk_plugin mk_plugin_cheetah = { + /* Identification */ + .shortname = "cheetah", + .name = "Cheetah! Shell", + .version = MK_VERSION_STR, + + /* Init / Exit */ + .init_plugin = mk_cheetah_plugin_init, + .exit_plugin = mk_cheetah_plugin_exit, + + /* Init Levels */ + .master_init = mk_cheetah_master_init, + .worker_init = NULL +}; diff --git a/src/fluent-bit/lib/monkey/plugins/cheetah/cheetah.h b/src/fluent-bit/lib/monkey/plugins/cheetah/cheetah.h new file mode 100644 index 000000000..5bf6c14e5 --- /dev/null +++ b/src/fluent-bit/lib/monkey/plugins/cheetah/cheetah.h @@ -0,0 +1,79 @@ +/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ + +/* Monkey HTTP Server + * ================== + * Copyright 2001-2017 Eduardo Silva <eduardo@monkey.io> + * + * 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. + */ + +#ifndef MK_CHEETAH_H +#define MK_CHEETAH_H + +/* Commands */ +#define MK_CHEETAH_CLEAR "clear" +#define MK_CHEETAH_CLEAR_SC "\\c" + +#define MK_CHEETAH_CONFIG "config" +#define MK_CHEETAH_CONFIG_SC "\\f" + +#define MK_CHEETAH_STATUS "status" +#define MK_CHEETAH_STATUS_SC "\\s" + +#define MK_CHEETAH_HELP "help" +#define MK_CHEETAH_HELP_SC "\\h" + +#define MK_CHEETAH_SHELP "?" +#define MK_CHEETAH_SHELP_SC "\\?" + +#define MK_CHEETAH_UPTIME "uptime" +#define MK_CHEETAH_UPTIME_SC "\\u" + +#define MK_CHEETAH_PLUGINS "plugins" +#define MK_CHEETAH_PLUGINS_SC "\\g" + +#define MK_CHEETAH_VHOSTS "vhosts" +#define MK_CHEETAH_VHOSTS_SC "\\v" + +#define MK_CHEETAH_WORKERS "workers" +#define MK_CHEETAH_WORKERS_SC "\\w" + +#define MK_CHEETAH_QUIT "quit" +#define MK_CHEETAH_QUIT_SC "\\q" + +/* Constants */ +#define MK_CHEETAH_PROMPT "%s%scheetah>%s " +#define MK_CHEETAH_PROC_TASK "/proc/%i/task/%i/stat" +#define MK_CHEETAH_ONEDAY 86400 +#define MK_CHEETAH_ONEHOUR 3600 +#define MK_CHEETAH_ONEMINUTE 60 + +/* Configurarion: Listen */ +#define LISTEN_STDIN_STR "STDIN" +#define LISTEN_SERVER_STR "SERVER" + +#define LISTEN_STDIN 0 +#define LISTEN_SERVER 1 + +int listen_mode; + +char *cheetah_server; + +int cheetah_socket; +FILE *cheetah_input; +FILE *cheetah_output; + +/* functions */ +void mk_cheetah_welcome_msg(); + +#endif diff --git a/src/fluent-bit/lib/monkey/plugins/cheetah/cmd.c b/src/fluent-bit/lib/monkey/plugins/cheetah/cmd.c new file mode 100644 index 000000000..6f02dad23 --- /dev/null +++ b/src/fluent-bit/lib/monkey/plugins/cheetah/cmd.c @@ -0,0 +1,472 @@ +/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ + +/* Monkey HTTP Server + * ================== + * Copyright 2001-2017 Eduardo Silva <eduardo@monkey.io> + * + * 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 <monkey/mk_api.h> + +#include <pwd.h> +#include <ctype.h> + +#include "cheetah.h" +#include "cutils.h" +#include "cmd.h" + +/* strip leading and trailing space from input command line. */ +static char *strip_whitespace(char *cmd) +{ + char *end; + while (isspace(*cmd)) + cmd++; + if (*cmd == 0) + return cmd; + end = cmd + strlen(cmd) - 1; + while (end > cmd && isspace(*end)) + end--; + end++; + *end = '\0'; + return cmd; +} + +int mk_cheetah_cmd(char *raw_cmd, struct mk_server *server) +{ + char *cmd = strip_whitespace(raw_cmd); + if (strcmp(cmd, MK_CHEETAH_CONFIG) == 0 || + strcmp(cmd, MK_CHEETAH_CONFIG_SC) == 0) { + mk_cheetah_cmd_config(server); + } + else if (strcmp(cmd, MK_CHEETAH_STATUS) == 0 || + strcmp(cmd, MK_CHEETAH_STATUS_SC) == 0) { + mk_cheetah_cmd_status(server); + } + else if (strcmp(cmd, MK_CHEETAH_CLEAR) == 0 || + strcmp(cmd, MK_CHEETAH_CLEAR_SC) == 0) { + mk_cheetah_cmd_clear(); + } + else if (strcmp(cmd, MK_CHEETAH_UPTIME) == 0 || + strcmp(cmd, MK_CHEETAH_UPTIME_SC) == 0) { + mk_cheetah_cmd_uptime(server); + } + else if (strcmp(cmd, MK_CHEETAH_PLUGINS) == 0 || + strcmp(cmd, MK_CHEETAH_PLUGINS_SC) == 0) { + mk_cheetah_cmd_plugins(server); + } + else if (strcmp(cmd, MK_CHEETAH_WORKERS) == 0 || + strcmp(cmd, MK_CHEETAH_WORKERS_SC) == 0) { + mk_cheetah_cmd_workers(server); + } + else if (strcmp(cmd, MK_CHEETAH_VHOSTS) == 0 || + strcmp(cmd, MK_CHEETAH_VHOSTS_SC) == 0) { + mk_cheetah_cmd_vhosts(server); + } + else if (strcmp(cmd, MK_CHEETAH_HELP) == 0 || + strcmp(cmd, MK_CHEETAH_HELP_SC) == 0 || + strcmp(cmd, MK_CHEETAH_SHELP) == 0 || + strcmp(cmd, MK_CHEETAH_SHELP_SC) == 0) { + mk_cheetah_cmd_help(); + } + else if (strcmp(cmd, MK_CHEETAH_QUIT) == 0 || + strcmp(cmd, MK_CHEETAH_QUIT_SC) == 0) { + return mk_cheetah_cmd_quit(); + } + else if (strlen(cmd) == 0) { + return 0; + } + else { + CHEETAH_WRITE("Invalid command, type 'help' for a list of available commands\n"); + } + + CHEETAH_FLUSH(); + return 0; +} + +void mk_cheetah_cmd_clear() +{ + CHEETAH_WRITE("\033[2J\033[1;1H"); +} + +void mk_cheetah_cmd_uptime(struct mk_server *server) +{ + int days; + int hours; + int minutes; + int seconds; + long int upmind; + long int upminh; + long int uptime; + (void) server; + + /* uptime in seconds */ + uptime = time(NULL) - init_time; + + /* days */ + days = uptime / MK_CHEETAH_ONEDAY; + upmind = uptime - (days * MK_CHEETAH_ONEDAY); + + /* hours */ + hours = upmind / MK_CHEETAH_ONEHOUR; + upminh = upmind - hours * MK_CHEETAH_ONEHOUR; + + /* minutes */ + minutes = upminh / MK_CHEETAH_ONEMINUTE; + seconds = upminh - minutes * MK_CHEETAH_ONEMINUTE; + + CHEETAH_WRITE + ("Server has been running: %i day%s, %i hour%s, %i minute%s and %i second%s\n\n", + days, (days > 1) ? "s" : "", hours, (hours > 1) ? "s" : "", minutes, + (minutes > 1) ? "s" : "", seconds, (seconds > 1) ? "s" : ""); +} + +void mk_cheetah_cmd_plugins_print_core(struct mk_list *list) +{ + struct mk_plugin *p; + struct mk_list *head; + + CHEETAH_WRITE("\n%s[MASTER INIT]%s", ANSI_BOLD ANSI_BLUE, ANSI_RESET); + + mk_list_foreach(head, list) { + p = mk_list_entry(head, struct mk_plugin, _head); + + if (p->master_init) { + CHEETAH_WRITE("\n [%s] %s v%s on \"%s\"", + p->shortname, p->name, p->version, p->path); + } + } + + CHEETAH_WRITE("\n\n%s[WORKER INIT]%s", ANSI_BOLD ANSI_BLUE, ANSI_RESET); + + mk_list_foreach(head, list) { + p = mk_list_entry(head, struct mk_plugin, _head); + + if (p->worker_init) { + CHEETAH_WRITE("\n [%s] %s v%s on \"%s\"", + p->shortname, p->name, p->version, p->path); + } + } + + CHEETAH_WRITE("\n\n"); +} + +void mk_cheetah_cmd_plugins_print_network(struct mk_list *list) +{ + struct mk_plugin *p; + struct mk_list *head; + + CHEETAH_WRITE("%s[NETWORK I/O]%s", ANSI_BOLD ANSI_RED, ANSI_RESET); + + mk_list_foreach(head, list) { + p = mk_list_entry(head, struct mk_plugin, _head); + if (p->hooks & MK_PLUGIN_NETWORK_LAYER) { + CHEETAH_WRITE("\n [%s] %s v%s on \"%s\"", + p->shortname, p->name, p->version, p->path); + } + } + + CHEETAH_WRITE("\n"); +} + +void mk_cheetah_cmd_plugins(struct mk_server *server) +{ + struct mk_plugin *p; + struct mk_plugin_stage *s; + struct mk_list *head; + + if (mk_list_is_empty(&server->stage10_handler)) { + CHEETAH_WRITE("%s[%sSTAGE_10%s]%s", + ANSI_BOLD, ANSI_YELLOW, ANSI_WHITE, ANSI_RESET); + mk_list_foreach(head, &mk_api->config->stage10_handler) { + s = mk_list_entry(head, struct mk_plugin_stage, _head); + p = s->plugin; + CHEETAH_WRITE("\n [%s] %s v%s on \"%s\"", + p->shortname, p->name, p->version, p->path); + } + } + + if (mk_list_is_empty(&mk_api->config->stage20_handler)) { + CHEETAH_WRITE("%s[%sSTAGE_20%s]%s", + ANSI_BOLD, ANSI_YELLOW, ANSI_WHITE, ANSI_RESET); + mk_list_foreach(head, &mk_api->config->stage20_handler) { + s = mk_list_entry(head, struct mk_plugin_stage, _head); + p = s->plugin; + CHEETAH_WRITE("\n [%s] %s v%s on \"%s\"", + p->shortname, p->name, p->version, p->path); + } + } + + if (mk_list_is_empty(&mk_api->config->stage30_handler)) { + CHEETAH_WRITE("%s[%sSTAGE_30%s]%s", + ANSI_BOLD, ANSI_YELLOW, ANSI_WHITE, ANSI_RESET); + mk_list_foreach(head, &mk_api->config->stage30_handler) { + s = mk_list_entry(head, struct mk_plugin_stage, _head); + p = s->plugin; + CHEETAH_WRITE("\n [%s] %s v%s on \"%s\"", + p->shortname, p->name, p->version, p->path); + } + } + + if (mk_list_is_empty(&mk_api->config->stage40_handler)) { + CHEETAH_WRITE("%s[%sSTAGE_40%s]%s", + ANSI_BOLD, ANSI_YELLOW, ANSI_WHITE, ANSI_RESET); + mk_list_foreach(head, &mk_api->config->stage40_handler) { + s = mk_list_entry(head, struct mk_plugin_stage, _head); + p = s->plugin; + CHEETAH_WRITE("\n [%s] %s v%s on \"%s\"", + p->shortname, p->name, p->version, p->path); + } + } + + if (mk_list_is_empty(&mk_api->config->stage50_handler)) { + CHEETAH_WRITE("%s[%sSTAGE_50%s]%s", + ANSI_BOLD, ANSI_YELLOW, ANSI_WHITE, ANSI_RESET); + mk_list_foreach(head, &mk_api->config->stage50_handler) { + s = mk_list_entry(head, struct mk_plugin_stage, _head); + p = s->plugin; + CHEETAH_WRITE("\n [%s] %s v%s on \"%s\"", + p->shortname, p->name, p->version, p->path); + } + } + + CHEETAH_WRITE("\n\n"); +} + +void mk_cheetah_cmd_vhosts(struct mk_server *server) +{ + struct mk_vhost *entry_host; + struct mk_vhost_alias *entry_alias; + struct mk_rconf_section *section; + struct mk_rconf_entry *entry; + struct mk_list *hosts = &server->hosts; + struct mk_list *aliases; + struct mk_list *head_host; + struct mk_list *head_alias; + struct mk_list *head_sections; + struct mk_list *head_entries; + + mk_list_foreach(head_host, hosts) { + entry_host = mk_list_entry(head_host, struct mk_vhost, _head); + + aliases = &entry_host->server_names; + entry_alias = mk_list_entry_first(aliases, struct mk_vhost_alias, _head); + CHEETAH_WRITE("%s[%sVHost '%s'%s%s]%s\n", + ANSI_BOLD, ANSI_YELLOW, + entry_alias->name, ANSI_BOLD, ANSI_WHITE, ANSI_RESET); + + CHEETAH_WRITE(" - Names : "); + mk_list_foreach(head_alias, aliases) { + entry_alias = mk_list_entry(head_alias, struct mk_vhost_alias, _head); + CHEETAH_WRITE("%s ", entry_alias->name); + } + CHEETAH_WRITE("\n"); + + CHEETAH_WRITE(" - Document root : %s\n", entry_host->documentroot.data); + CHEETAH_WRITE(" - Config file : %s\n", entry_host->file); + + if (!entry_host->config) { + continue; + } + + mk_list_foreach(head_sections, &entry_host->config->sections) { + section = mk_list_entry(head_sections, struct mk_rconf_section, _head); + CHEETAH_WRITE(" %s+%s [%s]\n", ANSI_GREEN, ANSI_RESET, + section->name); + + mk_list_foreach(head_entries, §ion->entries) { + entry = mk_list_entry(head_entries, struct mk_rconf_entry, _head); + CHEETAH_WRITE(" - %11.10s : %s\n", entry->key, entry->val); + } + } + } + + CHEETAH_WRITE("\n"); +} + +void mk_cheetah_cmd_workers(struct mk_server *server) +{ + int i; + unsigned long long active_connections; + struct mk_sched_worker *node; + struct mk_sched_ctx *ctx; + + ctx = server->sched_ctx; + node = ctx->workers; + for (i=0; i < server->workers; i++) { + active_connections = (node[i].accepted_connections - node[i].closed_connections); + + CHEETAH_WRITE("* Worker %i\n", node[i].idx); + CHEETAH_WRITE(" - Task ID : %i\n", node[i].pid); + CHEETAH_WRITE(" - Active Connections: %llu\n", active_connections); + } + + CHEETAH_WRITE("\n"); +} + +int mk_cheetah_cmd_quit() +{ + CHEETAH_WRITE("Cheeta says: Good Bye!\n"); + if (listen_mode == LISTEN_STDIN) { + pthread_exit(NULL); + return 0; + } + else { + return -1; + } +} + +void mk_cheetah_cmd_help() +{ + CHEETAH_WRITE("List of available commands for Cheetah Shell\n"); + CHEETAH_WRITE("\ncommand shortcut description"); + CHEETAH_WRITE("\n----------------------------------------------------"); + CHEETAH_WRITE("\n? (\\?) Synonym for 'help'"); + CHEETAH_WRITE("\nconfig (\\f) Display global configuration"); + CHEETAH_WRITE("\nplugins (\\g) List loaded plugins and associated stages"); + CHEETAH_WRITE("\nstatus (\\s) Display general web server information"); + CHEETAH_WRITE("\nuptime (\\u) Display how long the web server has been running"); + CHEETAH_WRITE("\nvhosts (\\v) List virtual hosts configured"); + CHEETAH_WRITE("\nworkers (\\w) Show thread workers information\n"); + CHEETAH_WRITE("\nclear (\\c) Clear screen"); + CHEETAH_WRITE("\nhelp (\\h) Print this help"); + CHEETAH_WRITE("\nquit (\\q) Exit Cheetah shell :_(\n\n"); +} + +static void mk_cheetah_listen_config(struct mk_server *server) +{ + struct mk_list *head; + struct mk_config_listener *listener; + + mk_list_foreach(head, &server->listeners) { + listener = mk_list_entry(head, struct mk_config_listener, _head); + CHEETAH_WRITE("\nListen on : %s:%s", + listener->address, + listener->port); + } +} + +void mk_cheetah_cmd_config(struct mk_server *server) +{ + struct mk_string_line *entry; + struct mk_list *head; + struct mk_config_listener *listener; + + listener = mk_list_entry_first(&server->listeners, + struct mk_config_listener, + _head); + + CHEETAH_WRITE("Basic configuration"); + CHEETAH_WRITE("\n-------------------"); + mk_cheetah_listen_config(server); + CHEETAH_WRITE("\nWorkers : %i threads", mk_api->config->workers); + CHEETAH_WRITE("\nTimeout : %i seconds", mk_api->config->timeout); + CHEETAH_WRITE("\nPidFile : %s.%s", + mk_api->config->path_conf_pidfile, + listener->port); + CHEETAH_WRITE("\nUserDir : %s", + mk_api->config->conf_user_pub); + + + if (mk_list_is_empty(mk_api->config->index_files) == 0) { + CHEETAH_WRITE("\nIndexFile : No index files defined"); + } + else { + CHEETAH_WRITE("\nIndexFile : "); + mk_list_foreach(head, mk_api->config->index_files) { + entry = mk_list_entry(head, struct mk_string_line, _head); + CHEETAH_WRITE("%s ", entry->val); + } + + } + + CHEETAH_WRITE("\nHideVersion : "); + if (mk_api->config->hideversion == MK_TRUE) { + CHEETAH_WRITE("On"); + } + else { + CHEETAH_WRITE("Off"); + } + + CHEETAH_WRITE("\nResume : "); + if (mk_api->config->resume == MK_TRUE) { + CHEETAH_WRITE("On"); + } + else { + CHEETAH_WRITE("Off"); + } + + CHEETAH_WRITE("\nUser : %s", mk_api->config->user); + CHEETAH_WRITE("\n\nAdvanced configuration"); + CHEETAH_WRITE("\n----------------------"); + CHEETAH_WRITE("\nKeepAlive : "); + if (mk_api->config->keep_alive == MK_TRUE) { + CHEETAH_WRITE("On"); + } + else { + CHEETAH_WRITE("Off"); + } + CHEETAH_WRITE("\nMaxKeepAliveRequest : %i req/connection", + mk_api->config->max_keep_alive_request); + CHEETAH_WRITE("\nKeepAliveTimeout : %i seconds", mk_api->config->keep_alive_timeout); + CHEETAH_WRITE("\nMaxRequestSize : %i KB", + mk_api->config->max_request_size/1024); + CHEETAH_WRITE("\nSymLink : "); + if (mk_api->config->symlink == MK_TRUE) { + CHEETAH_WRITE("On"); + } + else { + CHEETAH_WRITE("Off"); + } + CHEETAH_WRITE("\n\n"); +} + +void mk_cheetah_cmd_status(struct mk_server *server) +{ + int nthreads = server->workers; + char tmp[64]; + + CHEETAH_WRITE("Monkey Version : %s\n", MK_VERSION_STR); + CHEETAH_WRITE("Configuration path : %s\n", server->path_conf_root); + + CHEETAH_WRITE("Cheetah! mode : "); + if (listen_mode == LISTEN_STDIN) { + CHEETAH_WRITE("STDIN\n"); + } + else { + CHEETAH_WRITE("SERVER @ %s\n", cheetah_server); + } + + CHEETAH_WRITE("Process ID : %i\n", getpid()); + CHEETAH_WRITE("Process User : "); + mk_cheetah_print_running_user(); + mk_cheetah_listen_config(server); + + CHEETAH_WRITE("\n"); + CHEETAH_WRITE("Worker Threads : %i (per configuration: %i)\n", + nthreads, server->workers); + + CHEETAH_WRITE("Memory Allocator : "); +#ifdef MALLOC_LIBC + CHEETAH_WRITE("libc, system default\n"); +#else + CHEETAH_WRITE("Jemalloc\n"); +#endif + + if (mk_api->kernel_features_print(tmp, sizeof(tmp), server) > 0) { + CHEETAH_WRITE("Kernel Features : %s\n", tmp); + } + + CHEETAH_WRITE("Events backend : %s\n", mk_api->ev_backend()); + CHEETAH_WRITE("\n"); +} diff --git a/src/fluent-bit/lib/monkey/plugins/cheetah/cmd.h b/src/fluent-bit/lib/monkey/plugins/cheetah/cmd.h new file mode 100644 index 000000000..617a88be1 --- /dev/null +++ b/src/fluent-bit/lib/monkey/plugins/cheetah/cmd.h @@ -0,0 +1,41 @@ +/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ + +/* Monkey HTTP Server + * ================== + * Copyright 2001-2017 Eduardo Silva <eduardo@monkey.io> + * + * 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. + */ + +time_t init_time; + +/* commands */ +int mk_cheetah_cmd(char *cmd, struct mk_server *server); + +void mk_cheetah_cmd_clear(); +void mk_cheetah_cmd_uptime(struct mk_server *server); + +/* Plugins commands */ +void mk_cheetah_cmd_plugins_print_stage(struct mk_list *list, const char *stage, + int stage_bw); +void mk_cheetah_cmd_plugins_print_core(struct mk_list *list); +void mk_cheetah_cmd_plugins_print_network(struct mk_list *list); +void mk_cheetah_cmd_plugins(struct mk_server *server); + +void mk_cheetah_cmd_vhosts(struct mk_server *server); +void mk_cheetah_cmd_workers(struct mk_server *server); + +int mk_cheetah_cmd_quit(); +void mk_cheetah_cmd_help(); +void mk_cheetah_cmd_config(struct mk_server *server); +void mk_cheetah_cmd_status(struct mk_server *server); diff --git a/src/fluent-bit/lib/monkey/plugins/cheetah/conf/CMakeLists.txt b/src/fluent-bit/lib/monkey/plugins/cheetah/conf/CMakeLists.txt new file mode 100644 index 000000000..b7224f351 --- /dev/null +++ b/src/fluent-bit/lib/monkey/plugins/cheetah/conf/CMakeLists.txt @@ -0,0 +1,9 @@ +set(conf_dir "${MK_PATH_CONF}/plugins/cheetah/") + +install(DIRECTORY DESTINATION ${conf_dir}) + +if(BUILD_LOCAL) + file(COPY cheetah.conf DESTINATION ${conf_dir}) +else() + install(FILES cheetah.conf DESTINATION ${conf_dir}) +endif() diff --git a/src/fluent-bit/lib/monkey/plugins/cheetah/conf/cheetah.conf b/src/fluent-bit/lib/monkey/plugins/cheetah/conf/cheetah.conf new file mode 100644 index 000000000..d9448926e --- /dev/null +++ b/src/fluent-bit/lib/monkey/plugins/cheetah/conf/cheetah.conf @@ -0,0 +1,17 @@ +# Cheetah! configuration +# ====================== +# Cheetah! is the Monkey Sheel and in this file you can define +# the basic setup and behavior expected. +# + +[CHEETAH] + # Listen : + # -------- + # Cheetah! listen for input commands as any shell, this can be done + # using the standard keyboard input or through a unix pipe where you + # need to connect using the Cheetah! client. + # + # The Listen directive allows you to define which input method to use. + # Valid values for Listen are STDIN or SERVER. + # + Listen SERVER diff --git a/src/fluent-bit/lib/monkey/plugins/cheetah/cutils.c b/src/fluent-bit/lib/monkey/plugins/cheetah/cutils.c new file mode 100644 index 000000000..3c0bd825f --- /dev/null +++ b/src/fluent-bit/lib/monkey/plugins/cheetah/cutils.c @@ -0,0 +1,117 @@ +/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ + +/* Monkey HTTP Server + * ================== + * Copyright 2001-2017 Eduardo Silva <eduardo@monkey.io> + * + * 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 <monkey/mk_api.h> + +#include <pwd.h> +#include <stdarg.h> +#include <string.h> + +#include "cheetah.h" +#include "cutils.h" + +void mk_cheetah_print_worker_memory_usage(pid_t pid) +{ + int s = 1024; + char *buf; + pid_t ppid; + FILE *f; + + ppid = getpid(); + buf = mk_api->mem_alloc(s); + sprintf(buf, MK_CHEETAH_PROC_TASK, ppid, pid); + + f = fopen(buf, "r"); + if (!f) { + CHEETAH_WRITE("Cannot get details\n"); + return; + } + + buf = fgets(buf, s, f); + fclose(f); + if (!buf) { + CHEETAH_WRITE("Cannot format details\n"); + return; + } + + CHEETAH_WRITE("\n"); + return; + +/* + int n, c; + int init = 0; + int last = 0; + char *value; + + while ((n = mk_api->str_search(buf + last, " ", MK_STR_SENSITIVE)) > 0) { + if (c == 23) { + value = mk_api->str_copy_substr(buf, init, last + n); + printf("%s\n", value); + mk_mem_free(buf); + mk_mem_free(value); + return; + } + init = last + n + 1; + last += n + 1; + c++; + }*/ +} + +void mk_cheetah_print_running_user() +{ + struct passwd pwd; + struct passwd *result; + char *buf; + long bufsize; + uid_t uid; + + bufsize = sysconf(_SC_GETPW_R_SIZE_MAX); + if (bufsize == -1) { + bufsize = 16384; + } + + buf = mk_api->mem_alloc_z(bufsize); + uid = getuid(); + getpwuid_r(uid, &pwd, buf, bufsize, &result); + + CHEETAH_WRITE("%s", pwd.pw_name); + mk_api->mem_free(buf); +} + +int mk_cheetah_write(const char *format, ...) +{ + int len = 0; + char buf[1024]; + va_list ap; + + va_start(ap, format); + len = vsprintf(buf, format, ap); + + if (listen_mode == LISTEN_STDIN) { + len = fprintf(cheetah_output, buf, NULL); + } + else if (listen_mode == LISTEN_SERVER) { + len = write(cheetah_socket, buf, len); + } + + memset(buf, '\0', sizeof(buf)); + va_end(ap); + + return len; +} diff --git a/src/fluent-bit/lib/monkey/plugins/cheetah/cutils.h b/src/fluent-bit/lib/monkey/plugins/cheetah/cutils.h new file mode 100644 index 000000000..c6948a55a --- /dev/null +++ b/src/fluent-bit/lib/monkey/plugins/cheetah/cutils.h @@ -0,0 +1,32 @@ +/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ + +/* Monkey HTTP Server + * ================== + * Copyright 2001-2017 Eduardo Silva <eduardo@monkey.io> + * + * 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. + */ + +#ifndef MK_CHEETAH_CUTILS_H +#define MK_CHEETAH_CUTILS_H + +#include <stdio.h> + +#define CHEETAH_WRITE(...) mk_cheetah_write(__VA_ARGS__); +#define CHEETAH_FLUSH() fflush(cheetah_output);fflush(cheetah_input); + +void mk_cheetah_print_worker_memory_usage(pid_t pid); +void mk_cheetah_print_running_user(); +int mk_cheetah_write(const char *format, ...); + +#endif diff --git a/src/fluent-bit/lib/monkey/plugins/cheetah/loop.c b/src/fluent-bit/lib/monkey/plugins/cheetah/loop.c new file mode 100644 index 000000000..e30f26f2c --- /dev/null +++ b/src/fluent-bit/lib/monkey/plugins/cheetah/loop.c @@ -0,0 +1,149 @@ +/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ + +/* Monkey HTTP Server + * ================== + * Copyright 2001-2017 Eduardo Silva <eduardo@monkey.io> + * + * 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 <monkey/mk_api.h> + +#include <sys/socket.h> +#include <sys/un.h> + +/* Monkey Plugin Interface */ +#include "cheetah.h" +#include "cutils.h" +#include "cmd.h" +#include "loop.h" + +void mk_cheetah_loop_stdin(struct mk_server *server) +{ + int len; + char cmd[200]; + char line[200]; + char *rcmd; + + mk_cheetah_welcome_msg(); + + while (1) { + CHEETAH_WRITE(MK_CHEETAH_PROMPT, ANSI_BOLD, ANSI_GREEN, ANSI_RESET); + + rcmd = fgets(line, sizeof(line), cheetah_input); + if (!rcmd) { + continue; + } + + len = strlen(line); + + if (len == 0){ + CHEETAH_WRITE("\n"); + mk_cheetah_cmd_quit(); + } + + strncpy(cmd, line, len - 1); + cmd[len - 1] = '\0'; + + mk_cheetah_cmd(cmd, server); + memset(line, '\0', sizeof(line)); + } +} + +void mk_cheetah_loop_server(struct mk_server *server) +{ + int n, ret; + int buf_len; + unsigned long len; + char buf[1024]; + char cmd[1024]; + int server_fd; + int remote_fd; + size_t address_length; + struct sockaddr_un address; + socklen_t socket_size = sizeof(struct sockaddr_in); + struct mk_config_listener *listener; + + /* Create listening socket */ + server_fd = socket(PF_UNIX, SOCK_STREAM, 0); + if (server_fd < 0) { + perror("socket() failed"); + exit(EXIT_FAILURE); + } + + listener = mk_list_entry_first(&mk_api->config->listeners, + struct mk_config_listener, + _head); + cheetah_server = NULL; + mk_api->str_build(&cheetah_server, &len, "/tmp/cheetah.%s", + listener->port); + unlink(cheetah_server); + + address.sun_family = AF_UNIX; + sprintf(address.sun_path, "%s", cheetah_server); + address_length = sizeof(address.sun_family) + len + 1; + + if (bind(server_fd, (struct sockaddr *) &address, address_length) != 0) { + perror("bind"); + mk_err("Cheetah: could not bind address %s", address.sun_path); + exit(EXIT_FAILURE); + } + + if (listen(server_fd, 5) != 0) { + perror("listen"); + exit(EXIT_FAILURE); + } + + while (1) { + /* Listen for incoming connections */ + remote_fd = accept(server_fd, (struct sockaddr *) &address, &socket_size); + cheetah_socket = remote_fd; + + buf_len = 0; + memset(buf, '\0', 1024); + + /* Send welcome message and prompt */ + mk_cheetah_welcome_msg(); + CHEETAH_WRITE(MK_CHEETAH_PROMPT, ANSI_BOLD, ANSI_GREEN, ANSI_RESET); + + while (1) { + /* Read incoming data */ + n = read(remote_fd, buf+buf_len, 1024 - buf_len); + if (n <= 0) { + break; + } + else { + buf_len += n; + if (buf[buf_len-1] == '\n') { + /* Filter command */ + strncpy(cmd, buf, buf_len - 1); + cmd[buf_len - 1] = '\0'; + + /* Run command */ + ret = mk_cheetah_cmd(cmd, server); + + if (ret == -1) { + break; + } + + /* Write prompt */ + CHEETAH_WRITE(MK_CHEETAH_PROMPT, ANSI_BOLD, ANSI_GREEN, ANSI_RESET); + buf_len = 0; + memset(buf, '\0', 1024); + } + } + } + + close(remote_fd); + } +} diff --git a/src/fluent-bit/lib/monkey/plugins/cheetah/loop.h b/src/fluent-bit/lib/monkey/plugins/cheetah/loop.h new file mode 100644 index 000000000..232a00b61 --- /dev/null +++ b/src/fluent-bit/lib/monkey/plugins/cheetah/loop.h @@ -0,0 +1,26 @@ +/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ + +/* Monkey HTTP Server + * ================== + * Copyright 2001-2017 Eduardo Silva <eduardo@monkey.io> + * + * 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. + */ + +#ifndef MK_CHEETAH_LOOP_H +#define MK_CHEETAH_LOOP_H + +void mk_cheetah_loop_stdin(); +void mk_cheetah_loop_server(); + +#endif diff --git a/src/fluent-bit/lib/monkey/plugins/dirlisting/ABOUT b/src/fluent-bit/lib/monkey/plugins/dirlisting/ABOUT new file mode 100644 index 000000000..2d71cb77e --- /dev/null +++ b/src/fluent-bit/lib/monkey/plugins/dirlisting/ABOUT @@ -0,0 +1,4 @@ +Directory Listing Plugin +======================== +When a directory is requested, this plugin will show +an HTML list of the available content to the client. diff --git a/src/fluent-bit/lib/monkey/plugins/dirlisting/CMakeLists.txt b/src/fluent-bit/lib/monkey/plugins/dirlisting/CMakeLists.txt new file mode 100644 index 000000000..7432e1091 --- /dev/null +++ b/src/fluent-bit/lib/monkey/plugins/dirlisting/CMakeLists.txt @@ -0,0 +1,7 @@ +set(src + dirlisting.c + ) + +MONKEY_PLUGIN(dirlisting "${src}") + +add_subdirectory(conf)
\ No newline at end of file diff --git a/src/fluent-bit/lib/monkey/plugins/dirlisting/OPTIONAL b/src/fluent-bit/lib/monkey/plugins/dirlisting/OPTIONAL new file mode 100644 index 000000000..e69de29bb --- /dev/null +++ b/src/fluent-bit/lib/monkey/plugins/dirlisting/OPTIONAL diff --git a/src/fluent-bit/lib/monkey/plugins/dirlisting/conf/CMakeLists.txt b/src/fluent-bit/lib/monkey/plugins/dirlisting/conf/CMakeLists.txt new file mode 100644 index 000000000..4faf80a4d --- /dev/null +++ b/src/fluent-bit/lib/monkey/plugins/dirlisting/conf/CMakeLists.txt @@ -0,0 +1,11 @@ +set(conf_dir "${MK_PATH_CONF}/plugins/dirlisting/") + +install(DIRECTORY DESTINATION ${conf_dir}) + +if(BUILD_LOCAL) + file(COPY dirhtml.conf DESTINATION ${conf_dir}) + file(COPY themes DESTINATION ${conf_dir}) +else() + install(FILES dirhtml.conf DESTINATION ${conf_dir}) + install(DIRECTORY themes DESTINATION ${conf_dir}) +endif()
\ No newline at end of file diff --git a/src/fluent-bit/lib/monkey/plugins/dirlisting/conf/dirhtml.conf b/src/fluent-bit/lib/monkey/plugins/dirlisting/conf/dirhtml.conf new file mode 100644 index 000000000..c73188df2 --- /dev/null +++ b/src/fluent-bit/lib/monkey/plugins/dirlisting/conf/dirhtml.conf @@ -0,0 +1,2 @@ +[DIRLISTING] + Theme bootstrap diff --git a/src/fluent-bit/lib/monkey/plugins/dirlisting/conf/themes/bootstrap/entry.theme b/src/fluent-bit/lib/monkey/plugins/dirlisting/conf/themes/bootstrap/entry.theme new file mode 100644 index 000000000..8c1ef026d --- /dev/null +++ b/src/fluent-bit/lib/monkey/plugins/dirlisting/conf/themes/bootstrap/entry.theme @@ -0,0 +1,5 @@ +<tr> + <td><a title='%_target_title_%' href='%_target_url_%'>%_target_name_%</a></td> + <td>%_target_time_%</td> + <td>%_target_size_%</td> +</tr> diff --git a/src/fluent-bit/lib/monkey/plugins/dirlisting/conf/themes/bootstrap/footer.theme b/src/fluent-bit/lib/monkey/plugins/dirlisting/conf/themes/bootstrap/footer.theme new file mode 100644 index 000000000..88290ac8b --- /dev/null +++ b/src/fluent-bit/lib/monkey/plugins/dirlisting/conf/themes/bootstrap/footer.theme @@ -0,0 +1,8 @@ + <tbody> +</table> +</div> +</div> +<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.1/jquery.min.js"></script> +<script src="//maxcdn.bootstrapcdn.com/bootstrap/3.2.0/js/bootstrap.min.js"></script> +</body> +</HTML> diff --git a/src/fluent-bit/lib/monkey/plugins/dirlisting/conf/themes/bootstrap/header.theme b/src/fluent-bit/lib/monkey/plugins/dirlisting/conf/themes/bootstrap/header.theme new file mode 100644 index 000000000..f7f6cfcfd --- /dev/null +++ b/src/fluent-bit/lib/monkey/plugins/dirlisting/conf/themes/bootstrap/header.theme @@ -0,0 +1,27 @@ +<html> + <head> + <meta http-equiv="content-type" content="text/html; charset=UTF-8"> + <title>Index of %_html_title_%</title> + <link rel="stylesheet" href="//maxcdn.bootstrapcdn.com/bootstrap/3.2.0/css/bootstrap.min.css"> + <link rel="stylesheet" href="//maxcdn.bootstrapcdn.com/bootstrap/3.2.0/css/bootstrap-theme.min.css"> + <link href="http://maxcdn.bootstrapcdn.com/font-awesome/4.1.0/css/font-awesome.min.css" rel="stylesheet"> + </head> + <body> + <div class="container"> + <h1>Index of %_html_title_%</h1> + <div class="table-responsive"> + <table class="table table-hover table-bordered"> + <thead> + <tr> + <th> + Name + </th> + <th> + Last time modified + </th> + <th> + Size + </th> + </tr> + </thead> + <tbody> diff --git a/src/fluent-bit/lib/monkey/plugins/dirlisting/conf/themes/guineo/entry.theme b/src/fluent-bit/lib/monkey/plugins/dirlisting/conf/themes/guineo/entry.theme new file mode 100644 index 000000000..af00e250c --- /dev/null +++ b/src/fluent-bit/lib/monkey/plugins/dirlisting/conf/themes/guineo/entry.theme @@ -0,0 +1,14 @@ +<TR> +<TD class="row"> + <A title='%_target_title_%' href='%_target_url_%'> + %_target_name_% + </A> +</TD> +<TD class="row"> + %_target_time_% +</TD> +<TD class="row"> + %_target_size_% + %_theme_path_% +</TD> +</TR> diff --git a/src/fluent-bit/lib/monkey/plugins/dirlisting/conf/themes/guineo/footer.theme b/src/fluent-bit/lib/monkey/plugins/dirlisting/conf/themes/guineo/footer.theme new file mode 100644 index 000000000..c785ba97f --- /dev/null +++ b/src/fluent-bit/lib/monkey/plugins/dirlisting/conf/themes/guineo/footer.theme @@ -0,0 +1,7 @@ +</TABLE> + +</TD> +</TR> +</TABLE> +</BODY></HTML> + diff --git a/src/fluent-bit/lib/monkey/plugins/dirlisting/conf/themes/guineo/header.theme b/src/fluent-bit/lib/monkey/plugins/dirlisting/conf/themes/guineo/header.theme new file mode 100644 index 000000000..6a11a458f --- /dev/null +++ b/src/fluent-bit/lib/monkey/plugins/dirlisting/conf/themes/guineo/header.theme @@ -0,0 +1,58 @@ +<HTML> + <HEAD> + <META http-equiv="content-type" content="text/html; charset=UTF-8"> + <STYLE type="text/css"> + H1.header { + text-align: center; + color: #4a77a0; + font-size: 12pt; + font-weight: bold; + } + .row { + color: #6c95bc; + font-size: 10pt; + background-color: #ffffff; + padding-left: 0.4em; + } + .title {color: #617691; font-size: 10pt; font-weight: bold;} + + a:link { + color: #475f7e; + } + a:visited { + color: #ac7a2a; + } + a:hover { + color: #475f7e; + background-color: #dbdbdb; + text-decoration: none; + } + a:active { + color: #333333; + } + </STYLE> + + <TITLE>Index of %_html_title_%</TITLE> + </HEAD> +<BODY> + +<CENTER> +<IMG src="/imgs/monkey_logo.png"> +<BR> +<h1 class="header">Index of %_html_title_%</h1> +<TABLE cellpadding="0" cellspacing="0" border="0" bgcolor="#000000" width="50%"> +<TR> +<TD> + +<TABLE cellpadding="2" cellspacing="1" border="0" bgcolor="#e7e7e7" width="100%"> +<TR bgcolor="#ececec"> + <TD class="title"> + Name + </TD> + <TD class="title"> + Last time modified + </TD> + <TD class="title"> + Size + </TD> +</TR> diff --git a/src/fluent-bit/lib/monkey/plugins/dirlisting/dirlisting.c b/src/fluent-bit/lib/monkey/plugins/dirlisting/dirlisting.c new file mode 100644 index 000000000..1ffd3fac4 --- /dev/null +++ b/src/fluent-bit/lib/monkey/plugins/dirlisting/dirlisting.c @@ -0,0 +1,949 @@ +/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ + +/* Monkey HTTP Server + * ================== + * Copyright 2001-2017 Eduardo Silva <eduardo@monkey.io> + * + * 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. + */ + +/* + * Some history about this plugin + * ------------------------------ + * 2008 - Rewrite module, suport dynamic themes by Eduardo Silva + * 2008 - Felipe Astroza (max) provided the mk_dirhtml_human_readable_size_func() + * 2007 - Add struct client_request support by Eduardo + * 2002 - Original version written by Daniel R. Ome + */ + +#include <monkey/mk_api.h> +#include <monkey/mk_stream.h> +#include "dirlisting.h" + +#include <time.h> +#include <dirent.h> +#include <sys/stat.h> + +const mk_ptr_t mk_dirhtml_default_mime = mk_ptr_init(MK_DIRHTML_DEFAULT_MIME); +const mk_ptr_t mk_dir_iov_dash = mk_ptr_init("-"); +const mk_ptr_t mk_dir_iov_none = mk_ptr_init(""); +const mk_ptr_t mk_dir_iov_slash = mk_ptr_init("/"); + +void mk_dirhtml_cb_body_rows(struct mk_stream_input *in); + +/* Function wrote by Max (Felipe Astroza), thanks! */ +static char *mk_dirhtml_human_readable_size(char *buf, size_t size, int len) +{ + unsigned long u = 1024, i; + static const char *__units[] = { + "b", "K", "M", "G", + "T", "P", "E", "Z", "Y", NULL + }; + + for (i = 0; __units[i] != NULL; i++) { + if ((size / u) == 0) { + break; + } + u *= 1024; + } + if (!i) { + snprintf(buf, size, "%lu%s", (long unsigned int) len, __units[0]); + } + else { + float fsize = (float) ((double) len / (u / 1024)); + snprintf(buf, size, "%.1f%s", fsize, __units[i]); + } + + return buf; +} + +static struct mk_f_list *mk_dirhtml_create_element(char *file, + unsigned char type, + char *full_path, + unsigned long *list_len) +{ + int n; + struct tm *st_time; + struct mk_f_list *entry; + + entry = mk_api->mem_alloc_z(sizeof(struct mk_f_list)); + + if (mk_api->file_get_info(full_path, &entry->info, MK_FILE_READ) != 0) { + mk_api->mem_free(entry); + return NULL; + } + + strcpy(entry->name, file); + entry->type = type; + + st_time = localtime((time_t *) & entry->info.last_modification); + n = strftime(entry->ft_modif, MK_DIRHTML_FMOD_LEN, "%d-%b-%G %H:%M", st_time); + if (n == 0) { + mk_mem_free(entry); + return NULL; + } + + if (type != DT_DIR) { + mk_dirhtml_human_readable_size(entry->size, + sizeof(entry->size), + entry->info.size); + } + else { + entry->size[0] = '-'; + entry->size[1] = '\0'; + } + + *list_len = *list_len + 1; + + return entry; +} + +static struct mk_list *mk_dirhtml_create_list(DIR * dir, char *path, + unsigned long *list_len) +{ + char full_path[PATH_MAX]; + struct mk_list *list; + struct dirent *ent; + struct mk_f_list *entry = 0; + + list = mk_api->mem_alloc(sizeof(struct mk_list)); + mk_list_init(list); + + while ((ent = readdir(dir)) != NULL) { + if ((ent->d_name[0] == '.') && (strcmp(ent->d_name, "..") != 0)) + continue; + + /* Look just for files and dirs */ + if (ent->d_type != DT_REG && ent->d_type != DT_DIR + && ent->d_type != DT_LNK && ent->d_type != DT_UNKNOWN) { + continue; + } + + snprintf(full_path, PATH_MAX, "%s%s", path, ent->d_name); + entry = mk_dirhtml_create_element(ent->d_name, + ent->d_type, full_path, list_len); + if (!entry) { + continue; + } + + mk_list_add(&entry->_head, list); + } + + return list; +} + +/* Read dirhtml config and themes */ +int mk_dirhtml_conf(char *confdir) +{ + int ret = 0; + unsigned long len; + char *conf_file = NULL; + + mk_api->str_build(&conf_file, &len, "%s", confdir); + + /* Read configuration */ + ret = mk_dirhtml_read_config(conf_file); + if (ret < 0) { + mk_mem_free(conf_file); + return -1; + } + + /* + * This function will load the default theme setted in dirhtml_conf struct + */ + mk_mem_free(conf_file); + return mk_dirhtml_theme_load(); +} + +/* + * Read the main configuration file for dirhtml: dirhtml.conf, + * it will alloc the dirhtml_conf struct +*/ +int mk_dirhtml_read_config(char *path) +{ + unsigned long len; + char *default_file = NULL; + struct mk_rconf *conf; + struct mk_rconf_section *section; + struct file_info finfo; + + mk_api->str_build(&default_file, &len, "%sdirhtml.conf", path); + conf = mk_api->config_open(default_file); + if (!conf) { + return -1; + } + + section = mk_api->config_section_get(conf, "DIRLISTING"); + if (!section) { + mk_err_ex(mk_api, "Could not find DIRLISTING tag in configuration file"); + exit(EXIT_FAILURE); + } + + /* alloc dirhtml config struct */ + dirhtml_conf = mk_api->mem_alloc(sizeof(struct dirhtml_config)); + dirhtml_conf->theme = mk_api->config_section_get_key(section, "Theme", + MK_RCONF_STR); + dirhtml_conf->theme_path = NULL; + + mk_api->str_build(&dirhtml_conf->theme_path, &len, + "%sthemes/%s/", path, dirhtml_conf->theme); + mk_api->mem_free(default_file); + + if (mk_api->file_get_info(dirhtml_conf->theme_path, + &finfo, MK_FILE_READ) != 0) { + mk_warn_ex(mk_api, "Dirlisting: cannot load theme from '%s'", dirhtml_conf->theme_path); + mk_warn_ex(mk_api, "Dirlisting: unloading plugin"); + return -1; + } + + mk_api->config_free(conf); + return 0; +} + +int mk_dirhtml_theme_load() +{ + /* Data */ + char *header, *entry, *footer; + + /* Load theme files */ + header = mk_dirhtml_load_file(MK_DIRHTML_FILE_HEADER); + entry = mk_dirhtml_load_file(MK_DIRHTML_FILE_ENTRY); + footer = mk_dirhtml_load_file(MK_DIRHTML_FILE_FOOTER); + + if (!header || !entry || !footer) { + mk_api->mem_free(header); + mk_api->mem_free(entry); + mk_api->mem_free(footer); + return -1; + } + + /* Parse themes */ + mk_dirhtml_tpl_header = mk_dirhtml_template_create(header); + mk_dirhtml_tpl_entry = mk_dirhtml_template_create(entry); + mk_dirhtml_tpl_footer = mk_dirhtml_template_create(footer); + +#ifdef DEBUG_THEME + /* Debug data */ + mk_dirhtml_theme_debug(&mk_dirhtml_tpl_header); + mk_dirhtml_theme_debug(&mk_dirhtml_tpl_entry); + mk_dirhtml_theme_debug(&mk_dirhtml_tpl_footer); + +#endif + mk_api->mem_free(header); + mk_api->mem_free(entry); + mk_api->mem_free(footer); + + return 0; +} + +#ifdef DEBUG_THEME +int mk_dirhtml_theme_debug(struct dirhtml_template **st_tpl) +{ + int i = 0; + struct dirhtml_template *aux; + + aux = *st_tpl; + + printf("\n** DEBUG_THEME **"); + fflush(stdout); + + while (aux) { + printf("\n%i) len=%i, tag_id=%i", i, aux->len, aux->tag_id); + if (aux->tag_id >= 0) { + printf(" (%s) ", aux->tags[aux->tag_id]); + } + fflush(stdout); + aux = aux->next; + i++; + } + return 0; +} +#endif + +/* Search which tag exists first in content : + * ex: %_html_title_% + */ +static int mk_dirhtml_theme_match_tag(char *content, char *tpl[]) +{ + int i, len, match; + + for (i = 0; tpl[i]; i++) { + len = strlen(tpl[i]); + match = (int) mk_api->str_search_n(content, tpl[i], MK_STR_INSENSITIVE, len); + if (match >= 0) { + return i; + } + } + + return -1; +} + +struct dirhtml_template *mk_dirhtml_template_create(char *content) +{ + int i = 0, cont_len; + int pos, last = 0; /* 0=search init, 1=search end */ + int n_tags = 0, tpl_idx = 0; + + char *_buf; + int _len; + + /* Global keys */ + char **_tpl = 0; + + /* Template to return */ + struct dirhtml_template *st_tpl = 0; + + cont_len = strlen(content); + if (cont_len <= 0) { + return NULL; + } + + /* Parsing content */ + while (i < cont_len) { + pos = (int) mk_api->str_search(content + i, + MK_DIRHTML_TAG_INIT, MK_STR_INSENSITIVE); + + if (pos < 0) { + break; + } + + /* Checking global tag, if it's not found, proceed with + * 'entry tags' + */ + _tpl = (char **) _tags_global; + tpl_idx = mk_dirhtml_theme_match_tag(content + i + pos, _tpl); + + /* if global template do not match, use the entry tags */ + if (tpl_idx < 0) { + _tpl = (char **) _tags_entry; + tpl_idx = mk_dirhtml_theme_match_tag(content + i + pos, _tpl); + } + + /* if tag found is known, we add them to our list */ + if (tpl_idx >= 0) { + + _buf = mk_api->str_copy_substr(content, i, i + pos); + _len = strlen(_buf); + + /* Dummy if/else to create or pass a created st_tpl */ + if (!st_tpl) { + st_tpl = mk_dirhtml_template_list_add(NULL, + _buf, _len, _tpl, -1); + } + else { + mk_dirhtml_template_list_add(&st_tpl, _buf, _len, _tpl, -1); + } + i += (pos + strlen(_tpl[tpl_idx])); + + /* This means that a value need to be replaced */ + mk_dirhtml_template_list_add(&st_tpl, NULL, -1, _tpl, tpl_idx); + n_tags++; + } + else { + i++; + } + } + + if (last < cont_len) { + _buf = mk_api->str_copy_substr(content, i, cont_len); + _len = strlen(_buf); + + if (n_tags <= 0) { + st_tpl = mk_dirhtml_template_list_add(NULL, _buf, _len, _tpl, -1); + } + else { + mk_dirhtml_template_list_add(&st_tpl, _buf, _len, _tpl, -1); + } + } + + return st_tpl; +} + +struct dirhtml_template *mk_dirhtml_template_list_add(struct dirhtml_template **header, + char *buf, int len, char **tpl, + int tag_id) +{ + struct dirhtml_template *node, *aux; + + node = mk_api->mem_alloc_z(sizeof(struct dirhtml_template)); + if (!node) { + return NULL; + } + + node->buf = buf; + node->len = len; + node->tag_id = tag_id; + node->tags = tpl; + node->next = NULL; + + if (!header || !(*header)) { + return (struct dirhtml_template *) node; + } + + aux = *header; + while ((*aux).next != NULL) { + aux = (*aux).next; + } + + (*aux).next = node; + return (struct dirhtml_template *) node; +} + +static int mk_dirhtml_template_len(struct dirhtml_template *tpl) +{ + int len = 0; + struct dirhtml_template *aux; + + aux = tpl; + while (aux) { + len++; + aux = aux->next; + } + + return len; +} + +static struct mk_iov *mk_dirhtml_theme_compose(struct dirhtml_template *template, + struct mk_list *list) +{ + /* + * template = struct { char buf ; int len, int tag } + * values = struct {int tag, char *value, struct *next} + */ + struct mk_iov *iov; + struct dirhtml_template *tpl = template; + struct dirhtml_value *val; + struct mk_list *head; + + int tpl_len; + + tpl_len = mk_dirhtml_template_len(template); + + /* we duplicate the lenght in case we get separators */ + iov = mk_api->iov_create(1 + tpl_len * 2, 1); + tpl = template; + + while (tpl) { + /* check for dynamic value */ + if (!tpl->buf && tpl->tag_id >= 0) { + mk_list_foreach(head, list) { + val = mk_list_entry(head, struct dirhtml_value, _head); + if (val->tags == tpl->tags && val->tag_id == tpl->tag_id) { + mk_api->iov_add(iov, + val->value, val->len, + MK_FALSE); + mk_api->iov_add(iov, + val->sep.data, val->sep.len, + MK_FALSE); + break; + } + } + } + /* static */ + else { + mk_api->iov_add(iov, + tpl->buf, tpl->len, + MK_FALSE); + } + tpl = tpl->next; + } + + return iov; +} + +struct dirhtml_value *mk_dirhtml_tag_assign(struct mk_list *list, + int tag_id, mk_ptr_t sep, + char *value, char **tags) +{ + struct dirhtml_value *aux = NULL; + + aux = mk_api->mem_alloc(sizeof(struct dirhtml_value)); + if (!aux) { + return NULL; + } + + aux->tag_id = tag_id; + aux->value = value; + aux->sep = sep; + aux->tags = tags; + + if (value) { + aux->len = strlen(value); + } + else { + aux->len = -1; + } + + mk_list_add(&aux->_head, list); + return (struct dirhtml_value *) aux; +} + +static void mk_dirhtml_tag_free_list(struct mk_list *list) +{ + struct mk_list *head; + struct mk_list *tmp; + struct dirhtml_value *target; + + mk_list_foreach_safe(head, tmp, list) { + target = mk_list_entry(head, struct dirhtml_value, _head); + mk_list_del(&target->_head); + mk_api->mem_free(target); + } +} + +char *mk_dirhtml_load_file(char *filename) +{ + char *tmp = 0, *data = 0; + unsigned long len; + + mk_api->str_build(&tmp, &len, "%s%s", dirhtml_conf->theme_path, filename); + + if (!tmp) { + return NULL; + } + + data = mk_api->file_to_buffer(tmp); + mk_api->mem_free(tmp); + + if (!data) { + return NULL; + } + + return (char *) data; +} + +static int mk_dirhtml_entry_cmp(const void *a, const void *b) +{ + struct mk_f_list *const *f_a = a; + struct mk_f_list *const *f_b = b; + + return strcasecmp((*f_a)->name, (*f_b)->name); +} + +static void mk_dirhtml_free_list(struct mk_dirhtml_request *request) +{ + struct mk_list *tmp; + struct mk_list *head; + struct mk_f_list *entry; + + mk_list_foreach_safe(head, tmp, request->file_list) { + entry = mk_list_entry(head, struct mk_f_list, _head); + mk_list_del(&entry->_head); + mk_api->mem_free(entry); + } + + mk_api->mem_free(request->file_list); + mk_api->mem_free(request->toc); +} + +static inline struct mk_iov *enqueue_row(int i, struct mk_dirhtml_request *request) +{ + mk_ptr_t sep; + struct mk_list list; + struct mk_iov *iov_entry; + + /* %_target_title_% */ + if (request->toc[i]->type == DT_DIR) { + sep = mk_dir_iov_slash; + } + else { + sep = mk_dir_iov_none; + } + + mk_list_init(&list); + + /* target title */ + mk_dirhtml_tag_assign(&list, 0, sep, + request->toc[i]->name, + (char **) _tags_entry); + + /* target url */ + mk_dirhtml_tag_assign(&list, 1, sep, + request->toc[i]->name, (char **) _tags_entry); + + /* target name */ + mk_dirhtml_tag_assign(&list, 2, sep, + request->toc[i]->name, (char **) _tags_entry); + + /* target modification time */ + mk_dirhtml_tag_assign(&list, 3, mk_dir_iov_none, + request->toc[i]->ft_modif, (char **) _tags_entry); + + /* target size */ + mk_dirhtml_tag_assign(&list, 4, mk_dir_iov_none, + request->toc[i]->size, (char **) _tags_entry); + + iov_entry = mk_dirhtml_theme_compose(mk_dirhtml_tpl_entry, &list); + + /* free entry list */ + mk_dirhtml_tag_free_list(&list); + return iov_entry; +} + +/* Release all resources for a given Request context */ +void mk_dirhtml_cleanup(struct mk_dirhtml_request *req) +{ + PLUGIN_TRACE("release resources"); + + if (req->iov_header) { + mk_api->iov_free(req->iov_header); + req->iov_header = NULL; + } + if (req->iov_entry) { + mk_api->iov_free(req->iov_entry); + req->iov_entry = NULL; + } + if (req->iov_footer) { + mk_api->iov_free(req->iov_footer); + req->iov_footer = NULL; + } + mk_dirhtml_free_list(req); + closedir(req->dir); + + req->sr->handler_data = NULL; + mk_api->mem_free(req); + req = NULL; +} + +void mk_dirhtml_cb_complete(struct mk_stream_input *in) +{ + struct mk_stream *stream; + struct mk_dirhtml_request *req; + + stream = in->stream; + req = stream->context; + if (req) { + mk_dirhtml_cleanup(req); + } +} + +void mk_dirhtml_cb_error(struct mk_stream *stream, int status) +{ +#ifndef TRACE + (void) status; +#endif + struct mk_dirhtml_request *req = stream->context; + + PLUGIN_TRACE("exception: %i", status); + + if (req) { + mk_dirhtml_cleanup(req); + } +} + +void mk_dirhtml_cb_chunk_body_rows(struct mk_stream_input *in, long bytes) +{ + (void) bytes; + + mk_dirhtml_cb_body_rows(in); +} + +void mk_dirhtml_cb_body_rows(struct mk_stream_input *in) +{ + int len; + char tmp[16]; + struct mk_stream *stream = in->stream; + struct mk_dirhtml_request *req = stream->context; + void (*cb_ok)(struct mk_stream_input *) = NULL; + + if (req->iov_entry) { + mk_api->iov_free(req->iov_entry); + req->iov_entry = NULL; + } + + if (req->toc_idx >= req->toc_len) { + if (req->chunked) { + len = snprintf(tmp, sizeof(tmp), "%x\r\n", + (int) req->iov_footer->total_len); + mk_stream_in_raw(req->stream, + NULL, + tmp, len, + NULL, NULL); + cb_ok = NULL; + } + else { + cb_ok = mk_dirhtml_cb_complete; + } + + mk_stream_in_iov(req->stream, + NULL, + req->iov_footer, + NULL, NULL); + if (req->chunked) { + mk_stream_in_raw(req->stream, + NULL, + "\r\n0\r\n\r\n", 7, + NULL, mk_dirhtml_cb_complete); + } + + return; + } + + req->iov_entry = enqueue_row(req->toc_idx, req); + if (req->chunked) { + len = snprintf(tmp, sizeof(tmp), "%x\r\n", + (int) req->iov_entry->total_len); + mk_stream_in_raw(req->stream, + NULL, + tmp, len, + NULL, NULL); + cb_ok = NULL; + } + else { + cb_ok = mk_dirhtml_cb_body_rows; + } + + mk_stream_in_iov(req->stream, + NULL, + req->iov_entry, + NULL, cb_ok); + + if (req->chunked) { + mk_stream_in_raw(req->stream, + NULL, + "\r\n", 2, + mk_dirhtml_cb_chunk_body_rows, NULL); + } + req->toc_idx++; +} + +/* + * The HTTP Headers were sent, now start registering the + * rows for each directory entry. + */ +void cb_header_finish(struct mk_stream_input *in) +{ + struct mk_stream *stream = in->stream; + struct mk_dirhtml_request *req; + + req = stream->context; + if (req->iov_header) { + mk_api->iov_free(req->iov_header); + req->iov_header = NULL; + } + mk_dirhtml_cb_body_rows(in); +} + +static int mk_dirhtml_init(struct mk_plugin *plugin, + struct mk_http_session *cs, struct mk_http_request *sr) +{ + DIR *dir; + int len; + char tmp[16]; + unsigned int i = 0; + struct mk_list *head; + struct mk_list list; + struct mk_f_list *entry; + struct mk_dirhtml_request *request; + struct mk_stream *stream; + + if (!(dir = opendir(sr->real_path.data))) { + return -1; + } + + /* Create the main context */ + request = mk_api->mem_alloc(sizeof(struct mk_dirhtml_request)); + if (!request) { + closedir(dir); + return -1; + } + + stream = mk_stream_set(NULL, cs->channel, request, + NULL, NULL, mk_dirhtml_cb_error); + if (!stream) { + closedir(dir); + free(request); + return -1; + } + + request->stream = stream; + request->state = MK_DIRHTML_STATE_HTTP_HEADER; + request->dir = dir; + request->toc_idx = 0; + request->cs = cs; + request->sr = sr; + request->toc_len = 0; + request->chunked = MK_FALSE; + request->iov_header = NULL; + request->iov_entry = NULL; + request->iov_footer = NULL; + + sr->handler_data = request; + + request->file_list = mk_dirhtml_create_list(dir, sr->real_path.data, + &request->toc_len); + + /* Building headers */ + mk_api->header_set_http_status(sr, MK_HTTP_OK); + sr->headers.cgi = SH_CGI; + sr->headers.breakline = MK_HEADER_BREAKLINE; + sr->headers.content_type = mk_dirhtml_default_mime; + sr->headers.content_length = -1; + + if (sr->protocol >= MK_HTTP_PROTOCOL_11) { + sr->headers.transfer_encoding = MK_HEADER_TE_TYPE_CHUNKED; + request->chunked = MK_TRUE; + } + + /* + * Creating response template + */ + + mk_list_init(&list); + + /* Set %_html_title_% */ + mk_dirhtml_tag_assign(&list, 0, mk_dir_iov_none, + sr->uri_processed.data, + (char **) _tags_global); + + /* Set %_theme_path_% */ + mk_dirhtml_tag_assign(&list, 1, mk_dir_iov_none, + dirhtml_conf->theme_path, (char **) _tags_global); + + /* HTML Header */ + request->iov_header = mk_dirhtml_theme_compose(mk_dirhtml_tpl_header, + &list); + + /* HTML Footer */ + request->iov_footer = mk_dirhtml_theme_compose(mk_dirhtml_tpl_footer, + &list); + mk_dirhtml_tag_free_list(&list); + + /* Creating table of contents and sorting */ + request->toc = mk_api->mem_alloc(sizeof(struct mk_f_list *) * request->toc_len); + + i = 0; + mk_list_foreach(head, request->file_list) { + entry = mk_list_entry(head, struct mk_f_list, _head); + request->toc[i] = entry; + i++; + } + + qsort(request->toc, + request->toc_len, + sizeof(*request->toc), + mk_dirhtml_entry_cmp); + + /* Prepare HTTP response headers */ + mk_api->header_prepare(plugin, cs, sr); + + if (request->chunked) { + len = snprintf(tmp, sizeof(tmp), "%x\r\n", + (int) request->iov_header->total_len); + mk_stream_in_raw(request->stream, + NULL, + tmp, len, + NULL, mk_dirhtml_cb_complete); + } + + mk_stream_in_iov(request->stream, + NULL, + request->iov_header, + NULL, cb_header_finish); + + if (request->chunked) { + mk_stream_in_raw(request->stream, + NULL, + "\r\n", 2, + NULL, NULL); + } + return 0; +} + +int mk_dirlisting_plugin_init(struct mk_plugin *plugin, char *confdir) +{ + mk_api = plugin->api; + + return mk_dirhtml_conf(confdir); +} + +int mk_dirlisting_plugin_exit(struct mk_plugin *plugin) +{ + (void) plugin; + + mk_api->mem_free(dirhtml_conf->theme); + mk_api->mem_free(dirhtml_conf->theme_path); + mk_api->mem_free(dirhtml_conf); + + return 0; +} + +int mk_dirlisting_stage30(struct mk_plugin *plugin, + struct mk_http_session *cs, + struct mk_http_request *sr, + int n_param, + struct mk_list *params) +{ + (void) plugin; + (void) n_param; + (void) params; + + /* validate file_info */ + if (sr->file_info.size == 0) { + return MK_PLUGIN_RET_NOT_ME; + } + + /* This plugin just handle directories */ + if (sr->file_info.is_directory == MK_FALSE) { + return MK_PLUGIN_RET_NOT_ME; + } + + PLUGIN_TRACE("Dirlisting attending socket %i", cs->socket); + if (mk_dirhtml_init(plugin, cs, sr)) { + /* + * If we failed here, we cannot return RET_END - that causes a mk_bug. + * dirhtml_init only fails if opendir fails. Usually we're at full + * capacity then and can't open new files. + */ + return MK_PLUGIN_RET_CLOSE_CONX; + } + + return MK_PLUGIN_RET_END; +} + +int mk_dirlisting_stage30_hangup(struct mk_plugin *plugin, + struct mk_http_session *cs, + struct mk_http_request *sr) +{ + (void) cs; + (void) plugin; + + if (sr->handler_data) { + mk_dirhtml_cleanup(sr->handler_data); + } + return 0; +} + +struct mk_plugin_stage mk_plugin_stage_dirlisting = { + .stage30 = &mk_dirlisting_stage30, + .stage30_hangup = &mk_dirlisting_stage30_hangup +}; + +struct mk_plugin mk_plugin_dirlisting = { + /* Identification */ + .shortname = "dirlisting", + .name = "Directory Listing", + .version = MK_VERSION_STR, + .hooks = MK_PLUGIN_STAGE, + + /* Init / Exit */ + .init_plugin = mk_dirlisting_plugin_init, + .exit_plugin = mk_dirlisting_plugin_exit, + + /* Init Levels */ + .master_init = NULL, + .worker_init = NULL, + + /* Type */ + .stage = &mk_plugin_stage_dirlisting +}; diff --git a/src/fluent-bit/lib/monkey/plugins/dirlisting/dirlisting.h b/src/fluent-bit/lib/monkey/plugins/dirlisting/dirlisting.h new file mode 100644 index 000000000..678a4887b --- /dev/null +++ b/src/fluent-bit/lib/monkey/plugins/dirlisting/dirlisting.h @@ -0,0 +1,181 @@ +/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ + +/* Monkey HTTP Server + * ================== + * Copyright 2001-2017 Eduardo Silva <eduardo@monkey.io> + * + * 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. + */ + +/* dir_html.c */ +#ifndef MK_DIRHTML_H +#define MK_DIRHTML_H + +#include <dirent.h> +#include <limits.h> + +#define MK_DIRHTML_URL "/_mktheme" +#define MK_DIRHTML_DEFAULT_MIME "Content-Type: text/html\r\n" + +/* For every directory requested, don't send more than + * this limit of entries. + */ +#define MK_DIRHTML_BUFFER_LIMIT 30 +#define MK_DIRHTML_BUFFER_GROW 5 + +#define MK_HEADER_CHUNKED "Transfer-Encoding: Chunked\r\n\r\n" +#define MK_DIRHTML_FMOD_LEN 24 + +/* Theme files */ +#define MK_DIRHTML_FILE_HEADER "header.theme" +#define MK_DIRHTML_FILE_ENTRY "entry.theme" +#define MK_DIRHTML_FILE_FOOTER "footer.theme" + +#define MK_DIRHTML_TAG_INIT "%_" +#define MK_DIRHTML_TAG_END "_%" +#define MK_DIRHTML_SIZE_DIR "-" + +/* Stream state */ +#define MK_DIRHTML_STATE_HTTP_HEADER 0 +#define MK_DIRHTML_STATE_TPL_HEADER 1 +#define MK_DIRHTML_STATE_BODY 2 +#define MK_DIRHTML_STATE_FOOTER 3 + +char *_tags_global[] = { "%_html_title_%", + "%_theme_path_%", + NULL +}; + +char *_tags_entry[] = { "%_target_title_%", + "%_target_url_%", + "%_target_name_%", + "%_target_time_%", + "%_target_size_%", + NULL +}; + +struct plugin_api *mk_api; + +struct mk_f_list +{ + char ft_modif[MK_DIRHTML_FMOD_LEN]; + struct file_info info; + char name[NAME_MAX + 1]; /* The name can be up to NAME_MAX long; include NULL. */ + char size[16]; + unsigned char type; + + struct mk_list _head; +}; + +/* Main configuration of dirhtml module */ +struct dirhtml_config +{ + char *theme; + char *theme_path; +}; + +/* Represent a request context */ +struct mk_dirhtml_request +{ + /* State */ + int state; + int chunked; + + /* Target directory */ + DIR *dir; + + /* Table of Content */ + unsigned int toc_idx; + unsigned long toc_len; + struct mk_f_list **toc; + struct mk_list *file_list; + + /* Stream handler */ + struct mk_stream *stream; + + /* Reference IOV stuff */ + struct mk_iov *iov_header; + struct mk_iov *iov_entry; + struct mk_iov *iov_footer; + + /* Session data */ + struct mk_http_session *cs; + struct mk_http_request *sr; +}; + + +extern const mk_ptr_t mk_dirhtml_default_mime; +extern const mk_ptr_t mk_iov_dash; + +/* Global config */ +struct dirhtml_config *dirhtml_conf; + +/* Used to keep splitted content of every template */ +struct dirhtml_template +{ + char *buf; + int tag_id; + int len; + struct dirhtml_template *next; + char **tags; /* array of theme tags: [%_xaa__%, %_xyz_%] */ +}; + +/* Templates for header, entries and footer */ +struct dirhtml_template *mk_dirhtml_tpl_header; +struct dirhtml_template *mk_dirhtml_tpl_entry; +struct dirhtml_template *mk_dirhtml_tpl_footer; + +struct dirhtml_value +{ + int tag_id; + mk_ptr_t sep; /* separator code after value */ + + /* string data */ + int len; + char *value; + + /* next node */ + struct mk_list _head; + + char **tags; /* array of tags which values correspond */ +}; + +struct dirhtml_value *mk_dirhtml_value_global; + +/* Configuration struct */ +struct mk_config *conf; + +char *check_string(char *str); +char *read_header_footer_file(char *file_path); + +int mk_dirhtml_conf(); +char *mk_dirhtml_load_file(char *filename); + +struct dirhtml_template *mk_dirhtml_template_create(char *content); + +struct dirhtml_template + *mk_dirhtml_template_list_add(struct dirhtml_template **header, + char *buf, int len, char **tpl, int tag); + +int mk_dirhtml_read_config(char *path); +int mk_dirhtml_theme_load(); +int mk_dirhtml_theme_debug(struct dirhtml_template **st_tpl); + +struct dirhtml_value *mk_dirhtml_tag_assign(struct mk_list *list, + int tag_id, mk_ptr_t sep, + char *value, char **tags); + +struct f_list *get_dir_content(struct mk_http_request *sr, char *path); + + +#endif diff --git a/src/fluent-bit/lib/monkey/plugins/fastcgi/CMakeLists.txt b/src/fluent-bit/lib/monkey/plugins/fastcgi/CMakeLists.txt new file mode 100644 index 000000000..46056eead --- /dev/null +++ b/src/fluent-bit/lib/monkey/plugins/fastcgi/CMakeLists.txt @@ -0,0 +1,7 @@ +set(src + fastcgi.c + fcgi_handler.c + ) + +MONKEY_PLUGIN(fastcgi "${src}") +add_subdirectory(conf) diff --git a/src/fluent-bit/lib/monkey/plugins/fastcgi/conf/CMakeLists.txt b/src/fluent-bit/lib/monkey/plugins/fastcgi/conf/CMakeLists.txt new file mode 100644 index 000000000..0623bf68b --- /dev/null +++ b/src/fluent-bit/lib/monkey/plugins/fastcgi/conf/CMakeLists.txt @@ -0,0 +1,9 @@ +set(conf_dir "${MK_PATH_CONF}/plugins/fastcgi/") + +install(DIRECTORY DESTINATION ${conf_dir}) + +if(BUILD_LOCAL) + file(COPY fastcgi.conf DESTINATION ${conf_dir}) +else() + install(FILES fastcgi.conf DESTINATION ${conf_dir}) +endif() diff --git a/src/fluent-bit/lib/monkey/plugins/fastcgi/conf/fastcgi.conf b/src/fluent-bit/lib/monkey/plugins/fastcgi/conf/fastcgi.conf new file mode 100644 index 000000000..c5651c748 --- /dev/null +++ b/src/fluent-bit/lib/monkey/plugins/fastcgi/conf/fastcgi.conf @@ -0,0 +1,16 @@ +# FastCGI +# ======= +# To enable this plugin you'll need at least one [FASTCGI_SERVER]. +# +# This configuration handles php scripts using php5-fpm running on +# localhost or over the network. + +[FASTCGI_SERVER] + # Each server must have a unique name, this is mandatory. + ServerName php5-fpm1 + + # Depending on your version of php5-fpm, one of these should be + # enabled. + # + # ServerAddr 127.0.0.1:9000 + ServerPath /var/run/php5-fpm.sock diff --git a/src/fluent-bit/lib/monkey/plugins/fastcgi/fastcgi.c b/src/fluent-bit/lib/monkey/plugins/fastcgi/fastcgi.c new file mode 100644 index 000000000..1c0e716a2 --- /dev/null +++ b/src/fluent-bit/lib/monkey/plugins/fastcgi/fastcgi.c @@ -0,0 +1,233 @@ +/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ + +/* Monkey HTTP Server + * ================== + * Copyright 2001-2017 Eduardo Silva <eduardo@monkey.io> + * + * 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 <monkey/mk_api.h> + +#include "fastcgi.h" +#include "fcgi_handler.h" + +static int mk_fastcgi_config(char *path) +{ + int ret; + int sep; + char *file = NULL; + char *cnf_srv_name = NULL; + char *cnf_srv_addr = NULL; + char *cnf_srv_port = NULL; + char *cnf_srv_path = NULL; + unsigned long len; + struct file_info finfo; + struct mk_rconf *conf; + struct mk_rconf_section *section; + + mk_api->str_build(&file, &len, "%sfastcgi.conf", path); + conf = mk_api->config_open(file); + if (!conf) { + return -1; + } + + section = mk_api->config_section_get(conf, "FASTCGI_SERVER"); + if (!section) { + return -1; + } + + /* Get section values */ + cnf_srv_name = mk_api->config_section_get_key(section, + "ServerName", + MK_RCONF_STR); + cnf_srv_addr = mk_api->config_section_get_key(section, + "ServerAddr", + MK_RCONF_STR); + cnf_srv_path = mk_api->config_section_get_key(section, + "ServerPath", + MK_RCONF_STR); + + /* Validations */ + if (!cnf_srv_name) { + mk_warn("[fastcgi] Invalid ServerName in configuration."); + return -1; + } + + /* Split the address, try to lookup the TCP port */ + if (cnf_srv_addr) { + sep = mk_api->str_char_search(cnf_srv_addr, ':', strlen(cnf_srv_addr)); + if (sep <= 0) { + mk_warn("[fastcgi] Missing TCP port con ServerAddress key"); + return -1; + } + + cnf_srv_port = mk_api->str_dup(cnf_srv_addr + sep + 1); + cnf_srv_addr[sep] = '\0'; + } + + /* Just one mode can exist (for now) */ + if (cnf_srv_path && cnf_srv_addr) { + mk_warn("[fastcgi] Use ServerAddr or ServerPath, not both"); + return -1; + } + + /* Unix socket path */ + if (cnf_srv_path) { + ret = mk_api->file_get_info(cnf_srv_path, &finfo, MK_FILE_READ); + if (ret == -1) { + mk_warn("[fastcgi] Cannot open unix socket: %s", cnf_srv_path); + return -1; + } + } + + /* Set the global configuration */ + fcgi_conf.server_name = cnf_srv_name; + fcgi_conf.server_addr = cnf_srv_addr; + fcgi_conf.server_port = cnf_srv_port; + fcgi_conf.server_path = cnf_srv_path; + + return 0; +} + + +/* Entry point for thread/co-routine */ +static void mk_fastcgi_stage30_thread(struct mk_plugin *plugin, + struct mk_http_session *cs, + struct mk_http_request *sr, + int n_params, + struct mk_list *params) +{ + struct fcgi_handler *handler; + (void) plugin; + (void) n_params; + (void) params; + + printf("entering thread\n"); + handler = fcgi_handler_new(cs, sr); + if (!handler) { + fprintf(stderr, "Could not create handler"); + } +} + +/* Callback handler */ +int mk_fastcgi_stage30(struct mk_plugin *plugin, + struct mk_http_session *cs, + struct mk_http_request *sr, + int n_params, + struct mk_list *params) +{ + (void) n_params; + (void) params; + struct fcgi_handler *handler; + + /* + * This plugin uses the Monkey Thread model (co-routines), for hence + * upon return MK_PLUGIN_RET_CONTINUE, Monkey core will create a + * new thread (co-routine) and defer the control to the stage30_thread + * callback function (mk_fastcgi_stage30_thread). + * + * We don't do any validation, so we are OK with MK_PLUGIN_RET_CONTINUE. + */ + + return MK_PLUGIN_RET_CONTINUE; + + ret = mk_fastcgi_start_processing(cs, sr); + if (ret == 0) { + return MK_PLUGIN_RET_CONTINUE; + } + + return MK_PLUGIN_RET_CONTINUE; +} + +int mk_fastcgi_stage30_hangup(struct mk_plugin *plugin, + struct mk_http_session *cs, + struct mk_http_request *sr) +{ + (void) plugin; + (void) cs; + struct fcgi_handler *handler; + + handler = sr->handler_data; + if (!handler) { + return -1; + } + + if (handler->hangup == MK_TRUE) { + return 0; + } + + handler->active = MK_FALSE; + handler->hangup = MK_TRUE; + + fcgi_exit(sr->handler_data); + + return 0; +} + +int mk_fastcgi_plugin_init(struct plugin_api **api, char *confdir) +{ + int ret; + + mk_api = *api; + + /* read global configuration */ + ret = mk_fastcgi_config(confdir); + if (ret == -1) { + mk_warn("[fastcgi] configuration error/missing, plugin disabled."); + } + return ret; +} + +int mk_fastcgi_plugin_exit() +{ + return 0; +} + +int mk_fastcgi_master_init(struct mk_server *server) +{ + (void) server; + return 0; +} + +void mk_fastcgi_worker_init() +{ +} + +struct mk_plugin_stage mk_plugin_stage_fastcgi = { + .stage30 = &mk_fastcgi_stage30, + .stage30_thread = &mk_fastcgi_stage30_thread, + .stage30_hangup = &mk_fastcgi_stage30_hangup +}; + +struct mk_plugin mk_plugin_fastcgi = { + /* Identification */ + .shortname = "fastcgi", + .name = "FastCGI Client", + .version = "1.0", + .hooks = MK_PLUGIN_STAGE, + + /* Init / Exit */ + .init_plugin = mk_fastcgi_plugin_init, + .exit_plugin = mk_fastcgi_plugin_exit, + + /* Init Levels */ + .master_init = mk_fastcgi_master_init, + .worker_init = mk_fastcgi_worker_init, + + /* Type */ + .stage = &mk_plugin_stage_fastcgi, + + /* Flags */ + .flags = MK_PLUGIN_THREAD +}; diff --git a/src/fluent-bit/lib/monkey/plugins/fastcgi/fastcgi.h b/src/fluent-bit/lib/monkey/plugins/fastcgi/fastcgi.h new file mode 100644 index 000000000..a367f761c --- /dev/null +++ b/src/fluent-bit/lib/monkey/plugins/fastcgi/fastcgi.h @@ -0,0 +1,36 @@ +/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ + +/* Monkey HTTP Server + * ================== + * Copyright 2001-2017 Eduardo Silva <eduardo@monkey.io> + * + * 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. + */ + +#ifndef MK_FASTCGI_H +#define MK_FASTCGI_H + +struct mk_fcgi_conf { + char *server_name; + + /* Unix Socket */ + char *server_path; + + /* TCP Server */ + char *server_addr; + char *server_port; +}; + +struct mk_fcgi_conf fcgi_conf; + +#endif diff --git a/src/fluent-bit/lib/monkey/plugins/fastcgi/fcgi_handler.c b/src/fluent-bit/lib/monkey/plugins/fastcgi/fcgi_handler.c new file mode 100644 index 000000000..0c7881309 --- /dev/null +++ b/src/fluent-bit/lib/monkey/plugins/fastcgi/fcgi_handler.c @@ -0,0 +1,967 @@ +/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ + +/* Monkey HTTP Server + * ================== + * Copyright 2001-2017 Eduardo Silva <eduardo@monkey.io> + * + * 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 <monkey/mk_api.h> +#include <monkey/mk_net.h> +#include <monkey/mk_stream.h> + +#include "fastcgi.h" +#include "fcgi_handler.h" + +#define FCGI_BUF(h) (char *) h->buf_data + h->buf_len +#define FCGI_PARAM_DYN(str) str, strlen(str), MK_FALSE +#define FCGI_PARAM_CONST(str) str, sizeof(str) -1, MK_FALSE +#define FCGI_PARAM_PTR(ptr) ptr.data, ptr.len, MK_FALSE +#define FCGI_PARAM_DUP(str) mk_api->str_dup(str), strlen(str), MK_TRUE + +int fcgi_pad[256] = {0}; + +static inline void fcgi_build_header(struct fcgi_record_header *rec, + uint8_t type, uint16_t request_id, + uint16_t content_length) +{ + rec->version = FCGI_VERSION_1; + rec->type = type; + fcgi_encode16(&rec->request_id, request_id); + fcgi_encode16(&rec->content_length, content_length); + rec->padding_length = 0; + rec->reserved = 0; +} + +static inline void fcgi_build_request_body(struct fcgi_begin_request_body *body) +{ + fcgi_encode16(&body->role, FCGI_RESPONDER); + body->flags = 0; + memset(body->reserved, '\0', sizeof(body->reserved)); +} + +static inline size_t fcgi_write_length(char *p, size_t len) +{ + if (len < 127) { + *p++ = len; + return 1; + } + else{ + *p++ = (len >> 24) | 0x80; + *p++ = (len >> 16) & 0xff; + *p++ = (len >> 8) & 0xff; + *p++ = (len) & 0xff; + return 4; + } +} + +static inline int fcgi_add_param_empty(struct fcgi_handler *handler) +{ + char *p; + + p = FCGI_BUF(handler); + fcgi_build_header((struct fcgi_record_header *) p, FCGI_PARAMS, 1, 0); + mk_api->iov_add(handler->iov, p, + sizeof(struct fcgi_record_header), MK_FALSE); + handler->buf_len += sizeof(struct fcgi_record_header); + return 0; +} + +static inline int fcgi_add_param(struct fcgi_handler *handler, + char *key, int key_len, int key_free, + char *val, int val_len, int val_free) +{ + int ret; + int len; + int diff; + int padding; + char *p; + char *buf; + struct fcgi_record_header *h; + + buf = p = (char * ) handler->buf_data + handler->buf_len; + + len = key_len + val_len; + len += key_len > 127 ? 4 : 1; + len += val_len > 127 ? 4 : 1; + + fcgi_build_header((struct fcgi_record_header *) p, FCGI_PARAMS, 1, len); + padding = ~(len - 1) & 7; + if (padding) { + h = (struct fcgi_record_header *) p; + h->padding_length = padding; + } + + p += sizeof(struct fcgi_record_header); + p += fcgi_write_length(p, key_len); + p += fcgi_write_length(p, val_len); + + diff = (p - buf); + handler->buf_len += diff; + ret = mk_api->iov_add(handler->iov, buf, diff, MK_FALSE); + if (ret == -1) { + return -1; + } + + mk_api->iov_add(handler->iov, key, key_len, key_free); + mk_api->iov_add(handler->iov, val, val_len, val_free); + + if (padding) { + mk_api->iov_add(handler->iov, fcgi_pad, h->padding_length, MK_FALSE); + } + + return 0; +} + +static inline int fcgi_add_param_http_header(struct fcgi_handler *handler, + struct mk_http_header *header) +{ + unsigned int i; + int avail; + int req; + int diff; + char *p; + char *buf; + + avail = (sizeof(handler->buf_data) - handler->buf_len); + req = sizeof(struct fcgi_record_header) + 8; + req += header->key.len + 5; + + if (avail < req) { + return -1; + } + + buf = p = (handler->buf_data + handler->buf_len); + *p++ = 'H'; + *p++ = 'T'; + *p++ = 'T'; + *p++ = 'P'; + *p++ = '_'; + + for (i = 0; i < header->key.len; i++) { + if (header->key.data[i] == '-') { + *p++ = '_'; + } + else { + *p++ = toupper(header->key.data[i]); + } + } + + diff = (p - buf); + handler->buf_len += diff; + fcgi_add_param(handler, + buf, diff, MK_FALSE, + header->val.data, header->val.len, MK_FALSE); + + return 0; +} + +static inline int fcgi_add_param_net(struct fcgi_handler *handler) +{ + int ret; + const char *p; + char buffer[256]; + + /* This is to identify whether its IPV4 or IPV6 */ + struct sockaddr_storage addr; + int port = 0; + socklen_t addr_len = sizeof(struct sockaddr_in); + + ret = getsockname(handler->cs->socket, (struct sockaddr *)&addr, &addr_len); + if (ret == -1) { +#ifdef TRACE + perror("getsockname"); +#endif + if (errno == EBADF) { + MK_TRACE("[fastcgi=%i] network connection broken", + handler->cs->socket); + } + return -1; + } + + if (addr.ss_family == AF_INET) { + struct sockaddr_in *s = (struct sockaddr_in *)&addr; + port = ntohs(s->sin_port); + p = inet_ntop(AF_INET, &s->sin_addr, buffer, sizeof(buffer)); + if (!p) { + perror("inet_ntop"); + return -1; + } + } else { /* AF_INET6 */ + struct sockaddr_in6 *s = (struct sockaddr_in6 *)&addr; + port = ntohs(s->sin6_port); + p = inet_ntop(AF_INET6, &s->sin6_addr, buffer, sizeof(buffer)); + if (!p) { + perror("inet_ntop"); + return -1; + } + } + + /* Server Address */ + fcgi_add_param(handler, + FCGI_PARAM_CONST("SERVER_ADDR"), + FCGI_PARAM_DUP(buffer)); + + /* Server Port */ + snprintf(buffer, 256, "%d", port); + fcgi_add_param(handler, + FCGI_PARAM_CONST("SERVER_PORT"), + FCGI_PARAM_DUP(buffer)); + + + ret = getpeername(handler->cs->socket, (struct sockaddr *)&addr, &addr_len); + if (ret == -1) { + perror("getpeername"); + return -1; + } + + if (addr.ss_family == AF_INET) { + struct sockaddr_in *s = (struct sockaddr_in *)&addr; + port = ntohs(s->sin_port); + p = inet_ntop(AF_INET, &s->sin_addr, buffer, sizeof(buffer)); + if (!p) { + perror("inet_ntop"); + return -1; + } + } else { /* AF_INET6 */ + struct sockaddr_in6 *s = (struct sockaddr_in6 *)&addr; + port = ntohs(s->sin6_port); + + if (IN6_IS_ADDR_V4MAPPED(&s->sin6_addr)) { + /* This is V4-Mapped-V6 - Lets convert it to plain IPV4 address. + * E.g. we would have received like this ::ffff:10.106.146.73. + * This would be converted to 10.106.146.73. + */ + struct sockaddr_in addr4; + struct sockaddr_in *s4 = (struct sockaddr_in *)&addr4; + memset(&addr4, 0, sizeof(addr4)); + addr4.sin_family = AF_INET; + addr4.sin_port = &s->sin6_port; + memcpy(&addr4.sin_addr.s_addr, + s->sin6_addr.s6_addr + 12, + sizeof(addr4.sin_addr.s_addr)); + p = inet_ntop(AF_INET, &s4->sin_addr, buffer, sizeof(buffer)); + if (!p) { + perror("inet_ntop"); + return -1; + } + } else { + p = inet_ntop(AF_INET6, &s->sin6_addr, buffer, sizeof(buffer)); + if (!p) { + perror("inet_ntop"); + return -1; + } + } + } + + /* Remote Addr */ + fcgi_add_param(handler, + FCGI_PARAM_CONST("REMOTE_ADDR"), + FCGI_PARAM_DUP(buffer)); + + /* Remote Port */ + snprintf(buffer, 256, "%d", port); + fcgi_add_param(handler, + FCGI_PARAM_CONST("REMOTE_PORT"), + FCGI_PARAM_DUP(buffer)); + + return 0; +} + +static inline int fcgi_stdin_chunk(struct fcgi_handler *handler) +{ + int padding = 0; + uint16_t max = 65535; + uint16_t chunk; + uint64_t total; + char *p; + char *eof; + struct fcgi_record_header *h; + + total = handler->stdin_length - handler->stdin_offset; + if (total > max) { + chunk = max; + } + else { + chunk = total; + } + + p = FCGI_BUF(handler); + h = (struct fcgi_record_header *) p; + fcgi_build_header(h, FCGI_STDIN, 1, chunk); + h->padding_length = ~(chunk - 1) & 7; + + MK_TRACE("[fastcgi] STDIN: length=%i", chunk); + + mk_api->iov_add(handler->iov, p, FCGI_RECORD_HEADER_SIZE, MK_FALSE); + handler->buf_len += FCGI_RECORD_HEADER_SIZE; + + + if (chunk > 0) { + mk_api->iov_add(handler->iov, + handler->stdin_buffer + handler->stdin_offset, + chunk, + MK_FALSE); + } + + if (h->padding_length > 0) { + mk_api->iov_add(handler->iov, + fcgi_pad, h->padding_length, + MK_FALSE); + } + + if (handler->stdin_offset + chunk == handler->stdin_length) { + eof = FCGI_BUF(handler); + fcgi_build_header((struct fcgi_record_header *) eof, FCGI_STDIN, 1, 0); + mk_api->iov_add(handler->iov, eof, FCGI_RECORD_HEADER_SIZE, MK_FALSE); + handler->buf_len += FCGI_RECORD_HEADER_SIZE + padding; + } + + handler->stdin_offset += chunk; + return 0; +} + +static inline int fcgi_add_stdin(struct fcgi_handler *handler) +{ + uint64_t bytes = handler->sr->data.len; + + if (bytes <= 0) { + return -1; + } + + handler->stdin_length = bytes; + handler->stdin_offset = 0; + handler->stdin_buffer = handler->sr->data.data; + fcgi_stdin_chunk(handler); + + return 0; +} + +static int fcgi_encode_request(struct fcgi_handler *handler) +{ + int ret; + struct mk_http_header *header; + struct fcgi_begin_request_record *request; + + MK_TRACE("ENCODE REQUEST"); + + request = &handler->header_request; + fcgi_build_header(&request->header, FCGI_BEGIN_REQUEST, 1, + FCGI_BEGIN_REQUEST_BODY_SIZE); + + fcgi_build_request_body(&request->body); + + /* BEGIN_REQUEST */ + mk_api->iov_add(handler->iov, + &handler->header_request, + sizeof(handler->header_request), + MK_FALSE); + + /* Server Software */ + fcgi_add_param(handler, + FCGI_PARAM_CONST("GATEWAY_INTERFACE"), + FCGI_PARAM_CONST("CGI/1.1")); + + /* Server Software */ + fcgi_add_param(handler, + FCGI_PARAM_CONST("REDIRECT_STATUS"), + FCGI_PARAM_CONST("200")); + + /* Server Software */ + fcgi_add_param(handler, + FCGI_PARAM_CONST("SERVER_SOFTWARE"), + FCGI_PARAM_DYN(mk_api->config->server_signature)); + + /* Server Name */ + fcgi_add_param(handler, + FCGI_PARAM_CONST("SERVER_PROTOCOL"), + FCGI_PARAM_CONST("HTTP/1.1")); + + /* Server Name */ + fcgi_add_param(handler, + FCGI_PARAM_CONST("SERVER_NAME"), + handler->sr->host_alias->name, + handler->sr->host_alias->len, + MK_FALSE); + + /* Document Root */ + fcgi_add_param(handler, + FCGI_PARAM_CONST("DOCUMENT_ROOT"), + FCGI_PARAM_PTR(handler->sr->host_conf->documentroot)); + + /* Network params: SERVER_ADDR, SERVER_PORT, REMOTE_ADDR & REMOTE_PORT */ + ret = fcgi_add_param_net(handler); + if (ret == -1) { + return -1; + } + + /* Script Filename */ + fcgi_add_param(handler, + FCGI_PARAM_CONST("SCRIPT_FILENAME"), + FCGI_PARAM_PTR(handler->sr->real_path)); + + /* Script Filename */ + fcgi_add_param(handler, + FCGI_PARAM_CONST("SCRIPT_NAME"), + FCGI_PARAM_PTR(handler->sr->uri_processed)); + + /* Request Method */ + fcgi_add_param(handler, + FCGI_PARAM_CONST("REQUEST_METHOD"), + FCGI_PARAM_PTR(handler->sr->method_p)); + + + /* Request URI */ + fcgi_add_param(handler, + FCGI_PARAM_CONST("REQUEST_URI"), + FCGI_PARAM_PTR(handler->sr->uri)); + + /* Query String */ + if (handler->sr->query_string.len > 0) { + fcgi_add_param(handler, + FCGI_PARAM_CONST("QUERY_STRING"), + FCGI_PARAM_PTR(handler->sr->query_string)); + } + + /* HTTPS */ + if (MK_SCHED_CONN_PROP(handler->cs->conn) & MK_CAP_SOCK_TLS) { + fcgi_add_param(handler, + FCGI_PARAM_CONST("HTTPS"), + FCGI_PARAM_CONST("on")); + } + + /* Content Length */ + if (handler->sr->_content_length.data) { + fcgi_add_param(handler, + FCGI_PARAM_CONST("CONTENT_LENGTH"), + FCGI_PARAM_PTR(handler->sr->_content_length)); + } + + /* Content Length */ + header = &handler->cs->parser.headers[MK_HEADER_CONTENT_TYPE]; + if (header->type == MK_HEADER_CONTENT_TYPE) { + fcgi_add_param(handler, + FCGI_PARAM_CONST("CONTENT_TYPE"), + FCGI_PARAM_PTR(header->val)); + + } + + /* Append HTTP request headers */ + struct mk_list *head; + struct mk_http_header *http_header; + mk_list_foreach(head, &handler->cs->parser.header_list) { + http_header = mk_list_entry(head, struct mk_http_header, _head); + fcgi_add_param_http_header(handler, http_header); + } + + /* Append the empty params record */ + fcgi_add_param_empty(handler); + + /* Data for FCGI_STDIN */ + fcgi_add_stdin(handler); + + return 0; +} + +size_t fcgi_read_header(void *p, struct fcgi_record_header *h) +{ + memcpy(h, p, sizeof(struct fcgi_record_header)); + h->request_id = htons(h->request_id); + h->content_length = htons(h->content_length); + + return sizeof(*h); +} + +static inline int fcgi_buffer_consume(struct fcgi_handler *handler, size_t bytes) +{ + if (bytes >= handler->buf_len) { + handler->buf_len = 0; + return 0; + } + + memmove(handler->buf_data, handler->buf_data + bytes, + handler->buf_len - bytes); + handler->buf_len -= bytes; + return 0; +} + +static char *getearliestbreak(const char buf[], const unsigned bufsize, + unsigned char * const advance) +{ + char *crend; + char *lfend; + + crend = memmem(buf, bufsize, "\r\n\r\n", 4); + lfend = memmem(buf, bufsize, "\n\n", 2); + + if (!crend && !lfend) + return NULL; + + /* If only one found, return that one */ + if (!crend) { + *advance = 2; + return lfend; + } + if (!lfend) + return crend; + + /* Both found, return the earlier one - the latter one is part of content */ + if (lfend < crend) { + *advance = 2; + return lfend; + } + return crend; +} + +static int fcgi_write(struct fcgi_handler *handler, char *buf, size_t len) +{ + mk_stream_in_raw(handler->stream, + NULL, + buf, len, + NULL, NULL); + + if (handler->headers_set == MK_TRUE) { + mk_stream_in_raw(handler->stream, + NULL, + "\r\n", 2, + NULL, NULL); + } + return 0; +} + +void fcgi_stream_eof(struct mk_stream_input *in) +{ + (void) in; + // FIXME + //struct fcgi_handler *handler; + + //handler = stream->data; + //if (handler->hangup == MK_FALSE) { + // fcgi_exit(handler); + //} +} + +int fcgi_exit(struct fcgi_handler *handler) +{ + /* Always disable any backend notification first */ + if (handler->server_fd > 0) { + mk_api->ev_del(mk_api->sched_loop(), &handler->event); + close(handler->server_fd); + handler->server_fd = -1; + } + + /* + * Before to exit our handler, we need to verify that our parent + * channel have sent the whole information, otherwise we may face + * some corruption. If there is still some data enqueued, just + * defer the exit process. + */ + if (mk_channel_is_empty(handler->cs->channel) != 0 && + handler->eof == MK_FALSE && + handler->active == MK_TRUE) { + MK_TRACE("[fastcgi=%i] deferring exit, EOF stream", + handler->server_fd); + + /* Now set an EOF stream/callback to resume the exiting process */ + mk_stream_in_eof(handler->stream, + NULL, + fcgi_stream_eof); + handler->eof = MK_TRUE; + return 1; + } + + MK_TRACE("[fastcgi] exiting"); + + if (handler->iov) { + mk_api->iov_free(handler->iov); + mk_api->sched_event_free((struct mk_event *) handler); + handler->iov = NULL; + } + + if (handler->active == MK_TRUE) { + handler->active = MK_FALSE; + mk_api->http_request_end(handler->plugin, handler->cs, handler->hangup); + } + + return 1; +} + +int fcgi_error(struct fcgi_handler *handler) +{ + fcgi_exit(handler); + mk_api->http_request_error(500, handler->cs, handler->sr, handler->plugin); + return 0; +} + +static int fcgi_response(struct fcgi_handler *handler, char *buf, size_t len) +{ + int status; + int diff; + int xlen; + char tmp[16]; + char *p; + char *end; + size_t p_len; + unsigned char advance; + + MK_TRACE("[fastcgi=%i] process response len=%lu", + handler->server_fd, len); + + p = buf; + p_len = len; + + if (len == 0 && handler->chunked && handler->headers_set == MK_TRUE) { + MK_TRACE("[fastcgi=%i] sending EOF", handler->server_fd); + mk_stream_in_raw(handler->stream, + NULL, + "0\r\n\r\n", 5, + NULL, NULL); + mk_api->channel_flush(handler->cs->channel); + return 0; + } + + if (handler->headers_set == MK_FALSE) { + advance = 4; + + if (!buf) { + return -1; + } + + end = getearliestbreak(buf, len, &advance); + if (!end) { + /* we need more data */ + return -1; + } + + handler->sr->headers.cgi = MK_TRUE; + if (strncasecmp(buf, "Status: ", 8) == 0) { + sscanf(buf + 8, "%d", &status); + MK_TRACE("FastCGI status %i", status); + mk_api->header_set_http_status(handler->sr, status); + } + else { + mk_api->header_set_http_status(handler->sr, 200); + } + + /* Set transfer encoding */ + if (handler->sr->protocol >= MK_HTTP_PROTOCOL_11) { + handler->sr->headers.transfer_encoding = MK_HEADER_TE_TYPE_CHUNKED; + handler->chunked = MK_TRUE; + } + + mk_api->header_prepare(handler->plugin, handler->cs, handler->sr); + + diff = (end - buf) + advance; + fcgi_write(handler, buf, diff); + + p = buf + diff; + p_len -= diff; + handler->write_rounds++; + handler->headers_set = MK_TRUE; + } + + if (p_len > 0) { + xlen = snprintf(tmp, 16, "%x\r\n", (unsigned int) p_len); + mk_stream_in_raw(handler->stream, + NULL, + tmp, xlen, + NULL, NULL); + fcgi_write(handler, p, p_len); + } + + return 0; +} + +int cb_fastcgi_on_read(void *data) +{ + int n; + int ret = 0; + int avail; + char *body; + size_t offset; + struct fcgi_handler *handler = data; + struct fcgi_record_header header; + + if (handler->active == MK_FALSE) { + fcgi_exit(handler); + return -1; + } + + avail = FCGI_BUF_SIZE - handler->buf_len; + n = read(handler->server_fd, handler->buf_data + handler->buf_len, avail); + MK_TRACE("[fastcgi=%i] read()=%i", handler->server_fd, n); + if (n <= 0) { + MK_TRACE("[fastcgi=%i] FastCGI server ended", handler->server_fd); + fcgi_exit(handler); + return -1; + } + else { + handler->buf_len += n; + } + + if ((unsigned) handler->buf_len < FCGI_RECORD_HEADER_SIZE) { + /* wait for more data */ + return n; + } + + while (1) { + /* decode the header */ + fcgi_read_header(&handler->buf_data, &header); + + if (header.type != FCGI_STDOUT && header.type != FCGI_STDERR && + header.type != FCGI_END_REQUEST) { + fcgi_exit(handler); + return -1; + } + + /* Check if the package is complete */ + if (handler->buf_len < (FCGI_RECORD_HEADER_SIZE + header.content_length)) { + /* we need more data */ + return n; + } + + body = handler->buf_data + FCGI_RECORD_HEADER_SIZE; + switch (header.type) { + case FCGI_STDOUT: + MK_TRACE("[fastcgi=%i] FCGI_STDOUT content_length=%i", + handler->server_fd, header.content_length); + /* + * Issue seen with Chrome & Firefox browsers: + * Sometimes content length is coming as ZERO and we are encoding a + * HTTP response packet with ZERO size data. This makes Chrome & Firefox + * browsers fail to proceed furhter and subsequent content loading fails. + * However, IE/Safari discards the packets with ZERO size data. + */ + if (0 != header.content_length) { + ret = fcgi_response(handler, body, header.content_length); + } + else { + MK_TRACE("[fastcgi=%i] ZERO byte content length in FCGI_STDOUT, discard!!", + handler->server_fd); + ret = 0; + } + break; + case FCGI_STDERR: + MK_TRACE("[fastcgi=%i] FCGI_STDERR content_length=%i", + handler->server_fd, header.content_length); + break; + case FCGI_END_REQUEST: + MK_TRACE("[fastcgi=%i] FCGI_END_REQUEST content_length=%i", + handler->server_fd, header.content_length); + ret = fcgi_response(handler, NULL, 0); + break; + default: + //fcgi_exit(handler); + return -1; + } + + if (ret == -1) { + /* Missing header breaklines ? */ + return n; + } + + /* adjust buffer content */ + offset = FCGI_RECORD_HEADER_SIZE + + header.content_length + header.padding_length; + + fcgi_buffer_consume(handler, offset); + } + return n; +} + +int cb_fastcgi_request_flush(void *data) +{ + int ret; + size_t count = 0; + struct fcgi_handler *handler = data; + + ret = mk_api->channel_write(&handler->fcgi_channel, &count); + + MK_TRACE("[fastcgi=%i] %lu bytes, ret=%i", + handler->server_fd, count, ret); + + if (ret == MK_CHANNEL_DONE || ret == MK_CHANNEL_EMPTY) { + /* Do we have more data for the stdin ? */ + if (handler->stdin_length - handler->stdin_offset > 0) { + mk_api->iov_free(handler->iov); + handler->iov = mk_api->iov_create(64, 0); + fcgi_stdin_chunk(handler); + + mk_api->stream_set(&handler->fcgi_stream, + MK_STREAM_IOV, + &handler->fcgi_channel, + handler->iov, + -1, + handler, + NULL, NULL, NULL); + return MK_CHANNEL_FLUSH; + } + + /* Request done, switch the event side to receive the FCGI response */ + handler->buf_len = 0; + handler->event.handler = cb_fastcgi_on_read; + ret = mk_api->ev_add(mk_api->sched_loop(), + handler->server_fd, + MK_EVENT_CUSTOM, MK_EVENT_READ, handler); + if (ret == -1) { + goto error; + } + } + else if (ret == MK_CHANNEL_ERROR) { + fcgi_exit(handler); + } + else if (ret == MK_CHANNEL_BUSY) { + return -1; + } + + return ret; + + error: + return -1; +} + +/* Callback: on connect to the backend server */ +static int fastcgi_on_connect(struct fcgi_handler *handler) +{ + int ret; + int s_err; + size_t count; + socklen_t s_len = sizeof(s_err); + struct mk_list *head; + struct mk_plugin *pio; + struct mk_channel *channel; + + /* Convert the original request to FCGI format */ + ret = fcgi_encode_request(handler); + if (ret == -1) { + goto error; + } + + /* Prepare the channel */ + channel = &handler->fcgi_channel; + channel->type = MK_CHANNEL_SOCKET; + channel->fd = handler->server_fd; + + /* FIXME: Discovery process needs to be fast */ + mk_list_foreach(head, &mk_api->config->plugins) { + pio = mk_list_entry(head, struct mk_plugin, _head); + if (strncmp(pio->shortname, "liana", 5) == 0) { + break; + } + pio = NULL; + } + channel->io = pio->network; + + mk_list_init(&channel->streams); + mk_api->stream_set(&handler->fcgi_stream, + MK_STREAM_IOV, + &handler->fcgi_channel, + handler->iov, + -1, + handler, + NULL, NULL, NULL); + + handler->event.handler = cb_fastcgi_request_flush; + handler->event.data = handler; + + return 0; + + error: + fcgi_error(handler); + mk_api->channel_write(handler->cs->channel, &count); + return 0; +} + +struct fcgi_handler *fcgi_handler_new(struct mk_plugin *plugin, + struct mk_http_session *cs, + struct mk_http_request *sr) +{ + int ret; + int entries; + struct fcgi_handler *h = NULL; + struct mk_net_connection *conn = NULL; + + /* Allocate handler instance and set fields */ + h = mk_api->mem_alloc_z(sizeof(struct fcgi_handler)); + if (!h) { + return NULL; + } + + stream = mk_stream_set(NULL, cs->channel, h, + NULL, NULL, NULL); + if (!stream) { + mk_api->mem_free(h); + return NULL; + } + + h->stream = stream; + h->plugin = plugin; + h->cs = cs; + h->sr = sr; + h->write_rounds = 0; + h->active = MK_TRUE; + h->server_fd = -1; + h->eof = MK_FALSE; + h->stdin_length = 0; + h->stdin_offset = 0; + h->stdin_buffer = NULL; + h->conn = NULL; + + /* Allocate enough space for our data */ + entries = 128 + (cs->parser.header_count * 3); + h->iov = mk_api->iov_create(entries, 0); + + /* Associate the handler with the Session Request */ + sr->handler_data = h; + + if (sr->protocol >= MK_HTTP_PROTOCOL_11) { + h->hangup = MK_FALSE; + } + else { + h->hangup = MK_TRUE; + } + + /* Params buffer set an offset to include the header */ + h->buf_len = FCGI_RECORD_HEADER_SIZE; + + /* Request and async connection to the server */ + if (fcgi_conf.server_addr) { + conn = mk_api->net_conn_create(fcgi_conf.server_addr, + atoi(fcgi_conf.server_port)); + if (!conn) { + goto error; + } + h->conn = conn; + h->server_fd = conn->fd; + } + else if (fcgi_conf.server_path) { + /* FIXME: unix socket connection NOT FUNCTIONAL for now */ + h->server_fd = mk_api->socket_open(fcgi_conf.server_path, MK_TRUE); + } + + if (h->server_fd == -1) { + goto error; + } + + fastcgi_on_connect(h); + return h; + + error: + mk_api->iov_free(h->iov); + mk_api->mem_free(h); + sr->handler_data = NULL; + mk_api->http_request_error(500, cs, sr, plugin); + + return NULL; +} diff --git a/src/fluent-bit/lib/monkey/plugins/fastcgi/fcgi_handler.h b/src/fluent-bit/lib/monkey/plugins/fastcgi/fcgi_handler.h new file mode 100644 index 000000000..a0083ac7d --- /dev/null +++ b/src/fluent-bit/lib/monkey/plugins/fastcgi/fcgi_handler.h @@ -0,0 +1,128 @@ +/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ + +/* Monkey HTTP Server + * ================== + * Copyright 2001-2017 Eduardo Silva <eduardo@monkey.io> + * + * 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. + */ + +#ifndef MK_FASTCGI_HANDLER_H +#define MK_FASTCGI_HANDLER_H + +#include <monkey/mk_api.h> + +/* + * Based on the information provided by the FastCGI spec, we use the + * following adapted structures: + * + * http://www.fastcgi.com/drupal/node/6?q=node/22 + */ +struct fcgi_record_header { + uint8_t version; + uint8_t type; + uint16_t request_id; + uint16_t content_length; + uint8_t padding_length; + uint8_t reserved; +}; + +struct fcgi_begin_request_body { + uint16_t role; + uint8_t flags; + uint8_t reserved[5]; +}; + +struct fcgi_begin_request_record { + struct fcgi_record_header header; + struct fcgi_begin_request_body body; +}; + +#define FCGI_VERSION_1 1 +#define FCGI_RECORD_MAX_SIZE 65535 +#define FCGI_RECORD_HEADER_SIZE sizeof(struct fcgi_record_header) +#define FCGI_BUF_SIZE FCGI_RECORD_MAX_SIZE + FCGI_RECORD_HEADER_SIZE +#define FCGI_BEGIN_REQUEST_BODY_SIZE sizeof(struct fcgi_begin_request_body) +#define FCGI_RESPONDER 1 +#define FCGI_AUTHORIZER 2 +#define FCGI_FILTER 3 + +/* + * Values for type component of FCGI_Header + */ +#define FCGI_BEGIN_REQUEST 1 +#define FCGI_ABORT_REQUEST 2 +#define FCGI_END_REQUEST 3 +#define FCGI_PARAMS 4 +#define FCGI_STDIN 5 +#define FCGI_STDOUT 6 +#define FCGI_STDERR 7 +#define FCGI_DATA 8 +#define FCGI_GET_VALUES 9 +#define FCGI_GET_VALUES_RESULT 10 + +/* + * FastCGI Handler context, it keeps information of states and other + * request/response references. + */ +struct fcgi_handler { + struct mk_event event; /* built-in event-loop data */ + + int server_fd; /* backend FastCGI server */ + int chunked; /* chunked response ? */ + int active; /* is this handler active ? */ + int hangup; /* hangup connection once ready ? */ + int headers_set; /* headers set ? */ + int eof; /* exiting: MK_TRUE / MK_FALSE */ + + /* stdin data */ + uint64_t stdin_length; + uint64_t stdin_offset; + char *stdin_buffer; + + struct mk_http_session *cs; /* HTTP session context */ + struct mk_http_request *sr; /* HTTP request context */ + + /* FastCGI */ + struct fcgi_begin_request_record header_request; + + uint64_t write_rounds; + unsigned int buf_len; + char buf_data[FCGI_BUF_SIZE]; + + /* Channel to stream request to the FCGI server */ + struct mk_channel fcgi_channel; + struct mk_stream fcgi_stream; + + struct mk_iov *iov; + struct mk_list _head; + + /* TCP connection context */ + struct mk_net_connection *conn; +}; + +static inline void fcgi_encode16(void *a, unsigned b) +{ + unsigned char *c = a; + + c[0] = (unsigned char) (b >> 8); + c[1] = (unsigned char) b; +} + +struct fcgi_handler *fcgi_handler_new(struct mk_plugin *plugin, + struct mk_http_session *cs, + struct mk_http_request *sr); + +int fcgi_exit(struct fcgi_handler *handler); + +#endif diff --git a/src/fluent-bit/lib/monkey/plugins/liana/ABOUT b/src/fluent-bit/lib/monkey/plugins/liana/ABOUT new file mode 100644 index 000000000..1c1407ba4 --- /dev/null +++ b/src/fluent-bit/lib/monkey/plugins/liana/ABOUT @@ -0,0 +1,4 @@ +Liana Plugin +============ +Liana gives network layer to Monkey so you can build your own +network layer for Monkey. diff --git a/src/fluent-bit/lib/monkey/plugins/liana/CMakeLists.txt b/src/fluent-bit/lib/monkey/plugins/liana/CMakeLists.txt new file mode 100644 index 000000000..1f3797dcd --- /dev/null +++ b/src/fluent-bit/lib/monkey/plugins/liana/CMakeLists.txt @@ -0,0 +1,5 @@ +set(src + liana.c +) + +MONKEY_PLUGIN(liana "${src}") diff --git a/src/fluent-bit/lib/monkey/plugins/liana/MANDATORY b/src/fluent-bit/lib/monkey/plugins/liana/MANDATORY new file mode 100644 index 000000000..e69de29bb --- /dev/null +++ b/src/fluent-bit/lib/monkey/plugins/liana/MANDATORY diff --git a/src/fluent-bit/lib/monkey/plugins/liana/Makefile.in b/src/fluent-bit/lib/monkey/plugins/liana/Makefile.in new file mode 100644 index 000000000..ef9fd4fc3 --- /dev/null +++ b/src/fluent-bit/lib/monkey/plugins/liana/Makefile.in @@ -0,0 +1,18 @@ +all: monkey-liana.so +include ../Make.common + +CC = @echo " CC $(_PATH)/$@"; $CC +CC_QUIET= @echo -n; $CC +AR = @echo " AR $(_PATH)/$@"; $AR +CFLAGS = $CFLAGS +LDFLAGS = $LDFLAGS +DEFS = $DEFS +LIANA_OBJECTS = liana.o + +-include $(LIANA_OBJECTS:.o=.d) + +monkey-liana.so: $(LIANA_OBJECTS) + $(CC) $(CFLAGS) $(LDFLAGS) $(DEFS) -shared -o $@ $^ -lc + +monkey-liana.a: $(LIANA_OBJECTS) + $(AR) rcs $@ $(LIANA_OBJECTS) diff --git a/src/fluent-bit/lib/monkey/plugins/liana/README b/src/fluent-bit/lib/monkey/plugins/liana/README new file mode 100644 index 000000000..69a4760f9 --- /dev/null +++ b/src/fluent-bit/lib/monkey/plugins/liana/README @@ -0,0 +1,7 @@ +Liana Networking Plugin +======================= + +Liana is the base network plugin for Monkey, it provides the +most basic socket calls such as: accept(), read(), write() etc. + +Without Liana do not expect a default HTTP Server working :) diff --git a/src/fluent-bit/lib/monkey/plugins/liana/STATIC b/src/fluent-bit/lib/monkey/plugins/liana/STATIC new file mode 100644 index 000000000..e69de29bb --- /dev/null +++ b/src/fluent-bit/lib/monkey/plugins/liana/STATIC diff --git a/src/fluent-bit/lib/monkey/plugins/liana/liana.c b/src/fluent-bit/lib/monkey/plugins/liana/liana.c new file mode 100644 index 000000000..3bfe288b8 --- /dev/null +++ b/src/fluent-bit/lib/monkey/plugins/liana/liana.c @@ -0,0 +1,221 @@ +/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ + +/* Monkey HTTP Server + * ================== + * Copyright 2001-2017 Eduardo Silva <eduardo@monkey.io> + * + * 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. + */ + +#define _GNU_SOURCE + +#include <stdio.h> +#include <string.h> + +#include <sys/types.h> +#include <fcntl.h> + +#ifdef _WIN32 +#include <winsock2.h> +#else +#include <arpa/inet.h> +#include <sys/socket.h> +#include <netdb.h> +#endif + +#if defined (__linux__) +#include <sys/sendfile.h> +#endif + +#include <monkey/mk_api.h> + +int mk_liana_plugin_init(struct mk_plugin *plugin, char *confdir) +{ + (void) confdir; + (void) plugin; + + return 0; +} + +int mk_liana_plugin_exit(struct mk_plugin *plugin) +{ + (void) plugin; + + return 0; +} + +int mk_liana_read(struct mk_plugin *plugin, int socket_fd, void *buf, int count) +{ + (void) plugin; + + return recv(socket_fd, (void*)buf, count, 0); +} + +int mk_liana_write(struct mk_plugin *plugin, int socket_fd, const void *buf, size_t count ) +{ + ssize_t bytes_sent = -1; + + (void) plugin; + + bytes_sent = send(socket_fd, buf, count, 0); + + return bytes_sent; +} + +int mk_liana_writev(struct mk_plugin *plugin, int socket_fd, struct mk_iov *mk_io) +{ + ssize_t bytes_sent = -1; + + (void) plugin; + + bytes_sent = plugin->api->iov_send(socket_fd, mk_io); + + return bytes_sent; +} + +int mk_liana_close(struct mk_plugin *plugin, int socket_fd) +{ + (void) plugin; + +#ifdef _WIN32 + return closesocket(socket_fd); +#else + return close(socket_fd); +#endif +} + +int mk_liana_send_file(struct mk_plugin *plugin, int socket_fd, int file_fd, off_t *file_offset, + size_t file_count) +{ + ssize_t ret = -1; + + (void) plugin; + +#if defined (__linux__) + ret = sendfile(socket_fd, file_fd, file_offset, file_count); + if (ret == -1 && errno != EAGAIN) { + PLUGIN_TRACE("[FD %i] error from sendfile(): %s", + socket_fd, strerror(errno)); + } + return ret; +#elif defined (__APPLE__) + off_t offset = *file_offset; + off_t len = (off_t) file_count; + + ret = sendfile(file_fd, socket_fd, offset, &len, NULL, 0); + if (ret == -1 && errno != EAGAIN) { + PLUGIN_TRACE("[FD %i] error from sendfile(): %s", + socket_fd, strerror(errno)); + } + else if (len > 0) { + *file_offset += len; + return len; + } + return ret; +#elif defined (__FreeBSD__) + off_t offset = *file_offset; + off_t len = (off_t) file_count; + + ret = sendfile(file_fd, socket_fd, offset, len, NULL, 0, 0); + if (ret == -1 && errno != EAGAIN) { + PLUGIN_TRACE("[FD %i] error from sendfile(): %s", + socket_fd, strerror(errno)); + } + else if (len > 0) { + *file_offset += len; + return len; + } + return ret; +#else + #pragma message ("This is a terrible sendfile \"implementation\" and just a crutch") + + ssize_t bytes_written = 0; + ssize_t to_be_sent = -1; + ssize_t send_ret = -1; + uint8_t temporary_buffer[1024]; + + if (NULL != file_offset) { + lseek(file_fd, *file_offset, SEEK_SET); + } + + while (1) { + memset(temporary_buffer, 0, sizeof(temporary_buffer)); + + ret = read(file_fd, temporary_buffer, sizeof(temporary_buffer)); + + if (0 == ret) + { + return bytes_written; + } + else if (0 > ret) + { + return -1; + } + else if (0 < ret) + { + to_be_sent = ret; + + while (to_be_sent > 0) + { + send_ret = send(file_fd, &temporary_buffer[ret - to_be_sent], to_be_sent, 0); + + if (-1 == send_ret) + { + if (EAGAIN != errno && + EWOULDBLOCK != errno) + { + return -1; + } + } + else + { + bytes_written += send_ret; + to_be_sent -= send_ret; + } + } + } + } +#endif +} + +/* Network Layer plugin Callbacks */ +struct mk_plugin_network mk_plugin_network_liana = { + .read = mk_liana_read, + .write = mk_liana_write, + .writev = mk_liana_writev, + .close = mk_liana_close, + .send_file = mk_liana_send_file, + .buffer_size = MK_REQUEST_CHUNK +}; + +struct mk_plugin mk_plugin_liana = { + /* Identification */ + .shortname = "liana", + .name = "Liana Network Layer", + .version = MK_VERSION_STR, + .hooks = MK_PLUGIN_NETWORK_LAYER, + + /* Init / Exit */ + .init_plugin = mk_liana_plugin_init, + .exit_plugin = mk_liana_plugin_exit, + + /* Init Levels */ + .master_init = NULL, + .worker_init = NULL, + + /* Type */ + .network = &mk_plugin_network_liana, + + /* Capabilities */ + .capabilities = MK_CAP_SOCK_PLAIN +}; diff --git a/src/fluent-bit/lib/monkey/plugins/logger/CMakeLists.txt b/src/fluent-bit/lib/monkey/plugins/logger/CMakeLists.txt new file mode 100644 index 000000000..156a0847f --- /dev/null +++ b/src/fluent-bit/lib/monkey/plugins/logger/CMakeLists.txt @@ -0,0 +1,21 @@ +set(src + pointers.c + logger.c + ) + +# Validate splice() +check_c_source_compiles(" + #define _GNU_SOURCE + #include <stdio.h> + #include <fcntl.h> + int main() { + return splice(0, NULL, 1, + NULL, 1, SPLICE_F_MOVE); + }" HAVE_SPLICE) + +if (HAVE_SPLICE) + MK_DEFINITION(MK_HAVE_SPLICE) +endif() + +MONKEY_PLUGIN(logger "${src}") +add_subdirectory(conf) diff --git a/src/fluent-bit/lib/monkey/plugins/logger/conf/CMakeLists.txt b/src/fluent-bit/lib/monkey/plugins/logger/conf/CMakeLists.txt new file mode 100644 index 000000000..2c4e9b069 --- /dev/null +++ b/src/fluent-bit/lib/monkey/plugins/logger/conf/CMakeLists.txt @@ -0,0 +1,11 @@ +set(conf_dir "${MK_PATH_CONF}/plugins/logger/") + +install(DIRECTORY DESTINATION ${conf_dir}) +configure_file( + "${PROJECT_SOURCE_DIR}/plugins/logger/conf/logger.conf.in" + "${PROJECT_BINARY_DIR}/conf/plugins/logger/logger.conf" + ) + +if(NOT BUILD_LOCAL) + install(FILES ${PROJECT_BINARY_DIR}/conf/plugins/logger/logger.conf DESTINATION ${conf_dir}) +endif() diff --git a/src/fluent-bit/lib/monkey/plugins/logger/conf/logger.conf.in b/src/fluent-bit/lib/monkey/plugins/logger/conf/logger.conf.in new file mode 100644 index 000000000..5e06a8d6c --- /dev/null +++ b/src/fluent-bit/lib/monkey/plugins/logger/conf/logger.conf.in @@ -0,0 +1,22 @@ +# Logger: +# ------- +# This plugin allows the creation of log files for each request that arrives. +# It uses an access and error file which are defined inside every virtual +# host file, this section set just global directives for the plugin. + +[LOGGER] + # FlushTimeout + # ------------ + # This key define in seconds, the waiting time before to flush the + # data to the log file. + # Allowed values must be greater than zero (FlushTimeout > 0). + + FlushTimeout 3 + + # MasterLog + # --------- + # This key define a master log file which is used when Monkey runs in daemon + # mode, so any Monkey output (stdout) will be redirected to the file + # specified here. The server port will be appended to the filename. + + MasterLog @MK_PATH_LOG@/master.log diff --git a/src/fluent-bit/lib/monkey/plugins/logger/logger.c b/src/fluent-bit/lib/monkey/plugins/logger/logger.c new file mode 100644 index 000000000..4dfcfb001 --- /dev/null +++ b/src/fluent-bit/lib/monkey/plugins/logger/logger.c @@ -0,0 +1,852 @@ +/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ + +/* Monkey HTTP Server + * ================== + * Copyright 2001-2017 Eduardo Silva <eduardo@monkey.io> + * + * 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. + */ + +#define _GNU_SOURCE + +#include <monkey/mk_api.h> + +/* System Headers */ +#include <time.h> +#include <sys/ioctl.h> +#include <sys/types.h> +#include <sys/stat.h> + +/* Local Headers */ +#include "logger.h" +#include "pointers.h" + +struct status_response { + int i_status; + char *s_status; +}; + +static struct status_response response_codes[] = { + /* Common matches first */ + {200, "200"}, {404, "404"}, + + {100, "100"}, {101, "101"}, + {201, "201"}, {202, "202"}, {203, "203"}, {204, "204"}, + {205, "205"}, {206, "206"}, + {300, "300"}, {301, "301"}, {302, "302"}, {303, "303"}, {304, "304"}, + {305, "305"}, + {400, "400"}, {401, "401"}, {402, "402"}, {403, "403"}, + {405, "405"}, {406, "406"}, {407, "407"}, {408, "408"}, {409, "409"}, + {410, "410"}, {411, "411"}, {412, "412"}, {413, "413"}, {414, "414"}, + {415, "415"}, + {500, "500"}, {501, "501"}, {502, "502"}, {503, "503"}, {504, "504"}, + {505, "505"}, +}; + +static struct log_target *mk_logger_match_by_host(struct mk_vhost *host, int is_ok) +{ + struct mk_list *head; + struct log_target *entry; + + mk_list_foreach(head, &targets_list) { + entry = mk_list_entry(head, struct log_target, _head); + if (entry->host == host && entry->is_ok == is_ok) { + return entry; + } + } + + return NULL; +} + +static struct iov *mk_logger_get_cache() +{ + return pthread_getspecific(cache_iov); +} + +static ssize_t _mk_logger_append(int pipe_fd_in, + int file_fd_out, + size_t bytes) +{ + ssize_t ret; +#ifdef MK_HAVE_SPLICE + ret = splice(pipe_fd_in, NULL, file_fd_out, + NULL, bytes, SPLICE_F_MOVE); + return ret; +#else + unsigned char buffer[4096]; + ssize_t buffer_used; + size_t bytes_written = 0; + + while (bytes_written < bytes) { + ret = read(pipe_fd_in, buffer, sizeof(buffer)); + if (ret < 0) { + break; + } + buffer_used = ret; + ret = write(file_fd_out, buffer, buffer_used); + if (ret < 0) { + break; + } + bytes_written += ret; + } + if (ret < 0 && bytes_written == 0) + return -1; + else + return bytes_written; +#endif +} + +static void mk_logger_start_worker(void *args) +{ + int fd; + int bytes, err; + int max_events = mk_api->config->nhosts; + int flog; + int clk; + long slen; + int timeout; + char *target; + (void) args; + struct mk_list *head; + struct log_target *entry; + struct mk_event *event; + struct mk_event_loop *evl; + + /* pipe_size: + * ---------- + * Linux set a pipe size usingto the PAGE_SIZE, + * check linux/include/pipe_fs_i.h for details: + * + * #define PIPE_SIZE PAGE_SIZE + * + * In the same header file we can found that every + * pipe has 16 pages, so our real memory allocation + * is: (PAGE_SIZE*PIPE_BUFFERS) + */ + long pipe_size; + + /* buffer_limit: + * ------------- + * it means the maximum data that a monkey log pipe can contain. + */ + long buffer_limit; + + mk_api->worker_rename("monkey: logger"); + + /* Monkey allow just 75% of a pipe capacity */ + pipe_size = sysconf(_SC_PAGESIZE) * 16; + buffer_limit = (pipe_size * MK_LOGGER_PIPE_LIMIT); + + /* Creating poll */ + evl = mk_api->ev_loop_create(max_events); + + /* Registering targets for virtualhosts */ + mk_list_foreach(head, &targets_list) { + entry = mk_list_entry(head, struct log_target, _head); + event = &entry->event; + event->mask = MK_EVENT_EMPTY; + event->data = entry; + event->handler = NULL; + event->status = MK_EVENT_NONE; + + /* Add access log file */ + if (entry->pipe[0] > 0) { + event->fd = entry->pipe[0]; + mk_api->ev_add(evl, entry->pipe[0], + MK_EVENT_CONNECTION, MK_EVENT_READ, entry); + } + } + + /* Set initial timeout */ + timeout = time(NULL) + mk_logger_timeout; + + /* Reading pipe buffer */ + while (1) { + usleep(1200); + + /* wait for events */ + mk_api->ev_wait(evl); + + /* get current time */ + clk = mk_api->time_unix(); + + /* translate the backend events triggered */ + mk_event_foreach(event, evl) { + entry = (struct log_target *) event; + target = entry->file; + fd = entry->pipe[0]; + + err = ioctl(fd, FIONREAD, &bytes); + if (mk_unlikely(err == -1)){ + perror("ioctl"); + } + + if (bytes < buffer_limit && clk <= timeout) { + continue; + } + + timeout = clk + mk_logger_timeout; + + flog = open(target, O_WRONLY | O_CREAT | O_CLOEXEC, 0644); + if (mk_unlikely(flog == -1)) { + mk_warn("Could not open logfile '%s' (%s)", target, strerror(errno)); + + int consumed = 0; + char buf[255]; + do { + slen = read(fd, buf, 255); + if (slen > 0) { + consumed += slen; + } + else { + break; + } + } while (consumed < bytes); + + continue; + } + + lseek(flog, 0, SEEK_END); + slen = _mk_logger_append(fd, flog, bytes); + if (mk_unlikely(slen == -1)) { + mk_warn("Could not write to log file: splice() = %ld", slen); + } + + MK_TRACE("written %i bytes", bytes); + close(flog); + } + } +} + +static int mk_logger_read_config(char *path) +{ + int timeout; + char *logfilename = NULL; + unsigned long len; + char *default_file = NULL; + struct mk_rconf *conf; + struct mk_rconf_section *section; + + mk_api->str_build(&default_file, &len, "%slogger.conf", path); + conf = mk_api->config_open(default_file); + if (!conf) { + return -1; + } + + section = mk_api->config_section_get(conf, "LOGGER"); + if (section) { + + /* FlushTimeout */ + timeout = (size_t) mk_api->config_section_get_key(section, + "FlushTimeout", + MK_RCONF_NUM); + if (timeout <= 0) { + mk_err("FlushTimeout does not have a proper value"); + exit(EXIT_FAILURE); + } + mk_logger_timeout = timeout; + MK_TRACE("FlushTimeout %i seconds", mk_logger_timeout); + + /* MasterLog */ + logfilename = mk_api->config_section_get_key(section, + "MasterLog", + MK_RCONF_STR); + if (logfilename == NULL) { + mk_err("MasterLog does not have a proper value"); + exit(EXIT_FAILURE); + } + + mk_logger_master_path = logfilename; + MK_TRACE("MasterLog '%s'", mk_logger_master_path); + } + + mk_api->mem_free(default_file); + mk_api->config_free(conf); + + return 0; +} + +static void mk_logger_print_listeners() +{ + struct mk_list *head; + struct mk_config_listener *listener; + + mk_list_foreach(head, &mk_api->config->listeners) { + listener = mk_list_entry(head, struct mk_config_listener, _head); + printf(" listen on %s:%s\n", + listener->address, + listener->port); + } +} + +static void mk_logger_print_details(void) +{ + time_t now; + struct tm *current; + + now = time(NULL); + current = localtime(&now); + printf("[%i/%02i/%02i %02i:%02i:%02i] Monkey Started\n", + current->tm_year + 1900, + current->tm_mon + 1, + current->tm_mday, + current->tm_hour, + current->tm_min, + current->tm_sec); + printf(" version : %s\n", MK_VERSION_STR); + printf(" number of workers: %i\n", mk_api->config->workers); + mk_logger_print_listeners(); + fflush(stdout); +} + +int mk_logger_plugin_init(struct plugin_api **api, char *confdir) +{ + int fd; + mk_api = *api; + + /* Specific thread key */ + pthread_key_create(&cache_iov, NULL); + pthread_key_create(&cache_content_length, NULL); + pthread_key_create(&cache_status, NULL); + pthread_key_create(&cache_ip_str, NULL); + + /* Global configuration */ + mk_logger_timeout = MK_LOGGER_TIMEOUT_DEFAULT; + mk_logger_master_path = NULL; + mk_logger_read_config(confdir); + + /* Check masterlog */ + if (mk_logger_master_path) { + fd = open(mk_logger_master_path, O_WRONLY | O_CREAT | O_CLOEXEC, 0644); + if (fd == -1) { + mk_err("Could not open/create master logfile %s", mk_logger_master_path); + exit(EXIT_FAILURE); + + } + else { + /* Close test FD for MasterLog */ + close(fd); + } + } + + return 0; +} + +int mk_logger_plugin_exit() +{ + struct mk_list *head, *tmp; + struct log_target *entry; + + mk_list_foreach_safe(head, tmp, &targets_list) { + entry = mk_list_entry(head, struct log_target, _head); + mk_list_del(&entry->_head); + if (entry->pipe[0] > 0) close(entry->pipe[0]); + if (entry->pipe[1] > 0) close(entry->pipe[1]); + mk_api->mem_free(entry->file); + mk_api->mem_free(entry); + } + + mk_api->mem_free(mk_logger_master_path); + + return 0; +} + +int mk_logger_master_init(struct mk_server_config *config) +{ + int ret; + struct log_target *new; + struct mk_vhost *entry_host; + struct mk_list *hosts = &mk_api->config->hosts; + struct mk_list *head_host; + struct mk_rconf_section *section; + char *access_file_name = NULL; + char *error_file_name = NULL; + pthread_t tid; + (void) config; + + /* Restore STDOUT if we are in background mode */ + if (mk_logger_master_path != NULL && mk_api->config->is_daemon == MK_TRUE) { + mk_logger_master_stdout = freopen(mk_logger_master_path, "ae", stdout); + mk_logger_master_stderr = freopen(mk_logger_master_path, "ae", stderr); + mk_logger_print_details(); + } + + MK_TRACE("Reading virtual hosts"); + + mk_list_init(&targets_list); + + mk_list_foreach(head_host, hosts) { + entry_host = mk_list_entry(head_host, struct mk_vhost, _head); + + /* Read logger section from virtual host configuration */ + section = mk_api->config_section_get(entry_host->config, "LOGGER"); + if (section) { + /* Read configuration entries */ + access_file_name = (char *) mk_api->config_section_get_key(section, + "AccessLog", + MK_RCONF_STR); + error_file_name = (char *) mk_api->config_section_get_key(section, + "ErrorLog", + MK_RCONF_STR); + + if (access_file_name) { + new = mk_api->mem_alloc(sizeof(struct log_target)); + new->is_ok = MK_TRUE; + + /* Set access pipe */ + if (pipe(new->pipe) < 0) { + mk_err("Could not create pipe"); + exit(EXIT_FAILURE); + } + if (fcntl(new->pipe[1], F_SETFL, O_NONBLOCK) == -1) { + perror("fcntl"); + } + if (fcntl(new->pipe[0], F_SETFD, FD_CLOEXEC) == -1) { + perror("fcntl"); + } + if (fcntl(new->pipe[1], F_SETFD, FD_CLOEXEC) == -1) { + perror("fcntl"); + } + new->file = access_file_name; + new->host = entry_host; + mk_list_add(&new->_head, &targets_list); + } + + /* Set error pipe */ + if (error_file_name) { + new = mk_api->mem_alloc(sizeof(struct log_target)); + new->is_ok = MK_FALSE; + + if (pipe(new->pipe) < 0) { + mk_err("Could not create pipe"); + exit(EXIT_FAILURE); + } + if (fcntl(new->pipe[1], F_SETFL, O_NONBLOCK) == -1) { + perror("fcntl"); + } + if (fcntl(new->pipe[0], F_SETFD, FD_CLOEXEC) == -1) { + perror("fcntl"); + } + if (fcntl(new->pipe[1], F_SETFD, FD_CLOEXEC) == -1 ){ + perror("fcntl"); + } + new->file = error_file_name; + new->host = entry_host; + mk_list_add(&new->_head, &targets_list); + + } + } + } + + ret = mk_api->worker_spawn((void *) mk_logger_start_worker, NULL, &tid); + if (ret == -1) { + return -1; + } + + return 0; +} + +void mk_logger_worker_init() +{ + struct mk_iov *iov_log; + mk_ptr_t *content_length; + mk_ptr_t *status; + mk_ptr_t *ip_str; + + + MK_TRACE("Creating thread cache"); + + /* Cache iov log struct */ + iov_log = mk_api->iov_create(15, 0); + pthread_setspecific(cache_iov, (void *) iov_log); + + /* Cache content length */ + content_length = mk_api->mem_alloc_z(sizeof(mk_ptr_t)); + content_length->data = mk_api->mem_alloc_z(MK_UTILS_INT2MKP_BUFFER_LEN); + content_length->len = -1; + pthread_setspecific(cache_content_length, (void *) content_length); + + /* Cahe status */ + status = mk_api->mem_alloc_z(sizeof(mk_ptr_t)); + status->data = mk_api->mem_alloc_z(MK_UTILS_INT2MKP_BUFFER_LEN); + status->len = -1; + pthread_setspecific(cache_status, (void *) status); + + /* Cache IP address */ + ip_str = mk_api->mem_alloc_z(sizeof(mk_ptr_t)); + ip_str->data = mk_api->mem_alloc_z(INET6_ADDRSTRLEN + 1); + ip_str->len = -1; + pthread_setspecific(cache_ip_str, (void *) ip_str); +} + +int mk_logger_stage40(struct mk_http_session *cs, struct mk_http_request *sr) +{ + int i, http_status, ret, tmp; + int array_len = ARRAY_SIZE(response_codes); + int access; + struct log_target *target; + struct mk_iov *iov; + mk_ptr_t *date; + mk_ptr_t *content_length; + mk_ptr_t *ip_str; + mk_ptr_t status; + + /* Set response status */ + http_status = sr->headers.status; + + if (http_status < 400) { + access = MK_TRUE; + } + else { + access = MK_FALSE; + } + + /* Look for target log file */ + target = mk_logger_match_by_host(sr->host_conf, access); + if (!target) { + MK_TRACE("No target found"); + return 0; + } + + /* Get iov cache struct and reset indexes */ + iov = (struct mk_iov *) mk_logger_get_cache(); + iov->iov_idx = 0; + iov->buf_idx = 0; + iov->total_len = 0; + + /* Format IP string */ + ip_str = pthread_getspecific(cache_ip_str); + ret = mk_api->socket_ip_str(cs->socket, + &ip_str->data, + INET6_ADDRSTRLEN + 1, + &ip_str->len); + /* + * If the socket is not longer available ip_str can be null, + * so we must check this condition and return + */ + if (mk_unlikely(ret < 0)) { + return 0; + } + + /* Add IP to IOV */ + mk_api->iov_add(iov, + ip_str->data, ip_str->len, + MK_FALSE); + mk_api->iov_add(iov, + mk_logger_iov_dash.data, + mk_logger_iov_dash.len, + MK_FALSE); + + /* Date/time when object was requested */ + date = mk_api->time_human(cs->server); + mk_api->iov_add(iov, + date->data, date->len, + MK_FALSE); + mk_api->iov_add(iov, + mk_logger_iov_space.data, + mk_logger_iov_space.len, + MK_FALSE); + + /* Access Log */ + if (http_status < 400) { + /* No access file defined */ + if (!target->file) { + return 0; + } + + /* HTTP Method */ + mk_api->iov_add(iov, + sr->method_p.data, + sr->method_p.len, + MK_FALSE); + mk_api->iov_add(iov, + mk_logger_iov_space.data, + mk_logger_iov_space.len, + MK_FALSE); + + /* HTTP URI required */ + mk_api->iov_add(iov, + sr->uri.data, sr->uri.len, + MK_FALSE); + mk_api->iov_add(iov, + mk_logger_iov_space.data, + mk_logger_iov_space.len, + MK_FALSE); + + /* HTTP Protocol */ + mk_api->iov_add(iov, + sr->protocol_p.data, sr->protocol_p.len, + MK_FALSE); + mk_api->iov_add(iov, + mk_logger_iov_space.data, + mk_logger_iov_space.len, + MK_FALSE); + + /* HTTP Status code response */ + for (i=0; i < array_len; i++) { + if (response_codes[i].i_status == http_status) { + break; + } + } + + if (array_len == i) { + mk_api->str_itop(http_status, &status); + status.len -= 2; + } + else { + status.data = response_codes[i].s_status; + status.len = 3; + } + mk_api->iov_add(iov, + status.data, + status.len, + MK_FALSE); + mk_api->iov_add(iov, + mk_logger_iov_space.data, + mk_logger_iov_space.len, + MK_FALSE); + + /* Content Length */ + if (sr->method != MK_METHOD_HEAD) { + /* Int to mk_ptr_t */ + content_length = pthread_getspecific(cache_content_length); + + tmp = sr->headers.content_length; + if (tmp < 0) { + tmp = 0; + } + + mk_api->str_itop(tmp, content_length); + + mk_api->iov_add(iov, + content_length->data, content_length->len - 2, + MK_FALSE); + mk_api->iov_add(iov, + mk_logger_iov_lf.data, + mk_logger_iov_lf.len, + MK_FALSE); + } + else { + mk_api->iov_add(iov, + mk_logger_iov_empty.data, + mk_logger_iov_empty.len, + MK_FALSE); + mk_api->iov_add(iov, + mk_logger_iov_lf.data, + mk_logger_iov_lf.len, + MK_FALSE); + } + + /* Write iov array to pipe */ + mk_api->iov_send(target->pipe[1], iov); + } + else { + if (mk_unlikely(!target->file)) { + return 0; + } + + /* For unknown errors. Needs to exist until iov_send. */ + char err_str[80]; + + switch (http_status) { + case MK_CLIENT_BAD_REQUEST: + mk_api->iov_add(iov, + error_msg_400.data, + error_msg_400.len, + MK_FALSE); + mk_api->iov_add(iov, + mk_logger_iov_lf.data, + mk_logger_iov_lf.len, + MK_FALSE); + break; + case MK_CLIENT_FORBIDDEN: + mk_api->iov_add(iov, + error_msg_403.data, + error_msg_403.len, + MK_FALSE); + mk_api->iov_add(iov, + mk_logger_iov_space.data, + mk_logger_iov_space.len, + MK_FALSE); + mk_api->iov_add(iov, + sr->uri.data, + sr->uri.len, + MK_FALSE); + mk_api->iov_add(iov, + mk_logger_iov_lf.data, + mk_logger_iov_lf.len, + MK_FALSE); + + break; + case MK_CLIENT_NOT_FOUND: + mk_api->iov_add(iov, + error_msg_404.data, + error_msg_404.len, + MK_FALSE); + mk_api->iov_add(iov, + mk_logger_iov_space.data, + mk_logger_iov_space.len, + MK_FALSE); + + mk_api->iov_add(iov, + sr->uri.data, + sr->uri.len, + MK_FALSE); + mk_api->iov_add(iov, + mk_logger_iov_lf.data, + mk_logger_iov_lf.len, + MK_FALSE); + + break; + case MK_CLIENT_METHOD_NOT_ALLOWED: + mk_api->iov_add(iov, + error_msg_405.data, + error_msg_405.len, + MK_FALSE); + mk_api->iov_add(iov, + mk_logger_iov_space.data, + mk_logger_iov_space.len, + MK_FALSE); + mk_api->iov_add(iov, + sr->method_p.data, + sr->method_p.len, + MK_FALSE); + mk_api->iov_add(iov, + mk_logger_iov_lf.data, + mk_logger_iov_lf.len, + MK_FALSE); + break; + case MK_CLIENT_REQUEST_TIMEOUT: + mk_api->iov_add(iov, + error_msg_408.data, + error_msg_408.len, + MK_FALSE); + mk_api->iov_add(iov, + mk_logger_iov_lf.data, + mk_logger_iov_lf.len, + MK_FALSE); + break; + case MK_CLIENT_LENGTH_REQUIRED: + mk_api->iov_add(iov, + error_msg_411.data, + error_msg_411.len, + MK_FALSE); + mk_api->iov_add(iov, + mk_logger_iov_lf.data, + mk_logger_iov_lf.len, + MK_FALSE); + break; + case MK_CLIENT_REQUEST_ENTITY_TOO_LARGE: + mk_api->iov_add(iov, + error_msg_413.data, + error_msg_413.len, + MK_FALSE); + mk_api->iov_add(iov, + mk_logger_iov_lf.data, + mk_logger_iov_lf.len, + MK_FALSE); + break; + case MK_SERVER_NOT_IMPLEMENTED: + mk_api->iov_add(iov, + error_msg_501.data, + error_msg_501.len, + MK_FALSE); + mk_api->iov_add(iov, + mk_logger_iov_space.data, + mk_logger_iov_space.len, + MK_FALSE); + mk_api->iov_add(iov, + sr->method_p.data, + sr->method_p.len, + MK_FALSE); + mk_api->iov_add(iov, + mk_logger_iov_lf.data, + mk_logger_iov_lf.len, + MK_FALSE); + break; + case MK_SERVER_INTERNAL_ERROR: + mk_api->iov_add(iov, + error_msg_500.data, + error_msg_500.len, + MK_FALSE); + mk_api->iov_add(iov, + mk_logger_iov_space.data, + mk_logger_iov_space.len, + MK_FALSE); + break; + case MK_SERVER_HTTP_VERSION_UNSUP: + mk_api->iov_add(iov, + error_msg_505.data, + error_msg_505.len, + MK_FALSE); + mk_api->iov_add(iov, + mk_logger_iov_lf.data, + mk_logger_iov_lf.len, + MK_FALSE); + break; + default: + { + int len = snprintf(err_str, 80, "[error %u] (no description)", http_status); + err_str[79] = '\0'; + if (len > 79) len = 79; + + mk_api->iov_add(iov, + err_str, + len, + MK_FALSE); + mk_api->iov_add(iov, + mk_logger_iov_space.data, + mk_logger_iov_space.len, + MK_FALSE); + mk_api->iov_add(iov, + sr->uri.data, + sr->uri.len, + MK_FALSE); + mk_api->iov_add(iov, + mk_logger_iov_lf.data, + mk_logger_iov_lf.len, + MK_FALSE); + } + break; + } + + + /* Write iov array to pipe */ + mk_api->iov_send(target->pipe[1], iov); + } + + return 0; +} + +struct mk_plugin_stage mk_plugin_stage_logger = { + .stage40 = &mk_logger_stage40 +}; + +struct mk_plugin mk_plugin_logger = { + /* Identification */ + .shortname = "logger", + .name = "Log Writer", + .version = MK_VERSION_STR, + .hooks = MK_PLUGIN_STAGE, + + /* Init / Exit */ + .init_plugin = mk_logger_plugin_init, + .exit_plugin = mk_logger_plugin_exit, + + /* Init Levels */ + .master_init = mk_logger_master_init, + .worker_init = mk_logger_worker_init, + + /* Type */ + .stage = &mk_plugin_stage_logger +}; diff --git a/src/fluent-bit/lib/monkey/plugins/logger/logger.h b/src/fluent-bit/lib/monkey/plugins/logger/logger.h new file mode 100644 index 000000000..6c9668bf4 --- /dev/null +++ b/src/fluent-bit/lib/monkey/plugins/logger/logger.h @@ -0,0 +1,58 @@ +/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ + +/* Monkey HTTP Server + * ================== + * Copyright 2001-2017 Eduardo Silva <eduardo@monkey.io> + * + * 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. + */ + +/* s_log status */ +#ifndef MK_LOGGER_H +#define MK_LOGGER_H + +#include <stdio.h> +#include <monkey/mk_api.h> + +#define MK_LOGGER_PIPE_LIMIT 0.75 +#define MK_LOGGER_TIMEOUT_DEFAULT 3 + +int mk_logger_timeout; + +/* MasterLog variables */ +char *mk_logger_master_path; +FILE *mk_logger_master_stdout; +FILE *mk_logger_master_stderr; + +pthread_key_t cache_content_length; +pthread_key_t cache_status; +pthread_key_t cache_ip_str; +pthread_key_t cache_iov; + +struct log_target +{ + struct mk_event event; + + /* Pipes */ + int is_ok; + int pipe[2]; + char *file; + + struct mk_vhost *host; + struct mk_list _head; +}; + +struct mk_list targets_list; + + +#endif diff --git a/src/fluent-bit/lib/monkey/plugins/logger/pointers.c b/src/fluent-bit/lib/monkey/plugins/logger/pointers.c new file mode 100644 index 000000000..11c48906a --- /dev/null +++ b/src/fluent-bit/lib/monkey/plugins/logger/pointers.c @@ -0,0 +1,43 @@ +/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ + +/* Monkey HTTP Server + * ================== + * Copyright 2001-2017 Eduardo Silva <eduardo@monkey.io> + * + * 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 <monkey/mk_plugin.h> + +#include "logger.h" +#include "pointers.h" + +const mk_ptr_t mk_logger_iov_none = mk_ptr_init(""); + +/* Writter helpers */ +const mk_ptr_t mk_logger_iov_dash = mk_ptr_init(MK_LOGGER_IOV_DASH); +const mk_ptr_t mk_logger_iov_space = mk_ptr_init(MK_IOV_SPACE); +const mk_ptr_t mk_logger_iov_lf = mk_ptr_init(MK_IOV_LF); +const mk_ptr_t mk_logger_iov_empty = mk_ptr_init(MK_LOGGER_IOV_EMPTY); + +/* Error messages */ +const mk_ptr_t error_msg_400 = mk_ptr_init(ERROR_MSG_400); +const mk_ptr_t error_msg_403 = mk_ptr_init(ERROR_MSG_403); +const mk_ptr_t error_msg_404 = mk_ptr_init(ERROR_MSG_404); +const mk_ptr_t error_msg_405 = mk_ptr_init(ERROR_MSG_405); +const mk_ptr_t error_msg_408 = mk_ptr_init(ERROR_MSG_408); +const mk_ptr_t error_msg_411 = mk_ptr_init(ERROR_MSG_411); +const mk_ptr_t error_msg_413 = mk_ptr_init(ERROR_MSG_413); +const mk_ptr_t error_msg_500 = mk_ptr_init(ERROR_MSG_500); +const mk_ptr_t error_msg_501 = mk_ptr_init(ERROR_MSG_501); +const mk_ptr_t error_msg_505 = mk_ptr_init(ERROR_MSG_505); diff --git a/src/fluent-bit/lib/monkey/plugins/logger/pointers.h b/src/fluent-bit/lib/monkey/plugins/logger/pointers.h new file mode 100644 index 000000000..d353c2583 --- /dev/null +++ b/src/fluent-bit/lib/monkey/plugins/logger/pointers.h @@ -0,0 +1,59 @@ +/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ + +/* Monkey HTTP Server + * ================== + * Copyright 2001-2017 Eduardo Silva <eduardo@monkey.io> + * + * 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. + */ + +#ifndef MK_LOGGER_POINTERS_H +#define MK_LOGGER_POINTERS_H + +#include <memory.h> + +/* Request error messages for log file */ +#define ERROR_MSG_400 "[error 400] Bad Request" +#define ERROR_MSG_403 "[error 403] Forbidden" +#define ERROR_MSG_404 "[error 404] Not Found" +#define ERROR_MSG_405 "[error 405] Method Not Allowed" +#define ERROR_MSG_408 "[error 408] Request Timeout" +#define ERROR_MSG_411 "[error 411] Length Required" +#define ERROR_MSG_413 "[error 413] Request Entity Too Large" +#define ERROR_MSG_500 "[error 500] Internal Server Error" +#define ERROR_MSG_501 "[error 501] Not Implemented" +#define ERROR_MSG_505 "[error 505] HTTP Version Not Supported" + +#define MK_LOGGER_IOV_DASH " - " +#define MK_LOGGER_IOV_SPACE " " +#define MK_LOGGER_IOV_EMPTY "-" + +/* mk pointers for errors */ +extern const mk_ptr_t error_msg_400; +extern const mk_ptr_t error_msg_403; +extern const mk_ptr_t error_msg_404; +extern const mk_ptr_t error_msg_405; +extern const mk_ptr_t error_msg_408; +extern const mk_ptr_t error_msg_411; +extern const mk_ptr_t error_msg_413; +extern const mk_ptr_t error_msg_500; +extern const mk_ptr_t error_msg_501; +extern const mk_ptr_t error_msg_505; + +/* mk pointer for IOV */ +extern const mk_ptr_t mk_logger_iov_dash; +extern const mk_ptr_t mk_logger_iov_space; +extern const mk_ptr_t mk_logger_iov_lf; +extern const mk_ptr_t mk_logger_iov_empty; + +#endif diff --git a/src/fluent-bit/lib/monkey/plugins/mandril/CMakeLists.txt b/src/fluent-bit/lib/monkey/plugins/mandril/CMakeLists.txt new file mode 100644 index 000000000..ba2e95717 --- /dev/null +++ b/src/fluent-bit/lib/monkey/plugins/mandril/CMakeLists.txt @@ -0,0 +1,6 @@ +set(src + mandril.c + ) + +MONKEY_PLUGIN(mandril "${src}") +add_subdirectory(conf) diff --git a/src/fluent-bit/lib/monkey/plugins/mandril/conf/CMakeLists.txt b/src/fluent-bit/lib/monkey/plugins/mandril/conf/CMakeLists.txt new file mode 100644 index 000000000..44e95175c --- /dev/null +++ b/src/fluent-bit/lib/monkey/plugins/mandril/conf/CMakeLists.txt @@ -0,0 +1,9 @@ +set(conf_dir "${MK_PATH_CONF}/plugins/mandril/") + +install(DIRECTORY DESTINATION ${conf_dir}) + +if(BUILD_LOCAL) + file(COPY mandril.conf DESTINATION ${conf_dir}) +else() + install(FILES mandril.conf DESTINATION ${conf_dir}) +endif() diff --git a/src/fluent-bit/lib/monkey/plugins/mandril/conf/mandril.conf b/src/fluent-bit/lib/monkey/plugins/mandril/conf/mandril.conf new file mode 100644 index 000000000..efeaf5d9a --- /dev/null +++ b/src/fluent-bit/lib/monkey/plugins/mandril/conf/mandril.conf @@ -0,0 +1,57 @@ +# Monkey HTTP Daemon - Mandril +# ============================ +# Mandril Plugin provide security rules to be applied to the incomming +# connections. If the client is rejected by some rule, it will get the +# 403 Forbidden error status. +# +# It supports two restriction modes, by request URI and by IP (or network +# range), make sure all your rules are defined under the section [RULES]: +# +# a) Restriction by request URI: +# +# You can define multiple keywords to restrict a specific incoming +# request which hold that string. Check this example: +# +# [RULES] +# URL documents +# URL pictures +# URL /private +# +# b) Restriction by IP or network range: +# +# Multiple rules can be defined to deny the access to specific incoming +# clients: +# +# [RULES] +# IP 10.20.1.1/24 +# IP 192.168.3.150 +# +# In the first rule we are blocking a range of IPs from 10.20.1.1 to +# 10.20.1.255. In the second example just one specific IP address. +# +# It also supports denying hotlinking from other domains. +# +# c) +# +# [RULES] +# deny_hotlink /imgs +# +# This rule will prevent access to all files under /imgs if the +# request's Referer header is not from the same domain or its +# subdomains. +# If the Referer header is missing, the request will be accepted. +# +# You can mix the rules type under the [RULE] section, so the following example +# is totally valid: +# +# [RULES] +# URL documents +# URL pictures +# URL /private +# IP 10.20.1.1/24 +# IP 192.168.3.150 +# + +[RULES] + # IP 127.0.0.1 + # URL /imgs diff --git a/src/fluent-bit/lib/monkey/plugins/mandril/mandril.c b/src/fluent-bit/lib/monkey/plugins/mandril/mandril.c new file mode 100644 index 000000000..e8a988d08 --- /dev/null +++ b/src/fluent-bit/lib/monkey/plugins/mandril/mandril.c @@ -0,0 +1,403 @@ +/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ + +/* Monkey HTTP Server + * ================== + * Copyright 2001-2017 Eduardo Silva <eduardo@monkey.io> + * Copyright 2012, Sonny Karlsson + * + * 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. + */ + +/* Monkey API */ +#include <monkey/mk_api.h> + + +/* system */ +#include <sys/socket.h> +#include <netinet/in.h> +#include <arpa/inet.h> +#include <sys/stat.h> + +#include "mandril.h" + +static struct mk_rconf *conf; + +/* Read database configuration parameters */ +static int mk_security_conf(struct mk_plugin *plugin, char *confdir) +{ + int n; + int ret = 0; + unsigned long len; + char *conf_path = NULL; + char *_net, *_mask; + + struct mk_secure_ip_t *new_ip; + struct mk_secure_url_t *new_url; + struct mk_secure_deny_hotlink_t *new_deny_hotlink; + + struct mk_rconf_section *section; + struct mk_rconf_entry *entry; + struct mk_list *head; + + /* Read configuration */ + plugin->api->str_build(&conf_path, &len, "%s/mandril.conf", confdir); + conf = plugin->api->config_open(conf_path); + if (!conf) { + return -1; + } + + section = plugin->api->config_section_get(conf, "RULES"); + if (!section) { + return -1; + } + + mk_list_foreach(head, §ion->entries) { + entry = mk_list_entry(head, struct mk_rconf_entry, _head); + + /* Passing to internal struct */ + if (strcasecmp(entry->key, "IP") == 0) { + new_ip = plugin->api->mem_alloc(sizeof(struct mk_secure_ip_t)); + n = plugin->api->str_search(entry->val, "/", 1); + + /* subnet */ + if (n > 0) { + /* split network addr and netmask */ + _net = plugin->api->str_copy_substr(entry->val, 0, n); + _mask = plugin->api->str_copy_substr(entry->val, + n + 1, + strlen(entry->val)); + + /* validations... */ + if (!_net || !_mask) { + mk_warn_ex(plugin->api, + "Mandril: cannot parse entry '%s' in RULES section", + entry->val); + goto ip_next; + } + + /* convert ip string to network address */ + if (inet_aton(_net, &new_ip->ip) == 0) { + mk_warn_ex(plugin->api, + "Mandril: invalid ip address '%s' in RULES section", + entry->val); + goto ip_next; + } + + /* parse mask */ + new_ip->netmask = strtol(_mask, (char **) NULL, 10); + if (new_ip->netmask <= 0 || new_ip->netmask >= 32) { + mk_warn_ex(plugin->api, + "Mandril: invalid mask value '%s' in RULES section", + entry->val); + goto ip_next; + } + + /* complete struct data */ + new_ip->is_subnet = MK_TRUE; + new_ip->network = MK_NET_NETWORK(new_ip->ip.s_addr, new_ip->netmask); + new_ip->hostmin = MK_NET_HOSTMIN(new_ip->ip.s_addr, new_ip->netmask); + new_ip->hostmax = MK_NET_HOSTMAX(new_ip->ip.s_addr, new_ip->netmask); + + /* link node with main list */ + mk_list_add(&new_ip->_head, &mk_secure_ip); + + /* + * I know, you were instructed to hate 'goto' statements!, ok, show this + * code to your teacher and let him blame :P + */ + ip_next: + if (_net) { + plugin->api->mem_free(_net); + } + if (_mask) { + plugin->api->mem_free(_mask); + } + } + else { /* normal IP address */ + + /* convert ip string to network address */ + if (inet_aton(entry->val, &new_ip->ip) == 0) { + mk_warn_ex(plugin->api, + "Mandril: invalid ip address '%s' in RULES section", + entry->val); + } + else { + new_ip->is_subnet = MK_FALSE; + mk_list_add(&new_ip->_head, &mk_secure_ip); + } + } + } + else if (strcasecmp(entry->key, "URL") == 0) { + /* simple allcotion and data association */ + new_url = plugin->api->mem_alloc(sizeof(struct mk_secure_url_t)); + new_url->criteria = entry->val; + + /* link node with main list */ + mk_list_add(&new_url->_head, &mk_secure_url); + } + else if (strcasecmp(entry->key, "deny_hotlink") == 0) { + new_deny_hotlink = plugin->api->mem_alloc(sizeof(*new_deny_hotlink)); + new_deny_hotlink->criteria = entry->val; + + mk_list_add(&new_deny_hotlink->_head, &mk_secure_deny_hotlink); + } + } + + plugin->api->mem_free(conf_path); + + return ret; +} + +static int mk_security_check_ip(int socket) +{ + int network; + struct mk_secure_ip_t *entry; + struct mk_list *head; + struct in_addr *addr; + struct sockaddr_in addr_t = {0}; + socklen_t len = sizeof(addr_t); + + if (getpeername(socket, (struct sockaddr *) &addr_t, &len) != 0) { + perror("getpeername"); + return -1; + } + + addr = &(addr_t).sin_addr; + + PLUGIN_TRACE("[FD %i] Mandril validating IP address", socket); + mk_list_foreach(head, &mk_secure_ip) { + entry = mk_list_entry(head, struct mk_secure_ip_t, _head); + + if (entry->is_subnet == MK_TRUE) { + /* Validate network */ + network = MK_NET_NETWORK(addr->s_addr, entry->netmask); + if (network != entry->network) { + continue; + } + /* Validate host range */ + if (addr->s_addr <= entry->hostmax && addr->s_addr >= entry->hostmin) { + PLUGIN_TRACE("[FD %i] Mandril closing by rule in ranges", socket); + return -1; + } + } + else { + if (addr->s_addr == entry->ip.s_addr) { + PLUGIN_TRACE("[FD %i] Mandril closing by rule in IP match", socket); + return -1; + } + } + } + return 0; +} + +/* Check if the incoming URL is restricted for some rule */ +static int mk_security_check_url(struct mk_plugin *plugin, mk_ptr_t url) +{ + int n; + struct mk_list *head; + struct mk_secure_url_t *entry; + + mk_list_foreach(head, &mk_secure_url) { + entry = mk_list_entry(head, struct mk_secure_url_t, _head); + n = plugin->api->str_search_n(url.data, entry->criteria, MK_STR_INSENSITIVE, url.len); + if (n >= 0) { + return -1; + } + } + + return 0; +} + +mk_ptr_t parse_referer_host(struct mk_http_header *header) +{ + unsigned int i, beginHost, endHost; + mk_ptr_t host; + + host.data = NULL; + host.len = 0; + + // Find end of "protocol://" + for (i = 0; i < header->val.len && !(header->val.data[i] == '/' && header->val.data[i+1] == '/'); i++); + if (i == header->val.len) { + goto error; + } + beginHost = i + 2; + + // Find end of any "user:password@" + for (; i < header->val.len && header->val.data[i] != '@'; i++); + if (i < header->val.len) { + beginHost = i + 1; + } + + // Find end of "host", (beginning of :port or /path) + for (i = beginHost; i < header->val.len && header->val.data[i] != ':' && header->val.data[i] != '/'; i++); + endHost = i; + + host.data = header->val.data + beginHost; + host.len = endHost - beginHost; + return host; +error: + host.data = NULL; + host.len = 0; + return host; +} + +static int mk_security_check_hotlink(struct mk_plugin *plugin, + mk_ptr_t url, mk_ptr_t host, + struct mk_http_header *referer) +{ + mk_ptr_t ref_host = parse_referer_host(referer); + unsigned int domains_matched = 0; + int i = 0; + const char *curA, *curB; + struct mk_list *head; + struct mk_secure_deny_hotlink_t *entry; + + if (ref_host.data == NULL) { + return 0; + } + else if (host.data == NULL) { + mk_err_ex(plugin->api, "No host data."); + return -1; + } + + mk_list_foreach(head, &mk_secure_url) { + entry = mk_list_entry(head, struct mk_secure_deny_hotlink_t, _head); + i = plugin->api->str_search_n(url.data, entry->criteria, MK_STR_INSENSITIVE, url.len); + if (i >= 0) { + break; + } + } + if (i < 0) { + return 0; + } + + curA = host.data + host.len; + curB = ref_host.data + ref_host.len; + + // Match backwards from root domain. + while (curA > host.data && curB > ref_host.data) { + i++; + curA--; + curB--; + + if ((*curA == '.' && *curB == '.') || + curA == host.data || curB == ref_host.data) { + if (i < 1) { + break; + } + else if (curA == host.data && + !(curB == ref_host.data || *(curB - 1) == '.')) { + break; + } + else if (curB == ref_host.data && + !(curA == host.data || *(curA - 1) == '.')) { + break; + } + else if (strncasecmp(curA, curB, i)) { + break; + } + domains_matched += 1; + i = 0; + } + } + + // Block connection if none or only top domain matched. + return domains_matched >= 2 ? 0 : -1; +} + +int mk_mandril_plugin_init(struct mk_plugin *plugin, char *confdir) +{ + /* Init security lists */ + mk_list_init(&mk_secure_ip); + mk_list_init(&mk_secure_url); + mk_list_init(&mk_secure_deny_hotlink); + + /* Read configuration */ + mk_security_conf(plugin, confdir); + + return 0; +} + +int mk_mandril_plugin_exit() +{ + return 0; +} + +int mk_mandril_stage10(int socket) +{ + /* Validate ip address with Mandril rules */ + if (mk_security_check_ip(socket) != 0) { + PLUGIN_TRACE("[FD %i] Mandril close connection", socket); + return MK_PLUGIN_RET_CLOSE_CONX; + } + + return MK_PLUGIN_RET_CONTINUE; +} + +int mk_mandril_stage30(struct mk_plugin *p, + struct mk_http_session *cs, + struct mk_http_request *sr, + int n_params, + struct mk_list *params) +{ + (void) p; + (void) cs; + (void) n_params; + (void) params; + + struct mk_http_header *header; + + PLUGIN_TRACE("[FD %i] Mandril validating URL", cs->socket); + + if (mk_security_check_url(p, sr->uri_processed) < 0) { + PLUGIN_TRACE("[FD %i] Close connection, blocked URL", cs->socket); + p->api->header_set_http_status(sr, MK_CLIENT_FORBIDDEN); + return MK_PLUGIN_RET_CLOSE_CONX; + } + + PLUGIN_TRACE("[FD %d] Mandril validating hotlinking", cs->socket); + + header = p->api->header_get(MK_HEADER_REFERER, sr, NULL, 0); + if (mk_security_check_hotlink(p, sr->uri_processed, sr->host, header) < 0) { + PLUGIN_TRACE("[FD %i] Close connection, deny hotlinking.", cs->socket); + p->api->header_set_http_status(sr, MK_CLIENT_FORBIDDEN); + return MK_PLUGIN_RET_CLOSE_CONX; + } + + return MK_PLUGIN_RET_NOT_ME; +} + +struct mk_plugin_stage mk_plugin_stage_mandril = { + .stage10 = &mk_mandril_stage10, + .stage30 = &mk_mandril_stage30 +}; + +struct mk_plugin mk_plugin_mandril = { + /* Identification */ + .shortname = "mandril", + .name = "Mandril Security", + .version = MK_VERSION_STR, + .hooks = MK_PLUGIN_STAGE, + + /* Init / Exit */ + .init_plugin = mk_mandril_plugin_init, + .exit_plugin = mk_mandril_plugin_exit, + + /* Init Levels */ + .master_init = NULL, + .worker_init = NULL, + + /* Type */ + .stage = &mk_plugin_stage_mandril +}; diff --git a/src/fluent-bit/lib/monkey/plugins/mandril/mandril.h b/src/fluent-bit/lib/monkey/plugins/mandril/mandril.h new file mode 100644 index 000000000..6cadf3857 --- /dev/null +++ b/src/fluent-bit/lib/monkey/plugins/mandril/mandril.h @@ -0,0 +1,57 @@ +/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ + +/* Monkey HTTP Server + * ================== + * Copyright 2001-2017 Eduardo Silva <eduardo@monkey.io> + * Copyright 2012, Sonny Karlsson + * + * 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. + */ + +/* security.c */ +#ifndef MK_SECURITY_H +#define MK_SECURITY_H + +struct mk_secure_ip_t +{ + struct in_addr ip; + + /* if subnet is true, next fields are populated */ + int is_subnet; + + int network; + int netmask; + unsigned int hostmin; + unsigned int hostmax; + + /* list head linker */ + struct mk_list _head; +}; + +struct mk_secure_url_t +{ + char *criteria; + struct mk_list _head; +}; + +struct mk_secure_deny_hotlink_t +{ + char *criteria; + struct mk_list _head; +}; + +struct mk_list mk_secure_ip; +struct mk_list mk_secure_url; +struct mk_list mk_secure_deny_hotlink; + +#endif diff --git a/src/fluent-bit/lib/monkey/plugins/tls/CMakeLists.txt b/src/fluent-bit/lib/monkey/plugins/tls/CMakeLists.txt new file mode 100644 index 000000000..2bde86cac --- /dev/null +++ b/src/fluent-bit/lib/monkey/plugins/tls/CMakeLists.txt @@ -0,0 +1,17 @@ +set(src + tls.c +) + +if(NOT WITH_MBEDTLS_SHARED) + option(ENABLE_TESTING OFF) + option(ENABLE_PROGRAMS OFF) + option(INSTALL_MBEDTLS_HEADERS OFF) + set(MK_MBEDTLS_SRC ../../deps/mbedtls-2.4.2) + add_subdirectory(${MK_MBEDTLS_SRC} ${CMAKE_BINARY_DIR}/mbedtls-2.4.2) + include_directories(${MK_MBEDTLS_SRC}/include) +endif() + +MONKEY_PLUGIN(tls "${src}") + +MONKEY_PLUGIN_LINK_LIB(tls mbedtls) +add_subdirectory(conf) diff --git a/src/fluent-bit/lib/monkey/plugins/tls/conf/CMakeLists.txt b/src/fluent-bit/lib/monkey/plugins/tls/conf/CMakeLists.txt new file mode 100644 index 000000000..c1886d340 --- /dev/null +++ b/src/fluent-bit/lib/monkey/plugins/tls/conf/CMakeLists.txt @@ -0,0 +1,9 @@ +set(conf_dir "${MK_PATH_CONF}/plugins/tls/") + +install(DIRECTORY DESTINATION ${conf_dir}) + +if(BUILD_LOCAL) + file(COPY tls.conf DESTINATION ${conf_dir}) +else() + install(FILES tls.conf DESTINATION ${conf_dir}) +endif() diff --git a/src/fluent-bit/lib/monkey/plugins/tls/conf/tls.conf b/src/fluent-bit/lib/monkey/plugins/tls/conf/tls.conf new file mode 100644 index 000000000..fcf00517a --- /dev/null +++ b/src/fluent-bit/lib/monkey/plugins/tls/conf/tls.conf @@ -0,0 +1,25 @@ +[TLS] + # If no absolute path is given, files are assumed to be located + # in plugin configuration directory. All files are assumed to be + # stored in PEM format. + + # Server certificate + # + CertificateFile srv_cert.pem + + # Certificate chain + # + # Not required, but can speed-up handshakes. + # + # CertificateChainFile srv_cert_chain.pem + + # Server RSA key + # + RSAKeyFile rsa_key.pem + + # Diffie-Hellman parameters + # + # Generate using openssl: + # $ openssl dhparam -out dhparam.pem 1024 + # + DHParameterFile dhparam.pem diff --git a/src/fluent-bit/lib/monkey/plugins/tls/tls.c b/src/fluent-bit/lib/monkey/plugins/tls/tls.c new file mode 100644 index 000000000..94c2afc58 --- /dev/null +++ b/src/fluent-bit/lib/monkey/plugins/tls/tls.c @@ -0,0 +1,862 @@ +/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ + +/* Monkey HTTP Server + * ================== + * Copyright 2001-2017 Eduardo Silva <eduardo@monkey.io> + * + * 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. + */ + +#define _GNU_SOURCE + +#include <stdio.h> +#include <errno.h> +#include <assert.h> +#include <unistd.h> + +#include <arpa/inet.h> +#include <sys/types.h> +#include <sys/socket.h> +#include <netdb.h> +#include <pthread.h> + +#include <mbedtls/version.h> +#include <mbedtls/error.h> +#include <mbedtls/net.h> +#include <mbedtls/ssl.h> +#include <mbedtls/bignum.h> +#include <mbedtls/entropy.h> +#include <mbedtls/ctr_drbg.h> +#include <mbedtls/certs.h> +#include <mbedtls/x509.h> +#include <mbedtls/ssl_cache.h> +#include <mbedtls/pk.h> +#include <mbedtls/dhm.h> +#include <monkey/mk_api.h> + +#ifndef SENDFILE_BUF_SIZE +#define SENDFILE_BUF_SIZE MBEDTLS_SSL_MAX_CONTENT_LEN +#endif + +#ifndef POLAR_DEBUG_LEVEL +#define POLAR_DEBUG_LEVEL 0 +#endif + +#if (!defined(MBEDTLS_BIGNUM_C) || !defined(MBEDTLS_ENTROPY_C) || \ + !defined(MBEDTLS_SSL_TLS_C) || !defined(MBEDTLS_SSL_SRV_C) || \ + !defined(MBEDTLS_NET_C) || !defined(MBEDTLS_RSA_C) || \ + !defined(MBEDTLS_CTR_DRBG_C)) +#error "One or more required POLARSSL modules not built." +#endif + +struct polar_config { + char *cert_file; + char *cert_chain_file; + char *key_file; + char *dh_param_file; + int8_t check_client_cert; +}; + +#if defined(MBEDTLS_SSL_CACHE_C) +struct polar_sessions { + pthread_mutex_t _mutex; + mbedtls_ssl_cache_context cache; +}; + +static struct polar_sessions global_sessions = { + ._mutex = PTHREAD_MUTEX_INITIALIZER, +}; + +#endif + +struct polar_context_head { + mbedtls_ssl_context context; + int fd; + struct polar_context_head *_next; +}; + +struct polar_thread_context { + + struct polar_context_head *contexts; + mbedtls_ctr_drbg_context ctr_drbg; + mbedtls_pk_context pkey; + mbedtls_ssl_config conf; + + struct mk_list _head; +}; + +struct polar_server_context { + + struct polar_config config; + mbedtls_x509_crt cert; + mbedtls_x509_crt ca_cert; + pthread_mutex_t mutex; + mbedtls_dhm_context dhm; + mbedtls_entropy_context entropy; + struct polar_thread_context threads; +}; + +struct polar_server_context *server_context; +static const char *my_dhm_P = MBEDTLS_DHM_RFC5114_MODP_2048_P; +static const char *my_dhm_G = MBEDTLS_DHM_RFC5114_MODP_2048_G; + +static pthread_key_t local_context; + +/* + * The following function is taken from PolarSSL sources to get + * the number of available bytes to read from a buffer. + * + * We copy this to make it inline and avoid extra context switches + * on each read routine. + */ +static inline size_t polar_get_bytes_avail(const mbedtls_ssl_context *ssl) +{ + return (ssl->in_offt == NULL ? 0 : ssl->in_msglen); +} + +static struct polar_thread_context *local_thread_context(void) +{ + return pthread_getspecific(local_context); +} + +#if (POLAR_DEBUG_LEVEL > 0) +static void polar_debug(void *ctx, int level, const char *str) +{ + (void)ctx; + + if (level < POLAR_DEBUG_LEVEL) { + mk_warn("%.*s", (int)strlen(str) - 1, str); + } +} +#endif + +static int handle_return(int ret) +{ +#if defined(TRACE) + char err_buf[72]; + if (ret < 0) { + mbedtls_strerror(ret, err_buf, sizeof(err_buf)); + PLUGIN_TRACE("[tls] SSL error: %s", err_buf); + } +#endif + if (ret < 0) { + switch( ret ) + { + case MBEDTLS_ERR_SSL_WANT_READ: + case MBEDTLS_ERR_SSL_WANT_WRITE: + if (errno != EAGAIN) + errno = EAGAIN; + return -1; + case MBEDTLS_ERR_SSL_CONN_EOF: + return 0; + default: + if (errno == EAGAIN) + errno = 0; + return -1; + } + } + else { + return ret; + } +} + +static int tls_cache_get(void *p, mbedtls_ssl_session *session) +{ + struct polar_sessions *session_cache; + int ret; + + session_cache = p; + pthread_mutex_lock(&session_cache->_mutex); + ret = mbedtls_ssl_cache_get(&session_cache->cache, session); + pthread_mutex_unlock(&session_cache->_mutex); + + return ret; +} + +static int tls_cache_set(void *p, const mbedtls_ssl_session *session) +{ + struct polar_sessions *session_cache; + int ret; + + session_cache = p; + pthread_mutex_lock(&session_cache->_mutex); + ret = mbedtls_ssl_cache_set(&session_cache->cache, session); + pthread_mutex_unlock(&session_cache->_mutex); + + return ret; +} + +static int config_parse(const char *confdir, struct polar_config *conf) +{ + long unsigned int len; + char *conf_path = NULL; + char *cert_file = NULL; + char *cert_chain_file = NULL; + char *key_file = NULL; + char *dh_param_file = NULL; + int8_t check_client_cert = MK_FALSE; + struct mk_rconf_section *section; + struct mk_rconf *conf_head; + + mk_api->str_build(&conf_path, &len, "%stls.conf", confdir); + conf_head = mk_api->config_open(conf_path); + mk_api->mem_free(conf_path); + + if (conf_head == NULL) { + goto fallback; + } + + section = mk_api->config_section_get(conf_head, "TLS"); + if (!section) { + goto fallback; + } + + cert_file = mk_api->config_section_get_key(section, + "CertificateFile", + MK_RCONF_STR); + cert_chain_file = mk_api->config_section_get_key(section, + "CertificateChainFile", + MK_RCONF_STR); + key_file = mk_api->config_section_get_key(section, + "RSAKeyFile", + MK_RCONF_STR); + dh_param_file = mk_api->config_section_get_key(section, + "DHParameterFile", + MK_RCONF_STR); + + check_client_cert = mk_api->config_section_get_key(section, + "CheckClientCert", + MK_RCONF_BOOL); +fallback: + /* Set default name if not specified */ + if (!cert_file) { + mk_api->str_build(&conf->cert_file, &len, + "%ssrv_cert.pem", confdir); + } + else { + /* Set absolute path or compose a new one based on the relative */ + if (*cert_file == '/') { + conf->cert_file = cert_file; + } + else { + mk_api->str_build(&conf->cert_file, &len, + "%s/%s", confdir, cert_file); + } + } + + /* Set default name if not specified */ + if (cert_chain_file) { + /* Set absolute path or compose a new one based on the relative */ + if (*cert_chain_file == '/') { + conf->cert_chain_file = cert_chain_file; + } + else { + mk_api->str_build(&conf->cert_chain_file, &len, + "%s/%s", confdir, cert_chain_file); + } + } + else { + conf->cert_chain_file = NULL; + } + + /* Set default name if not specified */ + if (!key_file) { + mk_api->str_build(&conf->key_file, &len, + "%srsa.pem", confdir); + } + else { + /* Set absolute path or compose a new one based on the relative */ + if (*key_file == '/') { + conf->key_file = key_file; + } + else { + mk_api->str_build(&conf->key_file, &len, + "%s/%s", confdir, key_file); + } + } + + /* Set default name if not specified */ + if (!dh_param_file) { + mk_api->str_build(&conf->dh_param_file, &len, + "%sdhparam.pem", confdir); + } + else { + /* Set absolute path or compose a new one based on the relative */ + if (*dh_param_file == '/') { + conf->dh_param_file = dh_param_file; + } + else { + mk_api->str_build(&conf->dh_param_file, &len, + "%s/%s", confdir, dh_param_file); + } + } + + /* Set client cert check */ + conf->check_client_cert = check_client_cert; + + if (conf_head) { + mk_api->config_free(conf_head); + } + + return 0; +} + +static int polar_load_certs(const struct polar_config *conf) +{ + char err_buf[72]; + int ret = -1; + + ret = mbedtls_x509_crt_parse_file(&server_context->cert, conf->cert_file); + if (ret < 0) { + mbedtls_strerror(ret, err_buf, sizeof(err_buf)); + mk_warn("[tls] Load cert '%s' failed: %s", + conf->cert_file, + err_buf); + +#if defined(MBEDTLS_CERTS_C) + mk_warn("[tls] Using test certificates, " + "please set 'CertificateFile' in tls.conf"); + + ret = mbedtls_x509_crt_parse(&server_context->cert, + (unsigned char *)mbedtls_test_srv_crt, strlen(mbedtls_test_srv_crt)); + + if (ret) { + mbedtls_strerror(ret, err_buf, sizeof(err_buf)); + mk_warn("[tls] Load built-in cert failed: %s", err_buf); + return -1; + } + + return 0; +#else + return -1; +#endif // defined(MBEDTLS_CERTS_C) + } + else if (conf->cert_chain_file != NULL) { + ret = mbedtls_x509_crt_parse_file(&server_context->ca_cert, + conf->cert_chain_file); + + if (ret) { + mbedtls_strerror(ret, err_buf, sizeof(err_buf)); + mk_warn("[tls] Load cert chain '%s' failed: %s", + conf->cert_chain_file, + err_buf); + } + } + + return 0; +} + +static int polar_load_key(struct polar_thread_context *thread_context, + const struct polar_config *conf) +{ + char err_buf[72]; + int ret; + + assert(conf->key_file); + + ret = mbedtls_pk_parse_keyfile(&thread_context->pkey, conf->key_file, NULL); + if (ret < 0) { + mbedtls_strerror(ret, err_buf, sizeof(err_buf)); + MK_TRACE("[tls] Load key '%s' failed: %s", + conf->key_file, + err_buf); + +#if defined(MBEDTLS_CERTS_C) + + ret = mbedtls_pk_parse_key(&thread_context->pkey, + (unsigned char *)mbedtls_test_srv_key, + strlen(mbedtls_test_srv_key), NULL, 0); + if (ret) { + mbedtls_strerror(ret, err_buf, sizeof(err_buf)); + mk_err("[tls] Failed to load built-in RSA key: %s", err_buf); + return -1; + } +#else + return -1; +#endif // defined(MBEDTLS_CERTS_C) + } + return 0; +} + +static int polar_load_dh_param(const struct polar_config *conf) +{ + char err_buf[72]; + int ret; + + assert(conf->dh_param_file); + + ret = mbedtls_dhm_parse_dhmfile(&server_context->dhm, conf->dh_param_file); + if (ret < 0) { + mbedtls_strerror(ret, err_buf, sizeof(err_buf)); + + ret = mbedtls_mpi_read_string(&server_context->dhm.P, 16, my_dhm_P); + if (ret < 0) { + mbedtls_strerror(ret, err_buf, sizeof(err_buf)); + mk_err("[tls] Load DH parameter failed: %s", err_buf); + return -1; + } + ret = mbedtls_mpi_read_string(&server_context->dhm.G, 16, my_dhm_G); + if (ret < 0) { + mbedtls_strerror(ret, err_buf, sizeof(err_buf)); + mk_err("[tls] Load DH parameter failed: %s", err_buf); + return -1; + } + } + + return 0; +} + +static int mk_tls_init() +{ + pthread_key_create(&local_context, NULL); + +#if defined(MBEDTLS_SSL_CACHE_C) + mbedtls_ssl_cache_init(&global_sessions.cache); +#endif + + pthread_mutex_lock(&server_context->mutex); + mk_list_init(&server_context->threads._head); + mbedtls_entropy_init(&server_context->entropy); + pthread_mutex_unlock(&server_context->mutex); + + PLUGIN_TRACE("[tls] Load certificates."); + if (polar_load_certs(&server_context->config)) { + return -1; + } + PLUGIN_TRACE("[tls] Load DH parameters."); + if (polar_load_dh_param(&server_context->config)) { + return -1; + } + + return 0; +} + + +static int entropy_func_safe(void *data, unsigned char *output, size_t len) +{ + int ret; + + pthread_mutex_lock(&server_context->mutex); + ret = mbedtls_entropy_func(data, output, len); + pthread_mutex_unlock(&server_context->mutex); + + return ret; +} + +static void contexts_free(struct polar_context_head *ctx) +{ + struct polar_context_head *cur, *next; + + if (ctx != NULL) { + cur = ctx; + next = cur->_next; + + for (; next; cur = next, next = next->_next) { + mbedtls_ssl_free(&cur->context); + memset(cur, 0, sizeof(*cur)); + mk_api->mem_free(cur); + } + + mbedtls_ssl_free(&cur->context); + memset(cur, 0, sizeof(*cur)); + mk_api->mem_free(cur); + } +} + +static void config_free(struct polar_config *conf) +{ + if (conf->cert_file) mk_api->mem_free(conf->cert_file); + if (conf->cert_chain_file) mk_api->mem_free(conf->cert_chain_file); + if (conf->key_file) mk_api->mem_free(conf->key_file); + if (conf->dh_param_file) mk_api->mem_free(conf->dh_param_file); +} + +/* Contexts may be requested from outside workers on exit so we should + * be prepared for an empty context. + */ +static mbedtls_ssl_context *context_get(int fd) +{ + struct polar_thread_context *thctx = local_thread_context(); + struct polar_context_head **cur = &thctx->contexts; + + if (cur == NULL) { + return NULL; + } + + for (; *cur; cur = &(*cur)->_next) { + if ((*cur)->fd == fd) { + return &(*cur)->context; + } + } + + return NULL; +} + +static mbedtls_ssl_context *context_new(int fd) +{ + struct polar_thread_context *thctx = local_thread_context(); + struct polar_context_head **cur = &thctx->contexts; + mbedtls_ssl_context *ssl = NULL; + mbedtls_ssl_cache_context cache; + + mbedtls_ssl_cache_init(&cache); + + assert(cur != NULL); + + for (; *cur; cur = &(*cur)->_next) { + if ((*cur)->fd == -1) { + break; + } + } + + if (*cur == NULL) { + PLUGIN_TRACE("[polarssl %d] New ssl context.", fd); + + *cur = mk_api->mem_alloc(sizeof(**cur)); + if (*cur == NULL) { + return NULL; + } + (*cur)->_next = NULL; + + ssl = &(*cur)->context; + + mbedtls_ssl_init(ssl); + mbedtls_ssl_setup(ssl, &thctx->conf); + + mbedtls_ssl_conf_session_cache(&thctx->conf, + &global_sessions, + tls_cache_get, + tls_cache_set); + + mbedtls_ssl_set_bio(ssl, &(*cur)->fd, + mbedtls_net_send, mbedtls_net_recv, NULL); + + mbedtls_ssl_conf_rng(&thctx->conf, mbedtls_ctr_drbg_random, + &thctx->ctr_drbg); + +#if (POLAR_DEBUG_LEVEL > 0) + mbedtls_ssl_conf_dbg(ssl, polar_debug, 0); +#endif + + mbedtls_ssl_conf_own_cert(&thctx->conf, &server_context->cert, &thctx->pkey); + mbedtls_ssl_conf_ca_chain(&thctx->conf, &server_context->ca_cert, NULL); + mbedtls_ssl_conf_dh_param_ctx(&thctx->conf, &server_context->dhm); + + if (server_context->config.check_client_cert == MK_TRUE) { + mbedtls_ssl_conf_authmode(&thctx->conf, MBEDTLS_SSL_VERIFY_REQUIRED); + } + } + else { + ssl = &(*cur)->context; + } + + (*cur)->fd = fd; + + return ssl; +} + +static int context_unset(int fd, mbedtls_ssl_context *ssl) +{ + struct polar_context_head *head; + + head = container_of(ssl, struct polar_context_head, context); + + if (head->fd == fd) { + head->fd = -1; + mbedtls_ssl_session_reset(ssl); + } + else { + mk_err("[polarssl %d] Context already unset.", fd); + } + + return 0; +} + +int mk_tls_read(int fd, void *buf, int count) +{ + size_t avail; + mbedtls_ssl_context *ssl = context_get(fd); + + if (!ssl) { + ssl = context_new(fd); + } + + int ret = handle_return(mbedtls_ssl_read(ssl, buf, count)); + PLUGIN_TRACE("IN: %i SSL READ: %i ; CORE COUNT: %i", + ssl->in_msglen, + ret, count); + + /* Check if the caller read less than the available data */ + if (ret > 0) { + avail = polar_get_bytes_avail(ssl); + if (avail > 0) { + /* + * A read callback would never read in buffer more than + * the size specified in 'count', but it aims to return + * as value the total information read in the buffer plugin + */ + ret += avail; + } + } + return ret; +} + +int mk_tls_write(int fd, const void *buf, size_t count) +{ + mbedtls_ssl_context *ssl = context_get(fd); + if (!ssl) { + ssl = context_new(fd); + } + + return handle_return(mbedtls_ssl_write(ssl, buf, count)); +} + +int mk_tls_writev(int fd, struct mk_iov *mk_io) +{ + mbedtls_ssl_context *ssl = context_get(fd); + const int iov_len = mk_io->iov_idx; + const struct iovec *io = mk_io->io; + const size_t len = mk_io->total_len; + unsigned char *buf; + size_t used = 0; + int ret = 0, i; + + if (!ssl) { + ssl = context_new(fd); + } + + buf = mk_api->mem_alloc(len); + if (buf == NULL) { + mk_err("malloc failed: %s", strerror(errno)); + return -1; + } + + for (i = 0; i < iov_len; i++) { + memcpy(buf + used, io[i].iov_base, io[i].iov_len); + used += io[i].iov_len; + } + + assert(used == len); + ret = mbedtls_ssl_write(ssl, buf, len); + mk_api->mem_free(buf); + + return handle_return(ret); +} + +int mk_tls_send_file(int fd, int file_fd, off_t *file_offset, + size_t file_count) +{ + mbedtls_ssl_context *ssl = context_get(fd); + unsigned char *buf; + ssize_t used, remain = file_count, sent = 0; + int ret; + + if (!ssl) { + ssl = context_new(fd); + } + + buf = mk_api->mem_alloc(SENDFILE_BUF_SIZE); + if (buf == NULL) { + return -1; + } + + do { + used = pread(file_fd, buf, SENDFILE_BUF_SIZE, *file_offset); + if (used == 0) { + ret = 0; + } + else if (used < 0) { + mk_err("[tls] Read from file failed: %s", strerror(errno)); + ret = -1; + } + else if (remain > 0) { + ret = mbedtls_ssl_write(ssl, buf, used < remain ? used : remain); + } + else { + ret = mbedtls_ssl_write(ssl, buf, used); + } + + if (ret > 0) { + if (remain > 0) { + remain -= ret; + } + sent += ret; + *file_offset += ret; + } + } while (ret > 0); + + mk_api->mem_free(buf); + + if (sent > 0) { + return sent; + } + else { + return handle_return(ret); + } +} + +int mk_tls_close(int fd) +{ + mbedtls_ssl_context *ssl = context_get(fd); + + PLUGIN_TRACE("[fd %d] Closing connection", fd); + + if (ssl) { + mbedtls_ssl_close_notify(ssl); + context_unset(fd, ssl); + } + + close(fd); + return 0; +} + +int mk_tls_plugin_init(struct plugin_api **api, char *confdir) +{ + int used; + struct mk_list *head; + struct mk_config_listener *listen; + + /* Evil global config stuff */ + mk_api = *api; + + /* Check if the plugin will be used by some listener */ + used = MK_FALSE; + mk_list_foreach(head, &mk_api->config->listeners) { + listen = mk_list_entry(head, struct mk_config_listener, _head); + if (listen->flags & MK_CAP_SOCK_TLS) { + used = MK_TRUE; + break; + } + } + + if (used) { + /* If it's used, load certificates.. mandatory */ + server_context = mk_api->mem_alloc_z(sizeof(struct polar_server_context)); + config_parse(confdir, &server_context->config); + return mk_tls_init(); + } + else { + /* Plugin is not used, just unregister in silence */ + return -2; + } +} + +void mk_tls_worker_init(void) +{ + int ret; + struct polar_thread_context *thctx; + const char *pers = "monkey"; + + PLUGIN_TRACE("[tls] Init thread context."); + + thctx = mk_api->mem_alloc(sizeof(*thctx)); + if (thctx == NULL) { + goto error; + } + thctx->contexts = NULL; + mk_list_init(&thctx->_head); + + + /* SSL confniguration */ + mbedtls_ssl_config_init(&thctx->conf); + mbedtls_ssl_config_defaults(&thctx->conf, + MBEDTLS_SSL_IS_SERVER, + MBEDTLS_SSL_TRANSPORT_STREAM, + MBEDTLS_SSL_PRESET_DEFAULT); + + pthread_mutex_lock(&server_context->mutex); + mk_list_add(&thctx->_head, &server_context->threads._head); + pthread_mutex_unlock(&server_context->mutex); + + mbedtls_ctr_drbg_init(&thctx->ctr_drbg); + ret = mbedtls_ctr_drbg_seed(&thctx->ctr_drbg, + entropy_func_safe, &server_context->entropy, + (const unsigned char *) pers, + strlen(pers)); + if (ret != 0) { + goto error; + } + + mbedtls_pk_init(&thctx->pkey); + + PLUGIN_TRACE("[tls] Load RSA key."); + if (polar_load_key(thctx, &server_context->config)) { + goto error; + } + + PLUGIN_TRACE("[tls] Set local thread context."); + pthread_setspecific(local_context, thctx); + + return; + + error: + exit(EXIT_FAILURE); +} + +int mk_tls_plugin_exit() +{ + struct mk_list *cur, *tmp; + struct polar_thread_context *thctx; + + mbedtls_x509_crt_free(&server_context->cert); + mbedtls_x509_crt_free(&server_context->ca_cert); + mbedtls_dhm_free(&server_context->dhm); + + mk_list_foreach_safe(cur, tmp, &server_context->threads._head) { + thctx = mk_list_entry(cur, struct polar_thread_context, _head); + contexts_free(thctx->contexts); + mk_api->mem_free(thctx); + + mbedtls_pk_free(&thctx->pkey); + } + pthread_mutex_destroy(&server_context->mutex); + +#if defined(MBEDTLS_SSL_CACHE_C) + mbedtls_ssl_cache_free(&global_sessions.cache); +#endif + + config_free(&server_context->config); + mk_api->mem_free(server_context); + + return 0; +} + +/* Network Layer plugin Callbacks */ +struct mk_plugin_network mk_plugin_network_tls = { + .read = mk_tls_read, + .write = mk_tls_write, + .writev = mk_tls_writev, + .close = mk_tls_close, + .send_file = mk_tls_send_file, + .buffer_size = MBEDTLS_SSL_MAX_CONTENT_LEN +}; + +struct mk_plugin mk_plugin_tls = { + /* Identification */ + .shortname = "tls", + .name = "SSL/TLS Network Layer", + .version = MK_VERSION_STR, + .hooks = MK_PLUGIN_NETWORK_LAYER, + + /* Init / Exit */ + .init_plugin = mk_tls_plugin_init, + .exit_plugin = mk_tls_plugin_exit, + + /* Init Levels */ + .master_init = NULL, + .worker_init = mk_tls_worker_init, + + /* Type */ + .network = &mk_plugin_network_tls, + .capabilities = MK_CAP_SOCK_TLS +}; |