/* * 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, "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, 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=[, 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); } }