From 16888dc6158327d089e9306b6ba6ebdf5a8aa9e6 Mon Sep 17 00:00:00 2001 From: MrIron Date: Mon, 18 Aug 2025 22:12:18 +0200 Subject: [PATCH 1/3] Added support for account-tags and server-time capabilities --- doc/example.conf | 2 ++ include/capab.h | 4 ++- include/ircd_features.h | 2 ++ include/msgq.h | 2 ++ include/s_misc.h | 1 + include/send.h | 2 +- ircd/channel.c | 2 +- ircd/client.c | 2 +- ircd/ircd_features.c | 2 ++ ircd/ircd_reply.c | 2 +- ircd/m_cap.c | 2 +- ircd/m_ison.c | 4 +-- ircd/msgq.c | 73 +++++++++++++++++++++++++++++++++++++++++ ircd/s_misc.c | 30 +++++++++++++++++ ircd/s_user.c | 2 +- ircd/send.c | 73 ++++++++++++++++++++++++++++++----------- 16 files changed, 176 insertions(+), 29 deletions(-) diff --git a/doc/example.conf b/doc/example.conf index 9ebc1634..af4d648d 100644 --- a/doc/example.conf +++ b/doc/example.conf @@ -921,6 +921,8 @@ features # "CAP_ECHOMESSAGE" = "TRUE"; # "CAP_EXTJOIN" = "TRUE"; # "CAP_INVITENOTIFY" = "TRUE"; +# "CAP_ACCOUNT_TAG" = "TRUE"; +# "CAP_SERVER_TIME" = "TRUE"; # These were introduced by Undernet CFV-165 to add "Head-In-Sand" (HIS) # behavior to hide most network topology from users. # "HIS_SNOTICES" = "TRUE"; diff --git a/include/capab.h b/include/capab.h index 972fd4cd..50042440 100644 --- a/include/capab.h +++ b/include/capab.h @@ -42,7 +42,9 @@ _CAP(CHGHOST, FEAT_CAP_CHGHOST, 0, "chghost"), \ _CAP(ECHOMESSAGE, FEAT_CAP_ECHOMESSAGE, 0, "echo-message"), \ _CAP(EXTJOIN, FEAT_CAP_EXTJOIN, 0, "extended-join"), \ - _CAP(INVITENOTIFY, FEAT_CAP_INVITENOTIFY, 0, "invite-notify") + _CAP(INVITENOTIFY, FEAT_CAP_INVITENOTIFY, 0, "invite-notify"), \ + _CAP(ACCOUNT_TAG, FEAT_CAP_ACCOUNT_TAG, 0, "account-tag"), \ + _CAP(SERVER_TIME, FEAT_CAP_SERVER_TIME, 0, "server-time") /** Client capabilities, counting by index. */ enum Capab { diff --git a/include/ircd_features.h b/include/ircd_features.h index 70acc947..057b73a6 100644 --- a/include/ircd_features.h +++ b/include/ircd_features.h @@ -110,6 +110,8 @@ enum Feature { FEAT_CAP_ECHOMESSAGE, FEAT_CAP_EXTJOIN, FEAT_CAP_INVITENOTIFY, + FEAT_CAP_ACCOUNT_TAG, + FEAT_CAP_SERVER_TIME, /* HEAD_IN_SAND Features */ FEAT_HIS_SNOTICES, diff --git a/include/msgq.h b/include/msgq.h index 409daabc..e8851479 100644 --- a/include/msgq.h +++ b/include/msgq.h @@ -77,6 +77,8 @@ extern int msgq_mapiov(const struct MsgQ *mq, struct iovec *iov, int count, extern struct MsgBuf *msgq_make(struct Client *dest, const char *format, ...); extern struct MsgBuf *msgq_vmake(struct Client *dest, const char *format, va_list args); +extern struct MsgBuf *msgq_tags(struct Client *dest, const char *tags_info); + extern void msgq_append(struct Client *dest, struct MsgBuf *mb, const char *format, ...); extern void msgq_clean(struct MsgBuf *mb); diff --git a/include/s_misc.h b/include/s_misc.h index 10939e40..7ced3197 100644 --- a/include/s_misc.h +++ b/include/s_misc.h @@ -98,6 +98,7 @@ extern int exit_client_msg(struct Client *cptr, struct Client *bcptr, struct Client *sptr, const char *pattern, ...); extern void initstats(void); extern char *date(time_t clock); +extern char *iso8601_timestamp(char *buffer, size_t buffer_size); extern int vexit_client_msg(struct Client *cptr, struct Client *bcptr, struct Client *sptr, const char *pattern, va_list vl); extern void tstats(struct Client *cptr, const struct StatDesc *sd, diff --git a/include/send.h b/include/send.h index 0bbbda83..b0c2016f 100644 --- a/include/send.h +++ b/include/send.h @@ -27,7 +27,7 @@ struct MsgBuf; */ extern struct SLink *opsarray[]; -extern void send_buffer(struct Client* to, struct MsgBuf* buf, int prio); +extern void send_buffer(struct Client* to, struct Client* from, struct MsgBuf* buf, int prio); extern void kill_highest_sendq(int servers_too); extern void flush_connections(struct Client* cptr); diff --git a/ircd/channel.c b/ircd/channel.c index 16eacc36..da4d4ca4 100644 --- a/ircd/channel.c +++ b/ircd/channel.c @@ -1101,7 +1101,7 @@ void send_channel_modes(struct Client *cptr, struct Channel *chptr) } } - send_buffer(cptr, mb, 0); /* Send this message */ + send_buffer(cptr, NULL, mb, 0); /* Send this message */ msgq_clean(mb); } /* Continue when there was something that didn't fit (full==1) */ diff --git a/ircd/client.c b/ircd/client.c index e861c876..84147fd3 100644 --- a/ircd/client.c +++ b/ircd/client.c @@ -279,7 +279,7 @@ client_report_privs(struct Client *to, struct Client *client) if (HasPriv(client, privtab[i].priv)) msgq_append(0, mb, "%s%s", found1++ ? " " : "", privtab[i].name); - send_buffer(to, mb, 0); /* send response */ + send_buffer(to, NULL, mb, 0); /* send response */ msgq_clean(mb); return 0; diff --git a/ircd/ircd_features.c b/ircd/ircd_features.c index 1f141259..840bba6d 100644 --- a/ircd/ircd_features.c +++ b/ircd/ircd_features.c @@ -375,6 +375,8 @@ static struct FeatureDesc { F_B(CAP_ECHOMESSAGE, 0, 1, 0), F_B(CAP_EXTJOIN, 0, 1, 0), F_B(CAP_INVITENOTIFY, 0, 1, 0), + F_B(CAP_ACCOUNT_TAG, 0, 1, 0), + F_B(CAP_SERVER_TIME, 0, 1, 0), /* HEAD_IN_SAND Features */ F_B(HIS_SNOTICES, 0, 1, 0), diff --git a/ircd/ircd_reply.c b/ircd/ircd_reply.c index 256d18c5..1cc686e7 100644 --- a/ircd/ircd_reply.c +++ b/ircd/ircd_reply.c @@ -107,7 +107,7 @@ int send_reply(struct Client *to, int reply, ...) va_end(vd.vd_args); /* send it to the user */ - send_buffer(to, mb, 0); + send_buffer(to, NULL, mb, 0); msgq_clean(mb); diff --git a/ircd/m_cap.c b/ircd/m_cap.c index 1709cfd1..65ea6190 100644 --- a/ircd/m_cap.c +++ b/ircd/m_cap.c @@ -194,7 +194,7 @@ send_caplist(struct Client *sptr, capset_t set, } msgq_append(0, mb, "%s", capbuf); /* append capabilities to the final cmd */ - send_buffer(sptr, mb, 0); /* send them out... */ + send_buffer(sptr, NULL, mb, 0); /* send them out... */ msgq_clean(mb); /* and release the buffer */ return 0; /* convenience return */ diff --git a/ircd/m_ison.c b/ircd/m_ison.c index 00adddc8..439d7a3e 100644 --- a/ircd/m_ison.c +++ b/ircd/m_ison.c @@ -127,7 +127,7 @@ int m_ison(struct Client *cptr, struct Client *sptr, int parc, char *parv[]) name = ircd_strtok(&p, 0, " ")) { if ((acptr = FindUser(name))) { if (msgq_bufleft(mb) < strlen(cli_name(acptr)) + 1) { - send_buffer(sptr, mb, 0); /* send partial response */ + send_buffer(sptr, NULL, mb, 0); /* send partial response */ msgq_clean(mb); /* then do another round */ mb = msgq_make(sptr, rpl_str(RPL_ISON), cli_name(&me), cli_name(sptr)); @@ -137,7 +137,7 @@ int m_ison(struct Client *cptr, struct Client *sptr, int parc, char *parv[]) } } - send_buffer(sptr, mb, 0); /* send response */ + send_buffer(sptr, NULL, mb, 0); /* send response */ msgq_clean(mb); return 0; diff --git a/ircd/msgq.c b/ircd/msgq.c index ec320500..2e51c573 100644 --- a/ircd/msgq.c +++ b/ircd/msgq.c @@ -318,6 +318,79 @@ msgq_clear_freembs(void) } } +/** Format a message buffer for a client from a tag string. + * Does not add \r\n at the end of the buffer such that this buffer will prefix the next immediate buffer. + * @param[in] dest %Client that receives the data (may be NULL). + * @param[in] tags_info Tag information to include in the message. + * @return Allocated MsgBuf. + */ +struct MsgBuf * +msgq_tags(struct Client *dest, const char *tags_info) +{ + struct MsgBuf *mb; + + assert(0 != tags_info); + + if (!(mb = msgq_alloc(0, BUFSIZE))) { /* Tags can be 4096 bytes, but not (yet) necessary. */ + if (feature_bool(FEAT_HAS_FERGUSON_FLUSHER)) { + /* + * from "Married With Children" episode were Al bought a REAL toilet + * on the black market because he was tired of the wimpy water + * conserving toilets they make these days --Bleep + */ + /* + * Apparently this doesn't work, the server _has_ to + * dump a few clients to handle the load. A fully loaded + * server cannot handle a net break without dumping some + * clients. If we flush the connections here under a full + * load we may end up starving the kernel for mbufs and + * crash the machine + */ + /* + * attempt to recover from buffer starvation before + * bailing this may help servers running out of memory + */ + flush_connections(0); + mb = msgq_alloc(0, BUFSIZE); + } + if (!mb) { /* OK, try clearing the buffer free list */ + msgq_clear_freembs(); + mb = msgq_alloc(0, BUFSIZE); + } + if (!mb) { /* OK, try killing a client */ + kill_highest_sendq(0); /* Don't kill any server connections */ + msgq_clear_freembs(); /* Release whatever was just freelisted */ + mb = msgq_alloc(0, BUFSIZE); + } + if (!mb) { /* hmmm... */ + kill_highest_sendq(1); /* Try killing a server connection now */ + msgq_clear_freembs(); /* Clear freelist again */ + mb = msgq_alloc(0, BUFSIZE); + } + if (!mb) /* AIEEEE! */ + server_panic("Unable to allocate buffers!"); + } + + mb->next = MQData.msglist; /* initialize the msgbuf */ + mb->prev_p = &MQData.msglist; + + /* fill the buffer */ + mb->length = ircd_snprintf(0, mb->msg, bufsize(mb) - 1, tags_info); + + if (mb->length > bufsize(mb)) + mb->length = bufsize(mb); + + mb->msg[mb->length] = '\0'; /* not strictly necessary */ + + assert(mb->length <= bufsize(mb)); + + if (MQData.msglist) /* link it into the list */ + MQData.msglist->prev_p = &mb->next; + MQData.msglist = mb; + + return mb; +} + /** Format a message buffer for a client from a format string. * @param[in] dest %Client that receives the data (may be NULL). * @param[in] format Format string for message. diff --git a/ircd/s_misc.c b/ircd/s_misc.c index a6a0cff3..ea02db28 100644 --- a/ircd/s_misc.c +++ b/ircd/s_misc.c @@ -64,6 +64,7 @@ #include #include #include +#include #include /** Array of English month names (0 = January). */ @@ -151,6 +152,35 @@ char *myctime(time_t value) return buf; } +/** Formats the current time as an ISO 8601 timestamp with milliseconds. + * @param[out] buffer Buffer to store the formatted timestamp. + * @param[in] buffer_size Size of the buffer. + * @return Pointer to the buffer containing a timestamp like "2025-08-17T14:23:45.123Z". + */ +char *iso8601_timestamp(char *buffer, size_t buffer_size) +{ + struct timeval tv; + struct tm *tm_info; + + /* Get current time with microsecond precision */ + gettimeofday(&tv, NULL); + + /* Convert to UTC time structure */ + tm_info = gmtime(&tv.tv_sec); + + /* Format the timestamp with milliseconds */ + snprintf(buffer, buffer_size, "%04d-%02d-%02dT%02d:%02d:%02d.%03dZ", + tm_info->tm_year + 1900, + tm_info->tm_mon + 1, + tm_info->tm_mday, + tm_info->tm_hour, + tm_info->tm_min, + tm_info->tm_sec, + (int)(tv.tv_usec / 1000)); /* Convert microseconds to milliseconds */ + + return buffer; +} + /** Return the name of the client for various tracking and admin * purposes. The main purpose of this function is to return the * "socket host" name of the client, if that differs from the diff --git a/ircd/s_user.c b/ircd/s_user.c index 13c61cb1..ec0b5783 100644 --- a/ircd/s_user.c +++ b/ircd/s_user.c @@ -869,7 +869,7 @@ void send_user_info(struct Client* sptr, char* names, int rpl, InfoFormatter fmt if (5 == ++arg_count) break; } - send_buffer(sptr, mb, 0); + send_buffer(sptr, NULL, mb, 0); msgq_clean(mb); } diff --git a/ircd/send.c b/ircd/send.c index 9dbd2921..ae7993c1 100644 --- a/ircd/send.c +++ b/ircd/send.c @@ -208,12 +208,44 @@ void send_queued(struct Client *to) update_write(to); } +void send_tags(struct Client* to, struct Client* from, int prio) +{ + char tags_buffer[512] = ""; + char *pos = tags_buffer; + struct MsgBuf* buf; + + if (IsServer(to)) + return; + + if (CapHas(cli_active(to), CAP_SERVER_TIME)) { + char timestamp[32]; + iso8601_timestamp(timestamp, sizeof(timestamp)); + pos += sprintf(pos, "@time=%s ", timestamp); + } + + if (CapHas(cli_active(to), CAP_ACCOUNT_TAG) + && from != NULL + && IsUser(from) + && cli_user(from) != NULL + && IsAccount(from)) { + pos += sprintf(pos, "@account=%s ", cli_user(from)->account); + } + + if (strlen(tags_buffer) > 0) { + buf = msgq_tags(to, tags_buffer); + msgq_add(&(cli_sendQ(to)), buf, prio); + client_add_sendq(cli_connect(to), &send_queues); + update_write(to); + msgq_clean(buf); + } +} + /** Try to send a buffer to a client, queueing it if needed. * @param[in,out] to Client to send message to. * @param[in] buf Message to send. * @param[in] prio If non-zero, send as high priority. */ -void send_buffer(struct Client* to, struct MsgBuf* buf, int prio) +void send_buffer(struct Client* to, struct Client* from, struct MsgBuf* buf, int prio) { assert(0 != to); assert(0 != buf); @@ -236,6 +268,7 @@ void send_buffer(struct Client* to, struct MsgBuf* buf, int prio) return; } + send_tags(to, from, prio); Debug((DEBUG_SEND, "Sending [%p] to %s", buf, cli_name(to))); msgq_add(&(cli_sendQ(to)), buf, prio); @@ -301,7 +334,7 @@ void sendrawto_one(struct Client *to, const char *pattern, ...) mb = msgq_vmake(to, pattern, vl); va_end(vl); - send_buffer(to, mb, 0); + send_buffer(to, NULL, mb, 0); msgq_clean(mb); } @@ -329,7 +362,7 @@ void sendcmdto_one(struct Client *from, const char *cmd, const char *tok, va_end(vd.vd_args); - send_buffer(to, mb, 0); + send_buffer(to, from, mb, 0); msgq_clean(mb); } @@ -358,7 +391,7 @@ void sendcmdto_prio_one(struct Client *from, const char *cmd, const char *tok, va_end(vd.vd_args); - send_buffer(to, mb, 1); + send_buffer(to, from, mb, 1); msgq_clean(mb); } @@ -398,7 +431,7 @@ void sendcmdto_flag_serv_butone(struct Client *from, const char *cmd, continue; if ((forbid < FLAG_LAST_FLAG) && HasFlag(lp->value.cptr, forbid)) continue; - send_buffer(lp->value.cptr, mb, 0); + send_buffer(lp->value.cptr, from, mb, 0); } msgq_clean(mb); @@ -431,7 +464,7 @@ void sendcmdto_serv_butone(struct Client *from, const char *cmd, for (lp = cli_serv(&me)->down; lp; lp = lp->next) { if (one && lp->value.cptr == cli_from(one)) continue; - send_buffer(lp->value.cptr, mb, 0); + send_buffer(lp->value.cptr, from, mb, 0); } msgq_clean(mb); @@ -502,12 +535,12 @@ void sendcmdto_common_channels_butone(struct Client *from, const char *cmd, && member->user != one && cli_sentalong(member->user) != sentalong_marker) { cli_sentalong(member->user) = sentalong_marker; - send_buffer(member->user, mb, 0); + send_buffer(member->user, from, mb, 0); } } if (MyConnect(from) && from != one) - send_buffer(from, mb, 0); + send_buffer(from, from, mb, 0); msgq_clean(mb); } @@ -563,7 +596,7 @@ void sendcmdto_capflag_common_channels_butone(struct Client *from, const char *c && (forbid == 0 || !CapHas(cli_active(member->user), forbid))) { cli_sentalong(member->user) = sentalong_marker; - send_buffer(member->user, mb, 0); + send_buffer(member->user, from, mb, 0); } } } @@ -572,7 +605,7 @@ void sendcmdto_capflag_common_channels_butone(struct Client *from, const char *c && from != one && (require == 0 || CapHas(cli_active(from), require)) && (forbid == 0 || !CapHas(cli_active(from), forbid))) - send_buffer(from, mb, 0); + send_buffer(from, from, mb, 0); msgq_clean(mb); } @@ -617,7 +650,7 @@ void sendcmdto_capflag_channel_butserv_butone(struct Client *from, const char *c || (forbid && CapHas(cli_active(member->user), forbid))) continue; - send_buffer(member->user, mb, 0); + send_buffer(member->user, from, mb, 0); } msgq_clean(mb); @@ -691,7 +724,7 @@ void sendcmdto_channel_butserv_butone(struct Client *from, const char *cmd, || (skip & SKIP_NONOPS && !IsChanOp(member)) || (skip & SKIP_NONVOICES && !IsChanOp(member) && !HasVoice(member))) continue; - send_buffer(member->user, mb, 0); + send_buffer(member->user, from, mb, 0); } msgq_clean(mb); @@ -734,7 +767,7 @@ void sendcmdto_channel_servers_butone(struct Client *from, const char *cmd, || (skip & SKIP_NONVOICES && !IsChanOp(member) && !HasVoice(member))) continue; cli_sentalong(member->user) = sentalong_marker; - send_buffer(member->user, serv_mb, 0); + send_buffer(member->user, from, serv_mb, 0); } msgq_clean(serv_mb); } @@ -789,9 +822,9 @@ void sendcmdto_channel_butone(struct Client *from, const char *cmd, cli_sentalong(member->user) = sentalong_marker; if (MyConnect(member->user)) /* pick right buffer to send */ - send_buffer(member->user, user_mb, 0); + send_buffer(member->user, from, user_mb, 0); else - send_buffer(member->user, serv_mb, 0); + send_buffer(member->user, from, serv_mb, 0); } msgq_clean(user_mb); @@ -851,7 +884,7 @@ void sendwallto_group_butone(struct Client *from, int type, struct Client *one, (!SendWallops(cptr) || (his_wallops && !IsAnOper(cptr)))) || (type == WALL_WALLUSERS && !SendWallops(cptr))) continue; /* skip it */ - send_buffer(cptr, mb, 1); + send_buffer(cptr, from, mb, 1); } msgq_clean(mb); @@ -865,7 +898,7 @@ void sendwallto_group_butone(struct Client *from, int type, struct Client *one, for (lp = cli_serv(&me)->down; lp; lp = lp->next) { if (one && lp->value.cptr == cli_from(one)) continue; - send_buffer(lp->value.cptr, mb, 1); + send_buffer(lp->value.cptr, from, mb, 1); } msgq_clean(mb); @@ -913,9 +946,9 @@ void sendcmdto_match_butone(struct Client *from, const char *cmd, cli_sentalong(cptr) = sentalong_marker; if (MyConnect(cptr)) /* send right buffer */ - send_buffer(cptr, user_mb, 0); + send_buffer(cptr, from, user_mb, 0); else - send_buffer(cptr, serv_mb, 0); + send_buffer(cptr, from, serv_mb, 0); } msgq_clean(user_mb); @@ -994,7 +1027,7 @@ void vsendto_opmask_butone(struct Client *one, unsigned int mask, for (; opslist; opslist = opslist->next) if (opslist->value.cptr != one) - send_buffer(opslist->value.cptr, mb, 0); + send_buffer(opslist->value.cptr, NULL, mb, 0); msgq_clean(mb); } From f5b45778555ce064871d3dc9d11a16bc6200bc00 Mon Sep 17 00:00:00 2001 From: MrIron Date: Thu, 18 Sep 2025 16:53:54 +0200 Subject: [PATCH 2/3] Fix tags syntax --- ircd/send.c | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/ircd/send.c b/ircd/send.c index ae7993c1..95190f7a 100644 --- a/ircd/send.c +++ b/ircd/send.c @@ -217,10 +217,12 @@ void send_tags(struct Client* to, struct Client* from, int prio) if (IsServer(to)) return; + int tag_written = 0; if (CapHas(cli_active(to), CAP_SERVER_TIME)) { char timestamp[32]; iso8601_timestamp(timestamp, sizeof(timestamp)); - pos += sprintf(pos, "@time=%s ", timestamp); + pos += sprintf(pos, "@time=%s", timestamp); + tag_written = 1; } if (CapHas(cli_active(to), CAP_ACCOUNT_TAG) @@ -228,7 +230,15 @@ void send_tags(struct Client* to, struct Client* from, int prio) && IsUser(from) && cli_user(from) != NULL && IsAccount(from)) { - pos += sprintf(pos, "@account=%s ", cli_user(from)->account); + if (tag_written) { + pos += sprintf(pos, ";account=%s", cli_user(from)->account); + } else { + pos += sprintf(pos, "@account=%s", cli_user(from)->account); + } + tag_written = 1; + } + if (tag_written) { + pos += sprintf(pos, " "); } if (strlen(tags_buffer) > 0) { From fa3ecd7672dbac95405eebdbc4d16d768407fa57 Mon Sep 17 00:00:00 2001 From: MrIron Date: Sat, 22 Nov 2025 20:25:18 +0100 Subject: [PATCH 3/3] Added support for batch capability with the netjoin and netsplit types --- include/batch.h | 79 +++++++++++ include/capab.h | 1 + include/ircd_defs.h | 5 + include/ircd_features.h | 1 + include/msg.h | 7 + include/msg_tag.h | 70 ++++++++++ include/send.h | 10 +- include/struct.h | 1 + ircd/Makefile.in | 2 + ircd/batch.c | 299 ++++++++++++++++++++++++++++++++++++++++ ircd/channel.c | 8 +- ircd/client.c | 2 +- ircd/ircd_features.c | 1 + ircd/ircd_reply.c | 2 +- ircd/list.c | 5 + ircd/m_burst.c | 13 +- ircd/m_cap.c | 2 +- ircd/m_endburst.c | 11 ++ ircd/m_invite.c | 8 +- ircd/m_ison.c | 4 +- ircd/m_server.c | 34 +++++ ircd/msg_tag.c | 105 ++++++++++++++ ircd/s_misc.c | 43 ++++-- ircd/s_user.c | 12 +- ircd/send.c | 165 +++++++++++++++------- 25 files changed, 809 insertions(+), 81 deletions(-) create mode 100644 include/batch.h create mode 100644 include/msg_tag.h create mode 100644 ircd/batch.c create mode 100644 ircd/msg_tag.c diff --git a/include/batch.h b/include/batch.h new file mode 100644 index 00000000..182a5018 --- /dev/null +++ b/include/batch.h @@ -0,0 +1,79 @@ +#ifndef INCLUDED_batch_h +#define INCLUDED_batch_h +/* + * IRC - Internet Relay Chat, include/batch.h + * Copyright (C) 2024 + * + * 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 1, 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, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ +/** @file + * @brief IRCv3 batch capability support + * @version $Id$ + */ + +#ifndef INCLUDED_client_h +#include "client.h" +#endif + +/* Batch types - stored as enum for efficiency */ +enum BatchType { + BATCH_TYPE_NETSPLIT = 0, + BATCH_TYPE_NETJOIN, + BATCH_TYPE_MAX +}; + +/* Generate unique batch ID */ +char *batch_generate_id(void); + +/* Register batch metadata (type and param) for a batch_id. + * This must be called before sending any messages with this batch_id. + * @param[in] batch_id Batch ID to register. + * @param[in] type Batch type. + * @param[in] param Batch parameter (can be NULL). + */ +void batch_register(const char *batch_id, enum BatchType type, const char *param); + +/* Unregister batch metadata when batch is complete. + * @param[in] batch_id Batch ID to unregister. + */ +void batch_unregister(const char *batch_id); + +/* Get batch metadata for a batch_id (internal use by send_tags). + * @param[in] batch_id Batch ID to look up. + * @param[out] type Batch type (if found). + * @param[out] param Batch parameter (if found, can be NULL). + * @return 1 if found, 0 if not found. + */ +int batch_get_meta(const char *batch_id, enum BatchType *type, const char **param); + +/* Send batch start to a client */ +void batch_send_start(struct Client *cptr, enum BatchType type, + const char *param, const char *batch_id); + +/* Send batch end to a client */ +void batch_send_end(struct Client *cptr, const char *batch_id); + +/* Complete batch cleanup: send batch end to all affected clients, unregister, and free batch_id. + * @param[in] batch_id Batch ID to complete (will be freed by this function). + */ +void batch_complete(char *batch_id); + +/* Remove a client from all batch lists (called on client exit). + * @param[in] cptr Client being removed. + */ +void batch_remove_client(struct Client *cptr); + +#endif /* INCLUDED_batch_h */ + diff --git a/include/capab.h b/include/capab.h index 50042440..971e47b4 100644 --- a/include/capab.h +++ b/include/capab.h @@ -39,6 +39,7 @@ #define CAPLIST \ _CAP(ACCOUNTNOTIFY, FEAT_CAP_ACCOUNTNOTIFY, 0, "account-notify"), \ _CAP(AWAYNOTIFY, FEAT_CAP_AWAYNOTIFY, 0 , "away-notify"), \ + _CAP(BATCH, FEAT_CAP_BATCH, 0, "batch"), \ _CAP(CHGHOST, FEAT_CAP_CHGHOST, 0, "chghost"), \ _CAP(ECHOMESSAGE, FEAT_CAP_ECHOMESSAGE, 0, "echo-message"), \ _CAP(EXTJOIN, FEAT_CAP_EXTJOIN, 0, "extended-join"), \ diff --git a/include/ircd_defs.h b/include/ircd_defs.h index c3138233..f023bf73 100644 --- a/include/ircd_defs.h +++ b/include/ircd_defs.h @@ -87,6 +87,11 @@ /** Maximum length for away messages. */ #define AWAYLEN 160 + +/** Maximum length for a batch ID. + */ +#define BATCHLEN 20 + /** Exactly long enough to hold one protocol message (RFC 1459) * including the line termination (\\r\\n). DO NOT CHANGE THIS!!!! */ diff --git a/include/ircd_features.h b/include/ircd_features.h index 057b73a6..a774c47b 100644 --- a/include/ircd_features.h +++ b/include/ircd_features.h @@ -106,6 +106,7 @@ enum Feature { /* IRCv3 capabilities */ FEAT_CAP_ACCOUNTNOTIFY, FEAT_CAP_AWAYNOTIFY, + FEAT_CAP_BATCH, FEAT_CAP_CHGHOST, FEAT_CAP_ECHOMESSAGE, FEAT_CAP_EXTJOIN, diff --git a/include/msg.h b/include/msg.h index 2fba1e79..c3cfcf5e 100644 --- a/include/msg.h +++ b/include/msg.h @@ -364,6 +364,13 @@ struct Client; #define TOK_CAP "CAP" #define CMD_CAP MSG_CAP, TOK_CAP +#ifdef MSG_BATCH +#undef MSG_BATCH +#endif +#define MSG_BATCH "BATCH" /* BATCH */ +#define TOK_BATCH "BATCH" +#define CMD_BATCH MSG_BATCH, TOK_BATCH + #define MSG_XQUERY "XQUERY" #define TOK_XQUERY "XQ" #define CMD_XQUERY MSG_XQUERY, TOK_XQUERY diff --git a/include/msg_tag.h b/include/msg_tag.h new file mode 100644 index 00000000..94f7f2d1 --- /dev/null +++ b/include/msg_tag.h @@ -0,0 +1,70 @@ +#ifndef INCLUDED_msg_tag_h +#define INCLUDED_msg_tag_h +/* + * IRC - Internet Relay Chat, include/msg_tag.h + * Copyright (C) 2024 + * + * 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 1, 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, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ +/** @file + * @brief IRCv3 message tag support + * @version $Id$ + */ + +struct Client; + +/** A single message tag (key-value pair). */ +struct MsgTag { + struct MsgTag *next; /**< Next tag in the list. */ + const char *key; /**< Tag key (e.g., "batch", "account"). */ + const char *value; /**< Tag value (e.g., batch ID, account name). */ +}; + +/** Build a tag list with a batch tag. + * @param[in] batch_id Batch ID to add (can be NULL). + * @return Head of tag list, or NULL if batch_id is NULL. + */ +struct MsgTag *msg_tag_build_batch(const char *batch_id); + +/** Add a tag to an existing tag list. + * @param[in,out] head Head of tag list (may be NULL). + * @param[in] key Tag key (must not be NULL). + * @param[in] value Tag value (can be NULL for tags without values). + * @return New head of tag list. + */ +struct MsgTag *msg_tag_add(struct MsgTag *head, const char *key, const char *value); + +/** Add an account tag to an existing tag list if the client has an account. + * @param[in,out] head Head of tag list (may be NULL). + * @param[in] from Client to get account from (can be NULL). + * @return New head of tag list (unchanged if no account). + */ +struct MsgTag *msg_tag_add_account(struct MsgTag *head, struct Client *from); + +/** Build a tag list from a client (for account tag) and batch ID. + * This is a convenience function that combines account and batch tags. + * @param[in] from Client to get account from (can be NULL). + * @param[in] batch_id Batch ID to add (can be NULL). + * @return Head of tag list, or NULL if no tags needed. + */ +struct MsgTag *msg_tag_build(struct Client *from, const char *batch_id); + +/** Free a tag list. + * @param[in] head Head of tag list to free. + */ +void msg_tag_free(struct MsgTag *head); + +#endif /* INCLUDED_msg_tag_h */ + diff --git a/include/send.h b/include/send.h index b0c2016f..cef0475a 100644 --- a/include/send.h +++ b/include/send.h @@ -21,13 +21,14 @@ struct Channel; struct Client; struct DBuf; struct MsgBuf; +struct MsgTag; /* * Prototypes */ extern struct SLink *opsarray[]; -extern void send_buffer(struct Client* to, struct Client* from, struct MsgBuf* buf, int prio); +extern void send_buffer(struct Client* to, struct MsgBuf* buf, int prio, struct MsgTag *tags); extern void kill_highest_sendq(int servers_too); extern void flush_connections(struct Client* cptr); @@ -64,6 +65,7 @@ extern void sendcmdto_common_channels_butone(struct Client *from, const char *cmd, const char *tok, struct Client *one, + struct MsgTag *tags, const char *pattern, ...); /* Send command to all channels user is on matching or not matching a capability flag */ @@ -80,6 +82,7 @@ void sendcmdto_capflag_channel_butserv_butone(struct Client *from, const char *c const char *tok, struct Channel *to, struct Client *one, unsigned int skip, capset_t require, capset_t forbid, + struct MsgTag *tags, const char *pattern, ...); /* Send command to all channel users on this server */ @@ -88,7 +91,7 @@ extern void sendcmdto_channel_butserv_butone(struct Client *from, const char *tok, struct Channel *to, struct Client *one, - unsigned int skip, + unsigned int skip, const char *pattern, ...); /* Send command to all servers interested in a channel */ @@ -111,7 +114,8 @@ extern void sendcmdto_channel_butone(struct Client *from, const char *cmd, extern void sendjointo_channel_butserv(struct Client *from, struct Channel *chptr, capset_t require, - capset_t forbid); + capset_t forbid, + struct MsgTag *tags); /* Send JOIN to a single user */ extern void sendjointo_one(struct Client *from, diff --git a/include/struct.h b/include/struct.h index 78d5f89e..60813f4e 100644 --- a/include/struct.h +++ b/include/struct.h @@ -65,6 +65,7 @@ struct Server { char *last_error_msg; /**< Allocated memory with last message receive with an ERROR */ char by[NICKLEN + 1]; /**< Numnick of client who requested the link */ + char *batch_id; /**< Batch ID for IRCv3 batching (NULL if none) */ }; /** Describes a user on the network. */ diff --git a/ircd/Makefile.in b/ircd/Makefile.in index 432c66e2..167d39b4 100644 --- a/ircd/Makefile.in +++ b/ircd/Makefile.in @@ -86,7 +86,9 @@ UMKPASSWD_SRC = ${CRYPTO_SRC} \ IRCD_SRC = \ IPcheck.c \ + batch.c \ channel.c \ + msg_tag.c \ class.c \ client.c \ crule.c \ diff --git a/ircd/batch.c b/ircd/batch.c new file mode 100644 index 00000000..2bd6e0dc --- /dev/null +++ b/ircd/batch.c @@ -0,0 +1,299 @@ +/* + * IRC - Internet Relay Chat, ircd/batch.c + * Copyright (C) 2024 + * + * 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 1, 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, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#include +#include + +#include "batch.h" +#include "client.h" +#include "ircd.h" +#include "ircd_alloc.h" +#include "ircd_log.h" +#include "ircd_snprintf.h" +#include "ircd_string.h" +#include "msg.h" +#include "send.h" + +static unsigned int batch_counter = 0; + +/* Client entry in a batch's client list */ +struct BatchClient { + struct BatchClient *next; + struct Client *client; /* Client that received batch start */ +}; + +/* Batch metadata structure */ +struct BatchMeta { + struct BatchMeta *next; + char *batch_id; + enum BatchType type; + char *param; /* Can be NULL */ + struct BatchClient *clients; /* Linked list of clients that received batch start */ +}; + +/* Linked list of active batch metadata */ +static struct BatchMeta *batch_meta_list = NULL; + +/* Convert batch type enum to string for protocol */ +static const char *batch_type_to_string(enum BatchType type) +{ + static const char *type_strings[] = { + "netsplit", + "netjoin" + }; + if (type >= 0 && type < BATCH_TYPE_MAX) + return type_strings[type]; + return "unknown"; +} + +/* Generate unique batch ID */ +char *batch_generate_id(void) +{ + char *batch_id = MyMalloc(BATCHLEN + 1); + ircd_snprintf(0, batch_id, BATCHLEN + 1, "%08x%04x", + (unsigned int)CurrentTime, + (unsigned int)(++batch_counter)); + return batch_id; +} + +/* Register batch metadata (type and param) for a batch_id. + * This must be called before sending any messages with this batch_id. + */ +void batch_register(const char *batch_id, enum BatchType type, const char *param) +{ + struct BatchMeta *meta; + + if (!batch_id) + return; + + /* Check if already registered */ + for (meta = batch_meta_list; meta; meta = meta->next) { + if (strcmp(meta->batch_id, batch_id) == 0) + return; /* Already registered */ + } + + /* Create new metadata entry */ + meta = MyMalloc(sizeof(struct BatchMeta)); + meta->batch_id = (char *)batch_id; /* We don't copy - caller owns it */ + meta->type = type; + if (param && param[0]) { + meta->param = MyMalloc(strlen(param) + 1); + strcpy(meta->param, param); + } else { + meta->param = NULL; + } + meta->clients = NULL; /* Initialize client list */ + meta->next = batch_meta_list; + batch_meta_list = meta; +} + +/* Unregister batch metadata when batch is complete. */ +void batch_unregister(const char *batch_id) +{ + struct BatchMeta *meta, *prev = NULL; + struct BatchClient *client, *next_client; + + if (!batch_id) + return; + + for (meta = batch_meta_list; meta; prev = meta, meta = meta->next) { + if (strcmp(meta->batch_id, batch_id) == 0) { + /* Free client list */ + for (client = meta->clients; client; client = next_client) { + next_client = client->next; + MyFree(client); + } + + /* Remove from list */ + if (prev) + prev->next = meta->next; + else + batch_meta_list = meta->next; + + /* Free param if allocated */ + if (meta->param) + MyFree(meta->param); + + /* Note: we don't free batch_id - caller owns it */ + MyFree(meta); + return; + } + } +} + +/* Get batch metadata for a batch_id */ +int batch_get_meta(const char *batch_id, enum BatchType *type, const char **param) +{ + struct BatchMeta *meta; + + if (!batch_id || !type || !param) + return 0; + + for (meta = batch_meta_list; meta; meta = meta->next) { + if (strcmp(meta->batch_id, batch_id) == 0) { + *type = meta->type; + *param = meta->param; + return 1; + } + } + + return 0; +} + +/* Check if a client has already received batch start for a batch_id */ +static int batch_has_client(const char *batch_id, struct Client *cptr) +{ + struct BatchMeta *meta; + struct BatchClient *client; + + if (!batch_id || !cptr) + return 0; + + for (meta = batch_meta_list; meta; meta = meta->next) { + if (strcmp(meta->batch_id, batch_id) == 0) { + for (client = meta->clients; client; client = client->next) { + if (client->client == cptr) + return 1; + } + return 0; + } + } + return 0; +} + +/* Add a client to a batch's client list */ +static void batch_add_client(const char *batch_id, struct Client *cptr) +{ + struct BatchMeta *meta; + struct BatchClient *client; + + if (!batch_id || !cptr) + return; + + for (meta = batch_meta_list; meta; meta = meta->next) { + if (strcmp(meta->batch_id, batch_id) == 0) { + /* Check if already in list */ + for (client = meta->clients; client; client = client->next) { + if (client->client == cptr) + return; /* Already in list */ + } + + /* Add to list */ + client = MyMalloc(sizeof(struct BatchClient)); + client->client = cptr; + client->next = meta->clients; + meta->clients = client; + return; + } + } +} + +/* Send batch start to a client and add to batch's client list */ +void batch_send_start(struct Client *cptr, enum BatchType type, + const char *param, const char *batch_id) +{ + const char *type_str = batch_type_to_string(type); + + if (!cptr || IsServer(cptr) || !MyConnect(cptr)) + return; + + /* Only send batches to clients with batch capability */ + if (!CapHas(cli_active(cptr), CAP_BATCH)) + return; + + /* Check if we've already sent batch start to this client */ + if (batch_has_client(batch_id, cptr)) + return; + + if (param && param[0]) + sendcmdto_one(&me, CMD_BATCH, cptr, "+%s %s %s", batch_id, type_str, param); + else + sendcmdto_one(&me, CMD_BATCH, cptr, "+%s %s", batch_id, type_str); + + /* Add client to batch's client list */ + batch_add_client(batch_id, cptr); +} + +/* Send batch end to a client */ +void batch_send_end(struct Client *cptr, const char *batch_id) +{ + if (!cptr || IsServer(cptr) || !MyConnect(cptr)) + return; + + /* Only send batches to clients with batch capability */ + if (!CapHas(cli_active(cptr), CAP_BATCH)) + return; + + sendcmdto_one(&me, CMD_BATCH, cptr, "-%s", batch_id); +} + +/* Complete batch cleanup: send batch end to all affected clients, unregister, and free batch_id */ +void batch_complete(char *batch_id) +{ + struct BatchMeta *meta; + struct BatchClient *client, *next_client; + + if (!batch_id) + return; + + /* Find batch metadata */ + for (meta = batch_meta_list; meta; meta = meta->next) { + if (strcmp(meta->batch_id, batch_id) == 0) { + /* Send batch end to all clients in this batch */ + for (client = meta->clients; client; client = next_client) { + next_client = client->next; + if (client->client && MyConnect(client->client) && IsUser(client->client)) { + batch_send_end(client->client, batch_id); + } + MyFree(client); + } + meta->clients = NULL; + break; + } + } + + batch_unregister(batch_id); + MyFree(batch_id); +} + +/* Remove a client from all batch lists (called on client exit) */ +void batch_remove_client(struct Client *cptr) +{ + struct BatchMeta *meta; + struct BatchClient *client, *prev, *next; + + if (!cptr) + return; + + for (meta = batch_meta_list; meta; meta = meta->next) { + prev = NULL; + for (client = meta->clients; client; prev = client, client = next) { + next = client->next; + if (client->client == cptr) { + /* Remove from list */ + if (prev) + prev->next = next; + else + meta->clients = next; + MyFree(client); + /* Continue to check for duplicates (shouldn't happen, but be safe) */ + } + } + } +} diff --git a/ircd/channel.c b/ircd/channel.c index da4d4ca4..aa90fd7c 100644 --- a/ircd/channel.c +++ b/ircd/channel.c @@ -1101,7 +1101,7 @@ void send_channel_modes(struct Client *cptr, struct Channel *chptr) } } - send_buffer(cptr, NULL, mb, 0); /* Send this message */ + send_buffer(cptr, mb, 0, NULL); /* Send this message */ msgq_clean(mb); } /* Continue when there was something that didn't fit (full==1) */ @@ -3556,7 +3556,7 @@ joinbuf_join(struct JoinBuf *jbuf, struct Channel *chan, unsigned int flags) if (!((chan->mode.mode & MODE_DELJOINS) && !(flags & CHFL_VOICED_OR_OPPED))) { /* Send the notification to the channel */ - sendjointo_channel_butserv(jbuf->jb_source, chan, 0, 0); + sendjointo_channel_butserv(jbuf->jb_source, chan, 0, 0, NULL); if (cli_user(jbuf->jb_source)->away) sendcmdto_capflag_common_channels_butone(jbuf->jb_source, CMD_AWAY, jbuf->jb_connect, CAP_AWAYNOTIFY, 0, ":%s", cli_user(jbuf->jb_source)->away); @@ -3651,10 +3651,10 @@ int IsInvited(struct Client* cptr, const void* chptr) void RevealDelayedJoin(struct Membership *member) { ClearDelayedJoin(member); - sendjointo_channel_butserv(member->user, member->channel, 0, 0); + sendjointo_channel_butserv(member->user, member->channel, 0, 0, NULL); if (cli_user(member->user)->away) sendcmdto_capflag_channel_butserv_butone(member->user, CMD_AWAY, member->channel, - NULL, 0, CAP_AWAYNOTIFY, 0, ":%s", cli_user(member->user)->away); + NULL, 0, CAP_AWAYNOTIFY, 0, NULL, ":%s", cli_user(member->user)->away); CheckDelayedJoins(member->channel); } diff --git a/ircd/client.c b/ircd/client.c index 84147fd3..22a69b3b 100644 --- a/ircd/client.c +++ b/ircd/client.c @@ -279,7 +279,7 @@ client_report_privs(struct Client *to, struct Client *client) if (HasPriv(client, privtab[i].priv)) msgq_append(0, mb, "%s%s", found1++ ? " " : "", privtab[i].name); - send_buffer(to, NULL, mb, 0); /* send response */ + send_buffer(to, mb, 0, NULL); /* send response */ msgq_clean(mb); return 0; diff --git a/ircd/ircd_features.c b/ircd/ircd_features.c index 840bba6d..24d7eca6 100644 --- a/ircd/ircd_features.c +++ b/ircd/ircd_features.c @@ -371,6 +371,7 @@ static struct FeatureDesc { /* IRCv3 capabilities */ F_B(CAP_ACCOUNTNOTIFY, 0, 1, 0), F_B(CAP_AWAYNOTIFY, 0, 1, 0), + F_B(CAP_BATCH, 0, 1, 0), F_B(CAP_CHGHOST, 0, 1, 0), F_B(CAP_ECHOMESSAGE, 0, 1, 0), F_B(CAP_EXTJOIN, 0, 1, 0), diff --git a/ircd/ircd_reply.c b/ircd/ircd_reply.c index 1cc686e7..73adfd7a 100644 --- a/ircd/ircd_reply.c +++ b/ircd/ircd_reply.c @@ -107,7 +107,7 @@ int send_reply(struct Client *to, int reply, ...) va_end(vd.vd_args); /* send it to the user */ - send_buffer(to, NULL, mb, 0); + send_buffer(to, mb, 0, NULL); msgq_clean(mb); diff --git a/ircd/list.c b/ircd/list.c index ace92a0c..4920a9aa 100644 --- a/ircd/list.c +++ b/ircd/list.c @@ -24,6 +24,7 @@ #include "config.h" #include "list.h" +#include "batch.h" #include "client.h" #include "ircd.h" #include "ircd_alloc.h" @@ -350,6 +351,10 @@ void remove_client_from_list(struct Client *cptr) } cli_next(cptr) = cli_prev(cptr) = 0; + /* Remove client from all batch lists */ + if (MyConnect(cptr)) + batch_remove_client(cptr); + if (IsUser(cptr) && cli_user(cptr)) { add_history(cptr, 0); off_history(cptr); diff --git a/ircd/m_burst.c b/ircd/m_burst.c index be9eaa26..bd3bfb07 100644 --- a/ircd/m_burst.c +++ b/ircd/m_burst.c @@ -82,6 +82,7 @@ #include "config.h" #include "channel.h" +#include "msg_tag.h" #include "client.h" #include "hash.h" #include "ircd.h" @@ -544,11 +545,13 @@ int ms_burst(struct Client *cptr, struct Client *sptr, int parc, char *parv[]) { add_user_to_channel(chptr, acptr, current_mode, oplevel); if (!(current_mode & CHFL_DELAYED)) { - sendjointo_channel_butserv(acptr, chptr, 0, 0); - if (cli_user(acptr)->away) - sendcmdto_capflag_channel_butserv_butone(acptr, CMD_AWAY, chptr, - NULL, 0, CAP_AWAYNOTIFY, 0, ":%s", cli_user(acptr)->away); - } + struct MsgTag *tags = cli_serv(sptr)->batch_id ? + msg_tag_build_batch(cli_serv(sptr)->batch_id) : NULL; + sendjointo_channel_butserv(acptr, chptr, 0, 0, tags); + if (cli_user(acptr)->away) + sendcmdto_capflag_channel_butserv_butone(acptr, CMD_AWAY, chptr, + NULL, 0, CAP_AWAYNOTIFY, 0, NULL, ":%s", cli_user(acptr)->away); + } } else { diff --git a/ircd/m_cap.c b/ircd/m_cap.c index 65ea6190..90263d00 100644 --- a/ircd/m_cap.c +++ b/ircd/m_cap.c @@ -194,7 +194,7 @@ send_caplist(struct Client *sptr, capset_t set, } msgq_append(0, mb, "%s", capbuf); /* append capabilities to the final cmd */ - send_buffer(sptr, NULL, mb, 0); /* send them out... */ + send_buffer(sptr, mb, 0, NULL); /* send them out... */ msgq_clean(mb); /* and release the buffer */ return 0; /* convenience return */ diff --git a/ircd/m_endburst.c b/ircd/m_endburst.c index 46d51ae0..cb40ef60 100644 --- a/ircd/m_endburst.c +++ b/ircd/m_endburst.c @@ -81,10 +81,12 @@ */ #include "config.h" +#include "batch.h" #include "channel.h" #include "client.h" #include "hash.h" #include "ircd.h" +#include "ircd_alloc.h" #include "ircd_log.h" #include "ircd_reply.h" #include "ircd_string.h" @@ -111,6 +113,8 @@ int ms_end_of_burst(struct Client* cptr, struct Client* sptr, int parc, char* parv[]) { struct Channel *chan, *next_chan; + struct Client *local_user; + char *batch_id; assert(0 != cptr); assert(0 != sptr); @@ -123,6 +127,13 @@ int ms_end_of_burst(struct Client* cptr, struct Client* sptr, int parc, char* pa if (MyConnect(sptr)) sendcmdto_one(&me, CMD_END_OF_BURST_ACK, sptr, ""); + /* Send batch end for netjoin batch if one was created */ + batch_id = cli_serv(sptr)->batch_id; + if (batch_id) { + batch_complete(batch_id); + cli_serv(sptr)->batch_id = NULL; + } + /* Count through channels... */ for (chan = GlobalChannelList; chan; chan = next_chan) { next_chan = chan->next; diff --git a/ircd/m_invite.c b/ircd/m_invite.c index 48c29670..c15233f2 100644 --- a/ircd/m_invite.c +++ b/ircd/m_invite.c @@ -189,14 +189,14 @@ int m_invite(struct Client* cptr, struct Client* sptr, int parc, char* parv[]) */ sendcmdto_capflag_channel_butserv_butone(sptr, CMD_INVITE, chptr, sptr, SKIP_NONOPS, - CAP_INVITENOTIFY, 0, + CAP_INVITENOTIFY, 0, NULL, "%C %H", acptr, chptr); if (feature_bool(FEAT_ANNOUNCE_INVITES)) { /* Announce to channel operators without CAP_INVITENOTIFY enabled. */ sendcmdto_capflag_channel_butserv_butone(&his, get_error_numeric(RPL_ISSUEDINVITE)->str, NULL, chptr, sptr, SKIP_NONOPS, - 0, CAP_INVITENOTIFY, + 0, CAP_INVITENOTIFY, NULL, "%H %C %C :%C has been invited by %C", chptr, acptr, sptr, acptr, sptr); /* Announce to servers with channel operators. */ @@ -300,14 +300,14 @@ int ms_invite(struct Client* cptr, struct Client* sptr, int parc, char* parv[]) /* Announce to channel operators with CAP_NOTIFY enabled. */ sendcmdto_capflag_channel_butserv_butone(sptr, CMD_INVITE, chptr, sptr, SKIP_NONOPS, - CAP_INVITENOTIFY, 0, + CAP_INVITENOTIFY, 0, NULL, "%C %H", acptr, chptr); if (feature_bool(FEAT_ANNOUNCE_INVITES)) { /* Announce to channel operators without CAP_NOTIFY enabled. */ sendcmdto_capflag_channel_butserv_butone(&his, get_error_numeric(RPL_ISSUEDINVITE)->str, NULL, chptr, sptr, SKIP_NONOPS, - 0, CAP_INVITENOTIFY, + 0, CAP_INVITENOTIFY, NULL, "%H %C %C :%C has been invited by %C", chptr, acptr, sptr, acptr, sptr); /* Announce to servers with channel operators. */ diff --git a/ircd/m_ison.c b/ircd/m_ison.c index 439d7a3e..b6b79cfb 100644 --- a/ircd/m_ison.c +++ b/ircd/m_ison.c @@ -127,7 +127,7 @@ int m_ison(struct Client *cptr, struct Client *sptr, int parc, char *parv[]) name = ircd_strtok(&p, 0, " ")) { if ((acptr = FindUser(name))) { if (msgq_bufleft(mb) < strlen(cli_name(acptr)) + 1) { - send_buffer(sptr, NULL, mb, 0); /* send partial response */ + send_buffer(sptr, mb, 0, NULL); /* send partial response */ msgq_clean(mb); /* then do another round */ mb = msgq_make(sptr, rpl_str(RPL_ISON), cli_name(&me), cli_name(sptr)); @@ -137,7 +137,7 @@ int m_ison(struct Client *cptr, struct Client *sptr, int parc, char *parv[]) } } - send_buffer(sptr, NULL, mb, 0); /* send response */ + send_buffer(sptr, mb, 0, NULL); /* send response */ msgq_clean(mb); return 0; diff --git a/ircd/m_server.c b/ircd/m_server.c index 9c3332ad..c4b2868d 100644 --- a/ircd/m_server.c +++ b/ircd/m_server.c @@ -27,6 +27,7 @@ #include "config.h" +#include "batch.h" #include "client.h" #include "hash.h" #include "ircd.h" @@ -635,6 +636,23 @@ int mr_server(struct Client* cptr, struct Client* sptr, int parc, char* parv[]) if (*parv[7] == '+') set_server_flags(cptr, parv[7] + 1); + if (!cli_serv(cptr)->batch_id) { + char batch_param[HOSTLEN + HOSTLEN + 2]; + + /* Determine the netjoin comment (same logic as netsplit but with *.join) */ + if (feature_bool(FEAT_HIS_NETSPLIT)) + strcpy(batch_param, "*.net *.join"); + else + { + strcpy(batch_param, cli_name(&me)); + strcat(batch_param, " "); + strcat(batch_param, cli_name(cptr)); + } + + cli_serv(cptr)->batch_id = batch_generate_id(); + batch_register(cli_serv(cptr)->batch_id, BATCH_TYPE_NETJOIN, batch_param); + } + recv_time = TStime(); check_start_timestamp(cptr, timestamp, start_timestamp, recv_time); ret = server_estab(cptr, aconf); @@ -757,6 +775,22 @@ int ms_server(struct Client* cptr, struct Client* sptr, int parc, char* parv[]) { SetBurst(acptr); SetJunction(acptr); + /* Generate batch ID for this netjoin burst */ + if (!cli_serv(acptr)->batch_id) { + char batch_param[HOSTLEN + HOSTLEN + 2]; + + /* Determine the netjoin comment (same logic as netsplit but with *.join) */ + if (feature_bool(FEAT_HIS_NETSPLIT)) + strcpy(batch_param, "*.net *.join"); + else { + strcpy(batch_param, cli_name(sptr)); + strcat(batch_param, " "); + strcat(batch_param, cli_name(acptr)); + } + + cli_serv(acptr)->batch_id = batch_generate_id(); + batch_register(cli_serv(acptr)->batch_id, BATCH_TYPE_NETJOIN, batch_param); + } for (bcptr = cli_serv(acptr)->up; !IsMe(bcptr); bcptr = cli_serv(bcptr)->up) if (IsBurstOrBurstAck(bcptr)) break; diff --git a/ircd/msg_tag.c b/ircd/msg_tag.c new file mode 100644 index 00000000..f3bb673e --- /dev/null +++ b/ircd/msg_tag.c @@ -0,0 +1,105 @@ +/* + * IRC - Internet Relay Chat, ircd/msg_tag.c + * Copyright (C) 2024 + * + * 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 1, 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, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#include "msg_tag.h" +#include "client.h" +#include "struct.h" +#include "ircd_alloc.h" +#include "ircd_features.h" + +/** Build a tag list with a batch tag. + * @param[in] batch_id Batch ID to add (can be NULL). + * @return Head of tag list, or NULL if batch_id is NULL. + */ +struct MsgTag *msg_tag_build_batch(const char *batch_id) +{ + if (!batch_id) + return NULL; + + struct MsgTag *tag = MyMalloc(sizeof(struct MsgTag)); + tag->next = NULL; + tag->key = "batch"; + tag->value = batch_id; + return tag; +} + +/** Add a tag to an existing tag list. + * @param[in,out] head Head of tag list (may be NULL). + * @param[in] key Tag key (must not be NULL). + * @param[in] value Tag value (can be NULL for tags without values). + * @return New head of tag list. + */ +struct MsgTag *msg_tag_add(struct MsgTag *head, const char *key, const char *value) +{ + if (!key) + return head; + + struct MsgTag *tag = MyMalloc(sizeof(struct MsgTag)); + tag->next = head; + tag->key = key; + tag->value = value; + return tag; +} + +/** Add an account tag to an existing tag list if the client has an account. + * @param[in,out] head Head of tag list (may be NULL). + * @param[in] from Client to get account from (can be NULL). + * @return New head of tag list (unchanged if no account). + */ +struct MsgTag *msg_tag_add_account(struct MsgTag *head, struct Client *from) +{ + if (!from || !IsUser(from) || !cli_user(from) || !IsAccount(from)) + return head; + + return msg_tag_add(head, "account", cli_user(from)->account); +} + +/** Build a tag list from a client (for account tag) and batch ID. + * This is a convenience function that combines account and batch tags. + * @param[in] from Client to get account from (can be NULL). + * @param[in] batch_id Batch ID to add (can be NULL). + * @return Head of tag list, or NULL if no tags needed. + */ +struct MsgTag *msg_tag_build(struct Client *from, const char *batch_id) +{ + struct MsgTag *head = NULL; + + if (batch_id) { + head = msg_tag_build_batch(batch_id); + } + + if (from) { + head = msg_tag_add_account(head, from); + } + + return head; +} + +/** Free a tag list. + * @param[in] head Head of tag list to free. + */ +void msg_tag_free(struct MsgTag *head) +{ + while (head) { + struct MsgTag *next = head->next; + MyFree(head); + head = next; + } +} + diff --git a/ircd/s_misc.c b/ircd/s_misc.c index ea02db28..a3a28a54 100644 --- a/ircd/s_misc.c +++ b/ircd/s_misc.c @@ -27,6 +27,8 @@ #include "config.h" #include "s_misc.h" +#include "batch.h" +#include "msg_tag.h" #include "IPcheck.h" #include "channel.h" #include "client.h" @@ -209,7 +211,7 @@ const char* get_client_name(const struct Client* sptr, int showip) * @param comment The QUIT comment to send. */ /* Rewritten by Run - 24 sept 94 */ -static void exit_one_client(struct Client* bcptr, const char* comment) +static void exit_one_client(struct Client* bcptr, const char* comment, const char *batch_id) { struct SLink *lp; struct Ban *bp; @@ -240,7 +242,8 @@ static void exit_one_client(struct Client* bcptr, const char* comment) * that the client can show the "**signoff" message). * (Note: The notice is to the local clients *only*) */ - sendcmdto_common_channels_butone(bcptr, CMD_QUIT, NULL, ":%s", comment); + struct MsgTag *tags = batch_id ? msg_tag_build_batch(batch_id) : NULL; + sendcmdto_common_channels_butone(bcptr, CMD_QUIT, NULL, tags, ":%s", comment); remove_user_from_all_channels(bcptr); @@ -328,7 +331,7 @@ static void exit_one_client(struct Client* bcptr, const char* comment) * @param sptr source who thought that this was a good idea * @param comment comment sent as sign off message to local clients */ -static void exit_downlinks(struct Client *cptr, struct Client *sptr, char *comment) +static void exit_downlinks(struct Client *cptr, struct Client *sptr, char *comment, const char *batch_id) { struct Client *acptr; struct DLink *next; @@ -342,15 +345,15 @@ static void exit_downlinks(struct Client *cptr, struct Client *sptr, char *comme next = lp->next; acptr = lp->value.cptr; /* Remove the downlinks and client of the downlink */ - exit_downlinks(acptr, sptr, comment); + exit_downlinks(acptr, sptr, comment, batch_id); /* Remove the downlink itself */ - exit_one_client(acptr, cli_name(&me)); + exit_one_client(acptr, cli_name(&me), batch_id); } /* Remove all clients of this server */ acptrp = cli_serv(cptr)->client_list; for (i = 0; i <= cli_serv(cptr)->nn_mask; ++acptrp, ++i) { if (*acptrp) - exit_one_client(*acptrp, comment); + exit_one_client(*acptrp, comment, batch_id); } } @@ -400,6 +403,9 @@ int exit_client(struct Client *cptr, time_t on_for; char comment1[HOSTLEN + HOSTLEN + 2]; + char *batch_id = NULL; + struct Client *local_user; + assert(killer); if (MyConnect(victim)) { @@ -508,6 +514,18 @@ int exit_client(struct Client *cptr, get_client_name(killer, HIDE_IP)); sendto_opmask_butone(0, SNO_NETWORK, "Net break: %C %C (%s)", cli_serv(victim)->up, victim, comment); + + /* This is a netsplit (server disconnect) - all server disconnects + * cause users on that server and its downlinks to quit, so we should + * batch the QUIT messages whether it's a local or remote server. + */ + /* Generate batch ID for this netsplit ONCE at the top level */ + batch_id = batch_generate_id(); + + /* Register batch metadata - send_tags() will automatically send batch start + * when it first sees a message with this batch_id + */ + batch_register(batch_id, BATCH_TYPE_NETSPLIT, comment1); } /* @@ -526,8 +544,17 @@ int exit_client(struct Client *cptr, } /* Then remove the client structures */ if (IsServer(victim)) - exit_downlinks(victim, killer, comment1); - exit_one_client(victim, comment); + exit_downlinks(victim, killer, comment1, batch_id); + exit_one_client(victim, comment, batch_id); + + /* Send batch end to all affected clients AFTER all quits are processed + * We match against the stored batch_id to identify which clients received + * batch start. This is more robust than using a sentalong marker and + * supports nested batches in the future. + */ + if (IsServer(victim) && batch_id) { + batch_complete(batch_id); + } /* * cptr can only have been killed if it was cptr itself that got killed here, diff --git a/ircd/s_user.c b/ircd/s_user.c index ec0b5783..3f664626 100644 --- a/ircd/s_user.c +++ b/ircd/s_user.c @@ -621,7 +621,7 @@ int set_nick_name(struct Client* cptr, struct Client* sptr, * on that channel. Propagate notice to other servers. */ if (IsUser(sptr)) { - sendcmdto_common_channels_butone(sptr, CMD_NICK, NULL, ":%s", nick); + sendcmdto_common_channels_butone(sptr, CMD_NICK, NULL, NULL, ":%s", nick); add_history(sptr, 1); sendcmdto_serv_butone(sptr, CMD_NICK, cptr, "%s %Tu", nick, cli_lastnick(sptr)); @@ -869,7 +869,7 @@ void send_user_info(struct Client* sptr, char* names, int rpl, InfoFormatter fmt if (5 == ++arg_count) break; } - send_buffer(sptr, NULL, mb, 0); + send_buffer(sptr, mb, 0, NULL); msgq_clean(mb); } @@ -924,18 +924,18 @@ hide_hostmask(struct Client *cptr, unsigned int flag) /* Send a JOIN unless the user's join has been delayed. */ if (!IsDelayedJoin(chan)) { - sendjointo_channel_butserv(cptr, chan->channel, 0, CAP_CHGHOST); + sendjointo_channel_butserv(cptr, chan->channel, 0, CAP_CHGHOST, NULL); if (cli_user(cptr)->away) sendcmdto_capflag_channel_butserv_butone(cptr, CMD_AWAY, chan->channel, - NULL, 0, CAP_AWAYNOTIFY, CAP_CHGHOST, ":%s", cli_user(cptr)->away); + NULL, 0, CAP_AWAYNOTIFY, CAP_CHGHOST, NULL, ":%s", cli_user(cptr)->away); } if (IsChanOp(chan) && HasVoice(chan)) sendcmdto_capflag_channel_butserv_butone(&his, CMD_MODE, chan->channel, cptr, 0, - 0, CAP_CHGHOST, "%H +ov %C %C", chan->channel, cptr, + 0, CAP_CHGHOST, NULL, "%H +ov %C %C", chan->channel, cptr, cptr); else if (IsChanOp(chan) || HasVoice(chan)) sendcmdto_capflag_channel_butserv_butone(&his, CMD_MODE, chan->channel, cptr, 0, - 0, CAP_CHGHOST, "%H +%c %C", chan->channel, IsChanOp(chan) ? 'o' : 'v', cptr); + 0, CAP_CHGHOST, NULL, "%H +%c %C", chan->channel, IsChanOp(chan) ? 'o' : 'v', cptr); } return 0; } diff --git a/ircd/send.c b/ircd/send.c index 95190f7a..31bd6efc 100644 --- a/ircd/send.c +++ b/ircd/send.c @@ -24,10 +24,12 @@ #include "config.h" #include "send.h" +#include "batch.h" #include "channel.h" #include "class.h" #include "client.h" #include "ircd.h" +#include "ircd_alloc.h" #include "ircd_features.h" #include "ircd_log.h" #include "ircd_snprintf.h" @@ -35,6 +37,7 @@ #include "list.h" #include "match.h" #include "msg.h" +#include "msg_tag.h" #include "numnicks.h" #include "parse.h" #include "s_bsd.h" @@ -208,36 +211,70 @@ void send_queued(struct Client *to) update_write(to); } -void send_tags(struct Client* to, struct Client* from, int prio) +void send_tags(struct Client* to, struct MsgTag *tags, int prio) { char tags_buffer[512] = ""; char *pos = tags_buffer; struct MsgBuf* buf; + struct MsgTag *tag; + const char *batch_id_in_tags = NULL; if (IsServer(to)) return; - int tag_written = 0; - if (CapHas(cli_active(to), CAP_SERVER_TIME)) { - char timestamp[32]; - iso8601_timestamp(timestamp, sizeof(timestamp)); - pos += sprintf(pos, "@time=%s", timestamp); - tag_written = 1; + /* Check for batch tag and handle batch start automatically */ + for (tag = tags; tag; tag = tag->next) { + if (strcmp(tag->key, "batch") == 0) { + batch_id_in_tags = tag->value; + break; + } + } + + if (batch_id_in_tags && CapHas(cli_active(to), CAP_BATCH)) { + /* Send batch start if we haven't already sent it for this batch */ + enum BatchType type; + const char *param; + if (batch_get_meta(batch_id_in_tags, &type, ¶m)) { + batch_send_start(to, type, param, batch_id_in_tags); + } } - if (CapHas(cli_active(to), CAP_ACCOUNT_TAG) - && from != NULL - && IsUser(from) - && cli_user(from) != NULL - && IsAccount(from)) { - if (tag_written) { - pos += sprintf(pos, ";account=%s", cli_user(from)->account); + /* Add tags from the tag list */ + for (tag = tags; tag; tag = tag->next) { + /* Check capability for each tag type */ + int should_add = 0; + if (strcmp(tag->key, "batch") == 0 && CapHas(cli_active(to), CAP_BATCH)) { + should_add = 1; + } else if (strcmp(tag->key, "account") == 0 && CapHas(cli_active(to), CAP_ACCOUNT_TAG)) { + should_add = 1; } else { - pos += sprintf(pos, "@account=%s", cli_user(from)->account); + should_add = 0; // TODO: We only add if message-tags capability is supported } - tag_written = 1; + + if (should_add) { + if (tag->value) { + pos += sprintf(pos, "%s=%s;", tag->key, tag->value); + } else { + pos += sprintf(pos, "%s;", tag->key); + } + } + } + + /* Add server-time tag if capability is enabled (time should never be added to the MsgTags list) */ + if (CapHas(cli_active(to), CAP_SERVER_TIME)) { + char timestamp[32]; + iso8601_timestamp(timestamp, sizeof(timestamp)); + pos += sprintf(pos, "time=%s;", timestamp); } - if (tag_written) { + + /* Remove trailing semicolon and prepend @ if we have tags */ + if (pos > tags_buffer) { + pos--; /* Remove last semicolon */ + *pos = '\0'; + /* Prepend @ to the beginning */ + memmove(tags_buffer + 1, tags_buffer, strlen(tags_buffer)); + tags_buffer[0] = '@'; + pos = tags_buffer + strlen(tags_buffer); pos += sprintf(pos, " "); } @@ -254,8 +291,9 @@ void send_tags(struct Client* to, struct Client* from, int prio) * @param[in,out] to Client to send message to. * @param[in] buf Message to send. * @param[in] prio If non-zero, send as high priority. + * @param[in] tags Optional list of message tags (can be NULL). */ -void send_buffer(struct Client* to, struct Client* from, struct MsgBuf* buf, int prio) +void send_buffer(struct Client* to, struct MsgBuf* buf, int prio, struct MsgTag *tags) { assert(0 != to); assert(0 != buf); @@ -278,7 +316,7 @@ void send_buffer(struct Client* to, struct Client* from, struct MsgBuf* buf, int return; } - send_tags(to, from, prio); + send_tags(to, tags, prio); Debug((DEBUG_SEND, "Sending [%p] to %s", buf, cli_name(to))); msgq_add(&(cli_sendQ(to)), buf, prio); @@ -344,7 +382,7 @@ void sendrawto_one(struct Client *to, const char *pattern, ...) mb = msgq_vmake(to, pattern, vl); va_end(vl); - send_buffer(to, NULL, mb, 0); + send_buffer(to, mb, 0, NULL); msgq_clean(mb); } @@ -372,7 +410,9 @@ void sendcmdto_one(struct Client *from, const char *cmd, const char *tok, va_end(vd.vd_args); - send_buffer(to, from, mb, 0); + struct MsgTag *tags = msg_tag_build(from, NULL); + send_buffer(to, mb, 0, tags); + msg_tag_free(tags); msgq_clean(mb); } @@ -401,7 +441,9 @@ void sendcmdto_prio_one(struct Client *from, const char *cmd, const char *tok, va_end(vd.vd_args); - send_buffer(to, from, mb, 1); + struct MsgTag *tags = msg_tag_build(from, NULL); + send_buffer(to, mb, 1, tags); + msg_tag_free(tags); msgq_clean(mb); } @@ -441,7 +483,7 @@ void sendcmdto_flag_serv_butone(struct Client *from, const char *cmd, continue; if ((forbid < FLAG_LAST_FLAG) && HasFlag(lp->value.cptr, forbid)) continue; - send_buffer(lp->value.cptr, from, mb, 0); + send_buffer(lp->value.cptr, mb, 0, NULL); } msgq_clean(mb); @@ -474,7 +516,7 @@ void sendcmdto_serv_butone(struct Client *from, const char *cmd, for (lp = cli_serv(&me)->down; lp; lp = lp->next) { if (one && lp->value.cptr == cli_from(one)) continue; - send_buffer(lp->value.cptr, from, mb, 0); + send_buffer(lp->value.cptr, mb, 0, NULL); } msgq_clean(mb); @@ -502,6 +544,8 @@ bump_sentalong(struct Client *one) cli_sentalong(one) = sentalong_marker; } + + /** Send a (prefixed) command to all channels that \a from is on. * @param[in] from Client originating the command. * @param[in] cmd Long name of command. @@ -510,7 +554,7 @@ bump_sentalong(struct Client *one) * @param[in] pattern Format string for command arguments. */ void sendcmdto_common_channels_butone(struct Client *from, const char *cmd, - const char *tok, struct Client *one, + const char *tok, struct Client *one, struct MsgTag *tags, const char *pattern, ...) { struct VarData vd; @@ -539,18 +583,23 @@ void sendcmdto_common_channels_butone(struct Client *from, const char *cmd, if (IsZombie(chan) || IsDelayedJoin(chan)) continue; for (member = chan->channel->members; member; - member = member->next_member) + member = member->next_member) if (MyConnect(member->user) && -1 < cli_fd(cli_from(member->user)) && member->user != one && cli_sentalong(member->user) != sentalong_marker) { - cli_sentalong(member->user) = sentalong_marker; - send_buffer(member->user, from, mb, 0); + cli_sentalong(member->user) = sentalong_marker; + struct MsgTag *final_tags = msg_tag_add_account(tags, from); + send_buffer(member->user, mb, 0, final_tags); + if (final_tags != tags) msg_tag_free(final_tags); } } - if (MyConnect(from) && from != one) - send_buffer(from, from, mb, 0); + if (MyConnect(from) && from != one) { + struct MsgTag *final_tags = msg_tag_add_account(tags, from); + send_buffer(from, mb, 0, final_tags); + if (final_tags != tags) msg_tag_free(final_tags); + } msgq_clean(mb); } @@ -606,7 +655,9 @@ void sendcmdto_capflag_common_channels_butone(struct Client *from, const char *c && (forbid == 0 || !CapHas(cli_active(member->user), forbid))) { cli_sentalong(member->user) = sentalong_marker; - send_buffer(member->user, from, mb, 0); + struct MsgTag *tags = msg_tag_build(from, NULL); + send_buffer(member->user, mb, 0, tags); + msg_tag_free(tags); } } } @@ -614,8 +665,11 @@ void sendcmdto_capflag_common_channels_butone(struct Client *from, const char *c if (MyConnect(from) && from != one && (require == 0 || CapHas(cli_active(from), require)) - && (forbid == 0 || !CapHas(cli_active(from), forbid))) - send_buffer(from, from, mb, 0); + && (forbid == 0 || !CapHas(cli_active(from), forbid))) { + struct MsgTag *tags = msg_tag_build(from, NULL); + send_buffer(from, mb, 0, tags); + msg_tag_free(tags); + } msgq_clean(mb); } @@ -629,12 +683,14 @@ void sendcmdto_capflag_common_channels_butone(struct Client *from, const char *c * @param[in] skip Bitmask of SKIP_DEAF, SKIP_NONOPS, SKIP_NONVOICES indicating which clients to skip. * @param[in] require Only send to clients with this Flag bit set. * @param[in] forbid Do not send to clients with this Flag bit set. + * @param[in] tags Optional list of message tags (can be NULL). * @param[in] pattern Format string for command arguments. */ void sendcmdto_capflag_channel_butserv_butone(struct Client *from, const char *cmd, const char *tok, struct Channel *to, struct Client *one, unsigned int skip, capset_t require, capset_t forbid, + struct MsgTag *tags, const char *pattern, ...) { struct VarData vd; @@ -660,7 +716,9 @@ void sendcmdto_capflag_channel_butserv_butone(struct Client *from, const char *c || (forbid && CapHas(cli_active(member->user), forbid))) continue; - send_buffer(member->user, from, mb, 0); + struct MsgTag *final_tags = msg_tag_add_account(tags, from); + send_buffer(member->user, mb, 0, final_tags); + if (final_tags != tags) msg_tag_free(final_tags); } msgq_clean(mb); @@ -675,13 +733,16 @@ void sendcmdto_capflag_channel_butserv_butone(struct Client *from, const char *c */ void sendjointo_channel_butserv(struct Client *from, struct Channel *chptr, capset_t require, - capset_t forbid) + capset_t forbid, + struct MsgTag *tags) { + /* Pass through tags if provided (from server struct during netjoin) */ + /* Send JOIN messages with tags */ sendcmdto_capflag_channel_butserv_butone(from, CMD_JOIN, chptr, NULL, - 0, require | CAP_EXTJOIN, forbid, "%H %s :%s", chptr, + 0, require | CAP_EXTJOIN, forbid, tags, "%H %s :%s", chptr, IsAccount(from) ? cli_account(from) : "*", cli_info(from)); sendcmdto_capflag_channel_butserv_butone(from, CMD_JOIN, chptr, NULL, - 0, require, forbid | CAP_EXTJOIN, "%H", chptr); + 0, require, forbid | CAP_EXTJOIN, tags, "%H", chptr); } /* Send JOIN to a single user. @@ -734,7 +795,9 @@ void sendcmdto_channel_butserv_butone(struct Client *from, const char *cmd, || (skip & SKIP_NONOPS && !IsChanOp(member)) || (skip & SKIP_NONVOICES && !IsChanOp(member) && !HasVoice(member))) continue; - send_buffer(member->user, from, mb, 0); + struct MsgTag *tags = msg_tag_build(from, NULL); + send_buffer(member->user, mb, 0, tags); + msg_tag_free(tags); } msgq_clean(mb); @@ -777,7 +840,9 @@ void sendcmdto_channel_servers_butone(struct Client *from, const char *cmd, || (skip & SKIP_NONVOICES && !IsChanOp(member) && !HasVoice(member))) continue; cli_sentalong(member->user) = sentalong_marker; - send_buffer(member->user, from, serv_mb, 0); + struct MsgTag *tags = msg_tag_build(from, NULL); + send_buffer(member->user, serv_mb, 0, tags); + msg_tag_free(tags); } msgq_clean(serv_mb); } @@ -831,10 +896,12 @@ void sendcmdto_channel_butone(struct Client *from, const char *cmd, continue; cli_sentalong(member->user) = sentalong_marker; + struct MsgTag *tags = msg_tag_build(from, NULL); if (MyConnect(member->user)) /* pick right buffer to send */ - send_buffer(member->user, from, user_mb, 0); + send_buffer(member->user, user_mb, 0, tags); else - send_buffer(member->user, from, serv_mb, 0); + send_buffer(member->user, serv_mb, 0, tags); + msg_tag_free(tags); } msgq_clean(user_mb); @@ -894,7 +961,9 @@ void sendwallto_group_butone(struct Client *from, int type, struct Client *one, (!SendWallops(cptr) || (his_wallops && !IsAnOper(cptr)))) || (type == WALL_WALLUSERS && !SendWallops(cptr))) continue; /* skip it */ - send_buffer(cptr, from, mb, 1); + struct MsgTag *tags = msg_tag_build(from, NULL); + send_buffer(cptr, mb, 1, tags); + msg_tag_free(tags); } msgq_clean(mb); @@ -908,7 +977,9 @@ void sendwallto_group_butone(struct Client *from, int type, struct Client *one, for (lp = cli_serv(&me)->down; lp; lp = lp->next) { if (one && lp->value.cptr == cli_from(one)) continue; - send_buffer(lp->value.cptr, from, mb, 1); + struct MsgTag *tags = msg_tag_build(from, NULL); + send_buffer(lp->value.cptr, mb, 1, tags); + msg_tag_free(tags); } msgq_clean(mb); @@ -955,10 +1026,12 @@ void sendcmdto_match_butone(struct Client *from, const char *cmd, continue; /* skip it */ cli_sentalong(cptr) = sentalong_marker; + struct MsgTag *tags = msg_tag_build(from, NULL); if (MyConnect(cptr)) /* send right buffer */ - send_buffer(cptr, from, user_mb, 0); + send_buffer(cptr, user_mb, 0, tags); else - send_buffer(cptr, from, serv_mb, 0); + send_buffer(cptr, serv_mb, 0, tags); + msg_tag_free(tags); } msgq_clean(user_mb); @@ -1037,7 +1110,7 @@ void vsendto_opmask_butone(struct Client *one, unsigned int mask, for (; opslist; opslist = opslist->next) if (opslist->value.cptr != one) - send_buffer(opslist->value.cptr, NULL, mb, 0); + send_buffer(opslist->value.cptr, mb, 0, NULL); msgq_clean(mb); }