Parcourir la source

Initial commit.
Started tracking of network state (adding objects only).

k4be il y a 5 ans
commit
50fa53f85c
6 fichiers modifiés avec 750 ajouts et 0 suppressions
  1. 39 0
      channel.js
  2. 373 0
      irc.js
  3. 274 0
      protocol/unrealircd.js
  4. 27 0
      server.js
  5. 3 0
      services.js
  6. 34 0
      user.js

+ 39 - 0
channel.js

@@ -0,0 +1,39 @@
+var channel = function(){
+	this.name = null;
+	this.TS = null;
+	this.topic = null;
+	this.modes = {};
+	this.users = [];
+
+	this.setTS = function(TS){
+		this.TS = TS;
+	};
+	
+	this.addModes = function(modes, args){
+		// TODO
+	};
+	
+	this.uJoin = function(user){
+		if(this.users.indexOf(user) >= 0) return;
+		this.users.push(user);
+	};
+	
+	this.setStatusModes = function(user, modes){
+		// TODO
+	};
+	
+	this.addBan = function(ban){
+		// TODO
+	};
+	
+	this.addExcept = function(except){
+		// TODO
+	};
+	
+	this.addInvex = function(invex){
+		// TODO
+	};
+}
+
+module.exports = channel;
+

+ 373 - 0
irc.js

