View | Details | Raw Unified | Return to bug 3828
Collapse All | Expand All

(-)lib/Mail/SpamAssassin/Dns.pm (-23 / +42 lines)
Lines 480-490 Link Here
480
  }
480
  }
481
481
482
  $self->enter_helper_run_mode();
482
  $self->enter_helper_run_mode();
483
  my $oldalarm = 0;
483
484
484
  eval {
485
  eval {
485
    local $SIG{ALRM} = sub { die "alarm\n" };
486
    # safe to use $SIG{ALRM} here instead of Util::trap_sigalrm_fully(),
487
    # since there are no killer regexp hang dangers here
488
    local $SIG{ALRM} = sub { die "__alarm__\n" };
486
489
487
    alarm($timeout);
490
    $oldalarm = alarm $timeout;
488
491
489
    my $sock = IO::Socket::UNIX->new(Type => SOCK_STREAM,
492
    my $sock = IO::Socket::UNIX->new(Type => SOCK_STREAM,
490
      Peer => $sockpath) || dbg("dcc: failed to open socket") && die;
493
      Peer => $sockpath) || dbg("dcc: failed to open socket") && die;
Lines 520-537 Link Here
520
    }
523
    }
521
524
522
    dbg("dcc: dccifd got response: $response");
525
    dbg("dcc: dccifd got response: $response");
526
    alarm $oldalarm;
523
  };
527
  };
524
  alarm(0); # if we die'd above, need to reset here
525
528
529
  # do NOT reinstate $oldalarm here; we may already have done that in
530
  # the success case.  leave it to the error handler below
531
  my $err = $@;
526
  $self->leave_helper_run_mode();
532
  $self->leave_helper_run_mode();
527
533
528
  if ($@) {
534
  if ($err) {
535
    alarm $oldalarm;
529
    $response = undef;
536
    $response = undef;
530
    if ($@ =~ /alarm/) {
537
    if ($err =~ /__alarm__/) {
531
      dbg("dcc: dccifd check timed out after $timeout secs.");
538
      dbg("dcc: dccifd check timed out after $timeout secs.");
532
      return 0;
539
      return 0;
533
    } else {
540
    } else {
534
      warn("dcc: dccifd -> check skipped: $! $@");
541
      warn("dcc: dccifd -> check skipped: $! $err");
535
      return 0;
542
      return 0;
536
    }
543
    }
537
  }
544
  }
Lines 588-599 Link Here
588
  # use a temp file here -- open2() is unreliable, buffering-wise,
595
  # use a temp file here -- open2() is unreliable, buffering-wise,
589
  # under spamd. :(
596
  # under spamd. :(
590
  my $tmpf = $self->create_fulltext_tmpfile($fulltext);
597
  my $tmpf = $self->create_fulltext_tmpfile($fulltext);
598
  my $oldalarm = 0;
591
599
592
  eval {
600
  eval {
601
    # safe to use $SIG{ALRM} here instead of Util::trap_sigalrm_fully(),
602
    # since there are no killer regexp hang dangers here
593
    local $SIG{ALRM} = sub { die "__alarm__\n" };
603
    local $SIG{ALRM} = sub { die "__alarm__\n" };
594
    local $SIG{PIPE} = sub { die "__brokenpipe__\n" };
604
    local $SIG{PIPE} = sub { die "__brokenpipe__\n" };
595
605
596
    alarm($timeout);
606
    $oldalarm = alarm $timeout;
597
607
598
    # Note: not really tainted, these both come from system conf file.
608
    # Note: not really tainted, these both come from system conf file.
599
    my $path = Mail::SpamAssassin::Util::untaint_file_path ($self->{conf}->{dcc_path});
609
    my $path = Mail::SpamAssassin::Util::untaint_file_path ($self->{conf}->{dcc_path});
Lines 633-654 Link Here
633
643
634
    dbg("dcc: got response: $response");
644
    dbg("dcc: got response: $response");
635
645
636
    alarm(0);
637
    $self->cleanup_kids($pid);
646
    $self->cleanup_kids($pid);
647
    alarm $oldalarm;
638
  };
648
  };
639
649
640
  alarm 0;
650
  # do NOT reinstate $oldalarm here; we may already have done that in
651
  # the success case.  leave it to the error handler below
652
  my $err = $@;
641
  $self->leave_helper_run_mode();
653
  $self->leave_helper_run_mode();
