diff --git a/include/client_tags.h b/include/client_tags.h new file mode 100644 index 000000000..c6ba1b4eb --- /dev/null +++ b/include/client_tags.h @@ -0,0 +1,46 @@ +/* + * Solanum: a slightly advanced ircd + * client_tags.h: client tags (message-tags) + * + * Copyright (C) 2022 Ryan Lahfa + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * 1.Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2.Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3.The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, + * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING + * IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef INCLUDED_client_tags_h +#define INCLUDED_client_tags_h + +#define MAX_CLIENT_TAGS 100 +#define CLIENT_TAG_MAX_LENGTH 100 + +struct client_tag_support { + char name[CLIENT_TAG_MAX_LENGTH]; +}; + +extern int add_client_tag(const char *); +extern void remove_client_tag(const char *); +extern void format_client_tags(char *, size_t, const char *, const char *); + +#endif /* INCLUDED_client_tags_h */ diff --git a/include/hook.h b/include/hook.h index c1971614c..949c35316 100644 --- a/include/hook.h +++ b/include/hook.h @@ -47,6 +47,7 @@ extern int h_outbound_msgbuf; extern int h_rehash; extern int h_priv_change; extern int h_cap_change; +extern int h_client_tag_accept; void init_hook(void); int register_hook(const char *name); @@ -156,10 +157,19 @@ typedef struct int del; } hook_data_cap_change; +typedef struct +{ + struct Client *client; + struct MsgBuf *outgoing_msgbuf; + const struct MsgTag *incoming_tag; + bool drop; +} hook_data_client_tag_accept; + enum message_type { MESSAGE_TYPE_NOTICE, MESSAGE_TYPE_PRIVMSG, MESSAGE_TYPE_PART, + MESSAGE_TYPE_TAGMSG, MESSAGE_TYPE_COUNT }; diff --git a/include/msgbuf.h b/include/msgbuf.h index 27e6d75d6..4571b862f 100644 --- a/include/msgbuf.h +++ b/include/msgbuf.h @@ -77,6 +77,12 @@ struct MsgBuf_cache { */ int msgbuf_parse(struct MsgBuf *msgbuf, char *line); +/* + * Parse partially a msgbuf without tags + * assuming msgbuf is already initialized. + */ +int msgbuf_partial_parse(struct MsgBuf *msgbuf, const char *line); + /* * Unparse the tail of a msgbuf perfectly, preserving framing details * msgbuf->para[n] will reach to the end of the line @@ -103,6 +109,8 @@ int msgbuf_vunparse_fmt(char *buf, size_t buflen, const struct MsgBuf *head, uns int msgbuf_unparse_linebuf_tags(char *buf, size_t buflen, void *data); int msgbuf_unparse_prefix(char *buf, size_t *buflen, const struct MsgBuf *msgbuf, unsigned int capmask); +const char *msgbuf_get_tag(const struct MsgBuf *buf, const char *name); + void msgbuf_cache_init(struct MsgBuf_cache *cache, const struct MsgBuf *msgbuf, const rb_strf_t *message); void msgbuf_cache_initf(struct MsgBuf_cache *cache, const struct MsgBuf *msgbuf, const rb_strf_t *message, const char *format, ...) AFP(4, 5); buf_head_t *msgbuf_cache_get(struct MsgBuf_cache *cache, unsigned int caps); diff --git a/include/parse.h b/include/parse.h index a60068dd8..90db4e902 100644 --- a/include/parse.h +++ b/include/parse.h @@ -42,4 +42,7 @@ extern char *reconstruct_parv(int parc, const char *parv[]); extern rb_dictionary *alias_dict; extern rb_dictionary *cmd_dict; +extern const struct MsgBuf *incoming_message; +extern const struct Client *incoming_client; + #endif /* INCLUDED_parse_h_h */ diff --git a/include/s_serv.h b/include/s_serv.h index b917aa6f4..8ff68a0c3 100644 --- a/include/s_serv.h +++ b/include/s_serv.h @@ -68,6 +68,7 @@ extern unsigned int CLICAP_USERHOST_IN_NAMES; extern unsigned int CLICAP_CAP_NOTIFY; extern unsigned int CLICAP_CHGHOST; extern unsigned int CLICAP_ECHO_MESSAGE; +extern unsigned int CLICAP_MESSAGE_TAGS; /* * XXX: this is kind of ugly, but this allows us to have backwards @@ -92,6 +93,7 @@ extern unsigned int CAP_EUID; /* supports EUID (ext UID + nonencap CHGHOST) */ extern unsigned int CAP_EOPMOD; /* supports EOPMOD (ext +z + ext topic) */ extern unsigned int CAP_BAN; /* supports propagated bans */ extern unsigned int CAP_MLOCK; /* supports MLOCK messages */ +extern unsigned int CAP_STAG; /* supports s2s tags and labeled-response */ /* XXX: added for backwards compatibility. --nenolod */ #define CAP_MASK (capability_index_mask(serv_capindex) & ~(CAP_TS6 | CAP_CAP)) diff --git a/ircd/Makefile.am b/ircd/Makefile.am index 4c0a0d24b..b08686af3 100644 --- a/ircd/Makefile.am +++ b/ircd/Makefile.am @@ -23,6 +23,7 @@ libircd_la_SOURCES = \ chmode.c \ class.c \ client.c \ + client_tags.c \ dns.c \ extban.c \ getopt.c \ diff --git a/ircd/client_tags.c b/ircd/client_tags.c new file mode 100644 index 000000000..cf3a5eaeb --- /dev/null +++ b/ircd/client_tags.c @@ -0,0 +1,84 @@ +/* + * Solanum: a slightly advanced ircd + * client_tags.c: client tags support + * + * Copyright (C) 2022 Ryan Lahfa + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * 1.Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2.Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3.The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, + * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING + * IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#include "stdinc.h" +#include "client_tags.h" + +struct client_tag_support *supported_client_tags; +size_t num_client_tags = 0; +size_t max_client_tags = MAX_CLIENT_TAGS; + +void +init_client_tags(void) +{ + supported_client_tags = rb_malloc(sizeof(struct client_tag_support) * MAX_CLIENT_TAGS); +} + +int +add_client_tag(const char *name) +{ + if (num_client_tags >= max_client_tags) + return -1; + + strcpy(supported_client_tags[num_client_tags].name, name); + num_client_tags++; + + return num_client_tags - 1; +} + +void +remove_client_tag(const char *name) +{ + for (size_t index = 0 ; index < num_client_tags ; index++) + { + if (strcmp(supported_client_tags[index].name, name)) { + strcpy(supported_client_tags[index].name, supported_client_tags[num_client_tags - 1].name); + num_client_tags--; + break; + } + } +} + +void +format_client_tags(char *dst, size_t dst_sz, const char *individual_fmt, const char *join_sep) +{ + size_t start = 0; + for (size_t index = 0 ; index < num_client_tags ; index++) { + if (start >= dst_sz) + break; + + if (index > 0) { + dst[start] = ','; + start++; + } + start += snprintf((dst + start), dst_sz - start, individual_fmt, supported_client_tags[index].name); + } +} diff --git a/ircd/hook.c b/ircd/hook.c index b77ceb105..c753775be 100644 --- a/ircd/hook.c +++ b/ircd/hook.c @@ -73,6 +73,7 @@ int h_outbound_msgbuf; int h_rehash; int h_priv_change; int h_cap_change; +int h_client_tag_accept; void init_hook(void) @@ -99,6 +100,7 @@ init_hook(void) h_rehash = register_hook("rehash"); h_priv_change = register_hook("priv_change"); h_cap_change = register_hook("cap_change"); + h_client_tag_accept = register_hook("client_tag_accept"); } /* grow_hooktable() diff --git a/ircd/msgbuf.c b/ircd/msgbuf.c index 7040123a1..a1caafd15 100644 --- a/ircd/msgbuf.c +++ b/ircd/msgbuf.c @@ -133,6 +133,14 @@ msgbuf_parse(struct MsgBuf *msgbuf, char *line) return 1; } } + + return msgbuf_partial_parse(msgbuf, ch); +} + +int +msgbuf_partial_parse(struct MsgBuf *msgbuf, const char *line) +{ + char *ch = strdup(line); /* truncate message if it's too long */ if (strlen(ch) > DATALEN) { @@ -404,6 +412,19 @@ msgbuf_unparse_fmt(char *buf, size_t buflen, const struct MsgBuf *head, unsigned return res; } +const char * +msgbuf_get_tag(const struct MsgBuf *buf, const char *name) +{ + for (size_t i = 0; i < buf->n_tags; i++) + { + if (strcmp(name, buf->tags[i].key)) + continue; + const char *v = buf->tags[i].value; + return v != NULL ? v : ""; + } + return NULL; +} + void msgbuf_cache_init(struct MsgBuf_cache *cache, const struct MsgBuf *msgbuf, const rb_strf_t *message) { diff --git a/ircd/parse.c b/ircd/parse.c index bef6be15e..ec1b8e95b 100644 --- a/ircd/parse.c +++ b/ircd/parse.c @@ -44,6 +44,9 @@ rb_dictionary *cmd_dict = NULL; rb_dictionary *alias_dict = NULL; +const struct MsgBuf *incoming_message = NULL; +const struct Client *incoming_client = NULL; + static void cancel_clients(struct Client *, struct Client *); static void remove_unknown(struct Client *, const char *, char *); @@ -153,6 +156,9 @@ parse(struct Client *client_p, char *pbuffer, char *bufend) return; } + incoming_message = &msgbuf; + incoming_client = client_p; + if(handle_command(mptr, &msgbuf, client_p, from) < -1) { char *p; @@ -177,6 +183,8 @@ parse(struct Client *client_p, char *pbuffer, char *bufend) } } + incoming_message = NULL; + incoming_client = NULL; } /* diff --git a/ircd/s_serv.c b/ircd/s_serv.c index 98caf93a6..b4441f6f5 100644 --- a/ircd/s_serv.c +++ b/ircd/s_serv.c @@ -86,6 +86,7 @@ unsigned int CAP_EUID; unsigned int CAP_EOPMOD; unsigned int CAP_BAN; unsigned int CAP_MLOCK; +unsigned int CAP_STAG; unsigned int CLICAP_MULTI_PREFIX; unsigned int CLICAP_ACCOUNT_NOTIFY; @@ -95,6 +96,7 @@ unsigned int CLICAP_USERHOST_IN_NAMES; unsigned int CLICAP_CAP_NOTIFY; unsigned int CLICAP_CHGHOST; unsigned int CLICAP_ECHO_MESSAGE; +unsigned int CLICAP_MESSAGE_TAGS; /* * initialize our builtin capability table. --nenolod @@ -126,6 +128,7 @@ init_builtin_capabs(void) CAP_EOPMOD = capability_put(serv_capindex, "EOPMOD", NULL); CAP_BAN = capability_put(serv_capindex, "BAN", NULL); CAP_MLOCK = capability_put(serv_capindex, "MLOCK", NULL); + CAP_STAG = capability_put(serv_capindex, "STAG", NULL); capability_require(serv_capindex, "QS"); capability_require(serv_capindex, "EX"); @@ -142,6 +145,7 @@ init_builtin_capabs(void) CLICAP_CAP_NOTIFY = capability_put(cli_capindex, "cap-notify", NULL); CLICAP_CHGHOST = capability_put(cli_capindex, "chghost", &high_priority); CLICAP_ECHO_MESSAGE = capability_put(cli_capindex, "echo-message", NULL); + CLICAP_MESSAGE_TAGS = capability_put(cli_capindex, "message-tags", NULL); } static CNCB serv_connect_callback; diff --git a/ircd/send.c b/ircd/send.c index 9d678a3d0..cdfe833ae 100644 --- a/ircd/send.c +++ b/ircd/send.c @@ -42,7 +42,8 @@ /* send the message to the link the target is attached to */ #define send_linebuf(a,b) _send_linebuf((a->from ? a->from : a) ,b) -#define CLIENT_CAPS_ONLY(x) ((IsClient((x)) && (x)->localClient) ? (x)->localClient->caps : 0) +#define CLIENT_CAP_MASK(x) (MyClient((x)) ? (x)->localClient->caps : \ + ((x)->from == &me || ((x)->from && (x)->from->localClient->caps & CAP_STAG)) ? (unsigned)-1 : 0) static void send_queued_write(rb_fde_t *F, void *data); @@ -221,7 +222,7 @@ send_queued_write(rb_fde_t *F, void *data) static void linebuf_put_tags(buf_head_t *linebuf, const struct MsgBuf *msgbuf, const struct Client *target_p, rb_strf_t *message) { - struct MsgBuf_str_data msgbuf_str_data = { .msgbuf = msgbuf, .caps = CLIENT_CAPS_ONLY(target_p) }; + struct MsgBuf_str_data msgbuf_str_data = { .msgbuf = msgbuf, .caps = CLIENT_CAP_MASK(target_p) }; rb_strf_t strings = { .func = msgbuf_unparse_linebuf_tags, .func_args = &msgbuf_str_data, .length = TAGSLEN + 1, .next = message }; message->length = DATALEN + 1; @@ -265,11 +266,14 @@ linebuf_put_msgf(buf_head_t *linebuf, const rb_strf_t *message, const char *form * notes - to make this reentrant, find a solution for `buf` below */ static void -build_msgbuf_tags(struct MsgBuf *msgbuf, struct Client *from) +build_msgbuf_tags(struct MsgBuf *msgbuf, struct Client *from, char* buf) { hook_data hdata; msgbuf_init(msgbuf); + if (buf != NULL) { + msgbuf_partial_parse(msgbuf, buf); + } hdata.client = from; hdata.arg1 = msgbuf; @@ -300,7 +304,7 @@ sendto_one(struct Client *target_p, const char *pattern, ...) rb_linebuf_newbuf(&linebuf); - build_msgbuf_tags(&msgbuf, &me); + build_msgbuf_tags(&msgbuf, &me, NULL); va_start(args, pattern); linebuf_put_tags(&linebuf, &msgbuf, target_p, &strings); va_end(args); @@ -334,8 +338,8 @@ sendto_one_prefix(struct Client *target_p, struct Client *source_p, sendto_realops_snomask(SNO_GENERAL, L_ALL, "Trying to send to myself!"); return; } - - build_msgbuf_tags(&msgbuf, source_p); + + build_msgbuf_tags(&msgbuf, source_p, NULL); rb_linebuf_newbuf(&linebuf); va_start(args, pattern); @@ -373,7 +377,7 @@ sendto_one_notice(struct Client *target_p, const char *pattern, ...) return; } - build_msgbuf_tags(&msgbuf, &me); + build_msgbuf_tags(&msgbuf, &me, NULL); rb_linebuf_newbuf(&linebuf); va_start(args, pattern); @@ -412,7 +416,7 @@ sendto_one_numeric(struct Client *target_p, int numeric, const char *pattern, .. return; } - build_msgbuf_tags(&msgbuf, &me); + build_msgbuf_tags(&msgbuf, &me, NULL); rb_linebuf_newbuf(&linebuf); va_start(args, pattern); @@ -512,12 +516,13 @@ sendto_channel_flags(struct Client *one, int type, struct Client *source_p, current_serial++; - build_msgbuf_tags(&msgbuf, source_p); - va_start(args, pattern); vsnprintf(buf, sizeof buf, pattern, args); va_end(args); + build_msgbuf_tags(&msgbuf, source_p, buf); + + // TODO: linebuf_put_msgf should not exist. linebuf_put_msgf(&rb_linebuf_remote, NULL, ":%s %s", use_id(source_p), buf); msgbuf_cache_initf(&msgbuf_cache, &msgbuf, &strings, IsPerson(source_p) ? ":%1$s!%2$s@%3$s " : ":%1$s ", @@ -556,7 +561,7 @@ sendto_channel_flags(struct Client *one, int type, struct Client *source_p, } else { - _send_linebuf(target_p, msgbuf_cache_get(&msgbuf_cache, CLIENT_CAPS_ONLY(target_p))); + _send_linebuf(target_p, msgbuf_cache_get(&msgbuf_cache, CLIENT_CAP_MASK(target_p))); } } @@ -565,7 +570,7 @@ sendto_channel_flags(struct Client *one, int type, struct Client *source_p, { target_p = one; - _send_linebuf(target_p, msgbuf_cache_get(&msgbuf_cache, CLIENT_CAPS_ONLY(target_p))); + _send_linebuf(target_p, msgbuf_cache_get(&msgbuf_cache, CLIENT_CAP_MASK(target_p))); } rb_linebuf_donebuf(&rb_linebuf_remote); @@ -596,7 +601,7 @@ sendto_channel_opmod(struct Client *one, struct Client *source_p, rb_linebuf_newbuf(&rb_linebuf_old); rb_linebuf_newbuf(&rb_linebuf_new); - build_msgbuf_tags(&msgbuf, source_p); + build_msgbuf_tags(&msgbuf, source_p, NULL); current_serial++; const char *statusmsg_prefix = (ConfigChannel.opmod_send_statusmsg ? "@" : ""); @@ -659,7 +664,7 @@ sendto_channel_opmod(struct Client *one, struct Client *source_p, target_p->from->serial = current_serial; } } else { - _send_linebuf(target_p, msgbuf_cache_get(&msgbuf_cache, CLIENT_CAPS_ONLY(target_p))); + _send_linebuf(target_p, msgbuf_cache_get(&msgbuf_cache, CLIENT_CAP_MASK(target_p))); } } @@ -668,7 +673,7 @@ sendto_channel_opmod(struct Client *one, struct Client *source_p, { target_p = one; - _send_linebuf(target_p, msgbuf_cache_get(&msgbuf_cache, CLIENT_CAPS_ONLY(target_p))); + _send_linebuf(target_p, msgbuf_cache_get(&msgbuf_cache, CLIENT_CAP_MASK(target_p))); } rb_linebuf_donebuf(&rb_linebuf_old); @@ -693,7 +698,7 @@ _sendto_channel_local(struct Client *source_p, int type, const char *priv, struc struct MsgBuf_cache msgbuf_cache; rb_strf_t strings = { .format = pattern, .format_args = args, .next = NULL }; - build_msgbuf_tags(&msgbuf, source_p); + build_msgbuf_tags(&msgbuf, source_p, NULL); msgbuf_cache_init(&msgbuf_cache, &msgbuf, &strings); @@ -711,7 +716,7 @@ _sendto_channel_local(struct Client *source_p, int type, const char *priv, struc if (priv != NULL && !HasPrivilege(target_p, priv)) continue; - _send_linebuf(target_p, msgbuf_cache_get(&msgbuf_cache, CLIENT_CAPS_ONLY(target_p))); + _send_linebuf(target_p, msgbuf_cache_get(&msgbuf_cache, CLIENT_CAP_MASK(target_p))); } msgbuf_cache_free(&msgbuf_cache); @@ -764,7 +769,7 @@ _sendto_channel_local_with_capability_butone(struct Client *source_p, struct Cli struct MsgBuf_cache msgbuf_cache; rb_strf_t strings = { .format = pattern, .format_args = args, .next = NULL }; - build_msgbuf_tags(&msgbuf, source_p); + build_msgbuf_tags(&msgbuf, source_p, NULL); msgbuf_cache_init(&msgbuf_cache, &msgbuf, &strings); RB_DLINK_FOREACH_SAFE(ptr, next_ptr, chptr->locmembers.head) @@ -783,7 +788,7 @@ _sendto_channel_local_with_capability_butone(struct Client *source_p, struct Cli if(type && ((msptr->flags & type) == 0)) continue; - _send_linebuf(target_p, msgbuf_cache_get(&msgbuf_cache, CLIENT_CAPS_ONLY(target_p))); + _send_linebuf(target_p, msgbuf_cache_get(&msgbuf_cache, CLIENT_CAP_MASK(target_p))); } msgbuf_cache_free(&msgbuf_cache); @@ -843,7 +848,7 @@ sendto_channel_local_butone(struct Client *one, int type, struct Channel *chptr, struct MsgBuf_cache msgbuf_cache; rb_strf_t strings = { .format = pattern, .format_args = &args, .next = NULL }; - build_msgbuf_tags(&msgbuf, one); + build_msgbuf_tags(&msgbuf, one, NULL); va_start(args, pattern); msgbuf_cache_init(&msgbuf_cache, &msgbuf, &strings); @@ -864,7 +869,7 @@ sendto_channel_local_butone(struct Client *one, int type, struct Channel *chptr, continue; /* attach the present linebuf to the target */ - _send_linebuf(target_p, msgbuf_cache_get(&msgbuf_cache, CLIENT_CAPS_ONLY(target_p))); + _send_linebuf(target_p, msgbuf_cache_get(&msgbuf_cache, CLIENT_CAP_MASK(target_p))); } msgbuf_cache_free(&msgbuf_cache); @@ -898,7 +903,7 @@ sendto_common_channels_local(struct Client *user, int cap, int negcap, const cha struct MsgBuf_cache msgbuf_cache; rb_strf_t strings = { .format = pattern, .format_args = &args, .next = NULL }; - build_msgbuf_tags(&msgbuf, user); + build_msgbuf_tags(&msgbuf, user, NULL); va_start(args, pattern); msgbuf_cache_init(&msgbuf_cache, &msgbuf, &strings); @@ -923,7 +928,7 @@ sendto_common_channels_local(struct Client *user, int cap, int negcap, const cha continue; target_p->serial = current_serial; - send_linebuf(target_p, msgbuf_cache_get(&msgbuf_cache, CLIENT_CAPS_ONLY(target_p))); + send_linebuf(target_p, msgbuf_cache_get(&msgbuf_cache, CLIENT_CAP_MASK(target_p))); } } @@ -932,7 +937,7 @@ sendto_common_channels_local(struct Client *user, int cap, int negcap, const cha */ if(MyConnect(user) && (user->serial != current_serial) && IsCapable(user, cap) && NotCapable(user, negcap)) { - send_linebuf(user, msgbuf_cache_get(&msgbuf_cache, CLIENT_CAPS_ONLY(user))); + send_linebuf(user, msgbuf_cache_get(&msgbuf_cache, CLIENT_CAP_MASK(user))); } msgbuf_cache_free(&msgbuf_cache); @@ -965,7 +970,7 @@ sendto_common_channels_local_butone(struct Client *user, int cap, int negcap, co struct MsgBuf_cache msgbuf_cache; rb_strf_t strings = { .format = pattern, .format_args = &args, .next = NULL }; - build_msgbuf_tags(&msgbuf, user); + build_msgbuf_tags(&msgbuf, user, NULL); va_start(args, pattern); msgbuf_cache_init(&msgbuf_cache, &msgbuf, &strings); @@ -992,7 +997,7 @@ sendto_common_channels_local_butone(struct Client *user, int cap, int negcap, co continue; target_p->serial = current_serial; - send_linebuf(target_p, msgbuf_cache_get(&msgbuf_cache, CLIENT_CAPS_ONLY(target_p))); + send_linebuf(target_p, msgbuf_cache_get(&msgbuf_cache, CLIENT_CAP_MASK(target_p))); } } @@ -1021,12 +1026,12 @@ sendto_match_butone(struct Client *one, struct Client *source_p, rb_linebuf_newbuf(&rb_linebuf_remote); - build_msgbuf_tags(&msgbuf, source_p); - va_start(args, pattern); vsnprintf(buf, sizeof(buf), pattern, args); va_end(args); + build_msgbuf_tags(&msgbuf, source_p, buf); + msgbuf_cache_initf(&msgbuf_cache, &msgbuf, &strings, IsServer(source_p) ? ":%s " : ":%s!%s@%s ", source_p->name, source_p->username, source_p->host); @@ -1040,7 +1045,7 @@ sendto_match_butone(struct Client *one, struct Client *source_p, target_p = ptr->data; if(match(mask, target_p->host)) - _send_linebuf(target_p, msgbuf_cache_get(&msgbuf_cache, CLIENT_CAPS_ONLY(target_p))); + _send_linebuf(target_p, msgbuf_cache_get(&msgbuf_cache, CLIENT_CAP_MASK(target_p))); } } /* what = MATCH_SERVER, if it doesnt match us, just send remote */ @@ -1049,7 +1054,7 @@ sendto_match_butone(struct Client *one, struct Client *source_p, RB_DLINK_FOREACH_SAFE(ptr, next_ptr, lclient_list.head) { target_p = ptr->data; - _send_linebuf(target_p, msgbuf_cache_get(&msgbuf_cache, CLIENT_CAPS_ONLY(target_p))); + _send_linebuf(target_p, msgbuf_cache_get(&msgbuf_cache, CLIENT_CAP_MASK(target_p))); } } @@ -1144,7 +1149,7 @@ sendto_local_clients_with_capability(int cap, const char *pattern, ...) struct MsgBuf_cache msgbuf_cache; rb_strf_t strings = { .format = pattern, .format_args = &args, .next = NULL }; - build_msgbuf_tags(&msgbuf, &me); + build_msgbuf_tags(&msgbuf, &me, NULL); va_start(args, pattern); msgbuf_cache_init(&msgbuf_cache, &msgbuf, &strings); @@ -1157,7 +1162,7 @@ sendto_local_clients_with_capability(int cap, const char *pattern, ...) if(IsIOError(target_p) || !IsCapable(target_p, cap)) continue; - send_linebuf(target_p, msgbuf_cache_get(&msgbuf_cache, CLIENT_CAPS_ONLY(target_p))); + send_linebuf(target_p, msgbuf_cache_get(&msgbuf_cache, CLIENT_CAP_MASK(target_p))); } msgbuf_cache_free(&msgbuf_cache); @@ -1180,7 +1185,7 @@ sendto_monitor(struct Client *source_p, struct monitor *monptr, const char *patt struct MsgBuf_cache msgbuf_cache; rb_strf_t strings = { .format = pattern, .format_args = &args, .next = NULL }; - build_msgbuf_tags(&msgbuf, source_p); + build_msgbuf_tags(&msgbuf, source_p, NULL); va_start(args, pattern); msgbuf_cache_init(&msgbuf_cache, &msgbuf, &strings); @@ -1193,7 +1198,7 @@ sendto_monitor(struct Client *source_p, struct monitor *monptr, const char *patt if(IsIOError(target_p)) continue; - _send_linebuf(target_p, msgbuf_cache_get(&msgbuf_cache, CLIENT_CAPS_ONLY(target_p))); + _send_linebuf(target_p, msgbuf_cache_get(&msgbuf_cache, CLIENT_CAP_MASK(target_p))); } msgbuf_cache_free(&msgbuf_cache); @@ -1223,7 +1228,7 @@ _sendto_anywhere(struct Client *dest_p, struct Client *target_p, } else { struct MsgBuf msgbuf; - build_msgbuf_tags(&msgbuf, source_p); + build_msgbuf_tags(&msgbuf, source_p, NULL); linebuf_put_tagsf(&linebuf, &msgbuf, dest_p, &strings, IsPerson(source_p) ? ":%1$s!%4$s@%5$s %2$s %3$s " : ":%1$s %2$s %3$s ", @@ -1298,13 +1303,13 @@ sendto_realops_snomask(int flags, int level, const char *pattern, ...) struct MsgBuf msgbuf; struct MsgBuf_cache msgbuf_cache; - build_msgbuf_tags(&msgbuf, &me); - /* rather a lot of copying around, oh well -- jilles */ va_start(args, pattern); vsnprintf(buf, sizeof(buf), pattern, args); va_end(args); + build_msgbuf_tags(&msgbuf, &me, buf); + msgbuf_cache_initf(&msgbuf_cache, &msgbuf, NULL, ":%s NOTICE * :*** Notice -- %s", me.name, buf); @@ -1336,7 +1341,7 @@ sendto_realops_snomask(int flags, int level, const char *pattern, ...) continue; if (client_p->snomask & flags) { - _send_linebuf(client_p, msgbuf_cache_get(&msgbuf_cache, CLIENT_CAPS_ONLY(client_p))); + _send_linebuf(client_p, msgbuf_cache_get(&msgbuf_cache, CLIENT_CAP_MASK(client_p))); } } @@ -1360,7 +1365,7 @@ sendto_realops_snomask_from(int flags, int level, struct Client *source_p, struct MsgBuf_cache msgbuf_cache; rb_strf_t strings = { .format = pattern, .format_args = &args, .next = NULL }; - build_msgbuf_tags(&msgbuf, &me); + build_msgbuf_tags(&msgbuf, &me, NULL); va_start(args, pattern); msgbuf_cache_initf(&msgbuf_cache, &msgbuf, &strings, @@ -1379,7 +1384,7 @@ sendto_realops_snomask_from(int flags, int level, struct Client *source_p, continue; if (client_p->snomask & flags) { - _send_linebuf(client_p, msgbuf_cache_get(&msgbuf_cache, CLIENT_CAPS_ONLY(client_p))); + _send_linebuf(client_p, msgbuf_cache_get(&msgbuf_cache, CLIENT_CAP_MASK(client_p))); } } @@ -1406,7 +1411,7 @@ sendto_wallops_flags(int flags, struct Client *source_p, const char *pattern, .. struct MsgBuf_cache msgbuf_cache; rb_strf_t strings = { .format = pattern, .format_args = &args, .next = NULL }; - build_msgbuf_tags(&msgbuf, source_p); + build_msgbuf_tags(&msgbuf, source_p, NULL); va_start(args, pattern); if (IsPerson(source_p)) { @@ -1424,7 +1429,7 @@ sendto_wallops_flags(int flags, struct Client *source_p, const char *pattern, .. client_p = ptr->data; if (client_p->umodes & flags) { - _send_linebuf(client_p, msgbuf_cache_get(&msgbuf_cache, CLIENT_CAPS_ONLY(client_p))); + _send_linebuf(client_p, msgbuf_cache_get(&msgbuf_cache, CLIENT_CAP_MASK(client_p))); } } @@ -1445,7 +1450,7 @@ kill_client(struct Client *target_p, struct Client *diedie, const char *pattern, struct MsgBuf msgbuf; rb_strf_t strings = { .format = pattern, .format_args = &args, .next = NULL }; - build_msgbuf_tags(&msgbuf, &me); + build_msgbuf_tags(&msgbuf, &me, NULL); rb_linebuf_newbuf(&linebuf); diff --git a/ircd/supported.c b/ircd/supported.c index 4aa653ecd..8e3f5d8f4 100644 --- a/ircd/supported.c +++ b/ircd/supported.c @@ -79,6 +79,7 @@ #include "supported.h" #include "chmode.h" #include "send.h" +#include "client_tags.h" static char allowed_chantypes[BUFSIZE]; rb_dlink_list isupportlist; @@ -300,6 +301,21 @@ isupport_nicklen(const void *ptr) return result; } +static const char * +isupport_client_tag_deny(const void *ptr) +{ + static char result[200]; + static char exceptions[198]; + + format_client_tags(exceptions, sizeof exceptions, "-%s", ","); + + if (EmptyString(exceptions)) + snprintf(result, sizeof result, "%s", "*"); + else + snprintf(result, sizeof result, "%s,%s", "*", exceptions); + return result; +} + void init_isupport(void) { @@ -326,6 +342,7 @@ init_isupport(void) add_isupport("DEAF", isupport_umode, "D"); add_isupport("TARGMAX", isupport_targmax, NULL); add_isupport("EXTBAN", isupport_extban, NULL); + add_isupport("CLIENTTAGDENY", isupport_client_tag_deny, NULL); } void diff --git a/modules/Makefile.am b/modules/Makefile.am index c99e431fa..1e63c5bb9 100644 --- a/modules/Makefile.am +++ b/modules/Makefile.am @@ -8,6 +8,7 @@ LIBS += $(top_srcdir)/ircd/libircd.la auto_load_moddir=@moduledir@/autoload auto_load_mod_LTLIBRARIES = \ + cap_message_tags.la \ cap_account_tag.la \ cap_server_time.la \ chm_nocolour.la \ diff --git a/modules/cap_message_tags.c b/modules/cap_message_tags.c new file mode 100644 index 000000000..0cbc06ab2 --- /dev/null +++ b/modules/cap_message_tags.c @@ -0,0 +1,84 @@ +/* + * Solanum: a slightly advanced ircd + * cap_allow_list.c: implement the message-tags IRCv3 specification + * + * Copyright (c) 2022 Ryan Lahfa + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice is present in all copies. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, + * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING + * IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#include "stdinc.h" +#include "modules.h" +#include "hook.h" +#include "client.h" +#include "ircd.h" +#include "send.h" +#include "s_conf.h" +#include "s_user.h" +#include "s_serv.h" +#include "numeric.h" +#include "chmode.h" +#include "inline/stringops.h" + +static const char cap_message_tags_desc[] = + "Propagate message tags"; + +static void cap_message_tags_process(void *); +unsigned int CLICAP_MESSAGE_TAGS = 0; + +mapi_hfn_list_av1 cap_message_tags_hfnlist[] = { + { "outbound_msgbuf", cap_message_tags_process }, + { NULL, NULL } +}; +mapi_cap_list_av2 cap_message_tags_cap_list[] = { + { MAPI_CAP_CLIENT, "message-tags", NULL, &CLICAP_MESSAGE_TAGS }, + { 0, NULL, NULL, NULL }, +}; + +static void +cap_message_tags_process(void *data_) +{ + hook_data_client_tag_accept moduledata; + + hook_data *data = data_; + struct MsgBuf *msgbuf = data->arg1; + + moduledata.client = data->client; + moduledata.outgoing_msgbuf = msgbuf; + + if (incoming_client != NULL) { + size_t n_tags = incoming_message->n_tags; + for (size_t index = 0 ; index < n_tags ; index++) { + if (incoming_message->tags[index].key[0] == '+') { + moduledata.incoming_tag = &incoming_message->tags[index]; + call_hook(h_client_tag_accept, &moduledata); + + // In case a downstream module decides this client-tag + // warrants a silent drop of the response + // We honor it + // e.g. typing notifications spamming + if (moduledata.drop) + { + //data->drop = true; + //return; + } + } + } + } +} + +DECLARE_MODULE_AV2(cap_message_tags, NULL, NULL, NULL, NULL, cap_message_tags_hfnlist, cap_message_tags_cap_list, NULL, cap_message_tags_desc); diff --git a/modules/cap_server_time.c b/modules/cap_server_time.c index 494498809..9abb5f07c 100644 --- a/modules/cap_server_time.c +++ b/modules/cap_server_time.c @@ -32,6 +32,7 @@ #include "s_serv.h" #include "numeric.h" #include "chmode.h" +#include "parse.h" #include "inline/stringops.h" static const char cap_server_time_desc[] = @@ -54,9 +55,18 @@ cap_server_time_process(void *data_) { hook_data *data = data_; static char buf[BUFSIZE]; + const char *tagged_time; struct MsgBuf *msgbuf = data->arg1; struct timeval tv; + if (incoming_client != NULL && IsServer(incoming_client) && !msgbuf_get_tag(msgbuf, "time") && (tagged_time = msgbuf_get_tag(incoming_message, "time"))) + { + msgbuf_append_tag(msgbuf, "time", tagged_time, CLICAP_SERVER_TIME); + } + + if (data->client != NULL && !IsMe(data->client) && !MyClient(data->client)) + return; + if (!rb_gettimeofday(&tv, NULL)) { if (strftime(buf, sizeof(buf), "%Y-%m-%dT%H:%M:%S.", gmtime(&tv.tv_sec)) == 0) return; diff --git a/modules/core/m_message.c b/modules/core/m_message.c index cebedeab9..67ece3a9c 100644 --- a/modules/core/m_message.c +++ b/modules/core/m_message.c @@ -41,14 +41,16 @@ #include "s_newconf.h" #include "s_stats.h" #include "tgchange.h" +#include "client_tags.h" #include "inline/stringops.h" static const char message_desc[] = - "Provides the PRIVMSG and NOTICE commands to send messages to users and channels"; + "Provides the PRIVMSG, NOTICE, TAGMSG commands to send messages to users and channels"; static void m_message(enum message_type, struct MsgBuf *, struct Client *, struct Client *, int, const char **); static void m_privmsg(struct MsgBuf *, struct Client *, struct Client *, int, const char **); static void m_notice(struct MsgBuf *, struct Client *, struct Client *, int, const char **); +static void m_tagmsg(struct MsgBuf *, struct Client *, struct Client *, int, const char **); static void m_echo(struct MsgBuf *, struct Client *, struct Client *, int, const char **); static void echo_msg(struct Client *, struct Client *, enum message_type, const char *); @@ -57,6 +59,14 @@ static void expire_tgchange(void *unused); static struct ev_entry *expire_tgchange_event; static unsigned int CAP_ECHO; +static unsigned int CAP_TAGS; + +struct entity +{ + void* ptr; + int type; + int flags; +}; static int modinit(void) @@ -80,27 +90,25 @@ struct Message notice_msgtab = { "NOTICE", 0, 0, 0, 0, {mg_unreg, {m_notice, 0}, {m_notice, 0}, {m_notice, 0}, mg_ignore, {m_notice, 0}} }; +struct Message tagmsg_msgtab = { + "TAGMSG", 0, 0, 0, 0, + {mg_unreg, {m_tagmsg, 0}, {m_tagmsg, 0}, mg_ignore, mg_ignore, {m_tagmsg, 0}} +}; struct Message echo_msgtab = { "ECHO", 0, 0, 0, 0, {mg_unreg, mg_ignore, {m_echo, 3}, mg_ignore, mg_ignore, mg_ignore} }; -mapi_clist_av1 message_clist[] = { &privmsg_msgtab, ¬ice_msgtab, &echo_msgtab, NULL }; +mapi_clist_av1 message_clist[] = { &privmsg_msgtab, ¬ice_msgtab, &echo_msgtab, &tagmsg_msgtab, NULL }; mapi_cap_list_av2 message_cap_list[] = { { MAPI_CAP_SERVER, "ECHO", NULL, &CAP_ECHO }, + { MAPI_CAP_SERVER, "TAGS", NULL, &CAP_TAGS }, { 0, NULL, NULL, NULL } }; DECLARE_MODULE_AV2(message, modinit, moddeinit, message_clist, NULL, NULL, message_cap_list, NULL, message_desc); -struct entity -{ - void *ptr; - int type; - int flags; -}; - static int build_target_list(enum message_type msgtype, struct Client *client_p, struct Client *source_p, const char *nicks_channels, const char *text); @@ -163,6 +171,7 @@ static void handle_special(enum message_type msgtype, const char *cmdname[MESSAGE_TYPE_COUNT] = { [MESSAGE_TYPE_PRIVMSG] = "PRIVMSG", [MESSAGE_TYPE_NOTICE] = "NOTICE", + [MESSAGE_TYPE_TAGMSG] = "TAGMSG" }; static void @@ -177,6 +186,14 @@ m_notice(struct MsgBuf *msgbuf_p, struct Client *client_p, struct Client *source m_message(MESSAGE_TYPE_NOTICE, msgbuf_p, client_p, source_p, parc, parv); } + +static void +m_tagmsg(struct MsgBuf *msgbuf_p, struct Client *client_p, struct Client *source_p, int parc, const char *parv[]) { + // TODO: test if target supports tags, if not, ignore. + m_message(MESSAGE_TYPE_TAGMSG, msgbuf_p, client_p, source_p, parc, parv); +} + + /* * inputs - flag privmsg or notice * - pointer to client_p @@ -257,6 +274,7 @@ m_echo(struct MsgBuf *msgbuf_p, struct Client *client_p, struct Client *source_p { case 'P': msgtype = MESSAGE_TYPE_PRIVMSG; break; case 'N': msgtype = MESSAGE_TYPE_NOTICE; break; + case 'T': msgtype = MESSAGE_TYPE_TAGMSG; break; default: return; } diff --git a/tests/send1.c b/tests/send1.c index 973323226..fa5af69ee 100644 --- a/tests/send1.c +++ b/tests/send1.c @@ -322,10 +322,10 @@ static void sendto_one_prefix1__tags(void) is_client_sendq("@time=" ADVENTURE_TIME ";account=test :" TEST_NICK " TEST LChanOp :Hello World!" CRLF, local_chan_o, MSG); sendto_one_prefix(local_chan_o, remote, "TEST", ":Hello %s!", "World"); - is_client_sendq("@time=" ADVENTURE_TIME ";account=rtest :" TEST_REMOTE_NICK " TEST LChanOp :Hello World!" CRLF, local_chan_o, MSG); + is_client_sendq("@account=rtest :" TEST_REMOTE_NICK " TEST LChanOp :Hello World!" CRLF, local_chan_o, MSG); sendto_one_prefix(local_chan_o, server, "TEST", ":Hello %s!", "World"); - is_client_sendq("@time=" ADVENTURE_TIME " :" TEST_SERVER_NAME " TEST LChanOp :Hello World!" CRLF, local_chan_o, MSG); + is_client_sendq(":" TEST_SERVER_NAME " TEST LChanOp :Hello World!" CRLF, local_chan_o, MSG); sendto_one_prefix(local_chan_ov, &me, "TEST", ":Hello %s!", "World"); @@ -335,10 +335,10 @@ static void sendto_one_prefix1__tags(void) is_client_sendq("@time=" ADVENTURE_TIME " :" TEST_NICK " TEST LChanOpVoice :Hello World!" CRLF, local_chan_ov, MSG); sendto_one_prefix(local_chan_ov, remote, "TEST", ":Hello %s!", "World"); - is_client_sendq("@time=" ADVENTURE_TIME " :" TEST_REMOTE_NICK " TEST LChanOpVoice :Hello World!" CRLF, local_chan_ov, MSG); + is_client_sendq(":" TEST_REMOTE_NICK " TEST LChanOpVoice :Hello World!" CRLF, local_chan_ov, MSG); sendto_one_prefix(local_chan_ov, server, "TEST", ":Hello %s!", "World"); - is_client_sendq("@time=" ADVENTURE_TIME " :" TEST_SERVER_NAME " TEST LChanOpVoice :Hello World!" CRLF, local_chan_ov, MSG); + is_client_sendq(":" TEST_SERVER_NAME " TEST LChanOpVoice :Hello World!" CRLF, local_chan_ov, MSG); sendto_one_prefix(local_chan_v, &me, "TEST", ":Hello %s!", "World"); @@ -776,8 +776,8 @@ static void sendto_channel_flags__remote__all_members__tags(void) local_chan_v->localClient->caps |= CAP_ACCOUNT_TAG; sendto_channel_flags(server, ALL_MEMBERS, remote_chan_p, channel, "TEST #placeholder :Hello %s!", "World"); - is_client_sendq("@time=" ADVENTURE_TIME ";account=test :RChanPeon" TEST_ID_SUFFIX " TEST #placeholder :Hello World!" CRLF, local_chan_o, "On channel; " MSG); - is_client_sendq("@time=" ADVENTURE_TIME " :RChanPeon" TEST_ID_SUFFIX " TEST #placeholder :Hello World!" CRLF, local_chan_ov, "On channel; " MSG); + is_client_sendq("@account=test :RChanPeon" TEST_ID_SUFFIX " TEST #placeholder :Hello World!" CRLF, local_chan_o, "On channel; " MSG); + is_client_sendq(":RChanPeon" TEST_ID_SUFFIX " TEST #placeholder :Hello World!" CRLF, local_chan_ov, "On channel; " MSG); is_client_sendq("@account=test :RChanPeon" TEST_ID_SUFFIX " TEST #placeholder :Hello World!" CRLF, local_chan_v, "On channel; " MSG); is_client_sendq(":RChanPeon" TEST_ID_SUFFIX " TEST #placeholder :Hello World!" CRLF, local_chan_p, "On channel; " MSG); is_client_sendq_empty(local_chan_d, "Deaf; " MSG); @@ -787,8 +787,8 @@ static void sendto_channel_flags__remote__all_members__tags(void) standard_ids(); sendto_channel_flags(server, ALL_MEMBERS, remote_chan_p, channel, "TEST #placeholder :Hello %s!", "World"); - is_client_sendq("@time=" ADVENTURE_TIME ";account=test :RChanPeon" TEST_ID_SUFFIX " TEST #placeholder :Hello World!" CRLF, local_chan_o, "On channel; " MSG); - is_client_sendq("@time=" ADVENTURE_TIME " :RChanPeon" TEST_ID_SUFFIX " TEST #placeholder :Hello World!" CRLF, local_chan_ov, "On channel; " MSG); + is_client_sendq("@account=test :RChanPeon" TEST_ID_SUFFIX " TEST #placeholder :Hello World!" CRLF, local_chan_o, "On channel; " MSG); + is_client_sendq(":RChanPeon" TEST_ID_SUFFIX " TEST #placeholder :Hello World!" CRLF, local_chan_ov, "On channel; " MSG); is_client_sendq("@account=test :RChanPeon" TEST_ID_SUFFIX " TEST #placeholder :Hello World!" CRLF, local_chan_v, "On channel; " MSG); is_client_sendq(":RChanPeon" TEST_ID_SUFFIX " TEST #placeholder :Hello World!" CRLF, local_chan_p, "On channel; " MSG); is_client_sendq_empty(local_chan_d, "Deaf; " MSG); @@ -1481,8 +1481,8 @@ static void sendto_channel_opmod__remote__tags(void) standard_server_caps(0, CAP_CHW | CAP_EOPMOD); sendto_channel_opmod(server2, remote2_chan_d, channel, "TEST", "Hello World!"); - is_client_sendq("@time=" ADVENTURE_TIME ";account=test :R2ChanDeaf" TEST_ID_SUFFIX " TEST " TEST_CHANNEL " :Hello World!" CRLF, local_chan_o, "On channel; " MSG); - is_client_sendq("@time=" ADVENTURE_TIME " :R2ChanDeaf" TEST_ID_SUFFIX " TEST " TEST_CHANNEL " :Hello World!" CRLF, local_chan_ov, "On channel; " MSG); + is_client_sendq("@account=test :R2ChanDeaf" TEST_ID_SUFFIX " TEST " TEST_CHANNEL " :Hello World!" CRLF, local_chan_o, "On channel; " MSG); + is_client_sendq(":R2ChanDeaf" TEST_ID_SUFFIX " TEST " TEST_CHANNEL " :Hello World!" CRLF, local_chan_ov, "On channel; " MSG); is_client_sendq_empty(local_chan_v, "Not +o; " MSG); is_client_sendq_empty(local_chan_d, "Deaf; " MSG); is_client_sendq_empty(server, "Message source; " MSG); @@ -1492,8 +1492,8 @@ static void sendto_channel_opmod__remote__tags(void) standard_server_caps(CAP_CHW, CAP_EOPMOD); sendto_channel_opmod(server2, remote2_chan_d, channel, "TEST", "Hello World!"); - is_client_sendq("@time=" ADVENTURE_TIME ";account=test :R2ChanDeaf" TEST_ID_SUFFIX " TEST " TEST_CHANNEL " :Hello World!" CRLF, local_chan_o, "On channel; " MSG); - is_client_sendq("@time=" ADVENTURE_TIME " :R2ChanDeaf" TEST_ID_SUFFIX " TEST " TEST_CHANNEL " :Hello World!" CRLF, local_chan_ov, "On channel; " MSG); + is_client_sendq("@account=test :R2ChanDeaf" TEST_ID_SUFFIX " TEST " TEST_CHANNEL " :Hello World!" CRLF, local_chan_o, "On channel; " MSG); + is_client_sendq(":R2ChanDeaf" TEST_ID_SUFFIX " TEST " TEST_CHANNEL " :Hello World!" CRLF, local_chan_ov, "On channel; " MSG); is_client_sendq_empty(local_chan_v, "Not +o; " MSG); is_client_sendq_empty(local_chan_d, "Deaf; " MSG); is_client_sendq(":" TEST_SERVER2_ID " NOTICE @" TEST_CHANNEL " : Hello World!" CRLF, server, MSG); @@ -3068,8 +3068,8 @@ static void sendto_match_butone__host__tags(void) // Remote sendto_match_butone(NULL, remote, "*.test", MATCH_HOST, "TEST Hello %s!", "World"); is_client_sendq(":" TEST_REMOTE_NICK TEST_ID_SUFFIX " TEST Hello World!" CRLF, user, "Host matches; " MSG); - is_client_sendq("@time=" ADVENTURE_TIME ";account=rtest :" TEST_REMOTE_NICK TEST_ID_SUFFIX " TEST Hello World!" CRLF, local_chan_o, "Host matches; " MSG); - is_client_sendq("@time=" ADVENTURE_TIME " :" TEST_REMOTE_NICK TEST_ID_SUFFIX " TEST Hello World!" CRLF, local_chan_ov, "Host matches; " MSG); + is_client_sendq("@account=rtest :" TEST_REMOTE_NICK TEST_ID_SUFFIX " TEST Hello World!" CRLF, local_chan_o, "Host matches; " MSG); + is_client_sendq(":" TEST_REMOTE_NICK TEST_ID_SUFFIX " TEST Hello World!" CRLF, local_chan_ov, "Host matches; " MSG); is_client_sendq("@account=rtest :" TEST_REMOTE_NICK TEST_ID_SUFFIX " TEST Hello World!" CRLF, local_chan_v, "Host matches; " MSG); is_client_sendq(":" TEST_REMOTE_NICK TEST_ID_SUFFIX " TEST Hello World!" CRLF, local_chan_p, "Host matches; " MSG); is_client_sendq(":" TEST_REMOTE_NICK TEST_ID_SUFFIX " TEST Hello World!" CRLF, local_chan_d, "Host matches; " MSG); @@ -3090,8 +3090,8 @@ static void sendto_match_butone__host__tags(void) sendto_match_butone(server, remote, "*.test", MATCH_HOST, "TEST Hello %s!", "World"); is_client_sendq(":" TEST_REMOTE_NICK TEST_ID_SUFFIX " TEST Hello World!" CRLF, user, "Host matches; " MSG); - is_client_sendq("@time=" ADVENTURE_TIME ";account=rtest :" TEST_REMOTE_NICK TEST_ID_SUFFIX " TEST Hello World!" CRLF, local_chan_o, "Host matches; " MSG); - is_client_sendq("@time=" ADVENTURE_TIME " :" TEST_REMOTE_NICK TEST_ID_SUFFIX " TEST Hello World!" CRLF, local_chan_ov, "Host matches; " MSG); + is_client_sendq("@account=rtest :" TEST_REMOTE_NICK TEST_ID_SUFFIX " TEST Hello World!" CRLF, local_chan_o, "Host matches; " MSG); + is_client_sendq(":" TEST_REMOTE_NICK TEST_ID_SUFFIX " TEST Hello World!" CRLF, local_chan_ov, "Host matches; " MSG); is_client_sendq("@account=rtest :" TEST_REMOTE_NICK TEST_ID_SUFFIX " TEST Hello World!" CRLF, local_chan_v, "Host matches; " MSG); is_client_sendq(":" TEST_REMOTE_NICK TEST_ID_SUFFIX " TEST Hello World!" CRLF, local_chan_p, "Host matches; " MSG); is_client_sendq(":" TEST_REMOTE_NICK TEST_ID_SUFFIX " TEST Hello World!" CRLF, local_chan_d, "Host matches; " MSG); @@ -3275,8 +3275,8 @@ static void sendto_match_butone__server__tags(void) // Remote sendto_match_butone(NULL, remote, "me.*", MATCH_SERVER, "TEST Hello %s!", "World"); is_client_sendq(":" TEST_REMOTE_NICK TEST_ID_SUFFIX " TEST Hello World!" CRLF, user, "Server matches; " MSG); - is_client_sendq("@time=" ADVENTURE_TIME ";account=rtest :" TEST_REMOTE_NICK TEST_ID_SUFFIX " TEST Hello World!" CRLF, local_chan_o, "Server matches; " MSG); - is_client_sendq("@time=" ADVENTURE_TIME " :" TEST_REMOTE_NICK TEST_ID_SUFFIX " TEST Hello World!" CRLF, local_chan_ov, "Server matches; " MSG); + is_client_sendq("@account=rtest :" TEST_REMOTE_NICK TEST_ID_SUFFIX " TEST Hello World!" CRLF, local_chan_o, "Server matches; " MSG); + is_client_sendq(":" TEST_REMOTE_NICK TEST_ID_SUFFIX " TEST Hello World!" CRLF, local_chan_ov, "Server matches; " MSG); is_client_sendq("@account=rtest :" TEST_REMOTE_NICK TEST_ID_SUFFIX " TEST Hello World!" CRLF, local_chan_v, "Server matches; " MSG); is_client_sendq(":" TEST_REMOTE_NICK TEST_ID_SUFFIX " TEST Hello World!" CRLF, local_chan_p, "Server matches; " MSG); is_client_sendq(":" TEST_REMOTE_NICK TEST_ID_SUFFIX " TEST Hello World!" CRLF, local_chan_d, "Server matches; " MSG); @@ -3297,8 +3297,8 @@ static void sendto_match_butone__server__tags(void) sendto_match_butone(server, remote, "me.*", MATCH_SERVER, "TEST Hello %s!", "World"); is_client_sendq(":" TEST_REMOTE_NICK TEST_ID_SUFFIX " TEST Hello World!" CRLF, user, "Server matches; " MSG); - is_client_sendq("@time=" ADVENTURE_TIME ";account=rtest :" TEST_REMOTE_NICK TEST_ID_SUFFIX " TEST Hello World!" CRLF, local_chan_o, "Server matches; " MSG); - is_client_sendq("@time=" ADVENTURE_TIME " :" TEST_REMOTE_NICK TEST_ID_SUFFIX " TEST Hello World!" CRLF, local_chan_ov, "Server matches; " MSG); + is_client_sendq("@account=rtest :" TEST_REMOTE_NICK TEST_ID_SUFFIX " TEST Hello World!" CRLF, local_chan_o, "Server matches; " MSG); + is_client_sendq(":" TEST_REMOTE_NICK TEST_ID_SUFFIX " TEST Hello World!" CRLF, local_chan_ov, "Server matches; " MSG); is_client_sendq("@account=rtest :" TEST_REMOTE_NICK TEST_ID_SUFFIX " TEST Hello World!" CRLF, local_chan_v, "Server matches; " MSG); is_client_sendq(":" TEST_REMOTE_NICK TEST_ID_SUFFIX " TEST Hello World!" CRLF, local_chan_p, "Server matches; " MSG); is_client_sendq(":" TEST_REMOTE_NICK TEST_ID_SUFFIX " TEST Hello World!" CRLF, local_chan_d, "Server matches; " MSG);