sub parse_authres()

in lib/Mail/SpamAssassin/Plugin/AuthRes.pm [377:597]


sub parse_authres {
  my ($self, $pms, $hdrname, $hdr) = @_;

  dbg("authres: parsing $hdrname: $hdr");

  my $authserv;
  my $version = 1;
  my @methods;
  my $arc_index;

  local $_ = $hdr;

  if ($hdrname =~ /^ARC-/i) {
    if (!/\Gi\b/gcs) {
      die("missing arc index: $hdr");
    }
    skip_cfws();
    if (!/\G=/gcs) {
      die("invalid arc index: ".substr($_, pos())."\n");
    }
    skip_cfws();
    if (!/\G(\d+)/gcs) {
      die("invalid arc index: ".substr($_, pos())."\n");
    }
    $arc_index = $1;
    if ($arc_index < 1 || $arc_index > 50) {
      die("invalid arc index: $arc_index\n");
    }
    skip_cfws();
    if (!/\G;/gcs) {
      die("missing delimiter: ".substr($_, pos())."\n");
    }
    skip_cfws();
  }

  # authserv-id
  if (!/\G($TOKEN)/gcs) {
    die("invalid authserv: ".substr($_, pos())."\n");
  }
  $authserv = lc($1);

  # some invalid headers start with spf=foo etc, missing authserv-id
  if (/\G=/gcs) {
    die("missing authserv: $hdr\n");
  }

  if (%{$pms->{conf}->{authres_trusted_authserv}}) {
    if (!$pms->{conf}->{authres_trusted_authserv}->{$authserv}) {
      die("authserv not trusted: $authserv\n");
    }
  }
  if ($pms->{conf}->{authres_ignored_authserv}->{$authserv}) {
    die("ignored authserv: $authserv\n");
  }

  # skip authserv version
  skip_cfws();
  if (/\G\d+/gcs) {
    skip_cfws();
  }

  if (!/\G;/gcs) {
    die("missing delimiter: ".substr($_, pos())."\n");
  }
  skip_cfws();

  while (pos() < length()) {
    my ($method, $result);
    my $reason = '';
    my $props = {};

    # some silly generators add duplicate authserv-id; here
    if (/\G\Q${authserv}\E\s*;/gcs) {
      skip_cfws();
    }

    # skip none method
    if (/\Gnone\b/igcs) {
      die("method none\n");
    }

    # method / version = result
    if (!/\G([\w-]+)/gcs) {
      die("invalid method: ".substr($_, pos())."\n");
    }
    $method = lc($1);
    if (!exists $method_result{$method}) {
      die("unknown method: $method: $hdr\n");
    }
    skip_cfws();
    if (/\G\//gcs) {
      skip_cfws();
      if (!/\G\d+/gcs) {
        die("invalid $method version: ".substr($_, pos())."\n");
      }
      $version = $1;
      skip_cfws();
    }
    if (!/\G=/gcs) {
      die("missing result for $method: ".substr($_, pos())."\n");
    }
    skip_cfws();
    if (!/\G(\w+)/gcs) {
      die("invalid result for $method: ".substr($_, pos())."\n");
    }
    $result = $1;
    if (!exists $method_result{$method}{$result}) {
      die("unknown result for $method: $result\n");
    }
    skip_cfws();

    # reason = value
    if (/\Greason\b/igcs) {
      skip_cfws();
      if (!/\G=/gcs) {
        die("invalid reason: ".substr($_, pos())."\n");
      }
      skip_cfws();
      if (!/\G$QUOTED_STRING|($TOKEN)/gcs) {
        die("invalid reason: ".substr($_, pos())."\n");
      }
      $reason = defined $1 ? $1 : $2;
      skip_cfws();
    }

    # action = value (some microsoft ARC stuff?)
    if (/\Gaction\b/igcs) {
      skip_cfws();
      if (!/\G=/gcs) {
        die("invalid action: ".substr($_, pos())."\n");
      }
      skip_cfws();
      if (!/\G$QUOTED_STRING|$TOKEN/gcs) {
        die("invalid action: ".substr($_, pos())."\n");
      }
      skip_cfws();
    }

    # ptype.property = value
    while (pos() < length()) {
      my ($ptype, $property, $value);

      # no props?
      if (/\G(?:;|$)/gcs) {
        skip_cfws();
        last;
      }

      # ptype
      if (!/\G([\w-]+)/gcs) {
        die("invalid ptype: ".substr($_,pos())."\n");
      }
      $ptype = lc($1);
      if (!exists $method_ptype_prop{$method}{$ptype}) {
        die("unknown ptype: $method/$ptype\n");
      }
      skip_cfws();

      # dot
      if (!/\G\./gcs) {
        die("missing property: ".substr($_, pos())."\n");
      }
      skip_cfws();

      # property
      if (!/\G([\w-]+)/gcs) {
        die("invalid property: ".substr($_, pos())."\n");
      }
      $property = lc($1);
      if (!exists $method_ptype_prop{$method}{$ptype}{$property} &&
          !exists $method_ptype_prop{$method}{$ptype}{'*'}) {
        die("unknown property for $method/$ptype: $property\n");
      }
      skip_cfws();

      # =
      if (!/\G=/gcs) {
        die("missing property value: ".substr($_, pos())."\n");
      }
      skip_cfws();

      # value:
      # The grammar is ( value / [ [ local-part ] "@" ] domain-name )
      # where value := token / quoted-string
      # and local-part := dot-atom / quoted-string / obs-local-part
      if (!/\G$QUOTED_STRING|($ATOM(?:\.$ATOM)*|$TOKEN)(?=(?:[\s;]|$))/gcs) {
        die("invalid $method/$ptype.$property value: ".substr($_, pos())."\n");
      }
      $value = defined $1 ? $1 : $2;
      skip_cfws();

      $props->{$ptype}->{$property} = $value;

      if (/\G(?:;|$)/gcs) {
        skip_cfws();
        last;
      }
    }

    push @methods, [$method, {
        'authserv' => $authserv,
        'version' => $version,
        'result' => $result,
        'reason' => $reason,
        'properties' => $props,
        'arc_index' => $arc_index,
        }];
  }

  # paranoid check..
  if (pos() < length()) {
    die("parse ended prematurely? ".substr($_, pos())."\n");
  }

  # Pushed to pms only if header parsed completely
  foreach my $marr (@methods) {
    push @{$pms->{authres_parsed}->{$marr->[0]}}, $marr->[1];
  }

  return 1;
}