@@ -0,0 +1,373 @@
+var net = require('net');
+
+var IRCserver = require('./server');
+var IRCuser = require('./user');
+var IRCchannel = require('./channel');
+
+var uplink = null;
+var connection = null;
+var recvData = '';
+var users = [];
+var servers = [];
+var channels = [];
+var me = null;
+
+module.exports.setConnection = setConnection;
+
+function findServer(s){
+	for(var i=0; i<servers.length; i++){
+		srv = servers[i];
+		if(srv.sid == s || srv.name == s) return srv;
+	}
+	return null;
+}
+
+function findUser(u){
+	for(var i=0; i<users.length; i++){
+		usr = users[i];
+		if(usr.uid == u || usr.name == u) return usr;
+	}
+	return null;
+}
+
+function getChannel(name){
+	var lowerName = name.toLowerCase();
+	var channel = null;
+	for(var i=0; i<channels.length; i++){
+		if(channels[i].name == lowerName){
+			channel = channels[i];
+			break;
+		}
+	}
+	if(!channel){
+		console.log('Creating channel '+name);
+		channel = new IRCchannel;
+		channel.name = name;
+		channels.push(channel);
+	}
+	return channel;
+}
+
+function killUser(user){
+	console.log('KILL');
+	console.log(user);
+// TODO
+}
+
+function newUser(nick, distance, TS, ident, host, uid, account, umodes, vhost, cloakedHost, ip, realname, uplink){ // nick, distance, TS, ident, host, uid, account, umodes, vhost, cloakedHost, ip, realname, uplink
+	var user;
+	if((user = findUser(uid)) || (user = findUser(nick))){
+		console.log(user);
+		throw 'User already exists';
+	}
+	console.log('Introducing user '+nick);
+	user = new IRCuser;
+	user.introduce(nick, distance, TS, ident, host, uid, account, umodes, vhost, cloakedHost, ip, realname, uplink);
+	users.push(user);
+}
+
+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+'")');
+	server = new IRCserver;
+	server.introduce(name, sid, desc, distance, uplink);
+	servers.push(server);
+}
+
+function parseUmodes(text){
+	return null;
+	// TODO
+}
+
+function setConnection(host, port, protocol){
+	uplink = require('./protocol/' + protocol);
+	me = new IRCserver;
+	me.introduce('serwisy.pirc.pl', '11K', 'Serwisy', 0, null);
+	servers.push(me);
+	uplink.setHandlers({
+		send: ircSendData,
+		findServer: findServer,
+		findUser: findUser,
+		killUser: killUser,
+		newServer: newServer,
+		newUser: newUser,
+		parseUmodes: parseUmodes,
+		getChannel: getChannel
+	});
+	uplink.setSettings({
+		ID: me.sid,
+		password: 'myservicespassword',
+		name: me.name,
+		description: me.description,
+		version: 'k4be-services',
+		me: me
+	});
+	connection = new net.Socket();
+	connection.connect(port, host, uplink.connected.bind(this));
+	connection.on('data', ircDataReceived);
+	connection.on('close', ircConnectionClosed);
+}
+
+function ircSendData(tags, from, cmd, args){
+	var data = makeTagsString(tags);
+	if(from){
+		data += ':' + from + ' ';
+	}
+	data += cmd;
+	if(args) for(var i=0; i<args.length; i++){
+		var arg = args[i];
+		if(arg.indexOf(' ') >= 0){ // valid for last arg only
+			arg = ':' + arg;
+		}
+		data += ' ' + arg;
+	}
+	console.log('<<< '+data);
+	data += "\r\n";
+	connection.write(data);
+}
+
+function ircDataReceived(data){
+	data = data.toString('utf8');
+	recvData += data;
+	var buffer = '';
+	for(var i=0; i<recvData.length; i++){
+		var c = recvData.charAt(i);
+		switch(c){
+			default: buffer += c; break;
+			case '\r': case '\n':
+				if(buffer.length > 0)
+					ircMessage(buffer);
+				buffer = '';
+				break;
+		}				
+	}
+	recvData = buffer;
+}
+
+function makeTagsString(tags){
+	return '';
+}
+
+function ircMessage(data){
+	var msg = irc.parseLine(data);
+	uplink.processMessage(msg);
+}
+
+function ircConnectionClosed(e){
+	console.log('Closed: '+e);
+	process.exit(1);
+}
+
+function processReplyTags(inputTags, newTags){
+	return newTags;
+	// TODO
+}
+
+var irc = {
+	'messagedata': function() {
+		this.text = '';
+		this.args = [];
+		this.tags = [];
+		this.command = '';
+		this.sender = {
+			'nick': '',
+			'ident': '',
+			'host': '',
+			'server': false,
+			'user': false
+		};
+		this.time = new Date();
+		this.reply = function(tags, cmd, args){
+			var outTags = processReplyTags(this.tags, tags);
+			ircSendData(outTags, this.sender.nick, cmd, args);
+		}
+		this.originalString = '';
+	},
+	'parseTags': function(tagsLine){
+		var tags = [];
+		var tagState = 'keyName';
+		var keyValue;
+		var keyName = '';
+		for(var i = 0; i < tagsLine.length; i++){
+			var cchar = tagsLine.charAt(i);
+			switch(tagState){
+				case 'keyName':
+					switch(cchar){
+						case '=':
+							tagState = 'keyValue';
+							keyValue = '';
+							break;
+						case ';':
+							tags[keyName] = '';
+							keyName = ''; // staying in tagStateKeyName
+							break;
+						default: keyName += cchar; break;
+					}
+					break;
+				case 'keyValue':
+					switch(cchar){
+						case '\\': tagState = 'keyValueEscape'; break;
+						case ';':
+							tags[keyName] = keyValue;
+							keyName = '';
+							tagState = 'keyName';
+							break;
+						default: keyValue += cchar; break;
+					}
+					break;
+				case 'keyValueEscape':
+					switch(cchar){
+						case ':': keyValue += ';'; break;
+						case 's': keyValue += ' '; break;
+						case 'r': keyValue += '\r'; break;
+						case 'n': keyValue += '\n'; break;
+						default: keyValue += cchar; break;
+					}
+					tagState = 'keyValue';
+					break;
+			}
+		}
+		if(keyName.length > 0) tags[keyName] = keyValue; // flush last tag
+		return tags;
+	},
+	'parseLine': function(line){
+		var ircmsg = new irc.messagedata();
+
+		var line = line.trim();
+		line.replace(/^\s+|\s+$/gm,'');
+		
+		if(line == ''){
+			return;
+		}
+		
+		ircmsg.originalString = line;
+		
+		var msglen = line.length;
+	
+		var pstate = 'start';
+		var currArg = '';
+		var tags = '';
+		var haveText = false;
+		
+		console.log('>>> ' + line);
+
+		for(var i = 0; i < msglen; i++){
+			var cchar = line.charAt(i);
+			switch(pstate){
+				case 'start':
+					switch(cchar){
+						case '@': pstate = 'tags'; break;
+						case ':': pstate = 'senderNick'; break;
+						default:
+							pstate = 'command';
+							ircmsg.command += cchar;
+							break;
+					}
+					break;
+				case 'tags':
+					switch(cchar){
+						case ' ':
+							pstate = 'start';
+							ircmsg.tags = irc.parseTags(tags);
+							break;
+						default: tags += cchar; break;
+					}
+					break;
+				case 'senderNick':
+					switch(cchar){
+						case '!': pstate = 'senderUser'; break;
+						case '@': pstate = 'senderHost'; break;
+						case ' ': pstate = 'command'; break;
+						default: ircmsg.sender.nick += cchar; break;
+					}
+					break;
+				case 'senderUser':
+					switch(cchar){
+						case '@': pstate = 'senderHost'; break;
+						case ' ': pstate = 'command'; break;
+						default: ircmsg.sender.ident += cchar; break;
+					}
+					break;
+				case 'senderHost':
+					switch(cchar){
+						case ' ': pstate = 'command'; break;
+						default: ircmsg.sender.host += cchar; break;
+					}
+					break;
+				case 'command':
+					switch(cchar){
+						case ' ': pstate = 'args'; break;
+						default: ircmsg.command += cchar; break;
+					}
+					break;
+				case 'args':
+					switch(cchar){
+						case ' ':
+							if(currArg != ''){
+								ircmsg.args.push(currArg);
+							}
+							currArg = '';
+							break;
+						case ':':
+							if(prevChar == ' '){
+								pstate = 'message';
+								haveText = true;
+							} else {
+								currArg += cchar;
+							}
+							break;
+						default: currArg += cchar; break;
+					}
+					break;
+				case 'message':
+					ircmsg.text += cchar;
+					break;
+			}
+			var prevChar = cchar;
+		}
+		if(pstate == 'args'){
+			ircmsg.args.push(currArg);
+		}
+		
+		ircmsg.sender = uplink.processSender(ircmsg.sender);
+		if(!ircmsg.sender){
+			return false;
+		}
+		
+		if(!ircmsg.sender.server && !ircmsg.sender.user){
+			if(ircmsg.sender.ident == '' && ircmsg.sender.host == '' && ircmsg.sender.nick.indexOf('.')!=-1){
+				ircmsg.sender.server = true;
+			} else {
+				ircmsg.sender.user = true;
+			}
+		}
+
+		if(!haveText){
+			ircmsg.text = ircmsg.args[ircmsg.args.length-1]; // handling last argument as text if : is missing
+		} else {
+			ircmsg.args.push(ircmsg.text); // handling text as a last argument as required by the protocol
+		}
+
+// add u@h
+/*		if(ircmsg.sender.user){
+			if(ircmsg.sender.ident) users.getUser(ircmsg.sender.nick).setIdent(ircmsg.sender.ident);
+			if(ircmsg.sender.host) users.getUser(ircmsg.sender.nick).setHost(ircmsg.sender.host);
+		}*/
+
+// process known tags
+/*		if('time' in ircmsg.tags){
+			ircmsg.time = parseISOString(ircmsg.tags['time']);
+		}
+
+		if('account' in ircmsg.tags){
+			users.getUser(ircmsg.sender.nick).setAccount(ircmsg.tags['account']);
+		}*/
+
+//		console.log(ircmsg);
+
+		return ircmsg;
+	}
+};
+

