1 /**
  2  * @fileOverview This is the main file for the RAI library to create text based servers
  3  * @author <a href="mailto:andris@node.ee">Andris Reinman</a>
  4  * @version 0.1.0
  5  */
  6 
  7 var netlib = require("net"),
  8     utillib = require("util"),
  9     EventEmitter = require('events').EventEmitter,
 10     starttls = require("./starttls"),
 11     tlslib = require("tls"),
 12     crypto = require("crypto"),
 13     fs = require("fs");
 14 
 15 // Default credentials for starting TLS server
 16 var defaultCredentials = {
 17     key: fs.readFileSync(__dirname+"/cert/key.pem"),
 18     cert: fs.readFileSync(__dirname+"/cert/cert.pem")
 19 }
 20 
 21 // Expose to the world
 22 module.exports.RAIServer = RAIServer;
 23 
 24 /**
 25  * <p>Creates instance of RAIServer</p>
 26  * 
 27  * <p>Options object has the following properties:</p>
 28  * 
 29  * <ul>
 30  *   <li><b>debug</b> - if set to true print traffic to console</li>
 31  *   <li><b>timeout</b> - timeout in milliseconds for disconnecting the client,
 32  *       defaults to 0 (no timeout)</li>
 33  * </ul>
 34  * 
 35  * <p><b>Events</b></p>
 36  * 
 37  * <ul>
 38  *     <li><b>'connect'</b> - emitted if a client connects to the server, param
 39  *         is a client ({@link RAISocket}) object</li>
 40  * </ul> 
 41  * 
 42  * @constructor
 43  * @param {Object} [options] Optional options object
 44  */
 45 function RAIServer(options){
 46     EventEmitter.call(this);
 47     
 48     this.options = options || {};
 49     
 50     this._createServer();
 51 }
 52 utillib.inherits(RAIServer, EventEmitter);
 53 
 54 /**
 55  * <p>Starts listening on selected port</p>
 56  * 
 57  * @param {Number} port The port to listen
 58  * @param {String} [host] The IP address to listen
 59  * @param {Function} callback The callback function to be run after the server
 60  * is listening, the only param is an error message if the operation failed 
 61  */
 62 RAIServer.prototype.listen = function(port, host, callback){
 63     if(!callback && typeof host=="function"){
 64         callback = host;
 65         host = undefined;
 66     }
 67     this._port = port;
 68     this._host = host;
 69     
 70     this._server.listen(port, host, function(err){
 71         if(err && !callback){
 72             this.emit("error", err);
 73         }else if(callback){
 74             callback(err || null);
 75         }
 76     });
 77 }
 78 
 79 /**
 80  * <p>Creates a server with listener callback</p> 
 81  */
 82 RAIServer.prototype._createServer = function(){
 83     this._server = netlib.createServer(this._serverListener.bind(this));
 84 }
 85 
 86 /**
 87  * <p>Server listener that is run on client connection</p>
 88  * 
 89  * <p>{@link RAISocket} object instance is created based on the client socket
 90  *    and a <code>'connection'</code> event is emitted</p>
 91  * 
 92  * @param {Object} socket The socket to the client 
 93  */
 94 RAIServer.prototype._serverListener = function(socket){
 95     if(this.options.debug){
 96         console.log("CONNECTION FROM "+socket.remoteAddress);
 97     }
 98     
 99     var handler = new RAISocket(socket, this.options);
100     
101     socket.on("data", handler._onReceiveData.bind(handler));
102     socket.on("end", handler._onEnd.bind(handler));
103     socket.on("error", handler._onError.bind(handler));
104     socket.on("timeout", handler._onTimeout.bind(handler));
105     socket.on("close", handler._onClose.bind(handler));
106 
107     this.emit("connection", handler);
108 }
109 
110 /**
111  * <p>Creates a instance for interacting with a client (socket)</p>
112  * 
113  * <p>Optional options object is the same that is passed to the parent
114  * {@link RAIServer} object</p>
115  * 
116  * <p><b>Events</b></p>
117  * 
118  * <ul>
119  *     <li><b>'command'</b> - emitted if a client sends a command. Gets two
120  *         params - command (String) and payload (Buffer)</li>
121  *     <li><b>'data'</b> - emitted when a chunk is received in data mode, the
122  *         param being the payload (Buffer)</li>
123  *     <li><b>'ready'</b> - emitted when data stream ends and normal command
124  *         flow is recovered</li>
125  *     <li><b>'tls'</b> - emitted when the connection is secured by TLS</li>
126  *     <li><b>'error'</b> - emitted when an error occurs. Connection to the
127  *         client is disconnected automatically. Param is an error object.</l>
128  *     <li><b>'timeout'</b> - emitted when a timeout occurs. Connection to the
129  *         client is disconnected automatically.</l>
130  *     <li><b>'end'</b> - emitted when the client disconnects</l>
131  * </ul>
132  * 
133  * @constructor
134  * @param {Object} socket Socket for the client
135  * @param {Object} [options] Optional options object
136  */
137 function RAISocket(socket, options){
138     EventEmitter.call(this);
139     
140     this.socket = socket;
141     this.options = options || {};
142     
143     this.remoteAddress = socket.remoteAddress;
144     
145     this._dataMode = false;
146     this._endDataModeSequence = /\r\n\.\r\n|^\.\r\n/;
147     
148     this._secureConnection = false;
149     this._destroyed = false;
150     this._remainder = "";
151     
152     this._ignore_data = false;
153     
154     if(this.options.timeout){
155         socket.setTimeout(this.options.timeout);
156     }
157 }
158 utillib.inherits(RAISocket, EventEmitter);
159 
160 /**
161  * <p>Sends some data to the client. <CR><LF> is automatically appended to
162  *    the data</p>
163  * 
164  * @param {String|Buffer} data Data to be sent to the client
165  */
166 RAISocket.prototype.send = function(data){
167     var buffer;
168     if(data instanceof Buffer || (typeof SlowBuffer != "undefined" && data instanceof SlowBuffer)){
169         buffer = new Buffer(data.length+2);
170         buffer[buffer.length-2] = 0xD;
171         buffer[buffer.length-1] = 0xA;
172         data.copy(buffer);
173     }else{
174         buffer = new Buffer((data || "").toString()+"\r\n", "binary");
175     }
176     
177     if(this.options.debug){
178         console.log("OUT: \"" +buffer.toString("utf-8").trim()+"\"");
179     }
180     
181     this.socket.write(buffer);
182 }
183 
184 /**
185  * <p>Instructs the server to be listening for mixed data instead of line based
186  *    commands</p>
187  * 
188  * @param {String|RegExp} [sequence="\r\n.\r\n"] - optional sequence for
189  *        matching the data end 
190  */
191 RAISocket.prototype.startDataMode = function(sequence){
192     this._dataMode = true;
193     if(sequence){
194         this._endDataModeSequence = typeof sequence == "string" ? new RegExp(sequence) : sequence;
195     }
196 }
197 
198 /**
199  * <p>Instructs the server to upgrade the connection to secure TLS connection</p>
200  * 
201  * <p>Emits <code>'tls'</code> on successful upgrade</p>
202  * 
203  * @param {Object} [credentials] - An object with PEM encoded key and 
204  *        certificate <code>{key:"---BEGIN...", cert:"---BEGIN..."}</code>,
205  *        if not set autogenerated values will be used.
206  */
207 RAISocket.prototype.startTLS = function(credentials){
208     if(this._secureConnection){
209         this._onError(new Error("Secure connection already established"));
210     }
211     
212     credentials = credentials || defaultCredentials;
213     
214     this._ignore_data = true;
215     
216     var secure_connector = starttls(this.socket, credentials, (function(ssl_socket){
217 
218         if(this.options.debug && !ssl_socket.authorized){
219             console.log("WARNING: TLS ERROR ("+ssl_socket.authorizationError+")");
220         }
221         
222         this._remainder = "";
223         this._ignore_data = false;
224         
225         this._secureConnection = true;
226     
227         this.socket = ssl_socket;
228         this.socket.on("data", this._onReceiveData.bind(this));
229         
230         if(this.options.debug){
231             console.log("TLS CONNECTION STARTED");
232         }
233         
234         this.emit("tls");
235         
236     }).bind(this));
237     
238     secure_connector.on("error", (function(err){
239         this._onError(err);
240     }).bind(this));
241 }
242 
243 /**
244  * <p>Closes the connection to the client</p>
245  */
246 RAISocket.prototype.end = function(){
247     this.socket.end();
248 }
249 
250 /**
251  * <p>Called when a chunk of data arrives from the client. If currently in data
252  * mode, transmit the data otherwise send it to <code>_processData</code></p>
253  * 
254  * @param {Buffer|String} chunk Data sent by the client
255  */
256 RAISocket.prototype._onReceiveData = function(chunk){
257     if(this._ignore_data){ // if currently setting up TLS connection
258         return;
259     }
260     
261     var str = typeof chunk=="string"?chunk:chunk.toString("binary"),
262         dataEndMatch, dataRemainderMatch, data;
263     
264     if(this._dataMode){
265         
266         str = this._remainder + str;
267         
268         if(dataEndMatch = str.match(/\r\n.*?$/)){
269             // if theres a line that is not ended, keep it for later
270             this._remainder = str.substr(dataEndMatch.index);
271             str = str.substr(0, dataEndMatch.index);
272         }else{
273             this._remainder = "";
274         }
275         
276         if((dataRemainderMatch = (str+this._remainder).match(this._endDataModeSequence))){
277             if(dataRemainderMatch.index){
278                 data = new Buffer((str+this._remainder).substr(0, dataRemainderMatch.index), "binary");
279                 if(this.options.debug){
280                     console.log("DATA:", data.toString("utf-8"));
281                 }
282                 this.emit("data", data);
283             }
284             this._remainder = "";
285             this.emit("ready");
286             this._dataMode = false;
287             
288             // send the remaining data for processing
289             this._processData(str.substr(dataRemainderMatch.index + dataRemainderMatch[0].length));
290         }else{
291             data = new Buffer(str, "binary");
292             if(this.options.debug){
293                 console.log("DATA:", data.toString("utf-8"));
294             }
295             this.emit("data", data);
296         }
297     }else{
298         this._processData(str);
299     }
300     
301 }
302 
303 /**
304  * <p>Processed incoming command lines and emits found data as 
305  * <code>'command'</code> with the command name as the first param and the rest
306  * of the data as second (Buffer)</p>
307  * 
308  * @param {String} str Binary string to be processed
309  */
310 RAISocket.prototype._processData = function(str){
311     if(!str.length){
312         return;
313     }
314     var lines = (this._remainder+str).split("\r\n"),
315         match, command;
316         
317     this._remainder = lines.pop();
318     
319     for(var i=0, len = lines.length; i<len; i++){
320         if(!this._dataMode){
321             if(match = lines[i].match(/\s*[\S]+\s?/)){
322                 command = (match[0] || "").trim();
323                 if(this.options.debug){
324                     console.log("COMMAND:", lines[i]);
325                 }
326                 this.emit("command", command, new Buffer(lines[i].substr(match.index + match[0].length), "binary"));
327             }
328         }else{
329             if(this._remainder){
330                 this._remainder += "\r\n";
331             }
332             this._onReceiveData(lines.slice(i).join("\r\n"));
333             break;
334         }
335     }
336     
337 }
338 
339 /**
340  * <p>Called when the connection is or is going to be ended</p> 
341  */
342 RAISocket.prototype._destroy = function(){
343     if(this._destroyed)return;
344     this._destroyed = true;
345     
346     this.removeAllListeners();
347 }
348 
349 /**
350  * <p>Called when the connection is ended. Emits <code>'end'</code></p>
351  */
352 RAISocket.prototype._onEnd = function(){
353     this.emit("end");
354     this._destroy();
355 }
356 
357 /**
358  * <p>Called when an error has appeared. Emits <code>'error'</code> with
359  * the error object as a parameter.</p>
360  * 
361  * @param {Object} err Error object
362  */
363 RAISocket.prototype._onError = function(err){
364     this.emit("error", err);
365     this._destroy();
366 }
367 
368 /**
369  * <p>Called when a timeout has occured. Connection will be closed and
370  * <code>'timeout'</code> is emitted.</p>
371  */
372 RAISocket.prototype._onTimeout = function(){
373     if(this.socket && !this.socket.destroyed){
374         this.socket.end();
375     }
376     this.emit("timeout");
377     this._destroy();
378 }
379 
380 /**
381  * <p>Called when the connection is closed</p>
382  * 
383  * @param {Boolean} hadError did the connection end because of an error?
384  */
385 RAISocket.prototype._onClose = function(hadError){
386     this._destroy();
387 }