├── .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 |
--------------------------------------------------------------------------------