pissircd/src/modules/chghost.c

373 lines
11 KiB
C

/*
* IRC - Internet Relay Chat, src/modules/chghost.c
* (C) 1999-2001 Carsten Munk (Techie/Stskeeps) <stskeeps@tspre.org>
*
* 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"
#define MSG_CHGHOST "CHGHOST"
CMD_FUNC(cmd_chghost);
void _userhost_save_current(Client *client);
void _userhost_changed(Client *client);
long CAP_CHGHOST = 0L;
ModuleHeader MOD_HEADER
= {
"chghost", /* Name of module */
"5.0", /* Version */
"/chghost", /* Short description of module */
"UnrealIRCd Team",
"unrealircd-6",
};
MOD_TEST()
{
MARK_AS_OFFICIAL_MODULE(modinfo);
EfunctionAddVoid(modinfo->handle, EFUNC_USERHOST_SAVE_CURRENT, _userhost_save_current);
EfunctionAddVoid(modinfo->handle, EFUNC_USERHOST_CHANGED, _userhost_changed);
return MOD_SUCCESS;
}
MOD_INIT()
{
ClientCapabilityInfo c;
CommandAdd(modinfo->handle, MSG_CHGHOST, cmd_chghost, MAXPARA, CMD_USER|CMD_SERVER);
MARK_AS_OFFICIAL_MODULE(modinfo);
memset(&c, 0, sizeof(c));
c.name = "chghost";
ClientCapabilityAdd(modinfo->handle, &c, &CAP_CHGHOST);
return MOD_SUCCESS;
}
MOD_LOAD()
{
return MOD_SUCCESS;
}
MOD_UNLOAD()
{
return MOD_SUCCESS;
}
static char remember_nick[NICKLEN+1];
static char remember_user[USERLEN+1];
static char remember_host[HOSTLEN+1];
/** Save current nick/user/host. Used later by userhost_changed(). */
void _userhost_save_current(Client *client)
{
strlcpy(remember_nick, client->name, sizeof(remember_nick));
strlcpy(remember_user, client->user->username, sizeof(remember_user));
strlcpy(remember_host, GetHost(client), sizeof(remember_host));
}
/** User/Host changed for user.
* Note that userhost_save_current() needs to be called before this
* to save the old username/hostname.
* This userhost_changed() function deals with notifying local clients
* about the user/host change by sending PART+JOIN+MODE if
* set::allow-userhost-change force-rejoin is in use,
* and it wills end "CAP chghost" to such capable clients.
* It will also deal with bumping fakelag for the user since a user/host
* change is costly, doesn't matter if it was self-induced or not.
*
* Please call this function for any user/host change by doing:
* userhost_save_current(client);
* << change username or hostname here >>
* userhost_changed(client);
*/
void _userhost_changed(Client *client)
{
Membership *channels;
Member *lp;
Client *acptr;
int impact = 0;
char buf[512];
long CAP_EXTENDED_JOIN = ClientCapabilityBit("extended-join");
if (strcmp(remember_nick, client->name))
{
unreal_log(ULOG_ERROR, "main", "BUG_USERHOST_CHANGED", client,
"[BUG] userhost_changed() was called but without calling userhost_save_current() first! Affected user: $client\n"
"Please report above bug on https://bugs.unrealircd.org/");
return; /* We cannot safely process this request anymore */
}
/* It's perfectly acceptable to call us even if the userhost didn't change. */
if (!strcmp(remember_user, client->user->username) && !strcmp(remember_host, GetHost(client)))
return; /* Nothing to do */
/* Most of the work is only necessary for set::allow-userhost-change force-rejoin */
if (UHOST_ALLOWED == UHALLOW_REJOIN)
{
/* Walk through all channels of this user.. */
for (channels = client->user->channel; channels; channels = channels->next)
{
Channel *channel = channels->channel;
char *modes;
char partbuf[512]; /* PART */
char joinbuf[512]; /* JOIN */
char exjoinbuf[512]; /* JOIN (for CAP extended-join) */
char modebuf[512]; /* MODE (if any) */
int chanops_only = invisible_user_in_channel(client, channel);
modebuf[0] = '\0';
/* If the user is banned, don't send any rejoins, it would only be annoying */
if (is_banned(client, channel, BANCHK_JOIN, NULL, NULL))
continue;
/* Prepare buffers for PART, JOIN, MODE */
ircsnprintf(partbuf, sizeof(partbuf), ":%s!%s@%s PART %s :%s",
remember_nick, remember_user, remember_host,
channel->name,
"Changing host");
ircsnprintf(joinbuf, sizeof(joinbuf), ":%s!%s@%s JOIN %s",
client->name, client->user->username, GetHost(client), channel->name);
ircsnprintf(exjoinbuf, sizeof(exjoinbuf), ":%s!%s@%s JOIN %s %s :%s",
client->name, client->user->username, GetHost(client), channel->name,
IsLoggedIn(client) ? client->user->account : "*",
client->info);
modes = get_chmodes_for_user(client, channels->member_modes);
if (!BadPtr(modes))
ircsnprintf(modebuf, sizeof(modebuf), ":%s MODE %s %s", me.name, channel->name, modes);
for (lp = channel->members; lp; lp = lp->next)
{
acptr = lp->client;
if (acptr == client)
continue; /* skip self */
if (!MyConnect(acptr))
continue; /* only locally connected clients */
if (chanops_only && !check_channel_access_member(lp, "hoaq"))
continue; /* skip non-ops if requested to (used for mode +D) */
if (HasCapabilityFast(acptr, CAP_CHGHOST))
continue; /* we notify 'CAP chghost' users in a different way, so don't send it here. */
impact++;
/* FIXME: if a client does not have the "chghost" cap then
* here we will not generate a proper new message, probably
* needs to be fixed... I skipped doing it for now.
*/
sendto_one(acptr, NULL, "%s", partbuf);
if (HasCapabilityFast(acptr, CAP_EXTENDED_JOIN))
sendto_one(acptr, NULL, "%s", exjoinbuf);
else
sendto_one(acptr, NULL, "%s", joinbuf);
if (*modebuf)
sendto_one(acptr, NULL, "%s", modebuf);
}
}
}
/* Now deal with "CAP chghost" clients.
* This only needs to be sent one per "common channel".
* This would normally call sendto_common_channels_local_butone() but the user already
* has the new user/host.. so we do it here..
*/
ircsnprintf(buf, sizeof(buf), ":%s!%s@%s CHGHOST %s %s",
remember_nick, remember_user, remember_host,
client->user->username,
GetHost(client));
current_serial++;
for (channels = client->user->channel; channels; channels = channels->next)
{
for (lp = channels->channel->members; lp; lp = lp->next)
{
acptr = lp->client;
if (MyUser(acptr) && HasCapabilityFast(acptr, CAP_CHGHOST) &&
(acptr->local->serial != current_serial) && (client != acptr))
{
/* FIXME: send mtag */
sendto_one(acptr, NULL, "%s", buf);
acptr->local->serial = current_serial;
}
}
}
RunHook(HOOKTYPE_USERHOST_CHANGE, client, remember_user, remember_host);
if (MyUser(client))
{
/* We take the liberty of sending the CHGHOST to the impacted user as
* well. This makes things easy for client coders.
* (Note that this cannot be merged with the for loop from 15 lines up
* since the user may not be in any channels)
*/
if (HasCapabilityFast(client, CAP_CHGHOST))
sendto_one(client, NULL, "%s", buf);
if (MyUser(client))
sendnumeric(client, RPL_HOSTHIDDEN, GetHost(client));
/* A userhost change always generates the following network traffic:
* server to server traffic, CAP "chghost" notifications, and
* possibly PART+JOIN+MODE if force-rejoin had work to do.
* We give the user a penalty so they don't flood...
*/
if (impact)
add_fake_lag(client, 7000); /* Resulted in rejoins and such. */
else
add_fake_lag(client, 4000); /* No rejoins */
}
}
/*
* cmd_chghost - 12/07/1999 (two months after I made SETIDENT) - Stskeeps
* :prefix CHGHOST <nick> <new hostname>
* parv[1] - target user
* parv[2] - hostname
*
*/
CMD_FUNC(cmd_chghost)
{
Client *target;
if (MyUser(client) && !ValidatePermissionsForPath("client:set:host",client,NULL,NULL,NULL))
{
sendnumeric(client, ERR_NOPRIVILEGES);
return;
}
if ((parc < 3) || BadPtr(parv[2]))
{
sendnumeric(client, ERR_NEEDMOREPARAMS, "CHGHOST");
return;
}
if (strlen(parv[2]) > (HOSTLEN))
{
sendnotice(client, "*** ChgName Error: Requested hostname too long -- rejected.");
return;
}
if (!valid_host(parv[2], 0))
{
sendnotice(client, "*** /ChgHost Error: A hostname may contain a-z, A-Z, 0-9, '-' & '.' - Please only use them");
return;
}
if (parv[2][0] == ':')
{
sendnotice(client, "*** A hostname cannot start with ':'");
return;
}
target = find_client(parv[1], NULL);
if (!MyUser(client) && !target && (target = find_server_by_uid(parv[1])))
{
/* CHGHOST for a UID that is not online.
* Let's assume it may not YET be online and forward the message to
* the remote server and stop processing ourselves.
* That server will then handle pre-registered processing of the
* CHGHOST and later communicate the host when the user actually
* comes online in the UID message.
*/
sendto_one(target, recv_mtags, ":%s CHGHOST %s %s", client->id, parv[1], parv[2]);
return;
}
if (!target || !target->user)
{
sendnumeric(client, ERR_NOSUCHNICK, parv[1]);
return;
}
if (!strcmp(GetHost(target), parv[2]))
{
sendnotice(client, "*** /ChgHost Error: requested host is same as current host.");
return;
}
userhost_save_current(target);
switch (UHOST_ALLOWED)
{
case UHALLOW_NEVER:
if (MyUser(client))
{
sendnumeric(client, ERR_DISABLED, "CHGHOST",
"This command is disabled on this server");
return;
}
break;
case UHALLOW_ALWAYS:
break;
case UHALLOW_NOCHANS:
if (IsUser(target) && MyUser(client) && target->user->joined)
{
sendnotice(client, "*** /ChgHost can not be used while %s is on a channel", target->name);
return;
}
break;
case UHALLOW_REJOIN:
/* rejoin sent later when the host has been changed */
break;
}
if (!IsULine(client))
{
const char *issuer = command_issued_by_rpc(recv_mtags);
if (issuer)
{
unreal_log(ULOG_INFO, "chgcmds", "CHGHOST_COMMAND", client,
"CHGHOST: $issuer changed the virtual hostname of $target.details to be $new_hostname",
log_data_string("issuer", issuer),
log_data_string("change_type", "hostname"),
log_data_client("target", target),
log_data_string("new_hostname", parv[2]));
} else {
unreal_log(ULOG_INFO, "chgcmds", "CHGHOST_COMMAND", client,
"CHGHOST: $client changed the virtual hostname of $target.details to be $new_hostname",
log_data_string("change_type", "hostname"),
log_data_client("target", target),
log_data_string("new_hostname", parv[2]));
}
}
target->umodes |= UMODE_HIDE;
target->umodes |= UMODE_SETHOST;
/* Send to other servers too, unless the client is still in the registration phase (SASL) */
if (IsUser(target))
sendto_server(client, 0, 0, recv_mtags, ":%s CHGHOST %s %s", client->id, target->id, parv[2]);
safe_strdup(target->user->virthost, parv[2]);
userhost_changed(target);
}