ph10 2004/10/07 16:04:36 BST
Added files:
exim-doc/doc-scripts BuildFAQ BuildHTML BuildInfo BuildPDF
DoConts DoIndex JoinPS Makefile f2h f2txt
faqchk fc2k g2h g2man g2t
exim-doc/doc-src FAQ.src filter.src markup.sg spec.src
exim-doc/doc-txt ChangeLog ChangeLog.0 Exim3.upgrade
Exim4.upgrade NewStuff OptionLists.txt
README README.SIEVE dbm.discuss.txt
pcrepattern.txt pcretest.txt
Log:
Start
Revision Changes Path
1.1 +59 -0 exim/exim-doc/doc-scripts/BuildFAQ (new)
1.1 +12 -0 exim/exim-doc/doc-scripts/BuildHTML (new)
1.1 +32 -0 exim/exim-doc/doc-scripts/BuildInfo (new)
1.1 +10 -0 exim/exim-doc/doc-scripts/BuildPDF (new)
1.1 +71 -0 exim/exim-doc/doc-scripts/DoConts (new)
1.1 +430 -0 exim/exim-doc/doc-scripts/DoIndex (new)
1.1 +130 -0 exim/exim-doc/doc-scripts/JoinPS (new)
1.1 +31 -0 exim/exim-doc/doc-scripts/Makefile (new)
1.1 +338 -0 exim/exim-doc/doc-scripts/f2h (new)
1.1 +107 -0 exim/exim-doc/doc-scripts/f2txt (new)
1.1 +102 -0 exim/exim-doc/doc-scripts/faqchk (new)
1.1 +344 -0 exim/exim-doc/doc-scripts/fc2k (new)
1.1 +1451 -0 exim/exim-doc/doc-scripts/g2h (new)
1.1 +251 -0 exim/exim-doc/doc-scripts/g2man (new)
1.1 +1347 -0 exim/exim-doc/doc-scripts/g2t (new)
1.1 +7015 -0 exim/exim-doc/doc-src/FAQ.src (new)
1.1 +1644 -0 exim/exim-doc/doc-src/filter.src (new)
1.1 +86 -0 exim/exim-doc/doc-src/markup.sg (new)
1.1 +27935 -0 exim/exim-doc/doc-src/spec.src (new)
1.1 +2058 -0 exim/exim-doc/doc-txt/ChangeLog (new)
1.1 +2862 -0 exim/exim-doc/doc-txt/ChangeLog.0 (new)
1.1 +673 -0 exim/exim-doc/doc-txt/Exim3.upgrade (new)
1.1 +1732 -0 exim/exim-doc/doc-txt/Exim4.upgrade (new)
1.1 +605 -0 exim/exim-doc/doc-txt/NewStuff (new)
1.1 +927 -0 exim/exim-doc/doc-txt/OptionLists.txt (new)
1.1 +84 -0 exim/exim-doc/doc-txt/README (new)
1.1 +433 -0 exim/exim-doc/doc-txt/README.SIEVE (new)
1.1 +322 -0 exim/exim-doc/doc-txt/dbm.discuss.txt (new)
1.1 +1413 -0 exim/exim-doc/doc-txt/pcrepattern.txt (new)
1.1 +455 -0 exim/exim-doc/doc-txt/pcretest.txt (new)
Index: BuildFAQ
====================================================================
#! /bin/sh
# $Cambridge: exim/exim-doc/doc-scripts/BuildFAQ,v 1.1 2004/10/07 15:04:35 ph10 Exp $
# Script to build the Exim FAQ in text and HTML formats.
/bin/rm -f FAQ.txt* html/FAQ* FAQ-html/* FAQ-html.tar.*
/bin/rm -f config.samples.tar.gz config.samples.tar.bz2
# The FAQchk Perl script checks for the numbers being in order and for the
# right number of blank lines at various places.
faqchk FAQ.src
if [ $? != 0 ]; then exit 1; fi
# HTML version
f2h FAQ.src html
echo "html/FAQ*.html made"
fc2k
echo "html/FAQ-KWIC*.html made"
cp html/FAQ* html/*.txt FAQ-html
echo "copied to FAQ-html"
tar cf FAQ-html.tar FAQ-html
gzip FAQ-html.tar
echo "FAQ-html.tar.gz made"
tar cf FAQ-html.tar FAQ-html
bzip2 -9 FAQ-html.tar
echo "FAQ-html.tar.gz2 made"
# ASCII version
f2txt FAQ.src FAQ.txt
echo "FAQ.txt made"
cp FAQ.txt FAQ.txt-t
gzip -v --best FAQ.txt-t
mv FAQ.txt-t.gz FAQ.txt.gz
echo "FAQ.txt.gz made"
cp FAQ.txt FAQ.txt-t
bzip2 -v -9 FAQ.txt-t
mv FAQ.txt-t.bz2 FAQ.txt.bz2
echo "FAQ.txt.bz2 made"
# Configuration samples
tar cf config.samples.tar config.samples
gzip config.samples.tar
echo "config.samples.tar.gz made"
tar cf config.samples.tar config.samples
bzip2 -9 config.samples.tar
echo "config.samples.tar.bz2 made"
# End
Index: BuildHTML
====================================================================
#! /bin/sh
# $Cambridge: exim/exim-doc/doc-scripts/BuildHTML,v 1.1 2004/10/07 15:04:35 ph10 Exp $
if [ $# != 1 ]; then
echo "*** Usage: BuildHTML <Exim version>"
exit 1
fi
g2h -split chapter filter.src "Exim Filter Specification"
g2h -split chapter spec.src "Exim $1 Specification"
# End
Index: BuildInfo
====================================================================
#! /bin/sh
# $Cambridge: exim/exim-doc/doc-scripts/BuildInfo,v 1.1 2004/10/07 15:04:35 ph10 Exp $
if [ "$1" = "filter" ]; then
g2t -filter filter.src >filter.texinfo
if [ $? != 0 ]; then exit 1; fi
cd info
makeinfo filter.texinfo
if [ $? != 0 ]; then exit 1; fi
echo ""
echo info filter.info
echo ""
info -f ./filter.info
exit
fi
if [ "$1" = "" ]; then
g2t spec.src >spec.texinfo
if [ $? != 0 ]; then exit 1; fi
cd info
makeinfo spec.texinfo
if [ $? != 0 ]; then exit 1; fi
echo ""
echo info spec.info
echo ""
info -f ./spec.info
exit
fi
echo "***Usage: null or filter argument required"
####
Index: BuildPDF
====================================================================
#! /bin/sh
# $Cambridge: exim/exim-doc/doc-scripts/BuildPDF,v 1.1 2004/10/07 15:04:35 ph10 Exp $
echo "PDFing the spec"
ps2pdf spec.ps spec.pdf
echo "PDFing the filter spec"
ps2pdf filter.ps filter.pdf
# End
Index: DoConts
====================================================================
#! /usr/bin/perl -w
# $Cambridge: exim/exim-doc/doc-scripts/DoConts,v 1.1 2004/10/07 15:04:35 ph10 Exp $
$style = (@ARGV > 0)? $ARGV[0] : "a4ps";
open(IN, "z-rawindex") || die "Can't open z-rawindex\n";
open(OUT, ">z-contents") || die "Can't open z-contents\n";
print OUT <<'EOF';
.if ~~sys.fancy
.linelength ~~sys.linelength + 0.2in
.pagedepth ~~sys.pagedepth - 0.2in
.linedepth 12.24
.fi
.include "markup.sg"
.set chapter -1
.set p 0
.format p roman
.tabset 2em 2em
.
.foot
.set p ~~sys.pagenumber
$c [~~p]
.endfoot
.
.chapter Contents
.disable filling
.justify left
EOF
while(<IN>)
{
if (/\$e/)
{
s/\$e\s*$//; # "see also" lines have no line number
s/--\s*\d+$//; # remove "extra" number for index page
s/\n$//; # trailing newline
if (!/^\$/)
{
print OUT ".blank\n";
print OUT ".if ~~sys.leftonpage < 2*~~sys.linedepth\n";
print OUT ".newpage\n";
print OUT ".fi\n";
print OUT "\$shead\{$_\}\n";
print OUT ".blank\n";
}
else
{
print OUT "$_\n";
}
}
}
close(IN);
close(OUT);
system("sgcal z-contents -to zc-gcode -style $style -index /dev/null");
if ($style eq "a4ps")
{
system("sgtops zc-gcode -to zc-ps");
print "PostScript in zc-ps\n";
}
else
{
system("mv -f zc-gcode zc-txt");
print "Text in zc-txt\n";
}
# End
Index: DoIndex
====================================================================
#! /usr/bin/perl -w
# $Cambridge: exim/exim-doc/doc-scripts/DoIndex,v 1.1 2004/10/07 15:04:35 ph10 Exp $
# Script for producing the Index for the Exim manual from the output of the
# SGCAL run. This is copied from the script for the Exim book.
##############################################################################
# Patterns for matching things to be removed from the sort keys
# This was copied from the Exim book processor, but we have now found a
# better way of doing this. Leave the code until I am quite sure...
# $pat[0] = qr/ \(\\\*see also\*\\[^)]+\)/;
# $pat[1] = qr/(?<!@)\/\//; # //
# $pat[2] = qr/(?<!@)\/\\/; # /\
# $pat[3] = qr/(?<!@)\\\//; # \/
# $pat[4] = qr/(?<!@) \\ # non-@ \, followed by one of
# (?:
# [\.\/] | # dot or slash
# !- | # !-
# !\+ | # !+
# !\. | # !.
# "\+ | # "+
# \([.\/]? | # ( and optional . or slash
# [[\$\\%?!-"] | # [ $ \ % ! " or -
# \*{1,2} | # * or **
# \^{1,2}\/? # ^ or ^^ and optional slash
# )/x;
# $pat[5] = qr/(?: []\$\\%)?!"] | # ] $ \ % ) ? " or ! )
# \*{1,2} | # * or ** ) optional
# \^{1,2})? # ^ or ^^ )
# \\/x; # then \
# $pat[6] = qr/(?<!@)::/;
# $pat[7] = qr/\sR[FS]\b/;
# $pat[8] = qr/``/;
# $pat[9] = qr/''/;
# $pat[10] = qr/`/;
# $pat[11] = qr/'/;
# $pat[12] = qr/,/;
# $pat[13] = qr/\(e?s\)/;
# Other patterns
# $keysplit = qr/^(.*?)(\|\|.*?)?\s(R[AZ])?\s?(\d+)$/;
$keysplit = qr/^(.*?)(\@\|\@\|.*?)?\s(R[AZ])?\s?(\d+)$/;
# The sort function
sub cf {
my($x,$y) = ($a,$b);
############old#############
#foreach $pattern (@pat) # Remove strings by pattern
# {
# $x =~ s/$pattern//g;
# $y =~ s/$pattern//g;
# }
##########################
# Turn || into @|@|
$x =~ s/\|\|/@|@|/g;
$y =~ s/\|\|/@|@|/g;
# Remove all special characters, except those preceded by @
$x =~ s/(?<!\@)[^\w\@\s]//g;
$y =~ s/(?<!\@)[^\w\@\s]//g;
# Remove the escaping @s
#$x =~ s/\@(.)/$1/g;
#$y =~ s/\@(.)/$1/g;
################old ########################
#$x =~ s/:(\w+):/$1/g; # :fail: etc => fail
#$y =~ s/:(\w+):/$1/g;
#$x =~ s/^\@[^a-z]+/\@/i; # Make keys starting with @
#$y =~ s/^\@[^a-z]+/\@/i; # sort on @ followed by the first letter
##############################################3
$x =~ s/\@_/\x7f/g; # Make underscore sort late (option names)
$y =~ s/\@_/\x7f/g;
# Split up to sort on individual parts
my($xp,$xs,$xr,$xn) = $x =~ /$keysplit/;
my($yp,$ys,$yr,$yn) = $y =~ /$keysplit/;
$xr = "" if !defined $xr;
$yr = "" if !defined $yr;
$xs = "" if !defined $xs;
$ys = "" if !defined $ys;
if ($show_keys)
{
print "a=$a\n x=$x\n xp=$xp\n xs=$xs\n xr=$xr\n xn=$xn\n";
print "b=$b\n y=$y\n yp=$yp\n ys=$ys\n yr=$yr\n yn=$yn\n";
}
my ($c) = "\L$xp" cmp "\L$yp"; # Caseless, primary text only
$c = $xp cmp $yp if $c == 0; # Caseful, primary text only
$c = "\L$xs" cmp "\L$ys" if $c == 0; # Caseless, secondary text only
$c = $xs cmp $ys if $c == 0; # Caseful, secondary text only
$c = $xn <=> $yn if $c == 0; # Compare the numbers
$c = $xr cmp $yr if $c == 0; # Sort RA before RZ
return $c;
}
##############################################################################
# Function for getting the next line from the @lines vector, using the global
# index $1. If the next pair of lines specifies a range of pages, combine them.
# That's why $linenumber has to be global - so we can increment it. If there's
# a range error, return "".
sub getnextentry {
my($line) = $lines[$linenumber];
my($aa,$zz,$tline,$nextline,$tnextline);
if ($line =~ / RA (\d+)/)
{
$aa = $1;
$nextline = $lines[++$linenumber];
if ($nextline =~ / RZ (\d+)/)
{
$zz = $1;
}
else
{
print STDERR "** Bad range data (1)\n";
print STDERR " $line\n";
print STDERR " $nextline\n";
return "";
}
$tline = $line;
$tnextline = $nextline;
$tline =~ s/ RA \d+//;
$tnextline =~ s/ RZ \d+//;
if ($tline ne $tnextline)
{
print STDERR "** Bad range data (2)\n";
print STDERR " $line\n";
print STDERR " $nextline\n";
return "";
}
$line = ($aa eq $zz)? "$tline $aa" : "$tline $aa--$zz";
}
elsif ($line =~ / RZ (\d+)/)
{
print STDERR "** Bad range data (RZ without RA)\n";
print STDERR " $line\n";
return "";
}
return $line
}
##############################################################################
# Function for outputting a line, checking for the current primary
# and indenting a bit for secondaries. We also need a newpar
# before each item, because the main indent is set to a largish indent
# for long reference lists, but the parindent is set to counter this.
# This is where we handle the break between letters. We know that any non-
# alphamerics at the start of lines are markup, except for @. A reference
# value of 99999 is for the "see also" lines. Suppress it.
sub outline {
my($text,$ref) = ($_[0],$_[1]);
my ($letter) = $text =~ /^[^A-Za-z0-9\@]*(.)/;
return if $text =~ /^\s*$/;
if ($ref eq "99999") # dummy for see also
{
$ref = ""
}
else
{
$ref = "#$ref"; # prepend space
}
if ($letter =~ /\d/) { $letter = "0"; } else { $letter = "\U$letter"; }
print OUT ".newpar\n";
if ($letter ne $currentletter && $letter ge "A")
{
print OUT ".newletter\n";
$currentletter = $letter;
}
$text =~ s/\@'/\$'/g; # Turns @' into $' so that it prints a non-curly quote
if ($text =~ /^(.+)\|\|(.*)$/)
{
my($primary,$secondary) = ($1,$2);
if ($primary ne $lastprimary)
{
print OUT ".primary $primary\n";
$lastprimary = $primary;
}
$primary =~ s/"/""/g;
$secondary =~ s/"/""/g;
my($contprim) = $primary;
$contprim =~ s/ \(\\\*see also\*\\[^)]+\)//;
print OUT ".secondary \"$primary\" \"$secondary$ref\" \"$contprim\"\n";
}
# Not a two-part item; insert @ if the first char is a dot
else
{
print OUT "@" if $text =~ /^\./;
print OUT "$text$ref\n";
$lastprimary = $text;
}
}
##############################################################################
# The main script
$save_sorted = 0;
$test_index = 0;
$show_keys = 0;
while (@ARGV > 0)
{
my($arg) = shift @ARGV;
if ($arg eq "-k") { $show_keys = 1; }
elsif ($arg eq "-s") { $save_sorted = 1; }
elsif ($arg eq "-t") { $test_index = $save_sorted = 1; }
else { die "Unknown option $arg\n"; }
}
if ($test_index)
{
open(IN, "z-testindex") || die "Can't open z-testindex\n";
}
else
{
open(IN, "z-rawindex") || die "Can't open z-rawindex\n";
}
open(OUT, ">z-index") || die "Can't open z-index\n";
# Extract index lines ($e lines are contents). Until we hit the first
# $e line, we are dealing with "see also" index lines, for which we want
# to turn the line number into 99999.
$#lines = -1;
$prestuff = 1;
while (<IN>)
{
s/\n$//;
if (/\$e/)
{
$prestuff = 0;
}
else
{
s/(\D)$/$1 99999/ if $prestuff; # No number in "see also"
push(@lines, $_);
}
$index_pagenumber = $1 if /^Index\$e(\d+)/;
}
close(IN);
# Sort, ignoring markup
print STDERR "Sorting ...\n";
@lines = sort cf @lines;
# Keep a copy of the sorted data, for reference
if ($save_sorted)
{
open(X, ">z-indexsorted") || die "Can't open z-indexsorted\n";
foreach $line (@lines)
{
print X "$line\n";
}
close(X);
}
# Heading for the index file
print OUT <<"EOF";
.library "a4ps"
.linelength ~~sys.linelength + 16.0
.include "markup.sg"
.indent 3em
.parspace 0
.parindent -3em
.justify left
.
.foot
\$c [~~sys.pagenumber]
.endfoot
.
.cancelflag #
.flag # "\$S*1"
.set INDEX true
.
.macro primary "text"
.if ~~sys.leftonpage < 2ld
.newcolumn
.fi
~~1
.newpar
.endm
.
.macro secondary "prim" "sec" "contprim"
.if ~~sys.leftonpage < 1ld
.newcolumn
.newpar
~~3 \$it\{(continued)\}
.newpar
.fi
##~~2
.endm
.
.macro newletter
.if ~~sys.leftonpage < 4ld
.newcolumn
.else
.space 1ld
.fi
.newpar
.endm
.
.set chapter -1
.page $index_pagenumber
.chapter Index
.columns 2
.newpar
.
EOF
# Process the lines and output the result.
# Note that $linenumber is global, and is changed by getnextentry() for
# pairs of lines that represent ranges.
$lastprimary = "";
$lastref = "";
$currenttext = $currentref = "";
$currentletter = "";
$badrange = 0;
print STDERR "Processing ...\n";
for ($linenumber = 0; $linenumber < @lines; $linenumber++)
{
$line = &getnextentry();
if ($line eq "") # Bad range data - but carry on to get all of it
{
$badrange = 1;
next;
}
# Split off the text and reference
($text,$ref) = $line =~ /^(.*)\s+([\d-]+)$/;
# If same as current text, just add the new reference, unless its a duplicate
if ($text eq $currenttext)
{
if ($ref ne $lastref)
{
$currentref .= ", $ref";
$lastref = $ref;
}
next;
}
# Not the same as the current text. Output the current text, then
# set up a new current.
&outline($currenttext, $currentref);
$currenttext = $text;
$currentref = $lastref = $ref;
}
# Output the final line and close the file
&outline($currenttext, $currentref);
close(OUT);
die "** Aborted\n" if $badrange;
# Format the index
system("sgcal z-index -to zi-gcode -index /dev/null");
system("sgtops zi-gcode -to zi-ps");
print "PostScript in zi-ps\n";
# End
Index: JoinPS
====================================================================
#!/usr/bin/perl
# $Cambridge: exim/exim-doc/doc-scripts/JoinPS,v 1.1 2004/10/07 15:04:35 ph10 Exp $
# Make the basic PostScript file for the Exim spec from the gcode file, then
# join it together with the contents and the index, to make a single
# PostScript file, suitable for double-sided printing.
sub blank_page {
my($title) = shift @_;
printf(OUT "%%%%Page: %s %d\nxpage\n\n", $title, $pagenumber++);
}
@roman = ("i", "ii", "iii", "iv", "v", "vi", "vii", "viii", "ix", "x",
"xi", "xii", "xiii", "xiv", "xv", "xvi", "xvii", "xviii", "xix");
$pagenumber = 1;
system("sgtops z-gcode -to z-ps") && die "sgtops run failed\n";
open(SPEC, "z-ps") || die "Can't open z-ps\n";
open(CONTS, "zc-ps") || die "Can't open zc-ps\n";
open(INDEX, "zi-ps") || die "Can't open zi-ps\n";
open(OUT, ">spec.ps") || die "Can't open spec.ps\n";
# Copy spec headings etc.
while (<SPEC>)
{
last if (/^%%Page:/) ;
print OUT;
}
# Copy the first two pages - the title page, and its blank verso
for ($i = 1; $i < 3; $i++)
{
printf(OUT "%%%%Page: title%s %d\n", ($i == 1)? "" : "-verso", $pagenumber++);
while (<SPEC>)
{
last if (/^%%Page:/) ;
print OUT;
}
}
# Skip the contents heading
while (<CONTS>)
{
last if (/^%%Page:/) ;
}
# Output the contents pages - fudge the roman numerals as we know there
# won't be a vast number of them.
$subpagenumber = 0;
while (!eof CONTS)
{
printf(OUT "%%%%Page: %s %d\n", $roman[$subpagenumber++], $pagenumber++);
while (<CONTS>)
{
next if (/^%%Pages:/);
next if (/^%%Trailer/);
last if (/^%%Page:/) ;
print OUT;
}
}
printf(OUT "\n");
# If contents was an odd number of pages, insert a blank page
&blank_page("contents-pad") if ($pagenumber & 1) == 0;
# Copy the rest of the main file
$subpagenumber = 1;
while (!eof SPEC)
{
printf(OUT "%%%%Page: %d %d\n", $subpagenumber++, $pagenumber++);
while (<SPEC>)
{
next if (/^%%Pages:/);
next if (/^%%Trailer/);
last if (/^%%Page:/) ;
print OUT;
}
}
printf(OUT "\n");
# If contents was an odd number of pages, insert a blank page
&blank_page("body-pad") if ($pagenumber & 1) == 0;
# Skip the index heading
while (<INDEX>)
{
last if (/^%%Page:/) ;
}
# Copy the index pages
$subpagenumber = 1;
while (!eof INDEX)
{
printf(OUT "%%%%Page: I%d %d\n", $subpagenumber++, $pagenumber++);
while (<INDEX>)
{
next if (/^%%Pages:/);
next if (/^%%Trailer/);
last if (/^%%Page:/) ;
print OUT;
}
}
# Add final comments
printf(OUT "%%%%Trailer\n");
printf(OUT "%%%%Pages: %d\n", $pagenumber-1);
# That's it
close(SPEC);
close(CONTS);
close(INDEX);
close(OUT);
# End
Index: Makefile
====================================================================
# $Cambridge: exim/exim-doc/doc-scripts/Makefile,v 1.1 2004/10/07 15:04:35 ph10 Exp $
# Makefile for Exim documentation
ps:; sgcal-fr spec.src -v -to z-gcode -index z-rawindex
sgtops z-gcode -to z-ps
txt:; g2man
sgcal-fr spec.src -style online -v -to z-txt -index z-rawindex
contents:; @DoConts
index:; @DoIndex
# The file z-rawindex is included by the filter source to create a TOC.
# First empty it, then do a dummy format to create it, then do a second
# pass. This works because the TOC occupies no more than the rest of the
# first page.
filterps:; /bin/rm -rf z-rawindex
touch z-rawindex
sgcal-fr filter.src -v -to z-gcode -index z-rawindex
sgcal-fr filter.src -v -to z-gcode -index /dev/null
sgtops z-gcode -to filter.ps
filtertxt:; /bin/rm -rf z-rawindex
touch z-rawindex
sgcal-fr filter.src -style online -v -to filter.txt -index z-rawindex
sgcal-fr filter.src -style online -v -to filter.txt -index /dev/null
clean:; /bin/rm -f z*
Index: f2h
====================================================================
#!/usr/bin/perl
# $Cambridge: exim/exim-doc/doc-scripts/f2h,v 1.1 2004/10/07 15:04:35 ph10 Exp $
# Script to turn the Exim FAQ into HTML.
use integer;
# Function to do text conversions that apply to both displays and non displays
sub process_both {
my($s) = $_[0];
$s =~ s/</</g; # Deal with < and >
$s =~ s/>/>/g;
return $s;
}
# Function to do text conversions to display paragraphs
sub process_display {
my($s) = $_[0];
$s =~ s/^==>/ /;
my($indent) = $s =~ /^(\s+)/;
my($remove) = " " x (length($indent) - 3);
$s =~ s/^$remove//mg;
$s = &process_both($s);
return $s;
}
# Function to do text conversions to paragraphs not in displays.
sub process_non_display {
my($s) = &process_both($_[0]);
$s =~ s/@\\/@@backslash@@/g; # @\ temporarily hidden
$s =~ s/\\#/ /g; # \# is a hard space
$s =~ s/\\\*\*([^*]*)\*\*\\/<b>$1<\/b>/g; # \**...**\ => bold
$s =~ s/\\\*([^*]*)\*\\/<i>$1<\/i>/g; # \*.....*\ => italic
$s =~ s/\\"([^"]*)"\\/<tt>$1<\/tt>/g; # \"....."\ => fixed pitch
$s =~ s/\\\$([^\$]*)\$\\/<i>\$$1<\/i>/g; # \$.....$\ => $italic
$s =~ s/\\\\([^\\]*)\\\\/<small>$1<\/small>/g; # \\.....\\ => small
$s =~ s/\\\(([^)]*)\)\\/<i>$1<\/i>/g; # \(.....)\ => italic
$s =~ s/\\-([^\\]*)-\\/<b>-$1<\/b>/g; # \-.....-\ => -bold
$s =~ s/\\\[([^]]*)\]\\/&\#60;<i>$1<\/i>&\#62;/gx; # \[.....]\ => <italic>
$s =~ s/\\\?(.*?)\?\\/<a href="$1">$1<\/a>/g; # \?.....?\ => URL
$s =~ s/\\\^\^([^^]*)\^\^\\/<i>$1<\/i>/g; # \^^...^^\ => italic
$s =~ s/\\\^([^^]*)\^\\/<i>$1<\/i>/g; # \^.....^\ => italic
$s =~ s/\\%([^%]*)%\\/<b>$1<\/b>/g; # \%.....%\ => bold
$s =~ s/\\\/([^\/]*)\/\\/<i>$1<\/i>/g; # \/...../\ => italic
$s =~ s/\\([^\\]+)\\/<tt>$1<\/tt>/g; # \.......\ => fixed pitch
$s =~ s"//([^/\"]*)//"<i>$1</i>"g; # //.....// => italic
$s =~ s/::([^:]*)::/<i>$1:<\/i>/g; # ::.....:: => italic:
$s =~ s/``(.*?)''/“$1”/g; # ``.....'' => quoted text
$s =~ s/\s*\[\[br\]\]\s*/<br>/g; # [[br]] => <br>
$s =~ s/@@backslash@@/\\/g; # Put back single backslash
$s =~ s/^(\s*\(\d\)\s)/$1 /; # Extra space after (1), etc.
# Cross references within paragraphs
$s =~ s/Q(\d{4})(?!:)/<a href="$xref{$1}">$&<\/a>/xg;
# References to configuration samples
$s =~ s/\b([CFLS]\d\d\d)\b/<a href="$1.txt">$1<\/a>/g;
# Remove white space preceding a newline in the middle of paragraphs,
# to keep the file smaller (and for human reading when debugging).
$s =~ s/^\s+//mg;
return $s;
}
# Main program
# We want to read the file paragraph by paragraph; Perl only does this if the
# separating lines are truly blank. Having been caught by lines containing
# whitespace before, do a detrailing pass first.
open(IN, "$ARGV[0]") || die "can't open $ARGV[0] (preliminary)\n";
open(OUT, ">$ARGV[0]-$$") || die "can't open $ARGV[0]-$$\n";
while (<IN>)
{
s/[ \t]+$//;
print OUT;
}
close(IN);
close(OUT);
rename("$ARGV[0]-$$", "$ARGV[0]") ||
die "can't rename $ARGV[0]-$$ as $ARGV[0]\n";
# The second argument is the name of a directory into which to put multiple
# HTML files. We start off with FAQ.html.
$hdir = $ARGV[1];
open(OUT, ">$hdir/FAQ.html") || die "can't open $hdir/FAQ.html\n";
# Initial output
print OUT <<End ;
<html>
<head>
<title>The Exim FAQ</title>
</head>
<body bgcolor="#F8F8F8" text="#00005A" link="#0066FF" alink="#0066FF" vlink="#000099">
<h1>The Exim FAQ</h1>
End
$/ = "";
# First pass to read the titles and questions and create the table of
# contents. We save it up in a vector so that it can be written after the
# introductory paragraphs.
open(IN, "$ARGV[0]") || die "can't open $ARGV[0] (first time)\n";
$toc = 0;
$sec = -1;
$inul = 0;
while ($_ = <IN>)
{
$count = s/\n/\n/g - 1; # Number of lines in paragraph
if ($count == 1 && /^\d+\./) # Look for headings
{
chomp;
push @toc, "</ul>" if $inul;
$inul = 0;
push @toc, "<br>\n\n" if $sec++ >= 0;
push @toc, "<a name=\"TOC$toc\" href=\"FAQ_$sec.html\">$_</a>\n";
$toc++;
($number,$title) = /^(\d+)\.\s+(.*)$/;
if ($title ne "UUCP" && $title ne "IRIX" && $title ne "BSDI" &&
$title ne "HP-UX")
{
($initial,$rest) = $title =~ /^(.)(.*)$/;
$title = "$initial\L$rest";
$title =~ s/isdn/ISDN/;
$title =~ s/\btls\b/TLS/;
$title =~ s/\bssl\b/SSL/;
$title =~ s/ os x/ OS X/;
}
push @seclist, "<a href=\"FAQ_$sec.html\">$number. $title</a>";
next;
}
if (/^(Q\d{4})/) # Q initial paragraph
{
if (!$inul)
{
push @toc, "<ul>\n";
$inul = 1;
}
$num = $1;
$rest = $';
$xref{substr($num,1)} = "FAQ_$sec.html#TOC$toc";
$rest =~ s/^: /: /;
$rest = &process_non_display($rest);
push @toc, "<li><a name=\"TOC$toc\" href=\"FAQ_$sec.html#TOC$toc\">$num</a>$rest<br><br></li>\n";
$toc++;
next;
}
}
push @toc, "</ul>\n" if $inul;
close(IN);
# This is the main processing pass. We have to detect the different kinds of
# "paragraph" and do appropriate things.
open(IN, "$ARGV[0]") || die "can't open $ARGV[0] (second time)\n";
# Skip the title line
$_ = <IN>;
# Handle the rest of the file
$toc = 0;
$maxsec = $sec;
$sec = -1;
while ($_ = <IN>)
{
$count = s/\n/\n/g - 1; # Number of lines in paragraph
chomp; # Trailing newlines
if (/^The FAQ is divided into/)
{
my($count) = scalar(@seclist);
my($cols) = ($count + 1)/2;
print OUT "<hr><a name=\"TOC\"><h1>Index</h1></a>\n";
print OUT "<p>A <i>Keyword-in-context</i> <a href=\"FAQ-KWIC_A.html\">index</a> " .
"to the questions is available. This is usually the " .
"quickest way to find information in the FAQ.</p>\n";
print OUT "<h1>Contents</h1>\n";
print OUT "<p>The FAQ is divided into the following sections:<br><br></p>\n";
print OUT "<table>\n";
for ($i = 0; $i < $cols; $i++)
{
print OUT "<tr>\n";
print OUT " <td>", " " x 4, "</td>\n";
print OUT " <td> $seclist[$i]</td>\n";
print OUT " <td>", " " x8, "$seclist[$cols+$i]</td>\n"
if $cols+$i < $count;
print OUT "</tr>\n";
}
print OUT "</table><br><p>\n<hr><br>\n";
print OUT "<h1>List of questions</h1>\n";
$_ = <IN>; # Skip section list
next;
}
if ($count == 1 && /^\d+\./) # Look for headings
{
if (@toc != 0) # TOC when hit first heading
{
while (@toc != 0) { print OUT shift @toc; }
}
# Output links at the bottom of this page
print OUT "<hr><br>\n";
print OUT "<a href=\"FAQ.html#TOC\">Contents</a> \n";
if ($sec > 0)
{
printf OUT ("<a href=\"FAQ_%d.html\">Previous</a> \n", $sec-1);
}
printf OUT ("<a href=\"FAQ_%d.html\">Next</a>\n", $sec+1);
# New section goes in new file
print OUT "</body>\n</html>\n";
close OUT;
$sec++;
open(OUT, ">$hdir/FAQ_$sec.html") ||
die "Can't open $hdir/FAQ_$sec.html\n";
print OUT "<html>\n<head>\n" .
"<title>The Exim FAQ Section $sec</title>\n" .
"</head>\n" .
"<body bgcolor=\"#F8F8F8\" text=\"#00005A\" " .
"link=\"#FF6600\" alink=\"#FF9933\" vlink=\"#990000\">\n";
printf OUT "<h1>The Exim FAQ</h1>\n";
print OUT "<a href=\"FAQ.html#TOC\">Contents</a> \n";
if ($sec > 0)
{
printf OUT ("<a href=\"FAQ_%d.html\">Previous</a> \n", $sec-1);
}
if ($sec < $maxsec)
{
printf OUT ("<a href=\"FAQ_%d.html\">Next</a>\n", $sec+1);
}
print OUT "<hr><br>\n";
print OUT "<h2><a href=\"FAQ.html#TOC$toc\">$_</a></h2>\n";
$toc++;
next;
}
s/^([QA]\d{4}|[CFLS]\d{3}): /$1: /;
if (/^(Q\d{4}:)/) # Q initial paragraph
{
print OUT "<p>\n<a name=\"TOC$toc\" href=\"FAQ.html#TOC$toc\">$1</a>";
$_ = &process_non_display($');
print OUT "$_\n</p>\n";
$toc++;
next;
}
if (/^A\d{4}:/) # A initial paragraph
{
$_ = &process_non_display($_);
s/^(A\d{4}:)/<font color="#00BB00">$1<\/font>/;
print OUT "<p>\n$_\n</p>\n";
next;
}
# If a paragraph begins ==> it is a display which must remain verbatin
# and not be reformatted. The flag gets turned into spaces.
if ($_ =~ /^==>/)
{
$_ = &process_display($_);
chomp;
print OUT "<pre>\n$_</pre>\n";
}
# Non-display paragraph; massage the final line & my sig.
elsif (/^\*\*\* End of Exim FAQ \*\*\*/)
{
}
else
{
$_ = &process_non_display($_);
if (/^Philip Hazel/)
{
s/\n/<br>\n/g;
s/<br>$/<hr><br>/;
}
print OUT "<p>\n$_\n</p>\n";
}
}
close(IN);
print OUT "<hr><br>\n";
print OUT "<a href=\"FAQ.html#TOC\">Contents</a> \n";
printf OUT ("<a href=\"FAQ_%d.html\">Previous</a>\n", $sec-1);
print OUT "</body>\n</html>\n";
close(OUT);
End
Index: f2txt
====================================================================
#!/usr/bin/perl
# $Cambridge: exim/exim-doc/doc-scripts/f2txt,v 1.1 2004/10/07 15:04:35 ph10 Exp $
# Script to turn the Exim FAQ into plain ASCII.
use integer;
# Function to do text conversions to display paragraphs
sub process_display {
my($s) = $_[0];
$s =~ s/^==>/ /;
return $s;
}
# Function to do text conversions to paragraphs not in displays.
sub process_non_display {
my($s) = $_[0];
$s =~ s/@\\/@@backslash@@/g; # @\ temporarily hidden
$s =~ s/\\#/ /g; # \# is a hard space
$s =~ s/\\\*\*([^*]*)\*\*\\/$1/g; # \**...**\ => text
$s =~ s/\\\*([^*]*)\*\\/"$1"/g; # \*.....*\ => "text"
$s =~ s/\\"([^"]*)"\\/"$1"/g; # \"....."\ => "text"
$s =~ s/\\\$([^\$]*)\$\\/\$$1/g; # \$.....$\ => $text
$s =~ s/\\\\([^\\]*)\\\\/$1/g; # \\.....\\ => text
$s =~ s/\\\(([^)]*)\)\\/$1/g; # \(.....)\ => text
$s =~ s/\\-([^-]*)-\\/-$1/g; # \-.....-\ => -text
$s =~ s/\\\[([^]]*)\]\\/<$1>/gx; # \[.....]\ => <text>
$s =~ s/\\\?(.*?)\?\\/$1/g; # \?.....?\ => text
$s =~ s/\\\^\^([^^]*)\^\^\\/$1/g; # \^^...^^\ => text
$s =~ s/\\\^([^^]*)\^\\/$1/g; # \^.....^\ => text
$s =~ s/\\%([^%]*)%\\/"$1"/g; # \%.....%\ => "text"
$s =~ s/\\\/([^\/]*)\/\\/$1/g; # \/...../\ => text
$s =~ s/\\([^\\]+)\\/"$1"/g; # \.......\ => "text"
$s =~ s"//([^/\"]*)//"$1"g; # //.....// => text
$s =~ s/::([^:]*)::/$1:/g; # ::.....:: => text:
$s =~ s/``(.*?)''/"$1"/g; # ``.....'' => "text"
$s =~ s/\s*\[\[br\]\]\s*\n/\n/g; # Remove [[br]]
$s =~ s/@@backslash@@/\\/g; # Put back single backslash
return $s;
}
# Main program
# We want to read the file paragraph by paragraph; Perl only does this if the
# separating lines are truly blank. Having been caught by lines containing
# whitespace before, do a detrailing pass first.
open(IN, "$ARGV[0]") || die "can't open $ARGV[0] (preliminary)\n";
open(OUT, ">$ARGV[0]-$$") || die "can't open $ARGV[0]-$$\n";
while (<IN>)
{
s/[ \t]+$//;
print OUT;
}
close(IN);
close(OUT);
rename("$ARGV[0]-$$", "$ARGV[0]") ||
die "can't rename $ARGV[0]-$$ as $ARGV[0]\n";
# The second argument is the name of the output file.
open(IN, "$ARGV[0]") || die "can't open $ARGV[0] (for real)\n";
open(OUT, ">$ARGV[1]") || die "can't open $ARGV[1]\n";
$/ = "";
while ($_ = <IN>)
{
# Comment lines start with ##
next if /^\#\#/;
# If a paragraph begins ==> it is a display which must remain verbatin
# and not be reformatted. The flag gets turned into spaces.
if ($_ =~ /^==>/)
{
$_ = &process_display($_);
}
# Non-display paragraph
else
{
$_ = &process_non_display($_);
}
print OUT;
}
close(IN);
close(OUT);
End
Index: faqchk
====================================================================
#!/usr/bin/perl -w
# $Cambridge: exim/exim-doc/doc-scripts/faqchk,v 1.1 2004/10/07 15:04:35 ph10 Exp $
# Script to check the FAQ source and make sure I have all the numbers
# in the right order without duplication. Also check the numbers of blank
# lines. It gives up on any error (other than bad blank line counts).
sub oops {
print "\nLine $line: $_[0]\n";
print;
exit 1;
}
sub poops {
print "\nLine $line: $_[0]\n";
print;
$precede_fail = 1;
}
$line = 0;
$section = -1;
$expect_answer = 0;
$precede_fail = 0;
while (<>)
{
$line++;
if (/^\s*$/)
{
$blankcount++;
next;
}
$preceded = $blankcount;
$blankcount = 0;
if (/^(\d+)\./)
{
&poops("$preceded empty lines precede (3 expected)") if $preceded != 3;
&oops(sprint("Answer %02d%02d is missing\n", $section, $question))
if $expect_answer;
$section = $1;
$question = ($section == 20)? 0 : 1;
$expected = 1;
if ($section == 99)
{
$c = 1;
$f = 1;
}
next;
}
if ($section != 99)
{
if (/^Q(\d\d)(\d\d):/)
{
&poops("$preceded empty lines precede ($expected expected)")
if $preceded != $expected;
$expected = 2;
&oops("Answer expected") if $expect_answer;
&oops("Q$1$2 is in the wrong section") if ($1 != $section);
&oops("Q$1$2 is out of order") if $2 != $question;
$expect_answer = 1;
next;
}
if (/^A(\d\d)(\d\d):/)
{
&poops("$preceded empty lines precede (1 expected)") if $preceded != 1;
&oops("Question expected") if !$expect_answer;
&oops("A$1$2 is in the wrong section") if $1 != $section;
&oops("A$1$2 is out of order") if $2 != $question++;
$expect_answer = 0;
next;
}
}
else
{
if (/^C(\d\d\d):/)
{
&oops("C$1 is mixed up in the Fs") if $f != 1;
# Some Cxxx configs are omitted (not translated from Exim 3) so can
# only check increasing number.
&oops("C$1 is out of order") if $1 < $c;
$c = $1;
}
elsif (/^F(\d\d\d):/)
{
&oops("F$1 is out of order") if $1 != $f++;
next;
}
}
}
exit 1 if $precede_fail;
# End
Index: fc2k
====================================================================
#! /usr/bin/perl -w
# $Cambridge: exim/exim-doc/doc-scripts/fc2k,v 1.1 2004/10/07 15:04:35 ph10 Exp $
# Script to read the HTML table of contents for the Exim FAQ and create an
# HTML KWIC index out of it.
########################################################################
# List of words to ignore - kept alphabetically for reference, but they
# don't have to be in order.
$ignore_list = "
a ability able about address addresses addressed affect affected
after against all allow allowed allows already also although always am an and
and/or any anybody anyone anything anywhere are aren't arrange arrive as at
back bad based basically be because been behave behaviour being best between
bob both bug build builds built busy but by
call called calls can can't cannot causes causing central certain code comes
coming command commands complain complaining complains configure configured
conjunction contact contain contains contained correct correctly could
currently customer
day days defined deliver delivers delivered delivery deliveries did do does
doesn't doing don't down during
e-mail e-mails each easy else email emails entirely entries entry especially
etc even ever every example exim exim's experiencing
far few file files find fine fly following for form found from fully
get gets getting given gives giving go goes going got
handle handles handled handling happen happens has have haven't having helpful
him host hosts how however
i i'd i'm i've if in indeed instead into is issue issues isn't it it's its
jim just
keep keeps know knows
like line lines look looked looking lot
machine machines machine's mail mails main make me mean means message messages
might more must my myself
near need neither no nor not now
occur of off often ok on one only or other our out over own
part parts particular per place possibility possible present problem problems
put puts
quite
raised rather really reason rid right round run runs
same say saying see seeing seem seems seen sees set setting she should so some
somehow something sometimes stand state statement still strange such supposed
system systems
take takes than that the their them then there these they things think this
those to try though to/for told too tried tries trying
under until up use uses used using usually
valid value values via
want wanted wanting was way we we've well what what's when where whereabouts
whenever whether which while who whose why will with within without wish won't
wondered work worked working works would wrong
xxx
yet yyy
";
########################################################################
# The regular expression fragment that defines the separator between words
$wordgap = "(?:[]().?,;:\"']|(?><[^>]*>))*(?:\\s+|\$)(?:[[(\"'`]|(?><[^>]*>))*";
########################################################################
# Function to add to a length to accommodate HTML stuff
sub setlen{
my($len, $s) = @_;
$len += length($1) while ($s =~ /(<\/?[a-z]+>)/ig);
$len += 1 while ($s =~ /&#\d+;/g);
return $len;
}
########################################################################
# Function to write out the list of initials with references
sub write_initials {
my($this_initial) = "$_[0]";
print OUT "<p>\n ";
foreach $initial (sort keys %initials)
{
if ($initial eq $this_initial)
{
print OUT " <font size=7 color=\"#FF0A0A\"><b>$initial</b></font> ";
}
else
{
print OUT "<a href=\"FAQ-KWIC_$initial.html\"> $initial</a>";
}
}
print OUT " "x4 . "<a href=\"FAQ.html#TOC\">FAQ Contents</a>\n</p>\n";
}
########################################################################
# The main program. We can pick out the contents lines because they lie
# between <li> and </li> in the file, sometimes on more than one physical
# line.
# Turn the list of ignorable words into a hash for quick lookup. Add the
# empty word to the list.
@words = split /\s+/, $ignore_list;
foreach $word (@words) { $ignore{$word} = 1; }
$ignore{""} = 1;
# Open the file and do the job
open(IN, "html/FAQ.html") || die "Can't open html/FAQ.html\n";
while (<IN>)
{
next unless /^<li>/;
$_ .= <IN> while !/<\/li>$/;
chomp;
s/\n\s*/ /g;
# Extract the operative text into $text, with the beginning in $pre.
my($pre,$text,$post) = /^<li>(.*<\/a>:(?: )*)(.*)<br><br><\/li>$/;
# Now split into words. As well as punctuation, there may be HTML thingies
# between words. Absorb them into the separators.
my(@words) = split /$wordgap/, $text;
# Lower case all the words, and remove those that we don't want.
# Then keep a list of all the used initials.
REMOVE_IGNORE:
for ($i = 0; $i < scalar @words; $i++)
{
my($word) = $words[$i] = "\L$words[$i]\E";
# Remove certain forms of word and those on the ignore list
if (defined $ignore{$word} || # word on ignore list
$word =~ /^-+$/ || # word consists entirely of hyphens
$word =~ /^-[^a-z]/ || # follows leading hyphen with non-letter
$word =~ /^[^a-z-]/ || # starts with a non-letter or hyphen
$word =~ /[@^.]/ # contains @ or ^ or .
)
{
splice(@words, $i, 1);
redo REMOVE_IGNORE if $i < scalar @words;
}
# Otherwise, build up a list of initials
else
{
my($inword) = $word;
$inword =~ s/^-//;
$initial = substr($inword, 0, 1);
$initials{"\U$initial\E"} = 1;
}
}
# Create the lines for the KWIC index, and store them in associative
# arrays, with the keyword as the key. That will get them sorted
# automatically.
while (scalar @words > 0)
{
my($word) = shift @words;
my($pretext, $casedword, $posttext) =
$text =~ /(.*?)(?<![a-z])(\Q$word\E)(?![a-z])(.*)/i;
# Remove a leading hyphen from $word so that it sorts according to
# the leading letter. What is actually output is $casedword, which
# retains the hyphen.
$word =~ s/^-//;
my($prelen) = length $pretext;
my($postlen) = length $posttext;
# We want to chop excessively long entries on either side. We can't set
# a fixed length because of the HTML control data. Call a function to
# add the given length to allow for HTML stuff. This is crude, but it
# does roughtly the right thing.
my($leftlen) = &setlen(70, $pretext);
my($rightlen) = &setlen(70, $posttext);
if ($prelen > $leftlen)
{
my($cutoff) = $leftlen;
$cutoff++
while ($cutoff < $prelen && substr($pretext, -$cutoff, 1) ne " ");
$pretext = "... " . substr($pretext, -$cutoff);
}
if ($postlen > $rightlen)
{
my($cutoff) = $rightlen;
$cutoff++
while ($cutoff < $postlen && substr($posttext, $cutoff, 1) ne " ");
$posttext = substr($posttext, 0, $cutoff) . "...";
}
# If the pre text has a font-ending not preceded by a font beginning
# (i.e. we've chopped the beginning off), we must insert a beginning.
while ($pretext =~ /^(.*?)<\/(small|tt|b|i)>/ && $1 !~ /<$2>/)
{
$pretext = "<$2>" . $pretext;
}
# If the pre text ends in a special font, we have to terminate that,
# and reset it at the start of the post text.
my($poststart) = "";
while ($pretext =~ /<(small|tt|b|i)>(?!.*?<\/\1>)/)
{
$pretext .= "</$1>";
$poststart .= "<$1>";
}
# If the post text changes font but doesn't close it, we must add
# the closure.
while ($posttext =~ /<(small|tt|b|i)>(?!.*?<\/\1>)/)
{
$posttext .= "</$1>";
}
# Remove any unnecessary changes in either of them
$pretext =~ s/<(small|tt|b|i)>\s*<\/\1>//g;
$posttext =~ s/<(small|tt|b|i)>\s*<\/\1>//g;
# Save the texts in associative arrays. Add the question number to
# the end of the word to make the key.
$pre =~ /(Q\d\d\d\d)/;
my($key) = "$word-$1";
$tableft{$key} = $pre . $pretext;
$tabright{$key} = $poststart .
"<font color=\"#FF0A0A\">$casedword</font>" . $posttext;
}
}
close(IN);
# Now write out the files. Each letter in the index goes in a different file
$current_initial = "";
foreach $key (sort keys %tableft)
{
my($initial) = $key =~ /^(.)/;
$initial = "\U$initial\E";
if ($initial ne $current_initial)
{
if ($current_initial ne "")
{
print OUT "</table>\n";
&write_initials($current_initial);
print OUT "</body>\n</html>\n";
close OUT;
}
open (OUT, ">html/FAQ-KWIC_$initial.html") ||
die "Can't open html/FAQ-KWIC_$initial.html\n";
print OUT
"<html>\n" .
"<head>\n" .
"<title>Exim FAQ: KWIC index section $initial</title>\n" .
"</head>\n" .
"<body bgcolor=\"#F8F8F8\" text=\"#00005A\" link=\"#0066FF\" alink=\"#0066FF\" vlink=\"#000099\">\n" .
"<h1>Exim FAQ: Keyword-in-context index</h1>\n";
write_initials($initial);
if ($initial eq "A")
{
print OUT <<End ;
<p>
This <i>Keyword-in-context</i> index for the Exim FAQ is generated
automatically from the FAQ source. Browsers may not display the data very
prettily, but it is hoped that it may provide a useful aid for finding things
in the FAQ.
</p>
End
}
print OUT "<table border>\n";
$current_initial = $initial;
}
print OUT "<tr>\n";
print OUT "<td align=\"right\">$tableft{$key}</td>\n";
print OUT "<td align=\"left\">$tabright{$key}</td>\n";
print OUT "</tr>\n";
}
# Close the final file
if ($current_initial ne "")
{
print OUT "</table>\n";
&write_initials($current_initial);
print OUT "</body>\n</html>\n";
close OUT;
}
# End
Index: g2h
====================================================================
#! /usr/bin/perl -w
# $Cambridge: exim/exim-doc/doc-scripts/g2h,v 1.1 2004/10/07 15:04:35 ph10 Exp $
# This is a script that turns the SGCAL source of Exim's documentation into
# HTML. It can be used for both the filter document and the main Exim
# specification. The syntax is
#
# g2h [-split no|section|chapter] <source file> <title>
#
# Previously, -split section was used for the filter document, and -split
# chapter for the main specification. However, the filter document has gained
# some chapters, so they are both split by chapter now. Only one -split can be
# specified.
#
# A number of assumptions about the style of the input markup are made.
#
# The HTML is written into the directory html/ using the source file base
# name as its base.
# Written by Philip Hazel
# Starting 21-Dec-2001
# Last modified 26-Nov-2003
#############################################################################
##################################################
# Open an output file #
##################################################
sub openout {
open (OUT, ">$_[0]") || die "Can't open $_[0]\n";
# Boilerplate
print OUT "<!DOCTYPE HTML PUBLIC \"-//IETF//DTD HTML//EN\">\n";
print OUT "<html>\n<head>\n<title>$doctitle" .
(($thischapter > 0)? " chapter $thischapter" : "") .
(($thissection > 0)? " section $thissection" : "") .
"</title>\n</head>\n" .
"<body bgcolor=\"#F8F8F8\" text=\"#00005A\" " .
"link=\"#FF6600\" alink=\"#FF9933\" vlink=\"#990000\">\n";
# Forward/backward links when chapter splitting
if ($chapsplit)
{
print OUT "<font size=2>\n";
printf OUT ("<a href=\"${file_base}_%s.html\">Previous</a> \n",
$thischapter - 1) if $thischapter > 1;
printf OUT ("<a href=\"${file_base}_%s.html\">Next</a> \n",
$thischapter + 1) if $thischapter < $maxchapter;
print OUT "<a href=\"${file_base}_toc.html\">Contents</a>\n";
print OUT " " x 6, "($doctitle)\n</font><hr>\n";
}
# Forward/backward links when section splitting
elsif ($sectsplit)
{
print OUT "<font size=2>\n";
printf OUT ("<a href=\"${file_base}_%s.html\">Previous</a> \n",
$thissection - 1) if $thissection > 1;
printf OUT ("<a href=\"${file_base}_%s.html\">Next</a> \n",
$thissection + 1) if $thissection < $maxsection;
print OUT "<a href=\"${file_base}_toc.html\">Contents</a>\n";
print OUT " " x 6, "($doctitle)\n</font><hr>\n";
}
# Save the final component of the current file name (for TOC creation)
$_[0] =~ /^(?:.*)\/([^\/]+)$/;
$current_file = $1;
}
##################################################
# Close an output file #
##################################################
# The first argument is one of:
#
# "CHAP" a chapter is ending
# "SECT" a section is ending
# "" the whole thing is ending
#
# In the first two cases $thischapter and $thissection contain the new chapter
# and section numbers, respectively. In the third case, we can deduce what is
# ending from the flags. The variables contain the current values.
sub closeout {
my($s) = $_[0];
print OUT "<hr>\n" if !$lastwasrule;
&setpar(0);
if ($s eq "CHAP")
{
print OUT "<font size=2>\n";
printf OUT ("<a href=\"${file_base}_%s.html\">Previous</a> ",
$thischapter - 2) if ($thischapter > 2);
print OUT "<a href=\"${file_base}_$thischapter.html\">Next</a> ";
print OUT "<a href=\"${file_base}_toc.html\">Contents</a>\n";
print OUT " " x 6, "($doctitle)\n</font>\n";
}
elsif ($s eq "SECT")
{
print OUT "<font size=2>\n";
printf OUT ("<a href=\"${file_base}_%s.html\">Previous</a> ",
$thissection - 2) if ($thissection > 2);
print OUT "<a href=\"${file_base}_$thissection.html\">Next</a> ";
print OUT "<a href=\"${file_base}_toc.html\">Contents</a>\n";
print OUT " " x 6, "($doctitle)\n</font>\n";
}
else
{
if ($chapsplit)
{
print OUT "<font size=2>\n";
printf OUT ("<a href=\"${file_base}_%s.html\">Previous</a> ",
$thischapter - 1) if ($thischapter > 1);
print OUT "<a href=\"${file_base}_toc.html\">Contents</a>\n";
print OUT " " x 6, "($doctitle)\n</font>\n";
}
elsif ($sectsplit)
{
print OUT "<font size=2>\n";
printf OUT ("<a href=\"${file_base}_%s.html\">Previous</a> ",
$thissection - 1) if ($thissection > 1);
print OUT "<a href=\"${file_base}_toc.html\">Contents</a>\n";
print OUT " " x 6, "($doctitle)\n</font>\n";
}
}
print OUT "</body>\n</html>\n";
close(OUT);
}
##################################################
# Handle an index line #
##################################################
# This function returns an empty string so that it can be called as part
# of an s operator when handling index items within paragraphs. The two
# arguments are:
#
# the text to index, already converted to HTML
# 1 for the concept index, 0 for the options index
sub handle_index {
my($text) = $_[0];
my($hash) = $_[1]? \%cindex : \%oindex;
my ($key,$ref);
# Up the index count, and compute the reference to the file and the
# label within it.
$index_count++;
$ref = $chapsplit?
"${file_base}_$thischapter.html#IX$index_count"
: $sectsplit?
"${file_base}_$thissection.html#IX$index_count"
:
"#IX$index_count";
# Create the index key, which consists of the text with all the HTML
# coding and any leading quotation marks removed. Turn the primary/secondary
# splitting string "||" into ":".
$text =~ s/\|\|/:/g;
$key = "$text";
$key =~ s/<[^>]+>//g;
$key =~ s/&#(\d+);/chr($1)/eg;
$key =~ s/^`+//;
# Turn all spaces in the text into so that they don't ever split.
# However, there may be spaces in the HTML that already exists in the
# text, so we have to avoid changing spaces inside <>.
$text =~ s/ (?=[^<>]*(?:<|$))/ /g;
# If this is the first encounter with this index key, we create a
# straightforward reference.
if (!defined $$hash{$key})
{
$$hash{$key} = "<a href=\"$ref\">$text</a>";
}
# For the second and subsequent encounters, add "[2]" etc. to the
# index text. We find out the number by counting occurrences of "<a"
# in the existing string.
else
{
my($number) = 1;
$number++ while $$hash{$key} =~ /<a/g;
$$hash{$key} .= " <a href=\"$ref\">[$number]</a>";
}
# Place the name in the current output
print OUT "<a name=\"IX$index_count\"></a>\n";
return "";
}
##################################################
# Handle emphasis bars #
##################################################
# Set colour green for text marked with "emphasis bars", keeping
# track in case the matching isn't perfect.
sub setinem {
if ($_[0])
{
return "" if $inem;
$inem = 1;
return "<font color=green>\n";
}
else
{
return "" if !$inem;
$inem = 0;
return "</font>\n";
}
}
##################################################
# Convert marked-up text #
##################################################
# This function converts text from SGCAL markup to HTML markup, with a couple
# of exceptions:
#
# 1. We don't touch $t because that is handled by the .display code.
#
# 2. The text may contain embedded .index, .em, and .nem directives. We
# handle .em and .nem, but leave .index because it must be done during
# paragraph outputting.
#
# In a non-"rm" display, we turn $rm{ into cancelling of <tt>. Otherwise
# it is ignored - in practice it is only used in that special case.
#
# The order in which things are done in this function is highly sensitive!
sub handle_text {
my($s) = $_[0];
my($rmspecial) = $_[1];
# Escape all & characters (they aren't involved in markup) but for the moment
# use &+ instead of &# so that we can handle # characters in the text.
$s =~ s/&/&+038;/g;
# Turn SGCAL literals into HTML literals that don't look like SGCAL
# markup, so won't be touched by what follows. Again, use + instead of #.
$s =~ s/@@/&+064;/g;
$s =~ s/@([^@])/"&+".sprintf("%0.3d",ord($1)).";"/eg;
# Now turn any #s that are markup into spaces, and convert the previously
# created literals to the correct form.
$s =~ s/#/ /g;
$s =~ s/&\+(\d+);/&#$1;/g;
# Some simple markup that doesn't involve argument text.
$s =~ s/\$~//g; # turn $~ into nothing
$s =~ s/__/_/g; # turn __ into _
$s =~ s/--(?=$|\s|\d)/–/mg; # turn -- into endash in text or number range
$s =~ s/\(c\)/©/g; # turn (c) into copyright symbol
# Use double quotes
# $s =~ s/`([^']+)'/``$1''/g;
$s =~ s/`([^']+)'/“$1”/g;
# This is a fudge for some specific usages of $<; can't just do a global
# is it occurs in things like "$<variable name>" as well.
$s =~ s/(\d)\$<-/$1-/g; # turn 0$<- into 0-
$s =~ s/\$<//g; # other $< is ignored
# Turn <<...>> into equivalent SGCAL markup that doesn't involve the use of
# < and >, and then escape the remaining < and > characters in the text.
$s =~ s/<<([^>]*?)>>/<\$it{$1}>/g; # turn <<xxx>> into <$it{xxx}>
$s =~ s/</</g;
$s =~ s/>/>/g;
# Other markup...
$s =~ s/\$sm\{//g; # turn $sm{ into nothing
$s =~ s/\$smc\{//g; # turn $smc{ into nothing
$s =~ s/\$smi\{//g; # turn $smi{ into nothing
$s =~ s/\$tt\{([^\}]*?)\}/<tt>$1<\/tt>/g; # turn $tt{xxx} into <tt>xxx</tt>
$s =~ s/\$it\{([^\}]*?)\}/<em>$1<\/em>/g; # turn $it{xxx} into <em>xxx</em>
$s =~ s/\$bf\{([^\}]*?)\}/<b>$1<\/b>/g; # turn $bf{xxx} into <b>xxx</b>
$s =~ s/\$cb\{([^\}]*?)\}/<tt><b>$1<\/b><\/tt>/g; # turn $cb{xxx} into
# <tt><b>xxx</b></tt>
$s =~ s/\\\\([^\\]*?)\\\\/<font size=-1>$1<\/font>/g; # turn \\xxx\\ into
# small font
$s =~ s/\\\?([^?]*?)\?\\/<a href="$1">$1<\/a>/g; # turn \?URL?\ into URL
$s =~ s/\\\(([^)]*?)\)\\/<i>$1<\/i>/g; # turn \(xxx)\ into <i>xxx</i>
$s =~ s/\\\"([^\"]*?)\"\\/<tt>$1<\/tt>/g; # turn \"xxx"\ into <tt>xxx</tt>
$s =~ s/\\\$([^\$]*?)\$\\/<tt>\$$1<\/tt>/g; # turn \$xxx$\ into <tt>$xxx</tt>
$s =~ s/\\\-([^\\]*?)\-\\/<i>-$1<\/i>/g; # turn \-xxx-\ into -italic
$s =~ s/\\\*\*([^*]*?)\*\*\\/<b>$1<\/b>/g; # turn \**xxx**\ into <b>xxx</b>
$s =~ s/\\\*([^*]*?)\*\\/<i>$1<\/i>/g; # turn \*xxx*\ into italic
$s =~ s/\\%([^*]*?)%\\/<b>$1<\/b>/g; # turn \%xxx%\ into bold
$s =~ s/\\([^\\]*?)\\/<tt>$1<\/tt>/g; # turn \xxx\ into <tt>xxx</tt>
$s =~ s/::([^\$]*?)::/<i>$1:<\/i>/g; # turn ::xxx:: into italic:
$s =~ s/\$\*\$/\*/g; # turn $*$ into *
# Handle $rm{...}
if ($rmspecial)
{
$s =~ s/\$rm\{([^\}]*?)\}/<\/tt>$1<tt>/g; # turn $rm{xxx} into </tt>xxx<tt>
}
else
{
$s =~ s/\$rm\{([^\}]*?)\}/$1/g; # turn $rm{xxx} into xxx
}
# There is one case where the terminating } of an escape sequence is
# in another paragraph - this follows $sm{ - it can be fixed by
# removing any stray } in a paragraph that contains no { chars.
$s =~ s/\}//g if !/\{/;
# Remove any null flags ($$)
$s =~ s/\$\$//g;
# If the paragraph starts with $c\b, remove it.
$s =~ s/^\$c\b//;
# If the paragraph starts with $e\b, indent it slightly.
$s =~ s/^\$e\b/ /;
# Handle .em, and .nem directives that occur within the paragraph
$s =~ s/\.em\s*\n/&setinem(1)/eg;
$s =~ s/\.nem\s*\n/&setinem(0)/eg;
# Explicitly included HTML
$s =~ s/\[\(([^)]+)\)\]/<$1>/g; # turn [(...)] into <...>
# Finally, do the substitutions and return the modified text.
$s =~ s/~~(\w+)/$var_value{$1}/eg;
return $s;
}
##################################################
# Start/end a paragraph #
##################################################
# We want to leave paragraphs unterminated until we know that a horizontal
# rule does not follow, to avoid getting space inserted before the rule,
# which doesn't look good. So we have this function to help control things.
# If the argument is 1 we are starting a new paragraph; if it is 0 we want
# to force the ending of any incomplete paragraph.
sub setpar {
if ($inpar)
{
print OUT "</p>\n";
$inpar = 0;
}
if ($_[0])
{
print OUT "<p>\n";
$inpar = 1;
}
}
##################################################
# Handle a "paragraph" #
##################################################
# Read a paragraph of text, which may contain many lines and may contain
# .index, .em, and .nem directives within it. We may also encounter
# ".if ~~html" within paragraphs. Process those directives,
# convert the markup, and output the rest as an HTML paragraph.
sub handle_paragraph{
my($par) = $_;
my($htmlcond) = 0;
while(<IN>)
{
if (/^\.if\s+~~html\b/)
{
$htmlcond = 1;
$par =~ s/\s+$//; # lose unwanted whitespace and newlines
next;
}
elsif ($htmlcond && /^\.else\b/)
{
while (<IN>) { last if /^\.fi\b/; }
$htmlcond = 0;
next;
}
elsif ($htmlcond && /^\.fi\b/)
{
$htmlcond = 0;
next;
}
last if /^\s*$/ || (/^\./ && !/^\.index\b/ && !/^\.em\b/ && !/^\.nem\b/);
$par .= $_;
}
$par = &handle_text($par, 0);
# We can't handle .index until this point, when we do it just before
# outputting the paragraph.
if ($par !~ /^\s*$/)
{
&setpar(1);
$par =~ s/\.index\s+([^\n]+)\n/&handle_index($1, 1)/eg;
print OUT "$par";
}
}
##################################################
# Handle a non-paragraph directive #
##################################################
# The directives .index, .em, and .nem can also appear within paragraphs,
# and are then handled within the handle_paragraph() code.
sub handle_directive{
my($new_lastwasitem) = 0;
$lastwasrule = 0;
if (/^\.r?set\b/ || /^\.(?:\s|$)/) {} # ignore .(r)set and comments
elsif (/^\.justify\b/) {} # and .justify
elsif (/^\.newline\b/) { print OUT "<br>\n"; }
elsif (/^\.blank\b/ || /^\.space\b/) { print OUT "<br>\n"; }
elsif (/^\.rule\b/) { &setpar(0); print OUT "<hr>\n"; $lastwasrule = 1; }
elsif (/^\.index\s+(.*)/) { &handle_index(&handle_text($1), 1); }
# Emphasis is handled by colour
elsif (/^\.em\b/)
{
&setpar(0);
print OUT "<font color=green>" if ! $inem;
$inem = 1;
}
elsif (/^\.nem\b/)
{
&setpar(0);
print OUT "</font>" if $inem;
$inem = 0;
}
# Ignore tab setting stuff - we use tables instead.
elsif (/^\.tabs(?:et)?\b/) {}
# .tempindent is used only to align some of the expansion stuff nicely;
# just ignore it. It is used in conjunction with .push/.pop.
elsif (/^\.(tempindent|push|pop)\b/) {}
# There are some instances of .if ~~sys.fancy in the source. Some of those
# that are not inside displays are two-part things, in which case we just keep
# the non-fancy part. For diagrams, however, they are in three parts:
#
# .if ~~sys.fancy
# <aspic drawing stuff for PostScript and PDF>
# .elif !~~html
# <ascii art for txt and Texinfo>
# .else
# <HTML instructions for including a gif>
# .fi
#
# In this case, we skip to the third part.
elsif (/^\.if\s+~~sys\.fancy/ || /^\.else\b/)
{
while (<IN>)
{ last if /^\.else\b/ || /^\.elif\s+!\s*~~html/ || /^\.fi\b/; }
if (/^\.elif\b/)
{
while (<IN>) { last if /^\.else\b/ || /^\.fi\b/; }
}
}
# Similarly, for .if !~~sys.fancy, take the non-fancy part.
elsif (/^\.if\s+!\s*~~sys.fancy/) {}
# There are some explicit tests for ~~html for direct HTML inclusions
elsif (/^\.if\s+~~html\b/) {}
# There are occasional requirements to do things differently for Texinfo/HTML
# and PS/txt versions. The latter are produced by SGCAL, so that's what the
# flag is called.
elsif (/\.if\s+~~sgcal/)
{
while (<IN>) { last if /\.else\b/ || /\.fi\b/; }
}
# Also there is a texinfo flag
elsif (/^\.if\s+~~texinfo\b/)
{
while (<IN>)
{ last if /^\.else\b/ || /^\.elif\s+!\s*~~html/ || /^\.fi\b/; }
}
# Ignore any other .if, .else, or .fi directives
elsif (/^\.if\b/ || /^\.fi\b/ || /^\.else\b/) {}
# Ignore .indent
elsif (/^\.indent\b/) {}
# Various flavours of numberpars map to corresponding list types.
elsif (/^\.numberpars\b/)
{
$rest = $';
&setpar(0);
if ($rest =~ /(?:\$\.|\" \")/)
{
unshift @endlist, "ul";
unshift @listtype, "";
print OUT "<ul>\n<li>";
}
else
{
$nptype = ($rest =~ /roman/)? "a" : "1";
unshift @endlist, "ol";
unshift @listtype, " TYPE=\"$nptype\"";
print OUT "<ol>\n<li$listtype[0]>";
}
}
elsif (/^\.nextp\b/)
{
&setpar(0);
print OUT "</li>\n<li$listtype[0]>";
}
elsif (/^\.endp\b/)
{
&setpar(0);
print OUT "</li>\n</$endlist[0]>\n";
shift @listtype;
shift @endlist;
}
# .display asis can use <pre> which uses a typewriter font.
# Otherwise, we have to do our own line breaking. Turn tabbed lines
# into an HTML table. There will always be a .tabs line first.
elsif (/^\.display\b/)
{
my($intable) = 0;
my($asis) = /asis/;
my($rm) = /rm/;
my($eol,$indent);
# For non asis displays, start a paragraph, and set up to put an
# explicit break after every line.
if (!$asis)
{
&setpar(1);
$eol = "<br>";
$indent = "<tt> </tt>";
}
# For asis displays, use <pre> and no explicit breaks
else
{
print OUT "<pre>\n";
$eol = "";
$indent = " ";
}
# Now read through until we hit .endd (or EOF, but that shouldn't happen)
# and process the lines in the display.
while (<IN>)
{
last if /^\.endd\b/;
# The presence of .tabs[et] starts a table
if (/^\.tabs/)
{
$intable = 1;
print OUT "<table cellspacing=0 cellpadding=0>\n";
}
# Some displays have an indent setting - ignore
elsif (/^\.indent\b/) {}
# Some displays have .blank inside them
elsif (/^\.blank\b/)
{
print OUT "<br>\n";
}
# Some displays have emphasis inside them
elsif (/^\.em\b/)
{
print OUT "<font color=green>" if ! $inem;
$inem = 1;
}
elsif (/^\.nem\b/)
{
print OUT "</font>" if $inem;
$inem = 0;
}
# There are occasional instances of .if [!]~~sys.fancy inside displays.
# In both cases we want the non-fancy alternative. (The only thing that
# matters in practice is noticing .tabs[et] actually.) Assume the syntax
# is valid.
elsif (/^\.if\s+~~sys.fancy/ || /^\.else\b/)
{
while (<IN>)
{
last if /^\.fi\b/ || /^\.else/;
}
}
elsif (/^\.if\s+!\s*~~sys.fancy/) {}
elsif (/^\.fi\b/) {}
# Ignore .newline and .linelength
elsif (/^\.newline\b/ || /^\.linelength\b/) {}
# Ignore comments
elsif (/^\.(\s|$)/) {}
# There shouldn't be any other directives inside displays
elsif (/^\./)
{
print "*** Ignored directive inside .display: $_";
}
# Handle a data line within a display. If it's an asis display, the only
# conversion is to escape the HTML characters. Otherwise, process the
# SGCAL markup.
else
{
chomp;
if ($asis)
{
s/&/&/g;
s/</</g;
s/>/>/g;
}
else
{
$_ = &handle_text($_, !$rm);
$_ = "<tt>$_</tt>" if !$rm && $_ ne "";
}
# In a table, break fields at $t. For non-rm we must break the
# <tt> group as well.
if ($intable)
{
if ($rm)
{
s/\s*\$t\s*/ <\/td><td>/g;
}
else
{
s/\s*\$t\s*/ <\/tt><\/td><td><tt>/g;
}
s/<tt><\/tt>//g;
print OUT "<tr><td> $_</td></tr>\n";
}
# Otherwise, output straight, with <br> for non asis displays
else
{
s/<tt><\/tt>//g;
print OUT "$indent$_$eol\n";
}
}
} # Loop for display contents
# Finish off the table and the <pre> - leave a paragraph open
print OUT "</table>\n" if $intable;
print OUT "</pre>\n" if $asis;
}
# Handle configuration option definitions
elsif (/^\.startconf\b/) {}
elsif (/^\.conf\b/)
{
my($option, $type, $default) =
/^\.conf\s+(\S+)\s+("(?:[^"]|"")+"|\S+)\s+("(?:[^"]|"")+"|.*)/;
$option =~ s/\@_/_/g; # Underscore will be quoted in option name
# If $type ends with $**$, add ",expanded" as there doesn't seem to be
# a dagger character generally available.
$type =~ s/^"([^"]+)"/$1/;
$type =~ s/\$\*\*\$/, expanded/;
# Default may be quoted, and it may also have quotes that are required,
# if it is a string.
$default =~ s/^"(.*)"$/$1/;
$default =~ s/""/"/g;
$default = &handle_text($default, 0);
print OUT "<hr>";
&setpar(0);
&handle_index($option, 0);
print OUT "<h3>$option</h3>\n" .
"<i>Type:</i> $type<br><i>Default:</i> $default<br>\n";
}
elsif (/^\.endconf\b/)
{
print OUT "<hr><br>\n";
}
# Handle "items" - used for expansion items and the like. We force the
# item text into bold, and put a rule between items.
elsif (/^\.startitems\b/) {}
elsif (/^\.item\s+(.*)/)
{
my($arg) = $1;
chomp($arg);
$arg =~ s/^"(.*)"$/$1/;
$arg = &handle_text($arg, 0);
# If there are two .items in a row, we don't want to put in the
# separator line or start a new paragraph.
if ($lastwasitem)
{
print OUT "<br>";
}
else
{
print OUT "<hr>";
&setpar(1);
}
print OUT "<b>$arg</b>\n";
$new_lastwasitem = 1;
}
elsif (/^\.enditems\b/)
{
print OUT "<hr><br>\n";
}
# Handle command line option items
elsif (/^\.startoptions\b/) {}
elsif (/^\.option\s+(.*)/)
{
my($arg) = $1;
$arg =~ s/^"(.*)"$/$1/;
print OUT "<hr>";
&setpar(0);
# For indexing, we want to take up to the first # or < in the line,
# before processing.
my($name) = $arg =~ /^([^#<]+)/;
$name = &handle_text($name, 0);
&handle_index("-$name", 0);
# Output as heading, after the index
$arg = &handle_text($arg, 0);
print OUT "<h3>-$arg</h3>\n";
}
elsif (/^\.endoptions\b/)
{
print OUT "<hr><br>\n";
}
# Found an SGCAL directive that isn't dealt with. Oh dear.
else
{
print "*** Unexpected SGCAL directive: line $. ignored:\n";
print "$_\n";
}
# Remember if last was a .item, and read the next line
$lastwasitem = $new_lastwasitem;
$_ = <IN>;
}
##################################################
# First Pass - collect references #
##################################################
sub pass_one{
$thischapter = 0;
open (IN, $source_file) || die "Can't open $source_file (first pass)\n";
$_ = <IN>;
# At the start of the specification text, there are some textual replacement
# definitions. They set values, but not cross-references.
while (/^\.r?set\s+(\S+)\s+"?([^"]+)\"?\s*$/)
{
$var_value{$1} = $2;
$_ = <IN>;
}
# Now skip on till we hit the start of the first chapter. It will be numbered
# 0 if we hit ".set chapter -1". There is only ever one unnumbered chapter.
while (!/^\.chapter/)
{
$thischapter = -1 if /^\.set\s+chapter\s+-1/;
$_ = <IN>;
}
# Loop for handling chapters
while ($_)
{
$thischapter++;
$thissection = 0;
# Scan through chapter, setting up cross-references to the chapter
# and to the sections within it.
while (<IN>)
{
last if /^\.chapter/;
chomp;
if (/^\.section/)
{
$thissection++;
next;
}
# Handle .(r)set directives.
if (/^\.r?set\s+(\S+)\s+"?([^"]+)\"?\s*$/ && $1 ne "runningfoot")
{
my($key,$value) = ($1,$2);
$value =~ s/~~chapter/$thischapter/e;
$value =~ s/~~section/$thissection/e;
# Only one of $chapsplit or $sectsplit can be set.
if ($key =~ /^CHAP/)
{
$value = $chapsplit?
"<a href=\"${file_base}_$thischapter.html\">$value</a>"
:
"<a href=\"#CHAP$thischapter\">$value</a>";
}
elsif ($key =~ /^SECT/)
{
$value = $chapsplit?
"<a href=\"${file_base}_$thischapter.html" .
"#SECT$thischapter.$thissection\">$value</a>"
:
$sectsplit? "<a href=\"${file_base}_$thissection.html\">$value</a>"
:
"<a href=\"#SECT$thischapter.$thissection\">$value</a>";
}
$var_value{$key} = $value;
}
}
}
close(IN);
}
##################################################
# Second Pass - generate HTML #
##################################################
sub pass_two{
my($tocn) = 0;
my($inmacro) = 0;
my($insection) = 0;
$inem = 0;
$thischapter = 0;
$thissection = 0;
# Open the source file and get the first line
open (IN, $source_file) || die "Can't open $source_file (2nd pass)\n";
$_ = <IN>;
# Skip on till we hit the start of the first chapter, but note if we
# pass ".set chapter -1", which is used to indicate no chapter numbering for
# the first chapter (we number is 0). Keep track of whether we are in macro
# definitions or not, and when not, notice occurrences of .index, because this
# are the "x see y" type entries.
while (!/^\.chapter/)
{
$thischapter = -1 if /^\.set\s+chapter\s+-1/;
$inmacro = 1 if /^\.macro/;
$inmacro = 0 if /^\.endm/;
if (!$inmacro && /^\.index\s+(.*)/)
{
my($key);
my($s) = $1;
$s = &handle_text($s, 0);
$s =~ s/ / /g; # All spaces unsplittable
$key = "\L$s";
$key =~ s/<[^>]+>//g;
$key =~ s/&#(\d+);/chr($1)/eg;
$cindex{$key} = $s;
}
$_ = <IN>;
}
# Open the TOC file
open (TOC, ">$html/${file_base}_toc.html") ||
die "Can't open $html/${file_base}_toc.html\n";
print TOC "<!DOCTYPE HTML PUBLIC \"-//IETF//DTD HTML//EN\">\n";
print TOC "<html>\n<head>\n<title>$doctitle Contents</title>\n</head>\n" .
"<body bgcolor=\"#F8F8F8\" text=\"#00005A\" " .
"link=\"#FF6600\" alink=\"#FF9933\" vlink=\"#990000\">\n";
print TOC "<h1>$doctitle</h1><hr>\n<ul>\n";
# Open the data file if we are not splitting at chapters
&openout("$html/${file_base}.html") if !$chapsplit;
# Loop for handling chapters. At the start of this loop, $_ is either EOF,
# or contains a .chapter line.
$firstchapter = $thischapter + 1;
while ($_)
{
print TOC "</ul>\n" if $insection;
$insection = 0;
$thischapter++;
$thissection = 0;
$lastwasrule = 0;
# Start a new file if required
if ($chapsplit)
{
&closeout("CHAP") if $thischapter != $firstchapter;
&openout("$html/${file_base}_$thischapter.html");
}
# Set up the chapter title. Save it for the TOC. Set up the anchor and
# link back to the TOC and show the title.
$_ =~ /^\.chapter\s+(.*)/;
my($title) = (($thischapter > 0)? "$thischapter. " : "") . &handle_text($1, 0);
$tocn++;
print TOC "<li><a " .
"name=\"TOC$tocn\" " .
"href=\"$current_file#CHAP$thischapter\">$title</a></li>\n";
print OUT "<h1>\n";
print OUT "<a name=\"CHAP$thischapter\" href=\"${file_base}_toc.html#TOC$tocn\">\n";
print OUT "$title\n</a></h1>\n";
# Scan the contents of the chapter
$_ = <IN>;
while ($_)
{
last if /^\.chapter/;
# Handle the start of a new section, starting a new file if required
if (/^\.section\s+(.*)/)
{
$thissection++;
print TOC "<ul>\n" if !$insection;
$insection = 1;
my($title) = (($thischapter > 0)? "$thischapter." : "") .
"$thissection. " . &handle_text($1, 0);
if ($sectsplit)
{
&closeout("SECT");
&openout("$html/${file_base}_$thissection.html");
}
$tocn++;
printf TOC ("<li><a " .
"name=\"TOC$tocn\" " .
"href=\"$current_file#SECT%s$thissection\">%s</a></li>\n",
($thischapter > 0)? "$thischapter." : "", $title);
&setpar(0);
print OUT "<h2>\n";
printf OUT ("<a name=\"SECT%s$thissection\" ",
($thischapter > 0)? "$thischapter." : "");
print OUT "href=\"${file_base}_toc.html#TOC$tocn\">\n";
print OUT "$title\n</a></h2>\n";
$_ = <IN>;
$lastwasrule = 0;
}
# Blank lines at this level are ignored
elsif (/^\s*$/)
{
$_ = <IN>;
}
# Directive and non-directive lines are handled independently, though
# in each case further lines may be read. Afterwards, the next line is
# in $_. If .em is at the start of a paragraph, treat it with the
# paragraph, because the matching .nem will be too. Messy!
elsif (/^\./)
{
if (/^\.em\b/)
{
$_=<IN>;
if (/^\./)
{
print OUT "<font color=green>" if ! $inem;
$inem = 1;
# Used to handle it here - but that fails if it is .section.
# Just let the next iteration of the loop handle it.
# &handle_directive();
}
else
{
$_ = ".em\n" . $_;
&handle_paragraph();
$lastwasrule = 0;
$lastwasitem = 0;
}
}
# Not .em
else
{
&handle_directive();
}
}
# Not a directive
else
{
&handle_paragraph();
$lastwasrule = 0;
$lastwasitem = 0;
}
} # Loop for each line in a chapter
} # Loop for each chapter
# Close the last file, end off the TOC, and we are done.
&closeout("");
print TOC "</ul>\n" if $insection;
if (defined %cindex)
{
$cindex_tocn = ++$tocn;
print TOC "<li><a name=\"TOC$tocn\" ".
"href=\"${file_base}_cindex.html\">Concept Index</a></li>\n";
}
if (defined %oindex)
{
$oindex_tocn = ++$tocn;
print TOC "<li><a name=\"TOC$tocn\" ".
"href=\"${file_base}_oindex.html\">Option Index</a></li>\n";
}
print TOC "</ul>\n</body>\n</html>\n";
close(TOC);
close(IN);
}
##################################################
# Adjust index points #
##################################################
# Because of the way the source is written, there are often index entries
# that immediately follow the start of chapters and sections and the definition
# of "items" like "helo = verify". This gets the correct page numbers for the
# PostScript and PDF formats. However, for HTML we want the index anchor to be
# before the section heading, because browsers tend to put the index point at
# the top of the screen. So we re-read all the files we've just created, and
# move some of the index points about. This is necessary only if indexes exist.
# The files are small enough to be handled entirely in memory.
sub adjust_index_points {
print "Adjusting index points to precede headings\n";
$" = "";
opendir(DIR, "$html") || die "Failed to opendir $html\n";
while ($file = readdir(DIR))
{
my($i);
next unless $file =~ /^${file_base}_\d+\.html$/;
open(IN, "<$html/$file") ||
die "Failed to open $html/$file (read)\n";
my(@lines) = <IN>;
close(IN);
for ($i = 0; $i < @lines; $i++)
{
if ($lines[$i] =~ /^<a name="IX\d+"><\/a>$/)
{
# Handle an index line that follows a heading definition. Move it back
# to just before the <h1> or whatever. This preserves the order of
# multiple index lines, not that that matters.
if ($lines[$i-1] =~ /^<\/a><\/h(\d)>/)
{
my($j);
my($found) = 0;
for ($j = $i-2; $j > 0 && $j > $i - 10; $j--)
{
if ($lines[$j] =~ /<h$1>/)
{
$found = 1;
last;
}
}
if ($found)
{
splice(@lines, $j, 0, splice(@lines, $i, 1));
}
}
# Handle an index line that follows an "item". Move it back one line.
elsif ($lines[$i-1] =~ /^<b>.*<\/b>\s*$/)
{
splice(@lines, $i-1, 0, splice(@lines, $i, 1));
}
# Handle an index line that follows a "conf" definition
elsif ($lines[$i-1] =~ /^<i>Type:<\/i>/ && $lines[$i-2] =~ /^<h3>/)
{
splice(@lines, $i-2, 0, splice(@lines, $i, 1));
}
# Handle an index line that follows an "option" definition
elsif ($lines[$i-1] =~ /^<h3>/)
{
splice(@lines, $i-1, 0, splice(@lines, $i, 1));
}
}
}
open(OUT, ">$html/$file") ||
die "Failed to open $html/$file (write)\n";
print OUT "@lines";
close OUT;
undef @lines;
}
}
##################################################
# Create Index #
##################################################
sub create_index{
my($hash) = $_[0];
my($ifname) = $_[1];
my($ititle) = $_[2];
my(%indexindex);
open(INDEX, ">$html/${file_base}_$_[1].html") ||
die "Failed to open $html/${file_base}_$ifname\n";
print INDEX "<!DOCTYPE HTML PUBLIC \"-//IETF//DTD HTML//EN\">\n";
print INDEX "<html>\n<head>\n<title>$doctitle $ititle</title>\n";
print INDEX "<base target=\"body\">\n</head>\n";
print INDEX "<body bgcolor=\"#FFFFDF\" text=\"#00005A\" " .
"link=\"#FF6600\" alink=\"#FF9933\" vlink=\"#990000\">\n";
print INDEX "<h3>$ititle</h3>\n";
# We have to scan the keys in the hash twice; first to build the list
# of initial letters, and then to do the business. The first time we
# do not need to sort them.
foreach $key (keys %$hash)
{
my($initial) = substr($key,0,1);
$initial = "\U$initial";
$indexindex{$initial} = 1 if $initial ge "A";
}
print INDEX "<p>\n";
foreach $key (sort keys %indexindex)
{
print INDEX " <a href=\"#$key\" target=\"index\">$key</a>\n";
}
print INDEX "<hr></p>\n";
my($letter) = "";
print INDEX "<p>\n";
foreach $key (sort
{ ("\L$a" eq "\L$b")? ("$a" cmp "$b") : ("\L$a" cmp "\L$b") }
keys %$hash)
{
my($initial) = substr($key,0,1);
$initial = "\U$initial";
if ($initial ne $letter)
{
if ($initial ge "A")
{
print INDEX "<br>\n" if $letter ne "";
print INDEX "<a name=\"$initial\"></a>\n";
print INDEX "<font size=\"+1\">\U$initial\E</font><br>\n";
}
$letter = $initial;
}
print INDEX "$$hash{$key}<br>\n";
}
print INDEX "</p>\n";
print INDEX "</body>\n</html>\n";
close(INDEX);
}
##################################################
# Show usage and die #
##################################################
sub usage {
die "Usage: g2h [-split no|section|chapter] <source> <title>\n";
}
##################################################
# Entry point and main program #
##################################################
# Directory in which to put the new HTML files
$html = "html";
# Global variables.
%cindex = ();
%oindex = ();
$chapsplit = 0;
$cindex_tocn = 0;
$file_base = "";
$index_count = 0;
$inem = 0;
$inpar = 0;
$lastwasitem = 0;
$lastwasrule = 0;
$oindex_tocn = 0;
$sectsplit = 0;
$source_file = "";
$thischapter = 0;
$thissection = 0;
# Handle options
my($splitset) = 0;
while (scalar @ARGV > 0 && $ARGV[0] =~ /^-/)
{
if ($ARGV[0] eq "-split" && !$splitset)
{
$splitset = 1;
shift @ARGV;
my($type) = shift @ARGV;
if ($type eq "section") { $sectsplit = 1; }
elsif ($type eq "chapter") { $chapsplit = 1; }
elsif ($type eq "no" ) { $sectsplit = $chapsplit = 0; }
else { &usage(); }
}
else { &usage(); }
}
# Get the source file and its base
&usage() if scalar @ARGV <= 0;
$source_file = shift @ARGV;
($file_base) = $source_file =~ /^(.*)\.src$/;
&usage() if scalar @ARGV <= 0;
$doctitle = shift @ARGV;
print "\nCreate HTML for $doctitle from $source_file\n";
# Remove the old HTML files
print "Removing old HTML files\n";
system("/bin/rm -rf $html/${file_base}_*.html");
# First pass identifies all the chapters and sections, and collects the
# values of the cross-referencing variables.
print "Scanning for cross-references\n";
&pass_one();
$maxchapter = $thischapter; # Used if chapter splitting
$maxsection = $thissection; # Used if section splitting
# Second pass actually creates the HTML files.
print "Creating the HTML files\n";
&pass_two();
# Reprocess for moving some of the index points, if indexes were created
&adjust_index_points() if scalar(keys %cindex) > 0 || scalar(keys %oindex) > 0;
# Finally, we must create the option and concept indexes if any data
# has been collected for them.
if (scalar(keys %cindex) > 0)
{
print "Creating concept index\n";
&create_index(\%cindex, "cindex", "Concepts");
}
if (scalar(keys %oindex) > 0)
{
print "Creating option index\n";
&create_index(\%oindex, "oindex", "Options");
}
# End of g2h
Index: g2man
====================================================================
#! /usr/bin/perl -w
# $Cambridge: exim/exim-doc/doc-scripts/g2man,v 1.1 2004/10/07 15:04:35 ph10 Exp $
# Script to find the command line options in the Exim spec, and turn them
# into a man page, because people like that.
##################################################
# De-markup one line #
##################################################
sub process {
my($x) = $_[0];
# Hide SGCAL escapes
$x =~ s/\@\@/&&a/g; # @@
$x =~ s/\@\\/&&b/g; # @\
$x =~ s/\@</&&l/g; # @<
$x =~ s/\@>/&&g/g; # @>
$x =~ s/\@\{/&&c/g; # @{
$x =~ s/\@\}/&&d/g; # @}
$x =~ s/\@#/&&s/g; # @#
$x =~ s/\@(.)/$1/g; # all other @s
# Convert SGCAL markup
$x =~ s/#/ /g; # turn # into a space
$x =~ s/\$~//g; # turn $~ into nothing
$x =~ s/__/_/g; # turn __ into _
$x =~ s/\$sc\{([^\}]*?)\}/$1/g; # turn $sc{xxx} into xxx
$x =~ s/\$st\{([^\}]*?)\}/$1/g; # turn $st{xxx} into xxx
$x =~ s/\$si\{([^\}]*?)\}/$1/g; # turn $si{xxx} into xxx
$x =~ s/\$tt\{([^\}]*?)\}/$1/g; # turn $tt{xxx} into xxx
$x =~ s/\$it\{([^\}]*?)\}/$1/g; # turn $it{xxx} into xxx
$x =~ s/\$bf\{([^\}]*?)\}/$1/g; # turn $bf{xxx} into xxx
$x =~ s/\$rm\{([^\}]*?)\}/$1/g; # turn $rm{xxx} into xxx
$x =~ s/\$cb\{([^\}]*?)\}/$1/g; # turn $cb{xxx} into xxx
$x =~ s/\\\\([^\\]*?)\\\\/\U$1/g; # turn \\xxx\\ into XXX
$x =~ s/\\\(([^)]*?)\)\\/$1/g; # turn \(xxx)\ into xxx
$x =~ s/\\\"([^\"]*?)\"\\/$1/g; # turn \"xxx"\ into xxx
$x =~ s/\\\%([^\%]*?)\%\\/"$1"/g; # turn \%xxx%\ into "xxx"
$x =~ s/\\\?([^?]*?)\?\\/$1/g; # turn \?URL?\ into URL
$x =~ s/<<([^>]*?)>>/<$1>/g; # turn <<xxx>> into <xxx>
$x =~ s/\\\$([^\$]*?)\$\\/\$$1/g; # turn \$xxx$\ into $xxx
$x =~ s/\\\-([^\\]*?)\-\\/\-$1/g; # turn \-xxx-\ into -xxx
$x =~ s/\\\*\*([^*]*?)\*\*\\/$1/g; # turn \**xxx**\ into xxx
$x =~ s/\[\(([\w\/]*)\)\]//g; # remove inline HTML
$x =~ s/\\\*([^*]*?)\*\\/$1/g; # turn \*xxx*\ into xxx
$x =~ s/\\([^\\]*?)\\/"$1"/g; # turn \xxx\ into "xxx"
$x =~ s/\$\*\$/\*/g; # turn $*$ into *
$x =~ s/\$t\b//g; # turn $t into nothing
$x =~ s/::([^:]+)::/$1:/g; # turn ::xxx:: into xxx:
# Put back escaped SGCAL specials
$x =~ s/&&a/\@/g; # @@ => @
$x =~ s/&&b/\\/g; # @\ => \
$x =~ s/&&l/</g; # @< => <
$x =~ s/&&g/>/g; # @> => >
$x =~ s/&&c/\@{/g; # @{ => @{
# $x =~ s/&&rc/{/g; #
# $x =~ s/&&rd/}/g; #
$x =~ s/&&d/\@}/g; # @} => @}
$x =~ s/&&s/#/g; # @#
# Remove any null flags ($$)
$x =~ s/\$\$//g;
$x;
}
##################################################
# De-reference a paragraph #
##################################################
# Remove sentences or parenthetical comments that contain references.
sub deref {
my($t) = $_[0];
$t =~ s/^(\n*)[^.()]+~~[^.]+\.\s*/$1/;
$t =~ s/\s?\.[^.()]+~~[^.]+\././g;
$t =~ s/\s?\([^~).]+~~[^)]+\)//g;
$t;
}
##################################################
# Quote what needs quoting #
##################################################
# This is for anything that must be quoted in the final output, independent
# of whether it is in "asis" text or not.
sub mustquote {
my($t) = $_[0];
$t =~ s/(?<!\\)-/\\-/g;
$t;
}
##################################################
# Main Program #
##################################################
open(IN, "spec.src") || die "Can't open spec.src\n";
open(OUT, ">exim.8" ) || die "Can't open exim.8\n";
print OUT <<End;
.TH EXIM 8
.SH NAME
exim \\- a Mail Transfer Agent
.SH SYNOPSIS
.B exim [options] arguments ...
.br
.B mailq [options] arguments ...
.br
.B rsmtp [options] arguments ...
.br
.B rmail [options] arguments ...
.br
.B runq [options] arguments ...
.br
.B newaliases [options] arguments ...
.SH DESCRIPTION
Exim is a mail transfer agent (MTA) developed at the University of Cambridge.
It is a large program with very many facilities. For a full specification, see
the reference manual. This man page contains only a description of the command
line options. It has been automatically generated from the reference manual
source, which is why the formatting is poor in some places.
.SH SETTING OPTIONS BY PROGRAM NAME
.TP 10
\\fBmailq\\fR
Behave as if the option \\-bp were present before any other options. The \\-bp
option requests a listing of the contents of the mail queue on the standard
output.
.TP
\\fBrsmtp\\fR
Behaves as if the option \\-bS were present before any other options, for
compatibility with Smail. The \\-bS option is used for reading in a number of
messages in batched SMTP format.
.TP
\\fBrmail\\fR
Behave as if the \\-i and \\-oee options were present before any other options,
for compatibility with Smail. The name \\fBrmail\\fR is used as an interface by
some UUCP systems. The \\-i option specifies that a dot on a line by itself
does not terminate a non\\-SMTP message; \\-oee requests that errors detected in
non\\-SMTP messages be reported by emailing the sender.
.TP
\\fBrunq\\fR
Behave as if the option \\-q were present before any other options, for
compatibility with Smail. The \\-q option causes a single queue runner process
to be started. It processes the queue once, then exits.
.TP
\\fBnewaliases\\fR
Behave as if the option \\-bi were present before any other options, for
compatibility with Sendmail. This option is used for rebuilding Sendmail's
alias file. Exim does not have the concept of a single alias file, but can be
configured to run a specified command if called with the \\-bi option.
.SH OPTIONS
.TP 10
End
while (<IN>) { last if /^\.startoptions/; }
die "Can't find start of options\n" if ! defined $_;
# Find the start of the first option
while (<IN>) { last if /^\.option/; }
die "Can't find start of first option\n" if ! defined $_;
# Loop for each individual option
while (/^\.option (.*)/)
{
$nlpending = 0;
$itemtext = "";
printf OUT ("\\fB\\-%s\\fR\n", &mustquote(&process($1)));
# Process the data for the option
while (<IN>)
{
last if /^\.(?:option|endoptions)/;
next if /^\.index/;
next if /^\.em\s*$/;
next if /^\.nem\s*$/;
if (/^\.display(?:\s+flow)?(?:\s+rm)?\s*$/)
{
print OUT &mustquote(&deref($itemtext));
$itemtext = "";
print OUT "\n";
while (($_ = <IN>) !~ /^\.endd/)
{
print OUT " ", &mustquote(&deref(&process($_))) if ! /^\./;
}
$nlpending = 1;
}
elsif (/^\.display asis\s*$/)
{
print OUT &mustquote(&deref($itemtext));
$itemtext = "";
print OUT "\n";
while (($_ = <IN>) !~ /^\.endd/)
{
print OUT &mustquote(" $_");
}
$nlpending = 1;
}
elsif (/^\s*$/)
{
print OUT &mustquote(&deref($itemtext));
$itemtext = "";
$nlpending++;
}
else
{
while ($nlpending > 0)
{
$itemtext .= "\n";
$nlpending--;
}
$itemtext .= &process($_);
}
}
print OUT &mustquote(&deref($itemtext));
print OUT ".TP\n";
}
# End of g2man
Index: g2t
====================================================================
#! /usr/bin/perl -w
# $Cambridge: exim/exim-doc/doc-scripts/g2t,v 1.1 2004/10/07 15:04:35 ph10 Exp $
# A Perl script to turn the SGCAL source of the Exim documentation into
# Texinfo input, more or less...
# Supply the source file names as arguments.
# The output goes to the standard output.
##################################################
# Ensure unique node name #
##################################################
# Node names must be unique. Occasionally in the Exim spec there are duplicate
# section names, and it's become too much of a hassle to keep them distinct
# manually. So it is now automated.
########### Never really got this working. Abandoned ###############
sub unique {
my($node) = $_[0];
if (defined $node_names{$node})
{
$node_names{$node} += 1;
$node = "$node ($node_names{$node})";
print STDERR "+++ $node\n";
}
else
{
$node_names{$node} = 0;
}
$node;
}
##################################################
# De-comma a node name #
##################################################
# Commas, colons, and apostrophes are not permitted in Texinfo
# node names. I find this incredible, but it is clearly documented.
# The Exim manual has been re-organized not to have colons or
# apostrophes in any chapter or section titles, but I can't manage
# without commas. This function turns "," into " and", which is
# the best that can be done; we can use some clever Perlery to
# just take out commas before "and".
# Sigh. The Sendmail option -p<rval>:<sval> now means that there's a colon
# in the node name for that option. Turn the colon into <colon>. This is also
# done for menus.
# Another thing that causes problems in node names in some versions of
# Texinfo is the use of @sc{xxx} for small caps. Just turn these all into
# real caps. This is also done for menus.
sub decomma {
$_[0] =~ s/,(?!\sand)/ and/g;
$_[0] =~ s/,//g;
$_[0] =~ s/\@sc\{([^}]*)\}/\U$1/g;
$_[0] =~ s/:/<colon>/g;
$_[0];
}
##################################################
# De-quote a string #
##################################################
# @x is turned into x, except when x=@, or when asis is set,
# in which case single @ must turn into @@. A single substitute
# doesn't work in the non-asis case, because of the problems of
# handling things like @@@$, so we do it the hard way.
sub dequote {
if ($asis) { $_[0] =~ s/@/@@/g; } else
{
$_[0] =~ s/@@/&at&/g;
$_[0] =~ s/@([^@])/$1/g;
$_[0] =~ s/&at&/@@/g;
}
$_[0];
}
##################################################
# Get next line #
##################################################
# Called from handle_directive, to get the next source line
# into $_.
sub get_next_line {
if ($processing_subsection)
{ return $_ = shift @SUBBUFFER; }
else
{ return $_ = <>; }
}
##################################################
# Handle text lines #
##################################################
# This function is handed whole paragraphs, and we assume that
# SGCAL font changing markup is always complete within a paragraph.
# We have to replace escaped versions of significant characters with
# some magic before performing general transformations, and then
# put them back afterwards. The character & is not common in the text,
# and && is unknown, so we use that.
sub handle_text {
$_ = $_[0];
if ($asis)
{
$_ = dequote($_);
s/(\{|\})/\@$1/g;
return $_;
}
while (/~~/)
{
$left = $`;
($name) = $' =~ /^(\w+)/;
$right = $';
$value = $references{$name};
$value = "" if !defined($value);
if ($value =~ /\*\*\*\*/)
{
$value = ($` eq $current_chapter)? "\"$'\"" :
"\"$'\" in chapter \"$`\"";
$value = "" if $value eq "\"\"";
}
elsif ($value !~ /^[0-9]+\.[0-9]+$/) # quote unless version number
{
$value = "\"$value\"";
}
$_ = "${left}${value}${right}";
}
s/\@\@/&&a/g; # @@
s/\@\\/&&b/g; # @\
s/\@</&&l/g; # @<
s/\@>/&&g/g; # @>
s/\@\{/&&c/g; # @{
s/\@\}/&&d/g; # @}
s/\@#/&&s/g; # @#
# Now remove all other @'s
$_ = dequote($_);
# Convert SGCAL markup
s/#/ /g; # turn # into a space
s/\$~//g; # turn $~ into nothing
s/__/_/g; # turn __ into _
s/\$sm\{//g; # turn $sm{ into nothing
s/\$sc\{([^\}]*?)\}/$1/g; # turn $sc{xxx} into xxx
s/\$st\{([^\}]*?)\}/$1/g; # turn $st{xxx} into xxx
s/\$si\{([^\}]*?)\}/$1/g; # turn $si{xxx} into xxx
s/\$tt\{([^\}]*?)\}/$1/g; # turn $tt{xxx} into xxx
s/\$it\{([^\}]*?)\}/$1/g; # turn $it{xxx} into xxx
s/\$bf\{([^\}]*?)\}/$1/g; # turn $bf{xxx} into xxx
s/\$rm\{([^\}]*?)\}/$1/g; # turn $rm{xxx} into xxx
s/\$cb\{([^\}]*?)\}/$1/g; # turn $cb{xxx} into xxx
# This is a fudge for some specific usages of $<; can't just do a global
# is it occurs in things like $<variable name> as well.
s/\[\$<\]/[]/g; # turn [$<] into []
s/&&b\$<\./&&b./g; # turn \$<. into \. (\ == &&b by now)
s/(\d)\$<-/$1-/g; # turn 0$<- into 0-
# There is one case where the terminating } of an escape sequence is
# in another paragraph - this follows $sm{ - it can be fixed by
# removing any stray } in a paragraph that contains no { chars.
s/\}//g if !/\{/;
# Any remaining {} must be escaped to prevent Texinfo from complaining
s/(\{|\})/\@$1/g;
# Now to conversions that put {} into the file.
# Change <<..>> from @var to just <...> as the caps that Texinfo
# uses look far too shouty.
s/\\\\([^\\]*?)\\\\/\@sc\{\L$1\}/g; # turn \\xxx\\ into @sc{xxx}
s/\\\(([^)]*?)\)\\/\@file\{$1\}/g; # turn \(xxx)\ into @file{xxx}
s/\\\"([^\"]*?)\"\\/\@file\{$1\}/g; # turn \"xxx"\ into @file{xxx}
s/\\\?([^?]*?)\?\\/$1/g; # turn \?URL?\ into URL
s/<<([^>]*?)>>/<$1>/g; # turn <<xxx>> into <xxx>
s/\\\$([^\$]*?)\$\\/\$$1/g; # turn \$xxx$\ into $xxx
s/\\\-([^-]*?)\-\\/\-$1/g; # turn \-xxx-\ into -xxx
s/\\\*\*([^*]*?)\*\*\\/$1/g; # turn \**xxx**\ into xxx
s/\[\(([\w\/]*)\)\]//g; # remove inline HTML
s/\\\*([^*]*?)\*\\/\@dfn\{$1\}/g; # turn \*xxx*\ into @dfn{xxx}
s/\\%([^*]*?)%\\/\@dfn\{$1\}/g; # turn \%xxx%\ into @dfn{xxx}
s/:::([^:]*?)::/\@dfn\{:$1:\}/g; # turn :::xxx:: into @dfn{:xxx:}
s/::([^:]*?)::/\@dfn\{$1:\}/g; # turn ::xxx:: into @dfn{xxx:}
s/\\([^\\]*?)\\/\@dfn\{$1\}/g; # turn \xxx\ into @dfn{xxx}
s/\$\*\$/\*/g; # turn $*$ into *
# Put back escaped SGCAL specials
s/&&a/\@\@/g;
s/&&b/\\/g;
s/&&l/</g;
s/&&g/>/g;
s/&&c/\@{/g;
s/&&rc/{/g;
s/&&rd/}/g;
s/&&d/\@}/g;
s/&&s/#/g;
# Remove any null flags ($$)
s/\$\$//g;
# If the paragraph starts with $c\b, change this into @center. Assume
# we don't ever get two of these in a row.
s/^\$c\b/\@center /;
# If the paragraph starts with $e\b, stuff some tabs in there, as
# Texinfo can't do this on its own (as far as I can see). They must
# tabs; Texinfo treats them as different to spaces. Sigh.
s/^\$e\b/\t\t\t\t\t\t\t/;
# Handle $t. The Exim spec only ever has one tab per line. Er, not
# quite true, but a good enough assumption. $t is always followed
# by a non-word character.
# The .tabs directive has stashed the value in the $tab variable.
# Don't count Texinfo font chars.
while (/(^|.+?\n)(.+?)\$t(\W.*\n)/)
{
$before = $` . $1;
$after = $';
$left = $2;
$right = $3;
$left =~ s/\s$//;
$right =~ s/^\s+//;
$plainleft = $left;
$plainleft =~ s/\@[a-z]+\{([^}]+?)\}/$1/g;
$plainleft =~ s/\@//g;
$_ = $before . $left . (" " x ($tab - length($plainleft))) . $right . $after;
# Fudge for the one case where there are two tabs
if ($tab2 != 0)
{
$temp = $tab;
$tab = $tab2;
$tab2 = $temp;
}
}
# Return the new line (paragraph)
$_;
}
##################################################
# Handle directive lines #
##################################################
# Use get_next_line() instead of <> because this is called to process
# stacked up subsection lines
sub handle_directive {
my($new_lastwasitem) = 0;
# Chapter directives just require . => @; however, dequoting the
# line thereafter will remove the first @, so just force it back
# afterwards. If the chapter is is one describing a driver, set
# the driver name.
if (/\.chapter/)
{
tr/./@/;
push(@ONESECTION, "@" . &dequote("$_\n"));
$driver_name = (/The\s+(\S+)\s+(director|router|transport|authenticator)/)? $1 :
(/Generic options common to both directors and routers/)?
"director or router" :
(/[Gg]eneric\s+options for (\S+)s/)? $1 : "";
$driver_name = &dequote($driver_name);
}
# Section directives just require . => @; however, dequoting the
# line thereafter will remove the first @, so just force it back
# afterwards. Remove any colons in section titles as they cause
# Texinfo trouble. Also remove any \\ (small caps) markup, which
# appears in a couple of cases.
elsif (/\.section/)
{
tr/./@/;
s/://;
s"\\\\""g;
push(@ONESECTION, "@" . &dequote("$_\n"));
# Horrible magic fudge to cope with the fact that exim_lock has
# -v and -q options, just like the main program.
$driver_name = "exim_lock" if /Mailbox maintenance/;
# Similar magic for exiqgrep, which also duplicates options
$driver_name = "exiqgrep" if /Selective queue listing/;
}
# .newline must put @* on the end of the previous line, if any, except
# inside a display, where it causes trouble.
elsif (/\.newline/)
{
if (@ONESECTION > 0 && ! $indisplay)
{
$_ = pop(@ONESECTION);
s/(\n*)$/\@*$1/;
push(@ONESECTION, $_);
}
}
# .blank turns into @sp, adding 1 if no argument
elsif (/\.blank/)
{
s/\.blank\s+(\d+)/\@sp $1/;
s/\.blank/\@sp 1/;
push(@ONESECTION, $_);
}
# .rule turns into a line of hyphens
elsif (/\.rule/)
{
push(@ONESECTION, ("-" x ($in_itemize? 68 : 73)) . "\@*\n");
}
# There's one explicit .tabset setting for two tab stops
elsif (/\.tabset\s*/)
{
$rest = $';
($first,$second) = $rest =~ /(\d+)em\s+(\d+)em/;
$tab = ($first * 7)/6;
$tab2 = $tab + ($second * 7)/6;
}
# .tabs remembers the first (and only) tab setting
elsif (/\.tabs\s*/)
{
$tab = ($' * 7)/6;
$tab2 = 0;
}
# .tempindent is used only to align some of the expansion stuff nicely;
# just ignore it. It is used in conjunction with .push/.pop.
elsif (/\.(tempindent|push|pop)\s*/)
{
}
# There are some instances of .if ~~sys.fancy in the source. Some of these
# are two-part things, in which case we just keep the non-fancy. For diagrams,
# however, they are in three parts:
#
# .if ~~sys.fancy
# <aspic drawing stuff>
# .elif ~~nothtml
# <ascii art for txt and Texinfo>
# .else
# <HTML instructions for including a gif>
# .fi
elsif (/\.if \~\~sys\.fancy/)
{
while (&get_next_line())
{ last if /\.else\b/ || /\.elif\s+\~\~nothtml/ || /\.fi\b/; }
if (/\.elif/)
{
$skip_else = 1;
}
}
# There are occasional requirements to do things differently for
# Texinfo/HTML and the PS/txt versions, and there are also some
# HTML-specific things.
elsif (/\.if\s+~~sgcal/ || /\.if\s+~~html/)
{
while (&get_next_line()) { last if /\.else\b/ || /\.fi\b/; }
}
# We may also have Texinfo-specific bits
elsif (/^\.if\s+~~texinfo/)
{
$skip_else = 1;
}
# Ignore any other .if directives
elsif (/\.if/) {}
# Skip else part if flag set
elsif (/\.else/ && $skip_else)
{
while (&get_next_line()) { last if /\.fi\b/; }
$skip_else = 0;
}
# Ignore other .fi and .else as any .if directives are handled specially
elsif (/\.fi/ || /\.else/) {}
# Ignore .indent
elsif (/\.indent/) {}
# Plain .index goes to @cindex - the "concept" index. Also, there
# are some calls to vindex and findex in the SGCAL source - treated
# as synonymous with .index - which are split into the equivalent
# indexes here.
elsif (/\.(.?)index/)
{
$rest = $';
$letter = ($1 eq "")? "c" : $1;
tr/./@/; # .index -> @index
$rest =~ s/\\\(//g; # Remove markup
$rest =~ s/\)\\//g;
$rest =~ s/\\%//g;
$rest =~ s/%\\//g;
$rest =~ s/\\\*//g;
$rest =~ s/\*\\//g;
$rest =~ s/\\"//g;
$rest =~ s/"\\//g;
$rest =~ s/:://g;
$rest =~ s/\\-/-/g;
$rest =~ s/-\\//g;
$rest =~ s/~~//g;
$rest =~ tr/\\//d; # Remove \
$rest =~ s/\@\$/\$/g; # @$ -> $
$rest =~ s/\@_/_/g; # @_ -> _
$rest =~ s/\@\+/+/g; # @+ -> +
$rest =~ s/\$\*\$/\*/g; # $*$ -> *
$rest =~ s/\$([^\$]+)\$/\$$1/g; # $x$ -> $x
$rest =~ s/^\s+//; # Remove leading spaces
$rest =~ s/\s+$//; # Remove trailing spaces
$rest =~ s/\|\|/:/; # || -> :
push(@ONESECTION, "\@${letter}index $rest\n");
# Duplicate entries for things that were listed as "x see y"
if (defined $indirections{$rest})
{
push(@ONESECTION, "\@${letter}index $indirections{$rest}\n");
}
}
# Various flavours of numberpars map to itemize and enumerate.
# Haven't found a way of having a blank space 'bullet' yet, so
# currently using minus.
elsif (/\.numberpars/)
{
$rest = $';
$type = "enumerate";
$flag = "";
if ($rest =~ /\$\./) { $flag = " \@bullet"; $type = "itemize" }
elsif ($rest =~ /\" \"/) { $flag = " \@minus"; $type = "itemize"; }
elsif ($rest =~ /roman/) { $flag = " a"; $type = "enumerate"; }
push(@ONESECTION, "\n\@$type$flag\n\n\@item\n");
push(@ENDLIST, $type);
$in_itemize++;
}
elsif (/\.nextp/)
{
push(@ONESECTION, "\n\@item\n");
}
elsif (/\.endp/)
{
$endname = pop(@ENDLIST);
push(@ONESECTION, "\@end $endname\n\n");
$in_itemize--;
}
# The normal .display (typewriter font) => @example, while the rm
# form goes to @display (no change of font). For Texinfo we need a
# blank line after @display.
elsif (/\.display/)
{
$type = /rm/? "display" : "example";
$asis = 1 if /asis/;
$indisplay = 1;
push(@ONESECTION, "\@$type\n\n");
push(@ENDLIST, $type);
}
elsif (/\.endd/)
{
$asis = 0;
$indisplay = 0;
$endname = pop(@ENDLIST);
push(@ONESECTION, "\@end $endname\n\n");
}
elsif (/\.conf/)
{
($option, $type, $default) =
/\.conf\s+(\S+)\s+("(?:[^"]|"")+"|\S+)\s+("(?:[^"]|"")+"|.*)/;
$option = &dequote($option);
# If $type ends with $**$ (turned into a dagger for PS version),
# replace with ", expanded". Remove any surrounding quotes.
$type =~ s/^"([^"]+)"/$1/;
$type =~ s/\$\*\*\$/, expanded/;
# Default may be quoted, and it may also have quotes that are required,
# if it is a string.
$default =~ s/^"(.*)"$/$1/;
$default =~ s/""/"/g;
$default = &handle_text($default);
push(@ONESECTION, "\nType: $type\@*\nDefault: $default\n\n");
}
# Handle .startitems, .enditems, and .item
elsif (/\.startitem/ || /\.enditem/) {}
elsif (/\.item/)
{
$arg = $';
$arg =~ s/^\s*"//;
$arg =~ s/"\s*$//;
$arg = &dequote($arg);
$arg = &handle_text("\\**$arg**\\");
# If there are two .items in a row, we don't want to put in the
# separator line.
# push(@ONESECTION, "\n\@example\n");
push(@ONESECTION, "\n");
if (! $lastwasitem)
{
push(@ONESECTION, "_" x 75, "\n\n");
}
# push(@ONESECTION, "$arg\n\@end example\n\n");
push(@ONESECTION, "$arg\n\n");
$new_lastwasitem = 1;
}
elsif (/\.option/)
{
chomp($arg = $');
$arg =~ s/^\s*//;
$arg = &dequote("-$arg");
$arg = &handle_text($arg);
}
# Texinfo has no facility for emphasis bars.
elsif (/\.em/) {}
elsif (/\.nem/) {}
# Just ignore any .(r)set directives pro tem (or maybe always!)
elsif (/\.r?set/) {}
# Ignore .space, .linelength, and .justify
elsif (/\.space/ || /\.justify/ || /\.linelength/) {}
# Found an SGCAL directive that isn't dealt with. Oh dear.
# Turn the embarrassing characters into question marks and
# output it in an emphasized way.
else
{
tr/@{}/???/;
push(@ONESECTION, "\n\>>>>>>> $_\n") if ! /^\.( |$)/;
}
$lastwasitem = $new_lastwasitem;
}
##################################################
# Flush a section #
##################################################
# $section_name is the name of the next section.
# $current_section is the name of the one we have buffered up.
# If it is unset, we are at the first section of a chapter.
# $previous_node is the section we last flushed if it was a node.
sub flush_section {
# If there is no text in the section, omit it entirely. However, it
# will have had a pointer set up at the start of the previous section.
# Remember what to replace this with when the chapter gets flushed.
my($skip) = 1;
foreach $s (@ONESECTION)
{
if ($s !~ /^(\@cindex|\@section|\s*$)/) { $skip = 0; last }
}
if ($skip)
{
pop @section_list;
$rewrite{$current_section} = $section_name;
@ONESECTION = ();
return;
}
# There is data in the section: write it out to the chapter file
if ($current_section)
{
printf ONECHAPTER ("\@node %s, %s, %s, %s\n",
&decomma($current_section), &decomma($section_name),
&decomma($previous_node), &decomma($current_up));
$previous_node = $current_section;
while(scalar(@ONESECTION))
{ print ONECHAPTER shift(@ONESECTION); }
}
else
{
while(scalar(@ONESECTION))
{ push(@TOPSECTION, shift(@ONESECTION)); }
}
}
##################################################
# Handle a "subsection" #
##################################################
# A "subsection" is a set of options that must have their own
# local menu. Do two passes; the first just collects the names
# for the menu. This is called for .conf and .option items.
sub handle_subsection{
my($type) = $_[0];
my($save_up) = $current_up;
$current_up = $current_section? $current_section : $current_chapter;
@sublist = ();
@SUBBUFFER = ();
while (<>)
{
last if /^\.end$type/;
push(@SUBBUFFER, $_);
# .conf takes the first non-space string as the name, but as there are
# duplicate confs in various parts of the spec, use the driver name to
# de-duplicate; .option takes the entire rest of arg as the name, but
# removes any sequence of ... because this disturbs TexInfo. Also, it
# turns @- into -.
if (/^\.$type\s+(\S+)(.*)/)
{
if ($type eq "conf")
{
$name = &handle_text($1);
$name .= " ($driver_name)" if ($driver_name ne "");
}
else
{
chomp($name = &handle_text("-$1$2"));
$name =~ s/\s*\.\.\.//g;
$name .= " ($driver_name)" if ($driver_name ne "");
# There seems to be a major problem in texinfo with the string "--".
# In the text it gets turned into a single hyphen. This happens if it
# is used as a menu item, but *not* as a node name. Exim has a command
# line option "--". With no special action, this appears in the menu
# as "-", but then the info software complains there is no node called
# "-". If we triple it in the menu it gets displayed OK, but building
# software complains about non-existent cross references etc.
# I have gone for the horrid kludge of turning it into "-<hyhen>"
# in the menus and nodes.
# Exim 4 has added --help, which has the same problem.
$name = "-<hyphen>" if ($name eq "--");
$name = "-<hyphen>help" if ($name eq "--help");
}
push(@sublist, $name);
}
}
push (@ONESECTION, "\n\@sp 2\n\@menu\n");
for ($i = 0; $i < scalar(@sublist); $i++)
{
$mitem = $sublist[$i];
$mitem =~ s/\@sc\{([^}]*)\}/\U$1/g; # Get rid of small caps
$mitem =~ s/:/<colon>/g; # Get rid of colons
push (@ONESECTION, "* ${mitem}::\n");
}
push (@ONESECTION, "\@end menu\n\n");
$prevsub = $current_up;
$processing_subsection = 1;
while ($_ = shift(@SUBBUFFER))
{
if (/^\.$type\s+(\S+)/)
{
$name = shift @sublist;
$next = (scalar(@sublist))? $sublist[0] : "";
push @ONESECTION, sprintf("\@node %s, %s, %s, %s\n",
&decomma($name), &decomma($next), &decomma($prevsub),
&decomma($current_up));
if ($name eq "-<hyphen>") # Fudge for Texinfo
{
push(@ONESECTION,
"\@findex $name\n",
"\@unnumberedsubsec --- option\n");
push(@ONESECTION,
"This option consists of two consecutive hyphens. It appears in\n",
"the menu as \"-<hyphen>\" because otherwise Texinfo gets\n",
"confused with its cross-referencing.\n");
}
elsif ($name eq "-<hyphen>help") # Fudge for Texinfo
{
push(@ONESECTION,
"\@findex $name\n",
"\@unnumberedsubsec ---help option\n");
push(@ONESECTION,
"This option consists of \"help\" preceded by two consecutive\n" .
"hyphens. It appears in the menu as \"-<hyphen>help\" because\n" .
"otherwise Texinfo gets confused with its cross-referencing.\n");
}
else
{
push(@ONESECTION,
"\@findex $name\n",
"\@unnumberedsubsec $name option\n");
}
$prevsub = $name;
}
# Then handle as text or directive
if (substr($_, 0, 1) eq ".")
{ handle_directive(); }
else
{
while($nextline = shift(@SUBBUFFER))
{
last if $nextline =~ /^(\.|\s*$)/;
$_ .= $nextline;
}
push(@ONESECTION, handle_text($_));
$_ = $nextline;
last if !defined($_);
redo;
}
}
$processing_subsection = 0;
$section_pending = 1;
$current_up = $save_up;
}
##################################################
# Handle a single chapter #
##################################################
sub handle_chapter{
chop;
($current_chapter) = /^\.chapter (.*)/;
$current_chapter = &dequote($current_chapter);
$current_chapter = $current_chapter;
my($tmp) = $current_chapter;
$tmp =~ s/\[\[\[\]\]\]/./;
print STDERR "processing chapter: $tmp\n";
# Remember the chapter name for the top-level menu
push(@chapter_list, $current_chapter);
# Open a temporary file to hold the chapter's text while collecting
# all its sections for a chapter-level menu.
$ONECHAPTER = "/tmp/ONECHAPTER.$$";
open(ONECHAPTER, ">$ONECHAPTER") || die "Can't open $ONECHAPTER for writing";
# Initialize for handling sections
@section_list = ();
%rewrite = ();
@ONESECTION = ();
@TOPSECTION = ();
undef $current_section;
undef $next_node;
$processing_subsection = 0;
$previous_node = $current_up = $current_chapter;
$section_pending = 0;
# Handle the .chapter directive as the first text of a section without
# a section title.
handle_directive();
# Loop, handling each section. Assume they are sufficiently short that
# we can buffer the text in store, in an array called ONESECTION, instead
# of thrashing yet another file.
while (<>)
{
last if /^\.chapter /;
# Handle a new section, preserving $_ (handle_text flattens it).
# It seems we cannot get a fullstop into a Texinfo node name; use a magic
# character string that gets turned back into a dot by the post-processing.
if (/^\.section\s+/)
{
$save = $_;
$section_name = $';
$section_name =~ s/(\s|\n)+$//;
$section_name =~ s/://;
$section_name = &handle_text($section_name);
flush_section();
push(@section_list, $section_name);
$current_section = $section_name;
$next_node = $section_name if !$next_node;
$section_pending = 0;
$_ = $save;
}
# The .startconf macro introduces a set of .conf's which must have
# their own local set of menus. Suspend processing the section while
# we sort out the menu and copy their data. This is all done in a
# subroutine that is shared with options.
elsif (/^\.startconf/)
{
handle_subsection("conf");
next;
}
elsif (/^\.startoption/)
{
handle_subsection("option");
next;
}
# Deal with the actual data lines; if there's a section pending
# start a new section on hitting some text. We hope this happens
# only once per chapter...
if (substr($_, 0, 1) eq ".")
{
handle_directive();
}
else
{
while($nextline = <>)
{
last if $nextline =~ /^(\.|\s*$)/;
$_ .= $nextline;
}
if ($section_pending && !/^\s*$/)
{
$section_name = (defined $current_section)?
"$current_section (continued)" :
"$current_chapter (continued)" ;
flush_section();
push(@section_list, $section_name);
$current_section = $section_name;
$next_node = $section_name if !$next_node;
$section_pending = 0;
}
push(@ONESECTION, handle_text($_));
$_ = $nextline;
last if !defined($_);
redo;
}
}
# Flush any pending text, making its next field null.
# and fudging section_name for the final section of the previous.
$section_name = "";
flush_section();
# Set up section name as the start of the next chapter
$section_name = "Concept Index" if (!$doing_filter);
if (defined $_ && /^\.chapter (.*)/)
{
$section_name = $1;
$section_name = &dequote($section_name);
}
$next_node = $section_name;
# Write out the chapter to the CHAPTERS file, sticking the chapter
# menu after the text that came before the first section heading. This
# will always at least contain the chapter title.
printf CHAPTERS ("\@node %s, %s, %s, Top\n",
&decomma($current_chapter), &decomma($next_node),
&decomma($previous_chapter));
# The pre-section stuff; if we hit an @end menu line, it is the menu of
# a "subsection" before the first section. In that case, we need to put
# the chapter's menu one the end of it, and then resume with the rest of
# the TOPSECTION data afterwards. We also need to thread together this
# "subsection"s nodes because they are all at the same level under the
# chapter.
$in_menu = 0;
while(scalar(@TOPSECTION))
{
$s = shift(@TOPSECTION);
if ($s =~ /^\@end menu/)
{
$in_menu = 1;
last;
}
print CHAPTERS $s;
}
# Menu for sections
undef $next_actual_section;
undef $point_back;
if (scalar(@section_list))
{
print CHAPTERS "\n\@sp 2\n\@menu\n" if ! $in_menu;
$next_actual_section = $section_list[0];
for ($i = 0; $i < scalar(@section_list); $i++)
{
$section_name = $section_list[$i];
$section_name =~ s/\@sc\{([^}]*)\}/\U$1/g;
print CHAPTERS "* ${section_name}::\n";
}
$in_menu = 1;
}
print CHAPTERS "\@end menu\n\n" if $in_menu;
# Remainder of topsection; we must arrange that the final @node in
# it (which will have a blank "next" field) actually points on to
# the next section, if any. If this happens, then the next section
# must point back to the final @node.
while(scalar(@TOPSECTION))
{
$s = shift(@TOPSECTION);
if ($next_actual_section && $s =~
/^\@node\s+([^,]+),\s*,\s*([^,]*),\s*(.*)/)
{
my($t1, $t2, $t3) = ($1, $2, $3); # So can be decomma'd
printf CHAPTERS ("\@node %s, %s, %s, %s\n", &decomma($t1),
&decomma($next_actual_section), &decomma($t2), &decomma($t3));
$point_back = $1;
}
else { print CHAPTERS $s; }
}
close(ONECHAPTER);
open(ONECHAPTER, "$ONECHAPTER") || die "Can't open $ONECHAPTER for reading";
# While copying the chapter data, check for node references to empty
# sections that got omitted and correct them, and correct the prev pointer
# in the first node if necessary.
while ($buff = <ONECHAPTER>)
{
foreach $key (keys %rewrite)
{
$buff =~ s/$key/$rewrite{$key}/;
}
if ($point_back && $buff =~ /^\@node\s+([^,]+),\s*([^,]*),\s*([^,]*),\s*(.*)/)
{
my($t1, $t2, $t4) = ($1, $2, $4); # so can be decomma'd
printf CHAPTERS ("\@node %s, %s, %s, %s\n", &decomma($t1),
&decomma($t2), &decomma($point_back), &decomma($t4));
undef $point_back;
}
else { print CHAPTERS $buff; }
}
$previous_chapter = $current_chapter;
close(ONECHAPTER);
unlink($ONECHAPTER);
}
##################################################
# Main Program #
##################################################
# This is a two-pass algorithm. The first pass goes through and gets the
# variable names for cross references. The second pass does the real work,
# but we can't just read through doing the translation in one pass. We need
# to know the list of chapters in order to build a top-level menu, and for
# each chapter we need to know the sections in order to build a section
# menu. Consequently, make use of temporary files to buffer things.
# This script is used for the filter document and the overview as well;
# flags tell it if it is doing one of them.
$doing_filter = 0;
$skip_else = 0;
$in_itemize = 0;
$lastwasitem = 0;
$chapter_number = 0;
$section_number = 0;
if ($#ARGV >= 0 && $ARGV[0] eq "-filter")
{
$doing_filter = 1;
shift @ARGV;
}
# First pass: Just fish out variable settings. Save the arguments so that
# they can be reinstated for a second pass.
print STDERR "Scanning for references\n";
@save_argv = @ARGV;
# Pick up any .set directives right at the very start
while (<>)
{
last if ! /^\.set\s+(\S+)\s+(.+)$/;
$name = $1;
$value = $2;
$value =~ s/^\"?(.*?)\"?\s*$/$1/;
$references{$name} = $value;
}
# Now skip everything before the first .chapter except for
# .index lines that set up indirections. Save these so that
# the relevant index entries can be duplicated.
while (<>)
{
if (/^\.chapter\s+(.+)$/)
{
$chapter_number++;
$section_number = 0;
$current_chapter = $1;
$current_chapter = $current_chapter;
$current_section = "";
last;
}
if (/^\.index\s+([^\$]+)\s+\$it\{see\s+([^}]+)\}\s*$/)
{
$indirections{"$2"} = $1;
}
}
# Do the business
while (<>)
{
if (/^\.chapter\s+(.+)$/)
{
$current_chapter = $1;
$current_chapter = &dequote($current_chapter);
$current_section = "";
}
elsif (/^\.section\s+(.+)$/)
{
$current_section = $1;
$current_section = &dequote($current_section);
$current_section =~ s/://;
}
elsif (/^\.r?set\s+(\S+)\s+(.+)$/ && $1 ne "runningfoot")
{
$name = $1;
$value = $2;
# Only set the first time. This handles a few special cases in part2
# which is included in the filter text as well.
if (!exists($references{$name}))
{
$value =~ s/^\"?(.*?)\"?\s*$/$1/;
$value =~ s/~~chapter\./~~chapter****/;
$value =~ s/~~chapter/$current_chapter/;
$value =~ s/~~section/$current_section/;
$references{$name} = $value;
}
}
}
$final_chapter = defined($current_chapter)? $current_chapter : "";
# Reinstate ARGV with the list of files and proceed to the main pass
@ARGV = @save_argv;
# $asis is set true when processing .display asis blocks, to stop
# characters getting interpreted.
$asis = 0;
# $indisplay is set true while processing .display blocks, to stop
# .newlines being handled therein (adding @* wrecks alignment)
$indisplay = 0;
# $tab is set to the value of the tab stop - only one stop is ever used
# in the Exim source.
$tab = 0;
# Current driver name, for disambiguating nodes
$driver_name = "";
# $section_pending is set if a new section is to be started on hitting
# any data lines.
$section_pending = 0;
# Open a file to buffer up the entire set of chapters
$CHAPTERS = "/tmp/CHAPTERS.$$";
open(CHAPTERS, ">$CHAPTERS") || die "Can't open $CHAPTERS for writing";
# Skip everything before the first .chapter
while (<>) { last if /^\.chapter /; }
# Loop, handling each chapter
$current_up = "";
$previous_chapter = "Top";
$previous_node = "Top";
$chapter_number = 0;
$section_number = 0;
while (defined ($_) && /^\.chapter /)
{
handle_chapter();
}
# Output the stuff at the start of the file
print "\\input texinfo\n";
print "\@set{wmYear} 2003\n";
print "\@set{wmAuthor} Philip Hazel\n";
print "\@set{wmAuthor_email} <ph10\@\@cus.cam.ac.uk>\n";
print "\@set{COPYRIGHT1} Copyright \@copyright{} \@value{wmYear} University of Cambridge\n";
print "\@c %**start of header\n";
if (!$doing_filter)
{
print "\@setfilename spec.info\n";
print "\@settitle Exim Specification\n";
}
else
{
print "\@setfilename filter.info\n";
print "\@settitle Exim Filter Specification\n";
}
print "\@paragraphindent 0\n";
print "\@c %**end of header\n\n";
print "\@titlepage\n";
print "\@title The Exim Mail Transfer Agent\n";
print "\@author \@value{wmAuthor}\n";
print "\@page\n";
print "\@vskip 0pt plus 1filll\n";
print "Permission is granted to make and distribute verbatim copies of this manual provided the\n";
print "copyright notice and this permission notice are preserved on all copies.\n";
print "\@sp2\n";
print "\@value{COPYRIGHT1}\@*\n";
print "\@end titlepage\n\n";
# Output the top-level node and its introductory blurb
print "\@node Top, $chapter_list[0], (dir), (dir)\n";
print "\@top\n";
if (!$doing_filter)
{
print <<End;
The Exim Mail Transfer Agent\@*
****************************
The specification of the Exim Mail Transfer Agent is converted mechanically
into Texinfo format from its original marked-up source. Some typographic
representations are changed, chapters and sections cannot be numbered, and
Texinfo lacks the ability to mark updated parts of the specification with
change bars.
Because the chapters and sections are unnumbered, cross references are set to
their names. This makes the English a bit odd, with phrases like \`see chapter
\"Retry configuration\"\' but it seemed very cumbersome to change this to \`see
the chapter entitled \"Retry configuration\"\' each time.
Each chapter, section, and configuration option has been placed in a separate
Texinfo node. Texinfo doesn\'t allow commas, colons, or apostrophes in node
names, which is a rather nasty restriction. I have arranged not to use colons
or apostrophes in section titles, but cannot bring myself to omit them from
titles such as \"The foo, bar and baz commands\". For the corresponding node
names I have just used multiple occurrences of \"and\", though it looks very
ugly.
If a chapter or section continues after a list of configuration options that is
not in a new section, a new node is started, using the chapter\'s or section\'s
name plus \`(continued)\'. The \`Up\' operation from a section or configuration
option returns to the start of the current chapter; the \`Up\' operation at a
chapter start returns to the top of the document; the \`Up\' in a list of
configuration options within a section returns to the top of that section.
A number of drivers have options with the same name, so they have been
disambiguated by adding the name of the driver to its option names in order to
create node names. Thus, for example, the specification of the \`command\'
options of the \`lmtp\' and \`pipe\' transports are in nodes called \`command
(lmtp)\' and \`command (pipe)\', respectively.
End
}
else
{
print <<End;
Filtering with the Exim Mail Transfer Agent\@*
*******************************************
The specifications of the Exim Mail Transfer Agent\'s filtering facility is
converted mechanically into Texinfo format from its original marked-up source.
Some typographic representations are changed, chapters and sections cannot be
numbered, and Texinfo lacks the ability to mark updated parts of the
specification with change bars.
Because the chapters and sections are unnumbered, cross references are set to
their names. This makes the English a bit odd, with phrases like \`see section
\"Multiple personal mailboxes\"\' but it seemed very cumbersome to change this to
\`see the section entitled \"Multiple personal mailboxes\"\' each time.
End
}
# Output the top-level menu
print "\@menu\n";
while (scalar(@chapter_list))
{
$name = &decomma(shift(@chapter_list));
print "* ${name}::\n";
}
print "* Concept Index::\n" if (!$doing_filter);
print "\@end menu\n\n";
# Copy the chapters, then delete the temporary file
close(CHAPTERS);
open(CHAPTERS, "$CHAPTERS") || die "Can't open $CHAPTERS for reading";
print $buff while($buff = <CHAPTERS>);
close(CHAPTERS);
unlink($CHAPTERS);
# Output the finishing off stuff
if (!$doing_filter)
{
print "\@node Concept Index, , $final_chapter, Top\n";
print "\@chapter Concept Index\n\@printindex cp\n";
print "\@chapter Function Index\n\@printindex fn\n";
}
print "\@contents\n";
print "\@bye\n";
# End
Index: FAQ.src
====================================================================
## $Cambridge: exim/exim-doc/doc-src/FAQ.src,v 1.1 2004/10/07 15:04:35 ph10 Exp $
##
## This file is processed by Perl scripts to produce an ASCII and an HTML
## version. Lines starting with ## are omitted. The markup used with paragraphs
## is as follows:
##
## Markup User for HTML Text
## ------------------------------------------------------
## \...\ option fixed-pitch "quoted"
## \$...$\ variable $italic $plain
## \*...*\ titles, quotes italic "quoted"
## \(...)\ file name italic plain
## \[...]\ replaceable <italic> <plain>
## \?...?\ URL URL plain
## \^...^\ Unix command italic plain
## \%...%\ Exim driver bold "quoted"
## \^^.^^\ C function bold plain
## ::...:: header name italic: plain:
## //...// domain italic plain
## \/.../\ local part italic plain
## \"..."\ literal fixed-pitch "quoted"
## \\...\\ SMTP, build small caps caps
## \**...**\ warn, item bold plain
## \-...-\ cmd option -italic -plain
## \# hard space space
##
## ``...'' quoted string “...” "..."
##
## @\ is used when a real backslash is required
##
## In addition, sequences of not blank lines that start with ==> are displayed
## in fixed-pitch with no further interpretation. A line containing only [[br]]
## is removed from the text version, but turned into <br> in the HTML version.
##
## The starts of sections and of questions and answers are automatically
## detected by the scripts.
##
##
THE EXIM FAQ
------------
This is the FAQ for the Exim Mail Transfer Agent. Many thanks to the many
people who provided the original information. This file would be amazingly
cluttered if I tried to list them all. Suggestions for corrections,
improvements, and additions are always welcome.
This version of the FAQ applies to Exim 4.00 and later releases. It has been
extensively revised, and material that was relevant only to earlier releases
has been removed. As this caused some whole sections to disappear, I've taken
the opportunity to re-arrange the sections and renumber everything except the
configuration samples.
References of the form Cnnn, Fnnn, Lnnn, and Snnn are to the sample
configuration, filter, \^^local_scan()^^\, and ``useful script'' files. These
are hyperlinked from the HTML version of this FAQ. They can also be found in
the separately distributed directory called \(config.samples)\. The primary
location is
\?
ftp://ftp.csx.cam.ac.uk/pub/software/email/exim/exim4/config.samples.tar.gz?\
\?
ftp://ftp.csx.cam.ac.uk/pub/software/email/exim/exim4/config.samples.tar.bz2?\
There are brief descriptions of these files at the end of this document.
Philip Hazel
Last update: 31-March-2004
The FAQ is divided into the following sections:
0. General Debugging
1. Building and Installing
2. Routing in general
3. Routing to remote hosts
4. Routing for local delivery
5. Filtering
6. Delivery
7. Policy controls
8. Rewriting addresses
9. Headers
10. Performance
11. Majordomo
12. Fetchmail
13. Perl
14. Dial-up and ISDN
15. UUCP
16. Modifying message bodies
17. Encryption (TLS/SSL)
20. Millennium
50. Miscellaneous
91. Mac OS X
92. FreeBSD
93. HP-UX
94. BSDI
95. IRIX
96. Linux
97. Sun sytems
98. Configuration cookbook
99. List of sample configurations
0. GENERAL DEBUGGING
Q0001: Exim is crashing. What is wrong?
A0001: Exim should never crash. The author is always keen to know about
crashes, so that they can be diagnosed and fixed. However, before you
start sending me email, please check that you are running the latest
release of Exim, in case the problem has already been fixed. The
techniques described below can also be useful in trying to pin down
exactly which circumstances caused the crash and what Exim was trying to
do at the time. If the crash is reproducable (by a particular message,
say) keep a copy of that message.
Q0002: Exim is not working. What is wrong? How can I check what it is doing?
A0002: Exactly how is it not working? Check the more specific questions in the
other sections of this FAQ. Some general techniques for debugging are:
(1) Look for information in Exim's log files. These are in the \(log)\
directory in Exim's spool directory, unless you have configured a
different path for them. Serious operational problems are reported
in paniclog.
(2) If the problem involves the delivery of one or more messages, try
forcing a delivery with the \-M-\ option and also set the \-d-\
option, to cause Exim to output debugging information. For example:
==> exim -d -M 0z6CXU-0005RR-00
The output is written to the standard error stream. You need to have
admin privileges to use \-M-\ and \-d-\.
(3) If the problem involves incoming SMTP mail, try using the \-bh-\
option to simulate an incoming connection from a specific host,
for example:
==> exim -bh 10.9.8.7
This goes through the motions of an SMTP session, without actually
accepting a message. Information about various policy checks is
output. You will need to know how to pretend to be an SMTP client.
(4) If the problem involves lack of recognition or incorrect handling
of local addresses, try using the \-bt-\ option with debugging turned
on, to see how Exim is handling the address. For example,
==> exim -d -bt z6abc
shows you how it would handle the local part \"z6abc"\.
Q0003: What does the error \*Child process of address_pipe transport returned
69 from command xxx*\ mean?
A0003: It means that when a transport called \%address_pipe%\ was run to pass an
email message by means of a pipe to another process running the command
xxx, the return code from that command was 69, which indicates some kind
of error (the success return code is 0).
The most common meaning of exit code 69 is ``unavailable'', and this often
means that when Exim tried to run the command \(xxx)\, it failed. One
cause of this might be incorrect permissions on the file containing the
command. See also Q0026.
Q0004: My virtual domain setup isn't working. How can I debug it?
A0004: You can use an exim command with \-d-\ to get it to show you how it is
processing addresses. You don't actually need to send a message; use the
\-bt-\ option like this:
==> exim -d -bt localpart@virtualhost
This will show you which routers it is using. If the problem appears
to be with the expansion of an option setting, you can use the
\debug_print\ option on a router to get Exim to output the expanded
string values as it goes along.
Q0005: Why is Exim not rejecting incoming messages addressed to non-existent
users at SMTP time?
A0005: This is controlled by the ACL that is run for each incoming RCPT
command. It is defined by the \acl_smtp_rcpt\ option. You can check this
part of your configuration by using the \-bh-\ option to run a simulated
SMTP session, during which Exim will tell you what things it is
checking.
Q0006: I've put an entry for \"*.my.domain"\ in a DBM lookup file, but it isn't
getting recognized.
A0006: You need to request ``partial matching'' by setting the search type to
\partial-dbm\ in order for this to work.
Q0007: I've put the entry \"*@domain.com"\ in a lookup database, but it isn't
working. The expansion I'm using is:
==> ${lookup{${lc:$sender_address}}dbm{/the/file} ...
A0007: As no sender address will ever be //*@domain.com// this will indeed have
no effect as it stands. You need to tell Exim that you want it to look
for defaults after the normal lookup has failed. In this case, change the
search type from \"dbm"\ to \"dbm*@"\. See the section on \*Default values in
single-key lookups*\ in the chapter entitled \*File and database
lookups*\ in the Exim manual.
Q0008: If I run \"./exim -d -bt user@domain"\ all seems well, but when I send
a message from my User Agent, it does not arrive at its destination.
A0008: Try sending a message directly to Exim by typing this:
==> exim -v user@domain
<some message, could be empty>
.
If the message gets delivered to a remote host, but never arrives at its
final destination, then the problem is at the remote host. If, however,
the message gets through correctly, then the problem may be between your
User Agent and Exim. Try setting Exim's \log_selector\ option to include
\"+arguments"\, to see with which arguments the UA is calling Exim.
Q0009: What does \*no immediate delivery: too many messages received in one SMTP
connection*\ mean?
A0009: An SMTP client may send any number of messages down a single SMTP
connection to a server. Initially, an Exim server starts up a delivery
process as soon as a message is received. However, in order not to start
up too many processes when lots of messages are arriving (typically
after a period of downtime), it stops doing immediate delivery after a
certain number of messages have arrived down the same connection. The
threshold is set by \smtp_accept_queue_per_connection\, and the default
value is 10. On large systems, the value should be increased. If you are
running a dial-in host and expecting to get all your mail down a single
SMTP connection, then you can disable the limit altogether by setting
the value to zero.
Q0010: Exim puts \*for \[address]\*\ in the ::Received:: headers of some, but not all,
messages. Is this a bug?
A0010: No. It is deliberate. Exim inserts a ``for'' phrase only if the incoming
message has precisely one recipient. If there is more than one
recipient, nothing is inserted. The reason for this is that not all
recipients appear in the ::To:: or ::Cc:: headers, and it is considered a
breach of privacy to expose such recipients to the others. A common
case is when a message has come from a mailing list.
Q0011: Instead of \^exim_dbmbuild^\, I'm using a homegrown program to build DBM
(or cdb) files, but Exim doesn't seem to be able to use them.
A0011: Exim expects there to be a binary zero value on the end of each key used
in a DBM file if you use the \"dbm"\ lookup type, but not for the \"dbmnz"\
lookup type or for the keys of a cdb file. Check that you haven't
slipped up in this regard.
Q0012: Exim is unable to route to any remote domains. It doesn't seen to be
able to access the DNS.
A0012: Try running \"exim -d+resolver -bt \[remote address]\"\. The \-d-\
options turns on debugging output, and the addition of \"+resolver"\
will make it show the resolver queries it is building and the results of
its DNS queries. If it appears unable to contact any name servers, check
the contents and permissions of \(/etc/resolv.conf)\.
Q0013: What does the error message \*transport system_aliases: cannot find
transport driver "redirect" in line 92*\ mean?
A0013: \%redirect%\ is a router, not a transport. You have put a configuration
for a router into the transports section of the configuration file.
Q0014: Exim is timing out after receiving and responding to the DATA command
from one particular host, and yet the client host also claims to be
timing out. This seems to affect only certain messages.
A0014: This kind of problem can have many different causes.
(1) This problem has been seen with a network that was dropping all
packets over a certain size, which mean that the first part of the SMTP
transaction worked, but when the body of a large message started
flowing, the main data bits never got through the network. See also
Q0017.
(2) This can also happen if a host has a broken TCP stack and won't
reassemble fragmented datagrams.
(3) A very few ISDN lines have been seen which failed when certain data
patterns were sent through them, and replacing the routers at both end
of the link did not fix things. One of them was triggered by more than 4
X's in a row in the data.
Q0015: What does the message \*Socket bind() to port 25 for address (any)
failed: address already in use*\ mean?
A0015: You are trying to run an Exim daemon when there is one already running -
or maybe some other MTA is running, or perhaps you have an SMTP line in
\(/etc/inetd.conf)\ which is causing \(inetd)\ to listen on port 25.
Q0016: I've set \"verify = header_syntax"\ in my ACL, but this causes Exim to
complain about header lines like \"To: Work: Jim <jims@email>,
Home: Bob <bobs@email>"\ which look all right to me. Is this a bug?
A0016: No. Header lines such as ::From::, ::To::, etc., which contain addresses, are
structured, and have to be in a specific format which is defined in RFC
2822. Unquoted colons are not allowed in the ``phrase'' part of an email
address (they are OK in other headers such as ::Subject::). The correct
form for that header is
==> To: "Work: Jim" <jims@email>, "Home: Bob" <bobs@email>
You will sometimes see unquoted colons in ::To:: and ::Cc:: headers, but only
in connection with name lists (called ``groups''), for example:
==> To: My friends: X <x@???>, Y <y@???>;,
My enemies: A <a@???>, B <b@???>;
Each list must be terminated by a semicolon, as shown.
Q0017: Whenever Exim tries to deliver a specific message to a particular
server, it fails, giving the error \*Remote end closed connection after
data*\ or \*Broken pipe*\ or a timeout. What's going on?
A0017: \*Broken pipe*\ is the error you get on some OS when the remote host just
drops the connection. The alternative is \*connection reset by peer*\.
There are many potential causes. Here are some of them (see also Q0068):
(1) There are some firewalls that fall over on binary zero characters
in email. Have a look, e.g. with \"hexdump -c mymail | tail"\ to see if
your mail contains any binary zero characters.
(2) There are broken SMTP servers around that just drop the connection
after the data has been sent if they don't like the message for some
reason (e.g. it is too big) instead of sending a 5xx error code. Have
you tried sending a small message to the same address?
It has been reported that some releases of Novell servers running NIMS
are unable to handle lines longer than 1024 characters, and just close
the connection. This is an example of this behaviour.
(3) If the problem occurs right at the start of the mail, then it could
be a network problem with mishandling of large packets. Many emails are
small and thus appear to propagate correctly, but big emails will
generate big IP datagrams.
There have been problems when something in the middle of the network
mishandles large packets due to IP tunnelling. In a tunnelled link, your
IP datagrams gets wrapped in a larger datagram and sent over a network.
This is how virtual private networks (VPNs), and some ISP transit
circuits work. Since the datagrams going over the tunnel require a
larger packet size, the tunnel needs a bigger maximum transfer unit
(MTU) in the network handling the tunnelled packets. However, MTUs
are often fixed, so the tunnel will try to fragment the packets.
If the systems outside the tunnel are using path MTU discovery, (most
Sun Sparc Solaris machines do by default), and set the DF (don't
fragment) bit because they don't send packets larger than their \(local)\
MTU, then ICMP control messages will be sent by the routers at the
ends of the tunnel to tell them to reduce their MTU, since the tunnel
can't fragment the data, and has to throw it away. If this mechanism
stops working, e.g. a firewall blocks ICMP, then your host never
knows it has hit the maximum path MTU, but it has received no ACK on
the packet either, so it continues to resend the same packet and the
connection stalls, eventually timing out.
You can test the link using pings of large packets and see what works:
==> ping -s host 2048
Try reducing the MTU on the sending host:
==> ifconfig le0 mtu 1300
Alternatively, you can reduce the size of the buffer Exim uses for SMTP
output by putting something like
==> DELIVER_OUT_BUFFER_SIZE=512
in your \(Local/Makefile)\ and rebuilding Exim (the default is 8192).
While this should not in principle have any effect on the size of
packets sent, in practice it does seem to have an effect on some OS.
You can also try disabling path MTU discovery on the sending host. On
Linux, try:
==> echo 1 >/proc/sys/net/ipv4/ip_no_pmtu_disc
For a general discussion and information about other operating systems, see
\?http://www.netheaven.com/pmtu.html?\. If disabling path MTU discovery
fixes the problem, try to find the broken or misconfigured
router/firewall that swallows the ICMP-unreachable packets. Increasing
timeouts on the receiving host will not work around the problem.
Q0018: Why do messages not get delivered down the same connection when I do
something like: \"exim -v -R @aol.com"\? For other domains, I do this and
I see the appropriate \*waiting for passed connections to get used*\
messages.
A0018: Recall that Exim does not keep separate queues for each domain, but
operates in a distributed fashion. Messages get into its `waiting for
host x' hints database only when a delivery has been tried, and has had
a temporary error. Here are some possibilities:
(1) The messages to \(aol.com)\ got put in your queue, but no previous
delivery attempt occured before you did the \-R-\. This might have been
because of your settings of \queue_only_load\, \smtp_accept_queue\, or any
other option that caused no immediate delivery attempt on arrival. If
this is the case, you can try using \-qqR-\ instead of \-R-\.
(2) You have set \connection_max_messages\ on the smtp transport, and
that limit was reached. This would show as a sequence of messages
down one connection, then another sequence down a new connection, etc.
(3) Exim tried to pass on the SMTP connection to another message, but
that message was in the process of being delivered to \(aol.com)\ by some
other process (typically, a normal queue runner). This will break the
sequence, though the other delivery should pass its connection on to
other messages if there are any.
(4) The folk at \(aol.com)\ changed the MX records so the host names have
changed - or a new host has been added. I don't know how likely this is.
(5) Exim is not performing as it should in this regard, for some reason.
Next time you have mail queued up for \(aol.com)\, try running
==> exim_dumpdb /var/spool/exim wait-remote_smtp
to see if those messages are listed among those waiting for the relevant
\(aol.com)\ hosts.
Q0019: There seems to be a problem in the string expansion code: it doesn't
recognize references to headers such as \"${h_to}"\.
A0019: The only valid syntax for header references is (for example) \"$h_to:"\
because header names are permitted by RFC 2822 to contain a very wide
range of characters. A colon (or white space) is required as the
terminator.
Q0020: Why do connections to my machine's SMTP port take a long time to respond
with the banner, when connections to other ports respond instantly? The
delay is sometimes as long as 30 seconds.
A0020: These kinds of delay are usually caused by some kind of network problem
that affects outgoing calls made by Exim at the start of an incoming
connection. Configuration options that cause outgoing calls are:
(1) \rfc1413_hosts\ and \rfc1413_query_timeout\ (for \*ident*\ calls).
Firewalls sometimes block ident connections so that they time out,
instead of refusing them immediately. This can cause this problem.
See Q5023 for a discussion of the usefulness of \*ident*\.
(2) The \host_lookup\ option, the \host_reject_connection\ option, or a
condition in the ACL that runs at connection time requires the
remote host's name to be looked up from its IP address. Sometimes
these DNS lookups time out. You can get this effect with ACL
statements like this:
==> deny hosts = *.x.example
If at all possible, you should use IP addresses instead of host
names in blocking lists in order to to avoid this problem.
You can use the \-bh-\ option to get more information about what is
happening at the start of a connection. However, note that the \-bh-\
option does not provide a complete simulation. In particular, no
\*ident*\ checks are done, so it won't show up a delay problem that is
related to (1) above.
Q0021: What does \*failed to create child process to send failure message*\ mean?
This is a busy mail server with \smtp_accept_max\ set to 500, but this
problem started to occur at about 300 incoming connections.
A0021: Some message delivery failed, and when Exim wanted to send a bounce
message, it was unable to create a process in which to do so. Probably
the limit on the maximum number of simultaneously active processes has
been reached. Most OS have some means of increasing this limit, and in
some operating systems there is also a limit per uid which can be
varied.
Q0022: What does \*No transport set by system filter*\ in a log line mean?
A0022: Your system filter contains a \"pipe"\ or \"save"\ or \"mail"\ command,
but you have not set the corresponding option which specifies which
transport is to be used. You need to set whichever of
\system_filter_pipe_transport\, \system_filter_file_transport\ or
\system_filter_reply_transport\ is relevant.
Q0023: Why is Exim refusing to relay, saying \*failed to find host name from IP
address*\ when I have the sender's IP address in an ACL condition? My
configuration contains this ACL statement:
==> accept hosts = lsearch;/etc/mail/relaydomains:192.168.96.0/24
A0023: When checking a host list, the items are tested in left-to-right
order. The first item in your list is a lookup on the incoming host's
name, so Exim has to determine the name from the incoming IP address in
order to perform the test. If it can't find the host name, it can't do
the check, so it gives up. You would have discovered what was going
on if you had run a test such as
==> exim -bh 192.168.96.131
The solution is to put all explicit IP addresses first in the list.
Alternatively, you can split the ACL statement into two like this:
==> accept hosts = lsearch;/etc/mail/relaydomains
accept hosts = 192.168.96.0/24
If the host lookup fails, the first \"accept"\ fails, but then the
second one is considered.
Q0024: When I run \"exim -bd -q10m"\ I get \*PANIC LOG: exec of exim -q failed*\.
A0024: This probably means that Exim doesn't know its own path so it can't
re-exec itself to do the first queue run. Check the output of
==> exim -bP exim_path
Q0025: I can't seem to get a pipe command to run when I include a \"${if"\
expansion in it. This fails:
==> command = perl -T /usr/local/rt/bin/rtmux.pl \
rt-mailgate helpdesk \
${if eq {$local_part}{rt} {correspond}{action}}
A0025: You need some internal quoting in there. Exim expands each individual
argument separately. Because you have (necessarily) got spaces in your
\"${if"\ item, you have to quote that argument. Try
==> command = perl -T /usr/local/rt/bin/rtmux.pl \
rt-mailgate helpdesk \
"${if eq {$local_part}{rt} {correspond}{action}}"
\**Warning:**\ If command starts with an item that requires quoting,
you cannot just put it in quotes, because a leading quote means that the
entire option setting is being quoted. What you have to do is to quote
the entire value, and use internally escaped quotes for the ones you
really want. For example:
==> command = "\"${if ....}\" arg1 arg2"
Any backslashes in the expansion items will have to be doubled to stop
them being interpreted by the string reader.
Q0026: I'm trying to get Exim to connect an alias to a pipe, but it always
gives error code 69, with the comment \*(could mean service or program
unavailable)*\.
A0026: If your alias entry looks like this:
==> alias: |"/some/command some parameters"
change it to look like this:
==> alias: "|/some/command some parameters"
Q0027: What does the error \*Spool file is locked*\ mean?
A0027: This is not an error. All it means is that when an Exim delivery
process (probably started by a queue runner process) looked at a message
in order to start delivering it, it found that another Exim process was
already busy delivering it. On a busy system this is quite a common
occurrence. If you set \"-skip_delivery"\ in the \log_selector\ option,
these messages are omitted from the log.
The only time when this message might indicate a problem is if it is
repeated for the same message for a very long time. That would suggest
that the process that is delivering the message has somehow got stuck.
Q0028: Exim is reporting IP addresses as 0.0.0.0 or 255.255.255.255 instead of
their correct values. What's going on?
A0028: You are using a version of Exim built with gcc on an IRIX box.
See Q9502.
Q0029: I can't seem to figure out why PAM support doesn't work correctly.
A0029: There is a problem using PAM with shadow passwords when the calling
program is not running as \/root/\. Exim is normally running as the
Exim user when authenticating a remote host. See this posting for one
way round the problem:
\?http://www.exim.org/mailman/htdig/exim-users/Week-of-Mon-20010917/030371.html?\
Another solution can be found at \?http://www.e-admin.de/pam_exim/?\.
PAM 0.72 allows authorization as non-\/root/\, using setuid helper programs.
Furthermore, in \(/etc/pam.d/exim)\ you can explicitelly specify that
this authorization (using setuid helpers) is only permitted for certain
users and groups.
Q0030: I'm trying to use a query-style lookup for hosts that are allowed to
relay, but it is giving really weird errors.
A0030: Does your query contain a colon character? Remember that host lists are
colon-separated, so you need to double any colons in the query. This
applies even if the query is defined as a macro.
Q0031: Exim is rejecting connections from hosts that have more than one IP
address, for no apparent reason.
A0031: You are using Solaris 7 or earlier, and have \"nis dns files"\ in
\(/etc/nsswitch.conf)\. Change this to \"dns nis files"\ to avoid hitting Sun
bug 1154236 (a bad interaction between NIS and the DNS).
Q0032: Exim is failing to find the MySQL library, even though is it present
within \\LD_LIBRARY_PATH\\. I'm getting this error:
==> /usr/local/bin/exim: fatal: libmysqlclient.so.6: open failed:
No such file or directory
A0032: Exim is suid, and \\LD_LIBRARY_PATH\\ is ignored for suid binaries on a
Solaris (and other?) systems. What you should be doing is adding
\"-R/local/lib/mysql"\ to the same place in the compilation that you added
\"-L/local/lib/mysql"\. This tells the binary where to look without
needing a path variable.
Q0033: What does the error \*lookup of host "xx.xx.xx" failed in yyy router*\
mean?
A0033: You configured a \%manualroute%\ router to send the message to xx.xx.xx. When
it tried to look up the IP address for that host, the lookup failed
with a permanent error. As this is a manual routing, this is a
considered to be a serious error which the postmaster needs to know
about (maybe you have a typo in your file), and there is little point
in keeping on trying. So it freezes the message.
(1) Don't set up routes to non-existent hosts.
(2) If you must set up routes to non-existent hosts, and don't want
freezing, set the \host_find_failed\ option on the router to do something
other than freeze.
Q0034: Exim works fine on one host, but when I copied the binary to another
identical host, it stopped working (it could not resolve DNS names).
A0034: Is the new host running exactly the same operating system? Most
importantly, are the versions of the dynamically loaded libraries
(files with names like \(libsocket.so.1)\) the same on both systems? If not,
that is probably the cause of the problem. Either arrange for the
libraries to be the same, or rebuild Exim from source on the new host.
Q0035: I set a \"hosts"\ condition in an ACL to do a lookup in a file of IP
addresses, but it doesn't work.
A0035: Did you remember to put \"net-"\ at the start of the the search type? If
you set something like this:
==> accept hosts = lsearch;/some/file
Exim searches the file for the host name, not the IP address. You need
to set
==> accept hosts = net-lsearch;/some/file
to make it use the IP address as the key to the lookup.
Q0036: Why do I get the error \*Permission denied: creating lock file hitching
post*\ when Exim tries to do a local delivery?
A0036: Your configuration specifies that local mailboxes are all held in
single directory, via configuration lines like these (taken from the
default configuration):
==> local_delivery:
driver = appendfile
file = /var/mail/$local_part
and the permissions on the directory probably look like this:
==> drwxrwxr-x 3 root mail 512 Jul 9 13:48 /var/mail/
Using the default configuration, Exim runs as the local user when doing
a local delivery, and it uses a lock file to prevent any other process
from updating the mailbox while it is writing to it. With those
permissions the delivery process, running as the user, is unable to
create a lock file in the \(/var/mail(\ directory. There are two solutions
to this problem:
(1) Set the \"write"\ and \"sticky bit"\ permissions on the directory, so
that it looks like this:
==> drwxrwxrwt 3 root mail 512 Jul 9 13:48 /var/mail/
The \"w"\ allows any user to create new files in the directory, but
the \"t"\ bit means that only the creator of a file is able to remove
it. This is the same setting as is normally used with the \(/tmp)\
directory.
(2) Arrange to run the local_delivery transport under a specific group
by changing the configuration to read
==> local_delivery:
driver = appendfile
file = /var/mail/${local_part}
group = mail
The delivery process still runs under the user's uid, but with the
group set to \"mail"\. The group permission on the directory allows
the process to create and remove the lock file.
The choice between (1) and (2) is up to the administrator. If the
second solution is used, users can empty their mailboxes by updating
them, but cannot delete them.
If your problem involves mail to \/root/\, see also Q0507.
Q0037: I am experiencing mailbox locking problems with Sun's \"mailtool"\ used
over a network.
A0037: See Q9705 in the Sun-specific section below.
Q0038: What does the error message \*error in forward file (filtering not
enabled): missing or malformed local part*\ mean?
A0038: If you are trying to use an Exim filter, you have forgotten to enable
the facility, which is disabled by default. In the \%redirect%\ router
(in the Exim run time configuration file) you need to set
==> allow_filter = true
to allow a \(.forward)\ file to be used as an Exim filter. If you are not
trying to use an Exim filter, then you have put a malformed address in
the \(.forward)\ file.
Q0039: I have installed Exim, but now I can't mail to \/root/\ any more. Why is
this?
A0039: Most people set up \/root/\ as an alias for the manager of the host. If
you haven't done this, Exim will attempt to deliver to \/root/\ as if it
were a normal user. This isn't really a good idea because the delivery
process would run as \/root/\. Exim has a trigger guard in the option
==> never_users = root
in the default configuration file. This prevents it from running as \/root/\
when doing any deliveries. If you really want to run local deliveries as
\/root/\, remove this line, but it would be better to create an alias for
\/root/\ instead.
Q0040: How can I stop undeliverable bounce messages (e.g. to routeable, but
undeliverable, spammer senders) from clogging up the queue for days?
A0040: If at all possible, you should try to avoid getting into this situation
in the first place, for example, by verifying recipients so that you
do not accept undeliverable messages that lead to these bounces.
You can, however, configure Exim to discard failing bounce messages
early. Just set \ignore_bounce_errors_after\ to specify a (short) time
to keep them for.
Q0041: What does the message \*unable to set gid=ddd or uid=ddd (euid=ddd):
local delivery to ... transport=ttt*\ mean?
A0041: Have you remembered to make Exim setuid \/root/\? It needs root privilege if
it is to do any local deliveries, because it does them ``as the user''.
Note also that the partition from which Exim is running (where the
binary is installed) must not have the \nosuid\ mount option set. You
can check this by looking at its \(/etc/fstab)\ entry (or \(/etc/vfstab)\,
depending on your OS).
Q0042: My ISP's mail server is rejecting bounce messages from Exim, complaining
that they have no sender. The SMTP trace does indeed show that the
sender address is \"<>"\. Why is the Sender on the bounce message empty?
A0042: Because the RFCs say it must be. Your ISP is at fault. Send them this
extract from RFC 2821 section 6.1 (\*Reliable Delivery and Replies by
Email*\):
If there is a delivery failure after acceptance of a message, the
receiver-SMTP MUST formulate and mail a notification message. This
notification MUST be sent using a null (\"<>"\) reverse path in the
envelope. The recipient of this notification MUST be the address
from the envelope return path (or the ::Return-Path:: header line).
However, if this address is null (\"<>"\), the receiver-SMTP MUST NOT
send a notification.
The reason that bounce messages have no sender is so that they
themselves cannot provoke further bounces, as this could lead to a
unending exchange of undeliverable messages.
Q0043: What does the error \*Unable to get interface configuration: 22 Invalid
argument*\ mean?
A0043: This is an error that occurs when Exim is trying to find out the all the
IP addresses on all of the local host's interfaces. If you have lots of
virtual interfaces, this can occur if there are more than around 250 of
them. The solution is to set the option \local_interfaces\ to list just
those IP addresses that you want to use for making and receiving SMTP
connections.
Q0044: What does the error \*Failed to create spool file*\ mean?
A0044: Exim has been unable to create a file in its spool area in which to
store an incoming message. This is most likely to be either a
permissions problem in the file hierarchy, or a problem with the uid
under which Exim is running, though it could be something more drastic
such as your disk being full.
If you are running Exim with an alternate configuration file using a
command such as \"exim -C altconfig..."\, remember that the use of -C
takes away Exim's root privilege.
Check that you have defined the spool directory correctly by running
==> exim -bP spool_directory
and examining the output. Check the mode of this directory. It should
look like this, assuming you are running Exim as user \/exim/\:
==> drwxr-x--- 6 exim exim 512 Jul 16 12:29 /var/spool/exim
If there are any subdirectories already in existence, they should have
the same permissions, owner, and group. Check also that you haven't got
incorrect permissions on superior directories (for example, \(/var/spool)\).
Check that you have set up the Exim binary to be setuid \/root/\. It should
look like this:
==> -rwsr-xr-x 1 root xxx 502780 Jul 16 14:16 exim
Note that it is not just the owner that must be \/root/\, but also the third
permission must be \"s"\ rather than \"x"\.
Q0045: I see entries in the log that mention two different IP addresses for the
same connection. Why is this? For example:
==> H=tip-mp8-ncs-13.stanford.edu ([36.173.0.189]) [36.173.0.156]
A0045: The actual IP address from which the call came is the final one.
Whenever there's something in parentheses in a host name, it is what the
host quoted as the domain part of an SMTP HELO or EHLO command. So in
this case, the client, despite being 36.173.0.156, issued the command
==> EHLO [36.173.0.189]
when it sent your server the message. This is, of course, very
misleading.
Q0046: A short time after I start Exim I see a defunct zombie process. What
is causing this?
A0046: Your system must be lightly loaded as far as mail is concerned. The
daemon sets off a queue runner process when it is started, but it only
tidies up completed child processes when it wakes up for some other
reason. When there's nothing much going on, you occasionally see
defunct processes like this waiting to be dealt with. This is
perfectly normal.
Q0047: On a reboot, or a restart of the mail system, I see the message \*Mailer
daemons: exim abandoned: unknown, malformed, or incomplete option
-bz sendmail*\. What does this mean?
A0047: \-bz-\ is a Sendmail option requesting it to create a `configuration freeze
file'. Exim has no such concept and so does not support the option. You
probably have a line like
==> /usr/lib/sendmail -bz
in some start-up script (e.g. \(/etc/init.d/mail)\) immedately before
==> /usr/lib/sendmail -bd -q15m
The first of these lines should be commented out.
Q0048: Whenever exim restarts it takes up to 3-5 minutes to start responding on
the SMTP port. Why is this?
A0048: Something else is hanging onto port 25 and not releasing it. One place
to look is \(/etc/inetd.conf)\ in case for any reason an SMTP stream is
configured there.
Q0049: What does the log message \*no immediate delivery: more than 10 messages
received in one connection*\ mean?
A0049: A remote MTA sent a number of messages in a single SMTP session. Exim
limits the number of immediate delivery processes it creates as a
result of a single SMTP connection, in order to avoid creating a zillion
processes on systems that can have many incoming connections. If you are
dialing in to collect mail from your ISP, you should probably set
\smtp_accept_queue_per_connection\ to some number larger than 10, or
arrange to start a queue runner for local delivery (using \-ql-\)
immediately after collecting the mail.
Q0050: I am getting complaints from a customer who uses my Exim server for
relaying that they are being blocked with a \*Too many connections*\
error.
A0050: See \smtp_accept_max\, \smep_accept_max_per_host\ and \smtp_accept_reserve\.
Q0051: When I try \"exim -bf"\ to test a system filter, I received the following
error message: \*Filter error: unavailable filtering command "fail" near
line 8 of filter file*\.
A0051: Use the \-bF-\ option to test system filters. This gives you access to the
freeze and fail actions.
Q0052: What does \*ridiculously long message header*\ in an error report mean?
A0052: There has to be some limit to the length of a message's header lines,
because otherwise a malefactor could open an SMTP channel to your host,
start a message, and then just send characters continuously until your
host ran out of memory. (Exim stores all the header lines in main
memory while processing a message). For this reason a limit is imposed
on the total amount of memory that can be used for header lines. The
default is 1MB, but this can be changed by setting \\HEADER_MAXSIZE\\ in
\(Local/Makefile)\ before building Exim. Exceeding the limit provokes
the ``ridiculous'' error message.
Q0053: Exim on my host responds to a connection with \"220 *****..."\ and
won't understand \\EHLO\\ commands.
A0053: This is the sign of a Cisco Pix ``Mailguard'' sitting in front of your
MTA. Pix breaks ESMTP and only does SMTP. It is a nuisance when you have
a secure MTA running on your box. Something like ``no fixup protocol
smtp 25'' in the Pix configuration is needed. It may be possible to do
this by logging into the Pix (using \^telnet^\ or \^ssh^\) and typing
\"no fixup smtp"\ to its console. (You may need to use other commands
before or after to set up configuration mode and to activate a changed
configuration. Consult your Pix documentation or expert.) See also
Q0078.
Q0054: I'm getting an Exim configuration error \*unknown rewrite flag
character (m) in line 386*\ but I haven't used any flags on my rewriting
rules.
A0054: You have probably forgotten to quote a replacement string that contains
white space.
Q0055: What does the error \*Failed to open wait-remote_smtp database: Invalid
argument*\ mean?
A0055: This is something that happens if you have existing DBM hints files when
you install a new version of Exim that is compiled to use a different or
upgraded DBM library. The simplest thing to try is
==> rm /var/spool/exim/db/*
This removes all the hints files. Exim will start afresh and build new
ones. If the symptom recurs, it suggests there is some problem with your
DBM library.
Q0056: We are using Exim to send mail from our web server. However, whenever a
user sends an email it gets sent with the return path (envelope sender)
//apache@server_name.com// because the PHP script is running as
\/apache/\.
A0056: You need to include \/apache/\ in the \trusted_users\ configuration option.
Only trusted users are permitted to specify senders when mail is passed
to Exim via the command line.
Q0057: We've got people complaining about attachments that don't show up
as attachments, but are included in the body of the message.
A0057: These symptoms can be seen when some software passes a CRLF line
terminated message via the command line to an MTA that expects lines to
be terminated by LF only, and so preserves the CRs as data. If you can
identify the software that is doing this, try setting the \-dropcr-\
option on the command it uses to call Exim. Alternatively, you can set
\drop_cr\ in the configuration file, but then that will apply to all
input.
Q0058: What does the error \*failed to open DB file \(/var/spool/exim/db/retry)\:
File exists*\ mean?
A0058: This error is most often caused when a hints file that was written with
one version of the Berkeley DB library is read by another version.
Sometimes this can happen if you change from a binary version of Exim to
a locally compiled version. Or it can happen if you compile and install
a new version of Exim after changing Berkeley DB versions. You can find
out which version your Exim is using by running:
==> ldd /usr/sbin/exim
The solution to the problem is to delete all the files in the
\(/var/spool/exim/db)\ directory, and let Exim recreate them.
Q0059: When my Outlook Express 6.0 client sends a STARTTLS command to begin a
TLS session, Exim doesn't seem to receive it. The Outlook log shows
this:
==> SMTP: 14:19:27 [tx] STARTTLS
SMTP: 14:19:27 [rx] 500 Unsupported command.
but the Exim debugging output shows this:
==> SMTP<< EHLO xxxx
SMTP>> 250-yyyy Hello xxxx [nnn.nnn.nnn.nnn]
250-SIZE 52428800
250-PIPELINING
250-AUTH CRAM-MD5 PLAIN LOGIN
250-STARTTLS
250 HELP
SMTP<< QUIT
A0059: Turn off scanning of outgoing email in Norton Antivirus. If you aren't
running Norton Antivirus, see if you are running some other kind of SMTP
proxying, either on the client or on a firewall between the client and
server. ``Unsupported command'' is not an Exim message.
Q0060: Why am I getting the error \*failed to expand \"/data/lists/lists/${lc"\
for require_files: \"${lc"\ is not a known operator*\ for this setting:
==> require_files = MAILMAN_HOME/lists/${lc:$local_part}/config.db
A0060: The value of \"require_files"\ is a \*list*\ in which each item is
separately expanded. You need either to double the colon, or switch to
a different list separator.
Q0061: What does the error \*Too many ``Received'' headers - suspected mail
loop*\ mean?
A0061: Whenever a message passes through an MTA, a ::Received:: header gets
added. Exim counts the number of these headers in incoming messages. If
there are more than the value of \received_headers_max\ (default 30),
Exim assumes there is some kind of mail routing loop occurring. For
example, host A passes the message to host B, which immediately passes
it back to host A. Check the ::Received:: headers and the mail logs to
determine exactly what is going on.
One common cause of this problem is users with accounts on both systems
who set up each one to forward to the other, thinking that will cause
copies of all messages to be delivered on both of them.
Q0062: When I try to start an Exim daemon it crashes. I ran a debugger and
discovered that the crash is happening in the function \^^getservbyname()^^\.
What's going on?
A0062: What have you got in the file \(/etc/nsswitch.conf)\? If it contains this
line:
==> services: db files
try removing the \"db"\. (Your system is trying to look in some kind of
database before searching the file \(/etc/services)\.)
Q0063: When I try to start an Exim daemon, nothing happens. There is no
process, and nothing is written to the Exim log.
A0063: Check to see if anything is written to \(syslog)\. This problem can be
caused by a permission problem that stops Exim from writing to its log
files, especially if you've specified that they should be written
somewhere other than under Exim's spool directory. You could also try
running the daemon with debugging turned on.
Q0064: When I run \"exim -d test@domain"\ it delivers fine, but when I send a
message from the \^mail^\ command, I get \*User unknown*\ and the mail
is saved in \(dead.letter)\.
A0064: It looks as if Exim isn't being called by \^mail^\; instead it is
calling some other program (probably Sendmail). Try running the command
==> /usr/sbin/sendmail -bV
(If you get \*No such file or directory*\ or \*Command not found*\ you
are running Solaris or IRIX. Try again with \(/usr/lib/sendmail)\.) The
output should be something like this:
==> Exim version 4.05 #1 built 13-Jun-2002 10:27:15
Copyright (c) University of Cambridge 2002
If you don't see this, your Exim installation isn't fully operational.
If you are running FreeBSD, see Q9201. For other systems, see Q0114.
Q0065: When (as \/root/\) I use -C to run Exim with an alternate configuration
file, it gives an error about being unable to create a spool file when
trying to run an \%autoreply%\ transport. Why is this?
A0065: When Exim is called with -C, it passes on -C to any instances of itself
that it calls (so that the whole sequence uses the same config file). If
it's running as \/exim/\ when it does this, all is well. However, if it
happens as a consequence of a non-privileged user running \%autoreply%\,
the called Exim gives up its root privilege. Then it can't write to the
spool.
This means that you can't use -C (even as \/root/\) to run an instance of
Exim that is going to try to run \%autoreply%\ from a process that is
neither \/root/\ nor \/exim/\. Because of the architecture of Exim (using
re-execs to regain privilege), there isn't any way round this
restriction. Therefore, the only way you can make this scenario work is
to run the \%autoreply%\ transport as \/exim/\ (that is, the user that
owns the Exim spool files). This may be satisfactory for autoreplies
that are essentially system-generated, but of course is no good for
autoreplies from unprivileged users, where you want the \%autoreply%\
transport to be run as the user. To get that to work with an alternate
configuration, you'll have to use two Exim binaries, with different
configuration file names in each. See S001 for a script that patches
the configuration name in an Exim binary.
Q0066: What does the message \*unable to set gid=xxx or uid=xxx*\ mean?
A0066: This message is given when an Exim process is unable to change uid or
gid when it needs to, because it does not have root privilege. This is a
serious problem that prevents Exim from carrying on with what it is
doing. The two most common situations where Exim needs to change uid/gid
are doing local deliveries and processing users' filter files. There are
two common causes of this error:
(1) You have forgotten to make the exim binary setuid to \/root/\. This
means that it can never change uid/gid in any situation. Also, the
setuid binary must reside on a disk partition that does not have the
\"nosuid"\ mount option set.
(2) The exim binary is setuid, but you have configured Exim so that,
while trying to verify an address at SMTP time, it runs a router
that needs to change uid/gid. Because Exim runs as \/exim/\ and not
\/root/\ while receiving messages, the router is unable to change
uid and therefore it cannot operate. The usual example of this is a
\%redirect%\ router for users' filter files.
Setting the \user\ or \check_local_user\ options on a \redirect\
router causes this to happen (except in the special case when the
redirection list is provided by the \data\ option and does not
contain \":include:"\).
The solution is to set \no_verify\ on the router that is causing the
problem. This means that it is skipped when an address is being
verified. In ``normal'' configurations where the router is indeed
handling users' filter files, this is quite acceptable, because you
do not usually need to process a filter file in order to verify that
the local part is valid. See, for example, the \%userforward%\
router in the default configuration.
Q0067: What does the error \*too many unrecognized commands*\ mean?
A0067: There have been instances of network abuse involving mail sent out by
web servers. In most cases, unrecognizable commands are sent as part of
the SMTP session. A real MTA never sends out such invalid commands. Exim
allows a few unrecognized commands in a session to permit humans who are
testing to make a few typos (it responds with a 5xx error). However, if
Exim receives too many such commands, it assumes that it is dealing with
an abuse of some kind, and so it drops the connection.
Q0068: Exim times out when trying to connect to some hosts, though those hosts
are known to be up and running. What's the problem?
A0068: There could be a number of reasons for this (see also Q0017). The
obvious one is that there is a networking problem between the hosts.
If you can ping between the hosts or connect in other ways, the problem
might be caused by ECN (Explicit Congestion Notification) being enabled
in your kernel. ECN uses TCP flags originally assigned to TOS - it's a
"new" invention, and some hosts and routers are known to be confused if
a client uses it. If you are running Linux, you can turn ECN off by
running this command:
==> /bin/echo "0" > /proc/sys/net/ipv4/tcp_ecn
This has also been reported to cure web connection problems from Mozilla
and Netscape browsers in Linux when there were no problems with Windows
Netscape browsers.
Q0069: What does the error \*SMTP data timeout (message abandoned) on connection
from...*\ mean?
A0069: It means that there was a timeout while Exim was reading the contents of
a message on an incoming SMTP connection. That is, it had successfully
accepted a MAIL command, one or more RCPT commands, and a DATA command,
and was in the process of reading the data itself. The length of timeout
is controlled by the \smtp_receive_timeout\ option.
If you get this error regularly, the cause may be incorrect handling of
large packets by a router or firewall. The maximum size of a packet is
restricted on some links; routers should split packets that are larger.
There is a feature called ``path MTU discovery'' that enables a sender
to discover the maximum packet size over an entire path (multiple
Internet links). This can be broken by misconfigured firewalls and
routers. There is a good explanation at \?http://www.netheaven.com/pmtu.html?\.
Reducing the MTU on your local network can sometimes work round this
problem. See Q0017 (3) for further discussion.
Q0070: What does the error \*SMTP command timeout on connection from...*\ mean?
A0070: Exim was expecting to read an SMTP command from the client, but no
command was read within the \smtp_receive_timeout\ time limit.
Q0071: What does the error \*failed to open DB file \(/var/spool/exim//db/retry)\:
Illegal argument*\ mean?
A0071: See Q0058. The cause of this error is usually the same.
Q0072: Exim will deliver to normal aliases, and aliases that are pipes or
files, but it objects to aliases that involve \":include:"\ items,
complaining that it can't change gid or uid. Why is this?
A0072: See Q0066 for a general answer. The problem happens during verification
of an incoming SMTP message, not during delivery itself. In this
particular case, you must have set up your aliasing router with a \user\
setting. This causes Exim to change uid/gid when reading \":include:"\
files. If you do not need the detailed verification provided by the
router, the easy solution is to set \no_verify\ so that the router isn't
used during verification.
Otherwise, if you set \user\ on the router in order to provide a user
for delivery to pipes or files, one solution is to put the \user\
setting on the transports instead of on the router. You may need to
create some special transports just for this router. The alternative is
to supply two different routers, one with \user\ and \no_verify\, and
the with \verify_only\ but no \user\ setting.
Q0073: I'm seeing log file corruption, with parts of log lines getting mangled
by other log entries.
A0073: The only time this has been seen is when several servers were writing to
the same log files over NFS. Exim assumes that its log file is on local
disk, and using NFS, especially for more than one server, will not work.
Q0074: What does the error message \*remote delivery process count got out of
step*\ mean?
A0074: Exim uses subprocesses for remote deliveries; this error means that the
master process expected to have a child process running, but found there
were none. Prior to release 4.11, this error could be caused by running
Exim under \^strace^\ on a Linux system, because stracing causes
children to be ``stolen'' such that a parent that tries to wait for
``any of my children'' is told that it has none. Current releases of
Exim have code to get round this problem.
Q0075: I'm using LDAP, and some email addresses that contain special characters
are causing parsing errors in my LDAP lookups.
A0075: You should be using \"${quote_ldap:$local_part}"\ instead of just
\"$local_part"\ in your lookups.
Q0076: I've configured Exim to use \^syslog^\ for its logs, with the main and
reject logs sent to different files, but whenever a message is rejected,
I get one message on the reject log and two messages on the main log.
A0076: You are probably putting your reject items into the main log as well;
remember \^syslog^\ levels are inclusive (for example, \"mail.info"\
includes all higher levels, so a \"mail.notice"\ message will be caught
by a \"mail.info"\ descriptor).
Test this by running the command:
==> logger -p mail.notice test
and seeing which logs it goes into.
Q0077: I've installed Exim and it is delivering mail just fine. However, when I
try to read mail from my PC I get \*connection rejected*\ or \*unable to
connect*\.
A0077: See Q5021.
Q0078: Exim is logging the unknown SMTP command \"XXXX"\ from my client hosts,
and they are unable to authenticate.
A0078: This is a sign of a Cisco PIX firewall getting in the way. It does not
support ESMTP, and turns EHLO commands into XXXX. You should configure
the Pix to leave SMTP alone; see Q0053 for how to do this.
Q0079: Our new PIX firewall is causing problems with incoming mail. How can
this be fixed?
A0079: See Q0053 and Q0078. If some messages get through and others do not,
see also Q0017.
Q0080: Am I to understand that the database lookups must only return one value?
They can not return a list of values? The documentation seems to
indicate that it's possible to return a list.
A0080: Lookups can be used in two different situations, and what they return is
different in the two cases. (Be thankful Exim 3 is gone; there was yet
another case!)
(1) You can use a lookup in any expanded string. The syntax is
==> ${lookup ..... }
In this case, whatever is looked up replaces the expansion item. It
may be one value or a list of values. Whether a single value or a
list is acceptable or not depends on where you are using the string
expansion. If it is for an option that expects just one value, then
only one value is allowed (for example).
(2) You can make use of the lookup mechanism to test whether something
(typically a host name or IP address) is in a list. For example,
==> hosts = a : b : c
in an ACL tests whether the calling host's name matches ``a'', or
``b'', or ``c''. Now, suppose you want to keep the list of names in
a database, or cdb file, or NIS map, or... By writing
==> hosts = pgsql;select ....
you are saying to Exim: ``Run this lookup; if it succeeds, behave as
if the host is in the list; if it fails, the host is not in the
list.'' You are using the indexing mechanism of the database as a
fast way of checking a list. A simpler example is
==> hosts = lsearch;/some/file
where the file contains the list of hosts to be searched.
The complication happens when a list is first expanded before being
interpreted as a list. This happens in a lot of cases. You can therefore
write either of these:
==> hosts = cdb;/some/file
hosts = ${lookup{something}cdb{/some/file}}
but they have different meanings. The first means ``see if the host name
is in the list in this file''. The second means ``run this lookup and
use the result of the lookup as a list of host items to check''. In the
second case, the list could contain multiple values (colon separated),
and one of those values could even be ``cdb;/some/file''.
Flexibility does lead to complexity, I'm afraid.
Q0081: What does \*error in redirect data: included file xxxx is too big*\
mean?
A0081: You are trying to include a very large file in a redirection list, using
the \":include:"\ feature. Exim has a built-in limit on the size, as a
safety precaution. The default is 1 megabyte. If you want to increase
this, you have to rebuild Exim. In your \(Local/Makefile)\, put
==> MAX_INCLUDE_SIZE = whatever
and then rebuild Exim. The value is a number of bytes, but you can give
it as a parenthesized arithmetic expression such as \"(3*1024*1024)"\.
However, an included file of more than a megabyte is likely to be quite
inefficient. How many addresses does yours contain? You get the best
performance out of Exim if you arrange to send mailing list messages
with no more than about 100 recipients (in order to get parallelism in
the routing).
Q0082: What does \*relocation error: /lib/libnss_dns.so.2: symbol
__libc_res_nquery, version GLIBC_PRIVATE not defined in file
libresolv.so.2 with link time reference*\ mean?
A0082: You have updated \^glibc^\ while an Exim daemon is running. Stop and
restart the daemon.
Q0083: Netscape on Unix is sending messages containing an unqualified user name
in the ::Sender:: header line, which Exim is rejecting because I have
set \"verify = header_syntax"\. How can I fix this?
A0083: The only thing you can do in Exim is to set the
\sender_unqualified_hosts\ option to allow unqualified sender addresses
form the relevant hosts; of course, this applies to all sender
addresses, not just the ::Sender:: header line.
Alternatively, you can configure Netscape not to include the header line
in the first place. Add the following line to the
\($HOME/.netscape/preferences.js)\ and \($HOME/.netscape/liprefs.js)\
files:
==> user_pref("mail.suppress_sender_header", true);
Netscape \*must*\ be shutdown while doing this.
Q0084: I want to set up an alias that pipes a message to \^gpg^\ and then pipes
the result to \^mailx^\ to resubmit the message, but when I use my
tested command in an alias file, I get an error from \^gpg^\.
A0084: Probably you are using a shell command with two pipe symbols in it. An
alias like this:
==> gpg-xxx: "|gpg <options> | mailx <options"
does not work, because Exim does not run pipes under a shell by default.
You must call a shell explicitly if you want to make use of the shell's
features for double-piping, either by piping to \"/bin/sh"\ with a
suitable \"-c"\ option, or by piping to a shell script.
Q0085: I see a lot of \*rejected EHLO ... syntactically invalid argument(s)*\.
I know it's because of the underscore in the host name, but is there a
switch to allow Exim to accept mail from such hosts?
A0085: Yes. Add this to your configuration:
==> helo_allow_chars = _
For more seriously malformed host names, see \helo_accept_junk_hosts\.
See also Q0732.
Q0086: What does \*SMTP protocol violation: synchronization error (next input
sent too soon)*\ mean?
A0086: SMTP is a ``lock-step'' protocol, which means that, at certain points in
the protocol, the client must wait for the server to respond before
sending more data. Exim checks for correct behaviour, and issues this
error if the client sends data too soon. This protects against
malefactious clients who send a bunch of SMTP commands (usually to
transmit spam) without waiting for any replies.
This error is also provoked if the client is trying to start up a TLS
session immediately on connection, without using the STARTTLS command.
See Q1707 for a discussion of this case.
Q0087: What does \*rejected after DATA: malformed address: xx@yy may not follow
<xx@yy> : failing address in "from" header*\ mean? (I've obscured the
real email addresses.)
A0087: Your DATA ACL contains
==> verify = header_syntax
and an incoming message contained the line
==> From: xx@yy <xx@yy>
This is syntactically invalid. The contents of an address in a header
line are either just the address, or a ``phrase'' followed by an address
in angle brackets. In the latter case, the ``phrase'' must be quoted if
it contains special characters such as @. The following are valid
versions of the bad header:
==> From: xx@yy
From: "xx@yy" <xx@yy>
though why on earth anything generates this kind of redundant nonsense I
can't think.
Q0088: The Windows mailer SENDFILE.EXE sometimes hangs while trying to send a
message to Exim 4, and eventually times out. It worked flawlessly with
Exim 3. What has changed?
A0088: Exim 4 sets an obscure TCP/IP parameter called TCP_NODELAY. This
disables the "Nagle algorithm" for the TCP/IP transmission. The Nagle
algorithm can improve network performance in interactive situations such
as a human typing at a keyboard, by buffering up outgoing data until the
previous packet has been acknowledged, and thereby reducing the number
of packets used. This is not relevant for mail transmission, which
mostly consists of quite large blocks of data; setting TCP_NODELAY
should improve performance. However, it seems that some Windows clients
do not function correctly if the server turns off the Nagle algorithm.
If you are using Exim 4.23 or later, you can set
==> tcp_nodelay = false
This stops Exim setting TCP_NODELAY on the sockets created by the
listening daemon.
Q0089: What does the error \*kernel: application bug: exim(12099) has SIGCHLD
set to SIG_IGN but calls wait()*\ mean?
A0089: This was a bad interaction between a relatively recent change to the
Linux kernel and some ``belt and braces'' programming in Exim. The
following explanation is taken from Exim's change log:
When Exim is receiving multiple messages on a single connection, and
spinning off delivery processess, it sets the SIGCHLD signal handling to
SIG_IGN, because it doesn't want to wait for these processes. However,
because on some OS this didn't work, it also has a paranoid call to
\^waitpid()^\ in the loop to reap any children that have finished. Some
versions of Linux now complain (to the system log) about this
``illogical'' call to \^waitpid()^\. I have therefore put it inside a
conditional compilation, and arranged for it to be omitted for Linux.
I am pretty sure I caught all the places in Exim where this happened.
However, there are still occasional reports of this error. I have not
heard of any resolutions, but my current belief is that they are caused
by something that Exim calls falling foul of the same check. There was
at one time a suspicion that the IPv6 stack was involved.
Q0090: I can't seem to get a pipe command to run when I include a \"${lookup"\
expansion in it.
A0090: See Q0025.
Q0091: Why is Exim giving the error \*Failed to send message from address_reply
transport*\ when I run it using -C to specify an alternate
configuration?
A0091: See Q0065.
1. BUILDING AND INSTALLING
Q0101: I'm having a problem with an Exim RPM.
A0101: Normally the thing to do if you have a problem with an RPM package is
to contact the person who built the package first, not the person who
made the software that's in the package. You can usually find out who
made a package using the following command:
==> rpm --query --package --queryformat '%{PACKAGER}\n' <rpm-package-file>
where \[rpm-package-file]\ is the actual file, e.g. \(exim-3.03-2.i386.rpm)\.
Or, if the package is installed on your system:
==> rpm --query --queryformat '%{PACKAGER}\n' <package-name>
where \[package-name]\ is the name component of the package, e.g. \"exim"\.
If the packager is unable or unwilling to help, only then should you
contact the actual author or associated mailing list of the software.
If you discover through the querying process that you can't tell who
the person (or company or group) is who built the package, or that they
no longer exist at the given address, then you should reconsider
whether you want a package from an unknown source on your system.
If you discover through the querying process that you yourself are the
person who built the package, then you should either (a) contact the
author or associated mailing list, or (b) reconsider whether you ought
to be building and distributing RPM packages of software you don't
understand.
Similar rules of thumb govern other binary package formats, including
debs, tarballs, and POSIX packages.
Q0102: I can't get Exim to compile with Berkeley DB version 2.x or 3.x.
A0102: Have you set \"USE_DB=yes\" in \(Local/Makefile)\? This causes Exim to use the
native interface to the DBM library instead of the compatibility
interface, which needs a header called \(ndbm.h)\ that may not exist on your
system.
Q0103: I'm getting an \*undefined symbol*\ error for \"hosts_ctl"\ when I try to
build Exim. (On some systems this error is \*undefined reference to
'hosts_ctl'*\.)
A0103: You should either remove the definition of \\USE_TCP_WRAPPERS\\ or add
\"-lwrap"\ to your \\EXTRALIBS\\ setting in Local/Makefile.
Q0104: I'm about to upgrade to a new Exim release. Do I need to ensure the
spool is empty, or take any other special action?
A0104: It depends on where you are coming from.
(1) If you are changing to release 4.00 or later from a release prior to
4.00, you will need to make changes to the run time configuration file.
See the file \(doc/Exim4.upgrade)\ for details. If you are coming from
before release 3.00, you should also see \(doc/Exim3.upgrade)\.
(2) If you are upgrading from an Exim 4 release to a later release, you
do not need to take special action. New releases are made backwards
compatible with old spool files and hints databases, so that upgrading
can be done on a running system. All that should be necessary is to
install a new binary and then HUP the daemon.
Q0105: What does the error \*install-info: command not found*\ mean?
A0105: You have set \\INFO_DIRECTORY\\ in your \(Local/Makefile)\, and Exim is trying
to install the Texinfo documentation, but cannot find the command called
\(install-info)\. If you have a version of Texinfo prior to 3.9, you
should upgrade. Otherwise, check your installation of Texinfo to see why
the \(install-info)\ command is not available.
Q0106: Exim doesn't seem to be recognizing my operating system type correctly,
and so is failing to build.
A0106: Run the command \"scripts/os-type -generic"\. The output should be one of
the known OS types, and should correspond to your operating system. You
can see which OS are supported by obeying \"ls OS/Makefile-*"\ and looking
at the file name suffixes.
If there is a discrepancy, it means that the script is failing to
interpret the output from the \"uname"\ command correctly, or that the
output is wrong. Meanwhile, you can build Exim by obeying
==> EXIM_OSTYPE=xxxx make
instead of just \"make"\, provided you are running a Bourne-compatible
shell, or otherwise by setting \\EXIM_OSTYPE\\ correctly in your
environment. It is probably best to start again from a clean
distribution, to avoid any wreckage left over from the failed attempt.
Q0107: Exim fails to build, complaining about the absence of the \"killpg"\
function.
A0107: This function should be present in all modern flavours of Unix. If you
are using an older version, you should be able to get round the problem
by inserting
==> #define killpg(pgid,sig) kill(-(pgid),sig)
into the file called \(OS/os.h-xxx)\, where xxx identifies your operating
system, and is the output of the command \"scripts/os-type -generic"\.
Q0108: I'm getting an unresolved symbol \"ldap_is_ldap_url"\ when trying to build
Exim.
A0108: You must have specified \"LOOKUP_LDAP=yes"\ in the configuration. Have you
remembered to set \"-lldap"\ somewhere (e.g. in \\LOOKUP_LIBS\\)? You need that
in order to get the LDAP library scanned when linking.
Q0109: I'm getting an unresolved symbol \"mysql_close"\ when trying to build Exim.
A0109: You must have specified \"LOOKUP_MYSQL=yes"\ in the configuration. Have you
remembered to set \"-lmysqlclient"\ somewhere (e.g. in \\LOOKUP_LIBS\\)? You
need that in order to get the MySQL library scanned when linking.
Q0110: I'm trying to build Exim with PAM support. I have included \"-lpam"\ in
\\EXTRALIBS\\, but I'm still getting a linking error:
==> /lib/libpam.so: undefined reference to `dlerror'
/lib/libpam.so: undefined reference to `dlclose'
/lib/libpam.so: undefined reference to `dlopen'
/lib/libpam.so: undefined reference to `dlsym'
A0110: Add \"-ldl"\ to \\EXTRALIBS\\. In some systems these dynamic loading functions
are in their own library.
Q0111: I'm getting the error \*db.h: No such file or directory*\ when I try to
build Exim.
A0111: This problem has been seen with RedHat 7.0, but could also happen in
other environments. If your system is using the DB library, you
need to install the DB development package in order to build Exim.
The package is called something like \"db3-devel-3.1.14-16.i386.rpm"\ for
Linux systems, but you should check which version of DB you have
installed (current releases are DB 4).
Q0112: I'm getting the error \*/usr/bin/ld: cannot find -ldb*\ when I try to
build Exim.
A0112: This is probably the same problem as Q0111.
Q0113: I've compiled Exim and I've managed to start it but there was one
problem - it always complained that \(libmsqlclient.so.10)\ was not found,
even though this file is in \(/usr/local/lib/mysql/)\.
A0113: Solaris: ensure you have this in your \(Local/Makefile)\:
==> LOOKUP_LIBS=-L/usr/local/lib/mysql -R/usr/local/lib/mysql
Net/Open/FreeBSD: Run this command (or ensure it gets run automatically
at boot time):
==> ldconfig -m /usr/local/lib/mysql
Linux: add \(/usr/local/lib/mysql)\ to \(/etc/ld.so.conf)\ and re-run \(ldconfig)\.
Alternatively, add
==> -Wl,-rpath -Wl,/usr/local/lib/mysql
to EXTRA_LIBS and then re-link (this is similar to the Solaris solution
above). This will probably also work on other systems that use GNU
Binutils.
Q0114: How can I remove Sendmail from my system? I've built Exim and run \"make
install"\, but it still doesn't seem to be fully operational.
A0114: If you are running FreeBSD, see Q9201. Otherwise, you need to arrange
that whichever of the paths \(/usr/sbin/sendmail)\ or \(/usr/lib/sendmail)\
exists on your system is changed to refer to Exim. For example, you
could use these commands (as \/root/\):
==> mv /usr/sbin/sendmail /usr/sbin/sendmail.original
chmod u-s /usr/sbin/sendmail.original
ln -s /path/to/exim /usr/sbin/sendmail
The second command removes the setuid privilege from the old MTA, as a
general safety precaution. In the third command, substitute the actual
path to the Exim binary for \(/path/to/exim)\.
Q0115: What does \*Can't open \(../scripts/newer)\: No such file or directory*\
mean? I got it while trying to build Exim.
A0115: You are using FreeBSD, or another OS that has a \^make^\ command which
tries to optimize the running of commands. Exim's \(Makefile)\ contains
targets with sequential commands like this:
==> buildpcre:
@cd pcre; $(MAKE) SHELL=$(SHELL) AR="$(AR)" $(MFLAGS) CC="$(CC)" \
CFLAGS="$(CFLAGS) $(PCRE_CFLAGS)" \
RANLIB="$(RANLIB)" HDRS="$(PHDRS)" \
INCLUDE="$(INCLUDE) $(IPV6_INCLUDE) $(TLS_INCLUDE)"
@if $(SHELL) $(SCRIPTS)/newer pcre/libpcre.a exim; then \
/bin/rm -f exim eximon.bin; fi
The second command assumes that the \"cd pcre"\ in the first command is
no longer in effect. If you have \"-j3"\ in your default set of
\"MAKEFLAGS"\, FreeBSD \^make^\ tries to optimize, and ends up up with both
commands in the same shell process. The result is that \"$(SCRIPTS)"\
(which has a value of \"../scripts"\) is not found.
The simplest solution is to force \^make^\ to use backwards compatibility
mode with each command in its own shell, by using the \-B\ flag. To
ensure that this happens throughout the build, it's best to export it in
your environment:
==> MAKEFLAGS='-B'
export MAKEFLAGS
make
Q0116: I have tried to build Exim with Berkeley DB 3 and 4, but I always get
errors.
A0116: One common problem, especially when you have several different versions
of BDB installed on the same host, is that the header files and library
files for BDB are not in a standard place. You therefore need to tell
Exim where they are, by setting INCLUDE and DBMLIB in your
\(Local/Makefile)\. For example, I use this on my workstation when
I want to build with DB 4.1:
==> INCLUDE=-I/opt/local/include/db-4.1
DBMLIB=/opt/local/lib/db-4.1/libdb.a
Specifying the complete library file like this will cause it to be
statically linked with Exim. You'll have to check to see where these
files are on your system. For example, on FreeBSD 5, the header is in
\(/usr/local/include/db4)\ and the library is in \(/usr/local/lib)\ and
called \(libdb4)\. In that environment, you could use:
==> INCLUDE=-I/usr/local/include/db4
DBMLIB=-L/usr/local/lib -ldb4
This time, DBMLIB is specifying the library directory (\(/usr/local/lib)\)
and the name of the library (\(db4)\) separately. The name of the actual
library file is \(/usr/local/lib/libdb4.something)\. If the library was
compiled for dynamic linking, that will be used.
Q0117: Is there a quick walk-through of an Exim install from source anywhere?
A0117: Here! This is a contribution from a RedHat user, somewhat edited. On
other operating systems things may be slightly different, but the
general approach is the same.
(1) Install the db needed for Exim. This needs to be done first if you
don't have a DBM library installed. Go to \?http://www.sleepycat.com?\
and download \(db-4.1.25.tar.gz)\, or whatever the current release is.
Then:
==> gunzip db-4.1.25.tar.gz
tar -xvf db-4.1.25.tar
cd db-4.1.25
cd build_unix
../dist/configure
make
make install
(2) Add a user for use by Exim, unless you want to use an existing user
such as \/mail/\:
==> adduser exim
(3) Now you can prepare to build Exim. Go to \?http://www.exim.org?\ or
one of its mirrors, or the master ftp site
\?ftp://ftp.csx.cam.ac.uk/pub/software/email/exim/exim4?\, and download
\(exim-4.20.tar.gz)\ or whatever the current release is. Then:
==> gunzip exim-4.20.tar.gz
tar -xvf exim-4.20.tar
cd exim-4.20
cp src/EDITME Local/Makefile
cp exim_monitor/EDITME Local/eximon.conf
(4) Edit \(Local/Makefile)\:
Comment out EXIM_MONITOR= unless you want to install the Exim
monitor (it requires X-windows).
Set the user you want Exim to use for itself:
==> EXIM_USER=exim
If your DBM library is Berkeley DB, set up to use its native interface:
==> USE_DB=yes
Make sure Exim's build can find the DBM library and its headers. If
you've installed Berkeley DB 4 you'll need to have settings like this
in \(Local/Makefile)\:
==> INCLUDE=-I/usr/local/BerkeleyDB.4.1/include
DBMLIB=/usr/local/BerkeleyDB.4.1/lib/libdb.a
(Check that the first directory contains the db.h file and that the
second library exists.)
You don't need to change anything else, but you might want to review
the default settings in the ``must specify'' section.
(4) Build Exim by running the \/make/\ command.
(5) Install Exim by running, as \/root/\:
==> make install
You \*must*\ be \/root/\ to do this. You do not have to be root for any of
the previous building activity.
(6) Run some tests on Exim; see if it will do local and remote
deliveries. Change the configuration if necessary (for example,
uncommenting \group\ on the \%local_delivery%\ transport if you don't
use a ``sticky bit'' directory).
(7) Change Sendmail to Exim (of course you need to have had Sendmail
installed to do this).
==> /etc/init.d/sendmail stop
mv /usr/sbin/sendmail /usr/sbin/sendmail.org
ln -s /usr/exim/bin/exim /usr/sbin/sendmail
/etc/init.d/sendmail start
(8) Check the Exim log. Either use the Exim monitor, or:
==> tail -f /var/spool/exim/log/mainlog
Q0118: I've set \"LOOKUP_INCLUDE=-I/client/include"\ in Local/Makefile, but the
compilation of \^exim_dumpdb^\ is ignoring this option and failing. Why?
A0118: LOOKUP_INCLUDE is the special include file for lookup modules in Exim
(e.g. mysql, LDAP). Confusingly, it doesn't apply to basic DBM code
which is used also for other things. Try setting INCLUDE and DBMLIB
instead. For example:
==> USE_DB=yes
INCLUDE=-I/client/include
DBMLIB=/client/lib/libdb.a
Q0119: I know there are some 3rd-party patches for Exim, for exiscan and
other things. Where are they?
A0119: Exiscan is at \?http://duncanthrax.net/exiscan-acl/?\.
[[br]]
Scanexi is at \?http://w1.231.telia.com/~u23107873/scanexi.html?\
[[br]]
A sample \^^local_scan()^^\ function for interfacing to \^uvscan^\ is
at \?http://www.dcs.qmul.ac.uk/~mb/local_scan/?\.
[[br]]
An interface to SpamAssassin at SMTP time is at
\?http://marc.merlins.org/linux/exim/sa.html?\.
[[br]]
A mini-HOWTO (PDF file) about scanning and virus scanning, and some RPMs
can be found at \?http://www.timj.co.uk/linux/exim.php?\.
2. ROUTING IN GENERAL
Q0201: How can I arrange that messages larger than some limit are handled by
a special router?
A0201: You can use a \condition\ option on the router line this:
==> condition = ${if >{$message_size}{100K}{yes}{no}}
Q0202: Can I specify a list of domains to explicitly reject?
A0202: Set up a named domain list containing the domains in the first section
of the configuration, for example:
==> domainlist reject_domains = list:of:domains:to:reject
You can use this list in an ACL to reject any SMTP recipients in those
domains. You can also give a customized error message, like this:
==> deny message = The domain $domain is no longer supported
domains = +reject_domains
If you also want to reject these domains in messages that are submitted
from the command line (not using SMTP), you need to set up a router to
do it, like this:
==> reject_domains:
driver = redirect
domains = +reject_domains
allow_fail
data = :fail: The domain $domain is no longer supported
Q0203: How can I arrange to do my own qualification of non-fully-qualified
domains, and then pass them on to the next router?
A0203: If you have some list of domains that you want to qualify, you can do
this using a redirect router. For example,
==> qualify:
driver = redirect
domains = *.a.b
data = ${quote:$local_part}@$domain.c.com
This adds \".c.com"\ to any domain that matches \"*.a.b"\.
If you want to do this in conjunction with a \%dnslookup%\ router, the
\widen_domains\ option of that router may be another way of achieving
what you want.
Q0204: Every system has a \"nobody"\ account under which httpd etc run. I would
like to know how to restrict mail which comes from that account to users
on that host only.
A0204: Set up a first router like this:
==> fail_nobody:
driver = redirect
senders = nobody@???
domains = ! +local_domains
allow_fail
data = :fail: Nobody may not mail off-site
This assumes you have defined \+local_domains\ as in the default
configuration.
Q0205: How can I get Exim to deliver to me locally and everyone else at the same
domain via SMTP to the MX record specified host?
A0205: Create an \%accept%\ router to pick off the one address and pass it to
an appropriate transport. Put this router before the one that does MX
routing:
==> me:
driver = accept
domains = dom.com
local_parts = me
transport = local_delivery
In the transport you will have to specify the \user\ option. An
alternative way of doing this is to add a condition to the router that
does MX lookups to make it skip your address. Subsequent routers can then
deliver your address locally. You'll need a condition like this:
==> condition = \
${if and {{eq{$domain}{dom.com}}{eq{$local_part}{me}}}{no}{yes}}
Q0206: How can I get Exim to deliver certain domains to a different SMTP port
on my local host?
A0206: You must set up a special \%smtp%\ transport, where you can specify the
\port\ option, and then set up a router to route the domains to that
transport. There are two possibilities for specifying the host:
(1) If you use a \%manualroute%\ router, you can specify the local host
in the router options. You must also set
==> self = send
so that it does not object to sending to the local host.
(2) If you use a router that cannot specify hosts (for example, an
\%accept%\ router with appropriate conditions), you have to specify
the host using the \hosts\ option of the transport. In this case,
you must also set \allow_localhost\ on the transport.
Q0207: Why does Exim lower-case the local-part of a non-local domain when
routing?
A0207: Because \caseful_local_part\ is not set (in the default configuration)
for the \%dnslookup%\ router. This does not matter because the local
part takes no part in the routing, and the actual local part that is
sent out in the RCPT command is always the original local part.
3. ROUTING TO REMOTE HOSTS
Q0301: What do \*lowest numbered MX record points to local host*\ and \*remote
host address is the local host*\ mean?
A0301: They mean exactly what they say. Exim expected to route an address to a
remote host, but the IP address it obtained from a router was for the
local host. If you really do want to send over TCP/IP to the local host
(to a different version of Exim or another MTA, for example), see Q0206.
More commonly, these errors arise when Exim thinks it is routing some
foreign domain. For example, the router configuration causes Exim to
look up the domain in the DNS, but when Exim examines the DNS output,
either the lowest numbered MX record points at the local host, or there
are no MX records, and the address record for the domain contains an
IP address that belongs to the local host.
There has been a rash of instances of domains being deliberately set up
with MX records pointing to \"localhost"\ (or other names with A records
that specify 127.0.0.1), which causes this behaviour. You can use the
\ignore_target_hosts\ option to get Exim to ignore these records. The
default contiguration does this. For more discussion, see Q0319. For
other cases:
(1) If the domain is meant to be handled as a local domain, there
is a problem with the configuration, because it should not then have
been looked up in the DNS. Check the \domains\ settings on your
routers.
(2) If the domain is one for which the local host is providing a
relaying service (called ``mail hubbing''), possibly as part of a
firewall, you need to set up a router to tell Exim where to send
messages addressed to this domain, because the DNS directs them to
the local host. You should put a router like this one before the one
that does DNS lookups:
==> hubbed_hosts:
driver = manualroute
transport = remote_smtp
route_list = see discussion below
The contents of the \route_list\ option depend on how many hosts you
are hubbing for, and how their names are related to the domain name.
Suppose the local host is a firewall, and all the domains in
\(*.foo.bar)\ have MX records pointing to it, and each domain
corresponds to a host of the same name. Then the setting could be
==> route_list = *.foo.bar $domain
If there isn't a convenient relationship between the domain names
and the host names, you either have to list each domain separately,
or use a lookup expansion to look up the host from the domain, or
put the routing information in a file and use the \route_data\
option with a lookup expansion.
(3) If neither (1) nor (2) is the case, the lowest numbered MX record or
the address record for the domain should not be pointing to your
host. You should arrange to get the DNS mended.
Q0302: Why does Exim say \*all relevant MX records point to non-existent hosts*\
when MX records point to IP addresses?
A0302: MX records cannot point to IP addresses. They are defined to point to
host names, so Exim always interprets them that way. (An IP address is a
syntactically valid host name.) The DNS for the domain you are having
problems with is misconfigured.
However, it appears that more and more DNS zones are breaking the rules
and putting IP addresses on the RHS of MX records. Exim follows the
rules and rejects this, but other MTAs do support it, so the
\allow_mx_to_ip\ was regretfully added at release 3.14 to permit this
heinous activity.
Q0303: How do I configure Exim to send all messages to a central server? I
don't want to do any local deliveries at all on this host.
A0303: Use this as your first and only router:
==> send_to_gateway:
driver = manualroute
transport = remote_smtp
route_list = * central.server.host
Q0304: How do I configure Exim to send all non-local mail to a gateway host?
A0304: Replace the \%dnslookup%\ router in the default configuration with the
following:
==> send_to_gateway:
driver = manualroute
domains = !+local_domains
transport = remote_smtp
route_list = * gate.way.host
If there are several hosts you can send to, you can specify them as a
colon-separated list.
Q0305: How can I arrange for mail on my local network to be delivered directly
to the relevant hosts, but all other mail to be sent to my ISP's mail
server? The local hosts are all DNS-registered and behave like normal
Internet hosts.
A0305: Set up a first router to pick off all the domains for your local
network. There are several ways you might do this. For example
==> local_network:
driver = dnslookup
transport = remote_smtp
domains = *.mydomain.com
This does a perfectly conventional DNS routing operation, but only for
the domains that match \(*.mydomain.com)\. Follow this with a `smart
host' router:
==> internet:
driver = manualroute
domains = !+local_domains
transport = remote_smtp
route_list = * mail.isp.net
This routes any other non-local domains to the smart host.
Q0306: How do I configure Exim to send all non-local mail to a central server
if it cannot be immediately delivered by my host? I don't want to have
queued mail waiting on my host.
A0306: Add to the \%remote_smtp%\ transport the following:
==> fallback_hosts = central.server.name(s)
If there are several names, they must be separated by colons.
Q0307: The \route_list\ setting \"^foo$:^bar$ $domain"\ in a \%manualroute%\
router does not work.
A0307: The first thing in a \route_list\ item is a single pattern, not a list of
patterns. You need to write that as \"^(foo|bar)$ $domain"\.
Alternatively, you could use several items and write
==> route_list = foo $domain; bar $domain
Note the semicolon separator. This is because the second thing in each
item can itself be a list - of hosts.
Q0308: I have a domain for which some local parts must be delivered locally,
but the remainder are to be treated like any other remote addresses.
A0308: One possible way of doing this is as follows: Assuming you are using a
configuration that is similar to the default one, first exclude your
domain from the first router by changing it to look like this:
==> non_special_remote:
driver = dnslookup
domains = ! +local_domains : ! special.domain
transport = remote_smtp
ignore_target_hosts = 127.0.0.0/8
no_more
Then add a second router which handles the local parts that are not to
be delivered locally:
==> special_remote:
driver = dnslookup
domains = special.domain
local_parts = ! lsearch;/list/of/special/localparts
transport = remote_smtp
ignore_target_hosts = 127.0.0.0/8
no_more
The remaining local parts will fall through to the remaining routers,
which can delivery them locally.
Q0309: How can I configure Exim on a firewall machine so that if mail arrives
addressed to a domain whose MX points to the firewall, it is forwarded
to the internal mail server, without having to have a list of all the
domains involved?
A0309: As your first router, have the standard \%dnslookup%\ router from the
default configuration, with the added option
==> self = pass
This will handle all domains whose lowest numbered MX records do not
point to your host. Because of the \no_more\ setting, if it encounters
an unknown domain, routing will fail. However, if it hits a domain whose
lowest numbered MX points to your host, the \self\ option comes into
play, and overrides \no_more\. The \"pass"\ setting causes it to pass
the address on to the next router. (The default causes it to generate an
error.)
The only non-local domains that reach the second router are those with
MX records pointing to the local host. Set it up to send them to the
internal mail server like this:
==> internal:
driver = manualroute
domains = ! +local_domains
transport = remote_smtp
route_list = * internal.server
Q0310: If a DNS lookup returns no MX records why doesn't Exim just bin the
message?
A0310: If a DNS lookup returns no MXs, Exim looks for an address record, in
accordance with the rules that are defined in the RFCs. If you want to
break the rules, you can set \mx_domains\ in the \%dnslookup%\ router, but
you will cut yourself off from those sites (and there still seem to be
plenty) who do not set up MX records.
Q0311: When a DNS lookup for MX records fails to complete, why doesn't Exim
send the messsage to the host defined by the A record?
A0311: The RFCs are quite clear on this. Only if it is known that there are no
MX records is an MTA allowed to make use of the A record. When an MX
lookup fails to complete, Exim does not know whether there are any MX
records or not. There seem to be some name servers (or some
configurations of some name servers) that give a ``server fail'' error when
asked for a non-existent MX record. Exim uses standard resolver calls,
which unfortunately do not distinguish between this case and a timeout,
so all Exim can do is try again later.
Q0312: Is it possible to use a conditional expression for the host item in a
\route_list\ for \%manualroute%\ router? I tried the following, but it
doesn't work:
==> route_list = * ${if match{$header_from:}{\N.*\.usa\.net$\N} \
{<smarthost1>}{<smarthost2>}
A0312: The problem is that the second item in \route_list\ contains white
space, which means that it gets terminated prematurely. To avoid this,
you must put the second item in quotes:
==> route_list = * "${if match{$header_from:}{\N.*\.usa\.net$\N} \
{<smarthost1>}{<smarthost2>}}"
Q0313: I send all external mail to a smart host, but this means that bad
addresses also get passed to the smart host. Can I avoid this?
A0313: Assuming you have DNS availability, set up a conventional \%dnslookup%\
router to do the routing, but in the \%remote_smtp%\ transport set this:
==> hosts = your.smart.host
hosts_override
This will override the hosts that the router finds so that everything
goes to the smart host, but any non-existent domains will be failed by
the router.
Q0314: I have a really annoying intermittent problem where attempts to mail to
valid sites are rejected with \*unknown mail domain*\. This only happens a
few times a day and there is no particular pattern to the sites it
rejects. If I try to lookup the same domain a few minutes later then it
is OK.
A0314: This is almost certainly a problem with the DNS resolver or the the
domain's name servers.
(1) Have you linked Exim against the newest DNS resolver library that
comes with Bind? If you are using SunOS4 that may be your problem, as
the resolver that comes with that OS is known to be buggy and to give
intermittent false negatives.
(2) Effects like this are sometimes seen if a domain's name servers get
out of step with each other.
Q0315: I'd like route all mail with addresses that can't be resolved (the DNS
lookup times out) to a relay machine.
A0315: Set \pass_on_timeout\ on your \%dnslookup%\ router, and add below it a
\%manualroute%\ router that routes all relevant domains to the relay.
Q0316: I would like to forward all incoming email for a particular domain to
another host via SMTP. Whereabouts would I configure that?
A0316: Use this as your first router:
==> special:
driver = manualroute
transport = remote_smtp
route_list = the.particular.domain the.other.host
You will also need to adjust the ACL for incoming SMTP so that this
domain is accepted for relaying. If you are using the default
configuration, there is a domain list called \relay_domains\ that is
set up for this.
Q0317: What I'd like to do is have alternative smart hosts, where the one to be
used is determined by which ISP I'm connected to.
A0317: The simplest way to do this is to arrange for the name of the smart host
du jour to be placed in a file when you connect, say \(/etc/smarthost)\.
Then you can read this file from a \%manualroute%\ router like this:
==> smarthost:
driver = manualroute
transport = remote_smtp
route_list = * ${readfile{/etc/smarthost}{}}
The second argument of the \"readfile"\ item is a string that replaces
any newline characters in the file (in this case, with nothing).
By keeping the data out of the main configuration file, you avoid having
to HUP the daemon when it changes.
Q0318: Exim won't route to a host with no MX record.
A0318: More than one thing may cause this.
(1) Are you sure there really is no MX record? Sometimes a typo results
in a malformed MX record in the zone file, in which case some name
servers give a SERVFAIL error rather than NXDOMAIN. Exim has to treat
this as a temporary error, so it can't go on to look for address records.
You can check for this state using one of the DNS interrogation commands,
such as \(nslookup)\, \(host)\, or \(dig)\.
(2) Is there a wildcard MX record for \(your)\ domain? Is the
\search_parents\ option on in your \%dnslookup%\ router? If the answer to
both these questions is ``yes'', that is the cause of the problem. When
the DNS resolver fails to find the MX record, it tries adding on your
domain if \search_parents\ is true, and thereby finds your wildcard MX
record. For example:
. There is a wildcard MX record for \(*.a.b.c)\.
. There is a host called \(x.y.z)\ that has an A record and no MX record.
. Somebody on the host \(m.a.b.c)\ domain tries to mail to \(user@???)\.
. Exim calls the DNS to look for an MX record for \(x.y.z)\.
. The DNS doesn't find any MX record. Because \search_parents\ is true,
it then tries searching the current host's parent domain, so it
looks for \(x.y.z.a.b.c)\ and picks up the wildcard MX record.
Setting \search_parents\ false makes this case work while retaining the
wildcard MX record. However, anybody on the host \(m.a.b.c)\ who mails to
\(user@???)\ (expecting it to go to \(user@???)\) now has a problem. The
\widen_domains\ option of the \%dnslookup%\ router may be helpful in this
circumstance.
Q0319: I have some mails on my queues that are sticking around longer than
the retry time indicates they should. They are all getting frozen
because some remote admin has set their MX record to 127.0.0.1.
A0319: The admin in question is an idiot. Exim will always freeze such messages
because they are apparently routed to the local host. To bounce these
messages immediately, set
==> ignore_target_hosts = 127.0.0.1
on the \%dnslookup%\ router. This causes Exim to completely ignore any hosts
with that IP address. In fact, there are quite a number of IP addresses
that should never be used. Here is a suggested configuration list for
the IPv4 ones:
==> # Don't allow domains whose single MX (or A) record is a
# "special-use IPv4 address", as listed in RFC 3330.
ignore_target_hosts = \
# Hosts on "this network"; RFC 1700 (page 4) states that these
# are only allowed as source addresses
0.0.0.0/8 : \
# Private networks, RFC 1918
10.0.0.0/8 : 172.16.0.0/12 : 192.168.0.0/16 : \
# Internet host loopback address, RFC 1700 (page 5)
127.0.0.0/8 : \
# "Link local" block
169.254.0.0/16 : \
# "TEST-NET" - should not appear on the public Internet
192.0.2.0/24 : \
# 6to4 relay anycast addresses, RFC 3068
192.88.99.0/24 : \
# Network interconnect device benchmark testing, RFC 2544
198.18.0.0/15 : \
# Multicast addresses, RFC 3171
224.0.0.0/4 : \
# Reserved for future use, RFC 1700 (page 4)
240.0.0.0/4
Q0320: How can I arrange for all mail to \*user@???*\ to be forwarded
to \*user@???*\?
A0320: Put this as your first router:
==> forward:
driver = redirect
domains = some.domain
data = ${quote:$local_part}@???
Q0321: How can I tell an Exim router to use only IPv4 or only IPv6 addresses
when it finds both types in the DNS?
A0321: You can do this by making it ignore the addresses you don't want. This
example ignores all IPv6 addresses and all IPv4 addresses in the 127
network:
==> ignore_target_hosts = <; 0000::0000/0 ; 127.0.0.0/8
To ignore all IPv4 addresses, use
==> ignore_target_hosts = 0.0.0.0/0
See Q0319 for a general discussion of \ignore_target_hosts\.
Q0322: How can I reroute all messages bound for 192.168.10.0 and 10.0.0.0 to
a specific mail server?
A0322: That is an odd requirement. However, there is an obscure feature in
Exim, originally implemented for packet radio people, that perhaps can
help. Check out the \translate_ip_address\ generic router option.
4. ROUTING FOR LOCAL DELIVERY
Q0401: I need to have any mail for \(virt.dom.ain)\ that doesn't match one of the
aliases in \(/usr/lib/aliases.virt)\ delivered to a particular address, for
example, \(postmaster@???)\.
A0401: Adding an asterisk to a search type causes Exim to look up ``*'' when the
normal lookup fails. So if your aliasing router is something like this:
==> virtual:
driver = redirect
domains = virt.dom.ain
data = ${lookup{$local_part}lsearch{/usr/lib/aliases.virt}}
no_more
you should change \"lsearch"\ to \"lsearch*"\, and put this in the alias
file:
==> *: postmaster@???
This solution has the feature that if there are several unknown
addresses in the same message, only one copy gets sent to the
postmaster, because of Exim's normal de-duplication rules.
NOTE: This solution works only if there is also an entry for \(postmaster)\
in the alias file, ultimately resolving to an address that is not in
\(virt.dom.ain)\. See also Q0434.
Q0402: How do I arrange for all incoming email for \(*@some.domain)\ to go into one
pop3 mail account? The customer doesn't want to add a list of specific
local parts to the system.
A0402: Set up a special transport that writes to the mailbox like this:
==> special_transport:
driver = appendfile
file = /pop/mailbox
envelope_to_add
return_path_add
delivery_date_add
user = exim
The file will be written as the user \"exim"\. Then arrange to route all
mail for that domain to that transport, with a router like this:
==> special_router:
driver = accept
domains = some.domain
transport = special_transport
Q0403: How do I configure Exim to send messages for unknown local users to a
central server?
A0403: Assuming you are using something like the default configuration, where
local users are processed by the later routers, you should add the
following router at the end:
==> unknown:
driver = manualroute
transport = remote_smtp
route_list = * server.host.name
no_verify
However, you should if possible try to verify that the user is known on
the central server before accepting the message in the first place. This
can be done by making use of Exim's ``call forward'' facility.
Q0404: How can I arrange for messages submitted by (for example) Majordomo to
be handled specially?
A0404: You can use the \condition\ option on a router, with a setting such as
==> condition = ${if and {{eq {$sender_host_address}{}} \
{eq {$sender_ident}{majordom}}} {yes}{no}}
This first tests for a locally-submitted message, by ensuring there is
no sending host address, and then it checks the identity of the user
that ran the submitting process.
Q0405: On a host that accepts mail for several domains, do I have to use fully
qualified addresses in \(/etc/aliases)\ or do I have to set up an alias
file for each domain?
A0405: You can do it either way. The default aliasing router contains this line:
==> data = ${lookup{$local_part}lsearch{/etc/aliases}}
which is what does the actual lookup. To make it look up the complete
address instead of just the local part, use
==> data = ${lookup{$local_part@$domain}lsearch{/etc/aliases}}
If you want to use a separate file for each domain, use
==> data = ${lookup{$local_part}lsearch{/etc/aliases/$domain}}
Q0406: Some of my users are using the \(.forward)\ to pipe to a shell command which
appends to the user's INBOX. How can I forbid this?
A0406: If you allow your users to run shells in pipes, you cannot control which
commands they run or which files they write to. However, you should point
out to them that writing to an INBOX by arbitrary commands is not
interlocked with the MTA and MUAs, and is liable to mess up the contents
of the file.
If a user simply wants to choose a specific file for the delivery of
messages, this can be done by putting a file name in a \(.forward)\ file
rather than using a pipe, or by using the \"save"\ command in an Exim
filter file.
You can set \forbid_pipe\ on the router, but that will prevent them from
running any pipe commands at all. Alternatively, you can restrict which
commands they may run in their pipes by setting the \allow_commands\
and/or \restrict_to_path\ options in the \%address_pipe%\ transport.
Q0407: How can I arrange for a default value when using a query-style lookup
such as LDAP or NIS+ to handle aliases?
A0407: Use a second query in the failure part of the original lookup, like
this:
==> data = ${lookup ldap\
{ldap://x.y.z/l=yvr?aliasaddress?sub?(&(mail=$local_part@$domain))}\
{$value}\
{\
${lookup ldap \
{ldap://x.y.z/l=yvr?aliasaddress?sub?(&(mail=default@$domain))}}\
}}
Of course, if the default is a fixed value you can just include it
directly.
Q0408: If I don't fully qualify the addresses in a virtual domain's alias file
then mail to aliases which also match the local domain get delivered to
the local domain.
A0408: Set the \qualify_preserve_domain\ option on the \%redirect%\ router.
Q0409: I want mail for any local part at certain virtual domains to go
to a single address for each domain.
A0409: One way to to this is
==> virtual:
driver = redirect
data = ${lookup{$domain}lsearch{/etc/virtual}}
The \(/etc/virtual)\ file contains a list of domains and the addresses to
which their mail should be sent. For example:
==> domain1: postmaster@???
domain2: joe@???
If the number of domains is large, using a DBM or cdb file would be more
efficient. If the lookup fails to find the domain in the file, the value
of the \data\ option is empty, causing the router to decline.
Q0410: How can I make Exim look in the alias NIS map instead of \(/etc/aliases)\?
A0410: The default configuration does not use NIS (many hosts don't run it).
You need to change this line in the \%system_aliases%\ router:
==> data = ${lookup{$local_part}lsearch{/etc/aliases}}
Change it to
==> data = ${lookup{$local_part}nis{mail.aliases}}
If you want to use \(/etc/aliases)\ as well as NIS, put this router (with
a different name) before or after the default one, depending on which
data source you want to take precedence.
Q0411: Why will Exim deliver a message locally to any username that is longer
than 8 characters as long as the first 8 characters match one of the
local usernames?
A0411: The problem is in your operating system. Exim just calls the \^^getpwnam()^^\
function to test a local part for being a local login name. It does not
presume to guess the maximum length of user name for the underlying
operating system. Many operating systems correctly reject names that are
longer than the maximum length; yours is apparently deficient in this
regard. To cope with such systems, Exim has an option called
\max_user_name_length\ which you can set to the maximum allowed length.
Q0412: Why am I seeing the error \*bad mode (100664) for /home/test/.forward*\?
I've looked through the documentation but can't see anything to suggest
that Exim has to do anything other than read the \(.forward)\ file.
A0412: For security, Exim checks for mode bits that shouldn't be set, by
default 022. You can change this by setting the \modemask\ option of the
\%redirect%\ router that is handling \(.forward)\ files.
Q0413: When a user's \(.forward)\ file is syntactially invalid, Exim defers
delivery of all messages to that user, which sometimes include the
user's own test messages. Can it be told to ignore the \(.forward)\ file
and/or inform the user of the error?
A0413: Setting \skip_syntax_errors\ on the redirect router causes syntax
errors to be skipped. When dealing with users' \(.forward)\ files it is best
to combine this with a setting of \syntax_errors_to\ in order to send
a message about the error to the user. However, to avoid an infinite
cascade of messages, you have to be able to send to an address that
bypasses \(.forward)\ file processing. This can be done by including a
router like this one
==> real_localuser:
driver = accept
check_local_user
transport = local_delivery
prefix = real-
before the \%redirect%\ router that handles \(.forward)\ files. This will
do an ordinary local delivery without \(.forward)\ processing, if the
local part is prefixed by \"real-"\. You can then set something like
the following options on the \%redirect%\ router:
==> skip_syntax_errors
syntax_errors_to = real-$local_part@$domain
syntax_errors_text = "\
This is an automatically generated message. An error has been \
found\nin your .forward file. Details of the error are reported \
below. While\nthis error persists, messages addressed to you will \
get delivered into\nyour normal mailbox and you will receive a \
copy of this message for\neach one."
A final tidying setting to go with this is a rewriting rule that changes
\"real-username"\ into just \"username"\ in the headers of the message:
==> \N^real-([^@]+)@your\.dom\.ain$\N $1@??? h
This means that users won't ever see the \"real-"\ prefix, unless they
look at the ::Envelope-To:: header.
Q0414: I have set \caseful_local_part\ on the routers that handle my local
domain because my users have upper case letters in their login names,
but incoming mail now has to use the correct case. Can I relax this
somehow?
A0414: If you really have to live with caseful user names but want incoming
local parts to be caseless, then you have to maintain a file, indexed by
the lower case forms, that gives the correct case for each login, like
this:
==> admin: Admin
steven: Steven
mcdonald: McDonald
lamanch: LaManche
...
and at the start of the routers that handle your local domain, put one
like this:
==> set_case_router:
driver = redirect
data = ${lookup{${lc:$local_part}}lsearch{/the/file}}
qualify_preserve_domain
For efficiency, you should also set the \redirect_router\ option to cause
processing of the changed address to begin at the next router. If you
are otherwise using the default configuration, the setting would be
==> redirect_router = system_aliases
If there are lots of users, then a DBM or cdb file would be more
efficient than a linear search. If you are handling several domains,
you will have to extend this configuration to cope appropriately.
Q0415: Can I use my existing alias files and forward files as well as procmail
and effectively drop in Exim in place of Sendmail ?
A0415: Yes, as long as your alias and forward files don't assume that pipes are
going to run under a shell. If they do, you either have to change them,
or configure Exim to use a shell (which it doesn't by default).
Q0416: What is quickest way to set up Exim so any message sent to a
non-existing user would bounce back with a different message, based
on the name of non-existing user?
A0416: Place this router last, so that it catches any local addresses that
are not otherwise handled:
==> non_exist:
driver = accept
transport = non_exist_reply
no_verify
Then add the following transport to the transports section:
==> non_exist_reply:
driver = autoreply
user = exim
to = $sender_address
subject = User does not exist
text = You sent mail to $local_part. That's not a valid user here. \
The subject was: $subject.
If you want to pick up a message from a file, you can use the \file\
option (use \file_expand\ if you want its contents expanded).
Q0417: What do I need to do to make Exim handle \(/usr/ucb/vacation)\ processing
automatically, so that people could just create a \(.vacation.msg)\ file in
their home directory and not have to edit their \(.forward)\ file?
A0417: Add a new router like this, immediately before the normal \%localuser%\
router:
==> vacation:
driver = accept
check_local_user
require_files = $home/.vacation.msg
transport = vacation_transport
unseen
and a matching new transport like this:
==> vacation_transport:
driver = pipe
command = /usr/ucb/vacation $local_part
However, some versions of \(/usr/ucb/vacation)\ do not work properly unless
the DBM file(s) it uses are created in advance - it won't create them
itself. You also need a way of removing them when the vacation is over.
Another possibility is to use a fixed filter file which is run whenever
\(.vacation.msg)\ exists, for example:
==> vacation:
driver = redirect
check_local_user
require_files = $home/.vacation.msg
file = /some/central/filter
allow_filter
The filter file should use the \"if personal"\ check before sending mail,
to avoid generating automatic responses to mailing lists. If sending a
message is all that it does, this doesn't count as a ``significant''
delivery, so the original message goes on to be delivered as normal.
Yet another possibility is to make use of Exim's \%autoreply%\ transport,
and not use \(/usr/ucb/vacation)\ at all.
Q0418: I want to use a default entry in my alias file to handle unknown local
parts, but it picks up the local parts that the aliases generate. For
example, if the alias file is
==> luke.skywalker: luke
ls: luke
*: postmaster
then messages addressed to \/luke.skywalker/\ end up at \/postmaster/\.
A0418: The default mechanism works best with virtual domains, where the
generated address is not in the same domain. If you just want to pick up
all unknown local parts and send them to postmaster, an easier way to do
it is to put this as your last router:
==> unknown:
driver = redirect
data = postmaster
no_verify
Another possibility is to put the redirect router for these aliases
after all the other routers, so that local parts which are user names
get picked off first. You will need to have two aliasing routers if
there are some local parts (e.g. \/root/\) which are login names, but which
you want to handle as aliases.
Q0419: I have some obsolete domains which people have been warned not to use
any more. How can I arrange to delete any mail that is sent to them?
A0419: To reject them at SMTP time, with a customized error message, place
statments like this in the ACL:
==> deny message = The domain $domain is obsolete
domains = lsearch;/etc/exim/obsolete.domains
For messages that don't arrive over SMTP, you can use a router like
this to bounce them:
==> obsolete:
driver = redirect
domains = lsearch;/etc/exim/obsolete.domains
allow_fail
data = :fail: the domain $domain is obsolete
If you just want to throw away mail to those domains, accept them at
SMTP time, and use a router like this:
==> obsolete:
domains = lsearch;/etc/exim/obsolete.domains
data = :blackhole:
Q0420: How can I arrange that mail addressed to \(anything@???)\
gets delivered to \(something@???)\?
A0420: Set up a router like this:
==> user_from_domain:
driver = redirect
data = ${if match{$domain}{\N^(.+)\.mydomain\.com$\N}\
{$1@???}}
Q0421: I can't get a regular expression to work in a \local_parts\ option on
one of my routers.
A0421: Have you remembered to protect any backslash and dollar characters in
your regex from unwanted expansion? The easiest way is to use the
\"@\N"\ facility, like this:
==> local_parts = \N^0740\d{6}\N
Q0422: How can I arrange for all addresses in a group of domains \(*.example.com)\
to share the same alias file? I have a number of such groups.
A0422: For a single group you could just hard wire the file name into a router
that had
==> domains = *.example.com
set, to restrict it to the relevant domains. For a number of such groups
you can create a file containing the domains, like this:
==> *.example1.com example1.com
*.example2.com example2.com
...
Then create a router like this
==> domain_aliases:
driver = redirect
domains = partial-lsearch;/that/file
data = ${lookup{$local_part}lsearch*{/etc/aliases.d/$domain_data}}
The variable \$domain_data$\ contains the data that was looked up when the
\domains\ option was matched, i.e. \"example1.com"\, \"example2.com"\, etc.
in this case.
Q0423: Some of our users have no home directories; the field in the password
file contains \(/no/home/dir)\. This causes the error \*failed to stat
/no/home/dir (No such file or directory)*\ when Exim tries to look for a
\(.forward file)\, and the delivery is deferred.
A0423: There are two issues involved here:
(1) With the default configuration, you are asking Exim to check for a
\(.forward)\ file in the user's home directory. If no file is found,
Exim tries to \^^stat()^^\ the home directory. This is so that it will
notice a missing NFS home directory, and not treat it as if the
\(.forward)\ file did not exist. This \^^stat()^^\ is failing when the
home directory really doesn't exist. You should arrange for the
\%userforward%\ router not to run for these special users, by adding
this line:
==> condition = ${if eq {$home}{/no/home/dir}{no}{yes}}
(2) If you use \check_local_user\ on another router to route to a local
transport (again, this is what is in the default configuration), you
will also have to specify a current directory for the transport, because
by default it makes the home directory current. This is easily done by
adding
==> current_directory = /
to the transport or
==> transport_current_directory = /
to the router. Or you can add \home_directory\ to the transport, because
the current directory defaults to the home directory.
Q0424: How can I disable Exim's de-duplication features? I want it to do two
deliveries if two different aliases expand to the same address.
A0424: This is not possible. Duplication has other ramifications other than
just (in)convenience. Consider:
. Message is addressed to A and to B.
. Both A and B are aliased to C.
. Without de-duplication, two deliveries to C are scheduled.
. One delivery happens, Exim records that it has delivered the message
to C.
. The next delivery fails (C's mailbox is over quota, say).
Next time round, Exim wants to know if it has already delivered to C or
not, before scheduling a new delivery. Has it? Obviously, if duplicate
deliveries are supported, it has to remember not only that it has
delivered to C but also the ``history'' of how that delivery happened - in
effect an ancestry list back to the original envelope address. This it
does not do, and changing it to work in that way would be a lot of work
and a big upheaval.
The best way to get duplicate deliveries if you want them is not to use
aliases, but to route the addresses directly to a transport, e.g.
==> duplicates:
driver = accept
local_parts = lsearch;/etc/list/of/special/local/parts
transport = local_delivery
user = exim
Q0425: My users' mailboxes are distributed between several servers according to
the first letter of the user name. All the servers receive incoming mail
at random. I would like to have the same configuration file for all the
servers, which does local delivery for the mailboxes it holds, and sends
other addresses to the correct other server. Is this possible?
A0425: It is easiest if you arrange for all the users to have password entries
on all the servers. This means that non-existent users can be detected
at the first server they reach. Set up a file containing a mapping from
the first letter of the user names to the servers where their mailboxes
are held. For example:
==> a: server1
b: server1
c: server2
...
Before the normal \%localuser%\ router, place the following router:
==> mailbox_host:
driver = manualroute
check_local_user
transport = remote_smtp
route_list = * ${lookup{${substr_0_1:$local_part}}lsearch{/etc/mapfile}}
self = pass
This router checks for a local account, then looks up the host from the
first character of the local part. If the host is not the local host,
the address is routed to the \%remote_smtp%\ transport, and sent to the
correct host. If the host is the local host, the \self\ option causes
the router to pass the address to the next router, which does a local
delivery.
The router is skipped for local parts that are not the names of local
users, and so these addresses fail.
Q0426: One of the things I want to set up is for \(anything@onedomain)\ to forward
to \(anything@anotherdomain)\. I tried adding \($local_part@anotherdomain)\ to
my aliases but it did not expand - it sent it to that literal address.
A0426: If you want to do it that way, you can use the \"expand"\ operator on
the lookup used in the data option of the redirect router. For example:
==> data = ${expand:${lookup{$local_part}lsearch*{/etc/aliases}}}
Another approach is to use a router like this:
==> forwarddomain:
driver = redirect
domains = onedomain
data = $local_part@anotherdomain
The value of \data\ can, of course, be more complicated, involving
lookups etc. if you have lots of different cases.
Q0427: How can I have an address looked up in two different alias files, and
delivered to all the addresses that are found?
A0427: Use a router like this:
==> multi_aliases:
driver = redirect
data = ${lookup{$local_part}lsearch{/etc/aliases1}\
{$value${lookup{$local_part}lsearch{/etc/aliases2}{,$value}}}\
{${lookup{$local_part}lsearch{/etc/aliases2}{$value}fail}}}\
If the first lookup succeeds, the result is its data, followed by the
data from the second lookup, if any, separated by a comma. If the first
lookup fails, the result is the data from the third lookup (which also
looks in the second file), but if this also fails, the entire expansion
is forced to fail, thereby causing the router to decline.
Another approach is to use two routers, with the first re-generating the
original local part when it succeeds. This won't get processed by the
same router again. For example:
==> multi_aliases1:
driver = redirect
data = ${lookup{$local_part}lsearch{/etc/aliases1}{$value,$local_part}}
==> multi_aliases2:
data = ${lookup{$local_part}lsearch{/etc/aliases2}}
This scales more easily to three or more alias files.
Q0428: I've converted from Sendmail, and I notice that Exim doesn't make use
of the \"owner-"\ entries in my alias file to change the sender address in
outgoing messages to a mailing list.
A0428: If you have an alias file with entries like this:
==> somelist: a@b, c@d, ...
owner-somelist: postmaster
Sendmail assumes that the second entry specifies a new sender address
for the first. Exim does not make this assumption. However, you can make
it take the same action, by adding
==> errors_to = owner-$local_part@???
to the configuration for your aliasing router. This is fail-safe,
because Exim verifies a new sender address before using it. Thus, the
change of sender address occurs only when the owner entry exists.
Q0429: I would like to deliver mail addressed to a given domain to local
mailboxes, but also to generate messages to the envelope senders.
A0429: You can do this with an ``unseen'' router and an \%autoreply%\ transport,
along the following lines:
==> # Router
auto_warning_r:
driver = accept
check_local_user
domains = <domains you want to do this for>
condition = ${if eq{$sender_address}{}{no}{yes}}
transport = warning_t
no_verify
unseen
Place this router immediately before the normal \%localuser%\ router. The
\unseen\ option means that the address is still passed on to the next
router. The transport is configured like this:
==> # Transport
warning_t:
driver = autoreply
file = /usr/local/mail/warning.txt
file_expand
from = postmaster@???
to = $sender_address
user = exim
subject = Re: Your mail to $local_part@$domain
Note the use of the \condition\ option to avoid attempting to send a
message when there is no sender (that is, when the incoming message is a
bounce message). You can of course extend this to include other
conditions. If you want to log the sending of messages, you can add
==> log = /some/file
to the transport and also make use of the \once\ option if you want to
send only one message to each sender.
Q0430: Whenever Exim tries to route a local address, it gives a permission
denied error for the \(.forward)\ file, like this:
==> 1998-08-10 16:55:32 0z5y2W-0000B8-00 == xxxx@??? <xxxx@???>
D=userforward defer (-1): failed to open /home/xxxx/.forward
(userforward router): Permission denied (euid=1234 egid=101)
A0430: Have you remembered to make Exim setuid \/root/\?
Q0431: How do I configure Exim to allow arbitrary extensions in local parts, of
the form \/+extension/\?
A0431: Add this pre-condition to the relevant router:
==> local_part_suffix = +*
If you want the extensions to be optional, also add the option
==> local_part_suffix_optional
When the router runs, \$local_part$\ contains the local part with the
extension removed, and the extension (if any) is in \$local_part_suffix$\.
If you have set \check_local_user\, the test is carried out after the
extension is removed.
Q0432: I use NIS for my user data. How can I stop Exim rejecting mail when my
NIS servers are being restarted?
A0432: Exim doesn't know that you are using NIS; it just calls the \^^getpwnam()^^\
function, which is routed by nsswitch. Unfortunately, \^^getpwnam()^^\
was never designed to be routed through NIS, and it returns NULL if the
entry is not found or if the connection to the NIS server fails. This
means that Exim cannot tell the difference between ``no such user'' and
``NIS is down''.
Crutches to help with this problem are \finduser_retries\ in Exim, and
\^nscd^\ on the Unix side, but they are not perfect, and mail can still
be lost. However, Nico Erfurth pointed out that you can create a router
for Exim that tests for the availability of NIS, and force a defer if
NIS is not running:
==> check_nis:
driver = redirect
data = ${lookup {$local_part} nis {passwd}{}}
This should be placed before any router that makes any use of NIS,
typically at the start of your local routers. How does it work? If
your NIS server is reachable, the lookup will take place, and whether it
succeeds or fails, the result is an empty strting. This causes the
router to decline, and the address is passed to the following routers.
If your NIS server is down, the lookup defers, and this causes the
router to defer. A verification of an incoming address gets a temporary
rejection, and a delivery is deferred till later.
Q0433: How can I arrange for a single address to be processed by \*both*\
\%redirect%\ \*and*\ \%accept%\?
A0433: Check out the \unseen\ option.
Q0434: How can I redirect all local parts that are not in my system aliases to
a single address? I tried using an asterisk in the system alias file
with an \"lsearch*"\ lookup, but that send \*all*\ messages to the
default address.
A0434: If your alias file generates addresses in the local domain, they are
also processed as a potential aliases. For example, suppose this is your
alias file:
==> caesar: jc
anthony: ma
*: brutus
The local part \/caesar/\ is aliased to \/jc/\, but that address is then
reprocessed by the routers. As the address is in the local domain, the
alias file is again consulted, and this time the default matches. In
fact after the second aliasing, \/brutus/\ is also processed again from
the start, and is aliased to itself. However, this happens only once,
because the next time, Exim notices that the aliasing router has already
processed \/brutus/\, so the router is skipped in order to avoid
looping.
There are several ways of solving this problem; which one you use
depends on your aliasing data.
(1) If the result of aliasing is always a local user name, that is,
aliasing never generates another alias, you can use the
\redirect_router\ option on the router to specify that processing
the generated addresses must start at the next router. For example:
==> redirect_router = userforward
assuming that the next router is called \%userforward%\. This
ensures that there is at most one pass through the aliasing router.
(2) If you cannot rely on aliases generating non-aliases, it is often
easier not to use a default alias, but instead to place a router
such as the one below after all the other local routers (for the
relevant domains):
==> catch_unknown:
driver = redirect
domains = ...
data = brutus@$domain
Note that the default aliasing technique works more successfully for
virtual domains (see Q0401) because the generated address for the
default is not usually in the same virtual domain as the incoming
address.
Q0435: My alias file contains fully qualified addresses as keys, and some
wildcard domains in the form @foo.bar. Can Exim handle these?
A0435: You can handle fully qualified addresses with this router:
==> qualified_aliases:
driver = redirect
data = ${lookup{$local_part@$domain}lsearch{/etc/aliases}}
(Add any other options you need for the \%redirect%\ router.) Place this
router either before or after the default aliases router that looks up
the local part only. (Or, if you have no unqualified aliases, replace
the default router.)
To handle wildcards in the form @foo.bar you will need yet another
router. (Wildcards of the form *@foo.bar can be handled by an lsearch*@
lookup.) Something like this:
==> wildcard_aliases:
driver = redirect
data = ${lookup{@$domain}lsearch{/etc/aliases}}
Place this after the routers that handle the more specific aliases.
5. FILTERING
Q0501: My filter isn't working. How can I test it?
A0501: Use the \-bf-\ option (\-bF-\ for a system filter) to test the basic operation
of your filter. You can request debugging information for filtering only
by adding \"-d-all+filter"\ to the command.
Q0502: What I really need is the ability to obtain the result of a pipe
command so that I can filter externally and redirect internally. Is
this possible?
A0502: The result of a pipe command is not available to a filter, because Exim
does not run any actual deliveries while filtering. It just sets up
deliveries at this time. They all actually happen later. If you want to
run pipes and examine their results, you need to set up a single
delivery to a delivery agent such as \^procmail^\ which provides this kind
of facility.
An possible alternative is to use the \"${run"\ expansion item to run an
external command while filtering. In this case, you can make use of some
of the results of the command.
Q0503: I received a message with a ::Subject:: line that contained a non-printing
character (a carriage return). This messed up my filter file. Is there a
way to get round it?
A0503: Instead of \"$h_subject:"\ use \"${escape:$h_subject:}"\
Q0504: I want to search for \"$"\ in the subject line, but I can't seem to get
the syntax.
A0504: Try one of these:
==> if $h_subject: contains \$ then ...
if $h_subject: contains "\\$" then ...
Q0505: My problem is that Exim replaces \$local_part$\ with an empty string in the
system filtering. What's wrong or what did I miss?
A0505: A message may have many recipients. The system filter is run just once
at the start of a delivery attempt. Consequently, it does not make sense
to set \$local_part$\. Which recipient should it be set to? However, you
can access all the recipients from a system filter via the variable
called \$recipients$\.
Q0506: Using \$recipients$\ in a system filter gives me another problem: how can
I do a string lookup if \$recipients$\ is a list of addresses?
A0506: Check out the section of the filter specification called \*Testing a list of
addresses*\. If that doesn't help, you may have to resort to calling an
embedded Perl interpreter - but that is expensive.
Q0507: What are the main differences between using an Exim filter and using
\^procmail^\?
A0507: Exim filters and \^procmail^\ provide different facilities. Exim filters run
at routing time, before any deliveries are done. A filter is like a
``\(.forward)\ file with conditions''. One of the benefits is de-duplication.
Another is that if you forward, you are forwarding the original message.
However, this does mean that pipes etc. are not run at filtering time,
nor can you change the headers, because the message may have other
recipients and Exim keeps only a single set of headers.
\^procmail^\ runs at delivery time. This is for one recipient only, and so
it can change headers, run pipes and check the results, etc. However, if
it wants to forward, it has to create a new message containing a copy
of the original message.
It's your choice as to which of these you use. You can of course use
both.
Q0508: How can I allow the use of relative paths in users' filter files when
the directories concerned are not available from the password data?
A0508: You need to be running Exim 4.11 or later. You can then specify a value
for \$home$\ by setting the router_home_directory option on the
\%redirect%\ router.
For earlier releases, there is no way to specify the value of \$home$\
for a \%redirect%\ router; it either comes from the password data as a
result of \check_local_user\, or is unset.
Q0509: How can I set up a filter file to detect and block virus attachments?
A0509: Exim's filter facilities aren't powerful enough to do much more than
very crude testing. Most people that want virus checking are nowadays
using one of the separate scanning programs such as \^exiscan^\ (see
\?http://duncanthrax.net/exiscan/?\). There is some further information
about scanning with Exim via \?http://www.timj.co.uk/linux/exim.php?\.
Q0510: Is it possible to write code for scanning messages in Python?
A0510: \^elspy^\ is a layer of glue code that enables you to write Python code
to scan email messages at SMTP time. \^elspy^\ also includes a small
Python library with common mail-scanning tools, including an interface
to SpamAssassin and a simple but effective virus detector. You can
optain \^elspy^\ from \?http://elspy.sourceforge.net/?\.
Q0511: Whenever my system filter uses a \mail\ command to send a message, I get
the error \*User 0 set for address_reply transport is on the never_users
list*\. What does this mean?
A0511: The system filter runs as \/root/\ in Exim 4, unless you set
\system_filter_user\ to specify otherwise. When you set up a delivery
direct from a system filter (an autoreply is a special kind of
``delivery'') the transport runs as the same user, unless it has a
\user\ setting of its own. Normally, deliveries are not allowed to run
as \/root/\ as a security precaution; this is implemented by the
\never_users\ option.
The easiest solution is to add this to your configuration:
==> system_filter_user = exim
The system filter then runs as \/exim/\ instead of \/root/\.
Alternatively, you can arrange for autoreplies from the system filter to
use a special transport of their own, and set the \user\ option on that
transport.
Q0512: I'm trying to reference the ::Envelope-To:: header in my filter, but
\$h_envelope-to:$\ is always empty.
A0512: ::Envelope-To:: is added at delivery time, by the transport. Therefore,
the header doesn't exist at filter time. In a user filter, the values
you probably want are in \$original_local_part$\ and
\$original_domain$\. In a system filter, the complete list of all
envelope recipients is in \$recipients$\.
Q0513: I want my system filter to freeze all mails greater than 500K in size,
but to exclude those to a specific domain. However, I don't seem to be
able to use \$domain$\ in a system filter.
A0513: You cannot do this in a system filter, because a single message may have
multiple recipients, some in the special domain, and some not. That is
also the reason why \$domain$\ is not set in a system filter.
If you want to take actions on a per-recipient basis, you have to do it
in a router. However, freezing is not appropriate, because freezing
stops all deliveries. You could, however, delay delivery to all but the
special domains by using something like this:
==> delay_if_too_big:
driver = redirect
domains = !the.special.domain
condition = ${if >{$message_size}{500K}{yes}{no}}
allow_defer
data = :defer: message too big.
However, there isn't an easy way of ``releasing'' such messages at
present.
Q0514: When I try to send to two addresses I get an error in the filter
file \*malformed address: , e@??? may not follow a@???*\. What
is going on?
A0514: Have you got
==> deliver "a@???, e@???"
in your filter? If so, that is your problem. You should have
==> deliver a@???
deliver e@???
Each \deliver\ command expects just one address.
6. DELIVERY
Q0601: What does the error \*Neither the xxx router nor the yyy transport set
a uid for local delivery of...*\ mean?
A0601: Whenever Exim does a local delivery, it runs a process under a specific
user and group id (uid and gid). For deliveries into mailboxes, and to
pipes and files set up by forwarding, it normally picks up the uid/gid
of the receiving user. However, if an address is directed to a pipe or a
file by some other means, such an entry in the system alias file of the
form
==> majordomo: |/local/mail/majordomo ...
then Exim has to be told what uid/gid to use for the delivery. This can
be done either on the routerr that handles the address, or on the
transport that actually does the delivery. If a pipe is going to run a
setuid program, then it doesn't matter what uid Exim starts it out with,
and so the most straightforward thing is to put
==> user = exim
on either the router or the transport. A setting on the transport
overrides a setting on the router, so if the same transport is being
used with several routers, you should set the user on it only if you
want the same uid to be used in all cases.
In the default configuration, the transports used for file and pipe
deliveries are the ones called \address_file\ and \address_pipe\. You
can specify different transports by setting, for example,
==> pipe_transport = special_pipe_transport
on the \%system_aliases%\ router. Then you can set up \%special_pipe_transport%\
==> special_pipe_transport:
driver = pipe
user = ????
which will be used only for pipe deliveries from that one router.
What you put for the ???? is up to you, and depends on the particular
circumstances.
Q0602: Exim keeps crashing with segmentation errors (signal 11 or 139) during
delivery. This seems to happen when it is about to contact a remote
host or when a delivery is deferred.
A0602: This could be a problem with Exim's databases. Try running a delivery
with debugging turned on. If the last line of the debug output is
something like this:
==> locked /var/spool/exim/db/retry.lockfile
the crash is happening inside the DBM library. Check that your DBM
library is correctly installed. In particular, if you have installed a
second DBM library onto a system that already had one, check that its
version of \(ndbm.h)\ is being seen first. For example, if the new
version is in \(/usr/local/include)\, check that there isn't another
version in \(/usr/include)\. If you are using Berkeley db, you can set
==> USE_DB=yes
in your \(Local/Makefile)\ to avoid using \(ndbm.h)\ altogether. This is
particularly relevant for version 2 (or later) of Berkeley db, because
no \(ndbm.h)\ file is distributed with it. Another thing you can try is
to run
==> exim_dumpdb /var/spool/exim retry
to see if it also crashes, or build the \^test_dbfn^\ tool and fiddle
around with it. If both fail, it is most almost certainly a problem with
your DBM library. You could try to update it, or force Exim to use
another library. See the file \(doc/dbm.discuss.txt)\ for hints about
this.
Q0603: How can mails that are being routed through routers that do not set
\check_local_user\ be delivered under the uid of the recipient?
A0603: Q0601 contains background information on this. If you are using, say, an
alias file to direct messages to specific mailboxes, you can use
the \user\ option on either the router or the transport to set the uid.
What you put in the setting depends on how the required uid is to be
found. It could be looked up in a file or computed somehow from the
local part, for example.
Q0604: I want to use MMDF-style mailboxes. How can I get Exim to append the
ctrl-A characters that separate indvidual emails?
A0604: Set the \message_suffix\ option in the \%appendfile%\ transport. In fact,
for MMDF mailboxes you need a prefix as well as a suffix to get it
working right, so your transport should contain these settings:
==> message_prefix = "\1\1\1\1\n"
message_suffix = "\1\1\1\1\n"
Also, you need to change the \check_string\ and \escape_string\ settings so
that the escaping happens for lines in the message that happen to begin
with the MMDF prefix or suffix string, rather than ``From'' (the default):
==> check_string = "\1\1\1\1\n"
escape_string = "\1\1\1\1 \n"
Adding a space to the line is sufficient to prevent it being taken as a
separator.
Q0605: If a user's mailbox is over quota, is there a way for me to set it up so
that the mail bounces to the sender and is not stored in the mail queue?
A0605: In the retry section of the configuration, put
==> *@your.dom.ain quota
That is, provide no retry timings for over quota errors. They will then
bounce immediately. Alternatively, you can set up retries for a short
time only, or use something like this:
==> *@your.dom.ain quota_7d
*@your.dom.ain quota F,2h,15m; F,3d,1h
which bounces immediately if the user's mailbox hasn't been read for 7
days, but otherwise tries for up to 3 days after the first quota
failure.
Q0606: I'm using tmail to do local deliveries, but when I turned on the
\use_crlf\ option on the \%pipe%\ transport (tmail prefers \"@\r@\n"\
terminations) message bodies started to vanish.
A0606: You need to unset the \mesage_prefix\ option, or change it so that its
default \"@\n"\ terminator becomes \"@\r@\n"\. For example, the
transport could be:
==> local_delivery_mbx:
driver = pipe
command = /usr/local/bin/tmail $local_part
user = exim
current_directory = /
use_crlf
message_prefix =
The reason for this is as follows: tmail uses the line terminator on
the first line it sees to determine whether lines are terminated by
\"@\r@\n"\ or \"@\n"\. If the latter, it moans to stderr and changes subsequent
\"@\n"\ terminators to \"@\r@\n"\. The default setting of the \message_prefix\
option is \"From ...@\n"\, and this is unaffected by the \use_crlf\ option.
If you don't change this, tmail sees the first line terminated by
\"@\n"\ and prepends \"@\r"\ to the \"@\n"\ terminator on all subsequent
lines. However, if \use_crlf\ is set, Exim makes all other lines
\"@\r@\n"\ terminated, leading to doubled \"@\r@\r@\n"\ lines and
corrupt mbx mailboxes.
Q0607: When I activate ``return receipt'' for example in Netscape Mailbox
sending options, then I get an error message from Exim... something
like \*not supported*\. Can I activate delivery confirmations?
A0607: Exim does not support any kind of delivery notification.
(1) You can configure it to recognize headers such as
\Return-receipt-to:\ if you wish.
(2) Some people want MSN (message status notification). Such services
are implemented in MUAs, and don't impact on the MTA at all.
(3) I investigated the RFCs which describe the DSN (delivery status
notification) system. However, I was unable to specify any sensible way
of actually doing anything with the data. There were comments on the
mailing list at the time; many people, including me, conclude that DSN
is in practice unworkable. The killer problem is with forwarding and
aliasing. Do you propagate the DSN data with the generated addresses?
Do you send back a ``reached end of the DSN world'' or ``expanded'' message?
Do you do this differently for different kinds of aliasing/forwarding?
For a user who has a \(.forward)\ file with a single address in, this
might seem easy - just propagate the data. But what if there are several
forwardings? If you propagate the DSN data, the sender may get back
several DSN messages - and should the sender really know about the
detail of the receiver's forwarding arrangements? There isn't really
any way to distinguish between a \(.forward)\ file that is forwarding
and one that is a mini mailing list. And so on, and so on. There are so
many questions that don't have obvious answers.
Q0608: What does the message \*retry time not reached [for any host]*\ on the log
mean? Why won't Exim try to deliver the message?
A0608: That is not an error. It means exactly what it says. A previous attempt
to deliver to that address failed with a temporary error, and Exim
computed the earliest time at which to try again. This can apply to
local as well as to remote deliveries. For remote deliveries, each host
(if there are several) has its own retry time.
If you are running on a dial-up host, the rest of this answer probably
does not apply to you. Go and read Q1404 instead. If your host is
permanently online, read on...
Some MTAs have a retrying schedule for each message. Exim does not work
like this. Retry timing is normally host-based for remote deliveries and
address-based for local deliveries. (There are some exceptions for certain
kinds of remote failure - see \*Errors in outgoing SMTP*\ in the manual.)
If a new message arrives for a failing address and the retry time has
not yet arrived, Exim will log \*retry time not reached*\ and leave the
message on the queue, without attempting delivery. Similarly, if a queue
runner notices the message before the time to retry has arrived, it
writes the same log entry. When the retry time has past, Exim attempts
delivery at the next queue run. If you want to know when that will be,
run the exinext utility on the address, for example:
==> exinext user@???
You can suppress these messages on the log by including \"-retry_defer"\
in the setting of \log_selector\. You can force a delivery attempt on a
specific message (overriding the retry time) by means of the -M option:
==> exim -M 10hCET-0000Bf-00
If you want to do this for the entire queue, use the \-qf-\ option.
Q0609: Exim seems to be sending the same message twice, according to the log,
although there is a difference in capitalization of the local part of
the address.
A0609: That is correct. The RFCs are explicit in stating that capitalization
matters for local parts. For remote domains, Exim is not entitled to
assume case independence of local parts. I know, it is utterly silly,
and it causes a lot of grief, but that's what the rules say. Here is a
quote from RFC 2821:
... a command verb, an argument value other than a mailbox local-part,
and free form text MAY be encoded in upper case, lower case, or any
mixture of upper and lower case with no impact on its meaning. This
is NOT true of a mailbox local-part. The local-part of a mailbox
MUST BE treated as case sensitive. Therefore, SMTP implementations
MUST take care to preserve the case of mailbox local-parts. Mailbox
domains are not case sensitive. In particular, for some hosts the
user "smith" is different from the user "Smith". However, exploiting
the case sensitivity of mailbox local-parts impedes interoperability
and is discouraged.
Q0610: How can I force the next retry time for a host to be now?
A0610: You can change the retry time with the \^exim_fixdb^\ utility, but its
interface is very clumsy. If you have a message for the host on the
queue, the simplest thing to do is to force a delivery with the \-M-\
command line option. If delivery succeeds, the retry data will get
cleared. If the host is past the cutoff time, so that messages are
bouncing immediately without trying a delivery, you can use \-odq-\ to
put a message on the queue without a delivery attempt, and then use
\-M-\ on it.
Q0611: I set up \"|/bin/grep Subject|/usr/bin/smbclient -M <netbiosname>"\ as an
alias but it doesn't work.
A0611: That is a shell command line. Exim does not run pipe commands under a
shell by default (for added security - and it saves a process). You
need something like
==> "|/bin/sh -c '/bin/grep Subject|/usr/bin/smbclient -M <netbiosname>'"
Q0612: Why does the \%pipe%\ transport add a line starting with \">From"\ to
messages?
A0612: Actually, it adds a line starting with \"From"\ followed by a space.
This is commonly referred to as the \"From_"\ line, to emphasize the
fact that \"From"\ is followed by a space and not a colon. This is a
pseudo-header line that contains the envelope sender address and the
time of delivery. It originated as a separator line in Berkeley format
mailboxes, but is also used in other contexts. (And yes, it is often
confused with the ::From:: header line, and this causes a lot of grief.
The use of \"From_"\ was one of the really bad email design decisions.)
Exim's \%pipe%\ transport adds this pseudo-header line by default
because \(/usr/ucb/vacation)\ needs it, and that is one of the the most
common uses of piping. The \^procmail^\ local delivery agent also makes
use of the \"From_"\ line. If you do not want it, change the setting of
\message_prefix\ on the \%pipe%\ transport. For example, to remove the
line altogether, use
==> message_prefix =
If you are not piping to \(/usr/ucb/vacation)\ or \^procmail^\, it is
likely that you do not need a \"From_"\ line, and indeed it may cause
problems if it is present.
One user reported that this line gave trouble when a pipe was used to
send messages to Courier's \^deliverquota^\ program. The line was
retained with the message, and caused problems for MS Exchange 2000 when
retrieving messages with its built-in POP collector. Specifically, it
caused Exchange to not be able to recognise message attachments.
Q0613: I have set \fallback_hosts\ on my \%smtp%\ transport, but after the error
\*sem@??? cannot be resolved at this time*\ Exim isn't using them.
A0613: \fallback_hosts\ works only if an attempt at delivery to the original
host(s) fails. In this case, Exim couldn't even resolve the domain
\(chat.ru)\ to discover what the original hosts were, so it never got as far
as the transport. However, see Q0315 for a possible solution.
Q0614: After the holidays my ISP has always hundreds of e-mails waiting for me.
These are forced down Exim's throat in one go. Exim spawns a lot of
kids, but is there some limit to the number of processes it creates?
A0614: Unless you have changed \smtp_accept_queue_per_connection\ it should
spawn only that many processes per connection (default 10). Your ISP
may be making many connections, of course. That is limited by
\smtp_accept_max\.
Q0615: When a message in the queue got to 12h old, Exim wrote \*retry timeout
exceeded*\ and removed all messages in the queue to this host - even
recent messages. How I can avoid this behaviour? I only want to remove
messages that have exceeded the maximum retry time.
A0615: Exim's retrying is host-based rather than message-based. The philosophy
is that if a host has been down for a very long time, there is no point
in keeping messages hanging around. However, you might like to check
out \delay_after_cutoff\ in the \%smtp%\ transport. It doesn't do what you
want, but it might help.
Q0616: Can Exim add a ::Content-Length:: header to messages it delivers?
A0616: You could include something like
==> headers_remove = "content-length"
headers_add = "Content-Length: $message_body_size"
to the \%appendfile%\ transport. However, the use of ::Content-Length:: can
cause several problems, and is not recommended unless you really know
what you are doing. There is a discussion of the problems in
\?http://home.netscape.com/eng/mozilla/2.0/relnotes/demo/content-length.html?\.
Q0617: Exim seems to be trying to deliver a message every 10 minutes, though
the retry rules specify longer times after a while, because it is
writing a log entry every time, like this:
==> 1999-08-26 14:51:19 11IVsE-000MuP-00 == example@??? T=smtp defer
(-34): some host address lookups failed and retry time not reached for
other hosts or connection limit reached
A0617: It is looking at the message every 10 minutes, but it isn't actually
trying to deliver. It's looking up \(example.com)\ in the DNS and finding
this information:
==> example.com. MX 10 example-com.isp.example.com.
example.com. MX 0 mail.example.com.
mail.example.com. A 202.77.183.45
A lookup for example-com.isp.example.com. yielded NXDOMAIN
The last line means that there is no address (A) record in the DNS for
\(example-com.isp.example.com)\. That accounts for \*some host address
lookups failed*\, but the retry time for \(mail.example.com)\ hasn't been
reached, which accounts for \*retry time not reached for other hosts*\.
Q0618: I am trying to set exim up to have a automatic failover if it sees that
the system that it is sending all mail to is down.
A0618: Add to the \%remote_smtp%\ transport the following:
==> fallback_hosts = failover.server.name(s)
If there are several names, they must be separated by colons.
Q0619: I can't get Exim to deliver over NFS. I get the error \*fcntl() failed:
No locks available*\, though the lock daemon is running on the NFS server
and other hosts are able to access it.
A0619: Check that you have \(lockd)\ running on the NFS client. This is not
always running by default on some systems (Red Hat is believed to be one
such system).
Q0620: Why does Exim bounce messages without even attempting delivery, giving
the error \*retry time not reached for any host after a long failure
period*\?
A0620: This message means that all hosts to which the message could be sent
have been failing for so long that the end of the retry period
(typically 4 or 5 days) has been reached. In this situation, Exim still
computes a next time to retry, but any messages that arrive in the
meantime are bounced straight away. You can alter this behaviour by
unsetting the \delay_after_cutoff\ option on the smtp transport. Then Exim
will try most messages for those hosts once before giving up.
Q0621: My \(.forward)\ file is \"|/usr/bin/procmail -f-"\ and mail gets delivered,
but there was a bounce to the sender, sending him the output of procmail.
How can I prevent this?
A0621: Exim's default configuration is set up like this:
==> address_pipe:
driver = pipe
return_output
The \return_output\ option requests that any output that the pipe
produces be returned to the sender. That is the safest default. If you
don't want this, you can either remove the option altogether, or change
it to \return_fail_output\, to return output only if the command fails.
Note that this will affect all pipes that users run, not just your
procmail one. It might be better to arrange for procmail not to produce
any output when it succeeds.
Q0622: Can I write an ordinary file when I run a perl script as a transport
filter for the \%remote_smtp%\ and \%address_pipe%\ transports?
A0622: Yes, provided the file is writeable by the uid under which the transport
runs (the Exim user in the case of the remote transport). However, if two
messages are being delivered at once, their data will get mixed up in
the file unless you implement your own locking scheme. If all you want
to do is to take a copy of the message, another approach that avoids
the locking problem is to use a system filter to set up an ``unseen''
delivery to a file. If you only want the message's headers, you can
set \message_filter_file_transport\ to point to a special \%appendfile%\
transport that has \headers_only\ set.
Q0623: My \(/var/spool/mail)\ has grown drastically. Is there any possibility of
using two directories?
A0623: You can use an expansion string to split mailboxes between two
directories. For example,
==> file = /var/spool/mail${nhash_2:$local_part}/$local_part
which does a hash on the local part, producing either 0 or 1, thereby
using \(mail0) or \(mail1)\. But remember, the MUAs that read these mailboxes
also have to know where they are.
Q0624: Sendmail has a program called \^smrsh^\ that restricts what binaries
can be run from sendmail aliases. Is there something like this in Exim ?
A0624: Check out the \allow_commands\ option in the \%pipe%\ transport.
Q0625: I wish to have large emails go out one at a time.
A0625: One possibility is to set up a router that defers all large messages,
except in queue runs. Since queue runners deliver just one
message at a time, if you limited the number of simultaneous queue
runners to 1, you would get the effect you wanted. A suitable router
might be
==> defer_if_large_unless_queue_run:
driver = redirect
condition = ${if or{{queue_running}{<{$message_size}{200K}}}{no}{yes}}
allow_defer
data = :defer: too large for immediate delivery
no_verify
Of course, this would always delay any large message until the next
queue runner, but if you run them fairly regularly, this shouldn't be a
huge problem, and may even be desirable. Note the use of \no_verify\ to
ensure that this router is not used when Exim is verifying addresses.
Q0626: Exim can route local parts independent of their case, but the Cyrus LMTP
daemon requires the correct case. How can I fix this?
A0626: You need to rewrite the local part to the correct case before running
the router that routes to Cyrus. For example, if you require all lower
case, and your router is called \local_user\, put this router in front
of it:
==> lowercase_local:
driver = redirect
redirect_router = local_user
domains = +local_domains
data = ${lc:$local_part}@$domain
The setting of \redirect_router\ causes processing of the rewritten
address to start at the next router, instead of the first router. See
also Q0630, and C045 for a more complete Cyrus configuration.
Q0627: Is there a command I can send to Exim to retry all queued messages
regardless of their retry schedule?
A0627: The \-qff-\ option starts a queue runner that forces a delivery attempt
for all messages, including frozen ones. If you use \-qf-\, frozen
messages are skipped.
Q0628: I have the default retry rule, which I thought meant that Exim should
keep trying for four days, but it seems to be bouncing some messages
immediately.
A0628: See Q0615 and Q0620.
Q0629: I'm having trouble with quotas and Courier, because Exim is not handling
maildirsize files.
A0629: You will do better to move the quota handling to Courier. Use \^maildrop^\
as your MDA rather than direct Exim delivery. This also has the
advantage that if you give web access to the mail spool (over \^sqwebmail^\)
you can then use the web front end to edit \^maildrop^\ filter files.
Q0630: How can I configure Exim to deliver to a Cyrus message store?
A0630: (1) The reference manual contains an example that uses pipe delivery.
(2) Here is a transport that uses LMTP delivery, assuming that
\$local_part$\ contains the username:
==> cyrus_inbox:
driver =lmtp
user = cyrus
socket = /var/cyrus/socket/lmtp
(3) This is a transport that delivers direct to a non-inbox mailbox:
==> cyrus_mailbox:
driver = pipe
user = $local_part
message_prefix =
message_suffix =
log_fail_output
return_output
command = "/usr/cyrus/bin/deliver -a $local_part \
-m <mailbox-name> $local_part"
This delivers to the Cyrus mailbox \"user.$local_part.<mailbox-name>"\.
Using \"user = $local_part"\ and \"-a $local_part"\ makes it work
without needing an explicit `p' ACL set for `anyone' on the mailbox.
Q0631: I would like to choose a retry rule based on on the sender rather than
the recipient address. Is this possible?
A0631: Yes. The address part of a retry rule is matched as a single-item
address list. Such lists are always expanded, so you can use something
like this:
==> "${if eq{$sender_address}{xxx}{*@*}{no@no}}" quota F,1h,10m; ...
If the sender address is ``xxx'', the pattern expands to ``*@*'', which
matches all recipient addresses; if you want to, you can make this a
more restrictive pattern. If the sender address is not ``xxx'', the
pattern expands to ``no@no'', which is assumed to be a recipient address
that can never match, so the retry rule is skipped.
Q0632: What does the error \*User 1 set for local_mbx_delivery transport is on
the never_users list*\ mean?
A0632: You have configured the \%local_mbx_delivery%\ to run as the user whose
id (uid) is 1. However, this user is on the list defined by the
\never_users\ runtime option, or the \\FIXED_NEVER_USERS\\ compile-time
option. These are ``safety catch'' lists; Exim refuses to deliver to any
user that is on them. The most common use of \never_users\ is to avoid
doing any deliveries as \/root/\, but it can contain other uids.
Q0633: Why is \$domain$\ not set in the \%smtp%\ transport?
A0633: The \%smtp%\ transport can handle several recipient addresses at once.
This happens by default if the host lists for the addresses are
identical. A single copy of the message is sent, using multiple \\RCPT\\
commands to transmit multiple envelope recipients. The \$domain$\
variable is set in the \%smtp%\ transport only if all the recipient
addresses have the same domain. You must have a case where several
addresses with different domains resolve to the same set of hosts.
If you want to restrict the transport so that it handles only a single
domain at once (but still possibly with more than one recipient), set
==> multi_domain = false
If you want to restrict the transport so that it handles only a single
address at once, set
==> max_rcpt = 1
Q0634: How can I stop a local transport from trying to access the user's home
directory, even when the delivery is to a file that is elsewhere?
A0634: See answer (2) for Q0423.
Q0635: The log message \*error ignored*\ appears after some delivery failures.
What does it mean?
A0635: This message is written when Exim fails to deliver a bounce message whose
age is greater than \ignore_bounce_errors_after\. It indicates that the
failing bounce message has been discarded.
The same message is written after failed deliveries when a filter file
uses the \noerror\ feature when setting up a delivery, or if a router
has the setting
==> errors_to = <>
Both of these specify that delivery failures are to be discarded.
7. POLICY CONTROLS
Q0701: How do I block unwanted messages from outside my host?
A0701: Exim uses Access Control Lists (ACLs) for controlling incoming mail from
other hosts. A whole chapter in the reference manual is devoted to
describing how they work. A wide variety of conditions can be imposed on
incoming messages.
The default Exim run time configuration contains an example of an ACL
which blocks all relaying, and messages whose senders cannot be
verified. This example is heavily commented and worth studying.
Q0702: I don't want to block spam entirely; how can I inspect each message
before deciding whether or not to deliver it?
A0702: Wherever possible, inspection and rejection is best done automatically
in an ACL, that is, before the message is accepted. If you want to
verify manually each message that is classified as spam by an automatic
check, you can arrange for a system filter to freeze such messages after
they have been accepted.
If, after inspection, you decide not to deliver the message, it is
safest to discard it, using the \-Mrm-\ option. Use of the \-Mg-\ option
to force a bounce carries the risk of ``collateral spam'' if the sender
address is faked.
Q0703: How can I test that my spam blocks are working?
A0703: The \-bh-\ option allows you to run a testing SMTP session as if from a
given IP address. For example,
==> exim -bh 192.168.178.39
In addition to the normal SMTP replies, it outputs commentary about
which tests have succeeded or failed. If you are not interested in the
details, but just want to know if a particular sender at a particular IP
address is able to mail to a particular recipient, you can use the
\exim_checkaccess\ utility, which provides a ``packaged'' version of
\-bh-\. You call it like this:
==> exim_checkaccess 192.168.53.23 recip@??? -f sender@???
If you don't give a sender, \"<>"\ is used (that it, it acts like a
bounce message).
Q0704: How can I test that Exim is correctly configured to use the Realtime
Blackhole List (RBL)?
A0704: The \-bh-\ option allows you to run a testing SMTP session as if from a
given address. The \^exim_checkaccess^\ utility provides a more packaged
version of this facility. You need to know a blocked IP address with
which to test. Such a testing address is kindly provided by Russell
Nelson:
==> linux.crynwr.com [192.203.178.39]
You can also send mail to \(nelson@???)\ from the server
whose RBL block you are testing. The robot that receives that email
will attempt to send a piece of test email in reply. If your RBL block
didn't work, you get a message to that effect. Regardless of whether the
RBL block succeeds or not, it emails you the results of the SMTP
conversation from a host that is not on the RBL, so you can see how your
server looks from the view of someone on the RBL.
Q0705: How can I use tcpwrappers in conjunction with Exim?
A0705: Exim's own control facilities can do all that tcpwrappers can do.
However, if you are already using tcpwrappers for other things it might
be convenient to include Exim controls in the same place.
First of all, ensure that Exim is built to call the tcpwrappers library,
by including \\USE_TCPWRAPPERS=yes\\ in \(Local/Makefile)\. You also need to
ensure that the header file \(tcpd.h)\ is available at compile time, and the
\(libwrap.a)\ library is available at link time, typically by including it in
\\EXTRALIBS\\. You may need to copy these two files from the tcpwrappers
build directory to, for example, \(/usr/local/include)\ and \(/usr/local/lib)\,
respectively. Then you could reference them by
==> CFLAGS=-I/usr/local/include
EXTRALIBS=-L/usr/local/lib -lwrap
in \(Local/Makefile)\. There are two ways to make use of the functionality,
depending on how you have tcpwrappers set up. If you have it set up to
use only one file, you ought to have something like:
==> /etc/hosts.allow:
==> exim : <client_list> : <allow_or_deny>
For example:
==> exim : LOCAL 192.168.0. .friendly.domain special.host : ALLOW
exim : ALL : DENY
This allows connections from local hosts (chiefly //localhost//), from
the subnet 192.168.0.0/24, from all hosts in \(*.friendly.domain)\, and
from a specific host called \(special.host)\. All other connections are
denied. If you have tcpwrappers set up to use two files, use the
following:
==> /etc/hosts.allow:
==> exim : <client_list>
==> /etc/hosts.deny:
==> exim : <client_list>
Read the \^hosts_access^\ man page for more ways of specifying clients,
including ports, etc., and on logging connections.
Q0706: How can I get POP-auth-before-relay (aka POP-before-SMTP) support in
Exim?
A0706: Exim 4 supports the ``whoson'' (\?http://whoson.sourceforge.net?\)
facility for doing this. If you set this up, you can do the check in an
Exim ACL by a statement like this:
==> require condition = \
${lookup whoson {$sender_host_address}{yes}{no}}
Otherwise you need to arrange for a list of permitted IP addresses to be
maintained in a file or database, and use this in a \hosts\ condition in
an ACL statement. An Exim user has published this recipe:
\#\#\#\#\?http://www.zeiss.cx/memo/computer/linux/email/exim-s-a-p.html?\
Another Exim user submitted the following idea:
Use a script to grab authenticated IP addresses from the log files of
the POP3 and IMAP4 daemons. These are used to create files in the
directory tree \(/var/db/popb4smtp)\. The existence of a file represents a
valid ``popped recently token'' for the IP address used as the filename.
Another script periodically removes stale files from the tree (after two
hours). There's a small race condition here; it's possible for a file
to be deleted just after it has been updated by the script that watches
the logs. For low-volume servers, the odds of hitting this window are
low.
A POPB4SMTP_CLIENT macro in the Exim configure file provides a reusable
``has this sender popped recently?'' query:
==> POPB4SMTP_SUBDIR = /var/db/popb4smtp/${substr_-1_1:$sender_host_address}
POPB4SMTP_CLIENT = ${if exists {POPB4SMTP_SUBDIR/$sender_host_address} \
{$sender_host_address} {0} }
Now you can use it just about anywhere, including in your ACLs. Simple
examples include:
==> hostlist relay_hosts = 127.0.0.1/32 : ... : POPB4SMTP_CLIENT
host_lookup = !127.0.0.1/32 : ... : !POPB4SMTP_CLIENT
rfc1413_hosts = !127.0.0.1/32 : ... : !POPB4SMTP_CLIENT
The two scripts (and a FreeBSD startup script for them) are available
for download at:
\#\#\#\#\?http://people.FreeBSD.org/~sheldonh/popb4smtp-nodb.tar.gz?\
Q0707: I have one or two cases where my host correctly rejects messages, but
the remote host is quite persistent, and keeps trying over and over.
A0707: It is an unfortunate fact that a number of SMTP clients, in violation of
the SMTP RFC, do not treat a permanent error code that is given after
the DATA portion of the transaction as a permanent error. Consequently
they keep resending the message, and the worst offenders do so at very
short intervals.
The only way to stop such behaviour is to blacklist the IP address, or
the envelope sender, or both, in such a way that future messages get
rejected at RCPT time instead of at DATA time. You could also complain
to the remote host's administrators.
Q0708: How can I run customized verification checks on incoming addresses?
A0708: There are a number of possibilities:
(1) If you can implement your checks in Perl, you can use Exim's
facility for running an embedded Perl interpreter. For example, if you
want to run special checks on local addresses, you could use ACL
an statement like this:
==> require domains = my.local.domain
condition = ${perl{verify}{$local_part}}
The result of the Perl function should be ``yes'' or ``no''.
(2) You could also run an external program in a similar way, by a
statement such as:
==> require domains = my.local.domain
condition = ${run{/my/verifier $local_part}}
This requires the use of another process, so could prove more expensive
than Perl.
(3) If you are prepared to write C code, read the chapter in the manual
entitled \*Adding a local scan function to Exim*\.
Q0709: Does Exim apply RBL checks to error messages, those with an envelope
sender of \"<>"\ ?
A0709: This depends on the ACL configuration. You can test for bounce messages
(by looking for an empty sender address) and thereby exclude them from
RBL checking if you want. This ACL statement does that:
==> deny senders = ! :
dnslist = blackholes.mail-abuse.org
However, some spam does come with an empty sender address, so this may
not be a good idea.
Q0710: I want to reject certain sender-recipient combinations, with a specific
message for each such combination.
A0710: Set up a file (or database) containing the messages, keyed by the
combination, for example:
==> sender1@sdomain1=>recipient1@rdomain1: blocked because...
sender2@sdomain2=>recipient2@rdomain2: blocked because...
If you have lots of recipients for the same sender, it might be easier
to generate this file from more convenient data. In your ACL that is run
for each RCPT command, you can then put:
==> deny message = ${lookup{$sender_address=>$local_part@$domain}\
lsearch{/that/file}}
condition = ${lookup{$sender_address=>$local_part@$domain}\
lsearch{/that/file}}{yes}{no}}
The condition is tested first. If the lookup succeeds, the condition
succeeds so access is denied. The message is then expanded, but the
lookup won't be repeated, because Exim will have cached the previous
result.
This approach blocks only incoming SMTP messages. If you need to do
similar blocks for messages that do not arrive over SMTP, you have to
set up a suitable \%redirect%\ router with a \:fail:\ setting.
Q0711: Will Exim allow me to create a file of regexs and match incoming
external email to the list - and if a match is found file the offending
message into a special location? Also is it possible to make Exim only
filter parts of an incoming email - e.g. ignore large MIME attachments
for example and only process text/plain?
A0711: You can do some of this in a system filter. For example:
==> if $message_body matches <...some complicated regex...> or
$message_body matches <...some other regex...> or
$header_from: matches <...regex...> or
etc.
then
save /some/special/file
endif
or instead of \"save"\ you could have \"deliver"\ (to some address) or
\"pipe"\ (to some script).
There isn't any mechanism for ignoring attachments, but \$message_body$\
only looks at the first n bytes of the body, where n defaults to 500 but
can be changed.
A more expensive alternative would be to run a Perl subroutine using the
embedded Perl mechanism. If you passed over the message id, the Perl
code could read the message files on the spool and implement any
algorithm it liked for deciding what should be done.
Q0712: I've hacked sendmail to make an ioctl call at the time of the SMTP RCPT
command, to check if a user has exceeded their email quota. If they have
I issue a temporary failure and a message - can I do this with Exim?
A0712: If you can make this happen in Perl you can use the embedded Perl
facility, and use it from a \condition\ condition in an ACL statement.
You can also use the expansion facility to run an external program, but
this uses more resources because it uses another process.
Q0713: I'd like to pass all messages through a virus-scanning system before
delivery. Can Exim do this?
A0713: One way of achieving this is to deliver all messages via a pipe to a
checking program that resubmits them for delivery in some private way
that can be checked (e.g. on a specific SMTP port, or IP address). One
possibility is to use the `received protocol` field that can be set
for locally submitted mail via the \-oMr-\ command line option. This
router sends all messages that are not from the local host and whose
received protocol is not \"scanned-ok"\ to the \%virus_scan%\ transport:
==> vircheck:
driver = accept
transport = virus_scan
condition = ${if or {{eq {$received_protocol}{scanned-ok}} \
{eq {$sender_host_address}{127.0.0.1}}}\
{0}{1}}
One problem is that this approach scans the message for each recipient,
not just once per message.
The virus_scan transport should be set up to pipe the message to a
suitable checking program or script which runs as a trusted user. This
can then re-submit the message to Exim, using \-oMr-\ to set the received
protocol to \"scanned-ok"\, and the \-f-\ option to set the correct envelope
sender address. \**Warning:**\ If you forget to make the resubmitting process
run as a trusted user, the received protocol does not get set, and you
are likely to generate a loop.
Q0714: Is there a way to configure Exim to reject mail to a certain local host?
A0714: No, only to certain domains. To reject at SMTP time, you can put a line
like this in your ACL:
==> deny message = this domain is deliberately rejected
domains = a.certain.domain
To fail addresses in messages that do not arrive over SMTP, you can set
up a router like this:
==> reject_a_certain_domain:
driver = redirect
domains = a.certain.domain
allow_fail
data = :fail: this domain is deliberately rejected
Q0715: How can I get Exim to remove attachments from messages?
A0715: Exim does not contain facilities for modifying messages. You must use
an external program if you want to do this. You can route messages that
have a ::Content-type:: header line via a pipe to a command that does
the job and then re-submits the message to Exim. Alternatively, you
could use a transport filter to do this job.
Q0716: How can I arrange for each user to have a file listing the only sender
addresses from which she will accept mail? I want to do this so my
family members don't get any spam (or other inappropriate mail).
A0716: Let's assume each user has a file called \(.acceptlist)\ in the home
directory. You can put in your ACL a line like this:
==> require senders = /home/$local_part/.acceptlist
This will reject RCPT commands when the sender is not in the accept
list for the recipient. (Replace \(/home/$local_part)\ with whatever
the correct path to your user's home directories is.)
One problem with this is that it will block bounce messages, which have
empty senders. You can get round this, by changing the line to this:
==> require senders = : /home/$local_part/.acceptlist
However, this will, of course, let in spam that has a null sender.
Q0717: When using Nessus on a system that runs Exim, a number of security
issues are raised. Nessus complains that Exim answers to EXPN and/or
VRFY; sometimes it even complains that Exim allows relaying.
A0717: Exim supports EXPN and VRFY only if you permit it to do so in the ACLs
defined by \acl_smtp_expn\ and \acl_smtp_vrfy\, respectively. Otherwise,
its responses are
==> 550 Administrative prohibition
252 Administrative prohibition
Maybe the use of 252 is the ``problem''. It is recommended that this be
done (by those that discuss these things) because there are stupid
clients that attempt VRFY before sending a message.
Q0718: Could anyone points me to right rules to prevent sending/receiving
messages to/for domains which have one MX to localhost or only have
address 127.0.0.1 ?
A0718: See Q0319.
Q0719: I would like to have a per-user limit for the maximum size of messages
that can be sent.
A0719: The simplest way to do this is to put something in a system filter along
these lines:
==> if $message_size is above
"${lookup{$sender_address}lsearch{/some/file}{$value}{10M}}"
then
fail "Message is larger than $sender_address is allowed to send"
endif
In practice, an additional check that the message has arrived from your
local host or local network is probably wise because sender addresses
are easily forged.
Q0720: I set \"accept hosts=192.168.122.96/32"\ in order to accept mail for
relaying from my local LAN, but it doesn't work. What's wrong?
A0720: 192.168.122.96/32 is not a network, it is a single host. Exim uses CIDR
notation for specifying networks, where the number after the slash is
the number of bits in the IP address that must match. Your setting says
``32 bits must match''. If you really mean to specify ``the next 32
IP addresses'', you need 192.168.122.96/27.
Q0721: I have POP-before-SMTP set up on my Exim server, but some clients use
Outlook Express, which sends queued messages before checking the
mailbox, so it doesn't work.
A0721: Implement SMTP authentication.
Q0722: I installed Amavis and it is working, but bounces are simply vanishing.
A0722: Check that you haven't inadvertently set up the transport like this:
==> amavis:
driver = pipe
command = "/usr/sbin/amavis -f ${sender_address} -d ${pipe_addresses}"
The last line should be:
==> command = /usr/sbin/amavis -f <$sender_address> -d $pipe_addresses
The important thing is the <> around the sender address; removal of
the unnecessary "" and {} is just tidying. See the amavis FAQ at
\?http://www.amavis.org/amavis-faq.php3?\.
Q0723: I can't get Pine to work with PLAIN authentication; Exim keeps
responding "535 Incorrect authentication data".
A0723: You need to have this setting in your PLAIN authenticator:
==> server_prompts = :
This is missing in the examples in all but the most recent Exim
documentation, because it was not realized that PLAIN authentication
could be requested by a client without sending the data with the
request. If the data is not sent, an empty prompt is expected.
Q0724: I have used \":fail:"\ in some aliases; when one of these addresses is
refused, I see the message on the log, but the response to the remote
user is ``unknown user'' instead of the message from the alias file.
How can I change this?
A0724: Have you got a \message\ qualifier in the relevant ACL? Exim uses the
message line in the ACL in preference to the message returned by the
router. This is so you can restrict the amount of information that
``escapes'' from your site via SMTP if you want to. Remove the \message\
line in the ACL entry that has \"verify = recipient"\ and your message
will get through.
Alternatively, if you are running Exim 4.10 or later, you can use the
\$acl_verify_message$\ variable in your message to include the message
from the router. See also Q0725.
Q0725: I've set up some specific rejection messages for certain recipients, but
when I test them, the SMTP message is always \*550 5.1.1
<user@???>... User unknown*\.
A0725: That is not an Exim message (the ``5.1.1'' is a clue; Exim doesn't use
those extended codes). You are probably being defeated by software that
sees the 550 error code, and insists on putting in its own text. There
is stupid software that does this. You can test Exim by using \-bh-\ or
making a telnet call to the SMTP port. That way, there's no other
software intervening.
Q0726: My SMTP authentication can be bypassed by sending an unknown user name
and an empty password. What is wrong with this condition in a PLAIN
authenticator?
==> server_condition = ${if eq{$2} {${lookup mysql{SELECT password FROM \
accounts WHERE username='${local_part:$1}'}}}{1}{0}}
A0726: Your lookup item returns an empty string when the user does not exist.
You should instead arrange for the lookup to fail:
==> server_condition = ${if eq{$2} {${lookup mysql{SELECT password FROM \
accounts WHERE username='${local_part:$1}'}{$value}fail}}{1}{0}}
Q0727: When a message has many recipients, how can I stop SpamAssassin from
being called for each of them? I'm running it from a pipe transport.
A0727: In the transport configuration, set \batch_max\ to a value greater than
one.
Q0728: How do I use Exiscan, SA-Exim, SpamAssassin, Clam Antivirus, Sophos
SAVI, or sophie with Exim?
A0728: There's a mini-HOWTO about these available via
\?http://www.timj.co.uk/linux/exim.php?\.
See also sample configuration C047.
Q0729: How can I screen out addresses that are neither valid usernames or
distribution lists on mail being forwarded to an internal Win2K server?
A0729: A user suggested using a router like this to do the recipient
verification:
==> verify_user_router:
driver = accept
domains = win2kdomain.com
local_parts=\
ldap;user="cn=ldap-guest,cn=Users,dc=win2kdomain,dc=com"\
pass=guest \
ldap:://win2kpdc/dc=win2kdomain,dc=com?mailNickname?\
sub?(&(mailNickname=$local_part)\
(showInAddressBook=*)(sAMAccountName=*))
verify_only
Set up ldap-guest as a normal domain user on the Win2K PDC.
Also, you need to set \no_verify\ on all the other routers that handle
that domain.
Q0730: How can I use the same passwords for SMTP authentication as I use for
Courier IMAP access to my server?
A0730: You can access the Courier authdaemon from an Exim authenticator. You
must arrange for the Exim user (often \/exim/\ but sometimes \/mail/\)
to be able to access \(/var/run/courier/authdaemon/socket)\. The
configuration is something of a hack, but it is reported to work. Here
is a LOGIN authenticator:
==> login:
driver = plaintext
public_name = LOGIN
server_prompts = Username:: : Password::
server_condition = \
${if eq {${readsocket{/var/run/courier/authdaemon/socket}\
{AUTH 76\n${length_76:exim\nlogin\n$1\n$2\
\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\
\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\
\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n}}}}{FAIL\n} {no}{yes}}
server_set_id = $1
Here is a PLAIN authenticator:
==> plain:
driver = plaintext
public_name = PLAIN
server_prompts = :
server_condition = \
${if eq {${readsocket{/var/run/courier/authdaemon/socket}\
{AUTH 76\n${length_76:exim\nlogin\n$2\n$3\
\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\
\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\
\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n}}}}{FAIL\n} {no}{yes}}
server_set_id = $2
Q0731: Is there any defence I can use against spam sent through an open proxy?
A0731: The \*ident*\ feature can be used in some cases. See the discussion in
Q5023.
Q0732: I would like to either warn or deny when a host uses an underscore in
the EHLO command.
A0732: First, set
==> helo_allow_chars = _
This tells Exim not to reject the EHLO or HELO command immediately. Once
you have done that, you can test for the underscore in an ACL. For
example, to log a warning for hosts in your LAN, and reject for other
hosts, you could do something like this:
==> deny message = Underscores are not valid in host names
hosts = ! +lan_hosts
condition = ${if match{$sender_helo_name}{_}{yes}{no}}
==> warn log_message = Accepted underscore from [$sender_host_address]
condition = ${if match{$sender_helo_name}{_}{yes}{no}}
Q0733: Is there any way to tell Exim not to lookup the IP address against any
DNS black list if the connection is over IPv6?
A0733: Use this condition in your ACL:
==> condition = ${if match{${mask:$sender_host_address/0}}\
{${mask:::0/0}}{no}{yes}}
From Exim 4.23 onwards, this can be simplified to
==> condition = ${if isip6{$sender_host_address}{no}{yes}}
Q0734: How do MailScanner and Exiscan compare? What are the pros and cons?
A0734: The big advantage of Exiscan is that it can reject messages at SMTP time
before you have accepted responsibility for them, which means you don't
have to deal with bouncing messages and thereby becoming a collateral
spammer.
The big advantage of MailScanner is that it gives you much greater
control over the load on your machines. You configure it according to
the maximum processing capacity of your computer and it will not exceed
that; in fact because it deals with messages in batches the cost of
processing a message actually goes down slightly as the load increases,
because the per-batch costs are shared by more messages.
With Exiscan, you have to rely on Exim's load protection mechanisms,
which basically means that you have to stop accepting messages when your
machine gets too loaded. This is bad if the machine happens to be an
SMTP smarthost. You therefore need more overcapacity with Exiscan than
with MailScanner.
Q0735: How can I block non-FQDNs in HELO/EHLOs?
A0735: Many workstation clients send single-component names; take care that you
do not block legitimate mail. With that proviso, you can do it using
something like this in an ACL:
==> drop message = HELO doesn't look like a hostname
log_message = Not a hostname
condition = ${if match{$sender_helo_name} \
{\N^[^.].*\.[^.]+$\N}{no}{yes}}
This means: Drop the HELO unless it contains a dot somewhere in the HELO
string, but the string may not begin or end with a dot. Thus, the
imposed minimum length is 3 characters.
The data for HELO/EHLO doesn't have to be a host name; it may
legitimately be an IP address literal instead. The above test succeeds
with an IPv4 address literal, but if you want also to accept IPv6
address literals, you will have to modify the regular expression.
Q0736: Is it possible to tell exim to drop the connection after a server
attempts to send a message to a number of unknown users?
A0736: Yes. Use \$rcpt_fail_count$\ and the \^drop^\ ACL command, as in this
example:
==> drop message = Too many unknown users
condition = ${if >{$rcpt_fail_count}{15}{yes}{no}}
Q0737: Is there some way to tell Exim not to consider 127.0.0.1 as a valid MX?
A0737: See Q0319.
Q0738: How can I configure Exim to delay the SMTP connection if more than 10
invalid recipients are received in one message?
A0738: Put something like this in your RCPT ACL:
==> deny message = Max $rcpt_fail_count failed recipients allowed
condition = ${if >{$rcpt_fail_count}{10} {1}}
! verify = recipient
delay = ${eval: $rcpt_fail_count * 10}s
log_message = $rcpt_fail_count failed recipient attempts
This example increases the delay for each failed recipient.
Q0739: Does Exim support SPF?
A0739: An Exim ACL can be used. See \?
http://spf.pobox.com/downloads.html?\.
8. REWRITING ADDRESSES
Q0801: How can I get Exim to strip the hostname from the sender's address?
A0801: If you set up a rewriting rule in the following form:
==> *@*.your.domain $1@???
then Exim will rewrite all addresses in the envelope and the headers,
removing anything between \"@"\ and \"your.domain"\. This applies to all
messages that Exim processes. If you want to rewrite sender addresses
only, the the rule should be
==> *@*.your.domain $1@??? Ffrs
This applies the rule only to the envelope sender address and to the
::From::, ::Reply-to::, and ::Sender:: headers.
Q0802: I have Exim configured to remove the hostname portion of the domain on
outgoing mail, and yet the hostname is present when the mail gets
delivered.
A0802: Check the DNS record for your domain. If the MX record points to a CNAME
record instead of to an A record, some MTAs (not Exim) are liable to
rewrite addresses, changing your domain name to its ``canonical'' form,
as obtained from the CNAME record.
Q0803: I want to rewrite local addresses in mail that goes to the outside
world, but not for messages that remain within the local intranet.
A0803: You can use the \headers_rewrite\ option on a transport to do this.
The rewriting will then apply to just those copies of a message that
pass through the transport. The \return_path\ option can similarly be
used to rewrite the sender address. There is no way of rewriting
recipient addresses at transport time. However, as these are by
definition remote addresses, you probably don't want to rewrite them.
You have to set up the configuration so that it uses different SMTP
transports for internal and external mail. If you are using a single
router in both cases, you could configure it like this:
==> dnslookup:
driver = dnslookup
transport = ${if match{$domain}{\N\.my\.domain$\N}{int_smtp}{ext_smtp}}
This example uses the \%int_smtp%\ transport for domains ending in
\(.my.domain)\, and \%ext_smtp%\ for everything else. The \%ext_smtp%\ transport
could be something like this:
==> ext_smtp:
driver = smtp
headers_rewrite = *@*.my.domain \
${lookup{$1}cdb{/etc/$2/mail.handles.cdb}{$value}fail}
return_path = \
${if match{$return_path}{\N^([^@]+)@(.*)\.my\.domain$\N}\
{\
${lookup{$1}cdb{/etc/$2/mail.handles.cdb}{$value}fail}\
}\
fail}
This example uses a separate file of local-to-external address
translations for each domain. This is not the only possibility, of
course. The \headers_rewrite\ and \return_path\ options apply the same
rewriting to the header lines and the envelope sender address,
respectively.
Q0804: I'm using this rewriting rule to change login names into ``friendly''
names, but if mail comes in for an upper case login name, it doesn't
get rewritten.
==> *@my.domain ${lookup{$1}dbm{/usr/lib/exim/longforms}\
{$value}fail}@??? bcfrtFT
The longforms database has entries of the form:
==> ano23: A.N.Other
A0804: Replace \"$1"\ in your rule by \"${lc:$1}"\ to force the local part to lower
case before it is used as a lookup key.
Q0805: Is it possible to completely fail a message if the rewrite rules fail?
A0805: It depends on what you mean by ``fail a message'' and what addresses you
are rewriting. If you are rewriting recipient addresses for your local
domain, you can do:
==> *@dom.ain ${lookup{$1}dbm{/wher/ever}{$value}{failaddr}} Ehq
and in your alias file put something like
==> failaddr: :fail: Rewriting failed
This fails a single recipient - others are processed independently.
Q0806: I'm using \$domain$\ as the key for a lookup in a rewriting rule, but its
contents are not being lowercased. Aren't domains supposed to be handled
caselessly?
A0806: The value of \$domain$\ is the actual domain that appears in the address.
It could of course be lower cased, but I know that would cause some
unhappiness, because some people have mixed-case domain names which look
silly if the case is changed. Thus, one wants to preserve the case in
rewrites such as
==> *@*.TheRap.com something@$domain
because ``therap'' doesn't look like two words. I know it seems trivial,
but it is important to some people - especially if by some unfortunate
accident the lowercased word is something indecent.
You can trivally force lower casing by means of the \"${lc:"\ operator.
Instead of \"$domain"\ write \"${lc:$domain}"\.
Q0807: I want to rewrite local sender addresses depending on the domain of the
recipient.
A0807: In general, this is not possible, because a message may have more than
one recipient and Exim keeps just a single copy of each message. It may
also deliver one copy of a message with several recipient addresses.
You can do an incomplete job by using a regular expression match in a
rewrite rule to test, for example, the contents of the ::To:: header. This
would work except in cases of multiple recipients.
9. HEADERS
Q0901: I would like add some custom headers to selected outgoing mail based on
a specific domain and the subject line.
A0901: To the remote_smtp transport, add something like
==> headers_add = ${if and{\
{eq{$domain}{spec.dom}}\
{matches{$h_subject:}{whatever}}}\
{Content-Type: text/html; charset="us-ascii"} fail }
This example shows a ::Content-Type:: header, but you can have anything you
like, and multiple headers can be inserted by using \"@\n"\ to separate them.
Q0902: Is it possible to have Exim add a header to only certain local parts of
outgoing mail?
A0902: Only if you arrange for each such local part to receive its own private
copy of the mail. See \max_rcpt\ in the SMTP transport. If you set this
to 1, you could use conditions in an expansion string to add or not add
a header.
Q0903: How can I remove some part of the ::Received:: header?
A0903: Set \received_header_text\.
Q0904: How I can insert the PGP header line using Exim filters?
A0904: You can't insert headers in a user filter. A system filter can do so,
but the inserted lines then are included for all recipients.
Q0905: I know I can use a system filter to replace certain headers in messages,
but how can I add text to existing headers? I want to add [SPAM] to
the subject line of messages that appear to be spam.
A0905: You can only do this in a round about way, using filter commands like
this:
==> headers add "New-Subject: SPAM: $h_subject:"
headers remove subject
neaders add "Subject: $h_new-subject:"
headers remove new-subject
This trick works only in system filters, where the commands are obeyed
in order, and affect the master list of headers that apply to the whole
message. You cannot do this with the \headers_add\ and \headers_remove\
options on drivers.
10. PERFORMANCE
Q1001: I'm running a large mail server. Should I set \split_spool_directory\ to
improve performance?
A1001: Splitting the spool directory has most benefit if there are times when
there are a large number of messages on the queue. If all mail is
delivered very quickly, and the queue is always less than, say, a few
hundred messages, there isn't any need to do this. With larger queues,
there is a definite performance benefit to splitting the spool. It shows
up earlier on some types of filing system, compared with others.
Exim was not designed for handling large queues. If you are in an
enviroment where lots of messages remain on the queue for long periods
of time, consider implementing a back up host to which you pass these
messages, so that the main host's queue remains short. You can use
\fallback_hosts\ to do this, or a router that is conditional on
\$message_age$\.
Q1002: How well does Exim scale?
A1002: Although the author did not specifically set out to write a high-
performance MTA, Exim does seem to be fairly efficient. The biggest
server at the University of Cambridge (a large Sun box) goes over
100,000 deliveries per day on busy days (it has over 20,000 users).
There was a report of a mailing list exploder that sometimes handles
over 100,000 deliveries a day on a big Linux box, the record being
177,000 deliveries (791MB in total). Up to 13,000 deliveries an hour
have been reported.
These are quotes from some Exim users:
"... Canada's largest internet provider, uses Exim on all of our mail
machines, and we're absolutely delighted with it. It brought life back
into one of our machines plagued with backlogs and high load averages.
Here's just an example of how much email our largest mail server
(quad SS1000) is seeing ... " [230,911 deliveries in a day: 4,475MB]
"... Exim has to ... do gethostbyname()s and RBL lookups on all of the
incoming mail servers, and he runs from inetd (TCP Wrappers connected).
All the same, it seems to me that he runs as fast as lightning on our
SCO 5.0.4 box (1 Pentium 166) - far faster than MMDF which I (and many
customers) had before."
"On a PII 400 with 128M of RAM running Linux 2.2.5, I have achieved
36656 messages per hour (outgoing unique messages and recipients). For
about a 5 minute period, I was able to achieve an average of 30 messages
per second (that would be 108000 m/hour)! We are using: (options that
make a difference):
==> queue_only
split_spool_directory
queue_run_max = 1
remote_max_parallel = 1
We have a cron job hat runs every five minutes that spawns 5 \"exim -q"\ if
there are less that 120 exim processes currently running. We found
that by manually controlling the concurrency of \"exim -q"\ processes
contending for the spool for \%remote_smtp%\ delivery that we gained
considerable performance - 10000 m/hour."
Q1003: We have a large password file. Can Exim use alternative lookups during
delivery to speed things up?
A1003: If you are using FreeBSD, this problem should not arise, because it
automatically uses an indexed password file. In some other operating
systems you can arrange for this to happen too. On Linux, for example,
all you need to do is
==> # cd /var/db
# make
and put \"db"\ before \"files"\ in any \(/etc/nsswitch.conf)\ lines you want to
use db for.
On systems that do not include support for indexed password files, you
can build one yourself, and reference it from the Exim configuration.
For example, for routing to local mailboxes you could use this:
==> localuser:
driver = accept
condition = ${lookup{$local_part}cdb{/etc/passwd.cdb}{yes}{no}}
transport = local_delivery
user = ${extract{1}{:}{${lookup{$local_part}cdb{/etc/passwd.cdb}}}
This assumes a cdb version of the password file.
Q1004: I just wondered if it might be helpful to put the hints database on a
RAM disk during regular operation. Did anybody try that yet?
A1004: A user reported thus: ``I have found that this works great under Solaris.
Make a RAM disk partition and keep everything in the \(db)\ directory on
it. However, when I try the same thing on Linux, I don't see the same
boost. I think that Linux's file buffer cache works about the same.
Plus, this leave more room for processes to run.''
There have been other reports that Linux's delayed buffer write provides
better overall performance in general.
Apparently there is support in the Solaris kernel for a delayed writing,
as in Linux, but Sun's server policy is to have it disabled so that you
don't lose so much if the server crashes. There is a program called
\^fastfs^\ to enable and disable this support. You have to download and
compile it yourself; find it by looking for \"fastfs.c"\ in a search
engine. Solaris performance is reported to be much improved, but you
should take care to understand the potential hazards. In particular,
\^fsck^\ may be unable to ``fix'' disks automatically after a crash.
Q1005: A lot of incoming mail is pushing up my system load too much, and there
are many Exim processes. How can I control this?
A1005: Have you set any of the Exim configuration options that limit what it
does under high load? For example, queue_only_load, deliver_queue_load_max?
See the list in the section entitled \*Resource control*\ in the manual.
It sounds like a lot of simultaneous incoming mail pushes your system
into uncontrolled overload. The multiple Exim processes are probably
just multiple incoming messages. You can use the \^exiwhat^\ utility to
confirm this.
11. MAJORDOMO
Q1101: How do I set up Majordomo to work with Exim?
A1101: Users have found several ways of setting up Exim for use with Majordomo.
One way has been documented at
\?http://www.averillpark.net/exim/majordomo.html?\.
Somewhere in the Majordomo docs or FAQ it mentions using batchmail or
other additional programs to improve the performance of large lists.
They are not needed with Exim, and their use can actually make things
worse. However, it's a good idea to set \remote_max_parallel\ to a value
greater than 1 in the Exim configuration.
Q1102: I have set \$mailer$\ in \(majordomo.cf)\, but it still isn't setting the
sender correctly in the messages it sends.
A1102: Make sure you have got the quoting correct in the \$mailer$\ setting. For
example,
==> $mailer = "$sendmail_command -oi -oee -f$sender\@lists.mydomain.de";
is not correct. It needs three backslashes, not one, and the $ at the
start of \$sender$\ has to be escaped with a backslash.
Q1103: I'm trying to set up majordomo, but I'm getting a wrong mode error
when I try to send it mail.
A1103: Check the mode of \(/var/lib/majordomo/lists/lists.aliases)\ and compare it
with the setting of the \modemask\ option in the Majordomo aliases
router. This option specifies bits which must not be set for the alias
file, and it defaults to 022.
Q1104: I'm getting return code 9 from \(/home/majordomo/majordomo-1.94.4/wrapper)\
when it is passed a message from Exim.
A1104: A problem like this turned out to be the Perl version that came with
RedHat 5.2. Rebuilding Perl 5.005x solved it.
Q1105: Exim is complaining about an invalid command line when Majordomo tries
to send it a message for delivery.
A1105: Take a look at your \(majordomo.cf)\ file, It should have something that
looks like
==> $sendmail_command = "/usr/lib/sendmail";
and another line like
==> $mailer = "$sendmail_command -oi -oee -f\$sender";
If you have modified \^resend^\ (one of the majordomo programs) to use
\$sendmail_command$\ instead of \$mailer$\ you will be calling Exim with no
command line arguments.
12. FETCHMAIL
Q1201: When I run fetchmail, I get the error \*SMTP listener doesn't like
recipient address xxx@localhost*\.
A1201: Make sure that //localhost// is recognized as a domain that is to be
delivered locally. If you are using the default Exim run time
configuration, you'll see a line near the top like this:
==> domainlist local_domains = @
Change it to
==> domainlist local_domains = @ : localhost
Q1202: I'm currently using Exim with fetchmail and I'd like to use the RBL on
Exim, but will it work? Do I need to configure fetchmail any particular
way? As far as Exim knows, all mail is coming from 127.0.0.1. Will it
check the source address against RBL? Or will it check the ::From:: header?
A1202: It will check 127.0.0.1 (not very useful). The point of the RBL is to
keep messages from black-listed hosts out of your machine. If you are
using fetchmail, you have got the messages into your machine before you
approach Exim. That kind of defeats the purpose of the RBL. The right
way to do this would be for the host from which you fetch your mail to
do the RBL checking and insert some kind of warning header for you to
test, as Exim does if you run RBL checks in warning mode.
13. PERL
Q1301: Exim built with Perl support exits with the error message \*./exim: can't
load library 'libperl.so'*\.
A1301: If you are using BSDI, see Q9401.
Q1302: Exim built with Perl support exits with several error messages of the
form \*undefined reference to `PL_stack_sp'*\.
A1302: This has been seen on FreeBSD systems that had two different versions of
Perl installed, the older with an \^a.out^\ library and the newer with an
ELF library. Ensure that the older package is removed.
14. DIAL-UP AND ISDN
Q1401: When I'm not connected to the Internet, how can I arrange for mail to
other hosts on my local network to be delivered, while at the
same time mail to Internet hosts is queued without any delivery
attempts?
A1401: Use the \queue_domains\ option to control which domains are held
on the queue for later delivery. For example,
==> queue_domains = ! *.localnet
allows delivery to domains ending in \(.localnet)\, while queueing all the
others.
Q1402: I have a dial-up machine, and I use the \queue_smtp_domains\ option so
that remote mail only goes out when I do a queue run. However, any email
I send with an address \(anything@???)\ is returned within about 15
minutes saying \*retry time exceeded*\, and all addresses are affected.
A1402: You should be using \queue_domains\ rather than \queue_smtp_domains\.
With the latter, Exim is trying to route the addresses, which involves a
DNS lookup. This is presumably timing out, causing a retry time to be
set for the domain, and somehow a valid lookup never happened before the
maximum retry time (default of 4 days) passed. Hence the bounce. The
fact that it is \(aol.com)\ is probably not relevant. You should probably
also be using \-qq-\ to do your queue run rather than \-q-\.
Q1403: How should Exim be configured when it is acting as a temporary storage
system for a domain on a dial-up host?
A1403: Exim isn't really designed for this, but... The lowest-numbered MX
record for the domain should be pointing to the dial-up host. A higher
numbered MX record (lower priority) should point to the Exim server that
is acting as a temporary storage system.
You should set a large retry time for the domain, so that Exim doesn't
----------------------------------------------
Diff block truncated. (Max lines = 10000)
----------------------------------------------