[READ-ONLY] Mirror of https://github.com/VibeDevelopers/Comet.
0

Configure Feed

Select the types of activity you want to include in your feed.

Add batch support

- Add "batch" client capability
- Support sending netsplit/netjoin batches
- Support receiving BATCH from clients and remote servers
- Support for modules to define supported batch types for incoming
batches
- Add "message_handler" hook to override the handler for a particular
incoming message
- Add technical and user-facing documentation for batches
- Add some more sendto_* overloads that support tags which were missing
from the initial message-tags implementation but turned out to be
needed

authored by

Ryan Schmidt and committed by
Aaron Jones
(Jan 21, 2026, 4:10 PM UTC) 4cc9be1d e2a499f7

+1310 -117
+1 -1
help/Makefile.am
··· 15 15 version kick who invite quit join list nick oper part \ 16 16 time credits motd userhost users whois ison lusers \ 17 17 user help pass error challenge knock ping pong \ 18 - map trace chantrace extban monitor 18 + map trace chantrace extban monitor batch 19 19 20 20 all: index 21 21 build:
+98
include/batch.h
··· 1 + /* 2 + * Solanum: a slightly advanced ircd 3 + * batch.h: support functions for batches 4 + * 5 + * Copyright (c) 2025 Ryan Schmidt <skizzerz@skizzerz.net> 6 + * 7 + * This program is free software; you can redistribute it and/or modify 8 + * it under the terms of the GNU General Public License as published by 9 + * the Free Software Foundation; either version 2 of the License, or 10 + * (at your option) any later version. 11 + * 12 + * This program is distributed in the hope that it will be useful, 13 + * but WITHOUT ANY WARRANTY; without even the implied warranty of 14 + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 + * GNU General Public License for more details. 16 + * 17 + * You should have received a copy of the GNU General Public License 18 + * along with this program; if not, write to the Free Software 19 + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 20 + */ 21 + 22 + #ifndef INCLUDED_batch_h 23 + #define INCLUDED_batch_h 24 + 25 + #include "msgbuf.h" 26 + 27 + #define BATCH_EXPIRY (15) 28 + #define BATCH_ID_LEN (16) 29 + 30 + /* If set, nested batches are not automatically dispatched to their handler. 31 + * Instead, the batch handler must manually process child batches. 32 + * Child batches are still automatically freed at the end of the handler invocation. 33 + */ 34 + #define BATCH_FLAG_SKIP_CHILDREN 0x01 35 + /* If set, allows all batch types to be nested under this batch. 36 + * The allowed_children field is ignored and may be set to NULL. */ 37 + #define BATCH_FLAG_ALLOW_ALL 0x02 38 + 39 + struct Batch 40 + { 41 + /* server-generated batch ID (15 random characters + trailing null byte) */ 42 + char id[BATCH_ID_LEN]; 43 + /* BATCH command that started this batch */ 44 + const struct MsgBuf *start; 45 + /* client-generated batch reference tag */ 46 + const char *tag; 47 + /* batch type */ 48 + const char *type; 49 + /* time when this batch expires (times out) */ 50 + time_t expires; 51 + /* For nested batches, points at the batch immediately encapsulating this one 52 + * NULL if this batch is not nested 53 + */ 54 + struct Batch *parent; 55 + /* All finished batches nested under this one (linked list of struct Batch *) */ 56 + rb_dlink_list children; 57 + /* Number of messages in this batch (including the start message) */ 58 + unsigned int len; 59 + /* A linked list of struct BatchMessage * for each message in the batch */ 60 + rb_dlink_list messages; 61 + }; 62 + 63 + struct BatchMessage 64 + { 65 + /* A copy of all string data for the message */ 66 + char *data; 67 + /* Size of data in bytes */ 68 + size_t datalen; 69 + /* A MsgBuf whose internal pointers all point to portions of the data array */ 70 + struct MsgBuf msg; 71 + }; 72 + 73 + struct Client; 74 + typedef void (*batch_cb)(struct Client *client_p, struct Client *source_p, struct Batch *batch, void *userdata); 75 + 76 + struct BatchHandler 77 + { 78 + /* function called when the batch is completed */ 79 + batch_cb handler; 80 + /* optional data pointer to pass into the handler */ 81 + void *userdata; 82 + /* bitfield of flags for this handler from the BATCH_FLAG_* constants */ 83 + unsigned int flags; 84 + /* batch type names that are allowed to be nested under this one (array of strings) 85 + * terminate the array with a NULL entry, e.g. { "foo", "bar", "baz", NULL } 86 + */ 87 + const char **allowed_children; 88 + }; 89 + 90 + bool register_batch_handler(const char *type, const struct BatchHandler *handler); 91 + const struct BatchHandler *get_batch_handler(const char *type); 92 + void remove_batch_handler(const char *type); 93 + void generate_batch_id(char *buf, size_t size); 94 + struct Batch *batch_init(struct MsgBuf *start); 95 + void batch_add_msgbuf(struct Batch *batch, struct MsgBuf *msg); 96 + void batch_free(struct Batch *batch); 97 + 98 + #endif /* INCLUDED_batch_h */
+1
include/channel.h
··· 317 317 extern int get_channel_access(struct Client *source_p, struct Channel *chptr, struct membership *msptr, int dir, const char *modestr); 318 318 319 319 extern void send_channel_join(struct Channel *chptr, struct Client *client_p); 320 + extern void send_batched_channel_join(struct Channel *chptr, struct Client *client_p, const char *batch); 320 321 321 322 #endif /* INCLUDED_channel_h */
+3
include/client.h
··· 275 275 unsigned int sasl_messages; 276 276 unsigned int sasl_failures; 277 277 time_t sasl_next_retry; 278 + 279 + uint32_t pending_batch_lines; /* number of lines held in pending client-initiated batches */ 280 + rb_dlink_list pending_batches; /* batches this client started but did not finish */ 278 281 }; 279 282 280 283 #define AUTHC_F_DEFERRED 0x01
+1
include/hook.h
··· 67 67 extern int h_priv_change; 68 68 extern int h_cap_change; 69 69 extern int h_message_tag; 70 + extern int h_message_handler; 70 71 71 72 void init_hook(void); 72 73 int register_hook(const char *name);
+1
include/s_serv.h
··· 69 69 extern unsigned int CLICAP_CHGHOST; 70 70 extern unsigned int CLICAP_ECHO_MESSAGE; 71 71 extern unsigned int CLICAP_MESSAGE_TAGS; 72 + extern unsigned int CLICAP_BATCH; 72 73 73 74 /* 74 75 * XXX: this is kind of ugly, but this allows us to have backwards
+17 -8
include/send.h
··· 72 72 size_t n_tags, const struct MsgTag tags[], const char *, ...) AFP (7, 8); 73 73 extern void sendto_channel_local_butone(struct Client *, int type, struct Channel *, const char *, ...) AFP(4, 5); 74 74 75 - extern void sendto_channel_local_with_capability(struct Client *, int type, int caps, int negcaps, struct Channel *, const char *, ...) AFP(6, 7); 76 - extern void sendto_channel_local_with_capability_butone(struct Client *, int type, int caps, int negcaps, struct Channel *, 77 - const char *, ...) AFP(6, 7); 78 - extern void sendto_channel_local_with_capability_butone_tags(struct Client *, int type, int caps, int negcaps, struct Channel *, 79 - size_t n_tags, const struct MsgTag tags[], const char *, ...) AFP(8, 9); 75 + extern void sendto_channel_local_with_capability(struct Client *, int type, int caps, int negcaps, 76 + struct Channel *, const char *, ...) AFP(6, 7); 77 + extern void sendto_channel_local_with_capability_tags(struct Client *, int type, int caps, int negcaps, 78 + struct Channel *, size_t n_tags, const struct MsgTag tags[], const char *, ...) AFP(8, 9); 79 + extern void sendto_channel_local_with_capability_butone(struct Client *, int type, int caps, int negcaps, 80 + struct Channel *, const char *, ...) AFP(6, 7); 81 + extern void sendto_channel_local_with_capability_butone_tags(struct Client *, int type, int caps, int negcaps, 82 + struct Channel *, size_t n_tags, const struct MsgTag tags[], const char *, ...) AFP(8, 9); 80 83 81 - extern void sendto_common_channels_local(struct Client *, int cap, int negcap, const char *, ...) AFP(4, 5); 82 - extern void sendto_common_channels_local_butone(struct Client *, int cap, int negcap, const char *, ...) AFP(4, 5); 84 + extern void sendto_common_channels_local(struct Client *, int caps, int negcaps, const char *, ...) AFP(4, 5); 85 + extern void sendto_common_channels_local_tags(struct Client *, int caps, int negcaps, 86 + size_t n_tags, const struct MsgTag tags[], const char *, ...) AFP(6, 7); 87 + extern void sendto_common_channels_local_butone(struct Client *, int caps, int negcaps, const char *, ...) AFP(4, 5); 88 + extern void sendto_common_channels_local_butone_tags(struct Client *, int caps, int negcaps, 89 + size_t n_tags, const struct MsgTag tags[], const char *, ...) AFP(6, 7); 83 90 84 91 85 92 extern void sendto_match_butone(struct Client *one, struct Client *source_p, ··· 88 95 const char *mask, int what, int cli_cap, int cli_negcap, int serv_cap, int serv_negcap, 89 96 size_t n_tags, const struct MsgTag tags[], const char *, ...) AFP(11, 12); 90 97 extern void sendto_match_servs(struct Client *source_p, const char *mask, 91 - int capab, int, const char *, ...) AFP(5, 6); 98 + int cap, int negcap, const char *, ...) AFP(5, 6); 99 + extern void sendto_match_servs_tags(struct Client *source_p, const char *mask, 100 + int cap, int negcap, size_t n_tags, const struct MsgTag tags[], const char *, ...) AFP(7, 8); 92 101 93 102 extern void sendto_monitor(struct Client *, struct monitor *monptr, const char *, ...) AFP(3, 4); 94 103
+1
ircd/Makefile.am
··· 17 17 libircd_la_SOURCES = \ 18 18 authproc.c \ 19 19 bandbi.c \ 20 + batch.c \ 20 21 cache.c \ 21 22 capability.c \ 22 23 channel.c \
+198
ircd/batch.c
··· 1 + /* 2 + * Solanum: a slightly advanced ircd 3 + * batch.c: support functions for batches 4 + * 5 + * Copyright (c) 2025 Ryan Schmidt <skizzerz@skizzerz.net> 6 + * 7 + * This program is free software; you can redistribute it and/or modify 8 + * it under the terms of the GNU General Public License as published by 9 + * the Free Software Foundation; either version 2 of the License, or 10 + * (at your option) any later version. 11 + * 12 + * This program is distributed in the hope that it will be useful, 13 + * but WITHOUT ANY WARRANTY; without even the implied warranty of 14 + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 + * GNU General Public License for more details. 16 + * 17 + * You should have received a copy of the GNU General Public License 18 + * along with this program; if not, write to the Free Software 19 + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 20 + */ 21 + 22 + #include "stdinc.h" 23 + #include "batch.h" 24 + #include "rb_dictionary.h" 25 + 26 + static rb_dictionary *handlers = NULL; 27 + 28 + bool 29 + register_batch_handler(const char *type, const struct BatchHandler *handler) 30 + { 31 + if (handlers == NULL) 32 + handlers = rb_dictionary_create("batch handlers", rb_strcasecmp); 33 + 34 + if (rb_dictionary_find(handlers, type) != NULL) 35 + return false; 36 + 37 + rb_dictionary_add(handlers, type, (void *)handler); 38 + return true; 39 + } 40 + 41 + const struct BatchHandler * 42 + get_batch_handler(const char *type) 43 + { 44 + if (handlers == NULL) 45 + return NULL; 46 + 47 + rb_dictionary_element *elem = rb_dictionary_find(handlers, type); 48 + if (elem == NULL) 49 + return NULL; 50 + 51 + return elem->data; 52 + } 53 + 54 + void 55 + remove_batch_handler(const char *type) 56 + { 57 + if (handlers == NULL) 58 + return; 59 + 60 + rb_dictionary_delete(handlers, type); 61 + } 62 + 63 + void 64 + generate_batch_id(char *buf, size_t size) 65 + { 66 + static const char *alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-A"; 67 + 68 + if (size > BATCH_ID_LEN + 1) 69 + size = BATCH_ID_LEN + 1; 70 + 71 + for (size_t i = 0; i < size - 1; i++) 72 + buf[i] = alphabet[rand() % 64]; 73 + 74 + buf[size - 1] = 0; 75 + } 76 + 77 + struct Batch * 78 + batch_init(struct MsgBuf *start) 79 + { 80 + struct Batch *batch = rb_malloc(sizeof(struct Batch)); 81 + batch_add_msgbuf(batch, start); 82 + 83 + generate_batch_id(batch->id, sizeof(batch->id)); 84 + batch->start = &((struct BatchMessage *)batch->messages.head->data)->msg; 85 + batch->tag = batch->start->para[1] + 1; 86 + batch->type = batch->start->para[2]; 87 + batch->expires = rb_current_time() + BATCH_EXPIRY; 88 + 89 + return batch; 90 + } 91 + 92 + void 93 + batch_free(struct Batch *batch) 94 + { 95 + rb_dlink_node *ptr, *next_ptr; 96 + 97 + RB_DLINK_FOREACH_SAFE(ptr, next_ptr, batch->messages.head) 98 + { 99 + struct BatchMessage *message = ptr->data; 100 + rb_dlinkDestroy(ptr, &batch->messages); 101 + rb_free(message->data); 102 + rb_free(message); 103 + } 104 + 105 + RB_DLINK_FOREACH_SAFE(ptr, next_ptr, batch->children.head) 106 + { 107 + struct Batch *child = ptr->data; 108 + rb_dlinkDestroy(ptr, &batch->children); 109 + batch_free(child); 110 + } 111 + 112 + rb_free(batch); 113 + } 114 + 115 + void 116 + batch_add_msgbuf(struct Batch *batch, struct MsgBuf *msg) 117 + { 118 + unsigned int len = 0; 119 + char *c; 120 + struct BatchMessage *copy = rb_malloc(sizeof(struct BatchMessage)); 121 + 122 + for (int i = 0; i < msg->n_tags; i++) 123 + { 124 + len += strlen(msg->tags[i].key) + 1; 125 + len += strlen(msg->tags[i].value) + 1; 126 + } 127 + 128 + for (int i = 0; i < msg->n_para; i++) 129 + { 130 + len += strlen(msg->para[i]) + 1; 131 + } 132 + 133 + if (msg->origin != NULL) 134 + len += strlen(msg->origin) + 1; 135 + 136 + if (msg->cmd != NULL) 137 + len += strlen(msg->cmd) + 1; 138 + 139 + if (msg->target != NULL) 140 + len += strlen(msg->target) + 1; 141 + 142 + copy->data = rb_malloc(len); 143 + copy->datalen = len; 144 + c = copy->data; 145 + 146 + copy->msg.n_tags = msg->n_tags; 147 + copy->msg.tagslen = msg->tagslen; 148 + for (int i = 0; i < msg->n_tags; i++) 149 + { 150 + strcpy(c, msg->tags[i].key); 151 + copy->msg.tags[i].key = c; 152 + c += strlen(c) + 1; 153 + 154 + strcpy(c, msg->tags[i].value); 155 + copy->msg.tags[i].value = c; 156 + c += strlen(c) + 1; 157 + } 158 + 159 + copy->msg.n_para = msg->n_para; 160 + for (int i = 0; i < msg->n_para; i++) 161 + { 162 + strcpy(c, msg->para[i]); 163 + copy->msg.para[i] = c; 164 + c += strlen(c) + 1; 165 + } 166 + 167 + copy->msg.endp = c - 1; 168 + 169 + if (msg->origin == NULL) 170 + copy->msg.origin = NULL; 171 + else 172 + { 173 + strcpy(c, msg->origin); 174 + copy->msg.origin = c; 175 + c += strlen(c) + 1; 176 + } 177 + 178 + if (msg->cmd == NULL) 179 + copy->msg.cmd = NULL; 180 + else 181 + { 182 + strcpy(c, msg->cmd); 183 + copy->msg.cmd = c; 184 + c += strlen(c) + 1; 185 + } 186 + 187 + if (msg->target == NULL) 188 + copy->msg.target = NULL; 189 + else 190 + { 191 + strcpy(c, msg->target); 192 + copy->msg.target = c; 193 + } 194 + 195 + copy->msg.preserve_trailing = msg->preserve_trailing; 196 + batch->len++; 197 + rb_dlinkAddTailAlloc(copy, &batch->messages); 198 + }
+26 -8
ircd/channel.c
··· 123 123 void 124 124 send_channel_join(struct Channel *chptr, struct Client *client_p) 125 125 { 126 + send_batched_channel_join(chptr, client_p, NULL); 127 + } 128 + 129 + /* 130 + * send_batched_channel_join() 131 + * 132 + * input - channel to join, client joining, batch reference id. 133 + * output - none 134 + * side effects - none 135 + */ 136 + void 137 + send_batched_channel_join(struct Channel *chptr, struct Client *client_p, const char *batch) 138 + { 139 + struct MsgTag tag = { "batch", batch, CLICAP_BATCH }; 140 + size_t n_tags = (batch == NULL) ? 0 : 1; 141 + 126 142 if (!IsClient(client_p)) 127 143 return; 128 144 129 - sendto_channel_local_with_capability(client_p, ALL_MEMBERS, NOCAPS, CLICAP_EXTENDED_JOIN, chptr, ":%s!%s@%s JOIN %s", 130 - client_p->name, client_p->username, client_p->host, chptr->chname); 145 + sendto_channel_local_with_capability_tags(client_p, ALL_MEMBERS, NOCAPS, CLICAP_EXTENDED_JOIN, chptr, 146 + n_tags, &tag, ":%s!%s@%s JOIN %s", 147 + client_p->name, client_p->username, client_p->host, chptr->chname); 131 148 132 - sendto_channel_local_with_capability(client_p, ALL_MEMBERS, CLICAP_EXTENDED_JOIN, NOCAPS, chptr, ":%s!%s@%s JOIN %s %s :%s", 133 - client_p->name, client_p->username, client_p->host, chptr->chname, 134 - EmptyString(client_p->user->suser) ? "*" : client_p->user->suser, 135 - client_p->info); 149 + sendto_channel_local_with_capability_tags(client_p, ALL_MEMBERS, CLICAP_EXTENDED_JOIN, NOCAPS, chptr, 150 + n_tags, &tag, ":%s!%s@%s JOIN %s %s :%s", 151 + client_p->name, client_p->username, client_p->host, chptr->chname, 152 + EmptyString(client_p->user->suser) ? "*" : client_p->user->suser, 153 + client_p->info); 136 154 137 155 /* Send away message to away-notify enabled clients. */ 138 156 if (client_p->user->away) 139 157 sendto_channel_local_with_capability_butone(client_p, ALL_MEMBERS, CLICAP_AWAY_NOTIFY, NOCAPS, chptr, 140 - ":%s!%s@%s AWAY :%s", client_p->name, client_p->username, 141 - client_p->host, client_p->user->away); 158 + ":%s!%s@%s AWAY :%s", client_p->name, client_p->username, 159 + client_p->host, client_p->user->away); 142 160 } 143 161 144 162 /* find_channel_membership()
+40 -22
ircd/client.c
··· 25 25 #include "stdinc.h" 26 26 #include "defaults.h" 27 27 28 + #include "batch.h" 28 29 #include "client.h" 29 30 #include "class.h" 30 31 #include "hash.h" ··· 60 61 static void free_exited_clients(void *unused); 61 62 static void exit_aborted_clients(void *unused); 62 63 63 - static int exit_remote_client(struct Client *, struct Client *, struct Client *,const char *); 64 + static int exit_remote_client(struct Client *, struct Client *, struct Client *, const char *, const char *); 64 65 static int exit_remote_server(struct Client *, struct Client *, struct Client *,const char *); 65 - static int exit_local_client(struct Client *, struct Client *, struct Client *,const char *); 66 + static int exit_local_client(struct Client *, struct Client *, struct Client *, const char *, const char *); 66 67 static int exit_unknown_client(struct Client *, struct Client *, struct Client *,const char *); 67 68 static int exit_local_server(struct Client *, struct Client *, struct Client *,const char *); 68 69 static int qs_server(struct Client *, struct Client *, struct Client *, const char *comment); ··· 92 93 }; 93 94 94 95 static rb_dlink_list abort_list; 95 - 96 96 97 97 /* 98 98 * init_client ··· 1215 1215 * added sanity test code.... source_p->serv might be NULL... 1216 1216 */ 1217 1217 static void 1218 - recurse_remove_clients(struct Client *source_p, const char *comment) 1218 + recurse_remove_clients(struct Client *source_p, const char *comment, const char *batch) 1219 1219 { 1220 1220 struct Client *target_p; 1221 1221 rb_dlink_node *ptr, *ptr_next; ··· 1236 1236 add_nd_entry(target_p->name); 1237 1237 1238 1238 if(!IsDead(target_p) && !IsClosing(target_p)) 1239 - exit_remote_client(NULL, target_p, &me, comment); 1239 + exit_remote_client(NULL, target_p, &me, comment, batch); 1240 1240 } 1241 1241 } 1242 1242 else ··· 1247 1247 target_p->flags |= FLAGS_KILLED; 1248 1248 1249 1249 if(!IsDead(target_p) && !IsClosing(target_p)) 1250 - exit_remote_client(NULL, target_p, &me, comment); 1250 + exit_remote_client(NULL, target_p, &me, comment, batch); 1251 1251 } 1252 1252 } 1253 1253 1254 1254 RB_DLINK_FOREACH_SAFE(ptr, ptr_next, source_p->serv->servers.head) 1255 1255 { 1256 1256 target_p = ptr->data; 1257 - recurse_remove_clients(target_p, comment); 1257 + recurse_remove_clients(target_p, comment, batch); 1258 1258 qs_server(NULL, target_p, &me, comment); 1259 1259 } 1260 1260 } ··· 1267 1267 static void 1268 1268 remove_dependents(struct Client *client_p, 1269 1269 struct Client *source_p, 1270 - struct Client *from, const char *comment, const char *comment1) 1270 + struct Client *from, const char *comment, const char *comment1, const char *batch) 1271 1271 { 1272 1272 struct Client *to; 1273 1273 rb_dlink_node *ptr, *next; ··· 1282 1282 sendto_one(to, "SQUIT %s :%s", get_id(source_p, to), comment); 1283 1283 } 1284 1284 1285 - recurse_remove_clients(source_p, comment1); 1285 + recurse_remove_clients(source_p, comment1, batch); 1286 1286 } 1287 1287 1288 1288 void ··· 1354 1354 rb_dlinkAdd(abt, &abt->node, &abort_list); 1355 1355 } 1356 1356 1357 - 1358 1357 /* This does the remove of the user from channels..local or remote */ 1359 1358 static inline void 1360 1359 exit_generic_client(struct Client *client_p, struct Client *source_p, struct Client *from, 1361 - const char *comment) 1360 + const char *comment, const char *batch) 1362 1361 { 1363 1362 rb_dlink_node *ptr, *next_ptr; 1363 + size_t n_tags = 0; 1364 + struct MsgTag batch_tag = {0}; 1364 1365 1365 1366 if(IsOper(source_p)) 1366 1367 rb_dlinkFindDestroy(source_p, &oper_list); 1367 1368 1368 - sendto_common_channels_local(source_p, NOCAPS, NOCAPS, ":%s!%s@%s QUIT :%s", 1369 - source_p->name, 1370 - source_p->username, source_p->host, comment); 1369 + if (batch != NULL) 1370 + { 1371 + n_tags = 1; 1372 + batch_tag.key = "batch"; 1373 + batch_tag.value = batch; 1374 + batch_tag.capmask = CLICAP_BATCH; 1375 + } 1376 + 1377 + sendto_common_channels_local_tags(source_p, NOCAPS, NOCAPS, n_tags, &batch_tag, 1378 + ":%s!%s@%s QUIT :%s", source_p->name, source_p->username, source_p->host, comment); 1371 1379 1372 1380 remove_user_from_channels(source_p); 1373 1381 ··· 1402 1410 1403 1411 static int 1404 1412 exit_remote_client(struct Client *client_p, struct Client *source_p, struct Client *from, 1405 - const char *comment) 1413 + const char *comment, const char *batch) 1406 1414 { 1407 - exit_generic_client(client_p, source_p, from, comment); 1415 + exit_generic_client(client_p, source_p, from, comment, batch); 1408 1416 1409 1417 if(source_p->servptr && source_p->servptr->serv) 1410 1418 { ··· 1473 1481 static char comment1[(HOSTLEN*2)+2]; 1474 1482 static char newcomment[BUFSIZE]; 1475 1483 struct Client *target_p; 1484 + char batch_id[BATCH_ID_LEN]; 1476 1485 1477 1486 if(ConfigServerHide.flatten_links) 1478 1487 strcpy(comment1, "*.net *.split"); ··· 1486 1495 snprintf(newcomment, sizeof(newcomment), "by %s: %s", 1487 1496 from->name, comment); 1488 1497 1489 - remove_dependents(client_p, source_p, from, IsPerson(from) ? newcomment : comment, comment1); 1498 + generate_batch_id(batch_id, sizeof(batch_id)); 1499 + sendto_local_clients_with_capability(CLICAP_BATCH, ":%s BATCH +%s netsplit %s", me.name, batch_id, comment1); 1500 + remove_dependents(client_p, source_p, from, IsPerson(from) ? newcomment : comment, comment1, batch_id); 1501 + sendto_local_clients_with_capability(CLICAP_BATCH, ":%s BATCH -%s", me.name, batch_id); 1490 1502 1491 1503 rb_dlinkDelete(&source_p->lnode, &source_p->servptr->serv->servers); 1492 1504 ··· 1548 1560 static char comment1[(HOSTLEN*2)+2]; 1549 1561 static char newcomment[BUFSIZE]; 1550 1562 unsigned int sendk, recvk; 1563 + char batch_id[BATCH_ID_LEN]; 1551 1564 1552 1565 rb_dlinkDelete(&source_p->localClient->tnode, &serv_list); 1553 1566 rb_dlinkFindDestroy(source_p, &global_serv_list); ··· 1587 1600 } 1588 1601 1589 1602 if(source_p->serv != NULL) 1590 - remove_dependents(client_p, source_p, from, IsPerson(from) ? newcomment : comment, comment1); 1603 + { 1604 + generate_batch_id(batch_id, sizeof(batch_id)); 1605 + sendto_local_clients_with_capability(CLICAP_BATCH, ":%s BATCH +%s netsplit %s", me.name, batch_id, comment1); 1606 + remove_dependents(client_p, source_p, from, IsPerson(from) ? newcomment : comment, comment1, batch_id); 1607 + sendto_local_clients_with_capability(CLICAP_BATCH, ":%s BATCH -%s", me.name, batch_id); 1608 + } 1591 1609 1592 1610 sendto_realops_snomask(SNO_GENERAL, L_ALL, "%s was connected" 1593 1611 " for %lld seconds. %d/%d sendK/recvK.", ··· 1615 1633 1616 1634 static int 1617 1635 exit_local_client(struct Client *client_p, struct Client *source_p, struct Client *from, 1618 - const char *comment) 1636 + const char *comment, const char *batch) 1619 1637 { 1620 1638 unsigned long on_for; 1621 1639 char tbuf[26]; 1622 1640 1623 - exit_generic_client(client_p, source_p, from, comment); 1641 + exit_generic_client(client_p, source_p, from, comment, batch); 1624 1642 clear_monitor(source_p); 1625 1643 1626 1644 s_assert(IsPerson(source_p)); ··· 1721 1739 { 1722 1740 /* Local clients of various types */ 1723 1741 if(IsPerson(source_p)) 1724 - ret = exit_local_client(client_p, source_p, from, comment); 1742 + ret = exit_local_client(client_p, source_p, from, comment, NULL); 1725 1743 else if(IsServer(source_p)) 1726 1744 ret = exit_local_server(client_p, source_p, from, comment); 1727 1745 /* IsUnknown || IsConnecting || IsHandShake */ ··· 1732 1750 { 1733 1751 /* Remotes */ 1734 1752 if(IsPerson(source_p)) 1735 - ret = exit_remote_client(client_p, source_p, from, comment); 1753 + ret = exit_remote_client(client_p, source_p, from, comment, NULL); 1736 1754 else if(IsServer(source_p)) 1737 1755 ret = exit_remote_server(client_p, source_p, from, comment); 1738 1756 }
+2
ircd/hook.c
··· 74 74 int h_priv_change; 75 75 int h_cap_change; 76 76 int h_message_tag; 77 + int h_message_handler; 77 78 78 79 void 79 80 init_hook(void) ··· 101 102 h_priv_change = register_hook("priv_change"); 102 103 h_cap_change = register_hook("cap_change"); 103 104 h_message_tag = register_hook("message_tag"); 105 + h_message_handler = register_hook("message_handler"); 104 106 } 105 107 106 108 /* grow_hooktable()
+1 -1
ircd/packet.c
··· 310 310 311 311 /* Check to make sure we're not flooding */ 312 312 if(!IsAnyServer(client_p) && 313 - (rb_linebuf_alloclen(&client_p->localClient->buf_recvq) > ConfigFileEntry.client_flood_max_lines)) 313 + (rb_linebuf_alloclen(&client_p->localClient->buf_recvq) + client_p->localClient->pending_batch_lines > ConfigFileEntry.client_flood_max_lines)) 314 314 { 315 315 if(!(ConfigFileEntry.no_oper_flood && IsOperGeneral(client_p))) 316 316 {
+5
ircd/parse.c
··· 275 275 mptr->count++; 276 276 277 277 ehandler = mptr->handlers[from->handler]; 278 + 279 + /* let hooks adjust which handler is ultimately called here */ 280 + hook_data hdata = { from, msgbuf_p, &ehandler }; 281 + call_hook(h_message_handler, &hdata); 282 + 278 283 handler = ehandler.handler; 279 284 280 285 /* check right amount of params is passed... --is */
+2
ircd/s_serv.c
··· 99 99 unsigned int CLICAP_CHGHOST; 100 100 unsigned int CLICAP_ECHO_MESSAGE; 101 101 unsigned int CLICAP_MESSAGE_TAGS; 102 + unsigned int CLICAP_BATCH; 102 103 103 104 /* 104 105 * initialize our builtin capability table. --nenolod ··· 150 151 CLICAP_CHGHOST = capability_put(cli_capindex, "chghost", &high_priority); 151 152 CLICAP_ECHO_MESSAGE = capability_put(cli_capindex, "echo-message", NULL); 152 153 CLICAP_MESSAGE_TAGS = capability_put(cli_capindex, "message-tags", NULL); 154 + CLICAP_BATCH = capability_put(cli_capindex, "batch", &high_priority); 153 155 } 154 156 155 157 static CNCB serv_connect_callback;
+140 -75
ircd/send.c
··· 893 893 va_end(args); 894 894 } 895 895 896 + /* sendto_channel_local_with_capability_tags() 897 + * 898 + * inputs - source, flags to send to, caps, negate caps, channel to send to, tags, va_args 899 + * outputs - message to local channel members 900 + * side effects - 901 + */ 902 + void 903 + sendto_channel_local_with_capability_tags(struct Client *source_p, int type, int caps, int negcaps, 904 + struct Channel *chptr, size_t n_tags, const struct MsgTag tags[], const char *pattern, ...) 905 + { 906 + va_list args; 907 + 908 + va_start(args, pattern); 909 + sendto_channel_local_internal(NULL, type, source_p, chptr, caps, negcaps, NULL, pattern, &args, n_tags, tags); 910 + va_end(args); 911 + } 896 912 897 913 /* sendto_channel_local_with_capability_butone() 898 914 * ··· 947 963 va_end(args); 948 964 } 949 965 950 - /* 951 - * sendto_common_channels_local() 966 + /* sendto_common_channels_local_internal() 952 967 * 953 - * inputs - pointer to client 954 - * - capability mask 955 - * - negated capability mask 956 - * - pattern to send 957 - * output - NONE 958 - * side effects - Sends a message to all people on local server who are 959 - * in same channel with user. 960 - * used by m_nick.c and exit_one_client. 968 + * inputs - client to exclude, user to check, caps, message, tags 969 + * outputs - NONE 970 + * side effects - Sends a message to all people on local server who are in the same channel with user 961 971 */ 962 - void 963 - sendto_common_channels_local(struct Client *user, int cap, int negcap, const char *pattern, ...) 972 + static void 973 + sendto_common_channels_local_internal(struct Client *one, struct Client *user, int caps, int negcaps, 974 + const char *pattern, va_list *args, size_t n_tags, const struct MsgTag tags[]) 964 975 { 965 - va_list args; 966 976 rb_dlink_node *ptr; 967 977 rb_dlink_node *next_ptr; 968 978 rb_dlink_node *uptr; ··· 974 984 struct MsgBuf msgbuf; 975 985 struct MsgBuf_cache msgbuf_cache; 976 986 char buf[DATALEN + 1]; 977 - rb_strf_t strings = { .format = pattern, .format_args = &args, .next = NULL }; 978 - 979 - va_start(args, pattern); 987 + rb_strf_t strings = { .format = pattern, .format_args = args, .next = NULL }; 980 988 rb_fsnprint(buf, sizeof(buf), &strings); 981 - va_end(args); 982 989 983 - build_msgbuf(&msgbuf, user, buf, 0, NULL); 990 + build_msgbuf(&msgbuf, user, buf, n_tags, tags); 984 991 /* source is already provided as part of pattern; don't overwrite it with anything else */ 985 992 msgbuf_cache_init(&msgbuf_cache, &msgbuf, NULL, NULL); 986 993 987 994 ++current_serial; 995 + 996 + /* skip the excluded user, if specified */ 997 + if (one != NULL) 998 + one->serial = current_serial; 988 999 989 1000 RB_DLINK_FOREACH_SAFE(ptr, next_ptr, user->user->channel.head) 990 1001 { ··· 998 1009 999 1010 if(IsIOError(target_p) || 1000 1011 target_p->serial == current_serial || 1001 - !IsCapable(target_p, cap) || 1002 - !NotCapable(target_p, negcap)) 1012 + !IsCapable(target_p, caps) || 1013 + !NotCapable(target_p, negcaps)) 1003 1014 continue; 1004 1015 1005 1016 target_p->serial = current_serial; ··· 1010 1021 /* this can happen when the user isn't in any channels, but we still 1011 1022 * need to send them the data, ie a nick change 1012 1023 */ 1013 - if (MyConnect(user) && (user->serial != current_serial) 1014 - && IsCapable(user, cap) && NotCapable(user, negcap)) { 1024 + if (MyConnect(user) && (user->serial != current_serial) && IsCapable(user, caps) && NotCapable(user, negcaps)) 1025 + { 1015 1026 send_linebuf(user, msgbuf_cache_get(&msgbuf_cache, CLIENT_CAP_MASK(user), false)); 1016 1027 } 1017 1028 1018 1029 msgbuf_cache_free(&msgbuf_cache); 1030 + } 1031 + 1032 + /* 1033 + * sendto_common_channels_local() 1034 + * 1035 + * inputs - pointer to client 1036 + * - capability mask 1037 + * - negated capability mask 1038 + * - pattern to send 1039 + * output - NONE 1040 + * side effects - Sends a message to all people on local server who are 1041 + * in same channel with user. 1042 + * used by m_nick.c and exit_one_client. 1043 + */ 1044 + void 1045 + sendto_common_channels_local(struct Client *user, int caps, int negcaps, const char *pattern, ...) 1046 + { 1047 + va_list args; 1048 + 1049 + va_start(args, pattern); 1050 + sendto_common_channels_local_internal(NULL, user, caps, negcaps, pattern, &args, 0, NULL); 1051 + va_end(args); 1052 + } 1053 + 1054 + /* 1055 + * sendto_common_channels_local_tags() 1056 + * 1057 + * inputs - pointer to client 1058 + * - capability mask 1059 + * - negated capability mask 1060 + * - message tags 1061 + * - pattern to send 1062 + * output - NONE 1063 + * side effects - Sends a message to all people on local server who are 1064 + * in same channel with user. 1065 + * used by m_nick.c and exit_one_client. 1066 + */ 1067 + void 1068 + sendto_common_channels_local_tags(struct Client *user, int caps, int negcaps, 1069 + size_t n_tags, const struct MsgTag tags[], const char *pattern, ...) 1070 + { 1071 + va_list args; 1072 + 1073 + va_start(args, pattern); 1074 + sendto_common_channels_local_internal(NULL, user, caps, negcaps, pattern, &args, n_tags, tags); 1075 + va_end(args); 1019 1076 } 1020 1077 1021 1078 /* ··· 1030 1087 * in same channel with user, except for user itself. 1031 1088 */ 1032 1089 void 1033 - sendto_common_channels_local_butone(struct Client *user, int cap, int negcap, const char *pattern, ...) 1090 + sendto_common_channels_local_butone(struct Client *user, int caps, int negcaps, const char *pattern, ...) 1034 1091 { 1035 1092 va_list args; 1036 - rb_dlink_node *ptr; 1037 - rb_dlink_node *next_ptr; 1038 - rb_dlink_node *uptr; 1039 - rb_dlink_node *next_uptr; 1040 - struct Channel *chptr; 1041 - struct Client *target_p; 1042 - struct membership *msptr; 1043 - struct membership *mscptr; 1044 - struct MsgBuf msgbuf; 1045 - struct MsgBuf_cache msgbuf_cache; 1046 - char buf[DATALEN + 1]; 1047 - rb_strf_t strings = { .format = pattern, .format_args = &args, .next = NULL }; 1048 1093 1049 1094 va_start(args, pattern); 1050 - rb_fsnprint(buf, sizeof(buf), &strings); 1095 + sendto_common_channels_local_internal(user, user, caps, negcaps, pattern, &args, 0, NULL); 1051 1096 va_end(args); 1097 + } 1052 1098 1053 - build_msgbuf(&msgbuf, user, buf, 0, NULL); 1054 - /* source is already provided as part of pattern; don't overwrite it with anything else */ 1055 - msgbuf_cache_init(&msgbuf_cache, &msgbuf, NULL, NULL); 1099 + /* 1100 + * sendto_common_channels_local_butone_tags() 1101 + * 1102 + * inputs - pointer to client 1103 + * - capability mask 1104 + * - negated capability mask 1105 + * - message tags 1106 + * - pattern to send 1107 + * output - NONE 1108 + * side effects - Sends a message to all people on local server who are 1109 + * in same channel with user, except for user itself. 1110 + */ 1111 + void 1112 + sendto_common_channels_local_butone_tags(struct Client *user, int caps, int negcaps, 1113 + size_t n_tags, const struct MsgTag tags[], const char *pattern, ...) 1114 + { 1115 + va_list args; 1056 1116 1057 - ++current_serial; 1058 - /* Skip them -- jilles */ 1059 - user->serial = current_serial; 1060 - 1061 - RB_DLINK_FOREACH_SAFE(ptr, next_ptr, user->user->channel.head) 1062 - { 1063 - mscptr = ptr->data; 1064 - chptr = mscptr->chptr; 1065 - 1066 - RB_DLINK_FOREACH_SAFE(uptr, next_uptr, chptr->locmembers.head) 1067 - { 1068 - msptr = uptr->data; 1069 - target_p = msptr->client_p; 1070 - 1071 - if(IsIOError(target_p) || 1072 - target_p->serial == current_serial || 1073 - !IsCapable(target_p, cap) || 1074 - !NotCapable(target_p, negcap)) 1075 - continue; 1076 - 1077 - target_p->serial = current_serial; 1078 - send_linebuf(target_p, msgbuf_cache_get(&msgbuf_cache, CLIENT_CAP_MASK(target_p), false)); 1079 - } 1080 - } 1081 - 1082 - msgbuf_cache_free(&msgbuf_cache); 1117 + va_start(args, pattern); 1118 + sendto_common_channels_local_internal(user, user, caps, negcaps, pattern, &args, n_tags, tags); 1119 + va_end(args); 1083 1120 } 1084 1121 1085 1122 /* sendto_match_internal() ··· 1182 1219 1183 1220 /* sendto_match_servs() 1184 1221 * 1185 - * inputs - source, mask to send to, caps needed, va_args 1222 + * inputs - source, mask to send to, caps needed, va_args, tags 1186 1223 * outputs - 1187 1224 * side effects - message is sent to matching servers with caps. 1188 1225 */ 1189 - void 1190 - sendto_match_servs(struct Client *source_p, const char *mask, int cap, 1191 - int nocap, const char *pattern, ...) 1226 + static void 1227 + sendto_match_servs_internal(struct Client *source_p, const char *mask, int cap, int negcap, 1228 + const char *pattern, va_list *args, size_t n_tags, const struct MsgTag tags[]) 1192 1229 { 1193 - va_list args; 1194 1230 rb_dlink_node *ptr; 1195 1231 struct Client *target_p; 1196 1232 struct MsgBuf msgbuf; 1197 1233 struct MsgBuf_cache msgbuf_cache; 1198 1234 char buf[DATALEN + 1]; 1199 - rb_strf_t strings = { .format = pattern, .format_args = &args, .next = NULL }; 1235 + rb_strf_t strings = { .format = pattern, .format_args = args, .next = NULL }; 1200 1236 1201 1237 if (EmptyString(mask)) 1202 1238 return; 1203 1239 1204 - va_start(args, pattern); 1205 1240 int used = snprintf(buf, sizeof(buf), ":%s ", use_id(source_p)); 1206 1241 rb_fsnprint(buf + used, sizeof(buf) - used, &strings); 1207 - va_end(args); 1208 1242 1209 - build_msgbuf(&msgbuf, source_p, buf, 0, NULL); 1243 + build_msgbuf(&msgbuf, source_p, buf, n_tags, tags); 1210 1244 msgbuf_cache_init(&msgbuf_cache, &msgbuf, NULL, NULL); 1211 1245 1212 1246 current_serial++; ··· 1232 1266 if (cap && !IsCapable(target_p->from, cap)) 1233 1267 continue; 1234 1268 1235 - if (nocap && !NotCapable(target_p->from, nocap)) 1269 + if (negcap && !NotCapable(target_p->from, negcap)) 1236 1270 continue; 1237 1271 1238 1272 send_linebuf(target_p->from, msgbuf_cache_get(&msgbuf_cache, CLIENT_CAP_MASK(target_p), true)); ··· 1240 1274 } 1241 1275 1242 1276 msgbuf_cache_free(&msgbuf_cache); 1277 + } 1278 + 1279 + /* sendto_match_servs() 1280 + * 1281 + * inputs - source, mask to send to, caps needed, va_args 1282 + * outputs - 1283 + * side effects - message is sent to matching servers with caps. 1284 + */ 1285 + void 1286 + sendto_match_servs(struct Client *source_p, const char *mask, int cap, int negcap, const char *pattern, ...) 1287 + { 1288 + va_list args; 1289 + va_start(args, pattern); 1290 + sendto_match_servs_internal(source_p, mask, cap, negcap, pattern, &args, 0, NULL); 1291 + va_end(args); 1292 + } 1293 + 1294 + /* sendto_match_servs_tags() 1295 + * 1296 + * inputs - source, mask to send to, caps needed, tags, va_args 1297 + * outputs - 1298 + * side effects - message is sent to matching servers with caps. 1299 + */ 1300 + void 1301 + sendto_match_servs_tags(struct Client *source_p, const char *mask, int cap, int negcap, 1302 + size_t n_tags, const struct MsgTag tags[], const char *pattern, ...) 1303 + { 1304 + va_list args; 1305 + va_start(args, pattern); 1306 + sendto_match_servs_internal(source_p, mask, cap, negcap, pattern, &args, n_tags, tags); 1307 + va_end(args); 1243 1308 } 1244 1309 1245 1310 /* sendto_local_clients_with_capability()
+1
modules/Makefile.am
··· 18 18 m_admin.la \ 19 19 m_alias.la \ 20 20 m_away.la \ 21 + m_batch.la \ 21 22 m_cap.la \ 22 23 m_capab.la \ 23 24 m_certfp.la \
+388
modules/m_batch.c
··· 1 + /* 2 + * Solanum: a slightly advanced ircd 3 + * m_batch.c: provides support for client-initiated and propagated BATCH commands 4 + * 5 + * Copyright (c) 2025 Ryan Schmidt <skizzerz@skizzerz.net> 6 + * 7 + * This program is free software; you can redistribute it and/or modify 8 + * it under the terms of the GNU General Public License as published by 9 + * the Free Software Foundation; either version 2 of the License, or 10 + * (at your option) any later version. 11 + * 12 + * This program is distributed in the hope that it will be useful, 13 + * but WITHOUT ANY WARRANTY; without even the implied warranty of 14 + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 + * GNU General Public License for more details. 16 + * 17 + * You should have received a copy of the GNU General Public License 18 + * along with this program; if not, write to the Free Software 19 + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 20 + */ 21 + 22 + #include "stdinc.h" 23 + #include "client.h" 24 + #include "hash.h" 25 + #include "ircd.h" 26 + #include "numeric.h" 27 + #include "s_conf.h" 28 + #include "s_serv.h" 29 + #include "send.h" 30 + #include "msg.h" 31 + #include "parse.h" 32 + #include "hook.h" 33 + #include "modules.h" 34 + #include "batch.h" 35 + 36 + static const char batch_desc[] = 37 + "Provides the BATCH command for client-initiated or propagated batches"; 38 + 39 + static struct ev_entry *timeout_ev; 40 + 41 + static int batch_modinit(void); 42 + static void batch_moddeinit(void); 43 + static void process_batch_tag(void *); 44 + static void handle_batch_line(void *); 45 + static void handle_client_exit(void *); 46 + static void m_queue(struct MsgBuf *msgbuf, struct Client *client_p, struct Client *source_p, int parc, const char *parv[]); 47 + static void m_batch(struct MsgBuf *msgbuf, struct Client *client_p, struct Client *source_p, int parc, const char *parv[]); 48 + 49 + static struct Message batch_msgtab = { 50 + "BATCH", 0, 0, 0, 0, 51 + {mg_unreg, {m_batch, 2}, {m_batch, 2}, {m_batch, 2}, mg_ignore, {m_batch, 2}} 52 + }; 53 + 54 + mapi_clist_av1 batch_clist[] = { &batch_msgtab, NULL }; 55 + 56 + mapi_hfn_list_av1 batch_hfnlist[] = { 57 + { "message_handler", handle_batch_line }, 58 + { "message_tag", process_batch_tag }, 59 + { "client_exit", handle_client_exit }, 60 + { NULL, NULL } 61 + }; 62 + 63 + DECLARE_MODULE_AV2(batch, batch_modinit, batch_moddeinit, batch_clist, NULL, batch_hfnlist, NULL, NULL, batch_desc); 64 + 65 + static void 66 + batch_timeout(void *arg) 67 + { 68 + /* non-NULL arg indicates this is being called during module unload so we need to "time out" all batches */ 69 + rb_dlink_node *cptr, *ptr, *next_ptr; 70 + struct Client *client; 71 + struct Batch *batch; 72 + time_t now = rb_current_time(); 73 + 74 + RB_DLINK_FOREACH(cptr, lclient_list.head) 75 + { 76 + client = cptr->data; 77 + RB_DLINK_FOREACH_SAFE(ptr, next_ptr, client->localClient->pending_batches.head) 78 + { 79 + batch = ptr->data; 80 + if (arg != NULL || batch->expires <= now) 81 + { 82 + sendto_one(client, ":%s FAIL BATCH TIMEOUT %s :Batch timed out", 83 + me.name, batch->tag); 84 + client->localClient->pending_batch_lines -= batch->len; 85 + batch_free(batch); 86 + rb_dlinkDestroy(ptr, &client->localClient->pending_batches); 87 + } 88 + } 89 + } 90 + 91 + if (arg != NULL) 92 + { 93 + /* get rid of server batches only on module unload */ 94 + RB_DLINK_FOREACH(cptr, serv_list.head) 95 + { 96 + client = cptr->data; 97 + RB_DLINK_FOREACH_SAFE(ptr, next_ptr, client->localClient->pending_batches.head) 98 + { 99 + batch_free(ptr->data); 100 + rb_dlinkDestroy(ptr, &client->localClient->pending_batches); 101 + } 102 + 103 + client->localClient->pending_batch_lines = 0; 104 + } 105 + } 106 + } 107 + 108 + static void 109 + handle_client_exit(void *data_) 110 + { 111 + hook_data_client_exit *data = data_; 112 + rb_dlink_node *ptr, *next_ptr; 113 + 114 + if (!MyConnect(data->target)) 115 + return; 116 + 117 + RB_DLINK_FOREACH_SAFE(ptr, next_ptr, data->target->localClient->pending_batches.head) 118 + { 119 + batch_free(ptr->data); 120 + rb_dlinkDestroy(ptr, &data->target->localClient->pending_batches); 121 + } 122 + 123 + /* probably unnecessary but makes me feel better */ 124 + data->target->localClient->pending_batch_lines = 0; 125 + } 126 + 127 + static void 128 + finish_batch(struct Client *client_p, struct Client *source_p, struct Batch *batch) 129 + { 130 + const struct BatchHandler *handler = get_batch_handler(batch->type); 131 + rb_dlink_node *ptr, *next_ptr; 132 + 133 + /* abort unfinished nested batches under this one */ 134 + RB_DLINK_FOREACH_SAFE(ptr, next_ptr, client_p->localClient->pending_batches.head) 135 + { 136 + struct Batch *other = ptr->data; 137 + if (other->parent == batch) 138 + { 139 + if (IsClient(source_p)) 140 + sendto_one(source_p, ":%s FAIL BATCH INCOMPLETE %s :Nested batch not finished before enclosing batch", 141 + me.name, other->tag); 142 + 143 + client_p->localClient->pending_batch_lines -= other->len; 144 + batch_free(other); 145 + rb_dlinkDestroy(ptr, &client_p->localClient->pending_batches); 146 + } 147 + } 148 + 149 + client_p->localClient->pending_batch_lines -= batch->len; 150 + 151 + if (handler == NULL) 152 + { 153 + /* getting here means that we originally accepted this batch type but no longer recognize it; 154 + * e.g. the supporting module got unloaded between batch start and finish. 155 + * There isn't a good solution here so just reject it and let the client send any fallbacks separately. */ 156 + if (IsClient(source_p)) 157 + sendto_one(source_p, ":%s FAIL BATCH UNKNOWN_TYPE %s %s :Unrecognized batch type", 158 + me.name, batch->tag, batch->type); 159 + 160 + batch_free(batch); 161 + return; 162 + } 163 + 164 + bool skip_children = (handler->flags & BATCH_FLAG_SKIP_CHILDREN) == BATCH_FLAG_SKIP_CHILDREN; 165 + 166 + /* don't trigger handlers for empty batches; treat as no-ops instead */ 167 + if (batch->len > 1 || (skip_children && batch->children.length > 0)) 168 + { 169 + handler->handler(client_p, source_p, batch, handler->userdata); 170 + } 171 + 172 + /* handle child batches */ 173 + if (!skip_children) 174 + { 175 + RB_DLINK_FOREACH_SAFE(ptr, next_ptr, batch->children.head) 176 + { 177 + struct Batch *child = ptr->data; 178 + rb_dlinkDestroy(ptr, &batch->children); 179 + finish_batch(client_p, source_p, child); 180 + } 181 + } 182 + 183 + batch_free(batch); 184 + } 185 + 186 + static void 187 + handle_batch_line(void *data_) 188 + { 189 + hook_data *data = data_; 190 + struct MsgBuf *msgbuf = data->arg1; 191 + struct MessageEntry *ehandler = data->arg2; 192 + 193 + if (msgbuf_get_tag(msgbuf, "batch") != NULL && strcasecmp(msgbuf->cmd, "BATCH") != 0) 194 + ehandler->handler = m_queue; 195 + } 196 + 197 + static void 198 + m_queue(struct MsgBuf *msgbuf, struct Client *client_p, struct Client *source_p, int parc, const char *parv[]) 199 + { 200 + rb_dlink_node *ptr; 201 + const char *tag = msgbuf_get_tag(msgbuf, "batch"); 202 + 203 + if (EmptyString(tag)) 204 + return; 205 + 206 + RB_DLINK_FOREACH(ptr, client_p->localClient->pending_batches.head) 207 + { 208 + struct Batch *batch = ptr->data; 209 + if (!strcmp(batch->tag, tag)) 210 + { 211 + batch_add_msgbuf(batch, msgbuf); 212 + client_p->localClient->pending_batch_lines++; 213 + return; 214 + } 215 + } 216 + } 217 + 218 + static void 219 + m_batch(struct MsgBuf *msgbuf, struct Client *client_p, struct Client *source_p, int parc, const char *parv[]) 220 + { 221 + struct Batch *batch = NULL, *parent = NULL; 222 + rb_dlink_node *ptr; 223 + bool found = false; 224 + bool adding = *parv[1] == '+'; 225 + const char *batch_tag = msgbuf_get_tag(msgbuf, "batch"); 226 + 227 + if (batch_tag != NULL) 228 + { 229 + bool found_parent = false; 230 + RB_DLINK_FOREACH(ptr, client_p->localClient->pending_batches.head) 231 + { 232 + parent = ptr->data; 233 + if (!strcmp(parent->tag, batch_tag)) 234 + { 235 + found_parent = true; 236 + break; 237 + } 238 + } 239 + 240 + if (!found_parent) 241 + return; 242 + } 243 + 244 + if (EmptyString(parv[1]) || parc < (adding ? 3 : 2)) 245 + { 246 + if (IsClient(source_p)) 247 + sendto_one(source_p, form_str(ERR_NEEDMOREPARAMS), me.name, source_p->name, "BATCH"); 248 + return; 249 + } 250 + 251 + if (adding && EmptyString(parv[2])) 252 + { 253 + if (IsClient(source_p)) 254 + sendto_one(source_p, form_str(ERR_NEEDMOREPARAMS), me.name, source_p->name, "BATCH"); 255 + return; 256 + } 257 + 258 + if ((!adding && *parv[1] != '-') || EmptyString(parv[1] + 1)) 259 + { 260 + if (IsClient(source_p)) 261 + sendto_one(source_p, ":%s FAIL BATCH INVALID_REFTAG %s :Invalid reference tag", 262 + me.name, parv[1]); 263 + return; 264 + } 265 + 266 + RB_DLINK_FOREACH(ptr, client_p->localClient->pending_batches.head) 267 + { 268 + batch = ptr->data; 269 + if (!strcmp(batch->tag, parv[1] + 1)) 270 + { 271 + found = true; 272 + break; 273 + } 274 + } 275 + 276 + if (adding) 277 + { 278 + if (found) 279 + { 280 + if (IsClient(source_p)) 281 + sendto_one(source_p, ":%s FAIL BATCH INVALID_REFTAG %s :Reference tag already exists", 282 + me.name, parv[1]); 283 + return; 284 + } 285 + 286 + if (get_batch_handler(parv[2]) == NULL) 287 + { 288 + if (IsClient(source_p)) 289 + sendto_one(source_p, ":%s FAIL BATCH UNKNOWN_TYPE %s %s :Unrecognized batch type", 290 + me.name, parv[1], parv[2]); 291 + return; 292 + } 293 + 294 + if (parent != NULL) 295 + { 296 + const struct BatchHandler *parent_handler = get_batch_handler(parent->type); 297 + if (parent_handler == NULL) 298 + { 299 + /* parent batch type got unregistered while we were in the middle of receiving this batch... */ 300 + if (IsClient(source_p)) 301 + sendto_one(source_p, ":%s FAIL BATCH UNKNOWN_TYPE %s %s :Unrecognized batch type", 302 + me.name, parent->tag, parent->type); 303 + return; 304 + } 305 + 306 + bool allowed = (parent_handler->flags & BATCH_FLAG_ALLOW_ALL) == BATCH_FLAG_ALLOW_ALL; 307 + if (!allowed && parent_handler->allowed_children != NULL) 308 + { 309 + for (int i = 0; parent_handler->allowed_children[i] != NULL; ++i) 310 + { 311 + if (!strcmp(parv[2], parent_handler->allowed_children[i])) 312 + { 313 + allowed = true; 314 + break; 315 + } 316 + } 317 + } 318 + 319 + if (!allowed) 320 + { 321 + if (IsClient(source_p)) 322 + sendto_one(source_p, ":%s FAIL BATCH INVALID_NESTING %s %s %s :The parent batch type does not allow this type to be nested under it", 323 + me.name, parv[1], parent->type, parv[2]); 324 + return; 325 + } 326 + } 327 + 328 + batch = batch_init(msgbuf); 329 + batch->parent = parent; 330 + rb_dlinkAddAlloc(batch, &client_p->localClient->pending_batches); 331 + client_p->localClient->pending_batch_lines++; 332 + } 333 + else 334 + { 335 + if (!found) 336 + { 337 + if (IsClient(source_p)) 338 + sendto_one(source_p, ":%s FAIL BATCH INVALID_REFTAG %s :Invalid reference tag", 339 + me.name, parv[1]); 340 + return; 341 + } 342 + 343 + rb_dlinkDestroy(ptr, &client_p->localClient->pending_batches); 344 + 345 + if (batch->parent != NULL) 346 + { 347 + /* Finished a nested batch but the outer batch isn't complete yet, don't process just yet 348 + * Instead, add this batch to the parent so it gets completed alongside the parent 349 + */ 350 + rb_dlinkAddTailAlloc(batch, &batch->parent->children); 351 + /* we deducted this earlier but the batch is still pending, so re-add the consumed lines */ 352 + client_p->localClient->pending_batch_lines += batch->len; 353 + } 354 + else 355 + { 356 + finish_batch(client_p, source_p, batch); 357 + } 358 + } 359 + } 360 + 361 + static void 362 + process_batch_tag(void *data_) 363 + { 364 + hook_data_message_tag *data = data_; 365 + 366 + if (!strcmp(data->key, "batch")) 367 + { 368 + if (IsServer(data->client) || !EmptyString(data->value)) 369 + { 370 + data->approved = MESSAGE_TAG_ALLOW; 371 + data->capmask = CLICAP_BATCH; 372 + } 373 + } 374 + } 375 + 376 + static int 377 + batch_modinit(void) 378 + { 379 + timeout_ev = rb_event_addish("batch-timeout", &batch_timeout, NULL, 30); 380 + return 1; 381 + } 382 + 383 + static void 384 + batch_moddeinit(void) 385 + { 386 + batch_timeout((void *)1); 387 + rb_event_delete(timeout_ev); 388 + }
+229
doc/features/batch.txt
··· 1 + BATCH support documentation 2 + ======================================== 3 + 4 + Portions of BATCH are supported in solanum core, whereas other portions are 5 + implemented via the m_batch module. The portions in core are sufficient for 6 + server-initiated batches sent to locally-connected clients. The module 7 + implements client-initiated batches as well as batches that are propagated 8 + between servers. 9 + 10 + Contents: 11 + 1. Specification 12 + 2. Core support 13 + 3. Server-initiated batches 14 + 4. Client-initiated batches 15 + 5. Propagated batches 16 + 6. Other notes 17 + 18 + ======================================== 19 + 1. Specification 20 + ======================================== 21 + 22 + Batches are specified as part of the IRCv3 effort. At the time of this 23 + writing, client-initiated batches are still in draft status. 24 + 25 + - Core spec: https://ircv3.net/specs/extensions/batch 26 + - Client-initiated batches: https://ircv3.net/specs/extensions/client-batch 27 + - Netsplit/netjoin batch types: https://ircv3.net/specs/batches/netsplit 28 + 29 + Individual batch types have their own specifications. 30 + 31 + ======================================== 32 + 2. Core support 33 + ======================================== 34 + 35 + To support server-initiated batches, the "batch" client capability is defined 36 + directly in core via CLICAP_BATCH. If a client has this capability, they are 37 + able to receive arbitrary batch types. Portions of core use this to implement 38 + the netsplit (in ircd/client.c) and netjoin (in modules/core/m_join.c) batch 39 + types. 40 + 41 + The netjoin batch type additionally depends on support added to ircd/channel.c 42 + to send a JOIN message to a client with an included batch tag. This ensures 43 + that only JOINs that are actually part of a netjoin are tagged with the batch, 44 + and other normal JOIN messages lack the tag. The netjoin batch is triggered by 45 + receiving an SJOIN command from a remote server that has not yet sent an EOB. 46 + 47 + Some support for client-initiated and propagated batches are also in core. 48 + The LocalUser struct in includes/client.h received two additional fields to 49 + support queueing messages for future processing. A new hook named 50 + "message_handler" was added as well to override which handler is used when 51 + parsing a command, to be used for the queueing support as well. Finally, 52 + functions for registering and unregistering recognized client-initiated and 53 + propagated batch types along with their handler is present in core. 54 + 55 + The two additional fields of LocalUser are `unsigned int pending_batch_lines` 56 + and `rb_dlink_list pending_batches`. pending_batch_lines keeps track of the 57 + number of messages currently queued for future processing. These messages are 58 + counted against the client's receive queue, so a client with too many combined 59 + lines between queued messages from incomplete batches and lines that simply 60 + haven't been parsed yet (perhaps due to fakelag) will be disconnected with 61 + "Excess Flood". In this way, we cap the overall memory that the ircd will be 62 + allocating for a client and ensure that batches cannot be used as a way to 63 + cheat flood protection controls. pending_batches keeps track of each batch 64 + that the client initiated but has not yet completed. The struct used for the 65 + list nodes is defined in includes/batch.h (explained in a couple paragraphs). 66 + 67 + The message_handler hook is called each time a command is parsed, before it is 68 + dispatched to the module that handles that command. The hook can be used to 69 + override the handler with a different one, allowing a different module to 70 + handle the command instead. This is used by m_batch for client-initiated 71 + batches and that usage is explained more in that section. 72 + 73 + Finally, a new header includes/batch.h contains helpful methods for both 74 + server-initiated and client-initiated/propagated batches. For the former, a 75 + method generates a randomized 15-character batch ID into a buffer for use with 76 + BATCH commands and the batch message tag on outgoing messages. For the latter, 77 + two functions are provided for modules to register/unregister recognized batch 78 + types along with the handlers that should be called when a batch of that type 79 + is received from a client or remote server. Attempting to register a type that 80 + has already been registered is an error. The header finally defines a struct 81 + that contains relevant metadata about an incoming batch as well as functions 82 + to allocate and free that structure and attach messages to an incoming batch. 83 + 84 + ======================================== 85 + 3. Server-initiated batches 86 + ======================================== 87 + 88 + Beyond CLICAP_BATCH, no special support exists in the server for batches 89 + initiated by the server to be sent to local clients. It is expected that users 90 + can generate their own batch reference tags (whether randomly or using some 91 + sort of static or data-dependent value) and make use of appropriate existing 92 + sendto_* functions to send the BATCH commands to relevant clients as well as 93 + the messages within the batch. The vast majority of sendto_* functions support 94 + variants that take message tags with their parameters so a module can 95 + explicitly add the batch tag to the outgoing message without needing to rely 96 + on the "outbound_msgbuf" hook. 97 + 98 + Core support exists ONLY for server-initiated batches being sent to local 99 + clients; the m_batch module is required if BATCH commands need to be sent 100 + between servers. 101 + 102 + ======================================== 103 + 4. Client-initiated batches 104 + ======================================== 105 + 106 + This section concerns batches that are initiated by locally-connected clients. 107 + Batches made by remote clients are described in the next section. 108 + 109 + While not mandated by the current draft specification for client-initiated 110 + batches, the m_batch module supports having multiple interleaved batches in 111 + flight by a single client, nested batches, and the ability for clients to send 112 + messages not associated with a batch while a batch is in flight. Messages sent 113 + by a client with a valid batch tag are queued into that respective batch, and 114 + will not be processed by the server until the client closes that batch. 115 + 116 + When the client initiates a new batch, m_batch checks if any module has 117 + registered a handler for that batch type. If there is no registered handler, 118 + the BATCH command is rejected. Any messages the client sends tagged with that 119 + batch and the closing BATCH command (if any is sent) are rejected as well. If 120 + the incoming batch has a registered handler, a deep copy of the BATCH command 121 + is made and attached to the client's pending_batches along with other metadata 122 + needed to track the batch (such as when the batch expires). The client's 123 + pending_batch_lines are incremented by 1 due to this new allocation. Deep 124 + copies (that is, copies that include copying all pointed-to values) are made 125 + because the BATCH command needs to be long-lived beyond the current message 126 + handler invocation and the pointer lifetimes of message parameters do not 127 + extend beyond the current handler invocation. 128 + 129 + When a message is received from a local client with a batch tag, and the tag 130 + references a valid batch, the "message_handler" hook function replaces the 131 + default handler for that command with a handler that appends a deep copy of 132 + that message to the relevant node in the client's pending_batches list as well 133 + as incrementing their pending_batch_lines by 1. If the tag does not reference 134 + a valid batch, the "message_handler" hook function replaces the default 135 + handler with m_ignore to discard the message. The specification requires this 136 + behavior when a batch has timed out, and the module does not keep track of 137 + expired batch reference tags, so any invalid reference tag receives this same 138 + treatment. 139 + 140 + When the closing BATCH message is received with a valid reference tag, the 141 + m_batch module once again looks up the handler for that batch type. We do not 142 + save the batch handler from the initial message due to the risk of the module 143 + handling that batch type being unloaded between the time the client begins a 144 + batch and ends that batch. In the event that the batch is no longer valid, the 145 + messages are dropped (as if they were originally received with a batch message 146 + tag containing an invalid reference). Otherwise, the handler is called. In 147 + either case, the batch is removed from the client's pending_batches list and 148 + the client's pending_batch_lines are reduced by the number of messages 149 + associated with the batch. 150 + 151 + No fake lag is applied to processing the messages inside of a batch when the 152 + client completes a batch, as fake lag was already applied to the client 153 + adding the messages to the batch and the client's receive queue was still 154 + counting those messages against the total. 155 + 156 + Client-initiated batches cannot remain open indefinitely. Approximately every 157 + 30 seconds, a timer executes which forcibly closes batches that have been open 158 + for longer than 15 seconds. This cleanup is non-essential as it is expected 159 + most clients will complete batches long before this timeout period. Because we 160 + need to iterate over every local client and then every batch pending by those 161 + clients, the timer does not run with high frequency. This means that clients 162 + have anywhere between 15-45 seconds (approximately) to finish a batch. If a 163 + batch is timed out, it is removed from the client's list of pending_batches, 164 + the number of lines in the batch are deducted from the client's 165 + pending_batch_lines, the messages in the batch are discarded without being 166 + processed, and the client is given a notification that the batch timed out. 167 + 168 + If a client is nesting batches and completes the outer batch before the nested 169 + inner batch, the inner batch will be discarded without being processed and the 170 + client will be given a notification that they did not properly close the inner 171 + batch. 172 + 173 + Empty client-initiated batches do not trigger any handlers and are treated as 174 + no-ops. No response is given to the client in such events (unless the client 175 + has negotiated labeled-response, in which case it is given an ACK). 176 + 177 + Exiting clients will cause all pending batches associated with that client to 178 + be silently aborted, with no output being sent to the client (since they're 179 + gone anyway). 180 + 181 + ======================================== 182 + 5. Propagated batches 183 + ======================================== 184 + 185 + The server protocol BATCH command is equivalent in syntax to the client 186 + protocol BATCH command. Like client batches, propagated batches are deferred 187 + for processing until the batch is complete. Once complete, the batch handler 188 + will be called on the full batch if the batch type is recognized. If the batch 189 + type is not recognized, it will send a FAIL message to the source if the source 190 + is a client, otherwise it will silently drop the message. 191 + 192 + Batch types should only be sent to other servers if the sending server is 193 + assured that the batch type is available, such as via a server capability sent 194 + via the CAPAB command for that batch type. The m_batch module does not define 195 + any propagated batch types or server capabilities. 196 + 197 + ======================================== 198 + 6. Other notes 199 + ======================================== 200 + 201 + If the m_batch module is unloaded, all pending client-initiated and propagated 202 + batches are aborted without their messages being processed. This will result 203 + in the pending_batches list becoming empty and pending_batch_lines being 0 for 204 + all locally-connected clients and servers. Local clients will be notified that 205 + their batches have timed out. 206 + 207 + Errors with client-initiated batches are sent to clients using the 208 + standard-replies framework specified in IRCv3. The codes used by m_batch are 209 + as follows: 210 + 211 + - FAIL BATCH TIMEOUT <reference-tag>: Indicates a client-initiated batch timed 212 + out or the m_batch module was unloaded. 213 + - FAIL BATCH INVALID_NESTING <reference-tag> <parent-type> <type>: Indicates 214 + that the specified batch type is not allowed to be nested under the parent's 215 + batch type (via the allowed_children field or BATCH_FLAG_ALLOW_ALL). 216 + - FAIL BATCH INVALID_REFTAG <reference-tag>: Indicates the batch contains an 217 + invalid reference tag (either creating a new batch with an already-existing 218 + tag, not beginning the tag with '+' or '-', the tag consists solely of "+" 219 + or "-", or closing a batch with a tag not associated with an open batch). 220 + - FAIL BATCH UNKNOWN_TYPE <reference-tag> <type>: Indicates the batch type is 221 + not recognized by a module on the ircd. 222 + - FAIL BATCH INCOMPLETE <reference-tag>: Indicates the client is using nested 223 + batches and closed an outer batch before closing the inner nested batch. 224 + 225 + Each batch is given a server-generated reference tag in addition to the tag 226 + specified by the client or remote server. When the batch is propagated, the 227 + locally-generated reference tag is used rather than the client-specified one. 228 + Doing this solves any moderation considerations regarding the contents of a 229 + client-specified batch reference tag being shared with other clients.
+36
help/opers/batch
··· 1 + BATCH +<reference-tag> <type> [params ...] 2 + BATCH -<reference-tag> 3 + 4 + BATCH is used to send a batch of commands to the server. 5 + Which types of batches are valid to send depends on which 6 + capabilities the client has requested via the CAP REQ 7 + command and have been confirmed via CAP ACK. Individual 8 + batch types have further restrictions on which commands 9 + are valid within that batch. 10 + 11 + A batch must be opened via the first syntax, and closed 12 + via the second syntax. Server-side processing of the batch 13 + is postponed until the batch is closed. Commands that are 14 + part of the batch must include the batch message-tag 15 + referencing the reference-tag value which began the batch. 16 + If the batch is kept open for 15 seconds, it will time out 17 + and be ignored. 18 + 19 + Errors with batches use the standard-replies framework via 20 + FAIL commands. The following error codes are used across 21 + all batch types, and individual batch types may have their 22 + own error codes. Each response additionally has a human- 23 + readable error message as the trailing parameter which 24 + varies to give a more detailed message of what is wrong. 25 + 26 + - FAIL BATCH TIMEOUT <reference-tag> 27 + The batch was kept open for too long and has timed out. 28 + - FAIL BATCH INVALID_NESTING <reference-tag> <parent-type> <type> 29 + The parent batch type does not allow batches of this type to be nested under it. 30 + - FAIL BATCH INVALID_REFTAG <reference-tag> 31 + The reference-tag supplied with the batch is not valid. 32 + - FAIL BATCH UNKNOWN_TYPE <reference-tag> <type> 33 + The type of batch specified is not valid. 34 + - FAIL BATCH INCOMPLETE <reference-tag> 35 + An outer batch was closed before a nested inner batch. 36 + The reference-tag refers to the inner batch.
+119 -2
modules/core/m_join.c
··· 41 41 #include "ratelimit.h" 42 42 #include "s_assert.h" 43 43 #include "hook.h" 44 + #include "batch.h" 44 45 45 46 static const char join_desc[] = "Provides the JOIN and TS6 SJOIN commands to facilitate joining and creating channels"; 46 47 47 48 static void m_join(struct MsgBuf *, struct Client *, struct Client *, int, const char **); 48 49 static void ms_join(struct MsgBuf *, struct Client *, struct Client *, int, const char **); 49 50 static void ms_sjoin(struct MsgBuf *, struct Client *, struct Client *, int, const char **); 51 + 52 + static void moddeinit(void); 53 + static void begin_netjoin_batch(void *); 54 + static void complete_netjoin_batch(void *); 55 + static void abort_netjoin_batch(void *); 50 56 51 57 static int h_can_create_channel; 52 58 static int h_channel_join; ··· 71 77 { NULL, NULL }, 72 78 }; 73 79 74 - DECLARE_MODULE_AV2(join, NULL, NULL, join_clist, join_hlist, NULL, NULL, NULL, join_desc); 80 + mapi_hfn_list_av1 join_hfn_list[] = { 81 + { "server_introduced", begin_netjoin_batch }, 82 + { "server_eob", complete_netjoin_batch }, 83 + { "client_exit", abort_netjoin_batch }, 84 + { NULL, NULL }, 85 + }; 86 + 87 + DECLARE_MODULE_AV2(join, NULL, moddeinit, join_clist, join_hlist, join_hfn_list, NULL, NULL, join_desc); 75 88 76 89 static void do_join_0(struct Client *client_p, struct Client *source_p); 77 90 static bool check_channel_name_loc(struct Client *source_p, const char *name); ··· 82 95 83 96 static void remove_ban_list(struct Channel *chptr, struct Client *source_p, 84 97 rb_dlink_list * list, char c, int mems); 98 + 99 + static rb_dlink_list netjoin_batches = {0}; 100 + 101 + struct NetjoinBatch 102 + { 103 + char id[BATCH_ID_LEN]; /* Batch reference ID */ 104 + struct Client *target; /* The server being introduced */ 105 + }; 85 106 86 107 /* Check what we will forward to, without sending any notices to the user 87 108 * -- jilles ··· 543 564 char *mbuf; 544 565 int pargs; 545 566 const char *para[MAXMODEPARAMS]; 567 + struct NetjoinBatch *batch = NULL; 568 + const char *batch_id = NULL; 546 569 547 570 if(parc < 5) 548 571 return; ··· 565 588 566 589 mbuf = modebuf; 567 590 newts = atol(parv[1]); 591 + 592 + RB_DLINK_FOREACH(ptr, netjoin_batches.head) 593 + { 594 + batch = ptr->data; 595 + if (batch->target == source_p) 596 + { 597 + batch_id = batch->id; 598 + break; 599 + } 600 + } 568 601 569 602 s = parv[3]; 570 603 while (*s) ··· 843 876 if(!IsMember(target_p, chptr)) 844 877 { 845 878 add_user_to_channel(chptr, target_p, fl); 846 - send_channel_join(chptr, target_p); 879 + send_batched_channel_join(chptr, target_p, batch_id); 847 880 joins++; 848 881 } 849 882 ··· 1339 1372 1340 1373 list->head = list->tail = NULL; 1341 1374 list->length = 0; 1375 + } 1376 + 1377 + static void 1378 + begin_netjoin_batch(void *arg) 1379 + { 1380 + /* data->target is the server being introduced */ 1381 + hook_data_client *data = arg; 1382 + char comment[(HOSTLEN*2)+2]; 1383 + struct NetjoinBatch *batch = rb_malloc(sizeof(struct NetjoinBatch)); 1384 + 1385 + generate_batch_id(batch->id, sizeof(batch->id)); 1386 + batch->target = data->target; 1387 + rb_dlinkAddAlloc(batch, &netjoin_batches); 1388 + 1389 + if (ConfigServerHide.flatten_links) 1390 + strcpy(comment, "*.net *.join"); 1391 + else 1392 + { 1393 + strcpy(comment, data->target->servptr->name); 1394 + strcat(comment, " "); 1395 + strcat(comment, data->target->name); 1396 + } 1397 + 1398 + sendto_local_clients_with_capability(CLICAP_BATCH, ":%s BATCH +%s netjoin %s", me.name, batch->id, comment); 1399 + } 1400 + 1401 + static void 1402 + moddeinit(void) 1403 + { 1404 + rb_dlink_node *ptr, *next_ptr; 1405 + struct NetjoinBatch *batch = NULL; 1406 + 1407 + RB_DLINK_FOREACH_SAFE(ptr, next_ptr, netjoin_batches.head) 1408 + { 1409 + batch = ptr->data; 1410 + sendto_local_clients_with_capability(CLICAP_BATCH, ":%s BATCH -%s", me.name, batch->id); 1411 + rb_free(batch); 1412 + rb_dlinkDestroy(ptr, &netjoin_batches); 1413 + } 1414 + } 1415 + 1416 + static void 1417 + complete_netjoin_batch(void *arg) 1418 + { 1419 + /* source_p is the server that completed the burst */ 1420 + struct Client *source_p = arg; 1421 + rb_dlink_node *ptr, *next_ptr; 1422 + struct NetjoinBatch *batch = NULL; 1423 + 1424 + RB_DLINK_FOREACH_SAFE(ptr, next_ptr, netjoin_batches.head) 1425 + { 1426 + batch = ptr->data; 1427 + if (batch->target == source_p) 1428 + { 1429 + sendto_local_clients_with_capability(CLICAP_BATCH, ":%s BATCH -%s", me.name, batch->id); 1430 + rb_free(batch); 1431 + rb_dlinkDestroy(ptr, &netjoin_batches); 1432 + return; 1433 + } 1434 + } 1435 + } 1436 + 1437 + static void 1438 + abort_netjoin_batch(void *arg) 1439 + { 1440 + /* data->target is the client that is exiting */ 1441 + hook_data_client_exit *data = arg; 1442 + rb_dlink_node *ptr, *next_ptr; 1443 + struct NetjoinBatch *batch = NULL; 1444 + 1445 + if (!IsServer(data->target) || HasSentEob(data->target)) 1446 + return; 1447 + 1448 + RB_DLINK_FOREACH_SAFE(ptr, next_ptr, netjoin_batches.head) 1449 + { 1450 + batch = ptr->data; 1451 + if (batch->target == data->target) 1452 + { 1453 + sendto_local_clients_with_capability(CLICAP_BATCH, ":%s BATCH -%s", me.name, batch->id); 1454 + rb_free(batch); 1455 + rb_dlinkDestroy(ptr, &netjoin_batches); 1456 + return; 1457 + } 1458 + } 1342 1459 }