SASL support
authorJack Allnutt <m2ys4u@Gmail.com>
Fri, 2 Nov 2012 18:52:04 +0000 (18:52 +0000)
committerJack Allnutt <m2ys4u@Gmail.com>
Fri, 2 Nov 2012 18:52:04 +0000 (18:52 +0000)
Falls back to `PASS` command if `CAP` is not supported, the `sasl` capability is not supported or SASL authentication fails.

Issue #110

server/irc/commands.js
server/irc/connection.js

index a47cb192459e503a661250458185b2f099cdf7c8..c5207eb51d900318e9996fc0eda890fd3e123fd1 100644 (file)
@@ -38,7 +38,14 @@ var irc_numerics = {
     ERR_BANNEDFROMCHAN:     '474',
     ERR_BADCHANNELKEY:      '475',
     ERR_CHANOPRIVSNEEDED:   '482',
-    RPL_STARTTLS:           '670'
+    RPL_STARTTLS:           '670',
+    RPL_SASLAUTHENTICATED:  '900',
+    RPL_SASLLOGGEDIN:       '903',
+    ERR_SASLNOTAUTHORISED:  '904',
+    ERR_SASLNOTAUTHORISED:  '905',
+    ERR_SASLABORTED:        '906',
+    ERR_SASLALREADYAUTHED:  '907'
+    
 };
 
 
@@ -74,7 +81,6 @@ var listeners = {
                 var nick =  command.params[0];
                 this.irc_connection.registered = true;
                 this.cap_negotation = false;
-                console.log(this.irc_connection.cap.enabled);
                 this.client.sendIrcCommand('connect', {server: this.con_num, nick: nick});
             },
     'RPL_ISUPPORT':           function (command) {
@@ -436,8 +442,12 @@ var listeners = {
                 // TODO: capability modifiers
                 // i.e. - for disable, ~ for requires ACK, = for sticky
                 var capabilities = command.trailing.replace(/[\-~=]/, '').split(' ');
-                var want = ['multi-prefix'];
                 var request;
+                var want = ['multi-prefix'];
+                
+                if (this.irc_connection.password) {
+                    want.push('sasl');
+                }
                 
                 switch (command.params[1]) {
                     case 'LS':
@@ -448,6 +458,7 @@ var listeners = {
                         } else {
                             this.irc_connection.write('CAP END');
                             this.irc_connection.cap_negotation = false;
+                            this.irc_connection.register();
                         }
                         break;
                     case 'ACK':
@@ -456,8 +467,14 @@ var listeners = {
                             this.irc_connection.cap.requested = _.difference(this.irc_connection.cap.requested, capabilities);
                         }
                         if (this.irc_connection.cap.requested.length > 0) {
-                            this.irc_connection.write('CAP END');
-                            this.irc_connection.cap_negotation = false;
+                            if (_.contains(this.irc_connection.cap.enabled, 'sasl')) {
+                                this.irc_connection.sasl = true;
+                                this.irc_connection.write('AUTHENTICATE PLAIN');
+                            } else {
+                                this.irc_connection.write('CAP END');
+                                this.irc_connection.cap_negotation = false;
+                                this.irc_connection.register();
+                            }
                         }
                         break;
                     case 'NAK':
@@ -467,6 +484,7 @@ var listeners = {
                         if (this.irc_connection.cap.requested.length > 0) {
                             this.irc_connection.write('CAP END');
                             this.irc_connection.cap_negotation = false;
+                            this.irc_connection.register();
                         }
                         break;
                     case 'LIST':
@@ -474,6 +492,45 @@ var listeners = {
                         break;
                 }
             },
+    'AUTHENTICATE':         function (command) {
+                var b = new Buffer(this.irc_connection.nick + "\0" + this.irc_connection.nick + "\0" + this.irc_connection.password, 'utf8');
+                var b64 = b.toString('base64');
+                if (command.params[0] === '+') {
+                    while (b64.length >= 400) {
+                        this.irc_connection.write('AUTHENTICATE ' + b64.slice(0, 399));
+                        b64 = b64.slice(399);
+                    }
+                    if (b64.length > 0) {
+                        this.irc_connection.write('AUTHENTICATE ' + b64);
+                    } else {
+                        this.irc_connection.write('AUTHENTICATE +');
+                    }
+                } else {
+                    this.irc_connection.write('CAP END');
+                    this.irc_connection.cap_negotation = false;
+                    this.irc_connection.register();
+                }
+            },
+    'RPL_SASLAUTHENTICATED':function (command) {
+                this.irc_connection.write('CAP END');
+                this.irc_connection.cap_negotation = false;
+                this.irc_connection.register();
+            },
+    'RPL_SASLLOGGEDIN':     function (command) {
+                // noop
+            },
+    'ERR_SASLNOTAUTHORISED':function (command) {
+                this.irc_connection.write('CAP END');
+                this.irc_connection.cap_negotation = false;
+                this.irc_connection.cap.enabled = _.without(this.irc_connection.cap.enabled, 'sasl');
+                this.irc_connection.register();
+            },
+    'ERR_SASLABORTED':      function (command) {
+                this.irc_connection.cap.enabled = _.without(this.irc_connection.cap.enabled, 'sasl');
+            },
+    'ERR_SASLALREADYAUTHED':function (command) {
+                // noop
+            },
     'ERROR':                function (command) {
                                /*command.server = this.con_num;
                                command.command = 'ERROR';
index 916d7cd3d5966b4f1d5ac3b083423685eb121d7e..28e6519d9d20608f5decc5374d3562d58664e4c5 100644 (file)
@@ -40,6 +40,7 @@ var IrcConnection = function (hostname, port, ssl, nick, user, pass) {
     this.ssl = !(!ssl);
     this.options = Object.create(null);
     this.cap = {requested: [], enabled: []};
+    this.sasl = false;
     
     this.password = pass;
     this.hold_last = false;
@@ -95,18 +96,30 @@ var connect_handler = function () {
         });
     }
 
-    if (this.password) {
-        this.write('PASS ' + this.password);
-    }
-    
     this.write('CAP LS');
-    this.write('NICK ' + connect_data.nick);
-    this.write('USER ' + connect_data.username + ' 0 0 :' + connect_data.realname);
+
+    this.registration_timeout = setTimeout(function () {
+        that.register.call(that);
+    }, 1000);
     
     this.connected = true;
     this.emit('connected');
 };
 
+IrcConnection.prototype.register = function () {
+    if (this.registration_timeout !== null) {
+        clearTimeout(this.registeration_timeout);
+        this.registration_timeout = null;
+    }
+    if ((this.password) && (!_.contains(this.cap.enabled, 'sasl'))) {
+        this.write('PASS ' + this.password);
+    }
+    this.write('NICK ' + this.nick);
+    this.write('USER ' + this.nick.replace(/[^0-9a-zA-Z\-_.]/, '') + ' 0 0 :' + '[www.kiwiirc.com] ' + this.nick);
+    if (this.cap_negotation) {
+        this.write('CAP END');
+    }
+};