
/*
	Jazz Jackrabbit 2 ListServer written for Node.JS
	Created by djazz (@daniel_hede)
	http://djazz.mine.nu/
	Fully compatible with JJ2 and Epic's listserver
*/
'use strict'; // For better debugging



/* SETTINGS */


var bindAddress  = "list1.digiex.net";	// Listen on/bind to this hostname/address
var localAddress = "94.23.157.50";	// Use this address when connecting to other listservers

var serverNetTimeoutTime   = 30;	// If no connection within 30 secs, abort attempt (timeout)
var serverNetReconnectTime = 10*60;	// Wait ten minutes before reconnecting

var mtime = new Date("5 November 2012 22:47:47 GMT+0200");	// Listserver creation/modification date and time, change only if you are the author
var listserverVersion = "1.02";		// Program version







// Modules

var net = require('net');
var fs = require('fs');
var path = require('path');
var dns = require('dns');
var http = require('http');
var url = require('url');
var mime = require('mime');
var os = require('os');

// Global variables

var serverlist = [];
var clients = [];
var listservers = {};
var listclients = {};
var specifiedservers = [];
var motd = "";
var banlist = [];

var gametypelog = [0, 0, 0, 0];
var totalgames = 0;

var serverStartTime = Date.now();

// Global functions

function Server(config) {
	if (!config) {
		config = {};
	}
	this.address = config.address;
	this.port = parseInt(config.port, 10) || 0;
	this.location = config.location || 'local';
	this['private'] = !!(config['private'] || false);
	this.gametype = parseInt(config.gametype, 10) || 0;
	this.version = new Buffer(4);
	if (Buffer.isBuffer(config.version)) {
		config.version.copy(this.version, 0, 0, 4);
	} else if (typeof config.version === 'string') {
		this.version.write(config.version, 0, 4, 'binary');
	}
	this.starttime = new Date(Date.now() - (config.uptime || 0) * 1000);
	if (!config.capacity) {
		config.capacity = [0, 0];
	}
	this.capacity = [+config.capacity[0] || 0, +config.capacity[1] || 0];
	this.servername = config.servername || '';
};

Server.prototype.getUptime = function () {
	return Math.round((Date.now() - this.starttime.getTime()) / 1000);
};

Server.prototype.getASCII = function () {
	var gt = 'unknown';
	switch(this.gametype) {
		case 1: gt = 'battle'; break;
		case 2: gt = 'treasure'; break;
		case 3: gt = 'ctf'; break;
	}
	
	return this.address+':'+this.port+' '+this.location+' '+(this['private']?'private':'public')+' '+gt+' 1.'+this.version.toString('binary')+' '+this.getUptime()+' ['+this.capacity.join('/')+'] '+this.servername.replace(/\r\n/g, ' ');
};

Server.prototype.getBinaryPacket = function () {
	var address = new Buffer(this.address.split(".").reverse());
	var port = new Buffer(2);
	port[1] = parseInt(this.port / 256, 10);
	port[0] = parseInt(this.port - port[1]*256, 10);
	var packet = new Buffer("l"+"addr"+"po"+this.servername, 'binary');
	address.copy(packet, 1);
	port.copy(packet, 1+4);
	packet[0] = packet.length;
	return packet;
};

Server.prototype.getAddPacket = function () {
	var packet = new Buffer("lt"+"addr"+"po"+"f"+"vers"+"upti"+"ca"+this.servername+"\x00", 'binary');
	packet[0] = packet.length-2;
	packet[1] = 0x00; // Add server packet
	
	new Buffer(this.address.split('.').reverse()).copy(packet, 2); // IP address
	packet.writeUInt16LE(this.port, 6);
	packet[8] = this.gametype | (this['private'] & 1); // Flags
	this.version.copy(packet, 9);
	packet.writeUInt32LE(this.getUptime(), 13);
	packet[17] = this.capacity[0];
	packet[18] = this.capacity[1];
	// Servername already added
	return packet;
};

Server.prototype.getRemovePacket = function () {
	var packet = new Buffer("lt"+"addr"+"po", 'binary');
	packet[0] = packet.length-2;
	packet[1] = 0x01; // Remove server packet
	
	new Buffer(this.address.split('.').reverse()).copy(packet, 2); // IP address
	packet.writeUInt16LE(this.port, 6);
	
	return packet;
};

