[Pkg-javascript-commits] [node-rai] 01/04: Imported Upstream version 0.1.12

Thorsten Alteholz alteholz at moszumanska.debian.org
Sun Feb 21 12:00:06 UTC 2016


This is an automated email from the git hooks/post-receive script.

alteholz pushed a commit to branch master
in repository node-rai.

commit ce6f893dbe87e33a67779be0838fd912ba4e6ba7
Author: Thorsten Alteholz <debian at alteholz.de>
Date:   Sun Feb 21 11:51:58 2016 +0100

    Imported Upstream version 0.1.12
---
 .gitignore       |   3 +
 .npmignore       |   2 +
 .travis.yml      |  12 +
 LICENSE          |  16 ++
 README.md        | 162 ++++++++++++
 cert/server.crt  |  15 ++
 cert/server.key  |  15 ++
 examples/smtp.js |  73 ++++++
 lib/mockup.js    | 123 +++++++++
 lib/rai.js       | 480 +++++++++++++++++++++++++++++++++++
 lib/starttls.js  | 111 ++++++++
 package.json     |  31 +++
 test/rai.js      | 750 +++++++++++++++++++++++++++++++++++++++++++++++++++++++
 13 files changed, 1793 insertions(+)

diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..8d4c2d1
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,3 @@
+node_modules
+.DS_Store
+npm-debug.log
\ No newline at end of file
diff --git a/.npmignore b/.npmignore
new file mode 100644
index 0000000..0c30401
--- /dev/null
+++ b/.npmignore
@@ -0,0 +1,2 @@
+test
+examples
\ No newline at end of file
diff --git a/.travis.yml b/.travis.yml
new file mode 100644
index 0000000..04f32c3
--- /dev/null
+++ b/.travis.yml
@@ -0,0 +1,12 @@
+language: node_js
+node_js:
+  - "0.8"
+  - "0.10"
+  - "0.11"
+
+notifications:
+  email:
+    recipients:
+      - andris at kreata.ee
+    on_success: change
+    on_failure: change
diff --git a/LICENSE b/LICENSE
new file mode 100644
index 0000000..a47b0ea
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,16 @@
+Copyright (c) 2012 Andris Reinman
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
\ No newline at end of file
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..5c10a2c
--- /dev/null
+++ b/README.md
@@ -0,0 +1,162 @@
+# RAI - Request-Answer-Interface
+
+**rai** is a node.js module to easily generate text based command line servers.
+When a client sends something to the server, the first word of the line is
+treated as a command and the rest of the line as binary payload.
+
+[![Build Status](https://secure.travis-ci.org/andris9/rai.png)](http://travis-ci.org/andris9/rai)
+
+In addition to line based commands, there's also a data mode, to transmit
+everygting received. And there's also an option to switch to TLS mode for
+secure connections.
+
+This way it is trivial to create SMTP, POP3 or similar servers.
+
+## Installation
+
+    npm install rai
+    
+## Usage
+
+### Simple server
+
+    var RAIServer = require("rai").RAIServer;
+    
+    // create a RAIServer on port 1234
+    var server = new RAIServer();
+    server.listen(1234);
+    
+    // Start listening for client connections
+    server.on("connect", function(client){
+    
+        // Greet the client
+        client.send("Hello!");
+        
+        // Wait for a command
+        client.on("command", function(command, payload){
+        
+            if(command == "STATUS"){
+                client.send("Status is OK!");
+            }else if(command == "QUIT"){
+                client.send("Goodbye");
+                client.end();
+            }else{
+                client.send("Unknown command");
+            }
+        
+        });
+    
+    });
+
+Server only emits `'connect'` and `'error'` events, while the client 
+objects emit `'timeout'`, `'error'` and `'end'` in addition to data 
+related events.
+
+### Starting a server
+
+Server can be started with `new RAIServer([options])` where options is an optional
+parameters object with the following properties:
+
+  * **debug** - if set to true print traffic to console
+  * **disconnectOnTimeout** - if set to true close the connection on disconnect
+  * **secureConnection** - if set to true close the connection on disconnect
+  * **credentials** - credentials for secureConnection and STARTTLS
+  * **timeout** - timeout in milliseconds for disconnecting the client, defaults to 0 (no timeout)
+  
+Once the server has been set up, it can start listening for client connections
+with `server.listen(port[, hostname][, callback])`. Callback function gets an error
+object as a parameter if the listening failed.
+
+    var server = new RAIServer();
+    server.listen(25); // start listening for port 25 on "localhost"
+
+### Closing server
+
+Server can be closed with `server.end([callback])` where callback is run when
+the server is finally closed.
+
+### Sending data
+
+Data can be sent with `client.send(data)` where `data` is either a String or
+a Buffer. `"\r\n"` is automatically appended to the data.
+
+    client.send("Greetings!");
+
+### Forcing connection close
+
+Connections can be ended with `client.end()`
+
+    if(command == "QUIT"){
+        client.send("Good bye!");
+        client.end();
+    }
+
+### TLS mode
+
+TLS can be switched on with `client.startTLS([credentials][, callback])` and the status can
+be listened with `'tls'` (emitted when secure connection is established)
+
+`credentials` is an object with strings of pem encoded `key`, `cert` and optionally an
+array `ca`. If `credentials` is not supplied, an autogenerated value is used.
+
+    if(command == "STARTTLS"){
+        client.startTLS();
+    }
+    
+    client.on("tls", function(){
+        console.log("Switched to secure connection");
+    });
+
+If `callback` is not set `'tls'` will be emitted on connection upgrade.
+
+### Data mode
+
+Data mode can be turned on with `client.startDataMode([endSequence])` and incoming
+chunks can be received with `'data'`. The end of data mode can be detected by
+`'ready'`.
+
+`endSequence` is a String for matching the end (entire line) of the data stream.
+By default it's `"."` which is suitable for SMTP and POP3.
+
+    if(command == "DATA"){
+        client.send("End data with <CR><LF>.<CR><LF>");
+        client.startDataMode();
+    }
+
+    client.on("data", function(chunk){
+        console.log("Data from client:", chunk);
+    });
+    
+    client.on("ready", function(){
+        client.send("Data received");
+    });
+
+## Testing
+
+There is a possibility to set up a mockup client which sends a batch of commands
+one by one to the server and returns the last response and an array of all responses(except the TLS negotiation).
+
+    var runClientMockup = require("rai").runClientMockup;
+    
+    var cmds = ["EHLO FOOBAR", "STARTTLS", "QUIT"];
+    runClientMockup(25, "mail.hot.ee", cmds, function(lastResponse, allResponses){
+        console.log("Final:", lastResponse.toString("utf-8").trim());
+        console.log("All:", allResponses.map(function(e){
+            return e.toString("utf-8").trim()
+        }).join(', '));
+    });
+
+`runClientMockup` has he following parameters in the following order:
+
+  * **port** - Port number
+  * **host** - Hostname to connect to
+  * **commands** - Command list (an array) to be sent to server
+  * **callback** - Callback function to run on completion
+  * **debug** - if set to true log all input/output
+
+Response from the callback function is a Buffer and contains the
+last data received from the server and an array of Buffers with all data received from the server.
+
+## License
+
+**MIT**
\ No newline at end of file
diff --git a/cert/server.crt b/cert/server.crt
new file mode 100644
index 0000000..153330e
--- /dev/null
+++ b/cert/server.crt
@@ -0,0 +1,15 @@
+-----BEGIN CERTIFICATE-----
+MIICYTCCAcoCCQDl53qKS6iIgDANBgkqhkiG9w0BAQUFADB1MQswCQYDVQQGEwJF
+RTEOMAwGA1UECBMFSGFyanUxEDAOBgNVBAcTB1RhbGxpbm4xDzANBgNVBAoTBkty
+ZWF0YTESMBAGA1UEAxMJbG9jYWxob3N0MR8wHQYJKoZIhvcNAQkBFhBhbmRyaXNA
+a3JlYXRhLmVlMB4XDTEzMDMxOTA5NTcxNVoXDTE0MDMxOTA5NTcxNVowdTELMAkG
+A1UEBhMCRUUxDjAMBgNVBAgTBUhhcmp1MRAwDgYDVQQHEwdUYWxsaW5uMQ8wDQYD
+VQQKEwZLcmVhdGExEjAQBgNVBAMTCWxvY2FsaG9zdDEfMB0GCSqGSIb3DQEJARYQ
+YW5kcmlzQGtyZWF0YS5lZTCBnzANBgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEAwke9
+RYIa5uOTqOSwJHO3lQyT6p9v7g/NI6FckuvqerGThS+irvP4Nd9xuqGjRB2HBEgM
+QqSlPqYQ+pI5zcI3V3r1/A9OxSQoR9ar6obKsAfHiWP1u96mpiAZJudYLPud69RR
+1/BoihM6t2FSvwGXO+q38wOLM9tBWgt5Ng68fM0CAwEAATANBgkqhkiG9w0BAQUF
+AAOBgQBZJBkf/piXM2Kl725w1ZESlt0m1DbpP55K3ZLLJEQ2IZxQ1wtWChl2duAe
+s9Hv5YGm1U44wDbzNXfqqgUIDJVwDzJlq8xtTbfUCJ8HtDKLqH7rGIgDArdtwACZ
+bOW7J6ei0ZDhtyDnc9eHB5CT8bgTR1VkMlx3v/bPSCEmTiRJNA==
+-----END CERTIFICATE-----
diff --git a/cert/server.key b/cert/server.key
new file mode 100644
index 0000000..81b163b
--- /dev/null
+++ b/cert/server.key
@@ -0,0 +1,15 @@
+-----BEGIN RSA PRIVATE KEY-----
+MIICXgIBAAKBgQDCR71Fghrm45Oo5LAkc7eVDJPqn2/uD80joVyS6+p6sZOFL6Ku
+8/g133G6oaNEHYcESAxCpKU+phD6kjnNwjdXevX8D07FJChH1qvqhsqwB8eJY/W7
+3qamIBkm51gs+53r1FHX8GiKEzq3YVK/AZc76rfzA4sz20FaC3k2Drx8zQIDAQAB
+AoGARhUM4LsLK0ji5iUAqVWY3sp3vUYgYVcP4A+ATnuNzQ6rsXq6i7P0ULK22uUd
++R9Rqii3S38LIOtU6p6+/UtXHLUxnNqGKx/6mSamKhv01UgqN65Laq1pCX40Wjgj
+k5R1wdkwQG+DOj+L6mPxnp92Stn+PYPCUqYpK1qLvPu7X2ECQQDvRToiJ0XRLLxa
+pz4eAUeN4KJWBJRIT+GZzx27qB3aAr6UT0ccXLmhYBl3AUNYcWyDdAp+ZoX+CQuZ
+yEc1Xob1AkEAz904LHgZSMNrKIPhU93Og7fXypY3Smlci5jPQ7rCxzGz0XWUslne
+pL8wRtwa8DpTufDj8Ihfw0E8BxUG+yYHeQJBAIL/quFSESaB0KntUNQKrUtfRmHD
+5g9lNMYKIGRCmf1nbUIz2WIM3lEdFTQTi/SbPOcHnEsyBIBeIWzTuzDcDRUCQQDC
+2cKgnOxGszkuP4Hn1hKSkrFsLKgjzuR7z4DrIpUXmNXRUYFUNr5ofPhKVGXEL0jx
+Eoj5nzz1kZ8tnF5w61MxAkEAmJYV3PN/V4pTqX/bnNwQWUAQfrDBdkjw2RAAUtbY
+MnunBSguCrxmQEKo/zomkZZ/R7/WpJyGZxN90SHa8mZJJA==
+-----END RSA PRIVATE KEY-----
diff --git a/examples/smtp.js b/examples/smtp.js
new file mode 100644
index 0000000..861e42d
--- /dev/null
+++ b/examples/smtp.js
@@ -0,0 +1,73 @@
+var RAIServer = require("../lib/rai").RAIServer;
+
+var server = new RAIServer({debug: true, timeout:25*1000});
+
+server.listen(1234, function(err){
+    console.log(err || "listening on port 1234...")
+});
+
+server.on("connection", function(socket){
+
+    socket.send("220 foo.bar"); // send banner greeting
+
+    socket.on("command", function(command, payload){
+
+        command = (command || "").toString().toUpperCase().trim();
+
+        switch(command){
+            case "EHLO":
+                socket.send("250-foo.bar at your service\r\n"+
+                            "250-PIPELINING\r\n" +
+                            "250-8BITMIME\r\n"+
+                            "250 STARTTLS");
+                break;
+            case "STARTTLS":
+                socket.send("220 Ready to start TLS");
+                socket.startTLS();
+                break;
+            case "MAIL":
+                socket.send("250 Ok");
+                break;
+            case "RCPT":
+                socket.send("250 Ok");
+                break;
+            case "DATA":
+                socket.send("354 End with <CR><LF>.<CR><LF>");
+                socket.startDataMode();
+                break;
+            case "QUIT":
+                socket.send("221 Good bye");
+                socket.end();
+                break;
+            default:
+                socket.send("500 Unknown command");
+        }
+
+    });
+
+    socket.on("tls", function(data){
+        console.log("TLS STARTED");
+    });
+
+    socket.on("data", function(data){
+        console.log("MAIL DATA", data);
+    });
+
+    socket.on("ready", function(data){
+        console.log("DATA READY");
+        socket.send("250 Ok: queued as FOOBAR");
+    });
+
+    socket.on("timeout", function(data){
+        console.log("TIMEOUT");
+    });
+
+    socket.on("error", function(err){
+        console.log("ERROR:", err.message || err);
+    });
+
+    socket.on("end", function(){
+        console.log("Connection closed");
+    });
+
+});
\ No newline at end of file
diff --git a/lib/mockup.js b/lib/mockup.js
new file mode 100644
index 0000000..ad65301
--- /dev/null
+++ b/lib/mockup.js
@@ -0,0 +1,123 @@
+"use strict";
+
+var net = require("net"),
+    crypto = require("crypto"),
+    tlslib = require("tls");
+
+// monkey patch net and tls to support nodejs 0.4
+if(!net.connect && net.createConnection){
+    net.connect = net.createConnection;
+}
+
+if(!tlslib.connect && tlslib.createConnection){
+    tlslib.connect = tlslib.createConnection;
+}
+
+/**
+ * @namespace Mockup module
+ * @name mockup
+ */
+module.exports = runClientMockup;
+
+/**
+ * <p>Runs a batch of commands against a server</p>
+ *
+ * <pre>
+ * var cmds = ["EHLO FOOBAR", "STARTTLS", "QUIT"];
+ * runClientMockup(25, "mail.hot.ee", cmds, function(resp){
+ *     console.log("Final:", resp.toString("utf-8").trim());
+ * });
+ * </pre>
+ *
+ * @memberOf mockup
+ * @param {Number} port Port number
+ * @param {String} host Hostname to connect to
+ * @param {Array} commands Command list to be sent to server
+ * @param {Function} callback Callback function to run on completion,
+ *        has the last response from the server as a param
+ * @param {Boolean} [debug] if set to true log all input/output
+ */
+function runClientMockup(port, host, commands, callback, debug){
+    host = host || "localhost";
+    port = port || 25;
+    commands = Array.isArray(commands) ? commands : [];
+
+    var command, ignore_data = false, responses = [], sslcontext, pair;
+
+    var socket = net.connect(port, host);
+    socket.on("connect", function(){
+        socket.on("data", function(chunk){
+            if(ignore_data){
+                return;
+            }
+
+            if(debug){
+                console.log("S: "+chunk.toString("utf-8").trim());
+            }
+
+            if(!commands.length){
+                socket.end();
+                if(typeof callback == "function"){
+                    responses.push(chunk);
+                    callback(chunk, responses);
+                }
+                return;
+            }else{
+                responses.push(chunk);
+            }
+
+            if(["STARTTLS", "STLS"].indexOf((command || "").trim().toUpperCase())>=0){
+                ignore_data = true;
+                if(debug){
+                    console.log("Initiated TLS connection");
+                }
+                sslcontext = crypto.createCredentials();
+                pair = tlslib.createSecurePair(sslcontext, false);
+
+                pair.encrypted.pipe(socket);
+                socket.pipe(pair.encrypted);
+                pair.fd = socket.fd;
+
+                pair.on("secure", function(){
+                    if(debug){
+                        console.log("TLS connection secured");
+                    }
+                    command = commands.shift();
+                    if(debug){
+                        console.log("C: "+command);
+                    }
+                    pair.cleartext.write(command+"\r\n");
+
+                    pair.cleartext.on("data", function(chunk){
+                        if(debug){
+                            console.log("S: "+chunk.toString("utf-8").trim());
+                        }
+
+                        if(!commands.length){
+                            pair.cleartext.end();
+                            if(typeof callback == "function"){
+                                responses.push(chunk);
+                                callback(chunk, responses);
+                            }
+                            return;
+                        }else{
+                            responses.push(chunk);
+                        }
+                        command = commands.shift();
+                        pair.cleartext.write(command+"\r\n");
+                        if(debug){
+                            console.log("C: "+command);
+                        }
+                    });
+                });
+            }else{
+                command = commands.shift();
+                socket.write(command+"\r\n");
+                if(debug){
+                    console.log("C: "+command);
+                }
+            }
+        });
+    });
+
+}
diff --git a/lib/rai.js b/lib/rai.js
new file mode 100644
index 0000000..0570fcf
--- /dev/null
+++ b/lib/rai.js
@@ -0,0 +1,480 @@
+"use strict";
+
+/**
+ * @fileOverview This is the main file for the RAI library to create text based servers
+ * @author <a href="mailto:andris at node.ee">Andris Reinman</a>
+ */
+
+var netlib = require("net"),
+    utillib = require("util"),
+    EventEmitter = require('events').EventEmitter,
+    starttls = require("./starttls").starttls,
+    tlslib = require("tls"),
+    fs = require("fs"),
+    SlowBuffer = require("buffer").SlowBuffer;
+
+// Default credentials for starting TLS server
+var defaultCredentials = {
+    key: fs.readFileSync(__dirname+"/../cert/server.key"),
+    cert: fs.readFileSync(__dirname+"/../cert/server.crt")
+};
+
+// Expose to the world
+module.exports.RAIServer = RAIServer;
+module.exports.runClientMockup = require("./mockup");
+
+/**
+ * <p>Creates instance of RAIServer</p>
+ *
+ * <p>Options object has the following properties:</p>
+ *
+ * <ul>
+ *   <li><b>debug</b> - if set to true print traffic to console</li>
+ *   <li><b>disconnectOnTimeout</b> - if set to true close the connection on disconnect</li>
+ *   <li><b>secureConnection</b> - if set to true close the connection on disconnect</li>
+ *   <li><b>credentials</b> - credentials for secureConnection and STARTTLS</li>
+ *   <li><b>timeout</b> - timeout in milliseconds for disconnecting the client,
+ *       defaults to 0 (no timeout)</li>
+ * </ul>
+ *
+ * <p><b>Events</b></p>
+ *
+ * <ul>
+ *     <li><b>'connect'</b> - emitted if a client connects to the server, param
+ *         is a client ({@link RAISocket}) object</li>
+ *     <li><b>'error'</b> - emitted on error, has an error object as a param</li>
+ * </ul>
+ *
+ * @constructor
+ * @param {Object} [options] Optional options object
+ */
+function RAIServer(options){
+    EventEmitter.call(this);
+
+    this.options = options || {};
+
+    if (this.options.debug) {
+        this._logger = this.options.debug === true ? console.log : this.options.debug;
+    } else {
+        this._logger = function(){};
+    }
+
+    this._createServer();
+}
+utillib.inherits(RAIServer, EventEmitter);
+
+/**
+ * <p>Starts listening on selected port</p>
+ *
+ * @param {Number} port The port to listen
+ * @param {String} [host] The IP address to listen
+ * @param {Function} callback The callback function to be run after the server
+ * is listening, the only param is an error message if the operation failed
+ */
+RAIServer.prototype.listen = function(port, host, callback){
+    if(!callback && typeof host=="function"){
+        callback = host;
+        host = undefined;
+    }
+    this._port = port;
+    this._host = host;
+
+    this._connected = false;
+    if(callback){
+        this._server.on("listening", (function(){
+            this._connected = true;
+            callback(null);
+        }).bind(this));
+
+        this._server.on("error", (function(err){
+            if(!this._connected){
+                callback(err);
+            }
+        }).bind(this));
+    }
+
+    this._server.listen(this._port, this._host);
+};
+
+/**
+ * <p>Stops the server</p>
+ *
+ * @param {Function} callback Is run when the server is closed
+ */
+RAIServer.prototype.end = function(callback){
+    this._server.on("close", callback);
+    this._server.close();
+};
+
+/**
+ * <p>Creates a server with listener callback</p>
+ */
+RAIServer.prototype._createServer = function(){
+    if(this.options.secureConnection){
+        this._server = tlslib.createServer(
+            this.options.credentials || defaultCredentials,
+            this._serverListener.bind(this));
+    }else{
+        this._server = netlib.createServer(this._serverListener.bind(this));
+    }
+    this._server.on("error", this._onError.bind(this));
+};
+
+/**
+ * <p>Listens for errors</p>
+ *
+ * @event
+ * @param {Object} err Error object
+ */
+RAIServer.prototype._onError = function(err){
+    if(this._connected){
+        this.emit("error", err);
+    }
+};
+
+/**
+ * <p>Server listener that is run on client connection</p>
+ *
+ * <p>{@link RAISocket} object instance is created based on the client socket
+ *    and a <code>'connection'</code> event is emitted</p>
+ *
+ * @param {Object} socket The socket to the client
+ */
+RAIServer.prototype._serverListener = function(socket){
+    this._logger("CONNECTION FROM "+socket.remoteAddress);
+
+    var handler = new RAISocket(socket, this.options);
+
+    socket.on("data", handler._onReceiveData.bind(handler));
+    socket.on("end", handler._onEnd.bind(handler));
+    socket.on("error", handler._onError.bind(handler));
+    socket.on("timeout", handler._onTimeout.bind(handler));
+    socket.on("close", handler._onClose.bind(handler));
+
+    if("setKeepAlive" in socket){
+        socket.setKeepAlive(true); // plaintext server
+    }else if(socket.encrypted && "setKeepAlive" in socket.encrypted){
+        socket.encrypted.setKeepAlive(true); // secure server
+    }
+
+    this.emit("connect", handler);
+};
+
+/**
+ * <p>Creates a instance for interacting with a client (socket)</p>
+ *
+ * <p>Optional options object is the same that is passed to the parent
+ * {@link RAIServer} object</p>
+ *
+ * <p><b>Events</b></p>
+ *
+ * <ul>
+ *     <li><b>'command'</b> - emitted if a client sends a command. Gets two
+ *         params - command (String) and payload (Buffer)</li>
+ *     <li><b>'data'</b> - emitted when a chunk is received in data mode, the
+ *         param being the payload (Buffer)</li>
+ *     <li><b>'ready'</b> - emitted when data stream ends and normal command
+ *         flow is recovered</li>
+ *     <li><b>'tls'</b> - emitted when the connection is secured by TLS</li>
+ *     <li><b>'error'</b> - emitted when an error occurs. Connection to the
+ *         client is disconnected automatically. Param is an error object.</l>
+ *     <li><b>'timeout'</b> - emitted when a timeout occurs. Connection to the
+ *         client is disconnected automatically if disconnectOnTimeout option
+ *         is set to true.</l>
+ *     <li><b>'end'</b> - emitted when the client disconnects</l>
+ * </ul>
+ *
+ * @constructor
+ * @param {Object} socket Socket for the client
+ * @param {Object} [options] Optional options object
+ */
+function RAISocket(socket, options){
+    EventEmitter.call(this);
+
+    this.socket = socket;
+    this.options = options || {};
+
+    if (this.options.debug) {
+        this._logger = this.options.debug === true ? console.log : this.options.debug;
+    } else {
+        this._logger = function(){};
+    }
+
+    this.remoteAddress = socket.remoteAddress;
+
+    this._dataMode = false;
+    this._endDataModeSequence = "\r\n.\r\n";
+    this._endDataModeSequenceRegEx = /\r\n\.\r\n/;
+
+    this.secureConnection = !!this.options.secureConnection;
+    this._destroyed = false;
+    this._remainder = "";
+
+    this._ignore_data = false;
+
+    if(this.options.timeout){
+        socket.setTimeout(this.options.timeout);
+    }
+}
+utillib.inherits(RAISocket, EventEmitter);
+
+/**
+ * <p>Sends some data to the client. <code><CR><LF></code> is automatically appended to
+ *    the data</p>
+ *
+ * @param {String|Buffer} data Data to be sent to the client
+ */
+RAISocket.prototype.send = function(data){
+    var buffer;
+    if(data instanceof Buffer || data instanceof SlowBuffer){
+        buffer = new Buffer(data.length+2);
+        buffer[buffer.length-2] = 0xD;
+        buffer[buffer.length-1] = 0xA;
+        data.copy(buffer);
+    }else{
+        buffer = new Buffer((data || "").toString()+"\r\n", "binary");
+    }
+
+    this._logger("OUT: \"" +buffer.toString("utf-8").trim()+"\"");
+
+    if(this.socket && this.socket.writable){
+        this.socket.write(buffer);
+    }else{
+        this.socket.end();
+    }
+};
+
+/**
+ * <p>Instructs the server to be listening for mixed data instead of line based
+ *    commands</p>
+ *
+ * @param {String} [sequence="."] - optional sequence on separate line for
+ *        matching the data end
+ */
+RAISocket.prototype.startDataMode = function(sequence){
+    this._dataMode = true;
+    if(sequence){
+        sequence = sequence.replace(/([\.\=\(\)\-\?\*\\\[\]\^\+\:\|\,])/g, "\\$1");
+        this._endDataModeSequence = "\r\n"+sequence+"\r\n";
+        this._endDataModeSequenceRegEx = new RegExp("\\r\\n" + sequence + "\\r\\n");
+    }
+};
+
+/**
+ * <p>Instructs the server to upgrade the connection to secure TLS connection</p>
+ *
+ * <p>Fires <code>callback</code> on successful connection upgrade if set,
+ * otherwise emits <code>'tls'</code></p>
+ *
+ * @param {Object} [credentials] An object with PEM encoded key and
+ *        certificate <code>{key:"---BEGIN...", cert:"---BEGIN..."}</code>,
+ *        if not set autogenerated values will be used.
+ * @param {Function} [callback] If calback is set fire it after successful connection
+ *        upgrade, otherwise <code>'tls'</code> is emitted
+ */
+RAISocket.prototype.startTLS = function(credentials, callback){
+
+    if(this.secureConnection){
+        return this._onError(new Error("Secure connection already established"));
+    }
+
+    if(!callback && typeof credentials == "function"){
+        callback = credentials;
+        credentials = undefined;
+    }
+
+    credentials = credentials || this.options.credentials || defaultCredentials;
+
+    this._ignore_data = true;
+
+    var secure_connector = starttls(this.socket, credentials, (function(ssl_socket){
+
+        if(!ssl_socket.authorized){
+            this._logger("WARNING: TLS ERROR ("+ssl_socket.authorizationError+")");
+        }
+
+        this._remainder = "";
+        this._ignore_data = false;
+
+        this.secureConnection = true;
+
+        this.socket = ssl_socket;
+        this.socket.on("data", this._onReceiveData.bind(this));
+        this.socket.on("error", this._onError.bind(this));
+
+        this._logger("TLS CONNECTION STARTED");
+
+        if(callback){
+            callback();
+        }else{
+            this.emit("tls");
+        }
+
+    }).bind(this));
+
+    secure_connector.on("error", (function(err){
+        this._onError(err);
+    }).bind(this));
+};
+
+/**
+ * <p>Closes the connection to the client</p>
+ */
+RAISocket.prototype.end = function(){
+    this.socket.end();
+};
+
+/**
+ * <p>Called when a chunk of data arrives from the client. If currently in data
+ * mode, transmit the data otherwise send it to <code>_processData</code></p>
+ *
+ * @event
+ * @param {Buffer|String} chunk Data sent by the client
+ */
+RAISocket.prototype._onReceiveData = function(chunk){
+
+    if(this._ignore_data){ // if currently setting up TLS connection
+        return;
+    }
+
+    var str = typeof chunk=="string"?chunk:chunk.toString("binary"),
+        dataRemainderMatch, data;
+
+    if(this._dataMode){
+
+        // prefix the incoming chunk with the remainder of the previous chunk
+        str = this._remainder + str;
+        this._remainder = "";
+
+        // check if a data end sequence is found from the data
+        if((dataRemainderMatch = str.match(this._endDataModeSequenceRegEx))){
+            // if the sequence is not on byte 0 emit remaining data
+            if(dataRemainderMatch.index){
+                data = new Buffer(str.substr(0, dataRemainderMatch.index), "binary");
+                this._logger("DATA:", data.toString("utf-8"));
+                this.emit("data", data);
+            }
+            // emit data ready
+            this.emit("ready");
+            this._dataMode = false;
+            // send the remaining data for processing
+            this._processData(str.substr(dataRemainderMatch.index + dataRemainderMatch[0].length)+"\r\n");
+        }else{
+            // check if there's not something in the end of the data that resembles
+            // end sequence - if so, cut it off and save it to the remainder
+            for(var i = Math.min(this._endDataModeSequence.length-1, str.length); i>0; i--){
+                if(str.substr(-i) == this._endDataModeSequence.substr(0, i)){
+                    this._remainder = str.substr(-i);
+                    str = str.substr(0, str.length - i);
+                }
+            }
+
+            // if there's some data left, emit it
+            if(str.length){
+                data = new Buffer(str, "binary");
+                this._logger("DATA:", data.toString("utf-8"));
+                this.emit("data", data);
+            }
+        }
+    }else{
+        // Not in data mode, process as command
+        this._processData(str);
+    }
+};
+
+/**
+ * <p>Processed incoming command lines and emits found data as
+ * <code>'command'</code> with the command name as the first param and the rest
+ * of the data as second (Buffer)</p>
+ *
+ * @param {String} str Binary string to be processed
+ */
+RAISocket.prototype._processData = function(str){
+    var lines = (this._remainder+str).split("\r\n"),
+        match, command;
+
+    this._remainder = lines.pop();
+
+    for(var i=0, len = lines.length; i<len; i++){
+        if(this._ignore_data){
+            // If TLS upgrade is initiated do not process current buffer
+            this._remainder = "";
+            break;
+        }
+        if(!this._dataMode){
+            if((match = lines[i].match(/\s*[\S]+\s?/)) || (match = lines[i].match(/^$/))){
+                command = (match[0] || "").trim();
+                this._logger("COMMAND:", lines[i]);
+                this.emit("command", command, new Buffer(lines[i].substr(match.index + match[0].length), "binary"));
+            }
+        }else{
+            if(this._remainder){
+                this._remainder += "\r\n";
+            }
+            this._onReceiveData(lines.slice(i).join("\r\n"));
+            break;
+        }
+    }
+};
+
+/**
+ * <p>Called when the connection is or is going to be ended</p>
+ */
+RAISocket.prototype._destroy = function(){
+    if(this._destroyed){
+        return;
+    }
+    this._destroyed = true;
+
+    this.removeAllListeners();
+};
+
+/**
+ * <p>Called when the connection is ended. Emits <code>'end'</code></p>
+ *
+ * @event
+ */
+RAISocket.prototype._onEnd = function(){
+    this.emit("end");
+    this._destroy();
+};
+
+/**
+ * <p>Called when an error has appeared. Emits <code>'error'</code> with
+ * the error object as a parameter.</p>
+ *
+ * @event
+ * @param {Object} err Error object
+ */
+RAISocket.prototype._onError = function(err){
+    this.emit("error", err);
+    this._destroy();
+};
+
+/**
+ * <p>Called when a timeout has occured. Connection will be closed and
+ * <code>'timeout'</code> is emitted.</p>
+ *
+ * @event
+ */
+RAISocket.prototype._onTimeout = function(){
+    if(this.options.disconnectOnTimeout){
+        if(this.socket && !this.socket.destroyed){
+            this.socket.end();
+        }
+        this.emit("timeout");
+        this._destroy();
+    }else{
+        this.emit("timeout");
+    }
+};
+
+/**
+ * <p>Called when the connection is closed</p>
+ *
+ * @event
+ * @param {Boolean} hadError did the connection end because of an error?
+ */
+RAISocket.prototype._onClose = function(/* hadError */){
+    this._destroy();
+};
diff --git a/lib/starttls.js b/lib/starttls.js
new file mode 100644
index 0000000..676236d
--- /dev/null
+++ b/lib/starttls.js
@@ -0,0 +1,111 @@
+"use strict";
+
+// SOURCE: https://gist.github.com/848444
+
+// Target API:
+//
+//  var s = require('net').createStream(25, 'smtp.example.com');
+//  s.on('connect', function() {
+//   require('starttls')(s, options, function() {
+//      if (!s.authorized) {
+//        s.destroy();
+//        return;
+//      }
+//
+//      s.end("hello world\n");
+//    });
+//  });
+//
+//
+
+/**
+ * @namespace STARTTLS module
+ * @name starttls
+ */
+module.exports.starttls = starttls;
+
+/**
+ * <p>Upgrades a socket to a secure TLS connection</p>
+ *
+ * @memberOf starttls
+ * @param {Object} socket Plaintext socket to be upgraded
+ * @param {Object} options Certificate data for the server
+ * @param {Function} callback Callback function to be run after upgrade
+ */
+function starttls(socket, options, callback) {
+    var sslcontext, pair, cleartext;
+
+    socket.removeAllListeners("data");
+    sslcontext = require('crypto').createCredentials(options);
+    pair = require('tls').createSecurePair(sslcontext, true);
+    cleartext = pipe(pair, socket);
+
+    pair.on('secure', function() {
+        var verifyError = (pair._ssl || pair.ssl).verifyError();
+
+        if (verifyError) {
+            cleartext.authorized = false;
+            cleartext.authorizationError = verifyError;
+        } else {
+            cleartext.authorized = true;
+        }
+
+        callback(cleartext);
+    });
+
+    cleartext._controlReleased = true;
+    return pair;
+}
+
+function forwardEvents(events, emitterSource, emitterDestination) {
+    var map = [], handler;
+
+    events.forEach(function(name){
+        handler = function(){
+            this.emit.apply(this, arguments);
+        }.bind(emitterDestination, name);
+
+        map.push(name);
+        emitterSource.on(name, handler);
+    });
+
+    return map;
+}
+
+function removeEvents(map, emitterSource) {
+    for(var i = 0, len = map.length; i < len; i++){
+        emitterSource.removeAllListeners(map[i]);
+    }
+}
+
+function pipe(pair, socket) {
+    pair.encrypted.pipe(socket);
+    socket.pipe(pair.encrypted);
+
+    pair.fd = socket.fd;
+
+    var cleartext = pair.cleartext;
+
+    cleartext.socket = socket;
+    cleartext.encrypted = pair.encrypted;
+    cleartext.authorized = false;
+
+    function onerror(e) {
+        if (cleartext._controlReleased) {
+            cleartext.emit('error', e);
+        }
+    }
+
+    var map = forwardEvents(["timeout", "end", "close", "drain", "error"], socket, cleartext);
+
+    function onclose() {
+        socket.removeListener('error', onerror);
+        socket.removeListener('close', onclose);
+        removeEvents(map,socket);
+    }
+
+    socket.on('error', onerror);
+    socket.on('close', onclose);
+
+    return cleartext;
+}
\ No newline at end of file
diff --git a/package.json b/package.json
new file mode 100644
index 0000000..e64f713
--- /dev/null
+++ b/package.json
@@ -0,0 +1,31 @@
+{
+    "name": "rai",
+    "description": "Request-Answer-Interface for generating text based command servers (SMTP, POP etc)",
+    "version": "0.1.12",
+    "author" : "Andris Reinman",
+    "maintainers":[
+        {
+            "name":"andris",
+            "email":"andris at node.ee"
+        }
+    ],
+    "repository" : {
+        "type" : "git",
+        "url" : "http://github.com/andris9/rai.git"
+    },
+    "scripts":{
+        "test": "nodeunit test"
+    },
+    "main" : "./lib/rai",
+    "licenses" : [
+        {
+            "type": "MIT",
+            "url": "http://github.com/andris9/rai/blob/master/LICENSE"
+        }
+    ],
+    "devDependencies": {
+        "nodeunit": "*"
+    },
+    "engines": ["node >=0.4.0"],
+    "keywords": ["servers", "text-based"]
+}
diff --git a/test/rai.js b/test/rai.js
new file mode 100644
index 0000000..b86997f
--- /dev/null
+++ b/test/rai.js
@@ -0,0 +1,750 @@
+process.env.NODE_TLS_REJECT_UNAUTHORIZED = "0";
+
+var RAIServer = require("../lib/rai").RAIServer,
+    runClientMockup = require("../lib/rai").runClientMockup,
+    testCase = require('nodeunit').testCase,
+    utillib = require("util"),
+    netlib = require("net"),
+    crypto = require("crypto"),
+    tlslib = require("tls");
+
+var PORT_NUMBER = 8397;
+
+// monkey patch net and tls to support nodejs 0.4
+if(!netlib.connect && netlib.createConnection){
+    netlib.connect = netlib.createConnection;
+}
+
+if(!tlslib.connect && tlslib.createConnection){
+    tlslib.connect = tlslib.createConnection;
+}
+
+exports["General tests"] = {
+    "Create and close a server": function(test){
+        var server = new RAIServer();
+        server.listen(PORT_NUMBER, function(err){
+            test.ifError(err);
+            server.end(function(){
+                test.ok(1, "Server closed");
+                test.done();
+            });
+        });
+    },
+    "Create a secure server": function(test){
+        var server = new RAIServer({secureConnection: true});
+        server.listen(PORT_NUMBER, function(err){
+            test.ifError(err);
+            server.end(function(){
+                test.ok(1, "Server closed");
+                test.done();
+            });
+        });
+    },
+    "Duplicate server fails": function(test){
+        var server = new RAIServer();
+        server.listen(PORT_NUMBER, function(err){
+            test.ifError(err);
+            
+            var duplicate = new RAIServer();
+            duplicate.listen(PORT_NUMBER, function(err){
+                test.ok(err, "Responds with error");
+                server.end(function(){
+                    test.ok(1, "Server closed");
+                    test.done();
+                });
+            });
+            
+        });
+    },
+    "Connection event": function(test){
+        var server = new RAIServer();
+        test.expect(3);
+        server.listen(PORT_NUMBER, function(err){
+            
+            server.on("connect", function(socket){
+                test.ok(socket, "Client connected");
+                
+                socket.on("end", function(){
+                    test.ok(1, "Connection closed");
+                    
+                    server.end(function(){
+                        test.done();
+                    });
+                });
+                
+                socket.on("error", function(err){
+                    test.isError(err);
+                });
+            });
+            
+            var client = netlib.connect(PORT_NUMBER, function(){
+                test.ok(1, "Connected to server");
+                client.end();
+            });
+
+        });
+    },
+    "Close client socket":  function(test){
+        var server = new RAIServer();
+        test.expect(4);
+        server.listen(PORT_NUMBER, function(err){
+            
+            server.on("connect", function(socket){
+                test.ok(socket, "Client connected");
+                
+                socket.on("end", function(){
+                    test.ok(1, "Connection closed");
+                    
+                    server.end(function(){
+                        test.done();
+                    });
+                });
+                
+                socket.on("error", function(err){
+                    test.isError(err);
+                });
+                
+                socket.end();
+            });
+            
+            var client = netlib.connect(PORT_NUMBER, function(){
+                test.ok(1, "Connected to server");
+            });
+            client.on("end", function(){
+                test.ok(1, "Connection closed by host");
+            });
+
+        });
+    },
+    "Send data to client":  function(test){
+        var server = new RAIServer();
+        server.listen(PORT_NUMBER, function(err){
+            
+            server.on("connect", function(socket){
+                
+                socket.send("HELLO");
+
+                socket.on("end", function(){
+                    server.end(function(){
+                        test.done();
+                    });
+                });
+                
+                socket.on("error", function(err){
+                    test.isError(err);
+                });
+            });
+            
+            var client = netlib.connect(PORT_NUMBER, function(){
+                client.on("data", function(chunk){
+                    test.equal(chunk.toString(), "HELLO\r\n");
+                    client.end();
+                });
+            });
+
+        });
+    }
+};
+
+exports["Secure connection"] = {
+    "STARTTLS with event":  function(test){
+        var server = new RAIServer();
+        server.listen(PORT_NUMBER, function(err){
+            
+            test.expect(2);
+            
+            server.on("connect", function(socket){
+                
+                socket.startTLS();
+                socket.on("tls", function(){
+                    test.ok(1, "Secure connection opened");
+                    socket.send("TEST");
+                });
+                
+                socket.on("end", function(){
+                    server.end(function(){
+                        test.done();
+                    });
+                });
+                
+                socket.on("error", function(err){
+                    test.isError(err);
+                });
+            });
+            
+            var client = netlib.connect(PORT_NUMBER, function(){
+                var sslcontext = crypto.createCredentials();
+                var pair = tlslib.createSecurePair(sslcontext, false);
+                
+                pair.encrypted.pipe(client);
+                client.pipe(pair.encrypted);
+                pair.fd = client.fd;
+                
+                pair.on("secure", function(){
+                    pair.cleartext.on("data", function(chunk){
+                        test.equal(chunk.toString(), "TEST\r\n");
+                        pair.cleartext.end();
+                    });
+                });
+            });
+
+        });
+    },
+    "STARTTLS Callback":  function(test){
+        var server = new RAIServer();
+        server.listen(PORT_NUMBER, function(err){
+            
+            test.expect(2);
+            
+            server.on("connect", function(socket){
+                
+                socket.startTLS(function(){
+                    test.ok(1, "Secure connection opened");
+                    socket.send("TEST");
+                });
+                
+                socket.on("tls", function(){
+                    test.ok(0, "Should not occur");
+                });
+                
+                socket.on("end", function(){
+                    server.end(function(){
+                        test.done();
+                    });
+                });
+                
+                socket.on("error", function(err){
+                    test.isError(err);
+                });
+            });
+            
+            var client = netlib.connect(PORT_NUMBER, function(){
+                var sslcontext = crypto.createCredentials();
+                var pair = tlslib.createSecurePair(sslcontext, false);
+                
+                pair.encrypted.pipe(client);
+                client.pipe(pair.encrypted);
+                pair.fd = client.fd;
+                
+                pair.on("secure", function(){
+                    pair.cleartext.on("data", function(chunk){
+                        test.equal(chunk.toString(), "TEST\r\n");
+                        pair.cleartext.end();
+                    });
+                });
+            });
+
+        });
+    },
+    "STARTTLS clears command buffer":  function(test){
+        var server = new RAIServer();
+        server.listen(PORT_NUMBER, function(err){
+            
+            test.expect(2);
+            
+            server.on("connect", function(socket){
+                
+                socket.on("command", function(command){
+                    if(command == "STARTTLS"){
+                        socket.startTLS();
+                        socket.send("OK");
+                    }else if(command == "KILL"){
+                        test.ok(0, "Should not occur");
+                    }else if(command == "OK"){
+                        test.ok(1, "OK");
+                    }
+                    
+                });
+                
+                socket.on("tls", function(){
+                    test.ok(1, "Secure connection opened");
+                    socket.send("TEST");
+                });
+                
+                socket.on("end", function(){
+                    server.end(function(){
+                        test.done();
+                    });
+                });
+                
+                socket.on("error", function(err){
+                    test.isError(err);
+                });
+            });
+            
+            var client = netlib.connect(PORT_NUMBER, function(){
+                
+                client.write("STARTTLS\r\nKILL\r\n");
+                
+                client.on("data", function(chunk){
+                    if(chunk.toString("utf-8").trim() == "OK"){
+                        var sslcontext = crypto.createCredentials();
+                        var pair = tlslib.createSecurePair(sslcontext, false);
+                        
+                        pair.encrypted.pipe(client);
+                        client.pipe(pair.encrypted);
+                        pair.fd = client.fd;
+                        
+                        pair.on("secure", function(){
+                            pair.cleartext.write("OK\r\n");
+                            pair.cleartext.end();
+                        });
+                    }
+                });
+                
+            });
+
+        });
+    },
+    "STARTTLS on secure server fails":  function(test){
+        var server = new RAIServer({secureConnection: true});
+        server.listen(PORT_NUMBER, function(err){
+            
+            test.expect(2);
+            server.on("connect", function(socket){
+                
+                socket.on("error", function(err){
+                    test.ok(err);
+                    socket.end();
+                    server.end(function(){
+                        test.done();
+                    });
+                });
+                
+                socket.on("command", (function(command){
+                    process.nextTick(socket.startTLS.bind(socket, function(){
+                        test.ok(false, "Secure connection opened"); // should not occur
+                        server.end(function(){
+                            test.done();
+                        });
+                    }));
+                    
+                }).bind(this));
+                
+                socket.on("tls", function(){
+                    test.ok(0, "Should not occur");
+                });
+                
+            });
+            
+            var client = tlslib.connect(PORT_NUMBER, function(){
+                test.ok(true);
+                client.write("HELLO!\r\n");
+            });
+
+        });
+    }
+};
+
+exports["Client commands"] = {
+    "Receive Simple Command":  function(test){
+        var server = new RAIServer();
+        server.listen(PORT_NUMBER, function(err){
+            
+            server.on("connect", function(socket){
+                
+                socket.on("command", function(command, payload){
+                    test.equal(command, "STATUS");
+                    test.equal(payload.toString(), "");
+                    socket.end();
+                    server.end(function(){
+                        test.done();
+                    });
+                });
+                
+                socket.on("error", function(err){
+                    test.isError(err);
+                });
+            });
+            
+            var client = netlib.connect(PORT_NUMBER, function(){
+                client.write("STATUS\r\n");
+            });
+
+        });
+    },
+    "Receive Command with payload":  function(test){
+        var server = new RAIServer();
+        server.listen(PORT_NUMBER, function(err){
+            
+            server.on("connect", function(socket){
+                
+                socket.on("command", function(command, payload){
+                    test.equal(command, "MAIL");
+                    test.equal(payload.toString(), "TO:");
+                    socket.end();
+                    
+                    server.end(function(){
+                        test.done();
+                    });
+                });
+                
+                socket.on("error", function(err){
+                    test.isError(err);
+                });
+            });
+            
+            var client = netlib.connect(PORT_NUMBER, function(){
+                client.write("MAIL TO:\r\n");
+            });
+
+        });
+    }
+};
+
+exports["Data mode"] = {
+    "DATA mode": function(test){
+        var server = new RAIServer(),
+            datapayload = "tere\r\nvana kere";
+        server.listen(PORT_NUMBER, function(err){
+            
+            server.on("connect", function(socket){
+                
+                socket.startDataMode();
+
+                test.expect(2);
+
+                socket.on("data", function(chunk){
+                    test.equal(datapayload, chunk.toString());
+                });
+                
+                socket.on("ready", function(){
+                    test.ok(1,"Data ready");
+                    server.end(function(){
+                        test.done();
+                    });
+                });
+                
+                socket.on("error", function(err){
+                    test.isError(err);
+                });
+            });
+            
+            var client = netlib.connect(PORT_NUMBER, function(){
+                client.write(datapayload+"\r\n.\r\n");
+                client.end();
+            });
+
+        });
+    },
+    "Small chunks DATA mode": function(test){
+        var server = new RAIServer(),
+            datapayload = "tere\r\nvana kere õäöü\r\n.\r",
+            databytes = [],
+            fullpayload = datapayload+"\r\n.\r\n";
+        server.listen(PORT_NUMBER, function(err){
+            
+            server.on("connect", function(socket){
+                
+                socket.startDataMode();
+
+                test.expect(1);
+
+                socket.on("data", function(chunk){
+                    databytes = databytes.concat(Array.prototype.slice.call(chunk));
+                });
+                
+                socket.on("ready", function(){
+                    test.equal(new Buffer(databytes).toString("utf-8"), datapayload);
+                    server.end(function(){
+                        test.done();
+                    });
+                });
+                
+                socket.on("error", function(err){
+                    test.isError(err);
+                });
+                
+                for(var i=0, len = fullpayload.length; i<len; i++){
+                    socket._onReceiveData(new Buffer(fullpayload.charAt(i), "utf-8").toString("binary"));
+                }
+                
+            });
+            
+            var client = netlib.connect(PORT_NUMBER, function(){
+                client.end();
+            });
+
+        });
+    }
+};
+
+exports["Pipelining support"] = {
+    "Pipelining": function(test){
+        var server = new RAIServer();
+        server.listen(PORT_NUMBER, function(err){
+            
+            test.expect(8);
+            
+            server.on("connect", function(socket){
+                
+                socket.on("command", function(command, payload){
+                    if(command == "STATUS"){
+                        test.ok(1, "Status received");
+                    }else if(command=="DATA"){
+                        test.ok(1, "data command received");
+                        socket.startDataMode();
+                    }else if(command=="END"){
+                        test.ok(1, "all received");
+                    }else{
+                        test.ok(0, "Unexpected command: "+command);
+                    }
+                });
+                
+                socket.on("data", function(chunk){
+                    test.equal(chunk.toString(), "TE\r\nST");
+                });
+                
+                socket.on("ready", function(){
+                    test.ok(1, "Data mode ended");
+                });
+                
+                socket.on("end", function(){
+                    test.ok(1, "All ready");
+                    server.end(function(){
+                        test.done();
+                    });
+                });
+                
+                socket.on("error", function(err){
+                    test.isError(err);
+                });
+            });
+            
+            var client = netlib.connect(PORT_NUMBER, function(){
+                client.write("STATUS\r\nSTATUS\r\nSTATUS\r\nDATA\r\nTE\r\nST\r\n.\r\nEND\r\n");
+                client.end();
+            });
+
+        });
+    }
+};
+
+exports["Timeout tests"] = {
+    "Timeout": function(test){
+        var server = new RAIServer({timeout: 300, disconnectOnTimeout: true});
+        test.expect(3);
+        server.listen(PORT_NUMBER, function(err){
+            
+            server.on("connect", function(socket){
+                test.ok(socket, "Client connected");
+                
+                socket.on("timeout", function(){
+                    test.ok(1, "Connection closed");
+                    
+                    server.end(function(){
+                        test.done();
+                    });
+                });
+                
+                socket.on("error", function(err){
+                    test.isError(err);
+                });
+            });
+            
+            var client = netlib.connect(PORT_NUMBER, function(){
+                test.ok(1, "Connected to server");
+            });
+
+        });
+    },
+    "Timeout with TLS":  function(test){
+        var server = new RAIServer({timeout: 300, disconnectOnTimeout: true});
+        server.listen(PORT_NUMBER, function(err){
+            
+            test.expect(3);
+            
+            server.on("connect", function(socket){
+                
+                socket.startTLS();
+                socket.on("tls", function(){
+                    test.ok(1, "Secure connection opened");
+                    socket.send("TEST");
+                });
+                
+                socket.on("timeout", function(){
+                    test.ok(1, "Timeout occurred");
+                    server.end(function(){
+                        test.done();
+                    });
+                });
+                
+                socket.on("error", function(err){
+                    test.isError(err);
+                });
+            });
+            
+            var client = netlib.connect(PORT_NUMBER, function(){
+                var sslcontext = crypto.createCredentials();
+                var pair = tlslib.createSecurePair(sslcontext, false);
+                
+                pair.encrypted.pipe(client);
+                client.pipe(pair.encrypted);
+                pair.fd = client.fd;
+                
+                pair.on("secure", function(){
+                    test.ok(1, "secure connection");
+                });
+            });
+
+        });
+    } 
+};
+
+exports["Client Mockup"] = {
+    "All command": function(test){
+        var server = new RAIServer();
+        server.listen(PORT_NUMBER, function(err){
+            server.on("connect", function(socket){
+                socket.send("220 Welcome");
+                socket.on("command", function(command, payload){
+                    switch(command) {
+                        case "HELO": socket.send("250 HI"); break;
+                        case "NOOP": socket.send("250 OK"); break;
+                        case "QUIT": socket.send("221 Bye"); socket.end(); break;
+                        default: socket.send("500");
+                    }
+                });
+
+                socket.on("error", function(err){
+                    test.isError(err);
+                });
+            });
+
+            var cmds = ["HELO", "NOOP", "QUIT"];
+            runClientMockup(PORT_NUMBER, "localhost", cmds, function(lastResponse, allResponses){
+                allResponses = allResponses.map(function(value) { return value.toString("utf-8"); });
+                test.deepEqual(allResponses, [ "220 Welcome\r\n", "250 HI\r\n", "250 OK\r\n", "221 Bye\r\n" ]);
+                server.end(function(){
+                    test.done();
+                });
+
+            });
+
+        });
+    },
+    "Last commands":  function(test){
+        var server = new RAIServer();
+        server.listen(PORT_NUMBER, function(err){
+            server.on("connect", function(socket){
+                socket.send("220 HI");
+                socket.on("command", function(command, payload){
+                    switch(command) {
+                        case "HELO": socket.send("250 HI"); break;
+                        case "NOOP": socket.send("250 OK"); break;
+                        case "QUIT": socket.send("221 Bye"); socket.end(); break;
+                        default: socket.send("500");
+                    }
+                });
+
+                socket.on("error", function(err){
+                    test.isError(err);
+                });
+            });
+
+            var cmds = ["HELO", "NOOP", "QUIT"];
+            runClientMockup(PORT_NUMBER, "localhost", cmds, function(lastResponse){
+                test.equal(lastResponse.toString("utf-8"), "221 Bye\r\n");
+                server.end(function(){
+                    test.done();
+                });
+
+            });
+
+        });
+    },
+    "All command(STARTTLS)": function(test){
+        var server = new RAIServer();
+        server.listen(PORT_NUMBER, function(err){
+
+            test.expect(2);
+
+            server.on("connect", function(socket){
+                socket.tlsStatus = 0;
+                socket.send("220 Welcome");
+                socket.on("command", function(command, payload){
+                    switch(command){
+                        case "EHLO":
+                            if(socket.tlsStatus===0){
+                                socket.send("250-HI\r\n250 STARTTLS");
+                            }else{
+                                socket.send("250 HI");
+                            }
+                            break;
+                        case "NOOP": socket.send("250 OK"); break;
+                        case "QUIT": socket.send("221 Bye"); socket.end(); break;
+                        case "STARTTLS": socket.startTLS(); socket.send("220 Go ahead"); break;
+                        default: socket.send("500");
+                    }
+                });
+
+                socket.on("tls", function(){
+                    test.ok(1, "Secure connection opened");
+                    socket.tlsStatus = 1;
+                });
+
+                socket.on("error", function(err){
+                    test.isError(err);
+                });
+            });
+
+            var cmds = ["EHLO", "STARTTLS", "EHLO", "NOOP", "QUIT"];
+            runClientMockup(PORT_NUMBER, "localhost", cmds, function(lastResponse, allResponses){
+                allResponses = allResponses.map(function(value) { return value.toString("utf-8"); });
+                test.deepEqual(allResponses, ["220 Welcome\r\n", "250-HI\r\n250 STARTTLS\r\n", "220 Go ahead\r\n",
+                                              "250 HI\r\n", "250 OK\r\n", "221 Bye\r\n" ]);
+                server.end(function(){
+                    test.done();
+                });
+
+            });
+
+        });
+    },
+    "Last commands(STARTTLS)":  function(test){
+        var server = new RAIServer();
+        server.listen(PORT_NUMBER, function(err){
+
+            test.expect(2);
+
+            server.on("connect", function(socket){
+                socket.tlsStatus = 0;
+                socket.send("220 Welcome");
+                socket.on("command", function(command, payload){
+                    switch(command){
+                        case "EHLO":
+                            if(socket.tlsStatus===0){
+                                socket.send("250-HI\r\n250 STARTTLS");
+                            }else{
+                                socket.send("250 HI");
+                            }
+                            break;
+                        case "NOOP": socket.send("250 OK"); break;
+                        case "QUIT": socket.send("221 Bye"); socket.end(); break;
+                        case "STARTTLS": socket.startTLS(); socket.send("220 Go ahead"); break;
+                        default: socket.send("500");
+                    }
+                });
+
+                socket.on("tls", function(){
+                    test.ok(1, "Secure connection opened");
+                    socket.tlsStatus = 1;
+                });
+
+                socket.on("error", function(err){
+                    test.isError(err);
+                });
+            });
+
+            var cmds = ["EHLO", "STARTTLS", "EHLO", "NOOP", "QUIT"];
+            runClientMockup(PORT_NUMBER, "localhost", cmds, function(lastResponse){
+                test.equal(lastResponse.toString("utf-8"), "221 Bye\r\n");
+                server.end(function(){
+                    test.done();
+                });
+
+            });
+
+        });
+    }
+};

-- 
Alioth's /usr/local/bin/git-commit-notice on /srv/git.debian.org/git/pkg-javascript/node-rai.git



More information about the Pkg-javascript-commits mailing list