mirror of https://github.com/pissnet/pissircd.git
412 lines
12 KiB
C
412 lines
12 KiB
C
/*
|
|
* IRC - Internet Relay Chat, src/modules/protoctl.c
|
|
* (C) 2004- 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"
|
|
|
|
CMD_FUNC(cmd_protoctl);
|
|
|
|
#define MSG_PROTOCTL "PROTOCTL"
|
|
|
|
ModuleHeader MOD_HEADER
|
|
= {
|
|
"protoctl",
|
|
"5.0",
|
|
"command /protoctl",
|
|
"UnrealIRCd Team",
|
|
"unrealircd-6",
|
|
};
|
|
|
|
MOD_INIT()
|
|
{
|
|
CommandAdd(modinfo->handle, MSG_PROTOCTL, cmd_protoctl, MAXPARA, CMD_UNREGISTERED|CMD_SERVER|CMD_USER);
|
|
MARK_AS_OFFICIAL_MODULE(modinfo);
|
|
return MOD_SUCCESS;
|
|
}
|
|
|
|
MOD_LOAD()
|
|
{
|
|
return MOD_SUCCESS;
|
|
}
|
|
|
|
MOD_UNLOAD()
|
|
{
|
|
return MOD_SUCCESS;
|
|
}
|
|
|
|
#define MAX_SERVER_TIME_OFFSET 60
|
|
|
|
/* The PROTOCTL command is used for negotiating capabilities with
|
|
* directly connected servers.
|
|
* See https://www.unrealircd.org/docs/Server_protocol:PROTOCTL_command
|
|
* for all technical documentation, especially if you are a server
|
|
* or services coder.
|
|
*/
|
|
CMD_FUNC(cmd_protoctl)
|
|
{
|
|
int i;
|
|
int first_protoctl = IsProtoctlReceived(client) ? 0 : 1; /**< First PROTOCTL we receive? Special ;) */
|
|
char proto[512];
|
|
char *name, *value, *p;
|
|
|
|
if (!MyConnect(client))
|
|
return; /* Remote PROTOCTL's are not supported */
|
|
|
|
SetProtoctlReceived(client);
|
|
|
|
for (i = 1; i < parc; i++)
|
|
{
|
|
strlcpy(proto, parv[i], sizeof proto);
|
|
p = strchr(proto, '=');
|
|
if (p)
|
|
{
|
|
name = proto;
|
|
*p++ = '\0';
|
|
value = p;
|
|
} else {
|
|
name = proto;
|
|
value = NULL;
|
|
}
|
|
|
|
if (!strcmp(name, "NAMESX"))
|
|
{
|
|
SetCapability(client, "multi-prefix");
|
|
}
|
|
else if (!strcmp(name, "UHNAMES") && UHNAMES_ENABLED)
|
|
{
|
|
SetCapability(client, "userhost-in-names");
|
|
}
|
|
else if (IsUser(client))
|
|
{
|
|
return;
|
|
}
|
|
else if (!strcmp(name, "VL"))
|
|
{
|
|
SetVL(client);
|
|
}
|
|
else if (!strcmp(name, "VHP"))
|
|
{
|
|
SetVHP(client);
|
|
}
|
|
else if (!strcmp(name, "CLK"))
|
|
{
|
|
SetCLK(client);
|
|
}
|
|
else if (!strcmp(name, "SJSBY") && iConf.ban_setter_sync)
|
|
{
|
|
SetSJSBY(client);
|
|
}
|
|
else if (!strcmp(name, "MTAGS"))
|
|
{
|
|
SetMTAGS(client);
|
|
}
|
|
else if (!strcmp(name, "NEXTBANS"))
|
|
{
|
|
SetNEXTBANS(client);
|
|
}
|
|
else if (!strcmp(name, "BIGLINES"))
|
|
{
|
|
SetBIGLINES(client);
|
|
}
|
|
else if (!strcmp(name, "NICKCHARS") && value)
|
|
{
|
|
if (!IsServer(client) && !IsEAuth(client) && !IsHandshake(client))
|
|
continue;
|
|
/* Ok, server is either authenticated, or is an outgoing connect... */
|
|
/* Some combinations are fatal because they would lead to mass-kills:
|
|
* - use of 'utf8' on our server but not on theirs
|
|
*/
|
|
if (strstr(charsys_get_current_languages(), "utf8") && !strstr(value, "utf8"))
|
|
{
|
|
unreal_log(ULOG_WARNING, "link", "LINK_WARNING_CHARSYS", client,
|
|
"Server $client has utf8 in set::allowed-nickchars but %me does not. Proceed with caution.",
|
|
log_data_string("me", me.name));
|
|
}
|
|
/* We compare the character sets to see if we should warn opers about any mismatch... */
|
|
if (strcmp(value, charsys_get_current_languages()))
|
|
{
|
|
unreal_log(ULOG_WARNING, "link", "LINK_WARNING_CHARSYS", client,
|
|
"Server link $client does not have the same set::allowed-nickchars settings, "
|
|
"this may possibly cause display issues. Our charset: '$our_charsys', theirs: '$their_charsys'",
|
|
log_data_string("our_charsys", charsys_get_current_languages()),
|
|
log_data_string("their_charsys", value));
|
|
}
|
|
if (client->server)
|
|
safe_strdup(client->server->features.nickchars, value);
|
|
|
|
/* If this is a runtime change (so post-handshake): */
|
|
if (IsServer(client))
|
|
broadcast_sinfo(client, NULL, client);
|
|
}
|
|
else if (!strcmp(name, "CHANNELCHARS") && value)
|
|
{
|
|
int their_value;
|
|
|
|
if (!IsServer(client) && !IsEAuth(client) && !IsHandshake(client))
|
|
continue;
|
|
|
|
their_value = allowed_channelchars_strtoval(value);
|
|
if (their_value != iConf.allowed_channelchars)
|
|
{
|
|
unreal_log(ULOG_ERROR, "link", "LINK_DENIED_ALLOWED_CHANNELCHARS_INCOMPATIBLE", client,
|
|
"Server link $client rejected. Server has set::allowed-channelchars setting "
|
|
"of $their_allowed_channelchars, while we have $our_allowed_channelchars.\n"
|
|
"Please set set::allowed-channelchars to the same value on all servers.",
|
|
log_data_string("their_allowed_channelchars", value),
|
|
log_data_string("our_allowed_channelchars", allowed_channelchars_valtostr(iConf.allowed_channelchars)));
|
|
exit_client(client, NULL, "Incompatible set::allowed-channelchars setting");
|
|
return;
|
|
}
|
|
}
|
|
else if (!strcmp(name, "SID") && value)
|
|
{
|
|
Client *aclient;
|
|
char *sid = value;
|
|
|
|
if (!IsServer(client) && !IsEAuth(client) && !IsHandshake(client))
|
|
{
|
|
exit_client(client, NULL, "Got PROTOCTL SID before EAUTH, that's the wrong order!");
|
|
return;
|
|
}
|
|
|
|
if (*client->id && (strlen(client->id)==3))
|
|
{
|
|
exit_client(client, NULL, "Got PROTOCTL SID twice");
|
|
return;
|
|
}
|
|
|
|
if (!valid_sid(value))
|
|
{
|
|
exit_client(client, NULL, "Invalid SID. The first character must be a digit and the other two characters must be A-Z0-9. Eg: 0AA.");
|
|
return;
|
|
}
|
|
|
|
if (IsServer(client))
|
|
{
|
|
exit_client(client, NULL, "Got PROTOCTL SID after SERVER, that's the wrong order!");
|
|
return;
|
|
}
|
|
|
|
if ((aclient = hash_find_id(sid, NULL)) != NULL)
|
|
{
|
|
unreal_log(ULOG_ERROR, "link", "LINK_DENIED_SID_COLLISION", client,
|
|
"Server link $client rejected. Server with SID $sid already exist via uplink $existing_client.server.uplink.",
|
|
log_data_string("sid", sid),
|
|
log_data_client("existing_client", aclient));
|
|
exit_client(client, NULL, "SID collision");
|
|
return;
|
|
}
|
|
|
|
if (*client->id)
|
|
del_from_id_hash_table(client->id, client); /* delete old UID entry (created on connect) */
|
|
strlcpy(client->id, sid, IDLEN);
|
|
add_to_id_hash_table(client->id, client); /* add SID */
|
|
}
|
|
else if (!strcmp(name, "EAUTH") && value)
|
|
{
|
|
/* Early authorization: EAUTH=servername,protocol,flags,software
|
|
* (Only servername is mandatory, rest is optional)
|
|
*/
|
|
int ret;
|
|
char *p;
|
|
char *servername = NULL, *protocol = NULL, *flags = NULL, *software = NULL;
|
|
char buf[512];
|
|
ConfigItem_link *aconf = NULL;
|
|
|
|
if (IsEAuth(client))
|
|
{
|
|
exit_client(client, NULL, "PROTOCTL EAUTH received twice");
|
|
return;
|
|
}
|
|
|
|
strlcpy(buf, value, sizeof(buf));
|
|
p = strchr(buf, ' ');
|
|
if (p)
|
|
{
|
|
*p = '\0';
|
|
p = NULL;
|
|
}
|
|
|
|
servername = strtoken_noskip(&p, buf, ",");
|
|
if (!servername || !valid_server_name(servername))
|
|
{
|
|
exit_client(client, NULL, "Bogus server name");
|
|
return;
|
|
}
|
|
|
|
|
|
protocol = strtoken_noskip(&p, NULL, ",");
|
|
if (protocol)
|
|
{
|
|
flags = strtoken_noskip(&p, NULL, ",");
|
|
if (flags)
|
|
software = strtoken_noskip(&p, NULL, ",");
|
|
}
|
|
|
|
/* Set client->name but don't add to hash list, this gives better
|
|
* log messages and should be safe. See CMTSRV941 in server.c.
|
|
*/
|
|
strlcpy(client->name, servername, sizeof(client->name));
|
|
|
|
if (!(aconf = verify_link(client)))
|
|
return;
|
|
|
|
/* note: software, protocol and flags may be NULL */
|
|
if (!check_deny_version(client, software, protocol ? atoi(protocol) : 0, flags))
|
|
return;
|
|
|
|
SetEAuth(client);
|
|
make_server(client); /* allocate and set client->server */
|
|
if (protocol)
|
|
client->server->features.protocol = atoi(protocol);
|
|
if (software)
|
|
safe_strdup(client->server->features.software, software);
|
|
if (is_services_but_not_ulined(client))
|
|
{
|
|
exit_client_fmt(client, NULL, "Services detected but no ulines { } for server name %s", client->name);
|
|
return;
|
|
}
|
|
if (!IsHandshake(client) && aconf) /* Send PASS early... */
|
|
sendto_one(client, NULL, "PASS :%s", (aconf->auth->type == AUTHTYPE_PLAINTEXT) ? aconf->auth->data : "*");
|
|
}
|
|
else if (!strcmp(name, "SERVERS") && value)
|
|
{
|
|
Client *aclient, *srv;
|
|
char *sid = NULL;
|
|
|
|
if (!IsEAuth(client))
|
|
continue;
|
|
|
|
if (client->server->features.protocol < 2351)
|
|
continue; /* old SERVERS= version */
|
|
|
|
/* Other side lets us know which servers are behind it.
|
|
* SERVERS=<sid-of-server-1>[,<sid-of-server-2[,..etc..]]
|
|
* Eg: SERVERS=001,002,0AB,004,005
|
|
*/
|
|
|
|
add_pending_net(client, value);
|
|
|
|
aclient = find_non_pending_net_duplicates(client);
|
|
if (aclient)
|
|
{
|
|
unreal_log(ULOG_ERROR, "link", "LINK_DENIED_DUPLICATE_SID", client,
|
|
"Denied server $client: Server with SID $existing_client.id ($existing_client) is already linked.",
|
|
log_data_client("existing_client", aclient));
|
|
exit_client(client, NULL, "Server Exists (or non-unique me::sid)");
|
|
return;
|
|
}
|
|
|
|
aclient = find_pending_net_duplicates(client, &srv, &sid);
|
|
if (aclient)
|
|
{
|
|
unreal_log(ULOG_ERROR, "link", "LINK_DENIED_DUPLICATE_SID_LINKED", client,
|
|
"Denied server $client: Server would (later) introduce SID $sid, "
|
|
"but we already have SID $sid linked ($existing_client)\n"
|
|
"Possible race condition, just wait a moment for the network to synchronize...",
|
|
log_data_string("sid", sid),
|
|
log_data_client("existing_client", aclient));
|
|
exit_client(client, NULL, "Server Exists (just wait a moment...)");
|
|
return;
|
|
}
|
|
|
|
/* Send our PROTOCTL SERVERS= back if this was NOT a response */
|
|
if (*value != '*')
|
|
send_protoctl_servers(client, 1);
|
|
}
|
|
else if (!strcmp(name, "TS") && value && (IsServer(client) || IsEAuth(client)))
|
|
{
|
|
long t = atol(value);
|
|
|
|
if (t < 10000)
|
|
continue; /* ignore */
|
|
|
|
if ((TStime() - t) > MAX_SERVER_TIME_OFFSET)
|
|
{
|
|
unreal_log(ULOG_ERROR, "link", "LINK_DENIED_CLOCK_INCORRECT", client,
|
|
"Denied server $client: clock on server $client is $time_delta "
|
|
"seconds behind the clock of $me_name.\n"
|
|
"Correct time is very important for IRC servers, "
|
|
"see https://www.unrealircd.org/docs/FAQ#fix-your-clock",
|
|
log_data_integer("time_delta", TStime() - t),
|
|
log_data_string("me_name", me.name));
|
|
exit_client_fmt(client, NULL, "Incorrect clock. Our clocks are %lld seconds apart.",
|
|
(long long)(TStime() - t));
|
|
return;
|
|
} else
|
|
if ((t - TStime()) > MAX_SERVER_TIME_OFFSET)
|
|
{
|
|
unreal_log(ULOG_ERROR, "link", "LINK_DENIED_CLOCK_INCORRECT", client,
|
|
"Denied server $client: clock on server $client is $time_delta "
|
|
"seconds ahead the clock of $me_name.\n"
|
|
"Correct time is very important for IRC servers, "
|
|
"see https://www.unrealircd.org/docs/FAQ#fix-your-clock",
|
|
log_data_integer("time_delta", t - TStime()),
|
|
log_data_string("me_name", me.name));
|
|
exit_client_fmt(client, NULL, "Incorrect clock. Our clocks are %lld seconds apart.",
|
|
(long long)(t - TStime()));
|
|
return;
|
|
}
|
|
}
|
|
else if (!strcmp(name, "MLOCK"))
|
|
{
|
|
client->local->proto |= PROTO_MLOCK;
|
|
}
|
|
else if (!strcmp(name, "CHANMODES") && value && client->server)
|
|
{
|
|
parse_chanmodes_protoctl(client, value);
|
|
/* If this is a runtime change (so post-handshake): */
|
|
if (IsServer(client))
|
|
broadcast_sinfo(client, NULL, client);
|
|
}
|
|
else if (!strcmp(name, "USERMODES") && value && client->server)
|
|
{
|
|
safe_strdup(client->server->features.usermodes, value);
|
|
/* If this is a runtime change (so post-handshake): */
|
|
if (IsServer(client))
|
|
broadcast_sinfo(client, NULL, client);
|
|
}
|
|
else if (!strcmp(name, "BOOTED") && value && client->server)
|
|
{
|
|
client->server->boottime = atol(value);
|
|
}
|
|
else if (!strcmp(name, "EXTSWHOIS"))
|
|
{
|
|
client->local->proto |= PROTO_EXTSWHOIS;
|
|
}
|
|
/* You can add protocol extensions here.
|
|
* Use 'name' and 'value' (the latter may be NULL).
|
|
*
|
|
* DO NOT error or warn on unknown proto; we just don't
|
|
* support it.
|
|
*/
|
|
}
|
|
|
|
if (first_protoctl && IsHandshake(client) && client->server && !IsServerSent(client)) /* first & outgoing connection to server */
|
|
{
|
|
/* SERVER message moved from completed_connection() to here due to EAUTH/SERVERS PROTOCTL stuff,
|
|
* which needed to be delayed until after both sides have received SERVERS=xx (..or not.. in case
|
|
* of older servers).
|
|
*/
|
|
send_server_message(client);
|
|
}
|
|
}
|