--- /dev/null
+# $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
--- /dev/null
+/* $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;
+ }
+}
--- /dev/null
+/* $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);
+}
--- /dev/null
+/* $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 */
--- /dev/null
+/* $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 */
--- /dev/null
+/* $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 */
--- /dev/null
+/* $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 */
--- /dev/null
+/* $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 */
+
--- /dev/null
+/* $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 */
--- /dev/null
+/* $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 */
--- /dev/null
+/* $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 */
--- /dev/null
+/* $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 */
--- /dev/null
+/* $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 */
--- /dev/null
+/* $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 */
--- /dev/null
+# $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.
--- /dev/null
+/* $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 */
--- /dev/null
+# $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
+
+####
--- /dev/null
+/* $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 */
--- /dev/null
+/* $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 */
--- /dev/null
+/* $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 */
--- /dev/null
+# $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