'use strict';

/**
 * @fileOverview This is the main file for the simplesmtp library to create custom SMTP servers
 * @author <a href='mailto:andris@node.ee'>Andris Reinman</a>
 */

var RAIServer = require('rai').RAIServer,
    EventEmitter = require('events').EventEmitter,
    oslib = require('os'),
    utillib = require('util'),
    dnslib = require('dns'),
    crypto = require('crypto');

// expose to the world
module.exports = function(options) {
    return new SMTPServer(options);
};

/**
 * <p>Constructs a SMTP server</p>
 *
 * <p>Possible options are:</p>
 *
 * <ul>
 *     <li><b>name</b> - the hostname of the server, will be used for
 *         informational messages</li>
 *     <li><b>debug</b> - if set to true, print out messages about the connection</li>
 *     <li><b>timeout</b> - client timeout in milliseconds, defaults to 60 000</li>
 *     <li><b>secureConnection</b> - start a server on secure connection</li>
 *     <li><b>SMTPBanner</b> - greeting banner that is sent to the client on connection</li>
 *     <li><b>requireAuthentication</b> - if set to true, require that the client
 *         must authenticate itself</li>
 *     <li><b>enableAuthentication</b> - if set to true, client may authenticate itself but don't have to</li>
 *     <li><b>maxSize</b> - maximum size of an e-mail in bytes</li>
 *     <li><b>credentials</b> - TLS credentials</li>
 *     <li><b>authMethods</b> - allowed authentication methods, defaults to <code>['PLAIN', 'LOGIN']</code></li>
 *     <li><b>disableEHLO</b> - if set, support HELO only</li>
 *     <li><b>ignoreTLS</b> - if set, allow client do not use STARTTLS</li>
 *     <li><b>disableDNSValidation</b> - if set, do not validate sender domains</li>
 *     <li><b>maxClients</b> - if set, limit the number of simultaneous connections to the server</li>
 * </ul>
 *
 * @constructor
 * @namespace SMTP Server module
 * @param {Object} [options] Options object
 */
function SMTPServer(options) {
    EventEmitter.call(this);

    this.connectedClients = 0;
    this.options = options || {};
    this.options.name = this.options.name || (oslib.hostname && oslib.hostname()) ||
        (oslib.getHostname && oslib.getHostname()) ||
        '127.0.0.1';

    this.options.authMethods = (this.options.authMethods || ['PLAIN', 'LOGIN']).map(
        function(auth) {
            return auth.toUpperCase().trim();
        });

    this.options.disableEHLO = !! this.options.disableEHLO;
    this.options.ignoreTLS = !! this.options.ignoreTLS;

    this.SMTPServer = new RAIServer({
        secureConnection: !! this.options.secureConnection,
        credentials: this.options.credentials,
        timeout: this.options.timeout || 60 * 1000,
        disconnectOnTimeout: false,
        debug: !! this.options.debug
    });

    this.SMTPServer.on('connect', this._createSMTPServerConnection.bind(this));
}
utillib.inherits(SMTPServer, EventEmitter);

/**
 * Server starts listening on defined port and hostname
 *
 * @param {Number} port The port number to listen
 * @param {String} [host] The hostname to listen
 * @param {Function} callback The callback function to run when the server is listening
 */
SMTPServer.prototype.listen = function(port, host, callback) {
    this.SMTPServer.listen(port, host, callback);
};

/**
 * <p>Closes the server</p>
 *
 * @param {Function} callback The callback function to run when the server is closed
 */
SMTPServer.prototype.end = function(callback) {
    this.SMTPServer.end(callback);
};

/**
 * <p>Creates a new {@link SMTPServerConnection} object and links the main server with
 * the client socket</p>
 *
 * @param {Object} client RAISocket object to a client
 */
SMTPServer.prototype._createSMTPServerConnection = function(client) {
    new SMTPServerConnection(this, client);
};

/**
 * <p>Sets up a handler for the connected client</p>
 *
 * <p>Restarts the state and sets up event listeners for client actions</p>
 *
 * @constructor
 * @param {Object} server {@link SMTPServer} instance
 * @param {Object} client RAISocket instance for the client
 */
