/* * 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); /* 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-5", }; 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); 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 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->serv && *client->serv->by && client->local->firsttime && (IsConnecting(client) || IsTLSConnectHandshake(client) || !IsSynched(client))) { return 1; } } list_for_each_entry(client, &server_list, special_node) { if (client->serv && *client->serv->by && client->local->firsttime && (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->serv || !*client->serv->by || !client->local->firsttime) continue; /* Handle set::server-linking::connect-timeout */ if ((IsConnecting(client) || IsTLSConnectHandshake(client)) && ((TStime() - client->local->firsttime) >= 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->firsttime) >= 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->serv && client->serv->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->serv) client->serv->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; char *inpath = get_client_name(client, TRUE); 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->serv && client->serv->conf) { /* This is an outgoing connect so we already know what link block we are * dealing with. It's the one in: client->serv->conf */ /* Actually we still need to double check the servername to avoid confusion. */ if (strcasecmp(client->name, client->serv->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->serv->conf)); exit_client_fmt(client, NULL, "Servername (%s) does not match name in my link block (%s)", client->name, client->serv->conf->servername); return 0; } link = client->serv->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; } 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(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 SSL/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 ((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)"); } else { unreal_log(ULOG_ERROR, "link", "LINK_DENIED_SERVER_EXISTS", client, "Link with server $client.details denied: " "A server with this name already exists via $existing_client.server.uplink", log_data_client("existing_client", acptr), log_data_link_block(link)); exit_client(acptr, NULL, "Server Exists"); } 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#ERROR:_Servers_need_to_use_SSL.2FTLS", log_data_link_block(link)); exit_client(client, NULL, "Servers need to use SSL/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 SSL/TLS protocol or cipher (set::outdated-tls-policy::server is 'deny')\n" "See https://www.unrealircd.org/docs/FAQ#server-outdated-tls", log_data_link_block(link)); exit_client(client, NULL, "Server using outdates SSL/TLS protocol or cipher (set::outdated-tls-policy::server is 'deny')"); return 0; } 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) { 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)) { char buf[256]; sendto_umode_global(UMODE_OPER, "Server %s introduced %s which is using old unsupported protocol from UnrealIRCd 3.2.x or earlier. " "See https://www.unrealircd.org/docs/FAQ#old-server-protocol", client->direction->name, 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_REJECTED_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_REJECTED_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->serv && client->serv->features.software && !strncmp(client->serv->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->local->ssl)), 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)) { sendto_realops("\002WARNING:\002 This link is unencrypted (not SSL/TLS). We highly recommend to use " "SSL/TLS for server linking. See https://www.unrealircd.org/docs/Linking_servers"); } if (IsSecure(client) && (iConf.outdated_tls_policy_server == POLICY_WARN) && outdated_tls_client(client)) { sendto_realops("\002WARNING:\002 This link is using an outdated SSL/TLS protocol or cipher (%s).", tls_get_cipher(client->local->ssl)); } } add_to_client_hash_table(client->name, client); /* doesnt duplicate client->serv if allocted this struct already */ make_server(client); client->serv->up = me.name; client->srvptr = &me; if (!client->serv->conf) client->serv->conf = aconf; /* Only set serv->conf to aconf if not set already! Bug #0003913 */ if (incoming) client->serv->conf->refcount++; client->serv->conf->class->clients++; client->local->class = client->serv->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; 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 $other_client.uplink.", log_data_string("servername", servername), log_data_client("other_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->firsttime > acptr->local->firsttime) ? acptr : direction; acptr = (direction->local->firsttime > acptr->local->firsttime) ? direction : acptr; // FIXME: Wait, this kills entire acptr? Without sending SQUIT even :D exit_client(acptr, NULL, "Server Exists"); 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->serv->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->serv->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); acptr->serv->up = find_or_add(acptr->srvptr->name); 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); unreal_log(ULOG_INFO, "link", "SERVER_LINKED_REMOTE", client, "Server linked: $client (via $client.server.uplink)"); RunHook(HOOKTYPE_SERVER_CONNECT, acptr); sendto_server(client, 0, 0, NULL, ":%s SID %s %d %s :%s", acptr->srvptr->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, 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->serv->features.chanmodes[0]) { snprintf(chanmodes, sizeof(chanmodes), "%s,%s,%s,%s", acptr->serv->features.chanmodes[0], acptr->serv->features.chanmodes[1], acptr->serv->features.chanmodes[2], acptr->serv->features.chanmodes[3]); } else { strlcpy(chanmodes, "*", sizeof(chanmodes)); } snprintf(buf, sizeof(buf), "%lld %d %s %s %s :%s", (long long)acptr->serv->boottime, acptr->serv->features.protocol, SafeStr(acptr->serv->features.usermodes), chanmodes, SafeStr(acptr->serv->features.nickchars), SafeStr(acptr->serv->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->srvptr->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->srvptr->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->serv->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_KEYCRC, ircnetwork); /* 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 *acptr, ConfigItem_link *aconf) { char *spki_fp; char *tls_fp; char *errstr = NULL; int verify_ok; if (!MyConnect(acptr) || !acptr->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(acptr, "certfp"); spki_fp = spki_fingerprint(acptr); 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 (!acptr->serv || acptr->serv->features.protocol < 4016) return; sendto_realops("You may want to consider verifying this server link."); sendto_realops("More information about this can be found on https://www.unrealircd.org/Link_verification"); verify_ok = verify_certificate(acptr->local->ssl, aconf->servername, &errstr); if (errstr && strstr(errstr, "not valid for hostname")) { sendto_realops("Unfortunately the certificate of server '%s' has a name mismatch:", acptr->name); sendto_realops("%s", errstr); sendto_realops("This isn't a fatal error but it will prevent you from using verify-certificate yes;"); } else if (!verify_ok) { sendto_realops("In short: in the configuration file, change the 'link %s {' block to use this as a password:", acptr->name); sendto_realops("password \"%s\" { spkifp; };", spki_fp); sendto_realops("And follow the instructions on the other side of the link as well (which will be similar, but will use a different hash)"); } else { sendto_realops("In short: in the configuration file, add the following to your 'link %s {' block:", acptr->name); sendto_realops("verify-certificate yes;"); sendto_realops("Alternatively, you could use SPKI fingerprint verification. Then change the password in the link block to be:"); sendto_realops("password \"%s\" { spkifp; };", 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 > : 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) */ 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); 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 = tbuf; if (lp->flags & MODE_CHANOP) *p++ = '@'; if (lp->flags & MODE_VOICE) *p++ = '+'; if (lp->flags & MODE_HALFOP) *p++ = '%'; if (lp->flags & MODE_CHANOWNER) *p++ = '*'; if (lp->flags & MODE_CHANADMIN) *p++ = '~'; p = mystpcpy(p, lp->client->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++; 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++; 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++; 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++; 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; }