642
654
643
  if ($@) {
655
  if ($err) {
644
    if ($@ =~ /^__alarm__$/) {
656
    alarm $oldalarm;
657
    if ($err =~ /^__alarm__$/) {
645
      dbg("dcc: check timed out after $timeout secs.");
658
      dbg("dcc: check timed out after $timeout secs.");
646
    } elsif ($@ =~ /^__brokenpipe__$/) {
659
    } elsif ($err =~ /^__brokenpipe__$/) {
647
      dbg("dcc: check failed: broken pipe");
660
      dbg("dcc: check failed: broken pipe");
648
    } elsif ($@ eq "no response\n") {
661
    } elsif ($err eq "no response\n") {
649
      dbg("dcc: check failed: no response");
662
      dbg("dcc: check failed: no response");
650
    } else {
663
    } else {
651
      warn("dcc: check failed: $@\n");
664
      warn("dcc: check failed: $err\n");
652
    }
665
    }
653
    return 0;
666
    return 0;
654
  }
667
  }
Lines 728-739 Link Here
728
  # use a temp file here -- open2() is unreliable, buffering-wise,
741
  # use a temp file here -- open2() is unreliable, buffering-wise,
729
  # under spamd. :(
742
  # under spamd. :(
730
  my $tmpf = $self->create_fulltext_tmpfile($fulltext);
743
  my $tmpf = $self->create_fulltext_tmpfile($fulltext);
744
  my $oldalarm = 0;
731
745
732
  eval {
746
  eval {
747
    # safe to use $SIG{ALRM} here instead of Util::trap_sigalrm_fully(),
748
    # since there are no killer regexp hang dangers here
733
    local $SIG{ALRM} = sub { die "__alarm__\n" };
749
    local $SIG{ALRM} = sub { die "__alarm__\n" };
734
    local $SIG{PIPE} = sub { die "__brokenpipe__\n" };
750
    local $SIG{PIPE} = sub { die "__brokenpipe__\n" };
735
751
736
    alarm($timeout);
752
    $oldalarm = alarm $timeout;
737
753
738
    # Note: not really tainted, this comes from system conf file.
754
    # Note: not really tainted, this comes from system conf file.
739
    my $path = Mail::SpamAssassin::Util::untaint_file_path ($self->{conf}->{pyzor_path});
755
    my $path = Mail::SpamAssassin::Util::untaint_file_path ($self->{conf}->{pyzor_path});
Lines 761-783 Link Here
761
      die("pyzor: internal error\n");
777
      die("pyzor: internal error\n");
762
    }
778
    }
763
779
764
    alarm(0);
765
    $self->cleanup_kids($pid);
780
    $self->cleanup_kids($pid);
781
    alarm $oldalarm;
766
  };
782
  };
767
783
768
  alarm 0;
784
  # do NOT reinstate $oldalarm here; we may already have done that in
785
  # the success case.  leave it to the error handler below
786
  my $err = $@;
769
  $self->leave_helper_run_mode();
787
  $self->leave_helper_run_mode();
770
788
771
  if ($@) {
789
  if ($err) {
772
    chomp $@;
790
    alarm $oldalarm;
773
    if ($@ eq "__alarm__") {
791
    chomp $err;
792
    if ($err eq "__alarm__") {
774
      dbg("pyzor: check timed out after $timeout secs.");
793
      dbg("pyzor: check timed out after $timeout secs.");
775
    } elsif ($@ eq "__brokenpipe__") {
794
    } elsif ($err eq "__brokenpipe__") {
776
      dbg("pyzor: check failed: broken pipe");
795
      dbg("pyzor: check failed: broken pipe");
777
    } elsif ($@ eq "no response") {
796
    } elsif ($err eq "no response") {
778
      dbg("pyzor: check failed: no response");
797
      dbg("pyzor: check failed: no response");
779
    } else {
798
    } else {
780
      warn("pyzor: check failed: $@\n");
799
      warn("pyzor: check failed: $err\n");
781
    }
800
    }
782
    return 0;
801
    return 0;
783
  }
802
  }
(-)lib/Mail/SpamAssassin/Reporter.pm (-14 / +22 lines)
Lines 150-161 Link Here
150
  # use a temp file here -- open2() is unreliable, buffering-wise,
150
  # use a temp file here -- open2() is unreliable, buffering-wise,
151
  # under spamd. :(
151
  # under spamd. :(
152
  my $tmpf = $self->create_fulltext_tmpfile(\$fulltext);
152
  my $tmpf = $self->create_fulltext_tmpfile(\$fulltext);