Server.prototype.getUpdatePlayersPacket = function () {
	var packet = new Buffer("lt"+"addr"+"po"+"p", 'binary');
	packet[0] = packet.length-2;
	packet[1] = 0x02; // Update player count packet
	
	new Buffer(this.address.split('.').reverse()).copy(packet, 2); // IP address
	packet.writeUInt16LE(this.port, 6);
	packet[8] = this.capacity[0];
	
	return packet;
};

function getProgramInfo() {
	return "Jazz Jackrabbit 2 List Server v"+listserverVersion+" of "+mtime.toUTCString();
}

function isBanned(ip) {
	var adr = ip.split("."), line, banadr, found, i, j;
	for(i=0; i < banlist.length; i+=1) {
		line = banlist[i];
		banadr = line.split(".");
		found = 0;
		for(j=0; j < 4; j+=1) {
			if(banadr[j]==='*') {found+=1;}
			else if(banadr[j]===adr[j]) {found+=1;}
		}
		if(found===4) {return true;}
	}
	return false;
}

function loadMOTD(callback) {
	fs.readFile(path.join(__dirname, "motd.txt"), "utf8", function (err, data) {
		if(err) {console.log("MOTD loading error: "+err); return;}
		motd = new Buffer(data.substr(0, data.length-1), "binary");
		if(callback) {callback();}
	});
}

function loadBanlist(callback) {
	fs.readFile(path.join(__dirname, "banlist.txt"), function (err, data) {
		if(err) {console.log("Banlist loading error: "+err); return;}
		var bans = data.toString("utf8").split("\n"), i;
		banlist = [];
		for(i = 0; i < bans.length; i++) {
			if(bans[i]!=="" && bans[i].substr(0, 1)!==";") {
				banlist.push(bans[i].trim());
			}
		}
		var bannedIps = [];
		for(i=0; i < serverlist.length; i++) {
			if(isBanned(serverlist[i].address)) {
				if(bannedIps.indexOf(clients[i].remoteAddress) === -1) {
					bannedIps.push(clients[i].remoteAddress);
				}
				serverNetBroadcast(serverlist[i].getRemovePacket());
				clients[i].destroy();
				serverlist.splice(i, 1);
				clients.splice(i, 1);
				i--;
			}
		}

		for(i in listclients) {
			if (isBanned(i)) {
				if(bannedIps.indexOf(i) === -1) {
					bannedIps.push(i);
				}
				listclients[i].socket.destroy();
				delete listclients[i];
			}
		}

		if(bannedIps.length > 0) {
			console.log(timestamp(), "Kicking the following addresses:", bannedIps.join(", "));
		}
		
		if(callback) {callback();}
	});
}

function sortListFunction(a, b) {
	
	var players = b.capacity[0]-a.capacity[0];
	
	if(players !== 0) {
		return players;
	}
	else {
		return b.starttime-a.starttime;
	}
}

function timestamp() {
	var now = new Date();
	return now.toString()+" ";
}

function buildServerList() {
	var list = [];
	
	for(var i=0; i < serverlist.length; i++) {
		list.push(serverlist[i]);
	}
	
	for(var i in listservers) {
		for(var j in listservers[i].serverlist) {
			list.push(listservers[i].serverlist[j]);
		}
	}
	
	for(var i in listclients) {
		for(var j in listclients[i].serverlist) {
			list.push(listclients[i].serverlist[j]);
		}
	}
	
	var outputlist = [];
	var addresses = [];
	for(var i=0; i < list.length; i++) {
		if(isBanned(list[i].address)) continue;
		var addr = list[i].address+':'+list[i].port;
		var index = addresses.indexOf(addr);
		if(index === -1) {
			addresses.push(addr);
			outputlist.push(list[i]);
		}
	}
	
	outputlist.sort(sortListFunction);
	
	return outputlist;
}