function SMTPServerConnection(server, client) {
    this.server = server;
    this.client = client;

    this.init();
    this.server.connectedClients++;

    if (!this.client.remoteAddress) {
        if (this.server.options.debug) {
            console.log('Client already disconnected');
        }
        this.client.end();
        return;
    }

    if (this.server.options.debug) {
        console.log('Connection from', this.client.remoteAddress);
    }

    this.client.on('timeout', this._onTimeout.bind(this));
    this.client.on('error', this._onError.bind(this));
    this.client.on('command', this._onCommand.bind(this));
    this.client.on('end', this._onEnd.bind(this));

    this.client.on('data', this._onData.bind(this));
    this.client.on('ready', this._onDataReady.bind(this));

    // Too many clients. Disallow processing
    if (this.server.options.maxClients && this.server.connectedClients > this.server.options.maxClients) {
        this.end('421 ' + this.server.options.name + ' ESMTP - Too many connections. Please try again later.');
    } else {
        // Send the greeting banner. Force ESMTP notice
        this.client.send('220 ' + this.server.options.name + ' ESMTP ' + (this.server.options.SMTPBanner || 'node.js simplesmtp'));
    }
}

/**
 * <p>Reset the envelope state</p>
 *
 * <p>If <code>keepAuthData</code> is set to true, then doesn't remove
 * authentication data</p>
 *
 * @param {Boolean} [keepAuthData=false] If set to true keep authentication data
 */
SMTPServerConnection.prototype.init = function(keepAuthData) {
    if (this.envelope === undefined) {
        this.envelope = {};
    }

    this.envelope.from = '';
    this.envelope.to = [];
    this.envelope.date = new Date();

    if (this.hostNameAppearsAs) {
        this.envelope.host = this.hostNameAppearsAs;
    }

    if (this.client.remoteAddress) {
        this.envelope.remoteAddress = this.client.remoteAddress;
    }

    if (!keepAuthData) {
        this.authentication = {
            username: false,
            authenticated: false,
            state: 'NORMAL'
        };
    }

    this.envelope.authentication = this.authentication;
};

/**
 * <p>Sends a message to the client and closes the connection</p>
 *
 * @param {String} [message] if set, send it to the client before disconnecting
 */
SMTPServerConnection.prototype.end = function(message) {
    if (message) {
        this.client.send(message);
    }
    this.client.end();
};

/**
 * <p>Will be called when the connection to the client is closed</p>
 *
 * @event
 */
SMTPServerConnection.prototype._onEnd = function() {
    if (this.server.options.debug) {
        console.log('Connection closed to', this.client.remoteAddress);
    }
    this.server.connectedClients--;
    try {
        this.client.end();
    } catch (E) {}
    this.server.emit('close', this.envelope);
};

/**
 * <p>Will be called when timeout occurs</p>
 *
 * @event
 */
SMTPServerConnection.prototype._onTimeout = function() {
    this.end('421 4.4.2 ' + this.server.options.name + ' Error: timeout exceeded');
};

/**
 * <p>Will be called when an error occurs</p>
 *
 * @event
 */
SMTPServerConnection.prototype._onError = function() {
    this.end('421 4.4.2 ' + this.server.options.name + ' Error: client error');
};

/**
 * <p>Will be called when a command is received from the client</p>
 *
 * <p>If there's curently an authentication process going on, route
 * the data to <code>_handleAuthLogin</code>, otherwise act as
 * defined</p>
 *
 * @event
 * @param {String} command Command
 * @param {Buffer} command Payload related to the command
 */