153
  my $oldalarm = 0;
153
154
154
  eval {
155
  eval {
155
    local $SIG{ALRM} = sub { die "__alarm__\n" };
156
    local $SIG{ALRM} = sub { die "__alarm__\n" };
156
    local $SIG{PIPE} = sub { die "__brokenpipe__\n" };
157
    local $SIG{PIPE} = sub { die "__brokenpipe__\n" };
157
158
158
    alarm $timeout;
159
    $oldalarm = alarm $timeout;
159
160
160
    # Note: not really tainted, these both come from system conf file.
161
    # Note: not really tainted, these both come from system conf file.
161
    my $path = Mail::SpamAssassin::Util::untaint_file_path ($self->{conf}->{dcc_path});
162
    my $path = Mail::SpamAssassin::Util::untaint_file_path ($self->{conf}->{dcc_path});
Lines 171-190 Link Here
171
    my @ignored = <DCC>;
172
    my @ignored = <DCC>;
172
    $self->close_pipe_fh (\*DCC);
173
    $self->close_pipe_fh (\*DCC);
173
174
174
    alarm(0);
175
    waitpid ($pid, 0);
175
    waitpid ($pid, 0);
176
    alarm $oldalarm;
176
  };
177
  };
177
178
178
  alarm 0;
179
  my $err = $@;
180
181
  # do not call alarm $oldalarm here, that *may* have already taken place
179
  $self->leave_helper_run_mode();
182
  $self->leave_helper_run_mode();
180
 
183
 
181
  if ($@) {
184
  if ($err) {
182
    if ($@ =~ /^__alarm__$/) {
185
    alarm $oldalarm;  # reinstate the one we missed
186
    if ($err =~ /^__alarm__$/) {
183
      dbg("reporter: DCC report timed out after $timeout seconds");
187
      dbg("reporter: DCC report timed out after $timeout seconds");
184
   } elsif ($@ =~ /^__brokenpipe__$/) {
188
    } elsif ($err =~ /^__brokenpipe__$/) {
185
      dbg("reporter: DCC report failed: broken pipe");
189
      dbg("reporter: DCC report failed: broken pipe");
186
    } else {
190
    } else {
187
      warn("reporter: DCC report failed: $@\n");
191
      warn("reporter: DCC report failed: $err\n");
188
    }
192
    }
189
    return 0;
193
    return 0;
190
  }
194
  }
Lines 201-212 Link Here
201
  # use a temp file here -- open2() is unreliable, buffering-wise,
205
  # use a temp file here -- open2() is unreliable, buffering-wise,
202
  # under spamd. :(
206
  # under spamd. :(
203
  my $tmpf = $self->create_fulltext_tmpfile(\$fulltext);
207
  my $tmpf = $self->create_fulltext_tmpfile(\$fulltext);
208
  my $oldalarm = 0;
204
209
205
  eval {
210
  eval {
206
    local $SIG{ALRM} = sub { die "__alarm__\n" };
211
    local $SIG{ALRM} = sub { die "__alarm__\n" };
207
    local $SIG{PIPE} = sub { die "__brokenpipe__\n" };
212
    local $SIG{PIPE} = sub { die "__brokenpipe__\n" };
208
213
209
    alarm $timeout;
214
    $oldalarm = alarm $timeout;
210
215
211
    # Note: not really tainted, this comes from system conf file.
216
    # Note: not really tainted, this comes from system conf file.
212
    my $path = Mail::SpamAssassin::Util::untaint_file_path ($self->{conf}->{pyzor_path});
217
    my $path = Mail::SpamAssassin::Util::untaint_file_path ($self->{conf}->{pyzor_path});
Lines 223-242 Link Here
223
    my @ignored = <PYZOR>;
228
    my @ignored = <PYZOR>;
224
    $self->close_pipe_fh (\*PYZOR);
229
    $self->close_pipe_fh (\*PYZOR);
225
230
226
    alarm(0);
231
    alarm $oldalarm;
227
    waitpid ($pid, 0);
232
    waitpid ($pid, 0);
228
  };
233
  };
229
234
230
  alarm 0;
235
  my $err = $@;
236
237
  # do not call alarm $oldalarm here, that *may* have already taken place
231
  $self->leave_helper_run_mode();
238
  $self->leave_helper_run_mode();
