def check_dmarc(msg,
                spf_result=None,
                dkim_result=None,
                dnsfunc=None,
                psddmarc=False):

    # get from domain
    headers, _ = rfc822_parse(msg)
    from_headers = [
        a[1] for a in getaddresses(x[1].decode(errors='ignore').strip()
                                   for x in headers if x[0].lower() == b"from")
    ]

    if len(from_headers) > 1:
        # multi-from processing per RFC 7489 6.6.1
        domain_results = []
        for from_header in from_headers:
            from_domain = get_domain_part(from_header)
            domain_results.append(
                dmarc_per_from(from_domain, spf_result, dkim_result, dnsfunc,
                               psddmarc))

        for domain in domain_results:
            if domain[3] == 'reject':
                result, result_comment, from_domain, policy = domain
                return DMARCAuthenticationResult(result=result,
                                                 result_comment=result_comment,
                                                 header_from=from_domain,
                                                 policy=policy)
        for domain in domain_results:
            if domain[3] == 'quarantine':
                result, result_comment, from_domain, policy = domain
                return DMARCAuthenticationResult(result=result,
                                                 result_comment=result_comment,
                                                 header_from=from_domain,
                                                 policy=policy)
        for domain in domain_results:
            if domain[3] == 'none':
                result, result_comment, from_domain, policy = domain
                return DMARCAuthenticationResult(result=result,
                                                 result_comment=result_comment,
                                                 header_from=from_domain,
                                                 policy=policy)
        result, result_comment, from_domain, policy = domain
    elif len(from_headers) == 1:
        from_header = from_headers[0]
        from_domain = get_domain_part(from_header)
        result, result_comment, from_domain, policy = dmarc_per_from(
            from_domain, spf_result, dkim_result, dnsfunc, psddmarc)
    else:
        result = 'none'

    if result != 'none':
        return DMARCAuthenticationResult(result=result,
                                         result_comment=result_comment,
                                         header_from=from_domain,
                                         policy=policy)
    else:
        return DMARCAuthenticationResult(result=result,
                                         header_from=from_domain)
Example #2
0
    def send(self, *args):
        """
        Must Be Two Parameters recipient and email body
        """
        if not args:
            raise SMTPServerError("Fatal error: Email Content must have the recipient and body") 
        if len(args) == 1:
            raise SMTPServerError("Fatal error: Email Content must have the body") 

        """
        try:
            self.server.connect(self.host,self.port)
        except Exception,e:
            raise SMTPServerError(e)
        """

        recipient=args[0]
        body=args[1]

        senddate="%s %s"%(time.strftime("%a, %d %b %Y %H:%M:%S",time.localtime()),"+0800")
        messageid="<%s>"%recipient
        mimeversion = "1.0"

        header="From:%s\r\nTo:%s\r\nSubject:%s\r\nMIME-Version:%s\r\nMessage-Id:%s\r\nDate:%s\r\nX-Mailer:%s\r\nContent-Type:%s"%(self.mailfrom,recipient,self.subject,mimeversion,messageid,senddate,XMailer,self.contenttype)
        content="%s\r\n\r\n%s"%(header,body)
        content ==  dkim.rfc822_parse(content)

        if self.dkim:
            domain = re.search(r"[^@]+$",self.mailfrom.strip()).group(0)
            sig = dkim.sign(content,'_dkim',domain,open(self.dkim).read(),include_headers=["From","To","Subject","Date","Message-Id","X-Mailer"],canonicalize=(dkim.Simple, dkim.Relaxed))
            content = sig + content

        Retry = 3
        while True:
            try:
                self.server.sendmail(self.sender,recipient,content)
                return True
                break
            except smtplib.SMTPServerDisconnected:
                if Retry == 0:
                    raise SMTPServerError("Lost the Connection with the Server")
                    return False
                    break
                else:
                    try:
                        self.connect()
                        Retry = Retry - 1
                    except:
                        Retry = Retry - 1
            except Exception,e:
                if Retry == 0:
                    raise SMTPServerError(e)
                    return False
                    break
                else:
                    Retry = Retry - 1
Example #3
0
def validate_mail(message):

    # Step 1: Read the from address, so that we at least know where to 
    #         send the error message to
    from_addr = None
    try:
        # Get the one and only from address
        (headers, body) = dkim.rfc822_parse(message)
        from_addr  = __get_raw_email_addr(__get_required_header(headers, "From"))
        if not is_email_address(from_addr):
            return (None, False, "'" + str(from_addr) + "' is not valid From: address")
    except Exception, e:
            return (None, False, e.__str__())
Example #4
0
def validate_mail(message):

    # Step 1: Read the from address, so that we at least know where to
    #         send the error message to
    from_addr = None
    try:
        # Get the one and only from address
        (headers, body) = dkim.rfc822_parse(message)
        from_addr = __get_raw_email_addr(__get_required_header(
            headers, "From"))
        if not is_email_address(from_addr):
            return (None, False,
                    "'" + str(from_addr) + "' is not valid From: address")
    except Exception, e:
        return (None, False, e.__str__())
Example #5
0
File: csdkim.py Project: bkzk/minja
def dkim_canon(canon, message):

    if True:
        m = re.match('^(simple|relaxed)/(simple|relaxed)$',
                     canon.strip(' \t\n\r').lower())
        if m:
            try:
                import dkim

                (headers, body) = dkim.rfc822_parse(message)

                ch = dkim.canonicalization.Simple
                cb = dkim.canonicalization.Simple

                #print headers
                #print body
                #dbginfo('info',str(headers))

                if m.group(1) == 'simple':
                    ch = dkim.canonicalization.Simple.canonicalize_headers(
                        headers)
                else:
                    ch = dkim.canonicalization.Relaxed.canonicalize_headers(
                        headers)
                #print ch

                if m.group(2) == 'simple':
                    cb = dkim.canonicalization.Simple.canonicalize_body(body)
                else:
                    cb = dkim.canonicalization.Relaxed.canonicalize_body(body)

                #print cb

                return (ch, cb)

            except ImportError:
                print
                dbginfo(
                    'warrning',
                    'Warrning: Missing module: dkimpy. Use: pip install dkimpy'
                )
                print
        else:
            #fpr.err('NO MATCH %s' %canon)
            return None

    return False
Example #6
0
def nofws(message, include_heads):
    """
    No Folding Whitespace
    参考: http://tools.ietf.org/html/rfc4870#page-19
    """
    headers, body = dkim.rfc822_parse(message)
    headers_dict = dict(headers)

    heads = [
        '%s:%s' % (k, re.sub('\s', '', v)) for k, v in headers_dict.items()
        if k in include_heads
    ]

    data = '\n'.join(heads) + '\n'

    for e in ('\n' + body.replace('\r', '')).split('\n'):
        data += re.sub('\s', '', e) + '\n'

    data = data.rstrip('\n').split('\n')
    data = '\r\n'.join(data) + '\r\n'

    return data
def check_dmarc(msg, spf_result=None, dkim_result=None, dnsfunc=None):
    # get from domain
    headers, _ = rfc822_parse(msg)
    from_headers = [x[1] for x in headers if x[0].lower() == b"from"]
    if len(from_headers) != 1:
        raise Exception("")
    from_header = from_headers[0]

    # kind of janky
    res = re.search(b'@(.*)>', from_header)
    from_domain = res.group(1).decode('ascii')

    # get dmarc record
    if(dnsfunc):
        record, _ = receiver_record(from_domain, dnsfunc=dnsfunc)
    else:
        record, _ = receiver_record(from_domain)

    adkim = record.get('adkim', 'r')
    aspf  = record.get('aspf',  'r')

    # get result
    result = "fail"
    if spf_result and spf_result.result == "pass":
        if aspf == "s" and from_domain == spf_result.smtp_mailfrom:
            result = "pass"
        elif aspf == "r" and get_org_domain(from_domain) == get_org_domain(spf_result.smtp_mailfrom):
            result = "pass"

    if dkim_result and dkim_result.result == "pass":
        if adkim == "s" and from_domain == dkim_result.header_d:
            result = "pass"
        elif adkim == "r" and get_org_domain(from_domain) == get_org_domain(dkim_result.header_d):
            result = "pass"

    return DMARCAuthenticationResult(result=result, header_from=from_domain)
Example #8
0
def __validate_mail(message):

    # First things first (and this is critical),
    # validate the DKIM signature
    if not dkim.verify(message):
        raise ValidateMailException("DKIM signature verification failed\n")

    # Parse the email
    (headers, body) = dkim.rfc822_parse(message)

    # Get the one and only DKIM-Signature header and from address
    from_addr = __get_raw_email_addr(__get_required_header(headers, "From"))
    dkim_sig = __get_required_header(headers, "DKIM-Signature")

    # Check that the from address and the Return-Path address are consistent
    return_paths = __get_headers_by_name(headers, "Return-Path")
    if len(return_paths) == 0:
        raise ValidateMailException("No return paths specified\n")
    for rp in return_paths:
        if __get_raw_email_addr(rp) != from_addr:
            raise ValidateMailException("'Return-Path: " + str(rp) +
                                        "' does not match from address.\n")

    # Check a few things in the DKIM header
    dkim_fields = parse_dkim_sig(dkim_sig)

    if 'bh' not in dkim_fields:
        raise ValidateMailException("Missing the DKIM body hash.\n")

    if 'h' not in dkim_fields:
        raise ValidateMailException("Missing DKIM headers key (h=).\n")

    if 'd' not in dkim_fields:
        raise ValidateMailException("Missing DKIM domain field.\n")

    if dkim_fields['d'] != "gmail.com":
        raise ValidateMailException("Not from gmail.com\n")

    signed_headers = [
        fld.lower().strip() for fld in dkim_fields['h'].split(":")
    ]
    if 'from' not in signed_headers:
        raise ValidateMailException(
            "From address is not included in signed headers!\n")

    # Some other magic stuff
    # NOTE: It is legal for there to be numerous Authentication-Results headers,
    #       but, we won't handle that yet. Instead, just fail if there is more than one.
    auth_results = __get_required_header(headers,
                                         "Authentication-Results").strip()
    if not re.match(r"^mx\.google\.com;", auth_results):
        raise ValidateMailException(
            "Authentication-Results header not from mx.google.com\n")

    # check various features
    auth_results = " " + re.sub(r"\s+", " ",
                                auth_results).lower().strip() + " "

    if " dkim=pass " not in auth_results:
        raise ValidateMailException(
            "Authentication-Results failure: No 'dkim=pass'\n")

    if " dmarc=pass " not in auth_results:
        raise ValidateMailException(
            "Authentication-Results failure: No 'dmarc=pass'\n")

    if " spf=pass " not in auth_results:
        raise ValidateMailException(
            "Authentication-Results failure: No 'spf=pass'\n")

    # Try to get the smtp.mailfrom header
    if not re.match(
            r"^.*spf=pass [^;]+? smtp.mailfrom=" + re.escape(from_addr) +
            "(;.*)?$", auth_results):
        raise ValidateMailException(
            "Authentication-Results failure: invalid or missing smtp.mailfrom address\n"
        )

    return from_addr