function loadServers(callback) {
	callback = callback || function () {};
	fs.readFile(path.join(__dirname, "servers.txt"), function (err, data) {
		if(err) {console.log(timestamp(), "servers.txt loading error: "+err); callback(); return;}
		if(data.length === 0) {console.log(timestamp(), "servers.txt is empty, ignoring.."); callback(); return;}
		var lines = data.toString("utf8").split("\n");
		
		var list = [];
		var counter = 0;
		var total = 0;
		
		function find_ip(ip) {
			for(var i=0; i < list.length; i++) {
				if(list[i].ip === ip) return true;
			}
			return false;
		}
		
		function allLoaded() {
			for(var i=0; i < list.length; i++) {
				if(!listservers.hasOwnProperty(list[i].ip)) {
					if (listclients[list[i].ip] && listclients[list[i].ip].socket) {
						listclients[list[i].ip].socket.destroy();
					}
					listservers[list[i].ip] = {serverlist: {}, host: list[i].host, starttime: 0};
					startServerNet(list[i].ip);
					console.log(timestamp(), 'Added '+list[i].host+' ('+list[i].ip+') to synclist');

				}
			}
			for(var i in listservers) {
				if(!find_ip(i)) {
					if (listclients[i] && listclients[i].socket) {
						listclients[i].socket.destroy();
					}
					console.log(timestamp(), 'Removed '+listservers[i].host+' ('+i+') from synclist');
					listservers[i].close();

				}
			}
			
			callback();
		}

		specifiedservers = [];
		
		for(var i=0; i < lines.length; i++) {
			if(lines[i] === "" || lines[i].substr(0, 1) === ";") {continue;}
			lines[i] = lines[i].split(";")[0].trim();
			if(lines[i].length === 0) continue;
			total++;
			(function (hostname) {
				var index = specifiedservers.length;
				specifiedservers[index] = {host: hostname};
				dns.lookup(hostname, 4, function (err, address, family) {
					if(err) {
						console.log(timestamp(), hostname+': DNS '+err);
					}
					else {
						if(specifiedservers[index].host === hostname) {
							specifiedservers[index].ip = address;
						}
						list.push({
							ip: address,
							host: hostname
						});
					}
					counter++;
				
					if(counter === total) {
						allLoaded();
					}
				});
			}(lines[i]));
		}
		if(total === 0) {
			allLoaded();
		}
	});
}

function startServerNet(address) {
	
	var listserver = listservers[address];
	
	listserver.connected = false;
	var connOptions = {port: 10056, host: listserver.host};
	if(localAddress) {
		connOptions.localAddress = localAddress;
	}
	var servernet = net.connect(connOptions);
	
	servernet.setTimeout(serverNetTimeoutTime*1000, function () {
		//console.log('timed out');
		servernet.destroy();
	});
	
	listserver.socket = servernet;
	
	var databuffer = [];
	
	var delay;
	var pinger;
	var timeout = setTimeout(function () {
		servernet.destroy();
	}, serverNetTimeoutTime*1000);
	
	servernet.on('error', function (err) {
		//console.log(timestamp(), 'ServerNet '+listserver.host+' -> '+err);
	});
	servernet.on('connect', function () {
		console.log(timestamp(), 'ServerNet '+listserver.host+' -> Connected');
		clearTimeout(timeout);
		pinger = setInterval(function () {
			servernet.write(new Buffer([0x00, 0x80])); // Ping
		}, 20*1000);
		listserver.connected = true;
		listserver.starttime = new Date();
	});
	servernet.on('data', function (data) {
		clearTimeout(timeout);
		timeout = setTimeout(function () {
			servernet.destroy();
		}, serverNetTimeoutTime*1000);
		Array.prototype.push.apply(
			databuffer,
			Array.prototype.slice.apply(data) // Convert from buffer to array
		); // Append data to array
		
		while(databuffer.length >= databuffer[0]+2) {
			var packet = new Buffer(databuffer.splice(0, databuffer[0]+2));
			// Note: packetLength = packet.length + 2;
			parseServerNetPacket(packet, listserver);
		}
		
	});
	servernet.on('close', function () {
		if(listserver.connected) {
			// Only show when getting disconnected after being connected
			console.log(timestamp(), 'ServerNet '+listserver.host+' -> Disconnected');
			if (databuffer.length > 0) {
				console.log(new Buffer(databuffer).toString('binary'));
				//console.log(new Buffer(databuffer));
			}
		}
		else {
			//console.log(timestamp(), 'ServerNet '+listserver.host+' -> Unable to connect (timeout)');
		}
		clearTimeout(timeout);
		clearInterval(pinger);
		listserver.starttime = 0;
		listserver.connected = false;
		for(var i in listserver.serverlist) {
			delete listserver.serverlist[i];
		}
		databuffer = [];
		delay = setInterval(function () {
			if(listclients[address]) return;
			clearInterval(delay);
			servernet.connect(10056, address);
			timeout = setTimeout(function () {
				servernet.destroy();
			}, serverNetTimeoutTime*1000);
		}, serverNetReconnectTime*1000);
	});
	
	

	listserver.close = function () {
		clearTimeout(timeout);
		clearInterval(delay);
		clearInterval(pinger);
		servernet.destroy();
		delete listservers[address];
	};
}