232
239
233
  if ($@) {
240
  if ($err) {
234
    if ($@ =~ /^__alarm__$/) {
241
    alarm $oldalarm;
242
    if ($err =~ /^__alarm__$/) {
235
      dbg("reporter: pyzor report timed out after $timeout seconds");
243
      dbg("reporter: pyzor report timed out after $timeout seconds");
236
    } elsif ($@ =~ /^__brokenpipe__$/) {
244
    } elsif ($err /^__brokenpipe__$/) {
237
      dbg("reporter: pyzor report failed: broken pipe");
245
      dbg("reporter: pyzor report failed: broken pipe");
238
    } else {
246
    } else {
239
      warn("reporter: pyzor report failed: $@\n");
247
      warn("reporter: pyzor report failed: $err\n");
240
    }
248
    }
241
    return 0;
249
    return 0;
242
  }
250
  }
(-)lib/Mail/SpamAssassin/Locker/Flock.pm (-6 / +11 lines)
Lines 67-72 Link Here
67
  dbg("locker: safe_lock: $$ created $lock_file");
67
  dbg("locker: safe_lock: $$ created $lock_file");
68
68
69
  my $unalarmed = 0;
69
  my $unalarmed = 0;
70
  my $oldalarm = 0;
71
70
  # use a SIGALRM-based timer -- more efficient than second-by-second
72
  # use a SIGALRM-based timer -- more efficient than second-by-second
71
  # sleeps
73
  # sleeps
72
  eval {
74
  eval {
Lines 74-84 Link Here
74
    dbg("locker: safe_lock: $$ trying to get lock on $path with $max_retries timeout");
76
    dbg("locker: safe_lock: $$ trying to get lock on $path with $max_retries timeout");
75
77
76
    # max_retries is basically seconds! so use it for the timeout
78
    # max_retries is basically seconds! so use it for the timeout
77
    alarm($max_retries);
79
    $oldalarm = alarm $max_retries;
78
80
79
    # HELLO!?! IO::File doesn't have a flock() method?!
81
    # HELLO!?! IO::File doesn't have a flock() method?!
80
    if (flock ($fh, LOCK_EX)) {
82
    if (flock ($fh, LOCK_EX)) {
81
      alarm(0) and $unalarmed = 1; # avoid calling alarm(0) twice
83
      alarm $oldalarm;
84
      $unalarmed = 1; # avoid calling alarm(0) twice
82
85
83
      dbg("locker: safe_lock: $$ link to $lock_file: link ok");
86
      dbg("locker: safe_lock: $$ link to $lock_file: link ok");
84
      $is_locked = 1;
87
      $is_locked = 1;
Lines 94-105 Link Here
94
    }
97
    }
95
  };
98
  };
96
99
97
  $unalarmed or alarm(0); # if we die'd above, need to reset here
100
  my $err = $@;
98
  if ($@) {
101
99
    if ($@ =~ /alarm/) {
102
  $unalarmed or alarm $oldalarm; # if we die'd above, need to reset here
103
  if ($err) {
104
    if ($err =~ /alarm/) {
100
      dbg("locker: safe_lock: $$ timed out after $max_retries seconds");
105
      dbg("locker: safe_lock: $$ timed out after $max_retries seconds");
101
    } else {
106
    } else {
102
      die "locker: safe_lock: $$ " . $@;
107
      die "locker: safe_lock: $$ $err";
103
    }
108
    }
104
  }
109
  }
105
110
(-)lib/Mail/SpamAssassin/Util.pm (+23 lines)
Lines 1129-1132 Link Here
1129
1129
1130
###########################################################################
1130
###########################################################################
1131
1131
1132
# As "perldoc perlvar" notes, in perl 5.8.0, the concept of "safe" signal
1133
# handling was added, which means that signals cannot interrupt a running OP.
1134
# unfortunately, a regexp match is a single OP, so a psychotic m// can
1135
# effectively "hang" the interpreter as a result, and a $SIG{ALRM} handler
1136
# will never get called.
1137
#
1138
# However, by using "unsafe" signals, we can still interrupt that -- and
1139
# POSIX::sigaction can create an unsafe handler on 5.8.x.   So this function
1140
# provides a portable way to do that.
1141
1142
sub trap_sigalrm_fully {
1143
  my ($handler) = @_;
1144
  if ($] < 5.008) {
1145
    # signals are always unsafe, just use %SIG
1146
    $SIG{ALRM} = $handler;
1147
  } else {
1148
    # may be using "safe" signals with %SIG; use POSIX to avoid it
1149
    POSIX::sigaction POSIX::SIGALRM(), new POSIX::SigAction $handler;
1150
  }
