Something went wrong. Try again.
[READ-ONLY] Mirror of https://github.com/VibeDevelopers/Comet.
Something went wrong. Try again.
18 kB · 570 lines
C
at dev
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571/* * Comet: a slightly advanced ircd * m_quarantine.c: restrict connections until they identify to services * * Copyright (c) 2026 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 "channel.h"#include "chmode.h"#include "modules.h"#include "hook.h"#include "client.h"#include "ircd.h"#include "logger.h"#include "newconf.h"#include "numeric.h"#include "send.h"#include "s_newconf.h"#include "s_serv.h"#include "s_user.h"
#define IsOperQuarantine(x) (HasPrivilege((x), "oper:quarantine"))#define IsQuarantined(x) ((x)->umodes & user_modes['q'])#define DEFAULT_JOIN_REASON "Cannot join channel (usermode +q) - you need to be logged into your NickServ account"#define DEFAULT_MSG_REASON "Cannot send to nick/channel (usermode +q) - you need to be logged into your NickServ account"#define DEFAULT_OTHER_MSG_REASON "Cannot send to user - they are quarantined and will be unable to respond to you"#define DEFAULT_APPLY_MSG "You have been quarantined and must log into your NickServ account before you can join channels. Please see /STATS p for assistance."#define DEFAULT_REMOVE_MSG "You are no longer quarantined and can freely join channels."
static const char quarantine_desc[] = "QUARANTINE system and umode +q to restrict unidentified connections.";static char *join_reason = NULL;static char *msg_reason = NULL;static char *other_msg_reason = NULL;static char *apply_msg = NULL;static char *remove_msg = NULL;static int part_channels = 1;static char **allowed_channels = NULL;
static struct Client *allowed_umode_change = NULL;
static int modinit(void);static void moddeinit(void);static void me_quarantine(struct MsgBuf *, struct Client *, struct Client *, int, const char *[]);static void mo_quarantine(struct MsgBuf *, struct Client *, struct Client *, int, const char *[]);static void me_unquarantine(struct MsgBuf *, struct Client *, struct Client *, int, const char *[]);static void mo_unquarantine(struct MsgBuf *, struct Client *, struct Client *, int, const char *[]);static void add_quarantine(struct Client *source_p, struct Client *target_p, const char *reason);static void remove_quarantine(struct Client *source_p, struct Client *target_p);static void quarantine_account_change(void *);static void quarantine_change_umode(void *);static void quarantine_conf_info(void *);static void quarantine_default_umode_begin(void *);static void quarantine_default_umode_end(void *);static void quarantine_join_channel(void *);static void quarantine_privmsg_channel(void *);static void quarantine_privmsg_user(void *);static void quarantine_reset_conf(void *);static void quarantine_set_allow_channels(void *);
struct Message quarantine_msgtab = { "QUARANTINE", 0, 0, 0, 0, { mg_unreg, mg_not_oper, mg_not_oper, mg_ignore, { me_quarantine, 3 }, { mo_quarantine, 3 } }};
struct Message unquarantine_msgtab = { "UNQUARANTINE", 0, 0, 0, 0, { mg_unreg, mg_not_oper, mg_not_oper, mg_ignore, { me_unquarantine, 2 }, { mo_unquarantine, 2 } }};
struct ConfEntry conf_quarantine_table[] = { { "allow_channels", CF_QSTRING | CF_FLIST, quarantine_set_allow_channels, 0, NULL }, { "apply_msg", CF_QSTRING, NULL, BUFSIZE, &apply_msg }, { "join_reason", CF_QSTRING, NULL, BUFSIZE, &join_reason }, { "msg_reason", CF_QSTRING, NULL, BUFSIZE, &msg_reason }, { "other_msg_reason", CF_QSTRING, NULL, BUFSIZE, &other_msg_reason }, { "part_channels", CF_YESNO, NULL, 0, &part_channels }, { "remove_msg", CF_QSTRING, NULL, BUFSIZE, &remove_msg }, { "\0", 0, NULL, 0, NULL }};
mapi_clist_av1 quarantine_clist[] = { &quarantine_msgtab, &unquarantine_msgtab, NULL };
mapi_hfn_list_av1 quarantine_hfn_list[] = { { "account_change", quarantine_account_change }, { "can_join", quarantine_join_channel }, { "conf_read_start", quarantine_reset_conf }, { "doing_info_conf", quarantine_conf_info }, { "introduce_client", quarantine_default_umode_end }, { "new_local_user", quarantine_default_umode_begin, HOOK_MONITOR }, { "privmsg_channel", quarantine_privmsg_channel }, { "privmsg_user", quarantine_privmsg_user }, { "umode_changed", quarantine_change_umode }, { NULL, NULL }};
DECLARE_MODULE_AV2(m_quarantine, modinit, moddeinit, quarantine_clist, NULL, quarantine_hfn_list, NULL, NULL, quarantine_desc);
static intmodinit(void){ if (find_top_conf("quarantine") != NULL) { ierror("m_quarantine: a top conf block named quarantine already exists, unloading extension"); return -1; }
user_modes['q'] = find_umode_slot(); if (!user_modes['q']) { ierror("m_quarantine: unable to allocate usermode slot for +q, unloading extension"); return -1; }
construct_umodebuf(); add_top_conf("quarantine", NULL, NULL, conf_quarantine_table); return 0;}
static voidmoddeinit(void){ user_modes['q'] = 0; construct_umodebuf(); remove_top_conf("quarantine"); quarantine_reset_conf(NULL);}
static voidmo_quarantine(struct MsgBuf *msgbuf_p, struct Client *client_p, struct Client *source_p, int parc, const char *parv[]){ if (!IsOperQuarantine(source_p)) { sendto_one(source_p, form_str(ERR_NOPRIVS), me.name, source_p->name, "quarantine"); return; }
struct Client *target_p = find_named_person(parv[1]); if (target_p == NULL) { sendto_one_numeric(source_p, ERR_NOSUCHNICK, form_str(ERR_NOSUCHNICK), parv[1]); return; }
if (MyClient(target_p)) add_quarantine(source_p, target_p, parv[2]); else sendto_one(target_p, ":%s ENCAP %s QUARANTINE %s :%s", get_id(source_p, target_p), target_p->servptr->name, get_id(target_p, target_p), parv[2]);}
static voidme_quarantine(struct MsgBuf *msgbuf_p, struct Client *client_p, struct Client *source_p, int parc, const char *parv[]){ struct Client *target_p = find_person(parv[1]); if (target_p == NULL) { sendto_one_numeric(source_p, ERR_NOSUCHNICK, form_str(ERR_NOSUCHNICK), parv[1]); return; }
if (MyClient(target_p)) add_quarantine(source_p, target_p, parv[2]);}
static voidmo_unquarantine(struct MsgBuf *msgbuf_p, struct Client *client_p, struct Client *source_p, int parc, const char *parv[]){ if (!IsOperQuarantine(source_p)) { sendto_one(source_p, form_str(ERR_NOPRIVS), me.name, source_p->name, "quarantine"); return; }
struct Client *target_p = find_named_person(parv[1]); if (target_p == NULL) { sendto_one_numeric(source_p, ERR_NOSUCHNICK, form_str(ERR_NOSUCHNICK), parv[1]); return; }
if (MyClient(target_p)) remove_quarantine(source_p, target_p); else sendto_one(target_p, ":%s ENCAP %s UNQUARANTINE %s", get_id(source_p, target_p), target_p->servptr->name, get_id(target_p, target_p));}
static voidme_unquarantine(struct MsgBuf *msgbuf_p, struct Client *client_p, struct Client *source_p, int parc, const char *parv[]){ struct Client *target_p = find_person(parv[1]); if (target_p == NULL) { sendto_one_numeric(source_p, ERR_NOSUCHNICK, form_str(ERR_NOSUCHNICK), parv[1]); return; }
if (MyClient(target_p)) remove_quarantine(source_p, target_p);}
static voidadd_quarantine(struct Client *source_p, struct Client *target_p, const char *reason){ const char *parv[4] = { target_p->name, target_p->name, "+q", NULL };
if (IsQuarantined(target_p)) { sendto_one_notice(source_p, ":*** %s is already quarantined.", target_p->name); return; }
if (!EmptyString(target_p->user->suser)) { sendto_one_notice(source_p, ":*** %s is already logged into services and cannot be quarantined.", target_p->name); return; }
if (IsOper(target_p)) { sendto_one_notice(source_p, ":*** %s is an oper and cannot be put into quarantine.", target_p->name); return; }
if (IsService(target_p)) { sendto_one_notice(source_p, ":*** %s is a network service and cannot be put into quarantine.", target_p->name); return; }
sendto_realops_snomask(SNO_GENERAL, L_NETWIDE, "%s has put %s into QUARANTINE [%s]", source_p->name, target_p->name, reason); sendto_one_notice(target_p, ":*** %s", EmptyString(apply_msg) ? DEFAULT_APPLY_MSG : apply_msg);
allowed_umode_change = target_p; user_mode(target_p, target_p, 3, parv); allowed_umode_change = NULL;
if (part_channels) { rb_dlink_node *ptr, *next_ptr; RB_DLINK_FOREACH_SAFE(ptr, next_ptr, target_p->user->channel.head) { struct membership *msptr = ptr->data; struct Channel *chptr = msptr->chptr; bool allowed = false; if (allowed_channels != NULL) { for (int i = 0; allowed_channels[i] != NULL; i++) { if (!irccmp(chptr->chname, allowed_channels[i])) { allowed = true; break; } } }
if (!allowed) { sendto_server(NULL, chptr, CAP_TS6, NOCAPS, ":%s PART %s", use_id(source_p), chptr->chname); sendto_channel_local(target_p, ALL_MEMBERS, chptr, ":%s!%s@%s PART %s", target_p->name, target_p->username, target_p->host, chptr->chname); remove_user_from_channel(msptr); } } }}
static voidremove_quarantine(struct Client *source_p, struct Client *target_p){ const char *parv[4] = { target_p->name, target_p->name, "-q", NULL };
if (!IsQuarantined(target_p)) { sendto_one_notice(source_p, ":*** %s is not currently quarantined.", target_p->name); return; }
if (source_p != target_p) sendto_realops_snomask(SNO_GENERAL, L_NETWIDE, "%s has removed %s from QUARANTINE", source_p->name, target_p->name); sendto_one_notice(target_p, ":*** %s", EmptyString(remove_msg) ? DEFAULT_REMOVE_MSG : remove_msg);
allowed_umode_change = target_p; user_mode(target_p, target_p, 3, parv); allowed_umode_change = NULL;}
static voidquarantine_account_change(void *data_){ hook_cdata *data = data_; if (!MyClient(data->client) || !IsQuarantined(data->client)) return;
if (!EmptyString(data->client->user->suser)) remove_quarantine(data->client, data->client);}
static voidquarantine_change_umode(void *data_){ hook_data_umode_changed *data = data_;
if (!MyClient(data->client)) return;
/* If someone successfully opers, unquarantine them. * Services might be down and we don't want to impede them doing what they need to do. */ if (IsOper(data->client) && IsQuarantined(data->client)) { sendto_one_notice(data->client, ":*** %s", EmptyString(remove_msg) ? DEFAULT_REMOVE_MSG : remove_msg); data->client->umodes &= ~user_modes['q']; return; }
bool quarantine_changed = ((data->client->umodes ^ data->oldumodes) & user_modes['q']) == user_modes['q']; if (data->client != allowed_umode_change && quarantine_changed) { if (data->oldumodes & user_modes['q']) data->client->umodes |= user_modes['q']; else data->client->umodes &= ~user_modes['q']; }}
static voidquarantine_conf_info(void *data_){ hook_data *data = data_;
/* quarantine::allow_channels not listed, since INFO in general doesn't seem * to support sending list-type conf options (e.g. no hidden_caps) */
sendto_one(data->client, ":%s %d %s :%-30s %-16s [%s]", get_id(&me, data->client), RPL_INFO, get_id(data->client, data->client), "quarantine::apply_msg", EmptyString(apply_msg) ? DEFAULT_APPLY_MSG : apply_msg, "Notice sent to users that have been quarantined");
sendto_one(data->client, ":%s %d %s :%-30s %-16s [%s]", get_id(&me, data->client), RPL_INFO, get_id(data->client, data->client), "quarantine::join_reason", EmptyString(join_reason) ? DEFAULT_JOIN_REASON : join_reason, "Sent to +q users when JOIN is denied");
sendto_one(data->client, ":%s %d %s :%-30s %-16s [%s]", get_id(&me, data->client), RPL_INFO, get_id(data->client, data->client), "quarantine::msg_reason", EmptyString(msg_reason) ? DEFAULT_MSG_REASON : msg_reason, "Sent to +q users when sending a message is denied");
sendto_one(data->client, ":%s %d %s :%-30s %-16s [%s]", get_id(&me, data->client), RPL_INFO, get_id(data->client, data->client), "quarantine::other_msg_reason", EmptyString(other_msg_reason) ? DEFAULT_OTHER_MSG_REASON : other_msg_reason, "Sent to users trying to message a +q user");
sendto_one(data->client, ":%s %d %s :%-30s %-16s [%s]", get_id(&me, data->client), RPL_INFO, get_id(data->client, data->client), "quarantine::part_channels", part_channels ? "YES" : "NO", "QUARANTINE causes user to part disallowed channels");
sendto_one(data->client, ":%s %d %s :%-30s %-16s [%s]", get_id(&me, data->client), RPL_INFO, get_id(data->client, data->client), "quarantine::remove_msg", EmptyString(remove_msg) ? DEFAULT_REMOVE_MSG : remove_msg, "Notice sent to users that are no longer quarantined");}
static voidquarantine_default_umode_begin(void *data){ /* if ircd.conf has +q as a default umode, and the user didn't identify * during registration (via SASL or PASS), allow the +q and notify the user */ struct Client *source_p = data; if (!IsAnyDead(source_p) && IsQuarantined(source_p)) { if (!EmptyString(source_p->user->suser)) source_p->umodes &= ~user_modes['q']; else { sendto_one_notice(source_p, ":*** %s", EmptyString(apply_msg) ? DEFAULT_APPLY_MSG : apply_msg); allowed_umode_change = source_p; } }}
static voidquarantine_default_umode_end(void *data){ /* Introducing a new local user calls three hooks in the following order: * 1. new_local_user * 2. umode_changed * 3. introduce_client * We make sure we clear the allowed_umode_change after introduce_client so it can't linger * after the client is introduced. Non-local users also call the latter two, but effectively * no-ops (umode_changed aborts if non-local, this just sets something already NULL to NULL), * so no issues there */ allowed_umode_change = NULL;}
static voidquarantine_join_channel(void *data_){ hook_data_channel *data = data_; if (!MyClient(data->client) || !IsQuarantined(data->client)) return;
/* if some other hook function already rejected this join attempt, keep that rejection */ if (data->approved != 0) return;
if (allowed_channels != NULL) { for (int i = 0; allowed_channels[i] != NULL; i++) { if (!irccmp(data->chptr->chname, allowed_channels[i])) return; } }
/* Remind the user that they're quarantined */ sendto_one_numeric(data->client, ERR_NEEDREGGEDNICK, "%s :%s", data->chptr->chname, EmptyString(join_reason) ? DEFAULT_JOIN_REASON : join_reason); data->approved = ERR_CUSTOM;}
static voidquarantine_privmsg_channel(void *data_){ hook_data_privmsg_channel *data = data_; if (!MyClient(data->source_p) || !IsQuarantined(data->source_p)) return;
/* if some other hook function already rejected this attempt, keep that rejection */ if (data->approved != 0) return;
if (allowed_channels != NULL) { for (int i = 0; allowed_channels[i] != NULL; i++) { if (!irccmp(data->chptr->chname, allowed_channels[i])) return; } }
data->approved = ERR_CANNOTSENDTOCHAN;
/* Don't give error messages for non-PRIVMSG since many clients autogenerate these (e.g. +typing) */ if (data->msgtype == MESSAGE_TYPE_PRIVMSG) sendto_one_numeric(data->source_p, ERR_CANNOTSENDTOCHAN, "%s :%s", data->chptr->chname, EmptyString(msg_reason) ? DEFAULT_MSG_REASON : msg_reason);}
static voidquarantine_privmsg_user(void *data_){ hook_data_privmsg_user *data = data_; if (!MyClient(data->source_p)) return;
/* if some other hook function already rejected this attempt, keep that rejection */ if (data->approved != 0) return;
if (!IsQuarantined(data->source_p)) { if (IsQuarantined(data->target_p) && !IsOper(data->source_p) && !IsService(data->source_p)) { if (data->msgtype == MESSAGE_TYPE_PRIVMSG) sendto_one_numeric(data->source_p, ERR_CANNOTSENDTOCHAN, "%s :%s", data->target_p->name, EmptyString(other_msg_reason) ? DEFAULT_OTHER_MSG_REASON : other_msg_reason); data->approved = ERR_CANNOTSENDTOCHAN; } return; }
/* let quarantined users message opers and services */ if (IsOper(data->target_p) || IsService(data->target_p)) return;
data->approved = ERR_CANNOTSENDTOCHAN;
/* Don't give error messages for non-PRIVMSG since many clients autogenerate these (e.g. +typing) */ if (data->msgtype == MESSAGE_TYPE_PRIVMSG) sendto_one_numeric(data->source_p, ERR_CANNOTSENDTOCHAN, "%s :%s", data->target_p->name, EmptyString(msg_reason) ? DEFAULT_MSG_REASON : msg_reason);}
static voidquarantine_reset_conf(void *unused){ if (allowed_channels != NULL) { for (int i = 0; allowed_channels[i] != NULL; i++) rb_free(allowed_channels[i]); rb_free(allowed_channels); allowed_channels = NULL; }
rb_free(apply_msg); rb_free(join_reason); rb_free(msg_reason); rb_free(other_msg_reason); rb_free(remove_msg);
apply_msg = NULL; join_reason = NULL; msg_reason = NULL; other_msg_reason = NULL; remove_msg = NULL;}
static voidquarantine_set_allow_channels(void *data){ size_t n = 0; for (conf_parm_t *arg = data; arg; arg = arg->next) n++;
allowed_channels = rb_malloc((n + 1) * sizeof(char *));
n = 0; for (conf_parm_t *arg = data; arg; arg = arg->next) allowed_channels[n++] = rb_strdup(arg->v.string); allowed_channels[n] = NULL;}