Example #9
0
def __validate_mail(message):

    # First things first (and this is critical), 
    # validate the DKIM signature
    if not dkim.verify(message):
        raise ValidateMailException("DKIM signature verification failed\n")

    # Parse the email
    (headers, body) = dkim.rfc822_parse(message)

    # Get the one and only DKIM-Signature header and from address
    from_addr = __get_raw_email_addr(__get_required_header(headers, "From"))
    dkim_sig  = __get_required_header(headers, "DKIM-Signature")

    # Check that the from address and the Return-Path address are consistent
    return_paths = __get_headers_by_name(headers, "Return-Path")
    if len(return_paths) == 0:
        raise ValidateMailException("No return paths specified\n")
    for rp in return_paths:
        if __get_raw_email_addr(rp) != from_addr:
            raise ValidateMailException("'Return-Path: " + str(rp) + "' does not match from address.\n")

    # Check a few things in the DKIM header
    dkim_fields = parse_dkim_sig(dkim_sig)

    if 'bh' not in dkim_fields:
        raise ValidateMailException("Missing the DKIM body hash.\n")

    if 'h' not in dkim_fields:
        raise ValidateMailException("Missing DKIM headers key (h=).\n")

    if 'd' not in dkim_fields:
        raise ValidateMailException("Missing DKIM domain field.\n")

    if dkim_fields['d'] != "gmail.com":
        raise ValidateMailException("Not from gmail.com\n")

    signed_headers = [fld.lower().strip() for fld in dkim_fields['h'].split(":")]
    if 'from' not in signed_headers:
        raise ValidateMailException("From address is not included in signed headers!\n")

    # Some other magic stuff
    # NOTE: It is legal for there to be numerous Authentication-Results headers, 
    #       but, we won't handle that yet. Instead, just fail if there is more than one.
    auth_results = __get_required_header(headers, "Authentication-Results").strip()
    if not re.match(r"^mx\.google\.com;", auth_results):
        raise ValidateMailException("Authentication-Results header not from mx.google.com\n")
 
    # check various features    
    auth_results = " " + re.sub(r"\s+", " ", auth_results).lower().strip() + " "

    if " dkim=pass " not in auth_results:
        raise ValidateMailException("Authentication-Results failure: No 'dkim=pass'\n")

    if " dmarc=pass " not in auth_results:
        raise ValidateMailException("Authentication-Results failure: No 'dmarc=pass'\n")

    if " spf=pass " not in auth_results:
        raise ValidateMailException("Authentication-Results failure: No 'spf=pass'\n")

    # Try to get the smtp.mailfrom header
    if not re.match(r"^.*spf=pass [^;]+? smtp.mailfrom=" + re.escape(from_addr) + "(;.*)?$", auth_results):
        raise ValidateMailException("Authentication-Results failure: invalid or missing smtp.mailfrom address\n")

    return from_addr
Example #10
0
def check_dmarc(msg,
                spf_result=None,
                dkim_result=None,
                dnsfunc=None,
                psddmarc=False):
    def check_psddmarc_list(psdname, dnsfunc=dns_query):
        """Check psddmarc.org list of PSD DMARC participants"""
        try:
            # If the PSD registry is locally available, use it.
            psdfile_name = resource_filename('authheaders', 'psddmarc.csv')
            psd_file = open(psdfile_name)
            psds = []
            for line in psd_file.readlines():
                sp = line.split(',')
                if sp[1] == 'Active':
                    psds += sp[0]
            if psdname in psds:
                return True
            else:
                return False
        except:
            # If not, use the DNS query list.
            psd_list_host = '.psddmarc.org'
            psd_lookup = psdname + psd_list_host
            answer = dnsfunc(psd_lookup)
            if answer:
                return True
            else:
                return False

    def dmarc_per_from(from_domain,
                       spf_result=None,
                       dkim_result=None,
                       dnsfunc=None,
                       psddmarc=False):
        # Get dmarc record for domain
        if (dnsfunc):
            record, orgdomain = receiver_record(from_domain, dnsfunc=dnsfunc)
        else:
            record, orgdomain = receiver_record(from_domain)
        # Report if DMARC record is From Domain or Org Domain
        if record and orgdomain:
            result_comment = 'Used Org Domain Record'
        elif record:
            result_comment = 'Used From Domain Record'

        # Get psddmarc record if doing PSD DMARC, no DMARC record, and PSD is
        #  listed
        psddomain = False
        if (not record) and psddmarc:
            org_domain = get_org_domain(from_domain)
            if (dnsfunc):
                if check_psddmarc_list(org_domain.split('.', 1)[-1],
                                       dnsfunc=dnsfunc):
                    record, _ = receiver_record(org_domain.split('.', 1)[-1],
                                                dnsfunc=dnsfunc)
            else:
                if check_psddmarc_list(org_domain.split('.', 1)[-1]):
                    record, _ = receiver_record(org_domain.split('.', 1)[-1])
            if record:
                psddomain = True
                result_comment = 'Used Public Suffix Domain Record'

        if record and record.get('p'):
            # find policy
            policy = record['p']
            if policy[-1:] == '\\':
                policy = policy[:-1]
            try:
                sp = record['sp']
                if sp[-1:] == '\\':
                    sp = sp[:-1]
            except KeyError:
                sp = policy
            try:
                np = record['np']
                if np[-1:] == '\\':
                    np = np[:-1]
            except KeyError:
                np = None

            if orgdomain or psddomain:
                if np:
                    exists = False
                    for qtype in ['a', 'mx', 'aaaa']:
                        if (dnsfunc):
                            res = dnsfunc(from_domain)
                        else:
                            res = dns_query(from_domain, qtype)
                        if res:
                            exists = True
                            break
                    if exists:
                        policy = sp
                    else:
                        policy = np
                else:
                    policy = sp

            adkim = record.get('adkim', 'r')
            aspf = record.get('aspf', 'r')

            # get result
            result = "fail"
            if spf_result and spf_result.result == "pass":
                # The domain in SPF results often includes the local part, even though
                # generally it SHOULD NOT (RFC 7601, Section 2.7.2, last paragraph).
                mail_from_domain = get_domain_part(spf_result.smtp_mailfrom)
                spf_result.smtp_mailfrom = mail_from_domain
                if aspf == "s" and from_domain == mail_from_domain:
                    result = "pass"
                elif aspf == "r" and get_org_domain(
                        from_domain) == get_org_domain(mail_from_domain):
                    result = "pass"

            if dkim_result and dkim_result.result == "pass":
                if adkim == "s" and from_domain == dkim_result.header_d:
                    result = "pass"
                elif adkim == "r" and get_org_domain(
                        from_domain) == get_org_domain(dkim_result.header_d):
                    result = "pass"
        else:
            # If no DMARC record, no result
            result = 'none'
            result_comment = ''
            from_domain = ''
            policy = ''
        return (result, result_comment, from_domain, policy)

    # get from domain
    headers, _ = rfc822_parse(msg)
    from_headers = [
        a[1] for a in getaddresses(x[1].decode(errors='ignore').strip()
                                   for x in headers if x[0].lower() == b"from")
    ]

    if len(from_headers) > 1:
        # multi-from processing per RFC 7489 6.6.1
        domain_results = []
        for from_header in from_headers:
            from_domain = get_domain_part(from_header)
            domain_results.append(
                dmarc_per_from(from_domain, spf_result, dkim_result, dnsfunc,
                               psddmarc))

        for domain in domain_results:
            if domain[3] == 'reject':
                result, result_comment, from_domain, policy = domain
                return DMARCAuthenticationResult(result=result,
                                                 result_comment=result_comment,
                                                 header_from=from_domain,
                                                 policy=policy)
        for domain in domain_results:
            if domain[3] == 'quarantine':
                result, result_comment, from_domain, policy = domain
                return DMARCAuthenticationResult(result=result,
                                                 result_comment=result_comment,
                                                 header_from=from_domain,
                                                 policy=policy)
        for domain in domain_results:
            if domain[3] == 'none':
                result, result_comment, from_domain, policy = domain
                return DMARCAuthenticationResult(result=result,
                                                 result_comment=result_comment,
                                                 header_from=from_domain,
                                                 policy=policy)
        result, result_comment, from_domain, policy = domain
    else:
        from_header = from_headers[0]
        from_domain = get_domain_part(from_header)
        result, result_comment, from_domain, policy = dmarc_per_from(
            from_domain, spf_result, dkim_result, dnsfunc, psddmarc)

    if result != 'none':
        return DMARCAuthenticationResult(result=result,
                                         result_comment=result_comment,
                                         header_from=from_domain,
                                         policy=policy)
    else:
        return DMARCAuthenticationResult(result=result,
                                         header_from=from_domain)