1151
}
1152
1153
###########################################################################
1154
1132
1;
1155
1;
(-)lib/Mail/SpamAssassin/Plugin/SPF.pm (-6 / +8 lines)
Lines 222-242 Link Here
222
222
223
  my ($result, $comment);
223
  my ($result, $comment);
224
  my $timeout = 5;
224
  my $timeout = 5;
225
  my $oldalarm;
225
226
226
  eval {
227
  eval {
227
    local $SIG{ALRM} = sub { die "__alarm__\n" };
228
    local $SIG{ALRM} = sub { die "__alarm__\n" };
228
    alarm($timeout);
229
    $oldalarm = alarm($timeout);
229
    ($result, $comment) = $query->result();
230
    ($result, $comment) = $query->result();
230
    alarm(0);
231
    alarm $oldalarm;
231
  };
232
  };
232
233
233
  alarm 0;
234
  my $err = $@;
234
235
235
  if ($@) {
236
  if ($err) {
236
    if ($@ =~ /^__alarm__$/) {
237
    alarm $oldalarm;
238
    if ($err =~ /^__alarm__$/) {
237
      dbg("spf: lookup timed out after $timeout seconds");
239
      dbg("spf: lookup timed out after $timeout seconds");
238
    } else {
240
    } else {
239
      warn("spf: lookup failed: $@\n");
241
      warn("spf: lookup failed: $err\n");
240
    }
242
    }
241
    return 0;
243
    return 0;
242
  }
244
  }
(-)lib/Mail/SpamAssassin/Plugin/Razor2.pm (-26 / +27 lines)
Lines 115-127 Link Here
115
  $conf->{parser}->register_commands(\@cmds);
115
  $conf->{parser}->register_commands(\@cmds);
116
}
116
}
117
117
118
# This is to reset the alarm before die() - spamd can die of a stray alarm!
119
sub adie {
120
  my $msg = shift;
121
  alarm 0;
122
  die $msg;
123
}
124
125
sub razor2_access {
118
sub razor2_access {
126
  my ($self, $fulltext, $type) = @_;
119
  my ($self, $fulltext, $type) = @_;
127
  my $timeout=$self->{main}->{conf}->{razor_timeout};
120
  my $timeout=$self->{main}->{conf}->{razor_timeout};
Lines 137-148 Link Here
137
  }
130
  }
138
131
139
  Mail::SpamAssassin::PerMsgStatus::enter_helper_run_mode($self);
132
  Mail::SpamAssassin::PerMsgStatus::enter_helper_run_mode($self);
133
  my $oldalarm = 0;
140
134
135
  # note: adie() is obsolete as a result of $oldalarm; alarms will always
136
  # be reset this way
137
141
    eval {
138
    eval {
142
      local ($^W) = 0;    # argh, warnings in Razor
139
      local ($^W) = 0;    # argh, warnings in Razor
143
140
144
      local $SIG{ALRM} = sub { die "alarm\n" };
141
      local $SIG{ALRM} = sub { die "alarm\n" };
145
      alarm $timeout;
142
      $oldalarm = alarm $timeout;
146
143
147
      # everything's in the module!
144
      # everything's in the module!
148
      my $rc = Razor2::Client::Agent->new("razor-$type");
145
      my $rc = Razor2::Client::Agent->new("razor-$type");
Lines 153-171 Link Here
153
		   foreground => 1,
150
		   foreground => 1,
154
		   config     => $self->{main}->{conf}->{razor_config}
151
		   config     => $self->{main}->{conf}->{razor_config}
155
        };
152
        };
156
        $rc->do_conf() or adie "$debug: " . $rc->errstr;
153
        $rc->do_conf() or die "$debug: " . $rc->errstr;
157
154
158
        # Razor2 requires authentication for reporting
155
        # Razor2 requires authentication for reporting
159
        my $ident;
156
        my $ident;
160
	if ($type ne 'check') {
157
	if ($type ne 'check') {
161
	  $ident = $rc->get_ident
158
	  $ident = $rc->get_ident
162
            or adie("reporter: razor2: $type requires authentication");
159
            or die("reporter: razor2: $type requires authentication");
163
        }
160
        }
164
161
165
        my @msg = ($fulltext);
162
        my @msg = ($fulltext);
166
        my $objects = $rc->prepare_objects( \@msg )
163
        my $objects = $rc->prepare_objects( \@msg )