SMTPServerConnection.prototype._onCommand = function(command, payload) {
    if (this.authentication.state == 'AUTHPLAINUSERDATA') {
        this._handleAuthPlain(command.toString('utf-8').trim().split(' '));
        return;
    }

    if (this.authentication.state == 'AUTHENTICATING') {
        this._handleAuthLogin(command);
        return;
    }

    if (this.authentication.state == 'AUTHXOAUTH2') {
        this._handleAuthXOAuth2(command);
        return;
    }

    switch ((command || '').toString().trim().toUpperCase()) {

        // Should not occur too often
        case 'HELO':
            this._onCommandHELO(payload.toString('utf-8').trim());
            break;

            // Lists server capabilities
        case 'EHLO':
            if (!this.server.options.disableEHLO) {
                this._onCommandEHLO(payload.toString('utf-8').trim());
            } else {
                this.client.send('502 5.5.2 Error: command not recognized');
            }
            break;

            // Closes the connection
        case 'QUIT':
            this.end('221 2.0.0 Goodbye!');
            break;

            // Resets the current state
        case 'RSET':
            this._onCommandRSET();
            break;

            // Doesn't work for spam related purposes
        case 'VRFY':
            this.client.send('252 2.1.5 Send some mail, I\'ll try my best');
            break;

            // Initiate an e-mail by defining a sender
        case 'MAIL':
            this._onCommandMAIL(payload.toString('utf-8').trim());
            break;

            // Add recipients to the e-mail envelope
        case 'RCPT':
            this._onCommandRCPT(payload.toString('utf-8').trim());
            break;

            // Authenticate if needed
        case 'AUTH':
            this._onCommandAUTH(payload);
            break;

            // Start accepting binary data stream
        case 'DATA':
            this._onCommandDATA();
            break;

            // Upgrade connection to secure TLS
        case 'STARTTLS':
            this._onCommandSTARTTLS();
            break;

            // No operation
        case 'NOOP':
            this._onCommandNOOP();
            break;

            // No operation
        case '':
            // ignore blank lines
            break;

            // Display an error on anything else
        default:
            this.client.send('502 5.5.2 Error: command not recognized');
    }
};

/**
 * <p>Initiate an e-mail by defining a sender.</p>
 *
 * <p>This doesn't work if authorization is required but the client is
 * not logged in yet.</p>
 *
 * <p>If <code>validateSender</code> option is set to true, then emits
 * <code>'validateSender'</code> and wait for the callback before moving
 * on</p>
 *
 * @param {String} mail Address payload in the form of 'FROM:&lt;address&gt;'
 */
SMTPServerConnection.prototype._onCommandMAIL = function(mail) {
    var self = this,
        match,
        email,
        domain;

    if (!this.hostNameAppearsAs) {
        return this.client.send('503 5.5.1 Error: send HELO/EHLO first');
    }

    if (this.server.options.requireAuthentication && !this.authentication.authenticated) {
        return this.client.send('530 5.5.1 Authentication Required');
    }

    if (this.envelope.from) {
        return this.client.send('503 5.5.1 Error: nested MAIL command');
    }

    if (!(match = mail.match(/^from\:\s*<([^@>]+\@([^@>]+))>(\s|$)/i)) && !(mail.match(/^from\:\s*<>/i))) {
        return this.client.send('501 5.1.7 Bad sender address syntax');
    }

    if (this.server.options.maxSize) {
        mail.replace(/> size=(\d+)\b\s*/i, function(o, size) {
            self.envelope.messageSize = size;
        });
    }

    email = (match !== null && match[1]) || '';
    domain = ((match !== null && match[2]) || '').toLowerCase();

    this._validateAddress('sender', email, domain, function(err) {
        if (err) {
            return self.client.send(err.message);
        }
        email = email.substr(0, email.length - domain.length) + domain;
        self.envelope.from = email;
        self.client.send('250 2.1.0 Ok');
    });
};

/**
 * <p>Add recipients to the e-mail envelope</p>
 *
 * <p>This doesn't work if <code>MAIL</code> command is not yet executed</p>
 *
 * <p>If <code>validateRecipients</code> option is set to true, then emits
 * <code>'validateRecipient'</code> and wait for the callback before moving
 * on</p>
 *
 * @param {String} mail Address payload in the form of 'TO:&lt;address&gt;'
 */
SMTPServerConnection.prototype._onCommandRCPT = function(mail) {
    var self = this,
        match,
        email,
        domain;

    if (!this.envelope.from) {
        return this.client.send('503 5.5.1 Error: need MAIL command');
    }

    if (!(match = mail.match(/^to\:\s*<([^@>]+\@([^@>]+))>$/i))) {
        return this.client.send('501 5.1.7 Bad recipient address syntax');
    }

    email = match[1] || '';
    domain = (match[2] || '').toLowerCase();

    this._validateAddress('recipient', email, domain, function(err) {
        if (err) {
            return self.client.send(err.message);
        }

        // force domain part to be lowercase
        email = email.substr(0, email.length - domain.length) + domain;

        // add to recipients list
        if (self.envelope.to.indexOf(email) < 0) {
            self.envelope.to.push(email);
        }
        self.client.send('250 2.1.0 Ok');
    });

};

