[exim-cvs] cvs commit: exim/exim-src/exim_monitor EDITME em…

Αρχική Σελίδα
Delete this message
Reply to this message
Συντάκτης: Philip Hazel
Ημερομηνία:  
Προς: exim-cvs
Αντικείμενο: [exim-cvs] cvs commit: exim/exim-src/exim_monitor EDITME em_StripChart.c em_TextPop.c em_globals.c em_hdr.h em_init.c em_log.c em_main.c em_menu.c em_queue.c em_strip.c em_text.c em_version.c em_xs
ph10 2004/10/07 11:39:03 BST

  Added files:
    exim-src/exim_monitor EDITME em_StripChart.c em_TextPop.c 
                          em_globals.c em_hdr.h em_init.c em_log.c 
                          em_main.c em_menu.c em_queue.c 
                          em_strip.c em_text.c em_version.c 
                          em_xs.c 
    exim-src/src         EDITME acl.c aliases.default 
                         buildconfig.c child.c config.h.defaults 
                         configure.default convert4r3.src 
                         convert4r4.src crypt16.c daemon.c dbfn.c 
                         dbfunctions.h dbstuff.h debug.c deliver.c 
                         directory.c dns.c drtables.c dummies.c 
                         enq.c exicyclog.src exigrep.src exim.c 
                         exim.h exim_checkaccess.src 
                         exim_dbmbuild.c exim_dbutil.c exim_lock.c 
                         eximon.src eximstats.src exinext.src 
                         exipick.src exiqgrep.src exiqsumm.src 
                         exiwhat.src expand.c filter.c 
                         filtertest.c functions.h globals.c 
                         globals.h header.c host.c ip.c 
                         local_scan.c local_scan.h log.c lss.c 
                         macros.h match.c moan.c mytypes.h os.c 
                         osfunctions.h parse.c perl.c queue.c 
                         rda.c readconf.c receive.c retry.c 
                         rewrite.c rfc2047.c route.c search.c 
                         sieve.c smtp_in.c smtp_out.c spool_in.c 
                         spool_out.c store.c store.h string.c 
                         structs.h tls-gnu.c tls-openssl.c tls.c 
                         tod.c transport-filter.src transport.c 
                         tree.c verify.c version.c 
    exim-src/util        cramtest.pl logargs.sh unknownuser.sh 
  Log:
  Start


  Revision  Changes    Path
  1.1       +181 -0    exim/exim-src/exim_monitor/EDITME (new)
  1.1       +507 -0    exim/exim-src/exim_monitor/em_StripChart.c (new)
  1.1       +770 -0    exim/exim-src/exim_monitor/em_TextPop.c (new)
  1.1       +197 -0    exim/exim-src/exim_monitor/em_globals.c (new)
  1.1       +327 -0    exim/exim-src/exim_monitor/em_hdr.h (new)
  1.1       +239 -0    exim/exim-src/exim_monitor/em_init.c (new)
  1.1       +398 -0    exim/exim-src/exim_monitor/em_log.c (new)
  1.1       +939 -0    exim/exim-src/exim_monitor/em_main.c (new)
  1.1       +986 -0    exim/exim-src/exim_monitor/em_menu.c (new)
  1.1       +813 -0    exim/exim-src/exim_monitor/em_queue.c (new)
  1.1       +266 -0    exim/exim-src/exim_monitor/em_strip.c (new)
  1.1       +73 -0     exim/exim-src/exim_monitor/em_text.c (new)
  1.1       +39 -0     exim/exim-src/exim_monitor/em_version.c (new)
  1.1       +46 -0     exim/exim-src/exim_monitor/em_xs.c (new)
  1.1       +1020 -0   exim/exim-src/src/EDITME (new)
  1.1       +2151 -0   exim/exim-src/src/acl.c (new)
  1.1       +42 -0     exim/exim-src/src/aliases.default (new)
  1.1       +748 -0    exim/exim-src/src/buildconfig.c (new)
  1.1       +454 -0    exim/exim-src/src/child.c (new)
  1.1       +147 -0    exim/exim-src/src/config.h.defaults (new)
  1.1       +598 -0    exim/exim-src/src/configure.default (new)
  1.1       +1369 -0   exim/exim-src/src/convert4r3.src (new)
  1.1       +2514 -0   exim/exim-src/src/convert4r4.src (new)
  1.1       +77 -0     exim/exim-src/src/crypt16.c (new)
  1.1       +1802 -0   exim/exim-src/src/daemon.c (new)
  1.1       +673 -0    exim/exim-src/src/dbfn.c (new)
  1.1       +30 -0     exim/exim-src/src/dbfunctions.h (new)
  1.1       +634 -0    exim/exim-src/src/dbstuff.h (new)
  1.1       +237 -0    exim/exim-src/src/debug.c (new)
  1.1       +6738 -0   exim/exim-src/src/deliver.c (new)
  1.1       +91 -0     exim/exim-src/src/directory.c (new)
  1.1       +838 -0    exim/exim-src/src/dns.c (new)
  1.1       +809 -0    exim/exim-src/src/drtables.c (new)
  1.1       +150 -0    exim/exim-src/src/dummies.c (new)
  1.1       +102 -0    exim/exim-src/src/enq.c (new)
  1.1       +254 -0    exim/exim-src/src/exicyclog.src (new)
  1.1       +154 -0    exim/exim-src/src/exigrep.src (new)
  1.1       +4641 -0   exim/exim-src/src/exim.c (new)
  1.1       +461 -0    exim/exim-src/src/exim.h (new)
  1.1       +174 -0    exim/exim-src/src/exim_checkaccess.src (new)
  1.1       +487 -0    exim/exim-src/src/exim_dbmbuild.c (new)
  1.1       +1262 -0   exim/exim-src/src/exim_dbutil.c (new)
  1.1       +602 -0    exim/exim-src/src/exim_lock.c (new)
  1.1       +190 -0    exim/exim-src/src/eximon.src (new)
  1.1       +2856 -0   exim/exim-src/src/eximstats.src (new)
  1.1       +251 -0    exim/exim-src/src/exinext.src (new)
  1.1       +1096 -0   exim/exim-src/src/exipick.src (new)
  1.1       +195 -0    exim/exim-src/src/exiqgrep.src (new)
  1.1       +146 -0    exim/exim-src/src/exiqsumm.src (new)
  1.1       +130 -0    exim/exim-src/src/exiwhat.src (new)
  1.1       +4626 -0   exim/exim-src/src/expand.c (new)
  1.1       +2513 -0   exim/exim-src/src/filter.c (new)
  1.1       +259 -0    exim/exim-src/src/filtertest.c (new)
  1.1       +310 -0    exim/exim-src/src/functions.h (new)
  1.1       +1071 -0   exim/exim-src/src/globals.c (new)
  1.1       +617 -0    exim/exim-src/src/globals.h (new)
  1.1       +444 -0    exim/exim-src/src/header.c (new)
  1.1       +3011 -0   exim/exim-src/src/host.c (new)
  1.1       +369 -0    exim/exim-src/src/ip.c (new)
  1.1       +66 -0     exim/exim-src/src/local_scan.c (new)
  1.1       +166 -0    exim/exim-src/src/local_scan.h (new)
  1.1       +1054 -0   exim/exim-src/src/log.c (new)
  1.1       +144 -0    exim/exim-src/src/lss.c (new)
  1.1       +777 -0    exim/exim-src/src/macros.h (new)
  1.1       +1232 -0   exim/exim-src/src/match.c (new)
  1.1       +701 -0    exim/exim-src/src/moan.c (new)
  1.1       +102 -0    exim/exim-src/src/mytypes.h (new)
  1.1       +810 -0    exim/exim-src/src/os.c (new)
  1.1       +23 -0     exim/exim-src/src/osfunctions.h (new)
  1.1       +1777 -0   exim/exim-src/src/parse.c (new)
  1.1       +174 -0    exim/exim-src/src/perl.c (new)
  1.1       +1366 -0   exim/exim-src/src/queue.c (new)
  1.1       +945 -0    exim/exim-src/src/rda.c (new)
  1.1       +3702 -0   exim/exim-src/src/readconf.c (new)
  1.1       +3247 -0   exim/exim-src/src/receive.c (new)
  1.1       +881 -0    exim/exim-src/src/retry.c (new)
  1.1       +830 -0    exim/exim-src/src/rewrite.c (new)
  1.1       +351 -0    exim/exim-src/src/rfc2047.c (new)
  1.1       +1889 -0   exim/exim-src/src/route.c (new)
  1.1       +817 -0    exim/exim-src/src/search.c (new)
  1.1       +2780 -0   exim/exim-src/src/sieve.c (new)
  1.1       +3547 -0   exim/exim-src/src/smtp_in.c (new)
  1.1       +527 -0    exim/exim-src/src/smtp_out.c (new)
  1.1       +684 -0    exim/exim-src/src/spool_in.c (new)
  1.1       +473 -0    exim/exim-src/src/spool_out.c (new)
  1.1       +554 -0    exim/exim-src/src/store.c (new)
  1.1       +55 -0     exim/exim-src/src/store.h (new)
  1.1       +1502 -0   exim/exim-src/src/string.c (new)
  1.1       +776 -0    exim/exim-src/src/structs.h (new)
  1.1       +1212 -0   exim/exim-src/src/tls-gnu.c (new)
  1.1       +1009 -0   exim/exim-src/src/tls-openssl.c (new)
  1.1       +152 -0    exim/exim-src/src/tls.c (new)
  1.1       +191 -0    exim/exim-src/src/tod.c (new)
  1.1       +84 -0     exim/exim-src/src/transport-filter.src (new)
  1.1       +1798 -0   exim/exim-src/src/transport.c (new)
  1.1       +345 -0    exim/exim-src/src/tree.c (new)
  1.1       +2508 -0   exim/exim-src/src/verify.c (new)
  1.1       +59 -0     exim/exim-src/src/version.c (new)
  1.1       +59 -0     exim/exim-src/util/cramtest.pl (new)
  1.1       +27 -0     exim/exim-src/util/logargs.sh (new)
  1.1       +33 -0     exim/exim-src/util/unknownuser.sh (new)


