From b16c3d3d5a653ba30a64d5a05c0f402e29a5abf8 Mon Sep 17 00:00:00 2001 From: choonkeat Date: Sun, 2 Feb 2014 22:22:03 +0800 Subject: [PATCH] init --- README.md | 18 +++++++++++++++++ http.js | 50 +++++++++++++++++++++++++++++++++++++++++++++++ package.json | 28 ++++++++++++++++++++++++++ public/index.html | 30 ++++++++++++++++++++++++++++ server.js | 45 ++++++++++++++++++++++++++++++++++++++++++ smtp.js | 35 +++++++++++++++++++++++++++++++++ spec/.gitkeep | 0 7 files changed, 206 insertions(+) create mode 100644 README.md create mode 100644 http.js create mode 100644 package.json create mode 100644 public/index.html create mode 100644 server.js create mode 100644 smtp.js create mode 100644 spec/.gitkeep diff --git a/README.md b/README.md new file mode 100644 index 0000000..e713301 --- /dev/null +++ b/README.md @@ -0,0 +1,18 @@ +# diymailin + +## example + +``` +CONFIG=config.json PASSWORD=topsecret SMTP_PORT=1025 PORT=3000 npm start +``` + +* use `config.json` to store and retrieve email-to-url configurations +* use `http://localhost:3000/` as web interface to add config into `config.json` + * only update config if password matches `topsecret` +* run SMTP server at port `1025` +* when email comes into SMTP server + * if any of the recipient is configured in `config.json` + * HTTP POST `message` param to configured `url` + * if HTTP POST get error, tell remote SMTP client email was rejected + * otherwise, tell remote SMTP client email was accepted + * otherwise, tell remote SMTP client email was rejected diff --git a/http.js b/http.js new file mode 100644 index 0000000..9479777 --- /dev/null +++ b/http.js @@ -0,0 +1,50 @@ +var url = require('url'); +var path = require('path'); +var http = require('http'); +var querystring = require('querystring'); +var node_static = require('node-static'); + +function reply404(res) { + res = res || this; + res.writeHead(404, {'content-type': 'text/plain'}); + res.end(); +} + +module.exports = { + start: function(httpPort, callback) { + httpPort = httpPort || 3000; + var static_directory = new node_static.Server(path.join(__dirname, 'public')); + var server = http.createServer(); + server.addListener('request', function(req, res) { + var uri = url.parse(req.url, !!'true:query as object'); + if (req.method == 'POST') { + var content_type = (req.headers && req.headers['content-type'] || ""); + var content_length = +(req.headers && req.headers['content-length'] || 0); + if (content_type.match(/www-form-urlencoded/i)) { + var chunks = []; + req.on('data', chunks.push.bind(chunks)); + req.on('end', callback ? function() { + uri = querystring.parse(chunks.join("").toString()); + callback(uri.password, uri.email, uri.url, function(err) { + res.writeHead(200, {'content-type': 'text/plain'}); + res.write(err || "Ok"); + res.end(); + }); + } : reply404.bind(res)); + } else { + var chunks = []; + req.on('data', chunks.push.bind(chunks)); + req.on('end', reply404.bind(res)); + } + } else if (process.env.HIDE_FORM) { + reply404(res); + } else { + static_directory.serve(req, res); + } + }); + + server.listen(httpPort, '0.0.0.0', function(err) { + console.log( 'HTTP server on 0.0.0.0:' + httpPort, err || ""); + }); + } +} diff --git a/package.json b/package.json new file mode 100644 index 0000000..0987f42 --- /dev/null +++ b/package.json @@ -0,0 +1,28 @@ +{ + "name": "diymailin.js", + "version": "0.1.0", + "description": "standalone incoming-email-to-http-post server", + "repository": "none", + "readme": "See README.md", + "readmeFilename": "README.md", + "main": "server.js", + "dependencies": { + "request": "~2.33.0", + "node-static": "~0.7.3", + "simplesmtp": "~0.3.20", + "stream-buffers": "~0.2.5" + }, + "devDependencies": { + "jasmine-node": "~1.11.0" + }, + "scripts": { + "test": "which guard && guard || jasmine-node spec/", + "start": "node server.js" + }, + "author": "Chew Choon Keat ", + "license": "None", + "engines": { + "node": "0.10.x", + "npm": "1.3.x" + } +} diff --git a/public/index.html b/public/index.html new file mode 100644 index 0000000..1a0798a --- /dev/null +++ b/public/index.html @@ -0,0 +1,30 @@ + + + + + + +

+ diymailin +

+
+

+
+ +

+

+
+ +

+

+
+ +

+

+ +

+
+ + diff --git a/server.js b/server.js new file mode 100644 index 0000000..5c20c42 --- /dev/null +++ b/server.js @@ -0,0 +1,45 @@ +var fs = require('fs'), + path = require('path'), + request = require('request'), + smtp = require('./smtp'), + http = require('./http'); + +var config = process.env.CONFIG || process.env.TMPDIR + path.sep + 'config.json'; +fs.readFile(config, function(err, data) { + var whiteList = (data ? JSON.parse(data) : {}); + console.log("Loaded", config, whiteList); + + var url_for = function(req) { + var url = null; + for (var email in whiteList) { + url = whiteList[email]; + if (url && req.to.indexOf(email) != -1) return url; + } + } + + smtp.start(process.env.SMTP_PORT, url_for, function(req, buffer) { + var url = url_for(req); + console.log('POST', url); + var r = request.post(url); + r.form().append('message', buffer.getContents()); + r.on('error', function(err) { + req.reject(err); + console.log("FAIL", url, err); + }); + r.on('end', function(err) { + if (! err) return req.accept(); + req.reject(err); + console.log("FAIL", url, err); + }); + }); + + http.start(process.env.PORT, function(password, email, url, next) { + if (password == process.env.PASSWORD) { + whiteList[email] = url; + console.log("Writing", config, whiteList); + fs.writeFile(config, JSON.stringify(whiteList, null, 2), next); + } else { + next(); + } + }); +}); diff --git a/smtp.js b/smtp.js new file mode 100644 index 0000000..95befe7 --- /dev/null +++ b/smtp.js @@ -0,0 +1,35 @@ +var simplesmtp = require("simplesmtp"), + streamBuffers = require("stream-buffers"), + config = { + requireAuthentication: false, + enableAuthentication: false, + timeout: 30000, + disableEHLO: true, + disableDNSValidation: true, + SMTPBanner: "server" + }; + +module.exports = { + start: function(smtpPort, allowing, callback) { + smtpPort = smtpPort || 25; + allowing = allowing || function() { }; + simplesmtp.createSimpleServer(config, function(request) { + if (allowing(request)) { + console.log((new Date()) + "\tGOOD\t" + request.remoteAddress + "\t" + request.from + "\t" + request.to.join(' ')); + var buffer = new streamBuffers.WritableStreamBuffer({ + initialSize: (100 * 1024), // start as 100 kilobytes. + incrementAmount: (10 * 1024) // grow by 10 kilobytes each time buffer overflows. + }); + request.pipe(buffer); + request.on("end", function() { + callback(request, buffer); + }); + } else { + console.log((new Date()) + "\tFAIL\t" + request.remoteAddress + "\t" + request.from + "\t" + request.to.join(' ')); + request.reject(); + } + }).listen(smtpPort, '0.0.0.0', function(err) { + console.log('SMTP server on 0.0.0.0:' + smtpPort, err || ""); + }); + } +} diff --git a/spec/.gitkeep b/spec/.gitkeep new file mode 100644 index 0000000..e69de29