167
          or adie "$debug: error in prepare_objects";
164
          or die "$debug: error in prepare_objects";
168
        $rc->get_server_info() or adie $rc->errprefix("$debug: spamassassin");
165
        $rc->get_server_info() or die $rc->errprefix("$debug: spamassassin");
169
166
170
	# let's reset the alarm since get_server_info() calls
167
	# let's reset the alarm since get_server_info() calls
171
	# nextserver() which calls discover() which very likely will
168
	# nextserver() which calls discover() which very likely will
Lines 173-179 Link Here
173
	alarm $timeout;
170
	alarm $timeout;
174
171
175
        my $sigs = $rc->compute_sigs($objects)
172
        my $sigs = $rc->compute_sigs($objects)
176
          or adie "$debug: error in compute_sigs";
173
          or die "$debug: error in compute_sigs";
177
174
178
        # 
175
        # 
179
        # if mail isn't whitelisted, check it out
176
        # if mail isn't whitelisted, check it out
Lines 182-202 Link Here
182
        if ( $type ne 'check' || ! $rc->local_check($objects->[0]) ) {
179
        if ( $type ne 'check' || ! $rc->local_check($objects->[0]) ) {
183
          # provide a better error message when servers are unavailable,
180
          # provide a better error message when servers are unavailable,
184
          # than "Bad file descriptor Died".
181
          # than "Bad file descriptor Died".
185
          $rc->connect() or adie "$debug: could not connect to any servers\n";
182
          $rc->connect() or die "$debug: could not connect to any servers\n";
186
183
187
	  # Talk to the Razor server and do work
184
	  # Talk to the Razor server and do work
188
          if ($type eq 'check') {
185
          if ($type eq 'check') {
189
            $rc->check($objects) or adie $rc->errprefix("$debug: spamassassin");
186
            $rc->check($objects) or die $rc->errprefix("$debug: spamassassin");
190
	  }
187
	  }
191
	  else {
188
	  else {
192
            $rc->authenticate($ident) or adie($rc->errprefix("$debug: spamassassin"));
189
            $rc->authenticate($ident) or die($rc->errprefix("$debug: spamassassin"));
193
            $rc->report($objects) or adie($rc->errprefix("$debug: spamassassin"));
190
            $rc->report($objects) or die($rc->errprefix("$debug: spamassassin"));
194
          }
191
          }
195
192
196
          $rc->disconnect() or adie $rc->errprefix("$debug: spamassassin");
193
          $rc->disconnect() or die $rc->errprefix("$debug: spamassassin");
197
194
198
	  # if we got here, we're done doing remote stuff, abort the alert
195
	  # if we got here, we're done doing remote stuff, abort the alert
199
	  alarm 0;
196
	  alarm $oldalarm;
200
197
201
          # Razor 2.14 says that if we get here, we did ok.
198
          # Razor 2.14 says that if we get here, we did ok.
202
          $return = 1;
199
          $return = 1;
Lines 271-293 Link Here
271
        warn "$debug: undefined Razor2::Client::Agent\n";
268
        warn "$debug: undefined Razor2::Client::Agent\n";
272
      }
269
      }
273
  
270
  
274
      alarm 0;
271
      # note: this may be a double-reset.  not a big deal though; the
272
      # result should only be the extension of a preexisting timeout
273
      # by < 1 sec.
274
      alarm $oldalarm;
275
    };
275
    };
276
276
277
    alarm 0;    # just in case
277
    my $err = $@;
278
  
278
  
279
    if ($@) {
279
    if ($err) {
280
      if ( $@ =~ /alarm/ ) {
280
      alarm $oldalarm;    # just in case
281
      if ( $err =~ /alarm/ ) {
281
          dbg("$debug: razor2 $type timed out after $timeout seconds");
282
          dbg("$debug: razor2 $type timed out after $timeout seconds");
282
      } elsif ($@ =~ /(?:could not connect|network is unreachable)/) {
283
      } elsif ($err =~ /(?:could not connect|network is unreachable)/) {
283
        # make this a dbg(); SpamAssassin will still continue,
284
        # make this a dbg(); SpamAssassin will still continue,
284
        # but without Razor checking.  otherwise there may be
285
        # but without Razor checking.  otherwise there may be
285
        # DSNs and errors in syslog etc., yuck
286
        # DSNs and errors in syslog etc., yuck
286
        dbg("$debug: razor2 $type could not connect to any servers");
287
        dbg("$debug: razor2 $type could not connect to any servers");
287
      } elsif ($@ =~ /timeout/i) {
288
      } elsif ($err =~ /timeout/i) {
288
        dbg("$debug: razor2 $type timed out connecting to razor servers");
289
        dbg("$debug: razor2 $type timed out connecting to razor servers");
289
      } else {
290
      } else {
290
        warn("$debug: razor2 $type failed: $! $@");
291
        warn("$debug: razor2 $type failed: $! $err");
291
      }
292
      }
292
    }
293
    }
