mirror of
https://github.com/unrealircd/unrealircd.git
synced 2026-07-06 15:13:12 +02:00
b785d69309
These were not a surprise, but deliberate: we let the previous clang-format commit be 100% clang-format job (so reproducable) and after that we planend and did this commit, because we chose to leave some stuff alone and do it manually. This commit will (also) be added to .git-blame-ignore-revs
1577 lines
46 KiB
C
1577 lines
46 KiB
C
/*
|
|
* IRC - Internet Relay Chat, src/modules/mode.c
|
|
* (C) 2005-.. The UnrealIRCd Team
|
|
*
|
|
* See file AUTHORS in IRC package for additional names of
|
|
* the programmers.
|
|
*
|
|
* This program is free software; you can redistribute it and/or modify
|
|
* it under the terms of the GNU General Public License as published by
|
|
* the Free Software Foundation; either version 1, or (at your option)
|
|
* any later version.
|
|
*
|
|
* This program is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
* GNU General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License
|
|
* along with this program; if not, write to the Free Software
|
|
* Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
|
|
*/
|
|
|
|
#include "unrealircd.h"
|
|
|
|
ModuleHeader MOD_HEADER = {
|
|
"mode",
|
|
"5.0",
|
|
"command /mode",
|
|
"UnrealIRCd Team",
|
|
"unrealircd-6",
|
|
};
|
|
|
|
/* Forward declarations */
|
|
int list_mode_request(Client *client, Channel *channel, const char *req);
|
|
CMD_FUNC(cmd_mode);
|
|
CMD_FUNC(cmd_mlock);
|
|
void _do_mode(Channel *channel, Client *client, MessageTag *recv_mtags, int parc, const char *parv[], time_t sendts, int samode);
|
|
MultiLineMode *_set_mode(Channel *channel, Client *client, int parc, const char *parv[], u_int *pcount,
|
|
char pvar[MAXMODEPARAMS][MODEBUFLEN + 3]);
|
|
void _set_channel_mode(Channel *channel, MessageTag *mtags, const char *modes, const char *parameters);
|
|
CMD_FUNC(_cmd_umode);
|
|
|
|
/* local: */
|
|
int do_mode_char(Channel *channel, long modetype, char modechar, const char *param,
|
|
u_int what, Client *client,
|
|
u_int *pcount, char pvar[MAXMODEPARAMS][MODEBUFLEN + 3]);
|
|
int do_extmode_char(Channel *channel, Cmode *handler, const char *param, u_int what,
|
|
Client *client, u_int *pcount, char pvar[MAXMODEPARAMS][MODEBUFLEN + 3]);
|
|
void do_mode_char_member_mode_new(Channel *channel, Cmode *handler, const char *param, u_int what,
|
|
Client *client, u_int *pcount, char pvar[MAXMODEPARAMS][MODEBUFLEN + 3]);
|
|
MultiLineMode *make_mode_str(Client *client, Channel *channel, Cmode_t oldem, int pcount, char pvar[MAXMODEPARAMS][MODEBUFLEN + 3]);
|
|
|
|
static char *mode_cutoff(const char *s);
|
|
void mode_operoverride_msg(Client *client, Channel *channel, char *modebuf, char *parabuf);
|
|
|
|
static int samode_in_progress = 0;
|
|
|
|
MOD_TEST()
|
|
{
|
|
MARK_AS_OFFICIAL_MODULE(modinfo);
|
|
EfunctionAddVoid(modinfo->handle, EFUNC_DO_MODE, _do_mode);
|
|
EfunctionAddPVoid(modinfo->handle, EFUNC_SET_MODE, TO_PVOIDFUNC(_set_mode));
|
|
EfunctionAddVoid(modinfo->handle, EFUNC_CMD_UMODE, _cmd_umode);
|
|
EfunctionAddVoid(modinfo->handle, EFUNC_SET_CHANNEL_MODE, _set_channel_mode);
|
|
return MOD_SUCCESS;
|
|
}
|
|
|
|
MOD_INIT()
|
|
{
|
|
CommandAdd(modinfo->handle, "MODE", cmd_mode, MAXPARA, CMD_USER | CMD_SERVER);
|
|
CommandAdd(modinfo->handle, MSG_MLOCK, cmd_mlock, MAXPARA, CMD_SERVER);
|
|
MARK_AS_OFFICIAL_MODULE(modinfo);
|
|
return MOD_SUCCESS;
|
|
}
|
|
|
|
MOD_LOAD()
|
|
{
|
|
return MOD_SUCCESS;
|
|
}
|
|
|
|
MOD_UNLOAD()
|
|
{
|
|
return MOD_SUCCESS;
|
|
}
|
|
|
|
/*
|
|
* cmd_mode -- written by binary (garryb@binary.islesfan.net)
|
|
* Completely rewrote it. The old mode command was 820 lines of ICKY
|
|
* coding, which is a complete waste, because I wrote it in 570 lines of
|
|
* *decent* coding. This is also easier to read, change, and fine-tune. Plus,
|
|
* everything isn't scattered; everything's grouped where it should be.
|
|
*
|
|
* parv[1] - channel
|
|
*/
|
|
CMD_FUNC(cmd_mode)
|
|
{
|
|
long unsigned sendts = 0;
|
|
Ban *ban;
|
|
Channel *channel;
|
|
|
|
/* Now, try to find the channel in question */
|
|
if (parc > 1)
|
|
{
|
|
if (*parv[1] == '#')
|
|
{
|
|
channel = find_channel(parv[1]);
|
|
if (!channel)
|
|
{
|
|
sendnumeric(client, ERR_NOSUCHCHANNEL, parv[1]);
|
|
return;
|
|
}
|
|
} else
|
|
{
|
|
CALL_CMD_FUNC(cmd_umode);
|
|
return;
|
|
}
|
|
} else
|
|
{
|
|
sendnumeric(client, ERR_NEEDMOREPARAMS, "MODE");
|
|
return;
|
|
}
|
|
|
|
if (MyConnect(client) && !valid_channelname(parv[1]))
|
|
{
|
|
sendnumeric(client, ERR_NOSUCHCHANNEL, parv[1]);
|
|
return;
|
|
}
|
|
|
|
if (parc < 3)
|
|
{
|
|
char modebuf[BUFSIZE], parabuf[BUFSIZE];
|
|
*modebuf = *parabuf = '\0';
|
|
|
|
modebuf[1] = '\0';
|
|
channel_modes(client, modebuf, parabuf, sizeof(modebuf), sizeof(parabuf), channel, 0);
|
|
sendnumeric(client, RPL_CHANNELMODEIS, channel->name, modebuf, parabuf);
|
|
sendnumeric(client, RPL_CREATIONTIME, channel->name, (long long)channel->creationtime);
|
|
return;
|
|
}
|
|
|
|
/* List mode request? Eg: "MODE #channel b" to list all bans */
|
|
if (MyUser(client) && BadPtr(parv[3]) && list_mode_request(client, channel, parv[2]))
|
|
return;
|
|
|
|
opermode = 0;
|
|
|
|
#ifndef NO_OPEROVERRIDE
|
|
if (IsUser(client) && !IsULine(client) && !check_channel_access(client, channel, "oaq") &&
|
|
!check_channel_access(client, channel, "h") && ValidatePermissionsForPath("channel:override:mode", client, NULL, channel, NULL))
|
|
{
|
|
sendts = 0;
|
|
opermode = 1;
|
|
goto aftercheck;
|
|
}
|
|
|
|
if (IsUser(client) && !IsULine(client) && !check_channel_access(client, channel, "oaq") &&
|
|
check_channel_access(client, channel, "h") && ValidatePermissionsForPath("channel:override:mode", client, NULL, channel, NULL))
|
|
{
|
|
opermode = 2;
|
|
goto aftercheck;
|
|
}
|
|
#endif
|
|
|
|
/* User does not have permission to use the MODE command */
|
|
if (MyUser(client) && !IsULine(client) && !check_channel_access(client, channel, "hoaq") &&
|
|
!ValidatePermissionsForPath("channel:override:mode", client, NULL, channel, NULL))
|
|
{
|
|
sendnumeric(client, ERR_CHANOPRIVSNEEDED, channel->name);
|
|
return;
|
|
}
|
|
|
|
if (parv[2] && (*parv[2] == '&'))
|
|
{
|
|
/* We don't do any bounce-mode handling anymore since UnrealIRCd 6 */
|
|
return;
|
|
}
|
|
|
|
if (IsServer(client) && (sendts = atol(parv[parc - 1])) &&
|
|
!IsULine(client) && (sendts > channel->creationtime))
|
|
{
|
|
unreal_log(ULOG_INFO, "mode", "MODE_TS_IGNORED", client,
|
|
"MODE change ignored for $channel from $client: "
|
|
"timestamp mismatch, ours=$channel.creationtime, theirs=$their_ts",
|
|
log_data_channel("channel", channel),
|
|
log_data_integer("their_ts", sendts));
|
|
return;
|
|
}
|
|
if (IsServer(client) && !sendts && *parv[parc - 1] != '0')
|
|
sendts = -1;
|
|
if (IsServer(client) && sendts != -1)
|
|
parc--; /* server supplied a time stamp, remove it now */
|
|
|
|
aftercheck:
|
|
|
|
/* This is to prevent excess +<whatever> modes. -- Syzop */
|
|
if (MyUser(client) && parv[2])
|
|
{
|
|
parv[2] = mode_cutoff(parv[2]);
|
|
}
|
|
|
|
/* Filter out the unprivileged FIRST. *
|
|
* Now, we can actually do the mode. */
|
|
|
|
(void)do_mode(channel, client, recv_mtags, parc - 2, parv + 2, sendts, 0);
|
|
/* After this don't touch 'channel' anymore, as permanent module may have destroyed the channel */
|
|
opermode = 0; /* Important since sometimes forgotten. -- Syzop */
|
|
}
|
|
|
|
/** Cut off mode string (eg: +abcdfjkdsgfgs) at MAXMODEPARAMS modes.
|
|
* @param s The mode string (modes only, no parameters)
|
|
* @note Should only used on local clients
|
|
* @returns The cleaned up string
|
|
*/
|
|
static char *mode_cutoff(const char *i)
|
|
{
|
|
static char newmodebuf[BUFSIZE];
|
|
char *o;
|
|
unsigned short modesleft = MAXMODEPARAMS * 2; /* be generous... */
|
|
|
|
strlcpy(newmodebuf, i, sizeof(newmodebuf));
|
|
|
|
for (o = newmodebuf; *o && modesleft; o++)
|
|
if ((*o != '-') && (*o != '+'))
|
|
modesleft--;
|
|
*o = '\0';
|
|
return newmodebuf;
|
|
}
|
|
|
|
/* do_mode -- written by binary
|
|
* User or server is authorized to do the mode. This takes care of
|
|
* setting the mode and relaying it to other users and servers.
|
|
*/
|
|
void _do_mode(Channel *channel, Client *client, MessageTag *recv_mtags, int parc, const char *parv[], time_t sendts, int samode)
|
|
{
|
|
Client *orig_client = client; /* (needed for samode replacement in a loop) */
|
|
char pvar[MAXMODEPARAMS][MODEBUFLEN + 3];
|
|
int pcount;
|
|
int i;
|
|
char tschange = 0;
|
|
MultiLineMode *m;
|
|
|
|
/* Please keep the next 3 lines next to each other */
|
|
samode_in_progress = samode;
|
|
m = set_mode(channel, client, parc, parv, &pcount, pvar);
|
|
samode_in_progress = 0;
|
|
|
|
if (IsServer(client))
|
|
{
|
|
if (sendts > 0)
|
|
{
|
|
if (IsInvalidChannelTS(sendts))
|
|
{
|
|
unreal_log(ULOG_WARNING, "mode", "MODE_INVALID_TIMESTAMP", client,
|
|
"MODE for channel $channel has invalid timestamp $send_timestamp (from $client.name)\n"
|
|
"Buffer: $modebuf $parabuf",
|
|
log_data_channel("channel", channel),
|
|
log_data_integer("send_timestamp", sendts),
|
|
log_data_string("modebuf", m ? m->modeline[0] : ""),
|
|
log_data_string("parabuf", m ? m->modeline[0] : ""));
|
|
/* Yeah, so what to do in this case?
|
|
* Don't set channel->creationtime
|
|
* and assume merging.
|
|
*/
|
|
sendts = channel->creationtime;
|
|
} else if (sendts < channel->creationtime)
|
|
{
|
|
/* Our timestamp is wrong or this is a new channel */
|
|
tschange = 1;
|
|
channel->creationtime = sendts;
|
|
}
|
|
if (sendts > channel->creationtime && channel->creationtime)
|
|
{
|
|
/* Their timestamp is wrong */
|
|
sendts = channel->creationtime;
|
|
sendto_one(client, NULL, ":%s MODE %s + %lld", me.name,
|
|
channel->name, (long long)channel->creationtime);
|
|
}
|
|
}
|
|
if (sendts == -1)
|
|
sendts = channel->creationtime;
|
|
}
|
|
|
|
if (!m)
|
|
{
|
|
/* No modes changed (empty mode change) */
|
|
if (tschange && !m)
|
|
{
|
|
/* Message from the other server is an empty mode, BUT they
|
|
* did change the channel->creationtime to an earlier TS
|
|
* (see above "Our timestamp is wrong or this is a new channel").
|
|
* We need to relay this MODE message to all other servers
|
|
* (all except from where it came from, client).
|
|
*/
|
|
sendto_server(client, 0, 0, NULL, ":%s MODE %s + %lld",
|
|
me.id, channel->name,
|
|
(long long)channel->creationtime);
|
|
}
|
|
/* Nothing to send */
|
|
safe_free_multilinemode(m);
|
|
opermode = 0;
|
|
return;
|
|
}
|
|
|
|
/* Now loop through the multiline modes... */
|
|
for (i = 0; i < m->numlines; i++)
|
|
{
|
|
char *modebuf = m->modeline[i];
|
|
char *parabuf = m->paramline[i];
|
|
MessageTag *mtags = NULL;
|
|
int should_destroy = 0;
|
|
|
|
if (IsUser(orig_client) && samode && MyUser(orig_client))
|
|
{
|
|
if (!sajoinmode)
|
|
{
|
|
char buf[512];
|
|
snprintf(buf, sizeof(buf), "%s%s%s", modebuf, *parabuf ? " " : "", parabuf);
|
|
unreal_log(ULOG_INFO, "samode", "SAMODE_COMMAND", orig_client,
|
|
"Client $client used SAMODE $channel ($mode)",
|
|
log_data_channel("channel", channel),
|
|
log_data_string("mode", buf));
|
|
}
|
|
|
|
client = &me;
|
|
sendts = 0;
|
|
}
|
|
|
|
if (m->numlines == 1)
|
|
{
|
|
/* Single mode lines are easy: retain original msgid etc */
|
|
new_message(client, recv_mtags, &mtags);
|
|
} else
|
|
{
|
|
/* We have a multi-mode line:
|
|
* This only happens when the input was a single mode line
|
|
* that got expanded into a multi mode line due to expansion
|
|
* issues. The sender could be a local client, but could also
|
|
* be a remote server like UnrealIRCd 5.
|
|
* We can't use the same msgid multiple times, and (if the
|
|
* sender was a server) then we can't use the original msgid
|
|
* either, not for both events and not for the first event
|
|
* (since the modeline differs for all events, including first).
|
|
* Obviously message ids must be unique for the event...
|
|
* So here is our special version again, just like we use in
|
|
* SJOIN and elsewhere sporadically for cases like this:
|
|
*/
|
|
new_message_special(client, recv_mtags, &mtags, ":%s MODE %s %s %s", client->name, channel->name, modebuf, parabuf);
|
|
}
|
|
|
|
/* IMPORTANT: if you return, don't forget to free mtags!! */
|
|
|
|
if (MyConnect(client))
|
|
RunHook(HOOKTYPE_PRE_LOCAL_CHANMODE, client, channel, mtags, modebuf, parabuf, sendts, samode);
|
|
else
|
|
RunHook(HOOKTYPE_PRE_REMOTE_CHANMODE, client, channel, mtags, modebuf, parabuf, sendts, samode);
|
|
|
|
/* opermode for twimodesystem --sts */
|
|
#ifndef NO_OPEROVERRIDE
|
|
if ((opermode == 1) && IsUser(client))
|
|
{
|
|
mode_operoverride_msg(client, channel, modebuf, parabuf);
|
|
|
|
sendts = 0;
|
|
}
|
|
#endif
|
|
|
|
sendto_channel(channel, client, NULL, 0, 0, SEND_LOCAL, mtags,
|
|
":%s MODE %s %s %s",
|
|
client->name, channel->name, modebuf, parabuf);
|
|
|
|
if (IsServer(client) || IsMe(client))
|
|
{
|
|
sendto_server(client, 0, 0, mtags,
|
|
":%s MODE %s %s %s %lld",
|
|
client->id, channel->name,
|
|
modebuf, parabuf,
|
|
(sendts != -1) ? (long long)sendts : 0LL);
|
|
} else
|
|
{
|
|
sendto_server(client, 0, 0, mtags,
|
|
":%s MODE %s %s %s",
|
|
client->id, channel->name,
|
|
modebuf, parabuf);
|
|
}
|
|
|
|
if (MyConnect(client))
|
|
RunHook(HOOKTYPE_LOCAL_CHANMODE, client, channel, mtags, modebuf, parabuf, sendts, samode, &should_destroy);
|
|
else
|
|
RunHook(HOOKTYPE_REMOTE_CHANMODE, client, channel, mtags, modebuf, parabuf, sendts, samode, &should_destroy);
|
|
|
|
free_message_tags(mtags);
|
|
|
|
if (should_destroy)
|
|
break; /* eg channel went -P with nobody in it. 'channel' is freed now */
|
|
}
|
|
safe_free_multilinemode(m);
|
|
opermode = 0;
|
|
}
|
|
|
|
/* make_mode_str -- written by binary
|
|
* Reconstructs the mode string, to make it look clean. mode_buf will
|
|
* contain the +x-y stuff, and the parabuf will contain the parameters.
|
|
*/
|
|
MultiLineMode *make_mode_str(Client *client, Channel *channel, Cmode_t oldem, int pcount, char pvar[MAXMODEPARAMS][MODEBUFLEN + 3])
|
|
{
|
|
Cmode *cm;
|
|
int what;
|
|
int cnt, z, i;
|
|
MultiLineMode *m = safe_alloc(sizeof(MultiLineMode));
|
|
int curr = 0;
|
|
int initial_len;
|
|
|
|
if (client->user)
|
|
initial_len = strlen(client->name) + strlen(client->user->username) + strlen(GetHost(client)) + strlen(channel->name) + 11;
|
|
else
|
|
initial_len = strlen(client->name) + strlen(channel->name) + 11;
|
|
|
|
/* Reserve room for the first element */
|
|
curr = 0;
|
|
m->modeline[curr] = safe_alloc(BUFSIZE);
|
|
m->paramline[curr] = safe_alloc(BUFSIZE);
|
|
m->numlines = curr + 1;
|
|
what = 0;
|
|
|
|
/* The first element will be filled with all paramless modes.
|
|
* That is: both the ones that got set, and the ones that got unset.
|
|
* This will always fit.
|
|
*/
|
|
|
|
/* Which paramless modes got set? Eg +snt */
|
|
for (cm = channelmodes; cm; cm = cm->next)
|
|
{
|
|
if (!cm->letter || cm->paracount)
|
|
continue;
|
|
/* have it now and didn't have it before? */
|
|
if ((channel->mode.mode & cm->mode) &&
|
|
!(oldem & cm->mode))
|
|
{
|
|
if (what != MODE_ADD)
|
|
{
|
|
strlcat_letter(m->modeline[curr], '+', BUFSIZE);
|
|
what = MODE_ADD;
|
|
}
|
|
strlcat_letter(m->modeline[curr], cm->letter, BUFSIZE);
|
|
}
|
|
}
|
|
|
|
/* Which paramless modes got unset? Eg -r */
|
|
for (cm = channelmodes; cm; cm = cm->next)
|
|
{
|
|
if (!cm->letter || cm->unset_with_param)
|
|
continue;
|
|
/* don't have it now and did have it before */
|
|
if (!(channel->mode.mode & cm->mode) && (oldem & cm->mode))
|
|
{
|
|
if (what != MODE_DEL)
|
|
{
|
|
strlcat_letter(m->modeline[curr], '-', BUFSIZE);
|
|
what = MODE_DEL;
|
|
}
|
|
strlcat_letter(m->modeline[curr], cm->letter, BUFSIZE);
|
|
}
|
|
}
|
|
|
|
/* Now for parameter modes we do both addition and removal. Eg +b-e ban!x@y exempt!z@z */
|
|
for (cnt = 0; cnt < pcount; cnt++)
|
|
{
|
|
if ((strlen(m->modeline[curr]) + strlen(m->paramline[curr]) + strlen(&pvar[cnt][2])) > 507)
|
|
{
|
|
if (curr == MAXMULTILINEMODES)
|
|
{
|
|
/* Should be impossible.. */
|
|
unreal_log(ULOG_ERROR, "mode", "MODE_MULTILINE_EXCEEDED", client,
|
|
"A mode string caused an avalanche effect of more than $max_multiline_modes modes "
|
|
"in channel $channel. Caused by client $client. Expect a desync.",
|
|
log_data_integer("max_multiline_modes", MAXMULTILINEMODES),
|
|
log_data_channel("channel", channel));
|
|
break;
|
|
}
|
|
curr++;
|
|
m->modeline[curr] = safe_alloc(BUFSIZE);
|
|
m->paramline[curr] = safe_alloc(BUFSIZE);
|
|
m->numlines = curr + 1;
|
|
what = 0;
|
|
}
|
|
if ((*(pvar[cnt]) == '+') && what != MODE_ADD)
|
|
{
|
|
strlcat_letter(m->modeline[curr], '+', BUFSIZE);
|
|
what = MODE_ADD;
|
|
}
|
|
if ((*(pvar[cnt]) == '-') && what != MODE_DEL)
|
|
{
|
|
strlcat_letter(m->modeline[curr], '-', BUFSIZE);
|
|
what = MODE_DEL;
|
|
}
|
|
strlcat_letter(m->modeline[curr], *(pvar[cnt] + 1), BUFSIZE);
|
|
strlcat(m->paramline[curr], &pvar[cnt][2], BUFSIZE);
|
|
strlcat_letter(m->paramline[curr], ' ', BUFSIZE);
|
|
}
|
|
|
|
for (i = 0; i <= curr; i++)
|
|
{
|
|
char *para_buf = m->paramline[i];
|
|
/* Strip off useless space character (' ') at the end, if there is any */
|
|
z = strlen(para_buf);
|
|
if ((z > 0) && (para_buf[z - 1] == ' '))
|
|
para_buf[z - 1] = '\0';
|
|
}
|
|
|
|
/* Now check for completely empty mode: */
|
|
if ((curr == 0) && empty_mode(m->modeline[0]))
|
|
{
|
|
/* And convert it to a NULL result */
|
|
safe_free_multilinemode(m);
|
|
return NULL;
|
|
}
|
|
|
|
return m;
|
|
}
|
|
|
|
const char *mode_ban_handler(Client *client, Channel *channel, const char *param, int what, int extbtype, Ban **banlist)
|
|
{
|
|
const char *tmpstr;
|
|
BanContext *b;
|
|
|
|
tmpstr = clean_ban_mask(param, what, extbtype, client, channel, 0);
|
|
if (BadPtr(tmpstr))
|
|
{
|
|
/* Invalid ban. See if we can send an error about that (only for extbans) */
|
|
if (MyUser(client) && is_extended_ban(param))
|
|
{
|
|
const char *nextbanstr;
|
|
Extban *extban = findmod_by_bantype(param, &nextbanstr);
|
|
BanContext *b;
|
|
|
|
b = safe_alloc(sizeof(BanContext));
|
|
b->client = client;
|
|
b->channel = channel;
|
|
b->banstr = nextbanstr;
|
|
b->is_ok_check = EXBCHK_PARAM;
|
|
b->what = what;
|
|
b->ban_type = extbtype;
|
|
if (extban && extban->is_ok)
|
|
extban->is_ok(b);
|
|
safe_free(b);
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
if (MyUser(client) && is_extended_ban(param))
|
|
{
|
|
/* extban: check access if needed */
|
|
const char *nextbanstr;
|
|
Extban *extban = findmod_by_bantype(tmpstr, &nextbanstr);
|
|
if (extban)
|
|
{
|
|
if ((extbtype == EXBTYPE_INVEX) && !(extban->options & EXTBOPT_INVEX))
|
|
return NULL; /* this extended ban type does not support INVEX */
|
|
if (extban->is_ok)
|
|
{
|
|
BanContext *b = safe_alloc(sizeof(BanContext));
|
|
b->client = client;
|
|
b->channel = channel;
|
|
b->what = what;
|
|
b->ban_type = extbtype;
|
|
|
|
b->is_ok_check = EXBCHK_ACCESS;
|
|
b->banstr = nextbanstr;
|
|
if (!extban->is_ok(b))
|
|
{
|
|
if (ValidatePermissionsForPath("channel:override:mode:extban", client, NULL, channel, NULL))
|
|
{
|
|
/* TODO: send operoverride notice */
|
|
} else
|
|
{
|
|
b->banstr = nextbanstr;
|
|
b->is_ok_check = EXBCHK_ACCESS_ERR;
|
|
extban->is_ok(b);
|
|
safe_free(b);
|
|
return NULL;
|
|
}
|
|
}
|
|
b->banstr = nextbanstr;
|
|
b->is_ok_check = EXBCHK_PARAM;
|
|
if (!extban->is_ok(b))
|
|
{
|
|
safe_free(b);
|
|
return NULL;
|
|
}
|
|
safe_free(b);
|
|
}
|
|
}
|
|
}
|
|
|
|
if ((what == MODE_ADD && (add_listmode(banlist, client, channel, tmpstr) != 1)) ||
|
|
(what == MODE_DEL && del_listmode(banlist, channel, tmpstr)))
|
|
{
|
|
return NULL; /* ban to be added already exists, or ban to be deleted does not exist */
|
|
}
|
|
|
|
return tmpstr;
|
|
}
|
|
|
|
/** Write the result of a mode change.
|
|
* This is used by do_mode_char_list_mode(), do_mode_char_member_mode()
|
|
* and do_extmode_char().
|
|
* The result is later used by make_mode_str() to create the
|
|
* actual MODE line to be broadcasted to the channel and other servers.
|
|
*/
|
|
void do_mode_char_write(char pvar[MAXMODEPARAMS][MODEBUFLEN + 3], u_int *pcount, u_int what, char modeletter, const char *str)
|
|
{
|
|
/* Caller should have made sure there was room! */
|
|
if (*pcount >= MAXMODEPARAMS)
|
|
#ifdef DEBUGMODE
|
|
abort();
|
|
#else
|
|
return;
|
|
#endif
|
|
|
|
ircsnprintf(pvar[*pcount], MODEBUFLEN + 3,
|
|
"%c%c%s",
|
|
(what == MODE_ADD) ? '+' : '-',
|
|
modeletter,
|
|
str);
|
|
(*pcount)++;
|
|
}
|
|
|
|
int do_mode_char_list_mode(Channel *channel, long modetype, char modechar, const char *param,
|
|
u_int what, Client *client,
|
|
u_int *pcount, char pvar[MAXMODEPARAMS][MODEBUFLEN + 3])
|
|
{
|
|
const char *tmpstr;
|
|
|
|
/* Check if there is a parameter present */
|
|
if (!param || *pcount >= MAXMODEPARAMS)
|
|
return 0;
|
|
|
|
switch (modetype)
|
|
{
|
|
case MODE_BAN:
|
|
if (!(tmpstr = mode_ban_handler(client, channel, param, what, EXBTYPE_BAN, &channel->banlist)))
|
|
break; /* rejected or duplicate */
|
|
do_mode_char_write(pvar, pcount, what, modechar, tmpstr);
|
|
break;
|
|
case MODE_EXCEPT:
|
|
if (!(tmpstr = mode_ban_handler(client, channel, param, what, EXBTYPE_EXCEPT, &channel->exlist)))
|
|
break; /* rejected or duplicate */
|
|
do_mode_char_write(pvar, pcount, what, modechar, tmpstr);
|
|
break;
|
|
case MODE_INVEX:
|
|
if (!(tmpstr = mode_ban_handler(client, channel, param, what, EXBTYPE_INVEX, &channel->invexlist)))
|
|
break; /* rejected or duplicate */
|
|
do_mode_char_write(pvar, pcount, what, modechar, tmpstr);
|
|
break;
|
|
}
|
|
return 1;
|
|
}
|
|
|
|
/** Check access and if granted, set the extended chanmode to the requested value in memory.
|
|
* @returns amount of params eaten (0 or 1)
|
|
*/
|
|
int do_extmode_char(Channel *channel, Cmode *handler, const char *param, u_int what,
|
|
Client *client, u_int *pcount, char pvar[MAXMODEPARAMS][MODEBUFLEN + 3])
|
|
{
|
|
int paracnt = (what == MODE_ADD) ? handler->paracount : 0;
|
|
char mode = handler->letter;
|
|
int x;
|
|
const char *morphed;
|
|
|
|
if ((what == MODE_DEL) && handler->unset_with_param)
|
|
paracnt = 1; /* there's always an exception! */
|
|
|
|
/* Expected a param and it isn't there? */
|
|
if (paracnt && (!param || (*pcount >= MAXMODEPARAMS)))
|
|
return 0;
|
|
|
|
/* Prevent remote users from setting local channel modes */
|
|
if (handler->local && !MyUser(client))
|
|
return paracnt;
|
|
|
|
if (MyUser(client))
|
|
{
|
|
x = handler->is_ok(client, channel, mode, param, EXCHK_ACCESS, what);
|
|
if ((x == EX_ALWAYS_DENY) ||
|
|
((x == EX_DENY) && !op_can_override("channel:override:mode:del", client, channel, handler) && !samode_in_progress))
|
|
{
|
|
handler->is_ok(client, channel, mode, param, EXCHK_ACCESS_ERR, what);
|
|
return paracnt; /* Denied & error msg sent */
|
|
}
|
|
if ((x == EX_DENY) && !samode_in_progress)
|
|
opermode = 1; /* override in progress... */
|
|
} else
|
|
{
|
|
/* remote user: we only need to check if we need to generate an operoverride msg */
|
|
if (!IsULine(client) && IsUser(client) && op_can_override("channel:override:mode:del", client, channel, handler) &&
|
|
(handler->is_ok(client, channel, mode, param, EXCHK_ACCESS, what) != EX_ALLOW))
|
|
{
|
|
opermode = 1; /* override in progress... */
|
|
}
|
|
}
|
|
|
|
if (handler->type == CMODE_MEMBER)
|
|
{
|
|
do_mode_char_member_mode_new(channel, handler, param, what, client, pcount, pvar);
|
|
return 1;
|
|
}
|
|
|
|
/* Check for multiple changes in 1 command (like +y-y+y 1 2, or +yy 1 2). */
|
|
for (x = 0; x < *pcount; x++)
|
|
{
|
|
if (pvar[x][1] == handler->letter)
|
|
{
|
|
/* this is different than the old chanmode system, coz:
|
|
* "mode #chan +kkL #a #b #c" will get "+kL #a #b" which is wrong :p.
|
|
* we do eat the parameter. -- Syzop
|
|
*/
|
|
return paracnt;
|
|
}
|
|
}
|
|
|
|
/* w00t... a parameter mode */
|
|
if (handler->paracount)
|
|
{
|
|
if (what == MODE_DEL)
|
|
{
|
|
if (!(channel->mode.mode & handler->mode))
|
|
return paracnt; /* There's nothing to remove! */
|
|
if (handler->unset_with_param)
|
|
{
|
|
/* Special extended channel mode requiring a parameter on unset.
|
|
* Any provided parameter is ok, the current one (that is set) will be used.
|
|
*/
|
|
do_mode_char_write(pvar, pcount, what, handler->letter, cm_getparameter(channel, handler->letter));
|
|
} else
|
|
{
|
|
/* Normal extended channel mode: deleting is just -X, no parameter.
|
|
* Nothing needs to be done here.
|
|
*/
|
|
}
|
|
} else
|
|
{
|
|
/* add: is the parameter ok? */
|
|
if (handler->is_ok(client, channel, mode, param, EXCHK_PARAM, what) == FALSE)
|
|
return paracnt; /* rejected by is_ok */
|
|
|
|
morphed = handler->conv_param(param, client, channel);
|
|
if (!morphed)
|
|
return paracnt; /* rejected by conv_param */
|
|
|
|
/* is it already set at the same value? if so, ignore it. */
|
|
if (channel->mode.mode & handler->mode)
|
|
{
|
|
const char *now, *requested;
|
|
char flag = handler->letter;
|
|
now = cm_getparameter(channel, flag);
|
|
requested = handler->conv_param(param, client, channel);
|
|
if (now && requested && !strcmp(now, requested))
|
|
return paracnt; /* ignore... */
|
|
}
|
|
do_mode_char_write(pvar, pcount, what, handler->letter, handler->conv_param(param, client, channel));
|
|
param = morphed; /* set param to converted parameter. */
|
|
}
|
|
}
|
|
|
|
if (what == MODE_ADD)
|
|
{ /* + */
|
|
channel->mode.mode |= handler->mode;
|
|
if (handler->paracount)
|
|
cm_putparameter(channel, handler->letter, param);
|
|
RunHook(HOOKTYPE_MODECHAR_ADD, channel, (int)mode);
|
|
} else
|
|
{ /* - */
|
|
channel->mode.mode &= ~(handler->mode);
|
|
RunHook(HOOKTYPE_MODECHAR_DEL, channel, (int)mode);
|
|
if (handler->paracount)
|
|
cm_freeparameter(channel, handler->letter);
|
|
}
|
|
return paracnt;
|
|
}
|
|
|
|
/** Set or unset a mode on a member (eg +vhoaq/-vhoaq) */
|
|
void do_mode_char_member_mode_new(Channel *channel, Cmode *handler, const char *param, u_int what,
|
|
Client *client, u_int *pcount, char pvar[MAXMODEPARAMS][MODEBUFLEN + 3])
|
|
{
|
|
Member *member = NULL;
|
|
Membership *membership = NULL;
|
|
Client *target;
|
|
int chasing = 0;
|
|
Hook *h;
|
|
char c[2];
|
|
char modechar = handler->letter;
|
|
|
|
if (!(target = find_chasing(client, param, &chasing)))
|
|
return;
|
|
|
|
if (!target->user)
|
|
return;
|
|
|
|
if (!(membership = find_membership_link(target->user->channel, channel)))
|
|
{
|
|
sendnumeric(client, ERR_USERNOTINCHANNEL, target->name, channel->name);
|
|
return;
|
|
}
|
|
|
|
member = membership->related;
|
|
|
|
if ((what == MODE_ADD) && strchr(member->member_modes, modechar))
|
|
return; /* already set */
|
|
|
|
if ((what == MODE_DEL) && !strchr(member->member_modes, modechar))
|
|
return; /* already unset */
|
|
|
|
/* HOOKTYPE_MODE_DEOP code */
|
|
if (what == MODE_DEL)
|
|
{
|
|
int ret = EX_ALLOW;
|
|
const char *badmode = NULL;
|
|
Hook *h;
|
|
const char *my_access;
|
|
Membership *my_membership;
|
|
|
|
/* Set "my_access" to access flags of the requestor */
|
|
if (IsUser(client) && (my_membership = find_membership_link(client->user->channel, channel)))
|
|
my_access = my_membership->member_modes; /* client */
|
|
else
|
|
my_access = ""; /* server */
|
|
|
|
for (h = Hooks[HOOKTYPE_MODE_DEOP]; h; h = h->next)
|
|
{
|
|
int n = (*(h->func.intfunc))(client, target, channel, what, modechar, my_access, member->member_modes, &badmode);
|
|
if (n == EX_DENY)
|
|
{
|
|
ret = n;
|
|
} else if (n == EX_ALWAYS_DENY)
|
|
{
|
|
ret = n;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (ret == EX_ALWAYS_DENY)
|
|
{
|
|
if (MyUser(client) && badmode)
|
|
sendto_one(client, NULL, "%s", badmode); /* send error message, if any */
|
|
|
|
if (MyUser(client))
|
|
return; /* stop processing this mode */
|
|
}
|
|
|
|
/* This probably should work but is completely untested (the operoverride stuff, I mean): */
|
|
if (ret == EX_DENY)
|
|
{
|
|
if (!op_can_override("channel:override:mode:del", client, channel, handler))
|
|
{
|
|
if (badmode)
|
|
sendto_one(client, NULL, "%s", badmode); /* send error message, if any */
|
|
return; /* stop processing this mode */
|
|
} else
|
|
{
|
|
opermode = 1;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (what == MODE_ADD)
|
|
{
|
|
if (strchr(member->member_modes, modechar))
|
|
return; /* already set */
|
|
/* Set the mode */
|
|
add_member_mode_fast(member, membership, modechar);
|
|
} else
|
|
{
|
|
if (!strchr(member->member_modes, modechar))
|
|
return; /* already unset */
|
|
del_member_mode_fast(member, membership, modechar);
|
|
}
|
|
|
|
/* And write out the mode */
|
|
do_mode_char_write(pvar, pcount, what, modechar, target->name);
|
|
}
|
|
|
|
/** In 2003 I introduced PROTOCTL CHANMODES= so remote servers (and services)
|
|
* could deal with unknown "parameter eating" channel modes, minimizing desyncs.
|
|
* Now, in 2015, I finally added the code to deal with this. -- Syzop
|
|
*/
|
|
int paracount_for_chanmode_from_server(Client *client, u_int what, char mode)
|
|
{
|
|
if (MyUser(client))
|
|
return 0; /* no server, we have no idea, assume 0 paracount */
|
|
|
|
if (!client->server)
|
|
{
|
|
/* If it's from a remote client then figure out from which "uplink" we
|
|
* received this MODE. The uplink is the directly-connected-server to us
|
|
* and may differ from the server the user is actually on. This is correct.
|
|
*/
|
|
if (!client->direction || !client->direction->server)
|
|
return 0;
|
|
client = client->direction;
|
|
}
|
|
|
|
if (client->server->features.chanmodes[0] && strchr(client->server->features.chanmodes[0], mode))
|
|
return 1; /* 1 parameter for set, 1 parameter for unset */
|
|
|
|
if (client->server->features.chanmodes[1] && strchr(client->server->features.chanmodes[1], mode))
|
|
return 1; /* 1 parameter for set, 1 parameter for unset */
|
|
|
|
if (client->server->features.chanmodes[2] && strchr(client->server->features.chanmodes[2], mode))
|
|
return (what == MODE_ADD) ? 1 : 0; /* 1 parameter for set, no parameter for unset */
|
|
|
|
if (client->server->features.chanmodes[3] && strchr(client->server->features.chanmodes[3], mode))
|
|
return 0; /* no parameter for set, no parameter for unset */
|
|
|
|
if (mode == '&')
|
|
return 0; /* & indicates bounce, it is not an actual mode character */
|
|
|
|
if (mode == 'F')
|
|
return (what == MODE_ADD) ? 1 : 0; /* Future compatibility */
|
|
|
|
/* If we end up here it means we have no idea if it is a parameter-eating or paramless
|
|
* channel mode. That's actually pretty bad. This shouldn't happen since CHANMODES=
|
|
* is sent since 2003 and the (often also required) EAUTH PROTOCTL is in there since 2010.
|
|
*/
|
|
unreal_log(ULOG_WARNING, "mode", "REMOTE_UNKNOWN_CHANNEL_MODE", client,
|
|
"Server $client sent us an unknown channel mode $what$mode_character!",
|
|
log_data_string("what", ((what == MODE_ADD) ? "+" : "-")),
|
|
log_data_char("mode_character", mode));
|
|
|
|
return 0;
|
|
}
|
|
|
|
/** Quick way to find out parameter count for a channel mode.
|
|
* Only for LOCAL mode requests. For remote modes use
|
|
* paracount_for_chanmode_from_server() instead.
|
|
*/
|
|
int paracount_for_chanmode(u_int what, char mode)
|
|
{
|
|
if (me.server->features.chanmodes[0] && strchr(me.server->features.chanmodes[0], mode))
|
|
return 1; /* 1 parameter for set, 1 parameter for unset */
|
|
|
|
if (me.server->features.chanmodes[1] && strchr(me.server->features.chanmodes[1], mode))
|
|
return 1; /* 1 parameter for set, 1 parameter for unset */
|
|
|
|
if (me.server->features.chanmodes[2] && strchr(me.server->features.chanmodes[2], mode))
|
|
return (what == MODE_ADD) ? 1 : 0; /* 1 parameter for set, no parameter for unset */
|
|
|
|
if (me.server->features.chanmodes[3] && strchr(me.server->features.chanmodes[3], mode))
|
|
return 0; /* no parameter for set, no parameter for unset */
|
|
|
|
/* Not found: */
|
|
return 0;
|
|
}
|
|
|
|
MultiLineMode *_set_mode(Channel *channel, Client *client, int parc, const char *parv[], u_int *pcount,
|
|
char pvar[MAXMODEPARAMS][MODEBUFLEN + 3])
|
|
{
|
|
Cmode *cm = NULL;
|
|
MultiLineMode *mlm = NULL;
|
|
const char *curchr;
|
|
const char *argument;
|
|
char argumentbuf[MODEBUFLEN + 1];
|
|
u_int what = MODE_ADD;
|
|
long modetype = 0;
|
|
int paracount = 1;
|
|
#ifdef DEVELOP
|
|
char *tmpo = NULL;
|
|
#endif
|
|
CoreChannelModeTable *tab = &corechannelmodetable[0];
|
|
CoreChannelModeTable foundat;
|
|
int found = 0;
|
|
int sent_mlock_warning = 0;
|
|
int checkrestr = 0, warnrestr = 1;
|
|
Cmode_t oldem;
|
|
paracount = 1;
|
|
*pcount = 0;
|
|
|
|
oldem = channel->mode.mode;
|
|
if (RESTRICT_CHANNELMODES && !ValidatePermissionsForPath("immune:restrict-channelmodes", client, NULL, channel, NULL)) /* "cache" this */
|
|
checkrestr = 1;
|
|
|
|
for (curchr = parv[0]; *curchr; curchr++)
|
|
{
|
|
switch (*curchr)
|
|
{
|
|
case '+':
|
|
what = MODE_ADD;
|
|
break;
|
|
case '-':
|
|
what = MODE_DEL;
|
|
break;
|
|
default:
|
|
if (MyUser(client) && channel->mode_lock && strchr(channel->mode_lock, *curchr) != NULL)
|
|
{
|
|
if (!IsOper(client) || find_server(SERVICES_NAME, NULL) ||
|
|
!ValidatePermissionsForPath("channel:override:mlock", client, NULL, channel, NULL))
|
|
{
|
|
if (!sent_mlock_warning)
|
|
{
|
|
sendnumeric(client, ERR_MLOCKRESTRICTED, channel->name, *curchr, channel->mode_lock);
|
|
sent_mlock_warning++;
|
|
}
|
|
continue;
|
|
}
|
|
}
|
|
found = 0;
|
|
tab = &corechannelmodetable[0];
|
|
while ((tab->mode != 0x0) && found == 0)
|
|
{
|
|
if (tab->flag == *curchr)
|
|
{
|
|
found = 1;
|
|
foundat = *tab;
|
|
}
|
|
tab++;
|
|
}
|
|
if (found == 1)
|
|
{
|
|
modetype = foundat.mode;
|
|
} else
|
|
{
|
|
/* Maybe in extmodes */
|
|
for (cm = channelmodes; cm; cm = cm->next)
|
|
{
|
|
if (cm->letter == *curchr)
|
|
{
|
|
found = 2;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
if (found == 0) /* Mode char unknown */
|
|
{
|
|
if (!MyUser(client))
|
|
paracount += paracount_for_chanmode_from_server(client, what, *curchr);
|
|
else
|
|
sendnumeric(client, ERR_UNKNOWNMODE, *curchr);
|
|
break;
|
|
}
|
|
|
|
if (checkrestr && strchr(RESTRICT_CHANNELMODES, *curchr))
|
|
{
|
|
if (warnrestr)
|
|
{
|
|
sendnotice(client, "Setting/removing of channelmode(s) '%s' has been disabled.",
|
|
RESTRICT_CHANNELMODES);
|
|
warnrestr = 0;
|
|
}
|
|
paracount += paracount_for_chanmode(what, *curchr);
|
|
break;
|
|
}
|
|
|
|
if ((paracount < parc) && parv[paracount])
|
|
{
|
|
strlcpy(argumentbuf, parv[paracount], sizeof(argumentbuf));
|
|
argument = argumentbuf;
|
|
} else
|
|
{
|
|
argument = NULL;
|
|
}
|
|
|
|
if (found == 1)
|
|
paracount += do_mode_char_list_mode(channel, modetype, *curchr, argument, what, client, pcount, pvar);
|
|
else if (found == 2)
|
|
paracount += do_extmode_char(channel, cm, argument, what, client, pcount, pvar);
|
|
break;
|
|
} /* switch(*curchr) */
|
|
} /* for loop through mode letters */
|
|
|
|
mlm = make_mode_str(client, channel, oldem, *pcount, pvar);
|
|
return mlm;
|
|
}
|
|
|
|
/*
|
|
* cmd_umode() added 15/10/91 By Darren Reed.
|
|
* parv[1] - username to change mode for
|
|
* parv[2] - modes to change
|
|
*/
|
|
CMD_FUNC(_cmd_umode)
|
|
{
|
|
Umode *um;
|
|
const char *m;
|
|
Client *acptr;
|
|
int what;
|
|
long oldumodes = 0;
|
|
char oldsnomask[64];
|
|
const char *restrict_usermodes = NULL;
|
|
/* (small note: keep 'what' as an int. -- Syzop). */
|
|
short rpterror = 0, umode_restrict_err = 0, chk_restrict = 0, modex_err = 0;
|
|
|
|
what = MODE_ADD;
|
|
*oldsnomask = '\0';
|
|
|
|
/* If you plan to use 'recv_mtags' here, then you will need to change
|
|
* set_user_modes_dont_spread() to not pass NULL, and revert to do_cmd()
|
|
* there again. (Which has performance effects)
|
|
*/
|
|
recv_mtags = NULL;
|
|
|
|
if (parc < 2)
|
|
{
|
|
sendnumeric(client, ERR_NEEDMOREPARAMS, "MODE");
|
|
return;
|
|
}
|
|
|
|
if (!(acptr = find_user(parv[1], NULL)))
|
|
{
|
|
if (MyConnect(client))
|
|
{
|
|
sendnumeric(client, ERR_NOSUCHNICK, parv[1]);
|
|
}
|
|
return;
|
|
}
|
|
if (acptr != client)
|
|
{
|
|
sendnumeric(client, ERR_USERSDONTMATCH);
|
|
return;
|
|
}
|
|
|
|
if (parc < 3)
|
|
{
|
|
sendnumeric(client, RPL_UMODEIS, get_usermode_string(client));
|
|
if (client->user->snomask)
|
|
sendnumeric(client, RPL_SNOMASK, client->user->snomask);
|
|
return;
|
|
}
|
|
|
|
userhost_save_current(client); /* save host, in case we do any +x/-x or similar */
|
|
|
|
oldumodes = client->umodes;
|
|
|
|
if (MyUser(client) && !ValidatePermissionsForPath("immune:restrict-usermodes", client, NULL, NULL, NULL))
|
|
restrict_usermodes = get_setting_for_user_string(client, SET_RESTRICT_USERMODES);
|
|
|
|
if (client->user->snomask)
|
|
strlcpy(oldsnomask, client->user->snomask, sizeof(oldsnomask));
|
|
|
|
/*
|
|
* parse mode change string(s)
|
|
*/
|
|
for (m = parv[2]; *m; m++)
|
|
{
|
|
if (restrict_usermodes && strchr(restrict_usermodes, *m))
|
|
{
|
|
if (!umode_restrict_err)
|
|
{
|
|
sendnotice(client, "Setting/removing of usermode(s) '%s' has been disabled.",
|
|
restrict_usermodes);
|
|
umode_restrict_err = 1;
|
|
}
|
|
continue;
|
|
}
|
|
switch (*m)
|
|
{
|
|
case '+':
|
|
what = MODE_ADD;
|
|
break;
|
|
case '-':
|
|
what = MODE_DEL;
|
|
break;
|
|
/* we may not get these,
|
|
* but they shouldnt be in default
|
|
*/
|
|
case ' ':
|
|
case '\t':
|
|
break;
|
|
case 's':
|
|
if (what == MODE_DEL)
|
|
{
|
|
if (parc >= 4 && client->user->snomask)
|
|
{
|
|
set_snomask(client, parv[3]);
|
|
if (client->user->snomask == NULL)
|
|
goto def;
|
|
break;
|
|
} else
|
|
{
|
|
set_snomask(client, NULL);
|
|
goto def;
|
|
}
|
|
}
|
|
if ((what == MODE_ADD) && IsOper(client))
|
|
{
|
|
if (parc < 4)
|
|
set_snomask(client, OPER_SNOMASKS);
|
|
else
|
|
set_snomask(client, parv[3]);
|
|
goto def;
|
|
}
|
|
break;
|
|
case 'o':
|
|
case 'O':
|
|
if (IsQuarantined(client->direction))
|
|
{
|
|
unreal_log(ULOG_INFO, "mode", "OPER_KILLED_QUARANTINE", client,
|
|
"QUARANTINE: Oper $client.details on server $client.user.servername killed, due to quarantine");
|
|
sendto_server(NULL, 0, 0, NULL, ":%s KILL %s :Quarantined: no oper privileges allowed", me.id, client->name);
|
|
exit_client(client, NULL, "Quarantined: no oper privileges allowed");
|
|
return;
|
|
}
|
|
/* A local user trying to set himself +o/+O is denied here.
|
|
* A while later (outside this loop) it is handled as well (and +C, +N, etc too)
|
|
* but we need to take care here too because it might cause problems
|
|
* that's just asking for bugs! -- Syzop.
|
|
*/
|
|
if (MyUser(client) && (what == MODE_ADD)) /* Someone setting himself +o? Deny it. */
|
|
break;
|
|
goto def;
|
|
case 't':
|
|
case 'x':
|
|
/* set::anti-flood::vhost-flood */
|
|
if (MyUser(client))
|
|
{
|
|
if ((what == MODE_DEL) && !ValidatePermissionsForPath("immune:vhost-flood", client, NULL, NULL, NULL) &&
|
|
flood_limit_exceeded(client, FLD_VHOST))
|
|
{
|
|
/* Throttle... */
|
|
if (!modex_err)
|
|
{
|
|
sendnotice(client, "*** Setting -%c too fast. Please try again later.", *m);
|
|
modex_err = 1;
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
|
|
switch (UHOST_ALLOWED)
|
|
{
|
|
case UHALLOW_ALWAYS:
|
|
goto def;
|
|
case UHALLOW_NEVER:
|
|
if (MyUser(client))
|
|
{
|
|
if (!modex_err)
|
|
{
|
|
sendnotice(client, "*** Setting %c%c is disabled",
|
|
what == MODE_ADD ? '+' : '-', *m);
|
|
modex_err = 1;
|
|
}
|
|
break;
|
|
}
|
|
goto def;
|
|
case UHALLOW_NOCHANS:
|
|
if (MyUser(client) && client->user->joined)
|
|
{
|
|
if (!modex_err)
|
|
{
|
|
sendnotice(client, "*** Setting %c%c can not be done while you are on channels",
|
|
what == MODE_ADD ? '+' : '-', *m);
|
|
modex_err = 1;
|
|
}
|
|
break;
|
|
}
|
|
goto def;
|
|
case UHALLOW_REJOIN:
|
|
/* Handled later */
|
|
goto def;
|
|
}
|
|
break;
|
|
default:
|
|
def:
|
|
for (um = usermodes; um; um = um->next)
|
|
{
|
|
if (um->letter == *m)
|
|
{
|
|
if (um->allowed && !um->allowed(client, what))
|
|
break;
|
|
if (what == MODE_ADD)
|
|
client->umodes |= um->mode;
|
|
else
|
|
client->umodes &= ~um->mode;
|
|
break;
|
|
}
|
|
}
|
|
if (!um && MyConnect(client) && !rpterror)
|
|
{
|
|
sendnumeric(client, ERR_UMODEUNKNOWNFLAG);
|
|
rpterror = 1;
|
|
}
|
|
break;
|
|
} /* switch */
|
|
} /* for */
|
|
|
|
/* Don't let non-ircops set ircop-only modes or snomasks */
|
|
if (!ValidatePermissionsForPath("self:opermodes", client, NULL, NULL, NULL))
|
|
{
|
|
if ((oldumodes & UMODE_OPER) && IsOper(client))
|
|
{
|
|
/* User is an oper but does not have the self:opermodes capability.
|
|
* This only happens for heavily restricted IRCOps.
|
|
* Fixes bug https://bugs.unrealircd.org/view.php?id=5130
|
|
*/
|
|
int i;
|
|
|
|
/* MODES */
|
|
for (um = usermodes; um; um = um->next)
|
|
{
|
|
if (um->unset_on_deoper)
|
|
{
|
|
/* This is an oper mode. Is it set now and wasn't earlier?
|
|
* then it needs to be stripped, as setting it is not
|
|
* permitted.
|
|
*/
|
|
if ((client->umodes & um->mode) && !(oldumodes & um->mode))
|
|
client->umodes &= ~um->mode; /* remove */
|
|
}
|
|
}
|
|
|
|
/* SNOMASKS: user can delete existing but not add new ones */
|
|
if (client->user->snomask)
|
|
{
|
|
char rerun;
|
|
do
|
|
{
|
|
char *p;
|
|
|
|
rerun = 0;
|
|
for (p = client->user->snomask; *p; p++)
|
|
{
|
|
if (!strchr(oldsnomask, *p))
|
|
{
|
|
/* It is set now, but was not earlier?
|
|
* Then it needs to be stripped, as setting is not permitted.
|
|
* And re-run the loop
|
|
*/
|
|
delletterfromstring(client->user->snomask, *p);
|
|
rerun = 1;
|
|
break;
|
|
}
|
|
}
|
|
} while (rerun);
|
|
/* And make sure an empty snomask ("") becomes a NULL pointer */
|
|
if (client->user->snomask && !*client->user->snomask)
|
|
remove_all_snomasks(client);
|
|
}
|
|
} else
|
|
{
|
|
/* User isn't an ircop at all. The solution is simple: */
|
|
remove_oper_privileges(client, 0);
|
|
}
|
|
}
|
|
|
|
if (MyUser(client) && (client->umodes & UMODE_SECURE) && !IsSecure(client))
|
|
client->umodes &= ~UMODE_SECURE;
|
|
|
|
/* -x translates to -xt (if applicable) */
|
|
if ((oldumodes & UMODE_HIDE) && !IsHidden(client))
|
|
client->umodes &= ~UMODE_SETHOST;
|
|
|
|
/* Vhost unset = unset some other data as well */
|
|
if ((oldumodes & UMODE_SETHOST) && !IsSetHost(client))
|
|
{
|
|
swhois_delete(client, "vhost", "*", &me, NULL);
|
|
}
|
|
|
|
/* +x or -t+x */
|
|
if ((IsHidden(client) && !(oldumodes & UMODE_HIDE)) ||
|
|
((oldumodes & UMODE_SETHOST) && !IsSetHost(client) && IsHidden(client)))
|
|
{
|
|
if (!dontspread)
|
|
sendto_server(client, PROTO_VHP, 0, NULL, ":%s SETHOST :%s",
|
|
client->name, client->user->virthost);
|
|
|
|
/* Set the vhost */
|
|
safe_strdup(client->user->virthost, client->user->cloakedhost);
|
|
|
|
/* Notify */
|
|
userhost_changed(client);
|
|
}
|
|
|
|
/* -x */
|
|
if (!IsHidden(client) && (oldumodes & UMODE_HIDE))
|
|
{
|
|
/* (Re)create the cloaked virthost, because it will be used
|
|
* for ban-checking... free+recreate here because it could have
|
|
* been a vhost for example. -- Syzop
|
|
*/
|
|
safe_strdup(client->user->virthost, client->user->cloakedhost);
|
|
|
|
/* Notify */
|
|
userhost_changed(client);
|
|
}
|
|
/*
|
|
* If I understand what this code is doing correctly...
|
|
* If the user WAS an operator and has now set themselves -o/-O
|
|
* then remove their access, d'oh!
|
|
* In order to allow opers to do stuff like go +o, +h, -o and
|
|
* remain +h, I moved this code below those checks. It should be
|
|
* O.K. The above code just does normal access flag checks. This
|
|
* only changes the operflag access level. -Cabal95
|
|
*/
|
|
if ((oldumodes & UMODE_OPER) && !IsOper(client) && MyConnect(client))
|
|
{
|
|
list_del(&client->special_node);
|
|
if (MyUser(client))
|
|
RunHook(HOOKTYPE_LOCAL_OPER, client, 0, NULL, NULL);
|
|
remove_oper_privileges(client, 0);
|
|
}
|
|
|
|
if (!(oldumodes & UMODE_OPER) && IsOper(client))
|
|
irccounts.operators++;
|
|
|
|
/* deal with opercounts and stuff */
|
|
if ((oldumodes & UMODE_OPER) && !IsOper(client))
|
|
{
|
|
irccounts.operators--;
|
|
VERIFY_OPERCOUNT(client, "umode1");
|
|
} else /* YES this 'else' must be here, otherwise we can decrease twice. fixes opercount bug. */
|
|
if (!(oldumodes & UMODE_HIDEOPER) && IsHideOper(client))
|
|
{
|
|
irccounts.operators--;
|
|
VERIFY_OPERCOUNT(client, "umode2");
|
|
}
|
|
/* end of dealing with opercounts */
|
|
|
|
if ((oldumodes & UMODE_HIDEOPER) && !IsHideOper(client))
|
|
{
|
|
irccounts.operators++;
|
|
}
|
|
if (!(oldumodes & UMODE_INVISIBLE) && IsInvisible(client))
|
|
irccounts.invisible++;
|
|
if ((oldumodes & UMODE_INVISIBLE) && !IsInvisible(client))
|
|
irccounts.invisible--;
|
|
|
|
if (MyConnect(client) && !IsOper(client))
|
|
remove_oper_privileges(client, 0);
|
|
|
|
/*
|
|
* compare new flags with old flags and send string which
|
|
* will cause servers to update correctly.
|
|
*/
|
|
if (oldumodes != client->umodes)
|
|
RunHook(HOOKTYPE_UMODE_CHANGE, client, oldumodes, client->umodes);
|
|
if (dontspread == 0)
|
|
send_umode_out(client, 1, oldumodes);
|
|
|
|
if (MyConnect(client) && client->user->snomask && strcmp(oldsnomask, client->user->snomask))
|
|
sendnumeric(client, RPL_SNOMASK, client->user->snomask);
|
|
}
|
|
|
|
CMD_FUNC(cmd_mlock)
|
|
{
|
|
Channel *channel = NULL;
|
|
time_t t;
|
|
|
|
if ((parc < 3) || BadPtr(parv[2]))
|
|
return;
|
|
|
|
t = (time_t)atol(parv[1]);
|
|
|
|
/* Now, try to find the channel in question */
|
|
channel = find_channel(parv[2]);
|
|
if (!channel)
|
|
return;
|
|
|
|
/* Senders' Channel t is higher, drop it. */
|
|
if (t > channel->creationtime)
|
|
return;
|
|
|
|
if (IsServer(client))
|
|
set_channel_mlock(client, channel, parv[3], TRUE);
|
|
}
|
|
|
|
void mode_operoverride_msg(Client *client, Channel *channel, char *modebuf, char *parabuf)
|
|
{
|
|
char buf[1024];
|
|
|
|
if (empty_mode(modebuf))
|
|
return;
|
|
|
|
/* Internally we have this distinction between modebuf and parabuf,
|
|
* but this makes little sense to maintain in JSON.
|
|
*/
|
|
snprintf(buf, sizeof(buf), "%s %s", modebuf, parabuf);
|
|
|
|
unreal_log(ULOG_INFO, "operoverride", "OPEROVERRIDE_MODE", client,
|
|
"OperOverride: $client.details changed channel mode of $channel to: $channel_mode",
|
|
log_data_string("override_type", "mode"),
|
|
log_data_string("channel_mode", buf),
|
|
log_data_channel("channel", channel));
|
|
}
|
|
|
|
/* Deal with information requests from local users, such as:
|
|
* MODE #chan b Show the ban list
|
|
* MODE #chan e Show the ban exemption list
|
|
* MODE #chan I Show the invite exception list
|
|
* MODE #chan q Show list of channel owners
|
|
* MODE #chan a Show list of channel admins
|
|
* @returns 1 if processed as a mode list (please return),
|
|
* 0 if not (continue with the MODE as it likely is a set request).
|
|
*/
|
|
int list_mode_request(Client *client, Channel *channel, const char *req)
|
|
{
|
|
const char *p;
|
|
Ban *ban;
|
|
Member *member;
|
|
|
|
for (p = req; *p; p++)
|
|
if (strchr("beIqa", *p))
|
|
break;
|
|
|
|
if (!*p)
|
|
return 0; /* not handled, proceed with the MODE set attempt */
|
|
|
|
/* First, check access */
|
|
if (strchr("beI", *p))
|
|
{
|
|
if (!IsMember(client, channel) && !ValidatePermissionsForPath("channel:see:mode:remotebanlist", client, NULL, channel, NULL))
|
|
{
|
|
sendnumeric(client, ERR_NOTONCHANNEL, channel->name);
|
|
return 1; /* handled */
|
|
}
|
|
} else
|
|
{
|
|
if (!IsMember(client, channel) && !ValidatePermissionsForPath("channel:see:mode:remoteownerlist", client, NULL, channel, NULL))
|
|
{
|
|
sendnumeric(client, ERR_NOTONCHANNEL, channel->name);
|
|
return 1; /* handled */
|
|
}
|
|
}
|
|
|
|
switch (*p)
|
|
{
|
|
case 'b':
|
|
for (ban = channel->banlist; ban; ban = ban->next)
|
|
sendnumeric(client, RPL_BANLIST, channel->name, ban->banstr, ban->who, (long long)ban->when);
|
|
sendnumeric(client, RPL_ENDOFBANLIST, channel->name);
|
|
break;
|
|
case 'e':
|
|
for (ban = channel->exlist; ban; ban = ban->next)
|
|
sendnumeric(client, RPL_EXLIST, channel->name, ban->banstr, ban->who, (long long)ban->when);
|
|
sendnumeric(client, RPL_ENDOFEXLIST, channel->name);
|
|
break;
|
|
case 'I':
|
|
for (ban = channel->invexlist; ban; ban = ban->next)
|
|
sendnumeric(client, RPL_INVEXLIST, channel->name, ban->banstr, ban->who, (long long)ban->when);
|
|
sendnumeric(client, RPL_ENDOFINVEXLIST, channel->name);
|
|
break;
|
|
case 'q':
|
|
for (member = channel->members; member; member = member->next)
|
|
if (strchr(member->member_modes, 'q'))
|
|
sendnumeric(client, RPL_QLIST, channel->name, member->client->name);
|
|
sendnumeric(client, RPL_ENDOFQLIST, channel->name);
|
|
break;
|
|
case 'a':
|
|
for (member = channel->members; member; member = member->next)
|
|
if (strchr(member->member_modes, 'a'))
|
|
sendnumeric(client, RPL_ALIST, channel->name, member->client->name);
|
|
sendnumeric(client, RPL_ENDOFALIST, channel->name);
|
|
break;
|
|
}
|
|
|
|
return 1; /* handled */
|
|
}
|
|
|
|
void _set_channel_mode(Channel *channel, MessageTag *mtags, const char *modes, const char *parameters)
|
|
{
|
|
char buf[512];
|
|
char *p, *param;
|
|
int myparc = 1, i;
|
|
char *myparv[512];
|
|
|
|
memset(&myparv, 0, sizeof(myparv));
|
|
myparv[0] = raw_strdup(modes);
|
|
|
|
strlcpy(buf, parameters, sizeof(buf));
|
|
for (param = strtoken(&p, buf, " "); param; param = strtoken(&p, NULL, " "))
|
|
myparv[myparc++] = raw_strdup(param);
|
|
myparv[myparc] = NULL;
|
|
|
|
SetULine(&me); // hack for crash.. set ulined so no access checks.
|
|
do_mode(channel, &me, mtags, myparc, (const char **)myparv, 0, 0);
|
|
ClearULine(&me); // and clear it again..
|
|
|
|
for (i = 0; i < myparc; i++)
|
|
safe_free(myparv[i]);
|
|
}
|