/**
 * <p>If <code>disableDNSValidation</code> option is set to false, then performs
 * validation via DNS lookup.
 *
 * <p>If <code>validate{type}</code> option is set to true, then emits
 * <code>'validate{type}'</code> and waits for the callback before moving
 * on</p>
 *
 * @param {String} addressType 'sender' or 'recipient'
 * @param {String} email
 * @param {String} domain
 * @param {Function} callback
 */
SMTPServerConnection.prototype._validateAddress = function(addressType, email, domain, callback) {

    var validateEvent,
        validationFailedEvent,
        dnsErrorMessage,
        localErrorMessage;

    if (addressType === 'sender') {
        validateEvent = 'validateSender';
        validationFailedEvent = 'senderValidationFailed';
        dnsErrorMessage = '450 4.1.8 <' + email + '>: Sender address rejected: Domain not found';
        localErrorMessage = '550 5.1.1 <' + email + '>: Sender address rejected: User unknown in local sender table';
    } else if (addressType === 'recipient') {
        validateEvent = 'validateRecipient';
        validationFailedEvent = 'recipientValidationFailed';
        dnsErrorMessage = '450 4.1.8 <' + email + '>: Recipient address rejected: Domain not found';
        localErrorMessage = '550 5.1.1 <' + email + '>: Recipient address rejected: User unknown in local recipient table';
    } else {
        // How are internal errors handled?
        throw new Error('Address type not supported');
    }

    var validateViaLocal = function() {
        if (this.server.listeners(validateEvent).length) {
            this.server.emit(validateEvent, this.envelope, email, (function(err) {
                if (err) {
                    return callback(new Error(err.SMTPResponse || localErrorMessage));
                }
                return callback();
            }).bind(this));
        } else {
            return callback();
        }
    };

    var validateViaDNS = function() {
        dnslib.resolveMx(domain, (function(err, addresses) {
            if (err || !addresses || !addresses.length) {
                this.server.emit(validationFailedEvent, email);
                return callback(new Error(err && err.SMTPResponse || dnsErrorMessage));
            }
            validateViaLocal.call(this);
        }).bind(this));
    };

    if (!this.server.options.disableDNSValidation) {
        validateViaDNS.call(this);
    } else {
        return validateViaLocal.call(this);
    }
};

/**
 * <p>Switch to data mode and starts waiting for a binary data stream. Emits
 * <code>'startData'</code>.</p>
 *
 * <p>If <code>RCPT</code> is not yet run, stop</p>
 */
SMTPServerConnection.prototype._onCommandDATA = function() {

    if (!this.envelope.to.length) {
        return this.client.send('503 5.5.1 Error: need RCPT command');
    }

    this.client.startDataMode();
    this.client.send('354 End data with <CR><LF>.<CR><LF>');
    this.server.emit('startData', this.envelope);
};

/**
 * <p>Resets the current state - e-mail data and authentication info</p>
 */
SMTPServerConnection.prototype._onCommandRSET = function() {
    this.init();
    this.client.send('250 2.0.0 Ok');
};

/**
 * <p>If the server is in secure connection mode, start the authentication
 * process. Param <code>payload</code> defines the authentication mechanism.</p>
 *
 * <p>Currently supported - PLAIN and LOGIN. There is no need for more
 * complicated mechanisms (different CRAM versions etc.) since authentication
 * is only done in secure connection mode</p>
 *
 * @param {Buffer} payload Defines the authentication mechanism
 */
SMTPServerConnection.prototype._onCommandAUTH = function(payload) {
    var method;

    if (!this.server.options.requireAuthentication && !this.server.options.enableAuthentication) {
        return this.client.send('503 5.5.1 Error: authentication not enabled');
    }

    if (!this.server.options.ignoreTLS && !this.client.secureConnection) {
        return this.client.send('530 5.7.0 Must issue a STARTTLS command first');
    }

    if (this.authentication.authenticated) {
        return this.client.send('503 5.7.0 No identity changes permitted');
    }

    payload = payload.toString('utf-8').trim().split(' ');
    method = payload.shift().trim().toUpperCase();

    if (this.server.options.authMethods.indexOf(method) < 0) {
        return this.client.send('535 5.7.8 Error: authentication failed: no mechanism available');
    }

    switch (method) {
        case 'PLAIN':
            this._handleAuthPlain(payload);
            break;
        case 'XOAUTH2':
            this._handleAuthXOAuth2(payload);
            break;
        case 'LOGIN':
            var username = payload.shift();
            if (username) {
                username = username.trim();
                this.authentication.state = 'AUTHENTICATING';
            }
            this._handleAuthLogin(username);
            break;
    }
};

