Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
195 changes: 178 additions & 17 deletions src/chanserv.c
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@
#define KEY_NODELETE_LEVEL "nodelete_level"
#define KEY_MAX_USERINFO_LENGTH "max_userinfo_length"
#define KEY_GIVEOWNERSHIP_PERIOD "giveownership_timeout"
#define KEY_RENAME_DNR_DURATION "rename_dnr_duration"
#define KEY_VALID_CHANNEL_REGEX "valid_channel_regex"

/* ChanServ database */
Expand Down Expand Up @@ -631,6 +632,7 @@ static struct
unsigned int greeting_length;
unsigned int refresh_period;
unsigned int giveownership_period;
unsigned long rename_dnr_duration;

unsigned int max_owned;
unsigned int max_chan_users;
Expand Down Expand Up @@ -1735,12 +1737,14 @@ unregister_channel(struct chanData *channel, const char *reason)

timeq_del(0, NULL, channel, TIMEQ_IGNORE_FUNC | TIMEQ_IGNORE_WHEN);

if(off_channel > 0)
{
mod_chanmode_init(&change);
change.modes_clear |= MODE_REGISTERED;
mod_chanmode_announce(chanserv, channel->channel, &change);
}
/* Always clear the ircd's registration marker (+R) on unregistration,
* independent of off_channel (which only governs whether ChanServ
* itself leaves the channel). Clear the persist exmode (+z) too:
* an unregistered channel must be able to die when it empties (the
* ircd schedules the destruct when -z lands on an empty channel). */
mod_chanmode_init(&change);
change.modes_clear |= MODE_REGISTERED | MODE_PERSIST;
mod_chanmode_announce(chanserv, channel->channel, &change);

wipe_adduser_pending(channel->channel, NULL);

Expand Down Expand Up @@ -2107,6 +2111,27 @@ chanserv_is_dnr(const char *chan_name, struct handle_info *handle)
return dnr;
}

void
chanserv_rename_dnr(const char *old_name)
{
const char *setter;

if(!chanserv_conf.rename_dnr_duration)
return;
if(chanserv_is_dnr(old_name, NULL))
return; /* already covered by a DNR (plain or mask); don't stack */
/* chanserv is NULL when the bot's nick is disabled via the "."
* convention (init_chanserv only AddLocalUser()s it when nick !=
* NULL) -- but a channel can still be registered (saxdb load doesn't
* care whether the bot exists) and RN can still arrive over the
* wire. Fall back to a literal setter string rather than dereference
* a NULL chanserv; it's a plain string and serializes to saxdb the
* same as any other setter value. */
setter = chanserv ? chanserv->nick : "ChanServ";
chanserv_add_dnr(old_name, setter, now + chanserv_conf.rename_dnr_duration,
"Channel was renamed");
}

