├── .gitignore ├── bin ├── ircd.js └── pwgen.js ├── Makefile ├── scripts └── upstart │ └── ircd.js.conf ├── lib ├── ircd.js ├── protocol.js ├── storage.js ├── user.js ├── server.js ├── channel.js └── commands.js ├── doc ├── man │ └── ircdjs.8 ├── configuration.md ├── rfc2810.txt └── rfc2811.txt ├── config ├── config.example.json ├── config.example-password.json └── config.json ├── test ├── user.test.js ├── sockets.test.js ├── server.test.js ├── helpers.js ├── protocol.test.js ├── channels.test.js └── clients.test.js ├── package.json ├── History.md ├── README.textile └── LICENSE /.gitignore: -------------------------------------------------------------------------------- 1 | *.swp 2 | *.swo 3 | .DS_Store 4 | node_modules/ 5 | -------------------------------------------------------------------------------- /bin/ircd.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | var Server = require('../lib/server.js').Server; 3 | Server.boot(); 4 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | test: 2 | ./node_modules/.bin/mocha --reporter list -c --ui exports test/*.test.js 3 | 4 | .PHONY: test 5 | -------------------------------------------------------------------------------- /scripts/upstart/ircd.js.conf: -------------------------------------------------------------------------------- 1 | description "ircd.js - IRC server implemented in node.js" 2 | 3 | start on filesystem 4 | stop on runlevel [016] 5 | 6 | exec ircdjs 7 | -------------------------------------------------------------------------------- /bin/pwgen.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | var ircd = require(__dirname + '/../lib/ircd'); 3 | ircd.hash(process.argv[2], function(err, hash) { 4 | if (err) { 5 | throw(err); 6 | } else { 7 | console.log(hash); 8 | } 9 | }); 10 | -------------------------------------------------------------------------------- /lib/ircd.js: -------------------------------------------------------------------------------- 1 | var bcrypt = require('bcrypt'); 2 | 3 | module.exports = { 4 | hash: function(text, fn) { 5 | bcrypt.hash(text, 10, function(err, hash) { 6 | fn(err, hash); 7 | }); 8 | }, 9 | 10 | compareHash: bcrypt.compare 11 | }; 12 | -------------------------------------------------------------------------------- /doc/man/ircdjs.8: -------------------------------------------------------------------------------- 1 | .TH IRCD.JS "1" "2012" "" "" 2 | 3 | 4 | .SH "NAME" 5 | ircdjs \- A JavaScript IRC Daemon 6 | 7 | .SH SYNOPSIS 8 | 9 | .B ircdjs 10 | 11 | Run the daemon. 12 | 13 | .SH DESCRIPTION 14 | 15 | ircdjs is an IRC daemon. It's pretty simple right now. 16 | -------------------------------------------------------------------------------- /config/config.example.json: -------------------------------------------------------------------------------- 1 | { "network": "ircn", 2 | "hostname": "localhost", 3 | "serverDescription": "A Node IRC daemon", 4 | "serverName": "server1", 5 | "motd": "Message of the day", 6 | "port": 6667, 7 | "whoWasLimit": 10000, 8 | "opers": { 9 | "alex": { "password": "5baa61e4c9b93f3f0682250b6cf8331b7ee68fd8" } 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /config/config.example-password.json: -------------------------------------------------------------------------------- 1 | { "network": "ircn", 2 | "hostname": "localhost", 3 | "serverDescription": "A Node IRC daemon", 4 | "serverName": "server1", 5 | "serverPassword": "$2a$10$T1UJYlinVUGHqfInKSZQz./CHrYIVVqbDO3N1fRNEUvFvSEcshNdC", 6 | "motd": "Message of the day", 7 | "port": 6667, 8 | "whoWasLimit": 10000, 9 | "opers": { 10 | "alex": { "password": "5baa61e4c9b93f3f0682250b6cf8331b7ee68fd8" } 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /test/user.test.js: -------------------------------------------------------------------------------- 1 | var assert = require('assert') 2 | , path = require('path') 3 | , User = require(path.join(__dirname, '..', 'lib', 'user')).User 4 | ; 5 | 6 | module.exports = { 7 | 'User': { 8 | 'test timeout calculation': function(done) { 9 | var server = { 10 | config: { idleTimeout: 60 } 11 | } 12 | , user = new User(null, server); 13 | 14 | assert.ok(!user.hasTimedOut()); 15 | user.lastPing = (Date.now() - 61000); 16 | assert.ok(user.hasTimedOut()); 17 | 18 | done(); 19 | } 20 | } 21 | }; 22 | -------------------------------------------------------------------------------- /config/config.json: -------------------------------------------------------------------------------- 1 | { "network": "ircn", 2 | "hostname": "localhost", 3 | "serverDescription": "A Node IRC daemon", 4 | "serverName": "server1", 5 | "port": 6667, 6 | "linkPort": 7777, 7 | "motd": "Message of the day", 8 | "whoWasLimit": 10000, 9 | "token": 1, 10 | "opers": { 11 | "alex": { "password": "$2a$10$T1UJYlinVUGHqfInKSZQz./CHrYIVVqbDO3N1fRNEUvFvSEcshNdC" } 12 | }, 13 | "channels": { 14 | "channel1": { "topic": "First Channel" }, 15 | "channel2": { "topic": "Second Channel" }, 16 | "!channel3": { "topic": "Third channel" }, 17 | "+channel4": { "topic": "Fourth Channel" } 18 | }, 19 | "links": { 20 | "server2": { "host": "127.0.0.1", 21 | "password": "$2a$10$T1UJYlinVUGHqfInKSZQz./CHrYIVVqbDO3N1fRNEUvFvSEcshNdC", 22 | "port": 7778, 23 | "token": 2 } 24 | }, 25 | "pingTimeout": 120, 26 | "maxNickLength": 30 27 | } 28 | -------------------------------------------------------------------------------- /test/sockets.test.js: -------------------------------------------------------------------------------- 1 | var assert = require('assert') 2 | , net = require('net') 3 | , helpers = require('./helpers') 4 | ; 5 | 6 | module.exports = { 7 | 'Sockets': { 8 | beforeEach: function(done) { 9 | this.server = new helpers.MockServer(done, false, 6663); 10 | }, 11 | 12 | afterEach: function(done) { 13 | this.server.close(done); 14 | }, 15 | 16 | 'test destroy a socket': function(done) { 17 | var server = this.server.server 18 | , bob = net.createConnection(server.config.port, server.config.hostname); 19 | 20 | bob.write('garbage'); 21 | process.nextTick(function() { 22 | bob.destroy(); 23 | done(); 24 | }); 25 | }, 26 | 27 | 'test send garbage': function(done) { 28 | var server = this.server.server 29 | , alice = net.createConnection(server.config.port, server.config.hostname); 30 | 31 | alice.write('NICK alice\n\x00\x07abc\r\uAAAA', 'ascii', function() { 32 | alice.end(); 33 | done(); 34 | }); 35 | } 36 | } 37 | }; 38 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "ircdjs", 3 | "description": "An IRCD for Node", 4 | "version": "0.0.22", 5 | "homepage": "https://github.com/alexyoung/ircd.js", 6 | "author": "Alex R. Young (http://alexyoung.org)", 7 | "main" : "./lib/server.js", 8 | "directories": { 9 | "lib": "./lib", 10 | "man": "./doc/man" 11 | }, 12 | "scripts": { 13 | "test": "make test" 14 | }, 15 | "keywords": ["irc", "ircd", "daemons", "servers", "chat"], 16 | "repository": "git://github.com/alexyoung/ircd.js.git", 17 | "engines": { 18 | "node": ">= 0.10.3" 19 | }, 20 | "dependencies": { 21 | "carrier": ">= 0.1.13", 22 | "bcrypt": ">= 0.8.0", 23 | "winston": "0.7.3", 24 | "commander": "git://github.com/tj/commander.js.git" 25 | }, 26 | "devDependencies": { 27 | "mocha": "1.21.4", 28 | "irc": "0.3.5" 29 | }, 30 | "bin": { 31 | "ircdjs-pwgen": "bin/pwgen.js", 32 | "ircdjs": "bin/ircd.js" 33 | }, 34 | "licenses": [ 35 | { 36 | "type": "GPL", 37 | "url": "http://github.com/alexyoung/ircd.js/raw/master/LICENSE" 38 | } 39 | ] 40 | } 41 | -------------------------------------------------------------------------------- /doc/configuration.md: -------------------------------------------------------------------------------- 1 | # Configuring ircd.js 2 | 3 | This is what a configuration file looks like: 4 | 5 | { "network": "ircn", 6 | "hostname": "localhost", 7 | "serverDescription": "A Node IRC daemon", 8 | "serverName": "server1", 9 | "port": 6667, 10 | "whoWasLimit": 10000, 11 | "opers": { 12 | "alex": { "password": "5baa61e4c9b93f3f0682250b6cf8331b7ee68fd8" } 13 | }, 14 | "channels": { 15 | "channel1": { "topic": "First Channel" }, 16 | "channel2": { "topic": "Second Channel" } 17 | }, 18 | "links": { 19 | "server2": { "host": "127.0.0.1", 20 | "password": "5baa61e4c9b93f3f0682250b6cf8331b7ee68fd8", 21 | "port": 9999 } 22 | } 23 | } 24 | 25 | * `network`: The name of your IRC network 26 | * `hostname`: The hostname for your server 27 | * `serverDescription`: A textual description of the server 28 | * `serverName`: The name of the server 29 | * `port`: The port the server should listen on 30 | * `whoWasLimit`: The number of `WHOWAS` records to store in memory 31 | * `opers`: A list of operators with bcrypted passwords (the `pwgen.js` script can encrypt passwords for you) 32 | * `channels`: A list of channels that are created on startup. 33 | * `links`: This is for other server links and can be ignored for now 34 | 35 | ## Configuration File Locations 36 | 37 | These are the current configuration search paths: 38 | 39 | * `/etc/ircdjs/config.json` 40 | * `./config/config.json` (inside the source path) 41 | 42 | ## TLS 43 | 44 | if `config.key` and `config.cert` is provided it will start a tls-server. 45 | 46 | `config.{key,cert}` have to be paths to the key-files. 47 | -------------------------------------------------------------------------------- /test/server.test.js: -------------------------------------------------------------------------------- 1 | var assert = require('assert') 2 | , Server = require('../lib/server').Server 3 | , helpers = require('./helpers.js') 4 | ; 5 | 6 | module.exports = { 7 | 'Server': { 8 | beforeEach: function(done) { 9 | this.server = new helpers.MockServer(done, true, 6662); 10 | }, 11 | 12 | afterEach: function(done) { 13 | this.server.close(done); 14 | }, 15 | 16 | 'test connection passwords': function(done) { 17 | var createClient = this.server.createClient.bind(this.server); 18 | createClient({ nick: 'testbot1', channel: '#test', password: 'test' }, function(testbot1) { 19 | testbot1.on('raw', function(data) { 20 | if (data.command === 'rpl_channelmodeis') { 21 | // Ensure users can't join with the same nicks 22 | createClient({ nick: 'testbot1', channel: '#test', password: 'test' }, function(testbot2) { 23 | testbot2.on('raw', function(data) { 24 | if (data.command === 'rpl_channelmodeis') { 25 | assert.notEqual(testbot1.nick, testbot2.nick, "The same nick shouldn't be used more than once"); 26 | testbot1.disconnect(); 27 | testbot2.disconnect(); 28 | done(); 29 | } 30 | }); 31 | }); 32 | } 33 | }); 34 | }); 35 | }, 36 | 37 | 'test isValidPositiveInteger': function() { 38 | assert(Server.prototype.isValidPositiveInteger('1')); 39 | assert(!Server.prototype.isValidPositiveInteger('001')); 40 | assert(!Server.prototype.isValidPositiveInteger('999999999999999')); 41 | assert(!Server.prototype.isValidPositiveInteger('-1')); 42 | assert(!Server.prototype.isValidPositiveInteger('FF0')); 43 | assert(!Server.prototype.isValidPositiveInteger('1A')); 44 | } 45 | } 46 | }; 47 | -------------------------------------------------------------------------------- /test/helpers.js: -------------------------------------------------------------------------------- 1 | var path = require('path') 2 | , Server = require(path.join(__dirname, '..', 'lib', 'server')).Server 3 | , irc = require('irc') 4 | , winston = require('winston') 5 | ; 6 | 7 | winston.remove(winston.transports.Console); 8 | 9 | function MockServer(done, usepass, port) { 10 | this.server = new Server(); 11 | this.server.showLog = false; 12 | this.server.config = { 13 | 'network': 'ircn', 14 | 'hostname': 'localhost', 15 | 'serverDescription': 'A Node IRC daemon', 16 | 'serverName': 'server', 17 | 'port': port, 18 | 'linkPort': 7777, 19 | 'whoWasLimit': 10000, 20 | 'token': 1, 21 | 'opers': {}, 22 | 'links': {} 23 | }; 24 | 25 | if (usepass) { 26 | this.server.config.serverPassword = '$2a$10$T1UJYlinVUGHqfInKSZQz./CHrYIVVqbDO3N1fRNEUvFvSEcshNdC'; 27 | } 28 | 29 | this.server.start(done); 30 | } 31 | 32 | MockServer.prototype = { 33 | close: function(done) { 34 | this.server.close(done); 35 | }, 36 | 37 | createClient: function(options, fn) { 38 | options.port = this.server.config.port; 39 | 40 | var ranCallback = false 41 | , client = new irc.Client('localhost', options.nick, { 42 | channels: [options.channel] 43 | , port: options.port 44 | , debug: false 45 | , password: options.password 46 | }); 47 | 48 | client.addListener('join', function() { 49 | if (!ranCallback) { 50 | fn(client); 51 | ranCallback = true; 52 | } 53 | }); 54 | }, 55 | 56 | createClients: function(nicks, channel, fn) { 57 | var connected = [] 58 | , createClient = this.createClient.bind(this); 59 | 60 | nicks.forEach(function(nick) { 61 | createClient({ nick: nick, channel: channel }, function(bot) { 62 | connected.push(bot); 63 | if (connected.length == nicks.length) { 64 | fn(connected); 65 | } 66 | }); 67 | }); 68 | } 69 | }; 70 | 71 | module.exports = { 72 | MockServer: MockServer 73 | , createServer: function(usepass, port, fn) { 74 | var server = new MockServer(function() { fn(server); }, usepass, port); 75 | } 76 | }; 77 | -------------------------------------------------------------------------------- /lib/protocol.js: -------------------------------------------------------------------------------- 1 | exports.reply = { 2 | welcome: '001', 3 | yourHost: '002', 4 | created: '003', 5 | myInfo: '004', 6 | 7 | away: '301', 8 | unaway: '305', 9 | nowAway: '306', 10 | whoIsUser: '311', 11 | whoIsServer: '312', 12 | whoIsOperator: '313', 13 | whoWasUser: '314', 14 | whoIsIdle: '317', 15 | endOfWhoIs: '318', 16 | whoIsChannels: '319', 17 | 18 | topic: '332', 19 | noTopic: '331', 20 | inviting: '341', 21 | nameReply: '353', 22 | endNames: '366', 23 | endWhoWas: '369', 24 | 25 | listStart: '321', 26 | list: '322', 27 | listEnd: '323', 28 | 29 | version: '351', 30 | 31 | motdStart: '375', 32 | motd: '372', 33 | motdEnd: '376', 34 | who: '352', 35 | endWho: '315', 36 | channelModes: '324', 37 | banList: '367', 38 | endBan: '368', 39 | 40 | youAreOper: '381', 41 | 42 | time: '391' 43 | }; 44 | 45 | exports.errors = { 46 | // Errors 47 | noSuchNick: '401', 48 | noSuchServer: '402', 49 | cannotSend: '404', 50 | wasNoSuchNick: '406', 51 | noRecipient: '411', 52 | noTextToSend: '412', 53 | noNickGiven: '431', 54 | badNick: '432', 55 | nameInUse: '433', 56 | userNotInChannel: '441', 57 | userOnChannel: '443', 58 | noSuchChannel: '403', 59 | notOnChannel: '442', 60 | needMoreParams: '461', 61 | passwordWrong: '464', 62 | youAreBanned: '465', 63 | keySet: '467', 64 | channelIsFull: '471', 65 | inviteOnly: '473', 66 | banned: '474', 67 | badChannelKey: '475', 68 | noPrivileges: '481', 69 | channelOpsReq: '482', 70 | noOperHost: '491', 71 | 72 | usersDoNotMatch: '502' 73 | }; 74 | 75 | exports.validations = { 76 | // starts with letter, than more letters, digits or -[]\`^{} 77 | invalidNick: /^[^a-z]|[^\w_^`\\\[\]{}]/i, 78 | 79 | // any 8bit code except NUL, BELL, LF, CR, SPACE and comma 80 | invalidChannel: /[\x00\x07\n\r ,]/, 81 | 82 | // any 7-bit US_ASCII character, except NUL, TAB, LF, VT, FF, CR, SPACE and comma 83 | invalidChannelKey: /[\x80-\uFFFF\x00\t\n\x0B\x0C\r ,]/ 84 | }; 85 | -------------------------------------------------------------------------------- /History.md: -------------------------------------------------------------------------------- 1 | 0.0.17 / 2012-09-21 2 | 3 | * Fixes parsing of messages with extra colons (#49) 4 | 5 | 0.0.16 / 2012-08-31 6 | 7 | * Changed the way PASS is handled (must be sent before NICK/USER) 8 | * Fixed a bug where nick collisions could occur when server passwords were used 9 | 10 | 0.0.15 / 2012-07-04 11 | 12 | * Default channels (Sebastian A. Espindola) 13 | * Migrated tests to Mocha 14 | * Tested against Node 0.8 15 | * Locked down module versions 16 | 17 | 0.0.14 / 2012-06-05 18 | 19 | * Added connection passwords (PASS) 20 | 21 | 0.0.12 / 2012-05-01 22 | 23 | * Added MOTD (sespindola) 24 | 25 | 0.0.11 / 2012-02-15 26 | =================== 27 | 28 | * Added winston for logging 29 | * Added `SIGTERM` listener 30 | * Continued cleaning up tests 31 | 32 | 0.0.10 / 2012-02-04 33 | =================== 34 | 35 | * Connection timeouts are now managed 36 | * #25: Channels are removed when the last user leaves 37 | 38 | 0.0.9 / 2012-01-31 39 | ================== 40 | 41 | * #26: Fixed server crash when sending a message to a non-existent channel 42 | 43 | 0.0.6 / 2011-12-11 44 | ================== 45 | 46 | * #18: Fixed by forcing away to have a message 47 | * #17: Using `trim` on nicks 48 | 49 | 0.0.6 / 2011-12-11 50 | ================== 51 | 52 | * Fixed bug #19 by ensuring bans have valid ban masks 53 | 54 | 0.0.5 / 2011-12-03 55 | ================== 56 | 57 | * Added a fix from [treeform](https://github.com/treeform) for `NICK`, detailed in [#16](https://github.com/alexyoung/ircd.js/issues/16) 58 | 59 | Previous Releases 60 | ================= 61 | 62 | * [2011-10-22] Now based around EventEmitter, changed sha1 passwords to bcrypt 63 | * [2011-10-22] Removed server link code (it was overcomplicating development too much, I'll come back to it later) 64 | * [2011-09-09] Fixed bug #9: WHO on nonexistent channel. Improved unit tests 65 | * [2010-12-27] More work on server links (broadcasting of SERVER commands) 66 | * [2010-12-11] Now installable from npm, server now checks for config in /etc/ircdjs/config.json 67 | * [2010-12-11] First steps towards linked servers, major restructuring 68 | * [2010-12-04] Added user mode w and WALLOPS 69 | * [2010-12-01] Split code into channel.js, user.js, server.js 70 | * [2010-11-28] Added KICK 71 | * [2010-11-23] Added OPER command that uses sha1 passwords in the config file 72 | * [2010-11-23] Added invisible user mode and user support for WHO command 73 | * [2010-11-19] Added AWAY 74 | * [2010-11-18] Added VERSION, TIME, WHOWAS 75 | * [2010-11-16] Added +i and INVITE 76 | * [2010-11-14] Added +k (channel key) 77 | * [2010-11-12] Added +l (user limit) 78 | * [2010-11-11] Added voice/devoice support, and removing channel modes on part 79 | * [2010-11-07] Added ban support, commands are no-longer case sensitive 80 | 81 | -------------------------------------------------------------------------------- /README.textile: -------------------------------------------------------------------------------- 1 |
 2 |  ::::::::::..     .,-::::::::::::-.         ....:::::: .::::::. 
 3 |  ;;;;;;;``;;;;  ,;;;'````' ;;,   `';,    ;;;;;;;;;````;;;`    ` 
 4 |  [[[ [[[,/[[['  [[[        `[[     [[    ''`  `[[.    '[==/[[[[,
 5 |  $$$ $$$$$$c    $$$         $$,    $$   ,,,    `$$      '''    $
 6 |  888 888b "88bo,`88bo,__,o, 888_,o8P'd8b888boood88     88b    dP
 7 |  MMM MMMM   "W"   "YUMMMMMP"MMMMP"`  YMP"MMMMMMMM"      "YMmMY" 
 8 | 
 9 |                                             A Node.JS IRC Server
10 |  ircd.js
11 | 
12 | 13 | h3. About 14 | 15 | I'm implementing "RFC 1459":https://tools.ietf.org/html/rfc1459 / "RFC 2812":https://tools.ietf.org/html/rfc2812 for "Node.js":http://nodejs.org/. 16 | 17 | The server will allow clients to connect, join channels, change topics; basic stuff. 18 | 19 | Done: 20 | 21 | * PASS (connection password) 22 | * PING/PONG 23 | * PRIVMSG 24 | * MODE 25 | * JOIN 26 | * TOPIC 27 | * NAMES 28 | * LIST 29 | * INVITE 30 | * WHOWAS 31 | * TIME 32 | * VERSION 33 | * AWAY 34 | * WHO 35 | * OPER 36 | * KICK 37 | * WALLOP 38 | * CONNECT 39 | * Connection garbage like MOTD 40 | * Basic data validation 41 | * Simple JSON config file 42 | * Channel modes: o, p, s, t, n, m, i, l, b, v, k 43 | * User modes: i, w, o 44 | 45 | Planned: 46 | 47 | * Services 48 | * Bring back server links 49 | * Server-to-server NICK messages when nicks are changed or new clients join 50 | * Server-to-server messages for JOIN, NJOIN, MODE, PRIVSG and NOTICE 51 | * SQUIT and QUIT for links 52 | * Server to server communication 53 | * More basic commands: NOTICE, LINKS, TRACE, ADMIN, INFO 54 | * Log files and logging options 55 | * Local ops (+O) 56 | * Stats command 57 | 58 | h3. Documentation 59 | 60 | Install with npm install ircdjs. 61 | 62 | Set up configuration in /etc/ircdjs/config.json. 63 | 64 | h3. Contributions 65 | 66 | * overra 67 | * jazzychad (Chad Etzel) 68 | * sespindola (Sebastian A. Espindola) 69 | * niklasf 70 | * treeform 71 | * guybrush (Patrick Pfeiffer) 72 | * eirikb (Eirik Brandtzæg) 73 | * andrew12 (Andrew Herbig) 74 | * jrasanen (Jussi Räsänen) 75 | 76 | h3. License (GPL) 77 | 78 | This program is free software: you can redistribute it and/or modify 79 | it under the terms of the GNU General Public License as published by 80 | the Free Software Foundation, either version 3 of the License, or 81 | (at your option) any later version. 82 | 83 | This program is distributed in the hope that it will be useful, 84 | but WITHOUT ANY WARRANTY; without even the implied warranty of 85 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 86 | GNU General Public License for more details. 87 | 88 | You should have received a copy of the GNU General Public License 89 | along with this program. If not, see "http://www.gnu.org/licenses/":http://www.gnu.org/licenses/. 90 | -------------------------------------------------------------------------------- /test/protocol.test.js: -------------------------------------------------------------------------------- 1 | var path = require('path') 2 | , assert = require('assert') 3 | , protocol = require(path.join(__dirname, '..', 'lib', 'protocol')) 4 | , invalidNick = protocol.validations.invalidNick 5 | , invalidChannel = protocol.validations.invalidChannel 6 | , invalidChannelKey = protocol.validations.invalidChannelKey 7 | ; 8 | 9 | module.exports = { 10 | 'Protocol': { 11 | 'test nickname validation': function(done) { 12 | // Valid nicknames 13 | assert.strictEqual('alexyoung'.match(invalidNick), null); 14 | assert.strictEqual('AbC123'.match(invalidNick), null); 15 | assert.strictEqual('a{b[}]'.match(invalidNick), null); 16 | 17 | // Invalid nicknames 18 | // Nicknames shall not contain some special characters 19 | assert.notStrictEqual('abc#'.match(invalidNick), null); 20 | assert.notStrictEqual('abc*defg'.match(invalidNick), null); 21 | assert.notStrictEqual('abc~'.match(invalidNick), null); 22 | assert.notStrictEqual('a\0a'.match(invalidNick), null, 'NULL'); 23 | assert.notStrictEqual('abc\ndefg'.match(invalidNick), null, 'LF'); 24 | assert.notStrictEqual('abc\7xyz'.match(invalidNick), null, 'BELL'); 25 | 26 | // Invalid nicknames 27 | // RFC1459 says nicks must start with a letter 28 | // https://github.com/alexyoung/ircd.js/blob/5d7443847311d4d6d1ff7371fa1fdee021315b0f/doc/rfc1459.txt#L492 29 | assert.notStrictEqual('9abc'.match(protocol.validations.invalidNick), null, 'starting with a digit'); 30 | assert.notStrictEqual('^abc123'.match(protocol.validations.invalidNick), null, 'starting with a special character'); 31 | 32 | done(); 33 | }, 34 | 35 | 'test channelname validation': function(done) { 36 | // Valid channelnames 37 | assert.strictEqual('node.js'.match(invalidChannel), null); 38 | assert.strictEqual('#9'.match(invalidChannel), null); 39 | assert.strictEqual('bla\u01D2'.match(invalidChannel), null, 'random 8 bit character'); 40 | 41 | // Invalid channelnames 42 | // https://github.com/alexyoung/ircd.js/blob/5d7443847311d4d6d1ff7371fa1fdee021315b0f/doc/rfc1459.txt#L494 43 | assert.notStrictEqual('word1 word2'.match(invalidChannel), null, 'SPACE'); 44 | assert.notStrictEqual('ring\x07'.match(invalidChannel), null, 'BELL'); 45 | assert.notStrictEqual('zero\x00'.match(invalidChannel), null, 'NUL'); 46 | assert.notStrictEqual('word\rword'.match(invalidChannel), null, 'CR'); 47 | assert.notStrictEqual('word\nword'.match(invalidChannel), null, 'LF'); 48 | assert.notStrictEqual('first,secound,third'.match(invalidChannel), null, 'Comma (,)'); 49 | 50 | done(); 51 | }, 52 | 53 | 'test channelkey validation': function(done) { 54 | // Valid channelkeys 55 | assert.strictEqual('key'.match(invalidChannelKey), null); 56 | assert.strictEqual('key*'.match(invalidChannelKey), null); 57 | 58 | // Invalid channelkeys 59 | // any 7-bit US_ASCII character is valid, except NUL, CR, LF, FF, h/v TABs, and " " 60 | assert.notStrictEqual('bla\u01D2'.match(invalidChannelKey), null, 'random 8 bit character'); 61 | assert.notStrictEqual('zero\x00'.match(invalidChannelKey), null, 'NUL'); 62 | assert.notStrictEqual('word\rword'.match(invalidChannelKey), null, 'CR'); 63 | assert.notStrictEqual('word\nword'.match(invalidChannelKey), null, 'LF'); 64 | assert.notStrictEqual('word\x0C'.match(invalidChannelKey), null, 'FF'); 65 | assert.notStrictEqual('horizontal\x09vertical\x0B'.match(invalidChannelKey), null, 'tabs'); 66 | assert.notStrictEqual('space s'.match(invalidChannelKey), null, 'SPACE') 67 | 68 | done(); 69 | } 70 | } 71 | }; 72 | -------------------------------------------------------------------------------- /lib/storage.js: -------------------------------------------------------------------------------- 1 | var Channel = require('./channel').Channel, 2 | irc = require('./protocol'); 3 | 4 | function History(server) { 5 | this.server = server; 6 | this.config = server.config; 7 | this.items = []; 8 | } 9 | 10 | History.prototype = { 11 | add: function(user) { 12 | this.items.unshift({ nick: user.nick, 13 | username: user.username, 14 | realname: user.realname, 15 | host: user.hostname, 16 | server: user.serverName, 17 | time: new Date() }); 18 | if (this.config) { 19 | this.items.slice(0, this.config.whoWasLimit); 20 | } 21 | }, 22 | 23 | find: function(nick) { 24 | return this.items.filter(function(item) { 25 | return nick === item.nick; 26 | }); 27 | } 28 | }; 29 | 30 | function UserDatabase(server) { 31 | this.server = server; 32 | this.config = server.config; 33 | this.registered = []; 34 | } 35 | 36 | UserDatabase.prototype = { 37 | forEach: function(fn) { 38 | this.registered.forEach(fn); 39 | }, 40 | 41 | push: function(user) { 42 | this.registered.push(user); 43 | }, 44 | 45 | register: function(user, username, hostname, servername, realname) { 46 | user.username = username; 47 | user.realname = realname; 48 | this.registered.push(user); 49 | user.register(); 50 | }, 51 | 52 | find: function(nick) { 53 | nick = this.server.normalizeName(nick); 54 | for (var i = 0; i < this.registered.length; i++) { 55 | if (this.registered[i] && this.server.normalizeName(this.registered[i].nick) === nick) 56 | return this.registered[i]; 57 | } 58 | }, 59 | 60 | remove: function(user) { 61 | if (this.registered.indexOf(user) !== -1) { 62 | this.registered.splice(this.registered.indexOf(user), 1); 63 | } 64 | } 65 | }; 66 | 67 | function ChannelDatabase(server) { 68 | this.server = server; 69 | this.registered = {}; 70 | } 71 | 72 | ChannelDatabase.prototype = { 73 | message: function(user, channel, message) { 74 | if (!channel) return; 75 | channel.users.forEach(function(channelUser) { 76 | if (channelUser !== user) { 77 | channelUser.send(user.mask, 'PRIVMSG', channel.name, ':' + message); 78 | } 79 | }); 80 | }, 81 | 82 | expandMask: function(mask) { 83 | return mask.replace(/\./g, '\\.'). 84 | replace(/\*/g, '.*'); 85 | }, 86 | 87 | findWithMask: function(channelMask) { 88 | channelMask = this.expandMask(this.server.normalizeName(channelMask)); 89 | for (var channelName in this.registered) { 90 | if (channelMask.match(channelName)) { 91 | return this.registered[channelName]; 92 | } 93 | } 94 | }, 95 | 96 | find: function(channelName) { 97 | return this.registered[this.server.normalizeName(channelName)]; 98 | }, 99 | 100 | join: function(user, channelName, key) { 101 | // TODO: valid channel name? 102 | // Channels names are strings (beginning with a '&' or '#' character) of 103 | // length up to 200 characters. Apart from the the requirement that the 104 | // first character being either '&' or '#'; the only restriction on a 105 | // channel name is that it may not contain any spaces (' '), a control G 106 | // (^G or ASCII 7), or a comma (',' which is used as a list item 107 | // separator by the protocol). 108 | 109 | var channel = this.find(channelName); 110 | 111 | if (!channel) { 112 | channel = this.registered[this.server.normalizeName(channelName)] = new Channel(channelName, this.server); 113 | } 114 | 115 | if (channel.isMember(user)) { 116 | return; 117 | } 118 | 119 | if (channel.isInviteOnly && !channel.onInviteList(user)) { 120 | user.send(this.server.host, irc.errors.inviteOnly, user.nick, channel.name, ':Cannot join channel (+i)'); 121 | return; 122 | } 123 | 124 | if (channel.isBanned(user)) { 125 | user.send(this.server.host, irc.errors.banned, user.nick, channel.name, ':Cannot join channel (+b)'); 126 | return; 127 | } 128 | 129 | if (channel.isLimited && channel.users.length >= channel.userLimit) { 130 | user.send(this.server.host, irc.errors.channelIsFull, user.nick, channel.name, ':Channel is full.'); 131 | return; 132 | } 133 | 134 | if (channel.key) { 135 | if (key !== channel.key) { 136 | user.send(this.server.host, irc.errors.badChannelKey, user.nick, this.name, ":Invalid channel key"); 137 | return; 138 | } 139 | } 140 | 141 | if (channel.users.length === 0) { 142 | user.op(channel); 143 | } 144 | 145 | channel.users.push(user); 146 | user.channels.push(channel); 147 | 148 | channel.users.forEach(function(channelUser) { 149 | channelUser.send(user.mask, 'JOIN', channel.name); 150 | }); 151 | 152 | if (channel.topic) { 153 | user.send(this.server.host, irc.reply.topic, user.nick, channel.name, ':' + channel.topic); 154 | } else { 155 | user.send(this.server.host, irc.reply.noTopic, user.nick, channel.name, ':No topic is set'); 156 | } 157 | 158 | user.send(this.server.host, irc.reply.nameReply, user.nick, channel.type, channel.name, ':' + channel.names); 159 | user.send(this.server.host, irc.reply.endNames, user.nick, channel.name, ':End of /NAMES list.'); 160 | }, 161 | 162 | remove: function(channel) { 163 | delete this.registered[channel.name]; 164 | } 165 | }; 166 | 167 | exports.History = History; 168 | exports.ChannelDatabase = ChannelDatabase; 169 | exports.UserDatabase = UserDatabase; 170 | -------------------------------------------------------------------------------- /test/channels.test.js: -------------------------------------------------------------------------------- 1 | var assert = require('assert') 2 | , helpers = require('./helpers') 3 | ; 4 | 5 | module.exports = { 6 | 'Channels': { 7 | beforeEach: function(done) { 8 | this.server = new helpers.MockServer(done, false, 6660); 9 | }, 10 | 11 | afterEach: function(done) { 12 | this.server.close(done); 13 | }, 14 | 15 | 'test rejoin': function(done) { 16 | var createClient = this.server.createClient.bind(this.server); 17 | 18 | // Create two clients 19 | createClient({ nick: 'testbot1', channel: '#test' }, function(testbot1) { 20 | createClient({ nick: 'testbot2', channel: '#test' }, function(testbot2) { 21 | 22 | var i = 0; 23 | testbot2.on('raw', function(data) { 24 | switch (data.command) { 25 | case 'rpl_namreply': 26 | var names = data.args[3].split(' ').filter(function(f) { 27 | return f.match(/testbot/); 28 | }); 29 | assert.equal(2, names.length); 30 | i++; 31 | if (i === 1) { 32 | testbot2.part('#test'); 33 | } else { 34 | testbot1.disconnect(); 35 | testbot2.disconnect(); 36 | done(); 37 | } 38 | break; 39 | case 'PART': 40 | testbot2.join('#test'); 41 | break; 42 | } 43 | }); 44 | }); 45 | }); 46 | }, 47 | 48 | 'test bad join (#22)': function(done) { 49 | // Create two clients 50 | var createClient = this.server.createClient.bind(this.server); 51 | 52 | createClient({ nick: 'testbot1', channel: '#test' }, function(testbot1) { 53 | createClient({ nick: 'testbot2', channel: '#test' }, function(testbot2) { 54 | 55 | testbot1.on('error', function(message) { 56 | if (message.command === 'err_needmoreparams') { 57 | testbot1.disconnect(); 58 | testbot2.disconnect(); 59 | done(); 60 | } 61 | }); 62 | 63 | testbot1.on('raw', function(data) { 64 | if (data.command === 'JOIN') { 65 | testbot1.send('join'); 66 | } 67 | }); 68 | }); 69 | }); 70 | }, 71 | 72 | 'test messaging a non-existent channel (#26)': function(done) { 73 | var createClient = this.server.createClient.bind(this.server); 74 | 75 | // Create two clients 76 | createClient({ nick: 'testbot1', channel: '#test' }, function(testbot1) { 77 | createClient({ nick: 'testbot2', channel: '#test' }, function(testbot2) { 78 | testbot1.on('error', function(message) { 79 | if (message.command === 'err_nosuchnick') { 80 | testbot1.disconnect(); 81 | testbot2.disconnect(); 82 | done(); 83 | } 84 | }); 85 | 86 | testbot1.say('#error', 'Hello'); 87 | }); 88 | }); 89 | }, 90 | 91 | 'remove channels when the last person leaves (#25)': function(done) { 92 | var createClient = this.server.createClient.bind(this.server); 93 | 94 | // Create two clients 95 | createClient({ nick: 'testbot1', channel: '#test' }, function(testbot1) { 96 | function teardown() { 97 | testbot1.disconnect(); 98 | done(); 99 | } 100 | 101 | var seenList = false; 102 | 103 | testbot1.on('raw', function(data) { 104 | // Double equal, because this is returned as a string but could easily 105 | // be returned as an integer if the IRC client library changes 106 | if (data.rawCommand == 322) { 107 | if (seenList) { 108 | assert.fail('Channels should be deleted'); 109 | } else { 110 | assert.equal(data.args[1], '#test', 'The #test channel should be returned by LIST'); 111 | 112 | // Now part the channel 113 | testbot1.part('#test'); 114 | } 115 | } else if (data.rawCommand == 323 && !seenList) { 116 | seenList = true; 117 | } else if (data.rawCommand == 323 && seenList) { 118 | teardown(); 119 | } else if (data.command === 'PART') { 120 | testbot1.send('LIST'); 121 | } 122 | }); 123 | 124 | // Send a list command 125 | testbot1.send('LIST'); 126 | }); 127 | }, 128 | 129 | 'simultaneous user simulation': function(done) { 130 | var nicks = [], i; 131 | 132 | for (i = 1; i <= 100; i++) { 133 | nicks.push('user_' + i); 134 | } 135 | 136 | function assertReceive(bots, assertion, fn) { 137 | bots[0].say(bots[1].nick, assertion); 138 | 139 | var callback = function(from, to, message) { 140 | assert.equal(assertion, message); 141 | bots[1].removeListener('message', callback); 142 | fn(); 143 | }; 144 | 145 | bots[1].on('message', callback); 146 | } 147 | 148 | this.server.createClients(nicks, '#test', function(bots) { 149 | function teardown() { 150 | bots.forEach(function(bot) { 151 | bot.disconnect(); 152 | }); 153 | done(); 154 | } 155 | 156 | var tested = 0, max = bots.length - 1; 157 | for (var i = 0; i < max; i++) { 158 | assertReceive([bots[i], bots[i + 1]], 'Message ' + Math.random(), function() { 159 | tested++; 160 | if (tested === max) { 161 | teardown(); 162 | } 163 | }); 164 | } 165 | }); 166 | }, 167 | 168 | 'test join with invalid key': function(done) { 169 | var createClient = this.server.createClient.bind(this.server); 170 | 171 | createClient({ nick: 'testbot1', channel: '#test' }, function(testbot1) { 172 | // Set the channel key to 'test' 173 | testbot1.send('MODE #test +k test'); 174 | testbot1.on('raw', function(data) { 175 | if (data.rawCommand === '324') { 176 | createClient({ nick: 'testbot2', channel: '#test2' }, function(testbot2) { 177 | testbot2.on('error', function(message) { 178 | assert.equal(message.rawCommand, '475', 'Should receive a bad channel key'); 179 | testbot1.disconnect(); 180 | testbot2.disconnect(); 181 | 182 | done(); 183 | }); 184 | 185 | // Join without the correct key 186 | testbot2.send('JOIN #test'); 187 | }); 188 | } 189 | }); 190 | }); 191 | }, 192 | 193 | 'test join with valid key': function(done) { 194 | var createClient = this.server.createClient.bind(this.server); 195 | 196 | createClient({ nick: 'testbotv', channel: '#test' }, function(testbot1) { 197 | // Set the channel key to 'password' 198 | testbot1.send('MODE #test +k password'); 199 | testbot1.on('raw', function(data) { 200 | if (data.rawCommand === '324') { 201 | createClient({ nick: 'testbot2', channel: '#test password' }, function(testbot2) { 202 | testbot2.on('raw', function(data) { 203 | if (data.rawCommand === '324') { 204 | testbot1.disconnect(); 205 | testbot2.disconnect(); 206 | done(); 207 | } 208 | }); 209 | }); 210 | } 211 | }); 212 | }); 213 | } 214 | } 215 | }; 216 | -------------------------------------------------------------------------------- /test/clients.test.js: -------------------------------------------------------------------------------- 1 | var assert = require('assert') 2 | , helpers = require('./helpers.js') 3 | , irc = require('irc') 4 | , port = 6661 5 | ; 6 | 7 | module.exports = { 8 | 'Clients': { 9 | beforeEach: function(done) { 10 | this.server = new helpers.MockServer(done, false, port); 11 | }, 12 | 13 | afterEach: function(done) { 14 | this.server.close(done); 15 | }, 16 | 17 | 'test valid WHOIS': function(done) { 18 | var createClient = this.server.createClient.bind(this.server); 19 | 20 | createClient({ nick: 'testbot1', channel: '#test' }, function(testbot1) { 21 | createClient({ nick: 'testbot2', channel: '#test' }, function(testbot2) { 22 | testbot1.on('raw', function(data) { 23 | if (data.command === 'JOIN') { 24 | testbot1.send('WHOIS', 'testbot2'); 25 | } else if (data.command === 'rpl_whoisuser') { 26 | assert.equal('testbot2', data.args[1]); 27 | testbot1.disconnect(); 28 | testbot2.disconnect(); 29 | done(); 30 | } 31 | }); 32 | }); 33 | }); 34 | }, 35 | 36 | 'valid WHO': function(done) { 37 | var createClient = this.server.createClient.bind(this.server); 38 | 39 | createClient({ nick: 'testbot1', channel: '#test' }, function(testbot1) { 40 | createClient({ nick: 'testbot2', channel: '#test' }, function(testbot2) { 41 | testbot1.on('raw', function(data) { 42 | if (data.command === 'rpl_endofwho') { 43 | assert.equal('#test', data.args[1]); 44 | testbot1.disconnect(); 45 | testbot2.disconnect(); 46 | done(); 47 | } 48 | }); 49 | testbot1.send('WHO', '#test'); 50 | }); 51 | }); 52 | }, 53 | 54 | 'invalid WHO (bug #9)': function(done) { 55 | var createClient = this.server.createClient.bind(this.server); 56 | 57 | createClient({ nick: 'testbot1', channel: '#test' }, function(testbot1) { 58 | createClient({ nick: 'testbot2', channel: '#test' }, function(testbot2) { 59 | testbot1.addListener('error', function(message) { 60 | if (message.command === 'err_nosuchchannel') { 61 | testbot1.disconnect(); 62 | testbot2.disconnect(); 63 | done(); 64 | } 65 | }); 66 | 67 | testbot1.send('WHO', '#argh'); 68 | }); 69 | }); 70 | }, 71 | 72 | 'socket error handling (bug #10)': function(done) { 73 | var createClient = this.server.createClient.bind(this.server) 74 | , server = this.server.server; 75 | 76 | createClient({ nick: 'testbot1', channel: '#test' }, function(testbot1) { 77 | createClient({ nick: 'testbot2', channel: '#test' }, function(testbot2) { 78 | var user = server.users.registered.filter(function(user) { return user.nick == testbot2.nick; })[0]; 79 | 80 | // Simulate a socket issue by causing user.send to raise an exception 81 | user.stream = 'bad'; 82 | testbot2.send('WHO', '#test'); 83 | 84 | setTimeout(function() { 85 | // There should now be one user instead of two in the channel 86 | assert.equal(1, server.channels.registered['#test'].users.length); 87 | testbot1.disconnect(); 88 | testbot2.disconnect(); 89 | done(); 90 | }, 10); 91 | }); 92 | }); 93 | }, 94 | 95 | "users shouldn't be able to join channel twice (bug #12)": function(done) { 96 | var createClient = this.server.createClient.bind(this.server) 97 | , server = this.server.server; 98 | 99 | createClient({ nick: 'testbot1', channel: '#test' }, function(testbot1) { 100 | testbot1.join('#test', function() { 101 | setTimeout(function() { 102 | assert.equal(server.channels.registered['#test'].users.length, 1); 103 | testbot1.disconnect(); 104 | done(); 105 | }, 10); 106 | }); 107 | }); 108 | }, 109 | 110 | 'invalid ban mask (bug #19)': function(done) { 111 | var createClient = this.server.createClient.bind(this.server); 112 | 113 | createClient({ nick: 'huey', channel: '#aff' }, function(huey) { 114 | huey.send('MODE', '#aff', '+b'); 115 | huey.on('error', function(data) { 116 | if (data.command === 'err_needmoreparams') { 117 | createClient({ nick: 'dewey', channel: '#aff' }, function(dewey) { 118 | huey.disconnect(); 119 | dewey.disconnect(); 120 | done(); 121 | }); 122 | } 123 | }); 124 | }); 125 | }, 126 | 127 | 'invalid away status (bug #18)': function(done) { 128 | var createClient = this.server.createClient.bind(this.server); 129 | 130 | createClient({ nick: 'huey', channel: '#aff' }, function(huey) { 131 | var dewey; 132 | 133 | huey.send('AWAY'); 134 | huey.on('message', function(from, to, message) { 135 | assert.equal('dewey', from); 136 | assert.equal('huey', to); 137 | assert.equal('Hello', message); 138 | huey.disconnect(); 139 | dewey.disconnect(); 140 | done(); 141 | }); 142 | 143 | huey.on('error', function(data) { 144 | if (data.command === 'err_needmoreparams') { 145 | createClient({ nick: 'dewey', channel: '#aff' }, function(client) { 146 | dewey = client; 147 | dewey.say('huey', 'Hello'); 148 | }); 149 | } 150 | }); 151 | }); 152 | }, 153 | 154 | 'simultaneous user simulation': function(done) { 155 | var nicks = [], i; 156 | for (i = 1; i <= 10; i++) { 157 | nicks.push('user_' + i); 158 | } 159 | 160 | function assertReceive(bots, assertion, fn) { 161 | bots[0].say(bots[1].nick, assertion); 162 | 163 | var callback = function(from, to, message) { 164 | assert.equal(assertion, message); 165 | bots[1].removeListener('message', callback); 166 | fn(); 167 | }; 168 | 169 | bots[1].addListener('message', callback); 170 | } 171 | 172 | this.server.createClients(nicks, '#test', function(bots) { 173 | function teardown() { 174 | bots.forEach(function(bot) { 175 | bot.disconnect(); 176 | }); 177 | done(); 178 | } 179 | 180 | var i, tested = 0, max = bots.length - 1; 181 | for (i = 0; i < max; i++) { 182 | assertReceive([bots[i], bots[i + 1]], 'Message ' + Math.random(), function() { 183 | tested++; 184 | if (tested === max) { 185 | teardown(); 186 | } 187 | }); 188 | } 189 | }); 190 | }, 191 | 192 | 'send messages with colons (#49)': function(done) { 193 | var createClient = this.server.createClient.bind(this.server) 194 | , server = this.server.server 195 | , message = 'this is my message : hello tom' 196 | ; 197 | 198 | createClient({ nick: 'testbot1', channel: '#test' }, function(testbot1) { 199 | createClient({ nick: 'testbot2', channel: '#test' }, function(testbot2) { 200 | var user = server.users.registered.filter(function(user) { return user.nick == testbot2.nick; })[0]; 201 | 202 | testbot1.on('message', function(from, to, m) { 203 | assert.equal(message, m); 204 | 205 | testbot1.disconnect(); 206 | testbot2.disconnect(); 207 | done(); 208 | }); 209 | 210 | testbot2.say('#test', message); 211 | }); 212 | }); 213 | }, 214 | 215 | 'invalid nicks (#27)': function(done) { 216 | var nick = 'a|ex' 217 | , self = this 218 | , client = new irc.Client('localhost', nick, { 219 | channels: ['#test'] 220 | , port: port 221 | , debug: false 222 | }); 223 | 224 | client.addListener('error', function(message) { 225 | var connectedUsers = self.server.server.users.registered; 226 | if (message.command === 'err_erroneusnickname') { 227 | assert.equal(1, connectedUsers.length); 228 | assert.equal(null, connectedUsers[0].nick); 229 | client.disconnect(); 230 | done(); 231 | } 232 | }); 233 | } 234 | } 235 | }; 236 | -------------------------------------------------------------------------------- /lib/user.js: -------------------------------------------------------------------------------- 1 | var dns = require('dns'), 2 | winston = require('winston'), 3 | irc = require('./protocol'); 4 | 5 | function User(client, ircServer) { 6 | this.server = ircServer; 7 | this.config = ircServer.config; 8 | this.nick = null; 9 | this.username = null; 10 | this.realname = null; 11 | this.channels = []; 12 | this.quitMessage = 'Connection lost'; 13 | this.disconnected = false; 14 | this.pendingAuth = false; 15 | this.passwordAccepted = false; 16 | this.lastPing = null; 17 | this.postAuthQueue = []; 18 | 19 | if (client) { 20 | this.client = client; 21 | } 22 | 23 | if (client && client.stream) { 24 | this.stream = client.stream; 25 | this.remoteAddress = client.stream.remoteAddress; 26 | this.hostname = client.stream.remoteAddress; 27 | } 28 | 29 | this.registered = false; 30 | this._modes = []; 31 | this.channelModes = {}; 32 | this.serverName = ''; 33 | this.created = new Date() / 1000; 34 | this.updated = new Date(); 35 | this.isAway = false; 36 | this.awayMessage = null; 37 | this.serverOper = false; 38 | this.localOper = false; 39 | this.hopCount = 0; 40 | this.servertoken = null; 41 | 42 | this.hostLookup(); 43 | } 44 | 45 | User.prototype = { 46 | get id() { 47 | return this.nick; 48 | }, 49 | 50 | get mask() { 51 | return ':' + this.nick + '!' + this.username + '@' + this.hostname; 52 | }, 53 | 54 | get modes() { 55 | return '+' + this._modes.join(''); 56 | }, 57 | 58 | set modes(modes) { 59 | if (modes) { 60 | modes = modes.replace(/^\+/, ''); 61 | this._modes = modes.split(''); 62 | } 63 | }, 64 | 65 | get idle() { 66 | return parseInt(((new Date()) - this.updated) / 1000, 10); 67 | }, 68 | 69 | get isOper() { 70 | return this.modes.indexOf('o') !== -1; 71 | }, 72 | 73 | get isInvisible() { 74 | return this.modes.indexOf('i') !== -1; 75 | }, 76 | 77 | send: function() { 78 | if (!this.stream) return; 79 | 80 | var self = this, 81 | message = arguments.length === 1 ? 82 | arguments[0] 83 | : Array.prototype.slice.call(arguments).join(' '); 84 | 85 | winston.log('S: [' + this.nick + '] ' + message); 86 | 87 | try { 88 | this.stream.write(message + '\r\n'); 89 | } catch (exception) { 90 | winston.error('[' + this.nick + '] error writing to stream:', exception); 91 | 92 | // This setTimeout helps prevent against race conditions when multiple clients disconnect at the same time 93 | setTimeout(function() { 94 | if (!self.disconnected) { 95 | self.disconnected = true; 96 | self.server.disconnect(self); 97 | } 98 | }, 1); 99 | } 100 | }, 101 | 102 | expandMask: function(mask) { 103 | return mask.replace(/\./g, '\\.'). 104 | replace(/\*/g, '.*'); 105 | }, 106 | 107 | matchesMask: function(mask) { 108 | var parts = mask.match(/([^!]*)!([^@]*)@(.*)/) || [], 109 | matched = true, 110 | lastPart = parts.length < 4 ? parts.length : 4; 111 | parts = parts.slice(1, lastPart).map(this.expandMask); 112 | 113 | if (!this.nick.match(parts[0])) { 114 | return false; 115 | } else if (!this.username.match(parts[1])) { 116 | return false; 117 | } else if (!this.hostname.match(parts[2])) { 118 | return false; 119 | } else { 120 | return true; 121 | } 122 | }, 123 | 124 | sharedChannelWith: function(targetUser) { 125 | var user = this, 126 | channels = targetUser.channels, 127 | matchedChannel; 128 | channels.some(function(channel) { 129 | if (user.channels.indexOf(channel) !== -1) { 130 | matchedChannel = channel; 131 | return true; 132 | } 133 | }); 134 | 135 | return matchedChannel; 136 | }, 137 | 138 | // TODO: Voice 139 | channelNick: function(channel) { 140 | return this.isOp(channel) ? '@' + this.nick : this.nick; 141 | }, 142 | 143 | isOp: function(channel) { 144 | if (this.channelModes[channel]) 145 | return this.channelModes[channel].match(/o/); 146 | }, 147 | 148 | op: function(channel) { 149 | this.channelModes[channel] += 'o'; 150 | }, 151 | 152 | deop: function(channel) { 153 | if (this.channelModes[channel]) 154 | this.channelModes[channel] = this.channelModes[channel].replace(/o/, ''); 155 | }, 156 | 157 | oper: function() { 158 | if (!this.modes.match(/o/)) { 159 | this._modes.push('o'); 160 | this.send(this.mask, 'MODE', this.nick, '+o', this.nick); 161 | this.localOper = true; 162 | } 163 | }, 164 | 165 | deoper: function() { 166 | this.removeMode.o.apply(this); 167 | this.localOper = false; 168 | }, 169 | 170 | isHop: function(channel) { 171 | if (this.channelModes[channel]) 172 | return this.channelModes[channel].match(/h/) || this.isOp(channel); 173 | }, 174 | 175 | hop: function(channel) { 176 | this.channelModes[channel] += 'h'; 177 | }, 178 | 179 | dehop: function(channel) { 180 | if (this.channelModes[channel]) 181 | this.channelModes[channel] = this.channelModes[channel].replace(/h/, ''); 182 | }, 183 | 184 | isVoiced: function(channel) { 185 | if (this.channelModes[channel]) 186 | return this.channelModes[channel].match(/v/) || this.isHop(channel)|| this.isOp(channel); 187 | }, 188 | 189 | voice: function(channel) { 190 | this.channelModes[channel] += 'v'; 191 | }, 192 | 193 | devoice: function(channel) { 194 | if (this.channelModes[channel]) 195 | this.channelModes[channel] = this.channelModes[channel].replace(/v/, ''); 196 | }, 197 | 198 | hostLookup: function() { 199 | if (!this.remoteAddress) return; 200 | var user = this; 201 | dns.reverse(this.remoteAddress, function(err, addresses) { 202 | user.hostname = addresses && addresses.length > 0 ? addresses[0] : user.remoteAddress; 203 | }); 204 | }, 205 | 206 | register: function() { 207 | if (this.registered === false 208 | && this.nick 209 | && this.username) { 210 | this.serverName = this.config.name; 211 | this.send(this.server.host, irc.reply.welcome, this.nick, 'Welcome to the ' + this.config.network + ' IRC network', this.mask); 212 | this.send(this.server.host, irc.reply.yourHost, this.nick, 'Your host is', this.config.hostname, 'running version', this.server.version); 213 | this.send(this.server.host, irc.reply.created, this.nick, 'This server was created on', this.server.created); 214 | this.send(this.server.host, irc.reply.myInfo, this.nick, this.config.name, this.server.version); 215 | this.server.motd(this); 216 | this.registered = true; 217 | this.addMode.w.apply(this); 218 | } 219 | }, 220 | 221 | message: function(nick, message) { 222 | var user = this.server.users.find(nick); 223 | this.updated = new Date(); 224 | 225 | if (user) { 226 | if (user.isAway) { 227 | this.send(this.server.host, irc.reply.away, this.nick, user.nick, ':' + user.awayMessage); 228 | } 229 | user.send(this.mask, 'PRIVMSG', user.nick, ':' + message); 230 | } else { 231 | this.send(this.server.host, irc.errors.noSuchNick, this.nick, nick, ':No such nick/channel'); 232 | } 233 | }, 234 | 235 | addModes: function(user, modes, arg) { 236 | var thisUser = this; 237 | modes.slice(1).split('').forEach(function(mode) { 238 | if (thisUser.addMode[mode]) 239 | thisUser.addMode[mode].apply(thisUser, [user, arg]); 240 | }); 241 | }, 242 | 243 | addMode: { 244 | i: function(user, arg) { 245 | if (this.isOper || this === user) { 246 | if (!user.modes.match(/i/)) { 247 | user._modes.push('i'); 248 | user.send(user.mask, 'MODE', this.nick, '+i', user.nick); 249 | if (this !== user) { 250 | this.send(this.mask, 'MODE', this.nick, '+i', user.nick); 251 | } 252 | } 253 | } else { 254 | this.send(this.server.host, irc.errors.usersDoNotMatch, this.nick, user.nick, ':Cannot change mode for other users'); 255 | } 256 | }, 257 | 258 | o: function() { 259 | // Can only be issued by OPER 260 | }, 261 | 262 | w: function() { 263 | if (!this.modes.match(/w/)) { 264 | this._modes.push('w'); 265 | this.send(this.mask, 'MODE', this.nick, '+w', this.nick); 266 | } 267 | } 268 | }, 269 | 270 | removeModes: function(user, modes, arg) { 271 | var thisUser = this; 272 | modes.slice(1).split('').forEach(function(mode) { 273 | if (thisUser.removeMode[mode]) 274 | thisUser.removeMode[mode].apply(thisUser, [user, arg]); 275 | }); 276 | }, 277 | 278 | removeMode: { 279 | i: function(user, arg) { 280 | if (this.isOper || this === user) { 281 | if (user.modes.match(/i/)) { 282 | user._modes.splice(user._modes.indexOf('i'), 1); 283 | user.send(user.mask, 'MODE', this.nick, '-i', user.nick); 284 | if (this !== user) { 285 | this.send(this.mask, 'MODE', this.nick, '-i', user.nick); 286 | } 287 | } 288 | } else { 289 | this.send(this.server.host, irc.errors.usersDoNotMatch, this.nick, user.nick, ':Cannot change mode for other users'); 290 | } 291 | }, 292 | 293 | o: function() { 294 | if (this.modes.match(/o/)) { 295 | user._modes.splice(user._modes.indexOf('o'), 1); 296 | this.send(this.mask, 'MODE', this.nick, '-o', this.nick); 297 | } 298 | }, 299 | 300 | w: function() { 301 | if (this.modes.match(/w/)) { 302 | user._modes.splice(user._modes.indexOf('w'), 1); 303 | this.send(this.mask, 'MODE', this.nick, '-w', this.nick); 304 | } 305 | } 306 | }, 307 | 308 | queue: function(message) { 309 | this.postAuthQueue.push(message); 310 | }, 311 | 312 | runPostAuthQueue: function() { 313 | if (!this.passwordAccepted) return; 314 | 315 | var self = this; 316 | 317 | this.postAuthQueue.forEach(function(message) { 318 | self.server.respondToMessage(self, message); 319 | }); 320 | }, 321 | hasTimedOut: function() { 322 | return this.lastPing && (Math.floor((Date.now() - this.lastPing) / 1000) > (this.config.pingTimeout || this.config.idleTimeout)); 323 | }, 324 | 325 | closeStream: function() { 326 | if (this.stream && this.stream.end) { 327 | this.stream.end(); 328 | } 329 | }, 330 | 331 | quit: function(message) { 332 | this.quitMessage = message; 333 | this.closeStream(); 334 | } 335 | }; 336 | 337 | exports.User = User; 338 | -------------------------------------------------------------------------------- /lib/server.js: -------------------------------------------------------------------------------- 1 | // 2 | // ::::::::::.. .,-::::::::::::-. ....:::::: .::::::. 3 | // ;;;;;;;``;;;; ,;;;'````' ;;, `';, ;;;;;;;;;````;;;` ` 4 | // [[[ [[[,/[[[' [[[ `[[ [[ ''` `[[. '[==/[[[[, 5 | // $$$ $$$$$$c $$$ $$, $$ ,,, `$$ ''' $ 6 | // 888 888b "88bo,`88bo,__,o, 888_,o8P'd8b888boood88 88b dP 7 | // MMM MMMM "W" "YUMMMMMP"MMMMP"` YMP"MMMMMMMM" "YMmMY" 8 | // 9 | // A Node.JS IRC Server 10 | // ircd.js 11 | 12 | // libs: 13 | // http://github.com/pgte/carrier 14 | 15 | // rfcs: 16 | // http://www.networksorcery.com/enp/rfc/rfc2812.txt 17 | // http://tools.ietf.org/html/rfc1459 18 | // 19 | // spells out some stuff the RFC was light on: 20 | // http://docs.dal.net/docs/misc.html#5 21 | 22 | var net = require('net'), 23 | tls = require('tls'), 24 | carrier = require('carrier'), 25 | fs = require('fs'), 26 | irc = require('./protocol'), 27 | path = require('path'), 28 | assert = require('assert'), 29 | Channel = require('./channel').Channel, 30 | User = require('./user').User, 31 | History = require('./storage').History, 32 | ChannelDatabase = require('./storage').ChannelDatabase, 33 | UserDatabase = require('./storage').UserDatabase, 34 | ServerCommands = require('./commands'), 35 | winston = require('winston'), 36 | commander = require('commander') 37 | exists = fs.exists || path.exists // 0.8 moved exists to fs 38 | ; 39 | 40 | function AbstractConnection(stream) { 41 | this.stream = stream; 42 | this.object = null; 43 | 44 | this.__defineGetter__('id', function() { 45 | return this.object ? this.object.id : 'Unregistered'; 46 | }); 47 | } 48 | 49 | function Server() { 50 | this.history = new History(this); 51 | this.users = new UserDatabase(this); 52 | this.channels = new ChannelDatabase(this); 53 | this.config = null; 54 | this.commands = new ServerCommands(this); 55 | } 56 | 57 | Server.boot = function() { 58 | var server = new Server(); 59 | 60 | server.file = server.cliParse(); 61 | 62 | server.loadConfig(function() { 63 | server.start(); 64 | server.createDefaultChannels(); 65 | }); 66 | 67 | process.on('SIGHUP', function() { 68 | winston.info('Reloading config...'); 69 | server.loadConfig(); 70 | }); 71 | 72 | process.on('SIGTERM', function() { 73 | winston.info('Exiting...'); 74 | server.close(); 75 | }); 76 | }; 77 | 78 | Server.prototype = { 79 | version: '0.0.17', 80 | created: '2012-09-21', 81 | debug: false, 82 | get name() { return this.config.serverName; }, 83 | get info() { return this.config.serverDescription; }, 84 | get token() { return this.config.token; }, 85 | get host() { return ':' + this.config.hostname; }, 86 | 87 | cliParse: function() { 88 | var file = null; 89 | 90 | commander.option('-f --file [file]','Configuration file (Defaults: /etc/ircdjs/config.json or ../config/config.json)') 91 | .parse(process.argv); 92 | // When the -f switch is passwd without a parameter, commander.js evaluates it to true. 93 | if (commander.file && commander.file !== true) file = commander.file; 94 | return file; 95 | }, 96 | 97 | loadConfig: function(fn) { 98 | var server = this, 99 | paths = [ 100 | path.join('/', 'etc', 'ircdjs', 'config.json'), 101 | path.join(__dirname, '..', 'config', 'config.json') 102 | ]; 103 | 104 | this.config = null; 105 | if (server.file) paths.unshift(server.file); 106 | 107 | paths.forEach(function(name) { 108 | exists(name, function(exists) { 109 | if (!exists || server.config) return; 110 | try { 111 | server.config = JSON.parse(fs.readFileSync(name).toString()); 112 | server.config.idleTimeout = server.config.idleTimeout || 60; 113 | winston.info('Using config file: ' + name); 114 | if (fn) fn(); 115 | } catch (exception) { 116 | winston.error('Please ensure you have a valid config file.', exception); 117 | } 118 | }); 119 | }); 120 | }, 121 | 122 | normalizeName: function(name) { 123 | return name && 124 | name.toLowerCase() 125 | .replace(/{/g, '[') 126 | .replace(/}/g, ']') 127 | .replace(/\|/g, '\\') 128 | .trim(); 129 | }, 130 | 131 | isValidPositiveInteger: function(str) { 132 | var n = ~~Number(str); 133 | return String(n) === str && n >= 0; 134 | }, 135 | 136 | valueExists: function(value, collection, field) { 137 | var self = this; 138 | value = this.normalizeName(value); 139 | return collection.some(function(u) { 140 | return self.normalizeName(u[field]) === value; 141 | }) 142 | }, 143 | 144 | //make sure the channel name is valid as per RFC 2813 145 | channelTarget: function(target) { 146 | var prefix = target[0]; 147 | var channelPrefixes = ['#','&','!','+']; 148 | return (channelPrefixes.indexOf(prefix) !== -1); 149 | }, 150 | 151 | parse: function(data) { 152 | var parts = data.trim().split(/ :/), 153 | args = parts[0].split(' '); 154 | 155 | parts = [parts.shift(), parts.join(' :')]; 156 | 157 | if (parts.length > 0) { 158 | args.push(parts[1]); 159 | } 160 | 161 | if (data.match(/^:/)) { 162 | args[1] = args.splice(0, 1, args[1]); 163 | args[1] = (args[1] + '').replace(/^:/, ''); 164 | } 165 | 166 | return { 167 | command: args[0].toUpperCase(), 168 | args: args.slice(1) 169 | }; 170 | }, 171 | 172 | respondToMessage: function(user, message) { 173 | this.commands[message.command].apply(this.commands, [user].concat(message.args)); 174 | }, 175 | 176 | respond: function(data, client) { 177 | var message = this.parse(data); 178 | 179 | if (this.validCommand(message.command)) { 180 | if (this.config.serverPassword && !client.object.passwordAccepted) { 181 | this.queueResponse(client, message); 182 | } else { 183 | this.respondToMessage(client.object, message); 184 | } 185 | } 186 | }, 187 | 188 | queueResponse: function(client, message) { 189 | if ('PASS' === message.command) { 190 | // Respond now 191 | client.object.pendingAuth = false; 192 | this.respondToMessage(client.object, message); 193 | } else { 194 | client.object.queue(message); 195 | } 196 | }, 197 | 198 | validCommand: function(command) { 199 | return this.commands[command]; 200 | }, 201 | 202 | createDefaultChannels: function() { 203 | var self = this; 204 | if (this.config.channels) { 205 | Object.keys(this.config.channels).forEach(function(channel) { 206 | var channelName = ''; 207 | if (!self.channelTarget(channel)) { 208 | channelName = "#" + channel; 209 | } else { 210 | channelName = channel; 211 | } 212 | var newChannel = self.channels.registered[self.normalizeName(channelName)] = new Channel(channelName, self); 213 | newChannel.topic = self.config.channels[channel].topic; 214 | }); 215 | } 216 | }, 217 | 218 | motd: function(user) { 219 | user.send(this.host, irc.reply.motdStart, user.nick, ':- Message of the Day -'); 220 | user.send(this.host, irc.reply.motd, user.nick, this.config.motd || 'No message set'); 221 | user.send(this.host, irc.reply.motdEnd, user.nick, ':End of /MOTD command.'); 222 | }, 223 | 224 | startTimeoutHandler: function() { 225 | var self = this; 226 | var timeout = this.config.pingTimeout || 10; 227 | this.timeoutHandler = setInterval(function() { 228 | self.users.forEach(function(user) { 229 | if (user.hasTimedOut()) { 230 | winston.info('User timed out:', user.mask); 231 | self.disconnect(user); 232 | } else { 233 | // TODO: If no other activity is detected 234 | user.send('PING', self.config.hostname, self.host); 235 | } 236 | }); 237 | }, timeout * 1000); 238 | }, 239 | 240 | stopTimeoutHandler: function() { 241 | clearInterval(this.timeoutHandler); 242 | }, 243 | 244 | start: function(callback) { 245 | var server = this, key, cert, options; 246 | 247 | if (this.config.key && this.config.cert) { 248 | try { 249 | key = fs.readFileSync(this.config.key); 250 | cert = fs.readFileSync(this.config.cert); 251 | } catch (exception) { 252 | winston.error('Fatal error:', exception); 253 | } 254 | options = { key: key, cert: cert }; 255 | this.server = tls.createServer(options, handleStream); 256 | } else { 257 | this.server = net.createServer(handleStream); 258 | } 259 | 260 | assert.ok(callback === undefined || typeof callback == 'function'); 261 | this.server.listen(this.config.port, callback); 262 | winston.info('Server listening on port: ' + this.config.port); 263 | 264 | this.startTimeoutHandler(); 265 | 266 | function handleStream(stream) { 267 | try { 268 | var carry = carrier.carry(stream), 269 | client = new AbstractConnection(stream); 270 | 271 | client.object = new User(client, server); 272 | if (server.config.serverPassword) { 273 | client.object.pendingAuth = true; 274 | } 275 | 276 | stream.on('end', function() { server.end(client); }); 277 | stream.on('error', winston.error); 278 | carry.on('line', function(line) { server.data(client, line); }); 279 | } catch (exception) { 280 | winston.error('Fatal error:', exception); 281 | } 282 | } 283 | }, 284 | 285 | close: function(callback) { 286 | if (callback !== undefined) { 287 | assert.ok(typeof callback === 'function'); 288 | this.server.once('close', callback); 289 | } 290 | this.stopTimeoutHandler(); 291 | this.server.close(); 292 | }, 293 | 294 | end: function(client) { 295 | var user = client.object; 296 | 297 | if (user) { 298 | this.disconnect(user); 299 | } 300 | }, 301 | 302 | disconnect: function(user) { 303 | user.channels.forEach(function(channel) { 304 | channel.users.forEach(function(channelUser) { 305 | if (channelUser !== user) { 306 | channelUser.send(user.mask, 'QUIT', user.quitMessage); 307 | } 308 | }); 309 | 310 | channel.users.splice(channel.users.indexOf(user), 1); 311 | }); 312 | 313 | user.closeStream(); 314 | this.users.remove(user); 315 | user = null; 316 | }, 317 | 318 | data: function(client, line) { 319 | line = line.slice(0, 512); 320 | winston.info('[' + this.name + ', C: ' + client.id + '] ' + line); 321 | this.respond(line, client); 322 | } 323 | }; 324 | 325 | exports.Server = Server; 326 | exports.winston = winston; 327 | 328 | if (!module.parent) { 329 | Server.boot(); 330 | } 331 | -------------------------------------------------------------------------------- /lib/channel.js: -------------------------------------------------------------------------------- 1 | var irc = require('./protocol'), 2 | winston = require('winston'); 3 | 4 | function Channel(name, ircServer) { 5 | this.server = ircServer; 6 | this.name = name; 7 | this.users = []; 8 | this.topic = ''; 9 | this._modes = ['n', 't', 'r']; 10 | this.banned = []; 11 | this.userLimit = 0; 12 | this.key = null; 13 | this.inviteList = []; 14 | } 15 | 16 | Channel.prototype = { 17 | get modes() { 18 | return '+' + this._modes.join(''); 19 | }, 20 | 21 | set modes(modes) { 22 | this._modes = modes.split(''); 23 | }, 24 | 25 | get memberCount() { 26 | return this.users.length; 27 | }, 28 | 29 | get isLimited() { 30 | return this._modes.indexOf('l') > -1; 31 | }, 32 | 33 | get isPublic() { 34 | return !this.isSecret && !this.isPrivate; 35 | }, 36 | 37 | get isSecret() { 38 | return this._modes.indexOf('s') > -1; 39 | }, 40 | 41 | get isPrivate() { 42 | return this._modes.indexOf('p') > -1; 43 | }, 44 | 45 | get isModerated() { 46 | return this._modes.indexOf('m') > -1; 47 | }, 48 | 49 | get isInviteOnly() { 50 | return this._modes.indexOf('i') > -1; 51 | }, 52 | 53 | get names() { 54 | var channel = this; 55 | return this.users.map(function(user) { 56 | return user.channelNick(channel); 57 | }).join(' '); 58 | }, 59 | 60 | get type() { 61 | if (this.isPrivate) { 62 | return '*'; 63 | } else if (this.isSecret) { 64 | return '@'; 65 | } else { 66 | return '='; 67 | } 68 | }, 69 | 70 | onInviteList: function(user) { 71 | var userNick = this.server.normalizeName(user.nick), 72 | server = this.server; 73 | return this.inviteList.some(function(nick) { 74 | return server.normalizeName(nick) === userNick; 75 | }); 76 | }, 77 | 78 | isValidKey: function(key) { 79 | return key && key.length > 1 && key.length < 9 && !key.match(irc.validations.invalidChannelKey); 80 | }, 81 | 82 | isBanned: function(user) { 83 | return this.banned.some(function(ban) { 84 | return user.matchesMask(ban.mask); 85 | }); 86 | }, 87 | 88 | banMaskExists: function(mask) { 89 | return this.banned.some(function(ban) { 90 | return ban.mask === mask; 91 | }); 92 | }, 93 | 94 | findBan: function(mask) { 95 | for (var i in this.banned) { 96 | if (this.banned[i].mask === mask) { 97 | return this.banned[i]; 98 | } 99 | } 100 | }, 101 | 102 | sendToGroup: function(users, message) { 103 | var server = this.server; 104 | 105 | users.forEach(function(user) { 106 | try { 107 | // TODO: If this user is on another server, route the message to the user 108 | // 1. There needs to be a server map stored on each server 109 | // 2. This can then be used to route with BFS (http://en.wikipedia.org/wiki/Breadth-first_search) 110 | // 3. Spanning-tree loop detection should be implemented 111 | user.send(message); 112 | } catch (exception) { 113 | winston.error('Error writing to stream:', exception); 114 | } 115 | }); 116 | }, 117 | 118 | send: function() { 119 | var message = arguments.length === 1 ? arguments[0] : Array.prototype.slice.call(arguments).join(' '), 120 | server = this.server; 121 | 122 | this.users.forEach(function(user) { 123 | try { 124 | // TODO: If this user is on another server, route the message to the user 125 | // 1. There needs to be a server map stored on each server 126 | // 2. This can then be used to route with BFS (http://en.wikipedia.org/wiki/Breadth-first_search) 127 | // 3. Spanning-tree loop detection should be implemented 128 | user.send(message); 129 | } catch (exception) { 130 | winston.error('Error writing to stream:', exception); 131 | } 132 | }); 133 | }, 134 | 135 | findUserNamed: function(nick) { 136 | nick = this.server.normalizeName(nick); 137 | for (var i = 0; i < this.users.length; i++) { 138 | if (this.server.normalizeName(this.users[i].nick) === nick) { 139 | return this.users[i]; 140 | } 141 | } 142 | }, 143 | 144 | isMember: function(user) { 145 | return this.users.indexOf(user) !== -1; 146 | }, 147 | 148 | addModes: function(user, modes, arg) { 149 | var channel = this; 150 | modes.slice(1).split('').forEach(function(mode) { 151 | if (channel.addMode[mode]) 152 | channel.addMode[mode].apply(channel, [user, arg]); 153 | }); 154 | }, 155 | 156 | opModeAdd: function(mode, user, arg) { 157 | if (user.isOp(this)) { 158 | if (this.modes.indexOf(mode) === -1) { 159 | this.modes += mode; 160 | this.send(user.mask, 'MODE', this.name, '+' + mode, this.name); 161 | return true; 162 | } 163 | } else { 164 | user.send(this.server.host, irc.errors.channelOpsReq, user.nick, this.name, ":You're not channel operator"); 165 | } 166 | return false; 167 | }, 168 | 169 | opModeRemove: function(mode, user, arg) { 170 | if (user.isOp(this)) { 171 | if (this.modes.indexOf(mode) !== -1) { 172 | this.modes = this.modes.replace(mode, ''); 173 | this.send(user.mask, 'MODE', this.name, '-' + mode, this.name); 174 | return true; 175 | } 176 | } else { 177 | user.send(this.server.host, irc.errors.channelOpsReq, user.nick, this.name, ":You're not channel operator"); 178 | } 179 | return false; 180 | }, 181 | 182 | addMode: { 183 | o: function(user, arg) { 184 | if (user.isOp(this)) { 185 | var targetUser = this.findUserNamed(arg); 186 | if (targetUser && !targetUser.isOp(this)) { 187 | targetUser.op(this); 188 | this.send(user.mask, 'MODE', this.name, '+o', targetUser.nick); 189 | } 190 | } else { 191 | user.send(this.server.host, irc.errors.channelOpsReq, user.nick, this.name, ":You're not channel operator"); 192 | } 193 | }, 194 | 195 | h: function(user, arg) { 196 | if (user.isOp(this)) { 197 | var targetUser = this.findUserNamed(arg); 198 | if (targetUser && !targetUser.isHop(this)) { 199 | targetUser.hop(this); 200 | this.send(user.mask, 'MODE', this.name, '+h', targetUser.nick); 201 | } 202 | } else { 203 | user.send(this.server.host, irc.errors.channelOpsReq, user.nick, this.name, ":You're not channel operator"); 204 | } 205 | }, 206 | 207 | v: function(user, arg) { 208 | if (user.isHop(this)) { 209 | var targetUser = this.findUserNamed(arg); 210 | if (targetUser && !targetUser.isVoiced(this)) { 211 | targetUser.voice(this); 212 | this.send(user.mask, 'MODE', this.name, '+v', targetUser.nick); 213 | } 214 | } else { 215 | user.send(this.server.host, irc.errors.channelOpsReq, user.nick, this.name, ":You're must be at least half-op to do that!"); 216 | } 217 | }, 218 | 219 | i: function(user, arg) { 220 | this.opModeAdd('i', user, arg); 221 | }, 222 | 223 | k: function(user, arg) { 224 | if (user.isOp(this)) { 225 | if (this.key) { 226 | user.send(this.server.host, irc.errors.keySet, user.nick, this.name, ":Channel key already set"); 227 | } else if (this.isValidKey(arg)) { 228 | this.key = arg; 229 | this.modes += 'k'; 230 | this.send(user.mask, 'MODE', this.name, '+k ' + arg); 231 | } else { 232 | // TODO: I thought 475 was just returned when joining the channel 233 | user.send(this.server.host, irc.errors.badChannelKey, user.nick, this.name, ":Invalid channel key"); 234 | } 235 | } else { 236 | user.send(this.server.host, irc.errors.channelOpsReq, user.nick, this.name, ":You're not channel operator"); 237 | } 238 | }, 239 | 240 | l: function(user, arg) { 241 | if (user.isOp(this)) { 242 | if (this.server.isValidPositiveInteger(arg)) { 243 | var limit = parseInt(arg, 10); 244 | if (this.userLimit != limit) { 245 | this.modes += 'l'; 246 | this.userLimit = limit; 247 | this.send(user.mask, 'MODE', this.name, '+l ' + arg, this.name); 248 | } 249 | } 250 | } else { 251 | user.send(this.server.host, irc.errors.channelOpsReq, user.nick, this.name, ":You're not channel operator"); 252 | } 253 | }, 254 | 255 | m: function(user, arg) { 256 | this.opModeAdd('m', user, arg); 257 | }, 258 | 259 | n: function(user, arg) { 260 | this.opModeAdd('n', user, arg); 261 | }, 262 | 263 | t: function(user, arg) { 264 | this.opModeAdd('t', user, arg); 265 | }, 266 | 267 | p: function(user, arg) { 268 | this.opModeAdd('p', user, arg); 269 | }, 270 | 271 | s: function(user, arg) { 272 | this.opModeAdd('s', user, arg); 273 | }, 274 | 275 | b: function(user, arg) { 276 | if (user.isOp(this)) { 277 | // TODO: Valid ban mask? 278 | if (!arg || arg.length === 0) { 279 | user.send(this.server.host, irc.errors.needMoreParams, user.nick, this.name, ":Please enter ban mask"); 280 | } else if (!this.banMaskExists(arg)) { 281 | this.banned.push({ user: user, mask: arg, timestamp: (new Date()).valueOf() }); 282 | this.send(user.mask, 'MODE', this.name, '+b', ':' + arg); 283 | } 284 | } else { 285 | user.send(this.server.host, irc.errors.channelOpsReq, user.nick, this.name, ":You're not channel operator"); 286 | } 287 | } 288 | }, 289 | 290 | removeModes: function(user, modes, arg) { 291 | var channel = this; 292 | modes.slice(1).split('').forEach(function(mode) { 293 | if (channel.removeMode[mode]) 294 | channel.removeMode[mode].apply(channel, [user, arg]); 295 | }); 296 | }, 297 | 298 | removeMode: { 299 | o: function(user, arg) { 300 | if (user.isOp(this)) { 301 | var targetUser = this.findUserNamed(arg); 302 | if (targetUser && targetUser.isOp(this)) { 303 | targetUser.deop(this); 304 | this.send(user.mask, 'MODE', this.name, '-o', targetUser.nick); 305 | } 306 | } else { 307 | user.send(this.server.host, irc.errors.channelOpsReq, user.nick, this.name, ":You're not channel operator"); 308 | } 309 | }, 310 | 311 | v: function(user, arg) { 312 | if (user.isOp(this)) { 313 | var targetUser = this.findUserNamed(arg); 314 | if (targetUser && targetUser.isVoiced(this)) { 315 | targetUser.devoice(this); 316 | this.send(user.mask, 'MODE', this.name, '-v', targetUser.nick); 317 | } 318 | } else { 319 | user.send(this.server.host, irc.errors.channelOpsReq, user.nick, this.name, ":You're not channel operator"); 320 | } 321 | }, 322 | 323 | i: function(user, arg) { 324 | this.opModeRemove('i', user, arg); 325 | }, 326 | 327 | k: function(user, arg) { 328 | if (this.opModeRemove('k', user, arg)) { 329 | this.key = null; 330 | } 331 | }, 332 | 333 | l: function(user, arg) { 334 | if (this.opModeRemove('l', user, arg, ' ' + arg)) { 335 | this.userLimit = 0; 336 | } 337 | }, 338 | 339 | m: function(user, arg) { 340 | this.opModeRemove('m', user, arg); 341 | }, 342 | 343 | n: function(user, arg) { 344 | this.opModeRemove('n', user, arg); 345 | }, 346 | 347 | t: function(user, arg) { 348 | this.opModeRemove('t', user, arg); 349 | }, 350 | 351 | p: function(user, arg) { 352 | this.opModeRemove('p', user, arg); 353 | }, 354 | 355 | s: function(user, arg) { 356 | this.opModeRemove('s', user, arg); 357 | }, 358 | 359 | b: function(user, arg) { 360 | if (user.isOp(this)) { 361 | // TODO: Valid ban mask? 362 | if (!arg || arg.length === 0) { 363 | user.send(this.server.host, irc.errors.needMoreParams, user.nick, this.name, ":Please enter ban mask"); 364 | } else { 365 | var ban = this.findBan(arg); 366 | if (ban) { 367 | this.banned.splice(this.banned.indexOf(ban), 1); 368 | this.send(user.mask, 'MODE', this.name, '-b', ':' + arg); 369 | } 370 | } 371 | } else { 372 | user.send(this.server.host, irc.errors.channelOpsReq, user.nick, this.name, ":You're not channel operator"); 373 | } 374 | } 375 | }, 376 | 377 | part: function(user) { 378 | this.users.splice(this.users.indexOf(user), 1); 379 | user.channels.splice(user.channels.indexOf(this), 1); 380 | delete user.channelModes[this]; 381 | } 382 | }; 383 | 384 | exports.Channel = Channel; 385 | -------------------------------------------------------------------------------- /lib/commands.js: -------------------------------------------------------------------------------- 1 | var irc = require('./protocol'), 2 | ircd = require(__dirname + '/../lib/ircd'); 3 | 4 | function Commands(server) { 5 | this.server = server; 6 | } 7 | 8 | Commands.prototype = { 9 | PONG: function(user, hostname) { 10 | user.lastPing = Date.now(); 11 | }, 12 | 13 | PING: function(user, hostname) { 14 | user.lastPing = Date.now(); 15 | user.send(this.server.host, 'PONG', this.server.config.hostname, this.server.host); 16 | }, 17 | 18 | PASS: function(user, password) { 19 | var self = this.server; 20 | ircd.compareHash(password, self.config.serverPassword, function(err, res) { 21 | if (res) { 22 | user.passwordAccepted = true; 23 | user.server = self; 24 | user.runPostAuthQueue(); 25 | } else { 26 | user.send(self.host, irc.errors.passwordWrong, user.nick || 'user', ':Password incorrect'); 27 | user.quit(); 28 | } 29 | }); 30 | }, 31 | 32 | AWAY: function(user, message) { 33 | if (user.isAway && (!message || message.length === 0)) { 34 | user.isAway = false; 35 | user.awayMessage = null; 36 | user.send(this.server.host, irc.reply.unaway, user.nick, ':You are no longer marked as being away'); 37 | } else if (message && message.length > 0) { 38 | user.isAway = true; 39 | user.awayMessage = message; 40 | user.send(this.server.host, irc.reply.nowAway, user.nick, ':You have been marked as being away'); 41 | } else { 42 | user.send(this.server.host, irc.errors.needMoreParams, user.nick, ':Need more parameters'); 43 | } 44 | }, 45 | 46 | VERSION: function(user, server) { 47 | // TODO: server 48 | user.send(this.server.host, 49 | irc.reply.version, 50 | user.nick, 51 | this.server.version + '.' + (this.server.debug ? 'debug' : ''), 52 | this.server.config.hostname, ':' + this.server.config.name); 53 | }, 54 | 55 | TIME: function(user, server) { 56 | // TODO: server 57 | user.send(this.server.host, irc.reply.time, user.nick, this.server.config.hostname, ':' + (new Date())); 58 | }, 59 | 60 | NICK: function(user, nick) { 61 | var oldMask = user.mask; 62 | 63 | if (!nick || nick.length === 0) { 64 | return user.send(this.server.host, irc.errors.noNickGiven, ':No nickname given'); 65 | } else if (nick === user.nick) { 66 | return; 67 | } else if (nick.length > (this.server.config.maxNickLength || 9) || nick.match(irc.validations.invalidNick)) { 68 | return user.send(this.server.host, irc.errors.badNick, (user.nick || ''), nick, ':Erroneus nickname'); 69 | } else if (this.server.valueExists(nick, this.server.users.registered, 'nick')) { 70 | return user.send(this.server.host, irc.errors.nameInUse, '*', nick, ':is already in use'); 71 | } 72 | 73 | nick = nick.trim(); 74 | user.send(user.mask, 'NICK', ':' + nick); 75 | 76 | user.channels.forEach(function(channel) { 77 | var users = channel.users.splice(channel.users.indexOf(user), 1); 78 | channel.sendToGroup(users, user.mask + ' NICK : ' + nick); 79 | }); 80 | 81 | user.nick = nick.trim(); 82 | user.register(); 83 | }, 84 | 85 | USER: function(user, username, hostname, servername, realname) { 86 | this.server.users.register(user, username, hostname, servername, realname); 87 | }, 88 | 89 | JOIN: function(user, channelNames, key) { 90 | var server = this.server; 91 | if (!channelNames || !channelNames.length) { 92 | return user.send(this.server.host, irc.errors.needMoreParams, user.nick, ':Need more parameters'); 93 | } 94 | 95 | channelNames.split(',').forEach(function(args) { 96 | var nameParts = args.split(' '), 97 | channelName = nameParts[0]; 98 | 99 | if (!server.channelTarget(channelName) 100 | || channelName.match(irc.validations.invalidChannel)) { 101 | user.send(server.host, irc.errors.noSuchChannel, ':No such channel'); 102 | } else { 103 | server.channels.join(user, channelName, key); 104 | } 105 | }); 106 | }, 107 | 108 | // TODO: this.server can accept multiple channels according to the spec 109 | PART: function(user, channelName, partMessage) { 110 | var channel = this.server.channels.find(channelName); 111 | if (channel && user.channels.indexOf(channel) !== -1) { 112 | partMessage = partMessage ? ' :' + partMessage : ''; 113 | channel.send(user.mask, 'PART', channelName + partMessage); 114 | channel.part(user); 115 | if (channel.users.length === 0) { 116 | this.server.channels.remove(channel); 117 | } 118 | } 119 | }, 120 | 121 | KICK: function(user, channels, users, kickMessage) { 122 | var channelMasks = channels.split(','), 123 | userNames = users.split(','), 124 | server = this.server; 125 | 126 | kickMessage = kickMessage ? ':' + kickMessage : ':' + user.nick; 127 | 128 | // ERR_BADCHANMASK 129 | 130 | if (userNames.length !== channelMasks.length) { 131 | user.send(this.server.host, irc.errors.needMoreParams, user.nick, ':Need more parameters'); 132 | } else { 133 | channelMasks.forEach(function(channelMask, i) { 134 | var channel = server.channels.findWithMask(channelMask), 135 | userName = userNames[i], 136 | targetUser; 137 | 138 | if (!channel) { 139 | user.send(server.host, irc.errors.noSuchChannel, ':No such channel'); 140 | return; 141 | } 142 | 143 | targetUser = channel.findUserNamed(userName); 144 | 145 | if (!channel.findUserNamed(user.nick)) { 146 | user.send(server.host, irc.errors.notOnChannel, user.nick, channel.name, ':Not on channel'); 147 | } else if (!targetUser) { 148 | user.send(server.host, irc.errors.userNotInChannel, userName, channel.name, ':User not in channel'); 149 | } else if (!user.isOp(channel)) { 150 | user.send(server.host, irc.errors.channelOpsReq, user.nick, channel.name, ":You're not channel operator"); 151 | } else { 152 | channel.send(user.mask, 'KICK', channel.name, targetUser.nick, kickMessage); 153 | channel.part(targetUser); 154 | } 155 | }); 156 | } 157 | }, 158 | 159 | TOPIC: function(user, channelName, topic) { 160 | var channel = this.server.channels.find(channelName); 161 | 162 | if (!channel) { 163 | user.send(this.server.host, irc.errors.noSuchNick, user.nick, channelName, ':No such nick/channel'); 164 | } else { 165 | if (channel.modes.indexOf('t') === -1 || user.isHop(channel)) { 166 | channel.topic = topic; 167 | channel.send(user.mask, 'TOPIC', channel.name, ':' + topic); 168 | } else { 169 | user.send(this.server.host, irc.errors.channelOpsReq, user.nick, channel.name, ":You must be at least half-op to do that!"); 170 | } 171 | } 172 | }, 173 | 174 | // TODO: The RFC says the sender nick and actual user nick should be checked 175 | // TODO: Message validation 176 | PRIVMSG: function(user, target, message) { 177 | // ERR_NOTOPLEVEL 178 | // ERR_WILDTOPLEVEL 179 | // ERR_TOOMANYTARGETS 180 | // ERR_NOSUCHNICK 181 | // RPL_AWAY 182 | if (!target || target.length === 0) { 183 | user.send(this.server.host, irc.errors.noRecipient, ':No recipient given'); 184 | } else if (!message || message.length === 0) { 185 | user.send(this.server.host, irc.errors.noTextToSend, ':No text to send'); 186 | } else if (this.server.channelTarget(target)) { 187 | var channel = this.server.channels.find(target); 188 | if (!channel) { 189 | user.send(this.server.host, irc.errors.noSuchNick, user.nick, target, ':No such nick/channel'); 190 | } else if (channel.isModerated && !user.isVoiced(channel)) { 191 | user.send(this.server.host, irc.errors.cannotSend, channel.name, ':Cannot send to channel'); 192 | } else if (user.channels.indexOf(channel) === -1) { 193 | if (channel.modes.indexOf('n') !== -1) { 194 | user.send(this.server.host, irc.errors.cannotSend, channel.name, ':Cannot send to channel'); 195 | return; 196 | } 197 | } else { 198 | this.server.channels.message(user, channel, message); 199 | } 200 | } else { 201 | user.message(target, message); 202 | } 203 | }, 204 | 205 | INVITE: function(user, nick, channelName) { 206 | var channel = this.server.channels.find(channelName), 207 | targetUser = this.server.users.find(nick); 208 | 209 | // TODO: Can this.server accept multiple channel names? 210 | // TODO: ERR_NOTONCHANNEL 211 | if (!targetUser) { 212 | user.send(this.server.host, irc.errors.noSuchNick, user.nick, nick, ':No such nick/channel'); 213 | return; 214 | } else if (channel) { 215 | if (channel.isInviteOnly && !user.isOp(channel)) { 216 | user.send(this.server.host, irc.errors.channelOpsReq, user.nick, channel.name, ":You're not channel operator"); 217 | return; 218 | } else if (channel.onInviteList(targetUser)) { 219 | user.send(this.server.host, irc.errors.userOnChannel, user.nick, targetUser.nick, ':User is already on that channel'); 220 | return; 221 | } 222 | } else if (!this.server.channelTarget(channelName)) { 223 | // Invalid channel 224 | return; 225 | } else { 226 | // TODO: Make this.server a register function 227 | // Create the channel 228 | channel = this.server.channels.registered[this.server.normalizeName(channelName)] = new Channel(channelName, this.server); 229 | } 230 | 231 | user.send(this.server.host, irc.reply.inviting, user.nick, targetUser.nick, channelName); 232 | targetUser.send(user.mask, 'INVITE', targetUser.nick, ':' + channelName); 233 | 234 | // TODO: How does an invite list get cleared? 235 | channel.inviteList.push(targetUser.nick); 236 | }, 237 | 238 | MODE: function(user, target, modes, arg) { 239 | // TODO: This should work with multiple parameters, like the definition: 240 | // {[+|-]|o|p|s|i|t|n|b|v} [] [] [] 241 | // o - give/take channel operator privileges [done] 242 | // p - private channel flag [done] 243 | // s - secret channel flag; [done] - what's the difference? 244 | // i - invite-only channel flag; [done] 245 | // t - topic settable by channel operator only flag; [done] 246 | // n - no messages to channel from clients on the outside; [done] 247 | // m - moderated channel; [done] 248 | // l - set the user limit to channel; [done] 249 | // b - set a ban mask to keep users out; [done] 250 | // v - give/take the ability to speak on a moderated channel; [done] 251 | // k - set a channel key (password). [done] 252 | 253 | // User modes 254 | // a - user is flagged as away; [done] 255 | // i - marks a users as invisible; [done] 256 | // w - user receives wallops; [done] 257 | // r - restricted user connection; 258 | // o - operator flag; 259 | // O - local operator flag; 260 | // s - marks a user for receipt of server notices. 261 | var server = this.server; 262 | 263 | if (this.server.channelTarget(target)) { 264 | var channel = this.server.channels.find(target); 265 | if (!channel) { 266 | // TODO: Error 267 | } else if (modes) { 268 | if (modes[0] === '+') { 269 | channel.addModes(user, modes, arg); 270 | } else if (modes[0] === '-') { 271 | channel.removeModes(user, modes, arg); 272 | } else if (modes === 'b') { 273 | channel.banned.forEach(function(ban) { 274 | user.send(server.host, irc.reply.banList, user.nick, channel.name, ban.mask, ban.user.nick, ban.timestamp); 275 | }); 276 | user.send(this.server.host, irc.reply.endBan, user.nick, channel.name, ':End of Channel Ban List'); 277 | } 278 | } else { 279 | user.send(this.server.host, irc.reply.channelModes, user.nick, channel.name, channel.modes); 280 | } 281 | } else { 282 | // TODO: Server user modes 283 | var targetUser = this.server.users.find(target); 284 | if (targetUser) { 285 | if (modes[0] === '+') { 286 | targetUser.addModes(user, modes, arg); 287 | } else if (modes[0] === '-') { 288 | targetUser.removeModes(user, modes, arg); 289 | } 290 | } 291 | } 292 | }, 293 | 294 | LIST: function(user, targets) { 295 | // TODO: ERR_TOOMANYMATCHES 296 | // TODO: ERR_NOSUCHSERVER 297 | var server = this.server, 298 | channels = {}; 299 | user.send(this.server.host, irc.reply.listStart, user.nick, 'Channel', ':Users Name'); 300 | if (targets) { 301 | targets = targets.split(','); 302 | targets.forEach(function(target) { 303 | var channel = server.channels.find(target); 304 | if (channel) { 305 | channels[channel.name] = channel; 306 | } 307 | }); 308 | } else { 309 | channels = this.server.channels.registered; 310 | } 311 | 312 | for (var i in channels) { 313 | var channel = channels[i]; 314 | // if channel is secret or private, ignore 315 | if (channel.isPublic || channel.isMember(user)) { 316 | user.send(this.server.host, irc.reply.list, user.nick, channel.name, channel.memberCount, ':[' + channel.modes + '] ' + channel.topic); 317 | } 318 | } 319 | 320 | user.send(this.server.host, irc.reply.listEnd, user.nick, ':End of /LIST'); 321 | }, 322 | 323 | // TODO: LIST 324 | NAMES: function(user, targets) { 325 | var server = this.server; 326 | if (targets) { 327 | targets = targets.split(','); 328 | targets.forEach(function(target) { 329 | // if channel is secret or private, ignore 330 | var channel = server.channels.find(target); 331 | if (channel && (channel.isPublic || channel.isMember(user))) { 332 | user.send(server.host, irc.reply.nameReply, user.nick, channel.type, channel.name, ':' + channel.names); 333 | } 334 | }); 335 | } 336 | user.send(this.server.host, irc.reply.endNames, user.nick, '*', ':End of /NAMES list.'); 337 | }, 338 | 339 | WHO: function(user, target) { 340 | var server = this.server; 341 | 342 | if (this.server.channelTarget(target)) { 343 | // TODO: Channel wildcards 344 | var channel = this.server.channels.find(target); 345 | 346 | if (!channel) { 347 | user.send(this.server.host, irc.errors.noSuchChannel, user.nick, ':No such channel'); 348 | } else { 349 | channel.users.forEach(function(channelUser) { 350 | if (channelUser.isInvisible 351 | && !user.isOper 352 | && channel.users.indexOf(user) === -1) { 353 | return; 354 | } else { 355 | user.send(server.host, 356 | irc.reply.who, 357 | user.nick, 358 | channel.name, 359 | channelUser.username, 360 | channelUser.hostname, 361 | server.config.hostname, // The IRC server rather than the network 362 | channelUser.channelNick(channel), 363 | 'H', // TODO: H is here, G is gone, * is IRC operator, + is voice, @ is chanop 364 | ':0', 365 | channelUser.realname); 366 | } 367 | }); 368 | user.send(this.server.host, irc.reply.endWho, user.nick, channel.name, ':End of /WHO list.'); 369 | } 370 | } else { 371 | var matcher = this.server.normalizeName(target).replace(/\?/g, '.'); 372 | this.server.users.registered.forEach(function(targetUser) { 373 | try { 374 | if (!targetUser.nick.match('^' + matcher + '$')) return; 375 | } catch (e) { 376 | return; 377 | } 378 | 379 | var sharedChannel = targetUser.sharedChannelWith(user); 380 | if (targetUser.isInvisible 381 | && !user.isOper 382 | && !sharedChannel) { 383 | return; 384 | } else { 385 | user.send(server.host, 386 | irc.reply.who, 387 | user.nick, 388 | sharedChannel ? sharedChannel.name : '', 389 | targetUser.username, 390 | targetUser.hostname, 391 | server.config.hostname, 392 | targetUser.channelNick(channel), 393 | 'H', // TODO 394 | ':0', 395 | targetUser.realname); 396 | } 397 | }); 398 | user.send(this.server.host, irc.reply.endWho, user.nick, target, ':End of /WHO list.'); 399 | } 400 | }, 401 | 402 | WHOIS: function(user, nickmask) { 403 | // TODO: nick masks 404 | var target = this.server.users.find(nickmask); 405 | if (target) { 406 | var channels = target.channels.map(function(channel) { 407 | if (channel.isSecret && !channel.isMember(user)) return; 408 | 409 | if (target.isOp(channel)) { 410 | return '@' + channel.name; 411 | } else { 412 | return channel.name; 413 | } 414 | }); 415 | 416 | user.send(this.server.host, irc.reply.whoIsUser, user.nick, target.nick, 417 | target.username, target.hostname, '*', ':' + target.realname); 418 | user.send(this.server.host, irc.reply.whoIsChannels, user.nick, target.nick, ':' + channels); 419 | user.send(this.server.host, irc.reply.whoIsServer, user.nick, target.nick, this.server.config.hostname, ':' + this.server.config.serverDescription); 420 | if (target.isAway) { 421 | user.send(this.server.host, irc.reply.away, user.nick, target.nick, ':' + target.awayMessage); 422 | } 423 | user.send(this.server.host, irc.reply.whoIsIdle, user.nick, target.nick, target.idle, user.created, ':seconds idle, sign on time'); 424 | user.send(this.server.host, irc.reply.endOfWhoIs, user.nick, target.nick, ':End of /WHOIS list.'); 425 | } else if (!nickmask || nickmask.length === 0) { 426 | user.send(this.server.host, irc.errors.noNickGiven, user.nick, ':No nick given'); 427 | } else { 428 | user.send(this.server.host, irc.errors.noSuchNick, user.nick, nickmask, ':No such nick/channel'); 429 | } 430 | }, 431 | 432 | WHOWAS: function(user, nicknames, count, serverName) { 433 | // TODO: Server 434 | var server = this.server, 435 | found = false; 436 | nicknames.split(',').forEach(function(nick) { 437 | var matches = server.history.find(nick); 438 | if (count) matches = matches.slice(0, count); 439 | matches.forEach(function(item) { 440 | found = true; 441 | user.send(server.host, irc.reply.whoWasUser, user.nick, item.nick, item.username, item.host, '*', ':' + item.realname); 442 | user.send(server.host, irc.reply.whoIsServer, user.nick, item.nick, item.server, ':' + item.time); 443 | }); 444 | }); 445 | 446 | if (found) { 447 | user.send(this.server.host, irc.reply.endWhoWas, user.nick, nicknames, ':End of WHOWAS'); 448 | } else { 449 | user.send(this.server.host, irc.errors.wasNoSuchNick, user.nick, nicknames, ':There was no such nickname'); 450 | } 451 | }, 452 | 453 | WALLOPS: function(user, text) { 454 | if (!text || text.length === 0) { 455 | user.send(this.server.host, irc.errors.needMoreParams, user.nick, ':Need more parameters'); 456 | return; 457 | } 458 | 459 | this.server.users.registered.forEach(function(user) { 460 | if (user.modes.indexOf('w') !== -1) { 461 | user.send(this.server.host, 'WALLOPS', ':OPERWALL - ' + text); 462 | } 463 | }); 464 | }, 465 | 466 | // TODO: Local ops 467 | OPER: function(user, name, password) { 468 | if (!name || !password) { 469 | user.send(this.server.host, irc.errors.wasNoSuchNick, user.nick, ':OPER requires a nick and password'); 470 | } else { 471 | var userConfig, 472 | self = this.server, 473 | targetUser = self.config.opers[name]; 474 | 475 | if (targetUser === undefined) { 476 | user.send(self.host, irc.errors.noSuchNick, user.nick, ':No such nick.'); 477 | } else { 478 | ircd.compareHash(password, targetUser.password, function(err, res) { 479 | if (res) { 480 | user.send(self.host, irc.reply.youAreOper, user.nick, ':You are now an IRC operator'); 481 | user.oper(); 482 | } else { 483 | user.send(self.host, irc.errors.passwordWrong, user.nick || 'user', ':Password incorrect'); 484 | } 485 | }); 486 | } 487 | } 488 | }, 489 | 490 | QUIT: function(user, message) { 491 | user.quit(message); 492 | this.server.history.add(user); 493 | delete user; 494 | }, 495 | 496 | MOTD: function(user) { 497 | this.server.motd(user); 498 | } 499 | }; 500 | 501 | module.exports = Commands; 502 | -------------------------------------------------------------------------------- /doc/rfc2810.txt: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Network Working Group C. Kalt 8 | Request for Comments: 2810 April 2000 9 | Updates: 1459 10 | Category: Informational 11 | 12 | 13 | Internet Relay Chat: Architecture 14 | 15 | Status of this Memo 16 | 17 | This memo provides information for the Internet community. It does 18 | not specify an Internet standard of any kind. Distribution of this 19 | memo is unlimited. 20 | 21 | Copyright Notice 22 | 23 | Copyright (C) The Internet Society (2000). All Rights Reserved. 24 | 25 | Abstract 26 | 27 | The IRC (Internet Relay Chat) protocol is for use with text based 28 | conferencing. It has been developed since 1989 when it was originally 29 | implemented as a mean for users on a BBS to chat amongst themselves. 30 | 31 | First formally documented in May 1993 by RFC 1459 [IRC], the protocol 32 | has kept evolving. This document is an update describing the 33 | architecture of the current IRC protocol and the role of its 34 | different components. Other documents describe in detail the 35 | protocol used between the various components defined here. 36 | 37 | Table of Contents 38 | 39 | 1. Introduction ............................................... 2 40 | 2. Components ................................................. 2 41 | 2.1 Servers ................................................ 2 42 | 2.2 Clients ................................................ 3 43 | 2.2.1 User Clients ...................................... 3 44 | 2.2.2 Service Clients ................................... 3 45 | 3. Architecture ............................................... 3 46 | 4. IRC Protocol Services ...................................... 4 47 | 4.1 Client Locator ......................................... 4 48 | 4.2 Message Relaying ....................................... 4 49 | 4.3 Channel Hosting And Management ......................... 4 50 | 5. IRC Concepts ............................................... 4 51 | 5.1 One-To-One Communication ............................... 5 52 | 5.2 One-To-Many ............................................ 5 53 | 5.2.1 To A Channel ...................................... 5 54 | 5.2.2 To A Host/Server Mask ............................. 6 55 | 56 | 57 | 58 | Kalt Informational [Page 1] 59 | 60 | RFC 2810 Internet Relay Chat: Architecture April 2000 61 | 62 | 63 | 5.2.3 To A List ......................................... 6 64 | 5.3 One-To-All ............................................. 6 65 | 5.3.1 Client-to-Client .................................. 6 66 | 5.3.2 Client-to-Server .................................. 7 67 | 5.3.3 Server-to-Server .................................. 7 68 | 6. Current Problems ........................................... 7 69 | 6.1 Scalability ............................................ 7 70 | 6.2 Reliability ............................................ 7 71 | 6.3 Network Congestion ..................................... 7 72 | 6.4 Privacy ................................................ 8 73 | 7. Security Considerations .................................... 8 74 | 8. Current Support And Availability ........................... 8 75 | 9. Acknowledgements ........................................... 8 76 | 10. References ................................................ 8 77 | 11. Author's Address .......................................... 9 78 | 12. Full Copyright Statement .................................. 10 79 | 80 | 1. Introduction 81 | 82 | The IRC (Internet Relay Chat) protocol has been designed over a 83 | number of years for use with text based conferencing. This document 84 | describes its current architecture. 85 | 86 | The IRC Protocol is based on the client-server model, and is well 87 | suited to running on many machines in a distributed fashion. A 88 | typical setup involves a single process (the server) forming a 89 | central point for clients (or other servers) to connect to, 90 | performing the required message delivery/multiplexing and other 91 | functions. 92 | 93 | This distributed model, which requires each server to have a copy 94 | of the global state information, is still the most flagrant problem 95 | of the protocol as it is a serious handicap, which limits the maximum 96 | size a network can reach. If the existing networks have been able to 97 | keep growing at an incredible pace, we must thank hardware 98 | manufacturers for giving us ever more powerful systems. 99 | 100 | 2. Components 101 | 102 | The following paragraphs define the basic components of the IRC 103 | protocol. 104 | 105 | 2.1 Servers 106 | 107 | The server forms the backbone of IRC as it is the only component 108 | of the protocol which is able to link all the other components 109 | together: it provides a point to which clients may connect to talk to 110 | 111 | 112 | 113 | 114 | Kalt Informational [Page 2] 115 | 116 | RFC 2810 Internet Relay Chat: Architecture April 2000 117 | 118 | 119 | each other [IRC-CLIENT], and a point for other servers to connect to 120 | [IRC-SERVER]. The server is also responsible for providing the basic 121 | services defined by the IRC protocol. 122 | 123 | 2.2 Clients 124 | 125 | A client is anything connecting to a server that is not another 126 | server. There are two types of clients which both serve a different 127 | purpose. 128 | 129 | 2.2.1 User Clients 130 | 131 | User clients are generally programs providing a text based 132 | interface that is used to communicate interactively via IRC. This 133 | particular type of clients is often referred as "users". 134 | 135 | 2.2.2 Service Clients 136 | 137 | Unlike users, service clients are not intended to be used manually 138 | nor for talking. They have a more limited access to the chat 139 | functions of the protocol, while optionally having access to more 140 | private data from the servers. 141 | 142 | Services are typically automatons used to provide some kind of 143 | service (not necessarily related to IRC itself) to users. An example 144 | is a service collecting statistics about the origin of users 145 | connected on the IRC network. 146 | 147 | 3. Architecture 148 | 149 | An IRC network is defined by a group of servers connected to each 150 | other. A single server forms the simplest IRC network. 151 | 152 | The only network configuration allowed for IRC servers is that of 153 | a spanning tree where each server acts as a central node for the rest 154 | of the network it sees. 155 | 156 | 1--\ 157 | A D---4 158 | 2--/ \ / 159 | B----C 160 | / \ 161 | 3 E 162 | 163 | Servers: A, B, C, D, E Clients: 1, 2, 3, 4 164 | 165 | [ Fig. 1. Sample small IRC network ] 166 | 167 | 168 | 169 | 170 | Kalt Informational [Page 3] 171 | 172 | RFC 2810 Internet Relay Chat: Architecture April 2000 173 | 174 | 175 | The IRC protocol provides no mean for two clients to directly 176 | communicate. All communication between clients is relayed by the 177 | server(s). 178 | 179 | 4. IRC Protocol Services 180 | 181 | This section describes the services offered by the IRC protocol. The 182 | combination of these services allow real-time conferencing. 183 | 184 | 4.1 Client Locator 185 | 186 | To be able to exchange messages, two clients must be able to locate 187 | each other. 188 | 189 | Upon connecting to a server, a client registers using a label which 190 | is then used by other servers and clients to know where the client is 191 | located. Servers are responsible for keeping track of all the labels 192 | being used. 193 | 194 | 4.2 Message Relaying 195 | 196 | The IRC protocol provides no mean for two clients to directly 197 | communicate. All communication between clients is relayed by the 198 | server(s). 199 | 200 | 4.3 Channel Hosting And Management 201 | 202 | A channel is a named group of one or more users which will all 203 | receive messages addressed to that channel. A channel is 204 | characterized by its name and current members, it also has a set of 205 | properties which can be manipulated by (some of) its members. 206 | 207 | Channels provide a mean for a message to be sent to several clients. 208 | Servers host channels, providing the necessary message multiplexing. 209 | Servers are also responsible for managing channels by keeping track 210 | of the channel members. The exact role of servers is defined in 211 | "Internet Relay Chat: Channel Management" [IRC-CHAN]. 212 | 213 | 5. IRC Concepts 214 | 215 | This section is devoted to describing the actual concepts behind the 216 | organization of the IRC protocol and how different classes of 217 | messages are delivered. 218 | 219 | 220 | 221 | 222 | 223 | 224 | 225 | 226 | Kalt Informational [Page 4] 227 | 228 | RFC 2810 Internet Relay Chat: Architecture April 2000 229 | 230 | 231 | 5.1 One-To-One Communication 232 | 233 | Communication on a one-to-one basis is usually performed by clients, 234 | since most server-server traffic is not a result of servers talking 235 | only to each other. To provide a means for clients to talk to each 236 | other, it is REQUIRED that all servers be able to send a message in 237 | exactly one direction along the spanning tree in order to reach any 238 | client. Thus the path of a message being delivered is the shortest 239 | path between any two points on the spanning tree. 240 | 241 | The following examples all refer to Figure 1 above. 242 | 243 | Example 1: A message between clients 1 and 2 is only seen by server 244 | A, which sends it straight to client 2. 245 | 246 | Example 2: A message between clients 1 and 3 is seen by servers A & 247 | B, and client 3. No other clients or servers are allowed see the 248 | message. 249 | 250 | Example 3: A message between clients 2 and 4 is seen by servers A, B, 251 | C & D and client 4 only. 252 | 253 | 5.2 One-To-Many 254 | 255 | The main goal of IRC is to provide a forum which allows easy and 256 | efficient conferencing (one to many conversations). IRC offers 257 | several means to achieve this, each serving its own purpose. 258 | 259 | 5.2.1 To A Channel 260 | 261 | In IRC the channel has a role equivalent to that of the multicast 262 | group; their existence is dynamic and the actual conversation carried 263 | out on a channel MUST only be sent to servers which are supporting 264 | users on a given channel. Moreover, the message SHALL only be sent 265 | once to every local link as each server is responsible to fan the 266 | original message to ensure that it will reach all the recipients. 267 | 268 | The following examples all refer to Figure 2. 269 | 270 | Example 4: Any channel with 1 client in it. Messages to the channel 271 | go to the server and then nowhere else. 272 | 273 | Example 5: 2 clients in a channel. All messages traverse a path as if 274 | they were private messages between the two clients outside a 275 | channel. 276 | 277 | 278 | 279 | 280 | 281 | 282 | Kalt Informational [Page 5] 283 | 284 | RFC 2810 Internet Relay Chat: Architecture April 2000 285 | 286 | 287 | Example 6: Clients 1, 2 and 3 in a channel. All messages to the 288 | channel are sent to all clients and only those servers which must 289 | be traversed by the message if it were a private message to a 290 | single client. If client 1 sends a message, it goes back to 291 | client 2 and then via server B to client 3. 292 | 293 | 5.2.2 To A Host/Server Mask 294 | 295 | To provide with some mechanism to send messages to a large body of 296 | related users, host and server mask messages are available. These 297 | messages are sent to users whose host or server information match 298 | that of the mask. The messages are only sent to locations where 299 | users are, in a fashion similar to that of channels. 300 | 301 | 5.2.3 To A List 302 | 303 | The least efficient style of one-to-many conversation is through 304 | clients talking to a 'list' of targets (client, channel, mask). How 305 | this is done is almost self explanatory: the client gives a list of 306 | destinations to which the message is to be delivered and the server 307 | breaks it up and dispatches a separate copy of the message to each 308 | given destination. 309 | 310 | This is not as efficient as using a channel since the destination 311 | list MAY be broken up and the dispatch sent without checking to make 312 | sure duplicates aren't sent down each path. 313 | 314 | 5.3 One-To-All 315 | 316 | The one-to-all type of message is better described as a broadcast 317 | message, sent to all clients or servers or both. On a large network 318 | of users and servers, a single message can result in a lot of traffic 319 | being sent over the network in an effort to reach all of the desired 320 | destinations. 321 | 322 | For some class of messages, there is no option but to broadcast it to 323 | all servers so that the state information held by each server is 324 | consistent between servers. 325 | 326 | 5.3.1 Client-to-Client 327 | 328 | There is no class of message which, from a single message, results in 329 | a message being sent to every other client. 330 | 331 | 332 | 333 | 334 | 335 | 336 | 337 | 338 | Kalt Informational [Page 6] 339 | 340 | RFC 2810 Internet Relay Chat: Architecture April 2000 341 | 342 | 343 | 5.3.2 Client-to-Server 344 | 345 | Most of the commands which result in a change of state information 346 | (such as channel membership, channel mode, user status, etc.) MUST be 347 | sent to all servers by default, and this distribution SHALL NOT be 348 | changed by the client. 349 | 350 | 5.3.3 Server-to-Server 351 | 352 | While most messages between servers are distributed to all 'other' 353 | servers, this is only required for any message that affects a user, 354 | channel or server. Since these are the basic items found in IRC, 355 | nearly all messages originating from a server are broadcast to all 356 | other connected servers. 357 | 358 | 6. Current Problems 359 | 360 | There are a number of recognized problems with this protocol, this 361 | section only addresses the problems related to the architecture of 362 | the protocol. 363 | 364 | 6.1 Scalability 365 | 366 | It is widely recognized that this protocol does not scale 367 | sufficiently well when used in a large arena. The main problem comes 368 | from the requirement that all servers know about all other servers, 369 | clients and channels and that information regarding them be updated 370 | as soon as it changes. 371 | 372 | 6.2 Reliability 373 | 374 | As the only network configuration allowed for IRC servers is that of 375 | a spanning tree, each link between two servers is an obvious and 376 | quite serious point of failure. This particular issue is addressed 377 | more in detail in "Internet Relay Chat: Server Protocol" [IRC- 378 | SERVER]. 379 | 380 | 6.3 Network Congestion 381 | 382 | Another problem related to the scalability and reliability issues, as 383 | well as the spanning tree architecture, is that the protocol and 384 | architecture for IRC are extremely vulnerable to network congestions. 385 | This problem is endemic, and should be solved for the next 386 | generation: if congestion and high traffic volume cause a link 387 | between two servers to fail, not only this failure generates more 388 | network traffic, but the reconnection (eventually elsewhere) of two 389 | servers also generates more traffic. 390 | 391 | 392 | 393 | 394 | Kalt Informational [Page 7] 395 | 396 | RFC 2810 Internet Relay Chat: Architecture April 2000 397 | 398 | 399 | In an attempt to minimize the impact of these problems, it is 400 | strongly RECOMMENDED that servers do not automatically try to 401 | reconnect too fast, in order to avoid aggravating the situation. 402 | 403 | 6.4 Privacy 404 | 405 | Besides not scaling well, the fact that servers need to know all 406 | information about other entities, the issue of privacy is also a 407 | concern. This is in particular true for channels, as the related 408 | information is quite a lot more revealing than whether a user is 409 | online or not. 410 | 411 | 7. Security Considerations 412 | 413 | Asides from the privacy concerns mentioned in section 6.4 (Privacy), 414 | security is believed to be irrelevant to this document. 415 | 416 | 8. Current Support And Availability 417 | 418 | Mailing lists for IRC related discussion: 419 | General discussion: ircd-users@irc.org 420 | Protocol development: ircd-dev@irc.org 421 | 422 | Software implementations: 423 | ftp://ftp.irc.org/irc/server 424 | ftp://ftp.funet.fi/pub/unix/irc 425 | ftp://coombs.anu.edu.au/pub/irc 426 | 427 | Newsgroup: alt.irc 428 | 429 | 9. Acknowledgements 430 | 431 | Parts of this document were copied from the RFC 1459 [IRC] which 432 | first formally documented the IRC Protocol. It has also benefited 433 | from many rounds of review and comments. In particular, the 434 | following people have made significant contributions to this 435 | document: 436 | 437 | Matthew Green, Michael Neumayer, Volker Paulsen, Kurt Roeckx, Vesa 438 | Ruokonen, Magnus Tjernstrom, Stefan Zehl. 439 | 440 | 441 | 442 | 443 | 444 | 445 | 446 | 447 | 448 | 449 | 450 | Kalt Informational [Page 8] 451 | 452 | RFC 2810 Internet Relay Chat: Architecture April 2000 453 | 454 | 455 | 10. References 456 | 457 | [KEYWORDS] Bradner, S., "Key words for use in RFCs to Indicate 458 | Requirement Levels", BCP 14, RFC 2119, March 1997. 459 | 460 | [IRC] Oikarinen, J. and D. Reed, "Internet Relay Chat 461 | Protocol", RFC 1459, May 1993. 462 | 463 | [IRC-CLIENT] Kalt, C., "Internet Relay Chat: Client Protocol", RFC 464 | 2812, April 2000. 465 | 466 | [IRC-SERVER] Kalt, C., "Internet Relay Chat: Server Protocol", RFC 467 | 2813, April 2000. 468 | 469 | [IRC-CHAN] Kalt, C., "Internet Relay Chat: Channel Management", RFC 470 | 2811, April 2000. 471 | 472 | 11. Author's Address 473 | 474 | Christophe Kalt 475 | 99 Teaneck Rd, Apt #117 476 | Ridgefield Park, NJ 07660 477 | USA 478 | 479 | EMail: kalt@stealth.net 480 | 481 | 482 | 483 | 484 | 485 | 486 | 487 | 488 | 489 | 490 | 491 | 492 | 493 | 494 | 495 | 496 | 497 | 498 | 499 | 500 | 501 | 502 | 503 | 504 | 505 | 506 | Kalt Informational [Page 9] 507 | 508 | RFC 2810 Internet Relay Chat: Architecture April 2000 509 | 510 | 511 | 12. Full Copyright Statement 512 | 513 | Copyright (C) The Internet Society (2000). All Rights Reserved. 514 | 515 | This document and translations of it may be copied and furnished to 516 | others, and derivative works that comment on or otherwise explain it 517 | or assist in its implementation may be prepared, copied, published 518 | and distributed, in whole or in part, without restriction of any 519 | kind, provided that the above copyright notice and this paragraph are 520 | included on all such copies and derivative works. However, this 521 | document itself may not be modified in any way, such as by removing 522 | the copyright notice or references to the Internet Society or other 523 | Internet organizations, except as needed for the purpose of 524 | developing Internet standards in which case the procedures for 525 | copyrights defined in the Internet Standards process must be 526 | followed, or as required to translate it into languages other than 527 | English. 528 | 529 | The limited permissions granted above are perpetual and will not be 530 | revoked by the Internet Society or its successors or assigns. 531 | 532 | This document and the information contained herein is provided on an 533 | "AS IS" basis and THE INTERNET SOCIETY AND THE INTERNET ENGINEERING 534 | TASK FORCE DISCLAIMS ALL WARRANTIES, EXPRESS OR IMPLIED, INCLUDING 535 | BUT NOT LIMITED TO ANY WARRANTY THAT THE USE OF THE INFORMATION 536 | HEREIN WILL NOT INFRINGE ANY RIGHTS OR ANY IMPLIED WARRANTIES OF 537 | MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. 538 | 539 | Acknowledgement 540 | 541 | Funding for the RFC Editor function is currently provided by the 542 | Internet Society. 543 | 544 | 545 | 546 | 547 | 548 | 549 | 550 | 551 | 552 | 553 | 554 | 555 | 556 | 557 | 558 | 559 | 560 | 561 | 562 | Kalt Informational [Page 10] 563 | 564 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | GNU GENERAL PUBLIC LICENSE 2 | Version 3, 29 June 2007 3 | 4 | Copyright (C) 2007 Free Software Foundation, Inc. 5 | Everyone is permitted to copy and distribute verbatim copies 6 | of this license document, but changing it is not allowed. 7 | 8 | Preamble 9 | 10 | The GNU General Public License is a free, copyleft license for 11 | software and other kinds of works. 12 | 13 | The licenses for most software and other practical works are designed 14 | to take away your freedom to share and change the works. By contrast, 15 | the GNU General Public License is intended to guarantee your freedom to 16 | share and change all versions of a program--to make sure it remains free 17 | software for all its users. We, the Free Software Foundation, use the 18 | GNU General Public License for most of our software; it applies also to 19 | any other work released this way by its authors. You can apply it to 20 | your programs, too. 21 | 22 | When we speak of free software, we are referring to freedom, not 23 | price. Our General Public Licenses are designed to make sure that you 24 | have the freedom to distribute copies of free software (and charge for 25 | them if you wish), that you receive source code or can get it if you 26 | want it, that you can change the software or use pieces of it in new 27 | free programs, and that you know you can do these things. 28 | 29 | To protect your rights, we need to prevent others from denying you 30 | these rights or asking you to surrender the rights. Therefore, you have 31 | certain responsibilities if you distribute copies of the software, or if 32 | you modify it: responsibilities to respect the freedom of others. 33 | 34 | For example, if you distribute copies of such a program, whether 35 | gratis or for a fee, you must pass on to the recipients the same 36 | freedoms that you received. You must make sure that they, too, receive 37 | or can get the source code. And you must show them these terms so they 38 | know their rights. 39 | 40 | Developers that use the GNU GPL protect your rights with two steps: 41 | (1) assert copyright on the software, and (2) offer you this License 42 | giving you legal permission to copy, distribute and/or modify it. 43 | 44 | For the developers' and authors' protection, the GPL clearly explains 45 | that there is no warranty for this free software. For both users' and 46 | authors' sake, the GPL requires that modified versions be marked as 47 | changed, so that their problems will not be attributed erroneously to 48 | authors of previous versions. 49 | 50 | Some devices are designed to deny users access to install or run 51 | modified versions of the software inside them, although the manufacturer 52 | can do so. This is fundamentally incompatible with the aim of 53 | protecting users' freedom to change the software. The systematic 54 | pattern of such abuse occurs in the area of products for individuals to 55 | use, which is precisely where it is most unacceptable. Therefore, we 56 | have designed this version of the GPL to prohibit the practice for those 57 | products. If such problems arise substantially in other domains, we 58 | stand ready to extend this provision to those domains in future versions 59 | of the GPL, as needed to protect the freedom of users. 60 | 61 | Finally, every program is threatened constantly by software patents. 62 | States should not allow patents to restrict development and use of 63 | software on general-purpose computers, but in those that do, we wish to 64 | avoid the special danger that patents applied to a free program could 65 | make it effectively proprietary. To prevent this, the GPL assures that 66 | patents cannot be used to render the program non-free. 67 | 68 | The precise terms and conditions for copying, distribution and 69 | modification follow. 70 | 71 | TERMS AND CONDITIONS 72 | 73 | 0. Definitions. 74 | 75 | "This License" refers to version 3 of the GNU General Public License. 76 | 77 | "Copyright" also means copyright-like laws that apply to other kinds of 78 | works, such as semiconductor masks. 79 | 80 | "The Program" refers to any copyrightable work licensed under this 81 | License. Each licensee is addressed as "you". "Licensees" and 82 | "recipients" may be individuals or organizations. 83 | 84 | To "modify" a work means to copy from or adapt all or part of the work 85 | in a fashion requiring copyright permission, other than the making of an 86 | exact copy. The resulting work is called a "modified version" of the 87 | earlier work or a work "based on" the earlier work. 88 | 89 | A "covered work" means either the unmodified Program or a work based 90 | on the Program. 91 | 92 | To "propagate" a work means to do anything with it that, without 93 | permission, would make you directly or secondarily liable for 94 | infringement under applicable copyright law, except executing it on a 95 | computer or modifying a private copy. Propagation includes copying, 96 | distribution (with or without modification), making available to the 97 | public, and in some countries other activities as well. 98 | 99 | To "convey" a work means any kind of propagation that enables other 100 | parties to make or receive copies. Mere interaction with a user through 101 | a computer network, with no transfer of a copy, is not conveying. 102 | 103 | An interactive user interface displays "Appropriate Legal Notices" 104 | to the extent that it includes a convenient and prominently visible 105 | feature that (1) displays an appropriate copyright notice, and (2) 106 | tells the user that there is no warranty for the work (except to the 107 | extent that warranties are provided), that licensees may convey the 108 | work under this License, and how to view a copy of this License. If 109 | the interface presents a list of user commands or options, such as a 110 | menu, a prominent item in the list meets this criterion. 111 | 112 | 1. Source Code. 113 | 114 | The "source code" for a work means the preferred form of the work 115 | for making modifications to it. "Object code" means any non-source 116 | form of a work. 117 | 118 | A "Standard Interface" means an interface that either is an official 119 | standard defined by a recognized standards body, or, in the case of 120 | interfaces specified for a particular programming language, one that 121 | is widely used among developers working in that language. 122 | 123 | The "System Libraries" of an executable work include anything, other 124 | than the work as a whole, that (a) is included in the normal form of 125 | packaging a Major Component, but which is not part of that Major 126 | Component, and (b) serves only to enable use of the work with that 127 | Major Component, or to implement a Standard Interface for which an 128 | implementation is available to the public in source code form. A 129 | "Major Component", in this context, means a major essential component 130 | (kernel, window system, and so on) of the specific operating system 131 | (if any) on which the executable work runs, or a compiler used to 132 | produce the work, or an object code interpreter used to run it. 133 | 134 | The "Corresponding Source" for a work in object code form means all 135 | the source code needed to generate, install, and (for an executable 136 | work) run the object code and to modify the work, including scripts to 137 | control those activities. However, it does not include the work's 138 | System Libraries, or general-purpose tools or generally available free 139 | programs which are used unmodified in performing those activities but 140 | which are not part of the work. For example, Corresponding Source 141 | includes interface definition files associated with source files for 142 | the work, and the source code for shared libraries and dynamically 143 | linked subprograms that the work is specifically designed to require, 144 | such as by intimate data communication or control flow between those 145 | subprograms and other parts of the work. 146 | 147 | The Corresponding Source need not include anything that users 148 | can regenerate automatically from other parts of the Corresponding 149 | Source. 150 | 151 | The Corresponding Source for a work in source code form is that 152 | same work. 153 | 154 | 2. Basic Permissions. 155 | 156 | All rights granted under this License are granted for the term of 157 | copyright on the Program, and are irrevocable provided the stated 158 | conditions are met. This License explicitly affirms your unlimited 159 | permission to run the unmodified Program. The output from running a 160 | covered work is covered by this License only if the output, given its 161 | content, constitutes a covered work. This License acknowledges your 162 | rights of fair use or other equivalent, as provided by copyright law. 163 | 164 | You may make, run and propagate covered works that you do not 165 | convey, without conditions so long as your license otherwise remains 166 | in force. You may convey covered works to others for the sole purpose 167 | of having them make modifications exclusively for you, or provide you 168 | with facilities for running those works, provided that you comply with 169 | the terms of this License in conveying all material for which you do 170 | not control copyright. Those thus making or running the covered works 171 | for you must do so exclusively on your behalf, under your direction 172 | and control, on terms that prohibit them from making any copies of 173 | your copyrighted material outside their relationship with you. 174 | 175 | Conveying under any other circumstances is permitted solely under 176 | the conditions stated below. Sublicensing is not allowed; section 10 177 | makes it unnecessary. 178 | 179 | 3. Protecting Users' Legal Rights From Anti-Circumvention Law. 180 | 181 | No covered work shall be deemed part of an effective technological 182 | measure under any applicable law fulfilling obligations under article 183 | 11 of the WIPO copyright treaty adopted on 20 December 1996, or 184 | similar laws prohibiting or restricting circumvention of such 185 | measures. 186 | 187 | When you convey a covered work, you waive any legal power to forbid 188 | circumvention of technological measures to the extent such circumvention 189 | is effected by exercising rights under this License with respect to 190 | the covered work, and you disclaim any intention to limit operation or 191 | modification of the work as a means of enforcing, against the work's 192 | users, your or third parties' legal rights to forbid circumvention of 193 | technological measures. 194 | 195 | 4. Conveying Verbatim Copies. 196 | 197 | You may convey verbatim copies of the Program's source code as you 198 | receive it, in any medium, provided that you conspicuously and 199 | appropriately publish on each copy an appropriate copyright notice; 200 | keep intact all notices stating that this License and any 201 | non-permissive terms added in accord with section 7 apply to the code; 202 | keep intact all notices of the absence of any warranty; and give all 203 | recipients a copy of this License along with the Program. 204 | 205 | You may charge any price or no price for each copy that you convey, 206 | and you may offer support or warranty protection for a fee. 207 | 208 | 5. Conveying Modified Source Versions. 209 | 210 | You may convey a work based on the Program, or the modifications to 211 | produce it from the Program, in the form of source code under the 212 | terms of section 4, provided that you also meet all of these conditions: 213 | 214 | a) The work must carry prominent notices stating that you modified 215 | it, and giving a relevant date. 216 | 217 | b) The work must carry prominent notices stating that it is 218 | released under this License and any conditions added under section 219 | 7. This requirement modifies the requirement in section 4 to 220 | "keep intact all notices". 221 | 222 | c) You must license the entire work, as a whole, under this 223 | License to anyone who comes into possession of a copy. This 224 | License will therefore apply, along with any applicable section 7 225 | additional terms, to the whole of the work, and all its parts, 226 | regardless of how they are packaged. This License gives no 227 | permission to license the work in any other way, but it does not 228 | invalidate such permission if you have separately received it. 229 | 230 | d) If the work has interactive user interfaces, each must display 231 | Appropriate Legal Notices; however, if the Program has interactive 232 | interfaces that do not display Appropriate Legal Notices, your 233 | work need not make them do so. 234 | 235 | A compilation of a covered work with other separate and independent 236 | works, which are not by their nature extensions of the covered work, 237 | and which are not combined with it such as to form a larger program, 238 | in or on a volume of a storage or distribution medium, is called an 239 | "aggregate" if the compilation and its resulting copyright are not 240 | used to limit the access or legal rights of the compilation's users 241 | beyond what the individual works permit. Inclusion of a covered work 242 | in an aggregate does not cause this License to apply to the other 243 | parts of the aggregate. 244 | 245 | 6. Conveying Non-Source Forms. 246 | 247 | You may convey a covered work in object code form under the terms 248 | of sections 4 and 5, provided that you also convey the 249 | machine-readable Corresponding Source under the terms of this License, 250 | in one of these ways: 251 | 252 | a) Convey the object code in, or embodied in, a physical product 253 | (including a physical distribution medium), accompanied by the 254 | Corresponding Source fixed on a durable physical medium 255 | customarily used for software interchange. 256 | 257 | b) Convey the object code in, or embodied in, a physical product 258 | (including a physical distribution medium), accompanied by a 259 | written offer, valid for at least three years and valid for as 260 | long as you offer spare parts or customer support for that product 261 | model, to give anyone who possesses the object code either (1) a 262 | copy of the Corresponding Source for all the software in the 263 | product that is covered by this License, on a durable physical 264 | medium customarily used for software interchange, for a price no 265 | more than your reasonable cost of physically performing this 266 | conveying of source, or (2) access to copy the 267 | Corresponding Source from a network server at no charge. 268 | 269 | c) Convey individual copies of the object code with a copy of the 270 | written offer to provide the Corresponding Source. This 271 | alternative is allowed only occasionally and noncommercially, and 272 | only if you received the object code with such an offer, in accord 273 | with subsection 6b. 274 | 275 | d) Convey the object code by offering access from a designated 276 | place (gratis or for a charge), and offer equivalent access to the 277 | Corresponding Source in the same way through the same place at no 278 | further charge. You need not require recipients to copy the 279 | Corresponding Source along with the object code. If the place to 280 | copy the object code is a network server, the Corresponding Source 281 | may be on a different server (operated by you or a third party) 282 | that supports equivalent copying facilities, provided you maintain 283 | clear directions next to the object code saying where to find the 284 | Corresponding Source. Regardless of what server hosts the 285 | Corresponding Source, you remain obligated to ensure that it is 286 | available for as long as needed to satisfy these requirements. 287 | 288 | e) Convey the object code using peer-to-peer transmission, provided 289 | you inform other peers where the object code and Corresponding 290 | Source of the work are being offered to the general public at no 291 | charge under subsection 6d. 292 | 293 | A separable portion of the object code, whose source code is excluded 294 | from the Corresponding Source as a System Library, need not be 295 | included in conveying the object code work. 296 | 297 | A "User Product" is either (1) a "consumer product", which means any 298 | tangible personal property which is normally used for personal, family, 299 | or household purposes, or (2) anything designed or sold for incorporation 300 | into a dwelling. In determining whether a product is a consumer product, 301 | doubtful cases shall be resolved in favor of coverage. For a particular 302 | product received by a particular user, "normally used" refers to a 303 | typical or common use of that class of product, regardless of the status 304 | of the particular user or of the way in which the particular user 305 | actually uses, or expects or is expected to use, the product. A product 306 | is a consumer product regardless of whether the product has substantial 307 | commercial, industrial or non-consumer uses, unless such uses represent 308 | the only significant mode of use of the product. 309 | 310 | "Installation Information" for a User Product means any methods, 311 | procedures, authorization keys, or other information required to install 312 | and execute modified versions of a covered work in that User Product from 313 | a modified version of its Corresponding Source. The information must 314 | suffice to ensure that the continued functioning of the modified object 315 | code is in no case prevented or interfered with solely because 316 | modification has been made. 317 | 318 | If you convey an object code work under this section in, or with, or 319 | specifically for use in, a User Product, and the conveying occurs as 320 | part of a transaction in which the right of possession and use of the 321 | User Product is transferred to the recipient in perpetuity or for a 322 | fixed term (regardless of how the transaction is characterized), the 323 | Corresponding Source conveyed under this section must be accompanied 324 | by the Installation Information. But this requirement does not apply 325 | if neither you nor any third party retains the ability to install 326 | modified object code on the User Product (for example, the work has 327 | been installed in ROM). 328 | 329 | The requirement to provide Installation Information does not include a 330 | requirement to continue to provide support service, warranty, or updates 331 | for a work that has been modified or installed by the recipient, or for 332 | the User Product in which it has been modified or installed. Access to a 333 | network may be denied when the modification itself materially and 334 | adversely affects the operation of the network or violates the rules and 335 | protocols for communication across the network. 336 | 337 | Corresponding Source conveyed, and Installation Information provided, 338 | in accord with this section must be in a format that is publicly 339 | documented (and with an implementation available to the public in 340 | source code form), and must require no special password or key for 341 | unpacking, reading or copying. 342 | 343 | 7. Additional Terms. 344 | 345 | "Additional permissions" are terms that supplement the terms of this 346 | License by making exceptions from one or more of its conditions. 347 | Additional permissions that are applicable to the entire Program shall 348 | be treated as though they were included in this License, to the extent 349 | that they are valid under applicable law. If additional permissions 350 | apply only to part of the Program, that part may be used separately 351 | under those permissions, but the entire Program remains governed by 352 | this License without regard to the additional permissions. 353 | 354 | When you convey a copy of a covered work, you may at your option 355 | remove any additional permissions from that copy, or from any part of 356 | it. (Additional permissions may be written to require their own 357 | removal in certain cases when you modify the work.) You may place 358 | additional permissions on material, added by you to a covered work, 359 | for which you have or can give appropriate copyright permission. 360 | 361 | Notwithstanding any other provision of this License, for material you 362 | add to a covered work, you may (if authorized by the copyright holders of 363 | that material) supplement the terms of this License with terms: 364 | 365 | a) Disclaiming warranty or limiting liability differently from the 366 | terms of sections 15 and 16 of this License; or 367 | 368 | b) Requiring preservation of specified reasonable legal notices or 369 | author attributions in that material or in the Appropriate Legal 370 | Notices displayed by works containing it; or 371 | 372 | c) Prohibiting misrepresentation of the origin of that material, or 373 | requiring that modified versions of such material be marked in 374 | reasonable ways as different from the original version; or 375 | 376 | d) Limiting the use for publicity purposes of names of licensors or 377 | authors of the material; or 378 | 379 | e) Declining to grant rights under trademark law for use of some 380 | trade names, trademarks, or service marks; or 381 | 382 | f) Requiring indemnification of licensors and authors of that 383 | material by anyone who conveys the material (or modified versions of 384 | it) with contractual assumptions of liability to the recipient, for 385 | any liability that these contractual assumptions directly impose on 386 | those licensors and authors. 387 | 388 | All other non-permissive additional terms are considered "further 389 | restrictions" within the meaning of section 10. If the Program as you 390 | received it, or any part of it, contains a notice stating that it is 391 | governed by this License along with a term that is a further 392 | restriction, you may remove that term. If a license document contains 393 | a further restriction but permits relicensing or conveying under this 394 | License, you may add to a covered work material governed by the terms 395 | of that license document, provided that the further restriction does 396 | not survive such relicensing or conveying. 397 | 398 | If you add terms to a covered work in accord with this section, you 399 | must place, in the relevant source files, a statement of the 400 | additional terms that apply to those files, or a notice indicating 401 | where to find the applicable terms. 402 | 403 | Additional terms, permissive or non-permissive, may be stated in the 404 | form of a separately written license, or stated as exceptions; 405 | the above requirements apply either way. 406 | 407 | 8. Termination. 408 | 409 | You may not propagate or modify a covered work except as expressly 410 | provided under this License. Any attempt otherwise to propagate or 411 | modify it is void, and will automatically terminate your rights under 412 | this License (including any patent licenses granted under the third 413 | paragraph of section 11). 414 | 415 | However, if you cease all violation of this License, then your 416 | license from a particular copyright holder is reinstated (a) 417 | provisionally, unless and until the copyright holder explicitly and 418 | finally terminates your license, and (b) permanently, if the copyright 419 | holder fails to notify you of the violation by some reasonable means 420 | prior to 60 days after the cessation. 421 | 422 | Moreover, your license from a particular copyright holder is 423 | reinstated permanently if the copyright holder notifies you of the 424 | violation by some reasonable means, this is the first time you have 425 | received notice of violation of this License (for any work) from that 426 | copyright holder, and you cure the violation prior to 30 days after 427 | your receipt of the notice. 428 | 429 | Termination of your rights under this section does not terminate the 430 | licenses of parties who have received copies or rights from you under 431 | this License. If your rights have been terminated and not permanently 432 | reinstated, you do not qualify to receive new licenses for the same 433 | material under section 10. 434 | 435 | 9. Acceptance Not Required for Having Copies. 436 | 437 | You are not required to accept this License in order to receive or 438 | run a copy of the Program. Ancillary propagation of a covered work 439 | occurring solely as a consequence of using peer-to-peer transmission 440 | to receive a copy likewise does not require acceptance. However, 441 | nothing other than this License grants you permission to propagate or 442 | modify any covered work. These actions infringe copyright if you do 443 | not accept this License. Therefore, by modifying or propagating a 444 | covered work, you indicate your acceptance of this License to do so. 445 | 446 | 10. Automatic Licensing of Downstream Recipients. 447 | 448 | Each time you convey a covered work, the recipient automatically 449 | receives a license from the original licensors, to run, modify and 450 | propagate that work, subject to this License. You are not responsible 451 | for enforcing compliance by third parties with this License. 452 | 453 | An "entity transaction" is a transaction transferring control of an 454 | organization, or substantially all assets of one, or subdividing an 455 | organization, or merging organizations. If propagation of a covered 456 | work results from an entity transaction, each party to that 457 | transaction who receives a copy of the work also receives whatever 458 | licenses to the work the party's predecessor in interest had or could 459 | give under the previous paragraph, plus a right to possession of the 460 | Corresponding Source of the work from the predecessor in interest, if 461 | the predecessor has it or can get it with reasonable efforts. 462 | 463 | You may not impose any further restrictions on the exercise of the 464 | rights granted or affirmed under this License. For example, you may 465 | not impose a license fee, royalty, or other charge for exercise of 466 | rights granted under this License, and you may not initiate litigation 467 | (including a cross-claim or counterclaim in a lawsuit) alleging that 468 | any patent claim is infringed by making, using, selling, offering for 469 | sale, or importing the Program or any portion of it. 470 | 471 | 11. Patents. 472 | 473 | A "contributor" is a copyright holder who authorizes use under this 474 | License of the Program or a work on which the Program is based. The 475 | work thus licensed is called the contributor's "contributor version". 476 | 477 | A contributor's "essential patent claims" are all patent claims 478 | owned or controlled by the contributor, whether already acquired or 479 | hereafter acquired, that would be infringed by some manner, permitted 480 | by this License, of making, using, or selling its contributor version, 481 | but do not include claims that would be infringed only as a 482 | consequence of further modification of the contributor version. For 483 | purposes of this definition, "control" includes the right to grant 484 | patent sublicenses in a manner consistent with the requirements of 485 | this License. 486 | 487 | Each contributor grants you a non-exclusive, worldwide, royalty-free 488 | patent license under the contributor's essential patent claims, to 489 | make, use, sell, offer for sale, import and otherwise run, modify and 490 | propagate the contents of its contributor version. 491 | 492 | In the following three paragraphs, a "patent license" is any express 493 | agreement or commitment, however denominated, not to enforce a patent 494 | (such as an express permission to practice a patent or covenant not to 495 | sue for patent infringement). To "grant" such a patent license to a 496 | party means to make such an agreement or commitment not to enforce a 497 | patent against the party. 498 | 499 | If you convey a covered work, knowingly relying on a patent license, 500 | and the Corresponding Source of the work is not available for anyone 501 | to copy, free of charge and under the terms of this License, through a 502 | publicly available network server or other readily accessible means, 503 | then you must either (1) cause the Corresponding Source to be so 504 | available, or (2) arrange to deprive yourself of the benefit of the 505 | patent license for this particular work, or (3) arrange, in a manner 506 | consistent with the requirements of this License, to extend the patent 507 | license to downstream recipients. "Knowingly relying" means you have 508 | actual knowledge that, but for the patent license, your conveying the 509 | covered work in a country, or your recipient's use of the covered work 510 | in a country, would infringe one or more identifiable patents in that 511 | country that you have reason to believe are valid. 512 | 513 | If, pursuant to or in connection with a single transaction or 514 | arrangement, you convey, or propagate by procuring conveyance of, a 515 | covered work, and grant a patent license to some of the parties 516 | receiving the covered work authorizing them to use, propagate, modify 517 | or convey a specific copy of the covered work, then the patent license 518 | you grant is automatically extended to all recipients of the covered 519 | work and works based on it. 520 | 521 | A patent license is "discriminatory" if it does not include within 522 | the scope of its coverage, prohibits the exercise of, or is 523 | conditioned on the non-exercise of one or more of the rights that are 524 | specifically granted under this License. You may not convey a covered 525 | work if you are a party to an arrangement with a third party that is 526 | in the business of distributing software, under which you make payment 527 | to the third party based on the extent of your activity of conveying 528 | the work, and under which the third party grants, to any of the 529 | parties who would receive the covered work from you, a discriminatory 530 | patent license (a) in connection with copies of the covered work 531 | conveyed by you (or copies made from those copies), or (b) primarily 532 | for and in connection with specific products or compilations that 533 | contain the covered work, unless you entered into that arrangement, 534 | or that patent license was granted, prior to 28 March 2007. 535 | 536 | Nothing in this License shall be construed as excluding or limiting 537 | any implied license or other defenses to infringement that may 538 | otherwise be available to you under applicable patent law. 539 | 540 | 12. No Surrender of Others' Freedom. 541 | 542 | If conditions are imposed on you (whether by court order, agreement or 543 | otherwise) that contradict the conditions of this License, they do not 544 | excuse you from the conditions of this License. If you cannot convey a 545 | covered work so as to satisfy simultaneously your obligations under this 546 | License and any other pertinent obligations, then as a consequence you may 547 | not convey it at all. For example, if you agree to terms that obligate you 548 | to collect a royalty for further conveying from those to whom you convey 549 | the Program, the only way you could satisfy both those terms and this 550 | License would be to refrain entirely from conveying the Program. 551 | 552 | 13. Use with the GNU Affero General Public License. 553 | 554 | Notwithstanding any other provision of this License, you have 555 | permission to link or combine any covered work with a work licensed 556 | under version 3 of the GNU Affero General Public License into a single 557 | combined work, and to convey the resulting work. The terms of this 558 | License will continue to apply to the part which is the covered work, 559 | but the special requirements of the GNU Affero General Public License, 560 | section 13, concerning interaction through a network will apply to the 561 | combination as such. 562 | 563 | 14. Revised Versions of this License. 564 | 565 | The Free Software Foundation may publish revised and/or new versions of 566 | the GNU General Public License from time to time. Such new versions will 567 | be similar in spirit to the present version, but may differ in detail to 568 | address new problems or concerns. 569 | 570 | Each version is given a distinguishing version number. If the 571 | Program specifies that a certain numbered version of the GNU General 572 | Public License "or any later version" applies to it, you have the 573 | option of following the terms and conditions either of that numbered 574 | version or of any later version published by the Free Software 575 | Foundation. If the Program does not specify a version number of the 576 | GNU General Public License, you may choose any version ever published 577 | by the Free Software Foundation. 578 | 579 | If the Program specifies that a proxy can decide which future 580 | versions of the GNU General Public License can be used, that proxy's 581 | public statement of acceptance of a version permanently authorizes you 582 | to choose that version for the Program. 583 | 584 | Later license versions may give you additional or different 585 | permissions. However, no additional obligations are imposed on any 586 | author or copyright holder as a result of your choosing to follow a 587 | later version. 588 | 589 | 15. Disclaimer of Warranty. 590 | 591 | THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY 592 | APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT 593 | HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY 594 | OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, 595 | THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 596 | PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM 597 | IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF 598 | ALL NECESSARY SERVICING, REPAIR OR CORRECTION. 599 | 600 | 16. Limitation of Liability. 601 | 602 | IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING 603 | WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS 604 | THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY 605 | GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE 606 | USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF 607 | DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD 608 | PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), 609 | EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF 610 | SUCH DAMAGES. 611 | 612 | 17. Interpretation of Sections 15 and 16. 613 | 614 | If the disclaimer of warranty and limitation of liability provided 615 | above cannot be given local legal effect according to their terms, 616 | reviewing courts shall apply local law that most closely approximates 617 | an absolute waiver of all civil liability in connection with the 618 | Program, unless a warranty or assumption of liability accompanies a 619 | copy of the Program in return for a fee. 620 | 621 | END OF TERMS AND CONDITIONS 622 | 623 | How to Apply These Terms to Your New Programs 624 | 625 | If you develop a new program, and you want it to be of the greatest 626 | possible use to the public, the best way to achieve this is to make it 627 | free software which everyone can redistribute and change under these terms. 628 | 629 | To do so, attach the following notices to the program. It is safest 630 | to attach them to the start of each source file to most effectively 631 | state the exclusion of warranty; and each file should have at least 632 | the "copyright" line and a pointer to where the full notice is found. 633 | 634 | 635 | Copyright (C) 636 | 637 | This program is free software: you can redistribute it and/or modify 638 | it under the terms of the GNU General Public License as published by 639 | the Free Software Foundation, either version 3 of the License, or 640 | (at your option) any later version. 641 | 642 | This program is distributed in the hope that it will be useful, 643 | but WITHOUT ANY WARRANTY; without even the implied warranty of 644 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 645 | GNU General Public License for more details. 646 | 647 | You should have received a copy of the GNU General Public License 648 | along with this program. If not, see . 649 | 650 | Also add information on how to contact you by electronic and paper mail. 651 | 652 | If the program does terminal interaction, make it output a short 653 | notice like this when it starts in an interactive mode: 654 | 655 | Copyright (C) 656 | This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. 657 | This is free software, and you are welcome to redistribute it 658 | under certain conditions; type `show c' for details. 659 | 660 | The hypothetical commands `show w' and `show c' should show the appropriate 661 | parts of the General Public License. Of course, your program's commands 662 | might be different; for a GUI interface, you would use an "about box". 663 | 664 | You should also get your employer (if you work as a programmer) or school, 665 | if any, to sign a "copyright disclaimer" for the program, if necessary. 666 | For more information on this, and how to apply and follow the GNU GPL, see 667 | . 668 | 669 | The GNU General Public License does not permit incorporating your program 670 | into proprietary programs. If your program is a subroutine library, you 671 | may consider it more useful to permit linking proprietary applications with 672 | the library. If this is what you want to do, use the GNU Lesser General 673 | Public License instead of this License. But first, please read 674 | . 675 | -------------------------------------------------------------------------------- /doc/rfc2811.txt: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Network Working Group C. Kalt 8 | Request for Comments: 2811 April 2000 9 | Updates: 1459 10 | Category: Informational 11 | 12 | 13 | Internet Relay Chat: Channel Management 14 | 15 | Status of this Memo 16 | 17 | This memo provides information for the Internet community. It does 18 | not specify an Internet standard of any kind. Distribution of this 19 | memo is unlimited. 20 | 21 | Copyright Notice 22 | 23 | Copyright (C) The Internet Society (2000). All Rights Reserved. 24 | 25 | Abstract 26 | 27 | One of the most notable characteristics of the IRC (Internet Relay 28 | Chat) protocol is to allow for users to be grouped in forums, called 29 | channels, providing a mean for multiple users to communicate 30 | together. 31 | 32 | There was originally a unique type of channels, but with the years, 33 | new types appeared either as a response to a need, or for 34 | experimental purposes. 35 | 36 | This document specifies how channels, their characteristics and 37 | properties are managed by IRC servers. 38 | 39 | Table of Contents 40 | 41 | 1. Introduction ............................................... 2 42 | 2. Channel Characteristics .................................... 3 43 | 2.1 Namespace .............................................. 3 44 | 2.2 Channel Scope .......................................... 3 45 | 2.3 Channel Properties ..................................... 4 46 | 2.4 Privileged Channel Members ............................. 4 47 | 2.4.1 Channel Operators ................................. 5 48 | 2.4.2 Channel Creator ................................... 5 49 | 3. Channel lifetime ........................................... 5 50 | 3.1 Standard channels ...................................... 5 51 | 3.2 Safe Channels .......................................... 6 52 | 4. Channel Modes .............................................. 7 53 | 4.1 Member Status .......................................... 7 54 | 4.1.1 "Channel Creator" Status .......................... 7 55 | 56 | 57 | 58 | Kalt Informational [Page 1] 59 | 60 | RFC 2811 Internet Relay Chat: Channel Management April 2000 61 | 62 | 63 | 4.1.2 Channel Operator Status ........................... 8 64 | 4.1.3 Voice Privilege ................................... 8 65 | 4.2 Channel Flags .......................................... 8 66 | 4.2.1 Anonymous Flag .................................... 8 67 | 4.2.2 Invite Only Flag .................................. 8 68 | 4.2.3 Moderated Channel Flag ............................ 9 69 | 4.2.4 No Messages To Channel From Clients On The Outside 9 70 | 4.2.5 Quiet Channel ..................................... 9 71 | 4.2.6 Private and Secret Channels ....................... 9 72 | 4.2.7 Server Reop Flag .................................. 10 73 | 4.2.8 Topic ............................................. 10 74 | 4.2.9 User Limit ........................................ 10 75 | 4.2.10 Channel Key ...................................... 10 76 | 4.3 Channel Access Control ................................. 10 77 | 4.3.1 Channel Ban and Exception ......................... 11 78 | 4.3.2 Channel Invitation ................................ 11 79 | 5. Current Implementations .................................... 11 80 | 5.1 Tracking Recently Used Channels ........................ 11 81 | 5.2 Safe Channels .......................................... 12 82 | 5.2.1 Channel Identifier ................................ 12 83 | 5.2.2 Channel Delay ..................................... 12 84 | 5.2.3 Abuse Window ...................................... 13 85 | 5.2.4 Preserving Sanity In The Name Space ............... 13 86 | 5.2.5 Server Reop Mechanism ............................. 13 87 | 6. Current problems ........................................... 14 88 | 6.1 Labels ................................................. 14 89 | 6.1.1 Channel Delay ..................................... 14 90 | 6.1.2 Safe Channels ..................................... 15 91 | 6.2 Mode Propagation Delays ................................ 15 92 | 6.3 Collisions And Channel Modes ........................... 15 93 | 6.4 Resource Exhaustion .................................... 16 94 | 7. Security Considerations .................................... 16 95 | 7.1 Access Control ......................................... 16 96 | 7.2 Channel Privacy ........................................ 16 97 | 7.3 Anonymity ............................................... 17 98 | 8. Current support and availability ........................... 17 99 | 9. Acknowledgements ........................................... 17 100 | 10. References ................................................ 18 101 | 11. Author's Address .......................................... 18 102 | 12. Full Copyright Statement ................................... 19 103 | 104 | 1. Introduction 105 | 106 | This document defines in detail on how channels are managed by the 107 | IRC servers and will be mostly useful to people working on 108 | implementing an IRC server. 109 | 110 | 111 | 112 | 113 | 114 | Kalt Informational [Page 2] 115 | 116 | RFC 2811 Internet Relay Chat: Channel Management April 2000 117 | 118 | 119 | While the concepts defined here are an important part of IRC, they 120 | remain non essential for implementing clients. While the trend seems 121 | to be towards more and more complex and "intelligent" clients which 122 | are able to take advantage of knowing the internal workings of 123 | channels to provide the users with a more friendly interface, simple 124 | clients can be implemented without reading this document. 125 | 126 | Many of the concepts defined here were designed with the IRC 127 | architecture [IRC-ARCH] in mind and mostly make sense in this 128 | context. However, many others could be applied to other 129 | architectures in order to provide forums for a conferencing system. 130 | 131 | Finally, it is to be noted that IRC users may find some of the 132 | following sections of interest, in particular sections 2 (Channel 133 | Characteristics) and 4 (Channel Modes). 134 | 135 | 2. Channel Characteristics 136 | 137 | A channel is a named group of one or more users which will all 138 | receive messages addressed to that channel. A channel is 139 | characterized by its name, properties and current members. 140 | 141 | 2.1 Namespace 142 | 143 | Channels names are strings (beginning with a '&', '#', '+' or '!' 144 | character) of length up to fifty (50) characters. Channel names are 145 | case insensitive. 146 | 147 | Apart from the the requirement that the first character being either 148 | '&', '#', '+' or '!' (hereafter called "channel prefix"). The only 149 | restriction on a channel name is that it SHALL NOT contain any spaces 150 | (' '), a control G (^G or ASCII 7), a comma (',' which is used as a 151 | list item separator by the protocol). Also, a colon (':') is used as 152 | a delimiter for the channel mask. The exact syntax of a channel name 153 | is defined in "IRC Server Protocol" [IRC-SERVER]. 154 | 155 | The use of different prefixes effectively creates four (4) distinct 156 | namespaces for channel names. This is important because of the 157 | protocol limitations regarding namespaces (in general). See section 158 | 6.1 (Labels) for more details on these limitations. 159 | 160 | 2.2 Channel Scope 161 | 162 | A channel entity is known by one or more servers on the IRC network. 163 | A user can only become member of a channel known by the server to 164 | which the user is directly connected. The list of servers which know 165 | 166 | 167 | 168 | 169 | 170 | Kalt Informational [Page 3] 171 | 172 | RFC 2811 Internet Relay Chat: Channel Management April 2000 173 | 174 | 175 | of the existence of a particular channel MUST be a contiguous part of 176 | the IRC network, in order for the messages addressed to the channel 177 | to be sent to all the channel members. 178 | 179 | Channels with '&' as prefix are local to the server where they are 180 | created. 181 | 182 | Other channels are known to one (1) or more servers that are 183 | connected to the network, depending on the channel mask: 184 | 185 | If there is no channel mask, then the channel is known to all 186 | the servers. 187 | 188 | If there is a channel mask, then the channel MUST only be known 189 | to servers which has a local user on the channel, and to its 190 | neighbours if the mask matches both the local and neighbouring 191 | server names. Since other servers have absolutely no knowledge of 192 | the existence of such a channel, the area formed by the servers 193 | having a name matching the mask has to be contiguous for the 194 | channel to be known by all these servers. Channel masks are best 195 | used in conjunction with server hostmasking [IRC-SERVER]. 196 | 197 | 2.3 Channel Properties 198 | 199 | Each channel has its own properties, which are defined by channel 200 | modes. Channel modes can be manipulated by the channel members. The 201 | modes affect the way servers manage the channels. 202 | 203 | Channels with '+' as prefix do not support channel modes. This means 204 | that all the modes are unset, with the exception of the 't' channel 205 | flag which is set. 206 | 207 | 2.4 Privileged Channel Members 208 | 209 | In order for the channel members to keep some control over a channel, 210 | and some kind of sanity, some channel members are privileged. Only 211 | these members are allowed to perform the following actions on the 212 | channel: 213 | 214 | INVITE - Invite a client to an invite-only channel (mode +i) 215 | KICK - Eject a client from the channel 216 | MODE - Change the channel's mode, as well as 217 | members' privileges 218 | PRIVMSG - Sending messages to the channel (mode +n, +m, +v) 219 | TOPIC - Change the channel topic in a mode +t channel 220 | 221 | 222 | 223 | 224 | 225 | 226 | Kalt Informational [Page 4] 227 | 228 | RFC 2811 Internet Relay Chat: Channel Management April 2000 229 | 230 | 231 | 2.4.1 Channel Operators 232 | 233 | The channel operators (also referred to as a "chop" or "chanop") on a 234 | given channel are considered to 'own' that channel. Ownership of a 235 | channel is shared among channel operators. 236 | 237 | Channel operators are identified by the '@' symbol next to their 238 | nickname whenever it is associated with a channel (i.e., replies to 239 | the NAMES, WHO and WHOIS commands). 240 | 241 | Since channels starting with the character '+' as prefix do not 242 | support channel modes, no member can therefore have the status of 243 | channel operator. 244 | 245 | 2.4.2 Channel Creator 246 | 247 | A user who creates a channel with the character '!' as prefix is 248 | identified as the "channel creator". Upon creation of the channel, 249 | this user is also given channel operator status. 250 | 251 | In recognition of this status, the channel creators are endowed with 252 | the ability to toggle certain modes of the channel which channel 253 | operators may not manipulate. 254 | 255 | A "channel creator" can be distinguished from a channel operator by 256 | issuing the proper MODE command. See the "IRC Client Protocol" 257 | [IRC-CLIENT] for more information on this topic. 258 | 259 | 3. Channel lifetime 260 | 261 | In regard to the lifetime of a channel, there are typically two 262 | groups of channels: standard channels which prefix is either '&', '#' 263 | or '+', and "safe channels" which prefix is '!'. 264 | 265 | 3.1 Standard channels 266 | 267 | These channels are created implicitly when the first user joins it, 268 | and cease to exist when the last user leaves it. While the channel 269 | exists, any client can reference the channel using the name of the 270 | channel. 271 | 272 | The user creating a channel automatically becomes channel operator 273 | with the notable exception of channels which name is prefixed by the 274 | character '+', see section 4 (Channel modes). See section 2.4.1 275 | (Channel Operators) for more details on this title. 276 | 277 | 278 | 279 | 280 | 281 | 282 | Kalt Informational [Page 5] 283 | 284 | RFC 2811 Internet Relay Chat: Channel Management April 2000 285 | 286 | 287 | In order to avoid the creation of duplicate channels (typically when 288 | the IRC network becomes disjoint because of a split between two 289 | servers), channel names SHOULD NOT be allowed to be reused by a user 290 | if a channel operator (See Section 2.4.1 (Channel Operators)) has 291 | recently left the channel because of a network split. If this 292 | happens, the channel name is temporarily unavailable. The duration 293 | while a channel remains unavailable should be tuned on a per IRC 294 | network basis. It is important to note that this prevents local 295 | users from creating a channel using the same name, but does not 296 | prevent the channel to be recreated by a remote user. The latter 297 | typically happens when the IRC network rejoins. Obviously, this 298 | mechanism only makes sense for channels which name begins with the 299 | character '#', but MAY be used for channels which name begins with 300 | the character '+'. This mechanism is commonly known as "Channel 301 | Delay". 302 | 303 | 3.2 Safe Channels 304 | 305 | Unlike other channels, "safe channels" are not implicitly created. A 306 | user wishing to create such a channel MUST request the creation by 307 | sending a special JOIN command to the server in which the channel 308 | identifier (then unknown) is replaced by the character '!'. The 309 | creation process for this type of channel is strictly controlled. 310 | The user only chooses part of the channel name (known as the channel 311 | "short name"), the server automatically prepends the user provided 312 | name with a channel identifier consisting of five (5) characters. 313 | The channel name resulting from the combination of these two elements 314 | is unique, making the channel safe from abuses based on network 315 | splits. 316 | 317 | The user who creates such a channel automatically becomes "channel 318 | creator". See section 2.4.2 (Channel Creator) for more details on 319 | this title. 320 | 321 | A server MUST NOT allow the creation of a new channel if another 322 | channel with the same short name exists; or if another channel with 323 | the same short name existed recently AND any of its member(s) left 324 | because of a network split. Such channel ceases to exist after last 325 | user leaves AND no other member recently left the channel because of 326 | a network split. 327 | 328 | Unlike the mechanism described in section 5.2.2 (Channel Delay), in 329 | this case, channel names do not become unavailable: these channels 330 | may continue to exist after the last user left. Only the user 331 | creating the channel becomes "channel creator", users joining an 332 | existing empty channel do not automatically become "channel creator" 333 | nor "channel operator". 334 | 335 | 336 | 337 | 338 | Kalt Informational [Page 6] 339 | 340 | RFC 2811 Internet Relay Chat: Channel Management April 2000 341 | 342 | 343 | To ensure the uniqueness of the channel names, the channel identifier 344 | created by the server MUST follow specific rules. For more details 345 | on this, see section 5.2.1 (Channel Identifier). 346 | 347 | 4. Channel Modes 348 | 349 | The various modes available for channels are as follows: 350 | 351 | O - give "channel creator" status; 352 | o - give/take channel operator privilege; 353 | v - give/take the voice privilege; 354 | 355 | a - toggle the anonymous channel flag; 356 | i - toggle the invite-only channel flag; 357 | m - toggle the moderated channel; 358 | n - toggle the no messages to channel from clients on the 359 | outside; 360 | q - toggle the quiet channel flag; 361 | p - toggle the private channel flag; 362 | s - toggle the secret channel flag; 363 | r - toggle the server reop channel flag; 364 | t - toggle the topic settable by channel operator only flag; 365 | 366 | k - set/remove the channel key (password); 367 | l - set/remove the user limit to channel; 368 | 369 | b - set/remove ban mask to keep users out; 370 | e - set/remove an exception mask to override a ban mask; 371 | I - set/remove an invitation mask to automatically override 372 | the invite-only flag; 373 | 374 | Unless mentioned otherwise below, all these modes can be manipulated 375 | by "channel operators" by using the MODE command defined in "IRC 376 | Client Protocol" [IRC-CLIENT]. 377 | 378 | 4.1 Member Status 379 | 380 | The modes in this category take a channel member nickname as argument 381 | and affect the privileges given to this user. 382 | 383 | 4.1.1 "Channel Creator" Status 384 | 385 | The mode 'O' is only used in conjunction with "safe channels" and 386 | SHALL NOT be manipulated by users. Servers use it to give the user 387 | creating the channel the status of "channel creator". 388 | 389 | 390 | 391 | 392 | 393 | 394 | Kalt Informational [Page 7] 395 | 396 | RFC 2811 Internet Relay Chat: Channel Management April 2000 397 | 398 | 399 | 4.1.2 Channel Operator Status 400 | 401 | The mode 'o' is used to toggle the operator status of a channel 402 | member. 403 | 404 | 4.1.3 Voice Privilege 405 | 406 | The mode 'v' is used to give and take voice privilege to/from a 407 | channel member. Users with this privilege can talk on moderated 408 | channels. (See section 4.2.3 (Moderated Channel Flag). 409 | 410 | 4.2 Channel Flags 411 | 412 | The modes in this category are used to define properties which 413 | affects how channels operate. 414 | 415 | 4.2.1 Anonymous Flag 416 | 417 | The channel flag 'a' defines an anonymous channel. This means that 418 | when a message sent to the channel is sent by the server to users, 419 | and the origin is a user, then it MUST be masked. To mask the 420 | message, the origin is changed to "anonymous!anonymous@anonymous." 421 | (e.g., a user with the nickname "anonymous", the username "anonymous" 422 | and from a host called "anonymous."). Because of this, servers MUST 423 | forbid users from using the nickname "anonymous". Servers MUST also 424 | NOT send QUIT messages for users leaving such channels to the other 425 | channel members but generate a PART message instead. 426 | 427 | On channels with the character '&' as prefix, this flag MAY be 428 | toggled by channel operators, but on channels with the character '!' 429 | as prefix, this flag can be set (but SHALL NOT be unset) by the 430 | "channel creator" only. This flag MUST NOT be made available on 431 | other types of channels. 432 | 433 | Replies to the WHOIS, WHO and NAMES commands MUST NOT reveal the 434 | presence of other users on channels for which the anonymous flag is 435 | set. 436 | 437 | 4.2.2 Invite Only Flag 438 | 439 | When the channel flag 'i' is set, new members are only accepted if 440 | their mask matches Invite-list (See section 4.3.2) or they have been 441 | invited by a channel operator. This flag also restricts the usage of 442 | the INVITE command (See "IRC Client Protocol" [IRC-CLIENT]) to 443 | channel operators. 444 | 445 | 446 | 447 | 448 | 449 | 450 | Kalt Informational [Page 8] 451 | 452 | RFC 2811 Internet Relay Chat: Channel Management April 2000 453 | 454 | 455 | 4.2.3 Moderated Channel Flag 456 | 457 | The channel flag 'm' is used to control who may speak on a channel. 458 | When it is set, only channel operators, and members who have been 459 | given the voice privilege may send messages to the channel. 460 | 461 | This flag only affects users. 462 | 463 | 4.2.4 No Messages To Channel From Clients On The Outside 464 | 465 | When the channel flag 'n' is set, only channel members MAY send 466 | messages to the channel. 467 | 468 | This flag only affects users. 469 | 470 | 4.2.5 Quiet Channel 471 | 472 | The channel flag 'q' is for use by servers only. When set, it 473 | restricts the type of data sent to users about the channel 474 | operations: other user joins, parts and nick changes are not sent. 475 | From a user's point of view, the channel contains only one user. 476 | 477 | This is typically used to create special local channels on which the 478 | server sends notices related to its operations. This was used as a 479 | more efficient and flexible way to replace the user mode 's' defined 480 | in RFC 1459 [IRC]. 481 | 482 | 4.2.6 Private and Secret Channels 483 | 484 | The channel flag 'p' is used to mark a channel "private" and the 485 | channel flag 's' to mark a channel "secret". Both properties are 486 | similar and conceal the existence of the channel from other users. 487 | 488 | This means that there is no way of getting this channel's name from 489 | the server without being a member. In other words, these channels 490 | MUST be omitted from replies to queries like the WHOIS command. 491 | 492 | When a channel is "secret", in addition to the restriction above, the 493 | server will act as if the channel does not exist for queries like the 494 | TOPIC, LIST, NAMES commands. Note that there is one exception to 495 | this rule: servers will correctly reply to the MODE command. 496 | Finally, secret channels are not accounted for in the reply to the 497 | LUSERS command (See "Internet Relay Chat: Client Protocol" [IRC- 498 | CLIENT]) when the parameter is specified. 499 | 500 | 501 | 502 | 503 | 504 | 505 | 506 | Kalt Informational [Page 9] 507 | 508 | RFC 2811 Internet Relay Chat: Channel Management April 2000 509 | 510 | 511 | The channel flags 'p' and 's' MUST NOT both be set at the same time. 512 | If a MODE message originating from a server sets the flag 'p' and the 513 | flag 's' is already set for the channel, the change is silently 514 | ignored. This should only happen during a split healing phase 515 | (mentioned in the "IRC Server Protocol" document [IRC-SERVER]). 516 | 517 | 4.2.7 Server Reop Flag 518 | 519 | The channel flag 'r' is only available on channels which name begins 520 | with the character '!' and MAY only be toggled by the "channel 521 | creator". 522 | 523 | This flag is used to prevent a channel from having no channel 524 | operator for an extended period of time. When this flag is set, any 525 | channel that has lost all its channel operators for longer than the 526 | "reop delay" period triggers a mechanism in servers to reop some or 527 | all of the channel inhabitants. This mechanism is described more in 528 | detail in section 5.2.4 (Channel Reop Mechanism). 529 | 530 | 4.2.8 Topic 531 | 532 | The channel flag 't' is used to restrict the usage of the TOPIC 533 | command to channel operators. 534 | 535 | 4.2.9 User Limit 536 | 537 | A user limit may be set on channels by using the channel flag 'l'. 538 | When the limit is reached, servers MUST forbid their local users to 539 | join the channel. 540 | 541 | The value of the limit MUST only be made available to the channel 542 | members in the reply sent by the server to a MODE query. 543 | 544 | 4.2.10 Channel Key 545 | 546 | When a channel key is set (by using the mode 'k'), servers MUST 547 | reject their local users request to join the channel unless this key 548 | is given. 549 | 550 | The channel key MUST only be made visible to the channel members in 551 | the reply sent by the server to a MODE query. 552 | 553 | 4.3 Channel Access Control 554 | 555 | The last category of modes is used to control access to the channel, 556 | they take a mask as argument. 557 | 558 | 559 | 560 | 561 | 562 | Kalt Informational [Page 10] 563 | 564 | RFC 2811 Internet Relay Chat: Channel Management April 2000 565 | 566 | 567 | In order to reduce the size of the global database for control access 568 | modes set for channels, servers MAY put a maximum limit on the number 569 | of such modes set for a particular channel. If such restriction is 570 | imposed, it MUST only affect user requests. The limit SHOULD be 571 | homogeneous on a per IRC network basis. 572 | 573 | 4.3.1 Channel Ban and Exception 574 | 575 | When a user requests to join a channel, his local server checks if 576 | the user's address matches any of the ban masks set for the channel. 577 | If a match is found, the user request is denied unless the address 578 | also matches an exception mask set for the channel. 579 | 580 | Servers MUST NOT allow a channel member who is banned from the 581 | channel to speak on the channel, unless this member is a channel 582 | operator or has voice privilege. (See Section 4.1.3 (Voice 583 | Privilege)). 584 | 585 | A user who is banned from a channel and who carries an invitation 586 | sent by a channel operator is allowed to join the channel. 587 | 588 | 4.3.2 Channel Invitation 589 | 590 | For channels which have the invite-only flag set (See Section 4.2.2 591 | (Invite Only Flag)), users whose address matches an invitation mask 592 | set for the channel are allowed to join the channel without any 593 | invitation. 594 | 595 | 5. Current Implementations 596 | 597 | The only current implementation of these rules as part of the IRC 598 | protocol is the IRC server, version 2.10. 599 | 600 | The rest of this section deals with issues that are mostly of 601 | importance to those who wish to implement a server but some parts may 602 | also be of interest for client writers. 603 | 604 | 5.1 Tracking Recently Used Channels 605 | 606 | This mechanism is commonly known as "Channel Delay" and generally 607 | only applies to channels which names is prefixed with the character 608 | '#' (See Section 3.1 "Standard channels"). 609 | 610 | When a network split occurs, servers SHOULD keep track of which 611 | channels lost a "channel operator" as the result of the break. These 612 | channels are then in a special state which lasts for a certain period 613 | of time. In this particular state, the channels cannot cease to 614 | 615 | 616 | 617 | 618 | Kalt Informational [Page 11] 619 | 620 | RFC 2811 Internet Relay Chat: Channel Management April 2000 621 | 622 | 623 | exist. If all the channel members leave the channel, the channel 624 | becomes unavailable: the server local clients cannot join the channel 625 | as long as it is empty. 626 | 627 | Once a channel is unavailable, it will become available again either 628 | because a remote user has joined the channel (most likely because the 629 | network is healing), or because the delay period has expired (in 630 | which case the channel ceases to exist and may be re-created). 631 | 632 | The duration for which a channel death is delayed SHOULD be set 633 | considering many factors among which are the size (user wise) of the 634 | IRC network, and the usual duration of network splits. It SHOULD be 635 | uniform on all servers for a given IRC network. 636 | 637 | 5.2 Safe Channels 638 | 639 | This document introduces the notion of "safe channels". These 640 | channels have a name prefixed with the character '!' and great effort 641 | is made to avoid collisions in this name space. Collisions are not 642 | impossible, however they are very unlikely. 643 | 644 | 5.2.1 Channel Identifier 645 | 646 | The channel identifier is a function of the time. The current time 647 | (as defined under UNIX by the number of seconds elapsed since 648 | 00:00:00 GMT, January 1, 1970) is converted in a string of five (5) 649 | characters using the following base: 650 | "ABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890" (each character has a decimal 651 | value starting from 0 for 'A' to 35 for '0'). 652 | 653 | The channel identifier therefore has a periodicity of 36^5 seconds 654 | (about 700 days). 655 | 656 | 5.2.2 Channel Delay 657 | 658 | These channels MUST be subject to the "channel delay" mechanism 659 | described in section 5.1 (Channel Delay). However, the mechanism is 660 | slightly adapted to fit better. 661 | 662 | Servers MUST keep track of all such channels which lose members as 663 | the result of a network split, no matter whether the user is a 664 | "channel operator" or not. 665 | 666 | However, these channels do NOT ever become unavailable, it is always 667 | possible to join them even when they are empty. 668 | 669 | 670 | 671 | 672 | 673 | 674 | Kalt Informational [Page 12] 675 | 676 | RFC 2811 Internet Relay Chat: Channel Management April 2000 677 | 678 | 679 | 5.2.3 Abuse Window 680 | 681 | Because the periodicity is so long, attacks on a particular channel 682 | (name) may only occur once in a very long while. However, with luck 683 | and patience, it is still possible for a user to cause a channel 684 | collision. In order to avoid this, servers MUST "look in the future" 685 | and keep a list of channel names which identifier is about to be used 686 | (in the coming few days for example). Such list should remain small, 687 | not be a burden for servers to maintain and be used to avoid channel 688 | collisions by preventing the re-creation of such channel for a longer 689 | period of time than channel delay does. 690 | 691 | Eventually a server MAY choose to extend this procedure to forbid 692 | creation of channels with the same shortname only (then ignoring the 693 | channel identifier). 694 | 695 | 5.2.4 Preserving Sanity In The Name Space 696 | 697 | The combination of the mechanisms described in sections 5.2.2 and 698 | 5.2.3 makes it quite difficult for a user to create a channel 699 | collision. However, another type of abuse consists of creating many 700 | channels having the same shortname, but different identifiers. To 701 | prevent this from happening, servers MUST forbid the creation of a 702 | new channel which has the same shortname of a channel currently 703 | existing. 704 | 705 | 5.2.5 Server Reop Mechanism 706 | 707 | When a channel has been opless for longer than the "reop delay" 708 | period and has the channel flag 'r' set (See Section 4.2.7 (Server 709 | Reop Flag)), IRC servers are responsible for giving the channel 710 | operator status randomly to some of the members. 711 | 712 | The exact logic used for this mechanism by the current implementation 713 | is described below. Servers MAY use a different logic, but that it 714 | is strongly RECOMMENDED that all servers use the same logic on a 715 | particular IRC network to maintain coherence as well as fairness. 716 | For the same reason, the "reop delay" SHOULD be uniform on all 717 | servers for a given IRC network. As for the "channel delay", the 718 | value of the "reop delay" SHOULD be set considering many factors 719 | among which are the size (user wise) of the IRC network, and the 720 | usual duration of network splits. 721 | 722 | a) the reop mechanism is triggered after a random time following the 723 | expiration of the "reop delay". This should limit the eventuality 724 | of the mechanism being triggered at the same time (for the same 725 | channel) on two separate servers. 726 | 727 | 728 | 729 | 730 | Kalt Informational [Page 13] 731 | 732 | RFC 2811 Internet Relay Chat: Channel Management April 2000 733 | 734 | 735 | b) If the channel is small (five (5) users or less), and the "channel 736 | delay" for this channel has expired, 737 | Then reop all channel members if at least one member is local to 738 | the server. 739 | 740 | c) If the channel is small (five (5) users or less), and the "channel 741 | delay" for this channel has expired, and the "reop delay" has 742 | expired for longer than its value, 743 | Then reop all channel members. 744 | 745 | d) For other cases, reop at most one member on the channel, based on 746 | some method build into the server. If you don't reop a member, the 747 | method should be such that another server will probably op 748 | someone. The method SHOULD be the same over the whole network. A 749 | good heuristic could be just random reop. 750 | (The current implementation actually tries to choose a member 751 | local to the server who has not been idle for too long, eventually 752 | postponing action, therefore letting other servers have a chance 753 | to find a "not too idle" member. This is over complicated due to 754 | the fact that servers only know the "idle" time of their local 755 | users) 756 | 757 | 6. Current problems 758 | 759 | There are a number of recognized problems with the way IRC channels 760 | are managed. Some of these can be directly attributed to the rules 761 | defined in this document, while others are the result of the 762 | underlying "IRC Server Protocol" [IRC-SERVER]. Although derived from 763 | RFC 1459 [IRC], this document introduces several novelties in an 764 | attempt to solve some of the known problems. 765 | 766 | 6.1 Labels 767 | 768 | This document defines one of the many labels used by the IRC 769 | protocol. Although there are several distinct namespaces (based on 770 | the channel name prefix), duplicates inside each of these are not 771 | allowed. Currently, it is possible for users on different servers to 772 | pick the label which may result in collisions (with the exception of 773 | channels known to only one server where they can be averted). 774 | 775 | 6.1.1 Channel Delay 776 | 777 | The channel delay mechanism described in section 5.1 (Tracking 778 | Recently Used Channels) and used for channels prefixed with the 779 | character '#' is a simple attempt at preventing collisions from 780 | happening. Experience has shown that, under normal circumstances, it 781 | 782 | 783 | 784 | 785 | 786 | Kalt Informational [Page 14] 787 | 788 | RFC 2811 Internet Relay Chat: Channel Management April 2000 789 | 790 | 791 | is very efficient; however, it obviously has severe limitations 792 | keeping it from being an adequate solution to the problem discussed 793 | here. 794 | 795 | 6.1.2 Safe Channels 796 | 797 | "Safe channels" described in section 3.2 (Safe Channels) are a better 798 | way to prevent collisions from happening as it prevents users from 799 | having total control over the label they choose. The obvious 800 | drawback for such labels is that they are not user friendly. 801 | However, it is fairly trivial for a client program to improve on 802 | this. 803 | 804 | 6.2 Mode Propagation Delays 805 | 806 | Because of network delays induced by the network, and because each 807 | server on the path is REQUIRED to check the validity of mode changes 808 | (e.g., user exists and has the right privileges), it is not unusual 809 | for a MODE message to only affect part of the network, often creating 810 | a discrepancy between servers on the current state of a channel. 811 | 812 | While this may seem easy to fix (by having only the original server 813 | check the validity of mode changes), it was decided not to do so for 814 | various reasons. One concern is that servers cannot trust each 815 | other, and that a misbehaving servers can easily be detected. This 816 | way of doing so also stops wave effects on channels which are out of 817 | synch when mode changes are issued from different directions. 818 | 819 | 6.3 Collisions And Channel Modes 820 | 821 | The "Internet Relay Chat: Server Protocol" document [IRC-SERVER] 822 | describes how channel data is exchanged when two servers connect to 823 | each other. Channel collisions (either legitimate or not) are 824 | treated as inclusive events, meaning that the resulting channel has 825 | for members all the users who are members of the channel on either 826 | server prior to the connection. 827 | 828 | Similarly, each server sends the channel modes to the other one. 829 | Therefore, each server also receives these channel modes. There are 830 | three types of modes for a given channel: flags, masks, and data. 831 | The first two types are easy to deal with as they are either set or 832 | unset. If such a mode is set on one server, it MUST be set on the 833 | other server as a result of the connection. 834 | 835 | 836 | 837 | 838 | 839 | 840 | 841 | 842 | Kalt Informational [Page 15] 843 | 844 | RFC 2811 Internet Relay Chat: Channel Management April 2000 845 | 846 | 847 | As topics are not sent as part of this exchange, they are not a 848 | problem. However, channel modes 'l' and 'k' are exchanged, and if 849 | they are set on both servers prior to the connection, there is no 850 | mechanism to decide which of the two values takes precedence. It is 851 | left up to the users to fix the resulting discrepancy. 852 | 853 | 6.4 Resource Exhaustion 854 | 855 | The mode based on masks defined in section 4.3 make the IRC servers 856 | (and network) vulnerable to a simple abuse of the system: a single 857 | channel operator can set as many different masks as possible on a 858 | particular channel. This can easily cause the server to waste 859 | memory, as well as network bandwidth (since the info is propagated to 860 | other servers). For this reason it is RECOMMENDED that a limit be 861 | put on the number of such masks per channels as mentioned in section 862 | 4.3. 863 | 864 | Moreover, more complex mechanisms MAY be used to avoid having 865 | redundant masks set for the same channel. 866 | 867 | 7. Security Considerations 868 | 869 | 7.1 Access Control 870 | 871 | One of the main ways to control access to a channel is to use masks 872 | which are based on the username and hostname of the user connections. 873 | This mechanism can only be efficient and safe if the IRC servers have 874 | an accurate way of authenticating user connections, and if users 875 | cannot easily get around it. While it is in theory possible to 876 | implement such a strict authentication mechanism, most IRC networks 877 | (especially public networks) do not have anything like this in place 878 | and provide little guaranty about the accuracy of the username and 879 | hostname for a particular client connection. 880 | 881 | Another way to control access is to use a channel key, but since this 882 | key is sent in plaintext, it is vulnerable to traditional man in the 883 | middle attacks. 884 | 885 | 7.2 Channel Privacy 886 | 887 | Because channel collisions are treated as inclusive events (See 888 | Section 6.3), it is possible for users to join a channel overriding 889 | its access control settings. This method has long been used by 890 | individuals to "take over" channels by "illegitimately" gaining 891 | channel operator status on the channel. The same method can be used 892 | to find out the exact list of members of a channel, as well as to 893 | eventually receive some of the messages sent to the channel. 894 | 895 | 896 | 897 | 898 | Kalt Informational [Page 16] 899 | 900 | RFC 2811 Internet Relay Chat: Channel Management April 2000 901 | 902 | 903 | 7.3 Anonymity 904 | 905 | The anonymous channel flag (See Section 4.2.1) can be used to render 906 | all users on such channel "anonymous" by presenting all messages to 907 | the channel as originating from a pseudo user which nickname is 908 | "anonymous". This is done at the client-server level, and no 909 | anonymity is provided at the server-server level. 910 | 911 | It should be obvious to readers, that the level of anonymity offered 912 | is quite poor and insecure, and that clients SHOULD display strong 913 | warnings for users joining such channels. 914 | 915 | 8. Current support and availability 916 | 917 | Mailing lists for IRC related discussion: 918 | General discussion: ircd-users@irc.org 919 | Protocol development: ircd-dev@irc.org 920 | 921 | Software implementations: 922 | ftp://ftp.irc.org/irc/server 923 | ftp://ftp.funet.fi/pub/unix/irc 924 | ftp://coombs.anu.edu.au/pub/irc 925 | 926 | Newsgroup: alt.irc 927 | 928 | 9. Acknowledgements 929 | 930 | Parts of this document were copied from the RFC 1459 [IRC] which 931 | first formally documented the IRC Protocol. It has also benefited 932 | from many rounds of review and comments. In particular, the 933 | following people have made significant contributions to this 934 | document: 935 | 936 | Matthew Green, Michael Neumayer, Volker Paulsen, Kurt Roeckx, Vesa 937 | Ruokonen, Magnus Tjernstrom, Stefan Zehl. 938 | 939 | 940 | 941 | 942 | 943 | 944 | 945 | 946 | 947 | 948 | 949 | 950 | 951 | 952 | 953 | 954 | Kalt Informational [Page 17] 955 | 956 | RFC 2811 Internet Relay Chat: Channel Management April 2000 957 | 958 | 959 | 10. References 960 | 961 | [KEYWORDS] Bradner, S., "Key words for use in RFCs to Indicate 962 | Requirement Levels", BCP 14, RFC 2119, March 1997. 963 | 964 | [IRC] Oikarinen, J. and D. Reed, "Internet Relay Chat 965 | Protocol", RFC 1459, May 1993. 966 | 967 | [IRC-ARCH] Kalt, C., "Internet Relay Chat: Architecture", RFC 2810, 968 | April 2000. 969 | 970 | [IRC-CLIENT] Kalt, C., "Internet Relay Chat: Client Protocol", RFC 971 | 2812, April 2000. 972 | 973 | [IRC-SERVER] Kalt, C., "Internet Relay Chat: Server Protocol", RFC 974 | 2813, April 2000. 975 | 976 | 11. Author's Address 977 | 978 | Christophe Kalt 979 | 99 Teaneck Rd, Apt #117 980 | Ridgefield Park, NJ 07660 981 | USA 982 | 983 | EMail: kalt@stealth.net 984 | 985 | 986 | 987 | 988 | 989 | 990 | 991 | 992 | 993 | 994 | 995 | 996 | 997 | 998 | 999 | 1000 | 1001 | 1002 | 1003 | 1004 | 1005 | 1006 | 1007 | 1008 | 1009 | 1010 | Kalt Informational [Page 18] 1011 | 1012 | RFC 2811 Internet Relay Chat: Channel Management April 2000 1013 | 1014 | 1015 | 12. Full Copyright Statement 1016 | 1017 | Copyright (C) The Internet Society (2000). All Rights Reserved. 1018 | 1019 | This document and translations of it may be copied and furnished to 1020 | others, and derivative works that comment on or otherwise explain it 1021 | or assist in its implementation may be prepared, copied, published 1022 | and distributed, in whole or in part, without restriction of any 1023 | kind, provided that the above copyright notice and this paragraph are 1024 | included on all such copies and derivative works. However, this 1025 | document itself may not be modified in any way, such as by removing 1026 | the copyright notice or references to the Internet Society or other 1027 | Internet organizations, except as needed for the purpose of 1028 | developing Internet standards in which case the procedures for 1029 | copyrights defined in the Internet Standards process must be 1030 | followed, or as required to translate it into languages other than 1031 | English. 1032 | 1033 | The limited permissions granted above are perpetual and will not be 1034 | revoked by the Internet Society or its successors or assigns. 1035 | 1036 | This document and the information contained herein is provided on an 1037 | "AS IS" basis and THE INTERNET SOCIETY AND THE INTERNET ENGINEERING 1038 | TASK FORCE DISCLAIMS ALL WARRANTIES, EXPRESS OR IMPLIED, INCLUDING 1039 | BUT NOT LIMITED TO ANY WARRANTY THAT THE USE OF THE INFORMATION 1040 | HEREIN WILL NOT INFRINGE ANY RIGHTS OR ANY IMPLIED WARRANTIES OF 1041 | MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. 1042 | 1043 | Acknowledgement 1044 | 1045 | Funding for the RFC Editor function is currently provided by the 1046 | Internet Society. 1047 | 1048 | 1049 | 1050 | 1051 | 1052 | 1053 | 1054 | 1055 | 1056 | 1057 | 1058 | 1059 | 1060 | 1061 | 1062 | 1063 | 1064 | 1065 | 1066 | Kalt Informational [Page 19] 1067 | 1068 | --------------------------------------------------------------------------------