/**
 * <p>Upgrade the connection to a secure TLS connection</p>
 */
SMTPServerConnection.prototype._onCommandSTARTTLS = function() {
    if(this.server.options.disableSTARTTLS) {
        return this.client.send('502 5.5.2 Error: command not recognized');
    }
    if (this.client.secureConnection) {
        return this.client.send('554 5.5.1 Error: TLS already active');
    }

    this.client.send('220 2.0.0 Ready to start TLS');

    this.client.startTLS(this.server.options.credentials, (function() {
        // Connection secured
        // nothing to do here, since it is the client that should
        // make the next move
    }).bind(this));
};

/**
 * <p>Retrieve hostname from the client. Not very important, since client
 * IP is already known and the client can send fake data</p>
 *
 * @param {String} host Hostname of the client
 */
SMTPServerConnection.prototype._onCommandHELO = function(host) {
    if (!host) {
        return this.client.send('501 Syntax: EHLO hostname');
    } else {
        this.hostNameAppearsAs = host;
        this.envelope.host = host;
    }
    this.client.send('250 ' + this.server.options.name + ' at your service, [' +
        this.client.remoteAddress + ']');
};

/**
 * <p>Retrieve hostname from the client. Not very important, since client
 * IP is already known and the client can send fake data</p>
 *
 * <p>Additionally displays server capability list to the client</p>
 *
 * @param {String} host Hostname of the client
 */
SMTPServerConnection.prototype._onCommandEHLO = function(host) {
    var response = [this.server.options.name + ' at your service, [' +
        this.client.remoteAddress + ']', '8BITMIME', 'ENHANCEDSTATUSCODES'
    ];

    if (this.server.options.maxSize) {
        response.push('SIZE ' + this.server.options.maxSize);
    }

    if ((this.client.secureConnection || this.server.options.ignoreTLS) && (this.server.options.requireAuthentication || this.server.options.enableAuthentication)) {
        response.push('AUTH ' + this.server.options.authMethods.join(' '));
        response.push('AUTH=' + this.server.options.authMethods.join(' '));
    }

    if (!this.client.secureConnection && !this.server.options.disableSTARTTLS) {
        response.push('STARTTLS');
    }

    if (!host) {
        return this.client.send('501 Syntax: EHLO hostname');
    } else {
        this.hostNameAppearsAs = host;
        this.envelope.host = host;
    }

    this.client.send(response.map(function(feature, i, arr) {
        return '250' + (i < arr.length - 1 ? '-' : ' ') + feature;
    }).join('\r\n'));
};

/**
 * <p>No operation. Just returns OK.</p>
 */
SMTPServerConnection.prototype._onCommandNOOP = function() {
    this.client.send('250 OK');
};

/**
 * <p>Detect login information from the payload and initiate authentication
 * by emitting <code>'authorizeUser'</code> and waiting for its callback</p>
 *
 * @param {Buffer} payload AUTH PLAIN login information
 */
SMTPServerConnection.prototype._handleAuthPlain = function(payload) {
    if (payload.length) {
        var userdata = new Buffer(payload.join(' '), 'base64'),
            password;
        userdata = userdata.toString('utf-8').split('\u0000');

        if (userdata.length != 3) {
            return this.client.send('500 5.5.2 Error: invalid userdata to decode');
        }

        this.authentication.username = userdata[1] || userdata[0] || '';
        password = userdata[2] || '';

        this.server.emit('authorizeUser',
            this.envelope,
            this.authentication.username,
            password, (function(err, success) {
                if (err || !success) {
                    this.authentication.authenticated = false;
                    this.authentication.username = false;
                    this.authentication.state = 'NORMAL';
                    return this.client.send('535 5.7.8 Error: authentication failed: generic failure');
                }
                this.client.send('235 2.7.0 Authentication successful');
                this.authentication.authenticated = true;
                this.authentication.state = 'AUTHENTICATED';
            }).bind(this));
    } else {
        if (this.authentication.state == 'NORMAL') {
            this.authentication.state = 'AUTHPLAINUSERDATA';
            this.client.send('334');
        }
    }
};