293
294
(-)spamd/spamd.raw (-9 / +68 lines)
Lines 82-87 Link Here
82
  EX_PROTOCOL    => 76,    # remote error in protocol
82
  EX_PROTOCOL    => 76,    # remote error in protocol
83
  EX_NOPERM      => 77,    # permission denied
83
  EX_NOPERM      => 77,    # permission denied
84
  EX_CONFIG      => 78,    # configuration error
84
  EX_CONFIG      => 78,    # configuration error
85
  EX_TIMEOUT     => 79,    # read timeout
85
);
86
);
86
87
87
*dbg = \&Mail::SpamAssassin::dbg;
88
*dbg = \&Mail::SpamAssassin::dbg;
Lines 189-194 Link Here
189
  'ssl'                      => \$opt{'ssl'},
190
  'ssl'                      => \$opt{'ssl'},
190
  'syslog-socket=s'          => \$opt{'syslog-socket'},
191
  'syslog-socket=s'          => \$opt{'syslog-socket'},
191
  'syslog|s=s'               => \$opt{'syslog'},
192
  'syslog|s=s'               => \$opt{'syslog'},
193
  'timeout-tcp|T=i'          => \$opt{'timeout-tcp'},
194
  'timeout-child|t=i'        => \$opt{'timeout-child'},
192
  'user-config'              => \$opt{'user-config'},
195
  'user-config'              => \$opt{'user-config'},
193
  'username|u=s'             => \$opt{'username'},
196
  'username|u=s'             => \$opt{'username'},
194
  'version|V'                => \$opt{'version'},
197
  'version|V'                => \$opt{'version'},
Lines 515-520 Link Here
515
518
516
my $client;               # used for the client connection ...
519
my $client;               # used for the client connection ...
517
my $childlimit;           # max number of kids allowed
520
my $childlimit;           # max number of kids allowed
521
my $timeout_tcp;          # socket timeout (connect->headers), 0=no timeout
522
my $timeout_child;        # processing timeout (headers->finish), 0=no timeout
518
my $clients_per_child;    # number of clients each child should process
523
my $clients_per_child;    # number of clients each child should process
519
my %children = ();        # current children
524
my %children = ();        # current children
520
525
Lines 532-540 Link Here
532
  $clients_per_child = undef if ( $clients_per_child < 1 );
537
  $clients_per_child = undef if ( $clients_per_child < 1 );
533
}
538
}
534
539
540
if (defined $opt{'timeout-tcp'}) {
541
  $timeout_tcp = $opt{'timeout-tcp'};
542
  $timeout_tcp = undef if ($timeout_tcp < 1);
543
}
544
545
if (defined $opt{'timeout_child'}) {
546
  $timeout_child = $opt{'timeout_child'};
547
  $timeout_child = undef if ($timeout_child < 1);
548
}
549
535
# Set some "sane" limits for defaults
550
# Set some "sane" limits for defaults
536
$childlimit        ||= 5;
551
$childlimit        ||= 5;
537
$clients_per_child ||= 200;
552
$clients_per_child ||= 200;
553
$timeout_child     ||= 300;
554
$timeout_tcp       ||= 30;
538
555
539
# ensure scaling parameters are logical
556
# ensure scaling parameters are logical
540
if ($opt{'min-children'} < 1) {
557
if ($opt{'min-children'} < 1) {
Lines 552-558 Link Here
552
  $opt{'max-spare'} = $opt{'min-spare'}+1;
569
  $opt{'max-spare'} = $opt{'min-spare'}+1;
553
}
570
}
554
571
555
556
my $dontcopy = 1;
572
my $dontcopy = 1;
557
if ( $opt{'create-prefs'} ) { $dontcopy = 0; }
573
if ( $opt{'create-prefs'} ) { $dontcopy = 0; }
558
574
Lines 701-711 Link Here
701
    }
717
    }
702
  }
718
  }
703
719
704
  # sanity check!