+ 274 - 0
protocol/unrealircd.js

@@ -0,0 +1,274 @@
+module.exports.connected = connected;
+module.exports.processMessage = processMessage;
+module.exports.processSender = processSender;
+module.exports.setHandlers = setHandlers;
+module.exports.setSettings = setSettings;
+
+var handlers = {
+	sendHandler: null,
+	findServer: null,
+	findUser: null,
+	killUser: null,
+	newServer: null,
+	newUser: null,
+	parseUmodes: null,
+	getChannel: null
+};
+
+var settings = {
+	ID: null,
+	password: null,
+	name: null,
+	description: null,
+	version: null,
+	me: null
+};
+
+var prefixes = {
+	'~': 'q',
+	'&': 'a',
+	'@': 'o',
+	'%': 'h',
+	'+': 'v'
+};
+
+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;
+		} else break;
+	}
+	var uid = text.substring(i);
+	var user = handlers.findUser(uid);
+	if(!user){
+		handlers.killUser(uid);
+		return null;
+	}
+	return { 'user': user, 'modes': modes };
+}
+
+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', ['EAUTH=' + settings.name + ',,,' + settings.version]);
+	ircSend(null, settings.ID, 'PROTOCTL', ['SID=' + settings.ID]);
+	ircSend(null, settings.ID, 'SERVER', [settings.name, '1', settings.description]);
+}
+
+/*
+		this.sender = {
+			'nick': '',
+			'ident': '',
+			'host': '',
+			'server': false,
+			'user': false
+		};
+*/
+
+function processSender(sender){
+	if(sender.nick){
+		if(server = handlers.findServer(sender.nick)){
+			sender.nick = server.name;
+			sender.server = server;
+		} else if(user = handlers.findUser(sender.nick)){
+			sender.nick = user.name
+			sender.ident = user.ident;
+			sender.host = user.host;
+			sender.user = user;
+		} else {
+			handlers.killUser(sender.nick);
+			return false;
+		}
+	}
+	return sender;
+}
+
+function processMessage(msg){
+	if(!msg) return;
+	if(msg.command in cmdBinds){
+		cmdBinds[msg.command](msg);
+	} else {
+		console.log('Unhandled cmd: '+msg.command);
+		console.log(msg);
+	}
+}
+
+function setHandlers(newHandlers){
+	handlers = newHandlers;
+}
+
+function setSettings(newSettings){
+	settings = newSettings;
+}
+
+function ircSend(tags, from, cmd, args){
+	handlers.send(tags, from, cmd, args);
+}
+
+var cmdBinds = {
+	'PROTOCTL': function(msg){
+		console.log(msg);
+	},
+	
+	'SERVER': function(msg){
+		var expr = /^([^-]+)-([^-]+)-([^ ]+) (.*)$/;
+		var match = expr.exec(msg.args[2]);
+		if(match){
+			handlers.newServer(msg.args[0], match[3], match[4], msg.args[1], settings.me);
+		} else {
+			throw 'Unknown SERVER message';
+		}
+	},
+	
+	'MD': function(msg){
+	},
+	
+	'SMOD': function(msg){
+	},
+	
+	'EOS': function(msg){
+	},
+	
+	'SINFO': function(msg){
+		console.log(msg);
+	},
+
+	'SID': function(msg){
+		handlers.newServer(msg.args[0], msg.args[2], msg.args[3], msg.args[1], msg.sender);
+	},
+/*
+messagedata {
+  text: 'James Wheare',
+  args: [
+    'jwheare', // nick 0
+    '0', // hopcount 1
+    '1583326466', // TS 2
+    'sid2', // ident 3
+    '192.184.10.9', // host 4
+    '0932YWXX0', // uid 5
+    '0', // account 6
+    '+T', // umodes 7
+    '192.184.10.9', // vhost 8
+    '142AA462.921FA108.2DA10B49.IP', // cloakedHost 9
+    'wLgKCQ==', // ip 10
+    'James Wheare' // realname 11
+  ],
+  tags: [],
+  command: 'UID',
+  sender: {
+    nick: 'test1.pirc.pl',
+    ident: '',
+    host: '',
+    server: true,
+    user: false
+  },
+  time: 2020-03-09T08:33:46.447Z,
+  reply: [Function],
+  originalString: ':093 UID jwheare 0 1583326466 sid2 192.184.10.9 0932YWXX0 0 +T 192.184.10.9 142AA462.921FA108.2DA10B49.IP wLgKCQ== :James Wheare'
+}
+*/
+	'UID': function(msg){ // nick, distance, TS, ident, host, uid, account, umodes, vhost, cloakedHost, ip, realname, uplink
+		handlers.newUser(msg.args[0], msg.args[1], msg.args[2], msg.args[3], msg.args[4], msg.args[5], msg.args[6], handlers.parseUmodes(msg.args[7]), msg.args[8], msg.args[9], msg.args[10], msg.args[11], msg.sender.server);
+	},
+/*
+>>> :143 SJOIN 1580651090 #help +nt :0936CITMS 0931Y7O8W
+messagedata {
+  text: '0936CITMS 0931Y7O8W',
+  args: [ '1580651090', '#help', '+nt', '0936CITMS 0931Y7O8W' ],
+  tags: [],
+  command: 'SJOIN',
+  sender: {
+    nick: 'test3.pirc.pl',
+    ident: '',
+    host: '',
+    server: server {
+      name: 'test3.pirc.pl',
+      sid: '143',
+      description: 'serwer testowy!',
+      distance: '1',
+      uplink: [server],
+      introduce: [Function]
+    },
+    user: false
+  },
+  time: 2020-03-09T10:05:44.305Z,
+  reply: [Function],
+  originalString: ':143 SJOIN 1580651090 #help +nt :0936CITMS 0931Y7O8W'
+}
+>>> :143 SJOIN 1582647660 #jwheare +nt :@0936CITMS
+messagedata {
+  text: '@0936CITMS',
+  args: [ '1582647660', '#jwheare', '+nt', '@0936CITMS' ],
+  tags: [],
+  command: 'SJOIN',
+  sender: {
+    nick: 'test3.pirc.pl',
+    ident: '',
+    host: '',
+    server: server {
+      name: 'test3.pirc.pl',
+      sid: '143',
+      description: 'serwer testowy!',
+      distance: '1',
+      uplink: [server],
+      introduce: [Function]
+    },
+    user: false
+  },
+  time: 2020-03-09T10:05:44.306Z,
+  reply: [Function],
+  originalString: ':143 SJOIN 1582647660 #jwheare +nt :@0936CITMS'
+}
+*/
+	'SJOIN': function(msg){
+		var channel = handlers.getChannel(msg.args[1]);
+		channel.setTS(msg.args[0]);
+		var modeArgs = [];
+		for(var i=3; i<msg.args.length - 1; i++){
+			modeArgs.push(msg.args[i]);
+		}
+		channel.addModes(msg.args[2], modeArgs);
+		var members = msg.text.split(' ');
+		for(var i=0; i<members.length; i++){
+			var member = members[i];
+			var c = member.charAt(0);
+			if(c == '&'){ // +b
+				channel.addBan(member.substring(1));
+			} else if(c == '"'){ // +e
+				channel.addExcept(member.substring(1));
+			} else if(c == '\''){ // +I
+				channel.addInvex(member.substring(1));
+			} else {
+				var udata = parseModePrefix(member);
+				if(!udata) return;
+				console.log(udata.user.name + ' joined ' + msg.args[1]);
+				channel.uJoin(udata.user);
+				channel.setStatusModes(udata.user, udata.modes);
+			}
+		}
+		console.log(channel);
+	},
+	
+	'SWHOIS': function(msg){
+	},
+	
+	'TKL': function(msg){
+	},
+	
+	'METADATA': function(msg){
+	},
+	
+	'NETINFO': function(msg){
+		console.log(msg);
+	},
+	
+	'SASL': function(msg){
+	},
+	
+	'PING': function(msg){
+		msg.reply(null, 'PONG', msg.args);
+	}
+};
+

