From 2cb31a39a60985bbc89dfa96d2e4a756c844cc23 Mon Sep 17 00:00:00 2001
From: cyan <>
Date: Thu, 4 Dec 2003 10:37:51 +0000
Subject: [PATCH] Portion of the IRCd v1.1b that handles server connections.

---
 exec/load/ircd_server.js | 1106 ++++++++++++++++++++++++++++++++++++++
 1 file changed, 1106 insertions(+)
 create mode 100644 exec/load/ircd_server.js

diff --git a/exec/load/ircd_server.js b/exec/load/ircd_server.js
new file mode 100644
index 0000000000..23c8aefe99
--- /dev/null
+++ b/exec/load/ircd_server.js
@@ -0,0 +1,1106 @@
+// $Id$
+//
+// ircd_channel.js                
+//
+// 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 2 of the License, 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:
+// http://www.gnu.org/licenses/gpl.txt
+//
+// Synchronet IRC Daemon as per RFC 1459, link compatible with Bahamut 1.4
+//
+// Copyright 2003 Randolph Erwin Sommerfeld <sysop@rrx.ca>
+//
+// ** Server to server operation is governed here.
+//
+
+////////// Constants / Defines //////////
+const SERVER_REVISION = "$Revision$".split(' ')[1];
+
+// Various N:Line permission bits
+const NLINE_CHECK_QWKPASSWD		=(1<<0);	// q
+const NLINE_IS_QWKMASTER		=(1<<1);	// w
+const NLINE_CHECK_WITH_QWKMASTER	=(1<<2);	// k
+
+////////// Objects //////////
+function IRC_Server() {
+	////////// VARIABLES
+	// Bools/Flags that change depending on connection state.
+	this.hub = false;	// are we a hub?
+	this.local = true;	// are we a local socket?
+	this.pinged = false;	// have we sent a PING?
+	this.server = true;	// yep, we're a server.
+	this.uline = false;	// are we services?
+	// Variables containing user/server information as we receive it.
+	this.flags = 0;
+	this.hops = 0;
+	this.hostname = "";
+	this.id = 0;
+	this.ip = "";
+	this.ircclass = 0;
+	this.linkparent="";
+	this.nick = "";
+	this.parent = 0;
+	this.info = "";
+	this.idletime = time();
+	// Variables (consts, really) that point to various state information
+	this.socket = "";
+	////////// FUNCTIONS
+	// Functions we use to control clients (specific)
+	this.quit = Server_Quit;
+	this.work = Server_Work;
+	this.netsplit = IRCClient_netsplit;
+	// Socket functions
+	this.ircout=ircout;
+	this.originatorout=originatorout;
+	this.rawout=rawout;
+	// IRC protocol sending functions
+	this.bcast_to_channel=IRCClient_bcast_to_channel;
+	this.bcast_to_servers=IRCClient_bcast_to_servers;
+	this.bcast_to_servers_raw=IRCClient_bcast_to_servers_raw;
+	this.bcast_to_uchans_unique=IRCClient_bcast_to_uchans_unique;
+	// Output helpers
+	this.server_nick_info=IRCClient_server_nick_info;
+	this.server_chan_info=IRCClient_server_chan_info;
+	this.server_info=IRCClient_server_info;
+	this.synchronize=IRCClient_synchronize;
+	this.reintroduce_nick=IRCClient_reintroduce_nick;
+	this.finalize_server_connect=IRCClient_finalize_server_connect;
+	// Global Functions
+	this.check_timeout=IRCClient_check_timeout;
+	this.set_chanmode=IRCClient_set_chanmode;
+	// Output helper functions (shared)
+}
+
+////////// Command Parser //////////
+
+function Server_Work() {
+	var command;
+	var cmdline;
+	var origin;
+
+	if (!this.socket.is_connected) {
+		this.quit("Connection reset by peer");
+		return 0;
+	}
+
+	cmdline=this.socket.recvline(4096,0)
+
+	if(cmdline==null)
+		return 0;
+
+	if (debug)
+		log(format("[%s<-%s]: %s",servername,this.nick,cmdline));
+
+	cmd = cmdline.split(" ");
+
+	if (cmdline[0] == ":") {
+		// Silently ignore NULL originator commands.
+		if (!cmd[1])
+			return 0;
+		origin = cmd[0].slice(1);
+		command = cmd[1].toUpperCase();
+		cmdline = cmdline.slice(cmdline.indexOf(" ")+1);
+		// resplit cmd[]
+		cmd = cmdline.split(" ");
+	} else {
+		command = cmd[0].toUpperCase();
+		origin = this.nick;
+	}
+
+	var killtype;
+	var ThisOrigin;
+	if (origin.match(/[.]/)) {
+		ThisOrigin = Servers[origin.toLowerCase()];
+		killtype = "SQUIT";
+	} else {
+		ThisOrigin = Users[origin.toUpperCase()];
+		killtype = "KILL";
+	}
+	if (!ThisOrigin) {
+		umode_notice(USERMODE_OPER,"Notice","Server " + this.nick +
+			" trying to pass message for non-existent origin: " +
+			origin);
+		this.rawout(killtype + " " + origin + " :" + servername + " (" + origin + "(?) <- " + this.nick + ")");
+		return 0;
+	}
+
+	this.idletime = time();
+
+	if (command.match(/^[0-9]+/)) { // passing on a numeric to the client
+		if (!cmd[1])
+			return 0; // uh...?
+		var destination = Users[cmd[1].toUpperCase()];
+		if (!destination)
+			return 0;
+		destination.rawout(":" + ThisOrigin.nick + " " + cmdline);
+		return 1;
+	}
+
+	switch(command) {
+		// PING at the top thanks to RFC1459
+		case "PING":
+			if (!cmd[1])
+				break;
+			if (cmd[2] && (cmd[2][0] == ":"))
+				cmd[2] = cmd[2].slice(1);
+			var tmp_server;
+			if (cmd[2])
+				tmp_server = searchbyserver(cmd[2]);
+			if (tmp_server && (tmp_server != -1) &&
+			    (tmp_server.id != ThisOrigin.id)) {
+				tmp_server.rawout(":" + ThisOrigin.nick + " PING " + ThisOrigin.nick + " :" + tmp_server.nick);
+				break;
+			}
+			if (cmd[1][0] == ":")
+				cmd[1] = cmd[1].slice(1);
+			if (!cmd[2])
+				this.ircout("PONG " + servername + " :" + cmd[1]);
+			else
+				this.ircout("PONG " + cmd[2] + " :" + cmd[1]);
+			break;
+		case "ADMIN":
+			if (!cmd[1])
+				break;
+			if (cmd[1][0] == ":")
+				cmd[1] = cmd[1].slice(1);
+			if (IRC_match(servername,cmd[1])) {
+				ThisOrigin.do_admin();
+			} else {
+				var dest_server = searchbyserver(cmd[1]);
+				if (!dest_server)
+					break;
+				dest_server.rawout(":" + ThisOrigin.nick + " ADMIN :" + dest_server.nick);
+			}
+			break;
+		case "AKILL":
+			var this_uh;
+
+			if (!cmd[6])
+				break;
+			if (!ThisOrigin.uline) {
+				umode_notice(USERMODE_OPER,"Notice","Non-U:Lined server " + ThisOrigin.nick + " trying to utilize AKILL.");
+				break;
+			}
+
+			this_uh = cmd[2] + "@" + cmd[1];
+			if (isklined(this_uh))
+				break;
+			KLines.push(new KLine(this_uh,IRC_string(cmdline),"A"));
+			this.bcast_to_servers_raw(":" + ThisOrigin.nick + " " + cmdline);
+			scan_for_klined_clients();
+			break;
+		case "AWAY":
+			if (!cmd[1])
+				ThisOrigin.away = "";
+			else
+				ThisOrigin.away = IRC_string(cmdline);
+			break;
+		case "CHATOPS":
+			if (!cmd[1])
+				break;
+			var my_ircstr = IRC_string(cmdline);
+			umode_notice(USERMODE_CHATOPS,"ChatOps","from " +
+				ThisOrigin.nick + ": " + my_ircstr);
+			this.bcast_to_servers_raw(":" + ThisOrigin.nick + " " +
+				"CHATOPS :" + my_ircstr);
+			break;
+		case "CONNECT":
+			if (!cmd[3] || !this.hub)
+				break;
+			if (IRC_match(servername, cmd[3])) {
+				ThisOrigin.do_connect(cmd[1],cmd[2]);
+			} else {
+				var dest_server = searchbyserver(cmd[3]);
+				if (!dest_server)
+					break;
+				dest_server.rawout(":" + ThisOrigin.nick + " CONNECT " + cmd[1] + " " + cmd[2] + " " + dest_server.nick);
+			}
+			break;
+		case "GLOBOPS":
+			if (!cmd[1])
+				break;
+			var my_ircstr = IRC_string(cmdline);
+			ThisOrigin.globops(my_ircstr);
+			this.bcast_to_servers_raw(":" + ThisOrigin.nick + " " +
+				"GLOBOPS :" + my_ircstr);
+			break;
+		case "GNOTICE":
+			if (!cmd[1])
+				break;
+			var my_ircstr = IRC_string(cmdline);
+			umode_notice(USERMODE_ROUTING,"Routing","from " +
+				ThisOrigin.nick + ": " + my_ircstr);
+			this.bcast_to_servers_raw(":" + ThisOrigin.nick + " " +
+				"GNOTICE :" + my_ircstr);
+			break;
+		case "ERROR":
+			umode_notice(USERMODE_ROUTING,"Notice", "ERROR from " +
+				this.nick + "[(+)0@" + this.hostname + "] -- "+
+				IRC_string(cmdline));
+			ThisOrigin.quit(IRC_string(cmdline));
+			break;
+		case "INFO":
+			if (!cmd[1])
+				break;
+			if (cmd[1][0] == ":")
+				cmd[1] = cmd[1].slice(1);
+			if (IRC_match(servername, cmd[1])) {
+				ThisOrigin.do_info();
+			} else {
+				var dest_server = searchbyserver(cmd[1]);
+				if (!dest_server)
+					break;
+				dest_server.rawout(":" + ThisOrigin.nick + " INFO :" + dest_server.nick);
+			}
+			break;
+		case "INVITE":
+			if (!cmd[2])
+				break;
+			if (cmd[2][0] == ":")
+				cmd[2] = cmd[2].slice(1);
+			var chan = Channels[cmd[2].toUpperCase()];
+			if (!chan)
+				break;
+			if (!chan.modelist[CHANMODE_OP][ThisOrigin.id])
+				break;
+			var nick = Users[cmd[1].toUpperCase()];
+			if (!nick)
+				break;
+			if (!nick.channels[chan.nam.toUpperCase()])
+				break;
+			nick.originatorout("INVITE " + nick.nick + " :" +
+				chan.nam,ThisOrigin);
+			nick.invited = chan.nam.toUpperCase();
+			break;
+		case "JOIN":
+			if (!cmd[1])
+				break;
+			if (cmd[1][0] == ":")
+				cmd[1]=cmd[1].slice(1);
+			var the_channels = cmd[1].split(",");
+			for (jchan in the_channels) {
+				if (the_channels[jchan][0] != "#")
+					break;
+				ThisOrigin.do_join(the_channels[jchan].slice(0,max_chanlen),"");
+			}
+			break;
+		case "KICK":
+			var chanid;
+			var nickid;
+			var str;
+			var kick_reason;
+
+			if (!cmd[2])
+				break;
+			chanid = Channels[cmd[1].toUpperCase()];
+			if (!chanid)
+				break;
+			nickid = Users[cmd[2].toUpperCase()];
+			if (!nickid)
+				nickid = search_nickbuf(cmd[2]);
+			if (!nickid)
+				break;
+			if (!nickid.channels[chan.nam.toUpperCase()])
+				break;
+			if (cmd[3])
+				kick_reason = IRC_string(cmdline).slice(0,max_kicklen);
+			else
+				kick_reason = ThisOrigin.nick;
+			str = "KICK " + chanid.nam + " " + nickid.nick + " :" + kick_reason;
+			ThisOrigin.bcast_to_channel(chanid, str, false);
+			this.bcast_to_servers_raw(":" + ThisOrigin.nick + " " + str);
+			nickid.rmchan(chanid);
+			break;
+		case "KILL":
+			if (!cmd[1] || !cmd[2])
+				break;
+			if (cmd[1].match(/[.]/))
+				break;
+			if (cmd[2] == ":")
+				break;
+			var reason = IRC_string(cmdline);
+			var kills = cmd[1].split(",");
+			for(kill in kills) {
+				var target = Users[kills[kill].toUpperCase()];
+				if (!target)
+					target = search_nickbuf(kills[kill]);
+				if (target && (this.hub ||
+				    (target.parent == this.id)) ) {
+					umode_notice(USERMODE_KILL,"Notice","Received KILL message for " + target.nuh + ". From " + ThisOrigin.nick + " Path: target!Synchronet!" + ThisOrigin.nick + " (" + reason + ")");
+					this.bcast_to_servers_raw(":" + ThisOrigin.nick + " KILL " + target.nick + " :" + reason);
+					target.quit("KILLED by " + ThisOrigin.nick + " (" + reason + ")",true);
+				} else if (target && !this.hub) {
+					umode_notice(USERMODE_OPER,"Notice","Non-Hub server " + this.nick + " trying to KILL " + target.nick);
+					this.reintroduce_nick(target);
+				}
+			}
+			break;
+		case "LINKS":
+			if (!cmd[1] || !cmd[2])
+				break;
+			if (match_irc_mask(servername, cmd[1])) {
+				ThisOrigin.do_links(cmd[2]);
+			} else {
+				var dest_server = searchbyserver(cmd[1]);
+				if (!dest_server)
+					break;
+				dest_server.rawout(":" + ThisOrigin.nick + " LINKS " + dest_server.nick + " " + cmd[2]);
+			}
+			break;
+		case "MODE":
+			if (!cmd[1] || !cmd[2])
+				break;
+			if (cmd[1][0] == "#") {
+				var chan = Channels[cmd[1].toUpperCase()];
+				if (!chan)
+					break;
+				var modeline;
+				// Detect if this is a TSMODE.  If so, handle.
+				if (parseInt(cmd[2]) == cmd[2]) {
+					// desynchronized MODE command.
+					if (parseInt(cmd[2]) > chan.created)
+						break;
+					cmd.shift();
+				}
+				cmd.shift();
+				cmd.shift();
+				var modeline = cmd.join(" ");
+				ThisOrigin.set_chanmode(chan,modeline,false);
+			} else { // assume it's for a user
+				ThisOrigin.setusermode(cmd[2]);
+				this.bcast_to_servers_raw(":" + ThisOrigin.nick + " MODE " + ThisOrigin.nick + " " + cmd[2]);
+			}
+			break;
+		case "MOTD":
+			if (!cmd[1])
+				break;
+			if (cmd[1][0] == ":")
+				cmd[1] = cmd[1].slice(1);
+			if (IRC_match(servername, cmd[1])) {
+				umode_notice(USERMODE_SPY,"Spy",
+					"MOTD requested by " + ThisOrigin.nick+
+					" (" + ThisOrigin.uprefix + "@" +
+					ThisOrigin.hostname + ") [" +
+					ThisOrigin.servername + "]");
+				ThisOrigin.motd();
+			} else {
+				var dest_server = searchbyserver(cmd[1]);
+				if (!dest_server)
+					break;
+				dest_server.rawout(":" + ThisOrigin.nick + " MOTD :" + dest_server.nick);
+			}
+			break;
+		case "NICK":
+			if (!cmd[2] || (!cmd[8] && (cmd[2][0] != ":")) )
+				break;
+			var collide = Users[cmd[1].toUpperCase()];
+			if ((collide) && (parseInt(collide.created) >
+			    parseInt(cmd[3]) ) && this.hub) {
+				// Nuke our side of things, allow this newly
+				// introduced nick to overrule.
+				collide.numeric(436, collide.nick + " :Nickname Collision KILL.");
+				this.bcast_to_servers("KILL " + collide.nick + " :Nickname Collision.");
+				collide.quit("Nickname Collision",true);
+			} else if (collide && !this.hub) {
+				umode_notice(USERMODE_OPER,"Notice","Server " + this.nick + " trying to collide nick " + collide.nick + " forwards, reversing.");
+				// Don't collide our side of things from a leaf
+				this.ircout("KILL " + cmd[1] + " :Inverse Nickname Collision.");
+				// Reintroduce our nick, because the remote end
+				// probably killed it already on our behalf.
+				this.reintroduce_nick(collide);
+				break;
+			} else if (collide && this.hub) {
+				break;
+			}
+			if (cmd[2][0] == ":") {
+				cmd[2] = cmd[2].slice(1);
+				ThisOrigin.created = parseInt(cmd[2]);
+				ThisOrigin.bcast_to_uchans_unique("NICK " + cmd[1]);
+				this.bcast_to_servers_raw(":" + ThisOrigin.nick + " NICK " + cmd[1] + " :" + cmd[2]);
+				push_nickbuf(ThisOrigin.nick,cmd[1]);
+				Users[cmd[1].toUpperCase()] = ThisOrigin;
+				delete Users[ThisOrigin.nick.toUpperCase()];
+				ThisOrigin.nick = cmd[1];
+			} else if (cmd[10]) {
+				if (!this.hub) {
+					if(!this.check_nickname(cmd[1])) {
+						umode_notice(USERMODE_OPER,"Notice","Server " + this.nick + " trying to introduce invalid nickname: " + cmd[1] + ", killed.");
+						this.ircout("KILL " + cmd[1] + " :Bogus Nickname.");
+						break;
+					}
+					cmd[2] = 1; // Force hops to 1.
+					cmd[3] = time(); // Force TS on nick.
+					cmd[7] = this.nick // Force server name
+				} else { // if we're a hub
+					var test_server = searchbyserver(cmd[7]);
+					if (!test_server || (this.nick !=
+					    test_server.parent)) {
+						umode_notice(USERMODE_OPER,"Notice","Server " + this.nick + " trying to introduce nick from server not behind it: " + cmd[1] + "@" + cmd[7]);
+						this.ircout("KILL " + cmd[1] + " :Invalid Origin.");
+						break;
+					}
+				}
+				var new_id = "id" + next_client_id;
+				next_client_id++;
+				Users[cmd[1].toUpperCase()] = new IRC_User(new_id);
+				var NewNick = Users[cmd[1].toUpperCase()];
+				NewNick.nick = cmd[1];
+				NewNick.hops = cmd[2];
+				NewNick.created = cmd[3];
+				NewNick.uprefix = cmd[5];
+				NewNick.hostname = cmd[6];
+				NewNick.servername = cmd[7];
+				NewNick.realname = IRC_string(cmdline,10);
+				NewNick.parent = this.nick.toLowerCase();
+				NewNick.ip = int_to_ip(cmd[9]);
+				NewNick.setusermode(cmd[4]);
+				NewNick.local = false; // not local. duh.
+				for (u in ULines) {
+					if (ULines[u] == cmd[7]) {
+						NewNick.uline = true;
+						break;
+					}
+				}
+				var true_hops = parseInt(NewNick.hops)+1;
+				this.bcast_to_servers_raw("NICK " + NewNick.nick + " " + true_hops + " " + NewNick.created + " " + NewNick.get_usermode(true) + " " + NewNick.uprefix + " " + NewNick.hostname + " " + NewNick.servername + " 0 " + cmd[9] + " :" + NewNick.realname);
+			}
+			break;
+		case "NOTICE":
+			if (!cmd[1])
+				break;
+			var my_ircstr = IRC_string(cmdline);
+			if ( !cmd[2] || ( !cmd[3] && (
+			     (cmd[2] == ":") && (my_ircstr == "")
+			   ) ) )
+				break;
+			var targets = cmd[1].split(",");
+			for (nt in targets) {
+				if (targets[nt][0] != "&")
+					ThisOrigin.do_msg(targets[nt],"NOTICE",my_ircstr);
+			}
+			break;
+		case "PART":
+			if (!cmd[1])
+				break;
+			var the_channels = cmd[1].split(",");
+			for(pchan in the_channels) {
+				ThisOrigin.do_part(the_channels[pchan]);
+			}
+			break;
+		case "PASS":
+			var result;
+
+			if (!this.hub || !cmd[3])
+				break;
+			if (cmd[3] != "QWK")
+				break;
+			if (cmd[2][0] == ":")
+				cmd[2] = cmd[2].slice(1);
+			if (cmd[4]) { // pass the message on to target.
+				var dest_server = searchbyserver(cmd[4]);
+				if (!dest_server) {
+					break;
+				} else if ((dest_server == -1) &&
+				    (this.flags&NLINE_IS_QWKMASTER)) {
+					var qwkid = cmd[2].toLowerCase();
+					var hunt = qwkid + ".synchro.net";
+					var my_server = 0;
+					for (ur in Unregistered) {
+						if (Unregistered[ur].nick ==
+						    hunt) {
+							my_server = Unregistered[ur];
+							break;
+						}
+					}
+					if (!my_server)
+						break;
+					if (cmd[1] != "OK") {
+						my_server.quit("Server not configured.");
+						break;
+					}
+					Servers[my_server.nick.toLowerCase()] = new IRC_Server();
+					var ns = Servers[my_server.id];
+					ns.id = my_server.id;
+					ns.nick = my_server.nick;
+					ns.info = my_server.realname;
+					ns.socket = my_server.socket;
+					delete Unregistered[my_server.id];
+					ns.finalize_server_connect("QWK");
+					break;
+				} else if (dest_server) {
+					if (dest_server == -1)
+						break; // security trap
+					dest_server.rawout(":" + ThisOrigin.nick + " PASS " + cmd[1] + " :" + cmd[2] + " QWK " + dest_server.nick);
+				}
+				break;
+			}
+			// Are we passing this on to our qwk-master?
+			for (nl in NLines) {
+				if (NLines[nl].flags&NLINE_IS_QWKMASTER) {
+					var qwk_master = searchbyserver(NLines[nl].servername);
+					if (qwk_master) {
+						qwk_master.rawout(":" + ThisOrigin.nick + " PASS " + cmd[1] + " :" + cmd[2] + " QWK");
+						return 0;
+					}
+				}
+			}
+			// If we got here, we must be the qwk master. Process.
+			if (check_qwk_passwd(cmd[2],cmd[1]))
+				result = "OK";
+			else
+				result = "VOID";
+			this.rawout(":" + servername + " PASS " + result + " :" + cmd[2] + " QWK " + ThisOrigin.nick);
+			break;
+		case "PONG":
+			if (cmd[2]) {
+				if (cmd[2][0] == ":")
+					cmd[2] = cmd[2].slice(1);
+				var my_server = searchbyserver(cmd[2]);
+				if (!my_server) {
+					break;
+				} else if (my_server == -1) {
+					var my_nick = Users[cmd[2].toUpperCase()];
+					if (my_nick)
+						my_nick.rawout(":" + ThisOrigin.nick + " PONG " + cmd[1] + " :" + my_nick.nick);
+					else
+						this.pinged = false;
+					break;
+				} else if (my_server) {
+					my_server.rawout(":" + ThisOrigin.nick + " PONG " + cmd[1] + " :" + cmd[2]);
+					break;
+				}
+			}
+			this.pinged = false;
+			break;
+		case "PRIVMSG":
+			if (!cmd[1])
+				break;
+			var my_ircstr = IRC_string(cmdline);
+			if ( !cmd[2] || ( !cmd[3] && (
+			     (cmd[2] == ":") && (my_ircstr == "")
+			   ) ) )
+				break;
+			var targets = cmd[1].split(",");
+			for (pm in targets) {
+				if (targets[pm][0] != "&")
+					ThisOrigin.do_msg(targets[pm],"PRIVMSG",my_ircstr);
+			}
+			break;
+		case "QUIT":
+			ThisOrigin.quit(IRC_string(cmdline));
+			break;
+		case "SERVER":
+			if (!cmd[3])
+				break;
+			// FIXME: when on Earth does this happen? :P?
+			if ((cmd[2] == 1) && !this.info) {
+				umode_notice(USERMODE_OPER,"Notice","This wasn't supposed to happen!");
+				this.nick = cmd[1];
+				this.hops = 1;
+				this.info = IRC_string(cmdline);
+				this.linkparent = servername;
+				this.parent = this.nick.toLowerCase();
+				var newsrv = this;
+			} else if (parseInt(cmd[2]) > 1) {
+				if (this.hub) {
+					var new_id = "id" + next_client_id;
+					next_client_id++;
+					Servers[cmd[1].toLowerCase()] = new IRC_Server;
+					var newsrv = Servers[cmd[1].toLowerCase()];
+					newsrv.hops = cmd[2];
+					newsrv.nick = cmd[1];
+					newsrv.info = IRC_string(cmdline);
+					newsrv.parent = this.nick.toLowerCase();
+					newsrv.linkparent = ThisOrigin.nick;
+					newsrv.local = false;
+					for (u in ULines) {
+						if (ULines[u] == cmd[1]) {
+							newsrv.uline = true;
+							break;
+						}
+					}
+				} else {
+					umode_notice(USERMODE_ROUTING,"Routing","from " + servername + ": Non-Hub link " + this.nick + " introduced " + cmd[1] + "(*).");
+					this.quit("Too many servers.  You have no H:Line to introduce " + cmd[1] + ".",true);
+					return 0;
+				}
+			} else {
+				break;
+			}
+			this.bcast_to_servers_raw(":" + newsrv.linkparent + " SERVER " + newsrv.nick + " " + (parseInt(newsrv.hops)+1) + " :" + newsrv.realname);
+			break;
+		case "SJOIN":
+			if (!cmd[2])
+				break;
+			if (cmd[2][0] != "#")
+				break;
+
+			var chan_members;
+			var cm_array;
+
+			if (cmd[3]) {
+				var mode_args = "";
+				var tmp_modeargs = 0;
+
+				for (tmpmc in cmd[3]) {
+					if ((cmd[3][tmpmc] == "k") ||
+					    (cmd[3][tmpmc] == "l")) {
+						tmp_modeargs++;
+						mode_args += cmd[3 + tmp_modeargs];
+					}
+				}
+				if ((cmd[4] == "") && cmd[5])
+					tmp_modeargs++;
+				
+				chan_members = IRC_string(cmdline,4+tmp_modeargs).split(' ');
+
+				if (chan_members == "") {
+					umode_notice(USERMODE_OPER,"Notice","Server " + this.nick + " trying to SJOIN empty channel " + cmd[2] + " before processing.");
+					break;
+				}
+
+				cm_array = new Array;
+
+				for (cm in chan_members) {
+					var isop = false;
+					var isvoice = false;
+					if (chan_members[cm][0] == "@") {
+						isop = true;
+						chan_members[cm] = chan_members[cm].slice(1);
+					}
+					if (chan_members[cm][0] == "+") {
+						isvoice = true;
+						chan_members[cm] = chan_members[cm].slice(1);
+					}
+					var tmp_nick = Users[chan_members[cm].toUpperCase()];
+					if (!tmp_nick)
+						continue;
+					cm_array.push(new SJOIN_Nick(tmp_nick,isop,isvoice));
+				}
+
+				if (cm_array.length < 1) {
+					umode_notice(USERMODE_OPER,"Notice","Server " + this.nick + " trying to SJOIN empty channel " + cmd[2] + " post processing.");
+					break;
+				}
+
+			}
+
+			var cn_tuc = cmd[2].toUpperCase();
+			var chan = Channels[cmd[2].toUpperCase()];
+			if (!chan) {
+				Channels[cn_tuc]=new Channel(cn_tuc);
+				chan = Channels[cn_tuc];
+				chan.nam = cmd[2];
+				chan.created = parseInt(cmd[1]);
+				chan.topic = "";
+				chan.users = new Array;
+				chan.modelist[CHANMODE_BAN] = new Array;
+				chan.modelist[CHANMODE_VOICE] = new Array;
+				chan.modelist[CHANMODE_OP] = new Array;
+				chan.mode = CHANMODE_NONE;
+			}
+
+			if (cmd[3]) {
+				var bounce_modes = true;
+				if (!ThisOrigin.local ||
+				    (chan.created == parseInt(cmd[1])))
+					bounce_modes = false;
+				if (chan.created >= parseInt(cmd[1])) {
+					if (mode_args)
+						this.set_chanmode(chan, cmd[3] + " " + mode_args, bounce_modes);
+					else
+						this.set_chanmode(chan, cmd[3], bounce_modes);
+				}
+
+				var num_sync_modes = 0;
+				var push_sync_modes = "+";
+				var push_sync_args = "";
+				var new_chan_members = "";
+				for (member in cm_array) {
+					if (new_chan_members)
+						new_chan_members += " ";
+
+					var member_obj = cm_array[member].nick;
+					var is_voice = cm_array[member].isvoice;
+					var is_op = cm_array[member].isop;
+
+					if (member_obj.channels[chan.nam.toUpperCase()])
+						continue;
+					member_obj.channels[chan.nam.toUpperCase()] = chan;
+					chan.users[member_obj.id] = member_obj;
+					member_obj.bcast_to_channel(chan, "JOIN " + chan.nam, false);
+					if (chan.created >= parseInt(cmd[1])) {
+						if (is_op) {
+							chan.modelist[CHANMODE_OP][member_obj.id] = member_obj.id;
+							push_sync_modes += "o";
+							push_sync_args += " " + member_obj.nick;
+							num_sync_modes++;
+							new_chan_members += "@";
+						}
+						if (num_sync_modes >= max_modes) {
+							this.bcast_to_channel(chan, "MODE " + chan.nam + " " + push_sync_modes + push_sync_args);
+							push_sync_modes = "+";
+							push_sync_args = "";
+							num_sync_modes = 0;
+						}
+						if (is_voice) {
+							chan.modelist[CHANMODE_VOICE][member_obj.id] = member_obj;
+							push_sync_modes += "v";
+							push_sync_args += " " + member_obj.nick;
+							num_sync_modes++;
+							new_chan_members += "+";
+						}
+						if (num_sync_modes >= max_modes) {
+							this.bcast_to_channel(chan, "MODE " + chan.nam + " " + push_sync_modes + push_sync_args);
+							push_sync_modes = "+";
+							push_sync_args = "";
+							num_sync_modes = 0;
+						}
+					}
+					new_chan_members += member_obj.nick;
+				}
+				if (num_sync_modes)
+					this.bcast_to_channel(chan, "MODE " + chan.nam + " " + push_sync_modes + push_sync_args);
+
+				// Synchronize the TS to what we received.
+				if (chan.created > parseInt(cmd[1]))
+					chan.created = parseInt(cmd[1]);
+
+				this.bcast_to_servers_raw(":" + ThisOrigin.nick + " SJOIN " + chan.created + " " + chan.nam + " " + chan.chanmode(true) + " :" + new_chan_members)
+			} else {
+				if (ThisOrigin.channels[chan.nam.toUpperCase()])
+					break;
+				ThisOrigin.channels[chan.nam.toUpperCase()] = chan;
+				chan.users[ThisOrigin.id] = ThisOrigin;
+				ThisOrigin.bcast_to_channel(chan, "JOIN " + chan.nam, false);
+				this.bcast_to_servers_raw(":" + ThisOrigin.nick + " SJOIN " + chan.created + " " + chan.nam);
+			}
+			break;
+		case "SQUIT":
+			var sq_server;
+			var reason;
+
+			if (!cmd[1] || !this.hub)
+				sq_server = this;
+			else
+				sq_server = searchbyserver(cmd[1]);
+			if (!sq_server)
+				break;
+			reason = IRC_string(cmdline);
+			if (!reason || !cmd[2])
+				reason = ThisOrigin.nick;
+			if (sq_server == -1) {
+				this.bcast_to_servers_raw("SQUIT " + this.nick + " :Forwards SQUIT.");
+				this.quit("Forwards SQUIT.",true);
+				break;
+			}
+			// message from our uplink telling us a server is gone
+			if (this.id == sq_server.parent) {
+				sq_server.quit(reason,false,false,ThisOrigin);
+				break;
+			}
+			// oper or server going for squit of a server
+			if (!sq_server.local) {
+				sq_server.rawout(":" + ThisOrigin.nick + " SQUIT " + sq_server.nick + " :" + reason);
+				break;
+			}
+			var msg = "Received SQUIT " + cmd[1] + " from " +
+				ThisOrigin.nick + "(" + reason + ")";
+			server_bcast_to_servers("GNOTICE :" + msg);
+			umode_notice(USERMODE_ROUTING,"Routing","from " +
+				servername + ": " + msg);
+			sq_server.quit(reason);
+			break;
+		case "STATS":
+			if (!cmd[2])
+				break;
+			if (cmd[2][0] == ":")
+				cmd[2] = cmd[2].slice(1);
+			if (IRC_match(servername, cmd[2])) {
+				ThisOrigin.do_stats(cmd[1][0]);
+			} else {
+				var dest_server = searchbyserver(cmd[2]);
+				if (!dest_server)
+					break;
+				dest_server.rawout(":" + ThisOrigin.nick + " STATS " + cmd[1][0] + " :" + dest_server.nick);
+			}
+			break;
+		case "SUMMON":
+			if (!cmd[2])
+				break;
+			if (cmd[2][0] == ":")
+				cmd[2] = cmd[2].slice(1);
+			if (IRC_match(servername, cmd[2])) {
+				if (enable_users_summon) {
+					ThisOrigin.do_summon(cmd[1]);
+				} else {
+					ThisOrigin.numeric445();
+					break;
+				}
+			} else {
+				var dest_server = searchbyserver(cmd[1]);
+				if (!dest_server)
+					break;
+				dest_server.rawout(":" + ThisOrigin.nick + " SUMMON " + cmd[1] + " :" + dest_server.nick);
+			}
+			break;
+		case "TIME":
+			if (!cmd[1])
+				break;
+			if (cmd[1][0] == ":")
+				cmd[1] = cmd[1].slice(1);
+			if (IRC_match(servername, cmd[1])) {
+				ThisOrigin.numeric391();
+			} else {
+				var dest_server = searchbyserver(cmd[1]);
+				if (!dest_server)
+					break;
+				dest_server.rawout(":" + ThisOrigin.nick + " TIME :" + dest_server.nick);
+			}
+			break;
+		case "TOPIC":
+			if (!cmd[4])
+				break;
+			var chan = Channels[cmd[1].toUpperCase()];
+			if (!chan)
+				break;
+			var the_topic = IRC_string(cmdline);
+			if (the_topic == chan.topic)
+				break;
+			chan.topic = the_topic;
+			if (this.hub)
+				chan.topictime = cmd[3];
+			else
+				chan.topictime = time();
+			chan.topicchangedby = cmd[2];
+			var str = "TOPIC " + chan.nam + " :" + chan.topic;
+			ThisOrigin.bcast_to_channel(chan,str,false);
+			this.bcast_to_servers_raw(":" + ThisOrigin.nick + " TOPIC " + chan.nam + " " + ThisOrigin.nick + " " + chan.topictime + " :" + chan.topic);
+			break;
+		case "TRACE":
+			if (!cmd[1])
+				break;
+			ThisOrigin.do_trace(cmd[1]);
+			break;
+		case "USERS":
+			if (!cmd[1])
+				break;
+			if (cmd[1][0] == ":")
+				cmd[1] = cmd[1].slice(1);
+			if (IRC_match(servername, cmd[1])) {
+				ThisOrigin.numeric351();
+			} else {
+				// psst, pass it on
+				var dest_server = searchbyserver(cmd[1]);
+				if (!dest_server)
+					break;
+				dest_server.rawout(":" + ThisOrigin.nick + " VERSION :" + dest_server.nick);
+			}
+			break;
+		case "VERSION":
+			if (!cmd[1])
+				break;
+			if (cmd[1][0] == ":")
+				cmd[1] = cmd[1].slice(1);
+			if (IRC_match(servername, cmd[1])) {
+				// it's for us, return the message
+				ThisOrigin.numeric351();
+			} else {
+				// psst, pass it on
+				var dest_server = searchbyserver(cmd[1]);
+				if (!dest_server)
+					break; // someone messed up.
+				dest_server.rawout(":" + ThisOrigin.nick + " VERSION :" + dest_server.nick);
+			}
+			break;
+		case "WALLOPS":
+			if (!cmd[1])
+				break;
+			var str = ":" + ThisOrigin.nick + " WALLOPS :" +
+				IRC_string(cmdline);
+			wallopers(str);
+			this.bcast_to_servers_raw(str);
+			break;
+		case "WHOIS":
+			if (!cmd[2])
+				break;
+			if (cmd[2][0] == ":")
+				cmd[2] = cmd[2].slice(1);
+			if (IRC_match(servername, cmd[2])) {
+				var wi_nicks = cmd[1].split(",");
+				for (wi_nick in wi_nicks) {
+				var wi = Users[wi_nicks[wi_nick].toUpperCase()];
+					if (wi)
+						ThisOrigin.do_whois(wi);
+					else
+						ThisOrigin.numeric401(wi_nicks[wi_nick]);
+				} 
+				ThisOrigin.numeric(318, wi_nicks[0]+" :End of /WHOIS list.");
+			} else {
+				var dest_server = searchbyserver(cmd[2]);
+				if (!dest_server)
+					break;
+				dest_server.rawout(":" + ThisOrigin.nick + " WHOIS " + cmd[1] + " " + dest_server.nick);
+			}
+			break;
+		default:
+			umode_notice(USERMODE_OPER,"Notice","Server " + ThisOrigin.nick + " sent unrecognized command: " + cmdline);
+			break;
+	}
+}
+
+////////// Functions //////////
+
+function server_bcast_to_servers(str) {
+	for(thisClient in Local_Servers) {
+		Local_Servers[thisClient].rawout(str);
+	}
+}
+
+function IRCClient_bcast_to_servers(str) {
+	for(thisClient in Local_Servers) {
+		if (Local_Servers[thisClient].nick != this.parent)
+			Local_Servers[thisClient].originatorout(str,this);
+	}
+}
+
+function IRCClient_bcast_to_servers_raw(str) {
+	for(thisClient in Local_Servers) {
+		if (Local_Servers[thisClient].nick != this.parent)
+			Local_Servers[thisClient].rawout(str);
+	}
+}
+
+function Server_Quit(str,suppress_bcast,is_netsplit,origin) {
+	var tmp;
+
+	if (!str)
+		str = this.nick;
+
+	if (is_netsplit) {
+		this.netsplit(str);
+	} else if (this.local) {
+		if (!suppress_bcast)
+			this.bcast_to_servers_raw("SQUIT " + this.nick + " :" + str);
+		this.netsplit(servername + " " + this.nick);
+	} else if (origin) {
+		if (!suppress_bcast)
+			this.bcast_to_servers_raw(":" + origin.nick + " SQUIT " + this.nick + " :" + str);
+		this.netsplit(origin.nick + " " + this.nick);
+	} else {
+		umode_notice(USERMODE_OPER,"Notice",
+			"Netspliting a server which isn't local and doesn't " +
+			"have an origin?!");
+		if (!suppress_bcast)
+			this.bcast_to_servers_raw("SQUIT " + this.nick + " :" + str);
+		this.netsplit();
+	}
+
+	if((server.client_remove!=undefined) && this.local)
+		server.client_remove(this.socket);
+
+	if (this.local) {
+		this.rawout("ERROR :Closing Link: [" + this.uprefix + "@" + this.hostname + "] (" + str + ")");
+		// FIXME: wrong phrasing below
+		umode_notice(USERMODE_CLIENT,"Client","SERVER exiting: " +
+			this.nick + " (" + this.uprefix + "@" + this.hostname +
+			") [" + str + "] [" + this.ip + "]");
+		if (this.socket!=undefined)
+			this.socket.close();
+		delete Local_Sockets[this.id];
+	}
+	delete Local_Sockets[this.id];
+	delete Local_Sockets_Map[this.id];
+	delete Local_Servers[this.id];
+	delete Servers[this.nick.toLowerCase()];
+	delete this;
+	rebuild_socksel_array();
+}
+
+function IRCClient_synchronize() {
+	this.rawout("BURST"); // warn of impending synchronization
+	for (my_server in Servers) {
+		if (Servers[my_server].id != this.id)
+			this.server_info(Servers[my_server]);
+	}
+	for (my_client in Users) {
+		this.server_nick_info(Users[my_client]);
+	}
+	for (my_channel in Channels) {
+		if (my_channel[0] == "#")
+			this.server_chan_info(Channels[my_channel]);
+	}
+	umode_notice(USERMODE_ROUTING,"Routing","from " + servername +
+		": " + this.nick + " has processed user/channel burst, " +
+		"sending topic burst.");
+	for (my_channel in Channels) {
+		if ((my_channel[0] == "#") && Channels[my_channel].topic) {
+			var chan = Channels[my_channel];
+			this.rawout("TOPIC " + chan.nam + " " + chan.topicchangedby + " " + chan.topictime + " :" + chan.topic);
+		}
+	}
+	this.rawout("BURST 0"); // burst completed.
+	umode_notice(USERMODE_ROUTING,"Routing","from " + servername +
+		": " + this.nick + " has processed topic burst " +
+		"(synched to network data).");
+}
+
+function IRCClient_server_info(sni_server) {
+	var realhops = parseInt(sni_server.hops)+1;
+	this.rawout(":" + sni_server.linkparent + " SERVER " + sni_server.nick + " " + realhops + " :" + sni_server.info);
+}
+
+function IRCClient_server_nick_info(sni_client) {
+	var actual_hops = parseInt(sni_client.hops) + 1;
+	this.rawout("NICK " + sni_client.nick + " " + actual_hops + " " + sni_client.created + " " + sni_client.get_usermode(true) + " " + sni_client.uprefix + " " + sni_client.hostname + " " + sni_client.servername + " 0 " + ip_to_int(sni_client.ip) + " :" + sni_client.realname);
+	if (sni_client.away)
+		this.rawout(":" + sni_client.nick + " AWAY :" + sni_client.away);
+}
+
+function IRCClient_reintroduce_nick(nick) {
+	var chan;
+	var cmodes;
+
+	this.server_nick_info(nick);
+
+	for (uchan in nick.channels) {
+		cmodes = "";
+		chan = nick.channels[uchan];
+		if (chan.modelist[CHANMODE_OP][nick.id])
+			cmodes += "@";
+		if (chan.modelist[CHANMODE_VOICE][nick.id])
+			cmodes += "+";
+		this.rawout("SJOIN " + chan.created + " " + chan.nam + " " + chan.chanmode(true) + " :" + cmodes + nick.nick);
+		if (chan.topic)
+			this.rawout("TOPIC " + chan.nam + " " + chan.topicchangedby + " " + chan.topictime + " :" + chan.topic);
+	}
+}
+
+function IRCClient_server_chan_info(sni_chan) {
+	this.rawout("SJOIN " + sni_chan.created + " " + sni_chan.nam + " " + sni_chan.chanmode(true) + " :" + sni_chan.occupants())
+	var modecounter=0;
+	var modestr="+";
+	var modeargs="";
+	for (aBan in sni_chan.modelist[CHANMODE_BAN]) {
+		modecounter++;
+		modestr += "b";
+		if (modeargs)
+			modeargs += " ";
+		modeargs += sni_chan.modelist[CHANMODE_BAN][aBan];
+		if (modecounter >= max_modes) {
+			this.ircout("MODE " + sni_chan.nam + " " + modestr + " " + modeargs);
+			modecounter=0;
+			modestr="+";
+			modeargs="";
+		}
+	}
+	if (modeargs)
+		this.ircout("MODE " + sni_chan.nam + " " + modestr + " " + modeargs);
+}
-- 
GitLab