Synchronet now requires the libarchive development package (e.g. libarchive-dev on Debian-based Linux distros, libarchive.org for more info) to build successfully.

Commit 2c5aa02f authored by Rob Swindell's avatar Rob Swindell 💬

Merge branch 'master' into 'master'

IRCd 1.9b

See merge request !126
parents b6fe381f c04a8dcd
......@@ -26,6 +26,7 @@ load("sbbsdefs.js");
load("sockdefs.js");
load("nodedefs.js");
load("irclib.js");
load("dns.js");
/* Libraries specific to the IRCd */
load("ircd/core.js");
......@@ -35,293 +36,122 @@ load("ircd/channel.js");
load("ircd/server.js");
load("ircd/config.js");
/* Global Constants */
const VERSION = "SynchronetIRCd-1.9a";
/*** Global Constants - Always in ALL_UPPERCASE ***/
const VERSION = "SynchronetIRCd-1.9b";
const VERSION_STR = format(
"Synchronet %s%s-%s%s (IRCd by Randy Sommerfeld)",
system.version, system.revision,
system.platform, system.beta_version
);
/* This will be replaced with a dynamic CAPAB system later. */
const Server_CAPAB = "TS3 NOQUIT SSJOIN BURST UNCONNECT NICKIP TSMODE";
// The number of seconds to block before giving up on outbound CONNECT
// attempts (when connecting to another IRC server -- i.e. a hub) This value
// is important because connecing is a BLOCKING operation, so your IRC *will*
// freeze for the amount of time it takes to connect.
const ob_sock_timeout = 3;
// Should we enable the USERS and SUMMON commands? These allow IRC users to
// view users on the local BBS and summon them to IRC via a Synchronet telegram
// message respectively. Some people might be running the ircd standalone, or
// otherwise don't want anonymous IRC users to have access to these commands.
// We enable this by default because there's typically nothing wrong with
// seeing who's on an arbitrary BBS or summoning them to IRC.
const enable_users_summon = true;
// EVERY server on the network MUST have the same values in ALL of these
// categories. If you change these, you WILL NOT be able to link to the
// Synchronet IRC network. Linking servers with different values here WILL
// cause your network to desynchronize (and possibly crash the IRCD)
// Remember, this is Synchronet, not Desynchronet ;)
const max_chanlen = 100; // Maximum channel name length.
const max_nicklen = 30; // Maximum nickname length.
const max_modes = 6; // Maximum modes on single MODE command
const max_user_chans = 100; // Maximum channels users can join
const max_bans = 25; // Maximum bans (+b) per channel
const max_topiclen = 307; // Maximum length of topic per channel
const max_kicklen = 307; // Maximum length of kick reasons
const max_who = 100; // Maximum replies to WHO for non-oper users
const max_silence = 10; // Maximum entries on a user's SILENCE list
const server_uptime = time();
/* This will be replaced with a dynamic CAPAB system */
const SERVER_CAPAB = "TS3 NOQUIT SSJOIN BURST UNCONNECT NICKIP TSMODE";
/* This will be in the configuration for 2.0 */
const SUMMON = true;
/* Need to detect when a server doesn't line up with these on the network. */
const MAX_CHANLEN = 100; /* Maximum channel name length. */
const MAX_NICKLEN = 30; /* Maximum nickname length. */
const MAX_MODES = 6; /* Maximum modes on single MODE command */
const MAX_USER_CHANS = 100; /* Maximum channels users can join */
const MAX_BANS = 25; /* Maximum bans (+b) per channel */
const MAX_TOPICLEN = 307; /* Maximum length of topic per channel */
const MAX_KICKLEN = 307; /* Maximum length of kick reasons */
const MAX_WHO = 100; /* Maximum replies to WHO for non-oper users */
const MAX_SILENCE = 10; /* Maximum entries on a user's SILENCE list */
const MAX_WHOWAS = 1000; /* Size of the WHOWAS buffer */
const MAX_NICKHISTORY = 1000; /* Size of the nick change history buffer */
const MAX_CLIENT_RECVQ = 2560;/* Maximum size of unregistered & user recvq */
const MAX_AWAYLEN = 80; /* Maximum away message length */
const MAX_USERHOST = 6; /* Maximum arguments to USERHOST command */
const MAX_REALNAME = 50; /* Maximum length of users real name field */
const SERVER_UPTIME = system.timer;
const SERVER_UPTIME_STRF = strftime("%a %b %d %Y at %H:%M:%S %Z",time());
/*** Global Objects, Arrays and Variables - Always in Mixed_Case ***/
/* Global Objects */
var DNS_Resolver = new DNS();
/* Every object (unregistered, server, user) is tagged with a unique ID */
var Assigned_IDs = {}; /* Key: Numeric ID */
var Unregistered = {}; /* Key: Numeric ID */
var Users = {}; /* Key: .toUpperCase() nick */
var Servers = {}; /* Key: .toLowerCase() nick */
var Channels = {}; /* Key: .toUpperCase() channel name, including prefix */
var Local_Users = {};
var Local_Servers = {};
var WhoWas = {}; /* Stores uppercase nicks */
var WhoWasMap = []; /* An array pointing to WhoWas object entries */
var NickHistory = []; /* Nick change tracking */
var Profile = {}; /* CPU profiling */
/* Global Variables */
var Default_Port = 6667;
// This will dump all I/O to and from the server to your Synchronet console.
// It also enables some more verbose WALLOPS, especially as they pertain to
// blocking functions.
// The special "DEBUG" oper command also switches this value.
var debug = false;
var default_port = 6667;
/* This was previously on its own in the functions
Maybe there was a reason why? */
var time_config_read;
/* Primary arrays */
var Unregistered = new Object;
var Users = new Object;
var Servers = new Object;
var Channels = new Object;
var Local_Sockets = new Object;
var Local_Sockets_Map = new Object;
var Selectable_Sockets = new Object;
var Selectable_Sockets_Map = new Object;
/* Highest Connection Count tracking */
var hcc_total = 0;
var hcc_users = 0;
var hcc_counter = 0;
var WhoWas = new Object; /* Stores uppercase nicks */
var WhoWasMap = new Array; /* A true push/pop array pointing to WhoWas entries */
var WhoWas_Buffer = 1000; /* Maximum number of WhoWas entries to keep track of */
var NickHistory = new Array; /* A true array using push and pop */
var NickHistorySize = 1000;
/* Keep track of commands and how long they take to execute. */
var Profile = new Object;
/* This is where our unique ID for each client comes from for unreg'd clients. */
var next_client_id = 0;
// An array containing all the objects containing local sockets that we need
// to poll.
var Local_Users = new Object;
var Local_Servers = new Object;
var rebuild_socksel_array = true;
var network_debug = false;
var last_recvq_check = 0;
var servername = "server.invalid";
var serverdesc = "No description provided.";
log(VERSION + " started.");
// Parse command-line arguments.
var config_filename="";
var cmdline_port;
var cmdarg;
for (cmdarg=0;cmdarg<argc;cmdarg++) {
switch(argv[cmdarg].toLowerCase()) {
case "-f":
config_filename = argv[++cmdarg];
break;
case "-p":
cmdline_port = parseInt(argv[++cmdarg]);
break;
case "-d":
debug=true;
break;
case "-a":
cmdline_addr = argv[++cmdarg].split(',');
break;
}
}
var Time_Config_Read; /* Stores time() of when the config was last read */
/* Temporary hack to make JSexec testing code not complain */
var mline_port;
/* Will this server try to enforce good network behaviour? */
/* Setting to "true" results in bouncing bad modes, KILLing bogus NICKs, etc. */
var Enforcement = true;
read_config_file();
/* Highest Connection Count ("HCC") tracking */
var HCC_Total = 0;
var HCC_Users = 0;
var HCC_Counter = 0;
/* This tests if we're running from JSexec or not */
if(this.server==undefined) {
if (!jsexec_revision_detail)
var jsexec_revision_detail = "JSexec";
var ServerName;
var ServerDesc = "";
if (cmdline_port)
default_port = cmdline_port;
else if (typeof mline_port !== undefined)
default_port = mline_port;
/* Runtime configuration */
var Config_Filename = "";
var server = {
socket: false,
terminated: false,
version_detail: jsexec_revision_detail,
interface_ip_addr_list: ["0.0.0.0","::"]
};
var Restart_Password;
var Die_Password;
server.socket = create_new_socket(default_port)
var Admin1;
var Admin2;
var Admin3;
if (!server.socket)
exit();
}
var CLines = []; /* Server [C]onnect lines */
var NLines = []; /* Server inbound connect lines */
var HLines = []; /* Hubs */
var ILines = []; /* IRC Classes */
var KLines = []; /* user@hostname based bans */
var OLines = []; /* IRC Operators */
var PLines = []; /* Ports for the IRCd to listen to */
var QLines = []; /* [Q]uarantined (reserved) nicknames */
var ULines = []; /* Servers allowed to send unchecked MODE amongst other things */
var YLines = []; /* Defines what user & server objects get what settings */
var ZLines = []; /* IP based bans */
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
var open_plines = new Array(); /* True Array */
// Make our 'server' object the first open P:Line
open_plines[0] = server.socket;
for (pl in PLines) {
var new_pl_sock = create_new_socket(PLines[pl]);
if (new_pl_sock) {
new_pl_sock.nonblocking = true;
new_pl_sock.debug = false;
open_plines.push(new_pl_sock);
}
}
/** Begin executing code **/
js.branch_limit=0; // we're not an infinite loop.
js.auto_terminate=false; // we handle our own termination requests
/*** Main Loop ***/
while (!js.terminated) {
if(file_date(system.ctrl_dir + "ircd.rehash") > time_config_read)
read_config_file();
/* 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();
log(LOG_DEBUG,"Accepting new connection on port "
+ client_sock.local_port);
if(client_sock) {
client_sock.nonblocking = true;
switch(client_sock.local_port) {
case 994:
case 6697:
client_sock.ssl_server=1;
}
if (!client_sock.remote_ip_address) {
log(LOG_DEBUG,"Socket has no IP address. Closing.");
client_sock.close();
} else if (iszlined(client_sock.remote_ip_address)) {
client_sock.send(format(
":%s 465 * :You've been Z:Lined from this server.\r\n",
servername
));
client_sock.close();
} else {
var new_id = "id" + next_client_id;
next_client_id++;
if(server.client_add != undefined)
server.client_add(client_sock);
if(server.clients != undefined)
log(LOG_DEBUG,format("%d clients", server.clients));
Unregistered[new_id] = new Unregistered_Client(new_id,
client_sock);
}
} else
log(LOG_DEBUG,"!ERROR " + open_plines[pl].error
+ " accepting connection");
}
}
log(LOG_NOTICE, VERSION + " started.");
// Check for pending DNS hostname resolutions.
for(this_unreg in Unregistered) {
if (Unregistered[this_unreg] &&
Unregistered[this_unreg].pending_resolve_time)
Unregistered[this_unreg].resolve_check();
}
/* If we're running from JSexec we don't have a global server object, so fake one. */
if (server === undefined) {
/* Define the global here so Startup() can manipulate it. */
var server = "JSexec";
}
// Only rebuild our selectable sockets if required.
if (rebuild_socksel_array) {
Selectable_Sockets = new Array;
Selectable_Sockets_Map = new Array;
for (i in Local_Sockets) {
Selectable_Sockets.push(Local_Sockets[i]);
Selectable_Sockets_Map.push(Local_Sockets_Map[i]);
}
rebuild_socksel_array = false;
}
Startup();
/* Check for ping timeouts and process queues. */
/* FIXME/TODO: These need to be changed to a mapping system ASAP. */
for(this_sock in Selectable_Sockets) {
if (Selectable_Sockets_Map[this_sock]) {
Selectable_Sockets_Map[this_sock].check_timeout();
Selectable_Sockets_Map[this_sock].check_queues();
}
}
js.do_callbacks = true;
// do some work.
if (Selectable_Sockets.length) {
var readme = socket_select(Selectable_Sockets, 1 /*secs*/);
try {
for(thisPolled in readme) {
if (Selectable_Sockets_Map[readme[thisPolled]]) {
var conn = Selectable_Sockets_Map[readme[thisPolled]];
if (!conn.socket.is_connected) {
conn.quit("Connection reset by peer.");
continue;
}
conn.recvq.recv(conn.socket);
}
}
} catch(e) {
gnotice("FATAL ERROR: " + e);
log(LOG_ERR,"JavaScript exception: " + e);
terminate_everything("A fatal error occured!", /* ERROR? */true);
}
} else {
/* Nothing's connected to us, so hang out for a bit */
mswait(100);
}
// Scan C:Lines for servers to connect to automatically.
var my_cline;
for(thisCL in CLines) {
my_cline = CLines[thisCL];
if ( my_cline.port
&& YLines[my_cline.ircclass].connfreq
&& (YLines[my_cline.ircclass].maxlinks > YLines[my_cline.ircclass].active)
&& (search_server_only(my_cline.servername) < 1)
&& ((time() - my_cline.lastconnect) > YLines[my_cline.ircclass].connfreq)
) {
umode_notice(
USERMODE_ROUTING,
"Routing",
format("Auto-connecting to %s (%s)",
CLines[thisCL].servername,
CLines[thisCL].host
)
);
connect_to_server(CLines[thisCL]);
}
function config_rehash_semaphore_check() {
if(file_date(system.ctrl_dir + "ircd.rehash") > Time_Config_Read) {
Read_Config_File();
}
}
js.setInterval(config_rehash_semaphore_check, 1000 /* milliseconds */);
Open_PLines();
/* We've exited the main loop, so terminate everything. */
terminate_everything("Terminated.");
/* We exit here and pass everything to the callback engine. */
/* Deuce says I can't use exit(). So just pretend it's here instead. */
......@@ -19,69 +19,60 @@
*/
const CHANMODE_NONE =(1<<0); // NONE
const CHANMODE_BAN =(1<<1); // b
const CHANMODE_INVITE =(1<<2); // i
const CHANMODE_KEY =(1<<3); // k
const CHANMODE_LIMIT =(1<<4); // l
const CHANMODE_MODERATED=(1<<5); // m
const CHANMODE_NOOUTSIDE=(1<<6); // n
const CHANMODE_OP =(1<<7); // o
const CHANMODE_PRIVATE =(1<<8); // p
const CHANMODE_SECRET =(1<<9); // s
const CHANMODE_TOPIC =(1<<10); // t
const CHANMODE_VOICE =(1<<11); // v
/* 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,isnick) {
this.modechar = modechar; /* The mode's character */
this.args = args; /* Does this mode take only a single arg? */
this.state = state; /* Stateful? (changes channel behaviour) */
this.list = list; /* Does this mode accept a list? */
this.isnick = isnick; /* Is nick (true) or a n!u@h mask (false) */
}
/* Channel Modes */
const CHANMODE_NONE =(1<<0);
const CHANMODE_BAN =(1<<1); /* +b */
const CHANMODE_INVITE =(1<<2); /* +i */
const CHANMODE_KEY =(1<<3); /* +k */
const CHANMODE_LIMIT =(1<<4); /* +l */
const CHANMODE_MODERATED=(1<<5); /* +m */
const CHANMODE_NOOUTSIDE=(1<<6); /* +n */
const CHANMODE_OP =(1<<7); /* +o */
const CHANMODE_PRIVATE =(1<<8); /* +p */
const CHANMODE_SECRET =(1<<9); /* +s */
const CHANMODE_TOPIC =(1<<10); /* +t */
const CHANMODE_VOICE =(1<<11); /* +v */
const MODE = {};
MODE[CHANMODE_BAN] = new IRC_Channel_Mode("b",true,false,true,false);
MODE[CHANMODE_INVITE] = new IRC_Channel_Mode("i",false,true,false,false);
MODE[CHANMODE_KEY] = new IRC_Channel_Mode("k",true,true,false,false);
MODE[CHANMODE_LIMIT] = new IRC_Channel_Mode("l",true,true,false,false);
MODE[CHANMODE_MODERATED]= new IRC_Channel_Mode("m",false,true,false,false);
MODE[CHANMODE_NOOUTSIDE]= new IRC_Channel_Mode("n",false,true,false,false);
MODE[CHANMODE_OP] = new IRC_Channel_Mode("o",true,false,true,true);
MODE[CHANMODE_PRIVATE] = new IRC_Channel_Mode("p",false,true,false,false);
MODE[CHANMODE_SECRET] = new IRC_Channel_Mode("s",false,true,false,false);
MODE[CHANMODE_TOPIC] = new IRC_Channel_Mode("t",false,true,false,false);
MODE[CHANMODE_VOICE] = new IRC_Channel_Mode("v",true,false,true,true);
/* Object Prototypes */
MODE = new Object;
MODE[CHANMODE_BAN] = new Mode("b",true,false,true,false);
MODE[CHANMODE_INVITE] = new Mode("i",false,true,false,false);
MODE[CHANMODE_KEY] = new Mode("k",true,true,false,false);
MODE[CHANMODE_LIMIT] = new Mode("l",true,true,false,false);
MODE[CHANMODE_MODERATED]= new Mode("m",false,true,false,false);
MODE[CHANMODE_NOOUTSIDE]= new Mode("n",false,true,false,false);
MODE[CHANMODE_OP] = new Mode("o",true,false,true,true);
MODE[CHANMODE_PRIVATE] = new Mode("p",false,true,false,false);
MODE[CHANMODE_SECRET] = new Mode("s",false,true,false,false);
MODE[CHANMODE_TOPIC] = new Mode("t",false,true,false,false);
MODE[CHANMODE_VOICE] = new Mode("v",true,false,true,true);
////////// Objects //////////
function Channel(nam) {
this.nam=nam;
this.mode=CHANMODE_NONE;
this.topic="";
this.topictime=0;
this.topicchangedby="";
this.arg = new Object;
this.nam = nam;
this.mode = CHANMODE_NONE;
this.topic = "";
this.topictime = 0;
this.topicchangedby = "";
this.arg = {};
this.arg[CHANMODE_LIMIT] = 0;
this.arg[CHANMODE_KEY] = "";
this.users=new Object;
this.modelist=new Object;
this.modelist[CHANMODE_OP]=new Object;
this.modelist[CHANMODE_VOICE]=new Object;
this.modelist[CHANMODE_BAN]=new Array; /* True Array */
this.bantime=new Object;
this.bancreator=new Object;
this.created=time();
this.chanmode=Channel_chanmode;
this.isbanned=Channel_isbanned;
this.count_modelist=Channel_count_modelist;
this.occupants=Channel_occupants;
this.match_list_mask=Channel_match_list_mask;
this.users = {};
this.modelist = {};
this.modelist[CHANMODE_OP] = {};
this.modelist[CHANMODE_VOICE] = {};
this.modelist[CHANMODE_BAN] = [];
this.bantime = {};
this.bancreator = {};
this.created = time();
/* Functions */
this.chanmode = Channel_chanmode;
this.isbanned = Channel_isbanned;
this.count_modelist = Channel_count_modelist;
this.occupants = Channel_Occupants;
this.match_list_mask = Channel_match_list_mask;
}
////////// Functions //////////
function ChanMode_tweaktmpmode(tmp_bit,add) {
if ( !this.chan.modelist[CHANMODE_OP][this.user.id]
&& !this.user.server
......@@ -100,7 +91,9 @@ function ChanMode_tweaktmpmode(tmp_bit,add) {
}
}
function ChanMode_tweaktmpmodelist(tmp_bit,add,arg) {
function ChanMode_tweaktmpmodelist(bit,add,arg) {
var i;
if ( !this.chan.modelist[CHANMODE_OP][this.user.id]
&& !this.user.server
&& !this.user.uline
......@@ -109,29 +102,24 @@ function ChanMode_tweaktmpmodelist(tmp_bit,add,arg) {
this.user.numeric482(this.chan.nam);
return 0;
}
for (lstitem in this.tmplist[tmp_bit][add]) {
// Is this argument in our list for this mode already?
if (this.tmplist[tmp_bit][add][lstitem].toUpperCase() == arg.toUpperCase())
for (i in this.tmplist[bit][add]) {
/* Is this argument in our list for this mode already? */
if (this.tmplist[bit][add][i].toUpperCase() == arg.toUpperCase())
return 0;
}
// It doesn't exist on our mode, push it in.
this.tmplist[tmp_bit][add].push(arg);
// Check for it against the other mode, and maybe nuke it.
var oadd;
if (add)
oadd = false;
else
oadd = true;
for (x in this.tmplist[tmp_bit][oadd]) {
if (this.tmplist[tmp_bit][oadd][x].toUpperCase() == arg.toUpperCase()) {
delete this.tmplist[tmp_bit][oadd][x];
/* It doesn't exist on our mode, push it in. */
this.tmplist[bit][add].push(arg);
/* Check for it against the other mode, and maybe nuke it. */
for (i in this.tmplist[bit][add ? false : true]) {
if (this.tmplist[bit][add ? false : true][i].toUpperCase() == arg.toUpperCase()) {
delete this.tmplist[bit][add ? false : true][i];
return 0;
}
}
}
function ChanMode_affect_mode_list(list_bit) {
var tmp_nick;
var tmp_nick, add, z;
for (add in this.tmplist[list_bit]) {
for (z in this.tmplist[list_bit][add]) {
tmp_nick = Users[this.tmplist[list_bit][add][z].toUpperCase()];
......@@ -156,12 +144,15 @@ function ChanMode_affect_mode_list(list_bit) {
}
function Channel_count_modelist(list_bit) {
var tmp_counter=0;
for (tmp_count in this.modelist[list_bit]) {
if (this.modelist[list_bit][tmp_count])
tmp_counter++;
var i;
var ret = 0;
for (i in this.modelist[list_bit]) {
if (this.modelist[list_bit][i])
ret++;
}
return tmp_counter;
return ret;
}
function Channel_chanmode(show_args) {
......@@ -194,32 +185,37 @@ function Channel_chanmode(show_args) {
return tmp_mode + tmp_extras;
}
function Channel_isbanned(banned_nuh) {
for (this_ban in this.modelist[CHANMODE_BAN]) {
if(wildmatch(banned_nuh,this.modelist[CHANMODE_BAN][this_ban]))
function Channel_isbanned(nuh) {
var i;
for (i in this.modelist[CHANMODE_BAN]) {
if(wildmatch(nuh,this.modelist[CHANMODE_BAN][i]))
return 1;
}
return 0;
}
function Channel_occupants() {
var chan_occupants="";
for(thisChannel_user in this.users) {
var Channel_user=this.users[thisChannel_user];
if (Channel_user.nick) {
if (chan_occupants)
chan_occupants += " ";
if (this.modelist[CHANMODE_OP][Channel_user.id])