author Philip Hazel Thu, 7 Oct 2004 15:04:35 +0000 (15:04 +0000) committer Philip Hazel Thu, 7 Oct 2004 15:04:35 +0000 (15:04 +0000)
30 files changed:
 doc/doc-scripts/BuildFAQ [new file with mode: 0755] patch | blob doc/doc-scripts/BuildHTML [new file with mode: 0755] patch | blob doc/doc-scripts/BuildInfo [new file with mode: 0755] patch | blob doc/doc-scripts/BuildPDF [new file with mode: 0755] patch | blob doc/doc-scripts/DoConts [new file with mode: 0755] patch | blob doc/doc-scripts/DoIndex [new file with mode: 0755] patch | blob doc/doc-scripts/JoinPS [new file with mode: 0755] patch | blob doc/doc-scripts/Makefile [new file with mode: 0644] patch | blob doc/doc-scripts/f2h [new file with mode: 0755] patch | blob doc/doc-scripts/f2txt [new file with mode: 0755] patch | blob doc/doc-scripts/faqchk [new file with mode: 0755] patch | blob doc/doc-scripts/fc2k [new file with mode: 0755] patch | blob doc/doc-scripts/g2h [new file with mode: 0755] patch | blob doc/doc-scripts/g2man [new file with mode: 0755] patch | blob doc/doc-scripts/g2t [new file with mode: 0755] patch | blob doc/doc-src/FAQ.src [new file with mode: 0644] patch | blob doc/doc-src/filter.src [new file with mode: 0644] patch | blob doc/doc-src/markup.sg [new file with mode: 0644] patch | blob doc/doc-src/spec.src [new file with mode: 0644] patch | blob doc/doc-txt/ChangeLog [new file with mode: 0644] patch | blob doc/doc-txt/ChangeLog.0 [new file with mode: 0644] patch | blob doc/doc-txt/Exim3.upgrade [new file with mode: 0644] patch | blob doc/doc-txt/Exim4.upgrade [new file with mode: 0644] patch | blob doc/doc-txt/NewStuff [new file with mode: 0644] patch | blob doc/doc-txt/OptionLists.txt [new file with mode: 0644] patch | blob doc/doc-txt/README [new file with mode: 0644] patch | blob doc/doc-txt/README.SIEVE [new file with mode: 0644] patch | blob doc/doc-txt/dbm.discuss.txt [new file with mode: 0644] patch | blob doc/doc-txt/pcrepattern.txt [new file with mode: 0644] patch | blob doc/doc-txt/pcretest.txt [new file with mode: 0644] patch | blob