static unsigned int send_dnrs(struct userNode *user, dict_t dict)
{
struct do_not_register *dnr;
Expand Down Expand Up @@ -2618,8 +2643,15 @@ static CHANSERV_FUNC(cmd_register)
cData = register_channel(channel, user->handle_info->handle);
scan_user_presence(add_channel_user(cData, handle, UL_OWNER, 0, NULL, 0), NULL);
cData->modes = chanserv_conf.default_modes;
/* Always announce the ircd's registration marker (+R) on registration;
* off_channel only governs whether ChanServ itself joins/leaves the
* channel below, not whether the ircd learns it's registered.
* With off_channel>0 there is no bot presence to hold the channel
* open, so also set the persist exmode (+z) -- its original intended
* use -- to keep the registered channel alive while empty. */
cData->modes.modes_set |= MODE_REGISTERED;
if(off_channel > 0)
cData->modes.modes_set |= MODE_REGISTERED;
cData->modes.modes_set |= MODE_PERSIST;
if (IsOffChannel(cData))
{
mod_chanmode_announce(chanserv, channel, &cData->modes);
Expand Down Expand Up @@ -2809,16 +2841,19 @@ static CHANSERV_FUNC(cmd_move)
else if(!IsSuspended(channel->channel_info))
chanserv_join = 1;

/* Clear the server-managed markers from the old channel, add them to
* the new. Always, not just under off_channel -- the ircd's +R must
* follow registration regardless of whether ChanServ itself occupies
* the channel. The persist exmode (+z) moves too, but is only SET
* when off_channel>0 (bot presence otherwise holds the channel). */
change.argc = 0;
change.modes_clear = MODE_REGISTERED | MODE_PERSIST;
mod_chanmode_announce(chanserv, channel, &change);
change.modes_clear = 0;
change.modes_set = MODE_REGISTERED;
if(off_channel > 0)
{
/* Clear MODE_REGISTERED from old channel, add it to new. */
change.argc = 0;
change.modes_clear = MODE_REGISTERED;
mod_chanmode_announce(chanserv, channel, &change);
change.modes_clear = 0;
change.modes_set = MODE_REGISTERED;
mod_chanmode_announce(chanserv, target, &change);
}
change.modes_set |= MODE_PERSIST;
mod_chanmode_announce(chanserv, target, &change);

/* Move the channel_info to the target channel; it
shouldn't be necessary to clear timeq callbacks
Expand Down Expand Up @@ -8321,6 +8356,90 @@ handle_new_channel(struct chanNode *channel, UNUSED_ARG(void *extra))
SetChannelTopic(channel, chanserv, chanserv, channel->channel_info->topic, 1);
}

static void
chanserv_channel_rename(struct chanNode *old_chan, struct chanNode *new_chan, UNUSED_ARG(void *extra))
{
struct adduserPending *ap;
unsigned int ii;

/* memcpy() moved channel_info onto new_chan, but the chanData's back
* pointer still targets the old node. */
if (new_chan->channel_info)
new_chan->channel_info->channel = new_chan;

for (ap = adduser_pendings; ap; ap = ap->next)
if (ap->channel == old_chan)
ap->channel = new_chan;

for (ii = 0; ii < chanserv_conf.support_channels.used; ++ii)
if (chanserv_conf.support_channels.list[ii] == old_chan)
chanserv_conf.support_channels.list[ii] = new_chan;
}

/* Rename authorization check for the AC R RENAME query (proto-p10.c
* cmd_account). Owner-only, mirroring cmd_move's DNR gating against the
* NEW name — minus the IsHelping/"force" bypass, since this path has no
* force; staff bypass comes only from _GetChannelUser()'s override=1
* synthetic access entry. */
int
chanserv_rename_allowed(struct userNode *user, struct chanNode *chan, const char *new_name, const char **reason)
{
struct chanData *cData;
struct userData *uData;
struct do_not_register *dnr;

if(!user->handle_info)
{
*reason = "You must be authenticated";
return 0;
}

if(strlen(new_name) > CHANNELLEN)
{
*reason = "New channel name is too long";
return 0;
}

if(!(cData = chan->channel_info))
return 1; /* Nothing registered here to protect. */

if(IsProtected(cData) || IsSuspended(cData))
{
*reason = "Channel may not be renamed";
return 0;
}

uData = _GetChannelUser(cData, user->handle_info, 1, 0);
if(!uData || (uData->access < UL_OWNER))
{
*reason = "You must be the channel owner";
return 0;
}

if(opserv_bad_channel(new_name))
{
*reason = "New channel name is not allowed";
return 0;
}

if(GetChannel(new_name) && GetChannel(new_name)->channel_info)
{
*reason = "New channel name is already registered";
return 0;
}

for(uData = cData->users; uData; uData = uData->next)
{
if((uData->access == UL_OWNER) && (dnr = chanserv_is_dnr(new_name, uData->handle)))
{
*reason = "New channel name is blocked (do-not-register)";
return 0;
}
}

return 1;
}

int
trace_check_bans(struct userNode *user, struct chanNode *chan)
{
Expand Down Expand Up @@ -8438,6 +8557,33 @@ handle_join(struct modeNode *mNode, UNUSED_ARG(void *extra))
if(channel->members.used > cData->max)
cData->max = channel->members.used;

/* Self-heal the ircd's server-managed markers (+R always; +z persist
* when off_channel>0, since no bot presence then) for a channel we
* know is registered (channel_info set, not suspended -- both already
* checked above) but which the ircd doesn't currently show as such.
* This covers ircd restarts (fresh channel, no memory of +R), X3
* restarts racing a channel's first post-restart JOIN before the
* BURST/DB-load path re-set it, and channel re-creation. Unlike the
* join-flood/burst guards below (dynamic-limit timer resets,
* automode, greetings) this isn't gated on user->uplink->burst: it
* sends no message to the user and touches no timer, it only
* corrects channel state, and mod_chanmode_announce() applies the
* change to channel->modes immediately, so only the first joiner
* (burst or not) actually triggers the wire MODE -- every later
* handle_join() call in the same burst sees MODE_REGISTERED already
* set and no-ops here. */
{
unsigned int needed = MODE_REGISTERED
| ((off_channel > 0) ? MODE_PERSIST : 0);
if((channel->modes & needed) != needed)
{
struct mod_chanmode reg_change;
mod_chanmode_init(&reg_change);
reg_change.modes_set = needed & ~channel->modes;
mod_chanmode_announce(chanserv, channel, &reg_change);
}
}

#ifdef notdef
/* Check for bans. If they're joining through a ban, one of two
* cases applies:
Expand Down Expand Up @@ -9124,6 +9270,8 @@ chanserv_conf_read(void)
chanserv_conf.refresh_period = str ? ParseInterval(str) : 3*60*60;
str = database_get_data(conf_node, KEY_GIVEOWNERSHIP_PERIOD, RECDB_QSTRING);
chanserv_conf.giveownership_period = str ? ParseInterval(str) : 0;
str = database_get_data(conf_node, KEY_RENAME_DNR_DURATION, RECDB_QSTRING);
chanserv_conf.rename_dnr_duration = str ? ParseInterval(str) : 86400;
str = database_get_data(conf_node, KEY_CTCP_SHORT_BAN_DURATION, RECDB_QSTRING);
chanserv_conf.ctcp_short_ban_duration = str ? str : "3m";
str = database_get_data(conf_node, KEY_CTCP_LONG_BAN_DURATION, RECDB_QSTRING);
Expand Down Expand Up @@ -9617,8 +9765,14 @@ chanserv_channel_read(const char *key, struct record_data *hir)
&& (modes = mod_chanmode_parse(cNode, argv, argc, MCP_KEY_FREE, 0)))
{
cData->modes = *modes;
/* Always re-assert +R on DB-load reregistration; see
* unregister_channel()/register path. When this block doesn't run
* at all (channel has no stored KEY_MODES), handle_join()'s
* heal-on-join covers it instead. +z persist rides along when
* off_channel>0 (no bot presence to keep the channel alive). */
cData->modes.modes_set |= MODE_REGISTERED;
if(off_channel > 0)
cData->modes.modes_set |= MODE_REGISTERED;
cData->modes.modes_set |= MODE_PERSIST;
if(cData->modes.argc > 1)
cData->modes.argc = 1;
mod_chanmode_announce(chanserv, cNode, &cData->modes);
Expand Down Expand Up @@ -10044,6 +10198,13 @@ init_chanserv(const char *nick)
reg_auth_func(handle_auth, NULL);
}

/* Registered channels load from the DB regardless of whether the
* ChanServ bot nick is enabled ("." convention); an RN arriving over
* the wire must still repoint channel_info->channel via RenameChannel,
* or the DB is left holding a stale pointer into a freed chanNode
* (use-after-free at the next saxdb write). Keep this outside if(nick).
*/
reg_channel_rename_func(chanserv_channel_rename, NULL);
reg_handle_rename_func(handle_rename, NULL);
reg_unreg_func(handle_unreg, NULL);

Expand Down
13 changes: 13 additions & 0 deletions src/chanserv.h
Original file line number Diff line number Diff line change
Expand Up @@ -216,6 +216,19 @@ struct do_not_register
struct userData *_GetChannelUser(struct chanData *channel, struct handle_info *handle, int override, int allow_suspended);
struct banData *add_channel_ban(struct chanData *channel, const char *mask, char *owner, time_t set, time_t triggered, time_t expires, char *reason);

/* Rename authorization check, used by the AC R RENAME query handler in
* proto-p10.c (cmd_account). Returns 1 = allow; 0 = deny with *reason
* set to a static user-visible string. */
int chanserv_rename_allowed(struct userNode *user, struct chanNode *chan, const char *new_name, const char **reason);

/* Called by the RN handler in proto-p10.c (cmd_rename) after a registered
* channel is renamed, to place a timed do-not-register entry on the old
* name. No-op if chanserv_conf.rename_dnr_duration is 0, or if old_name
* is already covered by an existing (non-expired) DNR. proto-p10.c must
* not reach into chanserv's DNR internals (plain_dnrs/mask_dnrs/etc are
* file-static) — this wrapper is the exported escape hatch. */
void chanserv_rename_dnr(const char *old_name);

void init_chanserv(const char *nick);
void del_channel_user(struct userData *user, int do_gc);
struct channelList *chanserv_support_channels(void);
Expand Down
58 changes: 57 additions & 1 deletion src/hash.c
Original file line number Diff line number Diff line change
Expand Up @@ -555,7 +555,9 @@ wipeout_channel(struct chanNode *cNode, time_t new_time, char **modes, unsigned
strcpy(orig_upass, cNode->upass);
strcpy(orig_apass, cNode->apass);
cNode->modes = 0;
mod_chanmode(NULL, cNode, modes, modec, 0);
/* burst data IS server-origin; without the flag one unknown letter
* aborts the whole parse and strands modes at zero */
mod_chanmode(NULL, cNode, modes, modec, MCP_FROM_SERVER);
cNode->timestamp = new_time;

/* remove our old ban list, replace it with the new one */
Expand Down Expand Up @@ -704,6 +706,28 @@ reg_del_channel_func(del_channel_func_t handler, void *extra)
dcf_list_extra[dcf_used++] = extra;
}

static channel_rename_func_t *crf_list;
static void **crf_list_extra;
static unsigned int crf_size = 0, crf_used = 0;

void
reg_channel_rename_func(channel_rename_func_t handler, void *extra)
{
if (crf_used == crf_size) {
if (crf_size) {
crf_size <<= 1;
crf_list = realloc(crf_list, crf_size*sizeof(crf_list[0]));
crf_list_extra = realloc(crf_list_extra, crf_size*sizeof(void*));
} else {
crf_size = 8;
crf_list = malloc(crf_size*sizeof(crf_list[0]));
crf_list_extra = malloc(crf_size*sizeof(void*));
}
}
crf_list[crf_used] = handler;
crf_list_extra[crf_used++] = extra;
}

static void
DelChannel(struct chanNode *channel)
{
Expand Down Expand Up @@ -739,6 +763,36 @@ DelChannel(struct chanNode *channel)
free(channel);
}

struct chanNode *
RenameChannel(struct chanNode *channel, const char *new_name)
{
struct chanNode *nNode;
unsigned int n;

if (!IsChannelName(new_name) || GetChannel(new_name) || strlen(new_name) > CHANNELLEN)
return NULL; /* IsChannelName has no length cap; an overlong name would enter fixed-buffer sprintf paths downstream */
nNode = calloc(1, sizeof(*nNode) + strlen(new_name));
/* Copy the fixed head wholesale: modes, limit, LOCKS (inherited — chanserv
* registration lock + alert/support locks count on this node), keys,
* timestamp, topic, list headers (heap arrays move ownership), and the
* channel_info pointer. name[] is then overwritten. */
memcpy(nNode, channel, sizeof(*channel));
strcpy(nNode->name, new_name);
for (n = 0; n < nNode->members.used; n++)
nNode->members.list[n]->channel = nNode;
/* Old node's dict key is an interior pointer into its name[] — remove
* while it is still alive. */
dict_remove(channels, channel->name);
dict_insert(channels, nNode->name, nNode);
/* Modules re-point their own holders (design doc §8) with BOTH nodes
* alive: pointer-compare holders need old; name-keyed dicts need
* old->name intact. */
for (n = 0; n < crf_used; n++)
crf_list[n](channel, nNode, crf_list_extra[n]);
free(channel);
return nNode;
}

struct modeNode *
AddChannelUser(struct userNode *user, struct chanNode* channel)
{
Expand Down Expand Up @@ -1103,6 +1157,8 @@ hash_cleanup(UNUSED_ARG(void *extra))
free_hook_func_list(&jf_list);
free(dcf_list);
free(dcf_list_extra);
free(crf_list);
free(crf_list_extra);
free(pf_list);
free(pf_list_extra);
free(kf_list);
Expand Down
Loading