diff --git a/NEWS b/NEWS index 464460b..dd64c9e 100644 --- a/NEWS +++ b/NEWS @@ -16,6 +16,8 @@ * degesch: optimize buffer memory usage + * degesch: added logging of messages sent from /quote and plugins + * kike: add support for IRCv3.2 server-time * Remote addresses are now resolved asynchronously diff --git a/degesch.c b/degesch.c index 0a1cb6e..c509d96 100644 --- a/degesch.c +++ b/degesch.c @@ -3968,6 +3968,8 @@ irc_queue_reconnect (struct server *s) // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +static void irc_process_sent_message + (const struct irc_message *msg, struct server *s); static void irc_send (struct server *s, const char *format, ...) ATTRIBUTE_PRINTF (2, 3); @@ -3993,6 +3995,11 @@ irc_send (struct server *s, const char *format, ...) log_server_debug (s, "#a<< \"#S\"#r", ATTR_PART, str.str); + struct irc_message msg; + irc_parse_message (&msg, str.str); + irc_process_sent_message (&msg, s); + irc_free_message (&msg); + str_append_str (&s->write_buffer, &str); str_free (&str); str_append (&s->write_buffer, "\r\n"); @@ -5431,6 +5438,131 @@ irc_handle_mode_user (struct server *s, char **params) mode_processor_run (&p, params, mode_processor_apply_user); } +// --- Output processing ------------------------------------------------------- + +// Both user and plugins can send whatever the heck they want to, +// we need to parse it back so that it's evident what's happening + +static void +irc_handle_sent_cap (struct server *s, const struct irc_message *msg) +{ + if (msg->params.len < 2) + return; + + const char *subcommand = msg->params.vector[1]; + const char *args = (msg->params.len > 2) ? msg->params.vector[2] : ""; + if (!strcasecmp_ascii (subcommand, "REQ")) + log_server_status (s, s->buffer, + "#s: #S", "Capabilities requested", args); +} + +static void +irc_handle_sent_notice_text (struct server *s, + const struct irc_message *msg, struct str *text) +{ + const char *target = msg->params.vector[0]; + struct buffer *buffer = irc_get_buffer_for_message (s, msg, target); + if (buffer && soft_assert (s->irc_user)) + log_outcoming_notice (s, buffer, s->irc_user->nickname, text->str); + else + log_outcoming_orphan_notice (s, target, text->str); +} + +static void +irc_handle_sent_notice (struct server *s, const struct irc_message *msg) +{ + if (msg->params.len < 2 || s->cap_echo_message) + return; + + // This ignores empty messages which we should not normally send + struct ctcp_chunk *chunks = ctcp_parse (msg->params.vector[1]); + LIST_FOR_EACH (struct ctcp_chunk, iter, chunks) + { + if (iter->is_extended) + log_ctcp_reply (s, msg->params.vector[0], + xstrdup_printf ("%s %s", iter->tag.str, iter->text.str)); + else + irc_handle_sent_notice_text (s, msg, &iter->text); + } + ctcp_destroy (chunks); +} + +static void +irc_handle_sent_privmsg_text (struct server *s, + const struct irc_message *msg, struct str *text, bool is_action) +{ + const char *target = msg->params.vector[0]; + struct buffer *buffer = irc_get_buffer_for_message (s, msg, target); + if (buffer && soft_assert (s->irc_user)) + { + char *prefixes = irc_get_privmsg_prefix (s, s->irc_user, target); + if (is_action) + log_outcoming_action (s, buffer, s->irc_user->nickname, text->str); + else + log_outcoming_privmsg (s, buffer, + prefixes, s->irc_user->nickname, text->str); + free (prefixes); + } + else + // TODO: also handle actions here + log_outcoming_orphan_privmsg (s, target, text->str); +} + +static void +irc_handle_sent_privmsg (struct server *s, const struct irc_message *msg) +{ + if (msg->params.len < 2 || s->cap_echo_message) + return; + + // This ignores empty messages which we should not normally send + struct ctcp_chunk *chunks = ctcp_parse (msg->params.vector[1]); + LIST_FOR_EACH (struct ctcp_chunk, iter, chunks) + { + if (!iter->is_extended) + irc_handle_sent_privmsg_text (s, msg, &iter->text, false); + else if (!strcmp (iter->tag.str, "ACTION")) + irc_handle_sent_privmsg_text (s, msg, &iter->text, true); + else + log_ctcp_query (s, msg->params.vector[0], iter->tag.str); + } + ctcp_destroy (chunks); +} + +static struct irc_handler +{ + const char *name; + void (*handler) (struct server *s, const struct irc_message *msg); +} +g_irc_sent_handlers[] = +{ + // This list needs to stay sorted + { "CAP", irc_handle_sent_cap }, + { "NOTICE", irc_handle_sent_notice }, + { "PRIVMSG", irc_handle_sent_privmsg }, +}; + +static int +irc_handler_cmp_by_name (const void *a, const void *b) +{ + const struct irc_handler *first = a; + const struct irc_handler *second = b; + return strcasecmp_ascii (first->name, second->name); +} + +static void +irc_process_sent_message (const struct irc_message *msg, struct server *s) +{ + // The server is free to reject even a matching prefix + if (msg->prefix && !irc_is_this_us (s, msg->prefix)) + return; + + struct irc_handler key = { .name = msg->command }; + struct irc_handler *handler = bsearch (&key, g_irc_sent_handlers, + N_ELEMENTS (g_irc_sent_handlers), sizeof key, irc_handler_cmp_by_name); + if (handler) + handler->handler (s, msg); +} + // --- Input handling ---------------------------------------------------------- static void @@ -5496,8 +5628,6 @@ irc_handle_cap (struct server *s, const struct irc_message *msg) str_vector_free (&use); irc_send (s, "CAP REQ :%s", chosen_str); - log_server_status (s, s->buffer, - "#s: #S", "Capabilities requested", chosen_str); free (chosen_str); } @@ -5927,10 +6057,7 @@ irc_send_ctcp_reply (struct server *s, va_end (ap); irc_send (s, "NOTICE %s :\x01%s\x01", recipient, m.str); - if (!s->cap_echo_message) - log_ctcp_reply (s, recipient, str_steal (&m)); - else - str_free (&m); + str_free (&m); } static void @@ -6125,12 +6252,7 @@ irc_handle_topic (struct server *s, const struct irc_message *msg) // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -static struct irc_handler -{ - const char *name; - void (*handler) (struct server *s, const struct irc_message *msg); -} -g_irc_handlers[] = +static struct irc_handler g_irc_handlers[] = { // This list needs to stay sorted { "CAP", irc_handle_cap }, @@ -6148,14 +6270,6 @@ g_irc_handlers[] = { "TOPIC", irc_handle_topic }, }; -static int -irc_handler_cmp_by_name (const void *a, const void *b) -{ - const struct irc_handler *first = a; - const struct irc_handler *second = b; - return strcasecmp_ascii (first->name, second->name); -} - static bool irc_try_parse_word_for_userhost (struct server *s, const char *word) { @@ -6965,27 +7079,14 @@ irc_autosplit_message (struct server *s, const char *message, return true; } -struct send_autosplit_args; - -typedef void (*send_autosplit_logger_fn) (struct server *s, - struct send_autosplit_args *args, struct buffer *buffer, const char *line); - -struct send_autosplit_args -{ - const char *command; ///< E.g. PRIVMSG or NOTICE - const char *target; ///< User or channel - const char *message; ///< A message to be autosplit - send_autosplit_logger_fn logger; ///< Logger for all resulting lines - const char *prefix; ///< E.g. "\x01ACTION" - const char *suffix; ///< E.g. "\x01" -}; - static void -send_autosplit_message (struct server *s, struct send_autosplit_args a) +send_autosplit_message (struct server *s, + const char *command, const char *target, const char *message, + const char *prefix, const char *suffix) { - struct buffer *buffer = str_map_find (&s->irc_buffer_map, a.target); - int fixed_part = strlen (a.command) + 1 + strlen (a.target) + 1 + 1 - + strlen (a.prefix) + strlen (a.suffix); + struct buffer *buffer = str_map_find (&s->irc_buffer_map, target); + int fixed_part = strlen (command) + 1 + strlen (target) + 1 + 1 + + strlen (prefix) + strlen (suffix); // We might also want to preserve attributes across splits but // that would make this code a lot more complicated @@ -6993,71 +7094,29 @@ send_autosplit_message (struct server *s, struct send_autosplit_args a) struct str_vector lines; str_vector_init (&lines); struct error *e = NULL; - if (!irc_autosplit_message (s, a.message, fixed_part, &lines, &e)) + if (!irc_autosplit_message (s, message, fixed_part, &lines, &e)) { log_server_error (s, buffer ? buffer : s->buffer, "#s", e->message); error_free (e); - goto end; } - - for (size_t i = 0; i < lines.len; i++) + else { - irc_send (s, "%s %s :%s%s%s", a.command, a.target, - a.prefix, lines.vector[i], a.suffix); - if (!s->cap_echo_message) - a.logger (s, &a, buffer, lines.vector[i]); + for (size_t i = 0; i < lines.len; i++) + irc_send (s, "%s %s :%s%s%s", command, target, + prefix, lines.vector[i], suffix); } -end: str_vector_free (&lines); } -static void -log_autosplit_action (struct server *s, - struct send_autosplit_args *a, struct buffer *buffer, const char *line) -{ - (void) a; - - if (buffer && soft_assert (s->irc_user)) - log_outcoming_action (s, buffer, s->irc_user->nickname, line); - - // This can only be sent from a user or channel buffer -} - #define SEND_AUTOSPLIT_ACTION(s, target, message) \ - send_autosplit_message ((s), (struct send_autosplit_args) \ - { "PRIVMSG", (target), (message), log_autosplit_action, \ - "\x01" "ACTION ", "\x01" }) - -static void -log_autosplit_privmsg (struct server *s, - struct send_autosplit_args *a, struct buffer *buffer, const char *line) -{ - char *prefixes = irc_get_privmsg_prefix (s, s->irc_user, a->target); - if (buffer && soft_assert (s->irc_user)) - log_outcoming_privmsg (s, buffer, - prefixes, s->irc_user->nickname, line); - else - log_outcoming_orphan_privmsg (s, a->target, line); - free (prefixes); -} + send_autosplit_message ((s), "PRIVMSG", (target), (message), \ + "\x01" "ACTION ", "\x01") #define SEND_AUTOSPLIT_PRIVMSG(s, target, message) \ - send_autosplit_message ((s), (struct send_autosplit_args) \ - { "PRIVMSG", (target), (message), log_autosplit_privmsg, "", "" }) - -static void -log_autosplit_notice (struct server *s, - struct send_autosplit_args *a, struct buffer *buffer, const char *line) -{ - if (buffer && soft_assert (s->irc_user)) - log_outcoming_notice (s, buffer, s->irc_user->nickname, line); - else - log_outcoming_orphan_notice (s, a->target, line); -} + send_autosplit_message ((s), "PRIVMSG", (target), (message), "", "") #define SEND_AUTOSPLIT_NOTICE(s, target, message) \ - send_autosplit_message ((s), (struct send_autosplit_args) \ - { "NOTICE", (target), (message), log_autosplit_notice, "", "" }) + send_autosplit_message ((s), "NOTICE", (target), (message), "", "") // --- Configuration dumper ---------------------------------------------------- @@ -9584,9 +9643,6 @@ handle_command_ctcp (struct handler_args *a) irc_send (a->s, "PRIVMSG %s :\x01%s %s\x01", target, tag, a->arguments); else irc_send (a->s, "PRIVMSG %s :\x01%s\x01", target, tag); - - if (!a->s->cap_echo_message) - log_ctcp_query (a->s, target, tag); return true; }