diff --git a/Changes b/Changes index edd6aa254..80755a631 100644 --- a/Changes +++ b/Changes @@ -1861,3 +1861,31 @@ - Use RPL_STARTTLS/ERR_STARTTLS numerics - Removed log target 'kline' from documentation, as it didn't do anything (use 'tkl' instead). Reported by nephilim and Stealth (#0003849). +- Server protocol: added PROTOCTL EATH=servername, which allows us to + authenticate the server very early in the handshake process. That way, + certain commands and PROTOCTL tokens can 'trust' the server. + See doc/technical/protoctl.txt for details. +- Server protocol: between new Unreal servers we now do the handshake a + little bit different, so it waits with sending the SERVER command until + the first PROTOCTL is received. Needed for next. +- Server protocol: added PROTOCTL SERVERS=1,2,3,4,etc by which a server can + inform the other server which servers (server numeric, actually) it has + linked. See doc/technical/protoctl.txt and next for details. +- When our server was trying to link to some server, and at the same time + another server was also trying to link with us, this would lead to a + server collision: the server would link (twice) ok at first, but then a + second later or so both would quit with 'Server Exists' with quite some + mess as a result. This isn't unique to Unreal, btw. + This happened more often when you had a low connfreq in your link blocks + (aka: quick reconnects), or had multiple hubs on autoconnect (with same + connfreq), or when you (re)started all servers at the same time. + This should now be solved by a new server handshake design, which detects + this race condition and solves it by closing one of the two (or more) + connections to avoid the issue. + This also means that it should now be safe to have multiple hubs with low + connfreq's (eg: 10s) without risking that your network falls apart. + This new server handshake (protocol updates, etc) was actually quite some + work, especially for something that only happened sporadically. I felt it + was needed though, because (re)linking stability is extremely important. + This new feature/design/fix requires extensive testing. + This feature can be disabled by: set { new-linking-protocol 0; }; diff --git a/doc/technical/protoctl.txt b/doc/technical/protoctl.txt index d1da69da3..ef0fec5d1 100644 --- a/doc/technical/protoctl.txt +++ b/doc/technical/protoctl.txt @@ -139,3 +139,18 @@ NICKCHARS This specifies a list of language characters that are allowed in n The items in the list sent as NICKCHARS=.. must always be sorted. If a server sends NICKCHARS= and if the remote parameters do not match the charsets in use locally, then the server link is rejected. + +CHANMODES Like CHANMODES from the 005 numeric. Useful to see which channel modes are + supported/used, and can also be used to properly eat parameters in parameter + modes in the MODE command (for eg: +jk 1:1 a). + +EAUTH Early Authorization. This makes it possible for servers to authenticate each + other before the regular SERVER command. Needs to be done prior to using the + SERVERS token, and possibly other tokens or commands in the future. Hence, + is recommended to be sent as first (or early) PROTOCTL token. Note also that + the PASS command should be sent prior to this PROTOCTL token. + EAUTH=my.server.name[,options] + +SERVERS Informs the other server about the other servers (numerics) on this network + (including our own numeric). + Syntax: SERVERS=numeric1,numeric2,numeric3,etc diff --git a/include/dynconf.h b/include/dynconf.h index e30fdff62..7311c1240 100644 --- a/include/dynconf.h +++ b/include/dynconf.h @@ -125,6 +125,7 @@ struct zConfiguration { char *restrict_usermodes; char *restrict_channelmodes; char *restrict_extendedbans; + int new_linking_protocol; char *channel_command_prefix; long unknown_flood_bantime; long unknown_flood_amount; @@ -222,6 +223,7 @@ extern MODVAR aConfiguration iConf; #define RESTRICT_USERMODES iConf.restrict_usermodes #define RESTRICT_CHANNELMODES iConf.restrict_channelmodes #define RESTRICT_EXTENDEDBANS iConf.restrict_extendedbans +#define NEW_LINKING_PROTOCOL iConf.new_linking_protocol #ifdef THROTTLING #define THROTTLING_PERIOD iConf.throttle_period #define THROTTLING_COUNT iConf.throttle_count @@ -333,6 +335,7 @@ struct SetCheck { unsigned has_restrict_usermodes:1; unsigned has_restrict_channelmodes:1; unsigned has_restrict_extendedbans:1; + unsigned has_new_linking_protocol:1; unsigned has_channel_command_prefix:1; unsigned has_anti_flood_unknown_flood_bantime:1; unsigned has_anti_flood_unknown_flood_amount:1; diff --git a/include/h.h b/include/h.h index 3184c83e4..a73830626 100644 --- a/include/h.h +++ b/include/h.h @@ -547,6 +547,7 @@ extern int Auth_CheckError(ConfigEntry *ce); extern long xbase64dec(char *b64); extern aClient *find_server_b64_or_real(char *name); extern aClient *find_server_by_base64(char *b64); +extern aClient *find_server_by_numeric(long value); extern int is_chanownprotop(aClient *cptr, aChannel *chptr); extern int is_skochanop(aClient *cptr, aChannel *chptr); extern char *make_virthost(aClient *sptr, char *curr, char *new, int mode); @@ -747,6 +748,8 @@ extern MODVAR unsigned char *(*StripColors)(unsigned char *text); extern MODVAR const char *(*StripControlCodes)(unsigned char *text); extern MODVAR void (*spamfilter_build_user_string)(char *buf, char *nick, aClient *acptr); extern MODVAR int (*is_silenced)(aClient *sptr, aClient *acptr); +extern MODVAR void (*send_protoctl_servers)(aClient *sptr, int response); +extern MODVAR int (*verify_link)(aClient *cptr, aClient *sptr, char *servername, ConfigItem_link **link_out); /* /Efuncs */ extern MODVAR aMotd *opermotd, *svsmotd, *motd, *botmotd, *smotd; extern MODVAR int max_connection_count; @@ -795,3 +798,8 @@ extern void free_motd(aMotd *m); extern void fix_timers(void); extern char *chfl_to_sjoin_symbol(int s); extern char chfl_to_chanmode(int s); +extern void add_pending_net(aClient *sptr, char *str); +extern void free_pending_net(aClient *sptr); +extern aPendingNet *find_pending_net_by_numeric_butone(int numeric, aClient *exempt); +extern aClient *find_pending_net_duplicates(aClient *cptr, aClient **srv, int *numeric); +extern aClient *find_non_pending_net_duplicates(aClient *cptr); diff --git a/include/modules.h b/include/modules.h index d3ad9b26c..ac3d6cf1e 100644 --- a/include/modules.h +++ b/include/modules.h @@ -700,6 +700,8 @@ int CallCmdoverride(Cmdoverride *ovr, aClient *cptr, aClient *sptr, int parc, ch #define EFUNC_STRIPCONTROLCODES 32 #define EFUNC_SPAMFILTER_BUILD_USER_STRING 33 #define EFUNC_IS_SILENCED 34 +#define EFUNC_SEND_PROTOCTL_SERVERS 35 +#define EFUNC_VERIFY_LINK 36 /* Module flags */ #define MODFLAG_NONE 0x0000 diff --git a/include/struct.h b/include/struct.h index 692700b64..7b4be9921 100644 --- a/include/struct.h +++ b/include/struct.h @@ -151,6 +151,7 @@ typedef struct SMember Member; typedef struct SMembership Membership; typedef struct SMembershipL MembershipL; typedef struct JFlood aJFlood; +typedef struct PendingNet aPendingNet; #ifdef ZIP_LINKS typedef struct Zdata aZdata; @@ -317,7 +318,7 @@ typedef unsigned int u_int32_t; /* XXX Hope this works! */ #define FLAGS_SQUIT 0x20000 /* Server has been /squit by an oper */ #define FLAGS_PROTOCTL 0x40000 /* Received a PROTOCTL message */ #define FLAGS_PING 0x80000 -#define FLAGS_ASKEDPING 0x100000 +#define FLAGS_EAUTH 0x100000 #define FLAGS_NETINFO 0x200000 #define FLAGS_HYBNOTICE 0x400000 #define FLAGS_QUARANTINE 0x800000 @@ -412,7 +413,8 @@ typedef unsigned int u_int32_t; /* XXX Hope this works! */ #define GotNetInfo(x) ((x)->flags & FLAGS_NETINFO) #define SetNetInfo(x) ((x)->flags |= FLAGS_NETINFO) #define IsCGIIRC(x) ((x)->flags & FLAGS_CGIIRC) - +#define SetEAuth(x) ((x)->flags |= FLAGS_EAUTH) +#define IsEAuth(x) ((x)->flags & FLAGS_EAUTH) #define IsShunned(x) ((x)->flags & FLAGS_SHUNNED) #define SetShunned(x) ((x)->flags |= FLAGS_SHUNNED) #define ClearShunned(x) ((x)->flags &= ~FLAGS_SHUNNED) @@ -1855,6 +1857,13 @@ struct JFlood { }; #endif +struct PendingNet { + aPendingNet *prev, *next; /* Previous and next in list */ + aClient *sptr; /**< Client to which these servers belong */ + int numservers; /**< Amount of servers in list */ + int servers[1]; /** The list of servers (array of integer server numerics) */ +}; + void init_throttling_hash(); int hash_throttling(struct IN_ADDR *in); struct ThrottlingBucket *find_throttling_bucket(struct IN_ADDR *in); diff --git a/src/modules.c b/src/modules.c index 8d9eda08d..4fc1a491f 100644 --- a/src/modules.c +++ b/src/modules.c @@ -116,6 +116,8 @@ unsigned char *(*StripColors)(unsigned char *text); const char *(*StripControlCodes)(unsigned char *text); void (*spamfilter_build_user_string)(char *buf, char *nick, aClient *acptr); int (*is_silenced)(aClient *sptr, aClient *acptr); +void (*send_protoctl_servers)(aClient *sptr, int response); +int (*verify_link)(aClient *cptr, aClient *sptr, char *servername, ConfigItem_link **link_out); static const EfunctionsList efunction_table[MAXEFUNCTIONS] = { /* 00 */ {NULL, NULL}, @@ -153,7 +155,9 @@ static const EfunctionsList efunction_table[MAXEFUNCTIONS] = { /* 32 */ {"StripControlCodes", (void *)&StripControlCodes}, /* 33 */ {"spamfilter_build_user_string", (void *)&spamfilter_build_user_string}, /* 34 */ {"is_silenced", (void *)&is_silenced}, -/* 35 */ {NULL, NULL} +/* 35 */ {"send_protoctl_servers", (void *)&send_protoctl_servers}, +/* 36 */ {"verify_link", (void *)&verify_link}, +/* 37 */ {NULL, NULL} }; diff --git a/src/modules/l_commands.c b/src/modules/l_commands.c index 74377dc99..e62dfc732 100644 --- a/src/modules/l_commands.c +++ b/src/modules/l_commands.c @@ -75,7 +75,7 @@ ModuleHeader l_commands_Header extern int m_htm_Test(ModuleInfo *modinfo), m_join_Test(ModuleInfo *modinfo); extern int m_mode_Test(ModuleInfo *modinfo), m_nick_Test(ModuleInfo *modinfo); extern int m_tkl_Test(ModuleInfo *modinfo), m_list_Test(ModuleInfo *modinfo); -extern int m_message_Test(ModuleInfo *modinfo); +extern int m_message_Test(ModuleInfo *modinfo), m_server_Test(ModuleInfo *modinfo); extern int m_sethost_Init(ModuleInfo *modinfo), m_setname_Init(ModuleInfo *modinfo), m_chghost_Init(ModuleInfo *modinfo); extern int m_chgident_Init(ModuleInfo *modinfo), m_setident_Init(ModuleInfo *modinfo), m_sdesc_Init(ModuleInfo *modinfo); @@ -228,6 +228,7 @@ int l_commands_Test(ModuleInfo *modinfo) m_tkl_Test(ModCmdsInfo); m_list_Test(ModCmdsInfo); m_message_Test(ModCmdsInfo); + m_server_Test(ModCmdsInfo); return MOD_SUCCESS; } diff --git a/src/modules/m_protoctl.c b/src/modules/m_protoctl.c index 2bafb0926..3962f98f0 100644 --- a/src/modules/m_protoctl.c +++ b/src/modules/m_protoctl.c @@ -40,12 +40,12 @@ #ifdef STRIPBADWORDS #include "badwords.h" #endif -#ifdef _WIN32 #include "version.h" -#endif DLLFUNC int m_protoctl(aClient *cptr, aClient *sptr, int parc, char *parv[]); +extern MODVAR char serveropts[]; + #define MSG_PROTOCTL "PROTOCTL" #define TOK_PROTOCTL "_" @@ -91,9 +91,13 @@ CMD_FUNC(m_protoctl) #ifndef PROTOCTL_MADNESS int remove = 0; #endif + int first_protoctl = (GotProtoctl(sptr)) ? 0 : 1; /**< First PROTOCTL we receive? Special ;) */ char proto[128], *s; /* static char *dummyblank = ""; Yes, it is kind of ugly */ + if (!MyConnect(sptr)) + return 0; /* Remote PROTOCTL's are not supported at this time */ + #ifdef PROTOCTL_MADNESS if (GotProtoctl(sptr)) { @@ -334,7 +338,11 @@ CMD_FUNC(m_protoctl) } else if (strncmp(s, "NICKCHARS=", 10) == 0) { - /* Compare... */ + if (!IsServer(cptr) && !IsEAuth(cptr) && !IsHandshake(cptr)) + continue; + /* Ok, server is either authenticated, or is an outgoing connect... + * We now compare the character sets to see if we should warn opers about any mismatch... + */ if (strcmp(s+10, langsinuse)) { sendto_realops("\002WARNING!!!!\002 Link %s does not have the same set::allowed-nickchars settings (or is " @@ -343,6 +351,87 @@ CMD_FUNC(m_protoctl) /* return exit_client(cptr, cptr, &me, "Nick charset mismatch"); */ } } + else if ((strncmp(s, "EAUTH=", 6) == 0) && NEW_LINKING_PROTOCOL) + { + /* Early authorization: EAUTH=servername[,options] */ + int ret; + char *servername = s+6, *p; + ConfigItem_link *aconf = NULL; + + if (strlen(servername) > HOSTLEN) + servername[HOSTLEN] = '\0'; + + for (p = servername; *p; *p++) + { + if (*p == ',') + { + /* Upwards compatible, if we ever add any options through EAUTH=blah,options */ + *p = '\0'; + break; + } + if (*p <= ' ' || *p > '~') + break; + } + + if (*p || !index(servername, '.')) + { + sendto_one(sptr, "ERROR :Bogus server name in EAUTH (%s)", servername); + sendto_snomask + (SNO_JUNK, + "WARNING: Bogus server name (%s) from %s in EAUTH (maybe just a fishy client)", + servername, get_client_name(cptr, TRUE)); + + return exit_client(cptr, sptr, &me, "Bogus server name"); + } + + ret = verify_link(cptr, sptr, s+6, &aconf); + if (ret < 0) + return ret; /* FLUSH_BUFFER */ + + SetEAuth(cptr); + if (!IsHandshake(cptr) && aconf && !BadPtr(aconf->connpwd)) /* Send PASS early... */ + sendto_one(sptr, "PASS :%s", aconf->connpwd); + } + else if ((strncmp(s, "SERVERS=", 8) == 0) && NEW_LINKING_PROTOCOL) + { + aClient *acptr, *srv; + int numeric; + + if (!IsEAuth(cptr)) + continue; + + /* Other side lets us know which servers are behind it. + * SERVERS=[,serv->numeric, acptr->name); + sendto_realops("Link %s cancelled, server with numeric %d (%s) already exists", + get_client_name(acptr, TRUE), acptr->serv->numeric, acptr->name); + return exit_client(sptr, sptr, sptr, "Server Exists (or identical numeric)"); + } + + acptr = find_pending_net_duplicates(sptr, &srv, &numeric); + if (acptr) + { + sendto_one(sptr, "ERROR :Server with numeric %d is being introduced by another server as well. " + "Just wait a moment for it to synchronize...", numeric); + sendto_realops("Link %s cancelled, server would introduce server with numeric %d, which " + "server %s is also about to introduce. Just wait a moment for it to synchronize...", + get_client_name(acptr, TRUE), numeric, get_client_name(srv, TRUE)); + return exit_client(sptr, sptr, sptr, "Server Exists (just wait a moment)"); + } + + /* Send our PROTOCTL SERVERS= back if this was NOT a response */ + if (s[8] != '*') + send_protoctl_servers(sptr, 1); + } /* * Add other protocol extensions here, with proto * containing the base option, and options containing @@ -353,5 +442,16 @@ CMD_FUNC(m_protoctl) */ } + if (first_protoctl && IsHandshake(cptr) && sptr->serv) /* first & outgoing connection to server */ + { + /* SERVER message moved from completed_connection() to here due to EAUTH/SERVERS PROTOCTL stuff, + * which needed to be delayed until after both sides have received SERVERS=xx (..or not.. in case + * of older servers). + */ + sendto_one(cptr, "SERVER %s 1 :U%d-%s%s-%i %s", + me.name, UnrealProtocol, serveropts, extraflags ? extraflags : "", me.serv->numeric, + me.info); + } + return 0; } diff --git a/src/modules/m_server.c b/src/modules/m_server.c index 4b88d4e1f..fb4b667ec 100644 --- a/src/modules/m_server.c +++ b/src/modules/m_server.c @@ -46,6 +46,8 @@ void send_channel_modes(aClient *cptr, aChannel *chptr); void send_channel_modes_sjoin(aClient *cptr, aChannel *chptr); void send_channel_modes_sjoin3(aClient *cptr, aChannel *chptr); DLLFUNC int m_server(aClient *cptr, aClient *sptr, int parc, char *parv[]); +int _verify_link(aClient *cptr, aClient *sptr, char *servername, ConfigItem_link **link_out); +void _send_protoctl_servers(aClient *sptr, int response); static char buf[BUFSIZE]; @@ -62,6 +64,14 @@ ModuleHeader MOD_HEADER(m_server) NULL }; +DLLFUNC int MOD_TEST(m_server)(ModuleInfo *modinfo) +{ + MARK_AS_OFFICIAL_MODULE(modinfo); + EfunctionAddVoid(modinfo->handle, EFUNC_SEND_PROTOCTL_SERVERS, _send_protoctl_servers); + EfunctionAdd(modinfo->handle, EFUNC_VERIFY_LINK, _verify_link); + return MOD_SUCCESS; +} + DLLFUNC int MOD_INIT(m_server)(ModuleInfo *modinfo) { add_CommandX(MSG_SERVER, TOK_SERVER, m_server, MAXPARA, M_UNREGISTERED|M_SERVER); @@ -86,6 +96,190 @@ DLLFUNC int MOD_UNLOAD(m_server)(int module_unload) int m_server_synch(aClient *cptr, long numeric, ConfigItem_link *conf); +/** 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(aClient *sptr, int response) +{ +Link *lp; +char buf[512]; + + if (!NEW_LINKING_PROTOCOL) + return; + + ircsprintf(buf, "PROTOCTL EAUTH=%s SERVERS=%s", + me.name, response ? "*" : ""); + + for (lp = Servers; lp; lp = lp->next) + { + int numeric = lp->value.cptr->serv->numeric; + if (numeric <= 0) + continue; + ircsprintf(buf+strlen(buf),"%d,", numeric); + if (strlen(buf) > sizeof(buf)-12) + { + /* This should only happen if you have like more than 120 servers.. that would be a tad extreme... */ + sendto_realops("send_protoctl_servers: Ehm.. you have a whole lot of servers linked, don't you?"); + break; /* prevent overflow */ + } + } + + /* Remove final comma */ + if (buf[strlen(buf)-1] == ',') + buf[strlen(buf)-1] = '\0'; + + sendto_one(sptr, "%s", buf); +} + + + +/** Verify server link. + * This does authentication and authorization checks. + * @param cptr The client directly connected to us (cptr). + * @param sptr The client which (originally) issued the server command (sptr). + * @param servername The server name provided by the 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 0 on succesful auth, other values should be returned by + * the calling function, as it will always be FLUSH_BUFFER due to exit_client(). + */ +int _verify_link(aClient *cptr, aClient *sptr, char *servername, ConfigItem_link **link_out) +{ +char xerrmsg[256]; +ConfigItem_link *link; +char *inpath = get_client_name(cptr, TRUE); +aClient *acptr = NULL, *ocptr = NULL; +ConfigItem_ban *bconf; + + if (link_out) + *link_out = NULL; + + strcpy(xerrmsg, "No matching link configuration"); + + if (!cptr->passwd) + { + sendto_one(cptr, "ERROR :Missing password"); + return exit_client(cptr, sptr, &me, "Missing password"); + } + + + /* First check if the server is in the list */ + if (!servername) { + strcpy(xerrmsg, "Null servername"); + goto errlink; + } + if (cptr->serv && cptr->serv->conf) + { + /* We already know what block we are dealing with (outgoing connect!) */ + link = cptr->serv->conf; + } else { + /* Hunt the linkblock down ;) */ + for(link = conf_link; link; link = (ConfigItem_link *) link->next) + if (!match(link->servername, servername)) + break; + } + if (!link) { + snprintf(xerrmsg, 256, "No link block named '%s'", servername); + goto errlink; + } + if (link->username && match(link->username, cptr->username)) { + snprintf(xerrmsg, 256, "Username '%s' didn't match '%s'", + cptr->username, link->username); + /* I assume nobody will have 2 link blocks with the same servername + * and different username. -- Syzop + */ + goto errlink; + } + /* For now, we don't check based on DNS, it is slow, and IPs are better. + * We also skip checking if link::options::nohostcheck is set. + */ + if (link->options & CONNECT_NOHOSTCHECK) + goto nohostcheck; + link = Find_link(cptr->username, cptr->sockhost, cptr->sockhost, servername); + +#ifdef INET6 + /* + * We first try match on uncompressed form ::ffff:192.168.1.5 thing included + */ + if (!link) + link = Find_link(cptr->username, cptr->sockhost, Inet_ia2pNB(&cptr->ip, 0), servername); + /* + * Then on compressed + */ + if (!link) + link = Find_link(cptr->username, cptr->sockhost, Inet_ia2pNB(&cptr->ip, 1), servername); +#endif + if (!link) + { + snprintf(xerrmsg, 256, "Server is in link block but IP/host didn't match"); +errlink: + /* Send the "simple" error msg to the server */ + sendto_one(cptr, + "ERROR :Link denied (No matching link configuration) %s", + inpath); + /* And send the "verbose" error msg only to local failops */ + sendto_locfailops + ("Link denied for %s(%s@%s) (%s) %s", + servername, cptr->username, cptr->sockhost, xerrmsg, inpath); + return exit_client(cptr, sptr, &me, + "Link denied (No matching link configuration)"); + } +nohostcheck: + /* Now for checking passwords */ + if (Auth_Check(cptr, link->recvauth, cptr->passwd) == -1) + { + sendto_one(cptr, + "ERROR :Link denied (Authentication failed) %s", + inpath); + sendto_locfailops + ("Link denied (Authentication failed [Bad password?]) %s", inpath); + return exit_client(cptr, sptr, &me, + "Link denied (Authentication failed)"); + } + + /* + * Third phase, we check that the server does not exist + * already + */ + if ((acptr = find_server(servername, NULL))) + { + /* Found. Bad. Quit. */ + acptr = acptr->from; + ocptr = + (cptr->firsttime > acptr->firsttime) ? acptr : cptr; + acptr = + (cptr->firsttime > acptr->firsttime) ? cptr : acptr; + sendto_one(acptr, + "ERROR :Server %s already exists from %s", + servername, + (ocptr->from ? ocptr->from->name : "")); + sendto_realops + ("Link %s cancelled, server %s already exists from %s", + get_client_name(acptr, TRUE), servername, + (ocptr->from ? ocptr->from->name : "")); + return exit_client(acptr, acptr, acptr, + "Server Exists"); + } + if ((bconf = Find_ban(NULL, servername, CONF_BAN_SERVER))) + { + sendto_realops + ("Cancelling link %s, banned server", + get_client_name(cptr, TRUE)); + sendto_one(cptr, "ERROR :Banned server (%s)", bconf->reason ? bconf->reason : "no reason"); + return exit_client(cptr, cptr, &me, "Banned server"); + } + if (link->class->clients + 1 > link->class->maxclients) + { + sendto_realops + ("Cancelling link %s, full class", + get_client_name(cptr, TRUE)); + return exit_client(cptr, cptr, &me, "Full class"); + } + if (link_out) + *link_out = link; + return 0; +} + + /* ** m_server ** parv[0] = sender prefix @@ -104,8 +298,6 @@ DLLFUNC CMD_FUNC(m_server) /* char *password = NULL; */ char *ch = NULL; /* */ char *inpath = get_client_name(cptr, TRUE); - aClient *acptr = NULL, *ocptr = NULL; - ConfigItem_ban *bconf; int hop = 0, numeric = 0; char info[REALLEN + 61]; ConfigItem_link *aconf = NULL; @@ -177,125 +369,11 @@ DLLFUNC CMD_FUNC(m_server) if (IsUnknown(cptr) || IsHandshake(cptr)) { char xerrmsg[256]; - ConfigItem_link *link; - - strcpy(xerrmsg, "No matching link configuration"); - /* First check if the server is in the list */ - if (!servername) { - strcpy(xerrmsg, "Null servername"); - goto errlink; - } - if (cptr->serv && cptr->serv->conf) - { - /* We already know what block we are dealing with (outgoing connect!) */ - link = cptr->serv->conf; - } else { - /* Hunt the linkblock down ;) */ - for(link = conf_link; link; link = (ConfigItem_link *) link->next) - if (!match(link->servername, servername)) - break; - } - if (!link) { - snprintf(xerrmsg, 256, "No link block named '%s'", servername); - goto errlink; - } - if (link->username && match(link->username, cptr->username)) { - snprintf(xerrmsg, 256, "Username '%s' didn't match '%s'", - cptr->username, link->username); - /* I assume nobody will have 2 link blocks with the same servername - * and different username. -- Syzop - */ - goto errlink; - } - /* For now, we don't check based on DNS, it is slow, and IPs are better. - * We also skip checking if link::options::nohostcheck is set. - */ - if (link->options & CONNECT_NOHOSTCHECK) - { - aconf = link; - goto nohostcheck; - } - aconf = Find_link(cptr->username, cptr->sockhost, cptr->sockhost, - servername); - -#ifdef INET6 - /* - * We first try match on uncompressed form ::ffff:192.168.1.5 thing included - */ - if (!aconf) - aconf = Find_link(cptr->username, cptr->sockhost, Inet_ia2pNB(&cptr->ip, 0), servername); - /* - * Then on compressed - */ - if (!aconf) - aconf = Find_link(cptr->username, cptr->sockhost, Inet_ia2pNB(&cptr->ip, 1), servername); -#endif - if (!aconf) - { - snprintf(xerrmsg, 256, "Server is in link block but IP/host didn't match"); -errlink: - /* Send the "simple" error msg to the server */ - sendto_one(cptr, - "ERROR :Link denied (No matching link configuration) %s", - inpath); - /* And send the "verbose" error msg only to local failops */ - sendto_locfailops - ("Link denied for %s(%s@%s) (%s) %s", - servername, cptr->username, cptr->sockhost, xerrmsg, inpath); - return exit_client(cptr, sptr, &me, - "Link denied (No matching link configuration)"); - } -nohostcheck: - /* Now for checking passwords */ - if (Auth_Check(cptr, aconf->recvauth, cptr->passwd) == -1) - { - sendto_one(cptr, - "ERROR :Link denied (Authentication failed) %s", - inpath); - sendto_locfailops - ("Link denied (Authentication failed [Bad password?]) %s", inpath); - return exit_client(cptr, sptr, &me, - "Link denied (Authentication failed)"); - } - - /* - * Third phase, we check that the server does not exist - * already - */ - if ((acptr = find_server(servername, NULL))) - { - /* Found. Bad. Quit. */ - acptr = acptr->from; - ocptr = - (cptr->firsttime > acptr->firsttime) ? acptr : cptr; - acptr = - (cptr->firsttime > acptr->firsttime) ? cptr : acptr; - sendto_one(acptr, - "ERROR :Server %s already exists from %s", - servername, - (ocptr->from ? ocptr->from->name : "")); - sendto_realops - ("Link %s cancelled, server %s already exists from %s", - get_client_name(acptr, TRUE), servername, - (ocptr->from ? ocptr->from->name : "")); - return exit_client(acptr, acptr, acptr, - "Server Exists"); - } - if ((bconf = Find_ban(NULL, servername, CONF_BAN_SERVER))) - { - sendto_realops - ("Cancelling link %s, banned server", - get_client_name(cptr, TRUE)); - sendto_one(cptr, "ERROR :Banned server (%s)", bconf->reason ? bconf->reason : "no reason"); - return exit_client(cptr, cptr, &me, "Banned server"); - } - if (aconf->class->clients + 1 > aconf->class->maxclients) - { - sendto_realops - ("Cancelling link %s, full class", - get_client_name(cptr, TRUE)); - return exit_client(cptr, cptr, &me, "Full class"); - } + int ret; + ret = verify_link(cptr, sptr, servername, &aconf); + if (ret < 0) + return ret; /* FLUSH_BUFFER / failure */ + /* OK, let us check in the data now now */ hop = TS2ts(parv[2]); numeric = (parc > 4) ? TS2ts(parv[3]) : 0; @@ -621,8 +699,8 @@ int m_server_synch(aClient *cptr, long numeric, ConfigItem_link *aconf) /* If this is an incomming connection, then we have just received * their stuff and now send our stuff back. */ - send_proto(cptr, aconf); sendto_one(cptr, "PASS :%s", aconf->connpwd); + send_proto(cptr, aconf); sendto_one(cptr, "SERVER %s 1 :U%d-%s-%i %s", me.name, UnrealProtocol, serveropts, me.serv->numeric, @@ -659,6 +737,7 @@ int m_server_synch(aClient *cptr, long numeric, ConfigItem_link *aconf) } #endif /* Set up server structure */ + free_pending_net(cptr); SetServer(cptr); IRCstats.me_servers++; IRCstats.servers++; diff --git a/src/s_bsd.c b/src/s_bsd.c index 07502cf02..b0d596a22 100644 --- a/src/s_bsd.c +++ b/src/s_bsd.c @@ -835,10 +835,14 @@ int completed_connection(aClient *cptr) if (!BadPtr(aconf->connpwd)) sendto_one(cptr, "PASS :%s", aconf->connpwd); + send_protoctl_servers(cptr, 0); send_proto(cptr, aconf); - sendto_one(cptr, "SERVER %s 1 :U%d-%s%s-%i %s", - me.name, UnrealProtocol, serveropts, extraflags ? extraflags : "", me.serv->numeric, - me.info); + /* Sending SERVER message moved to m_protoctl, so it's send after the first PROTOCTL + * we receive from the remote server. Of course, this assumes that the remote server + * to which we are connecting will at least send one PROTOCTL... but since it's an + * outgoing connect, we can safely assume it's a remote UnrealIRCd server (or some + * other advanced server..). -- Syzop + */ if (!IsDead(cptr)) start_auth(cptr); diff --git a/src/s_conf.c b/src/s_conf.c index d0f646351..e9f70d0ec 100644 --- a/src/s_conf.c +++ b/src/s_conf.c @@ -1703,6 +1703,7 @@ void config_setdefaultsettings(aConfiguration *i) i->name_server = strdup("127.0.0.1"); /* default, especially needed for w2003+ in some rare cases */ i->level_on_join = CHFL_CHANOP; i->watch_away_notification = 1; + i->new_linking_protocol = 1; } /* 1: needed for set::options::allow-part-if-shunned, @@ -7203,6 +7204,9 @@ int _conf_set(ConfigFile *conf, ConfigEntry *ce) else if (!strcmp(cep->ce_varname, "restrict-extendedbans")) { ircstrdup(tempiConf.restrict_extendedbans, cep->ce_vardata); } + else if (!strcmp(cep->ce_varname, "new-linking-protocol")) { + tempiConf.new_linking_protocol = atoi(cep->ce_vardata); + } else if (!strcmp(cep->ce_varname, "anti-spam-quit-message-time")) { tempiConf.anti_spam_quit_message_time = config_checkval(cep->ce_vardata,CFG_TIME); } @@ -7897,6 +7901,11 @@ int _test_set(ConfigFile *conf, ConfigEntry *ce) CheckDuplicate(cep, restrict_extendedbans, "restrict-extendedbans"); CheckNull(cep); } + else if (!strcmp(cep->ce_varname, "new-linking-protocol")) + { + CheckDuplicate(cep, new_linking_protocol, "new-linking-protocol"); + CheckNull(cep); + } else if (!strcmp(cep->ce_varname, "dns")) { for (cepp = cep->ce_entries; cepp; cepp = cepp->ce_next) { CheckNull(cepp); diff --git a/src/s_misc.c b/src/s_misc.c index 087864233..9e9d43f97 100644 --- a/src/s_misc.c +++ b/src/s_misc.c @@ -538,7 +538,7 @@ int exit_client(aClient *cptr, aClient *sptr, aClient *from, char *comment) IRCstats.me_servers--; ircd_log(LOG_SERVER, "SQUIT %s (%s)", sptr->name, comment); } - + free_pending_net(sptr); if (sptr->listener) if (sptr->listener->class && !IsOutgoing(sptr)) { diff --git a/src/s_serv.c b/src/s_serv.c index 5d8985bbc..5471f759d 100644 --- a/src/s_serv.c +++ b/src/s_serv.c @@ -1276,3 +1276,131 @@ aClient *find_match_server(char *mask) } return acptr; } + +aPendingNet *pendingnet = NULL; + +void add_pending_net(aClient *sptr, char *str) +{ +aPendingNet *e = NULL; +int num = 1; +char *p, *name; + + if (BadPtr(str) || !sptr) + return; + + /* First, count them */ + for (p = str; *p; p++) + if (*p == ',') + num++; + + /* Allocate */ + e = MyMallocEx(sizeof(aPendingNet) + (sizeof(int) * num)); + + e->numservers = num; + e->sptr = sptr; + + /* Fill in */ + num = 0; + for (name = strtoken(&p, str, ","); name; name = strtoken(&p, NULL, ",")) + { + if (!*name) + continue; + + /* skip any non-digit prefixes, if necessary. Possibly needed in the future. */ + while (*name && !isdigit(*name)) + name++; + + e->servers[num++] = atoi(name); + } + + AddListItem(e, pendingnet); +} + +void free_pending_net(aClient *sptr) +{ +aPendingNet *e, *e_next; + + for (e = pendingnet; e; e = e_next) + { + e_next = e->next; + if (e->sptr == sptr) + { + DelListItem(e, pendingnet); + MyFree(e); + /* Don't break, there can be multiple objects */ + } + } +} + +aPendingNet *find_pending_net_by_numeric_butone(int numeric, aClient *exempt) +{ +aPendingNet *e; +int i; + + if (numeric <= 0) + return NULL; + + for (e = pendingnet; e; e = e->next) + { + if (e->sptr == exempt) + continue; + for (i = 0; i < e->numservers; i++) + if (e->servers[i] == numeric) + return e; + } + return NULL; +} + +/** Search the pending connections list for any identical numerics */ +aClient *find_pending_net_duplicates(aClient *cptr, aClient **srv, int *numeric) +{ +aPendingNet *e, *other; +int i; + + *srv = NULL; + *numeric = 0; + + for (e = pendingnet; e; e = e->next) + { + if (e->sptr != cptr) + continue; + /* Ok, found myself */ + for (i = 0; i < e->numservers; i++) + { + int curr_numeric = e->servers[i]; + other = find_pending_net_by_numeric_butone(curr_numeric, cptr); + if (other) + { + *srv = e->sptr; + *numeric = curr_numeric; + return other->sptr; /* Found another (pending) server with identical numeric */ + } + } + } + + return NULL; +} + +aClient *find_non_pending_net_duplicates(aClient *cptr) +{ +aPendingNet *e; +int i; +aClient *acptr; + + for (e = pendingnet; e; e = e->next) + { + if (e->sptr != cptr) + continue; + /* Ok, found myself */ + for (i = 0; i < e->numservers; i++) + { + int numeric = e->servers[i]; + acptr = find_server_by_numeric(numeric); + if (acptr) + return acptr; /* Found another (fully CONNECTED) server with identical numeric */ + } + } + + return NULL; +} +