-
-
Save btoews/1576165 to your computer and use it in GitHub Desktop.
node.js SMTP Server
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* | |
smtpd.js is SMTP server written for node.js | |
#Changes# | |
-made to work with 0.6.2 (replaced sys.print with console.log) | |
-a few bug fixes | |
sudo node smtpd.js | |
MIT License | |
*/ | |
var tcp = require('net'); | |
var enable_debug = false; | |
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.write( s + eol ); | |
}, | |
banner: function() { | |
reply.send("220 <hostname> ESMTP smtpd.js"); | |
}, | |
error: function(s) { | |
reply.send("500 " + s); | |
}, | |
ok: function() { | |
reply.send("250 OK"); | |
} | |
} | |
// list of domains we will accept mail for | |
var allowed_domains = [ | |
"example.com", | |
"mail.example.com" | |
] | |
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 ); | |
if(typeof this.cmd != 'undefined'){ | |
this.cmd = this.cmd.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.end(); | |
} | |
}, | |
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:' ); | |
// only accept email to domains we want email for | |
var index = allowed_domains.indexOf(this.recipient.split("@")[1].replace(">","")) | |
if(index > -1){ | |
reply.ok(); | |
} else { | |
reply.send('550 Not accepting mail for ' + this.recipient); | |
} | |
} | |
}, | |
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] == '.' && typeof(this.data[size+1])=="undefined" ) { | |
this.in_data = false; | |
//this used to pop twice. that doesn't make sense.... | |
this.data.pop(); | |
} | |
} | |
return this; | |
} | |
function debug(s) { | |
if( enable_debug && s != null ) { | |
console.log( s.toString() + eol ); | |
console.log('----------------------------' + eol ); | |
} | |
} | |
socket.addListener('connect', function() { | |
reply.banner(); | |
}); | |
var buffer = ""; | |
var cmd = {}; | |
socket.addListener('data', 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(); | |
console.log(cmd.data) | |
} | |
} | |
else { | |
cmd = Command( buffer ); | |
if ( cmd.isRecognized() ) { | |
cmd.exec(); | |
} else { | |
reply.error('unrecognized command'); | |
} | |
} | |
buffer = ""; | |
} | |
}); | |
socket.addListener('eof', function(){ | |
socket.end(); | |
}); | |
}); | |
server.listen( 25, "0.0.0.0" ); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment