Viewing File: /usr/local/cpanel/base/sharedjs/zone_editor/models/dmarc_record.js

/* eslint-disable camelcase */
/*
# models/dmarc_record.js                         Copyright(c) 2020 cPanel, L.L.C.
#                                                           All rights reserved.
# copyright@cpanel.net                                         http://cpanel.net
# This code is subject to the cPanel license. Unauthorized copying is prohibited
*/

define(["lodash"],
    function(_) {

        "use strict";

        var dmarc_regex = /^[vV]\s*=\s*DMARC1\s*;\s*[pP]\s*=/;
        var dmarc_uri_regex = /^[a-z][a-z0-9+.-]*:[^,!;]+(?:![0-9]+[kmgt]?)?$/i;
        var dmarc_uri_scrub = function(val) {

            /* If the value doesn't have a valid URI scheme and it looks
             * vaguely like an email, turn it into a mailto: URI.  The email
             * check is extremely open ended to allow for internationalized
             * emails, which may be used in mailto: URIs -- no punycode
             * required. */
            // TODO: convert domain to punycode for shorter storage (and better validation)
            if (!/^[a-z][a-z0-9+.-]*:/i.test(val) && /@[^\s@]{2,255}$/.test(val)) {

                /* See https://tools.ietf.org/html/rfc6068#section-2 */
                // eslint-disable-next-line no-useless-escape
                val = "mailto:" + encodeURI(val).replace(/[\/?#&;=]/g, function(c) {
                    return "%" + c.charCodeAt(0).toString(16);
                });
            }

            /* Additionally, DMARC requires [,!;] to be URI encoded, as they are
             * used specially by DMARC fields. */
            var invalidChars = /[,!;]/g;
            if (invalidChars.test(val)) {

                /* Strip off a valid file size suffix before munging */
                var size = "";
                val = val.replace(/![0-9]+[kmgt]?$/i, function(trail) {
                    size = trail;
                    return "";
                });

                val = val.replace(invalidChars, function(c) {
                    return "%" + c.charCodeAt(0).toString(16);
                });

                val += size;
            }
            return val;
        };

        /**
         * Checks if a variable is defined, null, and not an empty string
         *
         * @method is_defined_and_not_null
         */
        var is_defined_and_not_null = function(val) {
            return val !== void 0 && val !== null && ((typeof val === "string") ? val.length > 0 : true);
        };

        /**
         * Creates a DMARC Record object
         *
         * @class
         */
        function DMARCRecord() {
            this.resetProperties();
        }

        /**
         * Set (or reset) the object properties to defaults
         *
         * @method resetProperties
         */
        DMARCRecord.prototype.resetProperties = function() {
            this.p = "none";
            this.sp = "none";
            this.adkim = "r";
            this.aspf = "r";
            this.pct = 100;
            this.fo = "0";
            this.rf = "afrf";
            this.ri = 86400;
            this.rua = "";
            this.ruf = "";
        };

        DMARCRecord.prototype.validators = {
            p: {
                values: ["none", "quarantine", "reject"],
                defValue: "none"
            },
            sp: {
                values: ["none", "quarantine", "reject"],
            },
            adkim: {
                values: ["r", "s"],
                defValue: "r"
            },
            aspf: {
                values: ["r", "s"],
                defValue: "r"
            },
            rf: {
                multi: ":",
                values: ["afrf", "iodef"],
                defValue: "afrf",
            },
            fo: {
                multi: ":",
                values: ["0", "1", "s", "d"],
                defValue: "0"
            },
            pct: {
                pattern: /^[0-9]{1,2}$|^100$/,
                defValue: 100
            },
            ri: {
                pattern: /^\d+$/,
                defValue: 86400
            },
            rua: {
                multi: ",",
                scrub: dmarc_uri_scrub,
                pattern: dmarc_uri_regex,
                defValue: ""
            },
            ruf: {
                multi: ",",
                scrub: dmarc_uri_scrub,
                pattern: dmarc_uri_regex,
                defValue: ""
            }
        };

        /**
         * Check whether a text string represents a minimal
         * DMARC record
         *
         * @method isDMARC
         * @param {String} stringToTest
         */
        DMARCRecord.prototype.isDMARC = function(stringToTest) {
            return dmarc_regex.test(stringToTest);
        };

        var processValue = function(propValue, validationOpts, filter) {

            /* Split up multi-valued items (as applicable), and strip whitespace */
            var values = [ propValue ];
            if (validationOpts.multi) {
                values = propValue.split(validationOpts.multi).map(function(s) {
                    return s.trim();
                });
            }

            if (validationOpts.scrub) {
                values = values.map(validationOpts.scrub);
            }

            if (filter) {

                /* Define the appropriate test for finding valid entries */
                var test;
                if (validationOpts.pattern) {
                    test = function(val) {
                        return validationOpts.pattern.test(val.toLowerCase());
                    };
                } else if (validationOpts.values) {
                    test = function(val) {
                        return validationOpts.values.indexOf(val.toLowerCase()) > -1;
                    };
                }

                values = filter(values, test);
            }

            var cleanedValue = values.join(validationOpts.multi);
            return cleanedValue;
        };

        /**
         * Validate the value of a given property.
         *
         * @method isValid
         * @param {String} propName
         * @param {String} propValue
         */
        DMARCRecord.prototype.isValid = function(propName, propValue) {
            var isValid;

            /* Return true iff every value is valid */
            processValue(propValue, this.validators[propName], function(values, validator) {
                isValid = values.every(validator);
                return values;
            });

            return isValid;
        };

        /**
         * Validate and save the value of the given property.  Invalid values
         * are stripped from the property.  If no valid values remain, the
         * default value is saved.
         *
         * @method setValue
         * @param {String} propName
         * @param {String} propValue
         * @param {boolean} removeInvalid (optional)
         */
        DMARCRecord.prototype.setValue = function(propName, propValue, removeInvalid) {
            var filter;
            if (removeInvalid) {
                filter = function(values, validator) {
                    return values.filter(validator);
                };
            }

            var cleanedValue = processValue(propValue, this.validators[propName], filter);

            if (cleanedValue.length) {
                if (typeof this[propName] === "number") {
                    this[propName] = parseInt(cleanedValue, 10);
                } else {
                    this[propName] = cleanedValue;
                }
            } else if (propName === "sp") {
                this.sp = this.p;
            } else {
                this[propName] = this.validators[propName].defValue;
            }
        };

        /**
         * Populate the DMARC record properties from a TXT record
         *
         * @method fromTXT
         * @param {String} rawText - The text from a TXT DNS record
         */
        DMARCRecord.prototype.fromTXT = function(rawText) {
            this.resetProperties();

            if (_.isArray(rawText)) {

                // Multipart String Array (TXT Array)
                rawText = rawText.join(";");

            }

            if (typeof rawText === "string") {
                var properties = rawText.split(";");
                for (var i = 0; i < properties.length; i++) {
                    var keyValue = properties[i].split("=");
                    var propName = keyValue[0].trim().toLowerCase();
                    var propValue = keyValue.slice(1).join("=").trim();
                    if (propName !== "v" && this.hasOwnProperty(propName)) {
                        this.setValue(propName, propValue);
                    }
                }
            }
        };

        /**
         * Return a string version of the DMARC record suitable for saving
         * as a DNS TXT record
         *
         * @method toString
         * @return {String}
         */
        DMARCRecord.prototype.toString = function() {
            var generated_record = "v=DMARC1;p=" + this.p;
            if (is_defined_and_not_null(this.sp)) {
                generated_record += ";sp=" + this.sp;
            }
            if (is_defined_and_not_null(this.adkim)) {
                generated_record += ";adkim=" + this.adkim;
            }
            if (is_defined_and_not_null(this.aspf)) {
                generated_record += ";aspf=" + this.aspf;
            }
            if (is_defined_and_not_null(this.pct)) {
                generated_record += ";pct=" + this.pct;
            }
            if (is_defined_and_not_null(this.fo)) {
                generated_record += ";fo=" + this.fo;
            }
            if (is_defined_and_not_null(this.rf)) {
                generated_record += ";rf=" + this.rf;
            }
            if (is_defined_and_not_null(this.ri)) {
                generated_record += ";ri=" + this.ri;
            }
            if (is_defined_and_not_null(this.rua)) {

                // fix mailto uri list if necessary
                this.setValue("rua", this.rua);
                generated_record += ";rua=" + this.rua;
            }
            if (is_defined_and_not_null(this.ruf)) {

                // fix mailto uri list if necessary
                this.setValue("ruf", this.ruf);
                generated_record += ";ruf=" + this.ruf;
            }
            return generated_record;
        };

        return DMARCRecord;
    }
);
Back to Directory File Manager