diff --git a/ctrl/ircbot.ini b/ctrl/ircbot.ini
new file mode 100644
index 0000000000000000000000000000000000000000..c8ec943b692e76a5047f0ae5e54b51bfe0d33e8c
--- /dev/null
+++ b/ctrl/ircbot.ini
@@ -0,0 +1,23 @@
+;$Id$
+command_prefix=bot
+real_name=Synchronet IRC Bot
+help_filename=ircbot_help.txt
+config_write_delay=300
+squelch_list=
+
+[server_Synchronet]
+;addresses=127.0.0.1
+;nick=myIRCbot
+;services_password=nothing
+;channels=#synchronet,#bbs
+;port=6667
+
+[quotes]
+1=<DigitalMan> I'm not sure why JS really needs all this fancy math shit.
+2=<Cyan> "The box said 'Windows 95 or better required', therefore Linux was clearly a supported platform."
+3=<pcm> I have a very hard time understanding you. Is english your native language? <kernel2> no <pcm> where are you from? <kernel2> Virginia
+4=<Cyan> what bothers me more about TV is the high-pitched whine coming from the flyback transformer :P <Deuce> Uhhh... <Deuce> You sure that's not the power steering on your Ford?
+5=ircd.txt: "This document is not a replacement for your brain."
+6=<kernel2> For example mircosoft can not write software worth shit without releasing bugs in there programs, yahoo can not control there animals, hotmail.com is a joke because they are the second spammers in the us
+7=<Vagabond> heh. Deuce only Codes. He does not make things Pretty.
+
diff --git a/exec/ircbot.js b/exec/ircbot.js
new file mode 100644
index 0000000000000000000000000000000000000000..817e824789727afb1b447a206e76d36e1b3dcea7
--- /dev/null
+++ b/exec/ircbot.js
@@ -0,0 +1,235 @@
+// $Id$
+/*
+
+ 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
+
+ An IRC bot written in JS that interfaces with the local BBS.
+
+ Copyright 2010 Randolph E. Sommerfeld <sysop@rrx.ca>
+
+*/
+
+load("sockdefs.js");
+load("sbbsdefs.js");
+load("irclib.js");
+
+load("load/ircbot_functions.js");
+
+js.branch_limit=0; /* we're not an infinite loop. */
+
+Module_Save_Data = new Object();
+Bot_Commands = new Object();
+
+Config_Last_Write = time(); /* Store when the config was last written. */
+
+/* Global Arrays */
+bot_servers = new Array();
+masks = new Object();
+quotes = new Array();
+dcc_chats = new Array();
+squelch_list = new Array();
+
+var config_filename = "ircbot.ini";
+for (cmdarg=0;cmdarg<argc;cmdarg++) {
+	switch(argv[cmdarg].toLowerCase()) {
+		case "-f":
+			config_filename = argv[++cmdarg];
+			break;
+		default:
+			break;
+	}
+}
+
+var config = new File(system.ctrl_dir + config_filename);
+if (config.open("r")) {
+	/* Global Variables */
+	command_prefix = config.iniGetValue(null, "command_prefix");
+	real_name = config.iniGetValue(null, "real_name");
+	help_filename = config.iniGetValue(null, "help_filename");
+	help_file = new File(help_filename);
+	config_write_delay=parseInt(config.iniGetValue(null, "config_write_delay"));
+//	squelch_list = config.iniGetValue(null, "squelch_list").split(",");
+
+	/* Servers */
+	var ini_server_secs = config.iniGetSections("server_");
+	for (s in ini_server_secs) {
+	var mysec = ini_server_secs[s];
+		bot_servers.push(new Bot_IRC_Server(
+			0,  /* Socket */
+			config.iniGetValue(mysec, "addresses"),
+			config.iniGetValue(mysec, "nick"),
+			config.iniGetValue(mysec, "services_password"),
+			config.iniGetValue(mysec, "channels"),
+			parseInt(config.iniGetValue(mysec, "port")),
+			mysec.slice(7)  /* Network Name */
+		));
+	}
+
+	/* Quotes */
+	var ini_quotes = config.iniGetKeys("quotes");
+	for (q in ini_quotes) {
+		quotes.push(config.iniGetValue("quotes", ini_quotes[q]));
+	}
+
+	config.close();
+} else {
+	exit("Couldn't open config file!");
+}
+
+var user_settings_files = directory("/home/bbs/data/user/*.ircbot.ini");
+for (f in user_settings_files) {
+	var us_file = new File(user_settings_files[f]);
+	if (us_file.open("r")) {
+		var tokenized_path = user_settings_files[f].split("/");
+		var us_filename = tokenized_path[tokenized_path.length-1];
+		var uid_str = us_filename.split(".")[0];
+		while (uid_str[0] == "0") {
+			uid_str = uid_str.slice(1);
+		}
+		printf("***Reading: " + us_file.name + "\r\n");
+		var read_masks = us_file.iniGetValue(null, "masks");
+		if (read_masks)
+			masks[parseInt(uid_str)] = read_masks.split(",");
+	}
+}
+
+log("*** Entering Main Loop. ***");
+
+function main() {
+	while (!js.terminated) {
+		for (my_srv in bot_servers) {
+			var cmdline;
+			var srv = bot_servers[my_srv];
+			if (!srv.sock &&(srv.lastcon <time())) { //we're not connected.
+				var consock = IRC_client_connect(srv.host, srv.nick,
+					command_prefix, real_name, srv.port);
+				if (consock) {
+					srv.sock = consock;
+					log("--- Connected to " + srv.host);
+					/* If we just connected, then clear all our joined channels. */
+					for (c in srv.channel) {
+						srv.channel[c].is_joined = false;
+					}
+				} else {
+					log("--- Connect to " + srv.host + " failed, "
+						+ "retry in 60 seconds.");
+					srv.lastcon = time() + 60;
+				}
+			} else if (srv.sock && srv.sock.data_waiting &&
+					(cmdline=srv.sock.recvline(4096,0))) {
+				var onick;
+				var ouh;
+				var outline;
+				var sorigin = cmdline.split(" ")[0].slice(1);
+				if ((cmdline[0] == ":") && sorigin.match(/[@]/)) {
+					onick = sorigin.split("!")[0];
+					ouh = sorigin.split("!")[1];
+					outline = "["+onick+"("+ouh+")] " + cmdline;
+				} else {
+					onick = "";
+					ouh = "";
+					outline = cmdline;
+				}
+				log("<-- " + srv.host + ": " + outline);
+				srv.server_command(IRC_parsecommand(cmdline),onick,ouh);
+			}
+
+			// Run through some commands.
+			if (srv.sock && srv.is_registered) {
+				for (c in srv.channel) {
+					if (!srv.channel[c].is_joined &&
+						(srv.channel[c].lastjoin < time())) {
+						srv.writeout("JOIN " + srv.channel[c].name);
+						srv.channel[c].lastjoin = time() + 60;
+					}
+				}
+			}
+			mswait(10); /* Don't peg the CPU */
+		}
+		if ( (time() - Config_Last_Write) > config_write_delay )
+			save_everything();
+
+	}
+}
+
+//////////////////// Objects and Functions ////////////////////
+function Bot_IRC_Server(sock,host,nick,svspass,channels,port,name) {
+	// Static variables (never change)
+	this.sock = sock;
+	this.host = host;
+	this.nick = nick;
+	this.svspass = svspass;
+	this.port = port;
+	this.name = name;
+	// Channels
+	this.channel = new Array();
+	var my_channels = channels.split(",");
+	for (c in my_channels) {
+		log ("--- Adding Channel: " + my_channels[c]);
+		this.channel[my_channels[c].toUpperCase()] = new Bot_IRC_Channel(
+			my_channels[c]);
+	}
+	// Dynamic variables (used for the bot's state.
+	this.curnick = nick;
+	this.lastcon = 0;			// When it's OK to reconnect.
+	this.is_registered = false;
+	this.juped = false;
+	this.users = new Object();	// Store local nicks & uh info.
+	// Functions
+	this.ctcp = Server_CTCP;
+	this.ctcp_reply = Server_CTCP_Reply;
+	this.o = Server_target_out;
+	this.writeout = Server_writeout;
+	this.server_command = Server_command;
+	this.check_bot_command = Server_check_bot_command;
+	this.bot_access = Server_Bot_Access;
+}
+
+function Bot_IRC_Channel(name) {
+	// Statics.
+	this.name = name;
+	// Dynamics.
+	this.is_joined = false;
+	this.lastjoin = 0;
+	// Functions.
+}
+
+function Server_User(uh) {
+	this.uh = uh;
+	this.ident = false;
+}
+
+function Bot_Command(min_security,args_needed,ident_needed) {
+	this.min_security = min_security;
+	this.args_needed = args_needed;
+	this.ident_needed = ident_needed;
+	this.command = false;
+}
+
+function DCC_Chat(sock,id) {
+	this.sock = sock;
+	this.id = id;
+	/* State info */
+	this.waiting_for_password = true;
+	/* Functions */
+	this.o = DCC_Out;
+}
+
+function DCC_Out(target,str) {
+	this.sock.write(str + "\r\n");
+}
+
+/* This must be at the very bottom. */
+load("load/ircbot_commands.js");
+//load("load/ircbot_functions.js");
+//load("ircbot/fieldday.js");
+main();
diff --git a/exec/load/ircbot_commands.js b/exec/load/ircbot_commands.js
new file mode 100644
index 0000000000000000000000000000000000000000..1e84494a9d2516a2bc23f6d7642691390b5a9b75
--- /dev/null
+++ b/exec/load/ircbot_commands.js
@@ -0,0 +1,595 @@
+// $Id$
+/*
+
+ 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
+
+ Copyright 2010 Randolph Erwin Sommerfeld <sysop@rrx.ca>
+
+ An IRC bot written in JS that interfaces with the local BBS.
+
+*/
+
+Bot_Commands["RELOAD"] = new Bot_Command(50,false,false);
+Bot_Commands["RELOAD"].usage =
+	"RELOAD";
+Bot_Commands["RELOAD"].help =
+	"Reloads the internal bot command and function structure.  No arguments.";
+Bot_Commands["RELOAD"].command = function (target,onick,ouh,srv,lvl,cmd) {
+	load("load/ircbot_commands.js");
+	load("load/ircbot_functions.js");
+	srv.o(target,"Reloaded.");
+	return;
+}
+
+Bot_Commands["WHOIS"] = new Bot_Command(0,false,false);
+Bot_Commands["WHOIS"].usage =
+	"WHOIS <nick>";
+Bot_Commands["WHOIS"].help =
+	"Brings up information about a user.  If the <nick> argument is omitted, "
+	+ "then it will display information about you.";
+Bot_Commands["WHOIS"].command = function (target,onick,ouh,srv,lvl,cmd) {
+	if (!cmd[1]) {
+		srv.o(target,"You are recognized as access level " + lvl);
+		cmd[1] = onick;
+	}
+	var usr = new User(system.matchuser(cmd[1]));
+	if (usr.number > 0) {
+		srv.o(target,usr.alias+" has an access level of "
+			+usr.security.level+".");
+		if (masks[usr.number])
+			srv.o(target,"Masks: " + masks[usr.number].join(" "));
+		else
+			srv.o(target,usr.alias + " has no IRC masks defined.");
+		srv.o(target,usr.alias + " last signed on " + usr.laston_date + " via "
+			+ usr.connection + ".");
+	} else {
+		srv.o(target,"I have no such user in my database.");
+	}
+	return;
+}
+
+Bot_Commands["ADDMASK"] = new Bot_Command(50,1,false);
+Bot_Commands["ADDMASK"].command = function (target,onick,ouh,srv,lvl,cmd) {
+	var addmask = false;
+	var delmask = false;
+	if (cmd[0] == "ADDMASK") {
+		addmask = true;
+	} else if (cmd[0] == "DELMASK") {
+		delmask = true;
+	}
+	if (!addmask && !delmask) {
+		srv.o(target,"Huh? I'm confused :(");
+		return;
+	}
+	if (!cmd[2]) {
+		cmd[2] = cmd[1];
+		cmd[1] = onick;
+	}
+	if (!cmd[2].match(/[@]/)) {
+		srv.o(target,"Typically, hostmasks need a '@' in them.");
+		return;
+	}
+	var usr = new User(system.matchuser(cmd[1]));
+	if (usr.number == 0) {
+		srv.o(target,"That user doesn't exist!");
+		return;
+	}
+	var self_change = (onick.toUpperCase() == cmd[1].toUpperCase());
+	if ( (lvl < 80) && !self_change) {
+		srv.o(target,"You do not have permission to change IRC masks "
+			+ "for other users.");
+		return;
+	}
+	if ((usr.security.level >= lvl) && !self_change) {
+		srv.o(target,"You cannot add or delete masks for a user whose "
+			+ "access level is equal to or greater than yours.");
+		return;
+	}
+	if (addmask) {
+		for (m in masks[usr.number]) {
+			if (wildmatch(cmd[2],masks[usr.number][m])) {
+				srv.o(target,"This user already has a mask matching that. "
+					+ "Try deleting it with 'DELMASK' first.");
+				return;
+			}
+		}
+	}
+	if (delmask) {
+		for (m in masks[usr.number]) {
+			if (masks[usr.number][m].toUpperCase() == cmd[2].toUpperCase()) {
+				masks[usr.number].splice(m,1);
+				srv.o(target,"Mask deleted for user " + usr.alias + ": "
+					+ cmd[2]);
+				return;
+			}
+		}
+		srv.o(target,"Couldn't find the mask you were looking for.");
+	} else if (addmask) {
+		if (!masks[usr.number])
+			masks[usr.number] = new Array();
+		masks[usr.number].push(cmd[2]);
+		srv.o(target,"Mask added for user " + usr.alias + ": " + cmd[2]);
+	}
+	return;
+}
+Bot_Commands["DELMASK"] = Bot_Commands["ADDMASK"];
+
+Bot_Commands["ADDUSER"] = new Bot_Command(80,2,false);
+Bot_Commands["ADDUSER"].command = function (target,onick,ouh,srv,lvl,cmd) {
+	if (IRC_check_nick(cmd[1],40)) {
+		srv.o(target,cmd[1] + " isn't a valid nickname.");
+		return;
+	}
+	var usr = cmd[1].toUpperCase();
+	var syncusr = new User(system.matchuser(cmd[1]));
+	if (!srv.users[usr] && !cmd[2]) {
+		srv.o(target,cmd[1] + " is not on any channels that I'm currently on. "
+			+ "To force an add for this user, please specify a mask.");
+		return;
+	} else if (syncusr.number > 0) {
+		srv.o(target,cmd[1] + " already exists in my database!");
+		return;
+	}
+	var mask;
+	var level = 50;
+	if (cmd[2] && cmd[2].match(/[.]/)) {//2nd arg is a mask
+		mask = cmd[2];
+	} else if (cmd[2]) { // must be a level.
+		if (!srv.users[usr] && !cmd[3]) {
+			srv.o(target,cmd[1] + " is not on any channels that I'm "
+				+ "currently on.  To force an add for this user, please "
+				+ "specify a mask.");
+			return;
+		}
+		level = parseInt(cmd[2]);
+		if (level >= lvl) {
+			srv.o(target,"You may only add users with a lower access level "
+				+ "than your own.");
+			return;
+		}
+	}
+	if (cmd[3] && !mask)
+		mask = cmd[3];
+	// create a mask for this user.
+	if (!mask)
+		mask = IRC_create_default_mask(srv.users[usr].uh);
+	if (!mask && !level) {
+		srv.o(target,"Uh oh, something bogus happened.  "
+			+ "Alert the bot owner.  (!mask && !level)");
+		return;
+	}
+	var mask_array = mask.split(",");
+	var inval_mask = false;
+	for (my_mask in mask_array) {
+		if (IRC_check_host(mask_array[my_mask],true,true,false)) {
+			srv.o(target,"The mask (" + mask_array[my_mask] + ") is "
+				+ "invalid.  No user added.");
+			inval_mask = true;
+			return;
+		}
+	}
+	if (inval_mask)
+		return;
+	srv.o(target,"Added " + cmd[1] + " as level " + level
+		+ " with mask(s): " + mask);
+	srv.o(target,"This user should now set a password with /MSG " + srv.nick
+		+ " PASS");
+	var newuser = system.new_user(cmd[1]);
+	masks[newuser.number] = mask_array;
+	login_user(newuser);
+	newuser.settings |= USER_INACTIVE;
+	newuser.security.level = level;
+	return;
+}
+
+Bot_Commands["CHANGE"] = new Bot_Command(80,2,false);
+Bot_Commands["CHANGE"].command = function (target,onick,ouh,srv,lvl,cmd) {
+	var usr = new User(system.matchuser(cmd[1]));
+	if (usr.number == 0) {
+		srv.o(target,"The user " + cmd[1] + " doesn't exist in my database.");
+		return;
+	}
+	if (lvl <= usr.security.level) {
+		srv.o(target,"You can only use the change command on users with a "
+			+ "lower level than yours. (" + lvl + ")  "
+			+ "This error message Copyright 2006 Deuce. ;)");
+		return;
+	}
+	if (parseInt(cmd[2]) >= lvl) {
+		srv.o(target,"You cannot change an access level to be higher or equal "
+			+ "to your own. (" + lvl + ")");
+		return;
+	}
+	srv.o(target,"Access level for " + usr.alias + " changed to "
+		+ parseInt(cmd[2]));
+	usr.security.level = parseInt(cmd[2]);
+	return;
+}
+
+Bot_Commands["RESETPASS"] = new Bot_Command(90,true,false);
+Bot_Commands["RESETPASS"].command = function (target,onick,ouh,srv,lvl,cmd) {
+	var usr = new User(system.matchuser(cmd[1]));
+	if (usr.number > 0) {
+		srv.o(target,usr.alias + "'s password has been reset.  "
+			+ "They should now set a new one with /MSG " + srv.nick + " "
+			+ "PASS <newpass>");
+		usr.security.password = "";
+		usr.settings |= USER_INACTIVE;
+	} else {
+		srv.o(target,cmd[1] + " doesn't exist in my database!");
+	}
+	return;
+}
+
+Bot_Commands["PASS"] = new Bot_Command(50,true,false);
+Bot_Commands["PASS"].command = function (target,onick,ouh,srv,lvl,cmd) {
+	if ((target[0] == "#") || (target[0] == "&")) {
+		srv.o(target,"Fool!  I'm not setting your password to something "
+			+ "you broadcast in a public channel.  Pick a new password and "
+			+ "then /MSG " + srv.nick + " PASS <newpass>");
+		return;
+	}
+	var usr = new User(system.matchuser(onick));
+	if (usr.number == 0) {
+		srv.o(target,"Huh?  You don't exist.  Inform the bot owner  (!usr)");
+		return;
+	}
+	if (usr.security.password != "") {
+		if (!cmd[2]) {
+			srv.o(target,"I need your old password too, bud.  "
+				+ "/MSG " + srv.nick + " PASS <newpass> <oldpass>");
+			return;
+		}
+		if (cmd[2].toUpperCase() != usr.security.password) {
+			srv.o(target,"Password mismatch.  /MSG " + srv.nick + " PASS "
+				+ "<newpass> <oldpass>");
+			return;
+		}
+	}
+	srv.o(target,"Your password has now been set to '" + cmd[1] + "', "
+		+"don't forget it!");
+	usr.security.password = cmd[1];
+	if (usr.settings&USER_INACTIVE)
+		usr.settings &= ~USER_INACTIVE;
+	return;
+}
+
+Bot_Commands["IDENT"] = new Bot_Command(0,true,false);
+Bot_Commands["IDENT"].command = function (target,onick,ouh,srv,lvl,cmd) {
+	var usr = new User(system.matchuser(onick));
+	if (cmd[2]) { /* Username passed */
+		usr = new User(system.matchuser(cmd[1]));
+		cmd[1] = cmd[2];
+	}
+	if (!usr.number) {
+		srv.o(target,"No such user.");
+		return;
+	}
+	if ((target[0] == "#") || (target[0] == "&")) {
+		if (lvl >= 50) {
+			srv.o(target,"Fool!  You've just broadcasted your password to "
+				+ "a public channel!  Because of this, I've reset your "
+				+ "password.  Pick a new password, then /MSG " + srv.nick + " "
+				+ "PASS <newpass>");
+			usr.security.password = "";
+		} else {
+			srv.o(target,"Is broadcasting a password to a public channel "
+				+ "really a smart idea?");
+		}
+		return;
+	}
+	if (usr.security.password == "") {
+		srv.o(target,"Your password is blank.  Please set one with /MSG "
+			+ srv.nick + " PASS <newpass>, and then use IDENT.");
+		return;
+	}
+	if (cmd[1].toUpperCase() == usr.security.password) {
+		srv.o(target,"You are now recognized as user '" + usr.alias + "'");
+		srv.users[onick.toUpperCase()].ident = usr.number;
+		login_user(usr);
+		return;
+	}
+	srv.o(target,"Incorrect password.");
+	return;
+}
+
+Bot_Commands["EVAL"] = new Bot_Command(0,true,false);
+Bot_Commands["EVAL"].command = function (target,onick,ouh,srv,lvl,cmd) {
+	cmd.shift();
+	var query = cmd.join(" ");
+	js.branch_limit=1000; // protection
+	js.branch_counter=0; // reset
+	try {
+		srv.o(target, strip_ctrl(js.eval(query)));
+	} catch(e) {
+		srv.o(target,"ERROR: "+e);
+	}
+	js.branch_limit=0; // protection off
+	return;
+}
+
+Bot_Commands["SEVAL"] = new Bot_Command(99,true,true);
+Bot_Commands["SEVAL"].command = function (target,onick,ouh,srv,lvl,cmd) {
+	cmd.shift();
+	var query = cmd.join(" ");
+	try {
+		srv.o(target,eval(query));
+	} catch(e) {
+		srv.o(target,"ERROR: "+e);
+	}
+	return;
+}
+
+Bot_Commands["DIE"] = new Bot_Command(90,false,false);
+Bot_Commands["DIE"].command = function (target,onick,ouh,srv,lvl,cmd) {
+	for (s in bot_servers) {
+		bot_servers[s].writeout("QUIT :" + onick + " told me to die.");
+	}
+	js.terminated=true;
+	return;
+}
+
+Bot_Commands["RESTART"] = new Bot_Command(90,false,false);
+Bot_Commands["RESTART"].command = function (target,onick,ouh,srv,lvl,cmd) {
+	for (s in bot_servers) {
+		bot_servers[s].writeout("QUIT :Restarting as per " + onick);
+	}
+	exit();
+	return;
+}
+
+Bot_Commands["GROUPS"] = new Bot_Command(50,true,false);
+Bot_Commands["GROUPS"].command = function (target,onick,ouh,srv,lvl,cmd) {
+	for (g in msg_area.grp_list) {
+		srv.o(target,"[" + msg_area.grp_list[g].number + "] "
+			+ msg_area.grp_list[g].description);
+	}
+	return;
+}
+
+/* HELP needs to be rewritten.
+case "HELP":
+	if (!cmd[1])
+		cmd[1] = "HELP";
+	var search = "!" + cmd[1].toUpperCase();
+	if (help_file.open("r")) {
+		while (!help_file.eof) {
+			var hf_line = help_file.readln();
+			if (hf_line && (hf_line == search)) {
+				while (!hf_line[0] != "@") {
+					hf_line = help_file.readln();
+					if (hf_line[0] == "@") {
+						break;
+					} else if (hf_line[0] == "^") {
+						hf_line = parseInt(hf_line.slice(1));
+						if (bot_access(onick,ouh) < hf_line)
+							break;
+					} else if (hf_line[0] == ":") {
+						
+						var str = hf_line.slice(1);
+						if (!str)
+							str = " ";
+						this.writeout("NOTICE "+onick+" :" + str);
+					}
+				}
+				if ((hf_line[0] == "@") && hf_line[1]) {
+					hf_line = hf_line.slice(1);
+					this.writeout("NOTICE "+onick+" :Restricted to access level " + hf_line);
+				}
+				help_file.close();
+				break;
+			}
+		}
+	}
+	break;
+*/
+
+Bot_Commands["SUBS"] = new Bot_Command(50,true,false);
+Bot_Commands["SUBS"].command = function (target,onick,ouh,srv,lvl,cmd) {
+	var groupnum = parseInt(cmd[1]);
+	if (!msg_area.grp_list[groupnum]) {
+		srv.o(target,"Group number " + cmd[1] + " doesn't exist!");
+		return;
+	}
+	var sg = msg_area.grp_list[groupnum].sub_list;
+	for (g in msg_area.grp_list[groupnum].sub_list) {
+		srv.o(target,"[" + sg[g].number + "] " + sg[g].description
+			+ " (" + sg[g].code + ")");
+	}
+	return;
+}
+Bot_Commands["SUBGROUPS"] = Bot_Commands["SUBS"];
+
+Bot_Commands["READ"] = new Bot_Command(50,true,false);
+Bot_Commands["READ"].command = function (target,onick,ouh,srv,lvl,cmd) {
+	if (!cmd[2]) { // user wants to list msgs?
+		var msgs = new MsgBase(cmd[1]);
+		msgs.open();
+		srv.o(target,"There are " + msgs.total_msgs + " messages to read from "
+			+ msgs.first_msg + " to " + msgs.last_msg);
+		msgs.close();
+		return;
+	} else if (cmd[2]) { // reading a msg
+		var msgs = new MsgBase(cmd[1]);
+		var mn = parseInt(cmd[2]);
+		msgs.open();
+		var mh = msgs.get_msg_header(mn);
+		srv.o(target,"  To: " + mh.to);
+		srv.o(target,"From: " + mh.from);
+		srv.o(target,"Subj: " + mh.subject);
+		var my_msg = msgs.get_msg_body(mn).split("\r\n");
+		for (line in my_msg) {
+			if (!my_msg[line])
+				my_msg[line] = " ";
+			srv.o(target, my_msg[line]);
+		}
+		msgs.close();
+		return;
+	}
+	return;
+}
+
+Bot_Commands["FINGER"] = new Bot_Command(50,true,false);
+Bot_Commands["FINGER"].command = function (target,onick,ouh,srv,lvl,cmd) {
+	var udpfinger = false;
+	if (cmd[0] == "UDPFINGER")
+		udpfinger = true;
+	var f_host;
+	var f_user;
+	if (cmd[1].match(/[@]/)) { // user@host
+		f_host = cmd[1].split("@")[1];
+		f_user = cmd[1].split("@")[0];
+	} else { // assume just host
+		f_host = cmd[1];
+		f_user = "";
+	}
+	var f_sock;
+	if (udpfinger) {
+		f_sock = new Socket(SOCK_DGRAM);
+		f_sock.nonblocking = true;
+	} else {
+		f_sock = new Socket();
+	}
+	if (!f_sock.connect(f_host,79)) {
+		srv.o(target,"Couldn't connect to "+ f_host +": " + f_sock.last_error);
+		return;
+	} else {
+		f_sock.send(f_user + "\r\n");
+		var f_line_count = 0;
+		var timeout = time()+10;
+		while(f_sock.is_connected) {
+			if (udpfinger) {
+				var tmp = f_sock.read();
+				if (tmp) {
+					var udp_lines = tmp.split("\r\n");
+					for (ul in udp_lines) {
+						srv.o(target, strip_ctrl(udp_lines[ul]));
+						f_line_count++;
+						if ((f_line_count > 10) && (lvl < 75) &&
+						    ((target[0] == "#") || (target[0] == "&")) ) {
+							srv.o(target,"*** Connection Terminated "
+								+ "(output squelched after 10 lines)");
+							return;
+						}
+					}
+					return;
+				}
+			 } else {
+				srv.o(target, strip_ctrl(f_sock.readline()));
+				f_line_count++;
+				if ((f_line_count > 10) &&
+				    (lvl < 75) &&
+				    ((target[0] == "#") ||
+				     (target[0] == "&")) ) {
+					srv.o(target,"*** Connection Terminated "
+						+"(output squelched after 10 lines)");
+					return;
+				}
+			}
+			if (time() >= timeout) {
+				srv.o(target,"*** Your query timed out after 10 seconds.");
+				return;
+			}
+		}
+		f_sock.close();
+	}
+	return;
+}
+Bot_Commands["UDPFINGER"] = Bot_Commands["FINGER"];
+
+Bot_Commands["ADDQUOTE"] = new Bot_Command(80,true,false);
+Bot_Commands["ADDQUOTE"].command = function (target,onick,ouh,srv,lvl,cmd) {
+	cmd.shift();
+	var the_quote = cmd.join(" ");
+	quotes.push(the_quote);
+	srv.o(target,"Thanks for the quote!");
+	return;
+}
+
+Bot_Commands["GREET"] = new Bot_Command(50,false,false);
+Bot_Commands["GREET"].command = function (target,onick,ouh,srv,lvl,cmd) {
+	var usr = new User(system.matchuser(onick));
+	if (!usr.number) {
+		srv.o(target,"You don't exist.");
+		return;
+	}
+	if (cmd[1]) {
+		if (cmd[1].toUpperCase() == "NULL")
+			cmd[1] = "";
+		cmd.shift();
+		var the_greet = cmd.join(" ");
+		srv.o(target,"Your greet has been changed.");
+		usr.comment = the_greet;
+		return;
+	} else {
+		srv.o(target,"[" + onick + "] " + usr.comment);
+		return;
+	}
+	return;
+}
+
+Bot_Commands["QUOTE"] = new Bot_Command(0,false,false);
+Bot_Commands["QUOTE"].command = function (target,onick,ouh,srv,lvl,cmd) {
+	if (cmd[1]) {
+		cmd.shift();
+		var searched_quotes = new Array();
+		var search_params = cmd.join(" ");
+		var lucky_number;
+		var found_a_quote = false;
+		while (searched_quotes.length < quotes.length) {
+			lucky_number = random(quotes.length);
+			if (!searched_quotes[lucky_number]) {
+				if (quotes[lucky_number].toUpperCase().match(search_params.toUpperCase())) {
+					srv.o(target, quotes[lucky_number]);
+					found_a_quote = true;
+					break;
+				}
+				searched_quotes[lucky_number] = true;
+			}
+		}
+		if (!found_a_quote)
+			srv.o(target,"Couldn't find a quote that matches your criteria.");
+		return;
+	}
+	srv.o(target, quotes[random(quotes.length)]);
+	return;
+}
+
+Bot_Commands["SAVE"] = new Bot_Command(80,false,false);
+Bot_Commands["SAVE"].command = function (target,onick,ouh,srv,lvl,cmd) {
+	if (save_everything()) {
+		srv.o(target,"Data successfully written.  Congratulations.");
+	} else {
+		srv.o(target,"Oops, couldn't write to disk.  Sorry, bud.");
+	}
+	return;
+}
+
+Bot_Commands["EXEC"] = new Bot_Command(99,true,true);
+Bot_Commands["EXEC"].command = function (target,onick,ouh,srv,lvl,cmd) {
+	cmd.shift();
+	var query = cmd.join(" ");
+	var this_poutput = system.popen(query);
+	if (!this_poutput) {
+		srv.o(target,"Command failed. :(");
+		return;
+	}
+	for (line in this_poutput) {
+		if (!this_poutput[line])
+			this_poutput[line] = " ";
+		srv.o(target, this_poutput[line]);
+	}
+	return;
+}
+
diff --git a/exec/load/ircbot_functions.js b/exec/load/ircbot_functions.js
new file mode 100644
index 0000000000000000000000000000000000000000..0f508a5ada6256428cf765ba8e0e47394bfbc368
--- /dev/null
+++ b/exec/load/ircbot_functions.js
@@ -0,0 +1,256 @@
+// $Id$
+/*
+
+ 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
+
+ Copyright 2010 Randolph E. Sommerfeld <sysop@rrx.ca>
+
+*/
+
+/********** Command Processors. **********/
+function Server_command(cmd,onick,ouh) {
+	var cmdline = cmd.join(" ");
+	switch (cmd[0]) {
+		case "001":	// "Welcome."
+			this.is_registered = true;
+			break;
+		case "352":	// WHO reply.  Process into local cache.
+			var nick = cmd[6].toUpperCase();
+			this.users[nick] = new Server_User(cmd[3] + "@" + cmd[4]);
+			break;
+		case "433":	// Nick already in use.
+			this.juped = true;
+			var newnick = this.nick+"-"+random(50000).toString(36);
+			this.writeout("NICK " + newnick);
+			this.curnick = newnick;
+			log("*** Trying alternative nick, my nick is jupitered.  "
+				+ "(Temp: " + newnick + ")");
+			break;
+		case "JOIN":
+			if (cmd[1][0] == ":")
+				cmd[1] = cmd[1].slice(1);
+			var chan = this.channel[cmd[1].toUpperCase()];
+			if ((onick == this.curnick) && chan && !chan.is_joined) {
+				chan.is_joined = true;
+				this.writeout("WHO " + cmd[1]);
+				break;
+			}
+			// Someone else joining.
+			this.users[onick.toUpperCase()] = new Server_User(ouh);
+			var lvl = this.bot_access(onick,ouh);
+			if (lvl >= 50) {
+				var usr = new User(system.matchuser(onick));
+				if (lvl >= 60)
+					this.writeout("MODE " + cmd[1] + " +o " + onick);
+				if (usr.number > 0) {
+					if (usr.comment)
+						this.o(cmd[1],"[" + onick + "] " + usr.comment);
+					login_user(usr);
+				}
+			}
+			break;
+		case "PRIVMSG":
+			if ((cmd[1][0] == "#") || (cmd[1][0] == "&")) {
+				var chan = this.channel[cmd[1].toUpperCase()];
+				if (!chan)
+					break;
+				if (!chan.is_joined)
+					break;
+				cmd[2] = cmd[2].slice(1);
+				if ( (cmd[2].toUpperCase() == command_prefix.toUpperCase()) 
+					 && cmd[3]) {
+					cmd[3] = cmd[3].toUpperCase();
+					cmd.shift();
+					cmd.shift();
+					cmd.shift();
+					this.check_bot_command(chan.name,onick,ouh,cmd);
+					break;
+				}
+			} else if (cmd[1].toUpperCase() == 
+			           this.curnick.toUpperCase()) { // MSG?
+				cmd[2] = cmd[2].slice(1).toUpperCase();
+				cmd.shift();
+				cmd.shift();
+				if (cmd[0][0] == "\1") {
+					cmd[0] = cmd[0].slice(1).toUpperCase();
+					cmd[cmd.length-1] = cmd[cmd.length-1].slice(0,-1);
+					this.ctcp(onick,ouh,cmd);
+					break;
+				}
+				this.check_bot_command(onick,onick,ouh,cmd);
+			}
+			break;
+		case "PING":
+			this.writeout("PONG :" + IRC_string(cmdline));
+			break;
+		case "ERROR":
+			this.sock.close();
+			this.sock = 0;
+			break;
+		default:
+			break;
+	}
+}
+
+function Server_CTCP(onick,ouh,cmd) {
+	switch (cmd[0]) {
+		case "DCC":
+			if (cmd[4]) {
+				log("cmd1:" + cmd[1] + ":");
+				log("cmd2:" + cmd[2] + ":");
+				log("cmd3:" + cmd[3] + ":");
+				log("cmd4:" + cmd[4] + ":");
+				if ((cmd[1].toUpperCase() == "CHAT")
+					&& (cmd[2].toUpperCase() == "CHAT")
+					&& (parseInt(cmd[3]) == cmd[3])
+					&& (parseInt(cmd[4]) == cmd[4])) {
+						var ip = int_to_ip(cmd[3]);
+						var port = parseInt(cmd[4]);
+						var sock = new Socket();
+						sock.connect(ip, port, 3 /* Timeout */);
+						if (sock.is_connected) {
+							sock.write("Enter your password.\r\n");
+							dcc_chats.push(new DCC_Chat(sock,onick));
+						}
+				}
+			}
+			break;
+		case "PING":
+			var reply = "PING ";
+			if (parseInt(cmd[1]) == cmd[1]) {
+				reply += cmd[1];
+				if (cmd[2] && (parseInt(cmd[2]) == cmd[2]))
+					reply += " " + cmd[2];
+				this.ctcp_reply(onick, reply);
+			}
+			break;
+		case "VERSION":
+			this.ctcp_reply(onick, "VERSION "
+				+ "Synchronet IRC Bot by Randy E. Sommerfeld <cyan@rrx.ca>");
+			break;
+		case "FINGER":
+			this.ctcp_reply(onick, "FINGER "
+				+ "Finger message goes here.");
+			break;
+		default:
+			break;
+	}
+	return;
+}
+
+function Server_CTCP_Reply(nick,str) {
+	this.writeout("NOTICE " + nick + " :\1" + str + "\1");
+}
+
+function Server_check_bot_command(target,onick,ouh,cmd) {
+	var access_level = this.bot_access(onick,ouh);
+	var botcmd = Bot_Commands[cmd[0]];
+	if (botcmd) {
+		if (botcmd.ident_needed && !this.users[onick.toUpperCase()].ident) {
+			this.o(target,"You must be identified to use this command.");
+			return 0;
+		}
+		if (access_level < botcmd.min_security) {
+			this.o(target,"You do not have sufficient access to this command.");
+			return 0;
+		}
+		if ((botcmd.args_needed == true) && !cmd[1]) {
+			this.o(target,"Hey buddy, I need some arguments for this command.");
+			return 0;
+		} else if ((parseInt(botcmd.args_needed) == botcmd.args_needed)
+					&& !cmd[botcmd.args_needed]) {
+			this.o(target,"Hey buddy, incorrect number of arguments provided.");
+			return 0;
+		}
+		/* If we made it this far, we're good. */
+		botcmd.command(target,onick,ouh,this,access_level,cmd);
+		return 1;
+	}
+	return 0; /* No such command */
+}
+
+//////////////////// Non-object Functions ////////////////////
+
+/* Save everything */
+function save_everything() {
+	if (!config.open("r+"))
+		return false;
+
+	config.iniSetValue(null, "command_prefix", command_prefix);
+	config.iniSetValue(null, "real_name", real_name);
+	config.iniSetValue(null, "help_filename", help_filename);
+	config.iniSetValue(null, "config_write_delay", config_write_delay);
+	config.iniSetValue(null, "squelch_list", squelch_list.join(","));
+
+	for (m in masks) {
+		var uid_str = format("%04u", m);
+		var us_file = new File("/home/bbs/data/user/" +uid_str+ ".ircbot.ini");
+		if (us_file.open("r+")) {
+			us_file.iniSetValue(null, "masks", masks[m].join(","));
+			us_file.close();
+		}
+	}
+
+	for (q in quotes) {
+		config.iniSetValue("quotes", q, quotes[q]);
+	}
+
+	config.close();
+
+	for (s in Module_Save_Data) {
+		Module_Save_Data[s]();
+	}
+
+	Config_Last_Write = time();
+
+	return true;
+}
+
+function login_user(usr) {
+	usr.connection = "IRC";
+	usr.logontime = time();
+}
+
+// return the access level of this user.
+function Server_Bot_Access(nick,uh) {
+	var ucnick = nick.toUpperCase();
+	if (this.users[ucnick].ident) {
+		var usrnum = this.users[ucnick].ident;
+		var thisuser = new User(usrnum);
+		return thisuser.security.level;
+	}
+	var usrnum = system.matchuser(nick);
+    if (!usrnum)
+        return 0;
+    var thisuser = new User(usrnum);
+    for (m in masks[usrnum]) {
+        if (wildmatch(uh,masks[usrnum][m]))
+            return thisuser.security.level;
+    }
+    return 0; // assume failure
+}
+
+function Server_writeout(str) {
+	log("--> " + this.host + ": " + str);
+	this.sock.write(str + "\r\n");
+}
+
+function Server_target_out(target,str) {
+	for (c in squelch_list) {
+		if (target.toUpperCase() == squelch_list[c].toUpperCase())
+			return;
+	}
+	var outstr = "PRIVMSG " + target + " :" + str;
+	log("--> " + this.host + ": " + outstr);
+	this.sock.write(outstr + "\r\n");
+}
+