705
  if (!-S $path) {
706
    die "Could not find newly-created UNIX socket (2) on $path: $! ($@)\n";
707
  }
708
709
  if (!chmod $mode, $path) {    # make sure everybody can talk to it
720
  if (!chmod $mode, $path) {    # make sure everybody can talk to it
710
    die "Could not chmod $path to $mode: $! ($@)";
721
    die "Could not chmod $path to $mode: $! ($@)";
711
  }
722
  }
Lines 1053-1061 Link Here
1053
    }
1064
    }
1054
  }
1065
  }
1055
1066
1056
  # send the request to the child process
1067
  local ($_);
1057
  local ($_) = $client->getline;
1068
  eval {
1069
    Mail::SpamAssassin::Util::trap_sigalrm_fully(sub {
1070
                          die "tcp timeout";
1071
                        });
1072
    alarm $timeout_tcp if ($timeout_tcp);
1073
    # send the request to the child process
1074
    $_ = $client->getline;
1075
  };
1076
  alarm 0;
1058
1077
1078
  if ($@) {
1079
    service_timeout("($timeout_tcp second socket timeout reading input from client)");
1080
    $client->close;
1081
    return 0;
1082
  }
1083
1059
  if ( !defined $_ ) {
1084
  if ( !defined $_ ) {
1060
    protocol_error("(closed before headers)");
1085
    protocol_error("(closed before headers)");
1061
    $client->close;
1086
    $client->close;
Lines 1080-1086 Link Here
1080
  # message that we need to filter.
1105
  # message that we need to filter.
1081
1106
1082
  elsif (/(PROCESS|CHECK|SYMBOLS|REPORT|REPORT_IFSPAM) SPAMC\/(.*)/) {
1107
  elsif (/(PROCESS|CHECK|SYMBOLS|REPORT|REPORT_IFSPAM) SPAMC\/(.*)/) {
1083
    check( $1, $2, $start, $remote_hostname, $remote_hostaddr );
1108
    my $method = $1;
1109
    eval {
1110
      Mail::SpamAssassin::Util::trap_sigalrm_fully(sub {
1111
                          die "child processing timeout";
1112
                        });
1113
      alarm $timeout_child if ($timeout_child);
1114
      check($method, $2, $start, $remote_hostname, $remote_hostaddr);
1115
    };
1116
    alarm 0;
1117
1118
    if ($@) {
1119
      service_timeout("($timeout_child second timeout while trying to $method)");
1120
      $client->close();
1121
      return 1;
1122
    }
1084
  }
1123
  }
1085
1124
1086
  # Looks like a client is just seeing if we're alive.
1125
  # Looks like a client is just seeing if we're alive.
Lines 1437-1442 Link Here
1437
  logmsg("service unavailable: $err");
1476
  logmsg("service unavailable: $err");
1438
}
1477
}
1439
1478
1479
sub service_timeout {
1480
  my ($err) = @_;
1481
  my $resp = "EX_TIMEOUT";
1482
  print $client "SPAMD/1.0 $resphash{$resp} Timeout: $err\r\n";
1483
  logmsg("timeout: $err");
1484
}
1485
1440
###########################################################################
1486
###########################################################################
1441
1487
1442
sub auth_ident {
1488
sub auth_ident {
Lines 2379-2384 Link Here
2379
versions 3.0.0 and 3.0.1 will be used instead, where all processes receive an
2425
versions 3.0.0 and 3.0.1 will be used instead, where all processes receive an
2380
equal load and no scaling takes place.
2426
equal load and no scaling takes place.
2381
2427
2428
=item B<--timeout-tcp>=I<number>
2429
2430
This option specifies the number of seconds to wait for headers from a
2431
client (spamc) before closing the connection.  The minimum value is C<1>, 
2432
the default value is C<30>, and a value of C<0> will disable socket
2433
timeouts completely.
2434
2435
=item B<--timeout-child>=I<number>
2436
2437
This option specifies the number of seconds to wait for a spamd child to
2438
to process or check a message.  The minimum value is C<1>, the default 
2439
value is C<300>, and a value of C<0> will disable child timeouts completely.
2440
2382
=item B<-H> I<directory>, B<--helper-home-dir>=I<directory>
2441
=item B<-H> I<directory>, B<--helper-home-dir>=I<directory>
2383
2442
2384
Specify that external programs such as Razor, DCC, and Pyzor should have
2443
Specify that external programs such as Razor, DCC, and Pyzor should have

Return to bug 3828