Skip to content
Snippets Groups Projects
Select Git revision
  • dailybuild_linux-x64
  • dailybuild_win32
  • master default protected
  • sqlite
  • rip_abstraction
  • dailybuild_macos-armv8
  • dd_file_lister_filanem_in_desc_color
  • mode7
  • dd_msg_reader_are_you_there_warning_improvement
  • c23-playing
  • syncterm-1.3
  • syncterm-1.2
  • test-build
  • hide_remote_connection_with_telgate
  • 638-can-t-control-c-during-a-file-search
  • add_body_to_pager_email
  • mingw32-build
  • cryptlib-3.4.7
  • ree/mastermind
  • new_user_dat
  • sbbs320d
  • syncterm-1.6
  • syncterm-1.5
  • syncterm-1.4
  • sbbs320b
  • syncterm-1.3
  • syncterm-1.2
  • syncterm-1.2rc6
  • syncterm-1.2rc5
  • push
  • syncterm-1.2rc4
  • syncterm-1.2rc2
  • syncterm-1.2rc1
  • sbbs319b
  • sbbs318b
  • goodbuild_linux-x64_Sep-01-2020
  • goodbuild_win32_Sep-01-2020
  • goodbuild_linux-x64_Aug-31-2020
  • goodbuild_win32_Aug-31-2020
  • goodbuild_win32_Aug-30-2020
40 results

ircd.js

