From c399aab01672adeb4e8b2f55ffbf9c20e5931c88 Mon Sep 17 00:00:00 2001 From: Bram Matthys Date: Sat, 5 Oct 2019 18:15:48 +0200 Subject: [PATCH] Support for labeled-response in LIST (I really wanted this) --- include/h.h | 10 ++++ include/modules.h | 3 ++ src/api-efunctions.c | 9 +++- src/misc.c | 19 +++++++ src/modules/labeled-response.c | 95 +++++++++++++++++++++++++++------- src/modules/list.c | 36 +++++++++---- src/user.c | 7 ++- 7 files changed, 147 insertions(+), 32 deletions(-) diff --git a/include/h.h b/include/h.h index dd8bfeddd..faa85215e 100644 --- a/include/h.h +++ b/include/h.h @@ -673,6 +673,7 @@ extern void do_cmd(Client *client, MessageTag *mtags, char *cmd, int parc, char extern MODVAR char *me_hash; extern MODVAR int dontspread; extern MODVAR int labeled_response_inhibit; +extern MODVAR int labeled_response_inhibit_end; extern MODVAR int labeled_response_force; /* Efuncs */ @@ -758,8 +759,17 @@ extern MODVAR int (*find_tkl_exception)(int ban_type, Client *cptr); extern MODVAR int (*del_silence)(Client *client, const char *mask); extern MODVAR int (*add_silence)(Client *client, const char *mask, int senderr); extern MODVAR int (*is_silenced)(Client *client, Client *acptr); +extern MODVAR void *(*labeled_response_save_context)(void); +extern MODVAR void (*labeled_response_set_context)(void *ctx); +extern MODVAR void (*labeled_response_force_end)(void); /* /Efuncs */ +extern void parse_message_tags_default_handler(Client *client, char **str, MessageTag **mtag_list); +extern char *mtags_to_string_default_handler(MessageTag *m, Client *client); +extern void *labeled_response_save_context_default_handler(void); +extern void labeled_response_set_context_default_handler(void *ctx); +extern void labeled_response_force_end_default_handler(void); + extern MODVAR MOTDFile opermotd, svsmotd, motd, botmotd, smotd, rules; extern MODVAR int max_connection_count; extern int add_listmode(Ban **list, Client *cptr, Channel *channel, char *banid); diff --git a/include/modules.h b/include/modules.h index 353af2ae8..0ebf4fd4f 100644 --- a/include/modules.h +++ b/include/modules.h @@ -1297,6 +1297,9 @@ enum EfunctionType { EFUNC_ADD_SILENCE, EFUNC_DEL_SILENCE, EFUNC_IS_SILENCED, + EFUNC_LABELED_RESPONSE_SAVE_CONTEXT, + EFUNC_LABELED_RESPONSE_SET_CONTEXT, + EFUNC_LABELED_RESPONSE_FORCE_END, }; /* Module flags */ diff --git a/src/api-efunctions.c b/src/api-efunctions.c index f30ad2922..e2702cffb 100644 --- a/src/api-efunctions.c +++ b/src/api-efunctions.c @@ -99,9 +99,7 @@ int (*do_remote_nick_name)(char *nick); char *(*charsys_get_current_languages)(void); void (*broadcast_sinfo)(Client *client, Client *to, Client *except); void (*parse_message_tags)(Client *client, char **str, MessageTag **mtag_list); -extern void parse_message_tags_default_handler(Client *client, char **str, MessageTag **mtag_list); char *(*mtags_to_string)(MessageTag *m, Client *client); -extern char *mtags_to_string_default_handler(MessageTag *m, Client *client); int (*can_send_to_channel)(Client *client, Channel *channel, char **msgtext, char **errmsg, int notice); void (*broadcast_md_globalvar)(ModDataInfo *mdi, ModData *md); void (*broadcast_md_globalvar_cmd)(Client *except, Client *sender, char *varname, char *value); @@ -118,6 +116,9 @@ int (*find_tkl_exception)(int ban_type, Client *client); int (*is_silenced)(Client *client, Client *acptr); int (*del_silence)(Client *client, const char *mask); int (*add_silence)(Client *client, const char *mask, int senderr); +void *(*labeled_response_save_context)(void); +void (*labeled_response_set_context)(void *ctx); +void (*labeled_response_force_end)(void); Efunction *EfunctionAddMain(Module *module, EfunctionType eftype, int (*func)(), void (*vfunc)(), void *(*pvfunc)(), char *(*cfunc)()) { @@ -357,4 +358,8 @@ void efunctions_init(void) efunc_init_function(EFUNC_ADD_SILENCE, add_silence, NULL); efunc_init_function(EFUNC_DEL_SILENCE, del_silence, NULL); efunc_init_function(EFUNC_IS_SILENCED, is_silenced, NULL); + efunc_init_function(EFUNC_IS_SILENCED, is_silenced, NULL); + efunc_init_function(EFUNC_LABELED_RESPONSE_SAVE_CONTEXT, labeled_response_save_context, labeled_response_save_context_default_handler); + efunc_init_function(EFUNC_LABELED_RESPONSE_SET_CONTEXT, labeled_response_set_context, labeled_response_set_context_default_handler); + efunc_init_function(EFUNC_LABELED_RESPONSE_FORCE_END, labeled_response_force_end, labeled_response_force_end_default_handler); } diff --git a/src/misc.c b/src/misc.c index 718d18a1a..f518f4296 100644 --- a/src/misc.c +++ b/src/misc.c @@ -1326,6 +1326,25 @@ void generate_batch_id(char *str) gen_random_alnum(str, BATCHLEN); } +/* A default handler if labeled-response module is not loaded. + * Normally a NOOP, but since caller will safe_free it + * later we do actually allocate something. + */ +void *labeled_response_save_context_default_handler(void) +{ + return safe_alloc(8); +} + +/* A default handler for if labeled-response module is not loaded */ +void labeled_response_set_context_default_handler(void *ctx) +{ +} + +/* A default handler for if labeled-response module is not loaded */ +void labeled_response_force_end_default_handler(void) +{ +} + /** my_timegm: mktime()-like function which will use GMT/UTC. * Strangely enough there is no standard function for this. * On some *NIX OS's timegm() may be available, sometimes only diff --git a/src/modules/labeled-response.c b/src/modules/labeled-response.c index a59b9ad60..3068e691d 100644 --- a/src/modules/labeled-response.c +++ b/src/modules/labeled-response.c @@ -31,16 +31,9 @@ ModuleHeader MOD_HEADER "unrealircd-5", }; -/* Forward declarations */ -int lr_pre_command(Client *from, MessageTag *mtags, char *buf); -int lr_post_command(Client *from, MessageTag *mtags, char *buf); -int lr_packet(Client *from, Client *to, Client *intended_to, char **msg, int *len); - -/* Our special version of SupportBatch() assumes that remote servers always handle it */ -#define SupportBatch(x) (MyConnect(x) ? HasCapability((x), "batch") : 1) -#define SupportLabel(x) (HasCapabilityFast((x), CAP_LABELED_RESPONSE)) - -struct { +/* Data structures */ +typedef struct LabeledResponseContext LabeledResponseContext; +struct LabeledResponseContext { Client *client; /**< The client who issued the original command with a label */ char label[256]; /**< The label attached to this command */ char batch[BATCHLEN+1]; /**< The generated batch id */ @@ -48,14 +41,38 @@ struct { int sent_remote; /**< Command has been sent to remote server */ int version; /**< Which version? Zero for official, non-zero is draft */ char firstbuf[4096]; /**< First buffered response */ -} currentcmd; +}; -long CAP_LABELED_RESPONSE = 0L; +/* Forward declarations */ +int lr_pre_command(Client *from, MessageTag *mtags, char *buf); +int lr_post_command(Client *from, MessageTag *mtags, char *buf); +int lr_packet(Client *from, Client *to, Client *intended_to, char **msg, int *len); +void *_labeled_response_save_context(void); +void _labeled_response_set_context(void *ctx); +void _labeled_response_force_end(void); + +/* Our special version of SupportBatch() assumes that remote servers always handle it */ +#define SupportBatch(x) (MyConnect(x) ? HasCapability((x), "batch") : 1) +#define SupportLabel(x) (HasCapabilityFast((x), CAP_LABELED_RESPONSE)) + +/* Variables */ +static LabeledResponseContext currentcmd; +static long CAP_LABELED_RESPONSE = 0L; static char packet[8192]; int labeled_response_mtag_is_ok(Client *client, char *name, char *value); +MOD_TEST() +{ + MARK_AS_OFFICIAL_MODULE(modinfo); + EfunctionAddPVoid(modinfo->handle, EFUNC_LABELED_RESPONSE_SAVE_CONTEXT, _labeled_response_save_context); + EfunctionAddVoid(modinfo->handle, EFUNC_LABELED_RESPONSE_SET_CONTEXT, _labeled_response_set_context); + EfunctionAddVoid(modinfo->handle, EFUNC_LABELED_RESPONSE_FORCE_END, _labeled_response_force_end); + + return MOD_SUCCESS; +} + MOD_INIT() { ClientCapabilityInfo cap; @@ -96,7 +113,7 @@ MOD_UNLOAD() int lr_pre_command(Client *from, MessageTag *mtags, char *buf) { memset(¤tcmd, 0, sizeof(currentcmd)); - labeled_response_inhibit = labeled_response_force = 0; + labeled_response_inhibit = labeled_response_inhibit_end = labeled_response_force = 0; for (; mtags; mtags = mtags->next) { @@ -215,16 +232,19 @@ int lr_post_command(Client *from, MessageTag *mtags, char *buf) } /* End the batch */ - savedptr = currentcmd.client; - currentcmd.client = NULL; - if (MyConnect(savedptr)) - sendto_one(from, NULL, ":%s BATCH -%s", me.name, currentcmd.batch); - else - sendto_one(from, NULL, ":%s BATCH %s -%s", me.name, savedptr->name, currentcmd.batch); + if (!labeled_response_inhibit_end) + { + savedptr = currentcmd.client; + currentcmd.client = NULL; + if (MyConnect(savedptr)) + sendto_one(from, NULL, ":%s BATCH -%s", me.name, currentcmd.batch); + else + sendto_one(from, NULL, ":%s BATCH %s -%s", me.name, savedptr->name, currentcmd.batch); + } } done: memset(¤tcmd, 0, sizeof(currentcmd)); - labeled_response_inhibit = labeled_response_force = 0; + labeled_response_inhibit = labeled_response_inhibit_end = labeled_response_force = 0; return 0; } @@ -340,3 +360,38 @@ int labeled_response_mtag_is_ok(Client *client, char *name, char *value) return 0; } + +/** Save current context for later use in labeled-response. + * Currently used in /LIST. Is not planned for other places tbh. + */ +void *_labeled_response_save_context(void) +{ + LabeledResponseContext *ctx = safe_alloc(sizeof(LabeledResponseContext)); + memcpy(ctx, ¤tcmd, sizeof(LabeledResponseContext)); + return (void *)ctx; +} + +/** Set previously saved context 'ctx', or clear the context. + * @param ctx The context, or NULL to clear the context. + * @note The client from the previously saved context should be + * the same. Don't save one context when processing + * client A and then restore it when processing client B (duh). + */ +void _labeled_response_set_context(void *ctx) +{ + if (ctx == NULL) + { + /* This means: clear the current context */ + memset(¤tcmd, 0, sizeof(currentcmd)); + } else { + /* Set the current context to the provided one */ + memcpy(¤tcmd, ctx, sizeof(LabeledResponseContext)); + } +} + +/** Force an end of the labeled-response (only used in /LIST atm) */ +void _labeled_response_force_end(void) +{ + if (currentcmd.client) + lr_post_command(currentcmd.client, NULL, NULL); +} diff --git a/src/modules/list.c b/src/modules/list.c index bb0726938..dc8e6e3a2 100644 --- a/src/modules/list.c +++ b/src/modules/list.c @@ -23,7 +23,7 @@ #include "unrealircd.h" CMD_FUNC(cmd_list); -void send_list(Client *client); +int send_list(Client *client); #define MSG_LIST "LIST" @@ -49,6 +49,7 @@ struct ChannelListOptions { time_t chantimemax; time_t topictimemin; time_t topictimemax; + void *lr_context; }; /* Global variables */ @@ -161,8 +162,12 @@ CMD_FUNC(cmd_list) ALLOCATE_CHANNELLISTOPTIONS(client); CHANNELLISTOPTIONS(client)->showall = 1; - if (DBufLength(&client->local->sendQ) < 2048) - send_list(client); + if (send_list(client)) + { + /* Save context since there is more to be sent */ + CHANNELLISTOPTIONS(client)->lr_context = labeled_response_save_context(); + labeled_response_inhibit_end = 1; + } return; } @@ -298,8 +303,12 @@ CMD_FUNC(cmd_list) CHANNELLISTOPTIONS(client)->nolist = nolist; CHANNELLISTOPTIONS(client)->yeslist = yeslist; - if (DBufLength(&client->local->sendQ) < 2048) - send_list(client); + if (send_list(client)) + { + /* Save context since there is more to be sent */ + CHANNELLISTOPTIONS(client)->lr_context = labeled_response_save_context(); + labeled_response_inhibit_end = 1; + } return; } @@ -312,7 +321,7 @@ CMD_FUNC(cmd_list) * client = Local client to send the output back to. * Taken from bahamut, modified for Unreal by codemastr. */ -void send_list(Client *client) +int send_list(Client *client) { Channel *channel; ChannelListOptions *lopt = CHANNELLISTOPTIONS(client); @@ -426,7 +435,7 @@ void send_list(Client *client) { sendnumeric(client, RPL_LISTEND); free_list_options(client); - return; + return 0; } /* @@ -434,7 +443,7 @@ void send_list(Client *client) * at once. */ lopt->starthash = hashnum; - return; + return 1; } EVENT(send_queued_list_data) @@ -443,7 +452,15 @@ EVENT(send_queued_list_data) list_for_each_entry_safe(client, saved, &lclient_list, lclient_node) { if (DoList(client) && IsSendable(client)) - send_list(client); + { + labeled_response_set_context(CHANNELLISTOPTIONS(client)->lr_context); + if (!send_list(client)) + { + /* We are done! */ + labeled_response_force_end(); + } + labeled_response_set_context(NULL); + } } } @@ -457,6 +474,7 @@ void list_md_free(ModData *md) free_entire_name_list(lopt->yeslist); free_entire_name_list(lopt->nolist); + safe_free(lopt->lr_context); safe_free(md->ptr); } diff --git a/src/user.c b/src/user.c index 2b8095535..19d977a94 100644 --- a/src/user.c +++ b/src/user.c @@ -31,9 +31,10 @@ static char buf[BUFSIZE]; /** Inhibit labeled/response reply. This means it will result in an empty ACK * because we cannot handle the command via labeled-reponse. Rare, but * possible in for example /TRACE which multiple servers handle and which - * has no clear end. /LIST is another common example. + * has no clear end. */ MODVAR int labeled_response_inhibit = 0; + /** Force a labeled/response reply (of course, only if a label is present etc.). * This is used in case the "a remote server is handling the request" was * incorrect and there were 0 responses. This is the case for PRIVMSG. @@ -43,6 +44,10 @@ MODVAR int labeled_response_inhibit = 0; */ MODVAR int labeled_response_force = 0; +/** Inhibit labeled/response END. Only used in /LIST. + */ +MODVAR int labeled_response_inhibit_end = 0; + /** Set to 1 if an UTF8 incompatible nick character set is in use */ MODVAR int non_utf8_nick_chars_in_use = 0;