mirror of
https://github.com/unrealircd/unrealircd.git
synced 2026-06-30 22:06:37 +02:00
d5989695e8
but it seems there were still a couple left. These are now gone as well. There seem to be no issues with the ones that were left, but it is just too easy to get it wrong. Declaring buf in function now. This should be faster anyway, since it is located on nearby memory (stack). Inspired by previous find from westor (c708a99955c034e842f913479cc597d87b311394).
1318 lines
39 KiB
C
1318 lines
39 KiB
C
/*
|
|
* IRC - Internet Relay Chat, src/modules/nick.c
|
|
* (C) 1999-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
|
|
= {
|
|
"nick",
|
|
"5.0",
|
|
"command /nick",
|
|
"UnrealIRCd Team",
|
|
"unrealircd-6",
|
|
};
|
|
|
|
/* Defines */
|
|
|
|
#define NICKCOL_EQUAL 0
|
|
#define NICKCOL_NEW_WON 1
|
|
#define NICKCOL_EXISTING_WON 2
|
|
|
|
/* Assume that on collision a NICK is in flight and the other server will take
|
|
* the exact same decision we would do, and thus we don't send a KILL to cptr?
|
|
* This works great with this code, seems to kill the correct person and not
|
|
* cause desyncs even without UID/SID. HOWEVER.. who knows what code the other servers run?
|
|
* Should use UID/SID anyway, then this whole problem doesn't exist.
|
|
*/
|
|
#define ASSUME_NICK_IN_FLIGHT
|
|
|
|
/* Variables */
|
|
static char spamfilter_user[NICKLEN + USERLEN + HOSTLEN + REALLEN + 64];
|
|
|
|
/* Forward declarations */
|
|
CMD_FUNC(cmd_nick);
|
|
CMD_FUNC(cmd_nick_local);
|
|
CMD_FUNC(cmd_nick_remote);
|
|
CMD_FUNC(cmd_uid);
|
|
int _register_user(Client *client);
|
|
void nick_collision(Client *cptr, const char *newnick, const char *newid, Client *new, Client *existing, int type);
|
|
int AllowClient(Client *client);
|
|
|
|
MOD_TEST()
|
|
{
|
|
MARK_AS_OFFICIAL_MODULE(modinfo);
|
|
EfunctionAdd(modinfo->handle, EFUNC_REGISTER_USER, _register_user);
|
|
return MOD_SUCCESS;
|
|
}
|
|
|
|
MOD_INIT()
|
|
{
|
|
CommandAdd(modinfo->handle, "NICK", cmd_nick, MAXPARA, CMD_USER|CMD_SERVER|CMD_UNREGISTERED);
|
|
CommandAdd(modinfo->handle, "UID", cmd_uid, MAXPARA, CMD_SERVER);
|
|
MARK_AS_OFFICIAL_MODULE(modinfo);
|
|
return MOD_SUCCESS;
|
|
}
|
|
|
|
MOD_LOAD()
|
|
{
|
|
return MOD_SUCCESS;
|
|
}
|
|
|
|
MOD_UNLOAD()
|
|
{
|
|
return MOD_SUCCESS;
|
|
}
|
|
|
|
/** Hmm.. don't we already have such a function? */
|
|
void set_user_modes_dont_spread(Client *client, const char *umode)
|
|
{
|
|
const char *args[4];
|
|
|
|
args[0] = client->name;
|
|
args[1] = client->name;
|
|
args[2] = umode;
|
|
args[3] = NULL;
|
|
|
|
dontspread = 1;
|
|
do_cmd(client, NULL, "MODE", 3, args);
|
|
dontspread = 0;
|
|
}
|
|
|
|
/** Remote client (already fully registered) changing their nick */
|
|
CMD_FUNC(cmd_nick_remote)
|
|
{
|
|
TKL *tklban;
|
|
int ishold;
|
|
Client *acptr;
|
|
char nick[NICKLEN + 2];
|
|
char oldnick[NICKLEN + 1];
|
|
time_t lastnick = 0;
|
|
int differ = 1;
|
|
unsigned char removemoder = (client->umodes & UMODE_REGNICK) ? 1 : 0;
|
|
MessageTag *mtags = NULL;
|
|
|
|
/* 'client' is always the fully registered user doing the nick change */
|
|
|
|
strlcpy(nick, parv[1], NICKLEN + 1);
|
|
strlcpy(oldnick, client->name, sizeof(oldnick));
|
|
|
|
if (parc > 2)
|
|
lastnick = atol(parv[2]);
|
|
|
|
if (!do_remote_nick_name(nick) || !strcasecmp("ircd", nick) || !strcasecmp("irc", nick))
|
|
{
|
|
ircstats.is_kill++;
|
|
unreal_log(ULOG_ERROR, "nick", "BAD_NICK_REMOTE", client,
|
|
"Server link $server tried to introduce bad nick '$nick' -- rejected.",
|
|
log_data_string("nick", parv[1]),
|
|
log_data_client("server", client->direction));
|
|
mtags = NULL;
|
|
new_message(client, NULL, &mtags);
|
|
sendto_one(client, mtags, ":%s KILL %s :Illegal nick name", me.id, client->id);
|
|
SetKilled(client);
|
|
exit_client(client, mtags, "Illegal nick name");
|
|
free_message_tags(mtags);
|
|
mtags = NULL;
|
|
return;
|
|
}
|
|
|
|
/* Check Q-lines / ban nick */
|
|
if (!IsULine(client) && (tklban = find_qline(client, nick, &ishold)) && !ishold)
|
|
{
|
|
unreal_log(ULOG_INFO, "nick", "QLINE_NICK_REMOTE", client,
|
|
"Banned nick $nick [$ip] from server $server ($reason)",
|
|
log_data_string("nick", parv[1]),
|
|
log_data_string("ip", GetIP(client)),
|
|
log_data_client("server", client->uplink),
|
|
log_data_string("reason", tklban->ptr.nameban->reason));
|
|
/* Let it through */
|
|
}
|
|
|
|
if ((acptr = find_client(nick, NULL)))
|
|
{
|
|
/* If existing nick is still in handshake, kill it */
|
|
if (IsUnknown(acptr) && MyConnect(acptr))
|
|
{
|
|
SetKilled(acptr);
|
|
exit_client(acptr, NULL, "Overridden");
|
|
} else
|
|
if (acptr == client)
|
|
{
|
|
/* 100% identical? Must be a bug, but ok */
|
|
if (!strcmp(acptr->name, nick))
|
|
return;
|
|
/* Allows change of case in their nick */
|
|
removemoder = 0; /* don't set the user -r */
|
|
} else
|
|
{
|
|
/*
|
|
** A NICK change has collided (e.g. message type ":old NICK new").
|
|
*/
|
|
differ = (mycmp(acptr->user->username, client->user->username) ||
|
|
mycmp(acptr->user->realhost, client->user->realhost));
|
|
|
|
if (!(parc > 2) || lastnick == acptr->lastnick)
|
|
{
|
|
nick_collision(client, parv[1], client->id, client, acptr, NICKCOL_EQUAL);
|
|
return; /* Now that I killed them both, ignore the NICK */
|
|
} else
|
|
if ((differ && (acptr->lastnick > lastnick)) ||
|
|
(!differ && (acptr->lastnick < lastnick)))
|
|
{
|
|
nick_collision(client, parv[1], client->id, client, acptr, NICKCOL_NEW_WON);
|
|
/* fallthrough: their user won, continue and proceed with the nick change */
|
|
} else
|
|
if ((differ && (acptr->lastnick < lastnick)) ||
|
|
(!differ && (acptr->lastnick > lastnick)))
|
|
{
|
|
nick_collision(client, parv[1], client->id, client, acptr, NICKCOL_EXISTING_WON);
|
|
return; /* their user lost, ignore the NICK */
|
|
} else
|
|
{
|
|
return; /* just in case */
|
|
}
|
|
}
|
|
}
|
|
|
|
mtags = NULL;
|
|
|
|
if (!IsULine(client))
|
|
{
|
|
unreal_log(ULOG_INFO, "nick", "REMOTE_NICK_CHANGE", client,
|
|
"Client $client.details has changed their nickname to $new_nick",
|
|
log_data_string("new_nick", nick));
|
|
}
|
|
|
|
new_message(client, recv_mtags, &mtags);
|
|
RunHook(HOOKTYPE_REMOTE_NICKCHANGE, client, mtags, nick);
|
|
client->lastnick = lastnick ? lastnick : TStime();
|
|
add_history(client, 1);
|
|
sendto_server(client, 0, 0, mtags, ":%s NICK %s %lld",
|
|
client->id, nick, (long long)client->lastnick);
|
|
sendto_local_common_channels(client, client, 0, mtags, ":%s NICK :%s", client->name, nick);
|
|
if (removemoder)
|
|
client->umodes &= ~UMODE_REGNICK;
|
|
|
|
/* Finally set new nick name. */
|
|
del_from_client_hash_table(client->name, client);
|
|
strlcpy(client->name, nick, sizeof(client->name));
|
|
add_to_client_hash_table(nick, client);
|
|
|
|
RunHook(HOOKTYPE_POST_REMOTE_NICKCHANGE, client, mtags, oldnick);
|
|
free_message_tags(mtags);
|
|
}
|
|
|
|
/* Local user: either setting their nick for the first time (registration)
|
|
* or changing their nick (fully registered already, or not)
|
|
*/
|
|
CMD_FUNC(cmd_nick_local)
|
|
{
|
|
TKL *tklban;
|
|
int ishold;
|
|
Client *acptr;
|
|
char nick[NICKLEN + 2];
|
|
char oldnick[NICKLEN + 1];
|
|
char descbuf[BUFSIZE];
|
|
Membership *mp;
|
|
int newuser = 0;
|
|
unsigned char removemoder = (client->umodes & UMODE_REGNICK) ? 1 : 0;
|
|
Hook *h;
|
|
int ret;
|
|
|
|
strlcpy(oldnick, client->name, sizeof(oldnick));
|
|
|
|
/* Enforce minimum nick length */
|
|
if (iConf.min_nick_length && !IsOper(client) && !IsULine(client) && strlen(parv[1]) < iConf.min_nick_length)
|
|
{
|
|
snprintf(descbuf, sizeof descbuf, "A minimum length of %d chars is required", iConf.min_nick_length);
|
|
sendnumeric(client, ERR_ERRONEUSNICKNAME, parv[1], descbuf);
|
|
return;
|
|
}
|
|
|
|
/* Enforce maximum nick length */
|
|
strlcpy(nick, parv[1], iConf.nick_length + 1);
|
|
|
|
/* Check if this is a valid nick name */
|
|
if (!do_nick_name(nick))
|
|
{
|
|
sendnumeric(client, ERR_ERRONEUSNICKNAME, parv[1], "Illegal characters");
|
|
return;
|
|
}
|
|
|
|
/* Check for collisions / in use */
|
|
if (!strcasecmp("ircd", nick) || !strcasecmp("irc", nick))
|
|
{
|
|
sendnumeric(client, ERR_ERRONEUSNICKNAME, nick, "Reserved for internal IRCd purposes");
|
|
return;
|
|
}
|
|
|
|
if (MyUser(client))
|
|
{
|
|
/* Local client changing nick: check spamfilter */
|
|
spamfilter_build_user_string(spamfilter_user, nick, client);
|
|
if (match_spamfilter(client, spamfilter_user, SPAMF_USER, "NICK", NULL, 0, NULL))
|
|
return;
|
|
}
|
|
|
|
/* Check Q-lines / ban nick */
|
|
if (!IsULine(client) && (tklban = find_qline(client, nick, &ishold)))
|
|
{
|
|
if (ishold)
|
|
{
|
|
sendnumeric(client, ERR_ERRONEUSNICKNAME, nick, tklban->ptr.nameban->reason);
|
|
return;
|
|
}
|
|
if (!ValidatePermissionsForPath("immune:server-ban:ban-nick",client,NULL,NULL,nick))
|
|
{
|
|
add_fake_lag(client, 4000); /* lag them up */
|
|
sendnumeric(client, ERR_ERRONEUSNICKNAME, nick, tklban->ptr.nameban->reason);
|
|
unreal_log(ULOG_INFO, "nick", "QLINE_NICK_LOCAL_ATTEMPT", client,
|
|
"Attempt to use banned nick $nick [$ip] blocked ($reason)",
|
|
log_data_string("nick", parv[1]),
|
|
log_data_string("ip", GetIP(client)),
|
|
log_data_client("server", client->uplink),
|
|
log_data_string("reason", tklban->ptr.nameban->reason));
|
|
return; /* NICK message ignored */
|
|
}
|
|
/* fallthrough for ircops that have sufficient privileges */
|
|
}
|
|
|
|
if (!ValidatePermissionsForPath("immune:nick-flood",client,NULL,NULL,NULL))
|
|
add_fake_lag(client, 3000);
|
|
|
|
if ((acptr = find_client(nick, NULL)))
|
|
{
|
|
/* Shouldn't be possible since dot is disallowed: */
|
|
if (IsServer(acptr))
|
|
{
|
|
sendnumeric(client, ERR_NICKNAMEINUSE, nick);
|
|
return;
|
|
}
|
|
if (acptr == client)
|
|
{
|
|
/* New nick is exactly the same as the old nick? */
|
|
if (!strcmp(acptr->name, nick))
|
|
return;
|
|
/* Changing cAsE */
|
|
removemoder = 0;
|
|
} else
|
|
/* Collision with a nick of a session that is still in handshake */
|
|
if (IsUnknown(acptr) && MyConnect(acptr))
|
|
{
|
|
/* Kill the other connection that is still in progress */
|
|
SetKilled(acptr);
|
|
exit_client(acptr, NULL, "Overridden");
|
|
} else
|
|
{
|
|
sendnumeric(client, ERR_NICKNAMEINUSE, nick);
|
|
return; /* NICK message ignored */
|
|
}
|
|
}
|
|
|
|
/* set::anti-flood::nick-flood */
|
|
if (client->user &&
|
|
!ValidatePermissionsForPath("immune:nick-flood",client,NULL,NULL,NULL) &&
|
|
flood_limit_exceeded(client, FLD_NICK))
|
|
{
|
|
/* Throttle... */
|
|
sendnumeric(client, ERR_NCHANGETOOFAST, nick);
|
|
return;
|
|
}
|
|
|
|
/* New local client? */
|
|
if (!client->name[0])
|
|
{
|
|
newuser = 1;
|
|
|
|
if (iConf.ping_cookie)
|
|
{
|
|
/*
|
|
* Client setting NICK the first time.
|
|
*
|
|
* Generate a random string for them to pong with.
|
|
*/
|
|
client->local->nospoof = getrandom32();
|
|
|
|
sendto_one(client, NULL, "PING :%X", client->local->nospoof);
|
|
}
|
|
|
|
/* Copy password to the passwd field if it's given after NICK */
|
|
if ((parc > 2) && (strlen(parv[2]) <= PASSWDLEN))
|
|
safe_strdup(client->local->passwd, parv[2]);
|
|
|
|
/* This had to be copied here to avoid problems.. */
|
|
strlcpy(client->name, nick, sizeof(client->name));
|
|
|
|
/* Let's see if we can get online now... */
|
|
if (is_handshake_finished(client))
|
|
{
|
|
/* Send a CTCP VERSION */
|
|
if (!iConf.ping_cookie && USE_BAN_VERSION && MyConnect(client))
|
|
sendto_one(client, NULL, ":IRC!IRC@%s PRIVMSG %s :\1VERSION\1", me.name, nick);
|
|
|
|
client->lastnick = TStime();
|
|
if (!register_user(client))
|
|
{
|
|
if (IsDead(client))
|
|
return;
|
|
/* ..otherwise.. fallthrough so we run the same code
|
|
* as in case of !is_handshake_finished()
|
|
*/
|
|
} else {
|
|
/* New user! */
|
|
strlcpy(nick, client->name, sizeof(nick)); /* don't ask, but I need this. do not remove! -- Syzop */
|
|
}
|
|
}
|
|
} else
|
|
if (MyUser(client))
|
|
{
|
|
MessageTag *mtags = NULL;
|
|
int ret;
|
|
|
|
/* Existing client nick-changing */
|
|
|
|
/*
|
|
** If the client belongs to me, then check to see
|
|
** if client is currently on any channels where it
|
|
** is currently banned. If so, do not allow the nick
|
|
** change to occur.
|
|
** Also set 'lastnick' to current time, if changed.
|
|
*/
|
|
for (mp = client->user->channel; mp; mp = mp->next)
|
|
{
|
|
int ret = HOOK_CONTINUE;
|
|
Hook *h;
|
|
if (!check_channel_access(client, mp->channel, "hoaq") && is_banned(client, mp->channel, BANCHK_NICK, NULL, NULL))
|
|
{
|
|
sendnumeric(client, ERR_BANNICKCHANGE,
|
|
mp->channel->name);
|
|
return;
|
|
}
|
|
if (CHECK_TARGET_NICK_BANS && !check_channel_access(client, mp->channel, "hoaq") && is_banned_with_nick(client, mp->channel, BANCHK_NICK, nick, NULL, NULL))
|
|
{
|
|
sendnumeric(client, ERR_BANNICKCHANGE, mp->channel->name);
|
|
return;
|
|
}
|
|
|
|
for (h = Hooks[HOOKTYPE_CHAN_PERMIT_NICK_CHANGE]; h; h = h->next)
|
|
{
|
|
ret = (*(h->func.intfunc))(client,mp->channel);
|
|
if (ret != HOOK_CONTINUE)
|
|
break;
|
|
}
|
|
|
|
if (ret == HOOK_DENY)
|
|
{
|
|
sendnumeric(client, ERR_NONICKCHANGE, mp->channel->name);
|
|
return;
|
|
}
|
|
}
|
|
|
|
unreal_log(ULOG_INFO, "nick", "LOCAL_NICK_CHANGE", client,
|
|
"Client $client.details has changed their nickname to $new_nick",
|
|
log_data_string("new_nick", nick));
|
|
|
|
new_message(client, recv_mtags, &mtags);
|
|
RunHook(HOOKTYPE_LOCAL_NICKCHANGE, client, mtags, nick);
|
|
client->lastnick = TStime();
|
|
add_history(client, 1);
|
|
sendto_server(client, 0, 0, mtags, ":%s NICK %s %lld",
|
|
client->id, nick, (long long)client->lastnick);
|
|
sendto_local_common_channels(client, client, 0, mtags, ":%s NICK :%s", client->name, nick);
|
|
sendto_one(client, mtags, ":%s NICK :%s", client->name, nick);
|
|
free_message_tags(mtags);
|
|
if (removemoder)
|
|
client->umodes &= ~UMODE_REGNICK;
|
|
} else
|
|
{
|
|
/* Someone changing nicks in the pre-registered phase */
|
|
}
|
|
|
|
del_from_client_hash_table(client->name, client);
|
|
|
|
strlcpy(client->name, nick, sizeof(client->name));
|
|
add_to_client_hash_table(nick, client);
|
|
|
|
/* update fdlist --nenolod */
|
|
snprintf(descbuf, sizeof(descbuf), "Client: %s", nick);
|
|
fd_desc(client->local->fd, descbuf);
|
|
|
|
if (removemoder && MyUser(client))
|
|
sendto_one(client, NULL, ":%s MODE %s :-r", me.name, client->name);
|
|
|
|
if (MyUser(client) && !newuser)
|
|
RunHook(HOOKTYPE_POST_LOCAL_NICKCHANGE, client, recv_mtags, oldnick);
|
|
}
|
|
|
|
/*
|
|
** cmd_uid
|
|
** parv[1] = nickname
|
|
** parv[2] = hopcount
|
|
** parv[3] = timestamp
|
|
** parv[4] = username
|
|
** parv[5] = hostname
|
|
** parv[6] = UID
|
|
** parv[7] = account name (SVID)
|
|
** parv[8] = umodes
|
|
** parv[9] = virthost, * if none
|
|
** parv[10] = cloaked host, * if none
|
|
** parv[11] = ip
|
|
** parv[12] = info
|
|
**
|
|
** Technical documentation is available at:
|
|
** https://www.unrealircd.org/docs/Server_protocol:UID_command
|
|
*/
|
|
CMD_FUNC(cmd_uid)
|
|
{
|
|
TKL *tklban;
|
|
int ishold;
|
|
Client *acptr, *serv = NULL;
|
|
Client *acptrs;
|
|
char nick[NICKLEN + 1];
|
|
char buf[BUFSIZE];
|
|
long lastnick = 0;
|
|
int differ = 1;
|
|
const char *hostname, *username, *sstamp, *umodes, *virthost, *ip_raw, *realname;
|
|
const char *ip = NULL;
|
|
|
|
if (parc < 13)
|
|
{
|
|
sendnumeric(client, ERR_NEEDMOREPARAMS, "UID");
|
|
return;
|
|
}
|
|
|
|
/* It's not just the directly attached client which must be a
|
|
* server. The source itself needs to be a server.
|
|
*/
|
|
if (!IsServer(client))
|
|
{
|
|
sendnumeric(client, ERR_NOTFORUSERS, "UID");
|
|
return;
|
|
}
|
|
|
|
strlcpy(nick, parv[1], sizeof(nick));
|
|
hostname = parv[5];
|
|
sstamp = parv[7];
|
|
username = parv[4];
|
|
umodes = parv[8];
|
|
virthost = parv[9];
|
|
ip_raw = parv[11];
|
|
realname = parv[12];
|
|
|
|
/* Do some *MINIMAL* nick name checking for remote nicknames.
|
|
* This will only catch things that severely break things. -- Syzop
|
|
*/
|
|
if (!do_remote_nick_name(nick) || !strcasecmp("ircd", nick) || !strcasecmp("irc", nick))
|
|
{
|
|
unreal_log(ULOG_ERROR, "nick", "BAD_NICK_REMOTE", client->uplink,
|
|
"Server link $client tried to introduce bad nick '$nick' -- rejected.",
|
|
log_data_string("nick", parv[1]));
|
|
sendnumeric(client, ERR_ERRONEUSNICKNAME, parv[1], "Illegal nick name");
|
|
|
|
ircstats.is_kill++;
|
|
/* Send kill to uplink only, hasn't been broadcasted to the rest, anyway */
|
|
sendto_one(client, NULL, ":%s KILL %s :Bad nick", me.id, parv[1]);
|
|
return;
|
|
}
|
|
|
|
if (!valid_uid(parv[6]) || strncmp(parv[6], client->id, 3))
|
|
{
|
|
ircstats.is_kill++;
|
|
unreal_log(ULOG_ERROR, "link", "BAD_UID", client,
|
|
"Server link $client ($sid) used bad UID $uid in UID command.",
|
|
log_data_string("sid", client->id),
|
|
log_data_string("uid", parv[6]));
|
|
/* Send kill to uplink only, hasn't been broadcasted to the rest, anyway */
|
|
sendto_one(client, NULL, ":%s KILL %s :Bad UID", me.id, parv[6]);
|
|
return;
|
|
}
|
|
|
|
if (!valid_host(hostname, 0))
|
|
{
|
|
ircstats.is_kill++;
|
|
unreal_log(ULOG_ERROR, "link", "BAD_HOSTNAME", client,
|
|
"Server link $client ($client.id) introduced user $nick with bad host name: $bad_hostname.",
|
|
log_data_string("nick", nick),
|
|
log_data_string("bad_hostname", hostname));
|
|
/* Send kill to uplink only, hasn't been broadcasted to the rest, anyway */
|
|
sendto_one(client, NULL, ":%s KILL %s :Bad hostname", me.id, parv[6]);
|
|
return;
|
|
}
|
|
|
|
if (strcmp(virthost, "*") && !valid_host(virthost, 0))
|
|
{
|
|
ircstats.is_kill++;
|
|
unreal_log(ULOG_ERROR, "link", "BAD_HOSTNAME", client,
|
|
"Server link $client ($client.id) introduced user $nick with bad virtual hostname: $bad_hostname.",
|
|
log_data_string("nick", nick),
|
|
log_data_string("bad_hostname", virthost));
|
|
/* Send kill to uplink only, hasn't been broadcasted to the rest, anyway */
|
|
sendto_one(client, NULL, ":%s KILL %s :Bad virtual host", me.id, parv[6]);
|
|
return;
|
|
}
|
|
|
|
if (strcmp(ip_raw, "*"))
|
|
{
|
|
if (!(ip = decode_ip(ip_raw)))
|
|
{
|
|
ircstats.is_kill++;
|
|
unreal_log(ULOG_ERROR, "link", "BAD_IP", client,
|
|
"Server link $client ($client.id) introduced user $nick with bad IP: $bad_ip.",
|
|
log_data_string("nick", nick),
|
|
log_data_string("bad_ip", ip_raw));
|
|
/* Send kill to uplink only, hasn't been broadcasted to the rest, anyway */
|
|
sendto_one(client, NULL, ":%s KILL %s :Bad IP in UID command", me.id, parv[6]);
|
|
return;
|
|
}
|
|
}
|
|
|
|
/* Kill quarantined opers early... */
|
|
if (IsQuarantined(client->direction) && strchr(parv[8], 'o'))
|
|
{
|
|
ircstats.is_kill++;
|
|
/* Send kill to uplink only, hasn't been broadcasted to the rest, anyway */
|
|
unreal_log(ULOG_INFO, "link", "OPER_KILLED_QUARANTINE", NULL,
|
|
"QUARANTINE: Oper $nick on server $server killed, due to quarantine",
|
|
log_data_string("nick", parv[1]),
|
|
log_data_client("server", client));
|
|
sendto_one(client, NULL, ":%s KILL %s :Quarantined: no oper privileges allowed", me.id, parv[6]);
|
|
return;
|
|
}
|
|
|
|
if (!IsULine(client) && (tklban = find_qline(client, nick, &ishold)))
|
|
{
|
|
unreal_log(ULOG_INFO, "nick", "QLINE_NICK_REMOTE", client,
|
|
"Banned nick $nick [$nick.ip] from server $server ($reason)",
|
|
log_data_string("nick", parv[1]),
|
|
log_data_string("ip", ip),
|
|
log_data_client("server", client->uplink),
|
|
log_data_string("reason", tklban->ptr.nameban->reason));
|
|
/* Let it through */
|
|
}
|
|
|
|
/* Now check if 'nick' already exists - collision with a user (or still in handshake, unknown) */
|
|
if ((acptr = find_client(nick, NULL)) != NULL)
|
|
{
|
|
/* If there's a collision with a user that is still in handshake, on our side,
|
|
* then we can just kill our client and continue.
|
|
*/
|
|
if (MyConnect(acptr) && IsUnknown(acptr))
|
|
{
|
|
SetKilled(acptr);
|
|
exit_client(acptr, NULL, "Overridden");
|
|
goto nickkill2done;
|
|
}
|
|
|
|
lastnick = atol(parv[3]);
|
|
differ = (mycmp(acptr->user->username, parv[4]) || mycmp(acptr->user->realhost, parv[5]));
|
|
|
|
if (acptr->lastnick == lastnick)
|
|
{
|
|
nick_collision(client, parv[1], parv[6], NULL, acptr, NICKCOL_EQUAL);
|
|
return; /* We killed both users, now stop the process. */
|
|
}
|
|
|
|
if ((differ && (acptr->lastnick > lastnick)) ||
|
|
(!differ && (acptr->lastnick < lastnick)) || acptr->direction == client->direction) /* we missed a QUIT somewhere ? */
|
|
{
|
|
nick_collision(client, parv[1], parv[6], NULL, acptr, NICKCOL_NEW_WON);
|
|
/* We got rid of the "wrong" user. Introduce the correct one. */
|
|
/* ^^ hmm.. why was this absent in nenolod's code, resulting in a 'return 0'? seems wrong. */
|
|
goto nickkill2done;
|
|
}
|
|
|
|
if ((differ && (acptr->lastnick < lastnick)) || (!differ && (acptr->lastnick > lastnick)))
|
|
{
|
|
nick_collision(client, parv[1], parv[6], NULL, acptr, NICKCOL_EXISTING_WON);
|
|
return; /* Ignore the NICK */
|
|
}
|
|
return; /* just in case */
|
|
}
|
|
|
|
nickkill2done:
|
|
/* Proceed with introducing the new client, change source (replace client) */
|
|
|
|
serv = client;
|
|
client = make_client(serv->direction, serv);
|
|
strlcpy(client->id, parv[6], IDLEN);
|
|
add_client_to_list(client);
|
|
add_to_id_hash_table(client->id, client);
|
|
client->lastnick = atol(parv[3]);
|
|
strlcpy(client->name, nick, NICKLEN+1);
|
|
add_to_client_hash_table(nick, client);
|
|
|
|
make_user(client);
|
|
|
|
/* Note that cloaked host aka parv[10] is unused */
|
|
|
|
client->user->server = find_or_add(client->uplink->name);
|
|
strlcpy(client->user->realhost, hostname, sizeof(client->user->realhost));
|
|
if (ip)
|
|
safe_strdup(client->ip, ip);
|
|
|
|
if (*sstamp != '*')
|
|
strlcpy(client->user->account, sstamp, sizeof(client->user->account));
|
|
|
|
strlcpy(client->info, realname, sizeof(client->info));
|
|
strlcpy(client->user->username, username, USERLEN + 1);
|
|
SetUser(client);
|
|
|
|
make_cloakedhost(client, client->user->realhost, client->user->cloakedhost, sizeof(client->user->cloakedhost));
|
|
safe_strdup(client->user->virthost, client->user->cloakedhost);
|
|
|
|
/* Inherit flags from server, makes it easy in the send routines
|
|
* and this also makes clients inherit ulines.
|
|
*/
|
|
client->flags |= client->uplink->flags;
|
|
|
|
/* Update counts */
|
|
irccounts.clients++;
|
|
if (client->uplink->server)
|
|
client->uplink->server->users++;
|
|
if (client->umodes & UMODE_INVISIBLE)
|
|
irccounts.invisible++;
|
|
|
|
/* Set user modes */
|
|
set_user_modes_dont_spread(client, umodes);
|
|
|
|
/* Set the vhost */
|
|
if (*virthost != '*')
|
|
safe_strdup(client->user->virthost, virthost);
|
|
|
|
build_umode_string(client, 0, SEND_UMODES|UMODE_SERVNOTICE, buf);
|
|
|
|
sendto_serv_butone_nickcmd(client->direction, recv_mtags, client, (*buf == '\0' ? "+" : buf));
|
|
|
|
moddata_extract_s2s_mtags(client, recv_mtags);
|
|
|
|
if (IsLoggedIn(client))
|
|
{
|
|
user_account_login(recv_mtags, client);
|
|
/* no need to check for kill upon user_account_login() here
|
|
* since that can only happen for local users.
|
|
*/
|
|
}
|
|
|
|
RunHook(HOOKTYPE_REMOTE_CONNECT, client);
|
|
|
|
if (!IsULine(serv) && IsSynched(serv))
|
|
{
|
|
unreal_log(ULOG_INFO, "connect", "REMOTE_CLIENT_CONNECT", client,
|
|
"Client connecting: $client ($client.user.username@$client.hostname) [$client.ip] $extended_client_info",
|
|
log_data_string("extended_client_info", get_connect_extinfo(client)),
|
|
log_data_string("from_server_name", client->user->server));
|
|
}
|
|
}
|
|
|
|
/** The NICK command.
|
|
* In UnrealIRCd 4 and later this should only happen for:
|
|
* 1) A local user setting or changing the nick name ("NICK xyz")
|
|
* -> cmd_nick_local()
|
|
* 2) A remote user changing their nick name (":<uid> NICK <newnick>")
|
|
* -> cmd_nick_remote()
|
|
*/
|
|
CMD_FUNC(cmd_nick)
|
|
{
|
|
if ((parc < 2) || BadPtr(parv[1]))
|
|
{
|
|
sendnumeric(client, ERR_NONICKNAMEGIVEN);
|
|
return;
|
|
}
|
|
|
|
if (MyConnect(client) && !IsServer(client))
|
|
{
|
|
cmd_nick_local(client, recv_mtags, parc, parv);
|
|
} else
|
|
if (!IsUser(client))
|
|
{
|
|
unreal_log(ULOG_ERROR, "link", "LINK_OLD_PROTOCOL_NICK", client->direction,
|
|
"Server link $client tried to introduce $nick using NICK command. "
|
|
"Server is using an old and unsupported protocol from UnrealIRCd 3.2.x or earlier, should use the UID command. "
|
|
"See https://www.unrealircd.org/docs/FAQ#old-server-protocol",
|
|
log_data_string("nick", parv[1]));
|
|
/* Split the entire uplink, as it should never have allowed this (and probably they are to blame too) */
|
|
exit_client(client->direction, NULL, "Server used NICK command, bad, must use UID!");
|
|
return;
|
|
} else
|
|
{
|
|
cmd_nick_remote(client, recv_mtags, parc, parv);
|
|
}
|
|
}
|
|
|
|
/** Welcome the user on IRC.
|
|
* Send the RPL_WELCOME, LUSERS, MOTD, auto join channels, everything...
|
|
*/
|
|
void welcome_user(Client *client, TKL *viruschan_tkl)
|
|
{
|
|
int i;
|
|
ConfigItem_tld *tlds;
|
|
char buf[BUFSIZE];
|
|
|
|
/* Make creation time the real 'online since' time, excluding registration time.
|
|
* Otherwise things like set::anti-spam-quit-messagetime 10s could mean
|
|
* 1 second in practice (#2174).
|
|
*/
|
|
client->local->creationtime = TStime();
|
|
client->local->idle_since = TStime();
|
|
|
|
RunHook(HOOKTYPE_WELCOME, client, 0);
|
|
sendnumeric(client, RPL_WELCOME, NETWORK_NAME, client->name, client->user->username, client->user->realhost);
|
|
|
|
RunHook(HOOKTYPE_WELCOME, client, 1);
|
|
sendnumeric(client, RPL_YOURHOST, me.name, version);
|
|
|
|
RunHook(HOOKTYPE_WELCOME, client, 2);
|
|
sendnumeric(client, RPL_CREATED, creation);
|
|
|
|
RunHook(HOOKTYPE_WELCOME, client, 3);
|
|
sendnumeric(client, RPL_MYINFO, me.name, version, umodestring, cmodestring);
|
|
|
|
RunHook(HOOKTYPE_WELCOME, client, 4);
|
|
for (i = 0; ISupportStrings[i]; i++)
|
|
sendnumeric(client, RPL_ISUPPORT, ISupportStrings[i]);
|
|
|
|
RunHook(HOOKTYPE_WELCOME, client, 5);
|
|
|
|
if (IsHidden(client))
|
|
{
|
|
sendnumeric(client, RPL_HOSTHIDDEN, client->user->virthost);
|
|
RunHook(HOOKTYPE_WELCOME, client, 396);
|
|
}
|
|
|
|
if (IsSecureConnect(client))
|
|
{
|
|
if (client->local->ssl && !iConf.no_connect_tls_info)
|
|
{
|
|
sendnotice(client, "*** You are connected to %s with %s",
|
|
me.name, tls_get_cipher(client));
|
|
}
|
|
}
|
|
|
|
{
|
|
const char *parv[2];
|
|
parv[0] = NULL;
|
|
parv[1] = NULL;
|
|
do_cmd(client, NULL, "LUSERS", 1, parv);
|
|
if (IsDead(client))
|
|
return;
|
|
}
|
|
|
|
RunHook(HOOKTYPE_WELCOME, client, 266);
|
|
|
|
short_motd(client);
|
|
|
|
RunHook(HOOKTYPE_WELCOME, client, 376);
|
|
|
|
#ifdef EXPERIMENTAL
|
|
sendnotice(client,
|
|
"*** \2NOTE:\2 This server is running experimental IRC server software (UnrealIRCd %s). "
|
|
"If you find any bugs or problems, please report them at https://bugs.unrealircd.org/",
|
|
VERSIONONLY);
|
|
#endif
|
|
|
|
if (client->umodes & UMODE_INVISIBLE)
|
|
irccounts.invisible++;
|
|
|
|
build_umode_string(client, 0, SEND_UMODES|UMODE_SERVNOTICE, buf);
|
|
|
|
sendto_serv_butone_nickcmd(client->direction, NULL, client, (*buf == '\0' ? "+" : buf));
|
|
|
|
broadcast_moddata_client(client);
|
|
|
|
if (buf[0] != '\0' && buf[1] != '\0')
|
|
sendto_one(client, NULL, ":%s MODE %s :%s", client->name,
|
|
client->name, buf);
|
|
|
|
if (client->user->snomask)
|
|
sendnumeric(client, RPL_SNOMASK, client->user->snomask);
|
|
|
|
if (!IsSecure(client) && !IsLocalhost(client) && (iConf.plaintext_policy_user == POLICY_WARN))
|
|
sendnotice_multiline(client, iConf.plaintext_policy_user_message);
|
|
|
|
if (IsSecure(client) && (iConf.outdated_tls_policy_user == POLICY_WARN) && outdated_tls_client(client))
|
|
sendnotice(client, "%s", outdated_tls_client_build_string(iConf.outdated_tls_policy_user_message, client));
|
|
|
|
RunHook(HOOKTYPE_LOCAL_CONNECT, client);
|
|
|
|
/* Give the user a fresh start as far as fake-lag is concerned.
|
|
* Otherwise the user could be lagged up already due to all the CAP stuff.
|
|
*/
|
|
client->local->fake_lag = TStime();
|
|
|
|
RunHook(HOOKTYPE_WELCOME, client, 999);
|
|
|
|
/* NOTE: Code after this 'if (viruschan_tkl)' will not be executed for quarantined-
|
|
* virus-users. So be carefull with the order. -- Syzop
|
|
*/
|
|
// FIXME: verify if this works, trace code path upstream!!!!
|
|
if (viruschan_tkl)
|
|
{
|
|
join_viruschan(client, viruschan_tkl, SPAMF_USER);
|
|
return;
|
|
}
|
|
|
|
/* Force the user to join the given chans -- codemastr */
|
|
tlds = find_tld(client);
|
|
|
|
if (tlds && !BadPtr(tlds->channel))
|
|
{
|
|
char *chans = strdup(tlds->channel);
|
|
const char *args[3] = {
|
|
NULL,
|
|
chans,
|
|
NULL
|
|
};
|
|
do_cmd(client, NULL, "JOIN", 3, args);
|
|
safe_free(chans);
|
|
if (IsDead(client))
|
|
return;
|
|
}
|
|
else if (!BadPtr(AUTO_JOIN_CHANS) && strcmp(AUTO_JOIN_CHANS, "0"))
|
|
{
|
|
char *chans = strdup(AUTO_JOIN_CHANS);
|
|
const char *args[3] = {
|
|
NULL,
|
|
chans,
|
|
NULL
|
|
};
|
|
do_cmd(client, NULL, "JOIN", 3, args);
|
|
safe_free(chans);
|
|
if (IsDead(client))
|
|
return;
|
|
}
|
|
}
|
|
|
|
/** Validate client->user->username.
|
|
* @param client The client to check
|
|
* @param noident Whether we should ignore the first ~ or not
|
|
* @returns 1 if the username is acceptable, 0 if not.
|
|
* @note This function will modify client->user->username to make it valid.
|
|
* Only if there are zero valid characters it will return 0.
|
|
*/
|
|
int valid_username(Client *client, int noident)
|
|
{
|
|
static char stripuser[USERLEN + 1];
|
|
char *i;
|
|
char *o = stripuser;
|
|
char filtered = 0; /* any changes? */
|
|
|
|
*stripuser = '\0';
|
|
|
|
for (i = client->user->username + noident; *i; i++)
|
|
{
|
|
if (isallowed(*i))
|
|
*o++ = *i;
|
|
else
|
|
filtered = 1;
|
|
}
|
|
*o = '\0';
|
|
|
|
if (filtered == 0)
|
|
return 1; /* No change needed, all good */
|
|
|
|
if (*stripuser == '\0')
|
|
return 0; /* Zero valid characters, reject it */
|
|
|
|
strlcpy(client->user->username + 1, stripuser, sizeof(client->user->username)-1);
|
|
client->user->username[0] = '~';
|
|
client->user->username[USERLEN] = '\0';
|
|
return 1; /* Filtered, but OK */
|
|
}
|
|
|
|
/** Register the connection as a User - only for local connections!
|
|
* This is called after NICK + USER (in no particular order)
|
|
* and possibly other protocol messages as well (eg CAP).
|
|
* @param client Client to be made a user.
|
|
* @returns 1 if successfully registered, 0 if not (client might be killed).
|
|
*/
|
|
int _register_user(Client *client)
|
|
{
|
|
ConfigItem_ban *bconf;
|
|
char *tmpstr;
|
|
char noident = 0;
|
|
int i;
|
|
Hook *h;
|
|
TKL *savetkl = NULL;
|
|
char temp[USERLEN + 1];
|
|
char descbuf[BUFSIZE];
|
|
|
|
if (!MyConnect(client))
|
|
abort();
|
|
|
|
/* Set client->local->sockhost:
|
|
* First deal with the special 'localhost' case and
|
|
* then with generic setting based on DNS.
|
|
*/
|
|
if (!strcmp(GetIP(client), "127.0.0.1") ||
|
|
!strcmp(GetIP(client), "0:0:0:0:0:0:0:1") ||
|
|
!strcmp(GetIP(client), "0:0:0:0:0:ffff:127.0.0.1"))
|
|
{
|
|
set_sockhost(client, "localhost");
|
|
if (client->local->hostp)
|
|
{
|
|
unreal_free_hostent(client->local->hostp);
|
|
client->local->hostp = NULL;
|
|
}
|
|
} else
|
|
{
|
|
struct hostent *hp = client->local->hostp;
|
|
if (hp && hp->h_name)
|
|
set_sockhost(client, hp->h_name);
|
|
}
|
|
|
|
/* Set the hostname (client->user->realhost).
|
|
* This may later be overwritten by the AllowClient() call to
|
|
* revert to the IP again if allow::options::useip is set.
|
|
*/
|
|
strlcpy(client->user->realhost, client->local->sockhost, sizeof(client->local->sockhost));
|
|
|
|
/* Check allow { } blocks... */
|
|
if (!AllowClient(client))
|
|
{
|
|
ircstats.is_ref++;
|
|
/* For safety, we have an extra kill here */
|
|
if (!IsDead(client))
|
|
exit_client(client, NULL, "Rejected");
|
|
return 0;
|
|
}
|
|
|
|
if (IsUseIdent(client))
|
|
{
|
|
if (IsIdentSuccess(client))
|
|
{
|
|
/* ident succeeded: overwite client->user->username with the ident reply */
|
|
strlcpy(client->user->username, client->ident, sizeof(client->user->username));
|
|
} else
|
|
if (IDENT_CHECK)
|
|
{
|
|
/* ident check is enabled and it failed: prefix the username with ~ */
|
|
char temp[USERLEN+1];
|
|
strlcpy(temp, client->user->username, sizeof(temp));
|
|
snprintf(client->user->username, sizeof(client->user->username), "~%s", temp);
|
|
noident = 1;
|
|
}
|
|
}
|
|
|
|
/* Now validate the username. This may alter client->user->username
|
|
* or reject it completely.
|
|
*/
|
|
if (!valid_username(client, noident))
|
|
{
|
|
exit_client(client, NULL, "Hostile username. Please use only 0-9 a-z A-Z _ - and . in your username.");
|
|
return 0;
|
|
}
|
|
|
|
/* Check ban realname { } blocks */
|
|
if ((bconf = find_ban(NULL, client->info, CONF_BAN_REALNAME)))
|
|
{
|
|
ircstats.is_ref++;
|
|
banned_client(client, "realname", bconf->reason?bconf->reason:"", 0, 0);
|
|
return 0;
|
|
}
|
|
/* Check G/Z lines before shuns -- kill before quite -- codemastr */
|
|
if (find_tkline_match(client, 0))
|
|
{
|
|
if (!IsDead(client) && client->local->class)
|
|
{
|
|
/* Fix client count bug, in case that it was a hold such as via authprompt */
|
|
client->local->class->clients--;
|
|
client->local->class = NULL;
|
|
}
|
|
ircstats.is_ref++;
|
|
return 0;
|
|
}
|
|
find_shun(client);
|
|
|
|
spamfilter_build_user_string(spamfilter_user, client->name, client);
|
|
if (match_spamfilter(client, spamfilter_user, SPAMF_USER, NULL, NULL, 0, &savetkl))
|
|
{
|
|
if (savetkl && ((savetkl->ptr.spamfilter->action == BAN_ACT_VIRUSCHAN) ||
|
|
(savetkl->ptr.spamfilter->action == BAN_ACT_SOFT_VIRUSCHAN)))
|
|
{
|
|
/* 'viruschan' action:
|
|
* Continue with registering the client, and at the end
|
|
* of this function we will do the actual joining to the
|
|
* virus channel.
|
|
*/
|
|
} else {
|
|
/* Client is either dead or blocked (will hang, on purpose, and timeout) */
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
for (h = Hooks[HOOKTYPE_PRE_LOCAL_CONNECT]; h; h = h->next)
|
|
{
|
|
int ret = (*(h->func.intfunc))(client);
|
|
if (ret == HOOK_DENY)
|
|
{
|
|
if (!IsDead(client) && client->local->class)
|
|
{
|
|
/* Fix client count bug, in case that
|
|
* the HOOK_DENY was only meant temporarily.
|
|
*/
|
|
client->local->class->clients--;
|
|
client->local->class = NULL;
|
|
}
|
|
return 0;
|
|
}
|
|
if (ret == HOOK_ALLOW)
|
|
break;
|
|
}
|
|
|
|
SetUser(client);
|
|
|
|
make_cloakedhost(client, client->user->realhost, client->user->cloakedhost, sizeof(client->user->cloakedhost));
|
|
safe_strdup(client->user->virthost, client->user->cloakedhost);
|
|
|
|
snprintf(descbuf, sizeof descbuf, "Client: %s", client->name);
|
|
fd_desc(client->local->fd, descbuf);
|
|
|
|
/* Move user from unknown list to client list */
|
|
list_move(&client->lclient_node, &lclient_list);
|
|
|
|
/* Update counts */
|
|
irccounts.unknown--;
|
|
irccounts.clients++;
|
|
irccounts.me_clients++;
|
|
if (client->uplink && client->uplink->server)
|
|
client->uplink->server->users++;
|
|
|
|
if (IsSecure(client))
|
|
{
|
|
client->umodes |= UMODE_SECURE;
|
|
RunHook(HOOKTYPE_SECURE_CONNECT, client);
|
|
}
|
|
|
|
safe_free(client->local->passwd);
|
|
|
|
unreal_log(ULOG_INFO, "connect", "LOCAL_CLIENT_CONNECT", client,
|
|
"Client connecting: $client ($client.user.username@$client.hostname) [$client.ip] $extended_client_info",
|
|
log_data_string("extended_client_info", get_connect_extinfo(client)));
|
|
|
|
/* Send the RPL_WELCOME, LUSERS, MOTD, auto join channels, everything... */
|
|
welcome_user(client, savetkl);
|
|
|
|
return IsDead(client) ? 0 : 1;
|
|
}
|
|
|
|
/** Nick collission detected. A winner has been decided upstream. Deal with killing.
|
|
* I moved this all to a single routine here rather than having all code duplicated
|
|
* due to SID vs NICK and some code quadruplicated.
|
|
*/
|
|
void nick_collision(Client *cptr, const char *newnick, const char *newid, Client *new, Client *existing, int type)
|
|
{
|
|
char comment[512];
|
|
const char *new_server, *existing_server;
|
|
const char *who_won;
|
|
const char *nickcol_reason;
|
|
|
|
if (type == NICKCOL_NEW_WON)
|
|
who_won = "new";
|
|
else if (type == NICKCOL_EXISTING_WON)
|
|
who_won = "existing";
|
|
else
|
|
who_won = "none";
|
|
|
|
nickcol_reason = new ? "nick change" : "new user connecting";
|
|
|
|
unreal_log(ULOG_ERROR, "nick", "NICK_COLLISION", NULL,
|
|
"Nick collision: "
|
|
"$new_nick[$new_id]@$uplink (new) vs "
|
|
"$existing_client[$existing_client.id]@$existing_client.user.servername (existing). "
|
|
"Winner: $nick_collision_winner. "
|
|
"Cause: $nick_collision_reason",
|
|
log_data_string("new_nick", newnick),
|
|
log_data_string("new_id", newid),
|
|
log_data_client("uplink", cptr),
|
|
log_data_client("existing_client", existing),
|
|
log_data_string("nick_collision_winner", who_won),
|
|
log_data_string("nick_collision_reason", nickcol_reason));
|
|
|
|
new_server = cptr->name;
|
|
existing_server = (existing == existing->direction) ? me.name : existing->direction->name;
|
|
if (type == NICKCOL_EXISTING_WON)
|
|
snprintf(comment, sizeof(comment), "Nick collision: %s <- %s", new_server, existing_server);
|
|
else if (type == NICKCOL_NEW_WON)
|
|
snprintf(comment, sizeof(comment), "Nick collision: %s <- %s", existing_server, new_server);
|
|
else
|
|
snprintf(comment, sizeof(comment), "Nick collision: %s <-> %s", existing_server, new_server);
|
|
|
|
/* We only care about the direction from this point, not about the originally sending server */
|
|
cptr = cptr->direction;
|
|
|
|
if ((type == NICKCOL_EQUAL) || (type == NICKCOL_EXISTING_WON))
|
|
{
|
|
/* Kill 'new':
|
|
* - 'new' is known by the cptr-side as 'newnick' already
|
|
* - if not nick-changing then the other servers don't know this user
|
|
* - if nick-changing, then the the other servers know the user as new->name
|
|
*/
|
|
|
|
/* cptr case first... this side knows the user by newnick/newid */
|
|
/* SID server can kill 'new' by ID */
|
|
sendto_one(cptr, NULL, ":%s KILL %s :%s", me.id, newid, comment);
|
|
|
|
/* non-cptr case... only necessary if nick-changing. */
|
|
if (new)
|
|
{
|
|
MessageTag *mtags = NULL;
|
|
|
|
new_message(new, NULL, &mtags);
|
|
|
|
/* non-cptr side knows this user by their old nick name */
|
|
sendto_server(cptr, 0, 0, mtags, ":%s KILL %s :%s", me.id, new->id, comment);
|
|
|
|
/* Exit the client */
|
|
ircstats.is_kill++;
|
|
SetKilled(new);
|
|
exit_client(new, mtags, comment);
|
|
|
|
free_message_tags(mtags);
|
|
}
|
|
}
|
|
|
|
if ((type == NICKCOL_EQUAL) || (type == NICKCOL_NEW_WON))
|
|
{
|
|
MessageTag *mtags = NULL;
|
|
|
|
new_message(existing, NULL, &mtags);
|
|
|
|
/* Now let's kill 'existing' */
|
|
sendto_server(NULL, 0, 0, mtags, ":%s KILL %s :%s", me.id, existing->id, comment);
|
|
|
|
/* Exit the client */
|
|
ircstats.is_kill++;
|
|
SetKilled(existing);
|
|
exit_client(existing, mtags, comment);
|
|
|
|
free_message_tags(mtags);
|
|
}
|
|
}
|
|
|
|
/** Returns 1 if allow::maxperip is exceeded by 'client' */
|
|
int exceeds_maxperip(Client *client, ConfigItem_allow *aconf)
|
|
{
|
|
Client *acptr;
|
|
int local_cnt = 1;
|
|
int global_cnt = 1;
|
|
|
|
if (find_tkl_exception(TKL_MAXPERIP, client))
|
|
return 0; /* exempt */
|
|
|
|
list_for_each_entry(acptr, &client_list, client_node)
|
|
{
|
|
if (IsUser(acptr) && !strcmp(GetIP(acptr), GetIP(client)))
|
|
{
|
|
if (MyUser(acptr))
|
|
{
|
|
local_cnt++;
|
|
if (local_cnt > aconf->maxperip)
|
|
return 1;
|
|
}
|
|
global_cnt++;
|
|
if (global_cnt > aconf->global_maxperip)
|
|
return 1;
|
|
}
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
/** Allow or reject the client based on allow { } blocks and all other restrictions.
|
|
* @param client Client to check (local)
|
|
* @param username Username, for some reason...
|
|
* @returns 1 if OK, 0 if client is rejected (likely killed too)
|
|
*/
|
|
int AllowClient(Client *client)
|
|
{
|
|
static char sockhost[HOSTLEN + 1];
|
|
int i;
|
|
ConfigItem_allow *aconf;
|
|
char *hname;
|
|
static char uhost[HOSTLEN + USERLEN + 3];
|
|
static char fullname[HOSTLEN + 1];
|
|
|
|
if (!IsSecure(client) && !IsLocalhost(client) && (iConf.plaintext_policy_user == POLICY_DENY))
|
|
{
|
|
exit_client(client, NULL, iConf.plaintext_policy_user_message->line);
|
|
return 0;
|
|
}
|
|
|
|
if (IsSecure(client) && (iConf.outdated_tls_policy_user == POLICY_DENY) && outdated_tls_client(client))
|
|
{
|
|
const char *msg = outdated_tls_client_build_string(iConf.outdated_tls_policy_user_message, client);
|
|
exit_client(client, NULL, msg);
|
|
return 0;
|
|
}
|
|
|
|
for (aconf = conf_allow; aconf; aconf = aconf->next)
|
|
{
|
|
if (aconf->flags.tls && !IsSecure(client))
|
|
continue;
|
|
|
|
if (!user_allowed_by_security_group(client, aconf->match))
|
|
continue;
|
|
|
|
/* Check authentication */
|
|
if (aconf->auth && !Auth_Check(client, aconf->auth, client->local->passwd))
|
|
{
|
|
/* Incorrect password/authentication - but was is it required? */
|
|
if (aconf->flags.reject_on_auth_failure)
|
|
{
|
|
exit_client(client, NULL, iConf.reject_message_unauthorized);
|
|
return 0;
|
|
} else {
|
|
continue; /* Continue (this is the default behavior) */
|
|
}
|
|
}
|
|
|
|
if (!aconf->flags.noident)
|
|
SetUseIdent(client);
|
|
|
|
if (aconf->flags.useip)
|
|
set_sockhost(client, GetIP(client));
|
|
|
|
if (exceeds_maxperip(client, aconf))
|
|
{
|
|
/* Already got too many with that ip# */
|
|
exit_client(client, NULL, iConf.reject_message_too_many_connections);
|
|
return 0;
|
|
}
|
|
|
|
if (!((aconf->class->clients + 1) > aconf->class->maxclients))
|
|
{
|
|
client->local->class = aconf->class;
|
|
client->local->class->clients++;
|
|
}
|
|
else
|
|
{
|
|
/* Class is full */
|
|
sendnumeric(client, RPL_REDIR, aconf->server ? aconf->server : DEFAULT_SERVER, aconf->port ? aconf->port : 6667);
|
|
exit_client(client, NULL, iConf.reject_message_server_full);
|
|
return 0;
|
|
}
|
|
return 1;
|
|
}
|
|
/* User did not match any allow { } blocks: */
|
|
exit_client(client, NULL, iConf.reject_message_unauthorized);
|
|
return 0;
|
|
}
|