~ubuntu-branches/ubuntu/utopic/spamassassin/utopic-updates

« back to all changes in this revision

Viewing changes to .pc/98_bug721565-syntax-5.18/lib/Mail/SpamAssassin/DnsResolver.pm

  • Committer: Package Import Robot
  • Author(s): Noah Meyerhans
  • Date: 2014-02-14 22:45:15 UTC
  • mfrom: (0.8.1) (0.6.2) (5.1.22 sid)
  • Revision ID: package-import@ubuntu.com-20140214224515-z1es2twos8xh7n2y
Tags: 3.4.0-1
* New upstream version! (Closes: 738963, 738872, 738867)
* Scrub the environment when switching to the debian-spamd user in
  postinst and cron.daily. (Closes: 738951)
* Enhancements to postinst to better manage ownership of
  /var/lib/spamassassin, via Iain Lane <iain.lane@canonical.com>
  (Closes: 738974)

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
# <@LICENSE>
2
 
# Licensed to the Apache Software Foundation (ASF) under one or more
3
 
# contributor license agreements.  See the NOTICE file distributed with
4
 
# this work for additional information regarding copyright ownership.
5
 
# The ASF licenses this file to you under the Apache License, Version 2.0
6
 
# (the "License"); you may not use this file except in compliance with
7
 
# the License.  You may obtain a copy of the License at:
8
 
9
 
#     http://www.apache.org/licenses/LICENSE-2.0
10
 
11
 
# Unless required by applicable law or agreed to in writing, software
12
 
# distributed under the License is distributed on an "AS IS" BASIS,
13
 
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
 
# See the License for the specific language governing permissions and
15
 
# limitations under the License.
16
 
# </@LICENSE>
17
 
 
18
 
=head1 NAME
19
 
 
20
 
Mail::SpamAssassin::DnsResolver - DNS resolution engine
21
 
 
22
 
=head1 DESCRIPTION
23
 
 
24
 
This is a DNS resolution engine for SpamAssassin, implemented in order to
25
 
reduce file descriptor usage by Net::DNS and avoid a response collision bug in
26
 
that module.
27
 
 
28
 
=head1 METHODS
29
 
 
30
 
=over 4
31
 
 
32
 
=cut
33
 
 
34
 
# TODO: caching in this layer instead of in callers.
35
 
 
36
 
package Mail::SpamAssassin::DnsResolver;
37
 
 
38
 
use strict;
39
 
use warnings;
40
 
use bytes;
41
 
use re 'taint';
42
 
 
43
 
use Mail::SpamAssassin;
44
 
use Mail::SpamAssassin::Logger;
45
 
 
46
 
use Socket;
47
 
use IO::Socket::INET;
48
 
use Errno qw(EADDRINUSE EACCES);
49
 
use Time::HiRes qw(time);
50
 
 
51
 
use constant HAS_SOCKET_INET6 => eval { require IO::Socket::INET6; };
52
 
 
53
 
our @ISA = qw();
54
 
 
55
 
###########################################################################
56
 
 
57
 
sub new {
58
 
  my $class = shift;
59
 
  $class = ref($class) || $class;
60
 
 
61
 
  my ($main) = @_;
62
 
  my $self = {
63
 
    'main'              => $main,
64
 
    'conf'              => $main->{conf},
65
 
    'id_to_callback'    => { },
66
 
  };
67
 
  bless ($self, $class);
68
 
 
69
 
  $self->load_resolver();
70
 
  $self;
71
 
}
72
 
 
73
 
###########################################################################
74
 
 
75
 
=item $res->load_resolver()
76
 
 
77
 
Load the C<Net::DNS::Resolver> object.  Returns 0 if Net::DNS cannot be used,
78
 
1 if it is available.
79
 
 
80
 
=cut
81
 
 
82
 
