nodemailer / smtp-server

Create custom SMTP servers on the fly
Other
846 stars 145 forks source link

extend session with a reference to the server #148

Closed americrypt closed 4 years ago

americrypt commented 4 years ago

@andris9 Thank you for your work on Nodemailer. Its been a lot of fun hacking on it.

This pull request solves a problem I encountered. I now fully understand how you are gracefully exiting the server, but I don't see any example code here nor on SO showing where most folks are calling server.close() in their implementations. Since the 6 implementation functions do not have a reference to server, I'm at a loss how anyone could call server.close().

For my implementation, I want to be able to connect to the server normally and issue a back-door command to kick off the graceful shutdown. This eliminates having to SSH into a server to issue commands that way. I chose MAIL FROM to implement the following:

/*
  onMailFrom Implementation
  Copyright © 2020 David Walton
  Licensed: MIT (See https://opensource.org/licenses/MIT)

  LOGIC NOTES
  -----------
  onMailFrom is used for validating sender addresses
  docs at http://nodemailer.com/extras/smtp-server/#validating-sender-addresses

  BACK DOOR: The "MAIL FROM" command is the first implementation method with  
  access to command arguments (HELO/EHLO has an argument but no access). This is 
  therefore the most logical spot for installing a back door to activate a 
  graceful server shutdown, allowing existing clients to finish.

  SHUTDOWN COMMAND: 
    MAIL FROM:<shutdown@example.net> shutdown=ff570a28-a9da-49d0-8e10-4df4a4aeadcb

  This demonstration code relies on extending the `session` object with a reference
  to the server (`smtp-connection.js` line 38). In this way, all implementation
  methods can reach the server's methods and options via the session.
*/

const check = require("check-types"); // https://gitlab.com/philbooth/check-types.js

module.exports = function (args, session, callback) {
  /*
    +--------------------------------------------------------------------------+
    | STAGE 1 - process the shutdown backdoor first                            |
    +--------------------------------------------------------------------------+
  */

  // Phil Booth's check-types makes beautiful code
  let duck = { address: "", args: { SHUTDOWN: "" } };

  if (
    check.like(args, duck) &&
    args.address.toLowerCase() === "shutdown@example.net" &&
    args.args.SHUTDOWN.toLowerCase() === "ff570a28-a9da-49d0-8e10-4df4a4aeadcb"
  ) {
    // a valid shutdown command/args has been received
    session.server.close(); // with a server reference added to the session, this is easy
    console.log("S H U T D O W N   I N I T I A T E D");
    let e = new Error(`Shutdown initiated`);
    e.responseCode = 421;
    callback(e);
  }

  /*
    +--------------------------------------------------------------------------+
    | STAGE 2 - process the actual MAIL FROM data now                          |
    +--------------------------------------------------------------------------+
  */
  callback();
};

This solves my problem and may be useful to others, but I still would like to understand your vision for calling server.close() — where do you call it?

Also, not sure why you have this.session = this.session = { on line 38? Maybe cut/paste got carried away? 💯 All tests passed BTW except the proxy / OpenSSL tests as I do not have openSSL on this Windows box. Again, thanks for maintaining this great project! 👍

andris9 commented 4 years ago

It might be unintuitive but possible to get a reference for server

const server = new SMTPServer({
});
server.onMailFrom = (address, session, callback) => {
   server.close();
};

I would not prefer adding server property to session object as the session object is supposed to be something that is serialisable, adding a complex object breaks that convention.

americrypt commented 4 years ago

Yes, that's not intuitive, you're right.