[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