Parcourir la source

Expand protocol handling and network state tracking.

k4be il y a 5 ans
Parent
commit
156222a4fe
4 fichiers modifiés avec 217 ajouts et 22 suppressions
  1. 10 1
      channel.js
  2. 35 3
      irc.js
  3. 168 18
      protocol/unrealircd.js
  4. 4 0
      user.js

+ 10 - 1
channel.js

@@ -3,13 +3,15 @@ var channel = function(){
 	this.TS = null;
 	this.topic = null;
 	this.modes = {};
+	this.statusModes = {};
 	this.users = [];
 
 	this.setTS = function(TS){
 		this.TS = TS;
 	};
 	
-	this.addModes = function(modes, args){
+	this.addModes = function(modes){
+		console.log(modes);
 		// TODO
 	};
 	
@@ -19,6 +21,9 @@ var channel = function(){
 	};
 	
 	this.setStatusModes = function(user, modes){
+		console.log(user);
+		console.log(modes);
+		this.statusModes[user.uid] = modes;
 		// TODO
 	};
 	
@@ -35,6 +40,10 @@ var channel = function(){
 	};
 	
 	this.removeUser = function(user){
+		var index = this.statusModes.indexOf(user);
+		if(index >= 0){
+			this.statusModes.splice(index, 1); // remove status modes for this user
+		}
 		for(var i=0; i<this.users.length; i++){
 			if(this.users[i] == user){
 				this.users.splice(i, 1);

+ 35 - 3
irc.js

@@ -28,6 +28,33 @@ function findUser(u){
 	return null;
 }
 
+function removeServer(server){
+	var length = servers.length;
+	var done = false;
+	do {
+		for(var i=0; i<length; i++){
+			if(servers.length != length) break;
+			if(servers[i].uplink == server){
+				removeServer(servers[i]); // remove connected servers recursively
+			}
+		}
+		if(i == length) done = true;
+	} while(!done);
+	for(var i=users.length-1; i>=0; i--){
+		if(users[i].uplink == server){
+			quitUser(users[i]); // remove users connected to this server
+		}
+	}
+	for(var i=servers.length-1; i>=0; i--){ // finally, remove the given server
+		if(servers[i] == server){
+			console.log('Removing server '+servers[i].name);
+			servers.splice(i, 1);
+			delete server;
+			break;
+		}
+	}
+}
+
 function getChannel(name){
 	var lowerName = name.toLowerCase();
 	var channel = null;
@@ -68,7 +95,7 @@ function newServer(name, sid, desc, distance, uplink){
 	if(findServer(sid) || findServer(name)){
 		throw 'Server already exists';
 	}
-	console.log('Introducing server '+name+' (SID='+sid+', desc="'+desc+'")');
+	console.log('Introducing server '+name+' (SID='+sid+', uplink="'+uplink.name+'")');
 	server = new IRCserver;
 	server.introduce(name, sid, desc, distance, uplink);
 	servers.push(server);
@@ -142,6 +169,7 @@ function quitUser(user){
 	for(var i=0; i<users.length; i++){
 		if(users[i] == user){
 			users.splice(i, 1);
+			delete user;
 			break;
 		}
 	}
@@ -152,7 +180,9 @@ function removeEmptyChannels(){
 		if(channels[i].users.length == 0){
 			storeChannelData(channels[i]);
 			console.log('Removing channel '+channels[i].name);
+			var channel = channels[i];
 			channels.splice(i, 1);
+			delete channel;
 		}
 	}
 }
@@ -178,7 +208,8 @@ var irc = {
 		newUser: newUser,
 		parseUmodes: parseUmodes,
 		getChannel: getChannel,
-		quitUser: quitUser
+		quitUser: quitUser,
+		removeServer: removeServer
 	});
 	uplink.setSettings({
 		ID: me.sid,
@@ -186,7 +217,8 @@ var irc = {
 		name: me.name,
 		description: me.description,
 		version: 'k4be-services',
-		me: me
+		me: me,
+		maxUsers: 0
 	});
 	connection = new net.Socket();
 	connection.connect(port, host, uplink.connected.bind(this));

+ 168 - 18
protocol/unrealircd.js

@@ -13,7 +13,8 @@ var handlers = {
 	newUser: null,
 	parseUmodes: null,
 	getChannel: null,
-	quitUser: null
+	quitUser: null,
+	removeServer: null
 };
 
 var settings = {
@@ -26,20 +27,88 @@ var settings = {
 	maxUsers: null
 };
 
-var prefixes = {
-	'~': 'q',
-	'&': 'a',
-	'@': 'o',
-	'%': 'h',
-	'+': 'v'
+var connection = {
+	'protoctl': {},
+	'prefixes': {},
+	'chmodes': [], // 0 - list modes, 1 - argument modes, 2 - argument modes (add only), 3 - standard modes
+	'umodes': {}
 };
 
+var chmodeMap = {
+	'k': { 'name': 'KEY', 'user': true, 'services': true },
+	'L': { 'name': 'CHANNELLINK', 'user': true, 'services': true },
+	'f': { 'name': 'FLOODPROT', 'user': true, 'services': true },
+	'l': { 'name': 'LIMIT', 'user': true, 'services': true },
+	'H': { 'name': 'HISTORY', 'user': true, 'services': true },
+	'p': { 'name': 'PRIVATE', 'user': true, 'services': true },
+	's': { 'name': 'SECRET', 'user': true, 'services': true },
+	'm': { 'name': 'MODERATED', 'user': true, 'services': true },
+	'n': { 'name': 'NOEXTERNAL', 'user': true, 'services': true },
+	't': { 'name': 'TOPIC', 'user': true, 'services': true },
+	'i': { 'name': 'INVITE', 'user': true, 'services': true },
+	'r': { 'name': 'REGISTERED', 'user': false, 'services': true },
+	'z': { 'name': 'SECUREONLY', 'user': true, 'services': true },
+	'M': { 'name': 'NONREGMODERATED', 'user': true, 'services': true },
+	'Q': { 'name': 'NOKICK', 'user': true, 'services': true },
+	'N': { 'name': 'NONICK', 'user': true, 'services': true },
+	'R': { 'name': 'NONREGINVITE', 'user': true, 'services': true },
+	'T': { 'name': 'NOCTCPS', 'user': true, 'services': true },
+	'O': { 'name': 'OPERS', 'user': false, 'services': true },
+	'V': { 'name': 'NOINVITE', 'user': true, 'services': true },
+	'K': { 'name': 'NOKNOCK', 'user': true, 'services': true },
+	'D': { 'name': 'OLDDELAYJOIN', 'user': false, 'services': false },
+	'd': { 'name': 'DELAYJOIN', 'user': true, 'services': true },
+	'G': { 'name': 'CENSOR', 'user': true, 'services': true },
+	'P': { 'name': 'PERMANENT', 'user': false, 'services': true },
+	'Z': { 'name': 'SECURE', 'user': false, 'services': false },
+	'S': { 'name': 'COLORFILTER', 'user': true, 'services': true },
+	'C': { 'name': 'NOCTCP', 'user': true, 'services': true },
+	'c': { 'name': 'NOCOLOR', 'user': true, 'services': true }
+};
+
+function parseChannelModes(modes, args){
+	var plus = true;
+	var argIndex = 0;
+	var output = {};
+	for(var i=0; i<modes.length; i++){
+		var c = modes.charAt(i);
+		if(c in chmodeMap){
+			var name = chmodeMap[c].name;
+		} else {
+			var name = c;
+		}
+		if(c == '+'){
+			plus = true;
+		} else if(c == '-'){
+			plus = false;
+		} else if(connection.chmodes[0].indexOf(c) >= 0){
+			throw 'Unexpected list mode';
+		} else if(connection.chmodes[1].indexOf(c) >= 0){
+			if(plus){
+				output[name] = args[argIndex++];
+			} else {
+				output[name] = false;
+				argIndex++;
+			}
+		} else if(connection.chmodes[2].indexOf(c) >= 0){
+			if(plus){
+				output[name] = args[argIndex++];
+			} else {
+				output[name] = false;
+			}
+		} else { // treat unknown chars as type 3
+			output[name] = plus;
+		}
+	}
+	return output;
+}
+
 function parseModePrefix(text){
 	var modes = [];
 	for(var i=0; i<text.length; i++){
 		var c = text.charAt(i);
-		if(c in prefixes){
-			modes[prefixes[c]] = true;
+		if(c in connection.prefixes){
+			modes[connection.prefixes[c]] = true;
 		} else break;
 	}
 	var uid = text.substring(i);
@@ -53,7 +122,7 @@ function parseModePrefix(text){
 
 function connected(){
 	ircSend(null, settings.ID, 'PASS', [settings.password]);
-	ircSend(null, settings.ID, 'PROTOCTL', ['NICKv2', 'VHP', 'UMODE2', 'NICKIP', 'SJOIN', 'SJOIN2', 'SJ3', 'NOQUIT', 'TKLEXT', 'MLOCK', 'SID']);
+	ircSend(null, settings.ID, 'PROTOCTL', ['NICKv2', 'VHP', 'UMODE2', 'NICKIP', 'SJOIN', 'SJOIN2', 'SJ3', 'NOQUIT', 'TKLEXT', 'MLOCK', 'SID', 'MTAGS']);
 	ircSend(null, settings.ID, 'PROTOCTL', ['EAUTH=' + settings.name + ',,,' + settings.version]);
 	ircSend(null, settings.ID, 'PROTOCTL', ['SID=' + settings.ID]);
 	ircSend(null, settings.ID, 'SERVER', [settings.name, '1', settings.description]);
@@ -101,8 +170,31 @@ function ircSend(tags, from, cmd, args){
 
 var cmdBinds = {
 	'PROTOCTL': function(msg){
-		//TODO parse
-		console.log(msg);
+		for(var i=0; i<msg.args.length; i++){
+			var arg = msg.args[i];
+			if(arg.indexOf('=') >= 0){
+				var arg = arg.split('=');
+				connection.protoctl[arg[0]] = arg[1];
+				switch(arg[0]){
+					case 'PREFIX':
+						var data = arg[1].substring(1).split(')');
+						var modes = data[0].split('');
+						var chars = data[1].split('');
+						for(var i=0; i<modes.length; i++){
+							connection.prefixes[chars[i]] = modes[i];
+						}
+						break;
+					case 'CHANMODES':
+						connection.chmodes = arg[1].split(',');
+						break;
+					case 'USERMODES':
+						connection.umodes = arg[1];
+						break;						
+				}
+			} else {
+				connection.protoctl[arg] = true;
+			}
+		}
 	},
 	
 	'SERVER': function(msg){
@@ -144,7 +236,7 @@ var cmdBinds = {
 	},
 
 	'SID': function(msg){
-		handlers.newServer(msg.args[0], msg.args[2], msg.args[3], msg.args[1], msg.sender);
+		handlers.newServer(msg.args[0], msg.args[2], msg.args[3], msg.args[1], msg.sender.server);
 	},
 	
 	'UID': function(msg){
@@ -162,7 +254,6 @@ var cmdBinds = {
 		var realname = msg.args[11];
 		if(ip != '*'){
 			var ipBinary = Buffer.from(ip, 'base64');
-			console.log(ipBinary);
 			if(ipBinary.byteLength == 4){ // IPv4
 				ip = ipBinary[0].toString(10) + '.' + ipBinary[1].toString(10) + '.' + ipBinary[2].toString(10) + '.' + ipBinary[3].toString(10);
 			} else if(ipBinary.byteLength == 16){ // IPv6
@@ -196,7 +287,7 @@ var cmdBinds = {
 		for(var i=3; i<msg.args.length - 1; i++){
 			modeArgs.push(msg.args[i]);
 		}
-		channel.addModes(msg.args[2], modeArgs);
+		channel.addModes(parseChannelModes(msg.args[2], modeArgs));
 		var members = msg.text.split(' ');
 		for(var i=0; i<members.length; i++){
 			var member = members[i];
@@ -215,30 +306,40 @@ var cmdBinds = {
 				channel.setStatusModes(udata.user, udata.modes);
 			}
 		}
-		console.log(channel);
+	},
+	
+	'SENDUMODE': function(msg){ // ignore
+	},
+	
+	'PASS': function(msg){ // ignore
 	},
 	
 	'SWHOIS': function(msg){
+		console.log(msg);
 		//TODO
 	},
 	
 	'MODE': function(msg){
+		console.log(msg);
 		//TODO
 	},
 	
 	'TKL': function(msg){
+//		console.log(msg);
 		//TODO
 	},
 	
 	'METADATA': function(msg){
+		console.log(msg);
 		//TODO
 	},
 	
 	'NETINFO': function(msg){
-		ircSend(null, null, [settings.maxUsers, Math.floor(new Date() / 1000), msg.args[2], msg.args[3], "0", "0", "0", msg.args[7]]);
+		ircSend(null, null, 'NETINFO', [settings.maxUsers.toString(10), Math.floor(new Date() / 1000).toString(10), msg.args[2], msg.args[3], "0", "0", "0", msg.args[7]]);
 	},
 	
 	'SASL': function(msg){
+		console.log(msg);
 		//TODO
 	},
 	
@@ -278,23 +379,72 @@ var cmdBinds = {
 	},
 	
 	'SETHOST': function(msg){
+		console.log(msg);
 		//TODO
 	},
 	
 	'SETIDENT': function(msg){
+		console.log(msg);
 		//TODO
 	},
 	
 	'NICK': function(msg){
-		//TODO
+		msg.sender.user.changeNick(msg.args[0]);
 	},
 	
 	'SDESC': function(msg){
+		console.log(msg);
 		//TODO
 	},
 	
 	'TOPIC': function(msg){
+		console.log(msg);
+		//TODO
+	},
+/*
+messagedata {
+  text: '0',
+  args: [ '183.89.186.94', '0' ],
+  tags: [],
+  command: 'REPUTATION',
+  sender: {
+    nick: 'test1.pirc.pl',
+    ident: '',
+    host: '',
+    server: server {
+      name: 'test1.pirc.pl',
+      sid: '093',
+      description: 'serwer testowy!',
+      distance: '3',
+      uplink: [Object],
+      introduce: [Function]
+    },
+    user: false
+  },
+  time: 2020-03-14T10:48:06.207Z,
+  reply: [Function],
+  originalString: ':093 REPUTATION 183.89.186.94 0'
+}
+*/
+	'REPUTATION': function(msg){
+		//TODO
+	},
+	
+	'UMODE2': function(msg){
+		console.log(msg);
 		//TODO
 	},
+	
+	'KILL': function(msg){
+		var user = handlers.findUser(msg.args[0]);
+		if(!user) return;
+		handlers.quitUser(user);
+	},
+
+	'SQUIT': function(msg){
+		var server = handlers.findServer(msg.args[0]);
+		if(!server) return;
+		handlers.removeServer(server);
+	}
 };
 

+ 4 - 0
user.js

@@ -51,6 +51,10 @@ var user = function(){
 	this.setRealname = function(realname){
 		this.realname = realname;
 	}
+	
+	this.changeNick = function(nick){
+		this.name = nick;
+	}
 }
 
 module.exports = user;