PageRenderTime 39ms CodeModel.GetById 11ms RepoModel.GetById 0ms app.codeStats 0ms

/node_modules/nodemailer/node_modules/simplesmtp/node_modules/rai/lib/rai.js

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