diff --git a/doc/doc-scripts/BuildFAQ b/doc/doc-scripts/BuildFAQ
new file mode 100755 (executable)
index 0000000..9712eff
--- /dev/null
@@ -0,0 +1,59 @@
+#! /bin/sh
+# $Cambridge: 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 diff --git a/doc/doc-scripts/BuildHTML b/doc/doc-scripts/BuildHTML new file mode 100755 (executable) index 0000000..9d60034 --- /dev/null @@ -0,0 +1,12 @@ +#! /bin/sh +#$Cambridge: 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 diff --git a/doc/doc-scripts/BuildInfo b/doc/doc-scripts/BuildInfo new file mode 100755 (executable) index 0000000..9f8d105 --- /dev/null @@ -0,0 +1,32 @@ +#! /bin/sh +#$Cambridge: 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" + +#### diff --git a/doc/doc-scripts/BuildPDF b/doc/doc-scripts/BuildPDF new file mode 100755 (executable) index 0000000..c8a2bc2 --- /dev/null @@ -0,0 +1,10 @@ +#! /bin/sh +#$Cambridge: 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 diff --git a/doc/doc-scripts/DoConts b/doc/doc-scripts/DoConts new file mode 100755 (executable) index 0000000..410c3ba --- /dev/null @@ -0,0 +1,71 @@ +#! /usr/bin/perl -w +#$Cambridge: 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 diff --git a/doc/doc-scripts/DoIndex b/doc/doc-scripts/DoIndex new file mode 100755 (executable) index 0000000..1caddbd --- /dev/null @@ -0,0 +1,430 @@ +#! /usr/bin/perl -w +#$Cambridge: 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) = "\Lxp" cmp "\Lyp"; # Caseless, primary text only +c = xp cmp yp if c == 0; # Caseful, primary text only +c = "\Lxs" cmp "\Lys" 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 = "\Uletter"; } + +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\" \"secondaryref\" \"contprim\"\n"; + } + +# Not a two-part item; insert @ if the first char is a dot + +else + { + print OUT "@" if text =~ /^\./; + print OUT "textref\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 diff --git a/doc/doc-scripts/JoinPS b/doc/doc-scripts/JoinPS new file mode 100755 (executable) index 0000000..92ba59a --- /dev/null @@ -0,0 +1,130 @@ +#!/usr/bin/perl +# Cambridge: 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 diff --git a/doc/doc-scripts/Makefile b/doc/doc-scripts/Makefile new file mode 100644 (file) index 0000000..79b1f04 --- /dev/null @@ -0,0 +1,31 @@ +# Cambridge: 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* diff --git a/doc/doc-scripts/f2h b/doc/doc-scripts/f2h new file mode 100755 (executable) index 0000000..426e46e --- /dev/null @@ -0,0 +1,338 @@ +#!/usr/bin/perl +# Cambridge: 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/</&#60;/g; # Deal with < and > +s =~ s/>/&#62;/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/\\#/&nbsp;/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/(.*?)''/&#147;1&#148;/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&nbsp;/;                  # 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/^: /:&nbsp;&nbsp;/; +$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>", "&nbsp;" x 4, "</td>\n";
+      print OUT "  <td>&nbsp;$seclist[$i]</td>\n";
+      print OUT "  <td>", "&nbsp;" 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>&nbsp;&nbsp;\n";
+    if ($sec > 0) + { + printf OUT ("<a href=\"FAQ_%d.html\">Previous</a>&nbsp;&nbsp;\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>&nbsp;&nbsp;\n"; + if ($sec > 0)
+      {
+      printf OUT ("<a href=\"FAQ_%d.html\">Previous</a>&nbsp;&nbsp;\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:&nbsp;&nbsp;/;
+
+  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>&nbsp;&nbsp;\n"; +printf OUT ("<a href=\"FAQ_%d.html\">Previous</a>\n",$sec-1);
+
+print OUT "</body>\n</html>\n";
+close(OUT);
+End
diff --git a/doc/doc-scripts/f2txt b/doc/doc-scripts/f2txt
new file mode 100755 (executable)
index 0000000..ff5d703
--- /dev/null
@@ -0,0 +1,107 @@
+#!/usr/bin/perl
+# $Cambridge: 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 diff --git a/doc/doc-scripts/faqchk b/doc/doc-scripts/faqchk new file mode 100755 (executable) index 0000000..939d0b2 --- /dev/null @@ -0,0 +1,102 @@ +#!/usr/bin/perl -w +# Cambridge: 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("Q12 is in the wrong section") if (1 != section); + &oops("Q12 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("A12 is in the wrong section") if 1 != section; + &oops("A12 is out of order") if 2 != question++; + + expect_answer = 0; + next; + } + } + + else + { + if (/^C(\d\d\d):/) + { + &oops("C1 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("C1 is out of order") if 1 < c; + c = 1; + } + elsif (/^F(\d\d\d):/) + { + &oops("F1 is out of order") if 1 != f++; + next; + } + } + } + +exit 1 if precede_fail; + +# End diff --git a/doc/doc-scripts/fc2k b/doc/doc-scripts/fc2k new file mode 100755 (executable) index 0000000..9363929 --- /dev/null @@ -0,0 +1,344 @@ +#! /usr/bin/perl -w +# Cambridge: 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&nbsp;&nbsp;"; + +foreach initial (sort keys %initials) + { + if (initial eq this_initial) + { + print OUT "&nbsp;&nbsp;&nbsp;<font size=7 color=\"#FF0A0A\"><b>initial</b></font>&nbsp;"; + } + else + { + print OUT "<a href=\"FAQ-KWIC_initial.html\">&nbsp;&nbsp;initial</a>"; + } + } + +print OUT "&nbsp;"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>:(?:&nbsp;)*)(.*)<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] = "\Lwords[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{"\Uinitial\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])(\Qword\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 = "\Uinitial\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 diff --git a/doc/doc-scripts/g2h b/doc/doc-scripts/g2h new file mode 100755 (executable) index 0000000..e940e66 --- /dev/null @@ -0,0 +1,1451 @@ +#! /usr/bin/perl -w +# Cambridge: 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>&nbsp;&nbsp;\n", + thischapter - 1) if thischapter > 1; + printf OUT ("<a href=\"{file_base}_%s.html\">Next</a>&nbsp;&nbsp;\n", + thischapter + 1) if thischapter < maxchapter; + print OUT "<a href=\"{file_base}_toc.html\">Contents</a>\n"; + print OUT "&nbsp;" 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>&nbsp;&nbsp;\n", + thissection - 1) if thissection > 1; + printf OUT ("<a href=\"{file_base}_%s.html\">Next</a>&nbsp;&nbsp;\n", + thissection + 1) if thissection < maxsection; + print OUT "<a href=\"{file_base}_toc.html\">Contents</a>\n"; + print OUT "&nbsp;" 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>&nbsp;&nbsp;", + thischapter - 2) if (thischapter > 2); + print OUT "<a href=\"{file_base}_thischapter.html\">Next</a>&nbsp;&nbsp;"; + print OUT "<a href=\"{file_base}_toc.html\">Contents</a>\n"; + print OUT "&nbsp;" 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>&nbsp;&nbsp;", + thissection - 2) if (thissection > 2); + print OUT "<a href=\"{file_base}_thissection.html\">Next</a>&nbsp;&nbsp;"; + print OUT "<a href=\"{file_base}_toc.html\">Contents</a>\n"; + print OUT "&nbsp;" 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>&nbsp;&nbsp;", + thischapter - 1) if (thischapter > 1); + print OUT "<a href=\"{file_base}_toc.html\">Contents</a>\n"; + print OUT "&nbsp;" x 6, "(doctitle)\n</font>\n"; + } + elsif (sectsplit) + { + print OUT "<font size=2>\n"; + printf OUT ("<a href=\"{file_base}_%s.html\">Previous</a>&nbsp;&nbsp;", + thissection - 1) if (thissection > 1); + print OUT "<a href=\"{file_base}_toc.html\">Contents</a>\n"; + print OUT "&nbsp;" 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#IXindex_count" + : sectsplit? + "{file_base}_thissection.html#IXindex_count" + : + "#IXindex_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 &nbsp; 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/ (?=[^<>]*(?:<|))/&nbsp;/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} .= " &nbsp;<a href=\"ref\">[number]</a>"; + } + +# Place the name in the current output + +print OUT "<a name=\"IXindex_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/#/&nbsp;/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)/&#150;/mg; # turn -- into endash in text or number range +s =~ s/\(c/&copy;/g; # turn (c) into copyright symbol + +# Use double quotes + +# s =~ s/([^']+)'/1''/g; + +s =~ s/([^']+)'/&#147;1&#148;/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/</&#060;/g; +s =~ s/>/&#062;/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/&nbsp;&nbsp;/; + +# 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<lilisttype[0]>"; + } + } + +elsif (/^\.nextp\b/) + { + &setpar(0); + print OUT "</li>\n<lilisttype[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>&nbsp;&nbsp;</tt>"; + } + + # For asis displays, use <pre> and no explicit breaks + + else + { + print OUT "<pre>\n"; + eol = ""; + indent = "&nbsp;&nbsp;"; + } + + # 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/&/&#038;/g; + s/</&#060;/g; + s/>/&#062;/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*/&nbsp;&nbsp;<\/td><td>/g; + } + else + { + s/\s*\t\s*/&nbsp;&nbsp;<\/tt><\/td><td><tt>/g; + } + s/<tt><\/tt>//g; + print OUT "<tr><td>&nbsp;&nbsp;_</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>&nbsp; type<br><i>Default:</i>&nbsp; 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=\"#CHAPthischapter\">value</a>"; + } + + elsif (key =~ /^SECT/) + { + value = chapsplit? + "<a href=\"{file_base}_thischapter.html" . + "#SECTthischapter.thissection\">value</a>" + : + sectsplit? "<a href=\"{file_base}_thissection.html\">value</a>" + : + "<a href=\"#SECTthischapter.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/ /&nbsp;/g; # All spaces unsplittable + key = "\Ls"; + 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=\"TOCtocn\" " . + "href=\"current_file#CHAPthischapter\">title</a></li>\n"; + + print OUT "<h1>\n"; + print OUT "<a name=\"CHAPthischapter\" href=\"{file_base}_toc.html#TOCtocn\">\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=\"TOCtocn\" " . + "href=\"current_file#SECT%sthissection\">%s</a></li>\n", + (thischapter > 0)? "thischapter." : "", title); + + &setpar(0); + print OUT "<h2>\n"; + printf OUT ("<a name=\"SECT%sthissection\" ", + (thischapter > 0)? "thischapter." : ""); + print OUT "href=\"{file_base}_toc.html#TOCtocn\">\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=\"TOCtocn\" ". + "href=\"{file_base}_cindex.html\">Concept Index</a></li>\n"; + } + +if (defined %oindex) + { + oindex_tocn = ++tocn; + print TOC "<li><a name=\"TOCtocn\" ". + "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] =~ /<h1>/) + { + 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 = "\Uinitial"; + indexindex{initial} = 1 if initial ge "A"; + } + +print INDEX "<p>\n"; +foreach key (sort keys %indexindex) + { + print INDEX "&nbsp;<a href=\"#key\" target=\"index\">key</a>\n"; + } +print INDEX "<hr></p>\n"; + +my(letter) = ""; +print INDEX "<p>\n"; + +foreach key (sort + { ("\La" eq "\Lb")? ("a" cmp "b") : ("\La" cmp "\Lb") } + keys %hash) + { + my(initial) = substr(key,0,1); + initial = "\Uinitial"; + 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\">\Uinitial\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 diff --git a/doc/doc-scripts/g2man b/doc/doc-scripts/g2man new file mode 100755 (executable) index 0000000..e3006b5 --- /dev/null @@ -0,0 +1,251 @@ +#! /usr/bin/perl -w +#$Cambridge: 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/\\\$$[^\\]*?)\\\\/\U1/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
diff --git a/doc/doc-scripts/g2t b/doc/doc-scripts/g2t
new file mode 100755 (executable)
index 0000000..30c713c
--- /dev/null
@@ -0,0 +1,1347 @@
+#! /usr/bin/perl -w
+# $Cambridge: 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\{\L1\}/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\@typeflag\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 /^\.endtype/; + 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("-12")); + 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\{([^}]*)\}/\U1/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 "\@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
+
+
+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
diff --git a/doc/doc-src/FAQ.src b/doc/doc-src/FAQ.src
new file mode 100644 (file)
--- /dev/null
@@ -0,0 +1,7015 @@
+## $Cambridge: 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 &nbsp; space +## +## ...'' quoted string &#147;...&#148; "..." +## +## @\ 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.x>, Y <y@w.z>;, + My enemies: A <a@b.c>, B <b@c.d>; + + 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@your.domain + 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@x.y.z)\. + + . 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@n.a)\ (expecting it to go to \(user@n.a.b.c)$$ 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@some.domain*\ to be forwarded + to \*user@other.domain*\? + +A0320: Put this as your first router: + +==> forward: + driver = redirect + domains = some.domain + data =${quote:$local_part}@other.domain + + +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@virt.dom.ain)\. + +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@virt.dom.ain + + 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@some.where.else + domain2: joe@xyz.plc + + 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@your.dom.ain 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@something.mydomain.com)\ + gets delivered to \(something@mydomain.com)\? + +A0420: Set up a router like this: + +==> user_from_domain: + driver = redirect + data = {if match{domain}{\N^(.+)\.mydomain\.com\N}\ + {1@mydomain.com}} + + +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@whatever.domain + + 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@your.domain + 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@yyy.zzz <xxxx@yyy.zz> + 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@fgh.com may not follow a@bcd.com*\. What + is going on? + +A0514: Have you got + +==> deliver "a@bcd.com, e@fgh.com" + + in your filter? If so, that is your problem. You should have + +==> deliver a@bcd.com + deliver e@fgh.com + + 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@some.domain + + 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@chat.ru 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@example.com 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@my.domain -f sender@some.domain + + 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@linux.crynwr.com)\ 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@mydomain.com>... 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@your.domain
+
+       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@your.domain 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}@my.domain 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
+
+
+Q0903: How can I remove some part of the ::Received:: header?
+
+
+
+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
+