1
0
mirror of https://github.com/unrealircd/unrealircd.git synced 2026-07-07 02:43:11 +02:00

Add "CAP chghost" support. Internal recode of userhost changes.

Fix force-rejoin not working if doing SVSMODE -x/+x (Koragg, #5015).

Note to module coders:
Please use the following procedure in case of an user/host change:
* userhost_save_current(acptr);
* << change username or hostname here (or both) >>
* userhost_changed(acptr);
This function will take care of notifying other clients about
the userhost change, such as doing PART+JOIN+MODE if force-rejoin
is enabled, and sending :xx CHGHOST user host messages to
"CAP chghost" capable clients.

Also, small note to everyone:
If force-rejoin is enabled we will not send the PART+JOIN+MODE to
"CAP chghost" capable clients. Doing so is just a hack to notify
people of a userhost change. "CAP chghost" users can thus benefit
from the reduced noise in this respect.
This commit is contained in:
Bram Matthys
2017-10-07 13:31:30 +02:00
parent 0fd265349a
commit 5124e60b7c
15 changed files with 251 additions and 153 deletions
+199 -1
View File
@@ -27,7 +27,8 @@ CMD_FUNC(m_join);
DLLFUNC void _join_channel(aChannel *chptr, aClient *cptr, aClient *sptr, int flags);
CMD_FUNC(_do_join);
DLLFUNC int _can_join(aClient *cptr, aClient *sptr, aChannel *chptr, char *key, char *parv[]);
#define MAXBOUNCE 5 /** Most sensible */
void _userhost_save_current(aClient *sptr);
void _userhost_changed(aClient *sptr);
/* Externs */
extern MODVAR int spamf_ugly_vchanoverride;
@@ -36,6 +37,8 @@ extern int find_invex(aChannel *chptr, aClient *sptr);
/* Local vars */
static int bouncedtimes = 0;
/* Macros */
#define MAXBOUNCE 5 /** Most sensible */
#define MSG_JOIN "JOIN"
ModuleHeader MOD_HEADER(m_join)
@@ -53,6 +56,9 @@ MOD_TEST(m_join)
EfunctionAddVoid(modinfo->handle, EFUNC_JOIN_CHANNEL, _join_channel);
EfunctionAdd(modinfo->handle, EFUNC_DO_JOIN, _do_join);
EfunctionAdd(modinfo->handle, EFUNC_CAN_JOIN, _can_join);
EfunctionAddVoid(modinfo->handle, EFUNC_USERHOST_SAVE_CURRENT, _userhost_save_current);
EfunctionAddVoid(modinfo->handle, EFUNC_USERHOST_CHANGED, _userhost_changed);
return MOD_SUCCESS;
}
@@ -526,3 +532,195 @@ CMD_FUNC(_do_join)
RET(0)
#undef RET
}
/* Additional channel-related functions. I've put it here instead
* of the core so it could be upgraded on the fly should it be necessary.
*/
char *get_chmodes_for_user(aClient *sptr, int flags)
{
static char modebuf[512]; /* returned */
char flagbuf[8]; /* For holding "vhoaq" */
char *p = flagbuf;
char parabuf[512];
int n, i;
if (!flags)
return "";
if (flags & MODE_CHANOWNER)
*p++ = 'q';
if (flags & MODE_CHANPROT)
*p++ = 'a';
if (flags & MODE_CHANOP)
*p++ = 'o';
if (flags & MODE_VOICE)
*p++ = 'v';
if (flags & MODE_HALFOP)
*p++ = 'h';
*p = '\0';
parabuf[0] = '\0';
n = strlen(flagbuf);
if (n)
{
for (i=0; i < n; i++)
{
strlcat(parabuf, sptr->name, sizeof(parabuf));
if (i < n - 1)
strlcat(parabuf, " ", sizeof(parabuf));
}
/* And we have our mode line! */
snprintf(modebuf, sizeof(modebuf), "+%s %s", flagbuf, parabuf);
return modebuf;
}
return "";
}
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(aClient *sptr)
{
strlcpy(remember_nick, sptr->name, sizeof(remember_nick));
strlcpy(remember_user, sptr->user->username, sizeof(remember_user));
strlcpy(remember_host, GetHost(sptr), 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(acptr);
* << change username or hostname here >>
* userhost_changed(acptr);
*/
void _userhost_changed(aClient *sptr)
{
Membership *channels;
aChannel *chptr;
Member *lp;
aClient *acptr;
int i = 0;
int impact = 0;
char buf[512];
if (strcmp(remember_nick, sptr->name))
{
ircd_log(LOG_ERROR, "[BUG] userhost_changed() was called but without calling userhost_save_current() first! Affected user: %s",
sptr->name);
ircd_log(LOG_ERROR, "Please report above bug on https://bugs.unrealircd.org/");
sendto_realops("[BUG] userhost_changed() was called but without calling userhost_save_current() first! Affected user: %s",
sptr->name);
sendto_realops("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, sptr->user->username) && !strcmp(remember_host, GetHost(sptr)))
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 = sptr->user->channel; channels; channels = channels->next)
{
aChannel *chptr = channels->chptr;
int flags = channels->flags;
char *modes;
char partbuf[512]; /* PART */
char joinbuf[512]; /* JOIN */
char modebuf[512]; /* MODE (if any) */
int chanops_only = invisible_user_in_channel(sptr, chptr);
modebuf[0] = '\0';
/* If the user is banned, don't send any rejoins, it would only be annoying */
if (is_banned(sptr, chptr, BANCHK_JOIN))
continue;
/* Prepare buffers for PART, JOIN, MODE */
ircsnprintf(partbuf, sizeof(partbuf), ":%s!%s@%s PART %s :%s",
remember_nick, remember_user, remember_host,
chptr->chname,
"Changing host");
ircsnprintf(joinbuf, sizeof(joinbuf), ":%s!%s@%s JOIN %s",
sptr->name, sptr->user->username, GetHost(sptr), chptr->chname);
modes = get_chmodes_for_user(sptr, flags);
if (modes)
ircsnprintf(modebuf, sizeof(modebuf), ":%s MODE %s %s", me.name, chptr->chname, modes);
for (lp = chptr->members; lp; lp = lp->next)
{
acptr = lp->cptr;
if (acptr == sptr)
continue; /* skip self */
if (!MyConnect(acptr))
continue; /* only locally connected clients */
if (chanops_only && !(lp->flags & (CHFL_CHANOP|CHFL_CHANOWNER|CHFL_CHANPROT)))
continue; /* skip non-ops if requested to (used for mode +D) */
if (acptr->local->proto & PROTO_CAP_CHGHOST)
continue; /* we notify 'CAP chghost' users in a different way, so don't send it here. */
impact++;
sendbufto_one(acptr, partbuf, 0);
sendbufto_one(acptr, joinbuf, 0);
if (*modebuf)
sendbufto_one(acptr, modebuf, 0);
}
}
}
/* 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,
sptr->user->username,
GetHost(sptr));
current_serial++;
for (channels = sptr->user->channel; channels; channels = channels->next)
{
for (lp = channels->chptr->members; lp; lp = lp->next)
{
acptr = lp->cptr;
if (MyClient(acptr) && (acptr->local->proto & PROTO_CAP_CHGHOST) &&
(acptr->local->serial != current_serial) && (sptr != acptr))
{
sendbufto_one(acptr, buf, 0);
acptr->local->serial = current_serial;
}
}
}
/* 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)
sptr->local->since += 7; /* Resulted in rejoins and such. */
else
sptr->local->since += 4; /* No rejoins */
}