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;
+}
+