From 875f55a4017574810b8fbc8a2895570b7f655eac Mon Sep 17 00:00:00 2001 From: Jan Vidar Krey Date: Tue, 3 Jan 2012 23:02:28 +0100 Subject: [PATCH] Added a chat history plugin. The mod_chat_history plugin provides chat history for all public chat messages. Can be configured in the following ways: - history_max: max number of messages stored in history - history_default: the default number of messages to be returned when invoking !history - history_connect: if > 0, then this number of messages is automatically sent when connecting to the hub Removed the built-in !history command in favour of the mod_chat_history plug-in. Make sure we unescape all chat messages before forwarding any of them to plugins. Update example plugins.conf in documentation directory. --- doc/plugins.conf | 65 +++++++-- src/core/commands.c | 44 ------ src/core/commands.h | 2 +- src/core/hub.c | 56 ++------ src/core/hub.h | 11 -- src/plugins/mod_chat_history.c | 245 +++++++++++++++++++++++++++++++++ 6 files changed, 313 insertions(+), 110 deletions(-) create mode 100644 src/plugins/mod_chat_history.c diff --git a/doc/plugins.conf b/doc/plugins.conf index e55b75b..8841289 100644 --- a/doc/plugins.conf +++ b/doc/plugins.conf @@ -1,16 +1,63 @@ # ATTENTION! # Plugins are invoked in the order of listing in the plugin config file. - -# auth user -# file={path for DB file with user auth information} + + +# Sqlite based user authentication. +# +# This plugin provides a Sqlite based authentication database for +# registered users. +# Use the uhub-passwd utility to create the database and add/remove users. +# +# Parameters: +# file: path/filename for database. +# plugin /var/lib/uhub/mod_auth_sqlite.so "file=/etc/uhub/users.db" -# log subsystem. -# file={/path/to/logfile} + +# Log file writer +# +# Parameters: +# file: path/filename for log file. +# syslog: if true then syslog is used instead of writing to a file (Unix only) plugin /var/lib/uhub/mod_logging.so "file=/var/log/uhub.log" -# -# plugin /var/lib/uhub/mod_auth_simple.so - -# +# A simple example plugin # plugin /var/lib/uhub/mod_example.so + +# Load the chat history plugin. +# +# This plugin provides chat history when users are connecting, or +# when users invoke the !history command. +# The history command can optionally take a parameter to indicate how many lines of history is requested. +# +# Parameters: +# history_max: the maximum number of messages to keep in history +# history_default: when !history is provided without arguments, then this default number of messages are returned. +# history_connect: the number of chat history messages to send when users connect (0 = do not send any history) +plugin /var/lib/uhub/mod_chat_history.so "history_max=200 history_default=10 history_connect=5" + +# A plugin sending a welcome message. +# +# This plugin provides the following commands: +# !motd - Message of the day +# !rules - Show hub rules. +# +# Parameters: +# motd: path/filename for the welcome message (message of the day) +# rules: path/filenam for the rules file +# +# NOTE: The files MUST exist, however if you do not wish to provide one then these parameters can be omitted. +# +# The motd/rules files can do the following substitutions: +# %n - Nickname of the user who entered the hub or issued the command. +# %a - IP address of the user +# %% - Becomes '%' +# %H - Hour 24-hour format (00-23) (Hub local time) +# %I - Hour 12-hour format (01-12) (Hub local time) +# %P - 'AM' or 'PM' +# %p - 'am' or 'pm' +# %M - Minutes (00-59) (Hub local time) +# %S - Seconds (00-60) (Hub local time) +plugin /var/lib/uhub/mod_welcome.so "motd=/etc/uhub/motd.txt rules=/etc/uhub/rules.txt" + + diff --git a/src/core/commands.c b/src/core/commands.c index 508fa2d..4be4a19 100644 --- a/src/core/commands.c +++ b/src/core/commands.c @@ -685,49 +685,6 @@ static int command_broadcast(struct command_base* cbase, struct hub_user* user, return 0; } -static int command_history(struct command_base* cbase, struct hub_user* user, struct hub_command* cmd) -{ - struct cbuffer* buf; - struct linked_list* messages = cbase->hub->chat_history; - char* message; - char* maxlines_str = list_get_first(cmd->args); - int maxlines = 0; - int skiplines = 0; - int total = list_size(messages); - - if (total == 0) - return command_status(cbase, user, cmd, cbuf_create_const("No messages.")); - - buf = cbuf_create(MAX_HELP_MSG); - if (maxlines_str) - maxlines = uhub_atoi(maxlines_str); - - if (maxlines <= 0 || maxlines > total) - maxlines = total; - - if (maxlines != total) - { - skiplines = total - maxlines; - cbuf_append_format(buf, "*** %s: Displaying %d of %d message%s:", cmd->prefix, maxlines, total, ((total != 1) ? "s" : "")); - } - else - { - cbuf_append_format(buf, "*** %s: Displaying %d message%s:", cmd->prefix, total, ((total != 1) ? "s" : "")); - } - cbuf_append(buf, "\n"); - - message = (char*) list_get_first(messages); - while (message) - { - if (--skiplines < 0) - cbuf_append(buf, message); - message = (char*) list_get_next(messages); - } - cbuf_append(buf, "\n"); - send_message(cbase, user, buf); - return 0; -} - static int command_log(struct command_base* cbase, struct hub_user* user, struct hub_command* cmd) { struct cbuffer* buf; @@ -948,7 +905,6 @@ void commands_builtin_add(struct command_base* cbase) #endif ADD_COMMAND("getip", 5, "n", auth_cred_operator, command_getip, "Show IP address for a user" ); ADD_COMMAND("help", 4, "?c",auth_cred_guest, command_help, "Show this help message." ); - ADD_COMMAND("history", 7, "?N",auth_cred_guest, command_history, "Show the last chat messages." ); ADD_COMMAND("kick", 4, "n", auth_cred_operator, command_kick, "Kick a user" ); ADD_COMMAND("log", 3, "", auth_cred_operator, command_log, "Display log" ); ADD_COMMAND("mute", 4, "n", auth_cred_operator, command_mute, "Mute user" ); diff --git a/src/core/commands.h b/src/core/commands.h index 3f67f11..e74d742 100644 --- a/src/core/commands.h +++ b/src/core/commands.h @@ -54,10 +54,10 @@ enum command_parse_status */ struct hub_command { - enum command_parse_status status; /**<<< "Status of the hub_command." */ const char* message; /**<<< "The complete message." */ char* prefix; /**<<< "The prefix extracted from the message." */ struct linked_list* args; /**<<< "List of all parsed arguments from the message. Type depends on expectations." */ + enum command_parse_status status; /**<<< "Status of the hub_command." */ command_handler handler; /**<<< "The function handler to call in order to invoke this command." */ const struct hub_user* user; /**<<< "The user who invoked this command." */ void* ptr; /**<<< "A pointer of data which came from struct command_handler" */ diff --git a/src/core/hub.c b/src/core/hub.c index f936317..61e50b9 100644 --- a/src/core/hub.c +++ b/src/core/hub.c @@ -265,6 +265,13 @@ int hub_handle_chat_message(struct hub_info* hub, struct hub_user* u, struct adc if (!message) return 0; + message_decoded = adc_msg_unescape(message); + if (!message_decoded) + { + hub_free(message); + return 0; + } + if (!user_is_logged_in(u)) { hub_free(message); @@ -290,9 +297,7 @@ int hub_handle_chat_message(struct hub_info* hub, struct hub_user* u, struct adc } else { - message_decoded = adc_msg_unescape(message); relay = command_invoke(hub->commands, u, message_decoded); - hub_free(message_decoded); } } @@ -307,13 +312,13 @@ int hub_handle_chat_message(struct hub_info* hub, struct hub_user* u, struct adc plugin_st status = st_default; if (broadcast) { - status = plugin_handle_chat_message(hub, u, message, 0); + status = plugin_handle_chat_message(hub, u, message_decoded, 0); } else if (private_msg) { struct hub_user* target = uman_get_user_by_sid(hub, cmd->target); if (target) - status = plugin_handle_private_message(hub, u, target, message, 0); + status = plugin_handle_private_message(hub, u, target, message_decoded, 0); else relay = 0; } @@ -327,39 +332,15 @@ int hub_handle_chat_message(struct hub_info* hub, struct hub_user* u, struct adc /* adc_msg_remove_named_argument(cmd, "PM"); */ if (broadcast) { - hub_chat_history_add(hub, u, cmd); - plugin_log_chat_message(hub, u, message, 0); + plugin_log_chat_message(hub, u, message_decoded, 0); } ret = route_message(hub, u, cmd); } hub_free(message); + hub_free(message_decoded); return ret; } -void hub_chat_history_add(struct hub_info* hub, struct hub_user* user, struct adc_message* cmd) -{ - char* msg_esc = adc_msg_get_argument(cmd, 0); - char* message = adc_msg_unescape(msg_esc); - size_t loglen = strlen(message) + strlen(user->id.nick) + 13; - char* log = hub_malloc(loglen + 1); - snprintf(log, loglen, "%s <%s> %s\n", get_timestamp(time(NULL)), user->id.nick, message); - log[loglen] = '\0'; - list_append(hub->chat_history, log); - while (list_size(hub->chat_history) > (size_t) hub->config->max_chat_history) - { - char* msg = list_get_first(hub->chat_history); - list_remove(hub->chat_history, msg); - hub_free(msg); - } - hub_free(message); - hub_free(msg_esc); -} - -void hub_chat_history_clear(struct hub_info* hub) -{ - list_clear(hub->chat_history, &hub_free); -} - void hub_send_support(struct hub_info* hub, struct hub_user* u) { if (user_is_connecting(u) || user_is_logged_in(u)) @@ -803,20 +784,7 @@ struct hub_info* hub_start_service(struct hub_config* config) return 0; } - hub->chat_history = (struct linked_list*) list_create(); hub->logout_info = (struct linked_list*) list_create(); - if (!hub->chat_history) - { - net_con_close(hub->server); - list_destroy(hub->chat_history); - list_destroy(hub->logout_info); - hub_free(hub->recvbuf); - hub_free(hub->sendbuf); - uman_shutdown(hub); - hub_free(hub); - return 0; - } - server_alt_port_start(hub, config); hub->status = hub_status_running; @@ -844,8 +812,6 @@ void hub_shutdown_service(struct hub_info* hub) hub->status = hub_status_stopped; hub_free(hub->sendbuf); hub_free(hub->recvbuf); - hub_chat_history_clear(hub); - list_destroy(hub->chat_history); list_clear(hub->logout_info, &hub_free); list_destroy(hub->logout_info); command_shutdown(hub->commands); diff --git a/src/core/hub.h b/src/core/hub.h index 924f520..ae2d369 100644 --- a/src/core/hub.h +++ b/src/core/hub.h @@ -109,7 +109,6 @@ struct hub_info char* recvbuf; /* Global receive buffer */ char* sendbuf; /* Global send buffer */ - struct linked_list* chat_history; /* Chat history */ struct linked_list* logout_info; /* Log of people logging out. */ struct command_base* commands; /* Hub command handler */ @@ -151,16 +150,6 @@ extern int hub_handle_password(struct hub_info* hub, struct hub_user* u, struct */ extern int hub_handle_chat_message(struct hub_info* hub, struct hub_user* u, struct adc_message* cmd); -/** - * Add a chat message to the chat history - */ -extern void hub_chat_history_add(struct hub_info* hub, struct hub_user* user, struct adc_message* cmd); - -/** - * Clear the chat history. - */ -extern void hub_chat_history_clear(struct hub_info* hub); - /** * Used internally by hub_handle_info * @return 1 if nickname is OK, or 0 if nickname is not accepted. diff --git a/src/plugins/mod_chat_history.c b/src/plugins/mod_chat_history.c new file mode 100644 index 0000000..aca227a --- /dev/null +++ b/src/plugins/mod_chat_history.c @@ -0,0 +1,245 @@ +/* + * uhub - A tiny ADC p2p connection hub + * Copyright (C) 2007-2012, 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 "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." + struct linked_list* chat_history; ///<<< "The chat history storage." + struct plugin_command_handle* command_history_handle; ///<<< "A handle to the !history command." +}; + +/** + * Add a chat message to history. + */ +static void history_add(struct plugin_handle* plugin, struct plugin_user* from, const char* message, int flags) +{ + size_t loglen = strlen(message) + strlen(from->nick) + 13; + struct chat_history_data* data = (struct chat_history_data*) plugin->ptr; + char* log = hub_malloc(loglen + 1); + + snprintf(log, loglen, "%s <%s> %s\n", get_timestamp(time(NULL)), from->nick, message); + log[loglen] = '\0'; + list_append(data->chat_history, log); + while (list_size(data->chat_history) > data->history_max) + { + char* msg = list_get_first(data->chat_history); + list_remove(data->chat_history, msg); + hub_free(msg); + } +} + +/** + * Obtain 'num' messages from the chat history and append them to outbuf. + * + * @return the number of messages added to the buffer. + */ +static size_t get_messages(struct chat_history_data* data, size_t num, struct cbuffer* outbuf) +{ + struct linked_list* messages = data->chat_history; + char* message; + int skiplines = 0; + size_t lines = 0; + int total = list_size(messages); + + if (total == 0) + return 0; + + if (num <= 0 || num > total) + num = total; + + if (num != total) + skiplines = total - num; + + cbuf_append(outbuf, "\n"); + message = (char*) list_get_first(messages); + while (message) + { + if (--skiplines < 0) + { + cbuf_append(outbuf, message); + lines++; + } + message = (char*) list_get_next(messages); + } + cbuf_append(outbuf, "\n"); + return lines; +} + +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; + // size_t messages = 0; + + if (data->history_connect > 0 && list_size(data->chat_history) > 0) + { + buf = cbuf_create(MAX_HISTORY_SIZE); + cbuf_append(buf, "Chat history:\n"); + get_messages(data, data->history_connect, buf); + plugin->hub.send_message(plugin, user, cbuf_get(buf)); + cbuf_destroy(buf); + } +} + +/** + * Send a status message back to the user who issued the !history command. + */ +static int command_status(struct plugin_handle* plugin, struct plugin_user* user, struct plugin_command* cmd, struct cbuffer* buf) +{ + struct cbuffer* msg = cbuf_create(cbuf_size(buf) + strlen(cmd->prefix) + 8); + cbuf_append_format(msg, "*** %s: %s", cmd->prefix, cbuf_get(buf)); + plugin->hub.send_message(plugin, user, cbuf_get(msg)); + cbuf_destroy(msg); + cbuf_destroy(buf); + return 0; +} + +/** + * 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 cbuffer* buf; + struct chat_history_data* data = (struct chat_history_data*) plugin->ptr; + int maxlines = 0; + + if (!list_size(data->chat_history)) + return command_status(plugin, user, cmd, cbuf_create_const("No messages.")); + + if (list_size(cmd->args) > 0) + maxlines = (int) (intptr_t) ((intptr_t*) (void*) list_get_first(cmd->args)); + else + maxlines = data->history_default; + + buf = cbuf_create(MAX_HISTORY_SIZE); + cbuf_append_format(buf, "*** %s: Chat History:\n", cmd->prefix); + get_messages(data, maxlines, buf); + + 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); + + assert(data != NULL); + + data->history_max = 200; + data->history_default = 25; + data->history_connect = 5; + data->chat_history = list_create(); + + 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), "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, "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; + + 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); + + return 0; +} + +int plugin_unregister(struct plugin_handle* plugin) +{ + struct chat_history_data* data = (struct chat_history_data*) plugin->ptr; + + if (data) + { + list_clear(data->chat_history, &hub_free); + list_destroy(data->chat_history); + + plugin->hub.command_del(plugin, data->command_history_handle); + hub_free(data->command_history_handle); + hub_free(data); + } + + return 0; +} +