Handle hostnames with upper-case letters
[webmin.git] / filter / filter-lib.pl
1 # Functions for creating simple mail filtering rules
2 # XXX use same virtualmin spam detection trick for spam module
3
4 BEGIN { push(@INC, ".."); };
5 use WebminCore;
6 &init_config();
7 do 'aliases-lib.pl';
8 do 'autoreply-file-lib.pl';
9
10 if (&get_product_name() eq 'usermin') {
11         # If configured, check if this user has virtualmin spam filtering
12         # enabled before switching away from root
13         $autoreply_cmd = "$config_directory/forward/autoreply.pl";
14         if ($< == 0) {
15                 if ($config{'virtualmin_spam'} &&
16                     -x $config{'virtualmin_spam'}) {
17                         local $out = &backquote_command(
18                                 "$config{'virtualmin_spam'} $remote_user ".
19                                 "</dev/null 2>/dev/null");
20                         $out =~ s/\r|\n//g;
21                         if ($out =~ /\d/) {
22                                 # Yes - we can show the user this
23                                 $global_spamassassin = 2;
24                                 $virtualmin_domain_id = $out;
25                                 }
26                         }
27
28                 # Copy autoreply.pl to /etc/usermin/forward, while we
29                 # are still root
30                 local $autoreply_src = "$root_directory/forward/autoreply.pl";
31                 local @rst = stat($autoreply_src);
32                 local @cst = stat($autoreply_cmd);
33                 if (!@cst || $cst[7] != $rst[7]) {
34                         &copy_source_dest($autoreply_src, $autoreply_cmd);
35                         &set_ownership_permissions(
36                                 undef, undef, 0755, $autoreply_cmd);
37                         }
38
39                 &switch_to_remote_user();
40                 }
41
42         &create_user_config_dirs();
43         &foreign_require("procmail", "procmail-lib.pl");
44         }
45 else {
46         # Running under Webmin, so different modules are used
47         &foreign_require("procmail", "procmail-lib.pl");
48         &foreign_require("mailboxes", "mailboxes-lib.pl");
49         &foreign_require("usermin", "usermin-lib.pl");
50         $mail_system_module =
51                 $mailboxes::config{'mail_system'} == 1 ? "postfix" :
52                 $mailboxes::config{'mail_system'} == 2 ? "qmailadmin" :
53                                                          "sendmail";
54         $autoreply_cmd = "$config_directory/$mail_system_module/autoreply.pl";
55         $user_autoreply_cmd = "$usermin::config{'usermin_dir'}/forward/autoreply.pl";
56         }
57
58 # list_filters([file])
59 # Returns a list of filter objects, which have a 1-to-1 correlation with
60 # procmail recipes. Any recipes too complex for parsing are not included.
61 sub list_filters
62 {
63 local ($file) = @_;
64 local @rv;
65 local @pmrc = &procmail::parse_procmail_file($file || $procmail::procmailrc);
66 foreach my $r (@pmrc) {
67         # Check for un-supported recipes
68         local @conds = @{$r->{'conds'}};
69         if ($r->{'block'} || $r->{'name'}) {
70                 next;
71                 }
72
73         # Check for flags
74         local %flags = map { $_, 1 } @{$r->{'flags'}};
75
76         # Check for bounce condition
77         local $nobounce;
78         if (@conds && $conds[0]->[0] eq '!' &&
79             $conds[0]->[1] =~ /FROM_MAILER/) {
80                 $nobounce = 1;
81                 shift(@conds);
82                 }
83         next if (@conds > 1);   # Multiple conditions are not supported
84
85         # Work out condition type
86         local ($condtype, $cond);
87         if (@conds) {
88                 ($condtype, $cond) = @{$conds[0]};
89                 if ($condtype && $condtype ne "<" && $condtype ne ">") {
90                         # Unsupported conditon type
91                         next;
92                         }
93                 }
94
95         # Work out action type
96         local ($actionspam, $actionreply);
97         if ($r->{'type'} eq '|' &&
98             $r->{'action'} =~ /spamassassin|spamc/) {
99                 $actionspam = 1;
100                 }
101         elsif ($r->{'type'} eq '|' &&
102                ($r->{'action'} =~ /^\Q$autoreply_cmd\E\s+(\S+)/ ||
103                 $user_autoreply_cmd &&
104                 $user_autoreply_cmd &&
105                   $r->{'action'} =~ /^\Q$user_autoreply_cmd\E\s+(\S+)/)) {
106                 $actionreply = $1;
107                 }
108         elsif ($r->{'type'} && $r->{'type'} ne '!') {
109                 # Unsupported action type
110                 next;
111                 }
112
113         # Finally create the simple object
114         local $simple = { 'condtype' => $condtype,
115                           'cond' => $cond,
116                           'nocond' => !scalar(@{$r->{'conds'}}),
117                           'body' => $flags{'B'},
118                           'continue' => $flags{'c'},
119                           'actiontype' => $r->{'type'},
120                           'action' => $r->{'action'},
121                           'nobounce' => $nobounce,
122                           'index' => scalar(@rv),
123                           'recipe' => $r };
124
125         # Set spam flag
126         if ($actionspam) {
127                 $simple->{'actionspam'} = 1;
128                 delete($simple->{'actiontype'});
129                 delete($simple->{'action'});
130                 }
131
132         # Check for throw away
133         if ($simple->{'actiontype'} eq '' &&
134             $simple->{'action'} eq '/dev/null') {
135                 $simple->{'actionthrow'} = 1;
136                 delete($simple->{'actiontype'});
137                 delete($simple->{'action'});
138                 }
139
140         # Check for default delivery
141         if ($simple->{'actiontype'} eq '' &&
142             $simple->{'action'} eq '$DEFAULT') {
143                 $simple->{'actiondefault'} = 1;
144                 delete($simple->{'actiontype'});
145                 delete($simple->{'action'});
146                 }
147
148         # Read autoreply file
149         if ($actionreply) {
150                 $simple->{'actionreply'} = $actionreply;
151                 $simple->{'reply'} = { };
152                 &read_autoreply($actionreply, $simple->{'reply'});
153                 delete($simple->{'actiontype'});
154                 delete($simple->{'action'});
155                 }
156
157         # Split condition regexp into header and value, if possible
158         if ($simple->{'condtype'} ne '<' && $simple->{'condtype'} ne '>' &&
159             !$simple->{'body'} &&
160             $simple->{'cond'} =~ /^\^?([a-zA-Z0-9\-]+):\s*(.*)/) {
161                 local ($h, $v) = ($1, $2);
162                 if ($h eq "X-Spam-Status" && $v eq "Yes") {
163                         # Special case for spam detection
164                         $simple->{'condspam'} = 1;
165                         }
166                 elsif ($h eq "X-Spam-Level" && $v =~ /^(\\\*)+$/) {
167                         # Spam above some level
168                         $simple->{'condlevel'} = length($v)/2;
169                         }
170                 else {
171                         # Match on some header
172                         $simple->{'condheader'} = $h;
173                         $simple->{'condvalue'} = $v;
174                         }
175                 delete($simple->{'cond'});
176                 }
177
178         push(@rv, $simple);
179         }
180 return @rv;
181 }
182
183 # create_filter(&filter)
184 # Create a new filter by adding a procmail recipe
185 sub create_filter
186 {
187 local ($filter) = @_;
188 local $recipe = { };
189 &update_filter_recipe($filter, $recipe);
190 &procmail::create_recipe($recipe);
191 &setup_forward_procmail();
192 }
193
194 # modify_filter(&filter)
195 # Change a filter by modifying the underlying procmail recipe
196 sub modify_filter
197 {
198 local ($filter) = @_;
199 &update_filter_recipe($filter, $filter->{'recipe'});
200 &procmail::modify_recipe($filter->{'recipe'});
201 &setup_forward_procmail();
202 }
203
204 # insert_filter(&filter)
205 # Like create_filter, but adds to the top of the .procmailrc
206 sub insert_filter
207 {
208 local ($filter) = @_;
209 local $recipe = { };
210 &update_filter_recipe($filter, $recipe);
211 local @pmrc = &procmail::parse_procmail_file(
212         $filter->{'file'} || $procmail::procmailrc);
213 if (@pmrc) {
214         &procmail::create_recipe_before($recipe, $pmrc[0]);
215         }
216 else {
217         &procmail::create_recipe($recipe);
218         }
219 &setup_forward_procmail();
220 }
221
222 # update_filter_recipe(&filter, &recipe)
223 # Update a procmail recipe based on some filter
224 sub update_filter_recipe
225 {
226 local ($filter, $recipe) = @_;
227
228 # Set condition section
229 local @conds;
230 local @flags;
231 if ($filter->{'condspam'}) {
232         @conds = ( [ "", "X-Spam-Status: Yes" ] );
233         }
234 elsif ($filter->{'condlevel'}) {
235         local $stars = join("", map { "\\*" } (1..$filter->{'condlevel'}));
236         @conds = ( [ "", "^"."X-Spam-Level: $stars" ] );
237         }
238 elsif ($filter->{'condheader'}) {
239         @conds = ( [ "", "^".$filter->{'condheader'}.": ".
240                          $filter->{'condvalue'} ] );
241         }
242 elsif ($filter->{'condtype'} eq '<' || $filter->{'condtype'} eq '>') {
243         @conds = ( [ $filter->{'condtype'}, $filter->{'cond'} ] );
244         }
245 elsif ($filter->{'cond'}) {
246         @conds = ( [ "", $filter->{'cond'} ] );
247         }
248
249 # Set action section
250 if ($filter->{'actionspam'}) {
251         &foreign_require("spam", "spam-lib.pl");
252         $recipe->{'type'} = '|';
253         $recipe->{'action'} = &spam::get_procmail_command();
254         push(@flags, "f", "w");
255         }
256 elsif ($filter->{'actionthrow'}) {
257         $recipe->{'type'} = '';
258         $recipe->{'action'} = '/dev/null';
259         }
260 elsif ($filter->{'actiondefault'}) {
261         $recipe->{'type'} = '';
262         $recipe->{'action'} = '$DEFAULT';
263         }
264 elsif ($filter->{'actionreply'}) {
265         $recipe->{'type'} = '|';
266         $recipe->{'action'} =
267                 "$autoreply_cmd $filter->{'reply'}->{'autoreply'} $remote_user";
268         &write_autoreply($filter->{'reply'}->{'autoreply'},
269                          $filter->{'reply'});
270         }
271 else {
272         $recipe->{'type'} = $filter->{'actiontype'};
273         $recipe->{'action'} = $filter->{'action'};
274         local $folder = &file_to_folder($filter->{'action'}, [ ], undef, 1);
275         if ($recipe->{'type'} eq '' && $folder->{'type'} == 1) {
276                 # Enable locking for file delivery
277                 $recipe->{'lockfile'} ||= "";
278                 }
279         if ($filter->{'actiontype'} eq '!' && $filter->{'nobounce'}) {
280                 # Add condition to suppress forwarding of bounces
281                 unshift(@conds, [ '!', '^FROM_MAILER' ]);
282                 }
283         }
284 $recipe->{'conds'} = \@conds;
285
286 # Set flags
287 push(@flags, "B") if ($filter->{'body'});
288 push(@flags, "c") if ($filter->{'continue'});
289 $recipe->{'flags'} = [ &unique(@flags) ];
290 }
291
292 # delete_filter(&filter)
293 # Delete a filter by removing the underlying procmail rule
294 sub delete_filter
295 {
296 local ($filter) = @_;
297 &procmail::delete_recipe($filter->{'recipe'});
298 &setup_forward_procmail();
299 if ($filter->{'actionreply'} && !-d $filter->{'actionreply'}) {
300         &unlink_file($filter->{'actionreply'});
301         }
302 }
303
304 # swap_filters(&filter1, &filter2)
305 # Swap two filters in the config file
306 sub swap_filters
307 {
308 local ($filter1, $filter2) = @_;
309 &procmail::swap_recipes($filter1->{'recipe'}, $filter2->{'recipe'});
310 &setup_forward_procmail();
311 }
312
313 # file_to_folder(file, &folders, [homedir], [fake-if-missing])
314 # Given a path like mail/foo or ~/mail/foo or $HOME/mail/foo or
315 # /home/bob/mail/foo, returns the folder object for it.
316 sub file_to_folder
317 {
318 local ($file, $folders, $home, $fake) = @_;
319 $home ||= $remote_user_info[7];
320 $file =~ s/^\~/$home/;
321 $file =~ s/^\$HOME/$home/;
322 if ($file !~ /^\//) {
323         $file = "$home/$file";
324         }
325 local ($folder) = grep { $_->{'file'} eq $file ||
326                          $_->{'file'}.'/' eq $file } @$folders;
327 if (!$folder && $fake) {
328         # Create a fake folder object to match
329         $folder = { 'file' => $file,
330                     'type' => 1,
331                     'fake' => 1 };
332         if ($folder->{'file'} =~ s/\/$//) {
333                 $folder->{'type'} = 2;
334                 }
335         $folder->{'file'} =~ /\/\.?([^\/]+)$/;
336         $folder->{'name'} = $1;
337         if (lc($folder->{'name'}) eq 'spam') {
338                 $folder->{'spam'} = 1;
339                 $folder->{'name'} = "Spam";
340                 }
341         }
342 return $folder;
343 }
344
345 # get_global_spamassassin()
346 # Returns true if spamasassin is run globally
347 sub get_global_spamassassin
348 {
349 return $global_spamassassin if ($global_spamassassin);
350 &foreign_require("spam", "spam-lib.pl");
351 local @recipes = &procmail::parse_procmail_file(
352         $spam::config{'global_procmailrc'});
353 return &spam::find_spam_recipe(\@recipes) ? 1 : 0;
354 }
355
356 # get_global_spam_path()
357 # Returns the global path to which spam is delivered, typically by a 
358 # Virtualmin per-domain procmail file
359 sub get_global_spam_path
360 {
361 &foreign_require("spam", "spam-lib.pl");
362 if ($virtualmin_domain_id) {
363         # Read the Virtualmin procmailrc for the domain
364         local $vmpmrc = "$config{'virtualmin_config'}/procmail/".
365                         $virtualmin_domain_id;
366         local @vmrecipes = &procmail::parse_procmail_file($vmpmrc);
367         local $spamrec = &spam::find_file_recipe(\@vmrecipes);
368         if ($spamrec) {
369                 return $spamrec->{'action'};
370                 }
371         }
372 # Also check the global /etc/procmailrc
373 local @recipes = &procmail::parse_procmail_file(
374         $spam::config{'global_procmailrc'});
375 local $spamrec = &spam::find_file_recipe(\@recipes);
376 if ($spamrec) {
377         return $spamrec->{'action'};
378         }
379 else {
380         return undef;
381         }
382 }
383
384 # get_global_spam_delete()
385 # Returns the global score above which spam is deleted, typically by a 
386 # Virtualmin per-domain procmail file
387 sub get_global_spam_delete
388 {
389 &foreign_require("spam", "spam-lib.pl");
390 if ($virtualmin_domain_id) {
391         # Read the Virtualmin procmailrc for the domain
392         local $vmpmrc = "$config{'virtualmin_config'}/procmail/".
393                         $virtualmin_domain_id;
394         local @vmrecipes = &procmail::parse_procmail_file($vmpmrc);
395         local ($spamrec, $level) = &spam::find_delete_recipe(\@vmrecipes);
396         if ($spamrec) {
397                 return $level;
398                 }
399         }
400 # Also check the global /etc/procmailrc
401 local @recipes = &procmail::parse_procmail_file(
402         $spam::config{'global_procmailrc'});
403 local ($spamrec, $level) = &spam::find_delete_recipe(\@recipes);
404 if ($spamrec) {
405         return $level;
406         }
407 else {
408         return undef;
409         }
410 }
411
412 sub has_spamassassin
413 {
414 return &foreign_installed("spam");
415 }
416
417 # get_override_alias()
418 # Check for any mail alias matching this user, which is defined in /etc/aliases
419 # as an entry matching his username.
420 sub get_override_alias
421 {
422 local @afiles = split(/\t+/, $config{'alias_files'});
423 foreach my $alias (&list_aliases(\@afiles)) {
424         if ($alias->{'name'} eq $remote_user && $alias->{'enabled'}) {
425                 return $alias;
426                 }
427         }
428 return undef;
429 }
430
431 # describe_alias_dest(&values)
432 # Returns a text description of some alias destination
433 sub describe_alias_dest
434 {
435 local ($values) = @_;
436 local @rv;
437 foreach my $v (@$values) {
438         local ($atype, $adesc) = &alias_type($v);
439         if ($atype == 1 && $adesc eq "\\$remote_user") {
440                 push(@rv, $text{'aliases_your'});
441                 }
442         elsif ($atype == 1 && $adesc =~ /^\\(\S+)$/) {
443                 push(@rv, &text('aliases_other', "<tt>$1</tt>"));
444                 }
445         elsif ($atype == 3 && $adesc eq "/dev/null") {
446                 push(@rv, $text{'aliases_delete'});
447                 }
448         elsif ($atype == 4 && $adesc =~ /^(.*)\/autoreply.pl\s+(\S+)/) {
449                 # Autoreply from file .. check contents
450                 local $auto = &read_file_contents("$2");
451                 if ($auto) {
452                         local @lines = grep { !/^(\S+):/} split(/\r?\n/, $auto);
453                         local $msg = join(" ", @lines);
454                         $msg = substr($msg, 0, 100)." ..."
455                                 if (length($msg) > 100);
456                         push(@rv, &text('aliases_auto', "<i>$msg</i>"));
457                         }
458                 else {
459                         push(@rv, &text('aliases_type5', "<tt>$2</tt>"));
460                         }
461                 }
462         elsif ($atype == 4 && $adesc =~ /^(.*)\/filter.pl\s+(\S+)/) {
463                 # Apply filter file
464                 push(@rv, &text('aliases_type6', "<tt>$2</tt>"));
465                 }
466         else {
467                 push(@rv, &text('aliases_type'.$atype, $adesc));
468                 }
469         }
470 return @rv;
471 }
472
473 # is_table_comment(line, [force-prefix])
474 # Returns the comment text if a line contains a comment, like # foo. This is
475 # defined only because functions in aliases-lib.pl call it.
476 sub is_table_comment
477 {
478 local ($line, $force) = @_;
479 if ($force) {
480         return $line =~ /^\s*#+\s*Webmin:\s*(.*)/ ? $1 : undef;
481         }
482 else {
483         return $line =~ /^\s*#+\s*(.*)/ ? $1 : undef;
484         }
485 }
486
487 # describe_condition(&filter)
488 # Returns a human-readable description of the filter condition, and a flag
489 # indicating if this is an 'always' condition.
490 sub describe_condition
491 {
492 local ($f) = @_;
493 local $cond;
494 local $lastalways = 0;
495 if ($f->{'condspam'}) {
496         $cond = $text{'index_cspam'};
497         }
498 elsif ($f->{'condlevel'}) {
499         $cond = &text('index_clevel', $f->{'condlevel'});
500         }
501 elsif ($f->{'condheader'}) {
502         if ($f->{'condvalue'} =~ /^\.\*(.*)\$$/) {
503                 $cond = &text('index_cheader2',
504                         "<tt>".&html_escape($f->{'condheader'})."</tt>",
505                         "<tt>".&html_escape(&prettify_regexp("$1"))."</tt>");
506                 }
507         elsif ($f->{'condvalue'} =~ /^\.\*(.*)\.\*$/ ||
508                $f->{'condvalue'} =~ /^\.\*(.*)$/) {
509                 $cond = &text('index_cheader1',
510                         "<tt>".&html_escape($f->{'condheader'})."</tt>",
511                         "<tt>".&html_escape(&prettify_regexp("$1"))."</tt>");
512                 }
513         elsif ($f->{'condvalue'} =~ /^(.*)\.\*$/ ||
514                $f->{'condvalue'} =~ /^(.*)$/) {
515                 $cond = &text('index_cheader0',
516                         "<tt>".&html_escape($f->{'condheader'})."</tt>",
517                         "<tt>".&html_escape(&prettify_regexp("$1"))."</tt>");
518                 }
519         }
520 elsif ($f->{'condtype'} eq '<' || $f->{'condtype'} eq '>') {
521         $cond = &text('index_csize'.$f->{'condtype'},
522                       &nice_size($f->{'cond'}));
523         }
524 elsif ($f->{'cond'}) {
525         $cond = &text($f->{'body'} ? 'index_cre2' : 'index_cre',
526                        "<tt>".&html_escape($f->{'cond'})."</tt>");
527         }
528 else {
529         $cond = $text{'index_calways'};
530         if (!$f->{'continue'} && !$f->{'actionspam'}) {
531                 $lastalways = 1;
532                 }
533         }
534 return wantarray ? ( $cond, $lastalways ) : $cond;
535 }
536
537 # prettify_regexp(string)
538 # If a string contains only \ quoted special characters, remove the \s
539 sub prettify_regexp
540 {
541 my ($str) = @_;
542 my $re = $str;
543 $re =~ s/\\./x/g;
544 if ($re =~ /^[a-zA-Z0-9_ ]*$/) {
545         $str =~ s/\\(.)/$1/g;
546         }
547 return $str;
548 }
549
550 # describe_action(&filter, &folder, [homedir])
551 # Returns a human-readable description for the delivery action for some folder
552 sub describe_action
553 {
554 local ($f, $folders, $home) = @_;
555 local $action;
556 if ($f->{'actionspam'}) {
557         $action = $text{'index_aspam'};
558         }
559 elsif ($f->{'actionthrow'}) {
560         $action = $text{'index_athrow'};
561         }
562 elsif ($f->{'actiondefault'}) {
563         $action = $text{'index_adefault'};
564         }
565 elsif ($f->{'actiontype'} eq '!') {
566         $action = &text('index_aforward',
567                 "<tt>$f->{'action'}</tt>");
568         }
569 elsif ($f->{'actionreply'}) {
570         $action = &text('index_areply',
571             "<i>".&html_escape(substr(
572                 $f->{'reply'}->{'autotext'}, 0, 50))."</i>");
573         }
574 else {
575         # Work out what folder
576         local $folder = &file_to_folder($f->{'action'}, $folders, $home);
577         if ($folder) {
578                 if (&get_product_name() eq 'usermin') {
579                         &foreign_require("mailbox", "mailbox-lib.pl");
580                         local $id = &mailbox::folder_name($folder);
581                         $action = &text('index_afolder',
582                            "<a href='../mailbox/index.cgi?id=$id'>".
583                            "$folder->{'name'}</a>");
584                         }
585                 else {
586                         local $id = &mailboxes::folder_name($folder);
587                         if (&foreign_available("mailboxes")) {
588                                 $action = &text('index_afolder',
589                                   "<a href='../mailboxes/list_mail.cgi?user=".
590                                   &urlize($folder->{'user'})."&folder=".
591                                   $folder->{'index'}."'>$folder->{'name'}</a>");
592                                 }
593                         else {
594                                 $action = &text('index_afolder',
595                                                 $folder->{'name'});
596                                 }
597                         }
598                 }
599         else {
600                 $action = &text('index_afile',
601                                 "<tt>$f->{'action'}</tt>");
602                 }
603         }
604 if ($f->{'continue'}) {
605         $action = &text('index_acontinue', $action);
606         }
607 return $action;
608 }
609
610 # can_simple_autoreply()
611 # Returns 1 if the current filter rules are simple enough to allow an autoreply
612 # to be added or removed. 
613 sub can_simple_autoreply
614 {
615 return 1;       # Always true for now
616 }
617
618 # can_simple_forward()
619 # Returns 1 if the current filter rules are simple enough to allow a mail
620 # forwarder to be added or removed
621 sub can_simple_forward
622 {
623 return 1;       # Always can for now
624 }
625
626 # no_user_procmailrc()
627 # Returns 1 if /etc/procmailrc has a recipe to always deliver to the user's
628 # mailbox, which prevents this module from configuring anything useful
629 sub no_user_procmailrc
630 {
631 local %sconfig = &foreign_config("spam");
632 local @recipes = &procmail::parse_procmail_file(
633         $sconfig{'global_procmailrc'});
634 local ($force) = grep { $_->{'action'} eq '$DEFAULT' &&
635                         !@{$_->{'conds'}} } @recipes;
636 return $force;
637 }
638
639 # setup_forward_procmail()
640 # If configured, create a .forward file that runs procmail (if not setup yet)
641 sub setup_forward_procmail
642 {
643 return 0 if (!$config{'forward_procmail'});
644 return 0 if (!$module_info{'usermin'});
645 local $fwdfile = "$remote_user_info[7]/.forward";
646 local $procmail = &has_command("procmail");
647 return 0 if (!$procmail);
648 local $lref = &read_file_lines($fwdfile);
649 local $found;
650 foreach my $l (@$lref) {
651         if ($l =~ /\Q$procmail\E/) {
652                 $found++;
653                 }
654         }
655 if ($found) {
656         &unflush_file_lines($fwdfile);
657         }
658 else {
659         # Add procmail call
660         push(@$lref, "|$procmail");
661         &flush_file_lines($fwdfile);
662         }
663 }
664
665 1;
666