function parseServerNetPacket(packet, listserver) {
	var packetType = packet[1];
	switch(packetType) {
		case 0x00: // Add
			//console.log(packet, packet.toString('binary'));
			var ip = Array.prototype.slice.apply(packet.slice(2, 2+4)).reverse().join(".");
			var port = packet.readUInt16LE(6);
			var flags = packet[8];
			var version = packet.slice(9, 9+4);
			var uptime = packet.readUInt32LE(13, 13+4);
			var capacity = [packet[17], packet[18]];
			var servername = packet.slice(19, packet.length-1).toString('binary');
			//console.log('ServerNet', 'add', ip+':'+port, servername);
			listserver.serverlist[ip+':'+port] = new Server({
				address: ip,
				port: port,
				location: 'mirror',
				'private': flags & 1,
				gametype: ((flags >> 1) & 3),
				version: version,
				uptime: uptime,
				capacity: capacity,
				servername: servername
			});
			break;
		
		case 0x01: // Remove
			var ip = Array.prototype.slice.apply(packet.slice(2, 2+4)).reverse().join(".");
			var port = packet.readUInt16LE(6);
			//console.log('ServerNet', 'remove', ip+':'+port);
			delete listserver.serverlist[ip+':'+port];

			break;
		
		case 0x02: // Playercount update
			var ip = Array.prototype.slice.apply(packet.slice(2, 2+4)).reverse().join(".");
			var port = packet.readUInt16LE(6);
			var playerCount = packet[8];
			//console.log('ServerNet', 'update', ip+':'+port, playerCount);
			if(listserver.serverlist[ip+':'+port]) {
				listserver.serverlist[ip+':'+port].capacity[0] = playerCount;
			}
			break;
		
		case 0x80: // Ping
			// ??
			//console.log('ping');
			break;
		
		default:
			console.log(timestamp(), "ServerNet - Unknown packet (0x"+packetType.toString(16).toUpperCase()+")");
			break;
	}
}

function serverNetBroadcast(packet) {
	for(var i in listservers) {
		listservers[i].socket.write(packet);
	}
	for(var i in listclients) {
		listclients[i].socket.write(packet);
	}
}

function zf(n) {
	return (n>9?n:"0"+n);
} // For minutes and seconds (zerofill)

function sl_conv(time) {
	// 86400 = 24*60*60
	// 3600 = 60*60
	var days = Math.floor(time / 86400);
	return days+" day"+(days===1?'':'s')+", "+Math.floor((time % 86400)/3600)+":"+zf(Math.floor((time % 3600)/60))+":"+zf((time % 3600)%60);
}


// Load the files for the first time
// Then start the main code

loadMOTD(function () {
	loadBanlist(function () {
		main();
	});
});


// Main code

