Start
authorPhilip Hazel <ph10@hermes.cam.ac.uk>
Thu, 7 Oct 2004 10:39:01 +0000 (10:39 +0000)
committerPhilip Hazel <ph10@hermes.cam.ac.uk>
Thu, 7 Oct 2004 10:39:01 +0000 (10:39 +0000)
101 files changed:
src/exim_monitor/EDITME [new file with mode: 0644]
src/exim_monitor/em_StripChart.c [new file with mode: 0644]
src/exim_monitor/em_TextPop.c [new file with mode: 0644]
src/exim_monitor/em_globals.c [new file with mode: 0644]
src/exim_monitor/em_hdr.h [new file with mode: 0644]
src/exim_monitor/em_init.c [new file with mode: 0644]
src/exim_monitor/em_log.c [new file with mode: 0644]
src/exim_monitor/em_main.c [new file with mode: 0644]
src/exim_monitor/em_menu.c [new file with mode: 0644]
src/exim_monitor/em_queue.c [new file with mode: 0644]
src/exim_monitor/em_strip.c [new file with mode: 0644]
src/exim_monitor/em_text.c [new file with mode: 0644]
src/exim_monitor/em_version.c [new file with mode: 0644]
src/exim_monitor/em_xs.c [new file with mode: 0644]
src/src/EDITME [new file with mode: 0644]
src/src/acl.c [new file with mode: 0644]
src/src/aliases.default [new file with mode: 0644]
src/src/buildconfig.c [new file with mode: 0644]
src/src/child.c [new file with mode: 0644]
src/src/config.h.defaults [new file with mode: 0644]
src/src/configure.default [new file with mode: 0644]
src/src/convert4r3.src [new file with mode: 0755]
src/src/convert4r4.src [new file with mode: 0755]
src/src/crypt16.c [new file with mode: 0644]
src/src/daemon.c [new file with mode: 0644]
src/src/dbfn.c [new file with mode: 0644]
src/src/dbfunctions.h [new file with mode: 0644]
src/src/dbstuff.h [new file with mode: 0644]
src/src/debug.c [new file with mode: 0644]
src/src/deliver.c [new file with mode: 0644]
src/src/directory.c [new file with mode: 0644]
src/src/dns.c [new file with mode: 0644]
src/src/drtables.c [new file with mode: 0644]
src/src/dummies.c [new file with mode: 0644]
src/src/enq.c [new file with mode: 0644]
src/src/exicyclog.src [new file with mode: 0644]
src/src/exigrep.src [new file with mode: 0644]
src/src/exim.c [new file with mode: 0644]
src/src/exim.h [new file with mode: 0644]
src/src/exim_checkaccess.src [new file with mode: 0755]
src/src/exim_dbmbuild.c [new file with mode: 0644]
src/src/exim_dbutil.c [new file with mode: 0644]
src/src/exim_lock.c [new file with mode: 0644]
src/src/eximon.src [new file with mode: 0644]
src/src/eximstats.src [new file with mode: 0644]
src/src/exinext.src [new file with mode: 0644]
src/src/exipick.src [new file with mode: 0644]
src/src/exiqgrep.src [new file with mode: 0644]
src/src/exiqsumm.src [new file with mode: 0644]
src/src/exiwhat.src [new file with mode: 0644]
src/src/expand.c [new file with mode: 0644]
src/src/filter.c [new file with mode: 0644]
src/src/filtertest.c [new file with mode: 0644]
src/src/functions.h [new file with mode: 0644]
src/src/globals.c [new file with mode: 0644]
src/src/globals.h [new file with mode: 0644]
src/src/header.c [new file with mode: 0644]
src/src/host.c [new file with mode: 0644]
src/src/ip.c [new file with mode: 0644]
src/src/local_scan.c [new file with mode: 0644]
src/src/local_scan.h [new file with mode: 0644]
src/src/log.c [new file with mode: 0644]
src/src/lss.c [new file with mode: 0644]
src/src/macros.h [new file with mode: 0644]
src/src/match.c [new file with mode: 0644]
src/src/moan.c [new file with mode: 0644]
src/src/mytypes.h [new file with mode: 0644]
src/src/os.c [new file with mode: 0644]
src/src/osfunctions.h [new file with mode: 0644]
src/src/parse.c [new file with mode: 0644]
src/src/perl.c [new file with mode: 0644]
src/src/queue.c [new file with mode: 0644]
src/src/rda.c [new file with mode: 0644]
src/src/readconf.c [new file with mode: 0644]
src/src/receive.c [new file with mode: 0644]
src/src/retry.c [new file with mode: 0644]
src/src/rewrite.c [new file with mode: 0644]
src/src/rfc2047.c [new file with mode: 0644]
src/src/route.c [new file with mode: 0644]
src/src/search.c [new file with mode: 0644]
src/src/sieve.c [new file with mode: 0644]
src/src/smtp_in.c [new file with mode: 0644]
src/src/smtp_out.c [new file with mode: 0644]
src/src/spool_in.c [new file with mode: 0644]
src/src/spool_out.c [new file with mode: 0644]
src/src/store.c [new file with mode: 0644]
src/src/store.h [new file with mode: 0644]
src/src/string.c [new file with mode: 0644]
src/src/structs.h [new file with mode: 0644]
src/src/tls-gnu.c [new file with mode: 0644]
src/src/tls-openssl.c [new file with mode: 0644]
src/src/tls.c [new file with mode: 0644]
src/src/tod.c [new file with mode: 0644]
src/src/transport-filter.src [new file with mode: 0644]
src/src/transport.c [new file with mode: 0644]
src/src/tree.c [new file with mode: 0644]
src/src/verify.c [new file with mode: 0644]
src/src/version.c [new file with mode: 0644]
src/util/cramtest.pl [new file with mode: 0755]
src/util/logargs.sh [new file with mode: 0755]
src/util/unknownuser.sh [new file with mode: 0755]

diff --git a/src/exim_monitor/EDITME b/src/exim_monitor/EDITME
new file mode 100644 (file)
index 0000000..5c642e6
--- /dev/null
@@ -0,0 +1,181 @@
+# $Cambridge: exim/src/exim_monitor/EDITME,v 1.1 2004/10/07 10:39:01 ph10 Exp $
+
+##################################################
+#                The Exim Monitor                #
+##################################################
+
+# This is the template for the Exim monitor's main build-time configuration
+# file. It contains settings that are independent of any operating system. It
+# should be edited and then saved to a file called Local/eximon.conf before
+# running the make command to build the monitor, if any settings are required.
+# Local/eximon.conf can be empty if no changes are needed. The examples given
+# here (commented out) are the default settings.
+
+# Any settings made in the configuration file can be overridden at run time
+# by setting up an environment variable with the same name as any of these
+# options, but preceded by EXIMON_, for example, EXIMON_WINDOW_TITLE.
+
+
+##################################################################
+#      Set these variables as appropriate for your system        #
+##################################################################
+
+# The qualifying name for your domain. The only use made of this is for
+# testing that certain addresses are the same when displaying the
+# log tail, and for shortening sender addresses in the queue display.
+
+# QUALIFY_DOMAIN=
+
+# The default minimum width and height for the whole window are 103 and
+# 162 pixels respectively. This is enough to hold the left-most stripchart
+# and the quit button. The values can be changed here.
+
+# MIN_HEIGHT=162
+# MIN_WIDTH=103
+
+# If you uncomment the following setting, the window will start up at
+# its minimum size, instead of the default maximum. There may be a quick
+# flash during the start-up process. Defining it this way allows it to be
+# overridden by an environment variable.
+
+# START_SMALL=${EXIMON_START_SMALL-yes}
+
+# The title for eximon's main display window. It is possible to have
+# host name of the machine you are running on substituted into the
+# title string. If you include the string ${fullhostname} then the
+# complete name is used. If you include ${hostname} then the full
+# host name will have the string contained in the DOMAIN variable
+# stripped from its right-hand end before being substituted. Any other
+# shell or environment variables may also be included.
+
+# If you use any substitutions, remember to ensure that the $ and {}
+# characters are escaped from the shell, e.g. by using single quotes.
+
+# WINDOW_TITLE="${hostname} eximon"
+
+# The domain that you want to be stripped from the machine's full hostname
+# when forming the short host name for the eximon window title, as
+# described above.
+
+# DOMAIN=
+
+# Parameters for the rolling display of the tail of the exim log file.
+# The width and depth are measured in pixels; LOG_BUFFER specifies the
+# amount of store to set aside for holding the log tail, which is displayed
+# in a scrolling window. When this store is full, the earlier 50% of it
+# is discarded - this is much more efficient that throwing it away line
+# by line. The number given can be followed by the letter K to indicate
+# that the value is in kilobytes. A minimum value of 1K is enforced.
+
+# LOG_DEPTH=300
+# LOG_WIDTH=950
+# LOG_BUFFER=20K
+
+# The font which is used in the log tail display. This is defined in
+# the normal X manner. It must be a "character cell" font, because this
+# is required by the text widget.
+
+# LOG_FONT=-misc-fixed-medium-r-normal-*-14-140-*-*-*-*-iso8859-1
+
+# Parameters for the display of message that are on the exim queue.
+# The width and depth are measured in pixels.
+
+# QUEUE_DEPTH=200
+# QUEUE_WIDTH=950
+
+# The font which is used in the queue display.
+
+# QUEUE_FONT=$LOG_FONT
+
+# When a message has more than one undelivered address, they are listed
+# one below the other. A limit can be placed on the number of addresses
+# displayed for any one message. If there are more, then "..." is used
+# to indicate this.
+
+# QUEUE_MAX_ADDRESSES=10
+
+# The display of the contents of the queue is updated every QUEUE_INTERVAL
+# seconds by default (there is a button to request update).
+
+# QUEUE_INTERVAL=300
+
+# The size of the popup text window that is used for looking at the
+# contents of messages, etc.
+
+# TEXT_DEPTH=200
+
+# The keystroke/mouse-operation that is used to pop up the menu in the
+# queue window is configurable. The default is Shift with the lefthand
+# mouse button. The name of an alternative can be specified in the standard
+# X way of naming these things. With the default configuration for the monitor,
+# individuals can override this by setting the EXIMON_MENU_EVENT environment
+# variable.
+
+# MENU_EVENT='Shift<Btn1Down>'
+
+# When the menu is used to perform an operation on a message, the result of the
+# operation is normally visible in the log window, so Eximon doesn't display
+# the output of the generated Exim command. However, you can request that
+# this output be shown in a separate window by setting ACTION_OUTPUT to "yes".
+# This does not apply to the output generated from attempting to deliver a
+# message, which is always shown.
+
+# ACTION_OUTPUT=no
+
+# When some action is taken on a message, such as freezing it, or changing
+# its recipients, the queue display is normally automatically updated. On
+# systems that have very large queues, this can take some time and be dis-
+# tracting. If this option is set to "no", the queue display is no longer
+# automatically updated after an action is applied to a message.
+
+# ACTION_QUEUE_UPDATE=yes
+
+# When the menu item to display a message's body is invoked, the amount
+# of data is limited to BODY_MAX bytes. This limit is a safety precaution
+# to save the screen scrolling for ever on an enormous message.
+
+# BODY_MAX=20000
+
+# The stripcharts are updated every STRIPCHART_INTERVAL seconds.
+
+# STRIPCHART_INTERVAL=60
+
+# A stripchart showing the count of messages in the queue is always
+# displayed on the left of eximon's window. Its name is "queue" by
+# default, but can be changed by this variable.
+
+# QUEUE_STRIPCHART_NAME=queue
+
+# The following variable may be set to the name of a disc partition. If
+# it is, a stripchart showing the percentage fullness of the partition
+# will be displayed as the second stripchart. This can be used to keep
+# a display of a mail spool partition on the screen.
+
+# SIZE_STRIPCHART=/var/mail
+
+# The name of the size stripchart will be the last component of SIZE_STRIPCHART
+# unless the following variable is set to override it.
+
+# SIZE_STRIPCHART_NAME=space
+
+# The following variable contains a specification of which stripcharts
+# you want eximon to display based on log entries. The string consists of
+# pairs of strings, delimited by slash characters. The first string in each
+# pair is a regular expression that matches some distinguishing feature in a
+# exim log entry.
+
+# Entries that match the expression will be counted and displayed in a
+# stripchart whose title is given by the second string. The string may
+# be continued over several input lines, provided that it is split
+# after a slash, and an additional slash (optionally preceded by white
+# space) is included at the start of the continuation line.
+
+# Stripcharts configured by the following parameter are displayed to the
+# right of the queue and size stripcharts, in the order defined here.
+
+# LOG_STRIPCHARTS='/ <= /in/
+#                  / => /out/
+#                  / => .+ R=local/local/
+#                  / => .+ T=[^ ]*smtp/smtp/'
+
+# End of exim_monitor/EDITME
diff --git a/src/exim_monitor/em_StripChart.c b/src/exim_monitor/em_StripChart.c
new file mode 100644 (file)
index 0000000..3161021
--- /dev/null
@@ -0,0 +1,507 @@
+/* $Cambridge: exim/src/exim_monitor/em_StripChart.c,v 1.1 2004/10/07 10:39:01 ph10 Exp $ */
+/* $XConsortium: StripChart.c,v 1.20 91/05/24 17:20:42 converse Exp $ */
+
+/***********************************************************
+Copyright 1987, 1988 by Digital Equipment Corporation, Maynard, Massachusetts,
+and the Massachusetts Institute of Technology, Cambridge, Massachusetts.
+
+                        All Rights Reserved
+
+Permission to use, copy, modify, and distribute this software and its
+documentation for any purpose and without fee is hereby granted,
+provided that the above copyright notice appear in all copies and that
+both that copyright notice and this permission notice appear in
+supporting documentation, and that the names of Digital or MIT not be
+used in advertising or publicity pertaining to distribution of the
+software without specific, written prior permission.
+
+DIGITAL DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, INCLUDING
+ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO EVENT SHALL
+DIGITAL BE LIABLE FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR
+ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS,
+WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION,
+ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS
+SOFTWARE.
+
+******************************************************************/
+
+/* This is the Athena StripChart widget, slightly hacked by
+Philip Hazel <ph10@cus.cam.ac.uk> in order to give access to
+its repaint_window function so that a repaint can be forced.
+
+The repaint_window function has also been nobbled so that it only
+ever changes scale to 10. There is probably a better way to handle
+this - such as inventing some new resources, but I'm not up to
+that just at the moment.
+
+On SunOS4 there are name clashes when trying to link this with the
+Athena library. So to avoid them, rename a few things by inserting
+"my" at the front of "strip". */
+
+
+#include <stdio.h>
+#include <X11/IntrinsicP.h>
+#include <X11/StringDefs.h>
+#include <X11/Xaw/XawInit.h>
+#include <X11/Xaw/StripCharP.h>
+#include <X11/Xfuncs.h>
+
+#define MS_PER_SEC 1000
+
+/* Private Data */
+
+#define offset(field) XtOffsetOf(StripChartRec, field)
+
+static XtResource resources[] = {
+    {XtNwidth, XtCWidth, XtRDimension, sizeof(Dimension),
+       offset(core.width), XtRImmediate, (XtPointer) 120},
+    {XtNheight, XtCHeight, XtRDimension, sizeof(Dimension),
+       offset(core.height), XtRImmediate, (XtPointer) 120},
+    {XtNupdate, XtCInterval, XtRInt, sizeof(int),
+        offset(strip_chart.update), XtRImmediate, (XtPointer) 10},
+    {XtNminScale, XtCScale, XtRInt, sizeof(int),
+        offset(strip_chart.min_scale), XtRImmediate, (XtPointer) 1},
+    {XtNforeground, XtCForeground, XtRPixel, sizeof(Pixel),
+        offset(strip_chart.fgpixel), XtRString, XtDefaultForeground},
+    {XtNhighlight, XtCForeground, XtRPixel, sizeof(Pixel),
+        offset(strip_chart.hipixel), XtRString, XtDefaultForeground},
+    {XtNgetValue, XtCCallback, XtRCallback, sizeof(XtPointer),
+        offset(strip_chart.get_value), XtRImmediate, (XtPointer) NULL},
+    {XtNjumpScroll, XtCJumpScroll, XtRInt, sizeof(int),
+        offset(strip_chart.jump_val), XtRImmediate, (XtPointer) DEFAULT_JUMP},
+};
+
+#undef offset
+
+/* Added argument types to these to shut picky compilers up. PH */
+
+static void CreateGC(StripChartWidget, unsigned int);
+static void DestroyGC(StripChartWidget, unsigned int);
+static void Initialize(), Destroy(), Redisplay();
+static void MoveChart(StripChartWidget, Boolean);
+static void SetPoints(StripChartWidget);
+static Boolean SetValues();
+
+int repaint_window(StripChartWidget, int, int);     /* PH hack */
+/* static int repaint_window(); */
+
+StripChartClassRec stripChartClassRec = {
+    { /* core fields */
+    /* superclass              */      (WidgetClass) &simpleClassRec,
+    /* class_name              */      "StripChart",
+    /* size                    */      sizeof(StripChartRec),
+    /* class_initialize                */      XawInitializeWidgetSet,
+    /* class_part_initialize   */      NULL,
+    /* class_inited            */      FALSE,
+    /* initialize              */      Initialize,
+    /* initialize_hook         */      NULL,
+    /* realize                 */      XtInheritRealize,
+    /* actions                 */      NULL,
+    /* num_actions             */      0,
+    /* resources               */      resources,
+    /* num_resources           */      XtNumber(resources),
+    /* xrm_class               */      NULLQUARK,
+    /* compress_motion         */      TRUE,
+    /* compress_exposure       */      XtExposeCompressMultiple |
+                                       XtExposeGraphicsExposeMerged,
+    /* compress_enterleave     */      TRUE,
+    /* visible_interest                */      FALSE,
+    /* destroy                 */      Destroy,
+    /* resize                  */      (void (*)(Widget))SetPoints,
+    /* expose                  */      Redisplay,
+    /* set_values              */      SetValues,
+    /* set_values_hook         */      NULL,
+    /* set_values_almost       */      NULL,
+    /* get_values_hook         */      NULL,
+    /* accept_focus            */      NULL,
+    /* version                 */      XtVersion,
+    /* callback_private                */      NULL,
+    /* tm_table                        */      NULL,
+    /* query_geometry          */      XtInheritQueryGeometry,
+    /* display_accelerator     */      XtInheritDisplayAccelerator,
+    /* extension               */      NULL
+    },
+    { /* Simple class fields */
+    /* change_sensitive                */      XtInheritChangeSensitive
+    }
+};
+
+WidgetClass mystripChartWidgetClass = (WidgetClass) &stripChartClassRec;
+
+/****************************************************************
+ *
+ * Private Procedures
+ *
+ ****************************************************************/
+
+static void draw_it();
+
+/*     Function Name: CreateGC
+ *     Description: Creates the GC's
+ *     Arguments: w - the strip chart widget.
+ *                 which - which GC's to create.
+ *     Returns: none
+ */
+
+static void
+CreateGC(w, which)
+StripChartWidget w;
+unsigned int which;
+{
+  XGCValues    myXGCV;
+
+  if (which & FOREGROUND) {
+    myXGCV.foreground = w->strip_chart.fgpixel;
+    w->strip_chart.fgGC = XtGetGC((Widget) w, GCForeground, &myXGCV);
+  }
+
+  if (which & HIGHLIGHT) {
+    myXGCV.foreground = w->strip_chart.hipixel;
+    w->strip_chart.hiGC = XtGetGC((Widget) w, GCForeground, &myXGCV);
+  }
+}
+
+/*     Function Name: DestroyGC
+ *     Description: Destroys the GC's
+ *     Arguments: w - the strip chart widget.
+ *                 which - which GC's to destroy.
+ *     Returns: none
+ */
+
+static void
+DestroyGC(w, which)
+StripChartWidget w;
+unsigned int which;
+{
+  if (which & FOREGROUND)
+    XtReleaseGC((Widget) w, w->strip_chart.fgGC);
+
+  if (which & HIGHLIGHT)
+    XtReleaseGC((Widget) w, w->strip_chart.hiGC);
+}
+
+/* ARGSUSED */
+static void Initialize (greq, gnew)
+    Widget greq, gnew;
+{
+    StripChartWidget w = (StripChartWidget)gnew;
+
+    if (w->strip_chart.update > 0)
+        w->strip_chart.interval_id = XtAppAddTimeOut(
+                                       XtWidgetToApplicationContext(gnew),
+                                       w->strip_chart.update * MS_PER_SEC,
+                                       draw_it, (XtPointer) gnew);
+    CreateGC(w, (unsigned int) ALL_GCS);
+
+    w->strip_chart.scale = w->strip_chart.min_scale;
+    w->strip_chart.interval = 0;
+    w->strip_chart.max_value = 0.0;
+    w->strip_chart.points = NULL;
+    SetPoints(w);
+}
+
+static void Destroy (gw)
+     Widget gw;
+{
+     StripChartWidget w = (StripChartWidget)gw;
+
+     if (w->strip_chart.update > 0)
+         XtRemoveTimeOut (w->strip_chart.interval_id);
+     if (w->strip_chart.points)
+        XtFree((char *) w->strip_chart.points);
+     DestroyGC(w, (unsigned int) ALL_GCS);
+}
+
+/*
+ * NOTE: This function really needs to recieve graphics exposure
+ *       events, but since this is not easily supported until R4 I am
+ *       going to hold off until then.
+ */
+
+/* ARGSUSED */
+static void Redisplay(w, event, region)
+     Widget w;
+     XEvent *event;
+     Region region;
+{
+    if (event->type == GraphicsExpose)
+       (void) repaint_window ((StripChartWidget)w, event->xgraphicsexpose.x,
+                              event->xgraphicsexpose.width);
+    else
+       (void) repaint_window ((StripChartWidget)w, event->xexpose.x,
+                              event->xexpose.width);
+}
+
+/* ARGSUSED */
+static void
+draw_it(client_data, id)
+XtPointer client_data;
+XtIntervalId *id;              /* unused */
+{
+   StripChartWidget w = (StripChartWidget)client_data;
+   double value;
+
+   if (w->strip_chart.update > 0)
+       w->strip_chart.interval_id =
+       XtAppAddTimeOut(XtWidgetToApplicationContext( (Widget) w),
+                      w->strip_chart.update * MS_PER_SEC,draw_it,client_data);
+
+   if (w->strip_chart.interval >= (int)w->core.width)
+       MoveChart( (StripChartWidget) w, TRUE);
+
+   /* Get the value, stash the point and draw corresponding line. */
+
+   if (w->strip_chart.get_value == NULL)
+       return;
+
+   XtCallCallbacks( (Widget)w, XtNgetValue, (XtPointer)&value );
+
+   /*
+    * Keep w->strip_chart.max_value up to date, and if this data
+    * point is off the graph, change the scale to make it fit.
+    */
+
+   if (value > w->strip_chart.max_value) {
+       w->strip_chart.max_value = value;
+       if (w->strip_chart.max_value > w->strip_chart.scale) {
+          XClearWindow( XtDisplay (w), XtWindow (w));
+          w->strip_chart.interval = repaint_window(w, 0, (int) w->core.width);
+       }
+   }
+
+   w->strip_chart.valuedata[w->strip_chart.interval] = value;
+   if (XtIsRealized((Widget)w)) {
+       int y = (int) (w->core.height
+                     - (int)(w->core.height * value) / w->strip_chart.scale);
+
+       XFillRectangle(XtDisplay(w), XtWindow(w), w->strip_chart.fgGC,
+                     w->strip_chart.interval, y,
+                     (unsigned int) 1, w->core.height - y);
+       /*
+       * Fill in the graph lines we just painted over.
+       */
+
+       if (w->strip_chart.points != NULL) {
+          w->strip_chart.points[0].x = w->strip_chart.interval;
+          XDrawPoints(XtDisplay(w), XtWindow(w), w->strip_chart.hiGC,
+                      w->strip_chart.points, w->strip_chart.scale - 1,
+                      CoordModePrevious);
+       }
+
+       XFlush(XtDisplay(w));               /* Flush output buffers */
+   }
+   w->strip_chart.interval++;              /* Next point */
+} /* draw_it */
+
+/* Blts data according to current size, then redraws the stripChart window.
+ * Next represents the number of valid points in data.  Returns the (possibly)
+ * adjusted value of next.  If next is 0, this routine draws an empty window
+ * (scale - 1 lines for graph).  If next is less than the current window width,
+ * the returned value is identical to the initial value of next and data is
+ * unchanged.  Otherwise keeps half a window's worth of data.  If data is
+ * changed, then w->strip_chart.max_value is updated to reflect the
+ * largest data point.
+ */
+
+/* static int */
+int              /* PH hack */
+repaint_window(w, left, width)
+StripChartWidget w;
+int left, width;
+{
+    register int i, j;
+    register int next = w->strip_chart.interval;
+    int scale = w->strip_chart.scale;
+    int scalewidth = 0;
+
+    /* Compute the minimum scale required to graph the data, but don't go
+       lower than min_scale. */
+    if (w->strip_chart.interval != 0 || scale <= (int)w->strip_chart.max_value)
+      scale = ((int) (w->strip_chart.max_value)) + 1;
+    if (scale < w->strip_chart.min_scale)
+      scale = w->strip_chart.min_scale;
+
+/*    if (scale != w->strip_chart.scale) { */
+
+    if (scale != w->strip_chart.scale && scale == 10) {
+      w->strip_chart.scale = scale;
+      left = 0;
+      width = next;
+      scalewidth = w->core.width;
+
+      SetPoints(w);
+
+      if (XtIsRealized ((Widget) w))
+       XClearWindow (XtDisplay (w), XtWindow (w));
+
+    }
+
+    if (XtIsRealized((Widget)w)) {
+       Display *dpy = XtDisplay(w);
+       Window win = XtWindow(w);
+
+       width += left - 1;
+       if (!scalewidth) scalewidth = width;
+
+       if (next < ++width) width = next;
+
+       /* Draw data point lines. */
+       for (i = left; i < width; i++) {
+           int y = (int) (w->core.height -
+                          (int)(w->core.height * w->strip_chart.valuedata[i]) /
+                          w->strip_chart.scale);
+
+           XFillRectangle(dpy, win, w->strip_chart.fgGC,
+                          i, y, (unsigned int) 1,
+                          (unsigned int) (w->core.height - y));
+       }
+
+       /* Draw graph reference lines */
+       for (i = 1; i < w->strip_chart.scale; i++) {
+           j = i * ((int)w->core.height / w->strip_chart.scale);
+           XDrawLine(dpy, win, w->strip_chart.hiGC, left, j, scalewidth, j);
+       }
+    }
+    return(next);
+}
+
+/*     Function Name: MoveChart
+ *     Description: moves the chart over when it would run off the end.
+ *     Arguments: w - the load widget.
+ *                 blit - blit the bits? (TRUE/FALSE).
+ *     Returns: none.
+ */
+
+static void
+MoveChart(StripChartWidget w, Boolean blit)
+{
+    double old_max;
+    int left, i, j;
+    register int next = w->strip_chart.interval;
+
+    if (!XtIsRealized((Widget) w)) return;
+
+    if (w->strip_chart.jump_val == DEFAULT_JUMP)
+        j = w->core.width >> 1; /* Half the window width. */
+    else {
+        j = w->core.width - w->strip_chart.jump_val;
+       if (j < 0) j = 0;
+    }
+
+    bcopy((char *)(w->strip_chart.valuedata + next - j),
+         (char *)(w->strip_chart.valuedata), j * sizeof(double));
+    next = w->strip_chart.interval = j;
+
+    /*
+     * Since we just lost some data, recompute the
+     * w->strip_chart.max_value.
+     */
+
+    old_max = w->strip_chart.max_value;
+    w->strip_chart.max_value = 0.0;
+    for (i = 0; i < next; i++) {
+      if (w->strip_chart.valuedata[i] > w->strip_chart.max_value)
+       w->strip_chart.max_value = w->strip_chart.valuedata[i];
+    }
+
+    if (!blit) return;         /* we are done... */
+
+    if ( ((int) old_max) != ( (int) w->strip_chart.max_value) ) {
+      XClearWindow(XtDisplay(w), XtWindow(w));
+      repaint_window(w, 0, (int) w->core.width);
+      return;
+    }
+
+    XCopyArea(XtDisplay((Widget)w), XtWindow((Widget)w), XtWindow((Widget)w),
+             w->strip_chart.hiGC, (int) w->core.width - j, 0,
+             (unsigned int) j, (unsigned int) w->core.height,
+             0, 0);
+
+    XClearArea(XtDisplay((Widget)w), XtWindow((Widget)w),
+              (int) j, 0,
+              (unsigned int) w->core.width - j, (unsigned int)w->core.height,
+              FALSE);
+
+    /* Draw graph reference lines */
+    left = j;
+    for (i = 1; i < w->strip_chart.scale; i++) {
+      j = i * ((int)w->core.height / w->strip_chart.scale);
+      XDrawLine(XtDisplay((Widget) w), XtWindow( (Widget) w),
+               w->strip_chart.hiGC, left, j, (int)w->core.width, j);
+    }
+    return;
+}
+
+/* ARGSUSED */
+static Boolean SetValues (current, request, new)
+    Widget current, request, new;
+{
+    StripChartWidget old = (StripChartWidget)current;
+    StripChartWidget w = (StripChartWidget)new;
+    Boolean ret_val = FALSE;
+    unsigned int new_gc = NO_GCS;
+
+    if (w->strip_chart.update != old->strip_chart.update) {
+       if (old->strip_chart.update > 0)
+           XtRemoveTimeOut (old->strip_chart.interval_id);
+       if (w->strip_chart.update > 0)
+           w->strip_chart.interval_id =
+               XtAppAddTimeOut(XtWidgetToApplicationContext(new),
+                               w->strip_chart.update * MS_PER_SEC,
+                               draw_it, (XtPointer)w);
+    }
+
+    if ( w->strip_chart.min_scale > (int) ((w->strip_chart.max_value) + 1) )
+      ret_val = TRUE;
+
+    if ( w->strip_chart.fgpixel != old->strip_chart.fgpixel ) {
+      new_gc |= FOREGROUND;
+      ret_val = True;
+    }
+
+    if ( w->strip_chart.hipixel != old->strip_chart.hipixel ) {
+      new_gc |= HIGHLIGHT;
+      ret_val = True;
+    }
+
+    DestroyGC(old, new_gc);
+    CreateGC(w, new_gc);
+
+    return( ret_val );
+}
+
+/*     Function Name: SetPoints
+ *     Description: Sets up the polypoint that will be used to draw in
+ *                   the graph lines.
+ *     Arguments: w - the StripChart widget.
+ *     Returns: none.
+ */
+
+#define HEIGHT ( (unsigned int) w->core.height)
+
+static void
+SetPoints(w)
+StripChartWidget w;
+{
+    XPoint * points;
+    Cardinal size;
+    int i;
+
+    if (w->strip_chart.scale <= 1) { /* no scale lines. */
+       XtFree ((char *) w->strip_chart.points);
+       w->strip_chart.points = NULL;
+       return;
+    }
+
+    size = sizeof(XPoint) * (w->strip_chart.scale - 1);
+
+    points = (XPoint *) XtRealloc( (XtPointer) w->strip_chart.points, size);
+    w->strip_chart.points = points;
+
+    /* Draw graph reference lines into clip mask */
+
+    for (i = 1; i < w->strip_chart.scale; i++) {
+       points[i - 1].x = 0;
+       points[i - 1].y = HEIGHT / w->strip_chart.scale;
+    }
+}
diff --git a/src/exim_monitor/em_TextPop.c b/src/exim_monitor/em_TextPop.c
new file mode 100644 (file)
index 0000000..b2b684d
--- /dev/null
@@ -0,0 +1,770 @@
+/* $Cambridge: exim/src/exim_monitor/em_TextPop.c,v 1.1 2004/10/07 10:39:01 ph10 Exp $ */
+/* $XConsortium: TextPop.c,v 1.22 91/07/25 18:10:22 rws Exp $ */
+
+/***********************************************************
+Copyright 1989 by the Massachusetts Institute of Technology,
+Cambridge, Massachusetts.
+
+                        All Rights Reserved
+
+Permission to use, copy, modify, and distribute this software and its
+documentation for any purpose and without fee is hereby granted,
+provided that the above copyright notice appear in all copies and that
+both that copyright notice and this permission notice appear in
+supporting documentation, and that the names of Digital or MIT not be
+used in advertising or publicity pertaining to distribution of the
+software without specific, written prior permission.
+
+DIGITAL DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, INCLUDING
+ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO EVENT SHALL
+DIGITAL BE LIABLE FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR
+ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS,
+WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION,
+ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS
+SOFTWARE.
+
+******************************************************************/
+
+
+/****************************************************************************
+* Modified by Philip Hazel for use with Exim. The "replace" and "insert     *
+* file" features of the search facility have been removed.  Also took out   *
+* the declaration of sys_errlist, as it isn't used and causes trouble on    *
+* some systems that declare it differently. September 1996.                 *
+* Added the arguments into the static functions declared at the head, to    *
+* stop some compiler warnings. August 1999.                                 *
+* Took out the separate declarations of errno and sys_nerr at the start,    *
+* because they too aren't actually used, and the declaration causes trouble *
+* on some systems. December 2002.                                           *
+****************************************************************************/
+
+
+/************************************************************
+ *
+ * This file is broken up into three sections one dealing with
+ * each of the three popups created here:
+ *
+ * FileInsert, Search, and Replace.
+ *
+ * There is also a section at the end for utility functions
+ * used by all more than one of these dialogs.
+ *
+ * The following functions are the only non-static ones defined
+ * in this module.  They are located at the begining of the
+ * section that contains this dialog box that uses them.
+ *
+ * void _XawTextInsertFileAction(w, event, params, num_params);
+ * void _XawTextDoSearchAction(w, event, params, num_params);
+ * void _XawTextDoReplaceAction(w, event, params, num_params);
+ * void _XawTextInsertFile(w, event, params, num_params);
+ *
+ *************************************************************/
+
+#include <X11/IntrinsicP.h>
+#include <X11/StringDefs.h>
+#include <X11/Shell.h>
+
+#include <X11/Xaw/TextP.h>
+#include <X11/Xaw/AsciiText.h>
+#include <X11/Xaw/Cardinals.h>
+#include <X11/Xaw/Command.h>
+#include <X11/Xaw/Form.h>
+#include <X11/Xaw/Toggle.h>
+#include <X11/Xmu/CharSet.h>
+#include <stdio.h>
+#include <X11/Xos.h>           /* for O_RDONLY */
+#include <errno.h>
+
+/* extern int errno, sys_nerr; */
+/* extern char* sys_errlist[]; */
+
+#define DISMISS_NAME  ("cancel")
+#define DISMISS_NAME_LEN 6
+#define FORM_NAME     ("form")
+#define LABEL_NAME    ("label")
+#define TEXT_NAME     ("text")
+
+#define R_OFFSET      1
+
+/* Argument types added by PH August 1999 */
+
+static void CenterWidgetOnPoint(Widget, XEvent *);
+static void PopdownSearch(Widget, XtPointer, XtPointer);
+static void InitializeSearchWidget(struct SearchAndReplace *,
+  XawTextScanDirection, Boolean);
+static void  SetResource(Widget, char *, XtArgVal);
+static void  SetSearchLabels(struct SearchAndReplace *, String, String,
+  Boolean);
+static Widget CreateDialog(Widget, String, String,
+  void (*)(Widget, char *, Widget));
+static Widget  GetShell(Widget);
+static void SetWMProtocolTranslations(Widget w);
+static Boolean DoSearch(struct SearchAndReplace *);
+static String GetString(Widget);
+
+static void AddSearchChildren(Widget, char *, Widget);
+
+static char radio_trans_string[] =
+    "<Btn1Down>,<Btn1Up>:   set() notify()";
+
+static char search_text_trans[] =
+  "~Shift<Key>Return:      DoSearchAction(Popdown) \n\
+   Ctrl<Key>c:             PopdownSearchAction() \n\
+   ";
+
+
+
+/************************************************************
+ *
+ * This section of the file contains all the functions that
+ * the search dialog box uses.
+ *
+ ************************************************************/
+
+/*     Function Name: _XawTextDoSearchAction
+ *     Description: Action routine that can be bound to dialog box's
+ *                   Text Widget that will search for a string in the main
+ *                   Text Widget.
+ *     Arguments:   (Standard Action Routine args)
+ *     Returns:     none.
+ *
+ * Note:
+ *
+ * If the search was sucessful and the argument popdown is passed to
+ * this action routine then the widget will automatically popdown the
+ * search widget.
+ */
+
+/* ARGSUSED */
+void
+_XawTextDoSearchAction(w, event, params, num_params)
+Widget w;
+XEvent *event;
+String * params;
+Cardinal * num_params;
+{
+  TextWidget tw = (TextWidget) XtParent(XtParent(XtParent(w)));
+  Boolean popdown = FALSE;
+
+  if ( (*num_params == 1) &&
+       ((params[0][0] == 'p') || (params[0][0] == 'P')) )
+      popdown = TRUE;
+
+  if (DoSearch(tw->text.search) && popdown)
+    PopdownSearch(w, (XtPointer) tw->text.search, NULL);
+}
+
+/*     Function Name: _XawTextPopdownSearchAction
+ *     Description: Action routine that can be bound to dialog box's
+ *                   Text Widget that will popdown the search widget.
+ *     Arguments:   (Standard Action Routine args)
+ *     Returns:     none.
+ */
+
+/* ARGSUSED */
+void
+_XawTextPopdownSearchAction(w, event, params, num_params)
+Widget w;
+XEvent *event;
+String * params;
+Cardinal * num_params;
+{
+  TextWidget tw = (TextWidget) XtParent(XtParent(XtParent(w)));
+
+  PopdownSearch(w, (XtPointer) tw->text.search, NULL);
+}
+
+/*     Function Name: PopdownSeach
+ *     Description: Pops down the search widget and resets it.
+ *     Arguments: w - *** NOT USED ***.
+ *                 closure - a pointer to the search structure.
+ *                 call_data - *** NOT USED ***.
+ *     Returns: none
+ */
+
+/* ARGSUSED */
+static void
+PopdownSearch(w, closure, call_data)
+Widget w;
+XtPointer closure;
+XtPointer call_data;
+{
+  struct SearchAndReplace * search = (struct SearchAndReplace *) closure;
+
+  SetSearchLabels(search, "Search", "", FALSE);
+  XtPopdown( search->search_popup );
+}
+
+/*     Function Name: SearchButton
+ *     Description: Performs a search when the button is clicked.
+ *     Arguments: w - *** NOT USED **.
+ *                 closure - a pointer to the search info.
+ *                 call_data - *** NOT USED ***.
+ *     Returns:
+ */
+
+/* ARGSUSED */
+static void
+SearchButton(w, closure, call_data)
+Widget w;
+XtPointer closure;
+XtPointer call_data;
+{
+  (void) DoSearch( (struct SearchAndReplace *) closure );
+}
+
+/*     Function Name: _XawTextSearch
+ *     Description: Action routine that can be bound to the text widget
+ *                   it will popup the search dialog box.
+ *     Arguments:   w - the text widget.
+ *                   event - X Event (used to get x and y location).
+ *                   params, num_params - the parameter list.
+ *     Returns:     none.
+ *
+ * NOTE:
+ *
+ * The parameter list contains one or two entries that may be the following.
+ *
+ * First Entry:   The first entry is the direction to search by default.
+ *                This arguement must be specified and may have a value of
+ *                "left" or "right".
+ *
+ * Second Entry:  This entry is optional and contains the value of the default
+ *                string to search for.
+ */
+
+#define SEARCH_HEADER ("Text Widget - Search():")
+
+void
+_XawTextSearch(w, event, params, num_params)
+Widget w;
+XEvent *event;
+String * params;
+Cardinal * num_params;
+{
+  TextWidget ctx = (TextWidget)w;
+  XawTextScanDirection dir;
+  char * ptr, buf[BUFSIZ];
+  XawTextEditType edit_mode;
+  Arg args[1];
+
+#ifdef notdef
+  if (ctx->text.source->Search == NULL) {
+      XBell(XtDisplay(w), 0);
+      return;
+  }
+#endif
+
+  if ( (*num_params < 1) || (*num_params > 2) ) {
+    sprintf(buf, "%s %s\n%s", SEARCH_HEADER, "This action must have only",
+           "one or two parameters");
+    XtAppWarning(XtWidgetToApplicationContext(w), buf);
+    return;
+  }
+  else if (*num_params == 1)
+    ptr = "";
+  else
+    ptr = params[1];
+
+  switch(params[0][0]) {
+  case 'b':                    /* Left. */
+  case 'B':
+    dir = XawsdLeft;
+    break;
+  case 'f':                    /* Right. */
+  case 'F':
+    dir = XawsdRight;
+    break;
+  default:
+    sprintf(buf, "%s %s\n%s", SEARCH_HEADER, "The first parameter must be",
+           "Either 'backward' or 'forward'");
+    XtAppWarning(XtWidgetToApplicationContext(w), buf);
+    return;
+  }
+
+  if (ctx->text.search== NULL) {
+    ctx->text.search = XtNew(struct SearchAndReplace);
+    ctx->text.search->search_popup = CreateDialog(w, ptr, "search",
+                                                 AddSearchChildren);
+    XtRealizeWidget(ctx->text.search->search_popup);
+    SetWMProtocolTranslations(ctx->text.search->search_popup);
+  }
+  else if (*num_params > 1) {
+    XtVaSetValues(ctx->text.search->search_text, XtNstring, ptr, NULL);
+  }
+
+  XtSetArg(args[0], XtNeditType,&edit_mode);
+  XtGetValues(ctx->text.source, args, ONE);
+
+  InitializeSearchWidget(ctx->text.search, dir, (edit_mode == XawtextEdit));
+
+  CenterWidgetOnPoint(ctx->text.search->search_popup, event);
+  XtPopup(ctx->text.search->search_popup, XtGrabNone);
+}
+
+/*     Function Name: InitializeSearchWidget
+ *     Description: This function initializes the search widget and
+ *                   is called each time the search widget is poped up.
+ *     Arguments: search - the search widget structure.
+ *                 dir - direction to search.
+ *                 replace_active - state of the sensitivity for the
+ *                                  replace button.
+ *     Returns: none.
+ */
+
+static void
+InitializeSearchWidget(struct SearchAndReplace *search,
+  XawTextScanDirection dir, Boolean replace_active)
+{
+replace_active = replace_active; /* PH - shuts compilers up */
+
+  switch (dir) {
+  case XawsdLeft:
+    SetResource(search->left_toggle, XtNstate, (XtArgVal) TRUE);
+    break;
+  case XawsdRight:
+    SetResource(search->right_toggle, XtNstate, (XtArgVal) TRUE);
+    break;
+  default:
+    break;
+  }
+}
+
+/*     Function Name: AddSearchChildren
+ *     Description: Adds all children to the Search Dialog Widget.
+ *     Arguments: form - the form widget for the search widget.
+ *                 ptr - a pointer to the initial string for the Text Widget.
+ *                 tw - the main text widget.
+ *     Returns: none.
+ */
+
+static void
+AddSearchChildren(form, ptr, tw)
+Widget form, tw;
+char * ptr;
+{
+  Arg args[10];
+  Cardinal num_args;
+  Widget cancel, search_button, s_label, s_text;
+  XtTranslations trans;
+  struct SearchAndReplace * search = ((TextWidget) tw)->text.search;
+
+  num_args = 0;
+  XtSetArg(args[num_args], XtNleft, XtChainLeft); num_args++;
+  XtSetArg(args[num_args], XtNright, XtChainLeft); num_args++;
+  XtSetArg(args[num_args], XtNresizable, TRUE ); num_args++;
+  XtSetArg(args[num_args], XtNborderWidth, 0 ); num_args++;
+  search->label1 = XtCreateManagedWidget("label1", labelWidgetClass,
+                                        form, args, num_args);
+
+ /*
+ * We need to add R_OFFSET to the radio_data, because the value zero (0)
+ * has special meaning.
+ */
+
+  num_args = 0;
+  XtSetArg(args[num_args], XtNlabel, "Backward"); num_args++;
+  XtSetArg(args[num_args], XtNfromVert, search->label1); num_args++;
+  XtSetArg(args[num_args], XtNleft, XtChainLeft); num_args++;
+  XtSetArg(args[num_args], XtNright, XtChainLeft); num_args++;
+  XtSetArg(args[num_args], XtNradioData, (caddr_t) XawsdLeft + R_OFFSET);
+  num_args++;
+  search->left_toggle = XtCreateManagedWidget("backwards", toggleWidgetClass,
+                                             form, args, num_args);
+
+  num_args = 0;
+  XtSetArg(args[num_args], XtNlabel, "Forward"); num_args++;
+  XtSetArg(args[num_args], XtNfromVert, search->label1); num_args++;
+  XtSetArg(args[num_args], XtNfromHoriz, search->left_toggle); num_args++;
+  XtSetArg(args[num_args], XtNleft, XtChainLeft); num_args++;
+  XtSetArg(args[num_args], XtNright, XtChainLeft); num_args++;
+  XtSetArg(args[num_args], XtNradioGroup, search->left_toggle); num_args++;
+  XtSetArg(args[num_args], XtNradioData, (caddr_t) XawsdRight + R_OFFSET);
+  num_args++;
+  search->right_toggle = XtCreateManagedWidget("forwards", toggleWidgetClass,
+                                              form, args, num_args);
+
+  {
+    XtTranslations radio_translations;
+
+    radio_translations = XtParseTranslationTable(radio_trans_string);
+    XtOverrideTranslations(search->left_toggle, radio_translations);
+    XtOverrideTranslations(search->right_toggle, radio_translations);
+  }
+
+  num_args = 0;
+  XtSetArg(args[num_args], XtNfromVert, search->left_toggle); num_args++;
+  XtSetArg(args[num_args], XtNlabel, "Search for:  ");num_args++;
+  XtSetArg(args[num_args], XtNleft, XtChainLeft); num_args++;
+  XtSetArg(args[num_args], XtNright, XtChainLeft); num_args++;
+  XtSetArg(args[num_args], XtNborderWidth, 0 ); num_args++;
+  s_label = XtCreateManagedWidget("searchLabel", labelWidgetClass,
+                                 form, args, num_args);
+
+  num_args = 0;
+  XtSetArg(args[num_args], XtNfromVert, search->left_toggle); num_args++;
+  XtSetArg(args[num_args], XtNfromHoriz, s_label); num_args++;
+  XtSetArg(args[num_args], XtNleft, XtChainLeft); num_args++;
+  XtSetArg(args[num_args], XtNright, XtChainRight); num_args++;
+  XtSetArg(args[num_args], XtNeditType, XawtextEdit); num_args++;
+  XtSetArg(args[num_args], XtNresizable, TRUE); num_args++;
+  XtSetArg(args[num_args], XtNresize, XawtextResizeWidth); num_args++;
+  XtSetArg(args[num_args], XtNstring, ptr); num_args++;
+  s_text = XtCreateManagedWidget("searchText", asciiTextWidgetClass, form,
+                                args, num_args);
+  search->search_text = s_text;
+
+  num_args = 0;
+  XtSetArg(args[num_args], XtNlabel, "Search"); num_args++;
+  XtSetArg(args[num_args], XtNfromVert, s_text); num_args++;
+  XtSetArg(args[num_args], XtNleft, XtChainLeft); num_args++;
+  XtSetArg(args[num_args], XtNright, XtChainLeft); num_args++;
+  search_button = XtCreateManagedWidget("search", commandWidgetClass, form,
+                                       args, num_args);
+
+  num_args = 0;
+  XtSetArg(args[num_args], XtNlabel, "Cancel"); num_args++;
+  XtSetArg(args[num_args], XtNfromVert, s_text); num_args++;
+  XtSetArg(args[num_args], XtNfromHoriz, search_button); num_args++;
+  XtSetArg(args[num_args], XtNleft, XtChainLeft); num_args++;
+  XtSetArg(args[num_args], XtNright, XtChainLeft); num_args++;
+  cancel = XtCreateManagedWidget(DISMISS_NAME, commandWidgetClass, form,
+                                args, num_args);
+
+  XtAddCallback(search_button, XtNcallback, SearchButton, (XtPointer) search);
+  XtAddCallback(cancel, XtNcallback, PopdownSearch, (XtPointer) search);
+
+/*
+ * Initialize the text entry fields.
+ */
+
+  SetSearchLabels(search, "Search", "", FALSE);
+  XtSetKeyboardFocus(form, search->search_text);
+
+/*
+ * Bind Extra translations.
+ */
+
+  trans = XtParseTranslationTable(search_text_trans);
+  XtOverrideTranslations(search->search_text, trans);
+}
+
+/*     Function Name: DoSearch
+ *     Description: Performs a search.
+ *     Arguments: search - the serach structure.
+ *     Returns: TRUE if sucessful.
+ */
+
+/* ARGSUSED */
+static Boolean
+DoSearch(search)
+struct SearchAndReplace * search;
+{
+  char msg[BUFSIZ];
+  Widget tw = XtParent(search->search_popup);
+  XawTextPosition pos;
+  XawTextScanDirection dir;
+  XawTextBlock text;
+
+  text.ptr = GetString(search->search_text);
+  text.length = strlen(text.ptr);
+  text.firstPos = 0;
+  text.format = FMT8BIT;
+
+  dir = (XawTextScanDirection)(int) ((caddr_t)XawToggleGetCurrent(search->left_toggle) -
+                               R_OFFSET);
+
+  pos = XawTextSearch( tw, dir, &text);
+
+  if (pos == XawTextSearchError)
+    sprintf( msg, "Could not find string '%s'.", text.ptr);
+  else {
+    if (dir == XawsdRight)
+      XawTextSetInsertionPoint( tw, pos + text.length);
+    else
+      XawTextSetInsertionPoint( tw, pos);
+
+    XawTextSetSelection( tw, pos, pos + text.length);
+    search->selection_changed = FALSE; /* selection is good. */
+    return(TRUE);
+  }
+
+  XawTextUnsetSelection(tw);
+  SetSearchLabels(search, msg, "", TRUE);
+  return(FALSE);
+}
+
+
+/*     Function Name: SetSearchLabels
+ *     Description: Sets both the search labels, and also rings the bell
+ *  HACKED: Only one label needed now
+ *     Arguments: search - the search structure.
+ *                 msg1, msg2 - message to put in each search label.
+ *                 bell - if TRUE then ring bell.
+ *     Returns: none.
+ */
+
+static void
+SetSearchLabels(struct SearchAndReplace *search, String msg1, String msg2,
+  Boolean bell)
+{
+msg2 = msg2; /* PH - shuts compilers up */
+  (void) SetResource( search->label1, XtNlabel, (XtArgVal) msg1);
+  /* (void) SetResource( search->label2, XtNlabel, (XtArgVal) msg2); */
+  if (bell)
+    XBell(XtDisplay(search->search_popup), 0);
+}
+
+/************************************************************
+ *
+ * This section of the file contains utility routines used by
+ * other functions in this file.
+ *
+ ************************************************************/
+
+
+/*     Function Name: SetResource
+ *     Description: Sets a resource in a widget
+ *     Arguments: w - the widget.
+ *                 res_name - name of the resource.
+ *                 value - the value of the resource.
+ *     Returns: none.
+ */
+
+static void
+SetResource(w, res_name, value)
+Widget w;
+char * res_name;
+XtArgVal value;
+{
+  Arg args[1];
+
+  XtSetArg(args[0], res_name, value);
+  XtSetValues( w, args, ONE );
+}
+
+/*     Function Name: GetString
+ *     Description:   Gets the value for the string in the popup.
+ *     Arguments:     text - the text widget whose string we will get.
+ *     Returns:       the string.
+ */
+
+static String
+GetString(text)
+Widget text;
+{
+  String string;
+  Arg args[1];
+
+  XtSetArg( args[0], XtNstring, &string );
+  XtGetValues( text, args, ONE );
+  return(string);
+}
+
+/*     Function Name: CenterWidgetOnPoint.
+ *     Description: Centers a shell widget on a point relative to
+ *                   the root window.
+ *     Arguments: w - the shell widget.
+ *                 event - event containing the location of the point
+ *     Returns: none.
+ *
+ * NOTE: The widget is not allowed to go off the screen.
+ */
+
+static void
+CenterWidgetOnPoint(w, event)
+Widget w;
+XEvent *event;
+{
+  Arg args[3];
+  Cardinal num_args;
+  Dimension width, height, b_width;
+  Position x=0, y=0, max_x, max_y;
+
+  if (event != NULL) {
+    switch (event->type) {
+    case ButtonPress:
+    case ButtonRelease:
+      x = event->xbutton.x_root;
+      y = event->xbutton.y_root;
+      break;
+    case KeyPress:
+    case KeyRelease:
+      x = event->xkey.x_root;
+      y = event->xkey.y_root;
+      break;
+    default:
+      return;
+    }
+  }
+
+  num_args = 0;
+  XtSetArg(args[num_args], XtNwidth, &width); num_args++;
+  XtSetArg(args[num_args], XtNheight, &height); num_args++;
+  XtSetArg(args[num_args], XtNborderWidth, &b_width); num_args++;
+  XtGetValues(w, args, num_args);
+
+  width += 2 * b_width;
+  height += 2 * b_width;
+
+  x -= ( (Position) width/2 );
+  if (x < 0) x = 0;
+  if ( x > (max_x = (Position) (XtScreen(w)->width - width)) ) x = max_x;
+
+  y -= ( (Position) height/2 );
+  if (y < 0) y = 0;
+  if ( y > (max_y = (Position) (XtScreen(w)->height - height)) ) y = max_y;
+
+  num_args = 0;
+  XtSetArg(args[num_args], XtNx, x); num_args++;
+  XtSetArg(args[num_args], XtNy, y); num_args++;
+  XtSetValues(w, args, num_args);
+}
+
+/*     Function Name: CreateDialog
+ *     Description: Actually creates a dialog.
+ *     Arguments: parent - the parent of the dialog - the main text widget.
+ *                 ptr - initial_string for the dialog.
+ *                 name - name of the dialog.
+ *                 func - function to create the children of the dialog.
+ *     Returns: the popup shell of the dialog.
+ *
+ * NOTE:
+ *
+ * The function argument is passed the following arguements.
+ *
+ * form - the from widget that is the dialog.
+ * ptr - the initial string for the dialog's text widget.
+ * parent - the parent of the dialog - the main text widget.
+ */
+
+static Widget
+CreateDialog(parent, ptr, name, func)
+Widget parent;
+String ptr, name;
+void (*func)();
+{
+  Widget popup, form;
+  Arg args[5];
+  Cardinal num_args;
+
+  num_args = 0;
+  XtSetArg(args[num_args], XtNiconName, name); num_args++;
+  XtSetArg(args[num_args], XtNgeometry, NULL); num_args++;
+  XtSetArg(args[num_args], XtNallowShellResize, TRUE); num_args++;
+  XtSetArg(args[num_args], XtNtransientFor, GetShell(parent)); num_args++;
+  popup = XtCreatePopupShell(name, transientShellWidgetClass,
+                            parent, args, num_args);
+
+  form = XtCreateManagedWidget(FORM_NAME, formWidgetClass, popup,
+                              NULL, ZERO);
+
+  (*func) (form, ptr, parent);
+  return(popup);
+}
+
+ /*    Function Name: GetShell
+  *    Description: Walks up the widget hierarchy to find the
+  *            nearest shell widget.
+  *    Arguments: w - the widget whose parent shell should be returned.
+  *    Returns: The shell widget among the ancestors of w that is the
+  *            fewest levels up in the widget hierarchy.
+  */
+
+static Widget
+GetShell(w)
+Widget w;
+{
+    while ((w != NULL) && !XtIsShell(w))
+       w = XtParent(w);
+
+    return (w);
+}
+
+/* Add proper prototype to keep IRIX 6 compiler happy. PH */
+
+static Boolean InParams(String, String *, Cardinal);
+
+static Boolean InParams(str, p, n)
+    String str;
+    String *p;
+    Cardinal n;
+{
+    int i;
+    for (i=0; i < n; p++, i++)
+       if (! XmuCompareISOLatin1(*p, str)) return True;
+    return False;
+}
+
+static char *WM_DELETE_WINDOW = "WM_DELETE_WINDOW";
+
+static void WMProtocols(w, event, params, num_params)
+    Widget w;          /* popup shell */
+    XEvent *event;
+    String *params;
+    Cardinal *num_params;
+{
+    Atom wm_delete_window;
+    Atom wm_protocols;
+
+    wm_delete_window = XInternAtom(XtDisplay(w), WM_DELETE_WINDOW, True);
+    wm_protocols = XInternAtom(XtDisplay(w), "WM_PROTOCOLS", True);
+
+    /* Respond to a recognized WM protocol request iff
+     * event type is ClientMessage and no parameters are passed, or
+     * event type is ClientMessage and event data is matched to parameters, or
+     * event type isn't ClientMessage and parameters make a request.
+     */
+#define DO_DELETE_WINDOW InParams(WM_DELETE_WINDOW, params, *num_params)
+
+    if ((event->type == ClientMessage &&
+        event->xclient.message_type == wm_protocols &&
+        event->xclient.data.l[0] == wm_delete_window &&
+        (*num_params == 0 || DO_DELETE_WINDOW))
+       ||
+       (event->type != ClientMessage && DO_DELETE_WINDOW)) {
+
+#undef DO_DELETE_WINDOW
+
+       Widget cancel;
+       char descendant[DISMISS_NAME_LEN + 2];
+       sprintf(descendant, "*%s", DISMISS_NAME);
+       cancel = XtNameToWidget(w, descendant);
+       if (cancel) XtCallCallbacks(cancel, XtNcallback, (XtPointer)NULL);
+    }
+}
+
+static void SetWMProtocolTranslations(w)
+    Widget     w;      /* realized popup shell */
+{
+    int i;
+    XtAppContext app_context;
+    Atom wm_delete_window;
+    static XtTranslations compiled_table;      /* initially 0 */
+    static XtAppContext *app_context_list;     /* initially 0 */
+    static Cardinal list_size;                 /* initially 0 */
+
+    app_context = XtWidgetToApplicationContext(w);
+
+    /* parse translation table once */
+    if (! compiled_table) compiled_table = XtParseTranslationTable
+       ("<Message>WM_PROTOCOLS: XawWMProtocols()\n");
+
+    /* add actions once per application context */
+    for (i=0; i < list_size && app_context_list[i] != app_context; i++) ;
+    if (i == list_size) {
+       XtActionsRec actions[1];
+       actions[0].string = "XawWMProtocols";
+       actions[0].proc = WMProtocols;
+       list_size++;
+       app_context_list = (XtAppContext *) XtRealloc
+           ((char *)app_context_list, list_size * sizeof(XtAppContext));
+       XtAppAddActions(app_context, actions, 1);
+       app_context_list[i] = app_context;
+    }
+
+    /* establish communication between the window manager and each shell */
+    XtAugmentTranslations(w, compiled_table);
+    wm_delete_window = XInternAtom(XtDisplay(w), WM_DELETE_WINDOW, False);
+    (void) XSetWMProtocols(XtDisplay(w), XtWindow(w), &wm_delete_window, 1);
+}
diff --git a/src/exim_monitor/em_globals.c b/src/exim_monitor/em_globals.c
new file mode 100644 (file)
index 0000000..ae38e9f
--- /dev/null
@@ -0,0 +1,197 @@
+/* $Cambridge: exim/src/exim_monitor/em_globals.c,v 1.1 2004/10/07 10:39:01 ph10 Exp $ */
+
+/*************************************************
+*                Exim Monitor                    *
+*************************************************/
+
+/* Copyright (c) University of Cambridge 1995 - 2004 */
+/* See the file NOTICE for conditions of use and distribution. */
+
+
+#include "em_hdr.h"
+
+/* This source module contains all the global variables used in
+the exim monitor, including those that are used by the standard
+Exim modules that are included in Eximon. For comments on their
+usage, see em_hdr.h and globals.h. */
+
+
+/* The first set are unique to Eximon */
+
+Display *X_display;
+XtAppContext X_appcon;
+
+XtActionsRec actionTable[] = {
+  { "dialogAction",  (XtActionProc)dialogAction}};
+
+int actionTableSize = sizeof(actionTable)/sizeof(XtActionsRec);
+
+XtTranslations queue_trans;
+XtTranslations text_trans;
+
+Widget  dialog_ref_widget;
+Widget  toplevel_widget;
+Widget  log_widget = NULL;
+Widget  queue_widget;
+Widget  unhide_widget = NULL;
+
+
+FILE   *LOG;
+
+int     action_output = FALSE;
+int     action_queue_update = TRUE;
+uschar  actioned_message[24];
+uschar *action_required;
+uschar *alternate_config = NULL;
+
+int     body_max = 20000;
+
+uschar *exim_path              = US BIN_DIRECTORY "/exim"
+                       "\0<---------------Space to patch exim_path->";
+
+int     eximon_initialized = FALSE;
+
+int     log_buffer_size = 10240;
+BOOL    log_datestamping = FALSE;
+int     log_depth = 150;
+uschar *log_display_buffer;
+uschar *log_file = NULL;
+uschar  log_file_open[256];
+uschar *log_font = NULL;
+ino_t   log_inode;
+long int log_position;
+int     log_width = 600;
+
+uschar *menu_event = US"Shift<Btn1Down>";
+int     menu_is_up = FALSE;
+int     min_height = 162;
+int     min_width  = 103;
+
+pipe_item *pipe_chain = NULL;
+
+uschar *qualify_domain = NULL;
+int     queue_depth = 200;
+uschar *queue_font = NULL;
+int     queue_max_addresses = 10;
+skip_item *queue_skip = NULL;
+uschar *queue_stripchart_name = NULL;
+int     queue_update = 60;
+int     queue_width = 600;
+
+pcre   *yyyymmdd_regex;
+
+uschar *size_stripchart = NULL;
+uschar *size_stripchart_name = NULL;
+int     spool_is_split = FALSE;
+int     start_small = FALSE;
+int     stripchart_height = 90;
+int     stripchart_number = 1;
+pcre  **stripchart_regex;
+uschar **stripchart_title;
+int    *stripchart_total;
+int     stripchart_update = 60;
+int     stripchart_width = 80;
+int     stripchart_varstart = 1;
+
+int     text_depth = 200;
+int     tick_queue_accumulator = 999999;
+
+uschar *window_title = US"exim monitor";
+
+
+/***********************************************************/
+/***********************************************************/
+
+
+/* These ones are used by Exim modules included in Eximon. Not all are
+actually relevant to the operation of Eximon. If SPOOL_DIRECTORY is not
+defined (Exim was compiled with it unset), just define it empty. The script
+that fires up the monitor fishes the value out by using -bP anyway. */
+
+#ifndef SPOOL_DIRECTORY
+#define SPOOL_DIRECTORY ""
+#endif
+
+
+uschar *acl_var[ACL_C_MAX+ACL_M_MAX];
+
+uschar *active_hostname        = NULL;
+BOOL    allow_unqualified_recipient = FALSE;
+BOOL    allow_unqualified_sender = FALSE;
+uschar *authenticated_id       = NULL;
+uschar *authenticated_sender   = NULL;
+
+uschar *big_buffer             = NULL;
+int     big_buffer_size        = BIG_BUFFER_SIZE;
+int     body_linecount         = 0;
+int     body_zerocount         = 0;
+
+BOOL    deliver_firsttime      = FALSE;
+BOOL    deliver_freeze         = FALSE;
+int     deliver_frozen_at      = 0;
+BOOL    deliver_manual_thaw    = FALSE;
+BOOL    dont_deliver           = FALSE;
+
+header_line *header_last       = NULL;
+header_line *header_list       = NULL;
+
+BOOL    host_lookup_failed     = FALSE;
+uschar *interface_address      = NULL;
+int     interface_port         = 0;
+
+BOOL    local_error_message    = FALSE;
+uschar *local_scan_data        = NULL;
+BOOL    log_timezone           = FALSE;
+int     message_age            = 0;
+uschar *message_id;
+uschar *message_id_external;
+uschar  message_id_option[MESSAGE_ID_LENGTH + 3];
+
+int     message_linecount      = 0;
+int     message_size           = 0;
+uschar  message_subdir[2]      = { 0, 0 };
+
+gid_t   originator_gid;
+uschar *originator_login;
+uid_t   originator_uid;
+
+uschar *primary_hostname       = NULL;
+
+int     received_count         = 0;
+uschar *received_protocol      = NULL;
+int     received_time          = 0;
+int     recipients_count       = 0;
+recipient_item *recipients_list = NULL;
+int     recipients_list_max    = 0;
+int     running_in_test_harness=FALSE;
+
+uschar *sender_address         = NULL;
+uschar *sender_fullhost        = NULL;
+uschar *sender_helo_name       = NULL;
+uschar *sender_host_address    = NULL;
+uschar *sender_host_authenticated = NULL;
+uschar *sender_host_name       = NULL;
+int     sender_host_port       = 0;
+uschar *sender_ident           = NULL;
+BOOL    sender_local           = FALSE;
+BOOL    sender_set_untrusted   = FALSE;
+
+BOOL    split_spool_directory  = FALSE;
+uschar *spool_directory        = US SPOOL_DIRECTORY;
+int     string_datestamp_offset=-1;
+
+BOOL    timestamps_utc         = FALSE;
+BOOL    tls_certificate_verified = FALSE;
+uschar *tls_cipher             = NULL;
+uschar *tls_peerdn             = NULL;
+
+tree_node *tree_duplicates     = NULL;
+tree_node *tree_nonrecipients  = NULL;
+tree_node *tree_unusable       = NULL;
+
+uschar *version_date           = US"?";
+uschar *version_string         = US"?";
+
+int     warning_count          = 0;
+
+/* End of em_globals.c */
diff --git a/src/exim_monitor/em_hdr.h b/src/exim_monitor/em_hdr.h
new file mode 100644 (file)
index 0000000..c89b6db
--- /dev/null
@@ -0,0 +1,327 @@
+/* $Cambridge: exim/src/exim_monitor/em_hdr.h,v 1.1 2004/10/07 10:39:01 ph10 Exp $ */; iline//
+/*************************************************
+*                 Exim Monitor                   *
+*************************************************/
+
+/* Copyright (c) University of Cambridge 1995 - 2004 */
+/* See the file NOTICE for conditions of use and distribution. */
+
+
+/* This is the general header file for all the modules that comprise
+the exim monitor program. */
+
+/* If this macro is defined, Eximon will anonymize all email addresses. This
+feature is just so that screen shots can be obtained for documentation
+purposes! */
+
+/* #define ANONYMIZE */
+
+/* System compilation parameters */
+
+#define queue_index_size  10      /* Size of index into queue */
+
+/* Assume most systems have statfs() unless os.h undefines this macro */
+
+#define HAVE_STATFS
+
+/* Bring in the system-dependent stuff */
+
+#include "os.h"
+
+
+/* ANSI C includes */
+
+#include <ctype.h>
+#include <setjmp.h>
+#include <signal.h>
+#include <stdarg.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <time.h>
+
+/* Not-fully-ANSI systems (e.g. SunOS4 are missing some things) */
+
+#ifndef SEEK_SET
+#define SEEK_SET 0
+#define SEEK_CUR 1
+#define SEEK_END 2
+#endif
+
+/* Unix includes */
+
+#include <sys/types.h>
+#include <errno.h>
+#include <dirent.h>
+#include <fcntl.h>
+#include <pwd.h>
+#include <grp.h>
+#include <sys/param.h>
+#include <sys/stat.h>
+#include <unistd.h>
+
+/* The new standard is statvfs; some OS have statfs. Also arrange
+to be able to cut it out altogether for way-out OS that don't have
+anything. */
+
+#ifdef HAVE_STATFS
+#ifdef HAVE_SYS_STATVFS_H
+#include <sys/statvfs.h>
+
+#else
+  #define statvfs statfs
+  #ifdef HAVE_SYS_VFS_H
+    #include <sys/vfs.h>
+    #ifdef HAVE_SYS_STATFS_H
+    #include <sys/statfs.h>
+    #endif
+  #endif
+  #ifdef HAVE_SYS_MOUNT_H
+  #include <sys/mount.h>
+  #endif
+#endif
+#endif
+
+#include <sys/wait.h>
+
+/* Regular expression include */
+
+#include "pcre/pcre.h"
+
+/* Includes from the main source of Exim. We need to have MAXPACKET defined for
+the benefit of structs.h. One of these days I should tidy up this interface so
+that this kind of kludge isn't needed. */
+
+#define MAXPACKET 1024
+
+#include "mytypes.h"
+#include "macros.h"
+#include "config.h"
+
+#include "local_scan.h"
+#include "structs.h"
+#include "globals.h"
+#include "functions.h"
+#include "osfunctions.h"
+#include "store.h"
+
+/* The sys/resource.h header on SunOS 4 causes trouble with the gcc
+compiler. Just stuff the bit we want in here; pragmatic easy way out. */
+
+#ifdef NO_SYS_RESOURCE_H
+#define RLIMIT_NOFILE   6               /* maximum descriptor index + 1 */
+struct rlimit {
+        int     rlim_cur;               /* current (soft) limit */
+        int     rlim_max;               /* maximum value for rlim_cur */
+};
+#else
+#include <sys/time.h>
+#include <sys/resource.h>
+#endif
+
+/* X11 includes */
+
+#include <X11/Xlib.h>
+#include <X11/Intrinsic.h>
+#include <X11/StringDefs.h>
+#include <X11/cursorfont.h>
+#include <X11/keysym.h>
+#include <X11/Shell.h>
+#include <X11/Xaw/AsciiText.h>
+#include <X11/Xaw/Command.h>
+#include <X11/Xaw/Form.h>
+#include <X11/Xaw/Dialog.h>
+#include <X11/Xaw/Label.h>
+#include <X11/Xaw/SimpleMenu.h>
+#include <X11/Xaw/SmeBSB.h>
+#include <X11/Xaw/SmeLine.h>
+#include <X11/Xaw/TextSrc.h>
+#include <X11/Xaw/TextSink.h>
+
+/* These are required because exim monitor has its own munged
+version of the stripchart widget. */
+
+#include <X11/IntrinsicP.h>
+#include <X11/StringDefs.h>
+#include <X11/Xaw/XawInit.h>
+#include <X11/Xaw/StripCharP.h>
+
+extern WidgetClass mystripChartWidgetClass;
+
+
+
+/*************************************************
+*               Enumerations                     *
+*************************************************/
+
+/* Operations on the in-store message queue */
+
+enum { queue_noop, queue_add };
+
+/* Operations on the destinations queue */
+
+enum { dest_noop, dest_add, dest_remove };
+
+
+/*************************************************
+*          Structure for destinations            *
+*************************************************/
+
+typedef struct dest_item {
+  struct dest_item *next;
+  struct dest_item *parent;
+  uschar address[1];
+} dest_item;
+
+
+
+/*************************************************
+*           Structure for queue items            *
+*************************************************/
+
+typedef struct queue_item {
+  struct queue_item *next;
+  struct queue_item *prev;
+  struct dest_item  *destinations;
+  int  input_time;
+  int  update_time;
+  int  size;
+  uschar *sender;
+  uschar name[17];
+  uschar seen;
+  uschar frozen;
+  uschar dir_char;
+} queue_item;
+
+
+/*************************************************
+*          Structure for queue skip items        *
+*************************************************/
+
+typedef struct skip_item {
+  struct skip_item *next;
+  time_t reveal;
+  uschar text[1];
+} skip_item;
+
+
+/*************************************************
+*           Structure for delivery displays      *
+*************************************************/
+
+typedef struct pipe_item {
+  struct pipe_item *next;
+  int fd;
+  Widget widget;
+} pipe_item;
+
+
+
+/*************************************************
+*                Global variables                *
+*************************************************/
+
+extern Display *X_display;         /* Current display */
+extern XtAppContext X_appcon;      /* Application context */
+extern XtActionsRec actionTable[]; /* Actions table */
+
+extern XtTranslations queue_trans; /* translation table for queue text widget */
+extern XtTranslations text_trans;  /* translation table for other text widgets */
+
+extern Widget  dialog_ref_widget;   /* for positioning dialog box */
+extern Widget  toplevel_widget;
+extern Widget  log_widget;          /* widget for tail display */
+extern Widget  queue_widget;        /* widget for queue display */
+extern Widget  unhide_widget;       /* widget for unhide button */
+
+extern FILE   *LOG;
+
+extern int     action_output;       /* TRUE when wanting action command output */
+extern int     action_queue_update; /* controls auto updates */
+extern int     actionTableSize;     /* # entries in actionTable */
+extern uschar  actioned_message[];  /* For menu handling */
+extern uschar *action_required;
+extern uschar *alternate_config;    /* Alternate Exim configuration file */
+
+extern int     body_max;            /* Max size of body to display */
+
+extern int     eximon_initialized;  /* TRUE when initialized */
+
+extern int     log_buffer_size;     /* size of log buffer */
+extern BOOL    log_datestamping;    /* TRUE if logs are datestamped */
+extern int     log_depth;           /* depth of log tail window */
+extern uschar *log_display_buffer;  /* to hold display text */
+extern uschar *log_file;            /* supplied name of exim log file */
+extern uschar  log_file_open[256];  /* actual open file */
+extern uschar *log_font;            /* font for log display */
+extern ino_t   log_inode;           /* the inode of the log file */
+extern long int log_position;      /* position in log file */
+extern int     log_width;           /* width of log tail window */
+
+extern uschar *menu_event;          /* name of menu event */
+extern int     menu_is_up;          /* TRUE when menu displayed */
+extern int     min_height;          /* min window height */
+extern int     min_width;           /* min window width */
+
+extern pipe_item *pipe_chain;      /* for delivery displays */
+
+extern uschar *qualify_domain;
+extern int     queue_depth;         /* depth of queue window */
+extern uschar *queue_font;          /* font for queue display */
+extern int     queue_max_addresses; /* limit on per-message list */
+extern skip_item *queue_skip;      /* for hiding bits of queue */
+extern uschar *queue_stripchart_name; /* sic */
+extern int     queue_update;        /* update interval */
+extern int     queue_width;         /* width of queue window */
+
+extern pcre   *yyyymmdd_regex;    /* for matching yyyy-mm-dd */
+
+extern uschar *size_stripchart;     /* path for size monitoring */
+extern uschar *size_stripchart_name; /* name for size stripchart */
+extern uschar *spool_directory;     /* Name of exim spool directory */
+extern int     spool_is_split;      /* True if detected split spool */
+extern int     start_small;         /* True to start with small window */
+extern int     stripchart_height;   /* height of stripcharts */
+extern int     stripchart_number;   /* number of stripcharts */
+extern pcre  **stripchart_regex;  /* vector of regexps */
+extern uschar **stripchart_title;    /* vector of titles */
+extern int    *stripchart_total;    /* vector of accumulating values */
+extern int     stripchart_update;   /* update interval */
+extern int     stripchart_width;    /* width of stripcharts */
+extern int     stripchart_varstart; /* starting number for variable charts */
+
+extern int     text_depth;          /* depth of text windows */
+extern int     tick_queue_accumulator; /* For timing next auto update */
+
+extern uschar *window_title;        /* title of the exim monitor window */
+
+
+/*************************************************
+*                Global functions                *
+*************************************************/
+
+extern XtActionProc dialogAction(Widget, XEvent *, String *, Cardinal *);
+
+extern uschar *copystring(uschar *);
+extern void    create_dialog(uschar *, uschar *);
+extern void    create_stripchart(Widget, uschar *);
+extern void    debug(char *, ...);
+extern dest_item *find_dest(queue_item *, uschar *, int, BOOL);
+extern queue_item *find_queue(uschar *, int, int);
+extern void    init(int, uschar **);
+extern void    menu_create(Widget, XEvent *, String *, Cardinal *);
+extern void    NonMessageDialogue(uschar *);
+extern void    queue_display(void);
+extern void    read_log(void);
+extern int     read_spool(uschar *);
+extern int     read_spool_init(uschar *);
+extern void    read_spool_tidy(void);
+extern int     repaint_window(StripChartWidget, int, int);
+extern void    scan_spool_input(int);
+extern void    stripchart_init(void);
+extern void    text_empty(Widget);
+extern void    text_show(Widget, uschar *);
+extern void    text_showf(Widget, char *, ...);
+extern void    xs_SetValues(Widget, Cardinal, ...);
+
+/* End of em_hdr.h */
diff --git a/src/exim_monitor/em_init.c b/src/exim_monitor/em_init.c
new file mode 100644 (file)
index 0000000..ca71eb5
--- /dev/null
@@ -0,0 +1,239 @@
+/* $Cambridge: exim/src/exim_monitor/em_init.c,v 1.1 2004/10/07 10:39:01 ph10 Exp $ */
+
+/*************************************************
+*                  Exim monitor                  *
+*************************************************/
+
+/* Copyright (c) University of Cambridge 1995 - 2004 */
+/* See the file NOTICE for conditions of use and distribution. */
+
+/* This module contains code to initialize things from the
+environment and the arguments. */
+
+
+#include "em_hdr.h"
+
+
+
+/*************************************************
+*            Decode stripchart config            *
+*************************************************/
+
+/* First determine how many are requested, then compile the
+regular expressions and save the title strings. Note that
+stripchart_number is initialized to 1 or 2 to count the always-
+present queue stripchart, and the optional size-monitoring
+stripchart. */
+
+static void decode_stripchart_config(uschar *s)
+{
+int i;
+
+/* Loop: first time just counts, second time does the
+work. */
+
+for (i = 0; i <= 1; i++)
+
+  {
+  int first = 1;
+  int count = 0;
+  uschar *p = s;
+
+  if (*p == '/') p++;   /* allow optional / at start */
+
+  /* This loops for all the substrings, using the first flag
+  to determine whether each is the first or second of the pairs. */
+
+  while (*p)
+    {
+    uschar *pp;
+    /* Handle continuations */
+    if (*p == '\n')
+      {
+      while (*(++p) == ' ' || *p == '\t');
+      if (*p == '/') p++;
+      }
+
+    /* Find the end of the string and count if first string */
+
+    pp = p;
+    while (*p && *p != '/') p++;
+    if (first) count++;
+
+    /* Take action on the second time round. */
+
+    if (i != 0)
+      {
+      uschar buffer[256];
+      int indx = count + stripchart_varstart - 1;
+      Ustrncpy(buffer, pp, p-pp);
+      buffer[p-pp] = 0;
+      if (first)
+        {
+        int offset;
+        const uschar *error;
+        stripchart_regex[indx] = pcre_compile(CS buffer, PCRE_COPT,
+          (const char **)&error, &offset, NULL);
+        if (stripchart_regex[indx] == NULL)
+          {
+          printf("regular expression error: %s at offset %d "
+            "while compiling %s\n", error, offset, buffer);
+          exit(99);
+          }
+        }
+      else stripchart_title[indx] = string_copy(buffer);
+      }
+
+    /* Advance past the delimiter and flip the first/second flag */
+
+    p++;
+    first = !first;
+    }
+
+  /* On the first pass, we now know the number of stripcharts. Get
+  store for holding the pointers to the regular expressions and
+  title strings. */
+
+  if (i == 0)
+    {
+    stripchart_number += count;
+    stripchart_regex = (pcre **)store_malloc(stripchart_number * sizeof(pcre *));
+    stripchart_title = (uschar **)store_malloc(stripchart_number * sizeof(uschar *));
+    }
+  }
+}
+
+
+/*************************************************
+*                    Initialize                  *
+*************************************************/
+
+void init(int argc, uschar **argv)
+{
+int x;
+int erroroffset;
+uschar *s;
+const uschar *error;
+
+argc = argc;     /* These are currently unused. */
+argv = argv;
+
+/* Deal with simple values in the environment. */
+
+s = US getenv("ACTION_OUTPUT");
+if (s != NULL)
+  {
+  if (Ustrcmp(s, "no") == 0) action_output = FALSE;
+  if (Ustrcmp(s, "yes") == 0) action_output = TRUE;
+  }
+
+s = US getenv("ACTION_QUEUE_UPDATE");
+if (s != NULL)
+  {
+  if (Ustrcmp(s, "no") == 0) action_queue_update = FALSE;
+  if (Ustrcmp(s, "yes") == 0) action_queue_update = TRUE;
+  }
+
+s = US getenv("BODY_MAX");
+if (s != NULL && (x = Uatoi(s)) != 0) body_max = x;
+
+s = US getenv("EXIM_PATH");
+if (s != NULL) exim_path = string_copy(s);
+
+s = US getenv("EXIMON_EXIM_CONFIG");
+if (s != NULL) alternate_config = string_copy(s);
+
+s = US getenv("LOG_BUFFER");
+if (s != NULL)
+  {
+  uschar c[1];
+  if (sscanf(CS s, "%d%c", &x, c) > 0)
+    {
+    if (c[0] == 'K' || c[0] == 'k') x *= 1024;
+    if (x < 1024) x = 1024;
+    log_buffer_size = x;
+    }
+  }
+
+s = US getenv("LOG_DEPTH");
+if (s != NULL && (x = Uatoi(s)) != 0) log_depth = x;
+
+s = US getenv("LOG_FILE_NAME");
+if (s != NULL) log_file = string_copy(s);
+
+s = US getenv("LOG_FONT");
+if (s != NULL) log_font = string_copy(s);
+
+s = US getenv("LOG_WIDTH");
+if (s != NULL && (x = Uatoi(s)) != 0) log_width = x;
+
+s = US getenv("MENU_EVENT");
+if (s != NULL) menu_event = string_copy(s);
+
+s = US getenv("MIN_HEIGHT");
+if (s != NULL && (x = Uatoi(s)) > 0) min_height = x;
+
+s = US getenv("MIN_WIDTH");
+if (s != NULL && (x = Uatoi(s)) > 0) min_width = x;
+
+s = US getenv("QUALIFY_DOMAIN");
+if (s != NULL) qualify_domain = string_copy(s);
+  else qualify_domain = US"";  /* Don't want NULL */
+
+s = US getenv("QUEUE_DEPTH");
+if (s != NULL && (x = Uatoi(s)) != 0) queue_depth = x;
+
+s = US getenv("QUEUE_FONT");
+if (s != NULL) queue_font = string_copy(s);
+
+s = US getenv("QUEUE_INTERVAL");
+if (s != NULL && (x = Uatoi(s)) != 0) queue_update = x;
+
+s = US getenv("QUEUE_MAX_ADDRESSES");
+if (s != NULL && (x = Uatoi(s)) != 0) queue_max_addresses = x;
+
+s = US getenv("QUEUE_WIDTH");
+if (s != NULL && (x = Uatoi(s)) != 0) queue_width = x;
+
+s = US getenv("SPOOL_DIRECTORY");
+if (s != NULL) spool_directory = string_copy(s);
+
+s = US getenv("START_SMALL");
+if (s != NULL && Ustrcmp(s, "yes") == 0) start_small = 1;
+
+s = US getenv("TEXT_DEPTH");
+if (s != NULL && (x = Uatoi(s)) != 0) text_depth = x;
+
+s = US getenv("WINDOW_TITLE");
+if (s != NULL) window_title = string_copy(s);
+
+/* Deal with stripchart configuration. First see if we are monitoring
+the size of a partition, then deal with log stripcharts in a separate
+function */
+
+s = US getenv("SIZE_STRIPCHART");
+if (s != NULL && *s != 0)
+  {
+  stripchart_number++;
+  stripchart_varstart++;
+  size_stripchart = string_copy(s);
+  s = US getenv("SIZE_STRIPCHART_NAME");
+  if (s != NULL && *s != 0) size_stripchart_name = string_copy(s);
+  }
+
+s = US getenv("LOG_STRIPCHARTS");
+if (s != NULL) decode_stripchart_config(s);
+
+s = US getenv("STRIPCHART_INTERVAL");
+if (s != NULL && (x = Uatoi(s)) != 0) stripchart_update = x;
+
+s = US getenv("QUEUE_STRIPCHART_NAME");
+queue_stripchart_name = (s != NULL)? string_copy(s) : US"queue";
+
+/* Compile the regex for matching yyyy-mm-dd at the start of a string. */
+
+yyyymmdd_regex = pcre_compile("^\\d{4}-\\d\\d-\\d\\d\\s", PCRE_COPT,
+  (const char **)&error, &erroroffset, NULL);
+}
+
+/* End of em_init.c */
diff --git a/src/exim_monitor/em_log.c b/src/exim_monitor/em_log.c
new file mode 100644 (file)
index 0000000..5f0f9b2
--- /dev/null
@@ -0,0 +1,398 @@
+/* $Cambridge: exim/src/exim_monitor/em_log.c,v 1.1 2004/10/07 10:39:01 ph10 Exp $ */
+
+/*************************************************
+*                 Exim Monitor                   *
+*************************************************/
+
+/* Copyright (c) University of Cambridge 1995 - 2004 */
+/* See the file NOTICE for conditions of use and distribution. */
+
+/* This module contains code for scanning the smaill log,
+extracting information from it, and displaying a "tail". */
+
+#include "em_hdr.h"
+
+#define log_buffer_len 4096      /* For each log entry */
+
+/* If anonymizing, don't alter these strings (this is all an ad hoc hack). */
+
+#ifdef ANONYMIZE
+static char *oklist[] = {
+  "Completed",
+  "defer",
+  "from",
+  "Connection timed out",
+  "Start queue run: pid=",
+  "End queue run: pid=",
+  "host lookup did not complete",
+  "unexpected disconnection while reading SMTP command from",
+  "verify failed for SMTP recipient",
+  "H=",
+  "U=",
+  "id=",
+  "<",
+  ">",
+  "(",
+  ")",
+  "[",
+  "]",
+  "@",
+  "=",
+  "*",
+  ".",
+  "-",
+  "\"",
+  " ",
+  "\n"};
+static int oklist_size = sizeof(oklist) / sizeof(uschar *);
+#endif
+
+
+
+/*************************************************
+*             Write to the log display           *
+*************************************************/
+
+static int visible = 0;
+static int scrolled = FALSE;
+static int size = 0;
+static int top = 0;
+
+static void show_log(char *s, ...)
+{
+int length, newtop;
+va_list ap;
+XawTextBlock b;
+uschar buffer[log_buffer_len + 24];
+
+/* Do nothing if not tailing a log */
+
+if (log_widget == NULL) return;
+
+/* Initialize the text block structure */
+
+b.firstPos = 0;
+b.ptr = CS buffer;
+b.format = FMT8BIT;
+
+/* We want to know whether the window has been scrolled back or not,
+so that we can cease automatically scrolling with new text. This turns
+out to be tricky with the text widget. We can detect whether the
+scroll bar has been operated by checking on the "top" value, but it's
+harder to detect that it has been returned to the bottom. The following
+heuristic does its best. */
+
+newtop = XawTextTopPosition(log_widget);
+if (newtop != top)
+  {
+  if (!scrolled)
+    {
+    visible = size - top;      /* save size of window */
+    scrolled = newtop < top;
+    }
+  else if (newtop > size - visible) scrolled = FALSE;
+  top = newtop;
+  }
+
+/* Format the text that is to be written. */
+
+va_start(ap, s);
+vsprintf(CS buffer, s, ap);
+va_end(ap);
+length = Ustrlen(buffer);
+
+/* If we are anonymizing for screen shots, flatten various things. */
+
+#ifdef ANONYMIZE
+  {
+  uschar *p = buffer + 9;
+  if (p[6] == '-' && p[13] == '-') p += 17;
+
+  while (p < buffer + length)
+    {
+    int i;
+
+    /* Check for strings to be left alone */
+
+    for (i = 0; i < oklist_size; i++)
+      {
+      int len = Ustrlen(oklist[i]);
+      if (Ustrncmp(p, oklist[i], len) == 0)
+        {
+        p += len;
+        break;
+        }
+      }
+    if (i < oklist_size) continue;
+
+    /* Leave driver names, size, protocol, alone */
+
+    if ((*p == 'D' || *p == 'P' || *p == 'T' || *p == 'S' || *p == 'R') &&
+        p[1] == '=')
+      {
+      p += 2;
+      while (*p != ' ' && *p != 0) p++;
+      continue;
+      }
+
+    /* Leave C= text alone */
+
+    if (Ustrncmp(p, "C=\"", 3) == 0)
+      {
+      p += 3;
+      while (*p != 0 && *p != '"') p++;
+      continue;
+      }
+
+    /* Flatten remaining chars */
+
+    if (isdigit(*p)) *p++ = 'x';
+    else if (isalpha(*p)) *p++ = 'x';
+    else *p++ = '$';
+    }
+  }
+#endif
+
+/* If this would overflow the buffer, throw away 50% of the
+current stuff in the buffer. Code defensively against odd
+extreme cases that shouldn't actually arise. */
+
+if (size + length > log_buffer_size)
+  {
+  if (size == 0) length = log_buffer_size/2; else
+    {
+    int cutcount = log_buffer_size/2;
+    if (cutcount > size) cutcount = size; else
+      {
+      while (cutcount < size && log_display_buffer[cutcount] != '\n')
+        cutcount++;
+      cutcount++;
+      }
+    b.length = 0;
+    XawTextReplace(log_widget, 0, cutcount, &b);
+    size -= cutcount;
+    top -= cutcount;
+    if (top < 0) top = 0;
+    if (top < cutcount) XawTextInvalidate(log_widget, 0, 999999);
+    xs_SetValues(log_widget, 1, "displayPosition", top);
+    }
+  }
+
+/* Insert the new text at the end of the buffer. */
+
+b.length = length;
+XawTextReplace(log_widget, 999999, 999999, &b);
+size += length;
+
+/* When not scrolled back, we want to keep the bottom line
+always visible. Put the insert point at the start of it because
+this stops left/right scrolling with some X libraries. */
+
+if (!scrolled)
+  {
+  XawTextSetInsertionPoint(log_widget, size - length);
+  top = XawTextTopPosition(log_widget);
+  }
+}
+
+
+
+
+/*************************************************
+*            Function to read the log            *
+*************************************************/
+
+/* We read any new log entries, and use their data to
+updated total counts for the configured stripcharts.
+The count for the queue chart is handled separately.
+We also munge the log entries and display a one-line
+version in the log window. */
+
+void read_log(void)
+{
+struct stat statdata;
+uschar buffer[log_buffer_len];
+
+/* If log is not yet open, skip all of this. */
+
+if (LOG != NULL)
+  {
+  fseek(LOG, log_position, SEEK_SET);
+
+  while (Ufgets(buffer, log_buffer_len, LOG) != NULL)
+    {
+    uschar *id;
+    uschar *p = buffer;
+    void *reset_point;
+    int length = Ustrlen(buffer);
+    int i;
+
+    /* Skip totally blank lines (paranoia: there shouldn't be any) */
+
+    while (*p == ' ' || *p == '\t') p++;
+    if (*p == '\n') continue;
+
+    /* We should now have a complete log entry in the buffer; check
+    it for various regular expression matches and take appropriate
+    action. Get the current store point so we can reset to it. */
+
+    reset_point = store_get(0);
+
+    /* First, update any stripchart data values, noting that the zeroth
+    stripchart is the queue length, which is handled elsewhere, and the
+    1st may the a size monitor. */
+
+    for (i = stripchart_varstart; i < stripchart_number; i++)
+      {
+      if (pcre_exec(stripchart_regex[i], NULL, CS buffer, length, 0, PCRE_EOPT,
+            NULL, 0) >= 0)
+        stripchart_total[i]++;
+      }
+
+    /* Munge the log entry and display shortened form on one line.
+    We omit the date and show only the time. Remove any time zone offset. */
+
+    if (pcre_exec(yyyymmdd_regex,NULL,CS buffer,length,0,PCRE_EOPT,NULL,0) >= 0)
+      {
+      if ((buffer[20] == '+' || buffer[20] == '-') &&
+          isdigit(buffer[21]) && buffer[25] == ' ')
+        memmove(buffer + 20, buffer + 26, Ustrlen(buffer + 26) + 1);
+      id = string_copyn(buffer + 20, MESSAGE_ID_LENGTH);
+      show_log("%s", buffer+11);
+      }
+    else
+      {
+      id = US"";
+      show_log("%s", buffer);
+      }
+
+    /* Deal with frozen and unfrozen messages */
+
+    if (strstric(buffer, US"frozen", FALSE) != NULL)
+      {
+      queue_item *qq = find_queue(id, queue_noop, 0);
+      if (qq != NULL)
+        {
+        if (strstric(buffer, US"unfrozen", FALSE) != NULL)
+          qq->frozen = FALSE;
+        else qq->frozen = TRUE;
+        }
+      }
+
+    /* Notice defer messages, and add the destination if it
+    isn't already on the list for this message, with a pointer
+    to the parent if we can. */
+
+    if ((p = Ustrstr(buffer, "==")) != NULL)
+      {
+      queue_item *qq = find_queue(id, queue_noop, 0);
+      if (qq != NULL)
+        {
+        dest_item *d;
+        uschar *q, *r;
+        p += 2;
+        while (isspace(*p)) p++;
+        q = p;
+        while (*p != 0 && !isspace(*p))
+          {
+          if (*p++ != '\"') continue;
+          while (*p != 0)
+            {
+            if (*p == '\\') p += 2;
+              else if (*p++ == '\"') break;
+            }
+          }
+        *p++ = 0;
+        if ((r = strstric(q, qualify_domain, FALSE)) != NULL &&
+          *(--r) == '@') *r = 0;
+
+        /* If we already have this destination, as tested case-insensitively,
+        do not add it to the destinations list. */
+
+        d = find_dest(qq, q, dest_add, TRUE);
+
+        if (d->parent == NULL)
+          {
+          while (isspace(*p)) p++;
+          if (*p == '<')
+            {
+            dest_item *dd;
+            q = ++p;
+            while (*p != 0 && *p != '>') p++;
+            *p = 0;
+            if ((p = strstric(q, qualify_domain, FALSE)) != NULL &&
+              *(--p) == '@') *p = 0;
+            dd = find_dest(qq, q, dest_noop, FALSE);
+            if (dd != NULL && dd != d) d->parent = dd;
+            }
+          }
+        }
+      }
+
+    store_reset(reset_point);
+    }
+  }
+
+
+/* We have to detect when the log file is changed, and switch to the new file.
+In practice, for non-datestamped files, this means that some deliveries might
+go unrecorded, since they'll be written to the old file, but this usually
+happens in the middle of the night, and I don't think the hassle of keeping
+track of two log files is worth it.
+
+First we check the datestamped name of the log file if necessary; if it is
+different to the file we currently have open, go for the new file. As happens
+in Exim itself, we leave in the following inode check, even when datestamping
+because it does no harm and will cope should a file actually be renamed for
+some reason.
+
+The test for a changed log file is to look up the inode of the file by name and
+compare it with the saved inode of the file we currently are processing. This
+accords with the usual interpretation of POSIX and other Unix specs that imply
+"one file, one inode". However, it appears that on some Digital systems, if an
+open file is unlinked, a new file may be created with the same inode while the
+old file remains in existence. This can happen if the old log file is renamed,
+processed in some way, and then deleted. To work round this, also test for a
+link count of zero on the currently open file. */
+
+if (log_datestamping)
+  {
+  uschar log_file_wanted[256];
+  string_format(log_file_wanted, sizeof(log_file_wanted), CS log_file);
+  if (Ustrcmp(log_file_wanted, log_file_open) != 0)
+    {
+    if (LOG != NULL)
+      {
+      fclose(LOG);
+      LOG = NULL;
+      }
+    Ustrcpy(log_file_open, log_file_wanted);
+    }
+  }
+
+if (LOG == NULL ||
+    (fstat(fileno(LOG), &statdata) == 0 && statdata.st_nlink == 0) ||
+    (Ustat(log_file, &statdata) == 0 && log_inode != statdata.st_ino))
+  {
+  FILE *TEST;
+
+  /* Experiment shows that sometimes you can't immediately open
+  the new log file - presumably immediately after the old one
+  is renamed and before the new one exists. Therefore do a
+  trial open first to be sure. */
+
+  if ((TEST = fopen(CS log_file_open, "r")) != NULL)
+    {
+    if (LOG != NULL) fclose(LOG);
+    LOG = TEST;
+    fstat(fileno(LOG), &statdata);
+    log_inode = statdata.st_ino;
+    }
+  }
+
+/* Save the position we have got to in the log. */
+
+if (LOG != NULL) log_position = ftell(LOG);
+}
+
+/* End of em_log.c */
diff --git a/src/exim_monitor/em_main.c b/src/exim_monitor/em_main.c
new file mode 100644 (file)
index 0000000..6de8466
--- /dev/null
@@ -0,0 +1,939 @@
+/* $Cambridge: exim/src/exim_monitor/em_main.c,v 1.1 2004/10/07 10:39:01 ph10 Exp $ */
+
+/*************************************************
+*                  Exim Monitor                  *
+*************************************************/
+
+/* Copyright (c) University of Cambridge 1995 - 2004 */
+/* See the file NOTICE for conditions of use and distribution. */
+
+
+#include "em_hdr.h"
+
+/* This module contains the main program of the Exim monitor, which
+sets up the world and then lets the XtAppMainLoop function
+run things off X events. */
+
+
+/*************************************************
+*               Static variables                 *
+*************************************************/
+
+/* Fallback resources */
+
+static String fallback_resources[] = {"eximon.geometry: +150+0", NULL};
+
+/* X11 fixed argument lists */
+
+static Arg quit_args[] = {
+  {XtNfromVert, (XtArgVal) NULL},         /* must be first */
+  {XtNlabel,    (XtArgVal) " Quit "},
+  {"left",      XawChainLeft},
+  {"right",     XawChainLeft},
+  {"top",       XawChainTop},
+  {"bottom",    XawChainTop}
+};
+
+static Arg resize_args[] = {
+  {XtNfromVert, (XtArgVal) NULL},         /* must be first */
+  {XtNfromHoriz,(XtArgVal) NULL},         /* must be second */
+  {XtNlabel,    (XtArgVal) " Size "},
+  {"left",      XawChainLeft},
+  {"right",     XawChainLeft},
+  {"top",       XawChainTop},
+  {"bottom",    XawChainTop}
+};
+
+static Arg update_args[] = {
+  {XtNfromVert, (XtArgVal) NULL},         /* must be first */
+  {XtNlabel,    (XtArgVal) " Update "},
+  {"left",      XawChainLeft},
+  {"right",     XawChainLeft},
+  {"top",       XawChainTop},
+  {"bottom",    XawChainTop}
+};
+
+static Arg hide_args[] = {
+  {XtNfromVert, (XtArgVal) NULL},         /* must be first */
+  {XtNfromHoriz,(XtArgVal) NULL},         /* must be second */
+  {XtNlabel,    (XtArgVal) " Hide "},
+  {"left",      XawChainLeft},
+  {"right",     XawChainLeft},
+  {"top",       XawChainTop},
+  {"bottom",    XawChainTop}
+};
+
+static Arg unhide_args[] = {
+  {XtNfromVert, (XtArgVal) NULL},         /* must be first */
+  {XtNfromHoriz,(XtArgVal) NULL},         /* must be second */
+  {XtNlabel,    (XtArgVal) " Unhide "},
+  {"left",      XawChainLeft},
+  {"right",     XawChainLeft},
+  {"top",       XawChainTop},
+  {"bottom",    XawChainTop}
+};
+
+static Arg log_args[] = {
+  {XtNfromVert, (XtArgVal) NULL},         /* must be first */
+  {"editType",  XawtextEdit},
+  {"useStringInPlace", (XtArgVal)TRUE},
+  {"string",    (XtArgVal)""},            /* dummy to get it going */
+  {"scrollVertical", XawtextScrollAlways},
+  {"scrollHorizontal", XawtextScrollAlways},
+  {"right",     XawChainRight},
+  {"top",       XawChainTop},
+  {"bottom",    XawChainTop}
+};
+
+static Arg queue_args[] = {
+  {XtNfromVert, (XtArgVal) NULL},         /* must be first */
+  {"editType",  XawtextEdit},
+  {"string",    (XtArgVal)""},            /* dummy to get it going */
+  {"scrollVertical", XawtextScrollAlways},
+  {"right",     XawChainRight},
+  {"top",       XawChainTop},
+  {"bottom",    XawChainBottom}
+};
+
+static Arg sizepos_args[] = {
+  {"width",     (XtArgVal)NULL},
+  {"height",    (XtArgVal)NULL},
+  {"x",         (XtArgVal)NULL},
+  {"y",         (XtArgVal)NULL}
+};
+
+XtActionsRec menu_action_table[] = {
+  { "menu-create",  menu_create } };
+
+/* Types of non-message dialog action */
+
+enum { da_hide };
+
+/* Miscellaneous local variables */
+
+static int dialog_action;
+static int tick_stripchart_accumulator = 999999;
+static int tick_interval = 2;
+static int maxposset = 0;
+static int minposset = 0;
+static int x_adjustment = -1;
+static int y_adjustment = -1;
+static Dimension screenwidth, screenheight;
+static Dimension original_x, original_y;
+static Dimension maxposx, maxposy;
+static Dimension minposx, minposy;
+static Dimension maxwidth, maxheight;
+static Widget outer_form_widget;
+static Widget hide_widget;
+static Widget above_queue_widget;
+
+
+
+
+#ifdef STRERROR_FROM_ERRLIST
+/*************************************************
+*     Provide strerror() for non-ANSI libraries  *
+*************************************************/
+
+/* Some old-fashioned systems still around (e.g. SunOS4) don't have strerror()
+in their libraries, but can provide the same facility by this simple
+alternative function. */
+
+uschar *
+strerror(int n)
+{
+if (n < 0 || n >= sys_nerr) return "unknown error number";
+return sys_errlist[n];
+}
+#endif /* STRERROR_FROM_ERRLIST */
+
+
+
+/*************************************************
+*         Handle attempts to write the log       *
+*************************************************/
+
+/* The message gets written to stderr when log_write() is called from a
+utility. The message always gets '\n' added on the end of it. These calls come
+from modules such as store.c when things go drastically wrong (e.g. malloc()
+failing). In normal use they won't get obeyed.
+
+Arguments:
+  selector  not relevant when running a utility
+  flags     not relevant when running a utility
+  format    a printf() format
+  ...       arguments for format
+
+Returns:    nothing
+*/
+
+void
+log_write(unsigned int selector, int flags, char *format, ...)
+{
+va_list ap;
+va_start(ap, format);
+vfprintf(stderr, format, ap);
+fprintf(stderr, "\n");
+va_end(ap);
+selector = selector;     /* Keep picky compilers happy */
+flags = flags;
+}
+
+
+
+
+/*************************************************
+*        Extract port from address string        *
+*************************************************/
+
+/* In the spool file, a host plus port is given as an IP address followed by a
+dot and a port number. This function decodes this. It is needed by the
+spool-reading function, and copied here to avoid having to include the whole
+host.c module. One day the interaction between exim and eximon with regard to
+included code MUST be tidied up!
+
+Argument:
+  address    points to the string; if there is a port, the '.' in the string
+             is overwritten with zero to terminate the address
+
+Returns:     0 if there is no port, else the port number.
+*/
+
+int
+host_extract_port(uschar *address)
+{
+int skip = -3;                     /* Skip 3 dots in IPv4 addresses */
+address--;
+while (*(++address) != 0)
+  {
+  int ch = *address;
+  if (ch == ':') skip = 0;         /* Skip 0 dots in IPv6 addresses */
+    else if (ch == '.' && skip++ >= 0) break;
+  }
+if (*address == 0) return 0;
+*address++ = 0;
+return Uatoi(address);
+}
+
+
+
+
+/*************************************************
+*                SIGCHLD handler                 *
+*************************************************/
+
+/* Operations on messages are done in subprocesses; this handler
+just catches them when they finish. It causes a queue display update
+unless configured not to. */
+
+static void sigchld_handler(int sig)
+{
+while (waitpid(-1, NULL, WNOHANG) > 0);
+signal(sig, sigchld_handler);
+if (action_queue_update) tick_queue_accumulator = 999999;
+}
+
+
+
+/*************************************************
+*             Callback routines                  *
+*************************************************/
+
+
+void updateAction(Widget w, XtPointer client_data, XtPointer call_data)
+{
+w = w;       /* Keep picky compilers happy */
+client_data = client_data;
+call_data = call_data;
+scan_spool_input(TRUE);
+queue_display();
+tick_queue_accumulator = 0;
+}
+
+void hideAction(Widget w, XtPointer client_data, XtPointer call_data)
+{
+w = w;       /* Keep picky compilers happy */
+client_data = client_data;
+call_data = call_data;
+actioned_message[0] = 0;
+dialog_ref_widget = w;
+dialog_action = da_hide;
+create_dialog(US"Hide addresses ending with", US"");
+}
+
+void unhideAction(Widget w, XtPointer client_data, XtPointer call_data)
+{
+skip_item *sk = queue_skip;
+
+w = w;       /* Keep picky compilers happy */
+client_data = client_data;
+call_data = call_data;
+
+while (sk != NULL)
+  {
+  skip_item *next = sk->next;
+  store_free(sk);
+  sk = next;
+  }
+queue_skip = NULL;
+
+XtDestroyWidget(unhide_widget);
+unhide_widget = NULL;
+
+scan_spool_input(TRUE);
+queue_display();
+tick_queue_accumulator = 0;
+}
+
+void quitAction(Widget w, XtPointer client_data, XtPointer call_data)
+{
+w = w;       /* Keep picky compilers happy */
+client_data = client_data;
+call_data = call_data;
+exit(0);
+}
+
+
+/* Action when the "Size" button is pressed. This is a kludged up mess
+that I made work after much messing around. Reading the position of the
+toplevel widget gets the absolute position of the data portion of the window,
+excluding the window manager's furniture. However, positioning the toplevel
+widget's window seems to position the top corner of the furniture under the twm
+window manager, but not under fwvm and others. The two cases are distinguished
+by the values of x_adjustment and y_adjustment.
+
+For twm (adjustment >= 0), one has to fudge the miminizing function to ensure
+that we go back to exactly the same position as before.
+
+For fwvm (adjustment < 0), one has to fudge the "top left hand corner"
+positioning to ensure that the window manager's furniture gets displayed on the
+screen. I haven't found a way of discovering the thickness of the furniture, so
+some screwed-in values are used.
+
+This is all ad hoc, developed by floundering around as I haven't found any
+documentation that tells me what I really should do. */
+
+void resizeAction(Widget button, XtPointer client_data, XtPointer call_data)
+{
+Dimension x, y;
+Dimension width, height;
+XWindowAttributes a;
+Window w = XtWindow(toplevel_widget);
+
+button = button;    /* Keep picky compilers happy */
+client_data = client_data;
+call_data = call_data;
+
+/* Get the position and size of the top level widget. */
+
+sizepos_args[0].value = (XtArgVal)(&width);
+sizepos_args[1].value = (XtArgVal)(&height);
+sizepos_args[2].value = (XtArgVal)(&x);
+sizepos_args[3].value = (XtArgVal)(&y);
+XtGetValues(toplevel_widget, sizepos_args, 4);
+
+/* Get the position of the widget's window relative to its parent; this
+gives the thickness of the window manager's furniture. At least it does
+in twm. For fwvm it gives zero. The size/movement function uses this data.
+I tried doing this before entering the main loop, but it didn't always
+work properly with twm. Running it every time seems to be OK. */
+
+XGetWindowAttributes(X_display, XtWindow(toplevel_widget), &a);
+if (a.x != 0) x_adjustment = a.x;
+if (a.y != 0) y_adjustment = a.y;
+
+/* If at maximum size, reduce to minimum and move back to where it was
+when maximized, if that value is set, allowing for the furniture in cases
+where the positioning includes the furniture. */
+
+if (width == maxwidth && height == maxheight)
+  {
+  maxposx = x;
+  maxposy = y;
+  maxposset = 1;
+
+  if (minposset)
+    xs_SetValues(toplevel_widget, 4,
+      "width",     min_width,
+      "height",    min_height,
+      "x",         minposx - ((x_adjustment >= 0)? x_adjustment : 0),
+      "y",         minposy - ((y_adjustment >= 0)? y_adjustment : 0));
+  else
+    xs_SetValues(toplevel_widget, 2,
+      "width",     min_width,
+      "height",    min_height);
+  }
+
+/* Else always expand to maximum. If currently at minimum size, remember where
+it was for coming back. If we don't have a value for the thickness of the
+furniture, the implication is that the coordinates position the application
+window, so we can't use (0,0) because that loses the furniture. Use screwed in
+values that seem to work with fvwm. */
+
+else
+  {
+  int xx = x;
+  int yy = y;
+
+  if (width == min_width && height == min_height)
+    {
+    minposx = x;
+    minposy = y;
+    minposset = 1;
+    }
+
+  if ((int)(x + maxwidth) > (int)screenwidth ||
+      (int)(y + maxheight + 10) > (int)screenheight)
+    {
+    if (maxposset)
+      {
+      xx = maxposx - ((x_adjustment >= 0)? x_adjustment : 0);
+      yy = maxposy - ((y_adjustment >= 0)? y_adjustment : 0);
+      }
+    else
+      {
+      if ((int)(x + maxwidth) > (int)screenwidth)
+        xx = (x_adjustment >= 0)? 0 : 4;
+      if ((int)(y + maxheight + 10) > (int)screenheight)
+        yy = (y_adjustment >= 0)? 0 : 21;
+      }
+
+    xs_SetValues(toplevel_widget, 4,
+      "width",     maxwidth,
+      "height",    maxheight,
+      "x",         xx,
+      "y",         yy);
+    }
+
+  else xs_SetValues(toplevel_widget, 2,
+        "width",     maxwidth,
+        "height",    maxheight);
+  }
+
+/* Ensure the window is at the top */
+
+XRaiseWindow(X_display, w);
+}
+
+
+
+
+/*************************************************
+*          Handle input from non-msg dialogue    *
+*************************************************/
+
+/* The various cases here are: hide domain, (no more yet) */
+
+void NonMessageDialogue(uschar *s)
+{
+skip_item *sk;
+
+switch(dialog_action)
+  {
+  case da_hide:
+
+  /* Create the unhide button if not present */
+
+  if (unhide_widget == NULL)
+    {
+    unhide_args[0].value = (XtArgVal) above_queue_widget;
+    unhide_args[1].value = (XtArgVal) hide_widget;
+    unhide_widget = XtCreateManagedWidget("unhide", commandWidgetClass,
+      outer_form_widget, unhide_args, XtNumber(unhide_args));
+    XtAddCallback(unhide_widget, "callback",  unhideAction, NULL);
+    }
+
+  /* Add item to skip queue */
+
+  sk = (skip_item *)store_malloc(sizeof(skip_item) + Ustrlen(s));
+  sk->next = queue_skip;
+  queue_skip = sk;
+  Ustrcpy(sk->text, s);
+  sk->reveal = time(NULL) + 60 * 60;
+  scan_spool_input(TRUE);
+  queue_display();
+  tick_queue_accumulator = 0;
+  break;
+  }
+}
+
+
+
+/*************************************************
+*              Ticker function                   *
+*************************************************/
+
+/* This function is called initially to set up the starting data
+values; it then sets a timeout so that it continues to be called
+every 2 seconds. */
+
+static void ticker(XtPointer pt, XtIntervalId *i)
+{
+pipe_item **pp = &pipe_chain;
+pipe_item *p = pipe_chain;
+tick_queue_accumulator += tick_interval;
+tick_stripchart_accumulator += tick_interval;
+read_log();
+
+pt = pt;    /* Keep picky compilers happy */
+i = i;
+
+/* If we have passed the queue update time, we must do a full
+scan of the queue, checking for new arrivals, etc. This will
+as a by-product set the count of items for use by the stripchart
+display. On some systems, SIGCHLD signals can get lost at busy times,
+so just in case, clean up any completed children here. */
+
+if (tick_queue_accumulator >= queue_update)
+  {
+  scan_spool_input(TRUE);
+  queue_display();
+  tick_queue_accumulator = 0;
+  if (tick_stripchart_accumulator >= stripchart_update)
+    tick_stripchart_accumulator = 0;
+  while (waitpid(-1, NULL, WNOHANG) > 0);
+  }
+
+/* Otherwise, if we have exceeded the stripchart interval,
+do a reduced queue scan that simply provides the count for
+the stripchart. */
+
+else if (tick_stripchart_accumulator >= stripchart_update)
+  {
+  scan_spool_input(FALSE);
+  tick_stripchart_accumulator = 0;
+  }
+
+/* Scan any pipes that are set up for listening to delivery processes,
+and display their output if their windows are still open. */
+
+while (p != NULL)
+  {
+  int count;
+  uschar buffer[256];
+
+  while ((count = read(p->fd, buffer, 254)) > 0)
+    {
+    buffer[count] = 0;
+    if (p->widget != NULL) text_show(p->widget, buffer);
+    }
+
+  if (count == 0)
+    {
+    close(p->fd);
+    *pp = p->next;
+    store_free(p);
+    /* If configured, cause display update */
+    if (action_queue_update) tick_queue_accumulator = 999999;
+    }
+
+  else pp = &(p->next);
+
+  p = *pp;
+  }
+
+/* Reset the timer for next time */
+
+XtAppAddTimeOut(X_appcon, tick_interval * 1000, ticker, 0);
+}
+
+
+
+/*************************************************
+*             Find Num Lock modifiers            *
+*************************************************/
+
+/* Return a string with the modifiers generated by XK_Num_Lock, or return
+NULL if XK_Num_Lock doesn't generate any modifiers. This is needed because Num
+Lock isn't always the same modifier on all servers.
+
+Arguments:
+  display   the Display
+  buf       a buffer in which to put the answers (long enough to hold 5)
+
+Returns:    points to the buffer, or NULL
+*/
+
+static uschar *
+numlock_modifiers(Display *display, uschar *buf)
+{
+XModifierKeymap *m;
+int i, j;
+uschar *ret = NULL;
+
+m = XGetModifierMapping(display);
+if (m == NULL)
+  {
+  printf("Not enough memory\n");
+  exit (EXIT_FAILURE);
+  }
+
+/* Look at Mod1 through Mod5, and fill in the buffer as necessary. */
+
+buf[0] = 0;
+for (i = 3; i < 8; i++)
+  {
+  for (j = 0; j < m->max_keypermod; j++)
+    {
+    if (XKeycodeToKeysym(display, m->modifiermap [i*m->max_keypermod + j], 0)
+        == XK_Num_Lock)
+      {
+      sprintf(CS(buf+Ustrlen(buf)), " Mod%d", i-2);
+      ret = buf;
+      }
+    }
+  }
+
+XFreeModifiermap(m);
+return ret;
+}
+
+
+
+/*************************************************
+*               Initialize                       *
+*************************************************/
+
+int main(int argc, char **argv)
+{
+int i;
+struct stat statdata;
+uschar modbuf[] = " Mod1 Mod2 Mod3 Mod4 Mod5";
+uschar *numlock;
+Widget stripchart_form_widget,
+       update_widget,
+       quit_widget,
+       resize_widget;
+
+/* The exim global message_id needs to get set */
+
+message_id_external = message_id_option + 1;
+message_id = message_id_external + 1;
+message_subdir[1] = 0;
+
+/* Some store needs getting for big_buffer, which is used for
+constructing file names and things. This call will initialize
+the store_get() function. */
+
+big_buffer_size = 1024;
+big_buffer = store_get(big_buffer_size);
+
+/* Set up the version string and date and output them */
+
+version_init();
+printf("\nExim Monitor version %s (compiled %s) initializing\n",
+  version_string, version_date);
+
+/* Initialize various things from the environment and arguments. */
+
+init(argc, USS argv);
+
+/* Set up the SIGCHLD handler */
+
+signal(SIGCHLD, sigchld_handler);
+
+/* Get the buffer for storing the string for the log display. */
+
+log_display_buffer = (uschar *)store_malloc(log_buffer_size);
+log_display_buffer[0] = 0;
+
+/* Initialize the data structures for the stripcharts */
+
+stripchart_init();
+
+/* If log_file contains the empty string, then Exim is running using syslog
+only, and we can't tail the log. If not, open the log file and position to the
+end of it. Before doing so, we have to detect whether the log files are
+datestamped, and if so, sort out the name. The string in log_file already has
+%s replaced by "main"; if datestamping is occurring, %D will be present. In
+fact, we don't need to test explicitly - just process the string with
+string_format.
+
+Once opened, save the file's inode so that we can detect when the file is
+switched to another one for non-datestamped files. However, allow the monitor
+to start up without a log file (can happen if no messages have been sent
+today.) */
+
+if (log_file[0] != 0)
+  {
+  (void)string_format(log_file_open, sizeof(log_file_open), CS log_file);
+  log_datestamping = string_datestamp_offset >= 0;
+
+  LOG = fopen(CS log_file_open, "r");
+
+  if (LOG == NULL)
+    {
+    printf("*** eximon warning: can't open log file %s - will try "
+      "periodically\n", log_file_open);
+    }
+  else
+    {
+    fseek(LOG, 0, SEEK_END);
+    log_position = ftell(LOG);
+    fstat(fileno(LOG), &statdata);
+    log_inode = statdata.st_ino;
+    }
+  }
+else
+  {
+  printf("*** eximon warning: no log file available to tail\n");
+  }
+
+/* Now initialize the X world and create the top-level widget */
+
+toplevel_widget = XtAppInitialize(&X_appcon, "Eximon", NULL, 0, &argc, argv,
+  fallback_resources, NULL, 0);
+X_display = XtDisplay(toplevel_widget);
+xs_SetValues(toplevel_widget, 4,
+  "title",     window_title,
+  "iconName",  window_title,
+  "minWidth",  min_width,
+  "minHeight", min_height);
+
+
+/* Create the action for setting up the menu in the queue display
+window, and register the action for positioning the menu. */
+
+XtAppAddActions(X_appcon, menu_action_table, 1);
+XawSimpleMenuAddGlobalActions(X_appcon);
+
+/* Set up translation tables for the text widgets we use. We don't
+want all the generality of editing, etc. that the defaults provide.
+This cannot be done before initializing X - the parser complains
+about unknown events, modifiers, etc. in an unhelpful way... The
+queue text widget has a different table which includes the button
+for popping up the menu. Note that the order of things in these
+tables is significant. Shift<thing> must come before <thing> as
+otherwise it isn't noticed. */
+
+/*
+   <FocusIn>:      display-caret(on)\n\
+   <FocusOut>:     display-caret(off)\n\
+*/
+
+/* The translation manager sets up passive grabs for the menu popups as a
+result of MenuPopup(), but the grabs match only the exact modifiers listed,
+hence combinations with and without caps-lock and num-lock must be given,
+rather than just one "Shift<Btn1Down>" (or whatever menu_event is set to),
+despite the fact that that notation (without a leading !) should ignore the
+state of other modifiers. Thanks to Kevin Ryde for this information, and for
+the function above that discovers which modifier is Num Lock, because it turns
+out that it varies from server to server. */
+
+sprintf(CS big_buffer,
+  "!%s:            menu-create() XawPositionSimpleMenu(menu) MenuPopup(menu)\n\
+   !Lock %s:       menu-create() XawPositionSimpleMenu(menu) MenuPopup(menu)\n\
+  ", menu_event, menu_event);
+
+numlock = numlock_modifiers(X_display, modbuf); /* Get Num Lock modifier(s) */
+
+if (numlock != NULL) sprintf(CS big_buffer + Ustrlen(big_buffer),
+  "!%s %s:         menu-create() XawPositionSimpleMenu(menu) MenuPopup(menu)\n\
+   !Lock %s %s:    menu-create() XawPositionSimpleMenu(menu) MenuPopup(menu)\n\
+  ", numlock, menu_event, numlock, menu_event);
+
+sprintf(CS big_buffer + Ustrlen(big_buffer),
+  "<Btn1Down>:     select-start()\n\
+   <Btn1Motion>:   extend-adjust()\n\
+   <Btn1Up>:       extend-end(PRIMARY,CUT_BUFFER0)\n\
+   <Btn3Down>:     extend-start()\n\
+   <Btn3Motion>:   extend-adjust()\n\
+   <Btn3Up>:       extend-end(PRIMARY,CUT_BUFFER0)\n\
+   <Key>Up:        scroll-one-line-down()\n\
+   <Key>Down:      scroll-one-line-up()\n\
+   Ctrl<Key>R:     search(backward)\n\
+   Ctrl<Key>S:     search(forward)\n\
+  ");
+
+queue_trans = XtParseTranslationTable(CS big_buffer);
+
+text_trans = XtParseTranslationTable(
+  "<Btn1Down>:     select-start()\n\
+   <Btn1Motion>:   extend-adjust()\n\
+   <Btn1Up>:       extend-end(PRIMARY,CUT_BUFFER0)\n\
+   <Btn3Down>:     extend-start()\n\
+   <Btn3Motion>:   extend-adjust()\n\
+   <Btn3Up>:       extend-end(PRIMARY,CUT_BUFFER0)\n\
+   <Key>Up:        scroll-one-line-down()\n\
+   <Key>Down:      scroll-one-line-up()\n\
+   Ctrl<Key>R:     search(backward)\n\
+   Ctrl<Key>S:     search(forward)\n\
+  ");
+
+
+/* Create a toplevel form widget to hold all the other things */
+
+outer_form_widget = XtCreateManagedWidget("form", formWidgetClass,
+  toplevel_widget, NULL, 0);
+
+/* Now create an inner form to hold the stripcharts */
+
+stripchart_form_widget = XtCreateManagedWidget("form", formWidgetClass,
+  outer_form_widget, NULL, 0);
+xs_SetValues(stripchart_form_widget, 5,
+  "defaultDistance", 8,
+  "left",            XawChainLeft,
+  "right",           XawChainLeft,
+  "top",             XawChainTop,
+  "bottom",          XawChainTop);
+
+/* Create the queue count stripchart and its label. */
+
+create_stripchart(stripchart_form_widget, queue_stripchart_name);
+
+/* If configured, create the size monitoring stripchart, but
+only if the OS supports statfs(). */
+
+if (size_stripchart != NULL)
+  {
+#ifdef HAVE_STATFS
+  if (size_stripchart_name == NULL)
+    {
+    size_stripchart_name = size_stripchart + Ustrlen(size_stripchart) - 1;
+    while (size_stripchart_name > size_stripchart &&
+      *size_stripchart_name == '/') size_stripchart_name--;
+    while (size_stripchart_name > size_stripchart &&
+      *size_stripchart_name != '/') size_stripchart_name--;
+    }
+  create_stripchart(stripchart_form_widget, size_stripchart_name);
+#else
+  printf("Can't create size stripchart: statfs() function not available\n");
+#endif
+  }
+
+/* Now create the configured input/output stripcharts; note
+the total number includes the queue stripchart. */
+
+for (i = stripchart_varstart; i < stripchart_number; i++)
+  create_stripchart(stripchart_form_widget, stripchart_title[i]);
+
+/* Next in vertical order come the Resize & Quit buttons */
+
+quit_args[0].value = (XtArgVal) stripchart_form_widget;
+quit_widget = XtCreateManagedWidget("quit", commandWidgetClass,
+  outer_form_widget, quit_args, XtNumber(quit_args));
+XtAddCallback(quit_widget, "callback",  quitAction, NULL);
+
+resize_args[0].value = (XtArgVal) stripchart_form_widget;
+resize_args[1].value = (XtArgVal) quit_widget;
+resize_widget = XtCreateManagedWidget("resize", commandWidgetClass,
+  outer_form_widget, resize_args, XtNumber(resize_args));
+XtAddCallback(resize_widget, "callback",  resizeAction, NULL);
+
+/* In the absence of log tailing, the quit widget is the one above the
+queue listing. */
+
+above_queue_widget = quit_widget;
+
+/* Create an Ascii text widget for the log tail display if we are tailing a
+log. Skip it if not. */
+
+if (log_file[0] != 0)
+  {
+  log_args[0].value = (XtArgVal) quit_widget;
+  log_widget = XtCreateManagedWidget("log", asciiTextWidgetClass,
+    outer_form_widget, log_args, XtNumber(log_args));
+  XawTextDisplayCaret(log_widget, TRUE);
+  xs_SetValues(log_widget, 6,
+    "editType",  XawtextEdit,
+    "translations", text_trans,
+    "string",    log_display_buffer,
+    "length",    log_buffer_size,
+    "height",    log_depth,
+    "width",     log_width);
+
+  if (log_font != NULL)
+    {
+    XFontStruct *f = XLoadQueryFont(X_display, CS log_font);
+    if (f != NULL) xs_SetValues(log_widget, 1, "font", f);
+    }
+
+  above_queue_widget = log_widget;
+  }
+
+/* The update button */
+
+update_args[0].value = (XtArgVal) above_queue_widget;
+update_widget = XtCreateManagedWidget("update", commandWidgetClass,
+  outer_form_widget, update_args, XtNumber(update_args));
+XtAddCallback(update_widget, "callback",  updateAction, NULL);
+
+/* The hide button */
+
+hide_args[0].value = (XtArgVal) above_queue_widget;
+hide_args[1].value = (XtArgVal) update_widget;
+hide_widget = XtCreateManagedWidget("hide", commandWidgetClass,
+  outer_form_widget, hide_args, XtNumber(hide_args));
+XtAddCallback(hide_widget, "callback",  hideAction, NULL);
+
+/* Create an Ascii text widget for the queue display. */
+
+queue_args[0].value = (XtArgVal) update_widget;
+queue_widget = XtCreateManagedWidget("queue", asciiTextWidgetClass,
+  outer_form_widget, queue_args, XtNumber(queue_args));
+XawTextDisplayCaret(queue_widget, TRUE);
+
+xs_SetValues(queue_widget, 4,
+  "editType",  XawtextEdit,
+  "height",    queue_depth,
+  "width",     queue_width,
+  "translations", queue_trans);
+
+if (queue_font != NULL)
+  {
+  XFontStruct *f = XLoadQueryFont(X_display, CS queue_font);
+  if (f != NULL) xs_SetValues(queue_widget, 1, "font", f);
+  }
+
+/* Call the ticker function to get the initial data set up. It
+arranges to have itself recalled every 2 seconds. */
+
+ticker(NULL, NULL);
+
+/* Everything is now set up; this flag is used by the regerror
+function and also by the queue reader. */
+
+eximon_initialized = TRUE;
+printf("\nExim Monitor running\n");
+
+/* Realize the toplevel and thereby get things displayed */
+
+XtRealizeWidget(toplevel_widget);
+
+/* Find out the size of the initial window, and set that as its
+maximum. While we are at it, get the initial position. */
+
+sizepos_args[0].value = (XtArgVal)(&maxwidth);
+sizepos_args[1].value = (XtArgVal)(&maxheight);
+sizepos_args[2].value = (XtArgVal)(&original_x);
+sizepos_args[3].value = (XtArgVal)(&original_y);
+XtGetValues(toplevel_widget, sizepos_args, 4);
+
+xs_SetValues(toplevel_widget, 2,
+  "maxWidth",  maxwidth,
+  "maxHeight", maxheight);
+
+/* Set up the size of the screen */
+
+screenwidth = XDisplayWidth(X_display, 0);
+screenheight= XDisplayHeight(X_display,0);
+
+/* Register the action table */
+
+XtAppAddActions(X_appcon, actionTable, actionTableSize);
+
+/* Reduce the window to the small size if this is wanted */
+
+if (start_small) resizeAction(NULL, NULL, NULL);
+
+/* Enter the application loop which handles things from here
+onwards. The return statement is never obeyed, but is needed to
+keep pedantic ANSI compilers happy. */
+
+XtAppMainLoop(X_appcon);
+
+return 0;
+}
+
+/* End of em_main.c */
+
diff --git a/src/exim_monitor/em_menu.c b/src/exim_monitor/em_menu.c
new file mode 100644 (file)
index 0000000..ead5923
--- /dev/null
@@ -0,0 +1,986 @@
+/* $Cambridge: exim/src/exim_monitor/em_menu.c,v 1.1 2004/10/07 10:39:01 ph10 Exp $ */
+
+/*************************************************
+*                  Exim Monitor                  *
+*************************************************/
+
+/* Copyright (c) University of Cambridge 1995 - 2004 */
+/* See the file NOTICE for conditions of use and distribution. */
+
+
+#include "em_hdr.h"
+
+/* This module contains code for handling the popup menus. */
+
+static Widget menushell;
+static Widget queue_text_sink;
+static Widget dialog_shell, dialog_widget;
+
+static Widget text_create(uschar *, int);
+
+static int highlighted_start, highlighted_end, highlighted_x, highlighted_y;
+
+
+
+static Arg queue_get_arg[] = {
+  { "textSink",   (XtArgVal)NULL },
+  { "textSource", (XtArgVal)NULL },
+  { "string",     (XtArgVal)NULL } };
+
+static Arg dialog_arg[] = {
+  { "label",      (XtArgVal)"dialog" },
+  { "value",      (XtArgVal)"value" } };
+
+static Arg get_pos_args[] = {
+  {"x",           (XtArgVal)NULL },
+  {"y",           (XtArgVal)NULL } };
+
+static Arg menushell_arg[] = {
+  { "label",      (XtArgVal)NULL } };
+
+static Arg button_arg[] = {
+  { XtNfromVert, (XtArgVal) NULL },         /* must be first */
+  { XtNlabel,    (XtArgVal) " Dismiss " },
+  { "left",      XawChainLeft },
+  { "right",     XawChainLeft },
+  { "top",       XawChainBottom },
+  { "bottom",    XawChainBottom } };
+
+static Arg text_arg[] = {
+  { XtNfromVert, (XtArgVal) NULL },         /* must be first */
+  { "editType",  XawtextEdit },
+  { "string",    (XtArgVal)"" },            /* dummy to get it going */
+  { "scrollVertical", XawtextScrollAlways },
+  { "wrap",      XawtextWrapWord },
+  { "top",       XawChainTop },
+  { "bottom",    XawChainBottom } };
+
+static Arg item_1_arg[] = {
+  { XtNfromVert,  (XtArgVal)NULL },         /* must be first */
+  { "label",      (XtArgVal)" Message log" } };
+
+static Arg item_2_arg[] = {
+  { XtNfromVert,  (XtArgVal) NULL },        /* must be first */
+  { "label",      (XtArgVal)" Headers" } };
+
+static Arg item_3_arg[] = {
+  { XtNfromVert,  (XtArgVal) NULL },        /* must be first */
+  { "label",      (XtArgVal)" Body" } };
+
+static Arg item_4_arg[] = {
+  { XtNfromVert,  (XtArgVal) NULL },        /* must be first */
+  { "label",      (XtArgVal)" Deliver message" } };
+
+static Arg item_5_arg[] = {
+  { XtNfromVert,  (XtArgVal) NULL },        /* must be first */
+  { "label",      (XtArgVal)" Freeze message" } };
+
+static Arg item_6_arg[] = {
+  { XtNfromVert,  (XtArgVal) NULL },        /* must be first */
+  { "label",      (XtArgVal)" Thaw message" } };
+
+static Arg item_7_arg[] = {
+  { XtNfromVert,  (XtArgVal) NULL },        /* must be first */
+  { "label",      (XtArgVal)" Give up on msg" } };
+
+static Arg item_8_arg[] = {
+  { XtNfromVert,  (XtArgVal) NULL },        /* must be first */
+  { "label",      (XtArgVal)" Remove message" } };
+
+static Arg item_9_arg[] = {
+  { XtNfromVert,  (XtArgVal) NULL },        /* must be first */
+  { "label",      (XtArgVal)"----------------" } };
+
+static Arg item_10_arg[] = {
+  { XtNfromVert,  (XtArgVal) NULL },        /* must be first */
+  { "label",      (XtArgVal)" Add recipient" } };
+
+static Arg item_11_arg[] = {
+  { XtNfromVert,  (XtArgVal) NULL },        /* must be first */
+  { "label",      (XtArgVal)" Mark delivered" } };
+
+static Arg item_12_arg[] = {
+  { XtNfromVert,  (XtArgVal) NULL },        /* must be first */
+  { "label",      (XtArgVal)" Mark all delivered" } };
+
+static Arg item_13_arg[] = {
+  { XtNfromVert,  (XtArgVal) NULL },        /* must be first */
+  { "label",      (XtArgVal)" Edit sender" } };
+
+static Arg item_99_arg[] = {
+  { XtNfromVert,  (XtArgVal) NULL },        /* must be first */
+  { "label",      (XtArgVal)" " } };
+
+
+
+/*************************************************
+*        Destroy the menu when popped down       *
+*************************************************/
+
+static void popdownAction(Widget w, XtPointer client_data, XtPointer call_data)
+{
+client_data = client_data;    /* Keep picky compilers happy */
+call_data = call_data;
+if (highlighted_x >= 0)
+  XawTextSinkDisplayText(queue_text_sink,
+    highlighted_x, highlighted_y,
+    highlighted_start, highlighted_end, 0);
+XtDestroyWidget(w);
+menu_is_up = FALSE;
+}
+
+
+
+/*************************************************
+*          Display the message log               *
+*************************************************/
+
+static void msglogAction(Widget w, XtPointer client_data, XtPointer call_data)
+{
+int i;
+uschar buffer[256];
+Widget text = text_create((uschar *)client_data, text_depth);
+FILE *f = NULL;
+
+w = w;      /* Keep picky compilers happy */
+call_data = call_data;
+
+/* End up with the split version, so message looks right when non-exist */
+
+for (i = 0; i < (spool_is_split? 2:1); i++)
+  {
+  message_subdir[0] = (i != 0)? ((uschar *)client_data)[5] : 0;
+  sprintf(CS buffer, "%s/msglog/%s/%s", spool_directory, message_subdir,
+    (uschar *)client_data);
+  f = fopen(CS buffer, "r");
+  if (f != NULL) break;
+  }
+
+if (f == NULL)
+  text_showf(text, "%s: %s\n", buffer, strerror(errno));
+else
+  {
+  while (Ufgets(buffer, 256, f) != NULL) text_show(text, buffer);
+  fclose(f);
+  }
+}
+
+
+
+/*************************************************
+*          Display the message body               *
+*************************************************/
+
+static void bodyAction(Widget w, XtPointer client_data, XtPointer call_data)
+{
+int i;
+uschar buffer[256];
+Widget text = text_create((uschar *)client_data, text_depth);
+FILE *f = NULL;
+
+w = w;      /* Keep picky compilers happy */
+call_data = call_data;
+
+for (i = 0; i < (spool_is_split? 2:1); i++)
+  {
+  message_subdir[0] = (i != 0)? ((uschar *)client_data)[5] : 0;
+  sprintf(CS buffer, "%s/input/%s/%s-D", spool_directory, message_subdir,
+    (uschar *)client_data);
+  f = fopen(CS buffer, "r");
+  if (f != NULL) break;
+  }
+
+if (f == NULL)
+  text_showf(text, "Failed to open file: %s\n", strerror(errno));
+else
+  {
+  int count = 0;
+  while (Ufgets(buffer, 256, f) != NULL)
+    {
+    text_show(text, buffer);
+    count += Ustrlen(buffer);
+    if (count > body_max)
+      {
+      text_show(text, US"\n*** Message length exceeds BODY_MAX ***\n");
+      break;
+      }
+    }
+  fclose(f);
+  }
+}
+
+
+
+/*************************************************
+*        Do something to a message               *
+*************************************************/
+
+/* The output is not shown in a window for non-delivery actions that succeed,
+unless action_output is set. We can't, however, tell until we have run
+the command whether we want the output or not, so the pipe has to be set up in
+all cases. */
+
+static void ActOnMessage(uschar *id, uschar *action, uschar *address_arg)
+{
+int pid;
+int pipe_fd[2];
+int delivery = Ustrcmp(action + Ustrlen(action) - 2, "-M") == 0;
+uschar *quote = US"";
+uschar *at = US"";
+uschar *qualify = US"";
+uschar buffer[256];
+queue_item *qq;
+Widget text = NULL;
+
+/* If the address arg is not empty and does not contain @ and there is a
+qualify domain, qualify it. (But don't qualify '<>'.)*/
+
+if (address_arg[0] != 0)
+  {
+  quote = US"\'";
+  if (Ustrchr(address_arg, '@') == NULL &&
+      Ustrcmp(address_arg, "<>") != 0 &&
+      qualify_domain != NULL &&
+      qualify_domain[0] != 0)
+    {
+    at = US"@";
+    qualify = qualify_domain;
+    }
+  }
+sprintf(CS buffer, "%s %s %s %s %s %s%s%s%s%s", exim_path,
+  (alternate_config == NULL)? US"" : US"-C",
+  (alternate_config == NULL)? US"" : alternate_config,
+  action, id, quote, address_arg, at, qualify, quote);
+
+/* If we know we are going to need the window, create it now. */
+
+if (action_output || delivery)
+  {
+  text = text_create(id, text_depth);
+  text_showf(text, "%s\n", buffer);
+  }
+
+/* Create the pipe for output. Remember, on most systems pipe[0] is
+for reading and pipe[1] is for writing! Solaris, with its two-way
+pipes is a trap! */
+
+if (pipe(pipe_fd) != 0)
+  {
+  if (text == NULL)
+    {
+    text = text_create(id, text_depth);
+    text_showf(text, "%s\n", buffer);
+    }
+  text_show(text, US"*** Failed to create pipe ***\n");
+  return;
+  }
+
+fcntl(pipe_fd[0], F_SETFL, O_NONBLOCK);
+fcntl(pipe_fd[1], F_SETFL, O_NONBLOCK);
+
+/* Delivering a message can take some time, and we want to show the
+output as it goes along. This requires subprocesses and is coded below. For
+other commands, we can assume an immediate response, and so need not waste
+resources with subprocesses. If action_output is FALSE, don't show the
+output at all. */
+
+if (!delivery)
+  {
+  int count, rc;
+  int save_stdout = dup(1);
+  int save_stderr = dup(2);
+
+  close(1);
+  close(2);
+
+  dup2(pipe_fd[1], 1);
+  dup2(pipe_fd[1], 2);
+  close(pipe_fd[1]);
+
+  rc = system(CS buffer);
+
+  close(1);
+  close(2);
+
+  if (action_output || rc != 0)
+    {
+    if (text == NULL)
+      {
+      text = text_create(id, text_depth);
+      text_showf(text, "%s\n", buffer);
+      }
+    while ((count = read(pipe_fd[0], buffer, 254)) > 0)
+      {
+      buffer[count] = 0;
+      text_show(text, buffer);
+      }
+    }
+
+  close(pipe_fd[0]);
+
+  dup2(save_stdout, 1);
+  dup2(save_stderr, 2);
+  close(save_stdout);
+  close(save_stderr);
+
+  /* If action was to change the sender, and it succeeded, we have to
+  update the in-store data. */
+
+  if (rc == 0 && Ustrcmp(action + Ustrlen(action) - 4, "-Mes") == 0)
+    {
+    queue_item *q = find_queue(id, queue_noop, 0);
+    if (q != NULL)
+      {
+      if (q->sender != NULL) store_free(q->sender);
+      q->sender = store_malloc(Ustrlen(address_arg) + 1);
+      Ustrcpy(q->sender, address_arg);
+      }
+    }
+
+  /* If configured, cause a display update and return */
+
+  if (action_queue_update) tick_queue_accumulator = 999999;
+  return;
+  }
+
+/* Message is to be delivered. Ensure that it is marked unfrozen,
+because nothing will get written to the log to show that this has
+happened. (Other freezing/unfreezings get logged and picked up from
+there.) */
+
+qq = find_queue(id, queue_noop, 0);
+if (qq != NULL) qq->frozen = FALSE;
+
+/* New, asynchronous code runs in a subprocess for commands that
+will take some time. The main process does not wait. There is a
+SIGCHLD handler in the main program that cleans up any terminating
+sub processes. */
+
+if ((pid = fork()) == 0)
+  {
+  close(1);
+  close(2);
+
+  dup2(pipe_fd[1], 1);
+  dup2(pipe_fd[1], 2);
+  close(pipe_fd[1]);
+
+  system(CS buffer);
+
+  close(1);
+  close(2);
+  close(pipe_fd[0]);
+  _exit(0);
+  }
+
+/* Main process - set up an item for the main ticker to watch. */
+
+if (pid < 0) text_showf(text, "Failed to fork: %s\n", strerror(pid)); else
+  {
+  pipe_item *p = (pipe_item *)store_malloc(sizeof(pipe_item));
+
+  if (p == NULL)
+    {
+    text_show(text, US"Run out of store\n");
+    return;
+    }
+
+  p->widget = text;
+  p->fd = pipe_fd[0];
+
+  p->next = pipe_chain;
+  pipe_chain = p;
+
+  close(pipe_fd[1]);
+  }
+}
+
+
+
+
+/*************************************************
+*        Cause a message to be delivered         *
+*************************************************/
+
+static void deliverAction(Widget w, XtPointer client_data, XtPointer call_data)
+{
+w = w;      /* Keep picky compilers happy */
+call_data = call_data;
+ActOnMessage((uschar *)client_data, US"-v -M", US"");
+}
+
+
+
+/*************************************************
+*        Cause a message to be Frozen            *
+*************************************************/
+
+static void freezeAction(Widget w, XtPointer client_data, XtPointer call_data)
+{
+w = w;      /* Keep picky compilers happy */
+call_data = call_data;
+ActOnMessage((uschar *)client_data, US"-Mf", US"");
+}
+
+
+
+/*************************************************
+*        Cause a message to be thawed            *
+*************************************************/
+
+static void thawAction(Widget w, XtPointer client_data, XtPointer call_data)
+{
+w = w;      /* Keep picky compilers happy */
+call_data = call_data;
+ActOnMessage((uschar *)client_data, US"-Mt", US"");
+}
+
+
+
+/*************************************************
+*          Take action using dialog data         *
+*************************************************/
+
+/* This function is called after a dialog box has been filled
+in. It is global because it is set up in the action table at
+start-up time. If the string is empty, do nothing. */
+
+XtActionProc dialogAction(Widget w, XEvent *event, String *ss, Cardinal *c)
+{
+uschar *s = US XawDialogGetValueString(dialog_widget);
+
+w = w;      /* Keep picky compilers happy */
+event = event;
+ss = ss;
+c = c;
+
+XtPopdown((Widget)dialog_shell);
+XtDestroyWidget((Widget)dialog_shell);
+while (isspace(*s)) s++;
+if (s[0] != 0)
+  {
+  if (actioned_message[0] != 0)
+    ActOnMessage(actioned_message, action_required, s);
+  else
+    NonMessageDialogue(s);    /* When called from somewhere else */
+  }
+return NULL;
+}
+
+
+
+/*************************************************
+*              Create a dialog box               *
+*************************************************/
+
+/* The focus is grabbed exclusively, so nothing else can
+be done to the application until the box is filled in. This
+function is also used by the Hide button handler. */
+
+void create_dialog(uschar *label, uschar *value)
+{
+Arg warg[4];
+Dimension x, y, xx, yy;
+XtTranslations pop_trans;
+Widget text;
+
+/* Get the position of a reference widget so the dialog box can be put
+near to it. */
+
+get_pos_args[0].value = (XtArgVal)(&x);
+get_pos_args[1].value = (XtArgVal)(&y);
+XtGetValues(dialog_ref_widget, get_pos_args, 2);
+
+/* When this is not a message_specific thing, the position of the reference
+widget is relative to the window. Get the position of the top level widget and
+add to the position. */
+
+if (dialog_ref_widget != menushell)
+  {
+  get_pos_args[0].value = (XtArgVal)(&xx);
+  get_pos_args[1].value = (XtArgVal)(&yy);
+  XtGetValues(toplevel_widget, get_pos_args, 2);
+  x += xx;
+  y += yy;
+  }
+
+/* Create a transient shell for the dialog box. */
+
+XtSetArg(warg[0], XtNtransientFor, queue_widget);
+XtSetArg(warg[1], XtNx, x + 50);
+XtSetArg(warg[2], XtNy, y + 50);
+XtSetArg(warg[3], XtNallowShellResize, True);
+dialog_shell = XtCreatePopupShell("forDialog", transientShellWidgetClass,
+   toplevel_widget, warg, 4);
+
+/* Create the dialog box. */
+
+dialog_arg[0].value = (XtArgVal)label;
+dialog_arg[1].value = (XtArgVal)value;
+dialog_widget = XtCreateManagedWidget("dialog", dialogWidgetClass, dialog_shell,
+  dialog_arg, XtNumber(dialog_arg));
+
+/* Get the text widget from within the dialog box, give it the keyboard focus,
+make it wider than the default, and override its translations to make Return
+call the dialog action function. */
+
+text = XtNameToWidget(dialog_widget, "value");
+XawTextSetInsertionPoint(text, Ustrlen(value));
+XtSetKeyboardFocus(dialog_widget, text);
+xs_SetValues(text, 1, "width", 200);
+pop_trans = XtParseTranslationTable(
+  "<Key>Return:         dialogAction()\n");
+XtOverrideTranslations(text, pop_trans);
+
+/* Pop the thing up. */
+
+XtPopup(dialog_shell, XtGrabExclusive);
+XFlush(X_display);
+}
+
+
+
+
+
+/*************************************************
+*        Cause a recipient to be added           *
+*************************************************/
+
+/* This just sets up the dialog box; the action happens when it has been filled
+in. */
+
+static void addrecipAction(Widget w, XtPointer client_data, XtPointer call_data)
+{
+w = w;      /* Keep picky compilers happy */
+call_data = call_data;
+Ustrcpy(actioned_message, (uschar *)client_data);
+action_required = US"-Mar";
+dialog_ref_widget = menushell;
+create_dialog(US"Recipient address to add?", US"");
+}
+
+
+
+/*************************************************
+*    Cause an address to be marked delivered     *
+*************************************************/
+
+static void markdelAction(Widget w, XtPointer client_data, XtPointer call_data)
+{
+w = w;      /* Keep picky compilers happy */
+call_data = call_data;
+Ustrcpy(actioned_message, (uschar *)client_data);
+action_required = US"-Mmd";
+dialog_ref_widget = menushell;
+create_dialog(US"Recipient address to mark delivered?", US"");
+}
+
+
+/*************************************************
+*   Cause all addresses to be marked delivered   *
+*************************************************/
+
+static void markalldelAction(Widget w, XtPointer client_data, XtPointer call_data)
+{
+w = w;      /* Keep picky compilers happy */
+call_data = call_data;
+ActOnMessage((uschar *)client_data, US"-Mmad", US"");
+}
+
+
+/*************************************************
+*        Edit the message's sender               *
+*************************************************/
+
+static void editsenderAction(Widget w, XtPointer client_data,
+  XtPointer call_data)
+{
+queue_item *q;
+uschar *sender;
+w = w;      /* Keep picky compilers happy */
+call_data = call_data;
+Ustrcpy(actioned_message, (uschar *)client_data);
+q = find_queue(actioned_message, queue_noop, 0);
+sender = (q == NULL)? US"" : (q->sender[0] == 0)? US"<>" : q->sender;
+action_required = US"-Mes";
+dialog_ref_widget = menushell;
+create_dialog(US"New sender address?", sender);
+}
+
+
+/*************************************************
+*    Cause a message to be returned to sender    *
+*************************************************/
+
+static void giveupAction(Widget w, XtPointer client_data, XtPointer call_data)
+{
+w = w;      /* Keep picky compilers happy */
+call_data = call_data;
+ActOnMessage((uschar *)client_data, US"-v -Mg", US"");
+}
+
+
+
+/*************************************************
+*      Cause a message to be cancelled           *
+*************************************************/
+
+static void removeAction(Widget w, XtPointer client_data, XtPointer call_data)
+{
+w = w;      /* Keep picky compilers happy */
+call_data = call_data;
+ActOnMessage((uschar *)client_data, US"-Mrm", US"");
+}
+
+
+
+/*************************************************
+*             Display a message's headers        *
+*************************************************/
+
+static void headersAction(Widget w, XtPointer client_data, XtPointer call_data)
+{
+uschar buffer[256];
+header_line *h, *next;
+Widget text = text_create((uschar *)client_data, text_depth);
+void *reset_point;
+
+w = w;      /* Keep picky compilers happy */
+call_data = call_data;
+
+/* Remember the point in the dynamic store so we can recover to it afterwards.
+Then use Exim's function to read the header. */
+
+reset_point = store_get(0);
+
+sprintf(CS buffer, "%s-H", (uschar *)client_data);
+if (spool_read_header(buffer, TRUE, FALSE) != spool_read_OK)
+  {
+  if (errno == ERRNO_SPOOLFORMAT)
+    {
+    struct stat statbuf;
+    sprintf(CS big_buffer, "%s/input/%s", spool_directory, buffer);
+    if (Ustat(big_buffer, &statbuf) == 0)
+      text_showf(text, "Format error in spool file %s: size=%d\n", buffer,
+        statbuf.st_size);
+    else text_showf(text, "Format error in spool file %s\n", buffer);
+    }
+  else text_showf(text, "Read error for spool file %s\n", buffer);
+  store_reset(reset_point);
+  return;
+  }
+
+if (sender_address != NULL)
+  {
+  text_showf(text, "%s sender: <%s>\n", sender_local? "Local" : "Remote",
+    sender_address);
+  }
+
+if (recipients_list != NULL)
+  {
+  int i;
+  text_show(text, US"Recipients:\n");
+  for (i = 0; i < recipients_count; i++)
+    {
+    text_showf(text, "  %s %s\n",
+      (tree_search(tree_nonrecipients, recipients_list[i].address) == NULL)?
+        " ":"*", recipients_list[i].address);
+    }
+  text_show(text, US"\n");
+  }
+
+for (h = header_list; h != NULL; h = next)
+  {
+  next = h->next;
+  text_showf(text, "%c ", h->type);   /* Don't push h->text through a %s */
+  text_show(text, h->text);           /* expansion as it may be v large */
+  }
+
+store_reset(reset_point);
+}
+
+
+
+
+/*************************************************
+*              Dismiss a text window             *
+*************************************************/
+
+static void dismissAction(Widget w, XtPointer client_data, XtPointer call_data)
+{
+pipe_item *p = pipe_chain;
+
+w = w;      /* Keep picky compilers happy */
+call_data = call_data;
+
+XtPopdown((Widget)client_data);
+XtDestroyWidget((Widget)client_data);
+
+/* If this is a text widget for a sub-process, clear it out of
+the chain so that subsequent data doesn't try to use it. We have
+to search the parents of the saved widget to see if one of them
+is what we have just destroyed. */
+
+while (p != NULL)
+  {
+  Widget pp = p->widget;
+  while (pp != NULL)
+    {
+    if (pp == (Widget)client_data) { p->widget = NULL; return; }
+    pp = XtParent(pp);
+    }
+  p = p->next;
+  }
+}
+
+
+
+/*************************************************
+*             Set up popup text window           *
+*************************************************/
+
+static Widget text_create(uschar *name, int height)
+{
+Widget textshell, form, text, button;
+
+/* Create a popup shell widget to display as an additional
+toplevel window. */
+
+textshell = XtCreatePopupShell("textshell", topLevelShellWidgetClass,
+  toplevel_widget, NULL, 0);
+xs_SetValues(textshell, 4,
+  "title",     name,
+  "iconName",  name,
+  "minWidth",  100,
+  "minHeight", 100);
+
+/* Create a form widget, containing the text widget and the
+dismiss button widget. */
+
+form = XtCreateManagedWidget("textform", formWidgetClass,
+  textshell, NULL, 0);
+xs_SetValues(form, 1, "defaultDistance", 8);
+
+text = XtCreateManagedWidget("texttext", asciiTextWidgetClass,
+  form, text_arg, XtNumber(text_arg));
+xs_SetValues(text, 4,
+  "editType",        XawtextAppend,
+  "width",           700,
+  "height",          height,
+  "translations",    text_trans);
+XawTextDisplayCaret(text, TRUE);
+
+/* Use the same font as for the queue display */
+
+if (queue_font != NULL)
+  {
+  XFontStruct *f = XLoadQueryFont(X_display, CS queue_font);
+  if (f != NULL) xs_SetValues(text, 1, "font", f);
+  }
+
+button_arg[0].value = (XtArgVal)text;
+button = XtCreateManagedWidget("dismiss", commandWidgetClass,
+  form, button_arg, XtNumber(button_arg));
+XtAddCallback(button, "callback",  dismissAction, (XtPointer)textshell);
+
+/* Get the toplevel popup displayed, and yield the text widget so
+that text can be put into it. */
+
+XtPopup(textshell, XtGrabNone);
+return text;
+}
+
+
+
+
+/*************************************************
+*            Set up menu in queue window         *
+*************************************************/
+
+/* We have added an action table that causes this function to
+be called, and set up button 2 in the text widgets to call it. */
+
+void menu_create(Widget w, XEvent *event, String *actargs, Cardinal *count)
+{
+int line;
+int i;
+uschar *s;
+XawTextPosition p;
+Widget src, menu_line, item_1, item_2, item_3, item_4,
+  item_5, item_6, item_7, item_8, item_9, item_10, item_11,
+  item_12, item_13;
+XtTranslations menu_trans = XtParseTranslationTable(
+  "<EnterWindow>:   highlight()\n\
+   <LeaveWindow>:   unhighlight()\n\
+   <BtnMotion>:     highlight()\n\
+   <BtnUp>:         MenuPopdown()notify()unhighlight()\n\
+  ");
+
+actargs = actargs;   /* Keep picky compilers happy */
+count = count;
+
+/* Get the sink and source and the current text pointer */
+
+queue_get_arg[0].value = (XtArgVal)(&queue_text_sink);
+queue_get_arg[1].value = (XtArgVal)(&src);
+queue_get_arg[2].value = (XtArgVal)(&s);
+XtGetValues(w, queue_get_arg, 3);
+
+/* Find the line number of the pointer in the window, and the
+character offset of the top lefthand of the window. */
+
+line = (event->xbutton).y / XawTextSinkMaxHeight(queue_text_sink, 1);
+p = XawTextTopPosition(w);
+
+/* Find the start of the line on which the button was clicked. */
+
+i = line;
+while (i-- > 0)
+  {
+  while (s[p] != 0 && s[p++] != '\n');
+  }
+
+/* Now pointing either at 0 or 1st uschar after \n, or very 1st uschar.
+If 0, the click was beyond the end of the data; just set up a dummy
+menu. (Not easy to ignore as several actions are specified for the
+mouse click and it expects this one to set up a menu.) If on a
+continuation line, move back to the main line. */
+
+if (s[p] == 0)
+  {
+  menushell_arg[0].value = (XtArgVal)"No message selected";
+  menushell = XtCreatePopupShell("menu", simpleMenuWidgetClass,
+    queue_widget, menushell_arg, XtNumber(menushell_arg));
+  XtAddCallback(menushell, "popdownCallback", popdownAction, NULL);
+  xs_SetValues(menushell, 2,
+    "cursor",       XCreateFontCursor(X_display, XC_arrow),
+    "translations", menu_trans);
+
+  /* To keep the widgets in XFree86 happy, we have to create at least one menu
+  item, it seems. (Openwindows doesn't mind a menu with no items.) Otherwise
+  there's a complaint about a zero width menu, and a crash. */
+
+  menu_line = XtCreateManagedWidget("line", smeLineObjectClass, menushell,
+    NULL, 0);
+
+  item_99_arg[0].value = (XtArgVal)menu_line;
+  (void)XtCreateManagedWidget("item99", smeBSBObjectClass, menushell,
+    item_99_arg, XtNumber(item_99_arg));
+
+  highlighted_x = -1;
+  return;
+  }
+
+while (p > 0 && s[p+11] == ' ')
+  {
+  line--;
+  p--;
+  while (p > 0 && s[p-1] != '\n') p--;
+  }
+
+/* Now pointing at first character of a main line. */
+
+Ustrncpy(message_id, s+p+11, MESSAGE_ID_LENGTH);
+message_id[MESSAGE_ID_LENGTH] = 0;
+
+/* Highlight the line being menued, and save its parameters so that it
+can be de-highlighted at popdown. */
+
+highlighted_start = highlighted_end = p;
+while (s[highlighted_end] != '\n') highlighted_end++;
+highlighted_x = 17;
+highlighted_y = line * XawTextSinkMaxHeight(queue_text_sink, 1) + 2;
+
+XawTextSinkDisplayText(queue_text_sink,
+  highlighted_x, highlighted_y,
+  highlighted_start, highlighted_end, 1);
+
+/* Create the popup shell and the other widgets that comprise the menu.
+Set the translations and pointer shape, and add the callback pointers. */
+
+menushell_arg[0].value = (XtArgVal)message_id;
+menushell = XtCreatePopupShell("menu", simpleMenuWidgetClass,
+  queue_widget, menushell_arg, XtNumber(menushell_arg));
+XtAddCallback(menushell, "popdownCallback", popdownAction, NULL);
+
+xs_SetValues(menushell, 2,
+  "cursor",       XCreateFontCursor(X_display, XC_arrow),
+  "translations", menu_trans);
+
+menu_line = XtCreateManagedWidget("line", smeLineObjectClass, menushell,
+  NULL, 0);
+
+item_1_arg[0].value = (XtArgVal)menu_line;
+item_1 = XtCreateManagedWidget("item1", smeBSBObjectClass, menushell,
+  item_1_arg, XtNumber(item_1_arg));
+XtAddCallback(item_1, "callback",  msglogAction, (XtPointer)message_id);
+
+item_2_arg[0].value = (XtArgVal)item_1;
+item_2 = XtCreateManagedWidget("item2", smeBSBObjectClass, menushell,
+  item_2_arg, XtNumber(item_2_arg));
+XtAddCallback(item_2, "callback",  headersAction, (XtPointer)message_id);
+
+item_3_arg[0].value = (XtArgVal)item_2;
+item_3 = XtCreateManagedWidget("item3", smeBSBObjectClass, menushell,
+  item_3_arg, XtNumber(item_3_arg));
+XtAddCallback(item_3, "callback",  bodyAction, (XtPointer)message_id);
+
+item_4_arg[0].value = (XtArgVal)item_3;
+item_4 = XtCreateManagedWidget("item4", smeBSBObjectClass, menushell,
+  item_4_arg, XtNumber(item_4_arg));
+XtAddCallback(item_4, "callback",  deliverAction, (XtPointer)message_id);
+
+item_5_arg[0].value = (XtArgVal)item_4;
+item_5 = XtCreateManagedWidget("item5", smeBSBObjectClass, menushell,
+  item_5_arg, XtNumber(item_5_arg));
+XtAddCallback(item_5, "callback",  freezeAction, (XtPointer)message_id);
+
+item_6_arg[0].value = (XtArgVal)item_5;
+item_6 = XtCreateManagedWidget("item6", smeBSBObjectClass, menushell,
+  item_6_arg, XtNumber(item_6_arg));
+XtAddCallback(item_6, "callback",  thawAction, (XtPointer)message_id);
+
+item_7_arg[0].value = (XtArgVal)item_6;
+item_7 = XtCreateManagedWidget("item7", smeBSBObjectClass, menushell,
+  item_7_arg, XtNumber(item_7_arg));
+XtAddCallback(item_7, "callback",  giveupAction, (XtPointer)message_id);
+
+item_8_arg[0].value = (XtArgVal)item_7;
+item_8 = XtCreateManagedWidget("item8", smeBSBObjectClass, menushell,
+  item_8_arg, XtNumber(item_8_arg));
+XtAddCallback(item_8, "callback",  removeAction, (XtPointer)message_id);
+
+item_9_arg[0].value = (XtArgVal)item_8;
+item_9 = XtCreateManagedWidget("item9", smeBSBObjectClass, menushell,
+  item_9_arg, XtNumber(item_9_arg));
+
+item_10_arg[0].value = (XtArgVal)item_9;
+item_10 = XtCreateManagedWidget("item10", smeBSBObjectClass, menushell,
+  item_10_arg, XtNumber(item_10_arg));
+XtAddCallback(item_10, "callback",  addrecipAction, (XtPointer)message_id);
+
+item_11_arg[0].value = (XtArgVal)item_10;
+item_11 = XtCreateManagedWidget("item11", smeBSBObjectClass, menushell,
+  item_11_arg, XtNumber(item_11_arg));
+XtAddCallback(item_11, "callback",  markdelAction, (XtPointer)message_id);
+
+item_12_arg[0].value = (XtArgVal)item_11;
+item_12 = XtCreateManagedWidget("item12", smeBSBObjectClass, menushell,
+  item_12_arg, XtNumber(item_12_arg));
+XtAddCallback(item_12, "callback",  markalldelAction, (XtPointer)message_id);
+
+item_13_arg[0].value = (XtArgVal)item_12;
+item_13 = XtCreateManagedWidget("item13", smeBSBObjectClass, menushell,
+  item_13_arg, XtNumber(item_13_arg));
+XtAddCallback(item_13, "callback",  editsenderAction, (XtPointer)message_id);
+
+/* Arrange that the menu pops up with the first item selected. */
+
+xs_SetValues(menushell, 1, "popupOnEntry", item_1);
+
+/* Flag that the menu is up to suppress queue updates. */
+
+menu_is_up = TRUE;
+}
+
+/* End of em_menu.c */
diff --git a/src/exim_monitor/em_queue.c b/src/exim_monitor/em_queue.c
new file mode 100644 (file)
index 0000000..d670720
--- /dev/null
@@ -0,0 +1,813 @@
+/* $Cambridge: exim/src/exim_monitor/em_queue.c,v 1.1 2004/10/07 10:39:01 ph10 Exp $ */
+
+/*************************************************
+*                 Exim Monitor                   *
+*************************************************/
+
+/* Copyright (c) University of Cambridge 1995 - 2004 */
+/* See the file NOTICE for conditions of use and distribution. */
+
+
+#include "em_hdr.h"
+
+
+/* This module contains functions to do with scanning exim's
+queue and displaying the data therefrom. */
+
+
+/* If we are anonymizing for screen shots, define a function to anonymize
+addresses. Otherwise, define a macro that does nothing. */
+
+#ifdef ANONYMIZE
+static uschar *anon(uschar *s)
+{
+static uschar anon_result[256];
+uschar *ss = anon_result;
+for (; *s != 0; s++) *ss++ = (*s == '@' || *s == '.')? *s : 'x';
+*ss = 0;
+return anon_result;
+}
+#else
+#define anon(x) x
+#endif
+
+
+/*************************************************
+*                 Static variables               *
+*************************************************/
+
+static int queue_total = 0;   /* number of items in queue */
+
+/* Table for turning base-62 numbers into binary */
+
+static uschar tab62[] =
+          {0,1,2,3,4,5,6,7,8,9,0,0,0,0,0,0,     /* 0-9 */
+           0,10,11,12,13,14,15,16,17,18,19,20,  /* A-K */
+          21,22,23,24,25,26,27,28,29,30,31,32,  /* L-W */
+          33,34,35, 0, 0, 0, 0, 0,              /* X-Z */
+           0,36,37,38,39,40,41,42,43,44,45,46,  /* a-k */
+          47,48,49,50,51,52,53,54,55,56,57,58,  /* l-w */
+          59,60,61};                            /* x-z */
+
+/* Index for quickly finding things in the ordered queue. */
+
+static queue_item *queue_index[queue_index_size];
+
+
+
+/*************************************************
+*         Find/Create/Delete a destination       *
+*************************************************/
+
+/* If the action is dest_noop, then just return item or NULL;
+if it is dest_add, then add if not present, and return item;
+if it is dest_remove, remove if present and return NULL. The
+address is lowercased to start with, unless it begins with
+"*", which it does for error messages. */
+
+dest_item *find_dest(queue_item *q, uschar *name, int action, BOOL caseless)
+{
+dest_item *dd;
+dest_item **d = &(q->destinations);
+
+while (*d != NULL)
+  {
+  if ((caseless? strcmpic(name,(*d)->address) : Ustrcmp(name,(*d)->address))
+        == 0)
+    {
+    dest_item *ddd;
+
+    if (action != dest_remove) return *d;
+    dd = *d;
+    *d = dd->next;
+    store_free(dd);
+
+    /* Unset any parent pointers that were to this address */
+
+    for (ddd = q->destinations; ddd != NULL; ddd = ddd->next)
+      {
+      if (ddd->parent == dd) ddd->parent = NULL;
+      }
+
+    return NULL;
+    }
+  d = &((*d)->next);
+  }
+
+if (action != dest_add) return NULL;
+
+dd = (dest_item *)store_malloc(sizeof(dest_item) + Ustrlen(name));
+Ustrcpy(dd->address, name);
+dd->next = NULL;
+dd->parent = NULL;
+*d = dd;
+return dd;
+}
+
+
+
+/*************************************************
+*            Clean up a dead queue item          *
+*************************************************/
+
+static void clean_up(queue_item *p)
+{
+dest_item *dd = p->destinations;
+while (dd != NULL)
+  {
+  dest_item *next = dd->next;
+  store_free(dd);
+  dd = next;
+  }
+if (p->sender != NULL) store_free(p->sender);
+store_free(p);
+}
+
+
+/*************************************************
+*             Set up new queue item              *
+*************************************************/
+
+static queue_item *set_up(uschar *name, int dir_char)
+{
+int i, rc, save_errno;
+struct stat statdata;
+void *reset_point;
+uschar *p;
+queue_item *q = (queue_item *)store_malloc(sizeof(queue_item));
+uschar buffer[256];
+
+/* Initialize the block */
+
+q->next = q->prev = NULL;
+q->destinations = NULL;
+Ustrcpy(q->name, name);
+q->seen = TRUE;
+q->frozen = FALSE;
+q->dir_char = dir_char;
+q->sender = NULL;
+q->size = 0;
+
+/* Read the header file from the spool; if there is a failure it might mean
+inaccessibility as a result of protections. A successful read will have caused
+sender_address to get set and the recipients fields to be initialized. If
+there's a format error in the headers, we can still display info from the
+envelope.
+
+Before reading the header remember the position in the dynamic store so that
+we can recover the store into which the header is read. All data read by
+spool_read_header that is to be preserved is copied into malloc store. */
+
+reset_point = store_get(0);
+message_size = 0;
+message_subdir[0] = dir_char;
+sprintf(CS buffer, "%s-H", name);
+rc =  spool_read_header(buffer, FALSE, TRUE);
+save_errno = errno;
+
+/* If we failed to read the envelope, compute the input time by
+interpreting the id as a base-62 number. */
+
+if (rc != spool_read_OK && rc != spool_read_hdrerror)
+  {
+  int t = 0;
+  for (i = 0; i < 6; i++) t = t * 62 + tab62[name[i] - '0'];
+  q->update_time = q->input_time = t;
+  }
+
+/* Envelope read; get input time and remove qualify_domain from sender address,
+if it's there. */
+
+else
+  {
+  q->update_time = q->input_time = received_time;
+  if ((p = strstric(sender_address+1, qualify_domain, FALSE)) != NULL &&
+    *(--p) == '@') *p = 0;
+  }
+
+/* If we didn't read the whole header successfully, generate an error
+message. If the envelope was read, this appears as a first recipient;
+otherwise it sets set up in the sender field. */
+
+if (rc != spool_read_OK)
+  {
+  uschar *msg;
+
+  if (save_errno == ERRNO_SPOOLFORMAT)
+    {
+    struct stat statbuf;
+    sprintf(CS big_buffer, "%s/input/%s", spool_directory, buffer);
+    if (Ustat(big_buffer, &statbuf) == 0)
+      msg = string_sprintf("*** Format error in spool file: size = %d ***",
+        statbuf.st_size);
+    else msg = string_sprintf("*** Format error in spool file ***");
+    }
+  else msg = string_sprintf("*** Cannot read spool file ***");
+
+  if (rc == spool_read_hdrerror)
+    {
+    (void)find_dest(q, msg, dest_add, FALSE);
+    }
+  else
+    {
+    deliver_freeze = FALSE;
+    sender_address = msg;
+    recipients_count = 0;
+    }
+  }
+
+/* Now set up the remaining data. */
+
+q->frozen = deliver_freeze;
+
+if (sender_set_untrusted)
+  {
+  if (sender_address[0] == 0)
+    {
+    q->sender = store_malloc(Ustrlen(originator_login) + 6);
+    sprintf(CS q->sender, "<> (%s)", originator_login);
+    }
+  else
+    {
+    q->sender = store_malloc(Ustrlen(sender_address) +
+      Ustrlen(originator_login) + 4);
+    sprintf(CS q->sender, "%s (%s)", sender_address, originator_login);
+    }
+  }
+else
+  {
+  q->sender = store_malloc(Ustrlen(sender_address) + 1);
+  Ustrcpy(q->sender, sender_address);
+  }
+
+sender_address = NULL;
+
+sprintf(CS buffer, "%s/input/%s/%s-D", spool_directory, message_subdir, name);
+if (Ustat(buffer, &statdata) == 0)
+  q->size = message_size + statdata.st_size - SPOOL_DATA_START_OFFSET + 1;
+
+/* Scan and process the recipients list, skipping any that have already
+been delivered, and removing visible names. */
+
+if (recipients_list != NULL)
+  {
+  for (i = 0; i < recipients_count; i++)
+    {
+    uschar *r = recipients_list[i].address;
+    if (tree_search(tree_nonrecipients, r) == NULL)
+      {
+      if ((p = strstric(r+1, qualify_domain, FALSE)) != NULL &&
+        *(--p) == '@') *p = 0;
+      (void)find_dest(q, r, dest_add, FALSE);
+      }
+    }
+  }
+
+/* Recover the dynamic store used by spool_read_header(). */
+
+store_reset(reset_point);
+return q;
+}
+
+
+
+/*************************************************
+*             Find/Create a queue item           *
+*************************************************/
+
+/* The queue is kept as a doubly-linked list, sorted by name. However,
+to speed up searches, an index into the list is used. This is maintained
+by the scan_spool_input function when it goes down the list throwing
+out entries that are no longer needed. When the action is "add" and
+we don't need to add, mark the found item as seen. */
+
+
+#ifdef never
+static void debug_queue(void)
+{
+int i;
+int count = 0;
+queue_item *p;
+printf("\nqueue_total=%d\n", queue_total);
+
+for (i = 0; i < queue_index_size; i++)
+  printf("index %d = %d %s\n", i, (int)(queue_index[i]),
+    (queue_index[i])->name);
+
+printf("Queue is:\n");
+p = queue_index[0];
+while (p != NULL)
+  {
+  count++;
+  for (i = 0; i < queue_index_size; i++)
+    {
+    if (queue_index[i] == p) printf("count=%d index=%d\n", count, (int)p);
+    }
+  printf("%d %d %d %s\n", (int)p, (int)p->next, (int)p->prev, p->name);
+  p = p->next;
+  }
+}
+#endif
+
+
+
+queue_item *find_queue(uschar *name, int action, int dir_char)
+{
+int first = 0;
+int last = queue_index_size - 1;
+int middle = (first + last)/2;
+queue_item *p, *q, *qq;
+
+/* Handle the empty queue as a special case. */
+
+if (queue_total == 0)
+  {
+  if (action != queue_add) return NULL;
+  if ((qq = set_up(name, dir_char)) != NULL)
+    {
+    int i;
+    for (i = 0; i < queue_index_size; i++) queue_index[i] = qq;
+    queue_total++;
+    return qq;
+    }
+  return NULL;
+  }
+
+/* Also handle insertion at the start or end of the queue
+as special cases. */
+
+if (Ustrcmp(name, (queue_index[0])->name) < 0)
+  {
+  if (action != queue_add) return NULL;
+  if ((qq = set_up(name, dir_char)) != NULL)
+    {
+    qq->next = queue_index[0];
+    (queue_index[0])->prev = qq;
+    queue_index[0] = qq;
+    queue_total++;
+    return qq;
+    }
+  return NULL;
+  }
+
+if (Ustrcmp(name, (queue_index[queue_index_size-1])->name) > 0)
+  {
+  if (action != queue_add) return NULL;
+  if ((qq = set_up(name, dir_char)) != NULL)
+    {
+    qq->prev = queue_index[queue_index_size-1];
+    (queue_index[queue_index_size-1])->next = qq;
+    queue_index[queue_index_size-1] = qq;
+    queue_total++;
+    return qq;
+    }
+  return NULL;
+  }
+
+/* Use binary chopping on the index to get a range of the queue to search
+when the name is somewhere in the middle, if present. */
+
+while (middle > first)
+  {
+  if (Ustrcmp(name, (queue_index[middle])->name) >= 0) first = middle;
+    else last = middle;
+  middle = (first + last)/2;
+  }
+
+/* Now search down the part of the queue in which the item must
+lie if it exists. Both end points are inclusive - though in fact
+the bottom one can only be = if it is the original bottom. */
+
+p = queue_index[first];
+q = queue_index[last];
+
+for (;;)
+  {
+  int c = Ustrcmp(name, p->name);
+
+  /* Already on queue; mark seen if required. */
+
+  if (c == 0)
+    {
+    if (action == queue_add) p->seen = TRUE;
+    return p;
+    }
+
+  /* Not on the queue; add an entry if required. Note that set-up might
+  fail (the file might vanish under our feet). Note also that we know
+  there is always a previous item to p because the end points are
+  inclusive. */
+
+  else if (c < 0)
+    {
+    if (action == queue_add)
+      {
+      if ((qq = set_up(name, dir_char)) != NULL)
+        {
+        qq->next = p;
+        qq->prev = p->prev;
+        p->prev->next = qq;
+        p->prev = qq;
+        queue_total++;
+        return qq;
+        }
+      }
+    return NULL;
+    }
+
+  /* Control should not reach here if p == q, because the name
+  is supposed to be <= the name of the bottom item. */
+
+  if (p == q) return NULL;
+
+  /* Else might be further down the queue; continue */
+
+  p = p->next;
+  }
+
+/* Control should never reach here. */
+}
+
+
+
+/*************************************************
+*        Scan the exim spool directory           *
+*************************************************/
+
+/* If we discover that there are subdirectories, set a flag so that the menu
+code knows to look for them. We count the entries to set the value for the
+queue stripchart, and set up data for the queue display window if the "full"
+option is given. */
+
+void scan_spool_input(int full)
+{
+int i;
+int subptr;
+int subdir_max = 1;
+int count = 0;
+int indexptr = 1;
+queue_item *p;
+struct dirent *ent;
+DIR *dd;
+uschar input_dir[256];
+uschar subdirs[64];
+
+subdirs[0] = 0;
+stripchart_total[0] = 0;
+
+sprintf(CS input_dir, "%s/input", spool_directory);
+subptr = Ustrlen(input_dir);
+input_dir[subptr+2] = 0;               /* terminator for lengthened name */
+
+/* Loop for each spool file on the queue - searching any subdirectories that
+may exist. When initializing eximon, every file will have to be read. To show
+there is progress, output a dot for each one to the standard output. */
+
+for (i = 0; i < subdir_max; i++)
+  {
+  int subdirchar = subdirs[i];      /* 0 for main directory */
+  if (subdirchar != 0)
+    {
+    input_dir[subptr] = '/';
+    input_dir[subptr+1] = subdirchar;
+    }
+
+  dd = opendir(CS input_dir);
+  if (dd == NULL) continue;
+
+  while ((ent = readdir(dd)) != NULL)
+    {
+    uschar *name = US ent->d_name;
+    int len = Ustrlen(name);
+
+    /* If we find a single alphameric sub-directory on the first
+    pass, add it to the list for subsequent scans, and remember that
+    we are dealing with a split directory. */
+
+    if (i == 0 && len == 1 && isalnum(*name))
+      {
+      subdirs[subdir_max++] = *name;
+      spool_is_split = TRUE;
+      continue;
+      }
+
+    /* Otherwise, if it is a header spool file, add it to the list */
+
+    if (len == SPOOL_NAME_LENGTH &&
+        name[SPOOL_NAME_LENGTH - 2] == '-' &&
+        name[SPOOL_NAME_LENGTH - 1] == 'H')
+      {
+      uschar basename[SPOOL_NAME_LENGTH];
+      stripchart_total[0]++;
+      if (!eximon_initialized) { printf("."); fflush(stdout); }
+      Ustrcpy(basename, name);
+      basename[SPOOL_NAME_LENGTH - 2] = 0;
+      if (full) find_queue(basename, queue_add, subdirchar);
+      }
+    }
+  closedir(dd);
+  }
+
+/* If simply counting the number, we are done; same if there are no
+items in the in-store queue. */
+
+if (!full || queue_total == 0) return;
+
+/* Now scan the queue and remove any items that were not in the directory. At
+the same time, set up the index pointers into the queue. Because we are
+removing items, the total that we are comparing against isn't actually correct,
+but in a long queue it won't make much difference, and in a short queue it
+doesn't matter anyway!*/
+
+p = queue_index[0];
+while (p != NULL)
+  {
+  if (!p->seen)
+    {
+    queue_item *next = p->next;
+    if (p->prev == NULL) queue_index[0] = next;
+      else p->prev->next = next;
+    if (next == NULL)
+      {
+      int i;
+      queue_item *q = queue_index[queue_index_size-1];
+      for (i = queue_index_size - 1; i >= 0; i--)
+        if (queue_index[i] == q) queue_index[i] = p->prev;
+      }
+    else next->prev = p->prev;
+    clean_up(p);
+    queue_total--;
+    p = next;
+    }
+  else
+    {
+    if (++count > (queue_total * indexptr)/(queue_index_size-1))
+      {
+      queue_index[indexptr++] = p;
+      }
+    p->seen = FALSE;  /* for next time */
+    p = p->next;
+    }
+  }
+
+/* If a lot of messages have been removed at the bottom, we may not
+have got the index all filled in yet. Make sure all the pointers
+are legal. */
+
+while (indexptr < queue_index_size - 1)
+  {
+  queue_index[indexptr++] = queue_index[queue_index_size-1];
+  }
+}
+
+
+
+
+/*************************************************
+*    Update the recipients list for a message    *
+*************************************************/
+
+/* We read the spool file only if its update time differs from last time,
+or if there is a journal file in existence. */
+
+/* First, a local subroutine to scan the non-recipients tree and
+remove any of them from the address list */
+
+static void
+scan_tree(queue_item *p, tree_node *tn)
+{
+if (tn != NULL)
+  {
+  if (tn->left != NULL) scan_tree(p, tn->left);
+  if (tn->right != NULL) scan_tree(p, tn->right);
+  (void)find_dest(p, tn->name, dest_remove, FALSE);
+  }
+}
+
+/* The main function */
+
+static void update_recipients(queue_item *p)
+{
+int i;
+FILE *jread;
+void *reset_point;
+struct stat statdata;
+uschar buffer[1024];
+
+message_subdir[0] = p->dir_char;
+
+sprintf(CS buffer, "%s/input/%s/%s-J", spool_directory, message_subdir, p->name);
+jread = fopen(CS buffer, "r");
+if (jread == NULL)
+  {
+  sprintf(CS buffer, "%s/input/%s/%s-H", spool_directory, message_subdir, p->name);
+  if (Ustat(buffer, &statdata) < 0 || p->update_time == statdata.st_mtime)
+    return;
+  }
+
+/* Get the contents of the header file; if any problem, just give up.
+Arrange to recover the dynamic store afterwards. */
+
+reset_point = store_get(0);
+sprintf(CS buffer, "%s-H", p->name);
+if (spool_read_header(buffer, FALSE, TRUE) != spool_read_OK)
+  {
+  store_reset(reset_point);
+  if (jread != NULL) fclose(jread);
+  return;
+  }
+
+/* If there's a journal file, add its contents to the non-recipients tree */
+
+if (jread != NULL)
+  {
+  while (Ufgets(big_buffer, big_buffer_size, jread) != NULL)
+    {
+    int n = Ustrlen(big_buffer);
+    big_buffer[n-1] = 0;
+    tree_add_nonrecipient(big_buffer);
+    }
+  fclose(jread);
+  }
+
+/* Scan and process the recipients list, removing any that have already
+been delivered, and removing visible names. In the nonrecipients tree,
+domains are lower cased. */
+
+if (recipients_list != NULL)
+  {
+  for (i = 0; i < recipients_count; i++)
+    {
+    uschar *pp;
+    uschar *r = recipients_list[i].address;
+    tree_node *node = tree_search(tree_nonrecipients, r);
+
+    if (node == NULL)
+      {
+      uschar temp[256];
+      uschar *rr = temp;
+      Ustrcpy(temp, r);
+      while (*rr != 0 && *rr != '@') rr++;
+      while (*rr != 0) { *rr = tolower(*rr); rr++; }
+      node = tree_search(tree_nonrecipients, temp);
+      }
+
+    if ((pp = strstric(r+1, qualify_domain, FALSE)) != NULL &&
+      *(--pp) == '@') *pp = 0;
+    if (node == NULL)
+      (void)find_dest(p, r, dest_add, FALSE);
+    else
+      (void)find_dest(p, r, dest_remove, FALSE);
+    }
+  }
+
+/* We also need to scan the tree of non-recipients, which might
+contain child addresses that are not in the recipients list, but
+which may have got onto the address list as a result of eximon
+noticing an == line in the log. Then remember the update time,
+recover the dynamic store, and we are done. */
+
+scan_tree(p, tree_nonrecipients);
+p->update_time = statdata.st_mtime;
+store_reset(reset_point);
+}
+
+
+
+/*************************************************
+*              Display queue data                *
+*************************************************/
+
+/* The present implementation simple re-writes the entire information each
+time. Take some care to keep the scrolled position as it previously was, but,
+if it was at the bottom, keep it at the bottom. Take note of any hide list, and
+time out the entries as appropriate. */
+
+void
+queue_display(void)
+{
+int now = (int)time(NULL);
+queue_item *p = queue_index[0];
+
+if (menu_is_up) return;            /* Avoid nasty interactions */
+
+text_empty(queue_widget);
+
+while (p != NULL)
+  {
+  int count = 1;
+  dest_item *dd, *ddd;
+  uschar u = 'm';
+  int t = (now - p->input_time)/60;  /* minutes on queue */
+
+  if (t > 90)
+    {
+    u = 'h';
+    t = (t + 30)/60;
+    if (t > 72)
+      {
+      u = 'd';
+      t = (t + 12)/24;
+      if (t > 99)                    /* someone had > 99 days */
+        {
+        u = 'w';
+        t = (t + 3)/7;
+        if (t > 99)                  /* so, just in case */
+          {
+          u = 'y';
+          t = (t + 26)/52;
+          }
+        }
+      }
+    }
+
+  update_recipients(p);                   /* update destinations */
+
+  /* Can't set this earlier, as header data may change things. */
+
+  dd = p->destinations;
+
+  /* Check to see if this message is on the hide list; if any hide
+  item has timed out, remove it from the list. Hide if all destinations
+  are on the hide list. */
+
+  for (ddd = dd; ddd != NULL; ddd = ddd->next)
+    {
+    skip_item *sk;
+    skip_item **skp;
+    int len_address;
+
+    if (ddd->address[0] == '*') break;
+    len_address = Ustrlen(ddd->address);
+
+    for (skp = &queue_skip; ; skp = &(sk->next))
+      {
+      int len_skip;
+
+      sk = *skp;
+      while (sk != NULL && now >= sk->reveal)
+        {
+        *skp = sk->next;
+        store_free(sk);
+        sk = *skp;
+        if (queue_skip == NULL)
+          {
+          XtDestroyWidget(unhide_widget);
+          unhide_widget = NULL;
+          }
+        }
+      if (sk == NULL) break;
+
+      /* If this address matches the skip item, break (sk != NULL) */
+
+      len_skip = Ustrlen(sk->text);
+      if (len_skip <= len_address &&
+          Ustrcmp(ddd->address + len_address - len_skip, sk->text) == 0)
+        break;
+      }
+
+    if (sk == NULL) break;
+    }
+
+  /* Don't use more than one call of anon() in one statement - it uses
+  a fixed static buffer. */
+
+  if (ddd != NULL || dd == NULL)
+    {
+    text_showf(queue_widget, "%c%2d%c %s %s %-8s ",
+      (p->frozen)? '*' : ' ',
+      t, u,
+      string_format_size(p->size, big_buffer),
+      p->name,
+      (p->sender == NULL)? US"       " :
+        (p->sender[0] == 0)? US"<>     " : anon(p->sender));
+
+    text_showf(queue_widget, "%s%s%s",
+      (dd == NULL || dd->address[0] == '*')? "" : "<",
+      (dd == NULL)? US"" : anon(dd->address),
+      (dd == NULL || dd->address[0] == '*')? "" : ">");
+
+    if (dd != NULL && dd->parent != NULL && dd->parent->address[0] != '*')
+      text_showf(queue_widget, " parent <%s>", anon(dd->parent->address));
+
+    text_show(queue_widget, US"\n");
+
+    if (dd != NULL) dd = dd->next;
+    while (dd != NULL && count++ < queue_max_addresses)
+      {
+      text_showf(queue_widget, "                                     <%s>",
+        anon(dd->address));
+      if (dd->parent != NULL && dd->parent->address[0] != '*')
+        text_showf(queue_widget, " parent <%s>", anon(dd->parent->address));
+      text_show(queue_widget, US"\n");
+      dd = dd->next;
+      }
+    if (dd != NULL)
+      text_showf(queue_widget, "                                     ...\n");
+    }
+
+  p = p->next;
+  }
+}
+
+/* End of em_queue.c */
diff --git a/src/exim_monitor/em_strip.c b/src/exim_monitor/em_strip.c
new file mode 100644 (file)
index 0000000..a16179c
--- /dev/null
@@ -0,0 +1,266 @@
+/* $Cambridge: exim/src/exim_monitor/em_strip.c,v 1.1 2004/10/07 10:39:01 ph10 Exp $ */
+
+/*************************************************
+*                   Exim Monitor                 *
+*************************************************/
+
+/* Copyright (c) University of Cambridge 1995 - 2004 */
+/* See the file NOTICE for conditions of use and distribution. */
+
+
+#include "em_hdr.h"
+
+/* This module contains functions for handling stripcharts */
+
+
+/*************************************************
+*               Static variables                 *
+*************************************************/
+
+static int     queue_first_time = 1;         /* flag for resetting time */
+static int     size_first_time = 1;          /* and another */
+
+static int     stripchart_count = 0;         /* count stripcharts created */
+static int    *stripchart_delay;             /* vector of delay counts */
+static Widget *stripchart_label;             /* vector of label widgets */
+static int    *stripchart_last_total;        /* vector of prevous values */
+static int    *stripchart_max;               /* vector of maxima */
+static int    *stripchart_middelay;          /* vector of */
+static int    *stripchart_midmax;            /* vector of */
+static uschar  **stripchart_name;              /* vector of name strings */
+static Widget  stripchart_prev_chart = NULL; /* previously created chart */
+static Widget  stripchart_prev_label = NULL; /* previously created label */
+
+
+
+/*************************************************
+*               Initialize                       *
+*************************************************/
+
+void stripchart_init(void)
+{
+stripchart_delay =      (int *)store_malloc(stripchart_number * sizeof(int));
+stripchart_label =   (Widget *)store_malloc(stripchart_number * sizeof(Widget));
+stripchart_last_total = (int *)store_malloc(stripchart_number * sizeof(int));
+stripchart_max =        (int *)store_malloc(stripchart_number * sizeof(int));
+stripchart_middelay =   (int *)store_malloc(stripchart_number * sizeof(int));
+stripchart_midmax =     (int *)store_malloc(stripchart_number * sizeof(int));
+stripchart_name =     (uschar **)store_malloc(stripchart_number * sizeof(uschar *));
+stripchart_total =      (int *)store_malloc(stripchart_number * sizeof(int));
+}
+
+
+
+/*************************************************
+*           Stripchart callback function         *
+*************************************************/
+
+/* The client data is the index of the stripchart. We have to play
+a little game in order to ensure that the double value is correctly
+passed back via the value pointer without the compiler doing an
+unwanted cast. */
+
+static void stripchartAction(Widget w, XtPointer client_data, XtPointer value)
+{
+double *ptr = (double *)value;
+static int thresholds[] =
+  {10, 20, 50, 100, 200, 500, 1000, 2000, 5000, 10000, 0};
+int num = (int)client_data;
+int oldmax = 0;
+int newmax = 0;
+int newvalue = 0;
+int i = 0;
+
+/* For the queue stripchart, the value is the current vector value.
+We reset the initial delay of 1 second to the normal value. */
+
+if (num == 0)
+  {
+  newvalue = stripchart_total[0];
+  if (queue_first_time)
+    {
+    xs_SetValues(w, 1, "update", stripchart_update);
+    queue_first_time = 0;
+    }
+  }
+
+/* For the size monitoring stripchart, the value is the percentage
+fullness of the partition. A similar fudge to the above is implemented
+for the first time. Not all OS have statvfs(); for those that don't this
+code is omitted. In fact it should never be obeyed, as we don't allow
+size_stripchart to get set in that case. For some OS the old function
+and struct name statfs is used; that is handled by a macro. */
+
+else if (size_stripchart != NULL && num == 1)
+  {
+#ifdef HAVE_STATFS
+  struct statvfs statbuf;
+  if (statvfs(CS size_stripchart, &statbuf) == 0)
+    {
+    int used = statbuf.f_blocks - statbuf.f_bfree;
+    int max = used + statbuf.f_bavail;
+    double fraction = ((double)used) / ((double)max);
+    newvalue = (int)((fraction + 0.005) * 100.0);
+    }
+#endif
+  if (size_first_time)
+    {
+    xs_SetValues(w, 1, "update", stripchart_update);
+    size_first_time = 0;
+    }
+  }
+
+/* For the configured stripcharts, the value to be set is
+the difference from last time; save the current total for
+next time. */
+
+else
+  {
+  newvalue = stripchart_total[num] - stripchart_last_total[num];
+  stripchart_last_total[num] = stripchart_total[num];
+  }
+
+/* Adjust the scale of the stripchart according to the value;
+we delay enlarging the scale for a while after the values
+reduce. Keep the maximum value while delaying, and reset
+down to that. For the size stripchart, the threshold is always
+forced to be at least 100. */
+
+while (thresholds[i] > 0)
+  {
+  int thresh = (size_stripchart != NULL && num == 1)? 100 : thresholds[i++];
+  if (newvalue < (double)thresh)
+    {
+    /* If the current maximum is less than required, or if it is
+    greater and we have delayed long enough, adjust the scale. */
+
+    if (stripchart_max[num] < thresh ||
+       (stripchart_max[num] > thresh && stripchart_delay[num]++ > 20))
+      {
+      uschar buffer[128];
+      newmax = (thresh > stripchart_midmax[num])?
+        thresh : stripchart_midmax[num];
+      if (newmax == 10) sprintf(CS buffer, "%s", stripchart_name[num]);
+        else sprintf(CS buffer, "%s x%d", stripchart_name[num], newmax/10);
+      if (size_stripchart != NULL && num == 1) Ustrcat(buffer, "%");
+      xs_SetValues(stripchart_label[num], 1, "label", buffer);
+      oldmax = stripchart_max[num];
+      stripchart_max[num] = newmax;
+      stripchart_midmax[num] = 0;
+      stripchart_delay[num] -= stripchart_middelay[num];
+      }
+
+    /* Otherwise, if the current maximum is greater than required,
+    keep the highest value encountered during the delay, and its
+    position so we can adjust the delay when re-scaling. */
+
+    else if (stripchart_max[num] > thresh)
+      {
+      if (thresh > stripchart_midmax[num])
+        {
+        stripchart_midmax[num] = thresh;
+        stripchart_middelay[num] = stripchart_delay[num];
+        }
+      }
+
+    /* If the maximum is exactly what we need, reset the delay. */
+
+    if (stripchart_max[num] == thresh) stripchart_delay[num] = 0;
+    break;
+    }
+  }
+
+/* The vanilla Athena stripchart widget does not support change of
+scale - it just draws scale lines closer and closer together, which
+doesn't work when the number gets very large. However, we can cause
+it to change scale quite simply by recomputing all the values and
+then calling its repaint routine. I had to nobble the repaint routine
+too, to stop it changing scale to anything other than 10. There's
+probably a better way to do this, like adding some new resource, but
+I'm not a widget programmer and want to get on with the rest of
+eximon... */
+
+if (oldmax > 0)
+  {
+  int i;
+  StripChartWidget ww = (StripChartWidget)w;
+  ww->strip_chart.max_value = 0;
+  for (i = 0; i < (int)ww->strip_chart.interval; i++)
+    {
+    ww->strip_chart.valuedata[i] =
+      (ww->strip_chart.valuedata[i] * oldmax)/newmax;
+    if (ww->strip_chart.valuedata[i] > ww->strip_chart.max_value)
+      ww->strip_chart.max_value = ww->strip_chart.valuedata[i];
+    }
+  XClearWindow( XtDisplay(w), XtWindow(w));
+  ww->strip_chart.interval = repaint_window(ww, 0, (int)w->core.width);
+  }
+
+/* Pass back the new value at the new scale */
+
+*ptr = ((double)newvalue * 10.0)/(double)(stripchart_max[num]);
+}
+
+
+
+/*************************************************
+*            Create one stripchart               *
+*************************************************/
+
+/* This function creates two widgets, one being the title and the other being
+the stripchart. The client_data values for each stripchart are index into the
+stripchart_values vector; each new stripchart just gets the next number. There
+is a fudge for the very first stripchart, which is the queue length display,
+and for the second if it is a partition size display; its update time is
+initially set to 1 second so that it gives an immediate display of the queue.
+The first time its callback function is obeyed, the update time gets reset. */
+
+void create_stripchart(Widget parent, uschar *title)
+{
+Widget chart;
+
+Widget label = XtCreateManagedWidget("label",
+  labelWidgetClass, parent, NULL, 0);
+
+xs_SetValues(label, 10,
+  "label",          title,
+  "width",          stripchart_width + 2,
+  "borderWidth",    0,
+  "internalHeight", 0,
+  "internalWidth",  0,
+  "left",           XawChainLeft,
+  "right",          XawChainLeft,
+  "top",            XawChainTop,
+  "bottom",         XawChainTop,
+  XtNfromHoriz,     stripchart_prev_label);
+
+chart = XtCreateManagedWidget("stripchart",
+  mystripChartWidgetClass, parent, NULL, 0);
+
+xs_SetValues(chart, 11,
+  "jumpScroll", 1,
+  "update",     (stripchart_count < stripchart_varstart)? 1:stripchart_update,
+  "minScale",   10,
+  "width",      stripchart_width,
+  "height",     stripchart_height,
+  "left",       XawChainLeft,
+  "right",      XawChainLeft,
+  "top",        XawChainTop,
+  "bottom",     XawChainTop,
+  XtNfromHoriz, stripchart_prev_chart,
+  XtNfromVert,  label);
+
+XtAddCallback(chart, "getValue", stripchartAction,
+  (XtPointer)stripchart_count);
+
+stripchart_last_total[stripchart_count] = 0;
+stripchart_max[stripchart_count] = 10;
+stripchart_midmax[stripchart_count] = 0;
+stripchart_name[stripchart_count] = title;
+stripchart_prev_label = stripchart_label[stripchart_count] = label;
+stripchart_prev_chart = chart;
+stripchart_total[stripchart_count] = 0;
+stripchart_count++;
+}
+
+/* End of em_strip.c */
diff --git a/src/exim_monitor/em_text.c b/src/exim_monitor/em_text.c
new file mode 100644 (file)
index 0000000..1b3503c
--- /dev/null
@@ -0,0 +1,73 @@
+/* $Cambridge: exim/src/exim_monitor/em_text.c,v 1.1 2004/10/07 10:39:01 ph10 Exp $ */
+
+/*************************************************
+*               Exim Monitor                     *
+*************************************************/
+
+/* Copyright (c) University of Cambridge 1995 - 2004 */
+/* See the file NOTICE for conditions of use and distribution. */
+
+
+#include "em_hdr.h"
+
+
+/* This module contains functions for displaying text in a
+text widget. It is not used for the log widget, because that
+is dynamically updated and has special scrolling requirements. */
+
+
+/* Count of characters displayed */
+
+static int text_count = 0;
+
+
+/*************************************************
+*               Empty the widget                 *
+*************************************************/
+
+void text_empty(Widget w)
+{
+XawTextBlock b;
+b.firstPos = 0;
+b.ptr = CS &b;
+b.format = FMT8BIT;
+b.length = 0;
+XawTextReplace(w, 0, text_count, &b);
+text_count = 0;
+XawTextSetInsertionPoint(w, text_count);
+}
+
+
+
+/*************************************************
+*                 Display text                   *
+*************************************************/
+
+void text_show(Widget w, uschar *s)
+{
+XawTextBlock b;
+b.firstPos = 0;
+b.ptr = CS s;
+b.format = FMT8BIT;
+b.length = Ustrlen(s);
+XawTextReplace(w, text_count, text_count, &b);
+text_count += b.length;
+XawTextSetInsertionPoint(w, text_count);
+}
+
+
+/*************************************************
+*           Display text from format             *
+*************************************************/
+
+void text_showf(Widget w, char *s, ...)
+{
+va_list ap;
+uschar buffer[1024];
+va_start(ap, s);
+vsprintf(CS buffer, s, ap);
+va_end(ap);
+text_show(w, buffer);
+}
+
+/* End of em_text.c */
diff --git a/src/exim_monitor/em_version.c b/src/exim_monitor/em_version.c
new file mode 100644 (file)
index 0000000..7d35cbf
--- /dev/null
@@ -0,0 +1,39 @@
+/* $Cambridge: exim/src/exim_monitor/em_version.c,v 1.1 2004/10/07 10:39:01 ph10 Exp $ */
+
+/*************************************************
+*                  Exim Monitor                  *
+*************************************************/
+
+/* Copyright (c) University of Cambridge 1995 - 2004 */
+/* See the file NOTICE for conditions of use and distribution. */
+
+#include "mytypes.h"
+#include "macros.h"
+#include <string.h>
+#include <stdlib.h>
+
+extern uschar *version_string;
+extern uschar *version_date;
+
+void
+version_init(void)
+{
+int i = 0;
+uschar today[20];
+
+version_string = US"2.05";
+
+Ustrcpy(today, __DATE__);
+if (today[4] == ' ') i = 1;
+today[3] = today[6] = '-';
+
+version_date = (uschar *)malloc(32);
+version_date[0] = 0;
+Ustrncat(version_date, today+4+i, 3-i);
+Ustrncat(version_date, today, 4);
+Ustrncat(version_date, today+7, 4);
+Ustrcat(version_date, " ");
+Ustrcat(version_date, __TIME__);
+}
+
+/* End of em_version.c */
diff --git a/src/exim_monitor/em_xs.c b/src/exim_monitor/em_xs.c
new file mode 100644 (file)
index 0000000..e3368f5
--- /dev/null
@@ -0,0 +1,46 @@
+/* $Cambridge: exim/src/exim_monitor/em_xs.c,v 1.1 2004/10/07 10:39:01 ph10 Exp $ */
+
+/*************************************************
+*               Exim Monitor                     *
+*************************************************/
+
+/* Copyright (c) University of Cambridge, 1995 - 2004 */
+/* See the file NOTICE for conditions of use and distribution. */
+
+/* This file contains a number of subroutines that are in effect
+just alternative packaging for calls to various X functions that
+happen to be convenient for this program. */
+
+#include "em_hdr.h"
+
+
+
+/*************************************************
+*                  xs_SetValues                  *
+*************************************************/
+
+/* Unpick a variable-length argument list and set up an
+appropriate call to XtSetValues. To make it reasonably
+efficient, we keep a working Arg structure of length 15;
+the largest call in eximon sets 11 values. The code uses
+malloc/free if more, just in case there is ever a longer
+one that gets overlooked. */
+
+static Arg xs_temparg[15];
+
+void xs_SetValues(Widget w, Cardinal num_args, ...)
+{
+int i;
+va_list ap;
+Arg *aa = (num_args > 15)? (Arg *)malloc(num_args*sizeof(Arg)) : xs_temparg;
+va_start(ap, num_args);
+for (i = 0; i < num_args; i++)
+  {
+  aa[i].name = va_arg(ap, String);
+  aa[i].value = va_arg(ap, XtArgVal);
+  }
+XtSetValues(w, aa, num_args);
+if (num_args > 15) free(aa);
+}
+
+/* End of em_xs.c */
diff --git a/src/src/EDITME b/src/src/EDITME
new file mode 100644 (file)
index 0000000..a7af6e8
--- /dev/null
@@ -0,0 +1,1020 @@
+# $Cambridge: exim/src/src/EDITME,v 1.1 2004/10/07 10:39:01 ph10 Exp $ 
+
+##################################################
+#          The Exim mail transport agent         #
+##################################################
+
+# This is the template for Exim's main build-time configuration file. It
+# contains settings that are independent of any operating system. These are
+# things that are mostly sysadmin choices. The items below are divided into
+# those you must specify, those you probably want to specify, those you might
+# often want to specify, and those that you almost never need to mention.
+
+# Edit this file and save the result to a file called Local/Makefile within the
+# Exim distribution directory before running the "make" command.
+
+# Things that depend on the operating system have default settings in
+# OS/Makefile-Default, but these are overridden for some OS by files called
+# called OS/Makefile-<osname>. You can further override these by creating files
+# called Local/Makefile-<osname>, where "<osname>" stands for the name of your
+# operating system - look at the names in the OS directory to see which names
+# are recognized.
+
+# However, if you are building Exim for a single OS only, you don't need to
+# worry about setting up Local/Makefile-<osname>. Any build-time configuration
+# settings you require can in fact be placed in the one file called
+# Local/Makefile. It is only if you are building for several OS from the same
+# source files that you need to worry about splitting off your own OS-dependent
+# settings into separate files. (There's more explanation about how this all
+# works in the toplevel README file, under "Modifying the building process", as
+# well as in the Exim specification.)
+
+# One OS-specific thing that may need to be changed is the command for running
+# the C compiler; the overall default is gcc, but some OS Makefiles specify cc.
+# You can override anything that is set by putting CC=whatever in your
+# Local/Makefile.
+
+# NOTE: You should never need to edit any of the distributed Makefiles; all
+# overriding can be done in your Local/Makefile(s). This will make it easier
+# for you when the next release comes along.
+
+# The location of the X11 libraries is something else that is quite variable
+# even between different versions of the same operating system (and indeed
+# there are different versions of X11 as well, of course). The four settings
+# concerned here are X11, XINCLUDE, XLFLAGS (linking flags) and X11_LD_LIB
+# (dynamic run-time library). You need not worry about X11 unless you want to
+# compile the Exim monitor utility. Exim itself does not use X11.
+
+# Another area of variability between systems is the type and location of the
+# DBM library package. Exim has support for ndbm, gdbm, tdb, and Berkeley DB.
+# By default the code assumes ndbm; this often works with gdbm or DB, provided
+# they are correctly installed, via their compatibility interfaces. However,
+# Exim can also be configured to use the native calls for Berkeley DB (obsolete
+# versions 1.85, 2.x, 3.x, or the current 4.x version) and also for gdbm.
+
+# For some operating systems, a default DBM library (other than ndbm) is
+# selected by a setting in the OS-specific Makefile. Most modern OS now have
+# a DBM library installed as standard, and in many cases this will be selected
+# for you by the OS-specific configuration. If Exim compiles without any
+# problems, you probably do not have to worry about the DBM library. If you
+# do want or need to change it, you should first read the discussion in the
+# file doc/dbm.discuss.txt, which also contains instructions for testing Exim's
+# interface to the DBM library.
+
+# In Local/Makefiles blank lines and lines starting with # are ignored. It is
+# also permitted to use the # character to add a comment to a setting, for
+# example
+#
+# EXIM_GID=42   # the "mail" group
+#
+# However, with some versions of "make" this works only if there is no white
+# space between the end of the setting and the #, so perhaps it is best
+# avoided. A consequence of this facility is that it is not possible to have
+# the # character present in any setting, but I can't think of any cases where
+# this would be wanted.
+###############################################################################
+
+
+
+###############################################################################
+#                    THESE ARE THINGS YOU MUST SPECIFY                        #
+###############################################################################
+
+# Exim will not build unless you specify BIN_DIRECTORY, CONFIGURE_FILE, and
+# EXIM_USER. You also need EXIM_GROUP if EXIM_USER specifies a uid by number.
+
+# If you don't specify SPOOL_DIRECTORY, Exim won't fail to build. However, it
+# really is a very good idea to specify it here rather than at run time. This
+# is particularly true if you let the logs go to their default location in the
+# spool directory, because it means that the location of the logs is known
+# before Exim has read the run time configuration file.
+
+#------------------------------------------------------------------------------
+# BIN_DIRECTORY defines where the exim binary will be installed by "make
+# install". The path is also used internally by Exim when it needs to re-invoke
+# itself, either to send an error message, or to recover root privilege. Exim's
+# utility binaries and scripts are also installed in this directory. There is
+# no "standard" place for the binary directory. Some people like to keep all
+# the Exim files under one directory such as /usr/exim; others just let the
+# Exim binaries go into an existing directory such as /usr/sbin or
+# /usr/local/sbin. The installation script will try to create this directory,
+# and any superior directories, if they do not exist.
+
+BIN_DIRECTORY=/usr/exim/bin
+
+
+#------------------------------------------------------------------------------
+# CONFIGURE_FILE defines where Exim's run time configuration file is to be
+# found. It is the complete pathname for the file, not just a directory. The
+# location of all other run time files and directories can be changed in the
+# run time configuration file. There is a lot of variety in the choice of
+# location in different OS, and in the preferences of different sysadmins. Some
+# common locations are in /etc or /etc/mail or /usr/local/etc or
+# /usr/local/etc/mail. Another possibility is to keep all the Exim files under
+# a single directory such as /usr/exim. Whatever you choose, the installation
+# script will try to make the directory and any superior directories if they
+# don't exist. It will also install a default runtime configuration if this
+# file does not exist.
+
+CONFIGURE_FILE=/usr/exim/configure
+
+# It is possible to specify a colon-separated list of files for CONFIGURE_FILE.
+# In this case, Exim will use the first of them that exists when it is run.
+# However, if a list is specified, the installation script no longer tries to
+# make superior directories or to install a default runtime configuration.
+
+
+#------------------------------------------------------------------------------
+# The Exim binary must normally be setuid root, so that it starts executing as
+# root, but (depending on the options with which it is called) it does not
+# always need to retain the root privilege. These settings define the user and
+# group that is used for Exim processes when they no longer need to be root. In
+# particular, this applies when receiving messages and when doing remote
+# deliveries. (Local deliveries run as various non-root users, typically as the
+# owner of a local mailbox.) Specifying these values as root is very strongly
+# discouraged.
+
+EXIM_USER=
+
+# If you specify EXIM_USER as a name, this is looked up at build time, and the
+# uid number is built into the binary. However, you can specify that this
+# lookup is deferred until runtime. In this case, it is the name that is built
+# into the binary. You can do this by a setting of the form:
+
+# EXIM_USER=ref:exim
+
+# In other words, put "ref:" in front of the user name. If you set EXIM_USER
+# like this, any value specified for EXIM_GROUP is also passed "by reference".
+# Although this costs a bit of resource at runtime, it is convenient to use
+# this feature when building binaries that are to be run on multiple systems
+# where the name may refer to different uids. It also allows you to build Exim
+# on a system where there is no Exim user defined.
+
+# If the setting of EXIM_USER is numeric (e.g. EXIM_USER=42), there must
+# also be a setting of EXIM_GROUP. If, on the other hand, you use a name
+# for EXIM_USER (e.g. EXIM_USER=exim), you don't need to set EXIM_GROUP unless
+# you want to use a group other than the default group for the given user.
+
+# EXIM_GROUP=
+
+# Many sites define a user called "exim", with an appropriate default group,
+# and use
+#
+# EXIM_USER=exim
+#
+# while leaving EXIM_GROUP unspecified (commented out).
+
+
+#------------------------------------------------------------------------------
+# SPOOL_DIRECTORY defines the directory where all the data for messages in
+# transit is kept. It is strongly recommended that you define it here, though
+# it is possible to leave this till the run time configuration.
+
+# Exim creates the spool directory if it does not exist. The owner and group
+# will be those defined by EXIM_USER and EXIM_GROUP, and this also applies to
+# all the files and directories that are created in the spool directory.
+
+# Almost all installations choose this:
+
+SPOOL_DIRECTORY=/var/spool/exim
+
+
+
+###############################################################################
+#           THESE ARE THINGS YOU PROBABLY WANT TO SPECIFY                     #
+###############################################################################
+
+# You need to specify some routers and transports if you want the Exim that you
+# are building to be capable of delivering mail. You almost certainly need at
+# least one type of lookup. You should consider whether you want to build
+# the Exim monitor or not.
+
+
+#------------------------------------------------------------------------------
+# These settings determine which individual router drivers are included in the
+# Exim binary. There are no defaults in the code; those routers that are wanted
+# must be defined here by setting the appropriate variables to the value "yes".
+# Including a router in the binary does not cause it to be used automatically.
+# It has also to be configured in the run time configuration file. By
+# commenting out those you know you don't want to use, you can make the binary
+# a bit smaller. If you are unsure, leave all of these included for now.
+
+ROUTER_ACCEPT=yes
+ROUTER_DNSLOOKUP=yes
+ROUTER_IPLITERAL=yes
+ROUTER_MANUALROUTE=yes
+ROUTER_QUERYPROGRAM=yes
+ROUTER_REDIRECT=yes
+
+# This one is very special-purpose, so is not included by default.
+
+# ROUTER_IPLOOKUP=yes
+
+
+#------------------------------------------------------------------------------
+# These settings determine which individual transport drivers are included in
+# the Exim binary. There are no defaults; those transports that are wanted must
+# be defined here by setting the appropriate variables to the value "yes".
+# Including a transport in the binary does not cause it to be used
+# automatically. It has also to be configured in the run time configuration
+# file. By commenting out those you know you don't want to use, you can make
+# the binary a bit smaller. If you are unsure, leave all of these included for
+# now.
+
+TRANSPORT_APPENDFILE=yes
+TRANSPORT_AUTOREPLY=yes
+TRANSPORT_PIPE=yes
+TRANSPORT_SMTP=yes
+
+# This one is special-purpose, and commonly not required, so it is not
+# included by default.
+
+# TRANSPORT_LMTP=yes
+
+
+#------------------------------------------------------------------------------
+# The appendfile transport can write messages to local mailboxes in a number
+# of formats. The code for three specialist formats, maildir, mailstore, and
+# MBX, is included only when requested. If you do not know what this is about,
+# leave these settings commented out.
+
+# SUPPORT_MAILDIR=yes
+# SUPPORT_MAILSTORE=yes
+# SUPPORT_MBX=yes
+
+
+#------------------------------------------------------------------------------
+# These settings determine which file and database lookup methods are included
+# in the binary. See the manual chapter entitled "File and database lookups"
+# for discussion. DBM and lsearch (linear search) are included by default. If
+# you are unsure about the others, leave them commented out for now.
+# LOOKUP_DNSDB does *not* refer to general mail routing using the DNS. It is
+# for the specialist case of using the DNS as a general database facility (not
+# common).
+
+LOOKUP_DBM=yes
+LOOKUP_LSEARCH=yes
+
+# LOOKUP_CDB=yes
+# LOOKUP_DNSDB=yes
+# LOOKUP_DSEARCH=yes
+# LOOKUP_IBASE=yes
+# LOOKUP_LDAP=yes
+# LOOKUP_MYSQL=yes
+# LOOKUP_NIS=yes
+# LOOKUP_NISPLUS=yes
+# LOOKUP_ORACLE=yes
+# LOOKUP_PASSWD=yes
+# LOOKUP_PGSQL=yes
+# LOOKUP_WHOSON=yes
+
+# These two settings are obsolete; all three lookups are compiled when
+# LOOKUP_LSEARCH is enabled. However, we retain these for backward
+# compatibility. Setting one forces LOOKUP_LSEARCH if it is not set.
+
+# LOOKUP_WILDLSEARCH=yes
+# LOOKUP_NWILDLSEARCH=yes
+
+
+#------------------------------------------------------------------------------
+# If you have set LOOKUP_LDAP=yes, you should set LDAP_LIB_TYPE to indicate
+# which LDAP library you have. Unfortunately, though most of their functions
+# are the same, there are minor differences. Currently Exim knows about four
+# LDAP libraries: the one from the University of Michigan (also known as
+# OpenLDAP 1), OpenLDAP 2, the Netscape SDK library, and the library that comes
+# with Solaris 7 onwards. Uncomment whichever of these you are using.
+
+# LDAP_LIB_TYPE=OPENLDAP1
+# LDAP_LIB_TYPE=OPENLDAP2
+# LDAP_LIB_TYPE=NETSCAPE
+# LDAP_LIB_TYPE=SOLARIS
+
+# If you don't set any of these, Exim assumes the original University of
+# Michigan (OpenLDAP 1) library.
+
+
+#------------------------------------------------------------------------------
+# Additional libraries and include directories may be required for some
+# lookup styles (e.g. LDAP, MYSQL or PGSQL). LOOKUP_LIBS is included only on
+# the command for linking Exim itself, not on any auxiliary programs. You
+# don't need to set LOOKUP_INCLUDE if the relevant directories are already
+# specified in INCLUDE. The settings below are just examples; -lpq is for
+# PostgreSQL, -lgds is for Interbase.
+
+# LOOKUP_INCLUDE=-I /usr/local/ldap/include -I /usr/local/mysql/include -I /usr/local/pgsql/include
+# LOOKUP_LIBS=-L/usr/local/lib -lldap -llber -lmysqlclient -lpq -lgds
+
+#------------------------------------------------------------------------------
+# Compiling the Exim monitor: If you want to compile the Exim monitor, a
+# program that requires an X11 display, then EXIM_MONITOR should be set to the
+# value "eximon.bin". Comment out this setting to disable compilation of the
+# monitor. The locations of various X11 directories for libraries and include
+# files are defaulted in the OS/Makefile-Default file, but can be overridden in
+# local OS-specific make files.
+
+EXIM_MONITOR=eximon.bin
+
+
+
+###############################################################################
+#                 THESE ARE THINGS YOU MIGHT WANT TO SPECIFY                  #
+###############################################################################
+
+# The items in this section are those that are commonly changed according to
+# the sysadmin's preferences, but whose defaults are often acceptable. The
+# first five are concerned with security issues, where differing levels of
+# paranoia are appropriate in different environments. Sysadmins also vary in
+# their views on appropriate levels of defence in these areas. If you do not
+# understand these issues, go with the defaults, which are used by many sites.
+
+
+#------------------------------------------------------------------------------
+# Although Exim is normally a setuid program, owned by root, it refuses to run
+# local deliveries as root by default. There is a runtime option called
+# "never_users" which lists the users that must never be used for local
+# deliveries. There is also the setting below, which provides a list that
+# cannot be overridden at runtime. This guards against problems caused by
+# unauthorized changes to the runtime configuration. You are advised not to
+# remove "root" from this option, but you can add other users if you want. The
+# list is colon-separated.
+
+FIXED_NEVER_USERS=root
+
+
+#------------------------------------------------------------------------------
+# By default, Exim insists that its configuration file be owned either by root
+# or by the Exim user. You can specify one additional permitted owner here.
+
+# CONFIGURE_OWNER=
+
+# If you specify CONFIGURE_OWNER as a name, this is looked up at build time,
+# and the uid number is built into the binary. However, you can specify that
+# this lookup is deferred until runtime. In this case, it is the name that is
+# built into the binary. You can do this by a setting of the form:
+
+# CONFIGURE_OWNER=ref:mail
+
+# In other words, put "ref:" in front of the user name. Although this costs a
+# bit of resource at runtime, it is convenient to use this feature when
+# building binaries that are to be run on multiple systems where the name may
+# refer to different uids. It also allows you to build Exim on a system where
+# the relevant user is not defined.
+
+
+#------------------------------------------------------------------------------
+# The -C option allows Exim to be run with an alternate runtime configuration
+# file. When this is used by root or the Exim user, root privilege is retained
+# by the binary (for any other caller, it is dropped). You can restrict the
+# location of alternate configurations by defining a prefix below. Any file
+# used with -C must then start with this prefix (except that /dev/null is also
+# permitted if the caller is root, because that is used in the install script).
+# If the prefix specifies a directory that is owned by root, a compromise of
+# the Exim account does not permit arbitrary alternate configurations to be
+# used. The prefix can be more restrictive than just a directory (the second
+# example).
+
+# ALT_CONFIG_PREFIX=/some/directory/
+# ALT_CONFIG_PREFIX=/some/directory/exim.conf-
+
+
+#------------------------------------------------------------------------------
+# If you uncomment the following line, only root may use the -C or -D options
+# without losing root privilege. The -C option specifies an alternate runtime
+# configuration file, and the -D option changes macro values in the runtime
+# configuration. Uncommenting this line restricts what can be done with these
+# options. A call to receive a message (either one-off or via a daemon) cannot
+# successfully continue to deliver it, because the re-exec of Exim to regain
+# root privilege will fail, owing to the use of -C or -D by the Exim user.
+# However, you can still use -C for testing (as root) if you do separate Exim
+# calls for receiving a message and subsequently delivering it.
+
+# ALT_CONFIG_ROOT_ONLY=yes
+
+
+#------------------------------------------------------------------------------
+# Uncommenting this option disables the use of the -D command line option,
+# which changes the values of macros in the runtime configuration file.
+# This is another protection against somebody breaking into the Exim account.
+
+# DISABLE_D_OPTION=yes
+
+
+#------------------------------------------------------------------------------
+# Exim has support for the AUTH (authentication) extension of the SMTP
+# protocol, as defined by RFC 2554. If you don't know what SMTP authentication
+# is, you probably won't want to include this code, so you should leave these
+# settings commented out. If you do want to make use of SMTP authentication,
+# you must uncomment at least one of the following, so that appropriate code is
+# included in the Exim binary. You will then need to set up the run time
+# configuration to make use of the mechanism(s) selected.
+
+# AUTH_CRAM_MD5=yes
+# AUTH_CYRUS_SASL=yes
+# AUTH_PLAINTEXT=yes
+# AUTH_SPA=yes
+
+
+#------------------------------------------------------------------------------
+# If you specified AUTH_CYRUS_SASL above, you should ensure that you have the
+# Cyrus SASL library installed before trying to build Exim, and you probably
+# want to uncomment the following line:
+
+# AUTH_LIBS=-lsasl2
+
+
+#------------------------------------------------------------------------------
+# When Exim is decoding MIME "words" in header lines, most commonly for use
+# in the $header_xxx expansion, it converts any foreign character sets to the
+# one that is set in the headers_charset option. The default setting is
+# defined by this setting:
+
+HEADERS_CHARSET="ISO-8859-1"
+
+# If you are going to make use of $header_xxx expansions in your configuration
+# file, or if your users are going to use them in filter files, and the normal
+# character set on your host is something other than ISO-8859-1, you might
+# like to specify a different default here. This value can be overridden in
+# the runtime configuration, and it can also be overridden in individual filter
+# files.
+#
+# IMPORTANT NOTE: The iconv() function is needed for character code
+# conversions. Please see the next item...
+
+
+#------------------------------------------------------------------------------
+# Character code conversions are possible only if the iconv() function is
+# installed on your operating system. There are two places in Exim where this
+# is relevant: (a) The $header_xxx expansion (see the previous item), and (b)
+# the Sieve filter support. For those OS where iconv() is known to be installed
+# as standard, the file in OS/Makefile-xxxx contains
+#
+# HAVE_ICONV=yes
+#
+# If you are not using one of those systems, but have installed iconv(), you
+# need to uncomment that line above. In some cases, you may find that iconv()
+# and its header file are not in the default places. You might need to use
+# something like this:
+#
+# HAVE_ICONV=yes
+# CFLAGS=-O -I/usr/local/include
+# EXTRALIBS_EXIM=-L/usr/local/lib -liconv
+#
+# but of course there may need to be other things in CFLAGS and EXTRALIBS_EXIM
+# as well.
+
+
+#------------------------------------------------------------------------------
+# The passwords for user accounts are normally encrypted with the crypt()
+# function. Comparisons with encrypted passwords can be done using Exim's
+# "crypteq" expansion operator. (This is commonly used as part of the
+# configuration of an authenticator for use with SMTP AUTH.) At least one
+# operating system has an extended function called crypt16(), which uses up to
+# 16 characters of a password (the normal crypt() uses only the first 8). Exim
+# supports the use of crypt16() as well as crypt().
+
+# You can always indicate a crypt16-encrypted password by preceding it with
+# "{crypt16}". If you want the default handling (without any preceding
+# indicator) to use crypt16(), uncomment the following line:
+
+# DEFAULT_CRYPT=crypt16
+
+# If you do that, you can still access the basic crypt() function by preceding
+# an encrypted password with "{crypt}". For more details, see the description
+# of the "crypteq" condition in the manual chapter on string expansions.
+
+# Since most operating systems do not include a crypt16() function (yet?), Exim
+# has one of its own, which it uses unless HAVE_CRYPT16 is defined. Normally,
+# that will be set in an OS-specific Makefile for the OS that have such a
+# function, so you should not need to bother with it.
+
+
+#------------------------------------------------------------------------------
+# Exim can be built to support the SMTP STARTTLS command, which implements
+# Transport Layer Security using SSL (Secure Sockets Layer). To do this, you
+# must install the OpenSSL library package or the GnuTLS library. Exim contains
+# no cryptographic code of its own. Uncomment the following lines if you want
+# to build Exim with TLS support. If you don't know what this is all about,
+# leave these settings commented out.
+
+# This setting is required for any TLS support (either OpenSSL or GnuTLS)
+# SUPPORT_TLS=yes
+
+# Uncomment this setting if you are using OpenSSL
+# TLS_LIBS=-lssl -lcrypto
+
+# Uncomment these settings if you are using GnuTLS
+# USE_GNUTLS=yes
+# TLS_LIBS=-lgnutls -ltasn1 -lgcrypt
+
+# If you are running Exim as a server, note that just building it with TLS
+# support is not all you need to do. You also need to set up a suitable
+# certificate, and tell Exim about it by means of the tls_certificate
+# and tls_privatekey run time options. You also need to set tls_advertise_hosts
+# to specify the hosts to which Exim advertises TLS support. On the other hand,
+# if you are running Exim only as a client, building it with TLS support
+# is all you need to do.
+
+# Additional libraries and include files are required for both OpenSSL and
+# GnuTLS. The TLS_LIBS settings above assume that the libraries are installed
+# with all your other libraries. If they are in a special directory, you may
+# need something like
+
+# TLS_LIBS=-L/usr/local/openssl/lib -lssl -lcrypto
+# or
+# TLS_LIBS=-L/opt/gnu/lib -lgnutls -ltasn1 -lgcrypt
+
+# TLS_LIBS is included only on the command for linking Exim itself, not on any
+# auxiliary programs. If the include files are not in a standard place, you can
+# set TLS_INCLUDE to specify where they are, for example:
+
+# TLS_INCLUDE=-I/usr/local/openssl/include/
+# or
+# TLS_INCLUDE=-I/opt/gnu/include
+
+# You don't need to set TLS_INCLUDE if the relevant directories are already
+# specified in INCLUDE.
+
+
+#------------------------------------------------------------------------------
+# The default distribution of Exim contains only the plain text form of the
+# documentation. Other forms are available separately. If you want to install
+# the documentation in "info" format, first fetch the Texinfo documentation
+# sources from the ftp directory and unpack them, which should create files
+# with the extension "texinfo" in the doc directory. You may find that the
+# version number of the texinfo files is different to your Exim version number,
+# because the main documentation isn't updated as often as the code. For
+# example, if you have Exim version 4.43, the source tarball upacks into a
+# directory called exim-4.43, but the texinfo tarball unpacks into exim-4.40.
+# In this case, move the contents of exim-4.40/doc into exim-4.43/doc after you
+# have unpacked them. Then set INFO_DIRECTORY to the location of your info
+# directory. This varies from system to system, but is often /usr/share/info.
+# Once you have done this, "make install" will build the info files and
+# install them in the directory you have defined.
+
+# INFO_DIRECTORY=/usr/share/info
+
+
+#------------------------------------------------------------------------------
+# Exim log directory and files: Exim creates several log files inside a
+# single log directory. You can define the directory and the form of the
+# log file name here. If you do not set anything, Exim creates a directory
+# called "log" inside its spool directory (see SPOOL_DIRECTORY above) and uses
+# the filenames "mainlog", "paniclog", and "rejectlog". If you want to change
+# this, you can set LOG_FILE_PATH to a path name containing one occurrence of
+# %s. This will be replaced by one of the strings "main", "panic", or "reject"
+# to form the final file names. Some installations may want something like this:
+
+# LOG_FILE_PATH=/var/log/exim_%slog
+
+# which results in files with names /var/log/exim_mainlog, etc. The directory
+# in which the log files are placed must exist; Exim does not try to create
+# it for itself. It is also your responsibility to ensure that Exim is capable
+# of writing files using this path name. The Exim user (see EXIM_USER above)
+# must be able to create and update files in the directory you have specified.
+
+# You can also configure Exim to use syslog, instead of or as well as log
+# files, by settings such as these
+
+# LOG_FILE_PATH=syslog
+# LOG_FILE_PATH=syslog:/var/log/exim_%slog
+
+# The first of these uses only syslog; the second uses syslog and also writes
+# to log files. Do not include white space in such a setting as it messes up
+# the building process.
+
+
+#------------------------------------------------------------------------------
+# When logging to syslog, the following option caters for syslog replacements
+# that are able to accept log entries longer than the 1024 characters allowed
+# by RFC 3164. It is up to you to make sure your syslog daemon can handle this.
+# Non-printable characters are usually unacceptable regardless, so log entries
+# are still split on newline characters.
+
+# SYSLOG_LONG_LINES=yes
+
+# If you are not interested in the process identifier (pid) of the Exim that is
+# making the call to syslog, then comment out the following line.
+
+SYSLOG_LOG_PID=yes
+
+
+#------------------------------------------------------------------------------
+# Cycling log files: this variable specifies the maximum number of old
+# log files that are kept by the exicyclog log-cycling script. You don't have
+# to use exicyclog. If your operating system has other ways of cycling log
+# files, you can use them instead. The exicyclog script isn't run by default;
+# you have to set up a cron job for it if you want it.
+
+EXICYCLOG_MAX=10
+
+
+#------------------------------------------------------------------------------
+# The compress command is used by the exicyclog script to compress old log
+# files. Both the name of the command and the suffix that it adds to files
+# need to be defined here. See also the EXICYCLOG_MAX configuration.
+
+COMPRESS_COMMAND=/usr/bin/gzip
+COMPRESS_SUFFIX=gz
+
+
+#------------------------------------------------------------------------------
+# If the exigrep utility is fed compressed log files, it tries to uncompress
+# them using this command.
+
+ZCAT_COMMAND=/usr/bin/zcat
+
+
+#------------------------------------------------------------------------------
+# Compiling in support for embedded Perl: If you want to be able to
+# use Perl code in Exim's string manipulation language and you have Perl
+# (version 5.004 or later) installed, set EXIM_PERL to perl.o. Using embedded
+# Perl costs quite a lot of resources. Only do this if you really need it.
+
+# EXIM_PERL=perl.o
+
+
+#------------------------------------------------------------------------------
+# Exim has support for PAM (Pluggable Authentication Modules), a facility
+# which is available in the latest releases of Solaris and in some GNU/Linux
+# distributions (see http://ftp.kernel.org/pub/linux/libs/pam/). The Exim
+# support, which is intended for use in conjunction with the SMTP AUTH
+# facilities, is included only when requested by the following setting:
+
+# SUPPORT_PAM=yes
+
+# You probably need to add -lpam to EXTRALIBS, and in some releases of
+# GNU/Linux -ldl is also needed.
+
+
+#------------------------------------------------------------------------------
+# Support for authentication via Radius is also available. The Exim support,
+# which is intended for use in conjunction with the SMTP AUTH facilities,
+# is included only when requested by setting the following parameter to the
+# location of your Radius configuration file:
+
+# RADIUS_CONFIG_FILE=/etc/radiusclient/radiusclient.conf
+# RADIUS_CONFIG_FILE=/etc/radius.conf
+
+# If you have set RADIUS_CONFIG_FILE, you should also set one of these to
+# indicate which RADIUS library is used:
+#
+# RADIUSCLIENT is the radiusclient library; you probably need to add
+#   -libradiusclient to EXTRALIBS
+#
+# RADLIB is the Radius library that comes with FreeBSD (the header file is
+#   called radlib.h); you probably need to add -lradius to EXTRALIBS
+
+# RADIUS_LIB_TYPE=RADIUSCLIENT
+# RADIUS_LIB_TYPE=RADLIB
+
+# If you don't set one of these, Exim assumes the radiusclient library.
+
+
+#------------------------------------------------------------------------------
+# Support for authentication via the Cyrus SASL pwcheck daemon is available.
+# Note, however, that pwcheck is now deprecated in favour of saslauthd (see
+# next item). The Exim support for pwcheck, which is intented for use in
+# conjunction with the SMTP AUTH facilities, is included only when requested by
+# setting the following parameter to the location of the pwcheck daemon's
+# socket.
+#
+# There is no need to install all of SASL on your system. You just need to run
+# ./configure --with-pwcheck, cd to the pwcheck directory within the sources,
+# make and make install. You must create the socket directory (default
+# /var/pwcheck) and chown it to exim's user and group. Once you have installed
+# pwcheck, you should arrange for it to be started by root at boot time.
+
+# CYRUS_PWCHECK_SOCKET=/var/pwcheck/pwcheck
+
+
+#------------------------------------------------------------------------------
+# Support for authentication via the Cyrus SASL saslauthd daemon is available.
+# The Exim support, which is intented for use in conjunction with the SMTP AUTH
+# facilities, is included only when requested by setting the following
+# parameter to the location of the saslauthd daemon's socket.
+#
+# There is no need to install all of SASL on your system. You just need to run
+# ./configure --with-saslauthd (and any other options you need, for example, to
+# select or deselect authentication mechanisms), cd to the saslauthd directory
+# within the sources, make and make install. You must create the socket
+# directory (default /var/state/saslauthd) and chown it to exim's user and
+# group. Once you have installed saslauthd, you should arrange for it to be
+# started by root at boot time.
+
+# CYRUS_SASLAUTHD_SOCKET=/var/state/saslauthd/mux
+
+
+#------------------------------------------------------------------------------
+# TCP wrappers: If you want to use tcpwrappers from within Exim, uncomment
+# this setting. See the manual section entitled "Use of tcpwrappers" in the
+# chapter on building and installing Exim.
+#
+# USE_TCP_WRAPPERS=yes
+#
+# You may well also have to specify a local "include" file and an additional
+# library for TCP wrappers, so you probably need something like this:
+#
+# USE_TCP_WRAPPERS=yes
+# CFLAGS=-O -I/usr/local/include
+# EXTRALIBS_EXIM=-L/usr/local/lib -lwrap
+#
+# but of course there may need to be other things in CFLAGS and EXTRALIBS_EXIM
+# as well.
+
+
+#------------------------------------------------------------------------------
+# The default action of the exim_install script (which is run by "make
+# install") is to install the Exim binary with a unique name such as
+# exim-4.43-1, and then set up a symbolic link called "exim" to reference it,
+# moving the symbolic link from any previous version. If you define NO_SYMLINK
+# (the value doesn't matter), the symbolic link is not created or moved. You
+# will then have to "turn Exim on" by setting up the link manually.
+
+# NO_SYMLINK=yes
+
+
+#------------------------------------------------------------------------------
+# Another default action of the install script is to install a default runtime
+# configuration file if one does not exist. This configuration has a router for
+# expanding system aliases. The default assumes that these aliases are kept
+# in the traditional file called /etc/aliases. If such a file does not exist,
+# the installation script creates one that contains just comments (no actual
+# aliases). The following setting can be changed to specify a different
+# location for the system alias file.
+
+SYSTEM_ALIASES_FILE=/etc/aliases
+
+
+#------------------------------------------------------------------------------
+# There are some testing options (-be, -bt, -bv) that read data from the
+# standard input when no arguments are supplied. By default, the input lines
+# are read using the standard fgets() function. This does not support line
+# editing during interactive input (though the terminal's "erase" character
+# works as normal). If your operating system has the readline() function, and
+# in addition supports dynamic loading of library functions, you can cause
+# Exim to use readline() for the -be testing option (only) by uncommenting the
+# following setting. Dynamic loading is used so that the library is loaded only
+# when the -be testing option is given; by the time the loading occurs,
+# Exim has given up its root privilege and is running as the calling user. This
+# is the reason why readline() is NOT supported for -bt and -bv, because Exim
+# runs as root or as exim, respectively, for those options. When USE_READLINE
+# is "yes", as well as supporting line editing, a history of input lines in the
+# current run is maintained.
+
+# USE_READLINE=yes
+
+
+
+###############################################################################
+#              THINGS YOU ALMOST NEVER NEED TO MENTION                        #
+###############################################################################
+
+# The settings in this section are available for use in special circumstances.
+# In the vast majority of installations you need not change anything below.
+
+
+#------------------------------------------------------------------------------
+# The following commands live in different places in some OS. Either the
+# ultimate default settings, or the OS-specific files should already point to
+# the right place, but they can be overridden here if necessary. These settings
+# are used when building various scripts to ensure that the correct paths are
+# used when the scripts are run. They are not used in the Makefile itself. Perl
+# is not necessary for running Exim unless you set EXIM_PERL (see above) to get
+# it embedded, but there are some utilities that are Perl scripts. If you
+# haven't got Perl, Exim will still build and run; you just won't be able to
+# use those utilities.
+
+# CHOWN_COMMAND=/usr/bin/chown
+# CHGRP_COMMAND=/usr/bin/chgrp
+# MV_COMMAND=/bin/mv
+# RM_COMMAND=/bin/rm
+# PERL_COMMAND=/usr/bin/perl
+
+
+#------------------------------------------------------------------------------
+# The following macro can be used to change the command for building a library
+# of functions. By default the "ar" command is used, with options "cq".
+# Only in rare circumstances should you need to change this.
+
+# AR=ar cq
+
+
+#------------------------------------------------------------------------------
+# In some operating systems, the value of the TMPDIR environment variable
+# controls where temporary files are created. Exim does not make use of
+# temporary files, except when delivering to MBX mailboxes. However, if Exim
+# calls any external libraries (e.g. DBM libraries), they may use temporary
+# files, and thus be influenced by the value of TMPDIR. For this reason, when
+# Exim starts, it checks the environment for TMPDIR, and if it finds it is set,
+# it replaces the value with what is defined here. Commenting this setting
+# suppresses the check altogether.
+
+TMPDIR="/tmp"
+
+
+#------------------------------------------------------------------------------
+# The following macros can be used to change the default modes that are used
+# by the appendfile transport. In most installations the defaults are just
+# fine, and in any case, you can change particular instances of the transport
+# at run time if you want.
+
+# APPENDFILE_MODE=0600
+# APPENDFILE_DIRECTORY_MODE=0700
+# APPENDFILE_LOCKFILE_MODE=0600
+
+
+#------------------------------------------------------------------------------
+# In some installations there may be multiple machines sharing file systems,
+# where a different configuration file is required for Exim on the different
+# machines. If CONFIGURE_FILE_USE_NODE is defined, then Exim will first look
+# for a configuration file whose name is that defined by CONFIGURE_FILE,
+# with the node name obtained by uname() tacked on the end, separated by a
+# period (for example, /usr/exim/configure.host.in.some.domain). If this file
+# does not exist, then the bare configuration file name is tried.
+
+# CONFIGURE_FILE_USE_NODE=yes
+
+
+#------------------------------------------------------------------------------
+# In some esoteric configurations two different versions of Exim are run,
+# with different setuid values, and different configuration files are required
+# to handle the different cases. If CONFIGURE_FILE_USE_EUID is defined, then
+# Exim will first look for a configuration file whose name is that defined
+# by CONFIGURE_FILE, with the effective uid tacked on the end, separated by
+# a period (for eximple, /usr/exim/configure.0). If this file does not exist,
+# then the bare configuration file name is tried. In the case when both
+# CONFIGURE_FILE_USE_EUID and CONFIGURE_FILE_USE_NODE are set, four files
+# are tried: <name>.<euid>.<node>, <name>.<node>, <name>.<euid>, and <name>.
+
+# CONFIGURE_FILE_USE_EUID=yes
+
+
+#------------------------------------------------------------------------------
+# The size of the delivery buffers: These specify the sizes (in bytes) of
+# the buffers that are used when copying a message from the spool to a
+# destination. There is rarely any need to change these values.
+
+# DELIVER_IN_BUFFER_SIZE=8192
+# DELIVER_OUT_BUFFER_SIZE=8192
+
+
+#------------------------------------------------------------------------------
+# The mode of the database directory: Exim creates a directory called "db"
+# in its spool directory, to hold its databases of hints. This variable
+# determines the mode of the created directory. The default value in the
+# source is 0750.
+
+# EXIMDB_DIRECTORY_MODE=0750
+
+
+#------------------------------------------------------------------------------
+# Database file mode: The mode of files created in the "db" directory defaults
+# to 0640 in the source, and can be changed here.
+
+# EXIMDB_MODE=0640
+
+
+#------------------------------------------------------------------------------
+# Database lock file mode: The mode of zero-length files created in the "db"
+# directory to use for locking purposes defaults to 0640 in the source, and
+# can be changed here.
+
+# EXIMDB_LOCKFILE_MODE=0640
+
+
+#------------------------------------------------------------------------------
+# This parameter sets the maximum length of the header portion of a message
+# that Exim is prepared to process. The default setting is one megabyte. The
+# limit exists in order to catch rogue mailers that might connect to your SMTP
+# port, start off a header line, and then just pump junk at it for ever. The
+# message_size_limit option would also catch this, but it may not be set.
+# The value set here is the default; it can be changed at runtime.
+
+# HEADER_MAXSIZE="(1024*1024)"
+
+
+#------------------------------------------------------------------------------
+# The mode of the input directory: The input directory is where messages are
+# kept while awaiting delivery. Exim creates it if necessary, using a mode
+# which can be defined here (default 0750).
+
+# INPUT_DIRECTORY_MODE=0750
+
+
+#------------------------------------------------------------------------------
+# The mode of Exim's log directory, when it is created by Exim inside the spool
+# directory, defaults to 0750 but can be changed here.
+
+# LOG_DIRECTORY_MODE=0750
+
+
+#------------------------------------------------------------------------------
+# The log files themselves are created as required, with a mode that defaults
+# to 0640, but which can be changed here.
+
+# LOG_MODE=0640
+
+
+#------------------------------------------------------------------------------
+# The TESTDB lookup is for performing tests on the handling of lookup results,
+# and is not useful for general running. It should be included only when
+# debugging the code of Exim.
+
+# LOOKUP_TESTDB=yes
+
+
+#------------------------------------------------------------------------------
+# /bin/sh is used by default as the shell in which to run commands that are
+# defined in the makefiles. This can be changed if necessary, by uncommenting
+# this line and specifying another shell, but note that a Bourne-compatible
+# shell is expected.
+
+# MAKE_SHELL=/bin/sh
+
+
+#------------------------------------------------------------------------------
+# The maximum number of named lists of each type (address, domain, host, and
+# local part) can be increased by changing this value. It should be set to
+# a multiple of 16.
+
+# MAX_NAMED_LIST=16
+
+
+#------------------------------------------------------------------------------
+# Network interfaces: Unless you set the local_interfaces option in the runtime
+# configuration file to restrict Exim to certain interfaces only, it will run
+# code to find all the interfaces there are on your host. Unfortunately,
+# the call to the OS that does this requires a buffer large enough to hold
+# data for all the interfaces - it was designed in the days when a host rarely
+# had more than three or four interfaces. Nowadays hosts can have very many
+# virtual interfaces running on the same hardware. If you have more than 250
+# virtual interfaces, you will need to uncomment this setting and increase the
+# value.
+
+# MAXINTERFACES=250
+
+
+#------------------------------------------------------------------------------
+# Per-message logs: While a message is in the process of being delivered,
+# comments on its progress are written to a message log, for the benefit of
+# human administrators. These logs are held in a directory called "msglog"
+# in the spool directory. Its mode defaults to 0750, but can be changed here.
+# The message log directory is also used for storing files that are used by
+# transports for returning data to a message's sender (see the "return_output"
+# option for transports).
+
+# MSGLOG_DIRECTORY_MODE=0750
+
+
+#------------------------------------------------------------------------------
+# There are three options which are used when compiling the Perl interface and
+# when linking with Perl. The default values for these are placed automatically
+# at the head of the Makefile by the script which builds it. However, if you
+# want to override them, you can do so here.
+
+# PERL_CC=
+# PERL_CCOPTS=
+# PERL_LIBS=
+
+
+#------------------------------------------------------------------------------
+# Identifying the daemon: When an Exim daemon starts up, it writes its pid
+# (process id) to a file so that it can easily be identified. The path of the
+# file can be specified here. Some installations may want something like this:
+
+# PID_FILE_PATH=/var/lock/exim.pid
+
+# If PID_FILE_PATH is not defined, Exim writes a file in its spool directory
+# using the name "exim-daemon.pid".
+
+# If you start up a daemon without the -bd option (for example, with just
+# the -q15m option), a pid file is not written. Also, if you override the
+# configuration file with the -oX option, no pid file is written. In other
+# words, the pid file is written only for a "standard" daemon.
+
+
+#------------------------------------------------------------------------------
+# If Exim creates the spool directory, it is given this mode, defaulting in the
+# source to 0750.
+
+# SPOOL_DIRECTORY_MODE=0750
+
+
+#------------------------------------------------------------------------------
+# The mode of files on the input spool which hold the contents of messages can
+# be changed here. The default is 0640 so that information from the spool is
+# available to anyone who is a member of the Exim group.
+
+# SPOOL_MODE=0640
+
+
+#------------------------------------------------------------------------------
+# Moving frozen messages: If the following is uncommented, Exim is compiled
+# with support for automatically moving frozen messages out of the main spool
+# directory, a facility that is found useful by some large installations. A
+# run time option is required to cause the moving actually to occur. Such
+# messages become "invisible" to the normal management tools.
+
+# SUPPORT_MOVE_FROZEN_MESSAGES=yes
+
+# End of EDITME for Exim 4.
diff --git a/src/src/acl.c b/src/src/acl.c
new file mode 100644 (file)
index 0000000..d6a354c
--- /dev/null
@@ -0,0 +1,2151 @@
+/* $Cambridge: exim/src/src/acl.c,v 1.1 2004/10/07 10:39:01 ph10 Exp $ */
+
+/*************************************************
+*     Exim - an Internet mail transport agent    *
+*************************************************/
+
+/* Copyright (c) University of Cambridge 1995 - 2004 */
+/* See the file NOTICE for conditions of use and distribution. */
+
+/* Code for handling Access Control Lists (ACLs) */
+
+#include "exim.h"
+
+
+/* Default callout timeout */
+
+#define CALLOUT_TIMEOUT_DEFAULT 30
+
+/* ACL verb codes - keep in step with the table of verbs that follows */
+
+enum { ACL_ACCEPT, ACL_DEFER, ACL_DENY, ACL_DISCARD, ACL_DROP, ACL_REQUIRE,
+       ACL_WARN };
+
+/* ACL verbs */
+
+static uschar *verbs[] =
+  { US"accept", US"defer", US"deny", US"discard", US"drop", US"require",
+    US"warn" };
+
+/* For each verb, the condition for which "message" is used */
+
+static int msgcond[] = { FAIL, OK, OK, FAIL, OK, FAIL, OK };
+
+/* ACL condition and modifier codes - keep in step with the table that
+follows. */
+
+enum { ACLC_ACL, ACLC_AUTHENTICATED, ACLC_CONDITION, ACLC_CONTROL, ACLC_DELAY,
+  ACLC_DNSLISTS, ACLC_DOMAINS, ACLC_ENCRYPTED, ACLC_ENDPASS, ACLC_HOSTS,
+  ACLC_LOCAL_PARTS, ACLC_LOG_MESSAGE, ACLC_LOGWRITE, ACLC_MESSAGE,
+  ACLC_RECIPIENTS, ACLC_SENDER_DOMAINS, ACLC_SENDERS, ACLC_SET, ACLC_VERIFY };
+
+/* ACL conditions/modifiers: "delay", "control", "endpass", "message",
+"log_message", "logwrite", and "set" are modifiers that look like conditions
+but always return TRUE. They are used for their side effects. */
+
+static uschar *conditions[] = { US"acl", US"authenticated", US"condition",
+  US"control", US"delay", US"dnslists", US"domains", US"encrypted",
+  US"endpass", US"hosts", US"local_parts", US"log_message", US"logwrite",
+  US"message", US"recipients", US"sender_domains", US"senders", US"set",
+  US"verify" };
+
+/* Flags to indicate for which conditions /modifiers a string expansion is done
+at the outer level. In the other cases, expansion already occurs in the
+checking functions. */
+
+static uschar cond_expand_at_top[] = {
+  TRUE,    /* acl */
+  FALSE,   /* authenticated */
+  TRUE,    /* condition */
+  TRUE,    /* control */
+  TRUE,    /* delay */
+  TRUE,    /* dnslists */
+  FALSE,   /* domains */
+  FALSE,   /* encrypted */
+  TRUE,    /* endpass */
+  FALSE,   /* hosts */
+  FALSE,   /* local_parts */
+  TRUE,    /* log_message */
+  TRUE,    /* logwrite */
+  TRUE,    /* message */
+  FALSE,   /* recipients */
+  FALSE,   /* sender_domains */
+  FALSE,   /* senders */
+  TRUE,    /* set */
+  TRUE     /* verify */
+};
+
+/* Flags to identify the modifiers */
+
+static uschar cond_modifiers[] = {
+  FALSE,   /* acl */
+  FALSE,   /* authenticated */
+  FALSE,   /* condition */
+  TRUE,    /* control */
+  TRUE,    /* delay */
+  FALSE,   /* dnslists */
+  FALSE,   /* domains */
+  FALSE,   /* encrypted */
+  TRUE,    /* endpass */
+  FALSE,   /* hosts */
+  FALSE,   /* local_parts */
+  TRUE,    /* log_message */
+  TRUE,    /* log_write */
+  TRUE,    /* message */
+  FALSE,   /* recipients */
+  FALSE,   /* sender_domains */
+  FALSE,   /* senders */
+  TRUE,    /* set */
+  FALSE    /* verify */
+};
+
+/* Bit map of which conditions are not allowed at certain times. For each
+condition, there's a bitmap of dis-allowed times. */
+
+static unsigned int cond_forbids[] = {
+  0,                                               /* acl */
+  (1<<ACL_WHERE_NOTSMTP)|(1<<ACL_WHERE_CONNECT)|   /* authenticated */
+    (1<<ACL_WHERE_HELO),
+  0,                                               /* condition */
+
+  /* Certain types of control are always allowed, so we let it through
+  always and check in the control processing itself */
+
+  0,                                               /* control */
+  0,                                               /* delay */
+  (1<<ACL_WHERE_NOTSMTP),                          /* dnslists */
+
+  (1<<ACL_WHERE_NOTSMTP)|(1<<ACL_WHERE_AUTH)|      /* domains */
+    (1<<ACL_WHERE_CONNECT)|(1<<ACL_WHERE_HELO)|
+    (1<<ACL_WHERE_DATA)|(1<<ACL_WHERE_PREDATA)|
+    (1<<ACL_WHERE_ETRN)|(1<<ACL_WHERE_EXPN)|
+    (1<<ACL_WHERE_MAILAUTH)|(1<<ACL_WHERE_QUIT)|
+    (1<<ACL_WHERE_MAIL)|(1<<ACL_WHERE_STARTTLS)|
+    (1<<ACL_WHERE_VRFY),
+
+  (1<<ACL_WHERE_NOTSMTP)|(1<<ACL_WHERE_CONNECT)|   /* encrypted */
+    (1<<ACL_WHERE_HELO),
+  0,                                               /* endpass */
+  (1<<ACL_WHERE_NOTSMTP),                          /* hosts */
+
+  (1<<ACL_WHERE_NOTSMTP)|(1<<ACL_WHERE_AUTH)|      /* local_parts */
+    (1<<ACL_WHERE_CONNECT)|(1<<ACL_WHERE_HELO)|
+    (1<<ACL_WHERE_DATA)|(1<<ACL_WHERE_PREDATA)|
+    (1<<ACL_WHERE_ETRN)|(1<<ACL_WHERE_EXPN)|
+    (1<<ACL_WHERE_MAILAUTH)|(1<<ACL_WHERE_QUIT)|
+    (1<<ACL_WHERE_MAIL)|(1<<ACL_WHERE_STARTTLS)|
+    (1<<ACL_WHERE_VRFY),
+
+  0,                                               /* log_message */
+  0,                                               /* logwrite */
+  0,                                               /* message */
+
+  (1<<ACL_WHERE_NOTSMTP)|(1<<ACL_WHERE_AUTH)|      /* recipients */
+    (1<<ACL_WHERE_CONNECT)|(1<<ACL_WHERE_HELO)|
+    (1<<ACL_WHERE_DATA)|(1<<ACL_WHERE_PREDATA)|
+    (1<<ACL_WHERE_ETRN)|(1<<ACL_WHERE_EXPN)|
+    (1<<ACL_WHERE_MAILAUTH)|(1<<ACL_WHERE_QUIT)|
+    (1<<ACL_WHERE_MAIL)|(1<<ACL_WHERE_STARTTLS)|
+    (1<<ACL_WHERE_VRFY),
+
+  (1<<ACL_WHERE_AUTH)|(1<<ACL_WHERE_CONNECT)|      /* sender_domains */
+    (1<<ACL_WHERE_HELO)|
+    (1<<ACL_WHERE_MAILAUTH)|(1<<ACL_WHERE_QUIT)|
+    (1<<ACL_WHERE_ETRN)|(1<<ACL_WHERE_EXPN)|
+    (1<<ACL_WHERE_STARTTLS)|(1<<ACL_WHERE_VRFY),
+
+  (1<<ACL_WHERE_AUTH)|(1<<ACL_WHERE_CONNECT)|      /* senders */
+    (1<<ACL_WHERE_HELO)|
+    (1<<ACL_WHERE_MAILAUTH)|(1<<ACL_WHERE_QUIT)|
+    (1<<ACL_WHERE_ETRN)|(1<<ACL_WHERE_EXPN)|
+    (1<<ACL_WHERE_STARTTLS)|(1<<ACL_WHERE_VRFY),
+
+  0,                                               /* set */
+
+  /* Certain types of verify are always allowed, so we let it through
+  always and check in the verify function itself */
+
+  0                                                /* verify */
+
+};
+
+
+/* Return values from decode_control() */
+
+enum { CONTROL_ERROR, CONTROL_CASEFUL_LOCAL_PART, CONTROL_CASELOWER_LOCAL_PART,
+  CONTROL_ENFORCE_SYNC, CONTROL_NO_ENFORCE_SYNC, CONTROL_FREEZE,
+  CONTROL_QUEUE_ONLY, CONTROL_SUBMISSION, CONTROL_NO_MULTILINE };
+
+/* Structure listing various control arguments, with their characteristics.
+The maximum "where" value controls the ACLs in which the various controls are
+permitted to occur. Specifying ACL_WHERE_RCPT limits it to just the RCPT ACL;
+specifying ACL_WHERE_NOTSMTP limits it to "message" ACLs. */
+
+typedef struct control_def {
+  uschar *name;
+  int    value;                  /* CONTROL_xxx value */
+  int    where_max;              /* Maximum "where" value */
+  BOOL   has_option;             /* Has /option(s) following */
+} control_def;
+
+static control_def controls_list[] = {
+  { US"caseful_local_part",     CONTROL_CASEFUL_LOCAL_PART,
+    ACL_WHERE_RCPT,    FALSE },
+  { US"caselower_local_part",   CONTROL_CASELOWER_LOCAL_PART,
+    ACL_WHERE_RCPT,    FALSE },
+  { US"enforce_sync",           CONTROL_ENFORCE_SYNC,
+    INT_MAX,           FALSE },
+  { US"freeze",                 CONTROL_FREEZE,
+    ACL_WHERE_NOTSMTP, FALSE },
+  { US"no_enforce_sync",        CONTROL_NO_ENFORCE_SYNC,
+    INT_MAX,           FALSE },
+  { US"no_multiline_responses", CONTROL_NO_MULTILINE,
+    INT_MAX,           FALSE },
+  { US"queue_only",             CONTROL_QUEUE_ONLY,
+    ACL_WHERE_NOTSMTP, FALSE },
+  { US"submission",             CONTROL_SUBMISSION,
+    ACL_WHERE_NOTSMTP, TRUE  }
+  };
+
+/* Enable recursion between acl_check_internal() and acl_check_condition() */
+
+static int acl_check_internal(int, address_item *, uschar *, int, uschar **,
+         uschar **);
+
+
+/*************************************************
+*         Pick out name from list                *
+*************************************************/
+
+/* Use a binary chop method
+
+Arguments:
+  name        name to find
+  list        list of names
+  end         size of list
+
+Returns:      offset in list, or -1 if not found
+*/
+
+static int
+acl_checkname(uschar *name, uschar **list, int end)
+{
+int start = 0;
+
+while (start < end)
+  {
+  int mid = (start + end)/2;
+  int c = Ustrcmp(name, list[mid]);
+  if (c == 0) return mid;
+  if (c < 0) end = mid; else start = mid + 1;
+  }
+
+return -1;
+}
+
+
+/*************************************************
+*            Read and parse one ACL              *
+*************************************************/
+
+/* This function is called both from readconf in order to parse the ACLs in the
+configuration file, and also when an ACL is encountered dynamically (e.g. as
+the result of an expansion). It is given a function to call in order to
+retrieve the lines of the ACL. This function handles skipping comments and
+blank lines (where relevant).
+
+Arguments:
+  func        function to get next line of ACL
+  error       where to put an error message
+
+Returns:      pointer to ACL, or NULL
+              NULL can be legal (empty ACL); in this case error will be NULL
+*/
+
+acl_block *
+acl_read(uschar *(*func)(void), uschar **error)
+{
+acl_block *yield = NULL;
+acl_block **lastp = &yield;
+acl_block *this = NULL;
+acl_condition_block *cond;
+acl_condition_block **condp = NULL;
+uschar *s;
+
+*error = NULL;
+
+while ((s = (*func)()) != NULL)
+  {
+  int v, c;
+  BOOL negated = FALSE;
+  uschar *saveline = s;
+  uschar name[64];
+
+  /* Conditions (but not verbs) are allowed to be negated by an initial
+  exclamation mark. */
+
+  while (isspace(*s)) s++;
+  if (*s == '!')
+    {
+    negated = TRUE;
+    s++;
+    }
+
+  /* Read the name of a verb or a condition, or the start of a new ACL */
+
+  s = readconf_readname(name, sizeof(name), s);
+  if (*s == ':')
+    {
+    if (negated || name[0] == 0)
+      {
+      *error = string_sprintf("malformed ACL name in \"%s\"", saveline);
+      return NULL;
+      }
+    break;
+    }
+
+  /* If a verb is unrecognized, it may be another condition or modifier that
+  continues the previous verb. */
+
+  v = acl_checkname(name, verbs, sizeof(verbs)/sizeof(char *));
+  if (v < 0)
+    {
+    if (this == NULL)
+      {
+      *error = string_sprintf("unknown ACL verb in \"%s\"", saveline);
+      return NULL;
+      }
+    }
+
+  /* New verb */
+
+  else
+    {
+    if (negated)
+      {
+      *error = string_sprintf("malformed ACL line \"%s\"", saveline);
+      return NULL;
+      }
+    this = store_get(sizeof(acl_block));
+    *lastp = this;
+    lastp = &(this->next);
+    this->next = NULL;
+    this->verb = v;
+    this->condition = NULL;
+    condp = &(this->condition);
+    if (*s == 0) continue;               /* No condition on this line */
+    if (*s == '!')
+      {
+      negated = TRUE;
+      s++;
+      }
+    s = readconf_readname(name, sizeof(name), s);  /* Condition name */
+    }
+
+  /* Handle a condition or modifier. */
+
+  c = acl_checkname(name, conditions, sizeof(conditions)/sizeof(char *));
+  if (c < 0)
+    {
+    *error = string_sprintf("unknown ACL condition/modifier in \"%s\"",
+      saveline);
+    return NULL;
+    }
+
+  /* The modifiers may not be negated */
+
+  if (negated && cond_modifiers[c])
+    {
+    *error = string_sprintf("ACL error: negation is not allowed with "
+      "\"%s\"", conditions[c]);
+    return NULL;
+    }
+
+  /* ENDPASS may occur only with ACCEPT or DISCARD. */
+
+  if (c == ACLC_ENDPASS &&
+      this->verb != ACL_ACCEPT &&
+      this->verb != ACL_DISCARD)
+    {
+    *error = string_sprintf("ACL error: \"%s\" is not allowed with \"%s\"",
+      conditions[c], verbs[this->verb]);
+    return NULL;
+    }
+
+  cond = store_get(sizeof(acl_condition_block));
+  cond->next = NULL;
+  cond->type = c;
+  cond->u.negated = negated;
+
+  *condp = cond;
+  condp = &(cond->next);
+
+  /* The "set" modifier is different in that its argument is "name=value"
+  rather than just a value, and we can check the validity of the name, which
+  gives us a variable number to insert into the data block. */
+
+  if (c == ACLC_SET)
+    {
+    if (Ustrncmp(s, "acl_", 4) != 0 || (s[4] != 'c' && s[4] != 'm') ||
+        !isdigit(s[5]) || (!isspace(s[6]) && s[6] != '='))
+      {
+      *error = string_sprintf("unrecognized name after \"set\" in ACL "
+        "modifier \"set %s\"", s);
+      return NULL;
+      }
+
+    cond->u.varnumber = s[5] - '0';
+    if (s[4] == 'm') cond->u.varnumber += ACL_C_MAX;
+    s += 6;
+    while (isspace(*s)) s++;
+    }
+
+  /* For "set", we are now positioned for the data. For the others, only
+  "endpass" has no data */
+
+  if (c != ACLC_ENDPASS)
+    {
+    if (*s++ != '=')
+      {
+      *error = string_sprintf("\"=\" missing after ACL \"%s\" %s", name,
+        cond_modifiers[c]? US"modifier" : US"condition");
+      return NULL;
+      }
+    while (isspace(*s)) s++;
+    cond->arg = string_copy(s);
+    }
+  }
+
+return yield;
+}
+
+
+
+/*************************************************
+*               Handle warnings                  *
+*************************************************/
+
+/* This function is called when a WARN verb's conditions are true. It adds to
+the message's headers, and/or writes information to the log. In each case, this
+only happens once (per message for headers, per connection for log).
+
+Arguments:
+  where          ACL_WHERE_xxxx indicating which ACL this is
+  user_message   message for adding to headers
+  log_message    message for logging, if different
+
+Returns:         nothing
+*/
+
+static void
+acl_warn(int where, uschar *user_message, uschar *log_message)
+{
+int hlen;
+
+if (log_message != NULL && log_message != user_message)
+  {
+  uschar *text;
+  string_item *logged;
+
+  text = string_sprintf("%s Warning: %s",  host_and_ident(TRUE),
+    string_printing(log_message));
+
+  /* If a sender verification has failed, and the log message is "sender verify
+  failed", add the failure message. */
+
+  if (sender_verified_failed != NULL &&
+      sender_verified_failed->message != NULL &&
+      strcmpic(log_message, US"sender verify failed") == 0)
+    text = string_sprintf("%s: %s", text, sender_verified_failed->message);
+
+  /* Search previously logged warnings. They are kept in malloc store so they
+  can be freed at the start of a new message. */
+
+  for (logged = acl_warn_logged; logged != NULL; logged = logged->next)
+    if (Ustrcmp(logged->text, text) == 0) break;
+
+  if (logged == NULL)
+    {
+    int length = Ustrlen(text) + 1;
+    log_write(0, LOG_MAIN, "%s", text);
+    logged = store_malloc(sizeof(string_item) + length);
+    logged->text = (uschar *)logged + sizeof(string_item);
+    memcpy(logged->text, text, length);
+    logged->next = acl_warn_logged;
+    acl_warn_logged = logged;
+    }
+  }
+
+/* If there's no user message, we are done. */
+
+if (user_message == NULL) return;
+
+/* If this isn't a message ACL, we can't do anything with a user message.
+Log an error. */
+
+if (where > ACL_WHERE_NOTSMTP)
+  {
+  log_write(0, LOG_MAIN|LOG_PANIC, "ACL \"warn\" with \"message\" setting "
+    "found in a non-message (%s) ACL: cannot specify header lines here: "
+    "message ignored", acl_wherenames[where]);
+  return;
+  }
+
+/* Treat the user message as a sequence of one or more header lines. */
+
+hlen = Ustrlen(user_message);
+if (hlen > 0)
+  {
+  uschar *text, *p, *q;
+
+  /* Add a final newline if not present */
+
+  text = ((user_message)[hlen-1] == '\n')? user_message :
+    string_sprintf("%s\n", user_message);
+
+  /* Loop for multiple header lines, taking care about continuations */
+
+  for (p = q = text; *p != 0; )
+    {
+    uschar *s;
+    int newtype = htype_add_bot;
+    header_line **hptr = &acl_warn_headers;
+
+    /* Find next header line within the string */
+
+    for (;;)
+      {
+      q = Ustrchr(q, '\n');
+      if (*(++q) != ' ' && *q != '\t') break;
+      }
+
+    /* If the line starts with a colon, interpret the instruction for where to
+    add it. This temporarily sets up a new type. */
+
+    if (*p == ':')
+      {
+      if (strncmpic(p, US":after_received:", 16) == 0)
+        {
+        newtype = htype_add_rec;
+        p += 16;
+        }
+      else if (strncmpic(p, US":at_start:", 10) == 0)
+        {
+        newtype = htype_add_top;
+        p += 10;
+        }
+      else if (strncmpic(p, US":at_end:", 8) == 0)
+        {
+        newtype = htype_add_bot;
+        p += 8;
+        }
+      while (*p == ' ' || *p == '\t') p++;
+      }
+
+    /* See if this line starts with a header name, and if not, add X-ACL-Warn:
+    to the front of it. */
+
+    for (s = p; s < q - 1; s++)
+      {
+      if (*s == ':' || !isgraph(*s)) break;
+      }
+
+    s = string_sprintf("%s%.*s", (*s == ':')? "" : "X-ACL-Warn: ", q - p, p);
+    hlen = Ustrlen(s);
+
+    /* See if this line has already been added */
+
+    while (*hptr != NULL)
+      {
+      if (Ustrncmp((*hptr)->text, s, hlen) == 0) break;
+      hptr = &((*hptr)->next);
+      }
+
+    /* Add if not previously present */
+
+    if (*hptr == NULL)
+      {
+      header_line *h = store_get(sizeof(header_line));
+      h->text = s;
+      h->next = NULL;
+      h->type = newtype;
+      h->slen = hlen;
+      *hptr = h;
+      hptr = &(h->next);
+      }
+
+    /* Advance for next header line within the string */
+
+    p = q;
+    }
+  }
+}
+
+
+
+/*************************************************
+*         Verify and check reverse DNS           *
+*************************************************/
+
+/* Called from acl_verify() below. We look up the host name(s) of the client IP
+address if this has not yet been done. The host_name_lookup() function checks
+that one of these names resolves to an address list that contains the client IP
+address, so we don't actually have to do the check here.
+
+Arguments:
+  user_msgptr  pointer for user message
+  log_msgptr   pointer for log message
+
+Returns:       OK        verification condition succeeded
+               FAIL      verification failed
+               DEFER     there was a problem verifying
+*/
+
+static int
+acl_verify_reverse(uschar **user_msgptr, uschar **log_msgptr)
+{
+int rc;
+
+user_msgptr = user_msgptr;  /* stop compiler warning */
+
+/* Previous success */
+
+if (sender_host_name != NULL) return OK;
+
+/* Previous failure */
+
+if (host_lookup_failed)
+  {
+  *log_msgptr = string_sprintf("host lookup failed%s", host_lookup_msg);
+  return FAIL;
+  }
+
+/* Need to do a lookup */
+
+HDEBUG(D_acl)
+  debug_printf("looking up host name to force name/address consistency check\n");
+
+if ((rc = host_name_lookup()) != OK)
+  {
+  *log_msgptr = (rc == DEFER)?
+    US"host lookup deferred for reverse lookup check"
+    :
+    string_sprintf("host lookup failed for reverse lookup check%s",
+      host_lookup_msg);
+  return rc;    /* DEFER or FAIL */
+  }
+
+host_build_sender_fullhost();
+return OK;
+}
+
+
+
+/*************************************************
+*     Handle verification (address & other)      *
+*************************************************/
+
+/* This function implements the "verify" condition. It is called when
+encountered in any ACL, because some tests are almost always permitted. Some
+just don't make sense, and always fail (for example, an attempt to test a host
+lookup for a non-TCP/IP message). Others are restricted to certain ACLs.
+
+Arguments:
+  where        where called from
+  addr         the recipient address that the ACL is handling, or NULL
+  arg          the argument of "verify"
+  user_msgptr  pointer for user message
+  log_msgptr   pointer for log message
+  basic_errno  where to put verify errno
+
+Returns:       OK        verification condition succeeded
+               FAIL      verification failed
+               DEFER     there was a problem verifying
+               ERROR     syntax error
+*/
+
+static int
+acl_verify(int where, address_item *addr, uschar *arg,
+  uschar **user_msgptr, uschar **log_msgptr, int *basic_errno)
+{
+int sep = '/';
+int callout = -1;
+int callout_overall = -1;
+int verify_options = 0;
+int rc;
+BOOL verify_header_sender = FALSE;
+BOOL defer_ok = FALSE;
+BOOL callout_defer_ok = FALSE;
+BOOL no_details = FALSE;
+address_item *sender_vaddr = NULL;
+uschar *verify_sender_address = NULL;
+uschar *pm_mailfrom = NULL;
+uschar *se_mailfrom = NULL;
+uschar *list = arg;
+uschar *ss = string_nextinlist(&list, &sep, big_buffer, big_buffer_size);
+
+if (ss == NULL) goto BAD_VERIFY;
+
+/* Handle name/address consistency verification in a separate function. */
+
+if (strcmpic(ss, US"reverse_host_lookup") == 0)
+  {
+  if (sender_host_address == NULL) return OK;
+  return acl_verify_reverse(user_msgptr, log_msgptr);
+  }
+
+/* TLS certificate verification is done at STARTTLS time; here we just
+test whether it was successful or not. (This is for optional verification; for
+mandatory verification, the connection doesn't last this long.) */
+
+if (strcmpic(ss, US"certificate") == 0)
+  {
+  if (tls_certificate_verified) return OK;
+  *user_msgptr = US"no verified certificate";
+  return FAIL;
+  }
+
+/* We can test the result of optional HELO verification */
+
+if (strcmpic(ss, US"helo") == 0) return helo_verified? OK : FAIL;
+
+/* Handle header verification options - permitted only after DATA or a non-SMTP
+message. */
+
+if (strncmpic(ss, US"header_", 7) == 0)
+  {
+  if (where != ACL_WHERE_DATA && where != ACL_WHERE_NOTSMTP)
+    {
+    *log_msgptr = string_sprintf("cannot check header contents in ACL for %s "
+      "(only possible in ACL for DATA)", acl_wherenames[where]);
+    return ERROR;
+    }
+
+  /* Check that all relevant header lines have the correct syntax. If there is
+  a syntax error, we return details of the error to the sender if configured to
+  send out full details. (But a "message" setting on the ACL can override, as
+  always). */
+
+  if (strcmpic(ss+7, US"syntax") == 0)
+    {
+    int rc = verify_check_headers(log_msgptr);
+    if (rc != OK && smtp_return_error_details && *log_msgptr != NULL)
+      *user_msgptr = string_sprintf("Rejected after DATA: %s", *log_msgptr);
+    return rc;
+    }
+
+  /* Check that there is at least one verifiable sender address in the relevant
+  header lines. This can be followed by callout and defer options, just like
+  sender and recipient. */
+
+  else if (strcmpic(ss+7, US"sender") == 0) verify_header_sender = TRUE;
+
+  /* Unknown verify argument starting with "header_" */
+
+  else goto BAD_VERIFY;
+  }
+
+/* Otherwise, first item in verify argument must be "sender" or "recipient".
+In the case of a sender, this can optionally be followed by an address to use
+in place of the actual sender (rare special-case requirement). */
+
+else if (strncmpic(ss, US"sender", 6) == 0)
+  {
+  uschar *s = ss + 6;
+  if (where > ACL_WHERE_NOTSMTP)
+    {
+    *log_msgptr = string_sprintf("cannot verify sender in ACL for %s "
+      "(only possible for MAIL, RCPT, PREDATA, or DATA)",
+      acl_wherenames[where]);
+    return ERROR;
+    }
+  if (*s == 0)
+    verify_sender_address = sender_address;
+  else
+    {
+    while (isspace(*s)) s++;
+    if (*s++ != '=') goto BAD_VERIFY;
+    while (isspace(*s)) s++;
+    verify_sender_address = string_copy(s);
+    }
+  }
+else
+  {
+  if (strcmpic(ss, US"recipient") != 0) goto BAD_VERIFY;
+  if (addr == NULL)
+    {
+    *log_msgptr = string_sprintf("cannot verify recipient in ACL for %s "
+      "(only possible for RCPT)", acl_wherenames[where]);
+    return ERROR;
+    }
+  }
+
+/* Remaining items are optional */
+
+while ((ss = string_nextinlist(&list, &sep, big_buffer, big_buffer_size))
+      != NULL)
+  {
+  if (strcmpic(ss, US"defer_ok") == 0) defer_ok = TRUE;
+  else if (strcmpic(ss, US"no_details") == 0) no_details = TRUE;
+
+  /* These two old options are left for backwards compatibility */
+
+  else if (strcmpic(ss, US"callout_defer_ok") == 0)
+    {
+    callout_defer_ok = TRUE;
+    if (callout == -1) callout = CALLOUT_TIMEOUT_DEFAULT;
+    }
+
+  else if (strcmpic(ss, US"check_postmaster") == 0)
+     {
+     pm_mailfrom = US"";
+     if (callout == -1) callout = CALLOUT_TIMEOUT_DEFAULT;
+     }
+
+  /* The callout option has a number of sub-options, comma separated */
+
+  else if (strncmpic(ss, US"callout", 7) == 0)
+    {
+    callout = CALLOUT_TIMEOUT_DEFAULT;
+    ss += 7;
+    if (*ss != 0)
+      {
+      while (isspace(*ss)) ss++;
+      if (*ss++ == '=')
+        {
+        int optsep = ',';
+        uschar *opt;
+        uschar buffer[256];
+        while (isspace(*ss)) ss++;
+        while ((opt = string_nextinlist(&ss, &optsep, buffer, sizeof(buffer)))
+              != NULL)
+          {
+          if (strcmpic(opt, US"defer_ok") == 0) callout_defer_ok = TRUE;
+          else if (strcmpic(opt, US"no_cache") == 0)
+             verify_options |= vopt_callout_no_cache;
+          else if (strcmpic(opt, US"random") == 0)
+             verify_options |= vopt_callout_random;
+          else if (strcmpic(opt, US"use_sender") == 0)
+             verify_options |= vopt_callout_recipsender;
+          else if (strcmpic(opt, US"use_postmaster") == 0)
+             verify_options |= vopt_callout_recippmaster;
+          else if (strcmpic(opt, US"postmaster") == 0) pm_mailfrom = US"";
+
+          else if (strncmpic(opt, US"mailfrom", 8) == 0)
+            {
+            if (!verify_header_sender)
+              {
+              *log_msgptr = string_sprintf("\"mailfrom\" is allowed as a "
+                "callout option only for verify=header_sender (detected in ACL "
+                "condition \"%s\")", arg);
+              return ERROR;
+              }
+            opt += 8;
+            while (isspace(*opt)) opt++;
+            if (*opt++ != '=')
+              {
+              *log_msgptr = string_sprintf("'=' expected after "
+                "\"mailfrom\" in ACL condition \"%s\"", arg);
+              return ERROR;
+              }
+            while (isspace(*opt)) opt++;
+            se_mailfrom = string_copy(opt);
+            }
+
+          else if (strncmpic(opt, US"postmaster_mailfrom", 19) == 0)
+            {
+            opt += 19;
+            while (isspace(*opt)) opt++;
+            if (*opt++ != '=')
+              {
+              *log_msgptr = string_sprintf("'=' expected after "
+                "\"postmaster_mailfrom\" in ACL condition \"%s\"", arg);
+              return ERROR;
+              }
+            while (isspace(*opt)) opt++;
+            pm_mailfrom = string_copy(opt);
+            }
+
+          else if (strncmpic(opt, US"maxwait", 7) == 0)
+            {
+            opt += 7;
+            while (isspace(*opt)) opt++;
+            if (*opt++ != '=')
+              {
+              *log_msgptr = string_sprintf("'=' expected after \"maxwait\" in "
+                "ACL condition \"%s\"", arg);
+              return ERROR;
+              }
+            while (isspace(*opt)) opt++;
+            callout_overall = readconf_readtime(opt, 0, FALSE);
+            if (callout_overall < 0)
+              {
+              *log_msgptr = string_sprintf("bad time value in ACL condition "
+                "\"verify %s\"", arg);
+              return ERROR;
+              }
+            }
+          else    /* Plain time is callout connect/command timeout */
+            {
+            callout = readconf_readtime(opt, 0, FALSE);
+            if (callout < 0)
+              {
+              *log_msgptr = string_sprintf("bad time value in ACL condition "
+                "\"verify %s\"", arg);
+              return ERROR;
+              }
+            }
+          }
+        }
+      else
+        {
+        *log_msgptr = string_sprintf("'=' expected after \"callout\" in "
+          "ACL condition \"%s\"", arg);
+        return ERROR;
+        }
+      }
+    }
+
+  /* Option not recognized */
+
+  else
+    {
+    *log_msgptr = string_sprintf("unknown option \"%s\" in ACL "
+      "condition \"verify %s\"", ss, arg);
+    return ERROR;
+    }
+  }
+
+if ((verify_options & (vopt_callout_recipsender|vopt_callout_recippmaster)) ==
+      (vopt_callout_recipsender|vopt_callout_recippmaster))
+  {
+  *log_msgptr = US"only one of use_sender and use_postmaster can be set "
+    "for a recipient callout";
+  return ERROR;
+  }
+
+/* Handle sender-in-header verification. Default the user message to the log
+message if giving out verification details. */
+
+if (verify_header_sender)
+  {
+  rc = verify_check_header_address(user_msgptr, log_msgptr, callout,
+    callout_overall, se_mailfrom, pm_mailfrom, verify_options);
+  if (smtp_return_error_details)
+    {
+    if (*user_msgptr == NULL && *log_msgptr != NULL)
+      *user_msgptr = string_sprintf("Rejected after DATA: %s", *log_msgptr);
+    if (rc == DEFER) acl_temp_details = TRUE;
+    }
+  }
+
+/* Handle a sender address. The default is to verify *the* sender address, but
+optionally a different address can be given, for special requirements. If the
+address is empty, we are dealing with a bounce message that has no sender, so
+we cannot do any checking. If the real sender address gets rewritten during
+verification (e.g. DNS widening), set the flag to stop it being rewritten again
+during message reception.
+
+A list of verified "sender" addresses is kept to try to avoid doing to much
+work repetitively when there are multiple recipients in a message and they all
+require sender verification. However, when callouts are involved, it gets too
+complicated because different recipients may require different callout options.
+Therefore, we always do a full sender verify when any kind of callout is
+specified. Caching elsewhere, for instance in the DNS resolver and in the
+callout handling, should ensure that this is not terribly inefficient. */
+
+else if (verify_sender_address != NULL)
+  {
+  if ((verify_options & (vopt_callout_recipsender|vopt_callout_recippmaster))
+       != 0)
+    {
+    *log_msgptr = US"use_sender or use_postmaster cannot be used for a "
+      "sender verify callout";
+    return ERROR;
+    }
+
+  sender_vaddr = verify_checked_sender(verify_sender_address);
+  if (sender_vaddr != NULL &&               /* Previously checked */
+      callout <= 0)                         /* No callout needed this time */
+    {
+    /* If the "routed" flag is set, it means that routing worked before, so
+    this check can give OK (the saved return code value, if set, belongs to a
+    callout that was done previously). If the "routed" flag is not set, routing
+    must have failed, so we use the saved return code. */
+
+    if (testflag(sender_vaddr, af_verify_routed)) rc = OK; else
+      {
+      rc = sender_vaddr->special_action;
+      *basic_errno = sender_vaddr->basic_errno;
+      }
+    HDEBUG(D_acl) debug_printf("using cached sender verify result\n");
+    }
+
+  /* Do a new verification, and cache the result. The cache is used to avoid
+  verifying the sender multiple times for multiple RCPTs when callouts are not
+  specified (see comments above).
+
+  The cache is also used on failure to give details in response to the first
+  RCPT that gets bounced for this reason. However, this can be suppressed by
+  the no_details option, which sets the flag that says "this detail has already
+  been sent". The cache normally contains just one address, but there may be
+  more in esoteric circumstances. */
+
+  else
+    {
+    BOOL routed = TRUE;
+    sender_vaddr = deliver_make_addr(verify_sender_address, TRUE);
+    if (no_details) setflag(sender_vaddr, af_sverify_told);
+    if (verify_sender_address[0] != 0)
+      {
+      /* If this is the real sender address, save the unrewritten version
+      for use later in receive. Otherwise, set a flag so that rewriting the
+      sender in verify_address() does not update sender_address. */
+
+      if (verify_sender_address == sender_address)
+        sender_address_unrewritten = sender_address;
+      else
+        verify_options |= vopt_fake_sender;
+
+      /* The recipient, qualify, and expn options are never set in
+      verify_options. */
+
+      rc = verify_address(sender_vaddr, NULL, verify_options, callout,
+        callout_overall, se_mailfrom, pm_mailfrom, &routed);
+
+      HDEBUG(D_acl) debug_printf("----------- end verify ------------\n");
+
+      if (rc == OK)
+        {
+        if (Ustrcmp(sender_vaddr->address, verify_sender_address) != 0)
+          {
+          DEBUG(D_acl) debug_printf("sender %s verified ok as %s\n",
+            verify_sender_address, sender_vaddr->address);
+          }
+        else
+          {
+          DEBUG(D_acl) debug_printf("sender %s verified ok\n",
+            verify_sender_address);
+          }
+        }
+      else *basic_errno = sender_vaddr->basic_errno;
+      }
+    else rc = OK;  /* Null sender */
+
+    /* Cache the result code */
+
+    if (routed) setflag(sender_vaddr, af_verify_routed);
+    if (callout > 0) setflag(sender_vaddr, af_verify_callout);
+    sender_vaddr->special_action = rc;
+    sender_vaddr->next = sender_verified_list;
+    sender_verified_list = sender_vaddr;
+    }
+  }
+
+/* A recipient address just gets a straightforward verify; again we must handle
+the DEFER overrides. */
+
+else
+  {
+  address_item addr2;
+
+  /* We must use a copy of the address for verification, because it might
+  get rewritten. */
+
+  addr2 = *addr;
+  rc = verify_address(&addr2, NULL, verify_options|vopt_is_recipient, callout,
+    callout_overall, se_mailfrom, pm_mailfrom, NULL);
+  HDEBUG(D_acl) debug_printf("----------- end verify ------------\n");
+  *log_msgptr = addr2.message;
+  *user_msgptr = addr2.user_message;
+  *basic_errno = addr2.basic_errno;
+
+  /* Make $address_data visible */
+  deliver_address_data = addr2.p.address_data;
+  }
+
+/* We have a result from the relevant test. Handle defer overrides first. */
+
+if (rc == DEFER && (defer_ok ||
+   (callout_defer_ok && *basic_errno == ERRNO_CALLOUTDEFER)))
+  {
+  HDEBUG(D_acl) debug_printf("verify defer overridden by %s\n",
+    defer_ok? "defer_ok" : "callout_defer_ok");
+  rc = OK;
+  }
+
+/* If we've failed a sender, set up a recipient message, and point
+sender_verified_failed to the address item that actually failed. */
+
+if (rc != OK && verify_sender_address != NULL)
+  {
+  if (rc != DEFER)
+    {
+    *log_msgptr = *user_msgptr = US"Sender verify failed";
+    }
+  else if (*basic_errno != ERRNO_CALLOUTDEFER)
+    {
+    *log_msgptr = *user_msgptr = US"Could not complete sender verify";
+    }
+  else
+    {
+    *log_msgptr = US"Could not complete sender verify callout";
+    *user_msgptr = smtp_return_error_details? sender_vaddr->user_message :
+      *log_msgptr;
+    }
+
+  sender_verified_failed = sender_vaddr;
+  }
+
+/* Verifying an address messes up the values of $domain and $local_part,
+so reset them before returning if this is a RCPT ACL. */
+
+if (addr != NULL)
+  {
+  deliver_domain = addr->domain;
+  deliver_localpart = addr->local_part;
+  }
+return rc;
+
+/* Syntax errors in the verify argument come here. */
+
+BAD_VERIFY:
+*log_msgptr = string_sprintf("expected \"sender[=address]\", \"recipient\", "
+  "\"header_syntax\" or \"header_sender\" at start of ACL condition "
+  "\"verify %s\"", arg);
+return ERROR;
+}
+
+
+
+
+/*************************************************
+*        Check argument for control= modifier    *
+*************************************************/
+
+/* Called from acl_check_condition() below
+
+Arguments:
+  arg         the argument string for control=
+  pptr        set to point to the terminating character
+  where       which ACL we are in
+  log_msgptr  for error messages
+
+Returns:      CONTROL_xxx value
+*/
+
+static int
+decode_control(uschar *arg, uschar **pptr, int where, uschar **log_msgptr)
+{
+int len;
+control_def *d;
+
+for (d = controls_list;
+     d < controls_list + sizeof(controls_list)/sizeof(control_def);
+     d++)
+  {
+  len = Ustrlen(d->name);
+  if (Ustrncmp(d->name, arg, len) == 0) break;
+  }
+
+if (d >= controls_list + sizeof(controls_list)/sizeof(control_def) ||
+   (arg[len] != 0 && (!d->has_option || arg[len] != '/')))
+  {
+  *log_msgptr = string_sprintf("syntax error in \"control=%s\"", arg);
+  return CONTROL_ERROR;
+  }
+
+if (where > d->where_max)
+  {
+  *log_msgptr = string_sprintf("cannot use \"control=%s\" in %s ACL",
+    arg, acl_wherenames[where]);
+  return CONTROL_ERROR;
+  }
+
+*pptr = arg + len;
+return d->value;
+}
+
+
+
+/*************************************************
+*   Handle conditions/modifiers on an ACL item   *
+*************************************************/
+
+/* Called from acl_check() below.
+
+Arguments:
+  verb         ACL verb
+  cb           ACL condition block - if NULL, result is OK
+  where        where called from
+  addr         the address being checked for RCPT, or NULL
+  level        the nesting level
+  epp          pointer to pass back TRUE if "endpass" encountered
+                 (applies only to "accept" and "discard")
+  user_msgptr  user message pointer
+  log_msgptr   log message pointer
+  basic_errno  pointer to where to put verify error
+
+Returns:       OK        - all conditions are met
+               DISCARD   - an "acl" condition returned DISCARD - only allowed
+                             for "accept" or "discard" verbs
+               FAIL      - at least one condition fails
+               FAIL_DROP - an "acl" condition returned FAIL_DROP
+               DEFER     - can't tell at the moment (typically, lookup defer,
+                             but can be temporary callout problem)
+               ERROR     - ERROR from nested ACL or expansion failure or other
+                             error
+*/
+
+static int
+acl_check_condition(int verb, acl_condition_block *cb, int where,
+  address_item *addr, int level, BOOL *epp, uschar **user_msgptr,
+  uschar **log_msgptr, int *basic_errno)
+{
+uschar *user_message = NULL;
+uschar *log_message = NULL;
+uschar *p;
+int rc = OK;
+
+for (; cb != NULL; cb = cb->next)
+  {
+  uschar *arg;
+
+  /* The message and log_message items set up messages to be used in
+  case of rejection. They are expanded later. */
+
+  if (cb->type == ACLC_MESSAGE)
+    {
+    user_message = cb->arg;
+    continue;
+    }
+
+  if (cb->type == ACLC_LOG_MESSAGE)
+    {
+    log_message = cb->arg;
+    continue;
+    }
+
+  /* The endpass "condition" just sets a flag to show it occurred. This is
+  checked at compile time to be on an "accept" or "discard" item. */
+
+  if (cb->type == ACLC_ENDPASS)
+    {
+    *epp = TRUE;
+    continue;
+    }
+
+  /* For other conditions and modifiers, the argument is expanded now for some
+  of them, but not for all, because expansion happens down in some lower level
+  checking functions in some cases. */
+
+  if (cond_expand_at_top[cb->type])
+    {
+    arg = expand_string(cb->arg);
+    if (arg == NULL)
+      {
+      if (expand_string_forcedfail) continue;
+      *log_msgptr = string_sprintf("failed to expand ACL string \"%s\": %s",
+        cb->arg, expand_string_message);
+      return search_find_defer? DEFER : ERROR;
+      }
+    }
+  else arg = cb->arg;
+
+  /* Show condition, and expanded condition if it's different */
+
+  HDEBUG(D_acl)
+    {
+    int lhswidth = 0;
+    debug_printf("check %s%s %n",
+      (!cond_modifiers[cb->type] && cb->u.negated)? "!":"",
+      conditions[cb->type], &lhswidth);
+
+    if (cb->type == ACLC_SET)
+      {
+      int n = cb->u.varnumber;
+      int t = (n < ACL_C_MAX)? 'c' : 'm';
+      if (n >= ACL_C_MAX) n -= ACL_C_MAX;
+      debug_printf("acl_%c%d ", t, n);
+      lhswidth += 7;
+      }
+
+    debug_printf("= %s\n", cb->arg);
+
+    if (arg != cb->arg)
+      debug_printf("%.*s= %s\n", lhswidth,
+      US"                             ", CS arg);
+    }
+
+  /* Check that this condition makes sense at this time */
+
+  if ((cond_forbids[cb->type] & (1 << where)) != 0)
+    {
+    *log_msgptr = string_sprintf("cannot %s %s condition in %s ACL",
+      cond_modifiers[cb->type]? "use" : "test",
+      conditions[cb->type], acl_wherenames[where]);
+    return ERROR;
+    }
+
+  /* Run the appropriate test for each condition, or take the appropriate
+  action for the remaining modifiers. */
+
+  switch(cb->type)
+    {
+    /* A nested ACL that returns "discard" makes sense only for an "accept" or
+    "discard" verb. */
+
+    case ACLC_ACL:
+    rc = acl_check_internal(where, addr, arg, level+1, user_msgptr, log_msgptr);
+    if (rc == DISCARD && verb != ACL_ACCEPT && verb != ACL_DISCARD)
+      {
+      *log_msgptr = string_sprintf("nested ACL returned \"discard\" for "
+        "\"%s\" command (only allowed with \"accept\" or \"discard\")",
+        verbs[verb]);
+      return ERROR;
+      }
+    break;
+
+    case ACLC_AUTHENTICATED:
+    rc = (sender_host_authenticated == NULL)? FAIL :
+      match_isinlist(sender_host_authenticated, &arg, 0, NULL, NULL, MCL_STRING,
+        TRUE, NULL);
+    break;
+
+    case ACLC_CONDITION:
+    if (Ustrspn(arg, "0123456789") == Ustrlen(arg))     /* Digits, or empty */
+      rc = (Uatoi(arg) == 0)? FAIL : OK;
+    else
+      rc = (strcmpic(arg, US"no") == 0 ||
+            strcmpic(arg, US"false") == 0)? FAIL :
+           (strcmpic(arg, US"yes") == 0 ||
+            strcmpic(arg, US"true") == 0)? OK : DEFER;
+    if (rc == DEFER)
+      *log_msgptr = string_sprintf("invalid \"condition\" value \"%s\"", arg);
+    break;
+
+    case ACLC_CONTROL:
+    switch (decode_control(arg, &p, where, log_msgptr))
+      {
+      case CONTROL_ERROR:
+      return ERROR;
+
+      case CONTROL_CASEFUL_LOCAL_PART:
+      deliver_localpart = addr->cc_local_part;
+      break;
+
+      case CONTROL_CASELOWER_LOCAL_PART:
+      deliver_localpart = addr->lc_local_part;
+      break;
+
+      case CONTROL_ENFORCE_SYNC:
+      smtp_enforce_sync = TRUE;
+      break;
+
+      case CONTROL_NO_ENFORCE_SYNC:
+      smtp_enforce_sync = FALSE;
+      break;
+
+      case CONTROL_NO_MULTILINE:
+      no_multiline_responses = TRUE;
+      break;
+
+      case CONTROL_FREEZE:
+      deliver_freeze = TRUE;
+      deliver_frozen_at = time(NULL);
+      break;
+
+      case CONTROL_QUEUE_ONLY:
+      queue_only_policy = TRUE;
+      break;
+
+      case CONTROL_SUBMISSION:
+      submission_mode = TRUE;
+      if (Ustrncmp(p, "/domain=", 8) == 0)
+        {
+        submission_domain = string_copy(p+8);
+        }
+      else if (*p != 0)
+        {
+        *log_msgptr = string_sprintf("syntax error in argument for "
+          "\"control\" modifier \"%s\"", arg);
+        return ERROR;
+        }
+      break;
+      }
+    break;
+
+    case ACLC_DELAY:
+      {
+      int delay = readconf_readtime(arg, 0, FALSE);
+      if (delay < 0)
+        {
+        *log_msgptr = string_sprintf("syntax error in argument for \"delay\" "
+          "modifier: \"%s\" is not a time value", arg);
+        return ERROR;
+        }
+      else
+        {
+        HDEBUG(D_acl) debug_printf("delay modifier requests %d-second delay\n",
+          delay);
+        if (host_checking)
+          {
+          HDEBUG(D_acl)
+            debug_printf("delay skipped in -bh checking mode\n");
+          }
+        else sleep(delay);
+        }
+      }
+    break;
+
+    case ACLC_DNSLISTS:
+    rc = verify_check_dnsbl(&arg);
+    break;
+
+    case ACLC_DOMAINS:
+    rc = match_isinlist(addr->domain, &arg, 0, &domainlist_anchor,
+      addr->domain_cache, MCL_DOMAIN, TRUE, &deliver_domain_data);
+    break;
+
+    /* The value in tls_cipher is the full cipher name, for example,
+    TLSv1:DES-CBC3-SHA:168, whereas the values to test for are just the
+    cipher names such as DES-CBC3-SHA. But program defensively. We don't know
+    what may in practice come out of the SSL library - which at the time of
+    writing is poorly documented. */
+
+    case ACLC_ENCRYPTED:
+    if (tls_cipher == NULL) rc = FAIL; else
+      {
+      uschar *endcipher = NULL;
+      uschar *cipher = Ustrchr(tls_cipher, ':');
+      if (cipher == NULL) cipher = tls_cipher; else
+        {
+        endcipher = Ustrchr(++cipher, ':');
+        if (endcipher != NULL) *endcipher = 0;
+        }
+      rc = match_isinlist(cipher, &arg, 0, NULL, NULL, MCL_STRING, TRUE, NULL);
+      if (endcipher != NULL) *endcipher = ':';
+      }
+    break;
+
+    /* Use verify_check_this_host() instead of verify_check_host() so that
+    we can pass over &host_data to catch any looked up data. Once it has been
+    set, it retains its value so that it's still there if another ACL verb
+    comes through here and uses the cache. However, we must put it into
+    permanent store in case it is also expected to be used in a subsequent
+    message in the same SMTP connection. */
+
+    case ACLC_HOSTS:
+    rc = verify_check_this_host(&arg, sender_host_cache, NULL,
+      (sender_host_address == NULL)? US"" : sender_host_address, &host_data);
+    if (host_data != NULL) host_data = string_copy_malloc(host_data);
+    break;
+
+    case ACLC_LOCAL_PARTS:
+    rc = match_isinlist(addr->cc_local_part, &arg, 0,
+      &localpartlist_anchor, addr->localpart_cache, MCL_LOCALPART, TRUE,
+      &deliver_localpart_data);
+    break;
+
+    case ACLC_LOGWRITE:
+      {
+      int logbits = 0;
+      uschar *s = arg;
+      if (*s == ':')
+        {
+        s++;
+        while (*s != ':')
+          {
+          if (Ustrncmp(s, "main", 4) == 0)
+            { logbits |= LOG_MAIN; s += 4; }
+          else if (Ustrncmp(s, "panic", 5) == 0)
+            { logbits |= LOG_PANIC; s += 5; }
+          else if (Ustrncmp(s, "reject", 6) == 0)
+            { logbits |= LOG_REJECT; s += 6; }
+          else
+            {
+            logbits = LOG_MAIN|LOG_PANIC;
+            s = string_sprintf(":unknown log name in \"%s\" in "
+              "\"logwrite\" in %s ACL", arg, acl_wherenames[where]);
+            }
+          if (*s == ',') s++;
+          }
+        s++;
+        }
+      while (isspace(*s)) s++;
+      if (logbits == 0) logbits = LOG_MAIN;
+      log_write(0, logbits, "%s", string_printing(s));
+      }
+    break;
+
+    case ACLC_RECIPIENTS:
+    rc = match_address_list(addr->address, TRUE, TRUE, &arg, NULL, -1, 0,
+      &recipient_data);
+    break;
+
+    case ACLC_SENDER_DOMAINS:
+      {
+      uschar *sdomain;
+      sdomain = Ustrrchr(sender_address, '@');
+      sdomain = (sdomain == NULL)? US"" : sdomain + 1;
+      rc = match_isinlist(sdomain, &arg, 0, &domainlist_anchor,
+        sender_domain_cache, MCL_DOMAIN, TRUE, NULL);
+      }
+    break;
+
+    case ACLC_SENDERS:
+    rc = match_address_list(sender_address, TRUE, TRUE, &arg,
+      sender_address_cache, -1, 0, &sender_data);
+    break;
+
+    /* Connection variables must persist forever */
+
+    case ACLC_SET:
+      {
+      int old_pool = store_pool;
+      if (cb->u.varnumber < ACL_C_MAX) store_pool = POOL_PERM;
+      acl_var[cb->u.varnumber] = string_copy(arg);
+      store_pool = old_pool;
+      }
+    break;
+
+    /* If the verb is WARN, discard any user message from verification, because
+    such messages are SMTP responses, not header additions. The latter come
+    only from explicit "message" modifiers. */
+
+    case ACLC_VERIFY:
+    rc = acl_verify(where, addr, arg, user_msgptr, log_msgptr, basic_errno);
+    if (verb == ACL_WARN) *user_msgptr = NULL;
+    break;
+
+    default:
+    log_write(0, LOG_MAIN|LOG_PANIC_DIE, "internal ACL error: unknown "
+      "condition %d", cb->type);
+    break;
+    }
+
+  /* If a condition was negated, invert OK/FAIL. */
+
+  if (!cond_modifiers[cb->type] && cb->u.negated)
+    {
+    if (rc == OK) rc = FAIL;
+      else if (rc == FAIL || rc == FAIL_DROP) rc = OK;
+    }
+
+  if (rc != OK) break;   /* Conditions loop */
+  }
+
+
+/* If the result is the one for which "message" and/or "log_message" are used,
+handle the values of these options. Most verbs have but a single return for
+which the messages are relevant, but for "discard", it's useful to have the log
+message both when it succeeds and when it fails. Also, for an "accept" that
+appears in a QUIT ACL, we want to handle the user message. Since only "accept"
+and "warn" are permitted in that ACL, we don't need to test the verb.
+
+These modifiers act in different ways:
+
+"message" is a user message that will be included in an SMTP response. Unless
+it is empty, it overrides any previously set user message.
+
+"log_message" is a non-user message, and it adds to any existing non-user
+message that is already set.
+
+If there isn't a log message set, we make it the same as the user message. */
+
+if (((rc == FAIL_DROP)? FAIL : rc) == msgcond[verb] ||
+    (verb == ACL_DISCARD && rc == OK) ||
+    (where == ACL_WHERE_QUIT))
+  {
+  uschar *expmessage;
+
+  /* If the verb is "warn", messages generated by conditions (verification or
+  nested ACLs) are discarded. Only messages specified at this level are used.
+  However, the value of an existing message is available in $acl_verify_message
+  during expansions. */
+
+  uschar *old_user_msgptr = *user_msgptr;
+  uschar *old_log_msgptr = (*log_msgptr != NULL)? *log_msgptr : old_user_msgptr;
+
+  if (verb == ACL_WARN) *log_msgptr = *user_msgptr = NULL;
+
+  if (user_message != NULL)
+    {
+    acl_verify_message = old_user_msgptr;
+    expmessage = expand_string(user_message);
+    if (expmessage == NULL)
+      {
+      if (!expand_string_forcedfail)
+        log_write(0, LOG_MAIN|LOG_PANIC, "failed to expand ACL message \"%s\": %s",
+          user_message, expand_string_message);
+      }
+    else if (expmessage[0] != 0) *user_msgptr = expmessage;
+    }
+
+  if (log_message != NULL)
+    {
+    acl_verify_message = old_log_msgptr;
+    expmessage = expand_string(log_message);
+    if (expmessage == NULL)
+      {
+      if (!expand_string_forcedfail)
+        log_write(0, LOG_MAIN|LOG_PANIC, "failed to expand ACL message \"%s\": %s",
+          log_message, expand_string_message);
+      }
+    else if (expmessage[0] != 0)
+      {
+      *log_msgptr = (*log_msgptr == NULL)? expmessage :
+        string_sprintf("%s: %s", expmessage, *log_msgptr);
+      }
+    }
+
+  /* If no log message, default it to the user message */
+
+  if (*log_msgptr == NULL) *log_msgptr = *user_msgptr;
+  }
+
+acl_verify_message = NULL;
+return rc;
+}
+
+
+
+
+
+/*************************************************
+*        Get line from a literal ACL             *
+*************************************************/
+
+/* This function is passed to acl_read() in order to extract individual lines
+of a literal ACL, which we access via static pointers. We can destroy the
+contents because this is called only once (the compiled ACL is remembered).
+
+This code is intended to treat the data in the same way as lines in the main
+Exim configuration file. That is:
+
+  . Leading spaces are ignored.
+
+  . A \ at the end of a line is a continuation - trailing spaces after the \
+    are permitted (this is because I don't believe in making invisible things
+    significant). Leading spaces on the continued part of a line are ignored.
+
+  . Physical lines starting (significantly) with # are totally ignored, and
+    may appear within a sequence of backslash-continued lines.
+
+  . Blank lines are ignored, but will end a sequence of continuations.
+
+Arguments: none
+Returns:   a pointer to the next line
+*/
+
+
+static uschar *acl_text;          /* Current pointer in the text */
+static uschar *acl_text_end;      /* Points one past the terminating '0' */
+
+
+static uschar *
+acl_getline(void)
+{
+uschar *yield;
+
+/* This loop handles leading blank lines and comments. */
+
+for(;;)
+  {
+  while (isspace(*acl_text)) acl_text++;   /* Leading spaces/empty lines */
+  if (*acl_text == 0) return NULL;         /* No more data */
+  yield = acl_text;                        /* Potential data line */
+
+  while (*acl_text != 0 && *acl_text != '\n') acl_text++;
+
+  /* If we hit the end before a newline, we have the whole logical line. If
+  it's a comment, there's no more data to be given. Otherwise, yield it. */
+
+  if (*acl_text == 0) return (*yield == '#')? NULL : yield;
+
+  /* After reaching a newline, end this loop if the physical line does not
+  start with '#'. If it does, it's a comment, and the loop continues. */
+
+  if (*yield != '#') break;
+  }
+
+/* This loop handles continuations. We know we have some real data, ending in
+newline. See if there is a continuation marker at the end (ignoring trailing
+white space). We know that *yield is not white space, so no need to test for
+cont > yield in the backwards scanning loop. */
+
+for(;;)
+  {
+  uschar *cont;
+  for (cont = acl_text - 1; isspace(*cont); cont--);
+
+  /* If no continuation follows, we are done. Mark the end of the line and
+  return it. */
+
+  if (*cont != '\\')
+    {
+    *acl_text++ = 0;
+    return yield;
+    }
+
+  /* We have encountered a continuation. Skip over whitespace at the start of
+  the next line, and indeed the whole of the next line or lines if they are
+  comment lines. */
+
+  for (;;)
+    {
+    while (*(++acl_text) == ' ' || *acl_text == '\t');
+    if (*acl_text != '#') break;
+    while (*(++acl_text) != 0 && *acl_text != '\n');
+    }
+
+  /* We have the start of a continuation line. Move all the rest of the data
+  to join onto the previous line, and then find its end. If the end is not a
+  newline, we are done. Otherwise loop to look for another continuation. */
+
+  memmove(cont, acl_text, acl_text_end - acl_text);
+  acl_text_end -= acl_text - cont;
+  acl_text = cont;
+  while (*acl_text != 0 && *acl_text != '\n') acl_text++;
+  if (*acl_text == 0) return yield;
+  }
+
+/* Control does not reach here */
+}
+
+
+
+
+
+/*************************************************
+*        Check access using an ACL               *
+*************************************************/
+
+/* This function is called from address_check. It may recurse via
+acl_check_condition() - hence the use of a level to stop looping. The ACL is
+passed as a string which is expanded. A forced failure implies no access check
+is required. If the result is a single word, it is taken as the name of an ACL
+which is sought in the global ACL tree. Otherwise, it is taken as literal ACL
+text, complete with newlines, and parsed as such. In both cases, the ACL check
+is then run. This function uses an auxiliary function for acl_read() to call
+for reading individual lines of a literal ACL. This is acl_getline(), which
+appears immediately above.
+
+Arguments:
+  where        where called from
+  addr         address item when called from RCPT; otherwise NULL
+  s            the input string; NULL is the same as an empty ACL => DENY
+  level        the nesting level
+  user_msgptr  where to put a user error (for SMTP response)
+  log_msgptr   where to put a logging message (not for SMTP response)
+
+Returns:       OK         access is granted
+               DISCARD    access is apparently granted...
+               FAIL       access is denied
+               FAIL_DROP  access is denied; drop the connection
+               DEFER      can't tell at the moment
+               ERROR      disaster
+*/
+
+static int
+acl_check_internal(int where, address_item *addr, uschar *s, int level,
+  uschar **user_msgptr, uschar **log_msgptr)
+{
+int fd = -1;
+acl_block *acl = NULL;
+uschar *acl_name = US"inline ACL";
+uschar *ss;
+
+/* Catch configuration loops */
+
+if (level > 20)
+  {
+  *log_msgptr = US"ACL nested too deep: possible loop";
+  return ERROR;
+  }
+
+if (s == NULL)
+  {
+  HDEBUG(D_acl) debug_printf("ACL is NULL: implicit DENY\n");
+  return FAIL;
+  }
+
+/* At top level, we expand the incoming string. At lower levels, it has already
+been expanded as part of condition processing. */
+
+if (level == 0)
+  {
+  ss = expand_string(s);
+  if (ss == NULL)
+    {
+    if (expand_string_forcedfail) return OK;
+    *log_msgptr = string_sprintf("failed to expand ACL string \"%s\": %s", s,
+      expand_string_message);
+    return ERROR;
+    }
+  }
+else ss = s;
+
+while (isspace(*ss))ss++;
+
+/* If we can't find a named ACL, the default is to parse it as an inline one.
+(Unless it begins with a slash; non-existent files give rise to an error.) */
+
+acl_text = ss;
+
+/* Handle the case of a string that does not contain any spaces. Look for a
+named ACL among those read from the configuration, or a previously read file.
+It is possible that the pointer to the ACL is NULL if the configuration
+contains a name with no data. If not found, and the text begins with '/',
+read an ACL from a file, and save it so it can be re-used. */
+
+if (Ustrchr(ss, ' ') == NULL)
+  {
+  tree_node *t = tree_search(acl_anchor, ss);
+  if (t != NULL)
+    {
+    acl = (acl_block *)(t->data.ptr);
+    if (acl == NULL)
+      {
+      HDEBUG(D_acl) debug_printf("ACL \"%s\" is empty: implicit DENY\n", ss);
+      return FAIL;
+      }
+    acl_name = string_sprintf("ACL \"%s\"", ss);
+    HDEBUG(D_acl) debug_printf("using ACL \"%s\"\n", ss);
+    }
+
+  else if (*ss == '/')
+    {
+    struct stat statbuf;
+    fd = Uopen(ss, O_RDONLY, 0);
+    if (fd < 0)
+      {
+      *log_msgptr = string_sprintf("failed to open ACL file \"%s\": %s", ss,
+        strerror(errno));
+      return ERROR;
+      }
+
+    if (fstat(fd, &statbuf) != 0)
+      {
+      *log_msgptr = string_sprintf("failed to fstat ACL file \"%s\": %s", ss,
+        strerror(errno));
+      return ERROR;
+      }
+
+    acl_text = store_get(statbuf.st_size + 1);
+    acl_text_end = acl_text + statbuf.st_size + 1;
+
+    if (read(fd, acl_text, statbuf.st_size) != statbuf.st_size)
+      {
+      *log_msgptr = string_sprintf("failed to read ACL file \"%s\": %s",
+        ss, strerror(errno));
+      return ERROR;
+      }
+    acl_text[statbuf.st_size] = 0;
+    close(fd);
+
+    acl_name = string_sprintf("ACL \"%s\"", ss);
+    HDEBUG(D_acl) debug_printf("read ACL from file %s\n", ss);
+    }
+  }
+
+/* Parse an ACL that is still in text form. If it came from a file, remember it
+in the ACL tree, having read it into the POOL_PERM store pool so that it
+persists between multiple messages. */
+
+if (acl == NULL)
+  {
+  int old_pool = store_pool;
+  if (fd >= 0) store_pool = POOL_PERM;
+  acl = acl_read(acl_getline, log_msgptr);
+  store_pool = old_pool;
+  if (acl == NULL && *log_msgptr != NULL) return ERROR;
+  if (fd >= 0)
+    {
+    tree_node *t = store_get_perm(sizeof(tree_node) + Ustrlen(ss));
+    Ustrcpy(t->name, ss);
+    t->data.ptr = acl;
+    (void)tree_insertnode(&acl_anchor, t);
+    }
+  }
+
+/* Now we have an ACL to use. It's possible it may be NULL. */
+
+while (acl != NULL)
+  {
+  int cond;
+  int basic_errno = 0;
+  BOOL endpass_seen = FALSE;
+
+  *log_msgptr = *user_msgptr = NULL;
+  acl_temp_details = FALSE;
+
+  if (where == ACL_WHERE_QUIT &&
+      acl->verb != ACL_ACCEPT &&
+      acl->verb != ACL_WARN)
+    {
+    *log_msgptr = string_sprintf("\"%s\" is not allowed in a QUIT ACL",
+      verbs[acl->verb]);
+    return ERROR;
+    }
+
+  HDEBUG(D_acl) debug_printf("processing \"%s\"\n", verbs[acl->verb]);
+
+  /* Clear out any search error message from a previous check before testing
+  this condition. */
+
+  search_error_message = NULL;
+  cond = acl_check_condition(acl->verb, acl->condition, where, addr, level,
+    &endpass_seen, user_msgptr, log_msgptr, &basic_errno);
+
+  /* Handle special returns: DEFER causes a return except on a WARN verb;
+  ERROR always causes a return. */
+
+  switch (cond)
+    {
+    case DEFER:
+    HDEBUG(D_acl) debug_printf("%s: condition test deferred\n", verbs[acl->verb]);
+    if (basic_errno != ERRNO_CALLOUTDEFER)
+      {
+      if (search_error_message != NULL && *search_error_message != 0)
+        *log_msgptr = search_error_message;
+      if (smtp_return_error_details) acl_temp_details = TRUE;
+      }
+    else
+      {
+      acl_temp_details = TRUE;
+      }
+    if (acl->verb != ACL_WARN) return DEFER;
+    break;
+
+    default:      /* Paranoia */
+    case ERROR:
+    HDEBUG(D_acl) debug_printf("%s: condition test error\n", verbs[acl->verb]);
+    return ERROR;
+
+    case OK:
+    HDEBUG(D_acl) debug_printf("%s: condition test succeeded\n",
+      verbs[acl->verb]);
+    break;
+
+    case FAIL:
+    HDEBUG(D_acl) debug_printf("%s: condition test failed\n", verbs[acl->verb]);
+    break;
+
+    /* DISCARD and DROP can happen only from a nested ACL condition, and
+    DISCARD can happen only for an "accept" or "discard" verb. */
+
+    case DISCARD:
+    HDEBUG(D_acl) debug_printf("%s: condition test yielded \"discard\"\n",
+      verbs[acl->verb]);
+    break;
+
+    case FAIL_DROP:
+    HDEBUG(D_acl) debug_printf("%s: condition test yielded \"drop\"\n",
+      verbs[acl->verb]);
+    break;
+    }
+
+  /* At this point, cond for most verbs is either OK or FAIL or (as a result of
+  a nested ACL condition) FAIL_DROP. However, for WARN, cond may be DEFER, and
+  for ACCEPT and DISCARD, it may be DISCARD after a nested ACL call. */
+
+  switch(acl->verb)
+    {
+    case ACL_ACCEPT:
+    if (cond == OK || cond == DISCARD) return cond;
+    if (endpass_seen)
+      {
+      HDEBUG(D_acl) debug_printf("accept: endpass encountered - denying access\n");
+      return cond;
+      }
+    break;
+
+    case ACL_DEFER:
+    if (cond == OK)
+      {
+      acl_temp_details = TRUE;
+      return DEFER;
+      }
+    break;
+
+    case ACL_DENY:
+    if (cond == OK) return FAIL;
+    break;
+
+    case ACL_DISCARD:
+    if (cond == OK || cond == DISCARD) return DISCARD;
+    if (endpass_seen)
+      {
+      HDEBUG(D_acl) debug_printf("discard: endpass encountered - denying access\n");
+      return cond;
+      }
+    break;
+
+    case ACL_DROP:
+    if (cond == OK) return FAIL_DROP;
+    break;
+
+    case ACL_REQUIRE:
+    if (cond != OK) return cond;
+    break;
+
+    case ACL_WARN:
+    if (cond == OK)
+      acl_warn(where, *user_msgptr, *log_msgptr);
+    else if (cond == DEFER)
+      acl_warn(where, NULL, string_sprintf("ACL \"warn\" statement skipped: "
+        "condition test deferred: %s",
+        (*log_msgptr == NULL)? US"" : *log_msgptr));
+    *log_msgptr = *user_msgptr = NULL;  /* In case implicit DENY follows */
+    break;
+
+    default:
+    log_write(0, LOG_MAIN|LOG_PANIC_DIE, "internal ACL error: unknown verb %d",
+      acl->verb);
+    break;
+    }
+
+  /* Pass to the next ACL item */
+
+  acl = acl->next;
+  }
+
+/* We have reached the end of the ACL. This is an implicit DENY. */
+
+HDEBUG(D_acl) debug_printf("end of %s: implicit DENY\n", acl_name);
+return FAIL;
+}
+
+
+/*************************************************
+*        Check access using an ACL               *
+*************************************************/
+
+/* This is the external interface for ACL checks. It sets up an address and the
+expansions for $domain and $local_part when called after RCPT, then calls
+acl_check_internal() to do the actual work.
+
+Arguments:
+  where        ACL_WHERE_xxxx indicating where called from
+  data_string  RCPT address, or SMTP command argument, or NULL
+  s            the input string; NULL is the same as an empty ACL => DENY
+  user_msgptr  where to put a user error (for SMTP response)
+  log_msgptr   where to put a logging message (not for SMTP response)
+
+Returns:       OK         access is granted by an ACCEPT verb
+               DISCARD    access is granted by a DISCARD verb
+               FAIL       access is denied
+               FAIL_DROP  access is denied; drop the connection
+               DEFER      can't tell at the moment
+               ERROR      disaster
+*/
+
+int
+acl_check(int where, uschar *data_string, uschar *s, uschar **user_msgptr,
+  uschar **log_msgptr)
+{
+int rc;
+address_item adb;
+address_item *addr;
+
+*user_msgptr = *log_msgptr = NULL;
+sender_verified_failed = NULL;
+
+if (where == ACL_WHERE_RCPT)
+  {
+  adb = address_defaults;
+  addr = &adb;
+  addr->address = data_string;
+  if (deliver_split_address(addr) == DEFER)
+    {
+    *log_msgptr = US"defer in percent_hack_domains check";
+    return DEFER;
+    }
+  deliver_domain = addr->domain;
+  deliver_localpart = addr->local_part;
+  }
+else
+  {
+  addr = NULL;
+  smtp_command_argument = data_string;
+  }
+
+rc = acl_check_internal(where, addr, s, 0, user_msgptr, log_msgptr);
+
+smtp_command_argument = deliver_domain =
+  deliver_localpart = deliver_address_data = NULL;
+
+/* A DISCARD response is permitted only for message ACLs, excluding the PREDATA
+ACL, which is really in the middle of an SMTP command. */
+
+if (rc == DISCARD)
+  {
+  if (where > ACL_WHERE_NOTSMTP || where == ACL_WHERE_PREDATA)
+    {
+    log_write(0, LOG_MAIN|LOG_PANIC, "\"discard\" verb not allowed in %s "
+      "ACL", acl_wherenames[where]);
+    return ERROR;
+    }
+  return DISCARD;
+  }
+
+/* A DROP response is not permitted from MAILAUTH */
+
+if (rc == FAIL_DROP && where == ACL_WHERE_MAILAUTH)
+  {
+  log_write(0, LOG_MAIN|LOG_PANIC, "\"drop\" verb not allowed in %s "
+    "ACL", acl_wherenames[where]);
+  return ERROR;
+  }
+
+/* Before giving an error response, take a look at the length of any user
+message, and split it up into multiple lines if possible. */
+
+if (rc != OK && *user_msgptr != NULL && Ustrlen(*user_msgptr) > 75)
+  {
+  uschar *s = *user_msgptr = string_copy(*user_msgptr);
+  uschar *ss = s;
+
+  for (;;)
+    {
+    int i = 0;
+    while (i < 75 && *ss != 0 && *ss != '\n') ss++, i++;
+    if (*ss == 0) break;
+    if (*ss == '\n')
+      s = ++ss;
+    else
+      {
+      uschar *t = ss + 1;
+      uschar *tt = NULL;
+      while (--t > s + 35)
+        {
+        if (*t == ' ')
+          {
+          if (t[-1] == ':') { tt = t; break; }
+          if (tt == NULL) tt = t;
+          }
+        }
+
+      if (tt == NULL)          /* Can't split behind - try ahead */
+        {
+        t = ss + 1;
+        while (*t != 0)
+          {
+          if (*t == ' ' || *t == '\n')
+            { tt = t; break; }
+          t++;
+          }
+        }
+
+      if (tt == NULL) break;   /* Can't find anywhere to split */
+      *tt = '\n';
+      s = ss = tt+1;
+      }
+    }
+  }
+
+return rc;
+}
+
+/* End of acl.c */
diff --git a/src/src/aliases.default b/src/src/aliases.default
new file mode 100644 (file)
index 0000000..9d2c505
--- /dev/null
@@ -0,0 +1,42 @@
+# $Cambridge: exim/src/src/aliases.default,v 1.1 2004/10/07 10:39:01 ph10 Exp $
+
+# Default aliases file, installed by Exim. This file contains no real aliases.
+# You should edit it to taste.
+
+
+# The following alias is required by the mail RFCs 2821 and 2822.
+# Set it to the address of a HUMAN who deals with this system's mail problems.
+
+# postmaster: someone@your.domain
+
+# It is also common to set the following alias so that if anybody replies to a
+# bounce message from this host, the reply goes to the postmaster.
+
+# mailer-daemon: postmaster
+
+
+# You should also set up an alias for messages to root, because it is not
+# usually a good idea to deliver mail as root.
+
+# root: postmaster
+
+# It is a good idea to redirect any messages sent to system accounts so that
+# they don't just get ignored. Here are some common examples:
+
+# bin: root
+# daemon: root
+# ftp: root
+# nobody: root
+# operator: root
+# uucp: root
+
+# You should check your /etc/passwd for any others.
+
+
+# Other commonly enountered aliases are:
+#
+# abuse:       the person dealing with network and mail abuse
+# hostmaster:  the person dealing with DNS problems
+# webmaster:   the person dealing with your web site
+
+####
diff --git a/src/src/buildconfig.c b/src/src/buildconfig.c
new file mode 100644 (file)
index 0000000..6dd0d51
--- /dev/null
@@ -0,0 +1,748 @@
+/* $Cambridge: exim/src/src/buildconfig.c,v 1.1 2004/10/07 10:39:01 ph10 Exp $ */
+
+/*************************************************
+*     Exim - an Internet mail transport agent    *
+*************************************************/
+
+/* Copyright (c) University of Cambridge 1995 - 2004 */
+/* See the file NOTICE for conditions of use and distribution. */
+
+
+/*************************************************
+*       Build configuration header for Exim      *
+*************************************************/
+
+/* This auxiliary program builds the file config.h by the following
+process:
+
+First it reads Makefile, looking for certain OS-specific definitions which it
+uses to define macros. Then it reads the defaults file config.h.defaults.
+
+The defaults file contains normal C #define statements for various macros; if
+the name of a macro is found in the environment, the environment value replaces
+the default. If the default #define does not contain any value, then that macro
+is not copied to the created file unless there is some value in the
+environment.
+
+This program is compiled and run as part of the Make process and is not
+normally called independently. */
+
+
+#include <ctype.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/types.h>
+#include <pwd.h>
+#include <grp.h>
+
+typedef struct {
+  char *name;
+  int *flag;
+} have_item;
+
+typedef struct {
+  char *name;
+  char *data;
+} save_item;
+
+static char *db_opts[] = { "", "USE_DB", "USE_GDBM", "USE_TDB" };
+
+static int have_ipv6 = 0;
+static int have_iconv = 0;
+
+static char errno_quota[256];
+static char ostype[256];
+static char cc[256];
+
+/* If any entry is an initial substring of another, the longer one must
+appear first. */
+
+static have_item have_list[] = {
+  { "HAVE_IPV6",      &have_ipv6 },
+  { "HAVE_ICONV",     &have_iconv },
+  { NULL, NULL}
+};
+
+static save_item save_list[] = {
+  { "ERRNO_QUOTA",    errno_quota },
+  { "OSTYPE",         ostype },
+  { "CC",             cc },
+  { NULL, NULL}
+};
+
+
+/* Subroutine to check a string for precisely one instance of "%s". If not,
+bomb out. */
+
+void
+check_percent_ess(char *value, char *name)
+{
+int OK = 0;
+char *p = strstr(value, "%s");
+if (p != NULL) OK = strstr(p+2, "%s") == NULL;
+if (!OK)
+  {
+  printf("\n*** \"%s\" (%s) must contain precisely one occurrence of\n"
+    "*** \"%%s\". Please review your build-time configuration.\n\n/", value,
+    name);
+  exit(1);
+  }
+}
+
+
+/* Main program */
+
+int
+main(int argc, char **argv)
+{
+FILE *base;
+FILE *new;
+int last_initial = 'A';
+int linecount = 0;
+int have_auth = 0;
+int in_local_makefile = 0;
+int use_which_db = 0;
+int use_which_db_in_local_makefile = 0;
+int support_crypteq = 0;
+char buffer[1024];
+
+if (argc != 1)
+  {
+  printf("*** Buildconfig: called with incorrect arguments\n");
+  exit(1);
+  }
+
+new = fopen("config.h", "wb");
+if (new == NULL)
+  {
+  printf("*** Buildconfig: failed to open config.h for output\n");
+  exit(1);
+  }
+
+printf("Building configuration file config.h\n");
+
+fprintf(new, "/*************************************************\n");
+fprintf(new, "*           Configuration header for Exim        *\n");
+fprintf(new, "*************************************************/\n\n");
+
+fprintf(new, "/* This file was automatically generated from Makefile and "
+  "config.h.defaults,\n");
+fprintf(new, "using values specified in the configuration file Local/Makefile.\n");
+fprintf(new, "Do not edit it. Instead, edit Local/Makefile and "
+  "rerun make. */\n\n");
+
+/* First, search the makefile for certain settings */
+
+base = fopen("Makefile", "rb");
+if (base == NULL)
+  {
+  printf("*** Buildconfig: failed to open Makefile\n");
+  fclose(new);
+  exit(1);
+  }
+
+errno_quota[0] = 0;    /* no over-riding value set */
+ostype[0] = 0;         /* just in case */
+cc[0] = 0;
+
+while (fgets(buffer, sizeof(buffer), base) != NULL)
+  {
+  int i;
+  have_item *h;
+  save_item *s;
+  char *p = buffer + (int)strlen(buffer);
+  linecount++;
+  while (p > buffer && isspace((unsigned char)p[-1])) p--;
+  *p = 0;
+  p = buffer;
+  while (isspace((unsigned char)*p)) p++;
+
+  /* Notice when we hit the user's makefile */
+
+  if (strcmp(p, "# From Local/Makefile") == 0)
+    {
+    in_local_makefile = 1;
+    continue;
+    }
+
+  /* Remember the last DB option setting. If we hit two in the user's
+  Makefile, complain. */
+
+  for (i = 1; i < sizeof(db_opts)/sizeof(char *); i++)
+    {
+    int len = (int)strlen(db_opts[i]);
+    if (strncmp(p, db_opts[i], len) == 0 && (p[len] == ' ' || p[len] == '='))
+      {
+      if (in_local_makefile)
+        {
+        if (use_which_db_in_local_makefile)
+          {
+          printf("*** Only one of USE_DB, USE_GDBM, or USE_TDB should be "
+            "defined in Local/Makefile\n");
+          exit(1);
+          }
+        use_which_db_in_local_makefile = 1;
+        }
+      use_which_db = i;
+      break;
+      }
+    }
+  if (i < sizeof(db_opts)/sizeof(char *)) continue;
+
+  /* Items where we just save a boolean */
+
+  for (h = have_list; h->name != NULL; h++)
+    {
+    int len = (int)strlen(h->name);
+    if (strncmp(p, h->name, len) == 0)
+      {
+      p += len;
+      while (isspace((unsigned char)*p)) p++;
+      if (*p++ != '=')
+        {
+        printf("*** Buildconfig: syntax error in Makefile line %d\n", linecount);
+        exit(1);
+        }
+      while (isspace((unsigned char)*p)) p++;
+      if (strcmp(p, "YES") == 0 || strcmp(p, "yes") == 0) *(h->flag) = 1;
+        else *(h->flag) = 0;   /* Must reset in case multiple instances */
+      break;
+      }
+    }
+
+  if (h->name != NULL) continue;
+
+  /* Items where we save the complete string */
+
+  for (s = save_list; s->name != NULL; s++)
+    {
+    int len = (int)strlen(s->name);
+    if (strncmp(p, s->name, len) == 0)
+      {
+      p += len;
+      while (isspace((unsigned char)*p)) p++;
+      if (*p++ != '=')
+        {
+        printf("*** Buildconfig: syntax error in Makefile line %d\n", linecount);
+        exit(1);
+        }
+      while (isspace((unsigned char)*p)) p++;
+      strcpy(s->data, p);
+      }
+    }
+  }
+
+fprintf(new, "#define HAVE_IPV6             %s\n",
+  have_ipv6? "TRUE" : "FALSE");
+
+fprintf(new, "#define HAVE_ICONV            %s\n",
+  have_iconv? "TRUE" : "FALSE");
+
+if (errno_quota[0] != 0)
+  fprintf(new, "\n#define ERRNO_QUOTA           %s\n", errno_quota);
+
+if (strcmp(cc, "gcc") == 0 && strstr(ostype, "IRIX") != NULL)
+  {
+  fprintf(new, "\n/* This switch includes the code to fix the inet_ntoa() */");
+  fprintf(new, "\n/* bug when using gcc on an IRIX system. */");
+  fprintf(new, "\n#define USE_INET_NTOA_FIX");
+  }
+
+fprintf(new, "\n");
+fclose(base);
+
+
+/* Now handle the macros listed in the defaults */
+
+base = fopen("../src/config.h.defaults", "rb");
+if (base == NULL)
+  {
+  printf("*** Buildconfig: failed to open ../src/config.h.defaults\n");
+  fclose(new);
+  exit(1);
+  }
+
+while (fgets(buffer, sizeof(buffer), base) != NULL)
+  {
+  int i;
+  char name[256];
+  char *value;
+  char *p = buffer;
+  char *q = name;
+
+  while (*p == ' ' || *p == '\t') p++;
+
+  if (strncmp(p, "#define ", 8) != 0) continue;
+
+  p += 8;
+  while (*p == ' ' || *p == '\t') p++;
+
+  if (*p < last_initial) fprintf(new, "\n");
+  last_initial = *p;
+
+  while (*p && (isalnum((unsigned char)*p) || *p == '_')) *q++ = *p++;
+  *q = 0;
+
+  /* USE_DB, USE_GDBM, and USE_TDB are special cases. We want to have only
+  one of them set. The scan of the Makefile has saved which was the last one
+  encountered. */
+
+  for (i = 1; i < sizeof(db_opts)/sizeof(char *); i++)
+    {
+    if (strcmp(name, db_opts[i]) == 0)
+      {
+      if (use_which_db == i)
+        fprintf(new, "#define %s %.*syes\n", db_opts[i],
+          21 - (int)strlen(db_opts[i]), "                         ");
+      else
+        fprintf(new, "/* %s not set */\n", name);
+      break;
+      }
+    }
+  if (i < sizeof(db_opts)/sizeof(char *)) continue;
+
+  /* EXIM_USER is a special case. We look in the environment for EXIM_USER or
+  EXIM_UID (the latter for backward compatibility with Exim 3). If the value is
+  not numeric, we look up the user, and default the GID if found. Otherwise,
+  EXIM_GROUP or EXIM_GID must be in the environment. */
+
+  if (strcmp(name, "EXIM_UID") == 0)
+    {
+    uid_t uid = 0;
+    gid_t gid = 0;
+    int gid_set = 0;
+    char *username = NULL;
+    char *groupname = NULL;
+    char *s;
+    char *user = getenv("EXIM_USER");
+    char *group = getenv("EXIM_GROUP");
+
+    if (user == NULL) user = getenv("EXIM_UID");
+    if (group == NULL) group = getenv("EXIM_GID");
+
+    if (user == NULL)
+      {
+      printf("\n*** EXIM_USER has not been defined in any of the Makefiles in "
+        "the\n    \"Local\" directory. Please review your build-time "
+        "configuration.\n\n");
+      return 1;
+      }
+
+    while (isspace((unsigned char)(*user))) user++;
+    if (*user == 0)
+      {
+      printf("\n*** EXIM_USER is defined as an empty string in one of the "
+        "files\n    in the \"Local\" directory. Please review your build-time"
+        "\n    configuration.\n\n");
+      return 1;
+      }
+
+    for (s = user; *s != 0; s++)
+      {
+      if (iscntrl((unsigned char)(*s)))
+        {
+        printf("\n*** EXIM_USER contains the control character 0x%02X in one "
+          "of the files\n    in the \"Local\" directory. Please review your "
+          "build-time\n    configuration.\n\n", *s);
+        return 1;
+        }
+      }
+
+    /* Numeric uid given */
+
+    if (user[strspn(user, "0123456789")] == 0)
+      {
+      uid = (uid_t)atoi(user);
+      }
+
+    /* User name given. Normally, we look up the uid right away. However,
+    people building binary distributions sometimes want to retain the name till
+    runtime. This is supported if the name begins "ref:". */
+
+    else if (strncmp(user, "ref:", 4) == 0)
+      {
+      user += 4;
+      while (isspace(*user)) user++;
+      username = user;
+      gid_set = 1;
+      }
+
+    else
+      {
+      struct passwd *pw = getpwnam(user);
+      if (pw == NULL)
+        {
+        printf("\n*** User \"%s\" (specified in one of the Makefiles) does not "
+          "exist.\n    Please review your build-time configuration.\n\n",
+          user);
+        return 1;
+        }
+
+      uid = pw->pw_uid;
+      gid = pw->pw_gid;
+      gid_set = 1;
+      }
+
+    /* Use explicit group if set. */
+
+    if (group != NULL)
+      {
+      while (isspace((unsigned char)(*group))) group++;
+      if (*group == 0)
+        {
+        printf("\n*** EXIM_GROUP is defined as an empty string in one of "
+          "the files in the\n    \"Local\" directory. ");
+        if (gid_set)
+          {
+          printf("If you want the Exim group to be taken from the\n    "
+            "password data for the Exim user, just remove the EXIM_GROUP "
+            "setting.\n    Otherwise, p");
+          }
+        else printf("EXIM_USER is defined numerically, so there is no"
+          "\n    default for EXIM_GROUP and you must set it explicitly.\n    P");
+        printf("lease review your build-time configuration.\n\n");
+        return 1;
+        }
+
+      for (s = group; *s != 0; s++)
+        {
+        if (iscntrl((unsigned char)(*s)))
+          {
+          printf("\n*** EXIM_GROUP contains the control character 0x%02X in one "
+            "of the files\n    in the \"Local\" directory. Please review your "
+            "build-time\n    configuration.\n\n", *s);
+          return 1;
+          }
+        }
+
+      /* Group name given. This may be by reference or to be looked up now,
+      as for user. */
+
+      if (strncmp(group, "ref:", 4) == 0)
+        {
+        group += 4;
+        while (isspace(*group)) group++;
+        groupname = group;
+        }
+
+      else if (username != NULL)
+        {
+        groupname = group;
+        }
+
+      else if (group[strspn(group, "0123456789")] == 0)
+        {
+        gid = (gid_t)atoi(group);
+        }
+
+      else
+        {
+        struct group *gr = getgrnam(group);
+        if (gr == NULL)
+          {
+          printf("\n*** Group \"%s\" (specified in one of the Makefiles) does "
+            "not exist.\n   Please review your build-time configuration.\n\n",
+            group);
+          return 1;
+          }
+        gid = gr->gr_gid;
+        }
+      }
+
+    /* Else trouble unless found in passwd file with user */
+
+    else if (!gid_set)
+      {
+      printf("\n*** No group set for Exim. Please review your build-time "
+        "configuration.\n\n");
+      return 1;
+      }
+
+    /* Output user and group names or uid/gid. When names are set, uid/gid
+    are set to zero but will be replaced at runtime. */
+
+    if (username != NULL)
+      fprintf(new, "#define EXIM_USERNAME         \"%s\"\n", username);
+    if (groupname != NULL)
+      fprintf(new, "#define EXIM_GROUPNAME        \"%s\"\n", groupname);
+
+    fprintf(new, "#define EXIM_UID              %d\n", (int)uid);
+    fprintf(new, "#define EXIM_GID              %d\n", (int)gid);
+    continue;
+    }
+
+  /* CONFIGURE_OWNER is a special case. We look in the environment for
+  CONFIGURE_OWNER. If the value is not numeric, we look up the user. A lot of
+  this code is similar to that for EXIM_USER, but we aren't interested in a gid
+  here, and it's all optional, so just keep it separate. */
+
+  if (strcmp(name, "CONFIGURE_OWNER") == 0)
+    {
+    uid_t uid = 0;
+    char *s;
+    char *username = NULL;
+    char *user = getenv("CONFIGURE_OWNER");
+
+    if (user == NULL) user = "";
+    while (isspace((unsigned char)(*user))) user++;
+    if (*user == 0)
+      {
+      fprintf(new, "/* %s not set */\n", name);
+      continue;
+      }
+
+    for (s = user; *s != 0; s++)
+      {
+      if (iscntrl((unsigned char)(*s)))
+        {
+        printf("\n*** CONFIGURE_OWNER contains the control character 0x%02X in "
+          "one of the files\n    in the \"Local\" directory. Please review "
+          "your build-time\n    configuration.\n\n", *s);
+        return 1;
+        }
+      }
+
+    /* Numeric uid given */
+
+    if (user[strspn(user, "0123456789")] == 0)
+      {
+      uid = (uid_t)atoi(user);
+      }
+
+    /* User name given. Normally, we look up the uid right away. However,
+    people building binary distributions sometimes want to retain the name till
+    runtime. This is supported if the name begins "ref:". */
+
+    else if (strncmp(user, "ref:", 4) == 0)
+      {
+      user += 4;
+      while (isspace(*user)) user++;
+      username = user;
+      }
+
+    else
+      {
+      struct passwd *pw = getpwnam(user);
+      if (pw == NULL)
+        {
+        printf("\n*** User \"%s\" (specified in one of the Makefiles) does not "
+          "exist.\n    Please review your build-time configuration.\n\n",
+          user);
+        return 1;
+        }
+
+      uid = pw->pw_uid;
+      }
+
+    /* Output user and group names or uid/gid. When names are set, uid/gid
+    are set to zero but will be replaced at runtime. */
+
+    if (username != NULL)
+      fprintf(new, "#define CONFIGURE_OWNERNAME         \"%s\"\n", username);
+    fprintf(new, "#define CONFIGURE_OWNER              %d\n", (int)uid);
+    continue;
+    }
+
+  /* FIXED_NEVER_USERS is another special case. Look up the uid values and
+  create suitable initialization data for a vector. */
+
+  if (strcmp(name, "FIXED_NEVER_USERS") == 0)
+    {
+    char *list = getenv("FIXED_NEVER_USERS");
+    if (list == NULL)
+      {
+      fprintf(new, "#define FIXED_NEVER_USERS     0\n");
+      }
+    else
+      {
+      int count = 1;
+      int i;
+      uid_t *vector;
+      char *p = list;
+      while (*p != 0) if (*p++ == ':') count++;
+
+      vector = malloc((count+1) * sizeof(uid_t));
+      vector[0] = (uid_t)count;
+
+      for (i = 1; i <= count; list++, i++)
+        {
+        char name[64];
+        p = list;
+        while (*list != 0 && *list != ':') list++;
+        strncpy(name, p, list-p);
+        name[list-p] = 0;
+
+        if (name[strspn(name, "0123456789")] == 0)
+          {
+          vector[i] = (uid_t)atoi(name);
+          }
+        else
+          {
+          struct passwd *pw = getpwnam(name);
+          if (pw == NULL)
+            {
+            printf("\n*** User \"%s\" (specified for FIXED_NEVER_USERS in one of the Makefiles) does not "
+              "exist.\n    Please review your build-time configuration.\n\n",
+              name);
+            return 1;
+            }
+          vector[i] = pw->pw_uid;
+          }
+        }
+      fprintf(new, "#define FIXED_NEVER_USERS     %d, ", count);
+      for (i = 1; i <= count - 1; i++)
+        fprintf(new, "%d, ", (unsigned int)vector[i]);
+      fprintf(new, "%d\n", (unsigned int)vector[i]);
+      }
+    continue;
+    }
+
+  /* Otherwise, check whether a value exists in the environment. Remember if
+  it is an AUTH setting or SUPPORT_CRYPTEQ. */
+
+  if ((value = getenv(name)) != NULL)
+    {
+    int len;
+    len = 21 - (int)strlen(name);
+
+    if (strncmp(name, "AUTH_", 5) == 0) have_auth = 1;
+    if (strncmp(name, "SUPPORT_CRYPTEQ", 15) == 0) support_crypteq = 1;
+
+    /* The text value of LDAP_LIB_TYPE refers to a macro that gets set. */
+
+    if (strcmp(name, "LDAP_LIB_TYPE") == 0)
+      {
+      if (strcmp(value, "NETSCAPE") == 0 ||
+          strcmp(value, "UMICHIGAN") == 0 ||
+          strcmp(value, "OPENLDAP1") == 0 ||
+          strcmp(value, "OPENLDAP2") == 0 ||
+          strcmp(value, "SOLARIS") == 0 ||
+          strcmp(value, "SOLARIS7") == 0)              /* Compatibility */
+        {
+        fprintf(new, "#define LDAP_LIB_%s\n", value);
+        }
+      else
+        {
+        printf("\n*** LDAP_LIB_TYPE=%s is not a recognized LDAP library type."
+          "\n*** Please review your build-time configuration.\n\n", value);
+        return 1;
+        }
+      }
+
+    else if (strcmp(name, "RADIUS_LIB_TYPE") == 0)
+      {
+      if (strcmp(value, "RADIUSCLIENT") == 0 ||
+          strcmp(value, "RADLIB") == 0)
+        {
+        fprintf(new, "#define RADIUS_LIB_%s\n", value);
+        }
+      else
+        {
+        printf("\n*** RADIUS_LIB_TYPE=%s is not a recognized RADIUS library type."
+          "\n*** Please review your build-time configuration.\n\n", value);
+        return 1;
+        }
+      }
+
+    /* Other macros get set to the environment value. */
+
+    else
+      {
+      fprintf(new, "#define %s ", name);
+      while(len-- > 0) fputc(' ', new);
+
+      /* LOG_FILE_PATH is now messy because it can be a path containing %s or
+      it can be "syslog" or ":syslog" or "syslog:path" or even "path:syslog". */
+
+      if (strcmp(name, "LOG_FILE_PATH") == 0)
+        {
+        char *ss = value;
+        for(;;)
+          {
+          char *pp;
+          char *sss = strchr(ss, ':');
+          if (sss != NULL)
+            {
+            strncpy(buffer, ss, sss-ss);
+            buffer[sss-ss] = 0;  /* For empty case */
+            }
+          else strcpy(buffer, ss);
+          pp = buffer + (int)strlen(buffer);
+          while (pp > buffer && isspace((unsigned char)pp[-1])) pp--;
+          *pp = 0;
+          if (buffer[0] != 0 && strcmp(buffer, "syslog") != 0)
+            check_percent_ess(buffer, name);
+          if (sss == NULL) break;
+          ss = sss + 1;
+          while (isspace((unsigned char)*ss)) ss++;
+          }
+        fprintf(new, "\"%s\"\n", value);
+        }
+
+      /* Timezone values and HEADERS_CHARSET get quoted */
+
+      else if (strcmp(name, "TIMEZONE_DEFAULT") == 0||
+               strcmp(name, "HEADERS_CHARSET") == 0)
+        fprintf(new, "\"%s\"\n", value);
+
+      /* For others, quote any paths and don't quote anything else */
+
+      else
+        {
+        if (value[0] == '/') fprintf(new, "\"%s\"\n", value);
+          else fprintf(new, "%s\n", value);
+        }
+      }
+    }
+
+  /* Value not defined in the environment; use the default */
+
+  else
+    {
+    char *t = p;
+    while (*p == ' ' || *p == '\t') p++;
+    if (*p != '\n') fputs(buffer, new); else
+      {
+      *t = 0;
+      if (strcmp(name, "BIN_DIRECTORY")   == 0 ||
+          strcmp(name, "CONFIGURE_FILE")  == 0)
+        {
+        printf("\n*** %s has not been defined in any of the Makefiles in the\n"
+          "    \"Local\" directory. "
+          "Please review your build-time configuration.\n\n", name);
+        return 1;
+        }
+
+      if (strcmp(name, "TIMEZONE_DEFAULT") == 0)
+        {
+        char *tz = getenv("TZ");
+        fprintf(new, "#define TIMEZONE_DEFAULT      ");
+        if (tz == NULL) fprintf(new, "NULL\n"); else
+          fprintf(new, "\"%s\"\n", tz);
+        }
+
+      else fprintf(new, "/* %s not set */\n", name);
+      }
+    }
+  }
+
+fclose(base);
+
+/* If any AUTH macros were defined, ensure that SUPPORT_CRYPTEQ is also
+defined. */
+
+if (have_auth)
+  {
+  if (!support_crypteq) fprintf(new, "/* Force SUPPORT_CRYPTEQ for AUTH */\n"
+    "#define SUPPORT_CRYPTEQ\n");
+  }
+
+/* End off */
+
+fprintf(new, "\n/* End of config.h */\n");
+fclose(new);
+return 0;
+}
+
+/* End of buildconfig.c */
diff --git a/src/src/child.c b/src/src/child.c
new file mode 100644 (file)
index 0000000..1c48a4e
--- /dev/null
@@ -0,0 +1,454 @@
+/* $Cambridge: exim/src/src/child.c,v 1.1 2004/10/07 10:39:01 ph10 Exp $ */
+
+/*************************************************
+*     Exim - an Internet mail transport agent    *
+*************************************************/
+
+/* Copyright (c) University of Cambridge 1995 - 2004 */
+/* See the file NOTICE for conditions of use and distribution. */
+
+
+#include "exim.h"
+
+static void (*oldsignal)(int);
+
+
+/*************************************************
+*          Ensure an fd has a given value        *
+*************************************************/
+
+/* This function is called when we want to ensure that a certain fd has a
+specific value (one of 0, 1, 2). If it hasn't got it already, close the value
+we want, duplicate the fd, then close the old one.
+
+Arguments:
+  oldfd        original fd
+  newfd        the fd we want
+
+Returns:       nothing
+*/
+
+static void
+force_fd(int oldfd, int newfd)
+{
+if (oldfd == newfd) return;
+close(newfd);
+dup2(oldfd, newfd);
+close(oldfd);
+}
+
+
+
+/*************************************************
+*   Build argv list and optionally re-exec Exim  *
+*************************************************/
+
+/* This function is called when Exim wants to re-exec (overlay) itself in the
+current process. This is different to child_open_exim(), which runs another
+Exim process in parallel (but it then calls this function). The function's
+basic job is to build the argv list according to the values of current options
+settings. There is a basic list that all calls require, and an additional list
+that some do not require. Further additions can be given as additional
+arguments. An option specifies whether the exec() is actually to happen, and if
+so, what is to be done if it fails.
+
+Arguments:
+  exec_type      CEE_RETURN_ARGV => don't exec; return the argv list
+                 CEE_EXEC_EXIT   => just exit() on exec failure
+                 CEE_EXEC_PANIC  => panic-die on exec failure
+  kill_v         if TRUE, don't pass on the D_v flag
+  pcount         if not NULL, points to extra size of argv required, and if
+                   CEE_RETURN_ARGV is specified, it is updated to give the
+                   number of slots used
+  minimal        TRUE if only minimal argv is required
+  acount         number of additional arguments
+  ...            further values to add to argv
+
+Returns:         if CEE_RETURN_ARGV is given, returns a pointer to argv;
+                 otherwise, does not return
+*/
+
+uschar **
+child_exec_exim(int exec_type, BOOL kill_v, int *pcount, BOOL minimal,
+  int acount, ...)
+{
+int first_special = -1;
+int n = 0;
+int extra = (pcount != NULL)? *pcount : 0;
+uschar **argv =
+  store_get((extra + acount + MAX_CLMACROS + 16) * sizeof(char *));
+
+/* In all case, the list starts out with the path, any macros, and a changed
+config file. */
+
+argv[n++] = exim_path;
+if (clmacro_count > 0)
+  {
+  memcpy(argv + n, clmacros, clmacro_count * sizeof(uschar *));
+  n += clmacro_count;
+  }
+if (config_changed)
+  {
+  argv[n++] = US"-C";
+  argv[n++] = config_main_filename;
+  }
+
+/* These values are added only for non-minimal cases. If debug_selector is
+precisely D_v, we have to assume this was started by a non-admin user, and
+we suppress the flag when requested. (This happens when passing on an SMTP
+connection, and after ETRN.) If there's more debugging going on, an admin user
+was involved, so we do pass it on. */
+
+if (!minimal)
+  {
+  if (debug_selector == D_v)
+    {
+    if (!kill_v) argv[n++] = US"-v";
+    }
+  else
+    {
+    if (debug_selector != 0)
+      argv[n++] = string_sprintf("-d=0x%x", debug_selector);
+    }
+  if (dont_deliver) argv[n++] = US"-N";
+  if (queue_smtp) argv[n++] = US"-odqs";
+  if (synchronous_delivery) argv[n++] = US"-odi";
+  if (connection_max_messages >= 0)
+    argv[n++] = string_sprintf("-oB%d", connection_max_messages);
+  }
+
+/* Now add in any others that are in the call. Remember which they were,
+for more helpful diagnosis on failure. */
+
+if (acount > 0)
+  {
+  va_list ap;
+  va_start(ap, acount);
+  first_special = n;
+  while (acount-- > 0)
+    argv[n++] = va_arg(ap, uschar *);
+  va_end(ap);
+  }
+
+/* Terminate the list, and return it, if that is what is wanted. */
+
+argv[n] = NULL;
+if (exec_type == CEE_RETURN_ARGV)
+  {
+  if (pcount != NULL) *pcount = n;
+  return argv;
+  }
+
+/* Otherwise, do the exec() here, and handle the consequences of an unexpected
+failure. We know that there will always be at least one extra option in the
+call when exec() is done here, so it can be used to add to the panic data. */
+
+DEBUG(D_exec) debug_print_argv(argv);
+exim_nullstd();                            /* Make sure std{in,out,err} exist */
+execv(CS argv[0], (char *const *)argv);
+
+log_write(0,
+  LOG_MAIN | ((exec_type == CEE_EXEC_EXIT)? LOG_PANIC : LOG_PANIC_DIE),
+  "re-exec of exim (%s) with %s failed: %s", exim_path, argv[first_special],
+  strerror(errno));
+
+/* Get here if exec_type == CEE_EXEC_EXIT.
+Note: this must be _exit(), not exit(). */
+
+_exit(EX_EXECFAILED);
+
+return NULL;   /* To keep compilers happy */
+}
+
+
+
+
+/*************************************************
+*          Create a child Exim process           *
+*************************************************/
+
+/* This function is called when Exim wants to run a parallel instance of itself
+in order to inject a message via the standard input. The function creates a
+child process and runs Exim in it. It sets up a pipe to the standard input of
+the new process, and returns that to the caller via fdptr. The function returns
+the pid of the new process, or -1 if things go wrong. If debug_fd is
+non-negative, it is passed as stderr.
+
+Argument: fdptr   pointer to int for the stdin fd
+Returns:          pid of the created process or -1 if anything has gone wrong
+*/
+
+pid_t
+child_open_exim(int *fdptr)
+{
+int pfd[2];
+int save_errno;
+pid_t pid;
+
+/* Create the pipe and fork the process. Ensure that SIGCHLD is set to
+SIG_DFL before forking, so that the child process can be waited for. We
+sometimes get here with it set otherwise. Save the old state for resetting
+on the wait. */
+
+if (pipe(pfd) != 0) return (pid_t)(-1);
+oldsignal = signal(SIGCHLD, SIG_DFL);
+pid = fork();
+
+/* Child process: make the reading end of the pipe into the standard input and
+close the writing end. If debugging, pass debug_fd as stderr. Then re-exec
+Exim. Failure is signalled with EX_EXECFAILED, but this shouldn't occur! */
+
+if (pid == 0)
+  {
+  force_fd(pfd[pipe_read], 0);
+  close(pfd[pipe_write]);
+  if (debug_fd > 0) force_fd(debug_fd, 2);
+  if (bounce_sender_authentication != NULL)
+    child_exec_exim(CEE_EXEC_EXIT, FALSE, NULL, FALSE, 8,
+      US"-t", US"-oem", US"-oi", US"-f", US"<>", US"-oMas",
+      bounce_sender_authentication, message_id_option);
+  else
+    child_exec_exim(CEE_EXEC_EXIT, FALSE, NULL, FALSE, 6,
+      US"-t", US"-oem", US"-oi", US"-f", US"<>", message_id_option);
+  /* Control does not return here. */
+  }
+
+/* Parent process. Save fork() errno and close the reading end of the stdin
+pipe. */
+
+save_errno = errno;
+close(pfd[pipe_read]);
+
+/* Fork succeeded */
+
+if (pid > 0)
+  {
+  *fdptr = pfd[pipe_write];   /* return writing end of stdin pipe */
+  return pid;                 /* and pid of new process */
+  }
+
+/* Fork failed */
+
+close(pfd[pipe_write]);
+errno = save_errno;
+return (pid_t)(-1);
+}
+
+
+
+
+/*************************************************
+*         Create a non-Exim child process        *
+*************************************************/
+
+/* This function creates a child process and runs the given command in it. It
+sets up pipes to the standard input and output of the new process, and returns
+them to the caller. The standard error is cloned to the output. If there are
+any file descriptors "in the way" in the new process, they are closed. A new
+umask is supplied for the process, and an optional new uid and gid are also
+available. These are used by the queryprogram router to set an unprivileged id.
+The function returns the pid of the new process, or -1 if things go wrong.
+
+Arguments:
+  argv        the argv for exec in the new process
+  envp        the envp for exec in the new process
+  newumask    umask to set in the new process
+  newuid      point to uid for the new process or NULL for no change
+  newgid      point to gid for the new process or NULL for no change
+  infdptr     pointer to int into which the fd of the stdin of the new process
+                is placed
+  outfdptr    pointer to int into which the fd of the stdout/stderr of the new
+                process is placed
+  wd          if not NULL, a path to be handed to chdir() in the new process
+  make_leader if TRUE, make the new process a process group leader
+
+Returns:      the pid of the created process or -1 if anything has gone wrong
+*/
+
+pid_t
+child_open_uid(uschar **argv, uschar **envp, int newumask, uid_t *newuid,
+  gid_t *newgid, int *infdptr, int *outfdptr, uschar *wd, BOOL make_leader)
+{
+int save_errno;
+int inpfd[2], outpfd[2];
+pid_t pid;
+
+/* Create the pipes. */
+
+if (pipe(inpfd) != 0) return (pid_t)(-1);
+if (pipe(outpfd) != 0)
+  {
+  close(inpfd[pipe_read]);
+  close(inpfd[pipe_write]);
+  return (pid_t)(-1);
+  }
+
+/* Fork the process. Ensure that SIGCHLD is set to SIG_DFL before forking, so
+that the child process can be waited for. We sometimes get here with it set
+otherwise. Save the old state for resetting on the wait. */
+
+oldsignal = signal(SIGCHLD, SIG_DFL);
+pid = fork();
+
+/* The child process becomes a process group leader if requested, and then
+organizes the pipes. Any unexpected failure is signalled with EX_EXECFAILED;
+these are all "should never occur" failures, except perhaps for exec failing
+because the command doesn't exist. */
+
+if (pid == 0)
+  {
+  if (make_leader && setpgid(0,0) < 0) goto CHILD_FAILED;
+
+  close(inpfd[pipe_write]);
+  force_fd(inpfd[pipe_read], 0);
+
+  close(outpfd[pipe_read]);
+  force_fd(outpfd[pipe_write], 1);
+
+  close(2);
+  dup2(1, 2);
+
+  /* Set the required environment. If changing uid, ensure that
+  SIGUSR1 is ignored, as the process won't have the privilege to
+  write to the process log. */
+
+  if (newgid != NULL && setgid(*newgid) < 0) goto CHILD_FAILED;
+  if (newuid != NULL)
+    {
+    signal(SIGUSR1, SIG_IGN);
+    if (setuid(*newuid) < 0) goto CHILD_FAILED;
+    }
+  (void)umask(newumask);
+
+  /* Set the working directory if required */
+
+  if (wd != NULL && Uchdir(wd) < 0) goto CHILD_FAILED;
+
+  /* Now do the exec */
+
+  if (envp == NULL) execv(CS argv[0], (char *const *)argv);
+    else execve(CS argv[0], (char *const *)argv, (char *const *)envp);
+
+  /* Failed to execv. Signal this failure using EX_EXECFAILED. We are
+  losing the actual errno we got back, because there is no way to return
+  this. */
+
+  CHILD_FAILED:
+  _exit(EX_EXECFAILED);      /* Note: must be _exit(), NOT exit() */
+  }
+
+/* Parent process. Save any fork failure code, and close the reading end of the
+stdin pipe, and the writing end of the stdout pipe. */
+
+save_errno = errno;
+close(inpfd[pipe_read]);
+close(outpfd[pipe_write]);
+
+/* Fork succeeded; return the input/output pipes and the pid */
+
+if (pid > 0)
+  {
+  *infdptr = inpfd[pipe_write];
+  *outfdptr = outpfd[pipe_read];
+  return pid;
+  }
+
+/* Fork failed; reset fork errno before returning */
+
+close(inpfd[pipe_write]);
+close(outpfd[pipe_read]);
+errno = save_errno;
+return (pid_t)(-1);
+}
+
+
+
+
+/*************************************************
+*    Create child process without uid change     *
+*************************************************/
+
+/* This function is a wrapper for child_open_uid() that doesn't have the uid,
+gid, and working directory changing arguments. It is provided so as to have a
+clean interface for use from local_scan(), but also saves writing NULL
+arguments in other calls.
+
+Arguments:
+  argv        the argv for exec in the new process
+  envp        the envp for exec in the new process
+  newumask    umask to set in the new process
+  infdptr     pointer to int into which the fd of the stdin of the new process
+                is placed
+  outfdptr    pointer to int into which the fd of the stdout/stderr of the new
+                process is placed
+  make_leader if TRUE, make the new process a process group leader
+
+Returns:      the pid of the created process or -1 if anything has gone wrong
+*/
+
+pid_t
+child_open(uschar **argv, uschar **envp, int newumask, int *infdptr,
+  int *outfdptr, BOOL make_leader)
+{
+return child_open_uid(argv, envp, newumask, NULL, NULL, infdptr, outfdptr,
+  NULL, make_leader);
+}
+
+
+
+
+/*************************************************
+*           Close down child process             *
+*************************************************/
+
+/* Wait for the given process to finish, with optional timeout.
+
+Arguments
+  pid:      the pid to wait for
+  timeout:  maximum time to wait; 0 means for as long as it takes
+
+Returns:    >= 0          process terminated by exiting; value is process
+                            ending status; if an execve() failed, the value
+                            is typically 127 (defined as EX_EXECFAILED)
+            < 0 & > -256  process was terminated by a signal; value is the
+                            negation of the signal number
+            -256          timed out
+            -257          other error in wait(); errno still set
+*/
+
+int
+child_close(pid_t pid, int timeout)
+{
+int yield;
+
+if (timeout > 0)
+  {
+  sigalrm_seen = FALSE;
+  alarm(timeout);
+  }
+
+for(;;)
+  {
+  int status;
+  pid_t rc = waitpid(pid, &status, 0);
+  if (rc == pid)
+    {
+    int lowbyte = status & 255;
+    if (lowbyte == 0) yield = (status >> 8) & 255;
+      else yield = -lowbyte;
+    break;
+    }
+  if (rc < 0)
+    {
+    yield = (errno == EINTR && sigalrm_seen)? -256 : -257;
+    break;
+    }
+  }
+
+if (timeout > 0) alarm(0);
+
+signal(SIGCHLD, oldsignal);   /* restore */
+return yield;
+}
+
+/* End of child.c */
diff --git a/src/src/config.h.defaults b/src/src/config.h.defaults
new file mode 100644 (file)
index 0000000..39a17ae
--- /dev/null
@@ -0,0 +1,147 @@
+/* $Cambridge: exim/src/src/config.h.defaults,v 1.1 2004/10/07 10:39:01 ph10 Exp $ */
+
+/*************************************************
+*     Exim - an Internet mail transport agent    *
+*************************************************/
+
+/* Copyright (c) University of Cambridge 1995 - 2004 */
+/* See the file NOTICE for conditions of use and distribution. */
+
+/* The default settings for Exim configuration variables. A #define without
+any data just defines the existence of the variable; it won't get included
+in config.h unless some value is defined in Local/Makefile. */
+
+#define ALT_CONFIG_PREFIX
+#define ALT_CONFIG_ROOT_ONLY
+
+#define APPENDFILE_MODE            0600
+#define APPENDFILE_DIRECTORY_MODE  0700
+#define APPENDFILE_LOCKFILE_MODE   0600
+
+#define AUTH_CRAM_MD5
+#define AUTH_CYRUS_SASL
+#define AUTH_PLAINTEXT
+#define AUTH_SPA
+
+#define BIN_DIRECTORY
+
+#define CONFIGURE_FILE
+#define CONFIGURE_FILE_USE_EUID
+#define CONFIGURE_FILE_USE_NODE
+#define CONFIGURE_OWNER
+#define CYRUS_PWCHECK_SOCKET
+#define CYRUS_SASLAUTHD_SOCKET
+
+#define DEFAULT_CRYPT              crypt
+#define DELIVER_IN_BUFFER_SIZE     8192
+#define DELIVER_OUT_BUFFER_SIZE    8192
+#define DISABLE_D_OPTION
+
+#define EXIMDB_DIRECTORY_MODE      0750
+#define EXIMDB_LOCK_TIMEOUT          60
+#define EXIMDB_LOCKFILE_MODE       0640
+#define EXIMDB_MODE                0640
+
+#define EXIM_PERL
+
+/* Both uid and gid are triggered by this */
+#define EXIM_UID
+
+#define FIXED_NEVER_USERS         "root"
+
+#define HAVE_CRYPT16
+#define HAVE_SA_LEN
+#define HEADERS_CHARSET           "ISO-8859-1"
+#define HEADER_ADD_BUFFER_SIZE     8192
+#define HEADER_MAXSIZE            (1024*1024)
+
+#define INPUT_DIRECTORY_MODE       0750
+#define IPV6_USE_INET_PTON
+
+#define LDAP_LIB_TYPE
+#define LOCAL_SCAN_HAS_OPTIONS
+
+#define LOG_DIRECTORY_MODE         0750
+#define LOG_FILE_PATH
+#define LOG_MODE                   0640
+
+#define LOOKUP_CDB
+#define LOOKUP_DBM
+#define LOOKUP_DNSDB
+#define LOOKUP_DSEARCH
+#define LOOKUP_IBASE
+#define LOOKUP_LDAP
+#define LOOKUP_LSEARCH
+#define LOOKUP_MYSQL
+#define LOOKUP_NIS
+#define LOOKUP_NISPLUS
+#define LOOKUP_ORACLE
+#define LOOKUP_PASSWD
+#define LOOKUP_PGSQL
+#define LOOKUP_TESTDB
+#define LOOKUP_WHOSON
+#define LOOKUP_WILDLSEARCH
+#define LOOKUP_NWILDLSEARCH
+
+#define MAX_FILTER_SIZE           (1024*1024)
+#define MAX_LOCALHOST_NUMBER        256
+#define MAX_INCLUDE_SIZE          (1024*1024)
+#define MAX_INTERFACES              250
+#define MAX_NAMED_LIST               16
+#define MSGLOG_DIRECTORY_MODE      0750
+
+#define PID_FILE_PATH
+
+#define RADIUS_CONFIG_FILE
+#define RADIUS_LIB_TYPE
+
+#define ROUTER_ACCEPT
+#define ROUTER_DNSLOOKUP
+#define ROUTER_IPLITERAL
+#define ROUTER_IPLOOKUP
+#define ROUTER_MANUALROUTE
+#define ROUTER_QUERYPROGRAM
+#define ROUTER_REDIRECT
+
+#define SPOOL_DIRECTORY
+#define SPOOL_DIRECTORY_MODE       0750
+#define SPOOL_MODE                 0640
+#define STRING_SPRINTF_BUFFER_SIZE 8192
+
+#define SUPPORT_A6
+#define SUPPORT_CRYPTEQ
+#define SUPPORT_MAILDIR
+#define SUPPORT_MAILSTORE
+#define SUPPORT_MBX
+#define SUPPORT_MOVE_FROZEN_MESSAGES
+#define SUPPORT_PAM
+#define SUPPORT_TLS
+#define SUPPORT_TRANSLATE_IP_ADDRESS
+
+#define SYSLOG_LOG_PID
+#define SYSLOG_LONG_LINES
+
+#define TIMEZONE_DEFAULT
+#define TMPDIR
+
+#define TRANSPORT_APPENDFILE
+#define TRANSPORT_AUTOREPLY
+#define TRANSPORT_LMTP
+#define TRANSPORT_PIPE
+#define TRANSPORT_SMTP
+
+#define USE_DB
+#define USE_GDBM
+#define USE_GNUTLS
+#define USE_READLINE
+#define USE_TCP_WRAPPERS
+#define USE_TDB
+
+/* Things that are not routinely changed but are nevertheless configurable
+just in case. */
+
+#define DNS_MAXNAME                 256
+#define EXPAND_MAXN                  20
+#define ROOT_UID                      0
+
+/* End of config.h.defaults */
diff --git a/src/src/configure.default b/src/src/configure.default
new file mode 100644 (file)
index 0000000..6a29ec5
--- /dev/null
@@ -0,0 +1,598 @@
+# $Cambridge: exim/src/src/configure.default,v 1.1 2004/10/07 10:39:01 ph10 Exp $
+
+######################################################################
+#                  Runtime configuration file for Exim               #
+######################################################################
+
+
+# This is a default configuration file which will operate correctly in
+# uncomplicated installations. Please see the manual for a complete list
+# of all the runtime configuration options that can be included in a
+# configuration file. There are many more than are mentioned here. The
+# manual is in the file doc/spec.txt in the Exim distribution as a plain
+# ASCII file. Other formats (PostScript, Texinfo, HTML, PDF) are available
+# from the Exim ftp sites. The manual is also online at the Exim web sites.
+
+
+# This file is divided into several parts, all but the first of which are
+# headed by a line starting with the word "begin". Only those parts that
+# are required need to be present. Blank lines, and lines starting with #
+# are ignored.
+
+
+########### IMPORTANT ########## IMPORTANT ########### IMPORTANT ###########
+#                                                                          #
+# Whenever you change Exim's configuration file, you *must* remember to    #
+# HUP the Exim daemon, because it will not pick up the new configuration   #
+# until you do. However, any other Exim processes that are started, for    #
+# example, a process started by an MUA in order to send a message, will    #
+# see the new configuration as soon as it is in place.                     #
+#                                                                          #
+# You do not need to HUP the daemon for changes in auxiliary files that    #
+# are referenced from this file. They are read every time they are used.   #
+#                                                                          #
+# It is usually a good idea to test a new configuration for syntactic      #
+# correctness before installing it (for example, by running the command    #
+# "exim -C /config/file.new -bV").                                         #
+#                                                                          #
+########### IMPORTANT ########## IMPORTANT ########### IMPORTANT ###########
+
+
+
+######################################################################
+#                    MAIN CONFIGURATION SETTINGS                     #
+######################################################################
+
+# Specify your host's canonical name here. This should normally be the fully
+# qualified "official" name of your host. If this option is not set, the
+# uname() function is called to obtain the name. In many cases this does
+# the right thing and you need not set anything explicitly.
+
+# primary_hostname =
+
+
+# The next three settings create two lists of domains and one list of hosts.
+# These lists are referred to later in this configuration using the syntax
+# +local_domains, +relay_to_domains, and +relay_from_hosts, respectively. They
+# are all colon-separated lists:
+
+domainlist local_domains = @
+domainlist relay_to_domains =
+hostlist   relay_from_hosts = 127.0.0.1
+
+# Most straightforward access control requirements can be obtained by
+# appropriate settings of the above options. In more complicated situations, you
+# may need to modify the Access Control List (ACL) which appears later in this
+# file.
+
+# The first setting specifies your local domains, for example:
+#
+#   domainlist local_domains = my.first.domain : my.second.domain
+#
+# You can use "@" to mean "the name of the local host", as in the default
+# setting above. This is the name that is specified by primary_hostname,
+# as specified above (or defaulted). If you do not want to do any local
+# deliveries, remove the "@" from the setting above. If you want to accept mail
+# addressed to your host's literal IP address, for example, mail addressed to
+# "user@[192.168.23.44]", you can add "@[]" as an item in the local domains
+# list. You also need to uncomment "allow_domain_literals" below. This is not
+# recommended for today's Internet.
+
+# The second setting specifies domains for which your host is an incoming relay.
+# If you are not doing any relaying, you should leave the list empty. However,
+# if your host is an MX backup or gateway of some kind for some domains, you
+# must set relay_to_domains to match those domains. For example:
+#
+# domainlist relay_to_domains = *.myco.com : my.friend.org
+#
+# This will allow any host to relay through your host to those domains.
+# See the section of the manual entitled "Control of relaying" for more
+# information.
+
+# The third setting specifies hosts that can use your host as an outgoing relay
+# to any other host on the Internet. Such a setting commonly refers to a
+# complete local network as well as the localhost. For example:
+#
+# hostlist relay_from_hosts = 127.0.0.1 : 192.168.0.0/16
+#
+# The "/16" is a bit mask (CIDR notation), not a number of hosts. Note that you
+# have to include 127.0.0.1 if you want to allow processes on your host to send
+# SMTP mail by using the loopback address. A number of MUAs use this method of
+# sending mail.
+
+
+# All three of these lists may contain many different kinds of item, including
+# wildcarded names, regular expressions, and file lo