diff --git a/CHANGES b/CHANGES index 6237ae8fa..7417a5c75 100644 --- a/CHANGES +++ b/CHANGES @@ -2140,3 +2140,5 @@ * dns: apps can now use the dns port * httpPaths: allow apps to specify forwarding from custom paths to container ports (for OLS) * add elasticemail smtp relay option +* mail: add option to fts using solr + diff --git a/src/infra_version.js b/src/infra_version.js index 52f8753b4..f38b6fe12 100644 --- a/src/infra_version.js +++ b/src/infra_version.js @@ -20,7 +20,7 @@ exports = module.exports = { 'postgresql': { repo: 'cloudron/postgresql', tag: 'cloudron/postgresql:3.3.0@sha256:0daf1be5320c095077392bf21d247b93ceaddca46c866c17259a335c80d2f357' }, 'mongodb': { repo: 'cloudron/mongodb', tag: 'cloudron/mongodb:3.0.0@sha256:59e50b1f55e433ffdf6d678f8c658812b4119f631db8325572a52ee40d3bc562' }, 'redis': { repo: 'cloudron/redis', tag: 'cloudron/redis:2.3.0@sha256:0e31ec817e235b1814c04af97b1e7cf0053384aca2569570ce92bef0d95e94d2' }, - 'mail': { repo: 'cloudron/mail', tag: 'cloudron/mail:3.0.0@sha256:014b651f42384c915677acb8b323d1cd18fe5194fb0e6874020a1e1bbad0bbef' }, + 'mail': { repo: 'cloudron/mail', tag: 'cloudron/mail:3.0.0@sha256:fd87ff6a620d3f4b2e315165b4e11b634a10e0bebc7e280b23bf9cd94d9c416a' }, 'graphite': { repo: 'cloudron/graphite', tag: 'cloudron/graphite:2.3.0@sha256:b7bc1ca4f4d0603a01369a689129aa273a938ce195fe43d00d42f4f2d5212f50' }, 'sftp': { repo: 'cloudron/sftp', tag: 'cloudron/sftp:3.0.0@sha256:5b249db20ee559de2e3b669526763538cf1ec554966b51accdb7056b9be8fc0f' } } diff --git a/src/mail.js b/src/mail.js index 2d4602cc2..be9dba12d 100644 --- a/src/mail.js +++ b/src/mail.js @@ -668,7 +668,6 @@ function configureMail(mailFqdn, mailDomain, callback) { --memory-swap ${memoryLimit * 2}m \ -e CLOUDRON_MAIL_TOKEN="${cloudronToken}" \ -e CLOUDRON_RELAY_TOKEN="${relayToken}" \ - -e CLOUDRON_FTS="true" \ -v "${paths.MAIL_DATA_DIR}:/app/data" \ -v "${paths.PLATFORM_DATA_DIR}/addons/mail:/etc/mail" \ ${ports} \ diff --git a/src/middleware/index.js b/src/middleware/index.js index 37c9a5e35..b51b3cb04 100644 --- a/src/middleware/index.js +++ b/src/middleware/index.js @@ -5,7 +5,7 @@ exports = module.exports = { cors: require('./cors'), json: require('body-parser').json, morgan: require('morgan'), - proxy: require('proxy-middleware'), + proxy: require('./proxy-middleware.js'), lastMile: require('connect-lastmile'), multipart: require('./multipart.js'), timeout: require('connect-timeout'), diff --git a/src/middleware/proxy-middleware.js b/src/middleware/proxy-middleware.js new file mode 100644 index 000000000..0a145e182 --- /dev/null +++ b/src/middleware/proxy-middleware.js @@ -0,0 +1,149 @@ +// https://github.com/cloudron-io/node-proxy-middleware +// MIT license +// contains https://github.com/gonzalocasas/node-proxy-middleware/pull/59 + +var os = require('os'); +var http = require('http'); +var https = require('https'); +var owns = {}.hasOwnProperty; + +module.exports = function proxyMiddleware(options) { + //enable ability to quickly pass a url for shorthand setup + if(typeof options === 'string'){ + options = require('url').parse(options); + } + + var httpLib = options.protocol === 'https:' ? https : http; + var request = httpLib.request; + + options = options || {}; + options.hostname = options.hostname; + options.port = options.port; + options.pathname = options.pathname || '/'; + + return function (req, resp, next) { + var url = req.url; + // You can pass the route within the options, as well + if (typeof options.route === 'string') { + if (url === options.route) { + url = ''; + } else if (url.slice(0, options.route.length) === options.route) { + url = url.slice(options.route.length); + } else { + return next(); + } + } + + //options for this request + var opts = extend({}, options); + if (url && url.charAt(0) === '?') { // prevent /api/resource/?offset=0 + if (options.pathname.length > 1 && options.pathname.charAt(options.pathname.length - 1) === '/') { + opts.path = options.pathname.substring(0, options.pathname.length - 1) + url; + } else { + opts.path = options.pathname + url; + } + } else if (url) { + opts.path = slashJoin(options.pathname, url); + } else { + opts.path = options.pathname; + } + opts.method = req.method; + opts.headers = options.headers ? merge(req.headers, options.headers) : req.headers; + + applyViaHeader(req.headers, opts, opts.headers); + + if (!options.preserveHost) { + // Forwarding the host breaks dotcloud + delete opts.headers.host; + } + + var myReq = request(opts, function (myRes) { + var statusCode = myRes.statusCode + , headers = myRes.headers + , location = headers.location; + // Fix the location + if (((statusCode > 300 && statusCode < 304) || statusCode === 201) && location && location.indexOf(options.href) > -1) { + // absoulte path + headers.location = location.replace(options.href, slashJoin('/', slashJoin((options.route || ''), ''))); + } + applyViaHeader(myRes.headers, opts, myRes.headers); + rewriteCookieHosts(myRes.headers, opts, myRes.headers, req); + resp.writeHead(myRes.statusCode, myRes.headers); + myRes.on('error', function (err) { + next(err); + }); + myRes.on('end', function (err) { + next(); + }); + myRes.pipe(resp); + }); + myReq.on('error', function (err) { + next(err); + }); + if (!req.readable) { + myReq.end(); + } else { + req.pipe(myReq); + } + }; +}; + +function applyViaHeader(existingHeaders, opts, applyTo) { + if (!opts.via) return; + + var viaName = (true === opts.via) ? os.hostname() : opts.via; + var viaHeader = '1.1 ' + viaName; + if(existingHeaders.via) { + viaHeader = existingHeaders.via + ', ' + viaHeader; + } + + applyTo.via = viaHeader; +} + +function rewriteCookieHosts(existingHeaders, opts, applyTo, req) { + if (!opts.cookieRewrite || !owns.call(existingHeaders, 'set-cookie')) { + return; + } + + var existingCookies = existingHeaders['set-cookie'], + rewrittenCookies = [], + rewriteHostname = (true === opts.cookieRewrite) ? os.hostname() : opts.cookieRewrite; + + if (!Array.isArray(existingCookies)) { + existingCookies = [ existingCookies ]; + } + + for (var i = 0; i < existingCookies.length; i++) { + var rewrittenCookie = existingCookies[i].replace(/(Domain)=[a-z\.-_]*?(;|$)/gi, '$1=' + rewriteHostname + '$2'); + + if (!req.connection.encrypted) { + rewrittenCookie = rewrittenCookie.replace(/;\s*?(Secure)/i, ''); + } + rewrittenCookies.push(rewrittenCookie); + } + + applyTo['set-cookie'] = rewrittenCookies; +} + +function slashJoin(p1, p2) { + var trailing_slash = false; + + if (p1.length && p1[p1.length - 1] === '/') { trailing_slash = true; } + if (trailing_slash && p2.length && p2[0] === '/') {p2 = p2.substring(1); } + + return p1 + p2; +} + +function extend(obj, src) { + for (var key in src) if (owns.call(src, key)) obj[key] = src[key]; + return obj; +} + +//merges data without changing state in either argument +function merge(src1, src2) { + var merged = {}; + extend(merged, src1); + extend(merged, src2); + return merged; +} + diff --git a/src/routes/mailserver.js b/src/routes/mailserver.js index 02f6b3b85..29fb5e444 100644 --- a/src/routes/mailserver.js +++ b/src/routes/mailserver.js @@ -2,6 +2,7 @@ exports = module.exports = { proxy, + restart, getLocation, setLocation @@ -11,12 +12,18 @@ var addons = require('../addons.js'), assert = require('assert'), auditSource = require('../auditsource.js'), BoxError = require('../boxerror.js'), + debug = require('debug')('box:routes/mailserver'), HttpError = require('connect-lastmile').HttpError, HttpSuccess = require('connect-lastmile').HttpSuccess, mail = require('../mail.js'), middleware = require('../middleware/index.js'), url = require('url'); +function restart(req, res, next) { + mail.restartMail((error) => debug('Error restarting mail container', error)); + next(); +} + function proxy(req, res, next) { let parsedUrl = url.parse(req.url, true /* parseQueryString */); const pathname = req.path.split('/').pop(); diff --git a/src/server.js b/src/server.js index 1959dec40..78f34918d 100644 --- a/src/server.js +++ b/src/server.js @@ -263,6 +263,8 @@ function initializeExpressSync() { router.post('/api/v1/mailserver/spam_acl', token, authorizeAdmin, routes.mailserver.proxy); router.get ('/api/v1/mailserver/spam_custom_config', token, authorizeAdmin, routes.mailserver.proxy); router.post('/api/v1/mailserver/spam_custom_config', token, authorizeAdmin, routes.mailserver.proxy); + router.get ('/api/v1/mailserver/solr_config', token, authorizeAdmin, routes.mailserver.proxy); + router.post('/api/v1/mailserver/solr_config', token, authorizeAdmin, routes.mailserver.proxy, routes.mailserver.restart); router.get ('/api/v1/mail/:domain', token, authorizeAdmin, routes.mail.getDomain); router.get ('/api/v1/mail/:domain/status', token, authorizeAdmin, routes.mail.getStatus);