1#!/usr/local/bin/perl -U -I /usr/share/MailScanner/perl 2 3# (c) 2019-2020 MailScanner Project <https://www.mailscanner.info> 4# Version 1.6 5# 6# This program is free software; you can redistribute it and/or modify 7# it under the terms of the GNU General Public License as published by 8# the Free Software Foundation; either version 2 of the License, or 9# (at your option) any later version. 10# 11# This program is distributed in the hope that it will be useful, 12# but WITHOUT ANY WARRANTY; without even the implied warranty of 13# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14# GNU General Public License for more details. 15# 16# You should have received a copy of the GNU General Public License along 17# with this program; if not, write to the Free Software Foundation, Inc., 18# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. 19# 20# Contributed by Shawn Iverson for MailScanner <shawniverson@efa-project.org> 21 22use strict 'vars'; 23no strict 'refs'; 24no strict 'subs'; 25use POSIX; 26require 5.005; 27 28use DirHandle; 29use File::Basename; 30use File::Copy; 31use IO::File; 32use IO::Pipe; 33use Sendmail::PMilter; 34use Socket; 35use Socket qw(inet_ntop); 36use POSIX qw(strftime); 37use Sys::Hostname; 38use Time::HiRes qw( gettimeofday ); 39 40use MailScanner::Lock; 41use MailScanner::Log; 42use MailScanner::Config; 43use MailScanner::CustomConfig; 44use MailScanner::Message; 45 46my $ConfFile; 47my $PidFile; 48 49sub smtp_id { 50 my $type = MailScanner::Config::Value('msmailqueuetype'); 51 MailScanner::Log::DebugLog("msmailqueuetype = $type"); 52 if ($type =~ /long/i) { 53 # Long queue IDs 54 my $seconds=0; 55 my $microseconds=0; 56 use Time::HiRes qw( gettimeofday ); 57 ($seconds, $microseconds) = gettimeofday; 58 my $microseconds_orig=$microseconds; 59 my @BASE52_CHARACTERS = ("0","1","2","3","4","5","6","7","8","9", 60 "B","C","D","F","G","H","J","K","L","M", 61 "N","P","Q","R","S","T","V","W","X","Y", 62 "Z","b","c","d","f","g","h","j","k","l", 63 "m","n","p","q","r","s","t","v","w","x","y","z"); 64 my $encoded=''; 65 my $file_out; 66 my $count=0; 67 while ($count < 6) { 68 $encoded.=$BASE52_CHARACTERS[$seconds%52]; 69 $seconds/=52; 70 $count++; 71 } 72 $file_out=reverse $encoded; 73 $encoded=''; 74 $count=0; 75 while ($count < 4) { 76 $encoded.=$BASE52_CHARACTERS[$microseconds%52]; 77 $microseconds/=52; 78 $count++; 79 } 80 $file_out.=reverse $encoded; 81 82 $file_out.="z"; 83 84 # File doesn't exist yet, just randomize 85 my $inode=int(rand 1000000) + 1; 86 $encoded=''; 87 $count=0; 88 while ($count < 4) { 89 $encoded.=$BASE52_CHARACTERS[$inode%51]; 90 $inode/=51; 91 $count++; 92 } 93 $file_out.=reverse $encoded; 94 95 # We check the generated ID... 96 if ($file_out !~ /[A-Za-z0-9]{12,20}/) { 97 # Something has gone wrong, back to short ID for safety 98 MailScanner::Log::WarnLog("ERROR generating long queue ID"); 99 $file_out = sprintf("%05X%lX", int(rand 1000000)+1, int(rand 1000000)+1); 100 } 101 return $file_out; 102 } else { 103 # No file to stat, so just randomly generate 104 return sprintf("%05X%lX", int(rand 1000000)+1, int(rand 1000000)+1); 105 } 106} 107 108sub connect_callback 109{ 110 my $ctx = shift; 111 my $hostname = shift; 112 my $sockaddr_in = shift; 113 my ($port, $iaddr); 114 my $ip; 115 my $message_ref = $ctx->getpriv(); 116 117 my $message = $hostname; 118 119 if (defined $sockaddr_in) 120 { 121 my $family = sockaddr_family($sockaddr_in); 122 if ($family eq AF_INET) { 123 ($port, $iaddr) = sockaddr_in($sockaddr_in); 124 $ip = inet_ntop(AF_INET, $iaddr); 125 $message .= ' [' . $ip . ']'; 126 } elsif ($family eq AF_INET6) { 127 ($port, $iaddr) = sockaddr_in6($sockaddr_in); 128 $ip = inet_ntop(AF_INET6, $iaddr); 129 $message .= ' [' . $ip . ']'; 130 } else { 131 $ip = ''; 132 } 133 134 } 135 136 # Localhost relaying email? Accept the message for delivery if enabled 137 my $loopback = MailScanner::Config::Value('milterignoreloopback'); 138 if ($loopback == 1 && ($ip =~ /^127/ || $ip =~ /^::1$/)) { 139 MailScanner::Log::DebugLog("Localhost relay detected"); 140 return Sendmail::PMilter::SMFIS_ACCEPT; 141 } 142 143 144 MailScanner::Log::DebugLog("connect_callback: ip = $ip"); 145 146 ${$message_ref} = $message; 147 148 $ctx->setpriv($message_ref); 149 150 Sendmail::PMilter::SMFIS_CONTINUE; 151} 152 153sub helo_callback 154{ 155 my $ctx = shift; 156 my $helohost = shift; 157 my $message_ref = $ctx->getpriv(); 158 my $message = "Received: from $helohost"; 159 # Watch for the second callback 160 if ( $message ne substr(${$message_ref}, 0, length($message)) ) { 161 ${$message_ref} = $message . ' (' . ${$message_ref} . ')' ."\n"; 162 MailScanner::Log::DebugLog("helo_callback: $message"); 163 } 164 165 $ctx->setpriv($message_ref); 166 167 Sendmail::PMilter::SMFIS_CONTINUE; 168} 169 170sub envrcpt_callback 171{ 172 my $ctx = shift; 173 my @args = @_; 174 my $message_ref = $ctx->getpriv(); 175 my $symbols = $ctx->{symbols}; 176 my $mailfrom; 177 178 # Watch for second callback 179 if ( ${$message_ref} !~ /MailScanner Milter/ ) { 180 181 # Is this a subsequent message? Grab previous message id to reconstruct header 182 if ( ${$message_ref} =~ /^[0-9A-F]{7,20}|[0-9B-DF-HJ-NP-TV-Zb-df-hj-np-tv-z]{8,20}$/ ) { 183 # Read envelope 184 my $queuehandle = new FileHandle; 185 my $incoming = MailScanner::Config::Value('inqueuedir'); 186 MailScanner::Log::DebugLog("envrcpt_callback: incoming = " . @{$incoming}[0]); 187 if ($incoming eq '') { 188 MailScanner::Log::WarnLog("Unable to determine incoming queue!"); 189 Sendmail::PMilter::SMFIS_TEMPFAIL; 190 return; 191 } 192 my $file = @{$incoming}[0] . '/temp-' . ${$message_ref}; 193 my $file2 = @{$incoming}[0] . '/' . ${$message_ref}; 194 195 MailScanner::Log::DebugLog("envrcpt_callback: file = $file"); 196 197 my $ret; 198 $ret = MailScanner::Lock::openlock($queuehandle,'+<' . $file, 'w'); 199 if ($ret != 1) { 200 MailScanner::Log::WarnLog("Unable to to open temp queue file for reading!"); 201 Sendmail::PMilter::SMFIS_TEMPFAIL; 202 return; 203 } 204 205 my $data; 206 my $pos; 207 # Locate predata header 208 while($data = readline $queuehandle) { 209 last if $data !~ /^(O<|S<|E<)/; 210 } 211 212 ${$message_ref} = $data; 213 MailScanner::Lock::unlockclose($queuehandle); 214 215 # Done with previous message, make ready for mailscanner 216 move($file, $file2); 217 } 218 219 220 # Capture the Mail From address for further processing 221 if (defined($symbols->{'M'}) && defined($symbols->{'M'}->{'{mail_addr}'})) { 222 $mailfrom=$symbols->{'M'}->{'{mail_addr}'}; 223 } else { 224 # Null Sender 225 # RFC 1123, 821 226 $mailfrom=''; 227 } 228 229 ${$message_ref} = 'S<' . $mailfrom . ">\n" . ${$message_ref}; 230 231 if (defined($symbols->{'H'}) && defined($symbols->{'H'}->{'{tls_version}'}) && defined($symbols->{'H'}->{'{cipher}'}) && defined($symbols->{'H'}->{'{cipher_bits}'})) { 232 ${$message_ref} .= "\t(using " . $symbols->{'H'}->{'{tls_version}'} . ' with cipher ' . $symbols->{'H'}->{'{cipher}'} . ' (' . $symbols->{'H'}->{'{cipher_bits}'} . '/' . $symbols->{'H'}->{'{cipher_bits}'} . ' bits))' . "\n"; 233 MailScanner::Log::DebugLog("envrcpt_callback: ssl/tls detected"); 234 } 235 if (!defined($symbols->{'H'}->{'{cert_subject}'})) { 236 ${$message_ref} .= "\t(no client certificate requested)"; 237 MailScanner::Log::DebugLog("envrcpt_callback: no client certificate requested"); 238 } 239 if (defined($symbols->{'M'}->{'{auth_type}'}) && defined($symbols->{'M'}->{'{auth_authen}'})) { 240 ${$message_ref} .= "\t(Authenticated sender: " . $symbols->{'M'}->{'{auth_authen}'} . ")\n"; 241 MailScanner::Log::DebugLog("envrcpt_callback: Authenticated sender: " . $symbols->{'M'}->{'{auth_authen}'}); 242 } else { 243 ${$message_ref} .= "\n"; 244 } 245 ${$message_ref} .= "\tby " . hostname . ' (MailScanner Milter) with SMTP id '; 246 } 247 248 my $rcptto = $args[0]; 249 my $esmtpnotify = ''; 250 # Capture the ESMTP options for pass through MailScanner engine 251 # RFC 3461 252 foreach (@args) 253 { 254 if ($_ =~ /^NOTIFY=/) 255 { 256 MailScanner::Log::DebugLog("envrcpt_callback: NOTIFY argument found: " . $_); 257 $esmtpnotify = $_; 258 } 259 } 260 261 # Build original recipient milter header 262 ${$message_ref} = 'O' . $rcptto . "\n" . ${$message_ref}; 263 264 # Add options milter header 265 if ($esmtpnotify ne '') 266 { 267 ${$message_ref} = 'E<' . $esmtpnotify . ">\n" . ${$message_ref}; 268 } 269 270 $ctx->setpriv($message_ref); 271 272 Sendmail::PMilter::SMFIS_CONTINUE; 273} 274 275sub header_callback 276{ 277 my $ctx = shift; 278 my $headerf = shift; 279 my $headerv = shift; 280 my $message_ref = $ctx->getpriv(); 281 my $symbols = $ctx->{symbols}; 282 my $id; 283 my $datestring = strftime "%a, %e %b %Y %T %z (%Z)", localtime; 284 285 # Postfix queue id revealed during this phase, capture it if defined 286 # and populate the rest of the Received: header 287 if (${$message_ref} =~ /SMTP\sid\s$/ ) { 288 if (defined($symbols->{'L'}) && defined($symbols->{'L'}->{'i'}) ) { 289 $id = $symbols->{'L'}->{'i'}; 290 if ( $id ne '' ) { 291 MailScanner::Log::DebugLog("Postfix ID captured: $id"); 292 } else { 293 $id = smtp_id; 294 MailScanner::Log::DebugLog("Postfix id empty, generated one instead: $id"); 295 } 296 } else { 297 $id = smtp_id; 298 MailScanner::Log::DebugLog("Unable to capture real postfix id, generated one instead: $id"); 299 } 300 301 ${$message_ref} .= $id; 302 303 # Check for only one original recipient and add 'for <>' if so. 304 if (${$message_ref} =~ /^O(<.*>)\n(?!O)/ ) { 305 ${$message_ref} .= "\n\tfor " . $1 . "; "; 306 } else { 307 ${$message_ref} .= ";\n\t"; 308 } 309 ${$message_ref} .= "$datestring\n"; 310 MailScanner::Log::DebugLog("header_callback: datestring = $datestring"); 311 } 312 313 314 ${$message_ref} .= $headerf . ': ' . $headerv . "\n"; 315 $ctx->setpriv($message_ref); 316 317 Sendmail::PMilter::SMFIS_CONTINUE; 318} 319 320sub eoh_callback 321{ 322 my $ctx = shift; 323 my $message_ref = $ctx->getpriv(); 324 my $id; 325 my $line; 326 my $buffer; 327 my $ip; 328 my @to; 329 my $from; 330 331 # Are we in scanner mode? 332 my $scannermode = MailScanner::Config::Value('milterscanner'); 333 334 MailScanner::Log::DebugLog("eoh_callback: scannermode = $scannermode"); 335 336 while (${$message_ref} =~ /([^\n]+)\n?/g) { 337 my $line = $1; 338 $buffer .= $1 . "\n"; 339 if ($line =~ /^Received: / ) { 340 $ip = '127.0.0.1'; 341 if ($line =~ /^Received: .+?\(.*?\[(?:IPv6:)?([0-9a-f.:]+)\]/i) { 342 $ip = $1; 343 } 344 MailScanner::Log::DebugLog("eoh_callback: ip = $ip"); 345 } elsif ( $line =~ m/^.*SMTP id / ) { 346 # We may have added a trailing ; so remove it if so. 347 $line =~ s/^.*SMTP id ([^;]*)(?:;?)/$1/; 348 $id = $line; 349 MailScanner::Log::DebugLog("eoh_callback: id = $id"); 350 last; 351 } elsif ( $line =~ /^O/ ) { 352 $line =~ s/^O//; 353 $line =~ s/^\<//; 354 $line =~ s/\>.*$//; 355 push @to, $line; 356 MailScanner::Log::DebugLog("eoh_callback: to = $line"); 357 } elsif ( $line =~ /^F/ ) { 358 $line =~ s/^F//; 359 $line =~ s/^\<//; 360 $line =~ s/\>.*$//; 361 $from = $line; 362 MailScanner::Log::DebugLog("eoh_callback: from = $line"); 363 } 364 } 365 366 if ( $id eq '' ) { 367 # Missing a queue id, header_callback possibly skipped, reject 368 MailScanner::Log::WarnLog("Postfix queue id is missing"); 369 Sendmail::PMilter::SMFIS_TEMPFAIL; 370 return; 371 } 372 373 # Write header to file 374 my $queuehandle = new FileHandle; 375 my $incoming = MailScanner::Config::Value('inqueuedir'); 376 MailScanner::Log::DebugLog("eoh_callback: incoming = " . @{$incoming}[0]); 377 if ($incoming eq '') { 378 MailScanner::Log::WarnLog("Unable to determine incoming queue!"); 379 Sendmail::PMilter::SMFIS_TEMPFAIL; 380 return; 381 } 382 my $file = @{$incoming}[0] . '/temp-' . $id; 383 384 MailScanner::Log::DebugLog("eoh_callback: file = $file"); 385 386 my $ret; 387 $ret = MailScanner::Lock::openlock($queuehandle,'>>' . $file, 'w'); 388 if ($ret != 1) { 389 MailScanner::Log::WarnLog("Unable to to open queue temp file for writing!"); 390 Sendmail::PMilter::SMFIS_TEMPFAIL; 391 return; 392 } 393 394 $queuehandle->print(${$message_ref}); 395 396 # Signal end of header 397 $queuehandle->print("\n"); 398 399 $queuehandle->flush(); 400 MailScanner::Lock::unlockclose($queuehandle); 401 402 # Determine what to do next 403 if ($scannermode == 1) { 404 MailScanner::Log::DebugLog("eoh_callback: scanner mode enabled"); 405 # Blacklist test 406 # Build a message object to test against 407 my $message = {}; 408 if ($from ne '<>') { 409 $message->{from} = $from; 410 } else { 411 $message->{from} = ''; 412 } 413 @{$message->{to}} = @to; 414 $message->{clientip} = $ip; 415 my $user; 416 my $domain; 417 ($user, $domain) = MailScanner::Message::address2userdomain($message->{from}); 418 $message->{fromuser} = $user; 419 $message->{fromdomain} = $domain; 420 421 my $touser; 422 my $todomain; 423 my $count; 424 425 foreach my $msgto (@{$message->{to}}) { 426 ($touser, $todomain) = MailScanner::Message::address2userdomain($msgto); 427 push @{$message->{touser}}, $touser; 428 push @{$message->{todomain}}, $todomain; 429 $count++; 430 } 431 432 my $test; 433 # Check whitelist first 434 $test = MailScanner::Config::Value('spamwhitelist', $message); 435 436 my $maxrecips = MailScanner::Config::Value('whitelistmaxrecips'); 437 $maxrecips = 999999 unless $maxrecips; 438 439 # Not in whitelist or a large number of recipients, then check blacklist 440 if ($test == 0 || $count > $maxrecips) { 441 MailScanner::Log::DebugLog("eom_callback: no whitelist match, proceeding to blacklist test"); 442 # test 443 $test = MailScanner::Config::Value('spamblacklist', $message); 444 445 # Blacklisted, fire a reject 446 if ($test == 1) { 447 MailScanner::Log::DebugLog("eom_callback: blacklist match, firing reject"); 448 # Set reject code 449 $ctx->setreply('554', '5.7.1', 'Message Blacklisted'); 450 451 # Delete temp file 452 unlink $file; 453 return Sendmail::PMilter::SMFIS_REJECT; 454 } 455 } 456 } 457 458 MailScanner::Log::DebugLog("eoh_callback: End of Header detected"); 459 460 # Start storing just the id 461 ${$message_ref} = $id; 462 463 $ctx->setpriv($message_ref); 464 465 Sendmail::PMilter::SMFIS_CONTINUE; 466} 467 468sub body_callback 469{ 470 my $ctx = shift; 471 my $body_chunk = shift; 472 my $len = shift; 473 my $message_ref = $ctx->getpriv(); 474 475 my $queuehandle = new FileHandle; 476 my $incoming = MailScanner::Config::Value('inqueuedir'); 477 MailScanner::Log::DebugLog("body_callback: incoming = " . @{$incoming}[0]); 478 if ($incoming eq '') { 479 MailScanner::Log::WarnLog("Unable to determine incoming queue!"); 480 Sendmail::PMilter::SMFIS_TEMPFAIL; 481 return; 482 } 483 my $file = @{$incoming}[0] . '/temp-' . ${$message_ref}; 484 485 MailScanner::Log::DebugLog("body_callback: file = $file"); 486 487 my $ret; 488 $ret = MailScanner::Lock::openlock($queuehandle,'>>' . $file, 'w'); 489 if ($ret != 1) { 490 MailScanner::Log::WarnLog("Unable to to open queue temp file for writing!"); 491 Sendmail::PMilter::SMFIS_TEMPFAIL; 492 return; 493 } 494 $queuehandle->print($body_chunk); 495 496 $queuehandle->flush(); 497 MailScanner::Lock::unlockclose($queuehandle); 498 499 $ctx->setpriv($message_ref); 500 501 Sendmail::PMilter::SMFIS_CONTINUE; 502} 503 504sub eom_callback 505{ 506 my $ctx = shift; 507 my $message_ref = $ctx->getpriv(); 508 509 # Store reference for subsequent messages in connection or for connection close 510 $ctx->setpriv($message_ref); 511 512 # Send DISCARD signal to accept message and drop from postfix 513 # for mailscanner processing 514 Sendmail::PMilter::SMFIS_DISCARD; 515} 516 517sub close_callback 518{ 519 my $ctx = shift; 520 my $message_ref = $ctx->getpriv(); 521 522 my $incoming = MailScanner::Config::Value('inqueuedir'); 523 MailScanner::Log::DebugLog("eom_callback: incoming = " . @{$incoming}[0]); 524 if ($incoming eq '') { 525 MailScanner::Log::WarnLog("Unable to determine incoming queue!"); 526 Sendmail::PMilter::SMFIS_TEMPFAIL; 527 return; 528 } 529 my $file = @{$incoming}[0] . '/temp-' . ${$message_ref}; 530 my $file2 = @{$incoming}[0] . '/' . ${$message_ref}; 531 532 move($file, $file2); 533 534 $ctx->setpriv(undef); 535 536 Sendmail::PMilter::SMFIS_CONTINUE; 537} 538 539# 540# Create and write a PID file for a given process id 541# 542sub WritePIDFile { 543 my($process,$PidFile) = @_; 544 545 my $pidfh = new FileHandle; 546 $pidfh->open(">$PidFile") 547 or MailScanner::Log::WarnLog("Cannot write pid file %s, %s", $PidFile, $!); 548 print $pidfh "$process\n"; 549 $pidfh->close(); 550} 551 552# 553# Start logging 554# 555sub StartLogging { 556 my($filename) = @_; 557 558 my $procname = 'MSMilter'; 559 560 my $logbanner = "MSMilter Daemon starting..."; 561 562 MailScanner::Log::Configure($logbanner, 'syslog'); 563 564 # Need to know log facility *before* we have read the whole config file! 565 my $facility = MailScanner::Config::QuickPeek($filename, 'syslogfacility'); 566 my $logsock = MailScanner::Config::QuickPeek($filename, 'syslogsockettype'); 567 568 MailScanner::Log::Start($procname, $facility, $logsock); 569} 570 571sub ExitParent { 572 my($sig) = @_; # Arg is the signal name 573 574 # Reap children (wait up to one minute) 575 my $counter=0; 576 while ($counter < 30) { 577 if (wait() > 0) { 578 # Still active children 579 sleep(2); 580 $counter++; 581 MailScanner::Log::NoticeLog("Milter closing, waiting on children to finish..."); 582 } else { 583 $counter=30; 584 } 585 } 586 587 # Cleanup partial writes 588 my $incoming = MailScanner::Config::Value('inqueuedir'); 589 unlink glob @{$incoming}[0] . "/temp-*"; 590 591 unlink $PidFile; # Ditch the pid file 592 593 MailScanner::Log::Stop(); 594 595 exit 0; 596} 597 598 599BEGIN { 600 chdir('/usr/share/MailScanner/perl'); 601 602 $ENV{PATH}="/sbin:/bin:/usr/sbin:/usr/bin"; 603 604 umask 0077; 605 606 $ConfFile = $ARGV[0]; 607 # Use the default if we couldn't find theirs. Will save a lot of grief. 608 $ConfFile = '/etc/MailScanner/MailScanner.conf' if $ConfFile eq "" || 609 !(-f $ConfFile); 610 611 # Start logging 612 StartLogging($ConfFile); 613 614 MailScanner::Config::initialise(MailScanner::Config::QuickPeek($ConfFile, 615 'customfunctionsdir')); 616 # Read Config File 617 MailScanner::Config::Read($ConfFile); 618 619 my $pid = fork; 620 621 if (!defined($pid)) { 622 MailScanner::Log::WarnLog("Milter: Unable to fork!"); 623 exit 1; 624 } 625 if ($pid == 0) { 626 $0 = "MSMilter Daemon"; 627 628 # Need this new parent process to ignore SIGHUP, and catch SIGTERM 629 $SIG{'HUP'} = 'IGNORE'; 630 $SIG{'TERM'} = \&ExitParent; 631 632 my $PidFile = MailScanner::Config::Value('milterpidfile'); 633 WritePIDFile($$,$PidFile); 634 635 my $dispatcher = MailScanner::Config::Value('milterdispatcher'); 636 my $children = MailScanner::Config::Value('miltermaxchildren'); 637 my $user = MailScanner::Config::Value('runasuser'); 638 my $group = MailScanner::Config::Value('workgroup'); 639 my $bind = MailScanner::Config::Value('milterbind'); 640 my $port = MailScanner::Config::Value('milterport'); 641 642 MailScanner::Log::Reset(); 643 644 MailScanner::Log::DebugLog("initialization:\n PidFile = $PidFile\n user = $user\n group = $group\n bind = $bind\n port = $port"); 645 646 my %my_callbacks = 647 ( 648 'connect' => \&connect_callback, 649 'helo' => \&helo_callback, 650 'envrcpt' => \&envrcpt_callback, 651 'header' => \&header_callback, 652 'eoh' => \&eoh_callback, 653 'body' => \&body_callback, 654 'eom' => \&eom_callback, 655 'close' => \&close_callback, 656 ); 657 658 my $conn = 'inet:'. $port . '@' . $bind; 659 $ENV{PMILTER_DISPATCHER} = $dispatcher; 660 my $milter = Sendmail::PMilter->new(); 661 $milter->register('MSMilter', 662 \%my_callbacks, 663 Sendmail::PMilter::SMFI_CURR_ACTS 664 ); 665 $milter->setconn($conn); 666 $< = $> = getpwnam($user); 667 $( = $) = getgrnam($group); 668 if ( $dispatcher eq "prefork" ) { 669 $milter->main($children,100); 670 } else { 671 $milter->main(); 672 } 673 674 # Should never get here, if we did, it didn't work 675 MailScanner::Log::WarnLog("Unable to spawn milter!"); 676 exit 1 677 } 678 # Successful if we reach this point 679 exit 0; 680} 681 6821; 683