Skip to content

Instantly share code, notes, and snippets.

@kennethkalmer
Created January 16, 2010 13:14
Show Gist options
  • Star 16 You must be signed in to star a gist
  • Fork 14 You must be signed in to fork a gist
  • Save kennethkalmer/278814 to your computer and use it in GitHub Desktop.
Save kennethkalmer/278814 to your computer and use it in GitHub Desktop.
node.js SMTP Server
/*
smtpd.js is SMTP server written for node.js
MIT License
*/
var tcp = require('tcp');
var sys = require('sys');
var enable_debug = true;
var server = tcp.createServer( function( socket ) {
var eol = "\r\n";
// patterns for commands
var command_patterns = {
helo: /^HELO\s*/i,
ehlo: /^EHLO\s*/i,
quit: /^QUIT/i,
from: /^MAIL FROM:\s*/i,
rcpt: /^RCPT TO:\s*/i,
data: /^DATA/i,
noop: /^NOOP/i,
rset: /^RSET/i,
vrfy: /^VRFY\s+/i,
expn: /^EXPN\s+/,
help: /^HELP/i,
tls: /^STARTTLS/i,
auth: /^AUTH\s+/i
}
// our replies
var reply = {
send: function(s) {
debug( "reply: '" + (s || "null") + "'" );
socket.send( s + eol );
},
banner: function() {
reply.send("220 <hostname> ESMTP smtpd.js");
},
error: function(s) {
reply.send("500 " + s);
},
ok: function() {
reply.send("250 OK");
}
}
function Command( line ) {
function parseCommand( line ) {
for( var cmd in command_patterns) {
if (command_patterns[ cmd ].test( buffer ) ) {
return cmd;
}
}
}
function extractArguments( command ) {
return this.line.replace( command, '' ).replace(/^\s\s*/, '').replace(/\s\s*$/, '');
}
this.cmd = parseCommand( line ).toLowerCase();
this.line = line;
this.in_data = false;
this.data = [];
this.isRecognized = function() {
return typeof this.cmd != "undefined";
}
this.exec = function() {
if ( callbacks[this.cmd] ) {
callbacks[this.cmd].callback(this.line);
}
else {
reply.error("command not implemented");
}
}
var that = this;
var callbacks = {
quit: {
callback: function () {
reply.send( '221 <hostname> closing connection' );
socket.close();
}
},
ehlo: {
callback: function() {
var hostname = extractArguments( 'EHLO' );
reply.send('250-<hostname> Hello ' + socket.remoteAddress );
reply.send('250 8BITMIME');
}
},
helo: {
callback: function() {
reply.send('250 <hostname> Hello ' + socket.remoteAddress );
}
},
from: {
callback: function() {
this.from = extractArguments( 'MAIL FROM:' );
reply.ok();
}
},
rcpt: {
callback: function() {
this.recipient = extractArguments( 'RCPT TO:' );
reply.ok();
}
},
data: {
callback: function() {
that.in_data = true;
reply.send("354 Terminate with line containing only '.'");
}
}
}
this.appendData = function( buffer ) {
var size = buffer.length;
var line = "";
for( var i = 0; i < buffer.length; i++ ){
var chr = buffer[i];
if( chr + buffer[i + 1] == eol ) {
this.data.push( line );
line = "";
i++;
continue;
}
line += chr;
}
var size = this.data.length - 1;
if( this.data[size] == '.' && this.data[size - 1] == '' ) {
this.in_data = false;
this.data.pop();
this.data.pop();
}
}
return this;
}
function debug(s) {
if( enable_debug && s != null ) {
sys.print( s.toString() + eol );
sys.print('----------------------------' + eol );
}
}
socket.addListener('connect', function() {
reply.banner();
});
var buffer = "";
var cmd = {};
socket.addListener('receive', function(packet) {
buffer += packet;
while( buffer.indexOf(eol) != -1 ) {
if ( cmd.in_data ) {
cmd.appendData( buffer );
// we're finished
if( !cmd.in_data ) {
reply.ok();
}
}
else {
cmd = Command( buffer );
if ( cmd.isRecognized() ) {
cmd.exec();
} else {
reply.error('unrecognized command');
}
}
buffer = "";
}
});
socket.addListener('eof', function(){
socket.close();
});
});
exports.runServer = function() {
server.listen( 10025 );
}
@ydennisy
Copy link

ydennisy commented Mar 9, 2019

@kennethkalmer this is quite an old gist now, are you using anything newer for the same purpose?

I want to create a SMTP server which can run as a serverless function 👍

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment