Mail-DMARC

 view release on metacpan or  search on metacpan

lib/Mail/DMARC/Policy.pm  view on Meta::CPAN

            if ( !$warned ) {

                #warn "tv: $tv\n";
                warn "invalid DMARC record, please post this message to\n"
                    . "\thttps://github.com/msimerson/mail-dmarc/issues/39\n"
                    . "\t$str\n";
            }
            $warned++;
            next;
        }
        $policy{ lc $tag } = $value;
    }

    # RFC 9989: an unrecognized value for an optional tag is ignored (the tag
    # reverts to its default); it MUST NOT invalidate the whole record. The
    # setters croak, so normalize here on the parse-from-DNS path instead.
    if ( defined $policy{psd} && $policy{psd} !~ /^[ynu]$/i ) {
        warn "ignoring invalid psd ($policy{psd})\n";
        delete $policy{psd};
    }
    if ( defined $policy{t} && $policy{t} !~ /^[yn]$/i ) {
        warn "ignoring invalid t ($policy{t})\n";
        delete $policy{t};
    }

    return bless \%policy, ref $self;    # inherited defaults + overrides
}

sub stringify($self) {
    my %dmarc_record = %{$self};
    delete $dmarc_record{domain};

    my $dmarc_txt = 'v=' . ( delete $dmarc_record{v} );    # "v" tag must be first
    foreach my $key ( keys %dmarc_record ) {
        $dmarc_txt .= "; $key=$dmarc_record{$key}";
    }
    return $dmarc_txt;
}

sub apply_defaults($self) {
    $self->adkim('r') if !defined $self->adkim;
    $self->aspf('r')  if !defined $self->aspf;
    $self->fo(0)      if !defined $self->fo;

    # rf, ri, pct are deprecated in DMARCbis (RFC 9989) and MUST be ignored
    return 1;
}

sub v( $self, $val = undef ) {
    return $self->{v}                 if @_ == 1;
    croak "unsupported DMARC version" if 'DMARC1' ne uc $val;
    return $self->{v} = $val;
}

sub p( $self, $val = undef ) {
    return $self->{p} if @_ == 1;
    croak "invalid p" if !$self->is_valid_p($val);
    return $self->{p} = $val;
}

sub sp( $self, $val = undef ) {
    return $self->{sp}        if @_ == 1;
    croak "invalid sp ($val)" if !$self->is_valid_p($val);
    return $self->{sp} = $val;
}

sub np( $self, $val = undef ) {
    return $self->{np}        if @_ == 1;
    croak "invalid np ($val)" if !$self->is_valid_p($val);
    return $self->{np} = $val;
}

sub psd( $self, $val = undef ) {
    return $self->{psd}        if @_ == 1;
    croak "invalid psd ($val)" if 0 == grep {/^\Q$val\E$/i} qw/ y n u /;
    return $self->{psd} = lc $val;
}

sub t( $self, $val = undef ) {
    return $self->{t}        if @_ == 1;
    croak "invalid t ($val)" if 0 == grep {/^\Q$val\E$/i} qw/ y n /;
    return $self->{t} = lc $val;
}

sub adkim( $self, $val = undef ) {
    return $self->{adkim} if @_ == 1;
    croak "invalid adkim" if 0 == grep {/^\Q$val\E$/ix} qw/ r s /;
    return $self->{adkim} = $val;
}

sub aspf( $self, $val = undef ) {
    return $self->{aspf} if @_ == 1;
    croak "invalid aspf" if 0 == grep {/^\Q$val\E$/ix} qw/ r s /;
    return $self->{aspf} = $val;
}

sub fo( $self, $val = undef ) {
    return $self->{fo}       if @_ == 1;
    croak "invalid fo: $val" if $val !~ /^[01ds](:[01ds])*$/ix;
    return $self->{fo} = $val;
}

sub rua( $self, $val = undef ) {
    return $self->{rua} if @_ == 1;
    croak "invalid rua" if !$self->is_valid_uri_list($val);
    return $self->{rua} = $val;
}

sub ruf( $self, $val = undef ) {
    return $self->{ruf} if @_ == 1;
    croak "invalid rua" if !$self->is_valid_uri_list($val);
    return $self->{ruf} = $val;
}

sub rf( $self, $val = undef ) {
    return $self->{rf} if @_ == 1;
    foreach my $f ( split /,/, $val ) {
        croak "invalid format: $f" if !$self->is_valid_rf($f);
    }
    return $self->{rf} = $val;
}

sub ri( $self, $val = undef ) {
    return $self->{ri}          if @_ == 1;
    croak "not numeric ($val)!" if $val =~ /\D/;
    croak "not an integer!"     if $val != int $val;
    croak "out of range"        if ( $val < 0 || $val > 4294967295 );
    return $self->{ri} = $val;
}

sub pct( $self, $val = undef ) {
    return $self->{pct}         if @_ == 1;
    croak "not numeric ($val)!" if $val =~ /\D/;
    croak "not an integer!"     if $val != int $val;
    croak "out of range"        if $val < 0 || $val > 100;
    return $self->{pct} = $val;
}

sub domain( $self, $val = undef ) {
    return $self->{domain} if @_ == 1;
    return $self->{domain} = $val;
}

sub is_valid_rf( $self, $f ) {
    return ( grep {/^\Q$f\E$/i} qw/ iodef afrf / ) ? 1 : 0;
}

sub is_valid_p( $self, $p ) {
    croak "unspecified p" if !defined $p;
    return ( grep {/^\Q$p\E$/i} qw/ none reject quarantine / ) ? 1 : 0;
}

sub is_valid_uri_list( $self, $str ) {
    $self->{uri} ||= Mail::DMARC::Report::URI->new;
    my $uris = $self->{uri}->parse($str);
    return scalar @$uris;
}

sub is_valid( $self, $obj = undef ) {
    $obj = $self                      if !$obj;
    croak "missing version specifier" if !$obj->{v};
    croak "invalid version"           if 'DMARC1' ne uc $obj->{v};

    # psd=y domains (PSDs) are not required to have a p= tag
    my $is_psd = defined $obj->{psd} && lc $obj->{psd} eq 'y';

    if ( !$obj->{p} && !$is_psd ) {
        if ( $obj->{rua} && $self->is_valid_uri_list( $obj->{rua} ) ) {
            $obj->{p} = 'none';
        }
        else {
            croak "missing policy action (p=)";
        }
    }
    if ( $obj->{p} ) {
        croak "invalid policy action" if !$self->is_valid_p( $obj->{p} );
    }
    if ( defined $obj->{np} ) {
        croak "invalid np" if !$self->is_valid_p( $obj->{np} );
    }

    # everything else is optional
    return 1;



( run in 3.532 seconds using v1.01-cache-2.11-cpan-4ab04211f4c )