From 5672ba14e34cefd9ab0cbc8443bca11635af1718 Mon Sep 17 00:00:00 2001 From: mimicmod Date: Sun, 14 Oct 2012 22:38:36 -0400 Subject: [PATCH] Added mod_chat_history_sqlite and mod_chat_is_privileged. Use file=/path/to/db to specify the database file where chat history should be stored. Other config variables are the same as those for mod_chat_history. Code merged adapted and merged from Mimicmod's repository: https://github.com/mimicmod/uhub.git --- AUTHORS | 2 +- CMakeLists.txt | 8 +- src/plugins/mod_chat_history_sqlite.c | 347 ++++++++++++++++++++++++++ src/plugins/mod_chat_is_privileged.c | 171 +++++++++++++ 4 files changed, 526 insertions(+), 2 deletions(-) create mode 100644 src/plugins/mod_chat_history_sqlite.c create mode 100644 src/plugins/mod_chat_is_privileged.c diff --git a/AUTHORS b/AUTHORS index edb51af..b438741 100644 --- a/AUTHORS +++ b/AUTHORS @@ -4,7 +4,7 @@ Authors of uhub Jan Vidar Krey, Design and implementation E_zombie, Centos/RedHat customization scripts and heavy load testing FleetCommand, Hub topic plugin code -MiMic, Implemented user commands +MiMic, Implemented user commands, and plugins Boris Pek (tehnick), Debian/Ubuntu packaging Tillmann Karras (Tilka), Misc. bug fixes Yoran Heling (Yorhel), TLS/SSL handshake detection bugfixes diff --git a/CMakeLists.txt b/CMakeLists.txt index 6dfd1bc..689774c 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -92,7 +92,9 @@ add_library(mod_welcome MODULE ${PROJECT_SOURCE_DIR}/plugins/mod_welcome.c) add_library(mod_logging MODULE ${PROJECT_SOURCE_DIR}/plugins/mod_logging.c ${PROJECT_SOURCE_DIR}/adc/sid.c) add_library(mod_auth_simple MODULE ${PROJECT_SOURCE_DIR}/plugins/mod_auth_simple.c ) add_library(mod_chat_history MODULE ${PROJECT_SOURCE_DIR}/plugins/mod_chat_history.c ) +add_library(mod_chat_history_sqlite MODULE ${PROJECT_SOURCE_DIR}/plugins/mod_chat_history_sqlite.c ) add_library(mod_chat_only MODULE ${PROJECT_SOURCE_DIR}/plugins/mod_chat_only.c) +add_library(mod_chat_is_privileged MODULE ${PROJECT_SOURCE_DIR}/plugins/mod_chat_is_privileged.c) add_library(mod_topic MODULE ${PROJECT_SOURCE_DIR}/plugins/mod_topic.c) add_library(mod_no_guest_downloads MODULE ${PROJECT_SOURCE_DIR}/plugins/mod_no_guest_downloads.c) add_library(mod_auth_sqlite MODULE ${PROJECT_SOURCE_DIR}/plugins/mod_auth_sqlite.c) @@ -111,7 +113,9 @@ set_target_properties( mod_auth_simple mod_auth_sqlite mod_chat_history + mod_chat_history_sqlite mod_chat_only + mod_chat_is_privileged mod_no_guest_downloads mod_topic PROPERTIES PREFIX "") @@ -124,8 +128,10 @@ target_link_libraries(mod_welcome utils) target_link_libraries(mod_auth_simple utils) target_link_libraries(mod_auth_sqlite ${SQLITE3_LIBRARIES} utils) target_link_libraries(mod_chat_history utils) +target_link_libraries(mod_chat_history_sqlite ${SQLITE3_LIBRARIES} utils) target_link_libraries(mod_no_guest_downloads utils) target_link_libraries(mod_chat_only utils) +target_link_libraries(mod_chat_is_privileged utils) target_link_libraries(mod_logging utils) target_link_libraries(mod_topic utils) target_link_libraries(utils network) @@ -217,7 +223,7 @@ endif() if (UNIX) install( TARGETS uhub uhub-passwd RUNTIME DESTINATION bin ) - install( TARGETS mod_example mod_welcome mod_logging mod_auth_simple mod_auth_sqlite mod_chat_history mod_chat_only mod_topic mod_no_guest_downloads DESTINATION /usr/lib/uhub/ OPTIONAL ) + install( TARGETS mod_example mod_welcome mod_logging mod_auth_simple mod_auth_sqlite mod_chat_history mod_chat_history_sqlite mod_chat_only mod_chat_is_privileged mod_topic mod_no_guest_downloads DESTINATION /usr/lib/uhub/ OPTIONAL ) install( FILES ${CMAKE_SOURCE_DIR}/doc/uhub.conf ${CMAKE_SOURCE_DIR}/doc/plugins.conf ${CMAKE_SOURCE_DIR}/doc/rules.txt ${CMAKE_SOURCE_DIR}/doc/motd.txt DESTINATION /etc/uhub OPTIONAL ) endif() diff --git a/src/plugins/mod_chat_history_sqlite.c b/src/plugins/mod_chat_history_sqlite.c new file mode 100644 index 0000000..c83233b --- /dev/null +++ b/src/plugins/mod_chat_history_sqlite.c @@ -0,0 +1,347 @@ +/* + * uhub - A tiny ADC p2p connection hub + * Copyright (C) 2007-2013, Jan Vidar Krey + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + */ + +#include "plugin_api/handle.h" +#include "plugin_api/command_api.h" +#include "util/config_token.h" +#include +#include "util/memory.h" +#include "util/misc.h" +#include "util/list.h" +#include "util/cbuffer.h" + +#define MAX_HISTORY_SIZE 16384 + +struct chat_history_data +{ + size_t history_max; ///<<< "the maximum number of chat messages kept in history." + size_t history_default; ///<<< "the default number of chat messages returned if no limit was provided" + size_t history_connect; ///<<< "the number of chat messages provided when users connect to the hub." + sqlite3* db; ///<<< "The chat history storage database." + struct plugin_command_handle* command_history_handle; ///<<< "A handle to the !history command." + struct plugin_command_handle* command_historycleanup_handle; ///<<< "A handle to the !historycleanup command." +}; + +struct chat_history_line +{ + char message[MAX_HISTORY_SIZE]; + char from[MAX_NICK_LEN]; + char time[20]; +}; + +static int null_callback(void* ptr, int argc, char **argv, char **colName) { return 0; } + +static const char* sql_escape_string(const char* str) +{ + static char out[1024]; + size_t i = 0; + size_t n = 0; + for (; n < strlen(str); n++) + { + if (str[n] == '\'') + out[i++] = '\''; + out[i++] = str[n]; + } + out[i++] = '\0'; + return out; +} + +static int sql_execute(struct chat_history_data* sql, int (*callback)(void* ptr, int argc, char **argv, char **colName), void* ptr, const char* sql_fmt, ...) +{ + va_list args; + char query[1024]; + char* errMsg; + int rc; + + va_start(args, sql_fmt); + vsnprintf(query, sizeof(query), sql_fmt, args); + + rc = sqlite3_exec(sql->db, query, callback, ptr, &errMsg); + if (rc != SQLITE_OK) + { + sqlite3_free(errMsg); + return -rc; + } + + rc = sqlite3_changes(sql->db); + return rc; +} + +static void create_tables(struct plugin_handle* plugin) +{ + const char* table_create = "CREATE TABLE IF NOT EXISTS chat_history" + "(" + "from_nick CHAR," + "message TEXT," + "time TIMESTAMP DEFAULT (DATETIME('NOW'))" + ");"; + + struct chat_history_data* data = (struct chat_history_data*) plugin->ptr; + sql_execute(data, null_callback, NULL, table_create); +} +/** + * Add a chat message to history. + */ +static void history_add(struct plugin_handle* plugin, struct plugin_user* from, const char* message, int flags) +{ + struct chat_history_data* data = (struct chat_history_data*) plugin->ptr; + char* history_line = strdup(sql_escape_string(message)); + char* history_nick = strdup(sql_escape_string(from->nick)); + + sql_execute(data, null_callback, NULL, "INSERT INTO chat_history (from_nick, message) VALUES('%s', '%s');DELETE FROM chat_history WHERE time <= (SELECT time FROM chat_history ORDER BY time DESC LIMIT %d,1);", history_nick, history_line, data->history_max); + + hub_free(history_line); + hub_free(history_nick); +} + +/** + * Obtain messages from the chat history as a linked list + */ + +static int get_messages_callback(void* ptr, int argc, char **argv, char **colName) +{ + struct linked_list* messages = (struct linked_list*) ptr; + struct chat_history_line* line = hub_malloc(sizeof(struct chat_history_line)); + int i = 0; + + memset(line, 0, sizeof(struct chat_history_line)); + + for (; i < argc; i++) { + if (strcmp(colName[i], "from_nick") == 0) + strncpy(line->from, argv[i], MAX_NICK_LEN); + else if (strcmp(colName[i], "message") == 0) + strncpy(line->message, argv[i], MAX_HISTORY_SIZE); + else if (strcmp(colName[i], "time") == 0) + strncpy(line->time, argv[i], 20); + } + + list_append(messages, line); + + return 0; +} + +void user_login(struct plugin_handle* plugin, struct plugin_user* user) +{ + struct chat_history_data* data = (struct chat_history_data*) plugin->ptr; + struct cbuffer* buf = NULL; + struct linked_list* found = (struct linked_list*) list_create(); + + sql_execute(data, get_messages_callback, found, "SELECT * FROM chat_history ORDER BY time DESC LIMIT 0,%d;", (int) data->history_connect); + + if (data->history_connect > 0 && list_size(found) > 0) + { + buf = cbuf_create(MAX_HISTORY_SIZE); + cbuf_append(buf, "Chat history:\n\n"); + struct chat_history_line* history_line; + history_line = (struct chat_history_line*) list_get_last(found); + while (history_line) + { + cbuf_append_format(buf, "[%s] <%s> %s\n", history_line->time, history_line->from, history_line->message); + list_remove(found, history_line); + hub_free(history_line); + history_line = (struct chat_history_line*) list_get_last(found); + } + plugin->hub.send_message(plugin, user, cbuf_get(buf)); + cbuf_destroy(buf); + } + list_clear(found, &hub_free); + list_destroy(found); +} + +/** + * The callback function for handling the !history command. + */ +static int command_history(struct plugin_handle* plugin, struct plugin_user* user, struct plugin_command* cmd) +{ + struct chat_history_data* data = (struct chat_history_data*) plugin->ptr; + struct cbuffer* buf = cbuf_create(MAX_HISTORY_SIZE); + struct linked_list* found = (struct linked_list*) list_create(); + struct plugin_command_arg_data* arg = plugin->hub.command_arg_next(plugin, cmd, plugin_cmd_arg_type_integer); + int maxlines; + + if (arg) + maxlines = arg->data.integer; + else + maxlines = data->history_default; + + sql_execute(data, get_messages_callback, found, "SELECT * FROM chat_history ORDER BY time DESC LIMIT 0,%d;", maxlines); + + size_t linecount = list_size(found); + + if (linecount > 0) + { + cbuf_append_format(buf, "*** %s: Chat History:\n\n", cmd->prefix); + struct chat_history_line* history_line; + history_line = (struct chat_history_line*) list_get_last(found); + while (history_line) + { + cbuf_append_format(buf, "[%s] <%s> %s\n", history_line->time, history_line->from, history_line->message); + list_remove(found, history_line); + hub_free(history_line); + history_line = (struct chat_history_line*) list_get_last(found); + } + } + else + { + cbuf_append_format(buf, "*** %s: No messages found.", cmd->prefix); + } + + plugin->hub.send_message(plugin, user, cbuf_get(buf)); + cbuf_destroy(buf); + list_clear(found, &hub_free); + list_destroy(found); + + return 0; +} + +static int command_historycleanup(struct plugin_handle* plugin, struct plugin_user* user, struct plugin_command* cmd) +{ + struct chat_history_data* data = (struct chat_history_data*) plugin->ptr; + struct cbuffer* buf = cbuf_create(128); + int rc = 0; + + rc = sql_execute(data, null_callback, NULL, "DELETE FROM chat_history;"); + + if (!rc) + cbuf_append_format(buf, "*** %s: Unable to clean chat history table.", cmd->prefix); + else + cbuf_append_format(buf, "*** %s: Cleaned chat history table.", cmd->prefix); + + plugin->hub.send_message(plugin, user, cbuf_get(buf)); + cbuf_destroy(buf); + + return 0; +} + +static void set_error_message(struct plugin_handle* plugin, const char* msg) +{ + plugin->error_msg = msg; +} + +static struct chat_history_data* parse_config(const char* line, struct plugin_handle* plugin) +{ + struct chat_history_data* data = (struct chat_history_data*) hub_malloc_zero(sizeof(struct chat_history_data)); + struct cfg_tokens* tokens = cfg_tokenize(line); + char* token = cfg_token_get_first(tokens); + + uhub_assert(data != NULL); + + data->history_max = 200; + data->history_default = 25; + data->history_connect = 5; + + while (token) + { + struct cfg_settings* setting = cfg_settings_split(token); + + if (!setting) + { + set_error_message(plugin, "Unable to parse startup parameters"); + cfg_tokens_free(tokens); + hub_free(data); + return 0; + } + + if (strcmp(cfg_settings_get_key(setting), "file") == 0) + { + if (!data->db) + { + if (sqlite3_open(cfg_settings_get_value(setting), &data->db)) + { + cfg_tokens_free(tokens); + cfg_settings_free(setting); + hub_free(data); + set_error_message(plugin, "Unable to open database file"); + return 0; + } + } + } + else if (strcmp(cfg_settings_get_key(setting), "history_max") == 0) + { + data->history_max = (size_t) uhub_atoi(cfg_settings_get_value(setting)); + } + else if (strcmp(cfg_settings_get_key(setting), "history_default") == 0) + { + data->history_default = (size_t) uhub_atoi(cfg_settings_get_value(setting)); + } + else if (strcmp(cfg_settings_get_key(setting), "history_connect") == 0) + { + data->history_connect = (size_t) uhub_atoi(cfg_settings_get_value(setting)); + } + else + { + set_error_message(plugin, "Unknown startup parameters given"); + cfg_tokens_free(tokens); + cfg_settings_free(setting); + hub_free(data); + return 0; + } + + cfg_settings_free(setting); + token = cfg_token_get_next(tokens); + } + cfg_tokens_free(tokens); + return data; +} + +int plugin_register(struct plugin_handle* plugin, const char* config) +{ + struct chat_history_data* data; + PLUGIN_INITIALIZE(plugin, "SQLite chat history plugin", "1.0", "Provide a global chat history log."); + + plugin->funcs.on_user_chat_message = history_add; + plugin->funcs.on_user_login = user_login; + data = parse_config(config, plugin); + + if (!data) + return -1; + + plugin->ptr = data; + + create_tables(plugin); + + data->command_history_handle = (struct plugin_command_handle*) hub_malloc(sizeof(struct plugin_command_handle)); + PLUGIN_COMMAND_INITIALIZE(data->command_history_handle, plugin, "history", "?N", auth_cred_guest, &command_history, "Show chat message history."); + plugin->hub.command_add(plugin, data->command_history_handle); + + data->command_historycleanup_handle = (struct plugin_command_handle*) hub_malloc(sizeof(struct plugin_command_handle)); + PLUGIN_COMMAND_INITIALIZE(data->command_historycleanup_handle, plugin, "historycleanup", "", auth_cred_admin, &command_historycleanup, "Clean chat message history."); + plugin->hub.command_add(plugin, data->command_historycleanup_handle); + + return 0; +} + +int plugin_unregister(struct plugin_handle* plugin) +{ + struct chat_history_data* data = (struct chat_history_data*) plugin->ptr; + + if (data) + { + sqlite3_close(data->db); + + plugin->hub.command_del(plugin, data->command_history_handle); + plugin->hub.command_del(plugin, data->command_historycleanup_handle); + hub_free(data->command_history_handle); + hub_free(data->command_historycleanup_handle); + hub_free(data); + } + + return 0; +} + diff --git a/src/plugins/mod_chat_is_privileged.c b/src/plugins/mod_chat_is_privileged.c new file mode 100644 index 0000000..e59e84d --- /dev/null +++ b/src/plugins/mod_chat_is_privileged.c @@ -0,0 +1,171 @@ +/* + * uhub - A tiny ADC p2p connection hub + * Copyright (C) 2007-2013, Jan Vidar Krey + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + */ + +#include "plugin_api/handle.h" +#include "util/memory.h" + +struct user_info +{ + sid_t sid; + int warnings; +}; + +struct chat_restrictions_data +{ + size_t num_users; // number of users tracked. + size_t max_users; // max users (hard limit max 1M users due to limitations in the SID (20 bits)). + struct user_info* users; // array of max_users + + enum auth_credentials allow_privchat; // minimum credentials to allow using private chat + enum auth_credentials allow_op_contact; // minimum credentials to allow private chat to operators (including super and admins). + enum auth_credentials allow_mainchat; // minimum credentials to allow using main chat +}; + +static struct chat_data* parse_config(struct plugin_handle* plugin, const char* line) +{ + struct chat_data* data = (struct chat_data*) hub_malloc(sizeof(struct chat_data)); + struct cfg_tokens* tokens = cfg_tokenize(line); + char* token = cfg_token_get_first(tokens); + + // defaults + data->num_users = 0; + data->max_users = 512; + data->users = hub_malloc_zero(sizeof(struct user_info) * data->max_users); + data->allow_mainchat = auth_cred_guest; + data->allow_op_contact = auth_cred_guest; + data->allow_privchat = auth_cred_guest; + + while (token) + { + struct cfg_settings* setting = cfg_settings_split(token); + + if (!setting) + { + set_error_message(plugin, "Unable to parse startup parameters"); + cfg_tokens_free(tokens); + hub_free(data); + return 0; + } + + if (strcmp(cfg_settings_get_key(setting), "allow_privchat") == 0) + { + if (!string_to_boolean(cfg_settings_get_value(setting), &data->allow_privchat)) + data->allow_privchat = 0; + } + else if (strcmp(cfg_settings_get_key(setting), "minimum_access") == 0) + { + } + else + { + set_error_message(plugin, "Unknown startup parameters given"); + cfg_tokens_free(tokens); + cfg_settings_free(setting); + hub_free(data); + return 0; + } + + cfg_settings_free(setting); + token = cfg_token_get_next(tokens); + } + cfg_tokens_free(tokens); + + return data; +} + +static struct user_info* get_user_info(struct chat_data* data, sid_t sid) +{ + struct user_info* u; + + // resize buffer if needed. + if (sid >= data->max_users) + { + u = hub_malloc_zero(sizeof(struct user_info) * (sid + 1)); + memcpy(u, data->users, data->max_users); + hub_free(data->users); + data->users = u; + data->max_users = sid; + u = NULL; + } + + u = &data->users[sid]; + + // reset counters if the user was not previously known. + if (!u->sid) + { + u->sid = sid; + u->warnings = 0; + data->num_users++; + } + return u; +} + +static void on_user_login(struct plugin_handle* plugin, struct plugin_user* user) +{ + struct chat_data* data = (struct chat_data*) plugin->ptr; + /*struct user_info* info = */ + get_user_info(data, user->sid); +} + +static void on_user_logout(struct plugin_handle* plugin, struct plugin_user* user, const char* reason) +{ + struct chat_data* data = (struct chat_data*) plugin->ptr; + struct user_info* info = get_user_info(data, user->sid); + if (info->sid) + data->num_users--; + info->warnings = 0; + info->sid = 0; +} + +plugin_st on_chat_msg(struct plugin_handle* plugin, struct plugin_user* from, const char* message) +{ + struct chat_data* data = (struct chat_data*) plugin->ptr; + if (from->credentials >= + return st_default; +} + +plugin_st on_private_msg(struct plugin_handle* plugin, struct plugin_user* from, struct plugin_user* to, const char* message) +{ + return st_default; +} + + +int plugin_register(struct plugin_handle* plugin, const char* config) +{ + PLUGIN_INITIALIZE(plugin, "Privileged chat hub", "1.0", "Only registered users can send messages on the main chat."); + plugin->ptr = cip_initialize(); + + plugin->funcs.on_user_login = on_user_login; + plugin->funcs.on_user_logout = on_user_logout; + plugin->funcs.on_chat_msg = on_chat_msg; + plugin->funcs.on_private_msg = on_private_msg; + + return 0; +} + +int plugin_unregister(struct plugin_handle* plugin) +{ + struct chat_data* data = (struct chat_data*) plugin->ptr; + if (data) + { + hub_free(data->users); + hub_free(data); + } + return 0; +} +