Index: EDITME
====================================================================
# $Cambridge: exim/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

Index: em_StripChart.c
====================================================================
/* $Cambridge: exim/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@???> 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;
      }
  }


Index: em_TextPop.c
====================================================================
/* $Cambridge: exim/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);
  }


Index: em_globals.c
====================================================================
/* $Cambridge: exim/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 */

  Index: em_hdr.h
  ====================================================================
  /* $Cambridge: exim/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 */

Index: em_init.c
====================================================================
/* $Cambridge: exim/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 */

Index: em_log.c
====================================================================
/* $Cambridge: exim/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 */

Index: em_main.c
====================================================================
/* $Cambridge: exim/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 */


Index: em_menu.c
====================================================================
/* $Cambridge: exim/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 */

Index: em_queue.c
====================================================================
/* $Cambridge: exim/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 */

Index: em_strip.c
====================================================================
/* $Cambridge: exim/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 */

Index: em_text.c
====================================================================
/* $Cambridge: exim/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 */

Index: em_version.c
====================================================================
/* $Cambridge: exim/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 */

Index: em_xs.c
====================================================================
/* $Cambridge: exim/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 */

Index: EDITME
====================================================================
# $Cambridge: exim/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.

Index: acl.c
====================================================================
/* $Cambridge: exim/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 */

Index: aliases.default
====================================================================
# $Cambridge: exim/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@???

# 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


####

Index: buildconfig.c
====================================================================
/* $Cambridge: exim/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 */

Index: child.c
====================================================================
/* $Cambridge: exim/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)
    {


----------------------------------------------
Diff block truncated. (Max lines = 10000)
----------------------------------------------