// $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 Randolph Erwin Sommerfeld <sysop@rrx.ca> // load("sbbsdefs.js"); load("sockdefs.js"); load("nodedefs.js"); // CVS revision const 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.0b(" + REVISION + ")"; const VERSION_STR = "Synchronet " + system.version + system.revision + "-" + system.platform + " (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. // FIXME: This introduces privacy concerns. Should we make it difficult for // the sysop to snoop on private conversations? A diligent sysop will always // be able to snoop regardless (either by custom hacks, or, a packet sniffer.) var debug = false; // Resolve connecting clients' hostnames? If set to false, everyone will have // an IP address instead of a hostname in their nick!user@host identifier. // Resolving hostnames is a BLOCKING operation, so your IRCD *will* freeze for // the amount of time it takes to resolve a host. // If you have a local (caching) name server, this shouldn't be a problem, but // the slower your connection to the net and the further away your named is, // the longer lookups will block for. Busy servers should almost always // disable this. // Exception: 'localhost' and '127.0.0.1' always get resolved internally to // the hostname defined on the M:Line regardless of this setting. var resolve_hostnames = true; // 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. var ob_sock_timeout = 3; // what our server is capable of from a server point of view. // TS3 = Version 3 of accepted interserver timestamp protocol. // NOQUIT = NOQUIT interserver command supported. // SSJOIN = SJOIN interserver command supported without dual TS, single TS only. // BURST = Sending of network synch data is done in a 3-stage burst (BURST cmd) // UNCONNECT = UNCONNECT interserver command supported. // NICKIP = 9th parameter of interserver NICK command is an integer IP. // TSMODE = Timestamps are given on plain MODE commands. // ZIP = server supports gzip on the fly for interserver communication. var 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 ;) var max_chanlen = 100; // Maximum channel name length. var max_nicklen = 30; // Maximum nickname length. var max_modes = 6; // Maximum modes on single MODE command var max_user_chans = 10; // Maximum channels users can join var max_bans = 25; // Maximum bans (+b) per channel var max_topiclen = 307; // Maximum length of topic per channel var max_kicklen = 307; // Maximum length of kick reasons var default_port = 6667; // User modes var USERMODE_NONE =(1<<0); // NONE var USERMODE_OPER =(1<<1); // o var USERMODE_INVISIBLE =(1<<2); // i // Channel modes var CHANMODE_NONE =(1<<0); // NONE var CHANMODE_BAN =(1<<1); // b var CHANMODE_INVITE =(1<<2); // i var CHANMODE_KEY =(1<<3); // k var CHANMODE_LIMIT =(1<<4); // l var CHANMODE_MODERATED =(1<<5); // m var CHANMODE_NOOUTSIDE =(1<<6); // n var CHANMODE_OP =(1<<7); // o var CHANMODE_PRIVATE =(1<<8); // p var CHANMODE_SECRET =(1<<9); // s var CHANMODE_TOPIC =(1<<10); // t var CHANMODE_VOICE =(1<<11); // v // Channel lists. Inverses of a mode MUST always be 1 'away' from each other // in the reverse direction. For example, -o is +1 from +o, and +o is -1 // from -o. const CHANLIST_NONE =0; // null const CHANLIST_OP =1; // +o const CHANLIST_DEOP =2; // -o const CHANLIST_VOICE =3; // +v const CHANLIST_DEVOICE =4; // -v const CHANLIST_BAN =5; // +b const CHANLIST_UNBAN =6; // -b // These are used in the mode crunching section to figure out what character // to display in the crunched MODE line. function Mode (modechar,args,state,list) { this.modechar = modechar; this.args = args; this.state = state; this.list = list; } MODE = new Array(); MODE[CHANMODE_BAN] = new Mode("b",true,false,CHANLIST_BAN); MODE[CHANMODE_INVITE] = new Mode("i",false,true,0); MODE[CHANMODE_KEY] = new Mode("k",true,true,0); MODE[CHANMODE_LIMIT] = new Mode("l",true,true,0); MODE[CHANMODE_MODERATED] = new Mode("m",false,true,0); MODE[CHANMODE_NOOUTSIDE] = new Mode("n",false,true,0); MODE[CHANMODE_OP] = new Mode("o",true,false,CHANLIST_OP); MODE[CHANMODE_PRIVATE] = new Mode("p",false,true,0); MODE[CHANMODE_SECRET] = new Mode("s",false,true,0); MODE[CHANMODE_TOPIC] = new Mode("t",false,true,0); MODE[CHANMODE_VOICE] = new Mode("v",true,false,CHANLIST_VOICE); MODECHAR = new Array(); MODECHAR[CHANLIST_BAN] = "b"; MODECHAR[CHANLIST_OP] = "o"; MODECHAR[CHANLIST_VOICE] = "v"; // Connection Types const TYPE_EMPTY =0; const TYPE_UNREGISTERED =1; const TYPE_USER =2; const TYPE_USER_REMOTE =3; // Type 4 is reserved for a possible future client type. const TYPE_SERVER =5; const TYPE_ULINE =6; // Anything 5 or above SHOULD be a server-type connection. // Various permissions that can be set on an O:Line var OLINE_CAN_REHASH =(1<<0); // r var OLINE_CAN_RESTART =(1<<1); // R var OLINE_CAN_DIE =(1<<2); // D var OLINE_CAN_GLOBOPS =(1<<3); // g var OLINE_CAN_WALLOPS =(1<<4); // w var OLINE_CAN_LOCOPS =(1<<5); // l var OLINE_CAN_LSQUITCON =(1<<6); // c var OLINE_CAN_GSQUITCON =(1<<7); // C var OLINE_CAN_LKILL =(1<<8); // k var OLINE_CAN_GKILL =(1<<9); // K var OLINE_CAN_KLINE =(1<<10); // b var OLINE_CAN_UNKLINE =(1<<11); // B var OLINE_CAN_LGNOTICE =(1<<12); // n var OLINE_CAN_GGNOTICE =(1<<13); // N // Synchronet IRCd doesn't have umode +Aa RESERVED // Synchronet IRCd doesn't have umode +a RESERVED // Synchronet IRCd doesn't have umode +c RESERVED // Synchronet IRCd doesn't have umode +f RESERVED // Synchronet IRCd doesn't have umode +F RESERVED var OLINE_CAN_CHATOPS =(1<<19); // s var OLINE_CHECK_SYSPASSWD =(1<<20); // S var OLINE_CAN_DEBUG =(1<<21); // x // Various N:Line permission bits var NLINE_CHECK_QWKPASSWD =(1<<0); // q var NLINE_CAN_CLINE =(1<<1); // c ////////// Functions not linked to an object ////////// function ip_to_int(ip) { var quads = ip.split("."); var addr = (quads[0]&0xff)<<24; addr|=(quads[1]&0xff)<<16; addr|=(quads[2]&0xff)<<8; addr|=(quads[3]&0xff); return addr; } function int_to_ip(ip) { return(format("%u.%u.%u.%u" ,(ip>>24)&0xff ,(ip>>16)&0xff ,(ip>>8)&0xff ,ip&0xff )); } function terminate_everything(terminate_reason) { for(thisClient in Clients) { var Client = Clients[thisClient]; if (Client && Client.local) Client.quit(terminate_reason,false) } exit(); } function searchbynick(nick) { if (!nick) return 0; for(thisClient in Clients) { var Client=Clients[thisClient]; if (Client && nick.toUpperCase() == Client.nick.toUpperCase() && Client.conntype && !Client.server) return Client; // success! } return 0; // failure } function searchbychannel(chan) { if (!chan) return 0; for(i in Channels) { if (chan.toUpperCase() == i.toUpperCase()) return Channels[i]; // success! } return 0; // failure } function searchbyserver(server_name) { if (!server_name) return 0; if (match_irc_mask(servername,server_name)) return -1; // the server passed to us is our own. for(thisServer in Clients) { Server=Clients[thisServer]; if ( (Server.conntype == TYPE_SERVER) && match_irc_mask(Server.nick,server_name) ) return Server; } // if we've had no success so far, try nicknames and glean a server // from there. for(thisNick in Clients) { Nick=Clients[thisNick]; if (!Nick.server && match_irc_mask(Nick.nick,server_name)) return searchbyserver(Nick.servername); } return 0; // looks like we failed after all that hard work :( } function IRCClient_searchbyiline() { for(thisILine in ILines) { // FIXME: We don't compare connecting port for now. if ( (match_irc_mask(this.uprefix + "@" + this.socket.remote_ip_address,ILines[thisILine].ipmask)) && ((ILines[thisILine].password == "") || (this.password == ILines[thisILine].password)) && (match_irc_mask(this.uprefix + "@" + this.hostname, ILines[thisILine].hostmask)) ) return ILines[thisILine].ircclass; } return 0; } // IRC is funky. A "string" in IRC is anything after a :, and anything before // that is usually treated as fixed arguments. So, this function is commonly // used to fetch 'strings' from all sorts of commands. PRIVMSG, NOTICE, // USER, PING, etc. function ircstring(str,startword) { if (startword) { for(sw_counter=0;sw_counter<startword;sw_counter++) { str=str.slice(str.indexOf(" ")+1); } } cindex = str.indexOf(":")+1; if (!cindex) cindex = str.lastIndexOf(" ")+1; if (!cindex) return str; return(str.slice(cindex)); } // 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.replace(/[^\w]/g,""); 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 "c": nline_flags |= NLINE_CAN_CLINE; break; default: log("!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 "A": case "a": case "u": 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_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; break; default: log("!WARNING Unknown O:Line flag '" + flags[thisflag] + "' in config."); break; } } return oline_flags; } function oper_notice(ntype,nmessage) { for(thisoper in Clients) { var oper=Clients[thisoper]; if ((oper.mode&USERMODE_OPER) && !oper.parent) oper.rawout(":" + servername + " NOTICE " + oper.nick + " :*** " + ntype + " -- " + nmessage); } } function create_ban_mask(str,kline) { tmp_banstr = new Array; tmp_banstr[0] = ""; tmp_banstr[1] = ""; tmp_banstr[2] = ""; bchar_counter = 0; part_counter = 0; // BAN: 0!1@2 KLINE: 0@1 regexp="[A-Za-z\{\}\`\^\_\|\\]\\[\\\\0-9\-.*?\~]"; 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 match_irc_mask(mtchstr,mask) { final_mask="^"; mask=mask.replace(/[.]/g,"\\\."); mask=mask.replace(/[?]/g,"."); mask=mask.replace(/[*]/g,".*?"); final_mask=final_mask + mask + "$"; return mtchstr.toUpperCase().match(final_mask.toUpperCase()); } function isklined(kl_str) { for(the_kl in KLines) { if (KLines[the_kl].hostmask && match_irc_mask(kl_str,KLines[the_kl].hostmask)) return 1; } return 0; } function iszlined(zl_ip) { for(the_zl in ZLines) { if (ZLines[the_zl].ipmask && match_irc_mask(zl_ip,ZLines[the_zl].ipmask)) return 1; } return 0; } function scan_for_klined_clients() { for(thisUser in Clients) { if (Clients[thisUser]) { theuser=Clients[thisUser]; if (theuser.local && !theuser.server && (theuser.conntype == TYPE_USER)) { if (isklined(theuser.uprefix + "@" + theuser.hostname)) theuser.quit("User has been K:Lined (" + KLines[thiskl].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 && match_irc_mask(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) { 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.connect(this_cline.host,the_port,ob_sock_timeout); if (connect_sock.is_connected) { oper_notice("Routing","Connected! Sending info..."); connect_sock.send("PASS " + this_cline.password + " :TS\r\n"); connect_sock.send("CAPAB " + server_capab + "\r\n"); connect_sock.send("SERVER " + servername + " 1 :" + serverdesc + "\r\n"); new_id = get_next_clientid(); Clients[new_id]=new IRCClient(connect_sock,new_id,true,true); Clients[new_id].sentps = true; } this_cline.lastconnect = time(); } function wallopers(str) { for(thisoper in Clients) { oper=Clients[thisoper]; if ((oper.mode&USERMODE_OPER) && !oper.parent) 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=0;nb<NickHistory.length;nb++) { if (NickHistory[nb] && (bufnick.toUpperCase() == NickHistory[nb].oldnick.toUpperCase())) { if (!searchbynick(NickHistory[nb].newnick)) return search_nickbuf(NickHistory[nb].newnick); else return NickHistory[nb].newnick; } } return 0; } function IRCClient_tweaktmpmode(tmp_bit,chan) { if ((!chan.ismode(this.id,CHANLIST_OP)) && (!this.server) && (!this.parent)) { this.numeric482(chan.nam); return 0; } if (add) { addbits|=tmp_bit; delbits&=~tmp_bit; } else { addbits&=~tmp_bit; delbits|=tmp_bit; } } function IRCClient_tweaktmpmodelist(tmp_cl,tmp_ncl,chan) { if ((!chan.ismode(this.id,CHANLIST_OP)) && (!this.server) && (!this.parent)) { this.numeric482(chan.nam); return 0; } if (add) { tmp_match = false; for (lstitem in chan_tmplist[tmp_cl]) { if (chan_tmplist[tmp_cl][lstitem] && (chan_tmplist[tmp_cl][lstitem].toUpperCase() == cm_args[mode_args_counter].toUpperCase()) ) tmp_match = true; } if(!tmp_match) { chan_tmplist[tmp_cl][chan_tmplist[tmp_cl].length] = cm_args[mode_args_counter]; for (lstitem in chan_tmplist[tmp_ncl]) { if (chan_tmplist[tmp_ncl][lstitem] && (chan_tmplist[tmp_ncl][lstitem].toUpperCase() == cm_args[mode_args_counter].toUpperCase()) ) chan_tmplist[tmp_ncl][lstitem] = ""; } } } else { tmp_match = false; for (lstitem in chan_tmplist[tmp_ncl]) { if (chan_tmplist[tmp_ncl][lstitem] && (chan_tmplist[tmp_ncl][lstitem].toUpperCase() == cm_args[mode_args_counter].toUpperCase()) ) tmp_match = true; } if(!tmp_match) { chan_tmplist[tmp_ncl][chan_tmplist[tmp_ncl].length] = cm_args[mode_args_counter]; for (lstitem in chan_tmplist[tmp_cl]) { if (chan_tmplist[tmp_cl][lstitem] && (chan_tmplist[tmp_cl][lstitem].toUpperCase() == cm_args[mode_args_counter].toUpperCase()) ) chan_tmplist[tmp_cl][lstitem] = ""; } } } } function count_channels() { tmp_counter=0; for (tmp_count in Channels) { if (Channels[tmp_count]) tmp_counter++; } return tmp_counter; } function count_servers(count_all) { if (count_all) tmp_counter=1; // we start by counting ourself. else tmp_counter=0; // we're just counting servers connected to us for (tmp_count in Clients) { if ((Clients[tmp_count] != undefined) && Clients[tmp_count].server) { if (Clients[tmp_count].local || count_all) tmp_counter++; } } return tmp_counter; } function count_nicks(count_bit) { if(!count_bit) count_bit=USERMODE_NONE; tmp_counter=0; for (tmp_count in Clients) { if ((Clients[tmp_count] != undefined) && ((Clients[tmp_count].conntype == TYPE_USER) || (Clients[tmp_count].conntype == TYPE_USER_REMOTE)) && (Clients[tmp_count].mode&count_bit) && (!(Clients[tmp_count].mode&USERMODE_INVISIBLE) || (count_bit==USERMODE_INVISIBLE) || (count_bit==USERMODE_OPER) ) ) tmp_counter++; } return tmp_counter; } function count_local_nicks() { tmp_counter=0; for (tmp_count in Clients) { if ((Clients[tmp_count] != undefined) && !Clients[tmp_count].parent) tmp_counter++; } return tmp_counter; } function get_next_clientid() { for (tmp_client in Clients) { if (!Clients[tmp_client].conntype) { return tmp_client; } } return Clients.length + 1; } function read_config_file() { 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(); var fname=""; if (config_filename && config_filename.length) 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"; } var conf = new File(fname); if (conf.open("r")) { log("Reading Config: " + fname); while (!conf.eof) { conf_line = conf.readln(); if ((conf_line != null) && conf_line.match("[:]")) { 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],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; kline_mask = create_ban_mask(arg[1],true); if (!kline_mask) { log("!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("Creating new socket object on port " + port); var newsock = new Socket(); if(!newsock.bind(port)) { log("!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("!Error " + newsock.error + " setting up socket for listening"); return 0; } return newsock; } /////////////////////////////////////////////////////////////////////////////// /////////////////////////////////////////////////////////////////////////////// ////////////// End of functions. Start main() program here. ////////////// /////////////////////////////////////////////////////////////////////////////// /////////////////////////////////////////////////////////////////////////////// log(VERSION + " started."); Clients = new Array; Channels = new Array; hcc_total = 0; hcc_users = 0; hcc_counter = 0; server_uptime = time(); WhoWasHistory = new Array; NickHistory = new Array; whowas_buffer = 10000; whowas_pointer = 0; nick_buffer = 10000; nick_pointer = 0; // 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; } } read_config_file(); if(this.server==undefined) { // Running from JSexec? if (cmdline_port) default_port = cmdline_port; else if (mline_port) default_port = mline_port; server = { socket: false, terminated: false }; 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(); // 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); } } if(this.branch!=undefined) branch.limit=0; // we're not an infinite loop. else if (this.js!=undefined) js.branch_limit=0; // new style branch limit definition ///// Main Loop ///// while (!server.terminated) { mswait(1); // don't peg the CPU if(server.terminated) break; // Setup a new socket if a connection is accepted. for (pl in open_plines) { if (open_plines[pl].poll()) { var client_sock=open_plines[pl].accept(); if(client_sock) { 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 { new_id = get_next_clientid(); Clients[new_id]=new IRCClient(client_sock,new_id,true,true); if(server.client_add != undefined) server.client_add(client_sock); } } } } // Poll all sockets for commands, then pass to the appropriate func. for(thisClient in Clients) { ClientObject=Clients[thisClient]; if ( (ClientObject != undefined) && ClientObject.socket && ClientObject.conntype && ClientObject.local ) ClientObject.work(); } // Scan C:Lines for servers to connect to automatically. for(thisCL in CLines) { var my_cline = CLines[thisCL]; if (my_cline.port && YLines[my_cline.ircclass].connfreq && !(searchbyserver(my_cline.servername)) && ((time() - my_cline.lastconnect) > YLines[my_cline.ircclass].connfreq) ) { oper_notice("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 //////////////////////////////// // Client Object // FIXME: this whole thing is a mess. It should be cleaned up and streamlined. function IRCClient(socket,new_id,local_client,do_newconn) { this.pinged=false; this.nick = ""; this.away = ""; this.realname = ""; this.uprefix = ""; this.hostname = ""; this.conntype = TYPE_UNREGISTERED; this.ircclass = 0; this.flags = 0; this.idletime = ""; this.invited = ""; this.password = ""; this.sentps = false; this.local = local_client; this.mode = USERMODE_NONE; this.socket = socket; this.channels = new Array(); this.searchbyiline=IRCClient_searchbyiline; this.quit=IRCClient_Quit; this.netsplit=IRCClient_netsplit; this.ircnuh getter = function() { return(this.nick + "!" + this.uprefix + "@" + this.hostname); }; this.isulined getter = function() { for (my_ul in ULines) { if (this.nick.toUpperCase() == ULines[my_ul].toUpperCase()) return 1; } return 0; }; this.hops=0; this.server=false; this.hub=false; this.servername = servername; this.affect_mode_list=IRCClient_affect_mode_list; this.tweaktmpmode=IRCClient_tweaktmpmode; this.tweaktmpmodelist=IRCClient_tweaktmpmodelist; this.rmchan=IRCClient_RMChan; this.rawout=IRCClient_rawout; this.originatorout=IRCClient_originatorout; this.ircout=IRCClient_ircout; this.server_notice=IRCClient_server_notice; this.setusermode=IRCClient_setusermode; this.numeric=IRCClient_numeric; this.numeric351=IRCClient_numeric351; this.numeric353=IRCClient_numeric353; this.numeric391=IRCClient_numeric391; this.numeric401=IRCClient_numeric401; this.numeric402=IRCClient_numeric402; this.numeric403=IRCClient_numeric403; this.numeric411=IRCClient_numeric411; this.numeric412=IRCClient_numeric412; this.numeric440=IRCClient_numeric440; this.numeric441=IRCClient_numeric441; this.numeric442=IRCClient_numeric442; this.numeric451=IRCClient_numeric451; this.numeric461=IRCClient_numeric461; this.numeric462=IRCClient_numeric462; this.numeric481=IRCClient_numeric481; this.numeric482=IRCClient_numeric482; this.lusers=IRCClient_lusers; this.motd=IRCClient_motd; this.names=IRCClient_names; this.set_chanmode=IRCClient_set_chanmode; this.get_usermode=IRCClient_get_usermode; this.onchannel=IRCClient_onchannel; this.num_channels_on=IRCClient_num_channels_on; this.bcast_to_uchans_unique=IRCClient_bcast_to_uchans_unique; this.check_nickname=IRCClient_check_nickname; this.bcast_to_channel=IRCClient_bcast_to_channel; this.bcast_to_list=IRCClient_bcast_to_list; this.bcast_to_channel_servers=IRCClient_bcast_to_channel_servers; this.bcast_to_servers=IRCClient_bcast_to_servers; this.bcast_to_servers_raw=IRCClient_bcast_to_servers_raw; this.do_join=IRCClient_do_join; this.do_part=IRCClient_do_part; this.do_whois=IRCClient_do_whois; this.do_msg=IRCClient_do_msg; this.do_admin=IRCClient_do_admin; this.do_info=IRCClient_do_info; this.do_stats=IRCClient_do_stats; this.do_users=IRCClient_do_users; this.do_summon=IRCClient_do_summon; this.do_links=IRCClient_do_links; this.do_connect=IRCClient_do_connect; this.global=IRCClient_global; this.services_msg=IRCClient_services_msg; this.part_all=IRCClient_part_all; this.server_commands=IRCClient_server_commands; this.unregistered_commands=IRCClient_unregistered_commands; this.registered_commands=IRCClient_registered_commands; this.id=new_id; this.created=time(); this.parent=0; this.work=IRCClient_work; this.server_nick_info=IRCClient_server_nick_info; this.server_chan_info=IRCClient_server_chan_info; this.server_info=IRCClient_server_info; this.synchronize=IRCClient_synchronize; this.ip=""; this.linkparent=""; if (this.socket && local_client && do_newconn) { log(format("%04u",this.socket.descriptor) + " Accepted new connection: " + this.socket.remote_ip_address + " port " + this.socket.remote_port); this.nick = "*"; this.realname = ""; this.away = ""; this.uprefix = ""; this.mode = USERMODE_NONE; if (this.socket.remote_ip_address == "127.0.0.1") { this.hostname = servername; } else { if (debug && resolve_hostnames) went_into_hostname = time(); if (resolve_hostnames) possible_hostname = resolve_host(this.socket.remote_ip_address); if (resolve_hostnames && possible_hostname) { this.hostname = possible_hostname; if(server.client_update != undefined) server.client_update(this.socket,this.nick, this.hostname); } else this.hostname = this.socket.remote_ip_address; if (debug && resolve_hostnames) { went_into_hostname = time() - went_into_hostname; if (went_into_hostname == "NaN") went_into_hostname=0; oper_notice("DEBUG","resolve_host took " + went_into_hostname + " seconds."); } } if (this.socket.remote_ip_address) this.ip = this.socket.remote_ip_address; this.connecttime = time(); this.idletime = time(); this.talkidle = time(); this.conntype = TYPE_UNREGISTERED; this.server_notice("*** " + VERSION + " (" + serverdesc + ") Ready."); } } function IRCClient_Quit(str,do_bcast) { if (!str) str = this.nick; if (!this.server) { tmp = "QUIT :" + str; this.bcast_to_uchans_unique(tmp); for(thisChannel in this.channels) { this.rmchan(Channels[this.channels[thisChannel]]); } if (whowas_pointer >= whowas_buffer) whowas_pointer = 0; if (!this.parent) ww_serverdesc = serverdesc; else ww_serverdesc = Clients[this.parent].realname; WhoWasHistory[whowas_pointer] = new WhoWas(this.nick,this.uprefix,this.hostname,this.realname,this.servername,ww_serverdesc); whowas_pointer++; if (do_bcast && (this.conntype > TYPE_UNREGISTERED) ) this.bcast_to_servers(tmp); } else if (this.server) { this.netsplit(servername + " " + this.nick); this.bcast_to_servers_raw("SQUIT " + this.nick + " :" + str); } if((server.client_remove!=undefined) && (this.conntype > TYPE_UNREGISTERED)) server.client_remove(this.socket); if (this.local) { this.rawout("ERROR :Closing Link: [" + this.uprefix + "@" + this.hostname + "] (" + str + ")"); oper_notice("Notice","Client exiting: " + this.nick + " (" + this.uprefix + "@" + this.hostname + ") [" + str + "] [" + this.ip + "]"); this.socket.close(); } this.conntype=TYPE_EMPTY; delete this.nick; delete this.socket; delete Clients[this.id]; delete this; } function IRCClient_netsplit(ns_reason) { if (!ns_reason) ns_reason = "net.split.net.split net.split.net.split"; for (sqclient in Clients) { if (Clients[sqclient] && (Clients[sqclient] != null) && (Clients[sqclient] != undefined)) { if ((Clients[sqclient].servername == this.nick) || (Clients[sqclient].linkparent == this.nick)) Clients[sqclient].quit(ns_reason,false); } } } function IRCClient_synchronize() { this.rawout("BURST"); // warn of impending synchronization for (my_server in Clients) { if ((Clients[my_server].conntype == TYPE_SERVER) && (Clients[my_server].id != this.id) ) { this.server_info(Clients[my_server]); } } for (my_client in Clients) { if ((Clients[my_client].conntype == TYPE_USER) || (Clients[my_client].conntype == TYPE_USER_REMOTE)) { this.server_nick_info(Clients[my_client]); } } for (my_channel in Channels) { if (my_channel[0] == "#") { this.server_chan_info(Channels[my_channel]); } } oper_notice("Routing","from " + servername + ": " + this.nick + " has processed user/channel burst, sending topic burst."); for (my_channel in Channels) { if ((my_channel[0] == "#") && Channels[my_channel].topic) { var chan = Channels[my_channel]; this.rawout("TOPIC " + chan.nam + " " + chan.topicchangedby + " " + chan.topictime + " :" + chan.topic); } } this.rawout("BURST 0"); // burst completed. oper_notice("Routing","from " + servername + ": " + this.nick + " has processed topic burst (synched to network data)."); } function IRCClient_server_info(sni_server) { var realhops = parseInt(sni_server.hops)+1; this.rawout(":" + sni_server.linkparent + " SERVER " + sni_server.nick + " " + realhops + " :" + sni_server.realname); } function IRCClient_server_nick_info(sni_client) { this.rawout("NICK " + sni_client.nick + " " + sni_client.hops + " " + sni_client.created + " " + sni_client.get_usermode() + " " + sni_client.uprefix + " " + sni_client.hostname + " " + sni_client.servername + " 0 " + ip_to_int(sni_client.ip) + " :" + sni_client.realname); } function IRCClient_server_chan_info(sni_chan) { this.rawout("SJOIN " + sni_chan.created + " " + sni_chan.nam + " " + sni_chan.chanmode(true) + " :" + sni_chan.occupants()) var modecounter=0; var modestr="+"; var modeargs=""; for (aBan in sni_chan.modelist[CHANLIST_BAN]) { modecounter++; modestr += "b"; if (modeargs) modeargs += " "; modeargs += sni_chan.modelist[CHANLIST_BAN][aBan]; if (modecounter >= max_modes) { this.ircout("MODE " + sni_chan.nam + " " + modestr + " " + modeargs); modecounter=0; modestr="+"; modeargs=""; } } if (modeargs) this.ircout("MODE " + sni_chan.nam + " " + modestr + " " + modeargs); } function IRCClient_RMChan(rmchan_obj) { if (!rmchan_obj) return 0; for (k in rmchan_obj.users) { if (rmchan_obj.users[k] == this.id) delete rmchan_obj.users[k] } for(j in this.channels) { if (this.channels[j] == rmchan_obj.nam.toUpperCase()) delete this.channels[j]; } rmchan_obj.del_modelist(this.id,CHANLIST_OP); rmchan_obj.del_modelist(this.id,CHANLIST_VOICE); if (!rmchan_obj.count_users()) { delete rmchan_obj.users; delete rmchan_obj.mode; delete Channels[rmchan_obj.nam.toUpperCase()]; delete rmchan_obj.nam; } } //////////////////// Output Helper Functions //////////////////// function IRCClient_rawout(str) { if (debug) log(format("[RAW->%s]: %s",this.nick,str)); if(this.conntype && this.local) { this.socket.send(str + "\r\n"); } else if (this.conntype && this.parent) { 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; } Clients[this.parent].socket.send(str + "\r\n"); } } function IRCClient_originatorout(str,origin) { if (debug) log(format("[%s->%s]: %s",origin.nick,this.nick,str)); if((this.conntype == TYPE_USER) && this.local && !this.server) { this.socket.send(":" + origin.ircnuh + " " + str + "\r\n"); } else if (this.conntype && this.parent) { Clients[this.parent].socket.send(":" + origin.nick + " " + str + "\r\n"); } else if (this.conntype && this.server) { this.socket.send(":" + origin.nick + " " + str + "\r\n"); } } function IRCClient_ircout(str) { if (debug) log(format("[%s->%s]: %s",servername,this.nick,str)); if(this.conntype && this.local) { this.socket.send(":" + servername + " " + str + "\r\n"); } else if (this.conntype && this.parent) { Clients[this.parent].socket.send(":" + servername + " " + str + "\r\n"); } } 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_numeric351() { this.numeric(351, VERSION + " " + servername + " :" + VERSION_STR); } function IRCClient_numeric353(chan, str) { // = public @ secret * everything else if (Channels[chan].mode&CHANMODE_SECRET) var ctype_str = "@"; else var ctype_str = "="; this.numeric("353", ctype_str + " " + Channels[chan].nam + " :" + 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_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 " + count_nicks() + " users and " + count_nicks(USERMODE_INVISIBLE) + " invisible on " + count_servers(true) + " servers."); this.numeric("252", count_nicks(USERMODE_OPER) + " :IRC operators online."); this.numeric("254", count_channels() + " :channels formed."); this.numeric("255", ":I have " + count_local_nicks() + " clients and " + count_servers(false) + " servers."); this.numeric("250", ":Highest connection count: " + hcc_total + " (" + hcc_users + " clients.)"); this.server_notice(hcc_counter + " clients have connected since " + strftime("%a %b %e %H:%M:%S %Y %Z",server_uptime)); } function IRCClient_motd() { 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) { numnicks=0; tmp=""; for(thisChannel_user in Channels[chan].users) { Channel_user=Clients[Channels[chan].users[thisChannel_user]]; if (Channel_user.nick && (Channel_user.conntype != TYPE_SERVER) && ( !(Channel_user.mode&USERMODE_INVISIBLE) || (this.onchannel(chan)) ) ) { if (numnicks) tmp += " "; if (Channels[chan].ismode(Channel_user.id,CHANLIST_OP)) tmp += "@"; else if (Channels[chan].ismode(Channel_user.id,CHANLIST_VOICE)) 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); } function IRCClient_onchannel(chan_str) { if (!chan_str || !Channels[chan_str]) return 0; for (k in Channels[chan_str].users) { if (Channels[chan_str].users[k] == this.id) { return 1; } } return 0; // if we got this far, we failed. } function IRCClient_num_channels_on() { uchan_counter=0; for(tmp_uchan in this.channels) { if (this.channels[tmp_uchan]) uchan_counter++; } return uchan_counter; } //////////////////// Auxillary Functions //////////////////// function server_bcast_to_servers(str) { for(thisClient in Clients) { if ((Clients[thisClient].conntype == TYPE_SERVER) && (Clients[thisClient].hops == 1)) { Clients[thisClient].rawout(str,this); } } } function IRCClient_bcast_to_servers(str) { for(thisClient in Clients) { if ((Clients[thisClient].conntype == TYPE_SERVER) && (Clients[thisClient].id != this.parent) && (Clients[thisClient].id != this.id) && (Clients[thisClient].parent != this.id) && (Clients[thisClient].hops == 1)) { Clients[thisClient].originatorout(str,this); } } } function IRCClient_bcast_to_servers_raw(str) { for(thisClient in Clients) { if ((Clients[thisClient].conntype == TYPE_SERVER) && (Clients[thisClient].id != this.parent) && (Clients[thisClient].id != this.id) && (Clients[thisClient].parent != this.id) && (Clients[thisClient].hops == 1)) { Clients[thisClient].rawout(str); } } } function IRCClient_bcast_to_uchans_unique(str) { already_bcast = new Array(); for(thisChannel in this.channels) { userchannel=this.channels[thisChannel]; if(userchannel) { for (j in Channels[userchannel].users) { did_i_already_broadcast = false; for (alb in already_bcast) { if (already_bcast[alb] == Channels[userchannel].users[j]) { did_i_already_broadcast = true; break; } } if (!did_i_already_broadcast && (Channels[userchannel].users[j] != this.id) && Channels[userchannel].users[j] && !Clients[Channels[userchannel].users[j]].server && !Clients[Channels[userchannel].users[j]].parent ) { Clients[Channels[userchannel].users[j]].originatorout(str,this); already_bcast[already_bcast.length] = Channels[userchannel].users[j]; } } } } } function IRCClient_bcast_to_list(chan, str, bounce, list_bit) { if (chan != undefined) { for (thisUser in chan.users) { aUser = chan.users[thisUser]; if (aUser && ( aUser != this.id || (bounce) ) && ( chan.ismode(aUser,list_bit) ) ) Clients[aUser].originatorout(str,this); } } else { this.numeric401(chan.nam); } } function IRCClient_bcast_to_channel(chan_str, str, bounce) { chan_tuc = chan_str.toUpperCase(); if (Channels[chan_tuc] != undefined) { for(thisUser in Channels[chan_tuc].users) { aUser=Channels[chan_tuc].users[thisUser]; if (aUser && ( aUser != this.id || (bounce) ) && !Clients[aUser].parent && !Clients[aUser].server ) { Clients[aUser].originatorout(str,this); } } } else if (!this.parent && !this.server) { this.numeric401(chan_str); } } function IRCClient_bcast_to_channel_servers(chan_str, str) { var sent_to_servers = new Array(); var chan_tuc = chan_str.toUpperCase(); if (Channels[chan_tuc] != undefined) { for(thisUser in Channels[chan_tuc].users) { aUser=Channels[chan_tuc].users[thisUser]; if (aUser && ( Clients[aUser].parent ) && !sent_to_servers[Clients[aUser].parent] && (Clients[aUser].parent != this.parent) ) { Clients[aUser].originatorout(str,this); sent_to_servers[Clients[aUser].parent] = true; } } } } function IRCClient_check_nickname(newnick) { newnick = newnick.slice(0,max_nicklen); // If you're trying to NICK to yourself, drop silently. if(newnick.toUpperCase() == this.nick.toUpperCase()) return 0; // First, check for valid characters. regexp="^[A-Za-z\{\}\`\^\_\|\\]\\[\\\\][A-Za-z0-9\-\{\}\`\^\_\|\\]\\[\\\\]*$"; if(!newnick.match(regexp)) { if (!this.server) this.numeric("432", newnick + " :Foobar'd Nickname."); return 0; } // Second, check for existing nickname. if(searchbynick(newnick)) { if (!this.server) this.numeric("433", newnick + " :Nickname is already in use."); return 0; } // Third, match against Q:Lines for (ql in QLines) { qline_nick = QLines[ql].nick; qline_nick = qline_nick.replace(/[?]/g,"."); qline_nick = qline_nick.replace(/[*]/g,".*?"); regexp = new RegExp("^" + qline_nick + "$","i"); if(newnick.match(regexp)) { if (!this.server) 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) { if(wi.channels[i] && (!(Channels[wi.channels[i]].mode&CHANMODE_SECRET || Channels[wi.channels[i]].mode&CHANMODE_PRIVATE) || this.onchannel(wi.channels[i]))) { if (userchans) userchans += " "; if (Channels[wi.channels[i]].ismode(wi.id,CHANLIST_OP)) userchans += "@"; else if (Channels[wi.channels[i]].ismode(wi.id,CHANLIST_VOICE)) userchans += "+"; userchans += Channels[wi.channels[i]].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.realname); } 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) { if (!send_str) { this.numeric412(); return 0; } // First, make sure the nick exists. var service_nickname = searchbynick(svcnick); if (!service_nickname) { this.numeric440(svcnick); return 0; } var service_server = searchbyserver(service_nickname.servername); if (!service_server || !service_server.isulined) { 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 Clients) { var Client = Clients[globClient]; if (target[0] == "#") var global_match = Client.hostname; else // assume $ var global_match = Client.servername; if ((Client.conntype == TYPE_USER) && Client.local && match_irc_mask(global_match,global_mask) ) Client.originatorout(global_str,this); } global_str = ":" + this.nick + " " + global_str; if(this.parent) Clients[this.parent].bcast_to_servers_raw(global_str); else server_bcast_to_servers(global_str); return 1; } function IRCClient_do_msg(target,type_str,send_str) { if ((target[0] == "$") && (this.mode&USERMODE_OPER)) return this.global(target,type_str,send_str); send_to_list = -1; if (target[0] == "@" && ( (target[1] == "#") || target[1] == "&") ) { send_to_list = CHANLIST_OP; target = target.slice(1); } else if (target[0]=="+" && ((target[1] == "#")|| target[1] == "&")) { send_to_list = CHANLIST_VOICE; target = target.slice(1); } if ((target[0] == "#") || (target[0] == "&")) { chan = searchbychannel(target); if (!chan) { // check to see if it's a #*hostmask* oper message if ((target[0] == "#") && (this.mode&USERMODE_OPER)) { return this.global(target,type_str,send_str); } else { this.numeric401(target); return 0; } } if ( (chan.mode&CHANMODE_NOOUTSIDE) && !this.onchannel(chan.nam.toUpperCase())) { this.numeric(404, chan.nam + " :Cannot send to channel (+n: no outside messages)"); return 0; } if ( (chan.mode&CHANMODE_MODERATED) && !chan.ismode(this.id,CHANLIST_VOICE) && !chan.ismode(this.id,CHANLIST_OP) ) { this.numeric(404, chan.nam + " :Cannot send to channel (+m: moderated)"); return 0; } if (chan.isbanned(this.ircnuh) && !chan.ismode(this.id,CHANLIST_VOICE) && !chan.ismode(this.id,CHANLIST_OP) ) { this.numeric(404, chan.nam + " :Cannot send to channel (+b: you're banned!)"); return 0; } if(send_to_list == -1) { str = type_str +" "+ chan.nam +" :"+ send_str; this.bcast_to_channel(chan.nam, str, false); this.bcast_to_channel_servers(chan.nam, str); } else { if (send_to_list == CHANLIST_OP) prefix_chr="@"; else if (send_to_list == CHANLIST_VOICE) prefix_chr="+"; 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.nam, 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; } target_socket = searchbynick(real_target); 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; str = type_str + " " + target + " :" + send_str; target_socket.originatorout(str,this); if (target_socket.away && (type_str == "PRIVMSG") && !this.server) this.numeric(301, target_socket.nick + " :" + target_socket.away); } else { this.numeric401(target); return 0; } } return 1; } function IRCClient_do_admin() { 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() { this.numeric(371, ":" + VERSION + " Copyright 2003 Randy Sommerfeld."); this.numeric(371, ":" + system.version_notice + " " + system.copyright + "."); this.numeric(371, ": "); this.numeric(371, ":--- A big thanks to the following for their assistance: ---"); this.numeric(371, ": Deuce: Hacking and OOP conversions."); this.numeric(371, ":DigitalMan: Additional hacking and API stuff."); this.numeric(371, ": "); this.numeric(371, ":Running on " + system.os_version); this.numeric(371, ":Compiled with " + system.compiled_with + " at " + system.compiled_when); this.numeric(371, ":Socket Library: " + system.socket_lib); this.numeric(371, ":Message Base Library: " + system.msgbase_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, ": "); this.numeric(371, ":This program is free software; you can redistribute it and/or modify"); this.numeric(371, ":it under the terms of the GNU General Public License as published by"); this.numeric(371, ":the Free Software Foundation; either version 2 of the License, or"); this.numeric(371, ":(at your option) any later version."); this.numeric(371, ": "); this.numeric(371, ":This program is distributed in the hope that it will be useful,"); this.numeric(371, ":but WITHOUT ANY WARRANTY; without even the implied warranty of"); this.numeric(371, ":MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the"); this.numeric(371, ":GNU General Public License for more details:"); this.numeric(371, ":http://www.gnu.org/licenses/gpl.txt"); this.numeric(374, ":End of /INFO list."); } function IRCClient_do_stats(statschar) { switch(statschar[0]) { case "C": case "c": for (cline in CLines) { if(CLines[cline].port) var cline_port = CLines[cline].port; else var 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": for (iline in ILines) { if (!ILines[iline].port) var my_port = "*"; else var 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": case "l": this.numeric(211,"<linkname> <sendq> <sentmessages> <sentbytes> <receivedmessages> <receivedbytes> <timeopen>"); break; case "M": case "m": this.numeric(212,"<command> <count>"); 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 upsecs=time() - server_uptime; var updays=upsecs / 86400; if (updays < 1) updays=0; var uphours=(upsecs-(updays*86400))/3600; if (uphours < 1) uphours=0; var upmins=((upsecs-(updays*86400))-(uphours*3600))/60; if (upmins < 1) upmins=0; var upsec=(((upsecs-(updays*86400))-(uphours*3600))-(upmins*60)); this.numeric(242,":Server Up " + updays + " days " + uphours + ":" + upmins + ":" + upsec); break; case "Y": case "y": for (thisYL in YLines) { var 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() { this.numeric(392,':UserID Terminal Host'); var usersshown=0; for(node in system.node_list) { if(system.node_list[node].status == NODE_INUSE) { var 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) { // Check if exists. var usernum = system.matchuser(summon_user); if(!usernum) this.numeric(444,":No such user."); else { // Check if logged in var isonline = 0; for(node in system.node_list) { if(system.node_list[node].status == NODE_INUSE && system.node_list[node].useron == usernum) isonline=1; } if(!isonline) this.numeric(444,":User not logged in."); else { // var summon_message=ircstring(cmdline); // if(summon_message != '') // var summon_message=' ('+summon_message+')'; // system.put_telegram('^G^G'+usernum,this.nick+' is summoning you to IRC chat'+summon_message+'\r\n'); 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) var mask = "*"; for(thisServer in Clients) { var Server=Clients[thisServer]; if (Server && (Server.conntype == TYPE_SERVER) && match_irc_mask(Server.nick,mask) ) this.numeric(364, Server.nick + " " + Server.linkparent + " :" + Server.hops + " " + Server.realname); } if (match_irc_mask(servername,mask)) this.numeric(364, servername + " " + servername + " :0 " + serverdesc); this.numeric(365, mask + " :End of /LINKS list."); } function IRCClient_do_connect(con_server,con_port) { var con_cline = ""; for (ccl in CLines) { if (match_irc_mask(CLines[ccl].servername,con_server) || match_irc_mask(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 con_type = "Local"; if (this.parent) con_type = "Remote"; oper_notice("Routing","from " + servername + ": " + con_type + " CONNECT " + con_cline.servername + " " + con_port + " from " + this.nick + "[" + this.uprefix + "@" + this.hostname + "]"); connect_to_server(con_cline,con_port); return 1; } function IRCClient_do_join(chan_name,join_key) { if((chan_name[0] != "#") && (chan_name[0] != "&") && !this.parent) { this.numeric403(chan_name); return 0; } for (theChar in chan_name) { var theChar_code = chan_name[theChar].charCodeAt(0); if ((theChar_code <= 32) || (theChar_code == 44) || (chan_name[theChar].charCodeAt(0) == 160)) { if (!this.parent) this.numeric(479, chan_name + " :Channel name contains illegal characters."); return 0; } } if (this.onchannel(chan_name.toUpperCase())) return 0; if ((this.num_channels_on() >= max_user_chans) && !this.parent) { this.numeric("405", chan_name + " :You have joined too many channels."); return 0; } var chan = chan_name.toUpperCase().slice(0,max_chanlen); if (Channels[chan] != undefined) { if (!this.parent) { if ((Channels[chan].mode&CHANMODE_INVITE) && (chan != this.invited)) { this.numeric("473", Channels[chan].nam + " :Cannot join channel (+i: invite only)"); return 0; } if ((Channels[chan].mode&CHANMODE_LIMIT) && (Channels[chan].count_users() >= Channels[chan].arg[CHANMODE_LIMIT])) { this.numeric("471", Channels[chan].nam + " :Cannot join channel (+l: channel is full)"); return 0; } if ((Channels[chan].mode&CHANMODE_KEY) && (Channels[chan].arg[CHANMODE_KEY] != join_key)) { this.numeric("475", Channels[chan].nam + " :Cannot join channel (+k: key required)"); return 0; } if (Channels[chan].isbanned(this.ircnuh) && (chan != this.invited) ) { this.numeric("474", Channels[chan].nam + " :Cannot join channel (+b: you're banned!)"); return 0; } } // add to existing channel Channels[chan].users.push(this.id); var str="JOIN :" + Channels[chan].nam; if (this.parent) this.bcast_to_channel(Channels[chan].nam, str, false); else this.bcast_to_channel(Channels[chan].nam, str, true); if (chan_name[0] != "&") server_bcast_to_servers(":" + this.nick + " SJOIN " + Channels[chan].created + " " + Channels[chan].nam); } else { // create a new channel Channels[chan]=new Channel(chan); Channels[chan].nam=chan_name.slice(0,max_chanlen); Channels[chan].mode=CHANMODE_NONE; Channels[chan].topic=""; Channels[chan].created=time(); Channels[chan].users = new Array(); Channels[chan].users[0] = this.id; Channels[chan].modelist[CHANLIST_BAN] = new Array(); Channels[chan].modelist[CHANLIST_VOICE] = new Array(); Channels[chan].modelist[CHANLIST_OP] = new Array(); Channels[chan].modelist[CHANLIST_OP].push(this.id); str="JOIN :" + chan_name; this.originatorout(str,this); if (chan_name[0] != "&") server_bcast_to_servers(":" + servername + " SJOIN " + Channels[chan].created + " " + Channels[chan].nam + " " + Channels[chan].chanmode() + " :@" + this.nick); } if (this.invited.toUpperCase() == Channels[chan].nam.toUpperCase()) this.invited = ""; this.channels.push(chan); if (!this.parent) { this.names(chan); this.numeric(366, Channels[chan].nam + " :End of /NAMES list."); } return 1; // success } function IRCClient_do_part(chan_name) { if((chan_name[0] != "#") && (chan_name[0] != "&") && !this.parent) { this.numeric403(chan_name); return 0; } var chan = chan_name.toUpperCase(); if (Channels[chan] != undefined) { if (this.onchannel(chan)) { var str = "PART " + Channels[chan].nam; if (this.parent) this.bcast_to_channel(Channels[chan].nam, str, false); else this.bcast_to_channel(Channels[chan].nam, str, true); this.rmchan(Channels[chan]); if (chan_name[0] != "&") this.bcast_to_servers(str); } else if (!this.parent) { this.numeric442(chan_name); } } else if (!this.parent) { this.numeric403(chan_name); } } function IRCClient_part_all() { for(thisChannel in this.channels) { partingChannel=this.channels[thisChannel]; this.do_part(Channels[partingChannel].nam); } } function IRCClient_get_usermode() { var tmp_mode = "+"; if (this.mode&USERMODE_INVISIBLE) tmp_mode += "i"; if (this.mode&USERMODE_OPER) tmp_mode += "o"; return tmp_mode; } function IRCClient_set_chanmode(chan,modeline,bounce_modes) { if (!chan || !modeline) return; cm_args = modeline.split(' '); add=true; addbits=CHANMODE_NONE; delbits=CHANMODE_NONE; state_arg = new Array(); state_arg[CHANMODE_KEY] = ""; state_arg[CHANMODE_LIMIT] = ""; chan_tmplist=new Array(); chan_tmplist[CHANLIST_OP]=new Array(); chan_tmplist[CHANLIST_DEOP]=new Array(); chan_tmplist[CHANLIST_VOICE]=new Array(); chan_tmplist[CHANLIST_DEVOICE]=new Array(); chan_tmplist[CHANLIST_BAN]=new Array(); chan_tmplist[CHANLIST_UNBAN]=new Array(); mode_counter=0; mode_args_counter=1; // start counting at the args, not the modestring for (modechar in cm_args[0]) { mode_counter++; switch (cm_args[0][modechar]) { case "+": add=true; mode_counter--; break; case "-": add=false; mode_counter--; break; case "b": if(add && (cm_args.length <= mode_args_counter)) { addbits|=CHANMODE_BAN; // list bans break; } this.tweaktmpmodelist(CHANLIST_BAN,CHANLIST_UNBAN,chan); mode_args_counter++; break; case "i": this.tweaktmpmode(CHANMODE_INVITE,chan); break; case "k": if(cm_args.length > mode_args_counter) { this.tweaktmpmode(CHANMODE_KEY,chan); state_arg[CHANMODE_KEY]=cm_args[mode_args_counter]; mode_args_counter++; } break; case "l": if (add && (cm_args.length > mode_args_counter)) { this.tweaktmpmode(CHANMODE_LIMIT,chan); regexp = "^[0-9]{1,4}$"; if(cm_args[mode_args_counter].match(regexp)) state_arg[CHANMODE_LIMIT]=cm_args[mode_args_counter]; mode_args_counter++; } else if (!add) { this.tweaktmpmode(CHANMODE_LIMIT,chan); if (cm_args.length > mode_args_counter) mode_args_counter++; } break; case "m": this.tweaktmpmode(CHANMODE_MODERATED,chan); break; case "n": this.tweaktmpmode(CHANMODE_NOOUTSIDE,chan); break; case "o": if (cm_args.length <= mode_args_counter) break; this.tweaktmpmodelist(CHANLIST_OP,CHANLIST_DEOP,chan); mode_args_counter++; break; case "p": if( (add && !(chan.mode&CHANMODE_SECRET) || (delbits&CHANMODE_SECRET) ) || (!add) ) this.tweaktmpmode(CHANMODE_PRIVATE,chan); break; case "s": if( (add && !(chan.mode&CHANMODE_PRIVATE) || (delbits&CHANMODE_PRIVATE) ) || (!add) ) this.tweaktmpmode(CHANMODE_SECRET,chan); break; case "t": this.tweaktmpmode(CHANMODE_TOPIC,chan); break; case "v": if (cm_args.length <= mode_args_counter) break; this.tweaktmpmodelist(CHANLIST_VOICE,CHANLIST_DEVOICE,chan); mode_args_counter++; break; default: if ((!this.parent) && (!this.server)) this.numeric("472", cm_args[0][modechar] + " :is unknown mode char to me."); mode_counter--; break; } if (mode_counter == max_modes) break; } addmodes = ""; delmodes = ""; addmodeargs = ""; delmodeargs = ""; // If we're bouncing modes, traverse our side of what the modes look // like and remove any modes not mentioned by what was passed to the // function. Or, clear any ops, voiced members, or bans on the 'bad' // side of the network sync. FIXME: Bans get synchronized later. if (bounce_modes) { for (cm in MODE) { if (MODE[cm].state && (chan.mode&cm) && !(addbits&cm)) { delbits |= cm; } else if (MODE[cm].list) { for (member in chan.modelist[MODE[cm].list]) { delmodes += MODE[cm].modechar; delmodeargs += " " + Clients[chan.modelist[MODE[cm].list][member]].nick; chan.del_modelist(chan.modelist[MODE[cm].list][member],MODE[cm].list); } } } } // Now we run through all the mode toggles and construct our lists for // later display. We also play with the channel bit switches here. for (cm in MODE) { if (MODE[cm].state) { if ((cm&CHANMODE_KEY) && (addbits&CHANMODE_KEY) && state_arg[cm] && chan.arg[cm] && !this.server && !this.parent && !bounce_modes) { this.numeric(467, chan.nam + " :Channel key already set."); } else if ((addbits&cm) && !(chan.mode&cm)) { addmodes += MODE[cm].modechar; chan.mode |= cm; if (MODE[cm].args && MODE[cm].state) { addmodeargs += " " + state_arg[cm]; chan.arg[cm] = state_arg[cm]; } } else if ((delbits&cm) && (chan.mode&cm)) { delmodes += MODE[cm].modechar; chan.mode &= ~cm; if (MODE[cm].args && MODE[cm].state) { delmodeargs += " " + state_arg[cm]; chan.arg[cm] = ""; } } } } // This is a special case, if +b was passed to us without arguments, // we simply display a list of bans on the channel. if (addbits&CHANMODE_BAN) { for (the_ban in chan.modelist[CHANLIST_BAN]) { this.numeric(367, chan.nam + " " + chan.modelist[CHANLIST_BAN][the_ban] + " " + chan.bancreator[the_ban] + " " + chan.bantime[the_ban]); } this.numeric(368, chan.nam + " :End of Channel Ban List."); } // Now we play with the channel lists by adding or removing what was // given to us on the modeline. this.affect_mode_list(CHANLIST_OP,chan) this.affect_mode_list(CHANLIST_VOICE,chan); this.affect_mode_list(CHANLIST_BAN,chan); if (!addmodes && !delmodes) return 0; final_modestr = ""; if (addmodes) final_modestr += "+" + addmodes; if (delmodes) final_modestr += "-" + delmodes; if (addmodeargs) final_modestr += addmodeargs; if (delmodeargs) final_modestr += delmodeargs; var final_args = final_modestr.split(' '); var arg_counter = 0; var mode_counter = 0; var mode_output = ""; var f_mode_args = ""; for (marg in final_args[0]) { switch (final_args[0][marg]) { case "+": mode_output += "+"; add = true; break; case "-": mode_output += "-"; add = false; break; case "l": if (!add) { mode_counter++; mode_output += final_args[0][marg]; break; } case "b": // only modes with arguments case "k": case "l": case "o": case "v": arg_counter++; f_mode_args += " " + final_args[arg_counter]; default: mode_counter++; mode_output += final_args[0][marg]; break; } if (mode_counter >= max_modes) { var str = "MODE " + chan.nam + " " + mode_output + f_mode_args; if (!this.server) this.bcast_to_channel(chan.nam, str, true); else this.bcast_to_channel(chan.nam, str, false); if (chan.nam[0] != "&") this.bcast_to_servers(str); if (add) mode_output = "+"; else mode_output = "-"; f_mode_args = ""; } } if (mode_output.length > 1) { str = "MODE " + chan.nam + " " + mode_output + f_mode_args; if (!this.server) this.bcast_to_channel(chan.nam, str, true); else this.bcast_to_channel(chan.nam, str, false); if (chan.nam[0] != "&") this.bcast_to_servers(str); } return 1; } function IRCClient_setusermode(modestr) { if (!modestr) return 0; add=true; unknown_mode=false; addbits=USERMODE_NONE; delbits=USERMODE_NONE; for (modechar in modestr) { switch (modestr[modechar]) { case "+": add=true; break; case "-": add=false; break; case "i": if(add) { addbits|=USERMODE_INVISIBLE; delbits&=~USERMODE_INVISIBLE; } else { addbits&=~USERMODE_INVISIBLE; delbits|=USERMODE_INVISIBLE; } break; case "o": // Allow +o only by servers or non-local users. if (add && (this.parent) && Clients[this.parent].hub) { addbits|=USERMODE_OPER; delbits&=~USERMODE_OPER; } if(!add) { addbits&=~USERMODE_OPER; delbits|=USERMODE_OPER; } break; default: if (!unknown_mode && !this.parent) { this.numeric("501", ":Unknown MODE flag"); unknown_mode=true; } break; } } addmodes = ""; delmodes = ""; if ((addbits&USERMODE_INVISIBLE) && !(this.mode&USERMODE_INVISIBLE)) { addmodes += "i"; this.mode |= USERMODE_INVISIBLE; } else if ((delbits&USERMODE_INVISIBLE) && (this.mode&USERMODE_INVISIBLE)) { delmodes += "i"; this.mode &= ~USERMODE_INVISIBLE; } if ((addbits&USERMODE_OPER) && !(this.mode&USERMODE_OPER)) { addmodes += "o"; this.mode |= USERMODE_OPER; } else if ((delbits&USERMODE_OPER) && (this.mode&USERMODE_OPER)) { delmodes += "o"; this.mode &= ~USERMODE_OPER; } if (!addmodes && !delmodes) return 0; final_modestr = ""; if (addmodes) final_modestr += "+" + addmodes; if (delmodes) final_modestr += "-" + delmodes; if (!this.parent) { str = "MODE " + this.nick + " " + final_modestr; this.originatorout(str,this); this.bcast_to_servers(str); } return 1; } function IRCClient_affect_mode_list(list_bit,chan) { for (x=list_bit;x<=(list_bit+1);x++) { for (tmp_index in chan_tmplist[x]) { if (list_bit >= CHANLIST_BAN) { if (x == CHANLIST_BAN) { var set_ban = create_ban_mask(chan_tmplist[x][tmp_index]); if (chan.count_modelist(CHANLIST_BAN) >= max_bans) { this.numeric(478, chan.nam + " " + set_ban + " :Cannot add ban, channel's ban list is full."); } else if (set_ban && !chan.isbanned(set_ban)) { addmodes += "b"; addmodeargs += " " + set_ban; var banid = chan.add_modelist(set_ban,CHANLIST_BAN); chan.bantime[banid] = time(); chan.bancreator[banid] = this.ircnuh; } } else if (x == CHANLIST_UNBAN) { for (ban in chan.modelist[CHANLIST_BAN]) { if (chan_tmplist[CHANLIST_UNBAN][tmp_index].toUpperCase() == chan.modelist[CHANLIST_BAN][ban].toUpperCase()) { delmodes += "b"; delmodeargs += " " + chan_tmplist[CHANLIST_UNBAN][tmp_index]; var banid = chan.del_modelist(chan_tmplist[CHANLIST_UNBAN][tmp_index],CHANLIST_BAN); delete chan.bantime[banid]; delete chan.bancreator[banid]; } } } } else { var tmp_nick = searchbynick(chan_tmplist[x][tmp_index]); if (!tmp_nick) tmp_nick = searchbynick(search_nickbuf(chan_tmplist[x][tmp_index])); if (tmp_nick) { // FIXME: check for user existing on channel? if ((x == list_bit) && (!chan.ismode(tmp_nick.id,list_bit))) { addmodes += MODECHAR[list_bit]; addmodeargs += " " + tmp_nick.nick; chan.add_modelist(tmp_nick.id,x); } else if (chan.ismode(tmp_nick.id,list_bit)) { delmodes += MODECHAR[list_bit]; delmodeargs += " " + tmp_nick.nick; chan.del_modelist(tmp_nick.id,list_bit); } } else { this.numeric401(chan_tmplist[x][tmp_index]); } } } } } //////////////////// Command Parsers //////////////////// // Unregistered users are ConnType 1 function IRCClient_unregistered_commands(command, cmdline) { if (command.match(/^[0-9]+/)) return 0; // we ignore all numerics from unregistered clients. cmd=cmdline.split(' '); switch(command) { case "CAPAB": break; // silently ignore for now. case "NICK": if (!cmd[1]) { this.numeric(431, ":No nickname given."); break; } the_nick = ircstring(cmd[1]).slice(0,max_nicklen); if (this.check_nickname(the_nick)) this.nick = the_nick; break; case "PASS": if (!cmd[1]) break; this.password = cmd[1]; break; case "PING": if (!cmd[1]) { this.numeric461(command); break; } this.ircout("PONG " + servername + " :" + ircstring(cmdline)); break; case "PONG": this.pinged = false; break; case "SERVER": if (this.nick != "*") { this.numeric462(); break; } if (!cmd[3]) { this.numeric461("SERVER"); break; } var this_nline = 0; for (nl in NLines) { if ((NLines[nl].password == this.password) && (NLines[nl].servername == cmd[1])) { this_nline = NLines[nl]; break; } else if ((NLines[nl].flags&NLINE_CHECK_QWKPASSWD) && match_irc_mask(cmd[1],NLines[nl].servername)) { var qwkid = cmd[1].slice(0,cmd[1].indexOf(".")).toUpperCase(); var usernum = system.matchuser(qwkid); var bbsuser = new User(usernum); if ((this.password.toUpperCase() == bbsuser.security.password) && (bbsuser.security.restrictions&UFLAG_Q) ) { this_nline = NLines[nl]; break; } } } if (!this_nline) { this.quit("ERROR: Server not configured."); return 0; } if (searchbyserver(cmd[1])) { this.quit("ERROR: Server already exists."); return 0; } this.nick = cmd[1].toLowerCase(); this.hops = cmd[2]; this.realname = ircstring(cmdline); this.server = true; this.conntype = TYPE_SERVER; this.linkparent = servername; this.parent = this.id; this.flags = this_nline.flags; for (hl in HLines) { if (HLines[hl].servername.toUpperCase() == this.nick.toUpperCase()) { this.hub = true; break; } } break; case "USER": if (this.server) { this.numeric462(); break; } if (!cmd[4]) { this.numeric461("USER"); break; } this.realname = ircstring(cmdline).slice(0,50); var unreg_username = parse_username(cmd[1]); this.uprefix = "~" + unreg_username; break; case "QUIT": this.quit(ircstring(cmdline),true); return; default: this.numeric451(); break; } if (this.uprefix && isklined(this.uprefix + "@" + this.hostname)) { this.numeric(465, ":You've been K:Lined from this server."); this.quit("You've been K:Lined from this server."); return 0; } if (this.password && (unreg_username || (this.nick != "*")) && !this.server) { if (unreg_username) var usernum = system.matchuser(unreg_username); if (!usernum && (this.nick != "*")) var usernum = system.matchuser(this.nick); if (usernum) { bbsuser = new User(usernum); if (this.password.toUpperCase() == bbsuser.security.password) this.uprefix = parse_username(bbsuser.handle).toLowerCase().slice(0,10); } } if ( (count_local_nicks() + count_servers(false)) > hcc_total) hcc_total = count_local_nicks() + count_servers(false); if (count_local_nicks() > hcc_users) hcc_users = count_local_nicks(); if (this.realname && this.uprefix && (this.nick != "*") && !this.server) { // Check for a valid I:Line. this.ircclass = this.searchbyiline(); if (!this.ircclass) { this.quit("You are not authorized to use this server."); return 0; } // We meet registration criteria. Continue. hcc_counter++; this.conntype = TYPE_USER; this.numeric("001", ":Welcome to the Synchronet IRC Service, " + this.ircnuh); this.numeric("002", ":Your host is " + servername + ", running " + VERSION); this.numeric("003", ":This server was created " + strftime("%a %b %e %Y at %H:%M:%S %Z",server_uptime)); this.numeric("004", servername + " " + VERSION + " oi biklmnopstv"); this.numeric("005", "MODES=" + max_modes + " MAXCHANNELS=" + max_user_chans + " CHANNELLEN=" + max_chanlen + " MAXBANS=" + max_bans + " NICKLEN=" + max_nicklen + " TOPICLEN=" + max_topiclen + " KICKLEN=" + max_kicklen + " CHANTYPES=#& PREFIX=(ov)@+ NETWORK=Synchronet CASEMAPPING=ascii CHANMODES=b,k,l,imnpst STATUSMSG=@+ :are available on this server."); this.lusers(); this.motd(); oper_notice("Notice","Client connecting: " + this.nick + " (" + this.uprefix + "@" + this.hostname + ") [" + this.socket.remote_ip_address + "] {1}"); if (server.client_update != undefined) server.client_update(this.socket, this.nick, this.hostname); server_bcast_to_servers("NICK " + this.nick + " 1 " + this.created + " " + this.get_usermode() + " " + this.uprefix + " " + this.hostname + " " + servername + " 0 " + ip_to_int(this.ip) + " :" + this.realname); /// Otherwise, it's a server trying to connect. } else if (this.nick.match("[.]") && this.hops && this.realname && this.server && (this.conntype == TYPE_SERVER)) { hcc_counter++; oper_notice("Routing","Link with " + this.nick + "[unknown@" + this.hostname + "] established, states: TS"); if (server.client_update != undefined) server.client_update(this.socket, this.nick, this.hostname); if (!this.sentps) { for (cl in CLines) { if(CLines[cl].servername == this.nick) { this.rawout("PASS " + CLines[cl].password + " :TS"); break; } } this.rawout("CAPAB " + server_capab); this.rawout("SERVER " + servername + " 1 :" + serverdesc); this.sentps = true; } this.bcast_to_servers_raw(":" + servername + " SERVER " + this.nick + " 2 :" + this.realname); this.synchronize(); } } // Registered users are ConnType 2 function IRCClient_registered_commands(command, cmdline) { if (command.match(/^[0-9]+/)) return 0; // ignore numerics from clients. cmd=cmdline.split(' '); switch(command) { case "ADMIN": if (cmd[1]) { if (cmd[1][0] == ":") cmd[1] = cmd[1].slice(1); var dest_server = searchbyserver(cmd[1]); if (!dest_server) { this.numeric402(cmd[1]); break; } if (dest_server != -1) { dest_server.rawout(":" + this.nick + " ADMIN :" + dest_server.nick); break; } } this.do_admin(); break; case "AWAY": if (cmd[1] && (cmd[1] != ":") ) { this.away=ircstring(cmdline).slice(0,80); this.numeric("306", ":You have been marked as being away."); this.bcast_to_servers("AWAY :" + this.away); } else { this.away=""; this.numeric("305", ":You are no longer marked as being away."); this.bcast_to_servers("AWAY"); } break; case "CONNECT": if (!(this.mode&USERMODE_OPER)) { this.numeric481(); break; } if (!cmd[1]) { this.numeric461("CONNECT"); break; } if (cmd[3]) { var dest_server = searchbyserver(cmd[3]); if (!dest_server) { this.numeric402(cmd[3]); break; } else if (dest_server != -1) { dest_server.rawout(":" + this.nick + " CONNECT " + cmd[1] + " " + cmd[2] + " " + dest_server.nick); break; } } this.do_connect(cmd[1],cmd[2]); break; case "DEBUG": if (!((this.mode&USERMODE_OPER) && (this.flags&OLINE_CAN_DEBUG))) { this.numeric481(); break; } if (!cmd[1]) { this.server_notice("Usage:"); this.server_notice(" DEBUG D - Toggle DEBUG mode on/off"); this.server_notice(" DEBUG Y <val> - Set yield frequency to <val>"); break; } switch (cmd[1][0].toUpperCase()) { case "D": if (debug) { debug=false; oper_notice("Notice","Debug mode disabled by " + this.ircnuh); log("!NOTICE debug mode disabled by " + this.ircnuh); } else { debug=true; oper_notice("Notice","Debug mode enabled by " + this.ircnuh); log("!NOTICE debug mode enabled by " + this.ircnuh); } break; case "Y": if (cmd[2]) { oper_notice("Notice","branch.yield_freq set to " + cmd[2] + " by " + this.ircnuh); branch.yield_freq = parseInt(cmd[2]); } break; default: break; } break; case "EVAL": /* Evaluate a JavaScript expression */ if (!((this.mode&USERMODE_OPER) && (this.flags&OLINE_CAN_DEBUG))) { this.numeric481(); break; } cmd.shift(); var exp = cmd.join(' '); /* expression */ this.server_notice("Evaluating: " + exp); try { this.server_notice("Result: " + eval(exp)); } catch(e) { this.server_notice("!" + e); } break; case "INFO": if (cmd[1]) { if (cmd[1][0] == ":") cmd[1] = cmd[1].slice(1); var dest_server = searchbyserver(cmd[1]); if (!dest_server) { this.numeric402(cmd[1]); break; } if (dest_server != -1) { dest_server.rawout(":" + this.nick + " INFO :" + dest_server.nick); break; } } this.do_info(); break; case "INVITE": if (!cmd[2]) { this.numeric461("INVITE"); break; } chanid = searchbychannel(cmd[2]); if (!chanid) { this.numeric403(cmd[2]); break; } if (!chanid.ismode(this.id,CHANLIST_OP)) { this.numeric482(chanid.nam); break; } nickid = searchbynick(cmd[1]); if (!nickid) { this.numeric401(cmd[1]); break; } if (nickid.onchannel(chanid.nam.toUpperCase())) { this.numeric(443, nickid.nick + " " + chanid.nam + " :is already on channel."); break; } this.numeric("341", nickid.nick + " " + chanid.nam); nickid.originatorout("INVITE " + nickid.nick + " :" + chanid.nam,this); nickid.invited=chanid.nam.toUpperCase(); break; case "ISON": if (!cmd[1]) break; // drop silently if (cmd[1][0] == ":") cmd[1] = cmd[1].slice(1); isonstr = ":"; for(ison in cmd) { if (ison) { ison_nick_id = searchbynick(cmd[ison]); if (ison_nick_id) { if (isonstr != ":") isonstr += " "; isonstr += ison_nick_id.nick; } } } this.numeric("303", isonstr); break; case "JOIN": if (!cmd[1]) { this.numeric461("JOIN"); break; } if (cmd[1][0] == ":") cmd[1]=cmd[1].slice(1); var the_channels = cmd[1].split(","); var the_keys = ""; if (cmd[2]) the_keys = cmd[2].split(","); var key_counter = 0; for(jchan in the_channels) { regexp = "^[0]{1,}$" // 0 is a special case. if(the_channels[jchan].match(regexp)) { this.part_all(); } else { if (Channels[the_channels[jchan].toUpperCase()] && the_keys[key_counter] && (Channels[the_channels[jchan].toUpperCase()].mode&CHANMODE_KEY)) { this.do_join(the_channels[jchan].slice(0,max_chanlen),the_keys[key_counter]) key_counter++; } else { this.do_join(the_channels[jchan].slice(0,max_chanlen),""); } } } break; case "KICK": if (!cmd[2]) { this.numeric461("KICK"); break; } chanid = searchbychannel(cmd[1]); if (!chanid) { this.numeric403(cmd[1]); break; } if (!chanid.ismode(this.id,CHANLIST_OP)) { this.numeric482(chanid.nam); break; } var nickid = searchbynick(cmd[2]); if (!nickid) { nickid = searchbynick(search_nickbuf(cmd[2])); if (!nickid) { this.numeric401(cmd[2]); break; } } if (!nickid.onchannel(chanid.nam.toUpperCase())) { this.numeric("441", nickid.nick + " " + chanid.nam + " :They aren't on that channel!"); break; } if (cmd[3]) kick_reason = ircstring(cmdline).slice(0,max_kicklen); else kick_reason = this.nick; str = "KICK " + chanid.nam + " " + nickid.nick + " :" + kick_reason; this.bcast_to_channel(chanid.nam, str, true); this.bcast_to_servers(str); nickid.rmchan(Channels[chanid.nam.toUpperCase()]); break; case "KILL": if (!(this.mode&USERMODE_OPER)) { this.numeric481(); break; } if (!cmd[2]) { this.numeric461("KILL"); break; } if (cmd[1].match(/[.]+/)) { this.numeric("483", ":You can't kill a server!"); break; } if (cmd[2] == ":") { this.numeric("461", command + " :You MUST specify a reason for /KILL."); break; } reason = ircstring(cmdline); kills = cmd[1].split(","); for(kill in kills) { target = searchbynick(kills[kill]); if (!target) target = searchbynick(search_nickbuf(kills[kill])); if (target && target.local) { target.quit("Local kill by " + this.nick + " (" + reason + ")",true); } else if (target) { server_bcast_to_servers(":" + this.nick + " KILL " + target.nick + " :" + reason); target.quit("Killed (" + this.nick + " (" + reason + "))"); } else { this.numeric401(kills[kill]); } } break; case "KLINE": if (!((this.mode&USERMODE_OPER) && (this.flags&OLINE_CAN_KLINE))) { this.numeric481(); break; } if (!cmd[2]) { this.numeric461("KLINE"); break; } kline_mask = create_ban_mask(cmd[1],true); if (!kline_mask) { this.server_notice("Invalid K:Line mask."); break; } if (isklined(kline_mask)) { this.server_notice("K:Line already exists!"); break; } KLines.push(new KLine(kline_mask,ircstring(cmdline),"k")); oper_notice("Notice", this.nick + " added temporary 99 min. k-line for [" + kline_mask + "] [0]"); scan_for_klined_clients(); break; case "UNKLINE": if (!((this.mode&USERMODE_OPER) && (this.flags&OLINE_CAN_UNKLINE))) { this.numeric481(); break; } if (!cmd[1]) { this.numeric461("UNKLINE"); break; } kline_mask = create_ban_mask(cmd[1],true); if (!kline_mask) { this.server_notice("Invalid K:Line mask."); break; } if (!isklined(kline_mask)) { this.server_notice("No such K:Line."); break; } remove_kline(kline_mask); oper_notice("Notice", this.nick + " has removed the K-Line for: [" + kline_mask + "] (1 matches)"); break; case "LINKS": if (!cmd[1]) { // * this.do_links(); break; } else if (cmd[2]) { // <remote-server> <mask> var dest_server = searchbyserver(cmd[1]); if (!dest_server) { this.numeric402(cmd[1]); break; } if (dest_server != -1) { dest_server.rawout(":" + this.nick + " LINKS " + dest_server.nick + " " + cmd[2]); break; } } else if (cmd[1]) { // <mask> this.do_links(cmd[1]); break; } break; case "LIST": // ignore args for now this.numeric("321", "Channel :Users Name"); for(chan in Channels) { if (Channels[chan].mode&CHANMODE_PRIVATE) channel_name = "Priv"; else channel_name = Channels[chan].nam if (!(Channels[chan].mode&CHANMODE_SECRET)) this.numeric(322, channel_name + " " + Channels[chan].count_users() + " :" + Channels[chan].topic); } this.numeric("323", "End of /LIST"); break; case "LUSERS": this.lusers(); break; case "MODE": if (!cmd[1]) break; // silently ignore if (!cmd[2]) { // information only if ((cmd[1][0] == "#") || (cmd[1][0] == "&")) { chan=cmd[1].toUpperCase(); if (Channels[chan] != undefined) { if(this.onchannel(Channels[chan].nam.toUpperCase())) this.numeric("324", Channels[chan].nam + " " + Channels[chan].chanmode(true)); else this.numeric("324", Channels[chan].nam + " " + Channels[chan].chanmode(false)); this.numeric("329", Channels[chan].nam + " " + Channels[chan].created); } else { this.numeric401(cmd[1]); } } else { // getting the umode if (cmd[1].toUpperCase() == this.nick.toUpperCase()) this.numeric(221, this.get_usermode()); else if (searchbynick(cmd[1])) this.numeric(502, ":Can't view mode for other users."); else this.numeric401(cmd[1]); } } else { if ((cmd[1][0] == "#") || (cmd[1][0] == "&")) { chan = searchbychannel(cmd[1]) if (!chan) { this.numeric403(cmd[1]) break; } // Not an error: MODE #channel +xyz var modeline = cmdline.slice(cmdline.indexOf(" ")+1); var modeline = modeline.slice(modeline.indexOf(" ")+1); this.set_chanmode(chan,modeline,false); } else if (cmd[1].toUpperCase() == this.nick.toUpperCase()) { this.setusermode(cmd[2]); } else { this.numeric("502", ":Can't change mode for other users."); } } break; case "MOTD": if (cmd[1]) { if (cmd[1][0] == ":") cmd[1] = cmd[1].slice(1); var dest_server = searchbyserver(cmd[1]); if (!dest_server) { this.numeric402(cmd[1]); break; } if (dest_server != -1) { dest_server.rawout(":" + this.nick + " MOTD :" + dest_server.nick); break; } } this.motd(); break; case "NAMES": if (!cmd[1]) { for(chan in Channels) { if(!(Channels[chan].mode&CHANMODE_SECRET) && !(Channels[chan].mode&CHANMODE_PRIVATE)) this.names(chan); } numnicks=0; tmp=""; for(thisClient in Clients) { Client=Clients[thisClient]; if (!Client.num_channels_on() && !(Client.mode&USERMODE_INVISIBLE) && !Client.server) { if (numnicks) tmp += " "; tmp += Client.nick; numnicks++; if (numnicks >= 59) { this.numeric(353,"* * :"+tmp); numnicks=0; tmp=""; } } } if (numnicks) this.numeric(353,"* * :"+tmp); this.numeric(366, "* :End of /NAMES list."); } else { chans=cmd[1].split(','); for (nchan in chans) { if ((chans[nchan][0] == "#") || (chans[nchan][0] == "&")) { chan=chans[nchan].toUpperCase(); if (Channels[chan] != undefined) { this.names(chan); } else { this.numeric401(chans[nchan]); break; } } else { this.numeric403(chans[nchan]); break; } } this.numeric(366, Channels[chan].nam + " :End of /NAMES list."); } break; case "NICK": if (!cmd[1]) { this.numeric(431, ":No nickname given."); break; } the_nick = ircstring(cmd[1]).slice(0,max_nicklen); if(this.check_nickname(the_nick)) { str="NICK " + the_nick; this.bcast_to_uchans_unique(str); this.originatorout(str,this); this.created = time(); this.bcast_to_servers(str + " :" + this.created); push_nickbuf(this.nick,the_nick); this.nick = the_nick; } break; case "NOTICE": if (!cmd[1]) { this.numeric411("NOTICE"); break; } if ( !cmd[2] || ( !cmd[3] && ( (cmd[2] == ":") && (ircstring(cmdline) == "") ) ) ) { this.numeric412(); break; } notice_targets = cmd[1].split(','); for (notice in notice_targets) { this.do_msg(notice_targets[notice],"NOTICE",ircstring(cmdline)); } break; case "OPER": if (!cmd[2]) { this.numeric461(command); break; } if (this.mode&USERMODE_OPER) { this.server_notice("You're already an IRC operator."); break; } oper_success=false; for (ol in OLines) { if(((cmd[1].toUpperCase() == OLines[ol].nick.toUpperCase()) && (match_irc_mask(this.uprefix + "@" + this.hostname,OLines[ol].hostmask)) && (cmd[2] == OLines[ol].password) && !(OLines[ol].flags&OLINE_CHECK_SYSPASSWD)) || ((OLines[ol].flags&OLINE_CHECK_SYSPASSWD) && system.check_syspass(cmd[2]) ) ) { oper_success=true; this.ircclass = OLines[ol].ircclass; this.flags = OLines[ol].flags; break; } } if (oper_success) { this.numeric("381", ":You are now an IRC operator."); this.mode|=USERMODE_OPER; this.rawout(":" + this.nick + " MODE " + this.nick + " +o"); oper_notice("Notice", this.nick + " (" + this.uprefix + "@" + this.hostname + ") is now operator (O)"); this.bcast_to_servers("MODE "+ this.nick +" +o"); } else { this.numeric("491", ":No O:Lines for your host. Attempt logged."); oper_notice("Notice","Failed OPER attempt by " + this.nick + " (" + this.uprefix + "@" + this.hostname + ")"); log("!WARNING " + this.ircnuh + " tried to OPER unsuccessfully!"); } break; case "PART": if (!cmd[1]) { this.numeric461(command); break; } the_channels = cmd[1].split(","); for(pchan in the_channels) { this.do_part(the_channels[pchan]); } break; case "PASS": this.numeric462(); break; case "PING": if (!cmd[1]) { this.numeric461("PING"); break; } if (cmd[2]) { var dest_server = searchbyserver(cmd[2]); if (!dest_server) { this.numeric402(cmd[2]); break; } if (dest_server != -1) { dest_server.rawout(":" + this.nick + " PING " + cmd[1] + " " + dest_server.nick); break; } } if (cmd[1][0] == ":") cmd[1] = cmd[1].slice(1); this.ircout("PONG " + servername + " :" + cmd[1]); break; case "PONG": if (cmd[2]) { var dest_server = searchbyserver(cmd[2]); if (!dest_server) { this.numeric402(cmd[2]); break; } if (dest_server != -1) { dest_server.rawout(":" + this.nick + " PONG " + cmd[1] + " " + dest_server.nick); break; } } this.pinged = false; break; case "QUIT": this.quit(ircstring(cmdline),true); break; case "PRIVMSG": if (!cmd[1]) { this.numeric411("PRIVMSG"); break; } if ( !cmd[2] || ( !cmd[3] && ( (cmd[2] == ":") && (ircstring(cmdline) == "") ) ) ) { this.numeric412(); break; } privmsg_targets = cmd[1].split(','); for (privmsg in privmsg_targets) { this.do_msg(privmsg_targets[privmsg],"PRIVMSG",ircstring(cmdline)); } this.talkidle = time(); break; case "DIE": if (!((this.mode&USERMODE_OPER) && (this.flags&OLINE_CAN_DIE))) { this.numeric481(); break; } if (diepass && !cmd[1]) { this.numeric461("DIE"); break; } else if (diepass && (cmd[1] != diepass)) { this.server_notice("Invalid DIE password."); break; } log("!ERROR! Shutting down the ircd as per " + this.ircnuh); js.terminated = true; break; case "REHASH": if (!((this.mode&USERMODE_OPER) && (this.flags&OLINE_CAN_REHASH))) { this.numeric481(); break; } this.numeric(382, this.nick + " ircd.conf :Rehashing."); oper_notice("Notice",this.nick + " is rehashing Server config file while whistling innocently"); log("!NOTICE Rehashing server config as per " + this.ircnuh); read_config_file(); break; case "RESTART": if (!((this.mode&USERMODE_OPER) && (this.flags&OLINE_CAN_RESTART))) { this.numeric481(); break; } if (restartpass && !cmd[1]) { this.numeric461("RESTART"); break; } else if (restartpass && (cmd[1] != restartpass)) { this.server_notice("Invalid RESTART password."); break; } var rs_str = "Aieeeee!!! Restarting server..."; oper_notice("Notice",rs_str); log("!WARNING " + rs_str + " per " + this.ircnuh); terminate_everything(rs_str); break; case "SQUIT": if (!(this.mode&USERMODE_OPER)) { this.numeric481(); break; } if(!cmd[1]) break; var sq_server = searchbyserver(cmd[1]); if(!sq_server) { this.numeric402(cmd[1]); break; } var reason = ircstring(cmdline); if (!reason) reason = this.nick; if (sq_server == -1) { this.quit(reason); break; } oper_notice("Routing","from " + servername + ": Received SQUIT " + cmd[1] + " from " + this.nick + "[" + this.uprefix + "@" + this.hostname + "] (" + ircstring(cmdline) + ")"); sq_server.quit(ircstring(cmdline)); break; case "STATS": if(!cmd[1]) break; if (cmd[2]) { if (cmd[2][0] == ":") cmd[2] = cmd[2].slice(1); var dest_server = searchbyserver(cmd[2]); if (!dest_server) { this.numeric402(cmd[2]); break; } if (dest_server != -1) { dest_server.rawout(":" + this.nick + " STATS " + cmd[1][0] + " :" + dest_server.nick); break; } } this.do_stats(cmd[1][0]); break; case "SUMMON": if(!cmd[1]) { this.numeric411("SUMMON"); break; } if (cmd[2]) { if (cmd[2][0] == ":") cmd[2] = cmd[2].slice(1); var dest_server = searchbyserver(cmd[1]); if (!dest_server) { this.numeric402(cmd[2]); break; } if (dest_server != -1) { dest_server.rawout(":" + this.nick + " SUMMON " + cmd[1] + " :" + dest_server.nick); break; } } this.do_summon(cmd[1]); break; case "TIME": if (cmd[1]) { if (cmd[1][0] == ":") cmd[1] = cmd[1].slice(1); var dest_server = searchbyserver(cmd[1]); if (!dest_server) { this.numeric402(cmd[1]); break; } if (dest_server != -1) { dest_server.rawout(":" + this.nick + " TIME :" + dest_server.nick); break; } } this.numeric391(); break; case "TOPIC": if (!cmd[1]) { this.numeric461("TOPIC"); break; } chanid = searchbychannel(cmd[1]); if (!chanid) { this.numeric403(cmd[1]); break; } if (!this.onchannel(chanid.nam.toUpperCase())) { this.numeric442(chanid.nam); break; } if (cmd[2]) { if (!(chanid.mode&CHANMODE_TOPIC) || chanid.ismode(this.id,CHANLIST_OP) ) { var tmp_topic = ircstring(cmdline).slice(0,max_topiclen); if (tmp_topic == chanid.topic) break; chanid.topic = tmp_topic; chanid.topictime = time(); chanid.topicchangedby = this.nick; var str = "TOPIC " + chanid.nam + " :" + chanid.topic; this.bcast_to_channel(chanid.nam.toUpperCase(), str, true); this.bcast_to_servers("TOPIC " + chanid.nam + " " + this.nick + " " + chanid.topictime + " :" + chanid.topic); } else { this.numeric482(chanid.nam); } } else { // we're just looking at one if (chanid.topic) { this.numeric("332", chanid.nam + " :" + chanid.topic); this.numeric("333", chanid.nam + " " + chanid.topicchangedby + " " + chanid.topictime); } else { this.numeric("331", chanid.nam + " :No topic is set."); } } break; case "TRACE": this.server_notice("TRACE isn't implemented yet. Sorry."); break; case "USER": this.numeric462(); break; case "USERS": if (cmd[1]) { if (cmd[1][0] == ":") cmd[1] = cmd[1].slice(1); var dest_server = searchbyserver(cmd[1]); if (!dest_server) { this.numeric402(cmd[1]); break; } if (dest_server != -1) { dest_server.rawout(":" + this.nick + " USERS :" + dest_server.nick); break; } } this.do_users(); break; case "USERHOST": if (!cmd[1]) { this.numeric461("USERHOST"); break; } uhnick = searchbynick(cmd[1]); if (uhnick) this.numeric("302", ":" + uhnick.nick + "=+" + uhnick.uprefix + "@" + uhnick.hostname); else this.numeric("302", ":"); break; case "VERSION": if (cmd[1]) { if (cmd[1][0] == ":") cmd[1] = cmd[1].slice(1); var dest_server = searchbyserver(cmd[1]); if (!dest_server) { this.numeric402(cmd[1]); break; } if (dest_server != -1) { dest_server.rawout(":" + this.nick + " VERSION :" + dest_server.nick); break; } } this.numeric351(); break; case "LOCOPS": if (!((this.mode&USERMODE_OPER) && (this.flags&OLINE_CAN_LOCOPS))) { this.numeric481(); break; } oper_notice("LocOps","from " + this.nick + ": " + ircstring(cmdline)); break; case "GLOBOPS": case "WALLOPS": // allow non-opers to wallop for assistance, perhaps. if (!cmd[1]) { this.numeric461("WALLOPS"); break; } wallopers(":" + this.ircnuh + " WALLOPS :" + ircstring(cmdline)); server_bcast_to_servers(":" + this.nick + " WALLOPS :" + ircstring(cmdline)); break; case "WHO": if (!cmd[1]) { channel="*"; } else if ((cmd[1][0] == "#") || (cmd[1][0] == "&")) { chan=cmd[1].toUpperCase(); if (Channels[chan] != undefined) { channel=Channels[chan].nam; for(i in Channels[chan].users) { if (Channels[chan].users[i]) { Client=Clients[Channels[chan].users[i]]; who_mode=""; if (Client.away) who_mode += "G"; else who_mode += "H"; if (Channels[chan].ismode(Client.id,CHANLIST_OP)) who_mode += "@"; else if (Channels[chan].ismode(Client.id,CHANLIST_VOICE)) who_mode += "+"; if (Client.mode&USERMODE_OPER) who_mode += "*"; this.numeric("352", channel + " " + Client.uprefix + " " + Client.hostname + " " + servername + " " + Client.nick + " " + who_mode + " :0 " + Client.realname); } } } else { channel="*"; } } else { channel="*"; } this.numeric("315", channel + " :End of /WHO list."); break; case "WHOIS": if (!cmd[1]) { this.numeric(431, ":No nickname given."); break; } if (cmd[2]) { var dest_server = searchbyserver(cmd[1]); if (!dest_server) { this.numeric402(cmd[1]); break; } if (dest_server != -1) { dest_server.rawout(":" + this.nick + " WHOIS " + cmd[1] + " " + dest_server.nick); break; } } var wi_nicks = cmd[1].split(','); for (wi_nick in wi_nicks) { var wi = searchbynick(wi_nicks[wi_nick]); if (wi) this.do_whois(wi); else this.numeric401(wi_nicks[wi_nick]); } this.numeric(318, wi_nicks[0]+" :End of /WHOIS list."); break; case "WHOWAS": if (!cmd[1]) { this.numeric(431, ":No nickname given."); break; } firstnick=""; for (aWhoWas=whowas_pointer;aWhoWas>=0;aWhoWas--) { if(WhoWasHistory[aWhoWas] && (WhoWasHistory[aWhoWas].nick.toUpperCase() == cmd[1].toUpperCase())) { this.numeric(314,WhoWasHistory[aWhoWas].nick + " " + WhoWasHistory[aWhoWas].uprefix + " " + WhoWasHistory[aWhoWas].host + " * :" + WhoWasHistory[aWhoWas].realname); this.numeric(312,WhoWasHistory[aWhoWas].nick + " " + WhoWasHistory[aWhoWas].server + " :" + WhoWasHistory[aWhoWas].serverdesc); if (!firstnick) firstnick = WhoWasHistory[aWhoWas].nick; } } for (aWhoWas=whowas_buffer;aWhoWas>=whowas_pointer;aWhoWas--) { if(WhoWasHistory[aWhoWas] && (WhoWasHistory[aWhoWas].nick.toUpperCase() == cmd[1].toUpperCase())) { this.numeric(314,WhoWasHistory[aWhoWas].nick + " " + WhoWasHistory[aWhoWas].uprefix + " " + WhoWasHistory[aWhoWas].host + " * :" + WhoWasHistory[aWhoWas].realname); this.numeric(312,WhoWasHistory[aWhoWas].nick + " " + WhoWasHistory[aWhoWas].server + " :" + WhoWasHistory[aWhoWas].serverdesc); if (!firstnick) firstnick = WhoWasHistory[aWhoWas].nick; } } if (!firstnick) { this.numeric(406,cmd[1] + " :There was no such nickname."); firstnick = cmd[1]; } this.numeric(369,firstnick+" :End of /WHOWAS command."); break; case "ERROR": break; // silently ignore. case "CS": case "CHANSERV": if (!cmd[1]) { this.numeric412(); break; } var str = cmdline.slice(cmdline.indexOf(" ")+1); if (str[0] == ":") str = str.slice(1); this.services_msg("ChanServ",str); break; case "NS": case "NICKSERV": if (!cmd[1]) { this.numeric412(); break; } var str = cmdline.slice(cmdline.indexOf(" ")+1); if (str[0] == ":") str = str.slice(1); this.services_msg("NickServ",str); break; case "MS": case "MEMOSERV": if (!cmd[1]) { this.numeric412(); break; } var str = cmdline.slice(cmdline.indexOf(" ")+1); if (str[0] == ":") str = str.slice(1); this.services_msg("MemoServ",str); break; case "OS": case "OPERSERV": if (!cmd[1]) { this.numeric412(); break; } var str = cmdline.slice(cmdline.indexOf(" ")+1); if (str[0] == ":") str = str.slice(1); this.services_msg("OperServ",str); break; case "IDENTIFY": if (!cmd[1]) { this.numeric412(); break; } var str = cmdline.slice(cmdline.indexOf(" ")+1); if (str[0] == ":") str = str.slice(1); if (cmd[1][1] == "#") var services_target = "ChanServ"; else var services_target = "NickServ"; this.services_msg(services_target,"IDENTIFY " + str); break; default: this.numeric("421", command + " :Unknown command."); break; } } // Server connections are ConnType 5 function IRCClient_server_commands(origin, command, cmdline) { if (origin.match(/[.]/)) { var ThisOrigin = searchbyserver(origin); var killtype = "SQUIT"; } else { var ThisOrigin = searchbynick(origin); var killtype = "KILL"; } if (!ThisOrigin) { oper_notice("Notice","Server " + this.nick + " trying to pass message for non-existent origin: " + origin); this.rawout(killtype + " " + origin + " :" + servername + " (" + origin + "(?) <- " + this.nick + ")"); return 0; } cmd=cmdline.split(' '); if (command.match(/^[0-9]+/)) { // passing on a numeric to the client if (!cmd[1]) return 0; // uh...? var destination = searchbynick(cmd[1]); if (!destination) return 0; destination.rawout(":" + ThisOrigin.nick + " " + cmdline); return 1; } switch(command) { case "GNOTICE": case "GLOBOPS": case "WALLOPS": if (!cmd[1]) break; str = ":" + origin + " WALLOPS :" + ircstring(cmdline); wallopers(str); this.bcast_to_servers_raw(str); break; case "ADMIN": if (!cmd[1]) break; if (cmd[1][0] == ":") cmd[1] = cmd[1].slice(1); if (match_irc_mask(servername, cmd[1])) { ThisOrigin.do_admin(); } else { var dest_server = searchbyserver(cmd[1]); if (!dest_server) break; dest_server.rawout(":" + ThisOrigin.nick + " ADMIN :" + dest_server.nick); } break; case "AWAY": if (!cmd[1]) ThisOrigin.away = ""; else ThisOrigin.away = ircstring(cmdline); break; case "CONNECT": if (!cmd[3]) break; if (match_irc_mask(servername, cmd[3])) { ThisOrigin.do_connect(cmd[1],cmd[2]); } else { var dest_server = searchbyserver(cmd[3]); if (!dest_server) break; dest_server.rawout(":" + ThisOrigin.nick + " CONNECT " + cmd[1] + " " + cmd[2] + " " + dest_server.nick); } break; case "ERROR": oper_notice("Notice", "ERROR from " + ThisOrigin.nick + "[(+)0@" + this.hostname + "] -- " + ircstring(cmdline)); ThisOrigin.quit(); break; case "INFO": if (!cmd[1]) break; if (cmd[1][0] == ":") cmd[1] = cmd[1].slice(1); if (match_irc_mask(servername, cmd[1])) { ThisOrigin.do_info(); } else { var dest_server = searchbyserver(cmd[1]); if (!dest_server) break; dest_server.rawout(":" + ThisOrigin.nick + " INFO :" + dest_server.nick); } break; case "INVITE": if (!cmd[2]) break; chanid = searchbychannel(cmd[2]); if (!chanid) break; if (!chanid.ismode(this.id,CHANLIST_OP)) break; nickid = searchbynick(cmd[1]); if (!nickid) break; if (nickid.onchannel(chanid.nam.toUpperCase())) break; nickid.originatorout("INVITE " + nickid.nick + " :" + chanid.nam,this); nickid.invited=chanid.nam.toUpperCase(); break; case "KICK": if (!cmd[2]) break; chanid = searchbychannel(cmd[1]); if (!chanid) break; nickid = searchbynick(cmd[2]); if (!nickid) nickid = searchbynick(search_nickbuf(cmd[2])); if (!nickid) break; if (!nickid.onchannel(chanid.nam.toUpperCase())) break; if (cmd[3]) kick_reason = ircstring(cmdline).slice(0,max_kicklen); else kick_reason = ThisOrigin.nick; str = "KICK " + chanid.nam + " " + nickid.nick + " :" + kick_reason; ThisOrigin.bcast_to_channel(chanid.nam, str, false); this.bcast_to_servers_raw(":" + ThisOrigin.nick + " " + str); nickid.rmchan(Channels[chanid.nam.toUpperCase()]); break; case "JOIN": if (!cmd[1]) break; if (cmd[1][0] == ":") cmd[1]=cmd[1].slice(1); the_channels = cmd[1].split(","); for (jchan in the_channels) { if (the_channels[jchan][0] != "#") break; ThisOrigin.do_join(the_channels[jchan].slice(0,max_chanlen),""); } break; case "SJOIN": if (!cmd[2]) break; if (cmd[2][0] != "#") break; if (cmd[3]) { var mode_args = ""; var tmp_modeargs = 0; for (tmpmc in cmd[3]) { if (cmd[3][tmpmc] == "k") { tmp_modeargs++; mode_args += cmd[3 + tmp_modeargs]; } else if (cmd[3][tmpmc] == "l") { tmp_modeargs++; mode_args += cmd[3 + tmp_modeargs]; } } if ((cmd[4] == "") && cmd[5]) tmp_modeargs++; var chan_members = ircstring(cmdline,4+tmp_modeargs).split(' '); if (chan_members == "") { oper_notice("Notice","Server " + this.nick + " trying to SJOIN empty channel " + cmd[2]); break; } } var chan = searchbychannel(cmd[2]); if (!chan) { var cn_tuc = cmd[2].toUpperCase(); Channels[cn_tuc]=new Channel(cn_tuc); var chan = Channels[cn_tuc]; chan.nam = cmd[2]; chan.created = cmd[1]; chan.topic = ""; chan.users = new Array(); chan.modelist[CHANLIST_BAN] = new Array(); chan.modelist[CHANLIST_VOICE] = new Array(); chan.modelist[CHANLIST_OP] = new Array(); chan.mode = CHANMODE_NONE; } if (cmd[3]) { if (chan.created == cmd[1]) var bounce_modes = false; else var bounce_modes = true; if (chan.created >= cmd[1]) { if (mode_args) this.set_chanmode(chan, cmd[3] + " " + mode_args, bounce_modes); else this.set_chanmode(chan, cmd[3], bounce_modes); } var num_sync_modes = 0; var push_sync_modes = "+"; var push_sync_args = ""; for (member in chan_members) { var is_op = false; var is_voice = false; if (chan_members[member][0] == "@") { is_op = true; chan_members[member] = chan_members[member].slice(1); } if (chan_members[member][0] == "+") { is_voice = true; chan_members[member] = chan_members[member].slice(1); } var member_obj = searchbynick(chan_members[member]); if (!member_obj) break; member_obj.channels.push(chan.nam.toUpperCase()); chan.users.push(member_obj.id); member_obj.bcast_to_channel(chan.nam, "JOIN " + chan.nam, false); if (chan.created >= cmd[1]) { if (is_op) { chan.modelist[CHANLIST_OP].push(member_obj.id); push_sync_modes += "o"; push_sync_args += " " + member_obj.nick; num_sync_modes++; } if (num_sync_modes >= max_modes) { this.bcast_to_channel(chan.nam, "MODE " + chan.nam + " " + push_sync_modes + push_sync_args); push_sync_modes = "+"; push_sync_args = ""; num_sync_modes = 0; } if (is_voice) { chan.modelist[CHANLIST_VOICE].push(member_obj.id); push_sync_modes += "v"; push_sync_args += " " + member_obj.nick; num_sync_modes++; } if (num_sync_modes >= max_modes) { this.bcast_to_channel(chan.nam, "MODE " + chan.nam + " " + push_sync_modes + push_sync_args); push_sync_modes = "+"; push_sync_args = ""; num_sync_modes = 0; } } } if (num_sync_modes) this.bcast_to_channel(chan.nam, "MODE " + chan.nam + " " + push_sync_modes + push_sync_args); this.bcast_to_servers_raw(":" + servername + " SJOIN " + chan.created + " " + chan.nam + " " + chan.chanmode(true) + " :" + ircstring(cmdline)) } else { ThisOrigin.channels.push(chan.nam.toUpperCase()); chan.users.push(ThisOrigin.id); ThisOrigin.bcast_to_channel(chan.nam, "JOIN " + chan.nam, false); this.bcast_to_servers_raw(":" + ThisOrigin.nick + " SJOIN " + chan.created + " " + chan.nam); } break; case "SQUIT": if (!cmd[2]) break; var sq_server = searchbyserver(cmd[1]); if (!sq_server) break; sq_server.quit(ThisOrigin.nick + " " + sq_server.nick); break; case "KILL": if (!cmd[2]) break; if (cmd[1].match(/[.]+/)) break; if (cmd[2] == ":") break; if (!this.hub) { oper_notice("Notice","Non-Hub server " + this.nick + " trying to KILL " + cmd[1] + ", ignored and SQUITing."); this.quit("Leaf servers must not global KILL. SQUIT to avoid network desync."); return 0; } var reason = ircstring(cmdline); var kills = cmd[1].split(","); for(kill in kills) { var target = searchbynick(kills[kill]); if (!target) target = searchbynick(search_nickbuf(kills[kill])); if (target) { this.bcast_to_servers_raw(":" + ThisOrigin.nick + " KILL " + target.nick + " :" + reason); target.quit("KILLED by " + ThisOrigin.nick + " (" + reason + ")",false); } } break; case "LINKS": if (!cmd[2]) break; if (match_irc_mask(servername, cmd[1])) { ThisOrigin.do_links(cmd[2]); } else { var dest_server = searchbyserver(cmd[1]); if (!dest_server) break; dest_server.rawout(":" + ThisOrigin.nick + " LINKS " + dest_server.nick + " " + cmd[2]); } break; case "MODE": if (!cmd[1]) break; // nasty kludge since we don't support per-mode TS yet. if (cmd[2].match(/^[0-9]+$/) && (cmd[1][0] == "#") ) { cmdline="MODE " + cmd[1]; for(xx=3;xx<cmd.length;xx++) { cmdline += " "; cmdline += cmd[xx]; } } if (cmd[1][0] == "#") { var chan = searchbychannel(cmd[1]) if (!chan) break; var modeline = cmdline.slice(cmdline.indexOf(" ")+1); var modeline = modeline.slice(modeline.indexOf(" ")+1); ThisOrigin.set_chanmode(chan,modeline,false); } else { // assume it's for a user ThisOrigin.setusermode(cmd[2]); } break; case "MOTD": if (!cmd[1]) break; if (cmd[1][0] == ":") cmd[1] = cmd[1].slice(1); if (match_irc_mask(servername, cmd[1])) { ThisOrigin.motd(); } else { var dest_server = searchbyserver(cmd[1]); if (!dest_server) break; dest_server.rawout(":" + ThisOrigin.nick + " MOTD :" + dest_server.nick); } break; case "NICK": if (!cmd[8] && (cmd[2][0] != ":")) break; var collide = searchbynick(cmd[1]); if ((collide) && (parseInt(collide.created) > parseInt(cmd[3]) ) && this.hub) { // Nuke our side of things, allow this newly // introduced nick to overrule. this.bcast_to_servers("KILL " + collide.nick + " :Nickname Collision."); collide.quit("Nickname Collision"); } else if (collide && !this.hub) { oper_notice("Notice","Server " + this.nick + " trying to collide nick " + collide.nick + " forwards, reversing."); // Don't collide our side of things from a leaf this.ircout("KILL " + cmd[1] + " :Inverse Nickname Collision."); // Reintroduce our nick, because the remote end // probably killed it already on our behalf. this.server_nick_info(collide); break; } if (cmd[2][0] == ":") { cmd[2] = cmd[2].slice(1); ThisOrigin.created = cmd[2]; ThisOrigin.bcast_to_uchans_unique("NICK " + cmd[1]); this.bcast_to_servers_raw(":" + origin + " NICK " + cmd[1] + " :" + cmd[2]); push_nickbuf(ThisOrigin.nick,cmd[1]); ThisOrigin.nick = cmd[1]; } else if (cmd[10]) { if (!this.hub) { if(!this.check_nickname(cmd[1])) { oper_notice("Notice","Server " + this.nick + " trying to introduce invalid nickname: " + cmd[1] + ", killed."); this.ircout("KILL " + cmd[1] + " :Bogus Nickname."); break; } cmd[2] = 1; // Force hops to 1. cmd[3] = time(); // Force TS on nick. cmd[7] = this.nick // Force server name } new_id = get_next_clientid(); Clients[new_id]=new IRCClient(-1,new_id,false,true); NewNick = Clients[new_id]; NewNick.nick = cmd[1]; NewNick.hops = cmd[2]; NewNick.created = cmd[3]; NewNick.uprefix = cmd[5]; NewNick.hostname = cmd[6]; NewNick.servername = cmd[7]; NewNick.realname = ircstring(cmdline,10); NewNick.conntype = TYPE_USER_REMOTE; NewNick.away = ""; NewNick.mode = USERMODE_NONE; NewNick.connecttime = 0; NewNick.idletime = 0; NewNick.talkidle = 0; NewNick.parent = this.id; NewNick.ip = int_to_ip(cmd[9]); NewNick.setusermode(cmd[4]); true_hops = parseInt(NewNick.hops)+1; this.bcast_to_servers_raw("NICK " + NewNick.nick + " " + true_hops + " " + NewNick.created + " " + NewNick.get_usermode() + " " + NewNick.uprefix + " " + NewNick.hostname + " " + NewNick.servername + " 0 " + cmd[9] + " :" + NewNick.realname); } break; case "NOTICE": if (!cmd[1]) break; if ( !cmd[2] || ( !cmd[3] && ( (cmd[2] == ":") && (ircstring(cmdline) == "") ) ) ) break; notice_targets = cmd[1].split(','); for (notice in notice_targets) { if (notice_targets[notice][0] != "&") ThisOrigin.do_msg(notice_targets[notice],"NOTICE",ircstring(cmdline)); } break; case "PART": if (!cmd[1]) break; the_channels = cmd[1].split(","); for(pchan in the_channels) { ThisOrigin.do_part(the_channels[pchan]); } break; case "PRIVMSG": if (!cmd[1]) break; if ( !cmd[2] || ( !cmd[3] && ( (cmd[2] == ":") && (ircstring(cmdline) == "") ) ) ) break; privmsg_targets = cmd[1].split(','); for (privmsg in privmsg_targets) { if (privmsg_targets[privmsg][0] != "&") ThisOrigin.do_msg(privmsg_targets[privmsg],"PRIVMSG",ircstring(cmdline)); } break; case "PING": if (!cmd[1]) break; if (cmd[2] && !match_irc_mask(servername, cmd[2])) { var dest_server = searchbyserver(cmd[1]); if (!dest_server) break; dest_server.rawout(":" + ThisOrigin.nick + " PING " + cmd[1] + " " + dest_server.nick); break; } if (cmd[1][0] == ":") cmd[1] = cmd[1].slice(1); this.ircout("PONG " + servername + " :" + cmd[1]); break; case "PONG": if (cmd[2]) { if (cmd[2][0] == ":") cmd[2] = cmd[2].slice(1); if (!match_irc_mask(servername, cmd[2])) { var dest_server = searchbyserver(cmd[2]); if (!dest_server) break; dest_server.rawout(":" + ThisOrigin.nick + " PONG " + cmd[1] + " " + dest_server.nick); break; } } this.pinged = false; break; case "QUIT": ThisOrigin.quit(ircstring(cmdline),true); break; case "SERVER": if (!cmd[3]) break; if ((cmd[2] == 1) && !this.realname) { this.nick = cmd[1]; this.hops = 1; this.realname = ircstring(cmdline); this.linkparent = servername; this.parent = this.id; newsrv = this; } else if (parseInt(cmd[2]) > 1) { if (this.hub) { new_id = get_next_clientid(); Clients[new_id] = new IRCClient(-1,new_id,false,false); newsrv = Clients[new_id]; newsrv.hops = cmd[2]; newsrv.nick = cmd[1]; newsrv.realname = ircstring(cmdline); newsrv.server = true; newsrv.parent = this.id; newsrv.conntype = TYPE_SERVER; newsrv.linkparent = ThisOrigin.nick; } else { oper_notice("Routing","from " + servername + ": Non-Hub link " + this.nick + " introduced " + cmd[1] + "(*)."); this.quit("Too many servers. You have no H:Line to introduce " + cmd[1] + "."); return 0; } } else { break; } this.bcast_to_servers_raw(":" + newsrv.linkparent + " SERVER " + newsrv.nick + " " + (parseInt(newsrv.hops)+1) + " :" + newsrv.realname); break; case "STATS": if (!cmd[2]) break; if (cmd[2][0] == ":") cmd[2] = cmd[2].slice(1); if (match_irc_mask(servername, cmd[2])) { ThisOrigin.do_stats(cmd[1][0]); } else { var dest_server = searchbyserver(cmd[1]); if (!dest_server) break; dest_server.rawout(":" + ThisOrigin.nick + " STATS " + cmd[1][0] + " :" + dest_server.nick); } break; case "TIME": if (!cmd[1]) break; if (cmd[1][0] == ":") cmd[1] = cmd[1].slice(1); if (match_irc_mask(servername, cmd[1])) { ThisOrigin.numeric391(); } else { var dest_server = searchbyserver(cmd[1]); if (!dest_server) break; dest_server.rawout(":" + ThisOrigin.nick + " TIME :" + dest_server.nick); } break; case "SUMMON": if (!cmd[2]) break; if (cmd[2][0] == ":") cmd[2] = cmd[2].slice(1); if (match_irc_mask(servername, cmd[2])) { ThisOrigin.do_summon(cmd[1]); } else { var dest_server = searchbyserver(cmd[1]); if (!dest_server) break; dest_server.rawout(":" + ThisOrigin.nick + " SUMMON " + cmd[1] + " :" + dest_server.nick); } break; case "TOPIC": if (!cmd[4]) break; var chan = searchbychannel(cmd[1]); if (!chan) break; var the_topic = ircstring(cmdline); if (the_topic == chan.topic) break; chan.topic = the_topic; if (this.hub) chan.topictime = cmd[3]; else chan.topictime = time(); chan.topicchangedby = cmd[2]; str = "TOPIC " + chan.nam + " :" + chan.topic; ThisOrigin.bcast_to_channel(chan.nam,str,false); this.bcast_to_servers_raw(":" + ThisOrigin.nick + " TOPIC " + chan.nam + " " + ThisOrigin.nick + " " + chan.topictime + " :" + chan.topic); break; case "USERS": if (!cmd[1]) break; if (cmd[1][0] == ":") cmd[1] = cmd[1].slice(1); if (match_irc_mask(servername, cmd[1])) { ThisOrigin.do_users(); } else { var dest_server = searchbyserver(cmd[1]); if (!dest_server) break; dest_server.rawout(":" + ThisOrigin.nick + " USERS :" + dest_server.nick); } break; case "VERSION": if (!cmd[1]) break; if (cmd[1][0] == ":") cmd[1] = cmd[1].slice(1); if (match_irc_mask(servername, cmd[1])) { // it's for us, return the message ThisOrigin.numeric351(); } else { // psst, pass it on var dest_server = searchbyserver(cmd[1]); if (!dest_server) break; // someone messed up. dest_server.rawout(":" + ThisOrigin.nick + " VERSION :" + dest_server.nick); } break; case "WHOIS": if (!cmd[2]) break; if (cmd[2][0] == ":") cmd[2] = cmd[2].slice(1); if (match_irc_mask(servername, cmd[2])) { var wi_nicks = cmd[1].split(','); for (wi_nick in wi_nicks) { var wi = searchbynick(wi_nicks[wi_nick]); if (wi) this.do_whois(wi); else this.numeric401(wi_nicks[wi_nick]); } this.numeric(318, wi_nicks[0]+" :End of /WHOIS list."); } else { var dest_server = searchbyserver(cmd[2]); if (!dest_server) break; dest_server.rawout(":" + ThisOrigin.nick + " WHOIS " + cmd[1] + " " + dest_server.nick); } break; case "AKILL": if (!cmd[6]) break; var this_uh = cmd[2] + "@" + cmd[1]; if (isklined(this_uh)) break; KLines.push(new KLine(this_uh,ircstring(cmdline),"A")); this.bcast_to_servers_raw(":" + ThisOrigin.nick + " " + cmdline); scan_for_klined_clients(); break; default: break; } } function IRCClient_work() { if (!this.socket.is_connected) { this.quit("Connection reset by peer",true); return 0; } if (this.socket.data_waiting && (cmdline=this.socket.recvline(4096,0)) ) { if(cmdline==null) return 0; // nothing to do // Allow only 512 bytes per RFC for clients, however, servers // are allowed to fill up the entire buffer for bandwidth // savings, since we trust them more. if(!this.server) cmdline=cmdline.slice(0,512); // Some (broken) clients add extra CR garbage to the // beginning (end) of a line. Fix. // Eventually we should just strip cmdline of all // invalid characters, including these and NUL (00) // among others. if ((cmdline[0] == "\r") || (cmdline[0] == "\n")) cmdline=cmdline.slice(1); if (debug) log(format("[%s<-%s]: %s",servername,this.nick,cmdline)); cmd=cmdline.split(' '); if (cmdline[0] == ":") { // Silently ignore NULL originator commands. if (!cmd[1]) return 0; // if :<originator> doesn't match nick of originating // socket, drop silently per RFC. var origin = cmd[0].slice(1); if ((this.conntype == TYPE_USER) && (origin.toUpperCase() != this.nick.toUpperCase())) return 0; var command = cmd[1].toUpperCase(); cmdline = cmdline.slice(cmdline.indexOf(" ")+1); } else { command = cmd[0].toUpperCase(); var origin = this.nick; } this.idletime = time(); if (this.conntype == TYPE_UNREGISTERED) { this.unregistered_commands(command,cmdline); } else if (this.conntype == TYPE_USER) { this.registered_commands(command,cmdline); } else if (this.conntype == TYPE_SERVER) { this.server_commands(origin,command,cmdline); } else { log("!ERROR: Client has bogus conntype!"); oper_notice("Notice","Client has bogus conntype!"); } } if (this.nick) { if ( (this.pinged) && ((time() - this.pinged) > YLines[this.ircclass].pingfreq) ) { this.pinged = false; this.quit("Ping Timeout",true); } else if (!this.pinged && ((time() - this.idletime) > YLines[this.ircclass].pingfreq)) { this.pinged = time(); this.rawout("PING :" + servername); } } } /////////////////////////////////// /////////////////////////////////// ///// End of IRCClient object ///// /////////////////////////////////// /////////////////////////////////// function Channel(nam) { this.nam=nam; this.mode=CHANMODE_NONE; this.topic=""; this.topictime=0; this.topicchangedby=""; this.arg = new Array; this.arg[CHANMODE_LIMIT] = 0; this.arg[CHANMODE_KEY] = ""; this.users=new Array; this.modelist=new Array; this.modelist[CHANLIST_OP]=new Array; this.modelist[CHANLIST_VOICE]=new Array; this.modelist[CHANLIST_BAN]=new Array; this.bantime=new Array; this.bancreator=new Array; this.created=time(); this.ismode=Channel_ismode; this.add_modelist=Channel_add_modelist; this.del_modelist=Channel_del_modelist; this.locate_on_list=Channel_locate_on_list; this.count_users=Channel_count_users; this.chanmode=Channel_chanmode; this.isbanned=Channel_isbanned; this.count_modelist=Channel_count_modelist; this.occupants=Channel_occupants; } function Channel_ismode(tmp_str,mode_bit) { if (!this.modelist[mode_bit]) return 0; for (tmp_element in this.modelist[mode_bit]) { if (this.modelist[mode_bit][tmp_element] == tmp_str) return 1; } return 0; } function Channel_locate_on_list(tmp_str,mode_bit) { for (tmp_index in this.modelist[mode_bit]) { if (this.modelist[mode_bit][tmp_index] == tmp_str) { return tmp_index; } } } function Channel_add_modelist(tmp_nickid,list_bit) { if (this.ismode(tmp_nickid,list_bit)) return 0; pushed = this.modelist[list_bit].push(tmp_nickid); return pushed-1; } function Channel_del_modelist(tmp_nickid,list_bit) { if (!this.ismode(tmp_nickid,list_bit)) return 0; delete_index = this.locate_on_list(tmp_nickid,list_bit); delete this.modelist[list_bit][delete_index]; return delete_index; } function Channel_count_users() { tmp_counter=0; for (tmp_count in this.users) { if (this.users[tmp_count]) tmp_counter++; } return tmp_counter; } function Channel_count_modelist(list_bit) { tmp_counter=0; for (tmp_count in this.modelist[list_bit]) { if (this.modelist[list_bit][tmp_count]) tmp_counter++; } return tmp_counter; } function Channel_chanmode(show_args) { tmp_mode = "+"; tmp_extras = ""; if (this.mode&CHANMODE_INVITE) tmp_mode += "i"; if (this.mode&CHANMODE_KEY) { tmp_mode += "k"; if(show_args) tmp_extras += " " + this.arg[CHANMODE_KEY]; } if (this.mode&CHANMODE_LIMIT) { tmp_mode += "l"; if(show_args) tmp_extras += " " + this.arg[CHANMODE_LIMIT]; } if (this.mode&CHANMODE_MODERATED) tmp_mode += "m"; if (this.mode&CHANMODE_NOOUTSIDE) tmp_mode += "n"; if (this.mode&CHANMODE_PRIVATE) tmp_mode += "p"; if (this.mode&CHANMODE_SECRET) tmp_mode += "s"; if (this.mode&CHANMODE_TOPIC) tmp_mode += "t"; if (!tmp_extras) tmp_extras = " "; return tmp_mode + tmp_extras; } function inverse_chanmode(bitlist) { tmp_mode = "-"; if (bitlist&CHANMODE_INVITE) tmp_mode += "i"; if (bitlist&CHANMODE_KEY) tmp_mode += "k"; if (bitlist&CHANMODE_LIMIT) tmp_mode += "l"; if (bitlist&CHANMODE_MODERATED) tmp_mode += "m"; if (bitlist&CHANMODE_NOOUTSIDE) tmp_mode += "n"; if (bitlist&CHANMODE_PRIVATE) tmp_mode += "p"; if (bitlist&CHANMODE_SECRET) tmp_mode += "s"; if (bitlist&CHANMODE_TOPIC) tmp_mode += "t"; return tmp_mode; } function Channel_isbanned(banned_nuh) { for (this_ban in this.modelist[CHANLIST_BAN]) { if (match_irc_mask(banned_nuh,this.modelist[CHANLIST_BAN][this_ban])) return 1; } return 0; } function Channel_occupants() { chan_occupants=""; for(thisChannel_user in this.users) { Channel_user=Clients[this.users[thisChannel_user]]; if (Channel_user.nick && (Channel_user.conntype != TYPE_SERVER)) { if (chan_occupants) chan_occupants += " "; if (this.ismode(Channel_user.id,CHANLIST_OP)) chan_occupants += "@"; if (this.ismode(Channel_user.id,CHANLIST_VOICE)) chan_occupants += "+"; chan_occupants += Channel_user.nick; } } return chan_occupants; } 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 WhoWas(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; }