+ 27 - 0
server.js

@@ -0,0 +1,27 @@
+/*module.exports.introduce = introduce;
+module.exports.getSid = function(){ return sid; };
+module.exports.getName = function(){ return name; };
+module.exports.getDescription = function(){ return description; };
+module.exports.getDistance = function(){ return distance; };
+module.exports.getUplink = function(){ return uplink; };
+module.exports.isServer = true;
+module.exports.isUser = false;*/
+
+var server = function(){
+	this.name = null;
+	this.sid = null;
+	this.description = null;
+	this.distance = null;
+	this.uplink = null;
+
+	this.introduce = function(newName, newSid, newDescription, newDistance, newUplink){
+		this.name = newName;
+		this.sid = newSid;
+		this.description = newDescription;
+		this.distance = newDistance;
+		this.uplink = newUplink;
+	}
+}
+
+module.exports = server;
+

+ 3 - 0
services.js

@@ -0,0 +1,3 @@
+var irc = require('./irc');
+
+irc.setConnection('localhost', 6667, 'unrealircd');

+ 34 - 0
user.js

@@ -0,0 +1,34 @@
+var user = function(){
+	this.name = null;
+	this.TS = null;
+	this.ident = null;
+	this.host = null;
+	this.account = null;
+	this.umodes = null;
+	this.vhost = null;
+	this.cloakedHost = null;
+	this.ip = null;
+	this.realname = null;
+	this.uid = null;
+	this.distance = null;
+	this.uplink = null;
+
+	this.introduce = function(nick, distance, TS, ident, host, uid, account, umodes, vhost, cloakedHost, ip, realname, uplink){ // nick, distance, TS, ident, host, uid, account, umodes, vhost, cloakedHost, ip, realname, uplink
+		this.name = nick;
+		this.distance = distance;
+		this.TS = TS;
+		this.ident = ident;
+		this.host = host;
+		this.uid = uid;
+		this.account = account;
+		this.umodes = umodes;
+		this.vhost = vhost;
+		this.cloakedHost = cloakedHost;
+		this.ip = ip;
+		this.realname = realname;
+		this.uplink = uplink;
+	}
+}
+
+module.exports = user;
+