mirror of
https://github.com/unrealircd/unrealircd.git
synced 2026-06-29 03:36:37 +02:00
b078a9c8b5
using mixed UnrealIRCd 5 and UnrealIRCd 6 networks. This is a slightly complex rewrite of make_mode_str() and do_mode(), as we nog go from single mode lines to potentially multiple mode lines. In short: whenever we would be near buffer cut-off point (the famous 512 byte limit) then previously we would prevent the mode, though not succesfully in all cases where a network consists of mixed 5.x and 6.x. From this point onward we no longer do that. Instead we convert one MODE command to two MODE lines if that is needed. The benefit of this is that we no longer prevent it BEFORE processing the MODE, which is a flawed method and could be wrong (causing desyncs). And also, we no longer partially ignore MODE lines from clients when they would cause the limit to be exceeded, as we replace them with two MODE lines instead. These are more changes than I wanted at such a late point but.. they seem to be necessary to prevent U5-U6 compatibility issues.
1986 lines
64 KiB
C
1986 lines
64 KiB
C
/*
|
|
* IRC - Internet Relay Chat, src/modules/server.c
|
|
* (C) 2004-present 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"
|
|
|
|
/* Definitions */
|
|
typedef enum AutoConnectStrategy {
|
|
AUTOCONNECT_PARALLEL = 0,
|
|
AUTOCONNECT_SEQUENTIAL = 1,
|
|
AUTOCONNECT_SEQUENTIAL_FALLBACK = 2
|
|
} AutoConnectStrategy;
|
|
|
|
typedef struct cfgstruct cfgstruct;
|
|
struct cfgstruct {
|
|
AutoConnectStrategy autoconnect_strategy;
|
|
long connect_timeout;
|
|
long handshake_timeout;
|
|
};
|
|
|
|
/* Forward declarations */
|
|
void server_config_setdefaults(cfgstruct *cfg);
|
|
int server_config_test(ConfigFile *cf, ConfigEntry *ce, int type, int *errs);
|
|
int server_config_run(ConfigFile *cf, ConfigEntry *ce, int type);
|
|
EVENT(server_autoconnect);
|
|
EVENT(server_handshake_timeout);
|
|
void send_channel_modes_sjoin3(Client *to, Channel *channel);
|
|
CMD_FUNC(cmd_server);
|
|
CMD_FUNC(cmd_sid);
|
|
int _verify_link(Client *client, ConfigItem_link **link_out);
|
|
void _send_protoctl_servers(Client *client, int response);
|
|
void _send_server_message(Client *client);
|
|
void _introduce_user(Client *to, Client *acptr);
|
|
int _check_deny_version(Client *cptr, char *software, int protocol, char *flags);
|
|
void _broadcast_sinfo(Client *acptr, Client *to, Client *except);
|
|
int server_sync(Client *cptr, ConfigItem_link *conf, int incoming);
|
|
void tls_link_notification_verify(Client *acptr, ConfigItem_link *aconf);
|
|
void server_generic_free(ModData *m);
|
|
int server_post_connect(Client *client);
|
|
void _connect_server(ConfigItem_link *aconf, Client *by, struct hostent *hp);
|
|
static int connect_server_helper(ConfigItem_link *, Client *);
|
|
|
|
/* Global variables */
|
|
static char buf[BUFSIZE];
|
|
static cfgstruct cfg;
|
|
static char *last_autoconnect_server = NULL;
|
|
|
|
ModuleHeader MOD_HEADER
|
|
= {
|
|
"server",
|
|
"5.0",
|
|
"command /server",
|
|
"UnrealIRCd Team",
|
|
"unrealircd-6",
|
|
};
|
|
|
|
MOD_TEST()
|
|
{
|
|
MARK_AS_OFFICIAL_MODULE(modinfo);
|
|
EfunctionAddVoid(modinfo->handle, EFUNC_SEND_PROTOCTL_SERVERS, _send_protoctl_servers);
|
|
EfunctionAddVoid(modinfo->handle, EFUNC_SEND_SERVER_MESSAGE, _send_server_message);
|
|
EfunctionAdd(modinfo->handle, EFUNC_VERIFY_LINK, _verify_link);
|
|
EfunctionAddVoid(modinfo->handle, EFUNC_INTRODUCE_USER, _introduce_user);
|
|
EfunctionAdd(modinfo->handle, EFUNC_CHECK_DENY_VERSION, _check_deny_version);
|
|
EfunctionAddVoid(modinfo->handle, EFUNC_BROADCAST_SINFO, _broadcast_sinfo);
|
|
EfunctionAddVoid(modinfo->handle, EFUNC_CONNECT_SERVER, _connect_server);
|
|
HookAdd(modinfo->handle, HOOKTYPE_CONFIGTEST, 0, server_config_test);
|
|
return MOD_SUCCESS;
|
|
}
|
|
|
|
MOD_INIT()
|
|
{
|
|
MARK_AS_OFFICIAL_MODULE(modinfo);
|
|
LoadPersistentPointer(modinfo, last_autoconnect_server, server_generic_free);
|
|
server_config_setdefaults(&cfg);
|
|
HookAdd(modinfo->handle, HOOKTYPE_CONFIGRUN, 0, server_config_run);
|
|
HookAdd(modinfo->handle, HOOKTYPE_POST_SERVER_CONNECT, 0, server_post_connect);
|
|
CommandAdd(modinfo->handle, "SERVER", cmd_server, MAXPARA, CMD_UNREGISTERED|CMD_SERVER);
|
|
CommandAdd(modinfo->handle, "SID", cmd_sid, MAXPARA, CMD_SERVER);
|
|
|
|
return MOD_SUCCESS;
|
|
}
|
|
|
|
MOD_LOAD()
|
|
{
|
|
EventAdd(modinfo->handle, "server_autoconnect", server_autoconnect, NULL, 2000, 0);
|
|
EventAdd(modinfo->handle, "server_handshake_timeout", server_handshake_timeout, NULL, 1000, 0);
|
|
return MOD_SUCCESS;
|
|
}
|
|
|
|
MOD_UNLOAD()
|
|
{
|
|
SavePersistentPointer(modinfo, last_autoconnect_server);
|
|
return MOD_SUCCESS;
|
|
}
|
|
|
|
/** Convert 'str' to a AutoConnectStrategy value.
|
|
* @param str The string, eg "parallel"
|
|
* @returns a valid AutoConnectStrategy value or -1 if not found.
|
|
*/
|
|
AutoConnectStrategy autoconnect_strategy_strtoval(char *str)
|
|
{
|
|
if (!strcmp(str, "parallel"))
|
|
return AUTOCONNECT_PARALLEL;
|
|
if (!strcmp(str, "sequential"))
|
|
return AUTOCONNECT_SEQUENTIAL;
|
|
if (!strcmp(str, "sequential-fallback"))
|
|
return AUTOCONNECT_SEQUENTIAL_FALLBACK;
|
|
return -1;
|
|
}
|
|
|
|
/** Convert an AutoConnectStrategy value to a string.
|
|
* @param val The value to convert to a string
|
|
* @returns a string, such as "parallel".
|
|
*/
|
|
char *autoconnect_strategy_valtostr(AutoConnectStrategy val)
|
|
{
|
|
switch (val)
|
|
{
|
|
case AUTOCONNECT_PARALLEL:
|
|
return "parallel";
|
|
case AUTOCONNECT_SEQUENTIAL:
|
|
return "sequential";
|
|
case AUTOCONNECT_SEQUENTIAL_FALLBACK:
|
|
return "sequential-fallback";
|
|
default:
|
|
return "???";
|
|
}
|
|
}
|
|
|
|
void server_config_setdefaults(cfgstruct *cfg)
|
|
{
|
|
cfg->autoconnect_strategy = AUTOCONNECT_SEQUENTIAL;
|
|
cfg->connect_timeout = 10;
|
|
cfg->handshake_timeout = 20;
|
|
}
|
|
|
|
int server_config_test(ConfigFile *cf, ConfigEntry *ce, int type, int *errs)
|
|
{
|
|
int errors = 0;
|
|
ConfigEntry *cep;
|
|
|
|
if (type != CONFIG_SET)
|
|
return 0;
|
|
|
|
/* We are only interrested in set::server-linking.. */
|
|
if (!ce || strcmp(ce->name, "server-linking"))
|
|
return 0;
|
|
|
|
for (cep = ce->items; cep; cep = cep->next)
|
|
{
|
|
if (!cep->value)
|
|
{
|
|
config_error("%s:%i: blank set::server-linking::%s without value",
|
|
cep->file->filename, cep->line_number, cep->name);
|
|
errors++;
|
|
continue;
|
|
} else
|
|
if (!strcmp(cep->name, "autoconnect-strategy"))
|
|
{
|
|
if (autoconnect_strategy_strtoval(cep->value) < 0)
|
|
{
|
|
config_error("%s:%i: set::server-linking::autoconnect-strategy: invalid value '%s'. "
|
|
"Should be one of: parallel",
|
|
cep->file->filename, cep->line_number, cep->value);
|
|
errors++;
|
|
continue;
|
|
}
|
|
} else
|
|
if (!strcmp(cep->name, "connect-timeout"))
|
|
{
|
|
long v = config_checkval(cep->value, CFG_TIME);
|
|
if ((v < 5) || (v > 30))
|
|
{
|
|
config_error("%s:%i: set::server-linking::connect-timeout should be between 5 and 60 seconds",
|
|
cep->file->filename, cep->line_number);
|
|
errors++;
|
|
continue;
|
|
}
|
|
} else
|
|
if (!strcmp(cep->name, "handshake-timeout"))
|
|
{
|
|
long v = config_checkval(cep->value, CFG_TIME);
|
|
if ((v < 10) || (v > 120))
|
|
{
|
|
config_error("%s:%i: set::server-linking::handshake-timeout should be between 10 and 120 seconds",
|
|
cep->file->filename, cep->line_number);
|
|
errors++;
|
|
continue;
|
|
}
|
|
} else
|
|
{
|
|
config_error("%s:%i: unknown directive set::server-linking::%s",
|
|
cep->file->filename, cep->line_number, cep->name);
|
|
errors++;
|
|
continue;
|
|
}
|
|
}
|
|
|
|
*errs = errors;
|
|
return errors ? -1 : 1;
|
|
}
|
|
|
|
int server_config_run(ConfigFile *cf, ConfigEntry *ce, int type)
|
|
{
|
|
ConfigEntry *cep;
|
|
|
|
if (type != CONFIG_SET)
|
|
return 0;
|
|
|
|
/* We are only interrested in set::server-linking.. */
|
|
if (!ce || strcmp(ce->name, "server-linking"))
|
|
return 0;
|
|
|
|
for (cep = ce->items; cep; cep = cep->next)
|
|
{
|
|
if (!strcmp(cep->name, "autoconnect-strategy"))
|
|
{
|
|
cfg.autoconnect_strategy = autoconnect_strategy_strtoval(cep->value);
|
|
} else
|
|
if (!strcmp(cep->name, "connect-timeout"))
|
|
{
|
|
cfg.connect_timeout = config_checkval(cep->value, CFG_TIME);
|
|
} else
|
|
if (!strcmp(cep->name, "handshake-timeout"))
|
|
{
|
|
cfg.handshake_timeout = config_checkval(cep->value, CFG_TIME);
|
|
}
|
|
}
|
|
return 1;
|
|
}
|
|
|
|
int server_needs_linking(ConfigItem_link *aconf)
|
|
{
|
|
ConfigItem_deny_link *deny;
|
|
Client *client;
|
|
ConfigItem_class *class;
|
|
|
|
/* We're only interested in autoconnect blocks that are valid. Also, we ignore temporary link blocks. */
|
|
if (!(aconf->outgoing.options & CONNECT_AUTO) || !aconf->outgoing.hostname || (aconf->flag.temporary == 1))
|
|
return 0;
|
|
|
|
class = aconf->class;
|
|
|
|
/* Never do more than one connection attempt per <connfreq> seconds (for the same server) */
|
|
if ((aconf->hold > TStime()))
|
|
return 0;
|
|
|
|
aconf->hold = TStime() + class->connfreq;
|
|
|
|
client = find_client(aconf->servername, NULL);
|
|
if (client)
|
|
return 0; /* Server already connected (or connecting) */
|
|
|
|
if (class->clients >= class->maxclients)
|
|
return 0; /* Class is full */
|
|
|
|
/* Check connect rules to see if we're allowed to try the link */
|
|
for (deny = conf_deny_link; deny; deny = deny->next)
|
|
if (unreal_mask_match_string(aconf->servername, deny->mask) && crule_eval(deny->rule))
|
|
return 0;
|
|
|
|
/* Yes, this server is a linking candidate */
|
|
return 1;
|
|
}
|
|
|
|
void server_autoconnect_parallel(void)
|
|
{
|
|
ConfigItem_link *aconf;
|
|
|
|
for (aconf = conf_link; aconf; aconf = aconf->next)
|
|
{
|
|
if (!server_needs_linking(aconf))
|
|
continue;
|
|
|
|
connect_server(aconf, NULL, NULL);
|
|
}
|
|
}
|
|
|
|
/** Find first (valid) autoconnect server in link blocks.
|
|
* This function should not be used directly. It is a helper function
|
|
* for find_next_autoconnect_server().
|
|
*/
|
|
ConfigItem_link *find_first_autoconnect_server(void)
|
|
{
|
|
ConfigItem_link *aconf;
|
|
|
|
for (aconf = conf_link; aconf; aconf = aconf->next)
|
|
{
|
|
if (!server_needs_linking(aconf))
|
|
continue;
|
|
return aconf; /* found! */
|
|
}
|
|
return NULL; /* none */
|
|
}
|
|
|
|
/** Find next server that we should try to autoconnect to.
|
|
* Taking into account that we last tried server 'current'.
|
|
* @param current Server the previous autoconnect attempt was made to
|
|
* @returns A link block, or NULL if no servers are suitable.
|
|
*/
|
|
ConfigItem_link *find_next_autoconnect_server(char *current)
|
|
{
|
|
ConfigItem_link *aconf;
|
|
|
|
/* If the current autoconnect server is NULL then
|
|
* just find whichever valid server is first.
|
|
*/
|
|
if (current == NULL)
|
|
return find_first_autoconnect_server();
|
|
|
|
/* Next code is a bit convulted, it would have
|
|
* been easier if conf_link was a circular list ;)
|
|
*/
|
|
|
|
/* Otherwise, walk the list up to 'current' */
|
|
for (aconf = conf_link; aconf; aconf = aconf->next)
|
|
{
|
|
if (!strcmp(aconf->servername, current))
|
|
break;
|
|
}
|
|
|
|
/* If the 'current' server dissapeared, then let's
|
|
* just pick the first one from the list.
|
|
* It is a rare event to have the link { } block
|
|
* removed of a server that we just happened to
|
|
* try to link to before, so we can afford to do
|
|
* it this way.
|
|
*/
|
|
if (!aconf)
|
|
return find_first_autoconnect_server();
|
|
|
|
/* Check the remainder for the list, in other words:
|
|
* check all servers after 'current' if they are
|
|
* ready for an outgoing connection attempt...
|
|
*/
|
|
for (aconf = aconf->next; aconf; aconf = aconf->next)
|
|
{
|
|
if (!server_needs_linking(aconf))
|
|
continue;
|
|
return aconf; /* found! */
|
|
}
|
|
|
|
/* If we get here then there are no valid servers
|
|
* after 'current', so now check for before 'current'
|
|
* (and including 'current', since we may
|
|
* have to autoconnect to that one again,
|
|
* eg if it is the only autoconnect server)...
|
|
*/
|
|
for (aconf = conf_link; aconf; aconf = aconf->next)
|
|
{
|
|
if (!server_needs_linking(aconf))
|
|
{
|
|
if (!strcmp(aconf->servername, current))
|
|
break; /* need to stop here */
|
|
continue;
|
|
}
|
|
return aconf; /* found! */
|
|
}
|
|
|
|
return NULL; /* none */
|
|
}
|
|
|
|
/** Check if we are currently connecting to a server (outgoing).
|
|
* This function takes into account not only an outgoing TCP/IP connect
|
|
* or TLS handshake, but also if we are 'somewhat connected' to that
|
|
* server but have not completed the full sync, eg we may still need
|
|
* to receive SIDs or other sync data.
|
|
* NOTE: This implicitly assumes that outgoing links only go to
|
|
* servers that will (eventually) send "EOS".
|
|
* Should be a reasonable assumption given that in nearly all
|
|
* cases we only connect to UnrealIRCd servers for the outgoing
|
|
* case, as services are "always" incoming links.
|
|
* @returns 1 if an outgoing link is in progress, 0 if not.
|
|
*/
|
|
int current_outgoing_link_in_process(void)
|
|
{
|
|
Client *client;
|
|
|
|
list_for_each_entry(client, &unknown_list, lclient_node)
|
|
{
|
|
if (client->server && *client->server->by && client->local->creationtime &&
|
|
(IsConnecting(client) || IsTLSConnectHandshake(client) || !IsSynched(client)))
|
|
{
|
|
return 1;
|
|
}
|
|
}
|
|
|
|
list_for_each_entry(client, &server_list, special_node)
|
|
{
|
|
if (client->server && *client->server->by && client->local->creationtime &&
|
|
(IsConnecting(client) || IsTLSConnectHandshake(client) || !IsSynched(client)))
|
|
{
|
|
return 1;
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
void server_autoconnect_sequential(void)
|
|
{
|
|
ConfigItem_link *aconf;
|
|
|
|
if (current_outgoing_link_in_process())
|
|
return;
|
|
|
|
/* We are currently not in the process of doing an outgoing connect,
|
|
* let's see if we need to connect to somewhere...
|
|
*/
|
|
aconf = find_next_autoconnect_server(last_autoconnect_server);
|
|
if (aconf == NULL)
|
|
return; /* No server to connect to at this time */
|
|
|
|
/* Start outgoing link attempt */
|
|
safe_strdup(last_autoconnect_server, aconf->servername);
|
|
connect_server(aconf, NULL, NULL);
|
|
}
|
|
|
|
/** Perform autoconnect to servers that are not linked yet. */
|
|
EVENT(server_autoconnect)
|
|
{
|
|
switch (cfg.autoconnect_strategy)
|
|
{
|
|
case AUTOCONNECT_PARALLEL:
|
|
server_autoconnect_parallel();
|
|
break;
|
|
case AUTOCONNECT_SEQUENTIAL:
|
|
/* Fallback is the same as sequential but we reset last_autoconnect_server on connect */
|
|
case AUTOCONNECT_SEQUENTIAL_FALLBACK:
|
|
server_autoconnect_sequential();
|
|
break;
|
|
}
|
|
}
|
|
|
|
EVENT(server_handshake_timeout)
|
|
{
|
|
Client *client, *next;
|
|
|
|
list_for_each_entry_safe(client, next, &unknown_list, lclient_node)
|
|
{
|
|
/* We are only interested in outgoing server connects */
|
|
if (!client->server || !*client->server->by || !client->local->creationtime)
|
|
continue;
|
|
|
|
/* Handle set::server-linking::connect-timeout */
|
|
if ((IsConnecting(client) || IsTLSConnectHandshake(client)) &&
|
|
((TStime() - client->local->creationtime) >= cfg.connect_timeout))
|
|
{
|
|
/* If this is a connect timeout to an outgoing server then notify ops & log it */
|
|
unreal_log(ULOG_INFO, "link", "LINK_CONNECT_TIMEOUT", client,
|
|
"Connect timeout while trying to link to server '$client' ($client.ip)");
|
|
|
|
exit_client(client, NULL, "Connection timeout");
|
|
continue;
|
|
}
|
|
|
|
/* Handle set::server-linking::handshake-timeout */
|
|
if ((TStime() - client->local->creationtime) >= cfg.handshake_timeout)
|
|
{
|
|
/* If this is a handshake timeout to an outgoing server then notify ops & log it */
|
|
unreal_log(ULOG_INFO, "link", "LINK_HANDSHAKE_TIMEOUT", client,
|
|
"Connect handshake timeout while trying to link to server '$client' ($client.ip)");
|
|
|
|
exit_client(client, NULL, "Handshake Timeout");
|
|
continue;
|
|
}
|
|
}
|
|
}
|
|
|
|
/** Check deny version { } blocks.
|
|
* @param cptr Client (a server)
|
|
* @param software Software version in use (can be NULL)
|
|
* @param protoctol UnrealIRCd protocol version in use (can be 0)
|
|
* @param flags Server flags (hardly ever used, can be NULL)
|
|
* @returns 1 if link is denied (client is already killed), 0 if not.
|
|
*/
|
|
int _check_deny_version(Client *cptr, char *software, int protocol, char *flags)
|
|
{
|
|
ConfigItem_deny_version *vlines;
|
|
|
|
for (vlines = conf_deny_version; vlines; vlines = vlines->next)
|
|
{
|
|
if (match_simple(vlines->mask, cptr->name))
|
|
break;
|
|
}
|
|
|
|
if (vlines)
|
|
{
|
|
char *proto = vlines->version;
|
|
char *vflags = vlines->flags;
|
|
int result = 0, i;
|
|
switch (*proto)
|
|
{
|
|
case '<':
|
|
proto++;
|
|
if (protocol < atoi(proto))
|
|
result = 1;
|
|
break;
|
|
case '>':
|
|
proto++;
|
|
if (protocol > atoi(proto))
|
|
result = 1;
|
|
break;
|
|
case '=':
|
|
proto++;
|
|
if (protocol == atoi(proto))
|
|
result = 1;
|
|
break;
|
|
case '!':
|
|
proto++;
|
|
if (protocol != atoi(proto))
|
|
result = 1;
|
|
break;
|
|
default:
|
|
if (protocol == atoi(proto))
|
|
result = 1;
|
|
break;
|
|
}
|
|
if (protocol == 0 || *proto == '*')
|
|
result = 0;
|
|
|
|
if (result)
|
|
{
|
|
exit_client(cptr, NULL, "Denied by deny version { } block");
|
|
return 0;
|
|
}
|
|
|
|
if (flags)
|
|
{
|
|
for (i = 0; vflags[i]; i++)
|
|
{
|
|
if (vflags[i] == '!')
|
|
{
|
|
i++;
|
|
if (strchr(flags, vflags[i])) {
|
|
result = 1;
|
|
break;
|
|
}
|
|
}
|
|
else if (!strchr(flags, vflags[i]))
|
|
{
|
|
result = 1;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (*vflags == '*' || !strcmp(flags, "0"))
|
|
result = 0;
|
|
}
|
|
|
|
if (result)
|
|
{
|
|
exit_client(cptr, NULL, "Denied by deny version { } block");
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
return 1;
|
|
}
|
|
|
|
/** Send our PROTOCTL SERVERS=x,x,x,x stuff.
|
|
* When response is set, it will be PROTOCTL SERVERS=*x,x,x (mind the asterisk).
|
|
*/
|
|
void _send_protoctl_servers(Client *client, int response)
|
|
{
|
|
char buf[512];
|
|
Client *acptr;
|
|
int sendit = 1;
|
|
|
|
sendto_one(client, NULL, "PROTOCTL EAUTH=%s,%d,%s%s,%s",
|
|
me.name, UnrealProtocol, serveropts, extraflags ? extraflags : "", version);
|
|
|
|
ircsnprintf(buf, sizeof(buf), "PROTOCTL SERVERS=%s", response ? "*" : "");
|
|
|
|
list_for_each_entry(acptr, &global_server_list, client_node)
|
|
{
|
|
snprintf(buf+strlen(buf), sizeof(buf)-strlen(buf), "%s,", acptr->id);
|
|
sendit = 1;
|
|
if (strlen(buf) > sizeof(buf)-12)
|
|
{
|
|
if (buf[strlen(buf)-1] == ',')
|
|
buf[strlen(buf)-1] = '\0';
|
|
sendto_one(client, NULL, "%s", buf);
|
|
/* We use the asterisk here too for continuation lines */
|
|
ircsnprintf(buf, sizeof(buf), "PROTOCTL SERVERS=*");
|
|
sendit = 0;
|
|
}
|
|
}
|
|
|
|
/* Remove final comma (if any) */
|
|
if (buf[strlen(buf)-1] == ',')
|
|
buf[strlen(buf)-1] = '\0';
|
|
|
|
if (sendit)
|
|
sendto_one(client, NULL, "%s", buf);
|
|
}
|
|
|
|
void _send_server_message(Client *client)
|
|
{
|
|
if (client->server && client->server->flags.server_sent)
|
|
{
|
|
#ifdef DEBUGMODE
|
|
abort();
|
|
#endif
|
|
return;
|
|
}
|
|
|
|
sendto_one(client, NULL, "SERVER %s 1 :U%d-%s%s-%s %s",
|
|
me.name, UnrealProtocol, serveropts, extraflags ? extraflags : "", me.id, me.info);
|
|
|
|
if (client->server)
|
|
client->server->flags.server_sent = 1;
|
|
}
|
|
|
|
#define LINK_DEFAULT_ERROR_MSG "Link denied (No link block found with your server name or link::incoming::mask did not match)"
|
|
|
|
/** Verify server link.
|
|
* This does authentication and authorization checks.
|
|
* @param cptr The client directly connected to us (cptr).
|
|
* @param client The client which (originally) issued the server command (client).
|
|
* @param link_out Pointer-to-pointer-to-link block. Will be set when auth OK. Caller may pass NULL if he doesn't care.
|
|
* @returns This function returns 1 on successful authentication, 0 otherwise - in which case the client has been killed.
|
|
*/
|
|
int _verify_link(Client *client, ConfigItem_link **link_out)
|
|
{
|
|
ConfigItem_link *link, *orig_link;
|
|
Client *acptr = NULL, *ocptr = NULL;
|
|
ConfigItem_ban *bconf;
|
|
|
|
/* We set the sockhost here so you can have incoming masks based on hostnames.
|
|
* Perhaps a bit late to do it here, but does anyone care?
|
|
*/
|
|
if (client->local->hostp && client->local->hostp->h_name)
|
|
set_sockhost(client, client->local->hostp->h_name);
|
|
|
|
if (link_out)
|
|
*link_out = NULL;
|
|
|
|
if (!client->local->passwd)
|
|
{
|
|
unreal_log(ULOG_ERROR, "link", "LINK_DENIED_NO_PASSWORD", client,
|
|
"Link with server $client.details denied: No password provided. Protocol error.");
|
|
exit_client(client, NULL, "Missing password");
|
|
return 0;
|
|
}
|
|
|
|
if (client->server && client->server->conf)
|
|
{
|
|
/* This is an outgoing connect so we already know what link block we are
|
|
* dealing with. It's the one in: client->server->conf
|
|
*/
|
|
|
|
/* Actually we still need to double check the servername to avoid confusion. */
|
|
if (strcasecmp(client->name, client->server->conf->servername))
|
|
{
|
|
unreal_log(ULOG_ERROR, "link", "LINK_DENIED_SERVERNAME_MISMATCH", client,
|
|
"Link with server $client.details denied: "
|
|
"Outgoing connect from link block '$link_block' but server "
|
|
"introduced itself as '$client'. Server name mismatch.",
|
|
log_data_link_block(client->server->conf));
|
|
exit_client_fmt(client, NULL, "Servername (%s) does not match name in my link block (%s)",
|
|
client->name, client->server->conf->servername);
|
|
return 0;
|
|
}
|
|
link = client->server->conf;
|
|
goto skip_host_check;
|
|
} else {
|
|
/* Hunt the linkblock down ;) */
|
|
for(link = conf_link; link; link = link->next)
|
|
if (match_simple(link->servername, client->name))
|
|
break;
|
|
}
|
|
|
|
if (!link)
|
|
{
|
|
unreal_log(ULOG_ERROR, "link", "LINK_DENIED_UNKNOWN_SERVER", client,
|
|
"Link with server $client.details denied: No link block named '$client'");
|
|
exit_client(client, NULL, LINK_DEFAULT_ERROR_MSG);
|
|
return 0;
|
|
}
|
|
|
|
if (!link->incoming.mask)
|
|
{
|
|
unreal_log(ULOG_ERROR, "link", "LINK_DENIED_NO_INCOMING", client,
|
|
"Link with server $client.details denied: Link block exists, but there is no link::incoming::mask set.",
|
|
log_data_link_block(link));
|
|
exit_client(client, NULL, LINK_DEFAULT_ERROR_MSG);
|
|
return 0;
|
|
}
|
|
|
|
orig_link = link;
|
|
link = find_link(client->name, client);
|
|
|
|
if (!link)
|
|
{
|
|
unreal_log(ULOG_ERROR, "link", "LINK_DENIED_INCOMING_MASK_MISMATCH", client,
|
|
"Link with server $client.details denied: Server is in link block but link::incoming::mask didn't match",
|
|
log_data_link_block(orig_link));
|
|
exit_client(client, NULL, LINK_DEFAULT_ERROR_MSG);
|
|
return 0;
|
|
}
|
|
|
|
skip_host_check:
|
|
/* Try to authenticate the server... */
|
|
if (!Auth_Check(client, link->auth, client->local->passwd))
|
|
{
|
|
/* Let's help admins a bit with a good error message in case
|
|
* they mix different authentication systems (plaintext password
|
|
* vs an "TLS Auth type" like spkifp/tlsclientcert/tlsclientcertfp).
|
|
* The 'if' statement below is a bit complex but it consists of 2 things:
|
|
* 1. Check if our side expects a plaintext password but we did not receive one
|
|
* 2. Check if our side expects a non-plaintext password but we did receive one
|
|
*/
|
|
if (((link->auth->type == AUTHTYPE_PLAINTEXT) && client->local->passwd && !strcmp(client->local->passwd, "*")) ||
|
|
((link->auth->type != AUTHTYPE_PLAINTEXT) && client->local->passwd && strcmp(client->local->passwd, "*")))
|
|
{
|
|
unreal_log(ULOG_ERROR, "link", "LINK_DENIED_AUTH_FAILED", client,
|
|
"Link with server $client.details denied: Authentication failed: $auth_failure_msg",
|
|
log_data_string("auth_failure_msg", "different password types on both sides of the link\n"
|
|
"Read https://www.unrealircd.org/docs/FAQ#auth-fail-mixed for more information"),
|
|
log_data_link_block(link));
|
|
} else
|
|
if (link->auth->type == AUTHTYPE_SPKIFP)
|
|
{
|
|
unreal_log(ULOG_ERROR, "link", "LINK_DENIED_AUTH_FAILED", client,
|
|
"Link with server $client.details denied: Authentication failed: $auth_failure_msg",
|
|
log_data_string("auth_failure_msg", "spkifp mismatch"),
|
|
log_data_link_block(link));
|
|
} else
|
|
if (link->auth->type == AUTHTYPE_TLS_CLIENTCERT)
|
|
{
|
|
unreal_log(ULOG_ERROR, "link", "LINK_DENIED_AUTH_FAILED", client,
|
|
"Link with server $client.details denied: Authentication failed: $auth_failure_msg",
|
|
log_data_string("auth_failure_msg", "tlsclientcert mismatch"),
|
|
log_data_link_block(link));
|
|
} else
|
|
if (link->auth->type == AUTHTYPE_TLS_CLIENTCERTFP)
|
|
{
|
|
unreal_log(ULOG_ERROR, "link", "LINK_DENIED_AUTH_FAILED", client,
|
|
"Link with server $client.details denied: Authentication failed: $auth_failure_msg",
|
|
log_data_string("auth_failure_msg", "certfp mismatch"),
|
|
log_data_link_block(link));
|
|
} else
|
|
{
|
|
unreal_log(ULOG_ERROR, "link", "LINK_DENIED_AUTH_FAILED", client,
|
|
"Link with server $client.details denied: Authentication failed: $auth_failure_msg",
|
|
log_data_string("auth_failure_msg", "bad password"),
|
|
log_data_link_block(link));
|
|
}
|
|
exit_client(client, NULL, "Link denied (Authentication failed)");
|
|
return 0;
|
|
}
|
|
|
|
/* Verify the TLS certificate (if requested) */
|
|
if (link->verify_certificate)
|
|
{
|
|
char *errstr = NULL;
|
|
|
|
if (!IsTLS(client))
|
|
{
|
|
unreal_log(ULOG_ERROR, "link", "LINK_DENIED_VERIFY_CERTIFICATE_FAILED", client,
|
|
"Link with server $client.details denied: verify-certificate failed: $certificate_failure_msg",
|
|
log_data_string("certificate_failure_msg", "not using TLS"),
|
|
log_data_link_block(link));
|
|
exit_client(client, NULL, "Link denied (Not using TLS)");
|
|
return 0;
|
|
}
|
|
if (!verify_certificate(client->local->ssl, link->servername, &errstr))
|
|
{
|
|
unreal_log(ULOG_ERROR, "link", "LINK_DENIED_VERIFY_CERTIFICATE_FAILED", client,
|
|
"Link with server $client.details denied: verify-certificate failed: $certificate_failure_msg",
|
|
log_data_string("certificate_failure_msg", errstr),
|
|
log_data_link_block(link));
|
|
exit_client(client, NULL, "Link denied (Certificate verification failed)");
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
if ((bconf = find_ban(NULL, client->name, CONF_BAN_SERVER)))
|
|
{
|
|
unreal_log(ULOG_ERROR, "link", "LINK_DENIED_SERVER_BAN", client,
|
|
"Link with server $client.details denied: "
|
|
"Server is banned ($ban_reason)",
|
|
log_data_string("ban_reason", bconf->reason),
|
|
log_data_link_block(link));
|
|
exit_client_fmt(client, NULL, "Banned server: %s", bconf->reason);
|
|
return 0;
|
|
}
|
|
|
|
if (link->class->clients + 1 > link->class->maxclients)
|
|
{
|
|
unreal_log(ULOG_ERROR, "link", "LINK_DENIED_CLASS_FULL", client,
|
|
"Link with server $client.details denied: "
|
|
"class '$link_block.class' is full",
|
|
log_data_link_block(link));
|
|
exit_client(client, NULL, "Full class");
|
|
return 0;
|
|
}
|
|
if (!IsLocalhost(client) && (iConf.plaintext_policy_server == POLICY_DENY) && !IsSecure(client))
|
|
{
|
|
unreal_log(ULOG_ERROR, "link", "LINK_DENIED_NO_TLS", client,
|
|
"Link with server $client.details denied: "
|
|
"Server needs to use TLS (set::plaintext-policy::server is 'deny')\n"
|
|
"See https://www.unrealircd.org/docs/FAQ#server-requires-tls",
|
|
log_data_link_block(link));
|
|
exit_client(client, NULL, "Servers need to use TLS (set::plaintext-policy::server is 'deny')");
|
|
return 0;
|
|
}
|
|
if (IsSecure(client) && (iConf.outdated_tls_policy_server == POLICY_DENY) && outdated_tls_client(client))
|
|
{
|
|
unreal_log(ULOG_ERROR, "link", "LINK_DENIED_OUTDATED_TLS", client,
|
|
"Link with server $client.details denied: "
|
|
"Server is using an outdated TLS protocol or cipher ($tls_cipher) and set::outdated-tls-policy::server is 'deny'.\n"
|
|
"See https://www.unrealircd.org/docs/FAQ#server-outdated-tls",
|
|
log_data_link_block(link),
|
|
log_data_string("tls_cipher", tls_get_cipher(client)));
|
|
exit_client(client, NULL, "Server using outdates TLS protocol or cipher (set::outdated-tls-policy::server is 'deny')");
|
|
return 0;
|
|
}
|
|
/* This one is at the end, because it causes us to delink another server,
|
|
* so we want to be (reasonably) sure that this one will succeed before
|
|
* breaking the other one.
|
|
*/
|
|
if ((acptr = find_server(client->name, NULL)))
|
|
{
|
|
if (IsMe(acptr))
|
|
{
|
|
unreal_log(ULOG_ERROR, "link", "LINK_DENIED_SERVER_EXISTS", client,
|
|
"Link with server $client.details denied: "
|
|
"Server is trying to link with my name ($me_name)",
|
|
log_data_string("me_name", me.name),
|
|
log_data_link_block(link));
|
|
exit_client(client, NULL, "Server Exists (server trying to link with same name as myself)");
|
|
return 0;
|
|
} else {
|
|
unreal_log(ULOG_ERROR, "link", "LINK_DROPPED_REINTRODUCED", client,
|
|
"Link with server $client.details causes older link "
|
|
"with same server via $existing_client.server.uplink to be dropped.",
|
|
log_data_client("existing_client", acptr),
|
|
log_data_link_block(link));
|
|
exit_client_ex(acptr, client->direction, NULL, "Old link dropped, resyncing");
|
|
}
|
|
}
|
|
|
|
if (link_out)
|
|
*link_out = link;
|
|
return 1;
|
|
}
|
|
|
|
/** Server command. Only for locally connected servers!!.
|
|
* parv[1] = server name
|
|
* parv[2] = hop count
|
|
* parv[3] = server description, may include protocol version and other stuff too (VL)
|
|
*/
|
|
CMD_FUNC(cmd_server)
|
|
{
|
|
const char *servername = NULL; /* Pointer for servername */
|
|
char *ch = NULL; /* */
|
|
char descbuf[BUFSIZE];
|
|
int hop = 0;
|
|
char info[REALLEN + 61];
|
|
ConfigItem_link *aconf = NULL;
|
|
ConfigItem_deny_link *deny;
|
|
char *flags = NULL, *protocol = NULL, *inf = NULL, *num = NULL;
|
|
int incoming;
|
|
|
|
if (IsUser(client))
|
|
{
|
|
sendnumeric(client, ERR_ALREADYREGISTRED);
|
|
sendnotice(client, "*** Sorry, but your IRC program doesn't appear to support changing servers.");
|
|
return;
|
|
}
|
|
|
|
if (parc < 4 || (!*parv[3]))
|
|
{
|
|
exit_client(client, NULL, "Not enough SERVER parameters");
|
|
return;
|
|
}
|
|
|
|
servername = parv[1];
|
|
|
|
/* Remote 'SERVER' command is not possible on a 100% SID network */
|
|
if (!MyConnect(client))
|
|
{
|
|
unreal_log(ULOG_ERROR, "link", "LINK_OLD_PROTOCOL", client,
|
|
"Server link $client tried to introduce $servername using SERVER command. "
|
|
"Server is using an old and unsupported protocol from UnrealIRCd 3.2.x or earlier. "
|
|
"See https://www.unrealircd.org/docs/FAQ#old-server-protocol",
|
|
log_data_string("servername", servername));
|
|
exit_client(client->direction, NULL, "Introduced another server with unsupported protocol");
|
|
return;
|
|
}
|
|
|
|
if (client->local->listener && (client->local->listener->options & LISTENER_CLIENTSONLY))
|
|
{
|
|
exit_client(client, NULL, "This port is for clients only");
|
|
return;
|
|
}
|
|
|
|
if (!valid_server_name(servername))
|
|
{
|
|
exit_client(client, NULL, "Bogus server name");
|
|
return;
|
|
}
|
|
|
|
if (!client->local->passwd)
|
|
{
|
|
exit_client(client, NULL, "Missing password");
|
|
return;
|
|
}
|
|
|
|
/* We set the client->name early here, even though it is not authenticated yet.
|
|
* Reason is that it makes the notices and logging more useful.
|
|
* This should be safe as it is not in the server linked list yet or hash table.
|
|
* CMTSRV941 -- Syzop.
|
|
*/
|
|
strlcpy(client->name, servername, sizeof(client->name));
|
|
|
|
if (!verify_link(client, &aconf))
|
|
return; /* Rejected */
|
|
|
|
/* From this point the server is authenticated, so we can be more verbose
|
|
* with notices to ircops and in exit_client() and such.
|
|
*/
|
|
|
|
|
|
if (strlen(client->id) != 3)
|
|
{
|
|
unreal_log(ULOG_ERROR, "link", "LINK_OLD_PROTOCOL", client,
|
|
"Server link $servername rejected. Server is using an old and unsupported protocol from UnrealIRCd 3.2.x or earlier. "
|
|
"See https://www.unrealircd.org/docs/FAQ#old-server-protocol",
|
|
log_data_string("servername", servername));
|
|
exit_client(client, NULL, "Server using old unsupported protocol from UnrealIRCd 3.2.x or earlier. "
|
|
"See https://www.unrealircd.org/docs/FAQ#old-server-protocol");
|
|
return;
|
|
}
|
|
|
|
hop = atol(parv[2]);
|
|
if (hop != 1)
|
|
{
|
|
unreal_log(ULOG_ERROR, "link", "LINK_DENIED_INVALID_HOPCOUNT", client,
|
|
"Server link $servername rejected. Directly linked server provided a hopcount of $hopcount, while 1 was expected.",
|
|
log_data_string("servername", servername),
|
|
log_data_integer("hopcount", hop));
|
|
exit_client(client, NULL, "Invalid SERVER message, hop count must be 1");
|
|
return;
|
|
}
|
|
client->hopcount = hop;
|
|
|
|
strlcpy(info, parv[parc - 1], sizeof(info));
|
|
|
|
/* Parse "VL" data in description */
|
|
if (SupportVL(client))
|
|
{
|
|
char tmp[REALLEN + 61];
|
|
inf = protocol = flags = num = NULL;
|
|
strlcpy(tmp, info, sizeof(tmp)); /* work on a copy */
|
|
|
|
/* We are careful here to allow invalid syntax or missing
|
|
* stuff, which mean that particular variable will stay NULL.
|
|
*/
|
|
|
|
protocol = strtok(tmp, "-");
|
|
if (protocol)
|
|
flags = strtok(NULL, "-");
|
|
if (flags)
|
|
num = strtok(NULL, " ");
|
|
if (num)
|
|
inf = strtok(NULL, "");
|
|
if (inf)
|
|
{
|
|
strlcpy(client->info, inf[0] ? inf : "server", sizeof(client->info)); /* set real description */
|
|
|
|
if (!_check_deny_version(client, NULL, atoi(protocol), flags))
|
|
return; /* Rejected */
|
|
} else {
|
|
strlcpy(client->info, info[0] ? info : "server", sizeof(client->info));
|
|
}
|
|
} else {
|
|
strlcpy(client->info, info[0] ? info : "server", sizeof(client->info));
|
|
}
|
|
|
|
/* Process deny server { } restrictions */
|
|
for (deny = conf_deny_link; deny; deny = deny->next)
|
|
{
|
|
if (deny->flag.type == CRULE_ALL && unreal_mask_match_string(servername, deny->mask)
|
|
&& crule_eval(deny->rule))
|
|
{
|
|
unreal_log(ULOG_ERROR, "link", "LINK_DENIED_DENY_LINK_BLOCK", client,
|
|
"Server link $servername rejected by deny link { } block.",
|
|
log_data_string("servername", servername));
|
|
exit_client(client, NULL, "Disallowed by connection rule");
|
|
return;
|
|
}
|
|
}
|
|
|
|
if (aconf->options & CONNECT_QUARANTINE)
|
|
SetQuarantined(client);
|
|
|
|
ircsnprintf(descbuf, sizeof descbuf, "Server: %s", servername);
|
|
fd_desc(client->local->fd, descbuf);
|
|
|
|
incoming = IsUnknown(client) ? 1 : 0;
|
|
|
|
if (client->local->passwd)
|
|
safe_free(client->local->passwd);
|
|
|
|
/* Set up server structure */
|
|
free_pending_net(client);
|
|
SetServer(client);
|
|
irccounts.me_servers++;
|
|
irccounts.servers++;
|
|
irccounts.unknown--;
|
|
list_move(&client->client_node, &global_server_list);
|
|
list_move(&client->lclient_node, &lclient_list);
|
|
list_add(&client->special_node, &server_list);
|
|
|
|
if (find_uline(client->name))
|
|
{
|
|
if (client->server && client->server->features.software && !strncmp(client->server->features.software, "UnrealIRCd-", 11))
|
|
{
|
|
unreal_log(ULOG_ERROR, "link", "BAD_ULINES", client,
|
|
"Bad ulines! Server $client matches your ulines { } block, but this server "
|
|
"is an UnrealIRCd server. UnrealIRCd servers should never be ulined as it "
|
|
"causes security issues. Ulines should only be added for services! "
|
|
"See https://www.unrealircd.org/docs/FAQ#bad-ulines.");
|
|
exit_client(client, NULL, "Bad ulines. See https://www.unrealircd.org/docs/FAQ#bad-ulines");
|
|
}
|
|
SetULine(client);
|
|
}
|
|
|
|
find_or_add(client->name);
|
|
|
|
if (IsSecure(client))
|
|
{
|
|
unreal_log(ULOG_INFO, "link", "SERVER_LINKED", client,
|
|
"Server linked: $me -> $client [secure: $tls_cipher]",
|
|
log_data_string("tls_cipher", tls_get_cipher(client)),
|
|
log_data_client("me", &me));
|
|
tls_link_notification_verify(client, aconf);
|
|
}
|
|
else
|
|
{
|
|
unreal_log(ULOG_INFO, "link", "SERVER_LINKED", client,
|
|
"Server linked: $me -> $client",
|
|
log_data_client("me", &me));
|
|
/* Print out a warning if linking to a non-TLS server unless it's localhost.
|
|
* Yeah.. there are still other cases when non-TLS links are fine (eg: local IP
|
|
* of the same machine), we won't bother with detecting that. -- Syzop
|
|
*/
|
|
if (!IsLocalhost(client) && (iConf.plaintext_policy_server == POLICY_WARN))
|
|
{
|
|
unreal_log(ULOG_WARNING, "link", "LINK_WARNING_NO_TLS", client,
|
|
"Link with server $client.details is unencrypted (not TLS). "
|
|
"We highly recommend to use TLS for server linking. "
|
|
"See https://www.unrealircd.org/docs/Linking_servers",
|
|
log_data_link_block(aconf));
|
|
}
|
|
if (IsSecure(client) && (iConf.outdated_tls_policy_server == POLICY_WARN) && outdated_tls_client(client))
|
|
{
|
|
unreal_log(ULOG_WARNING, "link", "LINK_WARNING_OUTDATED_TLS", client,
|
|
"Link with server $client.details is using an outdated "
|
|
"TLS protocol or cipher ($tls_cipher).",
|
|
log_data_link_block(aconf),
|
|
log_data_string("tls_cipher", tls_get_cipher(client)));
|
|
}
|
|
}
|
|
|
|
add_to_client_hash_table(client->name, client);
|
|
/* doesnt duplicate client->server if allocted this struct already */
|
|
make_server(client);
|
|
client->uplink = &me;
|
|
if (!client->server->conf)
|
|
client->server->conf = aconf; /* Only set serv->conf to aconf if not set already! Bug #0003913 */
|
|
if (incoming)
|
|
client->server->conf->refcount++;
|
|
client->server->conf->class->clients++;
|
|
client->local->class = client->server->conf->class;
|
|
|
|
RunHook(HOOKTYPE_SERVER_CONNECT, client);
|
|
|
|
server_sync(client, aconf, incoming);
|
|
}
|
|
|
|
/** Remote server command (SID).
|
|
* parv[1] = server name
|
|
* parv[2] = hop count (always >1)
|
|
* parv[3] = SID
|
|
* parv[4] = server description
|
|
*/
|
|
CMD_FUNC(cmd_sid)
|
|
{
|
|
Client *acptr, *ocptr;
|
|
ConfigItem_link *aconf;
|
|
ConfigItem_ban *bconf;
|
|
int hop;
|
|
const char *servername = parv[1];
|
|
Client *direction = client->direction; /* lazy, since this function may be removed soon */
|
|
|
|
/* Only allow this command from server sockets */
|
|
if (!IsServer(client->direction))
|
|
{
|
|
sendnumeric(client, ERR_NOTFORUSERS, "SID");
|
|
return;
|
|
}
|
|
|
|
if (parc < 4 || BadPtr(parv[3]))
|
|
{
|
|
sendnumeric(client, ERR_NEEDMOREPARAMS, "SID");
|
|
return;
|
|
}
|
|
|
|
/* The SID check is done early because we do all the killing by SID,
|
|
* so we want to know if that won't work first.
|
|
*/
|
|
if (!valid_sid(parv[3]))
|
|
{
|
|
unreal_log(ULOG_ERROR, "link", "REMOTE_LINK_DENIED_INVALID_SID", client,
|
|
"Denied remote server $servername which was introduced by $client: "
|
|
"Invalid SID.",
|
|
log_data_string("servername", servername),
|
|
log_data_string("sid", parv[3]));
|
|
/* Since we cannot SQUIT via SID (since it is invalid), this gives
|
|
* us huge doubts about the accuracy of the uplink, so in this case
|
|
* we terminate the entire uplink.
|
|
*/
|
|
exit_client(client, NULL, "Trying to introduce a server with an invalid SID");
|
|
return;
|
|
}
|
|
|
|
/* Check if server already exists... */
|
|
if ((acptr = find_server(servername, NULL)))
|
|
{
|
|
/* Found. Bad. Quit. */
|
|
|
|
if (IsMe(acptr))
|
|
{
|
|
/* This should never happen, not even due to a race condition.
|
|
* We cannot send SQUIT here either since it is unclear what
|
|
* side would be squitted.
|
|
* As said, not really important, as this does not happen anyway.
|
|
*/
|
|
unreal_log(ULOG_ERROR, "link", "REMOTE_LINK_DENIED_DUPLICATE_SERVER_IS_ME", client,
|
|
"Denied remote server $servername which was introduced by $client: "
|
|
"Server is using our servername, this should be impossible!",
|
|
log_data_string("servername", servername));
|
|
sendto_one(client, NULL, "ERROR: Server %s exists (it's me!)", me.name);
|
|
exit_client(client, NULL, "Server Exists");
|
|
return;
|
|
}
|
|
|
|
unreal_log(ULOG_ERROR, "link", "REMOTE_LINK_DENIED_DUPLICATE_SERVER", client,
|
|
"Denied remote server $servername which was introduced by $client: "
|
|
"Already linked via $existing_client.server.uplink.",
|
|
log_data_string("servername", servername),
|
|
log_data_client("existing_client", acptr));
|
|
// FIXME: oldest should die.
|
|
// FIXME: code below looks wrong, it checks direction TS instead of anything else
|
|
acptr = acptr->direction;
|
|
ocptr = (direction->local->creationtime > acptr->local->creationtime) ? acptr : direction;
|
|
acptr = (direction->local->creationtime > acptr->local->creationtime) ? direction : acptr;
|
|
// FIXME: Wait, this kills entire acptr? Without sending SQUIT even :D
|
|
exit_client(acptr, NULL, "Server Exists");
|
|
return;
|
|
}
|
|
|
|
if ((acptr = find_client(parv[3], NULL)))
|
|
{
|
|
unreal_log(ULOG_ERROR, "link", "LINK_DENIED_DUPLICATE_SID_SERVER", client,
|
|
"Denied server $servername with SID $sid: Server with SID $existing_client.id ($existing_client) is already linked.",
|
|
log_data_string("servername", servername),
|
|
log_data_string("sid", parv[3]),
|
|
log_data_client("existing_client", acptr));
|
|
sendto_one(client, NULL, "SQUIT %s :Server with this SID (%s) already exists (%s)", parv[3], parv[3], acptr->name);
|
|
return;
|
|
}
|
|
|
|
/* Check deny server { } */
|
|
if ((bconf = find_ban(NULL, servername, CONF_BAN_SERVER)))
|
|
{
|
|
unreal_log(ULOG_ERROR, "link", "REMOTE_LINK_DENIED_SERVER_BAN", client,
|
|
"Denied remote server $servername which was introduced by $client: "
|
|
"Server is banned ($ban_reason)",
|
|
log_data_string("ban_reason", bconf->reason));
|
|
/* Before UnrealIRCd 6 this would SQUIT the server who introduced
|
|
* this server. That seems a bit of an overreaction, so we now
|
|
* send a SQUIT instead.
|
|
*/
|
|
sendto_one(client, NULL, "SQUIT %s :Banned server: %s", parv[3], bconf->reason);
|
|
return;
|
|
}
|
|
|
|
/* OK, let us check the data now */
|
|
if (!valid_server_name(servername))
|
|
{
|
|
unreal_log(ULOG_ERROR, "link", "REMOTE_LINK_DENIED_INVALID_SERVERNAME", client,
|
|
"Denied remote server $servername which was introduced by $client: "
|
|
"Invalid server name.",
|
|
log_data_string("servername", servername));
|
|
sendto_one(client, NULL, "SQUIT %s :Invalid servername", parv[3]);
|
|
return;
|
|
}
|
|
|
|
hop = atoi(parv[2]);
|
|
if (hop < 2)
|
|
{
|
|
unreal_log(ULOG_ERROR, "link", "REMOTE_LINK_DENIED_INVALID_HOP_COUNT", client,
|
|
"Denied remote server $servername which was introduced by $client: "
|
|
"Invalid server name.",
|
|
log_data_string("servername", servername),
|
|
log_data_integer("hop_count", hop));
|
|
sendto_one(client, NULL, "SQUIT %s :Invalid hop count (%d)", parv[3], hop);
|
|
return;
|
|
}
|
|
|
|
if (!client->direction->server->conf)
|
|
{
|
|
unreal_log(ULOG_ERROR, "link", "BUG_LOST_CONFIG", client,
|
|
"[BUG] Lost link conf record for link $direction.",
|
|
log_data_client("direction", direction));
|
|
exit_client(client->direction, NULL, "BUG: lost link configuration");
|
|
return;
|
|
}
|
|
|
|
aconf = client->direction->server->conf;
|
|
|
|
if (!aconf->hub)
|
|
{
|
|
unreal_log(ULOG_ERROR, "link", "REMOTE_LINK_DENIED_NO_HUB", client,
|
|
"Denied remote server $servername which was introduced by $client: "
|
|
"Server may not introduce this server ($direction is not a hub).",
|
|
log_data_string("servername", servername),
|
|
log_data_client("direction", client->direction));
|
|
sendto_one(client, NULL, "SQUIT %s :Server is not permitted to be a hub: %s",
|
|
parv[3], client->direction->name);
|
|
return;
|
|
}
|
|
|
|
if (!match_simple(aconf->hub, servername))
|
|
{
|
|
unreal_log(ULOG_ERROR, "link", "REMOTE_LINK_DENIED_NO_MATCHING_HUB", client,
|
|
"Denied remote server $servername which was introduced by $client: "
|
|
"Server may not introduce this server ($direction hubmask does not allow it).",
|
|
log_data_string("servername", servername),
|
|
log_data_client("direction", client->direction));
|
|
sendto_one(client, NULL, "SQUIT %s :Hub config for %s does not allow introducing this server",
|
|
parv[3], client->direction->name);
|
|
return;
|
|
}
|
|
|
|
if (aconf->leaf)
|
|
{
|
|
if (!match_simple(aconf->leaf, servername))
|
|
{
|
|
unreal_log(ULOG_ERROR, "link", "REMOTE_LINK_DENIED_NO_MATCHING_LEAF", client,
|
|
"Denied remote server $servername which was introduced by $client: "
|
|
"Server may not introduce this server ($direction leaf config does not allow it).",
|
|
log_data_string("servername", servername),
|
|
log_data_client("direction", client->direction));
|
|
sendto_one(client, NULL, "SQUIT %s :Leaf config for %s does not allow introducing this server",
|
|
parv[3], client->direction->name);
|
|
return;
|
|
}
|
|
}
|
|
|
|
if (aconf->leaf_depth && (hop > aconf->leaf_depth))
|
|
{
|
|
unreal_log(ULOG_ERROR, "link", "REMOTE_LINK_DENIED_LEAF_DEPTH", client,
|
|
"Denied remote server $servername which was introduced by $client: "
|
|
"Server may not introduce this server ($direction leaf depth config does not allow it).",
|
|
log_data_string("servername", servername),
|
|
log_data_client("direction", client->direction));
|
|
sendto_one(client, NULL, "SQUIT %s :Leaf depth config for %s does not allow introducing this server",
|
|
parv[3], client->direction->name);
|
|
return;
|
|
}
|
|
|
|
/* All approved, add the server */
|
|
acptr = make_client(direction, find_server(client->name, direction));
|
|
strlcpy(acptr->name, servername, sizeof(acptr->name));
|
|
acptr->hopcount = hop;
|
|
strlcpy(acptr->id, parv[3], sizeof(acptr->id));
|
|
strlcpy(acptr->info, parv[parc - 1], sizeof(acptr->info));
|
|
make_server(acptr);
|
|
SetServer(acptr);
|
|
/* If this server is U-lined, or the parent is, then mark it as U-lined */
|
|
if (IsULine(client) || find_uline(acptr->name))
|
|
SetULine(acptr);
|
|
irccounts.servers++;
|
|
find_or_add(acptr->name);
|
|
add_client_to_list(acptr);
|
|
add_to_client_hash_table(acptr->name, acptr);
|
|
add_to_id_hash_table(acptr->id, acptr);
|
|
list_move(&acptr->client_node, &global_server_list);
|
|
|
|
if (IsULine(client->direction) || IsSynched(client->direction))
|
|
{
|
|
/* Log these (but don't show when still syncing) */
|
|
unreal_log(ULOG_INFO, "link", "SERVER_LINKED_REMOTE", acptr,
|
|
"Server linked: $client -> $other_server",
|
|
log_data_client("other_server", client));
|
|
}
|
|
|
|
RunHook(HOOKTYPE_SERVER_CONNECT, acptr);
|
|
|
|
sendto_server(client, 0, 0, NULL, ":%s SID %s %d %s :%s",
|
|
acptr->uplink->id, acptr->name, hop + 1, acptr->id, acptr->info);
|
|
|
|
RunHook(HOOKTYPE_POST_SERVER_CONNECT, acptr);
|
|
}
|
|
|
|
void _introduce_user(Client *to, Client *acptr)
|
|
{
|
|
build_umode_string(acptr, 0, SEND_UMODES, buf);
|
|
|
|
sendto_one_nickcmd(to, NULL, acptr, buf);
|
|
|
|
send_moddata_client(to, acptr);
|
|
|
|
if (acptr->user->away)
|
|
sendto_one(to, NULL, ":%s AWAY :%s", acptr->id, acptr->user->away);
|
|
|
|
if (acptr->user->swhois)
|
|
{
|
|
SWhois *s;
|
|
for (s = acptr->user->swhois; s; s = s->next)
|
|
{
|
|
if (CHECKSERVERPROTO(to, PROTO_EXTSWHOIS))
|
|
{
|
|
sendto_one(to, NULL, ":%s SWHOIS %s + %s %d :%s",
|
|
me.id, acptr->name, s->setby, s->priority, s->line);
|
|
} else
|
|
{
|
|
sendto_one(to, NULL, ":%s SWHOIS %s :%s",
|
|
me.id, acptr->name, s->line);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
#define SafeStr(x) ((x && *(x)) ? (x) : "*")
|
|
|
|
/** Broadcast SINFO.
|
|
* @param cptr The server to send the information about.
|
|
* @param to The server to send the information TO (NULL for broadcast).
|
|
* @param except The direction NOT to send to.
|
|
* This function takes into account that the server may not
|
|
* provide all of the detailed info. If any information is
|
|
* absent we will send 0 for numbers and * for NULL strings.
|
|
*/
|
|
void _broadcast_sinfo(Client *acptr, Client *to, Client *except)
|
|
{
|
|
char chanmodes[128], buf[512];
|
|
|
|
if (acptr->server->features.chanmodes[0])
|
|
{
|
|
snprintf(chanmodes, sizeof(chanmodes), "%s,%s,%s,%s",
|
|
acptr->server->features.chanmodes[0],
|
|
acptr->server->features.chanmodes[1],
|
|
acptr->server->features.chanmodes[2],
|
|
acptr->server->features.chanmodes[3]);
|
|
} else {
|
|
strlcpy(chanmodes, "*", sizeof(chanmodes));
|
|
}
|
|
|
|
snprintf(buf, sizeof(buf), "%lld %d %s %s %s :%s",
|
|
(long long)acptr->server->boottime,
|
|
acptr->server->features.protocol,
|
|
SafeStr(acptr->server->features.usermodes),
|
|
chanmodes,
|
|
SafeStr(acptr->server->features.nickchars),
|
|
SafeStr(acptr->server->features.software));
|
|
|
|
if (to)
|
|
{
|
|
/* Targetted to one server */
|
|
sendto_one(to, NULL, ":%s SINFO %s", acptr->id, buf);
|
|
} else {
|
|
/* Broadcast (except one side...) */
|
|
sendto_server(except, 0, 0, NULL, ":%s SINFO %s", acptr->id, buf);
|
|
}
|
|
}
|
|
|
|
/** Sync all information with server 'client'.
|
|
* Eg: users, channels, everything.
|
|
* @param client The newly linked in server
|
|
* @param aconf The link block that belongs to this server
|
|
* @note This function (via cmd_server) is called from both sides, so
|
|
* from the incoming side and the outgoing side.
|
|
*/
|
|
int server_sync(Client *client, ConfigItem_link *aconf, int incoming)
|
|
{
|
|
Client *acptr;
|
|
|
|
if (incoming)
|
|
{
|
|
/* If this is an incomming connection, then we have just received
|
|
* their stuff and now send our PASS, PROTOCTL and SERVER messages back.
|
|
*/
|
|
if (!IsEAuth(client)) /* if eauth'd then we already sent the passwd */
|
|
sendto_one(client, NULL, "PASS :%s", (aconf->auth->type == AUTHTYPE_PLAINTEXT) ? aconf->auth->data : "*");
|
|
|
|
send_proto(client, aconf);
|
|
send_server_message(client);
|
|
}
|
|
|
|
/* Broadcast new server to the rest of the network */
|
|
sendto_server(client, 0, 0, NULL, ":%s SID %s 2 %s :%s",
|
|
client->uplink->id, client->name, client->id, client->info);
|
|
|
|
/* Broadcast the just-linked-in featureset to other servers on our side */
|
|
broadcast_sinfo(client, NULL, client);
|
|
|
|
/* Send moddata of &me (if any, likely minimal) */
|
|
send_moddata_client(client, &me);
|
|
|
|
list_for_each_entry_reverse(acptr, &global_server_list, client_node)
|
|
{
|
|
/* acptr->direction == acptr for acptr == client */
|
|
if (acptr->direction == client)
|
|
continue;
|
|
|
|
if (IsServer(acptr))
|
|
{
|
|
sendto_one(client, NULL, ":%s SID %s %d %s :%s",
|
|
acptr->uplink->id,
|
|
acptr->name, acptr->hopcount + 1,
|
|
acptr->id, acptr->info);
|
|
|
|
/* Also signal to the just-linked server which
|
|
* servers are fully linked.
|
|
* Now you might ask yourself "Why don't we just
|
|
* assume every server you get during link phase
|
|
* is fully linked?", well.. there's a race condition
|
|
* if 2 servers link (almost) at the same time,
|
|
* then you would think the other one is fully linked
|
|
* while in fact he was not.. -- Syzop.
|
|
*/
|
|
if (acptr->server->flags.synced)
|
|
sendto_one(client, NULL, ":%s EOS", acptr->id);
|
|
/* Send SINFO of our servers to their side */
|
|
broadcast_sinfo(acptr, client, NULL);
|
|
send_moddata_client(client, acptr); /* send moddata of server 'acptr' (if any, likely minimal) */
|
|
}
|
|
}
|
|
|
|
/* Synching nick information */
|
|
list_for_each_entry_reverse(acptr, &client_list, client_node)
|
|
{
|
|
/* acptr->direction == acptr for acptr == client */
|
|
if (acptr->direction == client)
|
|
continue;
|
|
if (IsUser(acptr))
|
|
introduce_user(client, acptr);
|
|
}
|
|
/*
|
|
** Last, pass all channels plus statuses
|
|
*/
|
|
{
|
|
Channel *channel;
|
|
for (channel = channels; channel; channel = channel->nextch)
|
|
{
|
|
send_channel_modes_sjoin3(client, channel);
|
|
if (channel->topic_time)
|
|
sendto_one(client, NULL, "TOPIC %s %s %lld :%s",
|
|
channel->name, channel->topic_nick,
|
|
(long long)channel->topic_time, channel->topic);
|
|
send_moddata_channel(client, channel);
|
|
}
|
|
}
|
|
|
|
/* Send ModData for all member(ship) structs */
|
|
send_moddata_members(client);
|
|
|
|
/* pass on TKLs */
|
|
tkl_sync(client);
|
|
|
|
RunHook(HOOKTYPE_SERVER_SYNC, client);
|
|
|
|
sendto_one(client, NULL, "NETINFO %i %lld %i %s 0 0 0 :%s",
|
|
irccounts.global_max, (long long)TStime(), UnrealProtocol,
|
|
CLOAK_KEY_CHECKSUM,
|
|
NETWORK_NAME);
|
|
|
|
/* Send EOS (End Of Sync) to the just linked server... */
|
|
sendto_one(client, NULL, ":%s EOS", me.id);
|
|
RunHook(HOOKTYPE_POST_SERVER_CONNECT, client);
|
|
return 0;
|
|
}
|
|
|
|
void tls_link_notification_verify(Client *client, ConfigItem_link *aconf)
|
|
{
|
|
const char *spki_fp;
|
|
const char *tls_fp;
|
|
char *errstr = NULL;
|
|
int verify_ok;
|
|
|
|
if (!MyConnect(client) || !client->local->ssl || !aconf)
|
|
return;
|
|
|
|
if ((aconf->auth->type == AUTHTYPE_TLS_CLIENTCERT) ||
|
|
(aconf->auth->type == AUTHTYPE_TLS_CLIENTCERTFP) ||
|
|
(aconf->auth->type == AUTHTYPE_SPKIFP))
|
|
{
|
|
/* Link verified by certificate or SPKI */
|
|
return;
|
|
}
|
|
|
|
if (aconf->verify_certificate)
|
|
{
|
|
/* Link verified by trust chain */
|
|
return;
|
|
}
|
|
|
|
tls_fp = moddata_client_get(client, "certfp");
|
|
spki_fp = spki_fingerprint(client);
|
|
if (!tls_fp || !spki_fp)
|
|
return; /* wtf ? */
|
|
|
|
/* Only bother the user if we are linking to UnrealIRCd 4.0.16+,
|
|
* since only for these versions we can give precise instructions.
|
|
*/
|
|
if (!client->server || client->server->features.protocol < 4016)
|
|
return;
|
|
|
|
|
|
verify_ok = verify_certificate(client->local->ssl, aconf->servername, &errstr);
|
|
if (errstr && strstr(errstr, "not valid for hostname"))
|
|
{
|
|
unreal_log(ULOG_INFO, "link", "HINT_VERIFY_LINK", client,
|
|
"You may want to consider verifying this server link.\n"
|
|
"More information about this can be found on https://www.unrealircd.org/Link_verification\n"
|
|
"Unfortunately the certificate of server '$client' has a name mismatch:\n"
|
|
"$tls_verify_error\n"
|
|
"This isn't a fatal error but it will prevent you from using verify-certificate yes;",
|
|
log_data_link_block(aconf),
|
|
log_data_string("tls_verify_error", errstr));
|
|
} else
|
|
if (!verify_ok)
|
|
{
|
|
unreal_log(ULOG_INFO, "link", "HINT_VERIFY_LINK", client,
|
|
"You may want to consider verifying this server link.\n"
|
|
"More information about this can be found on https://www.unrealircd.org/Link_verification\n"
|
|
"In short: in the configuration file, change the 'link $client {' block to use this as a password:\n"
|
|
"password \"$spki_fingerprint\" { spkifp; };\n"
|
|
"And follow the instructions on the other side of the link as well (which will be similar, but will use a different hash)",
|
|
log_data_link_block(aconf),
|
|
log_data_string("spki_fingerprint", spki_fp));
|
|
} else
|
|
{
|
|
unreal_log(ULOG_INFO, "link", "HINT_VERIFY_LINK", client,
|
|
"You may want to consider verifying this server link.\n"
|
|
"More information about this can be found on https://www.unrealircd.org/Link_verification\n"
|
|
"In short: in the configuration file, add the following to your 'link $client {' block:\n"
|
|
"verify-certificate yes;\n"
|
|
"Alternatively, you could use SPKI fingerprint verification. Then change the password in the link block to be:\n"
|
|
"password \"$spki_fingerprint\" { spki_fp; };",
|
|
log_data_link_block(aconf),
|
|
log_data_string("spki_fingerprint", spki_fp));
|
|
}
|
|
}
|
|
|
|
/** This will send "to" a full list of the modes for channel channel,
|
|
*
|
|
* Half of it recoded by Syzop: the whole buffering and size checking stuff
|
|
* looked weird and just plain inefficient. We now fill up our send-buffer
|
|
* really as much as we can, without causing any overflows of course.
|
|
*/
|
|
void send_channel_modes_sjoin3(Client *to, Channel *channel)
|
|
{
|
|
MessageTag *mtags = NULL;
|
|
Member *members;
|
|
Member *lp;
|
|
Ban *ban;
|
|
short nomode, nopara;
|
|
char tbuf[512]; /* work buffer, for temporary data */
|
|
char buf[1024]; /* send buffer */
|
|
char *bufptr; /* points somewhere in 'buf' */
|
|
char *p; /* points to somewhere in 'tbuf' */
|
|
int prebuflen = 0; /* points to after the <sjointoken> <TS> <chan> <fixmodes> <fixparas <..>> : part */
|
|
int sent = 0; /* we need this so we send at least 1 message about the channel (eg if +P and no members, no bans, #4459) */
|
|
char modebuf[BUFSIZE], parabuf[BUFSIZE];
|
|
|
|
if (*channel->name != '#')
|
|
return;
|
|
|
|
nomode = 0;
|
|
nopara = 0;
|
|
members = channel->members;
|
|
|
|
/* First we'll send channel, channel modes and members and status */
|
|
|
|
*modebuf = *parabuf = '\0';
|
|
channel_modes(to, modebuf, parabuf, sizeof(modebuf), sizeof(parabuf), channel, 1);
|
|
|
|
/* Strip final space if needed */
|
|
if (*parabuf && (parabuf[strlen(parabuf)-1] == ' '))
|
|
parabuf[strlen(parabuf)-1] = '\0';
|
|
|
|
if (!modebuf[1])
|
|
nomode = 1;
|
|
if (!(*parabuf))
|
|
nopara = 1;
|
|
|
|
/* Generate a new message (including msgid).
|
|
* Due to the way SJOIN works, we will use the same msgid for
|
|
* multiple SJOIN messages to servers. Rest assured that clients
|
|
* will never see these duplicate msgid's though. They
|
|
* will see a 'special' version instead with a suffix.
|
|
*/
|
|
new_message(&me, NULL, &mtags);
|
|
|
|
if (nomode && nopara)
|
|
{
|
|
ircsnprintf(buf, sizeof(buf),
|
|
":%s SJOIN %lld %s :", me.id,
|
|
(long long)channel->creationtime, channel->name);
|
|
}
|
|
if (nopara && !nomode)
|
|
{
|
|
ircsnprintf(buf, sizeof(buf),
|
|
":%s SJOIN %lld %s %s :", me.id,
|
|
(long long)channel->creationtime, channel->name, modebuf);
|
|
}
|
|
if (!nopara && !nomode)
|
|
{
|
|
ircsnprintf(buf, sizeof(buf),
|
|
":%s SJOIN %lld %s %s %s :", me.id,
|
|
(long long)channel->creationtime, channel->name, modebuf, parabuf);
|
|
}
|
|
|
|
prebuflen = strlen(buf);
|
|
bufptr = buf + prebuflen;
|
|
|
|
/* RULES:
|
|
* - Use 'tbuf' as a working buffer, use 'p' to advance in 'tbuf'.
|
|
* Thus, be sure to do a 'p = tbuf' at the top of the loop.
|
|
* - When one entry has been build, check if strlen(buf) + strlen(tbuf) > BUFSIZE - 8,
|
|
* if so, do not concat but send the current result (buf) first to the server
|
|
* and reset 'buf' to only the prebuf part (all until the ':').
|
|
* Then, in both cases, concat 'tbuf' to 'buf' and continue
|
|
* - Be sure to ALWAYS zero terminate (*p = '\0') when the entry has been build.
|
|
* - Be sure to add a space after each entry ;)
|
|
*
|
|
* For a more illustrated view, take a look at the first for loop, the others
|
|
* are pretty much the same.
|
|
*
|
|
* Follow these rules, and things would be smooth and efficient (network-wise),
|
|
* if you ignore them, expect crashes and/or heap corruption, aka: HELL.
|
|
* You have been warned.
|
|
*
|
|
* Side note: of course things would be more efficient if the prebuf thing would
|
|
* not be sent every time, but that's another story
|
|
* -- Syzop
|
|
*/
|
|
|
|
for (lp = members; lp; lp = lp->next)
|
|
{
|
|
p = mystpcpy(tbuf, modes_to_sjoin_prefix(lp->member_modes)); /* eg @+ */
|
|
p = mystpcpy(p, lp->client->id); /* nick (well, id) */
|
|
*p++ = ' ';
|
|
*p = '\0';
|
|
|
|
/* this is: if (strlen(tbuf) + strlen(buf) > BUFSIZE - 8) */
|
|
if ((p - tbuf) + (bufptr - buf) > BUFSIZE - 8)
|
|
{
|
|
/* Would overflow, so send our current stuff right now (except new stuff) */
|
|
sendto_one(to, mtags, "%s", buf);
|
|
sent++;
|
|
ircsnprintf(buf, sizeof(buf),
|
|
":%s SJOIN %lld %s :", me.id,
|
|
(long long)channel->creationtime, channel->name);
|
|
prebuflen = strlen(buf);
|
|
bufptr = buf + prebuflen;
|
|
*bufptr = '\0';
|
|
}
|
|
/* concat our stuff.. */
|
|
bufptr = mystpcpy(bufptr, tbuf);
|
|
}
|
|
|
|
for (ban = channel->banlist; ban; ban = ban->next)
|
|
{
|
|
p = tbuf;
|
|
if (SupportSJSBY(to))
|
|
p += add_sjsby(p, ban->who, ban->when);
|
|
*p++ = '&';
|
|
p = mystpcpy(p, ban->banstr);
|
|
*p++ = ' ';
|
|
*p = '\0';
|
|
|
|
/* this is: if (strlen(tbuf) + strlen(buf) > BUFSIZE - 8) */
|
|
if ((p - tbuf) + (bufptr - buf) > BUFSIZE - 8)
|
|
{
|
|
/* Would overflow, so send our current stuff right now (except new stuff) */
|
|
sendto_one(to, mtags, "%s", buf);
|
|
sent++;
|
|
ircsnprintf(buf, sizeof(buf),
|
|
":%s SJOIN %lld %s :", me.id,
|
|
(long long)channel->creationtime, channel->name);
|
|
prebuflen = strlen(buf);
|
|
bufptr = buf + prebuflen;
|
|
*bufptr = '\0';
|
|
}
|
|
/* concat our stuff.. */
|
|
bufptr = mystpcpy(bufptr, tbuf);
|
|
}
|
|
|
|
for (ban = channel->exlist; ban; ban = ban->next)
|
|
{
|
|
p = tbuf;
|
|
if (SupportSJSBY(to))
|
|
p += add_sjsby(p, ban->who, ban->when);
|
|
*p++ = '"';
|
|
p = mystpcpy(p, ban->banstr);
|
|
*p++ = ' ';
|
|
*p = '\0';
|
|
|
|
/* this is: if (strlen(tbuf) + strlen(buf) > BUFSIZE - 8) */
|
|
if ((p - tbuf) + (bufptr - buf) > BUFSIZE - 8)
|
|
{
|
|
/* Would overflow, so send our current stuff right now (except new stuff) */
|
|
sendto_one(to, mtags, "%s", buf);
|
|
sent++;
|
|
ircsnprintf(buf, sizeof(buf),
|
|
":%s SJOIN %lld %s :", me.id,
|
|
(long long)channel->creationtime, channel->name);
|
|
prebuflen = strlen(buf);
|
|
bufptr = buf + prebuflen;
|
|
*bufptr = '\0';
|
|
}
|
|
/* concat our stuff.. */
|
|
bufptr = mystpcpy(bufptr, tbuf);
|
|
}
|
|
|
|
for (ban = channel->invexlist; ban; ban = ban->next)
|
|
{
|
|
p = tbuf;
|
|
if (SupportSJSBY(to))
|
|
p += add_sjsby(p, ban->who, ban->when);
|
|
*p++ = '\'';
|
|
p = mystpcpy(p, ban->banstr);
|
|
*p++ = ' ';
|
|
*p = '\0';
|
|
|
|
/* this is: if (strlen(tbuf) + strlen(buf) > BUFSIZE - 8) */
|
|
if ((p - tbuf) + (bufptr - buf) > BUFSIZE - 8)
|
|
{
|
|
/* Would overflow, so send our current stuff right now (except new stuff) */
|
|
sendto_one(to, mtags, "%s", buf);
|
|
sent++;
|
|
ircsnprintf(buf, sizeof(buf),
|
|
":%s SJOIN %lld %s :", me.id,
|
|
(long long)channel->creationtime, channel->name);
|
|
prebuflen = strlen(buf);
|
|
bufptr = buf + prebuflen;
|
|
*bufptr = '\0';
|
|
}
|
|
/* concat our stuff.. */
|
|
bufptr = mystpcpy(bufptr, tbuf);
|
|
}
|
|
|
|
if (buf[prebuflen] || !sent)
|
|
sendto_one(to, mtags, "%s", buf);
|
|
|
|
free_message_tags(mtags);
|
|
}
|
|
|
|
void server_generic_free(ModData *m)
|
|
{
|
|
safe_free(m->ptr);
|
|
}
|
|
|
|
int server_post_connect(Client *client) {
|
|
if (cfg.autoconnect_strategy == AUTOCONNECT_SEQUENTIAL_FALLBACK && last_autoconnect_server
|
|
&& !strcmp(last_autoconnect_server, client->name))
|
|
{
|
|
last_autoconnect_server = NULL;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
/** Start an outgoing connection to a server, for server linking.
|
|
* @param aconf Configuration attached to this server
|
|
* @param by The user initiating the connection (can be NULL)
|
|
* @param hp The address to connect to.
|
|
*/
|
|
void _connect_server(ConfigItem_link *aconf, Client *by, struct hostent *hp)
|
|
{
|
|
Client *client;
|
|
|
|
if (!aconf->outgoing.hostname)
|
|
{
|
|
/* Actually the caller should make sure that this doesn't happen,
|
|
* so this error may never be triggered:
|
|
*/
|
|
unreal_log(ULOG_ERROR, "link", "LINK_ERROR_NO_OUTGOING", NULL,
|
|
"Connect to $link_block failed: link block is for incoming only (no link::outgoing::hostname set)",
|
|
log_data_link_block(aconf));
|
|
return;
|
|
}
|
|
|
|
if (!hp)
|
|
{
|
|
/* Remove "cache" */
|
|
safe_free(aconf->connect_ip);
|
|
}
|
|
/*
|
|
* If we dont know the IP# for this host and itis a hostname and
|
|
* not a ip# string, then try and find the appropriate host record.
|
|
*/
|
|
if (!aconf->connect_ip)
|
|
{
|
|
if (is_valid_ip(aconf->outgoing.hostname))
|
|
{
|
|
/* link::outgoing::hostname is an IP address. No need to resolve host. */
|
|
safe_strdup(aconf->connect_ip, aconf->outgoing.hostname);
|
|
} else
|
|
{
|
|
/* It's a hostname, let the resolver look it up. */
|
|
int ipv4_explicit_bind = 0;
|
|
|
|
if (aconf->outgoing.bind_ip && (is_valid_ip(aconf->outgoing.bind_ip) == 4))
|
|
ipv4_explicit_bind = 1;
|
|
|
|
/* We need this 'aconf->refcount++' or else there's a race condition between
|
|
* starting resolving the host and the result of the resolver (we could
|
|
* REHASH in that timeframe) leading to an invalid (freed!) 'aconf'.
|
|
* -- Syzop, bug #0003689.
|
|
*/
|
|
aconf->refcount++;
|
|
unrealdns_gethostbyname_link(aconf->outgoing.hostname, aconf, ipv4_explicit_bind);
|
|
unreal_log(ULOG_INFO, "link", "LINK_RESOLVING", NULL,
|
|
"Resolving hostname $link_block.hostname...",
|
|
log_data_link_block(aconf));
|
|
/* Going to resolve the hostname, in the meantime we return (asynchronous operation) */
|
|
return;
|
|
}
|
|
}
|
|
client = make_client(NULL, &me);
|
|
client->local->hostp = hp;
|
|
/*
|
|
* Copy these in so we have something for error detection.
|
|
*/
|
|
strlcpy(client->name, aconf->servername, sizeof(client->name));
|
|
strlcpy(client->local->sockhost, aconf->outgoing.hostname, HOSTLEN + 1);
|
|
|
|
if (!connect_server_helper(aconf, client))
|
|
{
|
|
fd_close(client->local->fd);
|
|
--OpenFiles;
|
|
client->local->fd = -2;
|
|
free_client(client);
|
|
/* Fatal error */
|
|
return;
|
|
}
|
|
/* The socket has been connected or connect is in progress. */
|
|
make_server(client);
|
|
client->server->conf = aconf;
|
|
client->server->conf->refcount++;
|
|
if (by && IsUser(by))
|
|
strlcpy(client->server->by, by->name, sizeof(client->server->by));
|
|
else
|
|
strlcpy(client->server->by, "AutoConn.", sizeof client->server->by);
|
|
SetConnecting(client);
|
|
SetOutgoing(client);
|
|
irccounts.unknown++;
|
|
list_add(&client->lclient_node, &unknown_list);
|
|
set_sockhost(client, aconf->outgoing.hostname);
|
|
add_client_to_list(client);
|
|
|
|
if (aconf->outgoing.options & CONNECT_TLS)
|
|
{
|
|
SetTLSConnectHandshake(client);
|
|
fd_setselect(client->local->fd, FD_SELECT_WRITE, unreal_tls_client_handshake, client);
|
|
}
|
|
else
|
|
fd_setselect(client->local->fd, FD_SELECT_WRITE, completed_connection, client);
|
|
|
|
unreal_log(ULOG_INFO, "link", "LINK_CONNECTING", client,
|
|
"Trying to activate link with server $client ($link_block.ip:$link_block.port)...",
|
|
log_data_link_block(aconf));
|
|
}
|
|
|
|
/** Helper function for connect_server() to prepare the actual bind()'ing and connect().
|
|
* This will also take care of logging/sending error messages.
|
|
* @param aconf Configuration entry of the server.
|
|
* @param client The client entry that we will use and fill in.
|
|
* @returns 1 on success, 0 on failure.
|
|
*/
|
|
static int connect_server_helper(ConfigItem_link *aconf, Client *client)
|
|
{
|
|
char *bindip;
|
|
char buf[BUFSIZE];
|
|
|
|
if (!aconf->connect_ip)
|
|
{
|
|
unreal_log(ULOG_ERROR, "link", "LINK_ERROR_NOIP", client,
|
|
"Connect to $client failed: no IP address to connect to",
|
|
log_data_link_block(aconf));
|
|
return 0; /* handled upstream or shouldn't happen */
|
|
}
|
|
|
|
if (strchr(aconf->connect_ip, ':'))
|
|
SetIPV6(client);
|
|
|
|
safe_strdup(client->ip, aconf->connect_ip);
|
|
|
|
snprintf(buf, sizeof buf, "Outgoing connection: %s", get_client_name(client, TRUE));
|
|
client->local->fd = fd_socket(IsIPV6(client) ? AF_INET6 : AF_INET, SOCK_STREAM, 0, buf);
|
|
if (client->local->fd < 0)
|
|
{
|
|
if (ERRNO == P_EMFILE)
|
|
{
|
|
unreal_log(ULOG_ERROR, "link", "LINK_ERROR_MAXCLIENTS", client,
|
|
"Connect to $client failed: no more sockets available",
|
|
log_data_link_block(aconf));
|
|
return 0;
|
|
}
|
|
unreal_log(ULOG_ERROR, "link", "LINK_ERROR_SOCKET", client,
|
|
"Connect to $client failed: could not create socket: $socket_error",
|
|
log_data_socket_error(-1),
|
|
log_data_link_block(aconf));
|
|
return 0;
|
|
}
|
|
if (++OpenFiles >= maxclients)
|
|
{
|
|
unreal_log(ULOG_ERROR, "link", "LINK_ERROR_MAXCLIENTS", client,
|
|
"Connect to $client failed: no more connections available",
|
|
log_data_link_block(aconf));
|
|
return 0;
|
|
}
|
|
|
|
set_sockhost(client, aconf->outgoing.hostname);
|
|
|
|
if (!aconf->outgoing.bind_ip && iConf.link_bindip)
|
|
bindip = iConf.link_bindip;
|
|
else
|
|
bindip = aconf->outgoing.bind_ip;
|
|
|
|
if (bindip && strcmp("*", bindip))
|
|
{
|
|
if (!unreal_bind(client->local->fd, bindip, 0, IsIPV6(client)))
|
|
{
|
|
unreal_log(ULOG_ERROR, "link", "LINK_ERROR_SOCKET_BIND", client,
|
|
"Connect to $client failed: could not bind socket to $link_block.bind_ip: $socket_error -- "
|
|
"Your link::outgoing::bind-ip is probably incorrect.",
|
|
log_data_socket_error(client->local->fd),
|
|
log_data_link_block(aconf));
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
set_sock_opts(client->local->fd, client, IsIPV6(client));
|
|
|
|
if (!unreal_connect(client->local->fd, client->ip, aconf->outgoing.port, IsIPV6(client)))
|
|
{
|
|
unreal_log(ULOG_ERROR, "link", "LINK_ERROR_CONNECT", client,
|
|
"Connect to $client ($link_block.ip:$link_block.port) failed: $socket_error",
|
|
log_data_socket_error(client->local->fd),
|
|
log_data_link_block(aconf));
|
|
return 0;
|
|
}
|
|
|
|
return 1;
|
|
}
|
|
|