Merge branch 'master' into bures-access

# Conflicts:
#	package.json
This commit is contained in:
witzig 2017-09-26 15:29:49 +02:00
commit 7a93628cc8
10 changed files with 108 additions and 52 deletions

3
.gitignore vendored
View file

@ -3,6 +3,7 @@
node_modules node_modules
npm-debug.log npm-debug.log
package-lock.json
.DS_Store .DS_Store
config/development.* config/development.*
config/production.* config/production.*
@ -30,4 +31,4 @@ public/grapejs/templates/*
config/production.toml config/production.toml
workers/reports/config/production.toml workers/reports/config/production.toml
docker-compose.override.yml docker-compose.override.yml

View file

@ -119,6 +119,50 @@ If you are using the bundled ZoneMTA then you should make sure you are using a p
With proper SPF, DKIM and PTR records (DMARC wouldn't hurt either) I got perfect 10/10 score out from [MailTester](https://www.mail-tester.com/) when sending a campaign message to a MailTester test address. I did not have VERP turned on, so the sender address matched return path address. With proper SPF, DKIM and PTR records (DMARC wouldn't hurt either) I got perfect 10/10 score out from [MailTester](https://www.mail-tester.com/) when sending a campaign message to a MailTester test address. I did not have VERP turned on, so the sender address matched return path address.
#### Getting your head around DKIM, DMARK, SPF and PTR
DKIM, DMARK, SPF and PTR are DNS records which spam filters use to figure out if e-mails were really sent by you (and not by a spammer who tries to conceal his identity to be able to continue send bulks of e-mails people never subscribed for). Assuming that you use zone-mta and your e-mails are to originate from a Mailtrain installation at `mailtrain.example.com` and optionally from `mail.example.net`, to practically set all these records up you will need to:
1. generate genrate a private and public DKIM key
```sh
mkdir /opt/dkim-keys
chmod 700 /opt/dkim-keys
pushd /opt/dkim-keys
openssl genrsa -out mailtrain.example.com.key 2048 # private key mailtrain.example.com.key
openssl rsa -in mailtrain.example.com.key -out mailtrain.example.com.pub -pubout -outform PEM # public key mailtrain.example.com.pub
```
2. add 3 new txt records for the mailtrain.example.com that will most likely similar to the example below:
```
default._domainkey.mailtrain.example.com TXT "k=rsa; p=[public key in one line];"
mailtrain.example.com TXT "v=spf1 mx a a:mail.example.net -all"
_dmarc.mailtrain.example.com TXT "v=DMARC1; p=reject"
```
(refer to a google search for a DKIM generator, SPF generator and DMARC genreator to get you up to speed). Configure your Mailtrain settings accoring to this:
**DKIM domain:** mailtrain.example.com
**DKIM selector:** default
**DKIM Private Key:** [copy and paste the private key in /opt/dkim-keys/mailtrain.example.com.key]
The above steps will have the following effect:
- all messages sent by Mailtrain / Zone-mta will be signed by the DKIM Private Key (the signature becomes a part of the e-mail)
- when a spamfilter encounters this signature, it will look for the **<DKIM selector>**._domainkey.**<DKIM domain>** TXT record, and use the public key stored there to verify that the signature is valid
- additionally, the spamfilter will look for a TXT SPF record and will look a if the e-mail was sent from the IP address of mailtrain.example.com or mail.example.net. If the sender IP or domain is different, it will discard the e-mail as spam.
- furthermore, the spamfilter looks for the DMARC record, which tells it what to do with mails that aren't signed with DKIM or which don't have a valid signature. The example above will tell the spamfilter to reject such a mail as well.
3. You are now almost set. To further confirm that you have full control over your network, the last step is to set up a PTR record, which will give the right answer for a reverse DNS lookup (answer to "what domain name is bound to IP address xxx.xxx.xxx.xxx). If you run your own DNS, you probably know it will look similar to this:
```
10.27/1.110.220.in-addr.arpa. 1800 PTR mailtrain.example.com.
```
If you run Mailtrain on a VPS, you will have to find the PTR configuration somewhere in your administration interface or ask your provider to help you.
### Simple Install (Docker) ### Simple Install (Docker)
#### Requirements: #### Requirements:

View file

@ -85,6 +85,7 @@ database="mailtrain"
# MAMP users should also turn on "Allow network access to MySQL" otherwise MySQL might not be accessible # MAMP users should also turn on "Allow network access to MySQL" otherwise MySQL might not be accessible
port=3306 port=3306
charset="utf8mb4" charset="utf8mb4"
# The timezone configured on the MySQL server. This can be 'local', 'Z', or an offset in the form +HH:MM or -HH:MM
timezone="local" timezone="local"
[redis] [redis]

1
docs/CNAME Normal file
View file

@ -0,0 +1 @@
docs.mailtrain.org

1
docs/README.md Normal file
View file

@ -0,0 +1 @@
Here should be the docs

View file

@ -28,95 +28,97 @@
"node": ">=5.0.0" "node": ">=5.0.0"
}, },
"devDependencies": { "devDependencies": {
"babel-eslint": "^7.2.3", "babel-eslint": "^8.0.0",
"chai": "^4.0.2", "bluebird": "^3.5.0",
"chai": "^4.1.2",
"eslint-config-nodemailer": "^1.2.0", "eslint-config-nodemailer": "^1.2.0",
"grunt": "^1.0.1", "grunt": "^1.0.1",
"grunt-cli": "^1.2.0", "grunt-cli": "^1.2.0",
"grunt-contrib-nodeunit": "^1.0.0", "grunt-contrib-nodeunit": "^1.0.0",
"grunt-eslint": "^20.0.0", "grunt-eslint": "^20.1.0",
"jsxgettext-andris": "^0.9.0-patch.1", "jsxgettext-andris": "^0.9.0-patch.1",
"mocha": "^3.3.0", "lodash": "^4.17.4",
"phantomjs-prebuilt": "^2.1.14", "mocha": "^3.5.3",
"selenium-webdriver": "^3.4.0", "phantomjs-prebuilt": "^2.1.15",
"selenium-webdriver": "^3.5.0",
"url-pattern": "^1.0.3" "url-pattern": "^1.0.3"
}, },
"optionalDependencies": { "optionalDependencies": {
"posix": "^4.1.1" "posix": "^4.1.1"
}, },
"dependencies": { "dependencies": {
"async": "^2.3.0", "async": "^2.5.0",
"aws-sdk": "^2.37.0", "aws-sdk": "^2.122.0",
"bcrypt-nodejs": "0.0.3", "bcrypt-nodejs": "0.0.3",
"bluebird": "^3.5.0", "bluebird": "^3.5.0",
"body-parser": "^1.17.1", "body-parser": "^1.18.2",
"bounce-handler": "^7.3.2-fork.2", "bounce-handler": "^7.3.2-fork.2",
"compression": "^1.6.2", "compression": "^1.7.0",
"config": "^1.25.1", "config": "^1.26.2",
"connect-flash": "^0.1.1", "connect-flash": "^0.1.1",
"connect-redis": "^3.2.0", "connect-redis": "^3.3.0",
"cookie-parser": "^1.4.3", "cookie-parser": "^1.4.3",
"cors": "^2.8.3", "cors": "^2.8.4",
"csurf": "^1.9.0", "csurf": "^1.9.0",
"csv-generate": "^1.0.0", "csv-parse": "^1.2.3",
"csv-parse": "^1.2.0",
"device": "^0.3.8", "device": "^0.3.8",
"dompurify": "^0.9.0", "dompurify": "^1.0.2",
"escape-html": "^1.0.3", "escape-html": "^1.0.3",
"express": "^4.15.2", "escape-string-regexp": "^1.0.5",
"express-session": "^1.15.2", "express": "^4.15.5",
"express-session": "^1.15.5",
"faker": "^4.1.0", "faker": "^4.1.0",
"feedparser": "^2.1.0", "feedparser": "^2.2.1",
"fs-extra": "^3.0.1", "fs-extra": "^4.0.2",
"geoip-ultralight": "^0.1.5", "geoip-ultralight": "^0.1.5",
"gettext-parser": "^1.2.2", "gettext-parser": "^1.3.0",
"gm": "^1.23.0", "gm": "^1.23.0",
"handlebars": "^4.0.6", "handlebars": "^4.0.10",
"hbs": "^4.0.1", "hbs": "^4.0.1",
"he": "^1.1.1", "he": "^1.1.1",
"html-to-text": "^3.2.0", "html-to-text": "^3.3.0",
"humanize": "0.0.9", "humanize": "0.0.9",
"is-url": "^1.2.2", "is-url": "^1.2.2",
"isemail": "^2.2.1", "isemail": "^2.2.1",
"jquery-file-upload-middleware": "^0.1.8", "jquery-file-upload-middleware": "^0.1.8",
"jsdom": "^9.12.0", "jsdom": "^9.12.0",
"json-stringify-date": "^0.1.4", "json-stringify-date": "^0.1.4",
"juice": "^4.0.2", "juice": "^4.1.1",
"knex": "^0.13.0", "knex": "^0.13.0",
"libmime": "^3.1.0", "libmime": "^3.1.0",
"mailparser": "^2.0.5", "mailparser": "^2.0.5",
"marked": "^0.3.6", "marked": "^0.3.6",
"memory-cache": "^0.1.6", "memory-cache": "^0.2.0",
"mjml": "3.3.3", "mjml": "3.3.5",
"mkdirp": "^0.5.1", "mkdirp": "^0.5.1",
"moment-timezone": "^0.5.12", "moment-timezone": "^0.5.13",
"morgan": "^1.8.1", "morgan": "^1.8.2",
"multer": "^1.3.0", "multer": "^1.3.0",
"multiparty": "^4.1.3", "multiparty": "^4.1.3",
"mysql2": "^1.3.5", "mysql2": "^1.3.5",
"node-gettext": "^2.0.0-rc.1", "node-gettext": "^2.0.0-rc.1",
"node-mocks-http": "^1.6.1", "node-mocks-http": "^1.6.5",
"node-object-hash": "^1.2.0", "node-object-hash": "^1.2.0",
"nodeify": "^1.0.1", "nodeify": "^1.0.1",
"nodemailer": "^4.0.1", "nodemailer": "^4.1.1",
"nodemailer-openpgp": "^1.0.2", "nodemailer-openpgp": "^1.1.0",
"npmlog": "^4.0.2", "npmlog": "^4.1.2",
"object-hash": "^1.1.7", "object-hash": "^1.1.8",
"openpgp": "^2.5.3", "openpgp": "^2.5.11",
"owasp-password-strength-test": "github:bures/owasp-password-strength-test", "owasp-password-strength-test": "github:bures/owasp-password-strength-test",
"passport": "^0.3.2", "passport": "^0.4.0",
"passport-local": "^1.0.0", "passport-local": "^1.0.0",
"premailer-api": "^1.0.4", "premailer-api": "^1.0.4",
"redfour": "^1.0.0", "redfour": "^1.0.2",
"redis": "^2.7.1", "redis": "^2.8.0",
"request": "^2.81.0", "request": "^2.82.0",
"request-promise": "^4.2.1", "request-promise": "^4.2.2",
"serve-favicon": "^2.4.2", "serve-favicon": "^2.4.4",
"shortid": "^2.2.8", "shortid": "^2.2.8",
"slugify": "^1.1.0", "slugify": "^1.2.1",
"smtp-server": "^3.0.1", "smtp-server": "^3.1.0",
"striptags": "^3.0.1", "striptags": "^3.1.0",
"toml": "^2.3.2", "toml": "^2.3.3",
"try-require": "^1.2.1" "try-require": "^1.2.1"
} }
} }

View file

@ -563,7 +563,7 @@ router.get('/subscription/:id/import/:importId', passport.csrfProtection, (req,
data.list = list; data.list = list;
data.csrfToken = req.csrfToken(); data.csrfToken = req.csrfToken();
data.customFields = fields.getRow(fieldList, data); data.customFields = fields.getRow(fieldList, data, false, true);
res.render('lists/subscription/import-preview', data); res.render('lists/subscription/import-preview', data);
}); });

View file

@ -319,11 +319,11 @@ router.post('/zone-mta/sender-config', (req, res) => {
res.json({ res.json({
dkim: { dkim: {
keys: { keys: [{
domainName: configItems.dkimDomain || domain, domainName: configItems.dkimDomain || domain,
keySelector: configItems.dkimSelector, keySelector: configItems.dkimSelector,
privateKey: configItems.dkimPrivateKey privateKey: configItems.dkimPrivateKey
} }]
} }
}); });
}); });

View file

@ -20,7 +20,7 @@ function feedLoop() {
return setTimeout(feedLoop, feed_timeout); return setTimeout(feedLoop, feed_timeout);
} }
let query = 'SELECT `id`, `source_url`, `from`, `address`, `subject`, `list`, `segment`, `html` FROM `campaigns` WHERE `type`=2 AND `status`=6 AND (`last_check` IS NULL OR `last_check`< NOW() - INTERVAL 10 MINUTE) LIMIT 1'; let query = 'SELECT `id`, `source_url`, `from`, `address`, `subject`, `list`, `segment`, `html`, `open_tracking_disabled`, `click_tracking_disabled` FROM `campaigns` WHERE `type`=2 AND `status`=6 AND (`last_check` IS NULL OR `last_check`< NOW() - INTERVAL 10 MINUTE) LIMIT 1';
connection.query(query, (err, rows) => { connection.query(query, (err, rows) => {
connection.release(); connection.release();
@ -148,7 +148,9 @@ function checkEntries(parent, entries, callback) {
address: parent.address, address: parent.address,
subject: entry.title || parent.subject, subject: entry.title || parent.subject,
list: parent.segment ? parent.list + ':' + parent.segment : parent.list, list: parent.segment ? parent.list + ':' + parent.segment : parent.list,
html html,
openTrackingDisabled: parent.openTrackingDisabled,
clickTrackingDisabled: parent.clickTrackingDisabled
}; };
campaigns.create(campaign, { campaigns.create(campaign, {

View file

@ -48,7 +48,11 @@ let server = net.createServer(socket => {
if ( queued ) { if ( queued ) {
queued = queued[1]; queued = queued[1];
queued_as = queued.match(/ queued as (\w+)/); queued_as = queued.match(/ queued as (\w+)/);
queued_as = queued_as[1]; if (queued_as) {
queued_as = queued_as[1];
} else {
queued_as = '';
}
} }
} }
@ -56,7 +60,7 @@ let server = net.createServer(socket => {
if (err || !message) { if (err || !message) {
return checkNextLine(); return checkNextLine();
} }
if ( queued_as ) { if ( queued_as || status === 'sent' ) {
log.verbose('POSTFIXBOUNCE', 'Message %s locally requeued as %s', queueId, queued_as); log.verbose('POSTFIXBOUNCE', 'Message %s locally requeued as %s', queueId, queued_as);
// Update message's previous queueId (thanks @mfechner ) // Update message's previous queueId (thanks @mfechner )
campaigns.updateMessageResponse(message, queued, queued_as, (err, updated) => { campaigns.updateMessageResponse(message, queued, queued_as, (err, updated) => {