sub load_resolver {
83
 
  my ($self) = @_;
84
 
 
85
 
  if (defined $self->{res}) { return 1; }
86
 
  $self->{no_resolver} = 1;
87
 
  # force only ipv4 if no IO::Socket::INET6 or ipv6 doesn't work
88
 
  # to be safe test both ipv6 and ipv4 addresses in INET6
89
 
  my $force_ipv4 = (!HAS_SOCKET_INET6) || $self->{main}->{force_ipv4} ||
90
 
    !eval {
91
 
      my $sock6 = IO::Socket::INET6->new(
92
 
                                         LocalAddr => "::",
93
 
                                         Proto     => 'udp',
94
 
                                         );
95
 
      if ($sock6) {
96
 
        $sock6->close()  or die "error closing inet6 socket: $!";
97
 
        1;
98
 
      }
99
 
    } ||
100
 
    !eval {
101
 
      my $sock6 = IO::Socket::INET6->new(
102
 
                                         LocalAddr => "0.0.0.0",
103
 
                                         PeerAddr => "0.0.0.0",
104
 
                                         PeerPort => 53,
105
 
                                         Proto     => 'udp',
106
 
                                         );
107
 
      if ($sock6) {
108
 
        $sock6->close()  or die "error closing inet4 socket: $!";
109
 
        1;
110
 
      }
111
 
    };
112
 
  
113
 
  eval {
114
 
    require Net::DNS;
115
 
    # force_v4 is set in new() to avoid error in older versions of Net::DNS that don't have it
116
 
    # other options are set by function calls so a typo or API change will cause an error here
117
 
    $self->{res} = Net::DNS::Resolver->new(force_v4 => $force_ipv4);
118
 
    if (defined $self->{res}) {
119
 
      $self->{no_resolver} = 0;
120
 
      $self->{force_ipv4} = $force_ipv4;
121
 
      $self->{retry} = 1;               # retries for non-backgrounded query
122
 
      $self->{retrans} = 3;   # initial timeout for "non-backgrounded" query run in background
123
 
      $self->{res}->retry(1);           # If it fails, it fails
124
 
      $self->{res}->retrans(0);         # If it fails, it fails
125
 
      $self->{res}->dnsrch(0);          # ignore domain search-list
126
 
      $self->{res}->defnames(0);        # don't append stuff to end of query
127
 
      $self->{res}->tcp_timeout(3);     # timeout of 3 seconds only
128
 
      $self->{res}->udp_timeout(3);     # timeout of 3 seconds only
129
 
      $self->{res}->persistent_tcp(0);  # bug 3997
130
 
      $self->{res}->persistent_udp(0);  # bug 3997
131
 
    }
132
 
    1;
133
 
  } or do {
134
 
    my $eval_stat = $@ ne '' ? $@ : "errno=$!";  chomp $eval_stat;
135
 
    dbg("dns: eval failed: $eval_stat");
136
 
  };
137
 
 
138
 
  dbg("dns: no ipv6") if $force_ipv4;
139
 
  dbg("dns: is Net::DNS::Resolver available? %s",
140
 
      $self->{no_resolver} ? "no" : "yes" );
141
 
  if (!$self->{no_resolver} && defined $Net::DNS::VERSION) {
142
 
    dbg("dns: Net::DNS version: %s", $Net::DNS::VERSION);
143
 
  }
144
 
 
145
 
  return (!$self->{no_resolver});
146
 
}
147
 
 
148
 
=item $resolver = $res->get_resolver()
149
 
 
150
 
Return the C<Net::DNS::Resolver> object.
151
 
 
152
 
=cut
153
 
 
154
 
sub get_resolver {
155
 
  my ($self) = @_;
156
 
  return $self->{res};
157
 
}
158
 
 
159
 
=item $res->nameservers()
160
 
 
161
 
Wrapper for Net::DNS::Resolver->nameservers to get or set list of nameservers
162
 
 
163
 
=cut
164
 
 
165
 
sub nameservers {
166
 
  my $self = shift;
167
 
  my $res = $self->{res};
168
 
  $self->connect_sock_if_reqd();
169
 
  return $res->nameservers(@_) if $res;
170
 
}
171
 
 
172
 
=item $res->connect_sock()
173
 
 
174
 
Re-connect to the first nameserver listed in C</etc/resolv.conf> or similar
175
 
platform-dependent source, as provided by C<Net::DNS>.
176
 
 
177
 
=cut
178
 
 
179
 
sub connect_sock {
180
 
  my ($self) = @_;
181
 
 
182
 
  return if $self->{no_resolver};
183
 
 
184
 
  if ($self->{sock}) {
185
 
    $self->{sock}->close()  or die "error closing socket: $!";
186
 
  }
187
 
  my $sock;
188
 
  my $errno;
189
 
 
190
 
  # IO::Socket::INET6 may choose wrong LocalAddr if family is unspecified,
191
 
  # causing EINVAL failure when automatically assigned local IP address
192
 
  # and remote address do not belong to the same address family:
193
 
  use Mail::SpamAssassin::Constants qw(:ip);
194
 
  my $ip64 = IP_ADDRESS;
195
 
  my $ip4 = IPV4_ADDRESS;
196
 
  my $ns = $self->{res}->{nameservers}[0];
197
 
  my $ipv6opt = !($self->{force_ipv4});
198
 
 
199
 
  # ensure families of src and dest addresses match (bug 4412 comment 29)
200
 
  my $srcaddr;
201
 
  if ($ipv6opt && $ns=~/^${ip64}$/o && $ns!~/^${ip4}$/o) {
202
 
    $srcaddr = "::";
203
 
  } else {
204
 
    $srcaddr = "0.0.0.0";
205
 
  }
206
 
 
207
 
  dbg("dns: name server: %s, LocalAddr: %s", $ns,$srcaddr);
208
 
 
209
 
  # find next available unprivileged port (1024 - 65535)
210
 
  # starting at a random value to spread out use of ports
211
 
  my $port_offset = int(rand(64511));  # 65535 - 1024
212
 
  for (my $i = 0; $i<64511; $i++) {
213
 
    my $lport = 1024 + (($port_offset + $i) % 64511);
214
 
 
215
 
    my %args = (
216
 
        PeerAddr => $ns,
217
 
        PeerPort => $self->{res}->{port},
218
 
        Proto => 'udp',
219
 
        LocalPort => $lport,
220
 
        Type => SOCK_DGRAM,
221
 
        LocalAddr => $srcaddr,
222
 
    );
223
 
 
224
 
    if ($ipv6opt) {
225
 
      $sock = IO::Socket::INET6->new(%args);
226
 
    } else {
227
 
      $sock = IO::Socket::INET->new(%args);
228
 
    }
229
 
    $errno = $!;
230
 
    if (defined $sock) {  # ok, got it
231
 
      last;
232
 
    } elsif ($! == EADDRINUSE || $! == EACCES) {  # in use, let's try another source port
233
 
      dbg("dns: UDP port %s already in use, trying another port", $lport);
234
 
    } else {
235
 
      warn "error creating a DNS resolver socket: $errno";
236
 
      goto no_sock;
237
 
    }
238
 
  }
239
 
  if (!defined $sock) {
240
 
    warn "cannot create a DNS resolver socket: $errno";
241
 
    goto no_sock;
242
 
  }
243
 
 
244
 
  eval {
245
 
    my($bufsiz,$newbufsiz);
246
 
    $bufsiz = $sock->sockopt(Socket::SO_RCVBUF)
247
 
      or die "cannot get a resolver socket rx buffer size: $!";
248
 
    if ($bufsiz >= 32*1024) {
249
 
      dbg("dns: resolver socket rx buffer size is %d bytes", $bufsiz);
250
 
    } else {
251
 
      $sock->sockopt(Socket::SO_RCVBUF, 32*1024)
252
 
        or die "cannot set a resolver socket rx buffer size: $!";
253
 
      $newbufsiz = $sock->sockopt(Socket::SO_RCVBUF)
254
 
        or die "cannot get a resolver socket rx buffer size: $!";
255
 
      dbg("dns: resolver socket rx buffer size changed from %d to %d bytes",
256
 
          $bufsiz, $newbufsiz);
257
 
    }
258
 
    1;
259
 
  } or do {
260
 
    my $eval_stat = $@ ne '' ? $@ : "errno=$!";  chomp $eval_stat;
261
 
    info("dns: socket buffer size error: $eval_stat");
262
 
  };
263
 
 
264
 
  $self->{sock} = $sock;
265
 
  $self->{sock_as_vec} = $self->fhs_to_vec($self->{sock});
266
 
  return;
267
 
 
268
 
no_sock:
269
 
  $self->{no_resolver} = 1;
270
 
}
271
 
 
272
 
sub connect_sock_if_reqd {
273
 
  my ($self) = @_;
274
 
  $self->connect_sock() if !$self->{sock};
275
 
}
276
 
 
277
 
=item $res->get_sock()
278
 
 
279
 
Return the C<IO::Socket::INET> object used to communicate with
280
 
the nameserver.
281
 
 
282
 
=cut
283
 
 
284
 
sub get_sock {
285
 
  my ($self) = @_;
286
 
  $self->connect_sock_if_reqd();
287
 
  return $self->{sock};
288
 
}
289
 
 
290
 
###########################################################################
291
 
 
292
 
=item $packet = new_dns_packet ($host, $type, $class)
293
 
 
294
 
A wrapper for C<Net::DNS::Packet::new()> which traps a die thrown by it.
295
 
 
296
 
To use this, change calls to C<Net::DNS::Resolver::bgsend> from:
297
 
 
298
 
    $res->bgsend($hostname, $type);
299
 
 
300
 
to:
301
 
 
302
 
    $res->bgsend(Mail::SpamAssassin::DnsResolver::new_dns_packet($hostname, $type, $class));
303
 
 
304
 
=cut
305
 
 
306
 
sub new_dns_packet {
307
 
  my ($self, $host, $type, $class) = @_;
308
 
 
309
 
  return if $self->{no_resolver};
310
 
 
311
 
  # construct a PTR query if it looks like an IPv4 address
312
 
  if ((!defined($type) || $type eq 'PTR') && $host =~ /^(\d+)\.(\d+)\.(\d+)\.(\d+)$/) {
313
 
    $host = "$4.$3.$2.$1.in-addr.arpa.";
314
 
    $type = 'PTR';
315
 
  }
316
 
 
317
 
  $self->connect_sock_if_reqd();
318
 
  my $packet;
319
 
  eval {
320
 
    $packet = Net::DNS::Packet->new($host, $type, $class);
321
 
 
322
 
    # a bit noisy, so commented by default...
323
 
    #dbg("dns: new DNS packet time=%s host=%s type=%s id=%s",
324
 
    #    time, $host, $type, $packet->id);
325
 
    1;
326
 
  } or do {
327
 
    my $eval_stat = $@ ne '' ? $@ : "errno=$!";  chomp $eval_stat;
328
 
    # this can happen if Net::DNS isn't available -- but in this
329
 
    # case this function should never be called!
330
 
    warn "dns: cannot create Net::DNS::Packet, but new_dns_packet() was called: $eval_stat";
331
 
  };
332
 
 
333
 
  return $packet;
334
 
}
335
 
 
336
 
# Internal function used only in this file
337
 
## compute an unique ID for a packet to match the query to the reply
338
 
## It must use only data that is returned unchanged by the nameserver.
339
 
## Argument is a Net::DNS::Packet that has a non-empty question section,
340
 
## return is an (opaque) string that can be used as a hash key
341
 
sub _packet_id {
342
 
  my ($self, $packet) = @_;
343
 
  my $header = $packet->header;
344
 
  my $id = $header->id;
345
 
  my @questions = $packet->question;
346
 
  my $ques = $questions[0];
347
 
 
348
 
  if (defined $ques) {
349
 
    # Bug 6232: Net::DNS::Packet::new is not consistent in keeping data in
350
 
    # sections of a packet either as original bytes or presentation-encoded:
351
 
    # creating a query packet as above in new_dns_packet() keeps label in
352
 
    # non-encoded form, yet on parsing an answer packet, its query section
353
 
    # is converted to presentation form by Net::DNS::Question::parse calling
354
 
    # Net::DNS::Packet::dn_expand and Net::DNS::wire2presentation in turn.
355
 
    # Let's undo the effect of the wire2presentation routine here to make
356
 
    # sure the query section of an answer packet matches the query section
357
 
    # in our packet formed by new_dns_packet():
358
 
    #
359
 
    my $qname = $ques->qname;
360
 
    $qname =~ s/\\([0-9]{3}|.)/length($1)==1 ? $1 : chr($1)/gse;
361
 
    return join '/', $id, $qname, $ques->qtype, $ques->qclass;
362
 
 
363
 
  } else {
364
 
    # odd.  this should not happen, but clearly some DNS servers
365
 
    # can return something that Net::DNS interprets as having no
366
 
    # question section.  Better support it; just return the
367
 
    # (safe) ID part, along with a text token indicating that
368
 
    # the packet had no question part.
369
 
    #
370
 
    return $id . "NO_QUESTION_IN_PACKET";
371
 
  }
372
 
}
373
 
 
374
 
###########################################################################
375
 
 
376
 
=item $id = $res->bgsend($host, $type, $class, $cb)
377
 
 
378
 
Quite similar to C<Net::DNS::Resolver::bgsend>, except that when a response
379
 
packet eventually arrives, and C<poll_responses> is called, the callback
380
 
sub reference C<$cb> will be called.
381
 
 
382
 
Note that C<$type> and C<$class> may be C<undef>, in which case they
383
 
will default to C<A> and C<IN>, respectively.
384
 
 
385
 
The callback sub will be called with three arguments -- the packet that was
386
 
delivered, and an id string that fingerprints the query packet and the expected
387
 
reply. The third argument is a timestamp (Unix time, floating point), captured
388
 
at the time the packet was collected. It is expected that a closure callback
389
 
be used, like so:
390
 
 
391
 
  my $id = $self->{resolver}->bgsend($host, $type, undef, sub {
392
 
        my ($reply, $reply_id, $timestamp) = @_;
393
 
        $self->got_a_reply ($reply, $reply_id);
394
 
      });
395
 
 
396
 
The callback can ignore the reply as an invalid packet sent to the listening
397
 
port if the reply id does not match the return value from bgsend.
398
 
 
399
 
=cut
400
 
 
401
 
sub bgsend {
402
 
  my ($self, $host, $type, $class, $cb) = @_;
403
 
  return if $self->{no_resolver};
404
 
 
405
 
  $self->{send_timed_out} = 0;
406
 
 
407
 
  my $pkt = $self->new_dns_packet($host, $type, $class);
408
 
 
409
 
  $self->connect_sock_if_reqd();
410
 
  if (!defined($self->{sock}->send($pkt->data, 0))) {
411
 
    warn "dns: sendto() failed: $!";
412
 
    return;
413
 
  }
414
 
  my $id = $self->_packet_id($pkt);
415
 
  dbg("dns: providing a callback for id: $id");
416
 
  $self->{id_to_callback}->{$id} = $cb;
417
 
  return $id;
418
 
}
419
 
 
420
 
###########################################################################
421
 
 
422
 
=item $nfound = $res->poll_responses()
423
 
 
424
 
See if there are any C<bgsend> response packets ready, and return
425
 
the number of such packets delivered to their callbacks.
426
 
 
427
 
=cut
428
 
 
429
 
sub poll_responses {
430
 
  my ($self, $timeout) = @_;
431
 
  return if $self->{no_resolver};
432
 
  return if !$self->{sock};
433
 
  my $cnt = 0;
434
 
 
435
 
  my $rin = $self->{sock_as_vec};
436
 
  my $rout;
437
 
 
438
 
  for (;;) {
439
 
    my ($nfound, $timeleft);
440
 
    { my $timer;  # collects timestamp when variable goes out of scope
441
 
      if (!defined($timeout) || $timeout > 0)
442
 
        { $timer = $self->{main}->time_method("poll_dns_idle") }
443
 
      ($nfound, $timeleft) = select($rout=$rin, undef, undef, $timeout);
444
 
    }
445
 
    if (!defined $nfound || $nfound < 0) {
446
 
      warn "dns: select failed: $!";
447
 
      return;
448
 
    }
449
 
 
450
 
    my $now = time;
451
 
    $timeout = 0;  # next time around collect whatever is available, then exit
452
 
    last  if $nfound == 0;
453
 
 
454
 
    my $packet = $self->{res}->bgread($self->{sock});
455
 
    my $err = $self->{res}->errorstring;
456
 
 
457
 
    if (defined $packet &&
458
 
        defined $packet->header &&
459
 
        defined $packet->question &&
460
 
        defined $packet->answer)
461
 
    {
462
 
      my $id = $self->_packet_id($packet);
463
 
 
464
 
      my $cb = delete $self->{id_to_callback}->{$id};
465
 
      if (!$cb) {
466
 
        dbg("dns: no callback for id: %s, ignored; packet: %s",
467
 
            $id,  $packet ? $packet->string : "undef" );
468
 
      } else {
469
 
        $cb->($packet, $id, $now);
470
 
        $cnt++;
471
 
      }
472
 
    }
473
 
    else {
474
 
      dbg("dns: no packet! err=%s packet=%s",
475
 
          $err,  $packet ? $packet->string : "undef" );
476
 
    }
477
 
  }
478
 
 
479
 
  return $cnt;
480
 
}
481
 
 
482
 
###########################################################################
483
 
 
484
 
=item $res->bgabort()
485
 
 
486
 
Call this to release pending requests from memory, when aborting backgrounded
487
 
requests, or when the scan is complete.
488
 
C<Mail::SpamAssassin::PerMsgStatus::check> calls this before returning.
489
 
 
490
 
=cut
491
 
 
492
 
sub bgabort {
493
 
  my ($self) = @_;
494
 
  $self->{id_to_callback} = {};
495
 
}
496
 
 
497
 
###########################################################################
498
 
 
499
 
=item $packet = $res->send($name, $type, $class)
500
 
 
501
 
Emulates C<Net::DNS::Resolver::send()>.
502
 
 
503
 
=cut
504
 
 
505
 
sub send {
506
 
  my ($self, $name, $type, $class) = @_;
507
 
  return if $self->{no_resolver};
508
 
 
509
 
  my $retrans = $self->{retrans};
510
 
  my $retries = $self->{retry};
511
 
  my $timeout = $retrans;
512
 
  my $answerpkt;
513
 
  my $answerpkt_avail = 0;
514
 
  for (my $i = 0;
515
 
       (($i < $retries) && !defined($answerpkt));
516
 
       ++$i, $retrans *= 2, $timeout = $retrans) {
517
 
 
518
 
    $timeout = 1 if ($timeout < 1);
519
 
    # note nifty use of a closure here.  I love closures ;)
520
 
    $self->bgsend($name, $type, $class, sub {
521
 
      my ($reply, $reply_id, $timestamp) = @_;
522
 
      $answerpkt = $reply; $answerpkt_avail = 1;
523
 
    });
524
 
 
525
 
    my $now = time;
526
 
    my $deadline = $now + $timeout;
527
 
 
528
 
    while (!$answerpkt_avail) {
529
 
      if ($now >= $deadline) { $self->{send_timed_out} = 1; last }
530
 
      $self->poll_responses(1);
531
 
      $now = time;
532
 
    }
533
 
  }
534
 
  return $answerpkt;
535
 
}
536
 
 
537
 
###########################################################################
538
 
 
539
 
=item $res->errorstring()
540
 
 
541
 
Little more than a stub for callers expecting this from C<Net::DNS::Resolver>.
542
 
 
543
 
If called immediately after a call to $res->send this will return
544
 
C<query timed out> if the $res->send DNS query timed out.  Otherwise 
545
 
C<unknown error or no error> will be returned.
546
 
 
547
 
No other errors are reported.
548
 
 
549
 
=cut
550
 
 
551
 
sub errorstring {
552
 
  my ($self) = @_;
553
 
  return 'query timed out' if $self->{send_timed_out};
554
 
  return 'unknown error or no error';
555
 
}
556
 
 
557
 
###########################################################################
558
 
 
559
 
=item $res->finish_socket()
560
 
 
561
 
Reset socket when done with it.
562
 
 
563
 
=cut
564
 
 
565
 
sub finish_socket {
566
 
  my ($self) = @_;
567
 
  if ($self->{sock}) {
568
 
    $self->{sock}->close()  or die "error closing socket: $!";
569
 
    delete $self->{sock};
570
 
  }
571
 
}
572
 
 
573
 
###########################################################################
574
 
 
575
 
=item $res->finish()
576
 
 
577
 
Clean up for destruction.
578
 
 
579
 
=cut
580
 
 
581
 
sub finish {
582
 
  my ($self) = @_;
583
 
  $self->finish_socket();
584
 
  %{$self} = ();
585
 
}
586
 
 
587
 
###########################################################################
588
 
# non-public methods.
589
 
 
590
 
# should move to Util.pm (TODO)
591
 
sub fhs_to_vec {
592
 
  my ($self, @fhlist) = @_;
593
 
  my $rin = '';
594
 
  foreach my $sock (@fhlist) {
595
 
    my $fno = fileno($sock);
596
 
    if (!defined $fno) {
597
 
      warn "dns: oops! fileno now undef for $sock";
598
 
    } else {
599
 
      vec ($rin, $fno, 1) = 1;
600
 
    }
601
 
  }
602
 
  return $rin;
603
 
}
604
 
 
605
 
# call Mail::SA::init() instead
606
 
sub reinit_post_fork {
607
 
  my ($self) = @_;
608
 
  # and a new socket, so we don't have 5 spamds sharing the same
609
 
  # socket
610
 
  $self->connect_sock();
611
 
}
612
 
 
613
 
1;
614
 
 
615
 
=back
616
 
 
617
 
=cut