Blame
  • ircd.js 82.02 KiB
    // $Id$
    //
    // ircd.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-2009 Randolph Erwin Sommerfeld <sysop@rrx.ca>
    //
    
    //load("synchronet-json.js");
    
    load("sbbsdefs.js");
    load("sockdefs.js");
    load("nodedefs.js");
    
    load("irclib.js");
    
    load("ircd_unreg.js");
    load("ircd_user.js");
    load("ircd_channel.js");
    load("ircd_server.js");
    
    // CVS revision
    const MAIN_REVISION = "$Revision$".split(' ')[1];
    
    // Please don't play with this, unless you're making custom hacks.
    // IF you're making a custom version, it'd be appreciated if you left the
    // version number alone, and add a token in the form of +hack (i.e. 1.0+cyan)
    // This is so everyone knows your revision base, AND type of hack used.
    const VERSION = "SynchronetIRCd-1.3a(" + MAIN_REVISION + ")";
    const VERSION_STR = "Synchronet " 
    	+ system.version + system.revision + "-" + system.platform 
    	+ system.beta_version + " (IRCd by Randy Sommerfeld)";
    
    // This will dump all I/O to and from the server to your Synchronet console.
    // It also enables some more verbose WALLOPS, especially as they pertain to
    // blocking functions.
    // The special "DEBUG" oper command also switches this value.
    var debug = false;
    
    // The number of seconds to block before giving up on outbound CONNECT
    // attempts (when connecting to another IRC server -- i.e. a hub)  This value
    // is important because connecing is a BLOCKING operation, so your IRC *will*
    // freeze for the amount of time it takes to connect.
    const ob_sock_timeout = 3;
    
    // Should we enable the USERS and SUMMON commands?  These allow IRC users to
    // view users on the local BBS and summon them to IRC via a Synchronet telegram
    // message respectively.  Some people might be running the ircd standalone, or
    // otherwise don't want anonymous IRC users to have access to these commands.
    // We enable this by default because there's typically nothing wrong with
    // seeing who's on an arbitrary BBS or summoning them to IRC.
    const enable_users_summon = true;
    
    // what our server is capable of from a server point of view.
    // TS3 = Version 3 of accepted interserver timestamp protocol.
    // NOQUIT = QUIT clients on behalf of a SQUIT server? (no netsplit spam)
    // SSJOIN = SJOIN interserver command without dual TS, single TS only.
    // BURST = Sending of network synch data is done in a 3-stage burst (BURST cmd)
    // UNCONNECT = Server SQUIT is routable.
    // NICKIP = 9th parameter of interserver NICK command is an integer IP.
    // TSMODE = 2nd arg to standard MODE is the channel's TS.
    const server_capab = "TS3 NOQUIT SSJOIN BURST UNCONNECT NICKIP TSMODE";
    
    // EVERY server on the network MUST have the same values in ALL of these
    // categories.  If you change these, you WILL NOT be able to link to the
    // Synchronet IRC network.  Linking servers with different values here WILL
    // cause your network to desynchronize (and possibly crash the IRCD)
    // Remember, this is Synchronet, not Desynchronet ;)
    const max_chanlen = 100;	// Maximum channel name length.
    const max_nicklen = 30;		// Maximum nickname length.
    const max_modes = 6;		// Maximum modes on single MODE command
    const max_user_chans = 10;	// Maximum channels users can join
    const max_bans = 25;		// Maximum bans (+b) per channel
    const max_topiclen = 307;	// Maximum length of topic per channel
    const max_kicklen = 307;	// Maximum length of kick reasons
    const max_who = 100;		// Maximum replies to WHO for non-oper users
    const max_silence = 10;		// Maximum entries on a user's SILENCE list
    
    /* Server types */
    const BAHAMUT = 1;
    const DREAMFORGE = 2;
    
    var default_port = 6667;
    
    log(VERSION + " started.");
    
    // Our primary arrays.
    Unregistered = new Object;
    Users = new Object;
    Servers = new Object;
    Channels = new Object;
    
    Local_Sockets = new Object;
    Local_Sockets_Map = new Object;
    
    Selectable_Sockets = new Object;
    Selectable_Sockets_Map = new Object;
    
    Global_CommandLine = ""; // We use this to track if a cmdline causes a crash.
    
    hcc_total = 0;
    hcc_users = 0;
    hcc_counter = 0;
    server_uptime = time();
    
    WhoWas = new Object;	/* Stores uppercase nicks */
    WhoWasMap = new Array;	/* A true push/pop array pointing to WhoWas entries */
    WhoWas_Buffer = 1000;	/* Maximum number of WhoWas entries to keep track of */
    
    NickHistory = new Array;	/* A true array using push and pop */
    nick_buffer = 1000;
    nick_pointer = 0;
    
    /* Keep track of commands and how long they take to execute. */
    Profile = new Object;
    
    // This is where our unique ID for each client comes from for unreg'd clients.
    next_client_id = 0;
    
    // An array containing all the objects containing local sockets that we need
    // to poll.
    Local_Users = new Object;
    Local_Servers = new Object;
    
    rebuild_socksel_array = true;
    
    network_debug = false;
    
    last_recvq_check = 0;
    
    /*
     * A tri-state variable indicating if socket.send is "old" (ie: returns bool)
     * or "new" (ie: returns count of bytes sent).
     */
    var new_socket_send;
    
    // Parse command-line arguments.
    config_filename="";
    var cmdline_port;
    for (cmdarg=0;cmdarg<argc;cmdarg++) {
    	switch(argv[cmdarg].toLowerCase()) {
    		case "-f":
    			config_filename = argv[++cmdarg];
    			break;
    		case "-p":
    			cmdline_port = parseInt(argv[++cmdarg]);
    			break;
    		case "-d":
    			debug=true;
    			break;
    	}
    }
    
    read_config_file();
    
    if(this.server==undefined) {		// Running from JSexec?
    	if (!jsexec_revision_detail)
    		jsexec_revision_detail = "JSexec";
    	if (cmdline_port)
    		default_port = cmdline_port;
    	else if (mline_port)
    		default_port = mline_port;
    
    	server = { socket: false, terminated: false,
    		version_detail: jsexec_revision_detail };
    	server.socket = create_new_socket(default_port)
    	if (!server.socket)
    		exit();
    }
    
    server.socket.nonblocking = true;	// REQUIRED!
    server.socket.debug = false;		// Will spam your log if true :)
    
    // Now open additional listening sockets as defined on the P:Line in ircd.conf
    open_plines = new Array(); /* True Array */
    // Make our 'server' object the first open P:Line
    open_plines[0] = server.socket;
    for (pl in PLines) {
    	var new_pl_sock = create_new_socket(PLines[pl]);
    	if (new_pl_sock) {
    		new_pl_sock.nonblocking = true;
    		new_pl_sock.debug = false;
    		open_plines.push(new_pl_sock);
    	}
    }
    
    js.branch_limit=0; // we're not an infinite loop.
    js.auto_terminate=false; // we handle our own termination requests
    
    ///// Main Loop /////
    while (!js.terminated) {
    
    	// Setup a new socket if a connection is accepted.
    	for (pl in open_plines) {
    		if (open_plines[pl].poll()) {
    			log(LOG_DEBUG,"Accepting new connection on port "
    				+ open_plines[pl].local_port);
    			var client_sock=open_plines[pl].accept();
    			if(client_sock) {
    				client_sock.nonblocking = true;
    				switch(client_sock.local_port) {
    					case 994:
    					case 6697:
    						client_sock.ssl_server=1;
    				}
    				if (!client_sock.remote_ip_address) {
    					log(LOG_DEBUG,"Socket has no IP address.  Closing.");
    					client_sock.close();
    				} else if (iszlined(client_sock.remote_ip_address)) {
    					client_sock.send(":" + servername
    						+ " 465 * :You've been Z:Lined from this server.\r\n");
    					client_sock.close();
    				} else {
    					var new_id = "id" + next_client_id;
    					next_client_id++;
    					if(server.client_add != undefined)
    						server.client_add(client_sock);
    					if(server.clients != undefined)
    						log(LOG_DEBUG,format("%d clients", server.clients));
    					Unregistered[new_id] = new Unregistered_Client(new_id,
    						client_sock);
    				}
    			} else
    				log(LOG_DEBUG,"!ERROR " + open_plines[pl].error
    					+ " accepting connection");
    		}
    	}
    
    	// Check for pending DNS hostname resolutions.
    	for(this_unreg in Unregistered) {
    		if (Unregistered[this_unreg] &&
    		    Unregistered[this_unreg].pending_resolve_time)
    			Unregistered[this_unreg].resolve_check();
    	}
    
    	// Only rebuild our selectable sockets if required.
    	if (rebuild_socksel_array) {
    		Selectable_Sockets = new Array;
    		Selectable_Sockets_Map = new Array;
    		for (i in Local_Sockets) {
    			Selectable_Sockets.push(Local_Sockets[i]);
    			Selectable_Sockets_Map.push(Local_Sockets_Map[i]);
    		}
    		rebuild_socksel_array = false;
    	}
    
    	/* Check for ping timeouts and process queues. */
    	/* FIXME/TODO: These need to be changed to a mapping system ASAP. */
    	for(this_sock in Selectable_Sockets) {
    		if (Selectable_Sockets_Map[this_sock]) {
    			Selectable_Sockets_Map[this_sock].check_timeout();
                Selectable_Sockets_Map[this_sock].check_queues();
            }
        }
    
    	// do some work.
    	if (Selectable_Sockets.length) {
    		var readme = socket_select(Selectable_Sockets, 1 /*secs*/);
    		try {
    			for(thisPolled in readme) {
    				if (Selectable_Sockets_Map[readme[thisPolled]]) {
    					var conn = Selectable_Sockets_Map[readme[thisPolled]];
    					if (!conn.socket.is_connected) {
    						conn.quit("Connection reset by peer.");
    						continue;
    					}
    					conn.recvq.recv(conn.socket);
    				}
    			}
    		} catch(e) {
    			gnotice("FATAL ERROR: " + e + " CMDLINE: " + Global_CommandLine);
    			log(LOG_ERR,"JavaScript exception: " + e + " CMDLINE: "
    				+ Global_CommandLine);
    			terminate_everything("A fatal error occured!", /* ERROR? */true);
    		}
    	} else {
    		mswait(100);
    	}
    
    	// Scan C:Lines for servers to connect to automatically.
    	var my_cline;
    	for(thisCL in CLines) {
    		my_cline = CLines[thisCL];
    		if (my_cline.port && YLines[my_cline.ircclass].connfreq &&
    		    (search_server_only(my_cline.servername) < 1) &&
    		     ((time() - my_cline.lastconnect) >
    		     YLines[my_cline.ircclass].connfreq)
    		   ) {
    			umode_notice(USERMODE_ROUTING,"Routing",
    				"Auto-connecting to " +
    				CLines[thisCL].servername);
    			connect_to_server(CLines[thisCL]);
    		}
    	}
    
    }
    
    // End of our run, so terminate everything before we go.
    terminate_everything("Terminated.");
    
    //////////////////////////////// END OF MAIN ////////////////////////////////
    
    // Okay, welcome to my world.
    // str = The string used for the quit reason UNLESS 'is_netsplit' is set to
    //       true, in which case it becomes the string used to QUIT individual
    //       clients in a netsplit (i.e. "server.one server.two")
    // suppress_bcast = Set to TRUE if you don't want the message to be broadcast
    //       accross the entire network.  Useful for netsplits, global kills, or
    //       other network messages where the rest of the network is nuking the
    //       client on their own.
    // is_netsplit = Should never be used except in a recursive call from the
    //       'this.netsplit()' function.  Tells the function that we're recursive
    //       and to use 'str' as the reason for quiting all the clients
    // origin = an object typically only passed in the case of a SQUIT, contains
    //       the client who originated the message (i.e. for generating netsplit
    //       messages.)
    // FIXME: this function split into three. comments kept for now, but nuke later
    
    ////////// Functions not linked to an object //////////
    
    // Sigh, there's no way to tell the length of an associative array in JS, so,
    // we have this to help us:
    function true_array_len(my_array) {
    	var counter = 0;
    	for (i in my_array) {
    		counter++;
    	}
    	return counter;
    }
    
    function terminate_everything(terminate_reason, error) {
    	log(error ? LOG_ERR : LOG_NOTICE, "Terminating: " + terminate_reason);
    	for(thisClient in Local_Sockets_Map) {
    		var Client = Local_Sockets_Map[thisClient];
    		Client.rawout("ERROR :" + terminate_reason);
    		Client.socket.close();
    	}
    	exit(error);
    }
    
    function search_server_only(server_name) {
    	if (!server_name)
    		return 0;
    	for(thisServer in Servers) {
    		var Server=Servers[thisServer];
    		if (wildmatch(Server.nick,server_name))
    			return Server;
    	}
    	if (wildmatch(servername,server_name))
    		return -1; // the server passed to us is our own.
    	// No success.
    	return 0;
    }
    
    function searchbyserver(servnick) {
    	if (!servnick)
    		return 0;
    	var server_try = search_server_only(servnick);
    	if (server_try) {
    		return server_try;
    	} else {
    		for(thisNick in Users) {
    			var Nick=Users[thisNick];
    			if (wildmatch(Nick.nick,servnick))
    				return search_server_only(Nick.servername);
    		}
    	}
    	return 0; // looks like we failed after all that hard work :(
    }
    
    // Only allow letters, numbers and underscore in username to a maximum of
    // 9 characters for 'anonymous' users (i.e. not using PASS to authenticate.)
    // hostile characters like !,@,: etc would be bad here :)
    function parse_username(str) {
    	str = str.replace(/[^\w]/g,"").toLowerCase();
    	if (!str)
    		str = "user"; // nothing? we'll give you something boring.
    	return str.slice(0,9);
    }
    
    function parse_nline_flags(flags) {
    	var nline_flags = 0;
    	for(thisflag in flags) {
    		switch(flags[thisflag]) {
    			case "q":
    				nline_flags |= NLINE_CHECK_QWKPASSWD;
    				break;
    			case "w":
    				nline_flags |= NLINE_IS_QWKMASTER;
    				break;
    			case "k":
    				nline_flags |= NLINE_CHECK_WITH_QWKMASTER;
    				break;
    			case "d":
    				nline_flags |= NLINE_IS_DREAMFORGE;
    				break;
    			default:
    				log(LOG_WARNING,"!WARNING Unknown N:Line flag '"
    					+ flags[thisflag] + "' in config.");
    				break;
    		}
    	}
    	return nline_flags;
    }
    
    function parse_oline_flags(flags) {
    	var oline_flags = 0;
    	for(thisflag in flags) {
    		switch(flags[thisflag]) {
    			case "r":
    				oline_flags |= OLINE_CAN_REHASH;
    				break;
    			case "R":
    				oline_flags |= OLINE_CAN_RESTART;
    				break;
    			case "D":
    				oline_flags |= OLINE_CAN_DIE;
    				break;
    			case "g":
    				oline_flags |= OLINE_CAN_GLOBOPS;
    				break;
    			case "w":
    				oline_flags |= OLINE_CAN_WALLOPS;
    				break;
    			case "l":
    				oline_flags |= OLINE_CAN_LOCOPS;
    				break;
    			case "c":
    				oline_flags |= OLINE_CAN_LSQUITCON;
    				break;
    			case "C":
    				oline_flags |= OLINE_CAN_GSQUITCON;
    				break;
    			case "k":
    				oline_flags |= OLINE_CAN_LKILL;
    				break;
    			case "K":
    				oline_flags |= OLINE_CAN_GKILL;
    				break;
    			case "b":
    				oline_flags |= OLINE_CAN_KLINE;
    				break;
    			case "B":
    				oline_flags |= OLINE_CAN_UNKLINE;
    				break;
    			case "n":
    				oline_flags |= OLINE_CAN_LGNOTICE;
    				break;
    			case "N":
    				oline_flags |= OLINE_CAN_GGNOTICE;
    				break;
    			case "u":
    				oline_flags |= OLINE_CAN_UMODEC;
    				break;
    			case "A":
    				oline_flags |= OLINE_IS_ADMIN;
    				break;
    			case "a":
    			case "f":
    			case "F":
    				break; // All reserved for future use.
    			case "s":
    				oline_flags |= OLINE_CAN_CHATOPS;
    				break;
    			case "S":
    				oline_flags |= OLINE_CHECK_SYSPASSWD;
    				break;
    			case "x":
    			case "X":
    				oline_flags |= OLINE_CAN_DEBUG;
    				break;
    			case "O":
    				oline_flags |= OLINE_IS_GOPER;
    				oline_flags |= OLINE_CAN_GSQUITCON;
    				oline_flags |= OLINE_CAN_GKILL;
    				oline_flags |= OLINE_CAN_GGNOTICE;
    				oline_flags |= OLINE_CAN_CHATOPS;
    			case "o":
    				oline_flags |= OLINE_CAN_REHASH;
    				oline_flags |= OLINE_CAN_GLOBOPS;
    				oline_flags |= OLINE_CAN_WALLOPS;
    				oline_flags |= OLINE_CAN_LOCOPS;
    				oline_flags |= OLINE_CAN_LSQUITCON;
    				oline_flags |= OLINE_CAN_LKILL;
    				oline_flags |= OLINE_CAN_KLINE;
    				oline_flags |= OLINE_CAN_UNKLINE;
    				oline_flags |= OLINE_CAN_LGNOTICE;
    				oline_flags |= OLINE_CAN_UMODEC;
    				break;
    			default:
    				log(LOG_WARNING,"!WARNING Unknown O:Line flag '"
    					+ flags[thisflag] + "' in config.");
    				break;
    		}
    	}
    	return oline_flags;
    }
    
    function umode_notice(bit,ntype,nmessage) {
    	log(ntype + ": " + nmessage);
    	for (thisuser in Local_Users) {
    		var user = Local_Users[thisuser];
    		if (user.mode && ((user.mode&bit)==bit))
    			user.rawout(":" + servername + " NOTICE " + user.nick
    				+ " :*** " + ntype + " -- " + nmessage);
    	}
    
    }
    
    function create_ban_mask(str,kline) {
    	var tmp_banstr = new Array;
    	tmp_banstr[0] = "";
    	tmp_banstr[1] = "";
    	tmp_banstr[2] = "";
    	var bchar_counter = 0;
    	var part_counter = 0; // BAN: 0!1@2 KLINE: 0@1
    	var regexp="[A-Za-z\{\}\`\^\_\|\\]\\[\\\\0-9\-.*?\~]";
    	var finalstr;
    	for (bchar in str) {
    		if (str[bchar].match(regexp)) {
    			tmp_banstr[part_counter] += str[bchar];
    			bchar_counter++;
    		} else if ((str[bchar] == "!") && (part_counter == 0) &&
    			   !kline) {
    			part_counter = 1;
    			bchar_counter = 0;
    		} else if ((str[bchar] == "@") && (part_counter == 1) &&
    			   !kline) {
    			part_counter = 2;
    			bchar_counter = 0;
    		} else if ((str[bchar] == "@") && (part_counter == 0)) {
    			if (kline) {
    				part_counter = 1;
    			} else {
    				tmp_banstr[1] = tmp_banstr[0];
    				tmp_banstr[0] = "*";
    				part_counter = 2;
    			}
    			bchar_counter = 0;
    		}
    	}
    	if (!tmp_banstr[0] && !tmp_banstr[1] && !tmp_banstr[2])
    		return 0;
    	if (tmp_banstr[0].match(/[.]/) && !tmp_banstr[1] && !tmp_banstr[2]) {
    		if (kline)
    			tmp_banstr[1] = tmp_banstr[0];
    		else
    			tmp_banstr[2] = tmp_banstr[0];
    		tmp_banstr[0] = "";
    	}
    	if (!tmp_banstr[0])
    		tmp_banstr[0] = "*";
    	if (!tmp_banstr[1])
    		tmp_banstr[1] = "*";
    	if (!tmp_banstr[2] && !kline)
    		tmp_banstr[2] = "*";
    	if (kline)
    		finalstr = tmp_banstr[0].slice(0,10) + "@" + tmp_banstr[1].slice(0,80);
    	else
    		finalstr = tmp_banstr[0].slice(0,max_nicklen) + "!"
    			+ tmp_banstr[1].slice(0,10) + "@" + tmp_banstr[2].slice(0,80);
            while (finalstr.match(/[*][*]/)) {
                    finalstr=finalstr.replace(/[*][*]/g,"*");
            }
    	return finalstr;
    }
    
    function isklined(kl_str) {
    	for(the_kl in KLines) {
    		if (KLines[the_kl].hostmask &&
    		    wildmatch(kl_str,KLines[the_kl].hostmask))
    			return KLines[the_kl];
    	}
    	return 0;
    }
    
    function iszlined(zl_ip) {
    	for(the_zl in ZLines) {
    		if (ZLines[the_zl].ipmask &&
    		    wildmatch(zl_ip,ZLines[the_zl].ipmask))
    			return 1;
    	}
    	return 0;
    }
    
    function scan_for_klined_clients() {
    	for(thisUser in Local_Users) {
    		var theuser=Local_Users[thisUser];
    		var kline=isklined(theuser.uprefix + "@" + theuser.hostname);
    		if (kline)
    			theuser.quit("User has been K:Lined (" + kline.reason + ")");
    		if (iszlined(theuser.ip))
    			theuser.quit("User has been Z:Lined");
    	}
    }
    
    function remove_kline(kl_hm) {
    	for(the_kl in KLines) {
    		if (KLines[the_kl].hostmask &&
    		    wildmatch(kl_hm,KLines[the_kl].hostmask)) {
    			KLines[the_kl].hostmask = "";
    			KLines[the_kl].reason = "";
    			KLines[the_kl].type = "";
    			return 1;
    		}
    	}
    	return 0; // failure.
    }
    
    function connect_to_server(this_cline,the_port) {
    	var connect_sock;
    	var new_id;
    
    	if (!the_port && this_cline.port)
    		the_port = this_cline.port;
    	else if (!the_port)
    		the_port = default_port; // try a safe default.
    	connect_sock = new Socket();
    	connect_sock.bind(0,server.interface_ip_address);
    	connect_sock.connect(this_cline.host,the_port,ob_sock_timeout);
    
    	var sendts = true; /* Assume Bahamut */
    
    	for (nl in NLines) {
    		var mynl = NLines[nl];
    		if ((mynl.flags&NLINE_IS_DREAMFORGE) && 
    		    (mynl.servername == this_cline.servername)) {
    			sendts = false;
    			break;
    		}
    	}
    
    	if (connect_sock.is_connected) {
    		umode_notice(USERMODE_ROUTING,"Routing",
    			"Connected!  Sending info...");
    		var sendstr = "PASS " + this_cline.password;
    		if (sendts)
    			sendstr += " :TS";
    		connect_sock.send(sendstr + "\r\n");
    		connect_sock.send("CAPAB " + server_capab + "\r\n");
    		connect_sock.send("SERVER " + servername + " 1 :" + serverdesc +"\r\n");
    		new_id = "id" + next_client_id;
    		next_client_id++;
    		Unregistered[new_id]=new Unregistered_Client(new_id,connect_sock);
    		Unregistered[new_id].sendps = false; // Don't do P/S pair again
    		Unregistered[new_id].outgoing = true; /* Outgoing Connection */
    	}
    	else
    		connect_sock.close();
    	this_cline.lastconnect = time();
    }
    
    function wallopers(str) {
    	for(thisoper in Local_Users) {
    		var oper=Local_Users[thisoper];
    		if (oper.mode&USERMODE_OPER)
    			oper.rawout(str);
    	}
    }
    
    function push_nickbuf(oldnick,newnick) {
    	NickHistory.unshift(new NickBuf(oldnick,newnick));
    	if(NickHistory.length >= nick_buffer)
    		NickHistory.pop();
    }
    
    function search_nickbuf(bufnick) {
    	for (nb=NickHistory.length-1;nb>-1;nb--) {
    		if (bufnick.toUpperCase() == NickHistory[nb].oldnick.toUpperCase()) {
    			if (!Users[NickHistory[nb].newnick.toUpperCase()])
    				bufnick = NickHistory[nb].newnick;
    			else
    				return Users[NickHistory[nb].newnick.toUpperCase()];
    		}
    	}
    	return 0;
    }
    
    function read_config_file() {
    	/* All of these variables are global. */
    	Admin1 = "";
    	Admin2 = "";
    	Admin3 = "";
    	CLines = new Array;
    	HLines = new Array;
    	ILines = new Array;
    	KLines = new Array;
    	NLines = new Array;
    	OLines = new Array;
    	PLines = new Array;
    	QLines = new Array;
    	ULines = new Array;
    	diepass = "";
    	restartpass = "";
    	YLines = new Array;
    	ZLines = new Array;
    	/* End of global variables */
    	var fname="";
    	if (config_filename && config_filename.length) {
    		if(config_filename.indexOf('/')>=0 || config_filename.indexOf('\\')>=0)
    			fname=config_filename;
    		else
    			fname=system.ctrl_dir + config_filename;
    	} else {
    		fname=system.ctrl_dir + "ircd." + system.local_host_name + ".conf";
    		if(!file_exists(fname))
    			fname=system.ctrl_dir + "ircd." + system.host_name + ".conf";
    		if(!file_exists(fname))
    			fname=system.ctrl_dir + "ircd.conf";
    	}
    	log(LOG_INFO,"Reading Config: " + fname);
    	if (fname.substr(fname.length-3,3) == "ini")
    		read_ini_config(fname);
    	else
    		read_conf_config(fname);
    }
    
    function read_ini_config(fname) {
    	var conf = new File(fname);
    	if (conf.open("r")) {
    		/* Global Variables */
    	}
    	conf.close();
    }
    
    function read_conf_config(fname) {
    	var conf = new File(fname);
    	if (conf.open("r")) {
    		while (!conf.eof) {
    			var conf_line = conf.readln();
    			if ((conf_line != null) && conf_line.match("[:]")) {
    				var arg = conf_line.split(":");
    				for(argument in arg) {
    					arg[argument]=arg[argument].replace(
    						/SYSTEM_HOST_NAME/g,system.host_name);
    					arg[argument]=arg[argument].replace(
    						/SYSTEM_NAME/g,system.name);
    					arg[argument]=arg[argument].replace(
    						/SYSTEM_QWKID/g,system.qwk_id.toLowerCase());
    					arg[argument]=arg[argument].replace(
    						/VERSION_NOTICE/g,system.version_notice);
    				}
    				switch (conf_line[0].toUpperCase()) {
    					case "A":
    						if (!arg[3])
    							break;
    						Admin1 = arg[1];
    						Admin2 = arg[2];
    						Admin3 = arg[3];
    						break;
    					case "C":
    						if (!arg[5])
    							break;
    						CLines.push(new CLine(arg[1],arg[2],arg[3],arg[4],
    							parseInt(arg[5]) ));
    						break;
    					case "H":
    						if (!arg[3])
    							break;
    						HLines.push(new HLine(arg[1],arg[3]));
    						break;
    					case "I":
    						if (!arg[5])
    							break;
    						ILines.push(new ILine(arg[1],arg[2],arg[3],arg[4],
    							arg[5]));
    						break;
    					case "K":
    						if (!arg[2])
    							break;
    						var kline_mask = create_ban_mask(arg[1],true);
    						if (!kline_mask) {
    							log(LOG_WARNING,"!WARNING Invalid K:Line ("
    								+ arg[1] + ")");
    							break;
    						}
    						KLines.push(new KLine(kline_mask,arg[2],"K"));
    						break;
    					case "M":
    						if (!arg[3])
    							break;
    						servername = arg[1];
    						serverdesc = arg[3];
    						mline_port = parseInt(arg[4]);
    						break;
    					case "N":
    						if (!arg[5])
    							break;
    						NLines.push(new NLine(arg[1],arg[2],arg[3],
    							parse_nline_flags(arg[4]),arg[5]) );
    						break;
    					case "O":
    						if (!arg[5])
    							break;
    						OLines.push(new OLine(arg[1],arg[2],arg[3],
    							parse_oline_flags(arg[4]),parseInt(arg[5]) ));
    						break;
    					case "P":
    						PLines.push(parseInt(arg[4]));
    						break;
    					case "Q":
    						if (!arg[3])
    							break;
    						QLines.push(new QLine(arg[3],arg[2]));
    						break;
    					case "U":
    						if (!arg[1])
    							break;
    						ULines.push(arg[1]);
    						break;
    					case "X":
    						diepass = arg[1];
    						restartpass = arg[2];
    						break;
    					case "Y":
    						if (!arg[5])
    							break;
    						YLines[parseInt(arg[1])] = new YLine(parseInt(arg[2]),
    							parseInt(arg[3]),parseInt(arg[4]),parseInt(arg[5]));
    						break;
    					case "Z":
    						if (!arg[2])
    							break;
    						ZLines.push(new ZLine(arg[1],arg[2]));
    						break;
    					case "#":
    					case ";":
    					default:
    						break;
    				}
    			}
    		}
    		conf.close();
    	} else {
    		log ("WARNING! No config file found or unable to open."
    			+ " Proceeding with defaults.");
    	}
    	scan_for_klined_clients();
    	YLines[0] = new YLine(120,600,1,5050000); // default irc class
    }
    
    function create_new_socket(port) {
    	log(LOG_DEBUG,"Creating new socket object on port " + port);
    	var newsock = new Socket();
    	if(!newsock.bind(port,server.interface_ip_address)) {
    		log(LOG_ERR,"!Error " + newsock.error + " binding socket to TCP port "
    			+ port);
    		return 0;
    	}
    	log(format("%04u ",newsock.descriptor)
    		+ "IRC server socket bound to TCP port " + port);
    	if(!newsock.listen(5 /* backlog */)) {
    		log(LOG_ERR,"!Error " + newsock.error
    			+ " setting up socket for listening");
    		return 0;
    	}
    	return newsock;
    }
    
    function check_qwk_passwd(qwkid,password) {
    	if (!password || !qwkid)
    		return 0;
    	qwkid = qwkid.toUpperCase();
    	var usernum = system.matchuser(qwkid);
    	var bbsuser = new User(usernum);
    	if ((password.toUpperCase() ==
    	     bbsuser.security.password.toUpperCase()) &&
    	    (bbsuser.security.restrictions&UFLAG_Q) )
    		return 1;
    	return 0;
    }
    function IRCClient_netsplit(ns_reason) {
    	if (!ns_reason)
    		ns_reason = "net.split.net.split net.split.net.split";
    	for (sqclient in Users) {
    		if (Users[sqclient] &&
    		    (Users[sqclient].servername == this.nick)
    		   )
    			Users[sqclient].quit(ns_reason,true,true);
    	}
    	for (sqserver in Servers) {
    		if (Servers[sqserver] &&
    		    (Servers[sqserver].linkparent == this.nick)
    		   )
    			Servers[sqserver].quit(ns_reason,true,true);
    	}
    }
    
    function IRCClient_RMChan(rmchan_obj) {
    	if (!rmchan_obj)
    		return 0;
    	if (rmchan_obj.users[this.id])
    		delete rmchan_obj.users[this.id];
    	if (this.channels[rmchan_obj.nam.toUpperCase()])
    		delete this.channels[rmchan_obj.nam.toUpperCase()];
    	delete rmchan_obj.modelist[CHANMODE_OP][this.id];
    	delete rmchan_obj.modelist[CHANMODE_VOICE][this.id];
    	if (!true_array_len(rmchan_obj.users))
    		delete Channels[rmchan_obj.nam.toUpperCase()];
    }
    
    //////////////////// Output Helper Functions ////////////////////
    function rawout(str) {
    	var sendconn;
    	var str_end;
    	var str_beg;
    
    	if (debug)
    		log(format("[RAW->%s]: %s",this.nick,str));
    
    	if (this.local) {
    		sendconn = this;
    	} else if (!this.local) {
    		if ((str[0] == ":") && str[0].match(["!"])) {
    			str_end = str.slice(str.indexOf(" ")+1);
    			str_beg = str.slice(0,str.indexOf("!"));
    			str = str_beg + " " + str_end;
    		}
    		sendconn = Servers[this.parent.toLowerCase()];
    	} else {
    		log(LOG_ERR,"!ERROR: No connection to send to?");
    		return 0;
    	}
    
    	sendconn.sendq.add(str);
    }
    
    function originatorout(str,origin) {
    	var send_data;
    	var sendconn;
    
    	if (debug)
    		log(format("[%s->%s]: %s",origin.nick,this.nick,str));
    
    	sendconn = this;
    	if(this.local && !this.server) {
    		if (origin.server)
    			send_data = ":" + origin.nick + " " + str;
    		else
    			send_data = ":" + origin.nuh + " " + str;
    	} else if (this.server) {
    		send_data = ":" + origin.nick + " " + str;
    	} else if (!this.local) {
    		sendconn = Servers[this.parent.toLowerCase()];
    		send_data = ":" + origin.nick + " " + str;
    	} else {
    		log(LOG_ERR,"!ERROR: No connection to send to?");
    		return 0;
    	}
    
    	sendconn.sendq.add(send_data);
    }
    
    function ircout(str) {
    	var send_data;
    	var sendconn;
    
    	if (debug)
    		log(format("[%s->%s]: %s",servername,this.nick,str));
    
    	if(this.local) {
    		sendconn = this;
    	} else if (this.parent) {
    		sendconn = Servers[this.parent.toLowerCase()];
    	} else {
    		log(LOG_ERR,"!ERROR: No socket to send to?");
    		return 0;
    	}
    
    	send_data = ":" + servername + " " + str;
    	sendconn.sendq.add(send_data);
    }
    
    function Queue_Recv(sock) {
    	var pos;
    	var cmd;
    	var str;
    
    	str = sock.recv(65536,0);
    	if (str !== null && str.length > 0) {
    		this._recv_bytes += str;
    		while ((pos = this._recv_bytes.search('\n')) != -1) {
    			cmd = this._recv_bytes.substr(0, pos);
    			this._recv_bytes = this._recv_bytes.substr(pos+1);
    			if (cmd[cmd.length-1] == '\r')
    				cmd = cmd.substr(0, cmd.length - 1);
    			this.add(cmd);
    		}
    	}
    }
    
    function Queue_Send(sock) {
    	var sent;
    	var oldnb;
    
    	if (this.queue.length) {
    		this._send_bytes += this.queue.join('\r\n')+'\r\n';
    		this.queue = [];
    	}
    	if (this._send_bytes.length) {
    		if (new_socket_send === undefined || new_socket_send === false) {
    			oldnb = sock.nonblocking;
    			sock.nonblocking = false;
    		}
    		sent = sock.send(this._send_bytes);
    		if (new_socket_send === undefined) {
    			if (sent === true)
    				new_socket_send = false;
    			else if (sent === false)
    				new_socket_send = false;
    			else {
    				new_socket_send = true;
    				sock.nonblocking = oldnb;
    			}
    		}
    		if (new_socket_send === false) {
    			sock.nonblocking = oldnb;
    			this._send_bytes = '';
    		}
    		if (new_socket_send === true) {
    			if (sent > 0)
    				this._send_bytes = this._send_bytes.substr(sent);
    		}
    	}
    }
    
    function Queue_Prepend(str) {
    	this.queue.unshift(str);
    }
    
    function Queue_Add(str) {
    	this.queue.push(str);
    }
    
    function Queue_Del() {
    	return this.queue.shift();
    }
    
    function IRCClient_server_notice(str) {
    	this.ircout("NOTICE " + this.nick + " :" + str);
    }
    
    function IRCClient_numeric(num, str) {
    	this.ircout(num + " " + this.nick + " " + str);
    }
    
    //////////////////// Numeric Functions ////////////////////
    function IRCClient_numeric200(dest,next) {
    	this.numeric(200, "Link " + VERSION + " " + dest + " " + next);
    }
    
    function IRCClient_numeric201(ircclass,server) {
    	this.numeric(201, "Try. " + ircclass + " " + server);
    }
    
    function IRCClient_numeric202(ircclass,server) {
    	this.numeric(202, "H.S. " + ircclass + " " + server);
    }
    
    function IRCClient_numeric203(ircclass,ip) {
    	this.numeric(203, "???? " + ircclass + " [" + ip + "]");
    }
    
    function IRCClient_numeric204(nick) {
    	this.numeric(204, "Oper " + nick.ircclass + " " + nick.nick);
    }
    
    function IRCClient_numeric205(nick) {
    	this.numeric(205, "User " + nick.ircclass + " " + nick.nick);
    }
    
    function IRCClient_numeric206(ircclass,sint,cint,server) {
    	this.numeric(206, "Serv " + ircclass + " " + sint + "S " + cint
    		+ "C *!*@" + server);
    }
    
    function IRCClient_numeric208(type,clientname) {
    	this.numeric(208, type + " 0 " + clientname);
    }
    
    function IRCClient_numeric261(file) {
    	this.numeric(261, "File " + file + " " + debug);
    }
    
    function IRCClient_numeric321() {
    	this.numeric("321", "Channel :Users  Name");
    }
    
    function IRCClient_numeric322(chan,show_modes) {
    	var channel_name;
    	var disp_topic = "";
    	var is_onchan = this.channels[chan.nam.toUpperCase()];
    
    	if (show_modes) {
    		var chanm = chan.chanmode()
    		disp_topic += "[" + chanm.slice(0,chanm.length-1) + "]"
    	}
    
    	if ((chan.mode&CHANMODE_PRIVATE) && !(this.mode&USERMODE_OPER) &&
    	    !is_onchan ) {
    		channel_name = "*";
    	} else {
    		channel_name = chan.nam;
    		if (disp_topic)
    			disp_topic += " ";
    		disp_topic += chan.topic;
    	}
    	if (!(chan.mode&CHANMODE_SECRET) || (this.mode&USERMODE_OPER) ||
    	    is_onchan )
    		this.numeric(322, channel_name + " " + true_array_len(chan.users)
    			+ " :" + disp_topic);
    }
    
    function IRCClient_numeric331(chan) {
    	this.numeric(331, chan.nam + " :No topic is set.");
    }
    
    function IRCClient_numeric332(chan) {
    	this.numeric(332, chan.nam + " :" + chan.topic);
    }
    
    function IRCClient_numeric333(chan) {
    	this.numeric(333, chan.nam + " " + chan.topicchangedby + " "
    		+ chan.topictime);
    }
    
    function IRCClient_numeric351() {
    	this.numeric(351, VERSION + " " + servername + " :" + VERSION_STR);
    }
    
    function IRCClient_numeric352(user,show_ips_only,chan) {
    	var who_mode="";
    	var disp;
    	var disphost;
    
    	if (!user)
    		return 0;
    
    	if (!chan)
    		disp = "*";
    	else
    		disp = chan.nam;
    
    	if (user.away)
    		who_mode += "G";
    	else
    		who_mode += "H";
    	if (chan) {
    		if (chan.modelist[CHANMODE_OP][user.id])
    			who_mode += "@";
    		else if (chan.modelist[CHANMODE_VOICE][user.id])
    			who_mode += "+";
    	}
    	if (user.mode&USERMODE_OPER)
    		who_mode += "*";
    
    	if (show_ips_only)
    		disphost = user.ip;
    	else
    		disphost = user.hostname;
    
    	this.numeric(352, disp + " " + user.uprefix + " " + disphost + " "
    		+ user.servername + " " + user.nick + " " + who_mode
    		+ " :" + user.hops + " " + user.realname);
    	return 1;
    }
    
    function IRCClient_numeric353(chan, str) {
    	// = public @ secret * everything else
    	if (chan.mode&CHANMODE_SECRET)
    		var ctype_str = "@";
    	else
    		var ctype_str = "=";
    	this.numeric("353", ctype_str + " " + chan.nam + " :" + str);
    }
    
    function IRCClient_numeric382(str) {
    	this.numeric(382, "ircd.conf :" + str);
    }
    
    function IRCClient_numeric391() {
    	this.numeric(391, servername + " :"
    		+ strftime("%A %B %d %Y -- %H:%M %z",time()));
    }
    
    function IRCClient_numeric401(str) {
    	this.numeric("401", str + " :No such nick/channel.");
    }
    
    function IRCClient_numeric402(str) {
    	this.numeric(402, str + " :No such server.");
    }
    
    function IRCClient_numeric403(str) {
    	this.numeric("403", str
    		+ " :No such channel or invalid channel designation.");
    }
    
    function IRCClient_numeric411(str) {
    	this.numeric("411", ":No recipient given. (" + str + ")");
    }
    
    function IRCClient_numeric412() {
    	this.numeric(412, " :No text to send.");
    }
    
    function IRCClient_numeric440(str) {
    	this.numeric(440, str + " :Services is currently down, sorry.");
    }
    
    function IRCClient_numeric441(str) {
    	this.numeric("441", str + " :They aren't on that channel.");
    }
    
    function IRCClient_numeric442(str) {
    	this.numeric("442", str + " :You're not on that channel.");
    }
    
    function IRCClient_numeric445() {
    	this.numeric(445, ":SUMMON has been disabled.");
    }
    
    function IRCClient_numeric446() {
    	this.numeric(446, ":USERS has been disabled.");
    }
    
    function IRCClient_numeric451() {
    	this.numeric("451", ":You have not registered.");
    }
    
    function IRCClient_numeric461(cmd) {
    	this.numeric("461", cmd + " :Not enough parameters.");
    }
    
    function IRCClient_numeric462() {
    	this.numeric("462", ":You may not re-register.");
    }
    
    function IRCClient_numeric481() {
    	this.numeric("481", ":Permission Denied.  "
    		+ "You do not have the correct IRC operator privileges.");
    }
    
    function IRCClient_numeric482(tmp_chan_nam) {
    	this.numeric("482", tmp_chan_nam + " :You're not a channel operator.");
    }
    
    //////////////////// Multi-numeric Display Functions ////////////////////
    
    function IRCClient_lusers() {
    	this.numeric(251, ":There are " + num_noninvis_users() + " users and "
    		+ num_invis_users() + " invisible on " + true_array_len(Servers)
    		+ " servers.");
    	this.numeric(252, num_opers() + " :IRC operators online.");
    	var unknown_connections = true_array_len(Unregistered);
    	if (unknown_connections)
    		this.numeric(253, unknown_connections + " :unknown connection(s).");
    	this.numeric(254, true_array_len(Channels) + " :channels formed.");
    	this.numeric(255, ":I have " + true_array_len(Local_Users)
    		+ " clients and " + true_array_len(Local_Servers) + " servers.");
    	this.numeric(250, ":Highest connection count: " + hcc_total + " ("
    		+ hcc_users + " clients.)");
    	this.server_notice(hcc_counter + " clients have connected since "
    		+ strftime("%a %b %d %H:%M:%S %Y %Z",server_uptime));
    }
    
    function num_noninvis_users() {
    	var counter = 0;
    	for(myuser in Users) {
    		if (!(Users[myuser].mode&USERMODE_INVISIBLE))
    			counter++;
    	}
    	return counter;
    }
    
    function num_invis_users() {
    	var counter = 0;
    	for(myuser in Users) {
    		if (Users[myuser].mode&USERMODE_INVISIBLE)
    			counter++;
    	}
    	return counter;
    }
    
    function num_opers() {
    	var counter = 0;
    	for(myuser in Users) {
    		if (Users[myuser].mode&USERMODE_OPER)
    			counter++;
    	}
    	return counter;
    }
    
    function IRCClient_motd() {
    	var motd_file = new File(system.text_dir + "ircmotd.txt");
    	if (motd_file.exists==false) {
    		this.numeric(422, ":MOTD file missing: " + motd_file.name);
    	} else if (motd_file.open("r")==false) {
    		this.numeric(424, ":MOTD error " + errno + " opening: "
    			+ motd_file.name);
    	} else {
    		this.numeric(375, ":- " + servername + " Message of the Day -");
    		this.numeric(372, ":- " + strftime("%m/%d/%Y %H:%M",motd_file.date));
    		while (!motd_file.eof) {
    			motd_line = motd_file.readln();
    			if (motd_line != null)
    				this.numeric(372, ":- " + motd_line);
    		}
    		motd_file.close();
    	}
    	this.numeric(376, ":End of /MOTD command.");
    }
    
    function IRCClient_names(chan) {
    	var Channel_user;
    	var numnicks=0;
    	var tmp="";
    	for(thisChannel_user in chan.users) {
    		Channel_user=chan.users[thisChannel_user];
    		if (!(Channel_user.mode&USERMODE_INVISIBLE) ||
    		     (this.channels[chan.nam.toUpperCase()]) ) {
    			if (numnicks)
    				tmp += " ";
    			if (chan.modelist[CHANMODE_OP][Channel_user.id])
    				tmp += "@";
    			else if (chan.modelist[CHANMODE_VOICE][Channel_user.id])
    				tmp += "+";
    			tmp += Channel_user.nick;
    			numnicks++;
    			if (numnicks >= 59) {
    				// dump what we've got, it's too much.
    				this.numeric353(chan, tmp);
    				numnicks=0;
    				tmp="";
    			}
    		}
    	}
    	if(numnicks)
    		this.numeric353(chan, tmp);
    }
    
    // Traverse each channel the user is on and see if target is on any of the
    // same channels.
    function IRCClient_onchanwith(target) {
    	for (c in this.channels) {
    		for (i in target.channels) {
    			if (c == i)
    				return 1; // success
    		}
    	}
    	return 0; // failure.
    }
    
    //////////////////// Auxillary Functions ////////////////////
    
    function IRCClient_bcast_to_uchans_unique(str) {
    	var already_bcast = new Object;
    	for(thisChannel in this.channels) {
    		var userchannel=this.channels[thisChannel];
    		for (j in userchannel.users) {
    			var usr = userchannel.users[j];
    			if (!already_bcast[usr.nick] && (usr.id != this.id) && usr.local) {
    				usr.originatorout(str,this);
    				already_bcast[usr.nick] = true;
    			}
    		}
    	}
    }
    
    function IRCClient_bcast_to_list(chan, str, bounce, list_bit) {
    	for (thisUser in chan.users) {
    		var aUser = chan.users[thisUser];
    		if (aUser && ( aUser.id != this.id || (bounce) ) &&
    		    chan.modelist[list_bit][aUser.id])
    			aUser.originatorout(str,this);
    	}
    }
    
    function IRCClient_bcast_to_channel(chan, str, bounce) {
    	for(thisUser in chan.users) {
    		var aUser=chan.users[thisUser];
    		if ( ( aUser.id != this.id || (bounce) ) &&
    		     aUser.local )
    			aUser.originatorout(str,this);
    	}
    }
    
    function IRCClient_bcast_to_channel_servers(chan, str) {
    	var sent_to_servers = new Object;
    	for(thisUser in chan.users) {
    		var aUser=chan.users[thisUser];
    		if (!aUser.local && (this.parent != aUser.parent) &&
    		    !sent_to_servers[aUser.parent.toLowerCase()]) {
    			aUser.originatorout(str,this);
    			sent_to_servers[aUser.parent.toLowerCase()] = true;
    		}
    	}
    }
    
    function IRCClient_check_nickname(newnick,squelch) {
    	var qline_nick;
    	var checknick;
    	var regexp;
    
    	newnick = newnick.slice(0,max_nicklen);
    	// If you're trying to NICK to yourself, drop silently.
    	if(newnick == this.nick)
    		return -1;
    	// First, check for valid nick against irclib.
    	if(IRC_check_nick(newnick)) {
    		if (!squelch)
    			this.numeric("432", newnick + " :Foobar'd Nickname.");
    		return 0;
    	}
    	// Second, check for existing nickname.
    	checknick = Users[newnick.toUpperCase()];
    	if(checknick && (checknick.nick != this.nick) ) {
    		if (!squelch)
    			this.numeric("433", newnick + " :Nickname is already in use.");
    		return 0;
    	}
    	// Third, match against Q:Lines
    	for (ql in QLines) {
    		if(wildmatch(newnick, QLines[ql].nick)) {
    			if (!squelch)
    				this.numeric(432, newnick + " :" + QLines[ql].reason);
    			return 0;
    		}
    	}
    	return 1; // if we made it this far, we're good!
    }
    
    function IRCClient_do_whois(wi) {
    	if (!wi)
    		return 0;
    	this.numeric(311, wi.nick + " " + wi.uprefix + " " + wi.hostname + " * :"
    		+ wi.realname);
    	var userchans="";
    	for (i in wi.channels) {
    		var chan = wi.channels[i];
    		if (!(chan.mode&CHANMODE_SECRET||chan.mode&CHANMODE_PRIVATE) ||
    		     this.channels[chan.nam.toUpperCase()] || this.mode&USERMODE_OPER) {
    			if (userchans)
    				userchans += " ";
    			if (chan.modelist[CHANMODE_OP][wi.id])
    				userchans += "@";
    			else if (chan.modelist[CHANMODE_VOICE][wi.id])
    				userchans += "+";
    			userchans += chan.nam;
    		}
    	}
    	if (userchans)
    		this.numeric(319, wi.nick + " :" + userchans);
    	if (wi.local)
    		this.numeric(312, wi.nick + " " + servername + " :" + serverdesc);
    	else {
    		wi_server = searchbyserver(wi.servername);
    		this.numeric(312, wi.nick + " " + wi_server.nick
    		+ " :" + wi_server.info);
    	}
    	if (wi.mode&USERMODE_OPER)
    		this.numeric(313, wi.nick + " :is an IRC operator.");
    	if (wi.away)
    		this.numeric(301, wi.nick + " :" + wi.away);
    	if (wi.local)
    		this.numeric(317, wi.nick + " " + (time() - wi.talkidle) + " "
    		+ wi.connecttime + " :seconds idle, signon time");
    }
    
    function IRCClient_services_msg(svcnick,send_str) {
    	var service_server;
    
    	if (!send_str) {
    		this.numeric412();
    		return 0;
    	}
    	// First, make sure the nick exists.
    	var usr = Users[svcnick.toUpperCase()];
    	if (!usr) {
    		this.numeric440(svcnick);
    		return 0;
    	}
    	service_server = searchbyserver(usr.servername);
    	if (!service_server || !service_server.uline) {
    		this.numeric440(svcnick);
    		return 0;
    	}
    	this.do_msg(svcnick,"PRIVMSG",send_str);
    }
    
    function IRCClient_global(target,type_str,send_str) {
    	if (!target.match("[.]")) {
    		this.numeric(413,target + " :No top-level domain specified.");
    		return 0;
    	}
    	var domain_part = target.split('.');
    	if (domain_part[domain_part.length - 1].match("[?*]")) {
    		this.numeric(414,target + " :Wildcard found in top-level domain.");
    		return 0;
    	}
    	var global_mask = target.slice(1);
    	var global_str = type_str + " " + target + " :" + send_str;
    	for(globClient in Local_Users) {
    		var Client = Local_Users[globClient];
    		if (target[0] == "#")
    			var global_match = Client.hostname;
    		else // assume $
    			var global_match = Client.servername;
    		if (wildmatch(global_match,global_mask))
    			Client.originatorout(global_str,this);
    	}
    	global_str = ":" + this.nick + " " + global_str;
    	if(this.local && this.parent) /* Incoming from a local server */
    		Servers[this.parent.toLowerCase()].bcast_to_servers_raw(global_str);
    	else if (this.flags&OLINE_CAN_GGNOTICE) /* From a local oper */
    		server_bcast_to_servers(global_str);
    	return 1;
    }
    
    function IRCClient_globops(str) {
    	var globops_bits = 0;
    	globops_bits |= USERMODE_OPER;
    	globops_bits |= USERMODE_GLOBOPS;
    	umode_notice(globops_bits,"Global","from " + this.nick +": " + str);
    	if (this.parent)
    		Servers[this.parent.toLowerCase()].bcast_to_servers_raw(":" + this.nick
    			+ " GLOBOPS :" + str);
    	else
    		server_bcast_to_servers(":" + this.nick + " GLOBOPS :" + str);
    }
    
    function IRCClient_do_msg(target,type_str,send_str) {
    	if ( (target[0] == "$") && (this.mode&USERMODE_OPER) &&
    	     ( (this.flags&OLINE_CAN_LGNOTICE) || !this.local)
    	   )
    		return this.global(target,type_str,send_str);
    
    	var send_to_list = -1;
    	if (target[0] == "@" && ( (target[1] == "#") || target[1] == "&") ) {
    		send_to_list = CHANMODE_OP;
    		target = target.slice(1);
    	} else if (target[0]=="+" && ((target[1] == "#")|| target[1] == "&")) {
    		send_to_list = CHANMODE_VOICE;
    		target = target.slice(1);
    	}
    		
    	if ((target[0] == "#") || (target[0] == "&")) {
    		var chan = Channels[target.toUpperCase()];
    		if (!chan) {
    			// check to see if it's a #*hostmask* oper message
    			if ( (target[0] == "#") && (this.mode&USERMODE_OPER) &&
    			     ( (this.flags&OLINE_CAN_LGNOTICE) || !this.local )
    			   ) {
    				return this.global(target,type_str,send_str);
    			} else {
    				this.numeric401(target);
    				return 0;
    			}
    		}
    		if ((chan.mode&CHANMODE_NOOUTSIDE)
    			&& !this.channels[chan.nam.toUpperCase()]) {
    			this.numeric(404, chan.nam + " :Cannot send to channel "
    				+ "(+n: no outside messages)");
    			return 0;
    		}
    		if ( (chan.mode&CHANMODE_MODERATED) &&
    		     !chan.modelist[CHANMODE_VOICE][this.id] &&
    		     !chan.modelist[CHANMODE_OP][this.id] ) {
    			this.numeric(404, chan.nam + " :Cannot send to channel "
    				+ "(+m: moderated)");
    			return 0;
    		}
    		if (chan.isbanned(this.nuh) &&
    		   !chan.modelist[CHANMODE_VOICE][this.id] &&
    		   !chan.modelist[CHANMODE_OP][this.id] ) {
    			this.numeric(404, chan.nam + " :Cannot send to channel "
    				+ "(+b: you're banned!)");
    			return 0;
    		}
    		if(send_to_list == -1) {
    			var str = type_str +" "+ chan.nam +" :"+ send_str;
    			this.bcast_to_channel(chan, str, false);
    			this.bcast_to_channel_servers(chan, str);
    		} else {
    			var prefix_chr;
    			if (send_to_list == CHANMODE_OP)
    				prefix_chr="@";
    			else if (send_to_list == CHANMODE_VOICE)
    				prefix_chr="+";
    			var str = type_str +" " + prefix_chr + chan.nam + " :"+ send_str;
    			this.bcast_to_list(chan, str, false, send_to_list);
    			this.bcast_to_channel_servers(chan, str);
    		}
    	} else {
    		if (target.match("[@]")) {
    			var msg_arg = target.split('@');
    			var real_target = msg_arg[0];
    			var target_server = searchbyserver(msg_arg[1]);
    			if (!target_server) {
    				this.numeric401(target);
    				return 0;
    			}
    			if (target_server == -1) {
    				this.numeric(407, target
    					+ " :Duplicate recipients, no message delivered.");
    				return 0;
    			}
    			target = msg_arg[0] + "@" + msg_arg[1];
    		} else {
    			var real_target = target;
    		}
    		var target_socket = Users[real_target.toUpperCase()];
    		if (target_socket) {
    			if (target_server &&
    			    (target_server.parent != target_socket.parent)) {
    				this.numeric401(target);
    				return 0;
    			}
    			if (target_server && 
    			    (target_server.id == target_socket.parent) )
    				target = real_target;
    			if (target_socket.issilenced(this.nuh))
    				return 0;	/* On SILENCE list.  Silently ignore. */
    			var str = type_str + " " + target + " :" + send_str;
    			target_socket.originatorout(str,this);
    			if (target_socket.away && (type_str == "PRIVMSG") &&
    			    !this.server && target_socket.local)
    				this.numeric(301, target_socket.nick + " :"
    					+ target_socket.away);
    		} else {
    			this.numeric401(target);
    			return 0;
    		}
    	}
    	return 1;
    }
    
    function IRCClient_do_admin() {
    	umode_notice(USERMODE_SPY,"ADMIN requested by " + this.nick + " (" +
    		this.uprefix + "@" + this.hostname + ") [" + this.servername +
    		"]");
    	if (Admin1 && Admin2 && Admin3) {
    		this.numeric(256, ":Administrative info about " + servername);
    		this.numeric(257, ":" + Admin1);
    		this.numeric(258, ":" + Admin2);
    		this.numeric(259, ":" + Admin3);
    	} else {
    		this.numeric(423, servername
    			+ " :No administrative information available.");
    	}
    }
    
    function IRCClient_do_info() {
    	umode_notice(USERMODE_SPY,"Spy","INFO requested by " + this.nick +
    		" (" + this.uprefix + "@" + this.hostname + ") [" +
    		this.servername + "]");
    	this.numeric(371, ":--=-=-=-=-=-=-=-=-=*[ The Synchronet IRCd v1.3a ]*=-=-=-=-=-=-=-=-=--");
    	this.numeric(371, ":  IRCd Copyright 2003-2009 by Randolph E. Sommerfeld <cyan@rrx.ca>");
    	this.numeric(371, ":" + system.version_notice + " " + system.copyright + ".");
    	this.numeric(371, ":--=-=-=-=-=-=-=-=-( A big thanks to the following )-=-=-=-=-=-=-=-=--");
    	this.numeric(371, ":DigitalMan (Rob Swindell): Resident coder god, various hacking all");
    	this.numeric(371, ":   around the IRCd, countless helpful suggestions and tips, and");
    	this.numeric(371, ":   tons of additions to the Synchronet JS API that made this possible.");
    	this.numeric(371, ":Deuce (Stephen Hurd): Resident Perl guru and ex-Saskatchewan zealot.");
    	this.numeric(371, ":   Originally converted the IRCd to be object-oriented, various small");
    	this.numeric(371, ":   hacks, and lots of guidance.");
    	this.numeric(371, ":Thanks to the DALnet Bahamut team for their help from time to time.");
    	this.numeric(371, ":Greets to: Arrak, ElvishMerchant, Foobar, Grimp, Kufat,");
    	this.numeric(371, ":   Psyko, Samael, Shaun, Torke, and all the #square oldbies.");
    	this.numeric(371, ":--=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=--");
    	this.numeric(371, ":Synchronet " + system.full_version);
    	this.numeric(371, ":Compiled with " + system.compiled_with + " at " + system.compiled_when);
    	this.numeric(371, ":Running on " + system.os_version);
    	this.numeric(371, ":Utilizing socket library: " + system.socket_lib);
    	this.numeric(371, ":Javascript library: " + system.js_version);
    	this.numeric(371, ":This BBS has been up since " + system.timestr(system.uptime));
    	this.numeric(371, ": -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -");
    	if (server.version_detail!=undefined) {
    		this.numeric(371, ":This IRCd was executed via:");
    		this.numeric(371, ":" + server.version_detail);
    	}
    	this.numeric(371, ":IRCd CVS revisions:")
    	this.numeric(371, ":Main(" + MAIN_REVISION + ") User(" + USER_REVISION + ") Channel(" + CHANNEL_REVISION + ") Server(" + SERVER_REVISION + ") Unreg(" + UNREG_REVISION + ")");
    	this.numeric(371, ":IRClib Version: " + IRCLIB_VERSION);
    	this.numeric(371, ":--=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=--");
    	this.numeric(371, ":This program is distributed under the terms of the GNU General Public");
    	this.numeric(371, ":License, version 2.  http://www.gnu.org/licenses/gpl.txt");
    	this.numeric(371, ":--=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=--");
    	this.numeric(374, ":End of /INFO list.");
    }
    
    function IRCClient_do_stats(statschar) {
    	switch(statschar[0]) {
    		case "C":
    		case "c":
    			var cline_port;
    			for (cline in CLines) {
    				if(CLines[cline].port)
    					cline_port = CLines[cline].port;
    				else
    					cline_port = "*";
    				this.numeric(213,"C " + CLines[cline].host + " * "
    					+ CLines[cline].servername + " " + cline_port + " "
    					+ CLines[cline].ircclass);
    				if (NLines[cline])
    					this.numeric(214,"N " + NLines[cline].host + " * "
    						+ NLines[cline].servername + " " + NLines[cline].flags
    						+ " " + NLines[cline].ircclass);
    			}
    			break;  
    		case "H":
    		case "h":
    			for (hl in HLines) {
    				this.numeric(244,"H " + HLines[hl].allowedmask + " * "
    					+ HLines[hl].servername);
    			}
    			break;
    		case "I":
    		case "i":
    			var my_port;
    			for (iline in ILines) {
    				if (!ILines[iline].port)
    					my_port = "*";
    				else
    					my_port = ILines[iline].port;
    				this.numeric(215,"I " + ILines[iline].ipmask + " * "
    					+ ILines[iline].hostmask + " " + my_port + " "
    					+ ILines[iline].ircclass);
    			}
    			break;
    		case "K":
    		case "k":
    			for (kline in KLines) {
    				if (KLines[kline].hostmask) {
    					this.numeric(216, KLines[kline].type + " "
    						+ KLines[kline].hostmask + " * * :"
    						+ KLines[kline].reason);
    				}
    			}
    			break;
    		case "L": /* FIXME */
    			this.numeric(241,"L <hostmask> * <servername> <maxdepth>");
    			break;
    		case "l": /* FIXME */
    			this.numeric(211,"<linkname> <sendq> <sentmessages> <sentbytes> <receivedmessages> <receivedbytes> <timeopen>");
    			break;
    		case "M":
    		case "m":
    			for (c in Profile) {
    				var sm = Profile[c];
    				this.numeric(212, c + " " + sm.ticks + " " + sm.executions);
    			}
    			break;          
    		case "O":       
    		case "o":
    			for (oline in OLines) {
    				this.numeric(243, "O " + OLines[oline].hostmask + " * "
    					+ OLines[oline].nick);
    			}
    			break;
    		case "U":
    			for (uline in ULines) {
    				this.numeric(246, "U " + ULines[uline] + " * * 0 -1");                  
    			}       
    			break;
    		case "u":
    			var this_uptime=time() - server_uptime;
    			var updays=Math.floor(this_uptime / 86400);
    			if (updays)
    				this_uptime %= 86400;
    			var uphours=Math.floor(this_uptime/(60*60));
    			var upmins=(Math.floor(this_uptime/60))%60;
    			var upsec=this_uptime%60;
    			var str = format("Server Up %u days, %u:%02u:%02u",
    				updays,uphours,upmins,upsec);
    			this.numeric(242,":" + str);
    			break;
    		case "Y":
    		case "y":
    			var yl;
    			for (thisYL in YLines) {
    				yl = YLines[thisYL];
    				this.numeric(218,"Y " + thisYL + " " + yl.pingfreq + " "
    					+ yl.connfreq + " " + yl.maxlinks + " " + yl.sendq);
    			}
    			break;
    		default:
    			break;
    	}
    	this.numeric(219, statschar[0] + " :End of /STATS Command.");
    }
    
    function IRCClient_do_users() {
    	var usersshown;
    	var u;
    
    	this.numeric(392,':UserID                    Terminal  Host');
    	usersshown=0;
    	for(node in system.node_list) {
    		if(system.node_list[node].status == NODE_INUSE) {
    			u=new User(system.node_list[node].useron);
    			this.numeric(393,format(':%-25s %-9s %-30s',u.alias,'Node'+node,
    				u.host_name));
    			usersshown++;
    		}
    	}
    	if(usersshown) {
    		this.numeric(394,':End of users');
    	} else {
    		this.numeric(395,':Nobody logged in');
    	}
    }
    
    function IRCClient_do_summon(summon_user) {
    	var usernum;
    	var isonline;
    
    	// Check if exists.
    	usernum = system.matchuser(summon_user);
    	if(!usernum)
    		this.numeric(444,":No such user.");
    	else {
    		// Check if logged in
    		isonline = 0;
    		for(node in system.node_list) {
    			if( (system.node_list[node].status == NODE_INUSE)
    				&& (system.node_list[node].useron == usernum) ) {
    				isonline = 1;
    				break;
    			}
    		}
    		if(!isonline) {
    			this.numeric(444,":User not logged in.");
    		} else {
    			system.put_telegram(usernum, "" + this.nick
    				+ " is summoning you to IRC chat.\r\n");
    			this.numeric(342, summon_user + " :Summoning user to IRC");
    		}
    	}
    }
    
    function IRCClient_do_links(mask) {
    	if (!mask)
    		mask = "*";
    	umode_notice(USERMODE_SPY,"Spy","LINKS " + mask + " requested by " +
    		this.nick + " (" + this.uprefix + "@" + this.hostname + ") [" +
    		this.servername + "]");
    	for(thisServer in Servers) {
    		var Server=Servers[thisServer];
    		if (wildmatch(Server.nick,mask)) {
    			this.numeric(364, Server.nick + " " + Server.linkparent + " :"
    				+ Server.hops + " " + Server.info);
    		}
    	}
    	if (wildmatch(servername,mask))
    		this.numeric(364, servername + " " + servername + " :0 " + serverdesc);
    	this.numeric(365, mask + " :End of /LINKS list.");
    }
    
    // Don't hunt for servers based on nicknames, as TRACE is more explicit.
    function IRCClient_do_trace(target) {
    	var server = searchbyserver(target);
    
    	if (server == -1) { // we hunted ourselves
    		// FIXME: What do these numbers mean? O_o?
    		this.numeric206("30","1","0",servername);
    		this.trace_all_opers();
    	} else if (server) {
    		server.rawout(":" + this.nick + " TRACE " + target);
    		this.numeric200(target,server.nick);
    		return 0;
    	} else {
    		// Okay, we've probably got a user.
    		var nick = Users[target.toUpperCase()];
    		if (!nick) {
    			this.numeric402(target);
    			return 0;
    		} else if (nick.local) {
    			if (nick.mode&USERMODE_OPER)
    				this.numeric204(nick);
    			else
    				this.numeric205(nick);
    		} else {
    			nick.rawout(":" + this.nick + " TRACE " + target);
    			this.numeric200(target,Servers[nick.parent.toLowerCase()].nick);
    		}
    	}
    	this.numeric(262, target + " :End of /TRACE.");
    }
    
    function IRCClient_trace_all_opers() {
    	for(thisoper in Local_Users) {
    		var oper=Local_Users[thisoper];
    		if (oper.mode&USERMODE_OPER)
    			this.numeric204(oper);
    	}
    }
    
    function IRCClient_do_connect(con_server,con_port) {
    	var con_cline = "";
    	for (ccl in CLines) {
    		if (wildmatch(CLines[ccl].servername,con_server) ||
    		    wildmatch(CLines[ccl].host,con_server) ) {
    			con_cline = CLines[ccl];
    			break;
    		}
    	}
    	if (!con_cline) {
    		this.numeric402(con_server);
    		return 0;
    	}
    	if (!con_port && con_cline.port)
    		con_port = con_cline.port;
    	if (!con_port && !con_cline.port)
    		con_port = String(default_port);
    	if (!con_port.match(/^[0-9]+$/)) {
    		this.server_notice("Invalid port: " + con_port);
    		return 0;
    	}
    	var msg = " CONNECT " + con_cline.servername + " " + con_port +
    		" from " + this.nick + "[" + this.uprefix + "@" +
    		this.hostname + "]";
    	var con_type = "Local";
    	if (this.parent) {
    		con_type = "Remote";
    		server_bcast_to_servers("GNOTICE :Remote" + msg);
    	}
    	umode_notice(USERMODE_ROUTING,"Routing","from "+servername+": " + 
    		con_type + msg);
    	connect_to_server(con_cline,con_port);
    	return 1;
    }
    
    function IRCClient_do_basic_who(whomask) {
    	var eow = "*";
    
    	var regexp = "^[0]{1,}$";
    	if (whomask.match(regexp))
    		whomask = "*";
    
    	if ((whomask[0] == "#") || (whomask[0] == "&")) {
    		var chan = Channels[whomask.toUpperCase()];
    		if (chan && ( ( !(chan.mode&CHANMODE_SECRET) &&
    		    !(chan.mode&CHANMODE_PRIVATE) ) ||
    		    this.channels[chan.nam.toUpperCase()] || (this.mode&USERMODE_OPER))
    		   ) {
    			for(i in chan.users) {
    				var usr = chan.users[i];
    				if (!(usr.mode&USERMODE_INVISIBLE) ||
    				    (this.mode&USERMODE_OPER) ||
    				    this.onchanwith(usr) ) {
    					var chkwho = this.numeric352(usr,false,chan);
    					if (!chkwho) {
    						umode_notice(USERMODE_OPER,"Notice",
    							"WHO returned 0 for user: " + usr.nick + " (A)");
    					}
    				}
    			}
    			eow = chan.nam;
    		}
    	} else {
    		for (i in Users) {
    			var usr = Users[i];
    			if (usr.match_who_mask(whomask) &&
    			    (!(usr.mode&USERMODE_INVISIBLE) ||
    			     (this.mode&USERMODE_OPER) ||
    			     this.onchanwith(usr) ) ) {
    				var chkwho = this.numeric352(usr);
    				if (!chkwho) {
    					umode_notice(USERMODE_OPER,"Notice",
    						"WHO returned 0 for user: " + usr.nick + " (B)");
    				}
    			}
    		}
    		eow = whomask;
    	}
    	this.numeric(315, eow + " :End of /WHO list. (Basic)");
    }
    
    function IRCClient_do_complex_who(cmd) {
    	var who = new Who();
    	var tmp;
    	var eow = "*";
    	var add = true;	// assume the user is doing + by default
    	var arg = 1;
    	var whomask = "";
    	var chan;
    
    	// RFC1459 Compatibility.  "WHO <mask> o"  Only do it if we find a
    	// wildcard, otherwise assume we're doing a complex WHO with 'modes'
    	if (cmd[2] && ( (cmd[1].match(/[*]/)) || cmd[1].match(/[?]/)) ||
    	    (cmd[1].match(/[0]/)) && !cmd[3] && (cmd[2].toLowerCase() == "o")) {
    		tmp = cmd[1];
    		cmd[1] = cmd[2];
    		cmd[2] = tmp;
    	}
    
    	for (myflag in cmd[1]) {
    		switch (cmd[1][myflag]) {
    			case "+":
    				if (!add)
    					add = true;
    				break;
    			case "-":
    				if (add)
    					add = false;
    				break;
    			case "a":
    				who.tweak_mode(WHO_AWAY,add);
    				break;
    			case "c":
    				arg++;
    				if (cmd[arg]) {
    					who.tweak_mode(WHO_CHANNEL,add);
    					who.Channel = cmd[arg];
    				}
    				break;
    			case "g":
    				arg++;
    				if (cmd[arg]) {
    					who.tweak_mode(WHO_REALNAME,add);
    					who.RealName = cmd[arg];
    				}
    				break;
    			case "h":
    				arg++;
    				if (cmd[arg]) {
    					who.tweak_mode(WHO_HOST,add);
    					who.Host = cmd[arg];
    				}
    				break;
    			case "i":
    				arg++;
    				if (cmd[arg]) {
    					who.tweak_mode(WHO_IP,add);
    					who.IP = cmd[arg];
    				}
    				break;
    			case "l":
    				arg++;
    				if (cmd[arg]) {
    					who.tweak_mode(WHO_CLASS,add);
    					who.Class = parseInt(cmd[arg]);
    				}
    			case "m": // we never set -m
    				arg++;
    				if (cmd[arg]) {
    					who.tweak_mode(WHO_UMODE,true);
    					if (!add) {
    						var tmp_umode = "";
    						if ((cmd[arg][0] != "+") ||
    						    (cmd[arg][0] != "-") )
    							tmp_umode += "+";
    						tmp_umode += cmd[arg].replace(/[-]/g," ");
    						tmp_umode = tmp_umode.replace(/[+]/g,"-");
    						who.UMode = tmp_umode.replace(/[ ]/g,"+");
    					} else {
    						who.UMode = cmd[arg];
    					}
    				}
    				break;
    			case "n":
    				arg++;
    				if (cmd[arg]) {
    					who.Nick = cmd[arg];
    					who.tweak_mode(WHO_NICK,add);
    				}
    				break;
    			case "o":
    				who.tweak_mode(WHO_OPER,add);
    				break;
    			case "s":
    				arg++;
    				if (cmd[arg]) {
    					who.Server = cmd[arg];
    					who.tweak_mode(WHO_SERVER,add);
    				}
    				break;
    			case "t":
    				arg++;
    				if (cmd[arg]) {
    					who.Time = parseInt(cmd[arg]);
    					who.tweak_mode(WHO_TIME,add);
    				}
    				break;
    			case "u":
    				arg++;
    				if (cmd[arg]) {
    					who.User = cmd[arg];
    					who.tweak_mode(WHO_USER,add);
    				}
    				break;
    			case "C":
    				who.tweak_mode(WHO_FIRST_CHANNEL,add);
    				break;
    			case "M":
    				who.tweak_mode(WHO_MEMBER_CHANNEL,add);
    				break;
    			case "I":
    				who.tweak_mode(WHO_SHOW_IPS_ONLY,add);
    				break;
    			default:
    				break;
    		}
    	}
    
    	// Check to see if the user passed a generic mask to us for processing.
    	arg++;
    	if (cmd[arg])
    		whomask = cmd[arg];
    
    	var regexp = "^[0]{1,}$";
    	if (whomask.match(regexp))
    		whomask = "*";
    
    	// allow +c/-c to override.
    	if (!who.Channel && ((whomask[0] == "#") || (whomask[0] == "&")))
    		who.Channel = whomask;
    
    	// Strip off any @ or + in front of a channel and set the flags.
    	var sf_op = false;
    	var sf_voice = false;
    	var sf_done = false;
    	var tmp_wc = who.Channel;
    	for (cc in tmp_wc) {
    		switch(tmp_wc[cc]) {
    			case "@":
    				sf_op = true;
    				who.Channel = who.Channel.slice(1);
    				break;
    			case "+":
    				sf_voice = true;
    				who.Channel = who.Channel.slice(1);
    				break;
    			default: // assume we're done
    				sf_done = true;
    				break;
    		}
    		if (sf_done)
    			break;
    	}
    	delete tmp_wc; // don't need this anymore.
    
    	// Now we traverse everything and apply the criteria the user passed.
    	var who_count = 0;
    	for (who_client in Users) {
    		var wc = Users[who_client];
    		var flag_M = this.onchanwith(wc);
    
    		// Don't even bother if the target is +i and the
    		// user isn't an oper or on a channel with the target.
    		if ( (wc.mode&USERMODE_INVISIBLE) &&
    		     !(this.mode&USERMODE_OPER) &&
    		     !flag_M)
    			continue;
    
    		if ((who.add_flags&WHO_AWAY) && !wc.away)
    			continue;
    		else if ((who.del_flags&WHO_AWAY) && wc.away)
    			continue;
    		if (who.add_flags&WHO_CHANNEL) {
    			if (!wc.channels[who.Channel.toUpperCase()])
    				continue;
    			if (sf_op && Channels[who.Channel.toUpperCase()]&&
    			    !Channels[who.Channel.toUpperCase()].modelist
    			    [CHANMODE_OP][wc.id])
    				continue;
    			if(sf_voice&&Channels[who.Channel.toUpperCase()]&&
    			    !Channels[who.Channel.toUpperCase()].modelist
    			    [CHANMODE_VOICE][wc.id])
    				continue;
    		} else if (who.del_flags&WHO_CHANNEL) {	
    			if (wc.channels[who.Channel.toUpperCase()])
    				continue;
    			if (sf_op && Channels[who.Channel.toUpperCase()]&&
    			    Channels[who.Channel.toUpperCase()].modelist
    			    [CHANMODE_OP][wc.id])
    				continue;
    			if(sf_voice&&Channels[who.Channel.toUpperCase()]&&
    			    Channels[who.Channel.toUpperCase()].modelist
    			    [CHANMODE_VOICE][wc.id])
    				continue;
    		}
    		if ((who.add_flags&WHO_REALNAME) &&
    		    !wildmatch(wc.realname,who.RealName))
    			continue;
    		else if ((who.del_flags&WHO_REALNAME) &&
    		    wildmatch(wc.realname,who.RealName))
    			continue;
    		if ((who.add_flags&WHO_HOST) &&
    		    !wildmatch(wc.hostname,who.Host))
    			continue;
    		else if ((who.del_flags&WHO_HOST) &&
    		    wildmatch(wc.hostname,who.Host))
    			continue;
    		if ((who.add_flags&WHO_IP) &&
    		    !wildmatch(wc.ip,who.IP))
    			continue;
    		else if ((who.del_flags&WHO_IP) &&
    		    wildmatch(wc.ip,who.IP))
    			continue;
    		if (who.add_flags&WHO_UMODE) { // no -m
    			var sic = false;
    			var madd = true;
    			for (mm in who.UMode) {
    				switch(who.UMode[mm]) {
    					case "+":
    						if (!madd)
    							madd = true;
    						break;
    					case "-":
    						if (madd)
    							madd = false;
    						break;
    					case "o":
    					case "i":
    					case "w":
    					case "b":
    					case "g":
    					case "s":
    					case "c":
    					case "r":
    					case "k":
    					case "f":
    					case "y":
    					case "d":
    					case "n":
    					case "h":
    					case "F":
    						if (
    						   (!madd && (wc.mode&
    						   USERMODE_CHAR
    							[who.UMode[mm]])
    						   )
    						||
    						   (madd && !(wc.mode&
    						   USERMODE_CHAR
    							[who.UMode[mm]])
    						   ) )
    							sic = true;
    						break;
    					default:
    						break;
    				}
    				if (sic)
    					break;
    			}
    			if (sic)
    				continue;
    		}
    		if ((who.add_flags&WHO_NICK) &&
    		    !wildmatch(wc.nick,who.Nick))
    			continue;
    		else if ((who.del_flags&WHO_NICK) &&
    		    wildmatch(wc.nick,who.Nick))
    			continue;
    		if ((who.add_flags&WHO_OPER) &&
    		    !(wc.mode&USERMODE_OPER))
    			continue;
    		else if ((who.del_flags&WHO_OPER) &&
    		    (wc.mode&USERMODE_OPER))
    			continue;
    		if ((who.add_flags&WHO_SERVER) &&
    		    !wildmatch(wc.servername,who.Server))
    			continue;
    		else if ((who.del_flags&WHO_SERVER) &&
    		    wildmatch(wc.servername,who.Server))
    			continue;
    		if ((who.add_flags&WHO_USER) &&
    		    !wildmatch(wc.uprefix,who.User))
    			continue;
    		else if ((who.del_flags&WHO_USER) &&
    		    wildmatch(wc.uprefix,who.User))
    			continue;
    		if ((who.add_flags&WHO_MEMBER_CHANNEL) && !flag_M)
    			continue;
    		else if ((who.del_flags&WHO_MEMBER_CHANNEL) && flag_M)
    			continue;
    		if ((who.add_flags&WHO_TIME) &&
    		    ((time() - wc.connecttime) < who.Time) )
    			continue;
    		else if ((who.del_flags&WHO_TIME) &&
    		    ((time() - wc.connecttime) > who.Time) )
    			continue;
    		if ((who.add_flags&WHO_CLASS) &&
    		    (wc.ircclass != who.Class))
    			continue;
    		else if ((who.del_flags&WHO_CLASS) &&
    		    (wc.ircclass == who.Class))
    			continue;
    
    		if (whomask && !wc.match_who_mask(whomask))
    			continue;       
    
    		chan = "";
    		if ((who.add_flags&WHO_FIRST_CHANNEL) && !who.Channel) {
    			for (x in wc.channels) {
    				if (!(Channels[x].mode&CHANMODE_SECRET
    						|| Channels[x].mode&CHANMODE_PRIVATE)
    					|| this.channels[x] || this.mode&USERMODE_OPER)
    				{
    					chan = Channels[x].nam;
    					break;
    				}
    			}
    		} else if (who.Channel) {
    			chan = who.Channel;
    		}
    
    		var show_ips_only;
    		if (who.add_flags&WHO_SHOW_IPS_ONLY)
    			show_ips_only = true;
    		else
    			show_ips_only = false;
    
    		// If we made it this far, we're good.
    		if (chan && Channels[chan.toUpperCase()]) {
    			var chkwho = this.numeric352(wc,show_ips_only,
    				Channels[chan.toUpperCase()]);
    			if (!chkwho) {
    				umode_notice(USERMODE_OPER,"Notice",
    					"WHO returned 0 for user: " + wc.nick + " (C)");
    			}
    		} else {
    			var chkwho = this.numeric352(wc,show_ips_only);
    			if (!chkwho) {
    				umode_notice(USERMODE_OPER,"Notice",
    					"WHO returned 0 for user: " + wc.nick + " (D)");
    			}
    		}
    		who_count++;
    
    		if (!(this.mode&USERMODE_OPER) && (who_count >= max_who))
    			break;
    	}
    
    	if (who.Channel)
    		eow = who.Channel;
    	else if (cmd[2])
    		eow = cmd[2];
    
    	this.numeric(315, eow + " :End of /WHO list. (Complex)");
    }
    
    // Object which stores WHO bits and arguments.
    function Who() {
    	this.add_flags = 0;
    	this.del_flags = 0;
    	this.tweak_mode = Who_tweak_mode;
    	this.Channel = "";
    	this.RealName = "";
    	this.Host = "";
    	this.IP = "";
    	this.UMode = "";
    	this.Nick = "";
    	this.Server = "";
    	this.User = "";
    	this.Time = 0;
    	this.Class = 0;
    }
    
    function Who_tweak_mode(bit,add) {
    	if (add) {
    		this.add_flags |= bit;
    		this.del_flags &= ~bit;
    	} else {
    		this.add_flags &= ~bit;
    		this.del_flags |= bit;
    	}
    }
    
    // Take a generic mask in and try to figure out if we're matching a channel,
    // mask, nick, or something else.  Return 1 if the user sent to us matches it
    // in some fashion.
    function IRCClient_match_who_mask(mask) {
    	if ((mask[0] == "#") || (mask[0] == "&")) { // channel
    		if (Channels[mask.toUpperCase()])
    			return 1;
    		else
    			return 0; // channel doesn't exist.
    	} else if (mask.match(/[!]/)) { // nick!user@host
    		if ( wildmatch(this.nick,mask.split("!")[0])
    		     && wildmatch(this.uprefix,
    				mask.slice(mask.indexOf("!")+1).split("@")[0])
    		     && wildmatch(this.hostname,mask.slice(mask.indexOf("@")+1)) )
    			return 1;
    	} else if (mask.match(/[@]/)) { // user@host
    		if ( wildmatch(this.uprefix,mask.split("@")[0]) &&
    		     wildmatch(this.hostname,mask.split("@")[1]) )
    			return 1;
    	} else if (mask.match(/[.]/)) { // host only
    		if ( wildmatch(this.hostname,mask) )
    			return 1;
    	} else { // must be a nick?
    		if ( wildmatch(this.nick,mask) )
    			return 1;
    	}
    	return 0;
    }
    
    function IRCClient_do_who_usage() {
    	this.numeric(334,":/WHO [+|-][acghilmnostuCIM] <args> <mask>");
    	this.numeric(334,":The modes as above work exactly like channel modes.");
    	this.numeric(334,":<mask> may be '*' or in nick!user@host notation.");
    	this.numeric(334,":i.e. '/WHO +a *.ca' would match all away users from *.ca");
    	this.numeric(334,":No args/mask matches to everything by default.");
    	this.numeric(334,":a       : User is away.");
    	this.numeric(334,":c <chan>: User is on <@+><#channel>, no wildcards. Can check +o/+v.");
    	this.numeric(334,":g <rnam>: Check against realname field, wildcards allowed.");
    	this.numeric(334,":h <host>: Check user's hostname, wildcards allowed.");
    	this.numeric(334,":i <ip>  : Check against IP address, wildcards allowed.");
    	this.numeric(334,":l <clas>: User is a member of <clas> irc class as defined on a Y:Line.");
    	this.numeric(334,":m <umde>: User has <umodes> set, -+ allowed.");
    	this.numeric(334,":n <nick>: User's nickname matches <nick>, wildcards allowed.");
    	this.numeric(334,":o       : User is an IRC Operator.");
    	this.numeric(334,":s <srvr>: User is on <server>, wildcards allowed.");
    	this.numeric(334,":t <time>: User has been on for more than (+) or less than (-) <time> secs.");
    	this.numeric(334,":u <user>: User's username field matches, wildcards allowed.");
    	this.numeric(334,":C       : Only display first channel that the user matches.");
    	this.numeric(334,":I       : Only return IP addresses (as opposed to hostnames.)");
    	this.numeric(334,":M       : Only check against channels you're a member of.");
    	this.numeric(315,"? :End of /WHO list. (Usage)");
    }
    
    function IRCClient_do_basic_list(mask) {
    	this.numeric321();
    	// Only allow commas if we're not doing wildcards, otherwise strip
    	// off anything past the first comma and pass to the generic parser
    	// to see if it can make heads or tails out of it.
    	if (mask.match(/[,]/)) {
    		if (mask.match(/[*?]/)) {
    			mask = mask.slice(0,mask.indexOf(","))
    		} else { // parse it out, but junk anything that's not a chan
    			var my_split = mask.split(",");
    			for (myChan in my_split) {
    				if (Channels[my_split[myChan].toUpperCase()])
    					this.numeric322(Channels[my_split[myChan].toUpperCase()]);
    			}
    			// our adventure ends here.
    			this.numeric(323, ":End of /LIST. (Basic: Comma-list)");
    			return;
    		}
    	}
    	for (chan in Channels) {
    		if (Channels[chan] && Channels[chan].match_list_mask(mask))
    			this.numeric322(Channels[chan]);
    	}
    	this.numeric(323, ":End of /LIST. (Basic)");
    	return;
    }
    
    // So, the user wants to go the hard way...
    function IRCClient_do_complex_list(cmd) {
    	var add = true;
    	var arg = 1;
    	var list = new List();
    	var listmask;
    	var listmask_items;
    
    	this.numeric321();
    
    	for (lc in cmd[1]) {
    		switch(cmd[1][lc]) {
    			case "+":
    				if (!add)
    					add = true;
    				break;
    			case "-":
    				if (add)
    					add = false;
    				break;
    			case "a":
    				arg++;
    				if (cmd[arg]) {
    					list.tweak_mode(LIST_CHANMASK,add);
    					list.Mask = cmd[arg];
    				}
    				break;
    			case "c":
    				arg++;
    				if (cmd[arg]) {
    					list.tweak_mode(LIST_CREATED,add);
    					list.Created = parseInt(cmd[arg])*60;
    				}
    				break;
    			case "m": // we never set -m, inverse.
    				arg++;
    				if (cmd[arg]) {
    					list.tweak_mode(LIST_MODES,true);
    					if (!add) {
    						var tmp_mode = "";
    						if((cmd[arg][0] != "+") ||
    						   (cmd[arg][0] != "-") )
    							tmp_mode += "+";
    						tmp_mode += cmd[arg].replace(/[-]/g," ");
    						tmp_mode = tmp_mode.replace(/[+]/g,"-");
    						list.Modes = tmp_mode.replace(/[ ]/g,"+");
    					} else {
    						list.Modes = cmd[arg];
    					}
    				}
    				break;
    			case "o":
    				arg++;
    				if (cmd[arg]) {
    					list.tweak_mode(LIST_TOPIC,add);
    					list.Topic = cmd[arg];
    				}
    				break;
    			case "p":
    				arg++;
    				if (cmd[arg]) {
    					list.tweak_mode(LIST_PEOPLE,add);
    					list.People = parseInt(cmd[arg]);
    				}
    				break;
    			case "t":
    				arg++;
    				if (cmd[arg]) {
    					list.tweak_mode(LIST_TOPICAGE,add);
    					list.TopicTime = parseInt(cmd[arg])*60;
    				}
    				break;
    			case "M":
    				list.tweak_mode(LIST_DISPLAY_CHAN_MODES,add);
    				break;
    			default:
    				break;
    		}
    	}
    
    	// Generic mask atop all this crap?
    	arg++;
    	if (cmd[arg])
    		listmask = cmd[arg];
    
    	// Here we go...
    	for (aChan in Channels) {
    		// Is the user allowed to see this channel, for starters?
    		if (!(Channels[aChan].mode&CHANMODE_SECRET) ||
    		     (this.mode&USERMODE_OPER) || this.channels[aChan]) {
    			
    			if ((list.add_flags&LIST_CHANMASK) &&
    			    !wildmatch(aChan,list.Mask.toUpperCase()))
    				continue;
    			else if ((list.del_flags&LIST_CHANMASK) &&
    			    wildmatch(aChan,list.Mask.toUpperCase()))
    				continue;
    			if ((list.add_flags&LIST_CREATED) &&
    			   (Channels[aChan].created < (time() - list.Created)))
    				continue;
    			else if ((list.del_flags&LIST_CREATED) &&
    			   (Channels[aChan].created > (time() - list.Created)))
    				continue;
    			if ((list.add_flags&LIST_TOPIC) &&
    			   (!wildmatch(Channels[aChan].topic,list.Topic)))
    				continue;
    			else if ((list.del_flags&LIST_TOPIC) &&
    			   (wildmatch(Channels[aChan].topic,list.Topic)))
    				continue;
    			if ((list.add_flags&LIST_PEOPLE) &&
    			    (true_array_len(Channels[aChan].users) < list.People) )
    				continue;
    			else if ((list.del_flags&LIST_PEOPLE) &&
    			    (true_array_len(Channels[aChan].users) >= list.People) )
    				continue;
    			if ((list.add_flags&LIST_TOPICAGE) && list.TopicTime &&
    			  (Channels[aChan].topictime > (time()-list.TopicTime)))
    				continue;
    			else if((list.del_flags&LIST_TOPICAGE)&&list.TopicTime&&
    			  (Channels[aChan].topictime < (time()-list.TopicTime)))
    				continue;
    			if (list.add_flags&LIST_MODES) { // there's no -m
    				var sic = false;
    				var madd = true;
    				var c = Channels[aChan];
    				for (mm in list.Modes) {
    					switch(list.Modes[mm]) {
    						case "+":
    							if (!madd)
    								madd = true;
    							break;
    						case "-":
    							if (madd)
    								madd = false;
    							break;
    						case "i":
    							if (
    							   (!madd && (c.mode&
    							   CHANMODE_INVITE))
    							||
    							   (madd && !(c.mode&
    							   CHANMODE_INVITE))
    							   )
    								sic = true;
    							break;
    						case "k":
    							if (
    							   (!madd && (c.mode&
    							   CHANMODE_KEY))
    							||
    							   (madd && !(c.mode&
    							   CHANMODE_KEY))
    							   )
    								sic = true;
    							break;
    						case "l":
    							if (
    							   (!madd && (c.mode&
    							   CHANMODE_LIMIT))
    							||
    							   (madd && !(c.mode&
    							   CHANMODE_LIMIT))
    							   )
    								sic = true;
    							break;
    						case "m":
    							if (
    							   (!madd && (c.mode&
    							   CHANMODE_MODERATED))
    							||
    							   (madd && !(c.mode&
    							   CHANMODE_MODERATED))
    							   )
    								sic = true;
    							break;
    						case "n":
    							if (
    							   (!madd && (c.mode&
    							   CHANMODE_NOOUTSIDE))
    							||
    							   (madd && !(c.mode&
    							   CHANMODE_NOOUTSIDE))
    							   )
    								sic = true;
    							break;
    						case "p":
    							if (
    							   (!madd && (c.mode&
    							   CHANMODE_PRIVATE))
    							||
    							   (madd && !(c.mode&
    							   CHANMODE_PRIVATE))
    							   )
    								sic = true;
    							break;
    						case "s":
    							if (
    							   (!madd && (c.mode&
    							   CHANMODE_SECRET))
    							||
    							   (madd && !(c.mode&
    							   CHANMODE_SECRET))
    							   )
    								sic = true;
    							break;
    						case "t":
    							if (
    							   (!madd && (c.mode&
    							   CHANMODE_TOPIC))
    							||
    							   (madd && !(c.mode&
    							   CHANMODE_TOPIC))
    							   )
    								sic = true;
    							break;
    						default:
    							break;
    					}
    					if (sic)
    						break;
    				}
    				if (sic)
    					continue;
    			}
    
    			if (listmask)
    				listmask_items = listmask.split(",");
    			var l_match = false; // assume we match nothing.
    			if (listmask_items) {
    				for (l in listmask_items) {
    					if (Channels[aChan].match_list_mask
    					   (listmask_items[l])) {
    						l_match = true;
    						break;
    					}
    				}
    				if (!l_match)
    					continue;
    			}
    
    			// We made it.
    			if (list.add_flags&LIST_DISPLAY_CHAN_MODES)
    				this.numeric322(Channels[aChan],true);
    			else
    				this.numeric322(Channels[aChan]);
    		}
    	}
    
    	this.numeric(323, ":End of /LIST. (Complex)");
    }
    		
    // Object which stores LIST bits and arguments.
    function List() {
    	this.add_flags = 0;
    	this.del_flags = 0;
    	this.tweak_mode = List_tweak_mode;
    	this.People = 0;
    	this.Created = 0;
    	this.TopicTime = 0;
    	this.Mask = "";
    	this.Modes = "";
    	this.Topic = "";
    }       
    
    function List_tweak_mode(bit,add) {
    	if (add) {
    		this.add_flags |= bit;
    		this.del_flags &= ~bit;
    	} else {
    		this.add_flags &= ~bit;
    		this.del_flags |= bit;
    	}
    }
    
    function IRCClient_do_list_usage() {
    	this.numeric(334,":/LIST [+|-][acmoptM] <args> <mask|channel{,channel}>");
    	this.numeric(334,":The modes as above work exactly like channel modes.");
    	this.numeric(334,":<mask> may be just like Bahamut notation.");
    	this.numeric(334,":(Bahamut Notation = >num,<num,C>num,C<num,T>num,T<num,*mask*,!*mask*)");
    	this.numeric(334,":i.e. '/LIST +p 50 #*irc*' lists chans w/ irc in the name and 50+ users.");
    	this.numeric(334,":No args/mask matches to everything by default.");
    	this.numeric(334,":a <mask>: List channels whose names match the mask.  Wildcards allowed.");
    	this.numeric(334,":c <time>: Chans created less than (-) or more than (+) <time> mins ago.");
    	this.numeric(334,":m <mods>: Channel has <modes> set.  -+ allowed.");
    	this.numeric(334,":o <topc>: Match against channel's <topic>, wildcards allowed.");
    	this.numeric(334,":p <num> : Chans with more or equal to (+) members, or (-) less than.");
    	this.numeric(334,":t <time>: Only channels whose topics were created <time> mins ago.");
    	this.numeric(334,":M       : Show channel's mode in front of the list topic.");
    	// No "end of" numeric for this.
    }
    
    // does 'this' (channel) match the 'mask' passed to us?  Use 'complex'
    // Bahamut parsing to determine that.
    function Channel_match_list_mask(mask) {
    	if (mask[0] == ">") { // Chan has more than X people?
    		if (true_array_len(this.users) < parseInt(mask.slice(1)))
    			return 0;
    	} else if (mask[0] == "<") { // Chan has less than X people?
    		if (true_array_len(this.users) >= parseInt(mask.slice(1)))
    			return 0;
    	} else if (mask[0].toUpperCase() == "C") { //created X mins ago?
    		if ((mask[1] == ">") && (this.created <
    		    (time() - (parseInt(mask.slice(2)) * 60)) ) )
    			return 0;
    		else if ((mask[1] == "<") && (this.created >
    		         (time() - (parseInt(mask.slice(2)) * 60)) ) )
    			return 0;
    	} else if (mask[0].toUpperCase() == "T") { //topics older than X mins?
    		if ((mask[1] == ">") && (this.topictime <
    		    (time() - (parseInt(mask.slice(2)) * 60)) ) )
    			return 0;
    		else if ((mask[1] == "<") && (this.topictime >
    		         (time() - (parseInt(mask.slice(2)) * 60)) ) )
    			return 0;
    	} else if (mask[0] == "!") { // doesn't match mask X
    		if (wildmatch(this.nam,mask.slice(1).toUpperCase()))
    			return 0;
    	} else { // if all else fails, we're matching a generic channel mask.
    		if (!wildmatch(this.nam,mask))
    			return 0;
    	}
    	return 1; // if we made it here, we matched something.
    }
    
    function IRCClient_get_usermode(bcast_modes) {
    	var tmp_mode = "+";
    	for (ch in USERMODE_CHAR) {
    		if ((!bcast_modes || (bcast_modes && USERMODE_BCAST[ch])) &&
    		    this.mode&USERMODE_CHAR[ch])
    			tmp_mode += ch;
    	}
    	return tmp_mode;
    }
    
    function UMode_tweak_mode(bit,add) {
    	if (add) {
    		this.add_flags |= bit;
    		this.del_flags &= ~bit;
    	} else {
    		this.add_flags &= ~bit;
    		this.del_flags |= bit;
    	}
    }
    
    function UMode() {
    	this.add_flags = 0;
    	this.del_flags = 0;
    	this.tweak_mode = UMode_tweak_mode;
    }
    
    function IRCClient_setusermode(modestr) {
    	if (!modestr)
    		return 0;
    	var add=true;
    	var unknown_mode=false;
    	var umode = new UMode();
    	for (modechar in modestr) {
    		switch (modestr[modechar]) {
    			case "+":
    				if (!add)
    					add=true;
    				break;
    			case "-":
    				if (add)
    					add=false;
    				break;
    			case "i":
    			case "w":
    			case "s":
    			case "k":
    			case "g":
    				umode.tweak_mode(USERMODE_CHAR
    					[modestr[modechar]],add);
    				break;
    			case "b":
    			case "r":
    			case "f":
    			case "y":
    			case "d":
    			case "n":
    				if (this.mode&USERMODE_OPER)
    					umode.tweak_mode(USERMODE_CHAR
    						[modestr[modechar]],add);
    				break;
    			case "o":
    				// Allow +o only by servers or non-local users.
    				if (add && this.parent &&
    				    Servers[this.parent.toLowerCase()].hub)
    					umode.tweak_mode(USERMODE_OPER,true);
    				else if (!add)
    					umode.tweak_mode(USERMODE_OPER,false);
    				break;
    			case "c":
    				if ((this.mode&USERMODE_OPER) &&
    				    (this.flags&OLINE_CAN_UMODEC))
    					umode.tweak_mode(USERMODE_CLIENT,add);
    				break;
    			case "A":
    				if ( ((this.mode&USERMODE_OPER) && (this.flags&OLINE_IS_ADMIN))
    					|| (this.parent && Servers[this.parent.toLowerCase()].hub) )
    					umode.tweak_mode(USERMODE_ADMIN,add);
    				break;
    			default:
    				if (!unknown_mode && !this.parent) {
    					this.numeric(501, ":Unknown MODE flag");
    					unknown_mode=true;
    				}
    				break;
    		}
    	}
    	var addmodes = "";
    	var delmodes = "";
    	var bcast_addmodes = "";
    	var bcast_delmodes = "";
    	for (mym in USERMODE_CHAR) {
    		if ((umode.add_flags&USERMODE_CHAR[mym]) &&
    		    !(this.mode&USERMODE_CHAR[mym])) {
    			addmodes += mym;
    			if (USERMODE_BCAST[mym])
    				bcast_addmodes += mym;
    			this.mode |= USERMODE_CHAR[mym];
    		} else if ((umode.del_flags&USERMODE_CHAR[mym]) &&
    		    (this.mode&USERMODE_CHAR[mym])) {
    			delmodes += mym;
    			if (USERMODE_BCAST[mym])
    				bcast_delmodes += mym;
    			this.mode &= ~USERMODE_CHAR[mym];
    		}
    	}
    	if (!addmodes && !delmodes)
    		return 0;
    	var final_modestr = "";
    	var bcast_modestr = "";
    	if (addmodes)
    		final_modestr += "+" + addmodes;
    	if (delmodes)
    		final_modestr += "-" + delmodes;
    	if (bcast_addmodes)
    		bcast_modestr += "+" + bcast_addmodes;
    	if (bcast_delmodes)
    		bcast_modestr += "-" + bcast_delmodes;
    	if (this.local && !this.server) {
    		this.originatorout("MODE "+this.nick+" "+final_modestr,this);
    		if (bcast_addmodes || bcast_delmodes)
    			this.bcast_to_servers("MODE "+this.nick+" "+bcast_modestr);
    	}
    	return bcast_modestr;
    }
    
    function IRCClient_check_timeout() {
    	if (!this.pinged && ((time() - this.idletime) >
    	    YLines[this.ircclass].pingfreq)) {
    		this.pinged = time();
    		this.rawout("PING :" + servername);
    	} else if (this.pinged && ((time() - this.pinged) >
    	    YLines[this.ircclass].pingfreq)) {
    		this.quit("Ping Timeout");
    		return 1; // ping timeout
    	}
    	return 0; // no ping timeout
    }
    
    function IRCClient_check_queues() {
    	var cmd;
    
    	this.sendq.send(this.socket);
    	while (this.recvq.queue.length) {
    		cmd = this.recvq.del();
    		Global_CommandLine = cmd;
    		this.work(cmd);
    		if (this.replaced_with !== undefined)
    			this.replaced_with.check_queues();
    	}
    }
    
    function IRCClient_finalize_server_connect(states,sendps) {
    	var statestr = " :" + states;
    	/* DreamForge-based servers do not support passing state information
    	   along on the PASS message */
    	if (this.type&DREAMFORGE)
    		statestr = "";
    	hcc_counter++;
    	gnotice("Link with " + this.nick + "[unknown@" + this.hostname +
    		"] established, states: " + states);
    	if (server.client_update != undefined)
    		server.client_update(this.socket, this.nick, this.hostname);
    	if (sendps) {
    		for (cl in CLines) {
    			if(wildmatch(this.nick,CLines[cl].servername)) {
    				this.rawout("PASS " + CLines[cl].password + statestr);
    				break;
    			}
    		}
    		if (this.type&BAHAMUT)
    			this.rawout("CAPAB " + server_capab);
    		this.rawout("SERVER " + servername + " 1 :" + serverdesc);
    	}
    	this.bcast_to_servers_raw(":" + servername + " SERVER " + this.nick
    		+ " 2 :" + this.info);
    	this.synchronize();
    }
    
    function CLine(host,password,servername,port,ircclass) {
    	this.host = host;
    	this.password = password;
    	this.servername = servername;
    	this.port = port;
    	this.ircclass = ircclass;
    	this.lastconnect = 0;
    }
    
    function HLine(allowedmask,servername) {
    	this.allowedmask = allowedmask;
    	this.servername = servername;
    }
    
    function ILine(ipmask,password,hostmask,port,ircclass) {
    	this.ipmask = ipmask;
    	this.password = password;
    	this.hostmask = hostmask;
    	this.port = port;
    	this.ircclass = ircclass;
    }
    
    function KLine(hostmask,reason,type) {
    	this.hostmask = hostmask;
    	this.reason = reason;
    	this.type = type;
    }
    
    function NLine(host,password,servername,flags,ircclass) {
    	this.host = host;
    	this.password = password;
    	this.servername = servername;
    	this.flags = flags;
    	this.ircclass = ircclass;
    }
    
    function OLine(hostmask,password,nick,flags,ircclass) {
    	this.hostmask = hostmask;
    	this.password = password;
    	this.nick = nick;
    	this.flags = flags;
    	this.ircclass = ircclass;
    }
    
    function QLine(nick,reason) {
    	this.nick = nick;
    	this.reason = reason;
    }
    
    function YLine(pingfreq,connfreq,maxlinks,sendq) {
    	this.pingfreq = pingfreq;
    	this.connfreq = connfreq;
    	this.maxlinks = maxlinks;
    	this.sendq = sendq;
    }
    
    function ZLine(ipmask,reason) {
    	this.ipmask = ipmask;
    	this.reason = reason;
    }
    
    function WhoWasObj(nick,uprefix,host,realname,server,serverdesc) {
    	this.nick = nick;
    	this.uprefix = uprefix;
    	this.host = host;
    	this.realname = realname;
    	this.server = server;
    	this.serverdesc = serverdesc;
    }
    
    function NickBuf(oldnick,newnick) {
    	this.oldnick = oldnick;
    	this.newnick = newnick;
    }
    
    // used for tracking true SJOIN nicks.
    function SJOIN_Nick(nick,isop,isvoice) {
    	this.nick = nick;
    	this.isop = isop;
    	this.isvoice = isvoice;
    }
    
    // Track IRC socket queues
    function IRC_Queue() {
    	this.queue = new Array;
    	this._recv_bytes = '';
    	this._send_bytes = '';
    	this.add = Queue_Add;
    	this.del = Queue_Del;
    	this.prepend = Queue_Prepend;
    	this.recv = Queue_Recv;
    	this.send = Queue_Send;
    }
    
    /* /STATS M, for profiling. */
    function StatsM() {
    	this.ticks = 0;
    	this.executions = 0;
    }