Something went wrong. Try again.
[READ-ONLY] Mirror of https://github.com/VibeDevelopers/Comet.
Something went wrong. Try again.
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425/* * Comet: a slightly advanced ircd * m_batch.c: provides support for client-initiated and propagated BATCH commands * * Copyright (c) 2025 Ryan Schmidt <skizzerz@skizzerz.net> * * 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 2 of the License, 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */
#include "stdinc.h"#include "client.h"#include "hash.h"#include "ircd.h"#include "numeric.h"#include "s_conf.h"#include "s_serv.h"#include "send.h"#include "msg.h"#include "parse.h"#include "hook.h"#include "modules.h"#include "batch.h"#include "response.h"
static const char batch_desc[] = "Provides the BATCH command for client-initiated or propagated batches";
static struct ev_entry *timeout_ev;
static int batch_modinit(void);static void batch_moddeinit(void);static void process_batch_tag(void *);static void handle_batch_line(void *);static void handle_client_exit(void *);static void m_queue(struct MsgBuf *msgbuf, struct Client *client_p, struct Client *source_p, int parc, const char *parv[]);static void m_batch(struct MsgBuf *msgbuf, struct Client *client_p, struct Client *source_p, int parc, const char *parv[]);
static struct Message batch_msgtab = { "BATCH", 0, 0, 0, 0, {mg_unreg, {m_batch, 2}, {m_batch, 2}, {m_batch, 2}, mg_ignore, {m_batch, 2}}};
mapi_clist_av1 batch_clist[] = { &batch_msgtab, NULL };
mapi_hfn_list_av1 batch_hfnlist[] = { { "message_handler", handle_batch_line }, { "message_tag", process_batch_tag }, { "client_exit", handle_client_exit }, { NULL, NULL }};
DECLARE_MODULE_AV2(batch, batch_modinit, batch_moddeinit, batch_clist, NULL, batch_hfnlist, NULL, NULL, batch_desc);
static struct{ const struct Client *incoming_client; const struct MsgBuf *incoming_message;} saved_global_context;
/* Restore global contextual pointers with the opening BATCH message */static voidrestore_global_context(struct Client *client_p, struct Batch *batch){ saved_global_context.incoming_client = incoming_client; saved_global_context.incoming_message = incoming_message; incoming_client = client_p; incoming_message = &batch->start->msg;}
static voidreset_global_context(void){ incoming_client = saved_global_context.incoming_client; incoming_message = saved_global_context.incoming_message;}
static voidbatch_timeout(void *arg){ /* non-NULL arg indicates this is being called during module reload so we need to "time out" all batches */ rb_dlink_node *cptr, *ptr, *next_ptr; struct Client *client; time_t now = rb_current_time();
RB_DLINK_FOREACH(cptr, lclient_list.head) { client = cptr->data; RB_DLINK_FOREACH_SAFE(ptr, next_ptr, client->localClient->pending_batches.head) { struct Batch* batch = ptr->data; if (arg != NULL || batch->expires <= now) { restore_global_context(client, batch); struct ResponseInfo *info = resume_response_batch(batch->response_info); sendto_one(client, ":%s FAIL BATCH TIMEOUT %s :Batch timed out", me.name, batch->tag); client->localClient->pending_batch_lines -= batch->len + 1; batch_free(batch, info); reset_global_context(); rb_dlinkDestroy(ptr, &client->localClient->pending_batches); } } }
if (arg != NULL) { /* get rid of server batches only on module reload */ RB_DLINK_FOREACH(cptr, serv_list.head) { client = cptr->data; RB_DLINK_FOREACH_SAFE(ptr, next_ptr, client->localClient->pending_batches.head) { batch_free(ptr->data, outgoing_response_info); rb_dlinkDestroy(ptr, &client->localClient->pending_batches); }
client->localClient->pending_batch_lines = 0; } }}
static voidhandle_client_exit(void *data_){ hook_data_client_exit *data = data_; rb_dlink_node *ptr, *next_ptr;
if (!MyConnect(data->target)) return;
RB_DLINK_FOREACH_SAFE(ptr, next_ptr, data->target->localClient->pending_batches.head) { batch_free(ptr->data, outgoing_response_info); rb_dlinkDestroy(ptr, &data->target->localClient->pending_batches); }
/* probably unnecessary but makes me feel better */ data->target->localClient->pending_batch_lines = 0;}
static voidfinish_batch(struct Client *client_p, struct Client *source_p, struct Batch *batch){ const struct BatchHandler *handler = get_batch_handler(batch->type); rb_dlink_node *ptr, *next_ptr; struct ResponseInfo *info;
/* abort unfinished nested batches under this one */ RB_DLINK_FOREACH_SAFE(ptr, next_ptr, client_p->localClient->pending_batches.head) { struct Batch *other = ptr->data; if (other->parent == batch) { restore_global_context(client_p, other); info = resume_response_batch(batch->response_info); if (IsClient(source_p)) sendto_one(source_p, ":%s FAIL BATCH INCOMPLETE %s :Nested batch not finished before enclosing batch", me.name, other->tag);
client_p->localClient->pending_batch_lines -= other->len + 1; batch_free(other, info); reset_global_context(); rb_dlinkDestroy(ptr, &client_p->localClient->pending_batches); } }
client_p->localClient->pending_batch_lines -= batch->len + 1; restore_global_context(client_p, batch); info = resume_response_batch(batch->response_info);
if (handler == NULL) { /* getting here means that we originally accepted this batch type but no longer recognize it; * e.g. the supporting module got unloaded between batch start and finish. * There isn't a good solution here so just reject it and let the client send any fallbacks separately. */ if (IsClient(source_p)) sendto_one(source_p, ":%s FAIL BATCH UNKNOWN_TYPE %s %s :Unrecognized batch type", me.name, batch->tag, batch->type);
batch_free(batch, info); reset_global_context(); return; }
handler->handler(client_p, source_p, batch, handler->userdata);
/* handle child batches */ if (!(handler->flags & BATCH_FLAG_SKIP_CHILDREN)) { RB_DLINK_FOREACH_SAFE(ptr, next_ptr, batch->children.head) { struct Batch *child = ptr->data; rb_dlinkDestroy(ptr, &batch->children); /* global context isn't a stack, so ensure we reset to base before recursing */ resume_response_batch(info); reset_global_context(); finish_batch(client_p, source_p, child); restore_global_context(client_p, batch); info = resume_response_batch(batch->response_info); } }
batch_free(batch, info); reset_global_context();}
static voidhandle_batch_line(void *data_){ hook_data *data = data_; struct MsgBuf *msgbuf = data->arg1; struct MessageEntry *ehandler = data->arg2;
if (msgbuf_get_tag(msgbuf, "batch") != NULL && strcasecmp(msgbuf->cmd, "BATCH") != 0) ehandler->handler = m_queue;}
static voidm_queue(struct MsgBuf *msgbuf, struct Client *client_p, struct Client *source_p, int parc, const char *parv[]){ rb_dlink_node *ptr; struct BatchMessage *msg; const char *tag = msgbuf_get_tag(msgbuf, "batch");
if (EmptyString(tag)) return;
RB_DLINK_FOREACH(ptr, client_p->localClient->pending_batches.head) { struct Batch *batch = ptr->data; if (!strcmp(batch->tag, tag)) { msg = allocate_batch_message(msgbuf); rb_dlinkAddTailAlloc(msg, &batch->messages); batch->len++; client_p->localClient->pending_batch_lines++; return; } }}
static voidm_batch(struct MsgBuf *msgbuf, struct Client *client_p, struct Client *source_p, int parc, const char *parv[]){ struct Batch *batch = NULL, *parent = NULL; rb_dlink_node *ptr; bool found = false; bool adding = *parv[1] == '+'; const char *batch_tag = msgbuf_get_tag(msgbuf, "batch");
if (batch_tag != NULL) { bool found_parent = false; RB_DLINK_FOREACH(ptr, client_p->localClient->pending_batches.head) { parent = ptr->data; if (!strcmp(parent->tag, batch_tag)) { found_parent = true; break; } }
if (!found_parent) return; }
if (EmptyString(parv[1]) || parc < (adding ? 3 : 2)) { if (IsClient(source_p)) sendto_one(source_p, form_str(ERR_NEEDMOREPARAMS), me.name, source_p->name, "BATCH"); return; }
if (adding && EmptyString(parv[2])) { if (IsClient(source_p)) sendto_one(source_p, form_str(ERR_NEEDMOREPARAMS), me.name, source_p->name, "BATCH"); return; }
if ((!adding && *parv[1] != '-') || EmptyString(parv[1] + 1)) { if (IsClient(source_p)) sendto_one(source_p, ":%s FAIL BATCH INVALID_REFTAG %s :Invalid reference tag", me.name, parv[1]); return; }
RB_DLINK_FOREACH(ptr, client_p->localClient->pending_batches.head) { batch = ptr->data; if (!strcmp(batch->tag, parv[1] + 1)) { found = true; break; } }
if (adding) { if (found) { if (IsClient(source_p)) sendto_one(source_p, ":%s FAIL BATCH INVALID_REFTAG %s :Reference tag already exists", me.name, parv[1]); return; }
if (get_batch_handler(parv[2]) == NULL) { if (IsClient(source_p)) sendto_one(source_p, ":%s FAIL BATCH UNKNOWN_TYPE %s %s :Unrecognized batch type", me.name, parv[1], parv[2]); return; }
if (parent != NULL) { const struct BatchHandler *parent_handler = get_batch_handler(parent->type); if (parent_handler == NULL) { /* parent batch type got unregistered while we were in the middle of receiving this batch... */ if (IsClient(source_p)) sendto_one(source_p, ":%s FAIL BATCH UNKNOWN_TYPE %s %s :Unrecognized batch type", me.name, parent->tag, parent->type); return; }
bool allowed = (parent_handler->flags & BATCH_FLAG_ALLOW_ALL) == BATCH_FLAG_ALLOW_ALL; const char *error = NULL; if (!allowed && parent_handler->child_allowed != NULL) allowed = parent_handler->child_allowed(client_p, source_p, parent, msgbuf, parent_handler->userdata, &error);
if (!allowed) { if (IsClient(source_p)) { if (error != NULL) sendto_one(source_p, ":%s FAIL BATCH %s", me.name, error); else 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", me.name, parv[1], parent->type, parv[2]); } return; } }
batch = batch_init(msgbuf); batch->parent = parent; rb_dlinkAddAlloc(batch, &client_p->localClient->pending_batches); client_p->localClient->pending_batch_lines++;
/* postpone any labeled-response to when the batch completes */ batch->response_info = suspend_response_batch(); } else { if (!found) { if (IsClient(source_p)) sendto_one(source_p, ":%s FAIL BATCH INVALID_REFTAG %s :Invalid reference tag", me.name, parv[1]); return; }
rb_dlinkDestroy(ptr, &client_p->localClient->pending_batches);
if (batch->parent != NULL) { /* Finished a nested batch but the outer batch isn't complete yet, don't process just yet * Instead, add this batch to the parent so it gets completed alongside the parent */ rb_dlinkAddTailAlloc(batch, &batch->parent->children); /* we deducted this earlier but the batch is still pending, so re-add the consumed lines */ client_p->localClient->pending_batch_lines += batch->len + 1; } else { finish_batch(client_p, source_p, batch); } }}
static voidprocess_batch_tag(void *data_){ hook_data_message_tag *data = data_;
if (!strcmp(data->key, "batch")) { if (IsServer(data->client) || !EmptyString(data->value)) { data->approved = MESSAGE_TAG_ALLOW; data->capmask = CLICAP_BATCH; } }}
static intbatch_modinit(void){ timeout_ev = rb_event_addish("batch-timeout", &batch_timeout, NULL, 30); return 1;}
static voidbatch_moddeinit(void){ batch_timeout((void *)1); rb_event_delete(timeout_ev);}