File: require_resolvable_fromhost

package info (click to toggle)
qpsmtpd 0.84-9
  • links: PTS
  • area: main
  • in suites: wheezy
  • size: 1,376 kB
  • sloc: perl: 8,012; sh: 382; makefile: 61
file content (150 lines) | stat: -rw-r--r-- 3,897 bytes parent folder | download | duplicates (4)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
use Qpsmtpd::DSN;
use Net::DNS qw(mx);
use Socket;
use Net::IP qw(:PROC);
use Qpsmtpd::TcpServer;

my %invalid = ();
my $has_ipv6 = Qpsmtpd::TcpServer::has_ipv6();

sub hook_mail {
  my ($self, $transaction, $sender, %param) = @_;

  return DECLINED
        if ($self->qp->connection->notes('whitelisthost'));

  foreach my $i ($self->qp->config("invalid_resolvable_fromhost")) {
    $i =~ s/^\s*//;
    $i =~ s/\s*$//;
    if ($i =~ m#^((\d{1,3}\.){3}\d{1,3})/(\d\d?)#) {
      $invalid{$1} = $3;
    }
  }

  if ($sender ne "<>" 
      and !$self->check_dns($sender->host)) {
    if ($sender->host) {
	  $transaction->notes('temp_resolver_failed', $sender->host);
    } 
    else {
      # default of addr_bad_from_system is DENY, we use DENYSOFT here to
      # get the same behaviour as without Qpsmtpd::DSN...
      return Qpsmtpd::DSN->addr_bad_from_system(DENYSOFT, 
                               "FQDN required in the envelope sender");
    }
  }
  return DECLINED;

}

sub hook_rcpt {
	my ($self, $transaction, $recipient, %args) = @_;

	if (my $host = $self->qp->connection->notes('temp_resolver_failed')) {
		# default of temp_resolver_failed is DENYSOFT
		return Qpsmtpd::DSN->temp_resolver_failed("Could not resolve " . $host);
	}

  return DECLINED;
}

sub check_dns {
  my ($self, $host) = @_;
  my @host_answers;

  # for stuff where we can't even parse a hostname out of the address
  return 0 unless $host;

  return 1 if $host =~ m/^\[(\d{1,3}\.){3}\d{1,3}\]$/;

  my $res = new Net::DNS::Resolver;
  $res->tcp_timeout(30);
  $res->udp_timeout(30);
  my @mx = mx($res, $host);
  foreach my $mx (@mx) {
    # if any MX is valid, then we consider the domain
    # resolvable
    return 1 if mx_valid($self, $mx->exchange, $host);
  }
  # if there are MX records, and we got here,
  # then none of them are valid
  return 0 if (@mx > 0);

  my $query = $res->search($host);
  if ($query) {
    foreach my $rrA ($query->answer) {
      push(@host_answers, $rrA);
    }
  }
  if ($has_ipv6) {
    my $query = $res->search($host, 'AAAA');
    if ($query) {
      foreach my $rrAAAA ($query->answer) {
        push(@host_answers, $rrAAAA);
      }
    }
  } 
  if (@host_answers) {
    foreach my $rr (@host_answers) {
      return is_valid($rr->address) if $rr->type eq "A" or $rr->type eq "AAAA";
      return mx_valid($self, $rr->exchange, $host) if $rr->type eq "MX";
    }
  }
  else {
    $self->log(LOGWARN, "$$ query for $host failed: ", $res->errorstring)
      unless $res->errorstring eq "NXDOMAIN";
  }
  return 0;
}

sub is_valid {
  my $ip = shift;
  my ($net,$mask);
  ### while (($net,$mask) = each %invalid) {
  ###         ... does NOT reset to beginning, will start on
  ###         2nd invocation after where it denied the first time..., so
  ###         2nd time the same "MAIL FROM" would be accepted!
  foreach $net (keys %invalid) {
    $mask = $invalid{$net};
    $mask = pack "B32", "1"x($mask)."0"x(32-$mask);
    return 0 
      if join(".", unpack("C4", inet_aton($ip) & $mask)) eq $net;
  }
  return 1; 
}

sub mx_valid {
  my ($self, $name, $host) = @_;
  my $res   = new Net::DNS::Resolver;
  # IP in MX
  return is_valid($name) if ip_is_ipv4($name) or ip_is_ipv6($name);

  my @mx_answers;
  my $query = $res->search($name, 'A');
  if ($query) {
    foreach my $rrA ($query->answer) {
      push(@mx_answers, $rrA);
    }
  }
  if ($has_ipv6) {
    my $query = $res->search($name, 'AAAA');
    if ($query) {
      foreach my $rrAAAA ($query->answer) {
        push(@mx_answers, $rrAAAA);
      }
    }
  }
  if (@mx_answers) {
    foreach my $rr (@mx_answers) {
      next unless $rr->type eq "A" or $rr->type eq "AAAA";
      return is_valid($rr->address);
    }
  }
  else {
    $self->log(LOGWARN, "$$ query for $host failed: ", $res->errorstring)
      unless $res->errorstring eq "NXDOMAIN";
  }
  return 0;
}

# vim: ts=2 sw=2 expandtab syn=perl