/**
 * <p>Sets authorization state to 'AUTHENTICATING' and reuqests for the
 * username and password from the client</p>
 *
 * <p>If username and password are set initiate authentication
 * by emitting <code>'authorizeUser'</code> and waiting for its callback</p>
 *
 * @param {Buffer} payload AUTH LOGIN login information
 */
SMTPServerConnection.prototype._handleAuthLogin = function(payload) {
    if (this.authentication.state == 'NORMAL') {
        this.authentication.state = 'AUTHENTICATING';
        this.client.send('334 VXNlcm5hbWU6');
    } else if (this.authentication.state == 'AUTHENTICATING') {
        if (this.authentication.username === false) {
            this.authentication.username = new Buffer(payload, 'base64').toString('utf-8');
            this.client.send('334 UGFzc3dvcmQ6');
        } else {
            this.authentication.state = 'VERIFYING';
            this.server.emit('authorizeUser',
                this.envelope,
                this.authentication.username,
                new Buffer(payload, 'base64').toString('utf-8'), (function(err, success) {
                    if (err || !success) {
                        this.authentication.authenticated = false;
                        this.authentication.username = false;
                        this.authentication.state = 'NORMAL';
                        return this.client.send('535 5.7.8 Error: authentication failed: generic failure');
                    }
                    this.client.send('235 2.7.0 Authentication successful');
                    this.authentication.authenticated = true;
                    this.authentication.state = 'AUTHENTICATED';
                }).bind(this));
        }

    }
};

/**
 * <p>Detect login information from the payload and initiate authentication
 * by emitting <code>'authorizeUser'</code> and waiting for its callback</p>
 *
 * @param {Buffer} payload AUTH XOAUTH2 login information
 */
SMTPServerConnection.prototype._handleAuthXOAuth2 = function(payload) {
    if (this.authentication.state == 'AUTHXOAUTH2') {
        // empty response from the client
        this.authentication.authenticated = false;
        this.authentication.username = false;
        this.authentication.state = 'NORMAL';
        return this.client.send('535 5.7.1 Username and Password not accepted');
    }

    var userdata = new Buffer(payload.join(' '), 'base64'),
        token;
    userdata = userdata.toString('utf-8').split('\x01');

    if (userdata.length != 4) {
        return this.client.send('500 5.5.2 Error: invalid userdata to decode');
    }

    this.authentication.username = userdata[0].substr(5) || '';
    token = userdata[1].split(' ')[1] || '';

    this.server.emit('authorizeUser',
        this.envelope,
        this.authentication.username,
        token, (function(err, success) {
            if (err || !success) {
                this.authentication.state = 'AUTHXOAUTH2';
                return this.client.send('334 eyJzdGF0dXMiOiI0MDEiLCJzY2hlbWVzIjoiYmVhcmVyIG1hYyIsInNjb3BlIjoiaHR0cHM6Ly9tYWlsLmdvb2dsZS5jb20vIn0K');
            }
            this.client.send('235 2.7.0 Authentication successful');
            this.authentication.authenticated = true;
            this.authentication.state = 'AUTHENTICATED';
        }).bind(this));
};

/**
 * <p>Emits the data received from the client with <code>'data'</code>
 *
 * @event
 * @param {Buffer} chunk Binary data sent by the client on data mode
 */
SMTPServerConnection.prototype._onData = function(chunk) {
    this.server.emit('data', this.envelope, chunk);
};

/**
 * <p>If the data stream ends, emit <code>'dataReady'</code>and wait for
 * the callback, only if server listened for it.</p>
 *
 * @event
 */
SMTPServerConnection.prototype._onDataReady = function() {
    if (this.server.listeners('dataReady').length) {
        this.server.emit('dataReady', this.envelope, (function(err, code) {
            this.init(true); //reset state, keep auth data

            if (err) {
                this.client.send(err && err.SMTPResponse || ('550 ' + (err && err.message || 'FAILED')));
            } else {
                this.client.send('250 2.0.0 Ok: queued as ' + (code || crypto.randomBytes(10).toString('hex')));
            }

        }).bind(this));
    } else {
        this.init(true); //reset state, keep auth data
        this.client.send('250 2.0.0 Ok: queued as ' + crypto.randomBytes(10).toString('hex'));
    }
};