1
0
mirror of https://github.com/unrealircd/unrealircd.git synced 2026-06-30 12:46:37 +02:00
Files
unrealircd/src/modules/protoctl.c
T
Bram Matthys 6940272290 Prevent early UID cut-off. This doesn't happen with current unrealircd traffic
because we send 9 character uids. However, IDLEN is defined as 12 so it is
natural for other people (services and other pseudo server writers) to assume
you could send 12, which failed until now, as it only accepted 11 characters.

Just to be clear:
* We generate and send 9 character uids in UnrealIRCd ourselves, this
  works perfectly fine
* In 114d54ac61 in 2021 (UnrealIRCd 5.2.1) i
  enlarged the buffers to allow INCOMING ids of up to 12 characters.
  The reason for that is that I want the option to allow slightly larger
  uids and could start doing that several years later without causing
  desynchs and other problems.
* That didn't work properly, it only allowed up to 11 chars at this point.
* From now on it allows 12 chars. I do NOT recommend sending that though, if
  you want to send bigger ids from your services/pseudo server then use
  11, or... actually just use 9 like in normal unrealircd traffic at the
  moment.

Reported on IRC by craftxbox
2024-11-24 09:56:06 +01:00

414 lines
13 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_ERROR, "link", "LINK_DENIED_CHARSYS_INCOMPATIBLE", client,
"Server link $client rejected. Server $me_name has utf8 in set::allowed-nickchars but $client does not.",
log_data_string("me_name", me.name));
exit_client(client, NULL, "Incompatible set::allowed-nickchars setting");
return;
}
/* 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, sizeof(client->id));
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);
}
}