msimerson/Haraka

View on GitHub
outbound/config.js

Summary

Maintainability
C
1 day
Test Coverage
'use strict';

const config = require('haraka-config');
const logger = require('../logger');

function load_config () {
    const cfg = exports.cfg = config.get('outbound.ini', {
        booleans: [
            '-disabled',
            '-always_split',
            '+enable_tls',
            '-ipv6_enabled',
            '-local_mx_ok',
        ],
    }, () => {
        load_config();
    }).main;

    // legacy config file support. Remove in Haraka 4.0
    if (!cfg.disabled && config.get('outbound.disabled')) {
        cfg.disabled = true;
    }
    if (!cfg.enable_tls && config.get('outbound.enable_tls')) {
        cfg.enable_tls = true;
    }
    if (!cfg.temp_fail_intervals) {
        cfg.temp_fail_intervals = config.get('outbound.temp_fail_intervals');
    }
    if (!cfg.maxTempFailures) {
        cfg.maxTempFailures = config.get('outbound.maxTempFailures') || 13;
    }
    if (!cfg.concurrency_max) {
        cfg.concurrency_max = config.get('outbound.concurrency_max') || 10000;
    }
    if (!cfg.connect_timeout) {
        cfg.connect_timeout = 30;
    }
    if (!cfg.ipv6_enabled && config.get('outbound.ipv6_enabled')) {
        cfg.ipv6_enabled = true;
    }
    if (!cfg.received_header) {
        cfg.received_header = config.get('outbound.received_header') || 'Haraka outbound';
    }

    exports.set_temp_fail_intervals();
}

exports.set_temp_fail_intervals = function () {
    // Set the outbound temp fail intervals (retry times) using the following rules:
    //   1) temp_fail_intervals takes precedence over maxTempFailures if both are specified
    //   2) if temp_fail_intervals is not specified or is illegally specified, then initialize
    //      it with the equivalent times of maxTempFailures using the original 2^N formula
    //   3) the word "none" can be specified if you do not want to retry a temp failure,
    //      equivalent behavior of specifying maxTempFailures=1
    const { cfg } = this;

    // Fallback function to create an array of the original retry times
    function set_old_defaults () {
        cfg.temp_fail_intervals = [];
        for (let i=1; i<cfg.maxTempFailures; i++) {
            cfg.temp_fail_intervals.push(2 ** (i + 5));
        }
    }

    // Helpful error function in case of parsing failure
    function error (i, msg) {
        logger.logerror(`outbound temp_fail_intervals syntax error parsing element ${i}: ${msg}`);
        logger.logwarn('Setting outbound temp_fail_intervals to old defaults because of previous error');
        set_old_defaults();
    }

    // If the new value isn't specified, then create the old defaults
    if (!cfg.temp_fail_intervals) {
        return set_old_defaults();
    }

    // If here then turn the text input into an expanded array of intervals (in seconds)
    // i.e, turn "1m,5m*2,1h*3" into [60,300,300,3600,3600,3600]
    // Parse manually to do better syntax checking and provide better failure messages
    const times = [];
    let input = cfg.temp_fail_intervals.replace(/\s+/g, '').toLowerCase();
    if (input.length === 0) return error(0, 'nothing specified');
    if (input === 'none') {
        cfg.temp_fail_intervals = [];
        return;
    }
    input = input.split(',')

    for (let i=0; i<input.length; i++) {
        const delay = input[i].split('*');
        if (delay.length === 1) delay.push(1);
        else if (delay.length === 2) delay[1] = Number(delay[1]);
        else return error(i, 'too many *');
        if (!Number.isInteger(delay[1])) return error(i, 'multiplier is not an integer');

        if (delay[0].length < 2) error(i, 'invalid time span');
        const symbol = delay[0].charAt(delay[0].length - 1);
        let num = Number(delay[0].slice(0, -1));
        if (isNaN(num)) return error(i, 'invalid number or symbol');

        switch (symbol) {
            case 's':
                // do nothing, this is the base unit
                break;
            case 'm':
                num *= 60;
                break;
            case 'h':
                num *= 3600;
                break;
            case 'd':
                num *= 86400;
                break;
            default:
                return error(i, 'invalid time span symbol');
        }
        // Sanity check (what should this number be?)
        if (num < 5) return error(i, 'delay time too small, should be >=5 seconds')
        for (let j = 0; j < delay[1]; j++) {
            times.push(num);
        }
    }

    // One last check, just in case...should never be true
    if (times.length === 0) return error(0, 'unexpected parsing result');

    // If here, success, so actually store the calculated array in the config
    cfg.temp_fail_intervals = times;
}

load_config();