Fix $regex<n> use-after-free. Bug 2915
[exim.git] / src / src / convert4r3.src
1 #! PERL_COMMAND
2
3 # This is a Perl script that reads an Exim run-time configuration file and
4 # checks for settings that were valid prior to release 3.00 but which were
5 # obsoleted by that release. It writes a new file with suggested changes to
6 # the standard output, and commentary about what it has done to stderr.
7
8 # It is assumed that the input is a valid Exim configuration file.
9
10 use warnings;
11 BEGIN { pop @INC if $INC[-1] eq '.' };
12
13 use Getopt::Long;
14 use File::Basename;
15
16 GetOptions(
17     'version' => sub {
18         print basename($0) . ": $0\n",
19             "build: EXIM_RELEASE_VERSIONEXIM_VARIANT_VERSION\n",
20             "perl(runtime): $^V\n";
21             exit 0;
22     },
23 );
24
25 ##################################################
26 #             Analyse one line                   #
27 ##################################################
28
29 # This is called for the main and the driver sections, not for retry
30 # or rewrite sections (which are unmodified).
31
32 sub checkline{
33 my($line) = $_[0];
34
35 return "comment" if $line =~ /^\s*(#|$)/;
36 return "end"     if $line =~ /^\s*end\s*$/;
37
38 # Macros are recognized only in the first section of the file.
39
40 return "macro" if $prefix eq "" && $line =~ /^\s*[A-Z]/;
41
42 # Pick out the name at the start and the rest of the line (into global
43 # variables) and return whether the start of a driver or not.
44
45 ($i1,$name,$i2,$rest) = $line =~ /^(\s*)([a-z0-9_]+)(\s*)(.*?)\s*$/;
46 return ($rest =~ /^:/)? "driver" : "option";
47 }
48
49
50
51
52 ##################################################
53 #       Add transport setting to a director      #
54 ##################################################
55
56 # This function adds a transport setting to an aliasfile or forwardfile
57 # director if a global setting exists and a local one does not. If neither
58 # exist, it adds file/pipe/reply, but not the directory ones.
59
60 sub add_transport{
61 my($option) = @_;
62
63 my($key) = "$prefix$driver.${option}_transport";
64 if (!exists $o{$key})
65   {
66   if (exists $o{"address_${option}_transport"})
67     {
68     print STDOUT "# >> Option added by convert4r3\n";
69     printf STDOUT "${i1}${option}_transport = %s\n",
70       $o{"address_${option}_transport"};
71     printf STDERR
72       "\n%03d ${option}_transport added to $driver director.\n",
73       ++$count;
74     }
75   else
76     {
77     if ($option eq "pipe" || $option eq "file" || $option eq "reply")
78       {
79       print STDOUT "# >> Option added by convert4r3\n";
80       printf STDOUT "${i1}${option}_transport = address_${option}\n";
81       printf STDERR
82         "\n%03d ${option}_transport added to $driver director.\n",
83         ++$count;
84       }
85     }
86   }
87 }
88
89
90
91
92 ##################################################
93 #       Negate a list of things                  #
94 ##################################################
95
96 sub negate {
97 my($list) = $_[0];
98
99 return $list if ! defined $list;
100
101 ($list) = $list =~ /^"?(.*?)"?\s*$/s;
102
103 # Under Perl 5.005 we can split very nicely at colons, ignoring double
104 # colons, like this:
105 #
106 # @split = split /\s*(?<!:):(?!:)\s*(?:\\\s*)?/s, $list;
107 #
108 # However, we'd better make this work under Perl 5.004, since there is
109 # a lot of that about.
110
111 $list =~ s/::/>%%%%</g;
112 @split = split /\s*:\s*(?:\\\s*)?/s, $list;
113 foreach $item (@split)
114   {
115   $item =~ s/>%%%%</::/g;
116   }
117
118 $" = " : \\\n    ! ";
119 return "! @split";
120 }
121
122
123
124
125
126 ##################################################
127 #          Skip blank lines                      #
128 ##################################################
129
130 # This function is called after we have generated no output for an option;
131 # it skips subsequent blank lines if the previous line was blank.
132
133 sub skipblanks {
134 my($i) = $_[0];
135 if ($last_was_blank)
136   {
137   $i++ while $c[$i+1] =~ /^\s*$/;
138   }
139 return $i;
140 }
141
142
143
144
145
146 ##################################################
147 #       Get base name of data key                #
148 ##################################################
149
150 sub base {
151 return "$_[0]" if $_[0] !~ /^(?:d|r|t)\.[^.]+\.(.*)/;
152 return $1;
153 }
154
155
156
157 ##################################################
158 #     Amalgamate accept/reject/reject_except     #
159 ##################################################
160
161 # This function amalgamates the three previous kinds of
162 # option into a single list, using negation for the middle one if
163 # the final argument is "+", or for the outer two if the final
164 # argument is "-".
165
166 sub amalgamate {
167 my($accept,$reject,$reject_except,$name);
168 my($last_was_negated) = 0;
169 my($join) = "";
170
171 $accept = $o{$_[0]};
172 $reject = $o{$_[1]};
173 $reject_except = $o{$_[2]};
174 $name = $_[3];
175
176 if ($_[4] eq "+")
177   {
178   ($accept) = $accept =~ /^"?(.*?)"?\s*$/s if defined $accept;
179   $reject = &negate($reject) if defined $reject;
180   ($reject_except) = $reject_except =~ /^"?(.*?)"?\s*$/s if defined $reject_except;
181   }
182 else
183   {
184   $accept = &negate($accept) if defined $accept;
185   ($reject) = $reject =~ /^"?(.*?)"?\s*$/s if defined $reject;
186   $reject_except = &negate($reject_except) if defined $reject_except;
187   }
188
189 print STDOUT "# >> Option rewritten by convert4r3\n";
190 print STDOUT "${i1}$name = \"";
191
192 if (defined $reject_except)
193   {
194   print STDOUT "$reject_except";
195   $join = " : \\\n    ";
196   $last_was_negated = ($_[4] ne "+");
197   }
198 if (defined $reject)
199   {
200   print STDOUT "$join$reject";
201   $join = " : \\\n    ";
202   $last_was_negated = ($_[4] eq "+");
203   }
204 if (defined $accept)
205   {
206   print STDOUT "$join$accept";
207   $last_was_negated = ($_[4] ne "+");
208   $join = " : \\\n    ";
209   }
210
211 print STDOUT "$join*" if $last_was_negated;
212
213 print STDOUT "\"\n";
214
215 my($driver_name);
216 my($driver_type) = "";
217
218 if ($_[0] =~ /^(d|r|t)\.([^.]+)\./ ||
219     $_[1] =~ /^(d|r|t)\.([^.]+)\./ ||
220     $_[2] =~ /^(d|r|t)\.([^.]+)\./)
221   {
222   $driver_type = ($1 eq 'd')? "director" : ($1 eq 'r')? "router" : "transport";
223   $driver_name = $2;
224   }
225
226 my($x) = ($driver_type ne "")? " in \"$driver_name\" $driver_type" : "";
227
228 my($l0) = &base($_[0]);
229 my($l1) = &base($_[1]);
230 my($l2) = &base($_[2]);
231
232
233 if ($l2 eq "")
234   {
235   if ($l0 eq "")
236     {
237     printf STDERR "\n%03d $l1 converted to $name$x.\n", ++$count;
238     }
239   else
240     {
241     printf STDERR "\n%03d $l0 and $l1\n    amalgamated into $name$x.\n",
242       ++$count;
243     }
244   }
245 else
246   {
247   if ($l1 eq "")
248     {
249     printf STDERR "\n%03d $l0 and $l2\n    amalgamated into $name$x.\n",
250       ++$count;
251     }
252   else
253     {
254     printf STDERR "\n%03d $l0, $l1 and $l2\n    amalgamated into " .
255       "$name$x.\n", ++$count;
256     }
257   }
258 }
259
260
261
262
263 ##################################################
264 #         Join two lists, if they exist          #
265 ##################################################
266
267 sub pair{
268 my($l1) = $o{"$_[0]"};
269 my($l2) = $o{"$_[1]"};
270
271 return $l2 if (!defined $l1);
272 return $l1 if (!defined $l2);
273
274 ($l1) = $l1 =~ /^"?(.*?)"?\s*$/s;
275 ($l2) = $l2 =~ /^"?(.*?)"?\s*$/s;
276
277 return "$l1 : $l2";
278 }
279
280
281
282
283 ##################################################
284 #  Amalgamate accept/reject/reject_except pairs  #
285 ##################################################
286
287 # This is like amalgamate, but it combines pairs of arguments, and
288 # doesn't output commentary (easier to write a generic one for the few
289 # cases).
290
291 sub amalgamatepairs {
292 my($accept) = &pair($_[0], $_[1]);
293 my($reject) = &pair($_[2], $_[3]);
294 my($reject_except) = &pair($_[4], $_[5]);
295 my($last_was_negated) = 0;
296 my($join) = "";
297
298 if ($_[7] eq "+")
299   {
300   ($accept) = $accept =~ /^"?(.*?)"?\s*$/s if defined $accept;
301   $reject = &negate($reject) if defined $reject;
302   ($reject_except) = $reject_except =~ /^"?(.*?)"?\s*$/s if defined $reject_except;
303   }
304 else
305   {
306   $accept = &negate($accept) if defined $accept;
307   ($reject) = $reject =~ /^"?(.*?)"?$/s if defined $reject;
308   $reject_except = &negate($reject_except) if defined $reject_except;
309   }
310
311 print STDOUT "# >> Option rewritten by convert4r3\n";
312 print STDOUT "${i1}$_[6] = \"";
313
314 if (defined $reject_except)
315   {
316   print STDOUT "$reject_except";
317   $join = " : \\\n    ";
318   $last_was_negated = ($_[7] ne "+");
319   }
320 if (defined $reject)
321   {
322   print STDOUT "$join$reject";
323   $join = " : \\\n    ";
324   $last_was_negated = ($_[7] eq "+");
325   }
326 if (defined $accept)
327   {
328   print STDOUT "$join$accept";
329   $last_was_negated = ($_[7] ne "+");
330   $join = " : \\\n    ";
331   }
332
333 print STDOUT "$join*" if $last_was_negated;
334 print STDOUT "\"\n";
335 }
336
337
338
339 ##################################################
340 #      Amalgamate boolean and exception list(s)  #
341 ##################################################
342
343 sub amalgboolandlist {
344 my($name,$bool,$e1,$e2) = @_;
345
346 print STDOUT "# >> Option rewritten by convert4r3\n";
347 if ($bool eq "false")
348   {
349   printf STDOUT "$i1$name =\n";
350   }
351 else
352   {
353   printf STDOUT "$i1$name = ";
354   my($n1) = &negate($o{$e1});
355   my($n2) = &negate($o{$e2});
356   if (!defined $n1 && !defined $n2)
357     {
358     print STDOUT "*\n";
359     }
360   elsif (!defined $n1)
361     {
362     print STDOUT "\"$n2 : \\\n    *\"\n";
363     }
364   elsif (!defined $n2)
365     {
366     print STDOUT "\"$n1 : \\\n    *\"\n";
367     }
368   else
369     {
370     print STDOUT "\"$n1 : \\\n    $n2 : \\\n    *\"\n";
371     }
372   }
373 }
374
375
376
377 ##################################################
378 #             Convert mask format                #
379 ##################################################
380
381 # This function converts an address and mask in old-fashioned dotted-quad
382 # format into an address plus a new format mask.
383
384 @byte_list = (0, 128, 192, 224, 240, 248, 252, 254, 255);
385
386 sub mask {
387 my($address,$mask) = @_;
388 my($length) = 0;
389 my($i, $j);
390
391 my(@bytes) = split /\./, $mask;
392
393 for ($i = 0; $i < 4; $i++)
394   {
395   for ($j = 0; $j <= 8; $j++)
396     {
397     if ($bytes[$i] == $byte_list[$j])
398       {
399       $length += $j;
400       if ($j != 8)
401         {
402         for ($i++; $i < 4; $i++)
403           {
404           $j = 9 if ($bytes[$i] != 0);
405           }
406         }
407       last;
408       }
409     }
410
411   if ($j > 8)
412     {
413     print STDERR "*** IP mask $mask cannot be converted to /n format. ***\n";
414     return "$address/$mask";
415     }
416   }
417
418 if (!defined $masks{$mask})
419   {
420   printf STDERR "\n%03d IP address mask $mask converted to /$length\n",
421     ++$count, $mask, $length;
422   $masks{$mask} = 1;
423   }
424
425 return sprintf "$address/%d", $length;
426 }
427
428
429
430
431
432 ##################################################
433 #                  Main program                  #
434 ##################################################
435
436 print STDERR "Exim pre-release 3.00 configuration file converter.\n";
437
438 $count = 0;
439 $seen_helo_accept_junk = 0;
440 $seen_hold_domains = 0;
441 $seen_receiver_unqualified = 0;
442 $seen_receiver_verify_except = 0;
443 $seen_receiver_verify_senders = 0;
444 $seen_rfc1413_except = 0;
445 $seen_sender_accept = 0;
446 $seen_sender_accept_recipients = 0;
447 $seen_sender_host_accept = 0;
448 $seen_sender_host_accept_recipients = 0;
449 $seen_sender_host_accept_relay = 0;
450 $seen_sender_unqualified = 0;
451 $seen_sender_verify_except_hosts = 0;
452 $seen_smtp_etrn = 0;
453 $seen_smtp_expn = 0;
454 $seen_smtp_reserve = 0;
455 $semicomma = 0;
456
457 # Read the entire file into an array
458
459 chomp(@c = <STDIN>);
460
461 # First, go through the input and covert any net masks in the old dotted-quad
462 # style into the new /n style.
463
464 for ($i = 0; $i < scalar(@c); $i++)
465   {
466   $c[$i] =~
467     s"((?:\d{1,3}\.){3}\d{1,3})/((?:\d{1,3}\.){3}\d{1,3})"&mask($1,$2)"eg;
468   }
469
470 # We now make two more passes over the input. In the first pass, we place all
471 # the option values into an associative array. Main options are keyed by their
472 # names; options for drivers are keyed by a driver type letter, the driver
473 # name, and the option name, dot-separated. In the second pass we modify
474 # the options if necessary, and write the output file.
475
476 for ($pass = 1; $pass < 3; $pass++)
477   {
478   $prefix = "";
479   $driver = "";
480   $last_was_blank = 0;
481
482   for ($i = 0; $i < scalar(@c); $i++)
483     {
484     # Everything after the router section is just copied in pass 2 and
485     # ignored in pass 1.
486
487     if ($prefix eq "end")
488       {
489       print STDOUT "$c[$i]\n" if $pass == 2;
490       next;
491       }
492
493     # Analyze the line
494
495     $type = &checkline($c[$i]);
496
497     # Skip comments in pass 1; copy in pass 2
498
499     if ($type eq "comment")
500       {
501       $last_was_blank = ($c[$i] =~ /^\s*$/)? 1 : 0;
502       print STDOUT "$c[$i]\n" if $pass == 2;
503       next;
504       }
505
506     # Skip/copy macro definitions, but must handle continuations
507
508     if ($type eq "macro")
509       {
510       print STDOUT "$c[$i]\n" if $pass == 2;
511       while ($c[$i] =~ /\\\s*$/)
512         {
513         $i++;
514         print STDOUT "$c[$i]\n" if $pass == 2;
515         }
516       $last_was_blank = 0;
517       next;
518       }
519
520     # Handle end of section
521
522     if ($type eq "end")
523       {
524       $prefix = "end"if $prefix eq "r.";
525       $prefix = "r." if $prefix eq "d.";
526       $prefix = "d." if $prefix eq "t.";
527       $prefix = "t." if $prefix eq "";
528       print STDOUT "$c[$i]\n" if $pass == 2;
529       $last_was_blank = 0;
530       next;
531       }
532
533     # Handle start of a new driver
534
535     if ($type eq "driver")
536       {
537       $driver = $name;
538       print STDOUT "$c[$i]\n" if $pass == 2;
539       $last_was_blank = 0;
540       $seen_domains = 0;
541       $seen_local_parts = 0;
542       $seen_senders = 0;
543       $seen_mx_domains = 0;
544       $seen_serialize = 0;
545       next;
546       }
547
548     # Handle definition of an option
549
550     if ($type eq "option")
551       {
552       # Handle continued strings
553
554       if ($rest =~ /^=\s*".*\\$/)
555         {
556         for (;;)
557           {
558           $rest .= "\n$c[++$i]";
559           last unless $c[$i] =~ /(\\\s*$|^\s*#)/;
560           }
561         }
562
563       # Remove any terminating commas and semicolons in pass 2
564
565       if ($pass == 2 && $rest =~ /[;,]\s*$/)
566         {
567         $rest =~ s/\s*[;,]\s*$//;
568         if (!$semicomma)
569           {
570           printf STDERR
571             "\n%03d Terminating semicolons and commas removed from driver " .
572             "options.\n", ++$count;
573           $semicomma = 1;
574           }
575         }
576
577       # Convert all booleans to "x = true/false" format, but save the
578       # original so that it can be reproduced unchanged for options that
579       # are not of interest.
580
581       $origname = $name;
582       $origrest = $rest;
583
584       if ($name =~ /^not?_(.*)/)
585         {
586         $name = $1;
587         $rest = "= false";
588         }
589       elsif ($rest !~ /^=/)
590         {
591         $rest = "= true";
592         }
593
594       # Set up the associative array key, and get rid of the = on the data
595
596       $key = ($prefix eq "")? "$name" : "$prefix$driver.$name";
597       ($rest) = $rest =~ /^=\s*(.*)/s;
598
599       # Create the associative array of values in pass 1
600
601       if ($pass == 1)
602         {
603         $o{$key} = $rest;
604         }
605
606       # In pass 2, test for interesting options and do the necessary; copy
607       # all the rest.
608
609       else
610         {
611         ##########  Global configuration ##########
612
613         # These global options are abolished
614
615         if ($name eq "address_directory_transport" ||
616             $name eq "address_directory2_transport" ||
617             $name eq "address_file_transport" ||
618             $name eq "address_pipe_transport" ||
619             $name eq "address_reply_transport")
620           {
621           ($n2) = $name =~ /^address_(.*)/;
622           printf STDERR "\n%03d $name option deleted.\n", ++$count;
623           printf STDERR "    $n2 will be added to appropriate directors.\n";
624           $i = &skipblanks($i);
625           next;
626           }
627
628         # This debugging option is abolished
629
630         elsif ($name eq "sender_verify_log_details")
631           {
632           printf STDERR "\n%03d $name option deleted.\n", ++$count;
633           printf STDERR "    (Little used facility abolished.)\n";
634           }
635
636         # This option has been renamed
637
638         elsif ($name eq "check_dns_names")
639           {
640           $origname =~ s/check_dns/dns_check/;
641           print STDOUT "# >> Option rewritten by convert4r3\n";
642           print STDOUT "$i1$origname$i2$origrest\n";
643           printf STDERR "\n%03d check_dns_names renamed as dns_check_names.\n",
644             ++$count;
645           }
646
647         # helo_accept_junk_nets is abolished
648
649         elsif ($name eq "helo_accept_junk_nets" ||
650                $name eq "helo_accept_junk_hosts")
651           {
652           if (!$seen_helo_accept_junk)
653             {
654             &amalgamate("helo_accept_junk_nets", "",
655               "helo_accept_junk_hosts", "helo_accept_junk_hosts", "+");
656             $seen_helo_accept_junk = 1;
657             }
658           else
659             {
660             $i = &skipblanks($i);
661             next;
662             }
663           }
664
665         # helo_verify_except_{hosts,nets} are abolished, and helo_verify
666         # is now a host list instead of a boolean.
667
668         elsif ($name eq "helo_verify")
669           {
670           &amalgboolandlist("helo_verify", $rest, "helo_verify_except_hosts",
671             "helo_verify_except_nets");
672           printf STDERR "\n%03d helo_verify converted to host list.\n",
673             ++$count;
674           }
675         elsif ($name eq "helo_verify_except_hosts" ||
676                $name eq "helo_verify_except_nets")
677           {
678           $i = &skipblanks($i);
679           next;
680           }
681
682         # helo_verify_nets was an old synonym for host_lookup_nets; only
683         # one of them will be encountered. Change to a new name.
684
685         elsif ($name eq "helo_verify_nets" ||
686                $name eq "host_lookup_nets")
687           {
688           print STDOUT "# >> Option rewritten by convert4r3\n";
689           print STDOUT "${i1}host_lookup$i2$origrest\n";
690           printf STDERR "\n%03d $name renamed as host_lookup.\n", ++$count;
691           }
692
693         # hold_domains_except is abolished; add as negated items to
694         # hold_domains.
695
696         elsif ($name eq "hold_domains_except" ||
697                $name eq "hold_domains")
698           {
699           if ($seen_hold_domains)         # If already done with these
700             {                             # omit, and following blanks.
701             $i = &skipblanks($i);
702             next;
703             }
704           $seen_hold_domains = 1;
705
706           if (exists $o{"hold_domains_except"})
707             {
708             &amalgamate("hold_domains", "hold_domains_except", "",
709               "hold_domains", "+");
710             }
711           else
712             {
713             print STDOUT "$i1$origname$i2$origrest\n";
714             }
715           }
716
717         # ignore_fromline_nets is renamed as ignore_fromline_hosts
718
719         elsif ($name eq "ignore_fromline_nets")
720           {
721           $origname =~ s/_nets/_hosts/;
722           print STDOUT "# >> Option rewritten by convert4r3\n";
723           print STDOUT "$i1$origname$i2$origrest\n";
724           printf STDERR
725             "\n%03d ignore_fromline_nets renamed as ignore_fromline_hosts.\n",
726             ++$count;
727           }
728
729         # Output a warning for message filters with no transports set
730
731         elsif ($name eq "message_filter")
732           {
733           print STDOUT "$i1$origname$i2$origrest\n";
734
735           if (!exists $o{"message_filter_directory_transport"} &&
736               !exists $o{"message_filter_directory2_transport"} &&
737               !exists $o{"message_filter_file_transport"} &&
738               !exists $o{"message_filter_pipe_transport"} &&
739               !exists $o{"message_filter_reply_transport"})
740             {
741             printf STDERR
742               "\n%03d message_filter is set, but no message_filter transports "
743               . "are defined.\n"
744               . "    If your filter generates file or pipe deliveries, or "
745               . "auto-replies,\n"
746               . "    you will need to define "
747               . "message_filter_{file,pipe,reply}_transport\n"
748               . "    options, as required.\n", ++$count;
749             }
750           }
751
752         # queue_remote_except is abolished, and queue_remote is replaced by
753         # queue_remote_domains, which is a host list.
754
755         elsif ($name eq "queue_remote")
756           {
757           &amalgboolandlist("queue_remote_domains", $rest,
758             "queue_remote_except", "");
759           printf STDERR
760             "\n%03d queue_remote converted to domain list queue_remote_domains.\n",
761             ++$count;
762           }
763         elsif ($name eq "queue_remote_except")
764           {
765           $i = &skipblanks($i);
766           next;
767           }
768
769         # queue_smtp_except is abolished, and queue_smtp is replaced by
770         # queue_smtp_domains, which is a host list.
771
772         elsif ($name eq "queue_smtp")
773           {
774           &amalgboolandlist("queue_smtp_domains", $rest,
775             "queue_smtp_except", "");
776           printf STDERR
777             "\n%03d queue_smtp converted to domain list queue_smtp_domains.\n",
778             ++$count;
779           }
780         elsif ($name eq "queue_smtp_except")
781           {
782           $i = &skipblanks($i);
783           next;
784           }
785
786         # rbl_except_nets is replaced by rbl_hosts
787
788         elsif ($name eq "rbl_except_nets")
789           {
790           &amalgamate("", "rbl_except_nets", "", "rbl_hosts", "+");
791           }
792
793         # receiver_unqualified_nets is abolished
794
795         elsif ($name eq "receiver_unqualified_nets" ||
796                $name eq "receiver_unqualified_hosts")
797           {
798           if (!$seen_receiver_unqualified)
799             {
800             &amalgamate("receiver_unqualified_nets", "",
801               "receiver_unqualified_hosts", "receiver_unqualified_hosts", "+");
802             $seen_receiver_unqualified = 1;
803             }
804           else
805             {
806             $i = &skipblanks($i);
807             next;
808             }
809           }
810
811         # receiver_verify_except_{hosts,nets} are replaced by
812         # receiver_verify_hosts.
813
814         elsif ($name eq "receiver_verify_except_hosts" ||
815                $name eq "receiver_verify_except_nets")
816           {
817           if (!$seen_receiver_verify_except)
818             {
819             &amalgboolandlist("receiver_verify_hosts", "true",
820               "receiver_verify_except_hosts", "receiver_verify_except_nets");
821             printf STDERR
822               "\n%03d receiver_verify_except_{hosts,nets} converted to " .
823               "receiver_verify_hosts.\n",
824               ++$count;
825             $seen_receiver_verify_except = 1;
826             }
827           else
828             {
829             $i = &skipblanks($i);
830             next;
831             }
832           }
833
834         # receiver_verify_senders_except is abolished
835
836         elsif ($name eq "receiver_verify_senders" ||
837                $name eq "receiver_verify_senders_except")
838           {
839           if (defined $o{"receiver_verify_senders_except"})
840             {
841             if (!$seen_receiver_verify_senders)
842               {
843               &amalgamate("receiver_verify_senders",
844                 "receiver_verify_senders_except", "",
845                 "receiver_verify_senders", "+");
846               $seen_receiver_verify_senders = 1;
847               }
848             else
849               {
850               $i = &skipblanks($i);
851               next;
852               }
853             }
854           else
855             {
856             print STDOUT "$i1$origname$i2$origrest\n";
857             }
858           }
859
860         # rfc1413_except_{hosts,nets} are replaced by rfc1413_hosts.
861
862         elsif ($name eq "rfc1413_except_hosts" ||
863                $name eq "rfc1413_except_nets")
864           {
865           if (!$seen_rfc1413_except)
866             {
867             &amalgboolandlist("rfc1413_hosts", "true",
868               "rfc1413_except_hosts", "rfc1413_except_nets");
869             printf STDERR
870               "\n%03d rfc1413_except_{hosts,nets} converted to rfc1413_hosts.\n",
871               ++$count;
872             $seen_rfc1413_except = 1;
873             }
874           else
875             {
876             $i = &skipblanks($i);
877             next;
878             }
879           }
880
881         # sender_accept and sender_reject_except are abolished
882
883         elsif ($name eq "sender_accept" ||
884                $name eq "sender_reject")
885           {
886           if (!$seen_sender_accept)
887             {
888             &amalgamate("sender_accept", "sender_reject",
889               "sender_reject_except", "sender_reject", "-");
890             $seen_sender_accept = 1;
891             }
892           else
893             {
894             $i = &skipblanks($i);
895             next;
896             }
897           }
898
899         # sender_accept_recipients is also abolished; sender_reject_except
900         # also used to apply to this, so we include it here as well.
901
902         elsif ($name eq "sender_accept_recipients" ||
903                $name eq "sender_reject_recipients")
904           {
905           if (!$seen_sender_accept_recipients)
906             {
907             &amalgamate("sender_accept_recipients", "sender_reject_recipients",
908               "sender_reject_except", "sender_reject_recipients", "-");
909             $seen_sender_accept_recipients = 1;
910             }
911           else
912             {
913             $i = &skipblanks($i);
914             next;
915             }
916           }
917
918         # sender_reject_except must be removed
919
920         elsif ($name eq "sender_reject_except")
921           {
922           $i = &skipblanks($i);
923           next;
924           }
925
926         # sender_{host,net}_{accept,reject}[_except] all collapse into
927         # host_reject.
928
929         elsif ($name eq "sender_host_accept" ||
930                $name eq "sender_net_accept" ||
931                $name eq "sender_host_reject" ||
932                $name eq "sender_net_reject")
933           {
934           if (!$seen_sender_host_accept)
935             {
936             &amalgamatepairs("sender_host_accept", "sender_net_accept",
937               "sender_host_reject", "sender_net_reject",
938               "sender_host_reject_except", "sender_net_reject_except",
939               "host_reject", "-");
940             printf STDERR "\n%03d sender_{host,net}_{accept,reject} and " .
941               "sender_{host_net}_reject_except\n" .
942               "    amalgamated into host_reject.\n", ++$count;
943             $seen_sender_host_accept = 1;
944             }
945           else
946             {
947             $i = &skipblanks($i);
948             next;
949             }
950           }
951
952         # sender_{host,net}_{accept,reject}_recipients all collapse into
953         # host_reject_recipients.
954
955         elsif ($name eq "sender_host_accept_recipients" ||
956                $name eq "sender_net_accept_recipients" ||
957                $name eq "sender_host_reject_recipients" ||
958                $name eq "sender_net_reject_recipients")
959           {
960           if (!$seen_sender_host_accept_recipients)
961             {
962             &amalgamatepairs("sender_host_accept_recipients",
963               "sender_net_accept_recipients",
964               "sender_host_reject_recipients",
965               "sender_net_reject_recipients",
966               "sender_host_reject_except", "sender_net_reject_except",
967               "host_reject_recipients", "-");
968             printf STDERR "\n%03d sender_{host,net}_{accept,reject}_recipients"
969               . "\n    and sender_{host_net}_reject_except"
970               . "\n    amalgamated into host_reject_recipients.\n", ++$count;
971             $seen_sender_host_accept_recipients = 1;
972             }
973           else
974             {
975             $i = &skipblanks($i);
976             next;
977             }
978           }
979
980         # sender_{host,net}_reject_except must be removed
981
982         elsif ($name eq "sender_host_reject_except" ||
983                $name eq "sender_net_reject_except")
984           {
985           $i = &skipblanks($i);
986           next;
987           }
988
989         # sender_{host,net}_{accept,reject}_relay all collapse into
990         # host_accept_relay.
991
992         elsif ($name eq "sender_host_accept_relay" ||
993                $name eq "sender_net_accept_relay" ||
994                $name eq "sender_host_reject_relay" ||
995                $name eq "sender_net_reject_relay")
996           {
997           if (!$seen_sender_host_accept_relay)
998             {
999             &amalgamatepairs("sender_host_accept_relay",
1000               "sender_net_accept_relay",
1001               "sender_host_reject_relay",
1002               "sender_net_reject_relay",
1003               "sender_host_reject_relay_except",
1004               "sender_net_reject_relay_except",
1005               "host_accept_relay", "+");
1006             printf STDERR "\n%03d sender_{host,net}_{accept,reject}_relay"
1007               . "\n    and sender_{host_net}_reject_relay_except"
1008               . "\n    amalgamated into host_accept_relay.\n", ++$count;
1009             $seen_sender_host_accept_relay = 1;
1010             }
1011           else
1012             {
1013             $i = &skipblanks($i);
1014             next;
1015             }
1016           }
1017
1018         # sender_{host,net}_reject_relay_except must be removed
1019
1020         elsif ($name eq "sender_host_reject_relay_except" ||
1021                $name eq "sender_net_reject_relay_except")
1022           {
1023           $i = &skipblanks($i);
1024           next;
1025           }
1026
1027
1028         # sender_unqualified_nets is abolished
1029
1030         elsif ($name eq "sender_unqualified_nets" ||
1031                $name eq "sender_unqualified_hosts")
1032           {
1033           if (!$seen_sender_unqualified)
1034             {
1035             &amalgamate("sender_unqualified_nets", "",
1036               "sender_unqualified_hosts", "sender_unqualified_hosts", "+");
1037             $seen_sender_unqualified = 1;
1038             }
1039           else
1040             {
1041             $i = &skipblanks($i);
1042             next;
1043             }
1044           }
1045
1046         # sender_verify_except_{hosts,nets} are replaced by sender_verify_hosts.
1047
1048         elsif ($name eq "sender_verify_except_hosts" ||
1049                $name eq "sender_verify_except_nets")
1050           {
1051           if (!$seen_sender_verify_except_hosts)
1052             {
1053             &amalgboolandlist("sender_verify_hosts", "true",
1054               "sender_verify_except_hosts", "sender_verify_except_nets");
1055             printf STDERR
1056               "\n%03d sender_verify_except_{hosts,nets} converted to " .
1057               "sender_verify_hosts.\n",
1058               ++$count;
1059             $seen_sender_verify_except_hosts = 1;
1060             }
1061           else
1062             {
1063             $i = &skipblanks($i);
1064             next;
1065             }
1066           }
1067
1068         # smtp_etrn_nets is abolished
1069
1070         elsif ($name eq "smtp_etrn_nets" ||
1071                $name eq "smtp_etrn_hosts")
1072           {
1073           if (!$seen_smtp_etrn)
1074             {
1075             &amalgamate("smtp_etrn_nets", "",
1076               "smtp_etrn_hosts", "smtp_etrn_hosts", "+");
1077             $seen_smtp_etrn = 1;
1078             }
1079           else
1080             {
1081             $i = &skipblanks($i);
1082             next;
1083             }
1084           }
1085
1086         # smtp_expn_nets is abolished
1087
1088         elsif ($name eq "smtp_expn_nets" ||
1089                $name eq "smtp_expn_hosts")
1090           {
1091           if (!$seen_smtp_expn)
1092             {
1093             &amalgamate("smtp_expn_nets", "",
1094               "smtp_expn_hosts", "smtp_expn_hosts", "+");
1095             $seen_smtp_expn = 1;
1096             }
1097           else
1098             {
1099             $i = &skipblanks($i);
1100             next;
1101             }
1102           }
1103
1104         # This option has been renamed
1105
1106         elsif ($name eq "smtp_log_connections")
1107           {
1108           $origname =~ s/smtp_log/log_smtp/;
1109           print STDOUT "# >> Option rewritten by convert4r3\n";
1110           print STDOUT "$i1$origname$i2$origrest\n";
1111           printf STDERR "\n%03d smtp_log_connections renamed as " .
1112             "log_smtp_connections.\n",
1113             ++$count;
1114           }
1115
1116         # smtp_reserve_nets is abolished
1117
1118         elsif ($name eq "smtp_reserve_nets" ||
1119                $name eq "smtp_reserve_hosts")
1120           {
1121           if (!$seen_smtp_reserve)
1122             {
1123             &amalgamate("smtp_reserve_nets", "",
1124               "smtp_reserve_hosts", "smtp_reserve_hosts", "+");
1125             $seen_smtp_reserve = 1;
1126             }
1127           else
1128             {
1129             $i = &skipblanks($i);
1130             next;
1131             }
1132           }
1133
1134         ###########  Driver configurations  ##########
1135
1136         # For aliasfile and forwardfile directors, add file, pipe, and
1137         # reply transports - copying from the globals if they are set.
1138
1139         elsif ($name eq "driver")
1140           {
1141           $driver_type = $rest;
1142           print STDOUT "$i1$origname$i2$origrest\n";
1143           if ($rest eq "aliasfile" || $rest eq "forwardfile")
1144             {
1145             &add_transport("directory");
1146             &add_transport("directory2");
1147             &add_transport("file");
1148             &add_transport("pipe");
1149             &add_transport("reply") if $rest eq "forwardfile";
1150             }
1151           }
1152
1153         # except_domains is abolished; add as negated items to domains.
1154
1155         elsif ($name eq "except_domains" ||
1156                $name eq "domains")
1157           {
1158           if ($seen_domains)              # If already done with these
1159             {                             # omit, and following blanks.
1160             $i = &skipblanks($i);
1161             next;
1162             }
1163           $seen_domains = 1;
1164
1165           if (exists $o{"$prefix$driver.except_domains"})
1166             {
1167             &amalgamate("$prefix$driver.domains",
1168                          "$prefix$driver.except_domains", "",
1169               "domains", "+");
1170             }
1171           else
1172             {
1173             print STDOUT "$i1$origname$i2$origrest\n";
1174             }
1175           }
1176
1177         # except_local_parts is abolished; add as negated items to
1178         # local_parts.
1179
1180         elsif ($name eq "except_local_parts" ||
1181                $name eq "local_parts")
1182           {
1183           if ($seen_local_parts)              # If already done with these
1184             {                             # omit, and following blanks.
1185             $i = &skipblanks($i);
1186             next;
1187             }
1188           $seen_local_parts = 1;
1189
1190           if (exists $o{"$prefix$driver.except_local_parts"})
1191             {
1192             &amalgamate("$prefix$driver.local_parts",
1193                          "$prefix$driver.except_local_parts", "",
1194               "local_parts", "+");
1195             }
1196           else
1197             {
1198             print STDOUT "$i1$origname$i2$origrest\n";
1199             }
1200           }
1201
1202         # except_senders is abolished; add as negated items to senders
1203
1204         elsif ($name eq "except_senders" ||
1205                $name eq "senders")
1206           {
1207           if ($seen_senders)              # If already done with these
1208             {                             # omit, and following blanks.
1209             $i = &skipblanks($i);
1210             next;
1211             }
1212           $seen_senders = 1;
1213
1214           if (exists $o{"$prefix$driver.except_senders"})
1215             {
1216             &amalgamate("$prefix$driver.senders",
1217                          "$prefix$driver.except_senders", "",
1218               "senders", "+");
1219             }
1220           else
1221             {
1222             print STDOUT "$i1$origname$i2$origrest\n";
1223             }
1224           }
1225
1226         # This option has been renamed
1227
1228         elsif ($name eq "directory" && $driver_type eq "aliasfile")
1229           {
1230           $origname =~ s/directory/home_directory/;
1231           print STDOUT "# >> Option rewritten by convert4r3\n";
1232           print STDOUT "$i1$origname$i2$origrest\n";
1233           printf STDERR "\n%03d directory renamed as " .
1234             "home_directory in \"$driver\" director.\n",
1235             ++$count;
1236           }
1237
1238         # This option has been renamed
1239
1240         elsif ($name eq "directory" && $driver_type eq "forwardfile")
1241           {
1242           $origname =~ s/directory/file_directory/;
1243           print STDOUT "# >> Option rewritten by convert4r3\n";
1244           print STDOUT "$i1$origname$i2$origrest\n";
1245           printf STDERR "\n%03d directory renamed as " .
1246             "file_directory in \"$driver\" director.\n",
1247             ++$count;
1248           }
1249
1250         # This option has been renamed
1251
1252         elsif ($name eq "forbid_filter_log" && $driver_type eq "forwardfile")
1253           {
1254           $origname =~ s/log/logwrite/;
1255           print STDOUT "# >> Option rewritten by convert4r3\n";
1256           print STDOUT "$i1$origname$i2$origrest\n";
1257           printf STDERR "\n%03d forbid_filter_log renamed as " .
1258             "forbid_filter_logwrite in \"$driver\" director.\n",
1259             ++$count;
1260           }
1261
1262         # This option has been renamed
1263
1264         elsif ($name eq "directory" && $driver_type eq "localuser")
1265           {
1266           $origname =~ s/directory/match_directory/;
1267           print STDOUT "# >> Option rewritten by convert4r3\n";
1268           print STDOUT "$i1$origname$i2$origrest\n";
1269           printf STDERR "\n%03d directory renamed as " .
1270             "match_directory in \"$driver\" director.\n",
1271             ++$count;
1272           }
1273
1274         # mx_domains_except (and old synonym non_mx_domains) are abolished
1275         # (both lookuphost router and smtp transport)
1276
1277         elsif ($name eq "mx_domains" ||
1278                $name eq "mx_domains_except" ||
1279                $name eq "non_mx_domains")
1280           {
1281           if ($seen_mx_domains)              # If already done with these
1282             {                             # omit, and following blanks.
1283             $i = &skipblanks($i);
1284             next;
1285             }
1286           $seen_mx_domains = 1;
1287
1288           if (exists $o{"$prefix$driver.mx_domains_except"} ||
1289               exists $o{"$prefix$driver.non_mx_domains"})
1290             {
1291             $o{"$prefix$driver.mx_domains_except"} =
1292               &pair("$prefix$driver.mx_domains_except",
1293                     "$prefix$driver.non_mx_domains");
1294
1295             &amalgamate("$prefix$driver.mx_domains",
1296                         "$prefix$driver.mx_domains_except", "",
1297               "mx_domains", "+");
1298             }
1299           else
1300             {
1301             print STDOUT "$i1$origname$i2$origrest\n";
1302             }
1303           }
1304
1305         # This option has been renamed
1306
1307         elsif ($name eq "directory" && $driver_type eq "pipe")
1308           {
1309           $origname =~ s/directory/home_directory/;
1310           print STDOUT "# >> Option rewritten by convert4r3\n";
1311           print STDOUT "$i1$origname$i2$origrest\n";
1312           printf STDERR "\n%03d directory renamed as " .
1313             "home_directory in \"$driver\" director.\n",
1314             ++$count;
1315           }
1316
1317         # serialize_nets is abolished
1318
1319         elsif ($name eq "serialize_nets" ||
1320                $name eq "serialize_hosts")
1321           {
1322           if (!$seen_serialize)
1323             {
1324             &amalgamate("$prefix$driver.serialize_nets", "",
1325               "$prefix$driver.serialize_hosts", "serialize_hosts", "+");
1326             $seen_serialize = 1;
1327             }
1328           else
1329             {
1330             $i = &skipblanks($i);
1331             next;
1332             }
1333           }
1334
1335
1336         # Option not of interest; reproduce verbatim
1337
1338         else
1339           {
1340           print STDOUT "$i1$origname$i2$origrest\n";
1341           }
1342
1343
1344         $last_was_blank = 0;
1345         }
1346       }
1347     }
1348
1349   }
1350
1351 # Debugging: show the associative array
1352 # foreach $key (sort keys %o) { print STDERR "$key = $o{$key}\n"; }
1353
1354 print STDERR "\nEnd of configuration file conversion.\n";
1355 print STDERR "\n*******************************************************\n";
1356 print STDERR   "***** Please review the generated file carefully. *****\n";
1357 print STDERR   "*******************************************************\n\n";
1358
1359 print STDERR "In particular:\n\n";
1360
1361 print STDERR "(1) If you use regular expressions in any options that have\n";
1362 print STDERR "    been rewritten by this script, they might have been put\n";
1363 print STDERR "    inside quotes, when then were not previously quoted. This\n";
1364 print STDERR "    means that any backslashes in them must now be escaped.\n\n";
1365
1366 print STDERR "(2) If your configuration refers to any external files that\n";
1367 print STDERR "    contain lists of network addresses, check that the masks\n";
1368 print STDERR "    are specified as single numbers, e.g. /24 and NOT as dotted\n";
1369 print STDERR "    quads (e.g. 255.255.255.0) because Exim release 3.00 does\n";
1370 print STDERR "    not recognize the dotted quad form.\n\n";
1371
1372 print STDERR "(3) If your configuration uses macros for lists of domains or\n";
1373 print STDERR "    hosts or addresses, check to see if any of the references\n";
1374 print STDERR "    have been negated. If so, you will have to rework things,\n";
1375 print STDERR "    because the negation will apply only to the first item in\n";
1376 print STDERR "    the macro-generated list.\n\n";
1377
1378 print STDERR "(4) If you do not generate deliveries to pipes, files, or\n";
1379 print STDERR "    auto-replies in your aliasfile and forwardfile directors,\n";
1380 print STDERR "    you can remove the added transport settings.\n\n";
1381
1382 # End of convert4r3