function main () {
	
	console.log(timestamp(), getProgramInfo());

	loadServers();
	
	// Watch files for changes
	fs.watch(path.join(__dirname, "servers.txt"), function () {
		console.log(timestamp(), "List of synced servers updated");
		loadServers();
	});
	fs.watch(path.join(__dirname, "motd.txt"), function () {
		console.log(timestamp(), "Message of the day updated");
		loadMOTD();
	});
	fs.watch(path.join(__dirname, "banlist.txt"), function () {
		console.log(timestamp(), "Banlist updated");
		loadBanlist();
	});
	
	// Binary serverlist
	var binaryList = net.createServer(function (socket) {

		socket.on('error', function (err) {
			console.log(timestamp(), 'binary list: '+err);
		});

		socket.write("\x07"+"LIST"+"\x01\x01");
		
		var outputlist = buildServerList();
		
		for(var i=0; i < outputlist.length; i++) {
			socket.write(outputlist[i].getBinaryPacket());
		}
		socket.end();
		
	}).listen(10053, bindAddress);
	binaryList.on('error', function (e) {
		throw e;
	});
	


	// Add/update server
	var serverAddition = net.createServer(function (socket) {
		
		var address = socket.remoteAddress;
		
		socket.on('error', function (err) {
			console.log(timestamp(), 'server addition: '+err);
			socket.destroy();
		});

		if(isBanned(socket.remoteAddress)) {
			socket.end("You have been banned from the ListServer");
			console.log(timestamp(), "Denied "+socket.remoteAddress+" from listing a server (banned IP)");
			return;
		}

		var pinger = setInterval(function () {
			socket.write(new Buffer([0x00])); // Ping
		}, 20*1000);

		socket.on('data', function (data) {
			var index = clients.indexOf(socket);
			//console.log(timestamp(), "Got data from "+index, data.length, data);
			
			if(data.length === 42) { // List a new server
				if(index === -1) { // New client, add it
					clients.push(socket);
					serverlist.push(new Server());
					index = clients.indexOf(socket);
				}
				var server = serverlist[index];
				server.address = socket.remoteAddress;
				
				server.port = data.readUInt16LE(0);
				var i = 2;
				var name = "";
				while(data[i] > 0 && i+2 < 33) {
					name += String.fromCharCode(data[i++]);
				}
				server.servername = name;
				
				i = 2+33;
				server.capacity[0] = data[i++];
				server.capacity[1] = data[i++];
				var flags = data[i++];
				server['private'] = flags & 1;
				server.gametype = (flags >> 1) & 3;
				data.copy(server.version, 0, i, i+4);
				console.log(timestamp(), socket.remoteAddress+' listed server "'+server.servername+'"');
				
				var gm = server.gametype;
				if(gm > 3) gm = 0;
				gametypelog[gm]++;
				totalgames++;

				serverNetBroadcast(server.getAddPacket());
				
			}
			else if(data.length === 2 && index !== -1) {
				var server = serverlist[index];
				switch(data[0]) {
					case 0x00: // Player count update
						server.capacity[0] = data[1];
						serverNetBroadcast(server.getUpdatePlayersPacket());
						break;
				}
			}
			else {
				//console.log(timestamp(), socket.remoteAddress+" tried to list from a browser");
				socket.end("Welcome to the ELSE-statement, program.", 'utf8');
			}
		});
		socket.on('close', function () {
			var index = clients.indexOf(socket);
			clearInterval(pinger);
			if(index !== -1) {
				console.log(timestamp(), address+" delisted");
				serverNetBroadcast(serverlist[index].getRemovePacket());
				serverlist.splice(index, 1);
				clients.splice(index, 1);
			} else {
				console.log(timestamp(), "Unknown server delisted ???")
			}
		});
		
		
	}).listen(10054, bindAddress);
	serverAddition.on('error', function (e) {
		throw e;
	});
	
	

	// Status page for the listserver
	var statusServer = net.createServer(function (socket) {
		
		socket.on('error', function (err) {
			console.log(timestamp(), 'status page: '+err);
		});

		var list = buildServerList();
		var ownServers = 0;
		var mirroredServers = 0;
		for(var i=0; i < list.length; i++) {
			if(list[i].location === 'mirror') {
				mirroredServers++;
			}
			else {
				ownServers++;
			}
		}
		
		var mem = process.memoryUsage().rss;
		
		var lines = [
			getProgramInfo(),
			"Created by djazz - Powered by node.js",
			"",
			"Uptime                           : "+sl_conv(Math.round((Date.now() - serverStartTime)/1000)),
			"Hostname                         : "+(bindAddress || ""),
			"",
			"Local servers                    : "+ownServers,
			"Mirrored servers                 : "+mirroredServers,
			"Servers in list                  : "+(ownServers+mirroredServers),
			"",
			"Number of Unknown games          : "+gametypelog[0]+" ("+(Math.round(gametypelog[0]/totalgames*100) || 0)+"%)",
			"Number of Battle games           : "+gametypelog[1]+" ("+(Math.round(gametypelog[1]/totalgames*100) || 0)+"%)",
			"Number of Treasure games         : "+gametypelog[2]+" ("+(Math.round(gametypelog[2]/totalgames*100) || 0)+"%)",
			"Number of Capture the flag games : "+gametypelog[3]+" ("+(Math.round(gametypelog[3]/totalgames*100) || 0)+"%)",
			"Total number of games            : "+totalgames,
			"",
			"Memory usage                     : "+Math.round((mem/1024/1024)*10)/10+" MB ("+Math.round((mem/os.totalmem())*1000)/10+"%)",
			"Node version                     : "+process.version,
			"CPU architecture & platform      : "+process.arch+" "+process.platform,
			""
		];
		
		var listServerCount = specifiedservers.length;
		var numConnectedTo = 0;
		var listservers_str = [];
		var index = 1;
		
		for(var i=0; i < listServerCount; i++) {
			var listserver_info = "";
			if(specifiedservers[i].ip && ((listservers[specifiedservers[i].ip] && listservers[specifiedservers[i].ip].connected) || listclients[specifiedservers[i].ip])) {
				numConnectedTo++;
				var isServer = listservers[specifiedservers[i].ip] && listservers[specifiedservers[i].ip].connected;
				var isClient = !!listclients[specifiedservers[i].ip];
				var totalGames = (isServer && Object.keys(listservers[specifiedservers[i].ip].serverlist).length) || (isClient && Object.keys(listclients[specifiedservers[i].ip].serverlist).length) || 0;
				var addS = totalGames === 1 ? '' : 's';
				
				var connection = "C <-> S"; // This should never be visible
				if(isServer && !isClient) connection = "S -> C";
				else if(!isServer && isClient) connection = "S <- C";
				
				listserver_info = " ("+specifiedservers[i].ip+", "+sl_conv(Math.round((Date.now() - (isServer? listservers[specifiedservers[i].ip].starttime : listclients[specifiedservers[i].ip].starttime))/1000))+", "+connection+", "+totalGames+" server"+addS+")";
			}
			
			
			listservers_str.push(" "+(index++)+". "+specifiedservers[i].host+listserver_info);
		}
		lines.push("Connections to other list servers ["+numConnectedTo+"/"+listServerCount+"]");
		lines.push(listservers_str.join("\r\n"));
		
		lines.push("");
		lines.push("Connected guests (S <- C)");
		var index = 1;
		for(var i in listclients) {
			if(!listservers[i]) {
				lines.push(" "+(index++)+". "+(listclients[i].hosts[0]? listclients[i].hosts[0]+' ('+i+')': i)+" ("+sl_conv(Math.round((Date.now() - listclients[i].starttime)/1000))+") ");
			}
		}
		
		socket.end(lines.join("\r\n"), 'binary');
		
	}).listen(10055, bindAddress);
	statusServer.on('error', function (e) {
		throw e;
	});
	
	

	// ServerNet server
	var serverNet = net.createServer(function (socket) {
		var address = socket.remoteAddress;
		
		socket.on('error', function (err) {
			console.log(timestamp(), 'servernet client/guest error: '+err);
		});
		
		if(!listservers[address] && isBanned(address)) {
			socket.end("You have been banned from the ListServer");
			console.log(timestamp(), "Denied "+socket.remoteAddress+" from accessing the ServerNet (banned IP)");
			return;
		}

		socket.setTimeout(serverNetTimeoutTime*1000, function () {
			//console.log('client timeout');
			socket.destroy();
		});
		
		if(listclients[address]) {
			console.log(timestamp(), address+': Double connection, closing old connection..');
			clearInterval(listclients[address].pinger);
			listclients[address].socket.destroy();
			for(var i in listclients[address].serverlist) {
				delete listclients[address].serverlist[i];
			}
			delete listclients[address];
		}
		
		if(listservers[address] && listservers[address].connected) {
			socket.destroy();
			console.log(timestamp(), address+" is already connected as listserver");
			return;
		}
		
		//console.log('servernet request from '+address);
		
		if(listservers[address]) {
			console.log(timestamp(), 'ServerNet '+address+' <- Connected');
		}
		else {
			console.log(timestamp(), 'Guest '+address+' <- Connected');
		}

		// Send my servers
		for(var i=0; i < serverlist.length; i++) {
			socket.write(serverlist[i].getAddPacket());
		}
		
		
		listclients[address] = {serverlist: {}, hosts: [], starttime: Date.now(), socket: socket};
		var listclient = listclients[address];
		
		dns.reverse(address, function (err, domains) {
			/*if(err) {
				console.error(timestamp(), address+" Reverse DNS "+err);
				return;
			}*/
			if(!err && listclient) {
				listclient.hosts = domains;
			}
		});
		
		var pinger = setInterval(function () {
			socket.write(new Buffer([0x00, 0x80])); // Ping
		}, 20*1000);
		
		listclient.pinger = pinger;
		
		var databuffer = [];
		
		if(listservers[address]) { // Only for trusted listservers
			socket.on('data', function (data) {
				Array.prototype.push.apply(
					databuffer,
					Array.prototype.slice.apply(data) // Convert from buffer to array
				); // Append data to array
				
				while(databuffer.length >= databuffer[0]+2) {
					var packet = new Buffer(databuffer.splice(0, databuffer[0]+2));
					// Note: packetLength = packet.length + 2;
					parseServerNetPacket(packet, listclient);
				}
			});
		}
		
		socket.on('close', function () {
			if(listservers[address]) {
				console.log(timestamp(), 'ServerNet '+address+' <- Disconnected');
			} else {
				console.log(timestamp(), 'Guest '+address+' <- Disconnected');
			}
			clearInterval(pinger);
			if(listclients[address] && listclients[address].socket === socket) {
				delete listclients[address];
			}
		});
		
	}).listen(10056, bindAddress);
	serverNet.on('error', function (e) {
		throw e;
	});
	
	

	// The main serverlist
	var serverList = net.createServer(function (socket) {
		
		socket.on('error', function (err) {
			console.log(timestamp(), 'ascii serverlist: '+err);
		});

		var outputlist = buildServerList();
		
		for(var i=0; i < outputlist.length; i++) {
			outputlist[i] = outputlist[i].getASCII();
		}
		
		socket.end(outputlist.join('\r\n')+'\r\n', 'binary');
		
	}).listen(10057, bindAddress);
	serverList.on('error', function (e) {
		throw e;
	});
	

	// Message of the day
	var motdServer = net.createServer(function (socket) {
		
		socket.on('error', function (err) {
			console.log(timestamp(), 'motd: '+err);
		});

		socket.end(motd, "utf8");
		//console.log(timestamp(), socket.remoteAddress+" requested the MOTD");
		
	}).listen(10058, bindAddress);
	motdServer.on('error', function (e) {
		throw e;
	});



	/*var httpServer = http.createServer(function (req, res) {
		var uri = url.parse(req.url);

		var mainUsername = "admin";
		var mainPassword = "password";
		
		var isLoggedIn = false;

		if(req.headers.authorization) {
			var details = new Buffer(req.headers.authorization.split(" ")[1], 'base64').toString('utf8').split(':');
			if(details[0].toLowerCase() === mainUsername && details[1] === mainPassword) {
				isLoggedIn = true;
			}
		}

		if(!isLoggedIn) {
			res.writeHead(401, {
				'WWW-Authenticate': 'Basic realm="ListServer Administration Area"',
				'Content-Type':     'text/html'
			});
			res.end('<h2>Wrong username or password</h2>');
			return;
		}
		

		if(uri.pathname.indexOf('/node/') === 0) {
			switch(uri.pathname.substr(6)) {
				case 'info':
					res.writeHead(200, {'Content-Type': 'text/plain'});
					res.end(process.arch+', '+process.platform);
					console.log(process.arch, process.platform);
					break;
				default:
					res.writeHead(404, {'Content-Type': 'text/plain'});
					res.end('404 Not found');
					break;
			}
			return;
		}
		var filename = path.join(__dirname, './static/', uri.pathname);
		if(filename.substr(-1) === "/") filename += "index.html";
		fs.stat(filename, function (err, stats) {
			
			if(err) {
				res.writeHead(404, {'Content-Type': 'text/plain'});
				res.end('404 Not found');
				return;
			}
			var type = mime.lookup(filename);
			
			var loadNormal = true;
			if(req.headers['if-modified-since']) {
				var req_date = new Date(req.headers['if-modified-since']);
				if (stats.mtime <= req_date && req_date <= Date.now()) {
					res.writeHead(304, {
						'Last-Modified': stats.mtime
					});
					res.end();
					loadNormal = false;
				}
			}
			if(loadNormal) {
				fs.readFile(filename, function (err, data) {
					if(err) {
						res.writeHead(404, {'Content-Type': 'text/plain'});
						res.end('404 Not found\r\n'+err);
					}
					else {
						res.writeHead(200, {
							'Content-Type': type,
							'Content-Length': stats.size,
							'Last-Modified': stats.mtim
						});
						res.end(data);
					}
					
				});
			}
		});
	}).listen(10059);*/
	

	console.log(timestamp(), 'All servers started');
};
