summaryrefslogtreecommitdiffstats
path: root/exim_monitor
diff options
context:
space:
mode:
Diffstat (limited to 'exim_monitor')
-rw-r--r--exim_monitor/EDITME179
-rw-r--r--exim_monitor/em_StripChart.c504
-rw-r--r--exim_monitor/em_TextPop.c767
-rw-r--r--exim_monitor/em_globals.c239
-rw-r--r--exim_monitor/em_hdr.h329
-rw-r--r--exim_monitor/em_init.c238
-rw-r--r--exim_monitor/em_log.c411
-rw-r--r--exim_monitor/em_main.c953
-rw-r--r--exim_monitor/em_menu.c930
-rw-r--r--exim_monitor/em_queue.c828
-rw-r--r--exim_monitor/em_strip.c266
-rw-r--r--exim_monitor/em_text.c73
-rw-r--r--exim_monitor/em_version.c65
-rw-r--r--exim_monitor/em_xs.c45
14 files changed, 5827 insertions, 0 deletions
diff --git a/exim_monitor/EDITME b/exim_monitor/EDITME
new file mode 100644
index 0000000..a70b7cb
--- /dev/null
+++ b/exim_monitor/EDITME
@@ -0,0 +1,179 @@
+##################################################
+# The Exim Monitor #
+##################################################
+
+# This is the template for the Exim monitor's main build-time configuration
+# file. It contains settings that are independent of any operating system. It
+# should be edited and then saved to a file called Local/eximon.conf before
+# running the make command to build the monitor, if any settings are required.
+# Local/eximon.conf can be empty if no changes are needed. The examples given
+# here (commented out) are the default settings.
+
+# Any settings made in the configuration file can be overridden at run time
+# by setting up an environment variable with the same name as any of these
+# options, but preceded by EXIMON_, for example, EXIMON_WINDOW_TITLE.
+
+
+##################################################################
+# Set these variables as appropriate for your system #
+##################################################################
+
+# The qualifying name for your domain. The only use made of this is for
+# testing that certain addresses are the same when displaying the
+# log tail, and for shortening sender addresses in the queue display.
+
+# QUALIFY_DOMAIN=
+
+# The default minimum width and height for the whole window are 103 and
+# 162 pixels respectively. This is enough to hold the left-most stripchart
+# and the quit button. The values can be changed here.
+
+# MIN_HEIGHT=162
+# MIN_WIDTH=103
+
+# If you uncomment the following setting, the window will start up at
+# its minimum size, instead of the default maximum. There may be a quick
+# flash during the start-up process. Defining it this way allows it to be
+# overridden by an environment variable.
+
+# START_SMALL=${EXIMON_START_SMALL-yes}
+
+# The title for eximon's main display window. It is possible to have
+# host name of the machine you are running on substituted into the
+# title string. If you include the string ${fullhostname} then the
+# complete name is used. If you include ${hostname} then the full
+# host name will have the string contained in the DOMAIN variable
+# stripped from its right-hand end before being substituted. Any other
+# shell or environment variables may also be included.
+
+# If you use any substitutions, remember to ensure that the $ and {}
+# characters are escaped from the shell, e.g. by using single quotes.
+
+# WINDOW_TITLE="${hostname} eximon"
+
+# The domain that you want to be stripped from the machine's full hostname
+# when forming the short host name for the eximon window title, as
+# described above.
+
+# DOMAIN=
+
+# Parameters for the rolling display of the tail of the exim log file.
+# The width and depth are measured in pixels; LOG_BUFFER specifies the
+# amount of store to set aside for holding the log tail, which is displayed
+# in a scrolling window. When this store is full, the earlier 50% of it
+# is discarded - this is much more efficient that throwing it away line
+# by line. The number given can be followed by the letter K to indicate
+# that the value is in kilobytes. A minimum value of 1K is enforced.
+
+# LOG_DEPTH=300
+# LOG_WIDTH=950
+# LOG_BUFFER=20K
+
+# The font which is used in the log tail display. This is defined in
+# the normal X manner. It must be a "character cell" font, because this
+# is required by the text widget.
+
+# LOG_FONT=-misc-fixed-medium-r-normal-*-14-140-*-*-*-*-iso8859-1
+
+# Parameters for the display of message that are on the exim queue.
+# The width and depth are measured in pixels.
+
+# QUEUE_DEPTH=200
+# QUEUE_WIDTH=950
+
+# The font which is used in the queue display.
+
+# QUEUE_FONT=$LOG_FONT
+
+# When a message has more than one undelivered address, they are listed
+# one below the other. A limit can be placed on the number of addresses
+# displayed for any one message. If there are more, then "..." is used
+# to indicate this.
+
+# QUEUE_MAX_ADDRESSES=10
+
+# The display of the contents of the queue is updated every QUEUE_INTERVAL
+# seconds by default (there is a button to request update).
+
+# QUEUE_INTERVAL=300
+
+# The size of the popup text window that is used for looking at the
+# contents of messages, etc.
+
+# TEXT_DEPTH=200
+
+# The keystroke/mouse-operation that is used to pop up the menu in the
+# queue window is configurable. The default is Shift with the lefthand
+# mouse button. The name of an alternative can be specified in the standard
+# X way of naming these things. With the default configuration for the monitor,
+# individuals can override this by setting the EXIMON_MENU_EVENT environment
+# variable.
+
+# MENU_EVENT='Shift<Btn1Down>'
+
+# When the menu is used to perform an operation on a message, the result of the
+# operation is normally visible in the log window, so Eximon doesn't display
+# the output of the generated Exim command. However, you can request that
+# this output be shown in a separate window by setting ACTION_OUTPUT to "yes".
+# This does not apply to the output generated from attempting to deliver a
+# message, which is always shown.
+
+# ACTION_OUTPUT=no
+
+# When some action is taken on a message, such as freezing it, or changing
+# its recipients, the queue display is normally automatically updated. On
+# systems that have very large queues, this can take some time and be dis-
+# tracting. If this option is set to "no", the queue display is no longer
+# automatically updated after an action is applied to a message.
+
+# ACTION_QUEUE_UPDATE=yes
+
+# When the menu item to display a message's body is invoked, the amount
+# of data is limited to BODY_MAX bytes. This limit is a safety precaution
+# to save the screen scrolling for ever on an enormous message.
+
+# BODY_MAX=20000
+
+# The stripcharts are updated every STRIPCHART_INTERVAL seconds.
+
+# STRIPCHART_INTERVAL=60
+
+# A stripchart showing the count of messages in the queue is always
+# displayed on the left of eximon's window. Its name is "queue" by
+# default, but can be changed by this variable.
+
+# QUEUE_STRIPCHART_NAME=queue
+
+# The following variable may be set to the name of a disc partition. If
+# it is, a stripchart showing the percentage fullness of the partition
+# will be displayed as the second stripchart. This can be used to keep
+# a display of a mail spool partition on the screen.
+
+# SIZE_STRIPCHART=/var/mail
+
+# The name of the size stripchart will be the last component of SIZE_STRIPCHART
+# unless the following variable is set to override it.
+
+# SIZE_STRIPCHART_NAME=space
+
+# The following variable contains a specification of which stripcharts
+# you want eximon to display based on log entries. The string consists of
+# pairs of strings, delimited by slash characters. The first string in each
+# pair is a regular expression that matches some distinguishing feature in a
+# exim log entry.
+
+# Entries that match the expression will be counted and displayed in a
+# stripchart whose title is given by the second string. The string may
+# be continued over several input lines, provided that it is split
+# after a slash, and an additional slash (optionally preceded by white
+# space) is included at the start of the continuation line.
+
+# Stripcharts configured by the following parameter are displayed to the
+# right of the queue and size stripcharts, in the order defined here.
+
+# LOG_STRIPCHARTS='/ <= /in/
+# / => /out/
+# / => .+ R=local/local/
+# / => .+ T=[^ ]*smtp/smtp/'
+
+# End of exim_monitor/EDITME
diff --git a/exim_monitor/em_StripChart.c b/exim_monitor/em_StripChart.c
new file mode 100644
index 0000000..3b94c22
--- /dev/null
+++ b/exim_monitor/em_StripChart.c
@@ -0,0 +1,504 @@
+/***********************************************************
+Copyright 1987, 1988 by Digital Equipment Corporation, Maynard, Massachusetts,
+and the Massachusetts Institute of Technology, Cambridge, Massachusetts.
+
+ All Rights Reserved
+
+Permission to use, copy, modify, and distribute this software and its
+documentation for any purpose and without fee is hereby granted,
+provided that the above copyright notice appear in all copies and that
+both that copyright notice and this permission notice appear in
+supporting documentation, and that the names of Digital or MIT not be
+used in advertising or publicity pertaining to distribution of the
+software without specific, written prior permission.
+
+DIGITAL DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, INCLUDING
+ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO EVENT SHALL
+DIGITAL BE LIABLE FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR
+ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS,
+WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION,
+ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS
+SOFTWARE.
+
+******************************************************************/
+
+/* This is the Athena StripChart widget, slightly hacked by
+Philip Hazel <ph10@cus.cam.ac.uk> in order to give access to
+its repaint_window function so that a repaint can be forced.
+
+The repaint_window function has also been nobbled so that it only
+ever changes scale to 10. There is probably a better way to handle
+this - such as inventing some new resources, but I'm not up to
+that just at the moment.
+
+On SunOS4 there are name clashes when trying to link this with the
+Athena library. So to avoid them, rename a few things by inserting
+"my" at the front of "strip". */
+
+
+#include <stdio.h>
+#include <X11/IntrinsicP.h>
+#include <X11/StringDefs.h>
+#include <X11/Xaw/XawInit.h>
+#include <X11/Xaw/StripCharP.h>
+#include <X11/Xfuncs.h>
+
+#define MS_PER_SEC 1000
+
+/* Private Data */
+
+#define offset(field) XtOffsetOf(StripChartRec, field)
+
+static XtResource resources[] = {
+ {XtNwidth, XtCWidth, XtRDimension, sizeof(Dimension),
+ offset(core.width), XtRImmediate, (XtPointer) 120},
+ {XtNheight, XtCHeight, XtRDimension, sizeof(Dimension),
+ offset(core.height), XtRImmediate, (XtPointer) 120},
+ {XtNupdate, XtCInterval, XtRInt, sizeof(int),
+ offset(strip_chart.update), XtRImmediate, (XtPointer) 10},
+ {XtNminScale, XtCScale, XtRInt, sizeof(int),
+ offset(strip_chart.min_scale), XtRImmediate, (XtPointer) 1},
+ {XtNforeground, XtCForeground, XtRPixel, sizeof(Pixel),
+ offset(strip_chart.fgpixel), XtRString, XtDefaultForeground},
+ {XtNhighlight, XtCForeground, XtRPixel, sizeof(Pixel),
+ offset(strip_chart.hipixel), XtRString, XtDefaultForeground},
+ {XtNgetValue, XtCCallback, XtRCallback, sizeof(XtPointer),
+ offset(strip_chart.get_value), XtRImmediate, (XtPointer) NULL},
+ {XtNjumpScroll, XtCJumpScroll, XtRInt, sizeof(int),
+ offset(strip_chart.jump_val), XtRImmediate, (XtPointer) DEFAULT_JUMP},
+};
+
+#undef offset
+
+/* Added argument types to these to shut picky compilers up. PH */
+
+static void CreateGC(StripChartWidget, unsigned int);
+static void DestroyGC(StripChartWidget, unsigned int);
+static void Initialize(), Destroy(), Redisplay();
+static void MoveChart(StripChartWidget, Boolean);
+static void SetPoints(StripChartWidget);
+static Boolean SetValues();
+
+int repaint_window(StripChartWidget, int, int); /* PH hack */
+/* static int repaint_window(); */
+
+StripChartClassRec stripChartClassRec = {
+ { /* core fields */
+ /* superclass */ (WidgetClass) &simpleClassRec,
+ /* class_name */ "StripChart",
+ /* size */ sizeof(StripChartRec),
+ /* class_initialize */ XawInitializeWidgetSet,
+ /* class_part_initialize */ NULL,
+ /* class_inited */ FALSE,
+ /* initialize */ Initialize,
+ /* initialize_hook */ NULL,
+ /* realize */ XtInheritRealize,
+ /* actions */ NULL,
+ /* num_actions */ 0,
+ /* resources */ resources,
+ /* num_resources */ XtNumber(resources),
+ /* xrm_class */ NULLQUARK,
+ /* compress_motion */ TRUE,
+ /* compress_exposure */ XtExposeCompressMultiple |
+ XtExposeGraphicsExposeMerged,
+ /* compress_enterleave */ TRUE,
+ /* visible_interest */ FALSE,
+ /* destroy */ Destroy,
+ /* resize */ (void (*)(Widget))SetPoints,
+ /* expose */ Redisplay,
+ /* set_values */ SetValues,
+ /* set_values_hook */ NULL,
+ /* set_values_almost */ NULL,
+ /* get_values_hook */ NULL,
+ /* accept_focus */ NULL,
+ /* version */ XtVersion,
+ /* callback_private */ NULL,
+ /* tm_table */ NULL,
+ /* query_geometry */ XtInheritQueryGeometry,
+ /* display_accelerator */ XtInheritDisplayAccelerator,
+ /* extension */ NULL
+ },
+ { /* Simple class fields */
+ /* change_sensitive */ XtInheritChangeSensitive
+ }
+};
+
+WidgetClass mystripChartWidgetClass = (WidgetClass) &stripChartClassRec;
+
+/****************************************************************
+ *
+ * Private Procedures
+ *
+ ****************************************************************/
+
+static void draw_it();
+
+/* Function Name: CreateGC
+ * Description: Creates the GC's
+ * Arguments: w - the strip chart widget.
+ * which - which GC's to create.
+ * Returns: none
+ */
+
+static void
+CreateGC(w, which)
+StripChartWidget w;
+unsigned int which;
+{
+ XGCValues myXGCV;
+
+ if (which & FOREGROUND) {
+ myXGCV.foreground = w->strip_chart.fgpixel;
+ w->strip_chart.fgGC = XtGetGC((Widget) w, GCForeground, &myXGCV);
+ }
+
+ if (which & HIGHLIGHT) {
+ myXGCV.foreground = w->strip_chart.hipixel;
+ w->strip_chart.hiGC = XtGetGC((Widget) w, GCForeground, &myXGCV);
+ }
+}
+
+/* Function Name: DestroyGC
+ * Description: Destroys the GC's
+ * Arguments: w - the strip chart widget.
+ * which - which GC's to destroy.
+ * Returns: none
+ */
+
+static void
+DestroyGC(w, which)
+StripChartWidget w;
+unsigned int which;
+{
+ if (which & FOREGROUND)
+ XtReleaseGC((Widget) w, w->strip_chart.fgGC);
+
+ if (which & HIGHLIGHT)
+ XtReleaseGC((Widget) w, w->strip_chart.hiGC);
+}
+
+/* ARGSUSED */
+static void Initialize (greq, gnew)
+ Widget greq, gnew;
+{
+ StripChartWidget w = (StripChartWidget)gnew;
+
+ if (w->strip_chart.update > 0)
+ w->strip_chart.interval_id = XtAppAddTimeOut(
+ XtWidgetToApplicationContext(gnew),
+ w->strip_chart.update * MS_PER_SEC,
+ draw_it, (XtPointer) gnew);
+ CreateGC(w, (unsigned int) ALL_GCS);
+
+ w->strip_chart.scale = w->strip_chart.min_scale;
+ w->strip_chart.interval = 0;
+ w->strip_chart.max_value = 0.0;
+ w->strip_chart.points = NULL;
+ SetPoints(w);
+}
+
+static void Destroy (gw)
+ Widget gw;
+{
+ StripChartWidget w = (StripChartWidget)gw;
+
+ if (w->strip_chart.update > 0)
+ XtRemoveTimeOut (w->strip_chart.interval_id);
+ if (w->strip_chart.points)
+ XtFree((char *) w->strip_chart.points);
+ DestroyGC(w, (unsigned int) ALL_GCS);
+}
+
+/*
+ * NOTE: This function really needs to receive graphics exposure
+ * events, but since this is not easily supported until R4 I am
+ * going to hold off until then.
+ */
+
+/* ARGSUSED */
+static void Redisplay(w, event, region)
+ Widget w;
+ XEvent *event;
+ Region region;
+{
+ if (event->type == GraphicsExpose)
+ (void) repaint_window ((StripChartWidget)w, event->xgraphicsexpose.x,
+ event->xgraphicsexpose.width);
+ else
+ (void) repaint_window ((StripChartWidget)w, event->xexpose.x,
+ event->xexpose.width);
+}
+
+/* ARGSUSED */
+static void
+draw_it(client_data, id)
+XtPointer client_data;
+XtIntervalId *id; /* unused */
+{
+ StripChartWidget w = (StripChartWidget)client_data;
+ double value;
+
+ if (w->strip_chart.update > 0)
+ w->strip_chart.interval_id =
+ XtAppAddTimeOut(XtWidgetToApplicationContext( (Widget) w),
+ w->strip_chart.update * MS_PER_SEC,draw_it,client_data);
+
+ if (w->strip_chart.interval >= (int)w->core.width)
+ MoveChart( (StripChartWidget) w, TRUE);
+
+ /* Get the value, stash the point and draw corresponding line. */
+
+ if (w->strip_chart.get_value == NULL)
+ return;
+
+ XtCallCallbacks( (Widget)w, XtNgetValue, (XtPointer)&value );
+
+ /*
+ * Keep w->strip_chart.max_value up to date, and if this data
+ * point is off the graph, change the scale to make it fit.
+ */
+
+ if (value > w->strip_chart.max_value) {
+ w->strip_chart.max_value = value;
+ if (w->strip_chart.max_value > w->strip_chart.scale) {
+ XClearWindow( XtDisplay (w), XtWindow (w));
+ w->strip_chart.interval = repaint_window(w, 0, (int) w->core.width);
+ }
+ }
+
+ w->strip_chart.valuedata[w->strip_chart.interval] = value;
+ if (XtIsRealized((Widget)w)) {
+ int y = (int) (w->core.height
+ - (int)(w->core.height * value) / w->strip_chart.scale);
+
+ XFillRectangle(XtDisplay(w), XtWindow(w), w->strip_chart.fgGC,
+ w->strip_chart.interval, y,
+ (unsigned int) 1, w->core.height - y);
+ /*
+ * Fill in the graph lines we just painted over.
+ */
+
+ if (w->strip_chart.points != NULL) {
+ w->strip_chart.points[0].x = w->strip_chart.interval;
+ XDrawPoints(XtDisplay(w), XtWindow(w), w->strip_chart.hiGC,
+ w->strip_chart.points, w->strip_chart.scale - 1,
+ CoordModePrevious);
+ }
+
+ XFlush(XtDisplay(w)); /* Flush output buffers */
+ }
+ w->strip_chart.interval++; /* Next point */
+} /* draw_it */
+
+/* Blts data according to current size, then redraws the stripChart window.
+ * Next represents the number of valid points in data. Returns the (possibly)
+ * adjusted value of next. If next is 0, this routine draws an empty window
+ * (scale - 1 lines for graph). If next is less than the current window width,
+ * the returned value is identical to the initial value of next and data is
+ * unchanged. Otherwise keeps half a window's worth of data. If data is
+ * changed, then w->strip_chart.max_value is updated to reflect the
+ * largest data point.
+ */
+
+/* static int */
+int /* PH hack */
+repaint_window(w, left, width)
+StripChartWidget w;
+int left, width;
+{
+ register int i, j;
+ register int next = w->strip_chart.interval;
+ int scale = w->strip_chart.scale;
+ int scalewidth = 0;
+
+ /* Compute the minimum scale required to graph the data, but don't go
+ lower than min_scale. */
+ if (w->strip_chart.interval != 0 || scale <= (int)w->strip_chart.max_value)
+ scale = ((int) (w->strip_chart.max_value)) + 1;
+ if (scale < w->strip_chart.min_scale)
+ scale = w->strip_chart.min_scale;
+
+/* if (scale != w->strip_chart.scale) { */
+
+ if (scale != w->strip_chart.scale && scale == 10) {
+ w->strip_chart.scale = scale;
+ left = 0;
+ width = next;
+ scalewidth = w->core.width;
+
+ SetPoints(w);
+
+ if (XtIsRealized ((Widget) w))
+ XClearWindow (XtDisplay (w), XtWindow (w));
+
+ }
+
+ if (XtIsRealized((Widget)w)) {
+ Display *dpy = XtDisplay(w);
+ Window win = XtWindow(w);
+
+ width += left - 1;
+ if (!scalewidth) scalewidth = width;
+
+ if (next < ++width) width = next;
+
+ /* Draw data point lines. */
+ for (i = left; i < width; i++) {
+ int y = (int) (w->core.height -
+ (int)(w->core.height * w->strip_chart.valuedata[i]) /
+ w->strip_chart.scale);
+
+ XFillRectangle(dpy, win, w->strip_chart.fgGC,
+ i, y, (unsigned int) 1,
+ (unsigned int) (w->core.height - y));
+ }
+
+ /* Draw graph reference lines */
+ for (i = 1; i < w->strip_chart.scale; i++) {
+ j = i * ((int)w->core.height / w->strip_chart.scale);
+ XDrawLine(dpy, win, w->strip_chart.hiGC, left, j, scalewidth, j);
+ }
+ }
+ return(next);
+}
+
+/* Function Name: MoveChart
+ * Description: moves the chart over when it would run off the end.
+ * Arguments: w - the load widget.
+ * blit - blit the bits? (TRUE/FALSE).
+ * Returns: none.
+ */
+
+static void
+MoveChart(StripChartWidget w, Boolean blit)
+{
+ double old_max;
+ int left, i, j;
+ register int next = w->strip_chart.interval;
+
+ if (!XtIsRealized((Widget) w)) return;
+
+ if (w->strip_chart.jump_val == DEFAULT_JUMP)
+ j = w->core.width >> 1; /* Half the window width. */
+ else {
+ j = w->core.width - w->strip_chart.jump_val;
+ if (j < 0) j = 0;
+ }
+
+ bcopy((char *)(w->strip_chart.valuedata + next - j),
+ (char *)(w->strip_chart.valuedata), j * sizeof(double));
+ next = w->strip_chart.interval = j;
+
+ /*
+ * Since we just lost some data, recompute the
+ * w->strip_chart.max_value.
+ */
+
+ old_max = w->strip_chart.max_value;
+ w->strip_chart.max_value = 0.0;
+ for (i = 0; i < next; i++) {
+ if (w->strip_chart.valuedata[i] > w->strip_chart.max_value)
+ w->strip_chart.max_value = w->strip_chart.valuedata[i];
+ }
+
+ if (!blit) return; /* we are done... */
+
+ if ( ((int) old_max) != ( (int) w->strip_chart.max_value) ) {
+ XClearWindow(XtDisplay(w), XtWindow(w));
+ repaint_window(w, 0, (int) w->core.width);
+ return;
+ }
+
+ XCopyArea(XtDisplay((Widget)w), XtWindow((Widget)w), XtWindow((Widget)w),
+ w->strip_chart.hiGC, (int) w->core.width - j, 0,
+ (unsigned int) j, (unsigned int) w->core.height,
+ 0, 0);
+
+ XClearArea(XtDisplay((Widget)w), XtWindow((Widget)w),
+ (int) j, 0,
+ (unsigned int) w->core.width - j, (unsigned int)w->core.height,
+ FALSE);
+
+ /* Draw graph reference lines */
+ left = j;
+ for (i = 1; i < w->strip_chart.scale; i++) {
+ j = i * ((int)w->core.height / w->strip_chart.scale);
+ XDrawLine(XtDisplay((Widget) w), XtWindow( (Widget) w),
+ w->strip_chart.hiGC, left, j, (int)w->core.width, j);
+ }
+ return;
+}
+
+/* ARGSUSED */
+static Boolean SetValues (current, request, new)
+ Widget current, request, new;
+{
+ StripChartWidget old = (StripChartWidget)current;
+ StripChartWidget w = (StripChartWidget)new;
+ Boolean ret_val = FALSE;
+ unsigned int new_gc = NO_GCS;
+
+ if (w->strip_chart.update != old->strip_chart.update) {
+ if (old->strip_chart.update > 0)
+ XtRemoveTimeOut (old->strip_chart.interval_id);
+ if (w->strip_chart.update > 0)
+ w->strip_chart.interval_id =
+ XtAppAddTimeOut(XtWidgetToApplicationContext(new),
+ w->strip_chart.update * MS_PER_SEC,
+ draw_it, (XtPointer)w);
+ }
+
+ if ( w->strip_chart.min_scale > (int) ((w->strip_chart.max_value) + 1) )
+ ret_val = TRUE;
+
+ if ( w->strip_chart.fgpixel != old->strip_chart.fgpixel ) {
+ new_gc |= FOREGROUND;
+ ret_val = True;
+ }
+
+ if ( w->strip_chart.hipixel != old->strip_chart.hipixel ) {
+ new_gc |= HIGHLIGHT;
+ ret_val = True;
+ }
+
+ DestroyGC(old, new_gc);
+ CreateGC(w, new_gc);
+
+ return( ret_val );
+}
+
+/* Function Name: SetPoints
+ * Description: Sets up the polypoint that will be used to draw in
+ * the graph lines.
+ * Arguments: w - the StripChart widget.
+ * Returns: none.
+ */
+
+#define HEIGHT ( (unsigned int) w->core.height)
+
+static void
+SetPoints(w)
+StripChartWidget w;
+{
+ XPoint * points;
+ Cardinal size;
+ int i;
+
+ if (w->strip_chart.scale <= 1) { /* no scale lines. */
+ XtFree ((char *) w->strip_chart.points);
+ w->strip_chart.points = NULL;
+ return;
+ }
+
+ size = sizeof(XPoint) * (w->strip_chart.scale - 1);
+
+ points = (XPoint *) XtRealloc( (XtPointer) w->strip_chart.points, size);
+ w->strip_chart.points = points;
+
+ /* Draw graph reference lines into clip mask */
+
+ for (i = 1; i < w->strip_chart.scale; i++) {
+ points[i - 1].x = 0;
+ points[i - 1].y = HEIGHT / w->strip_chart.scale;
+ }
+}
diff --git a/exim_monitor/em_TextPop.c b/exim_monitor/em_TextPop.c
new file mode 100644
index 0000000..ff5d1a8
--- /dev/null
+++ b/exim_monitor/em_TextPop.c
@@ -0,0 +1,767 @@
+/***********************************************************
+Copyright (c) The Exim Maintainers 2022
+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 beginning 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 successful 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: PopdownSearch
+ * 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 argument 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) {
+ 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) {
+ 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 search structure.
+ * Returns: TRUE if successful.
+ */
+
+/* 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) ((long)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 arguments.
+ *
+ * form - the from widget that is the dialog.
+ * ptr - the initial string for the dialog's text widget.
+ * parent - the parent of the dialog - the main text widget.
+ */
+
+static Widget
+CreateDialog(parent, ptr, name, func)
+Widget parent;
+String ptr, name;
+void (*func)();
+{
+ Widget popup, form;
+ Arg args[5];
+ Cardinal num_args;
+
+ num_args = 0;
+ XtSetArg(args[num_args], XtNiconName, name); num_args++;
+ XtSetArg(args[num_args], XtNgeometry, NULL); num_args++;
+ XtSetArg(args[num_args], XtNallowShellResize, TRUE); num_args++;
+ XtSetArg(args[num_args], XtNtransientFor, GetShell(parent)); num_args++;
+ popup = XtCreatePopupShell(name, transientShellWidgetClass,
+ parent, args, num_args);
+
+ form = XtCreateManagedWidget(FORM_NAME, formWidgetClass, popup,
+ NULL, ZERO);
+
+ (*func) (form, ptr, parent);
+ return(popup);
+}
+
+ /* Function Name: GetShell
+ * Description: Walks up the widget hierarchy to find the
+ * nearest shell widget.
+ * Arguments: w - the widget whose parent shell should be returned.
+ * Returns: The shell widget among the ancestors of w that is the
+ * fewest levels up in the widget hierarchy.
+ */
+
+static Widget
+GetShell(w)
+Widget w;
+{
+ while ((w != NULL) && !XtIsShell(w))
+ w = XtParent(w);
+
+ return (w);
+}
+
+/* Add proper prototype to keep IRIX 6 compiler happy. PH */
+
+static Boolean InParams(String, String *, Cardinal);
+
+static Boolean InParams(str, p, n)
+ String str;
+ String *p;
+ Cardinal n;
+{
+ int i;
+ for (i=0; i < n; p++, i++)
+ if (! XmuCompareISOLatin1(*p, str)) return True;
+ return False;
+}
+
+static char *WM_DELETE_WINDOW = "WM_DELETE_WINDOW";
+
+static void WMProtocols(w, event, params, num_params)
+ Widget w; /* popup shell */
+ XEvent *event;
+ String *params;
+ Cardinal *num_params;
+{
+ Atom wm_delete_window;
+ Atom wm_protocols;
+
+ wm_delete_window = XInternAtom(XtDisplay(w), WM_DELETE_WINDOW, True);
+ wm_protocols = XInternAtom(XtDisplay(w), "WM_PROTOCOLS", True);
+
+ /* Respond to a recognized WM protocol request iff
+ * event type is ClientMessage and no parameters are passed, or
+ * event type is ClientMessage and event data is matched to parameters, or
+ * event type isn't ClientMessage and parameters make a request.
+ */
+#define DO_DELETE_WINDOW InParams(WM_DELETE_WINDOW, params, *num_params)
+
+ if ((event->type == ClientMessage &&
+ event->xclient.message_type == wm_protocols &&
+ event->xclient.data.l[0] == wm_delete_window &&
+ (*num_params == 0 || DO_DELETE_WINDOW))
+ ||
+ (event->type != ClientMessage && DO_DELETE_WINDOW)) {
+
+#undef DO_DELETE_WINDOW
+
+ Widget cancel;
+ char descendant[DISMISS_NAME_LEN + 2];
+ sprintf(descendant, "*%s", DISMISS_NAME);
+ cancel = XtNameToWidget(w, descendant);
+ if (cancel) XtCallCallbacks(cancel, XtNcallback, (XtPointer)NULL);
+ }
+}
+
+static void SetWMProtocolTranslations(w)
+ Widget w; /* realized popup shell */
+{
+ int i;
+ XtAppContext app_context;
+ Atom wm_delete_window;
+ static XtTranslations compiled_table; /* initially 0 */
+ static XtAppContext *app_context_list; /* initially 0 */
+ static Cardinal list_size; /* initially 0 */
+
+ app_context = XtWidgetToApplicationContext(w);
+
+ /* parse translation table once */
+ if (! compiled_table) compiled_table = XtParseTranslationTable
+ ("<Message>WM_PROTOCOLS: XawWMProtocols()\n");
+
+ /* add actions once per application context */
+ for (i=0; i < list_size && app_context_list[i] != app_context; i++) ;
+ if (i == list_size) {
+ XtActionsRec actions[1];
+ actions[0].string = "XawWMProtocols";
+ actions[0].proc = WMProtocols;
+ list_size++;
+ app_context_list = (XtAppContext *) XtRealloc
+ ((char *)app_context_list, list_size * sizeof(XtAppContext));
+ XtAppAddActions(app_context, actions, 1);
+ app_context_list[i] = app_context;
+ }
+
+ /* establish communication between the window manager and each shell */
+ XtAugmentTranslations(w, compiled_table);
+ wm_delete_window = XInternAtom(XtDisplay(w), WM_DELETE_WINDOW, False);
+ (void) XSetWMProtocols(XtDisplay(w), XtWindow(w), &wm_delete_window, 1);
+}
diff --git a/exim_monitor/em_globals.c b/exim_monitor/em_globals.c
new file mode 100644
index 0000000..3d452c6
--- /dev/null
+++ b/exim_monitor/em_globals.c
@@ -0,0 +1,239 @@
+/*************************************************
+* Exim Monitor *
+*************************************************/
+
+/* Copyright (c) University of Cambridge 1995 - 2018 */
+/* Copyright (c) The Exim Maintainers 2021 */
+/* 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;
+
+#ifdef EXPERIMENTAL_BRIGHTMAIL
+int bmi_run = 0;
+uschar *bmi_verdicts = NULL;
+#endif
+
+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;
+
+pcre2_code *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;
+pcre2_code **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
+
+tree_node *acl_var_c = NULL;
+tree_node *acl_var_m = NULL;
+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;
+time_t deliver_frozen_at = 0;
+BOOL deliver_manual_thaw = FALSE;
+
+#ifndef DISABLE_DKIM
+uschar *dkim_cur_signer = NULL;
+uschar *dkim_signers = NULL;
+uschar *dkim_signing_domain = NULL;
+uschar *dkim_signing_selector = NULL;
+uschar *dkim_verify_signers = US"$dkim_signers";
+unsigned dkim_collect_input = 0;
+BOOL dkim_disable_verify = FALSE;
+#endif
+
+BOOL dont_deliver = FALSE;
+
+int dsn_ret = 0;
+uschar *dsn_envid = NULL;
+
+struct global_flags f = {
+ .sender_local = FALSE,
+};
+
+#ifdef WITH_CONTENT_SCAN
+int fake_response = OK;
+#endif
+
+header_line *header_last = NULL;
+header_line *header_list = NULL;
+
+BOOL host_lookup_deferred = FALSE;
+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;
+
+#ifdef WITH_CONTENT_SCAN
+uschar *spam_bar = NULL;
+uschar *spam_report = NULL;
+uschar *spam_score = NULL;
+uschar *spam_score_int = NULL;
+#endif
+
+int max_received_linelength= 0;
+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;
+
+uschar *queue_name = US"";
+
+int received_count = 0;
+uschar *received_protocol = NULL;
+struct timeval received_time = { 0, 0 };
+struct timeval received_time_complete = { 0, 0 };
+int recipients_count = 0;
+recipient_item *recipients_list = NULL;
+int recipients_list_max = 0;
+BOOL 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_auth_pubname = NULL;
+uschar *sender_host_authenticated = NULL;
+uschar *sender_host_name = NULL;
+int sender_host_port = 0;
+uschar *sender_ident = NULL;
+BOOL sender_set_untrusted = FALSE;
+uschar *smtp_active_hostname = NULL;
+
+BOOL split_spool_directory = FALSE;
+uschar *spool_directory = US SPOOL_DIRECTORY;
+int string_datestamp_offset=-1;
+int string_datestamp_length= 0;
+int string_datestamp_type = -1;
+
+BOOL timestamps_utc = FALSE;
+tls_support tls_in = {
+ .active = { .sock = -1 }
+ /* remainder zero/null/false */
+};
+
+tree_node *tree_duplicates = NULL;
+tree_node *tree_nonrecipients = NULL;
+tree_node *tree_unusable = NULL;
+
+uschar *version_date = US"?";
+uschar *version_string = US"?";
+
+int warning_count = 0;
+
+/* End of em_globals.c */
diff --git a/exim_monitor/em_hdr.h b/exim_monitor/em_hdr.h
new file mode 100644
index 0000000..ab37806
--- /dev/null
+++ b/exim_monitor/em_hdr.h
@@ -0,0 +1,329 @@
+/*************************************************
+* Exim Monitor *
+*************************************************/
+
+/* Copyright (c) University of Cambridge 1995 - 2009 */
+/* Copyright (c) The Exim Maintainers 2021 - 2022 */
+/* 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 */
+
+#define PCRE2_CODE_UNIT_WIDTH 8
+#include <pcre2.h>
+
+/* Includes from the main source of Exim. One of these days I should tidy up
+this interface so that this kind of kludge isn't needed. */
+
+#ifndef NS_MAXMSG
+# define NS_MAXMSG 65535
+#endif
+typedef void * hctx;
+
+#include "local_scan.h"
+#include "macros.h"
+#include "structs.h"
+#include "blob.h"
+#include "globals.h"
+#include "hintsdb.h"
+#include "hintsdb_structs.h"
+#include "functions.h"
+#include "osfunctions.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 pcre2_code *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 pcre2_code **stripchart_regex; /* vector of regexps */
+extern uschar **stripchart_title; /* vector of titles */
+extern int *stripchart_total; /* vector of accumulating values */
+extern int stripchart_update; /* update interval */
+extern int stripchart_width; /* width of stripcharts */
+extern int stripchart_varstart; /* starting number for variable charts */
+
+extern int text_depth; /* depth of text windows */
+extern int tick_queue_accumulator; /* For timing next auto update */
+
+extern uschar *window_title; /* title of the exim monitor window */
+
+
+/*************************************************
+* Global functions *
+*************************************************/
+
+extern XtActionProc dialogAction(Widget, XEvent *, String *, Cardinal *);
+
+extern uschar *copystring(uschar *);
+extern void create_dialog(uschar *, uschar *);
+extern void create_stripchart(Widget, uschar *);
+extern void debug(char *, ...);
+extern dest_item *find_dest(queue_item *, uschar *, int, BOOL);
+extern queue_item *find_queue(uschar *, int, int);
+extern void init(int, uschar **);
+extern void menu_create(Widget, XEvent *, String *, Cardinal *);
+extern void NonMessageDialogue(uschar *);
+extern void queue_display(void);
+extern void read_log(void);
+extern int read_spool(uschar *);
+extern int read_spool_init(uschar *);
+extern void read_spool_tidy(void);
+extern int repaint_window(StripChartWidget, int, int);
+extern void scan_spool_input(int);
+extern void stripchart_init(void);
+extern void text_empty(Widget);
+extern void text_show(Widget, uschar *);
+extern void text_showf(Widget, char *, ...);
+extern void xs_SetValues(Widget, Cardinal, ...);
+
+/* End of em_hdr.h */
diff --git a/exim_monitor/em_init.c b/exim_monitor/em_init.c
new file mode 100644
index 0000000..e0bc3b0
--- /dev/null
+++ b/exim_monitor/em_init.c
@@ -0,0 +1,238 @@
+/*************************************************
+* Exim monitor *
+*************************************************/
+
+/* Copyright (c) University of Cambridge 1995 - 2009 */
+/* Copyright (c) The Exim Maintainers 2020 - 2021 */
+/* 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)
+ {
+ size_t offset;
+ int err;
+
+ if (!(stripchart_regex[indx] =
+ pcre2_compile((PCRE2_SPTR)buffer,
+ PCRE2_ZERO_TERMINATED, PCRE_COPT,
+ &err, &offset, NULL)))
+ {
+ uschar errbuf[128];
+ pcre2_get_error_message(err, errbuf, sizeof(errbuf));
+ printf("regular expression error: %s at offset %ld "
+ "while compiling %s\n", errbuf, (long)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 = (pcre2_code **)store_malloc(stripchart_number * sizeof(pcre2_code *));
+ stripchart_title = (uschar **)store_malloc(stripchart_number * sizeof(uschar *));
+ }
+ }
+}
+
+
+/*************************************************
+* Initialize *
+*************************************************/
+
+void init(int argc, uschar **argv)
+{
+int x;
+size_t erroroffset;
+uschar *s;
+
+argc = argc; /* These are currently unused. */
+argv = argv;
+
+/* Deal with simple values in the environment. */
+
+if ((s = US getenv("ACTION_OUTPUT")))
+ {
+ if (Ustrcmp(s, "no") == 0) action_output = FALSE;
+ if (Ustrcmp(s, "yes") == 0) action_output = TRUE;
+ }
+
+if ((s = US getenv("ACTION_QUEUE_UPDATE")))
+ {
+ 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 && (x = Uatoi(s)) != 0) body_max = x;
+
+if ((s = US getenv("EXIM_PATH")))
+ exim_path = string_copy(s);
+
+if ((s = US getenv("EXIMON_EXIM_CONFIG")))
+ alternate_config = string_copy(s);
+
+if ((s = US getenv("LOG_BUFFER")))
+ {
+ 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 && (x = Uatoi(s)) != 0) log_depth = x;
+
+if ((s = US getenv("LOG_FILE_NAME")))
+ log_file = string_copy(s);
+
+if ((s = US getenv("LOG_FONT")))
+ log_font = string_copy(s);
+
+s = US getenv("LOG_WIDTH");
+if (s && (x = Uatoi(s)) != 0) log_width = x;
+
+if ((s = US getenv("MENU_EVENT")))
+ menu_event = string_copy(s);
+
+s = US getenv("MIN_HEIGHT");
+if (s && (x = Uatoi(s)) > 0) min_height = x;
+
+s = US getenv("MIN_WIDTH");
+if (s && (x = Uatoi(s)) > 0) min_width = x;
+
+if ((s = US getenv("QUALIFY_DOMAIN")))
+ qualify_domain = string_copy(s);
+else
+ qualify_domain = US""; /* Don't want NULL */
+
+s = US getenv("QUEUE_DEPTH");
+if (s && (x = Uatoi(s)) != 0) queue_depth = x;
+
+if ((s = US getenv("QUEUE_FONT")))
+ queue_font = string_copy(s);
+
+s = US getenv("QUEUE_INTERVAL");
+if (s && (x = Uatoi(s)) != 0) queue_update = x;
+
+s = US getenv("QUEUE_MAX_ADDRESSES");
+if (s && (x = Uatoi(s)) != 0) queue_max_addresses = x;
+
+s = US getenv("QUEUE_WIDTH");
+if (s && (x = Uatoi(s)) != 0) queue_width = x;
+
+if ((s = US getenv("SPOOL_DIRECTORY")))
+ spool_directory = string_copy(s);
+
+s = US getenv("START_SMALL");
+if (s && Ustrcmp(s, "yes") == 0) start_small = 1;
+
+s = US getenv("TEXT_DEPTH");
+if (s && (x = Uatoi(s)) != 0) text_depth = x;
+
+if ((s = US getenv("WINDOW_TITLE")))
+ 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 && *s)
+ {
+ 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);
+ }
+
+if ((s = US getenv("LOG_STRIPCHARTS")))
+ decode_stripchart_config(s);
+
+s = US getenv("STRIPCHART_INTERVAL");
+if (s && (x = Uatoi(s)) != 0) stripchart_update = x;
+
+s = US getenv("QUEUE_STRIPCHART_NAME");
+queue_stripchart_name = s ? string_copy(s) : US"queue";
+
+/* Compile the regex for matching yyyy-mm-dd at the start of a string. */
+
+yyyymmdd_regex = pcre2_compile((PCRE2_SPTR)"^\\d{4}-\\d\\d-\\d\\d\\s",
+ PCRE2_ZERO_TERMINATED, PCRE_COPT, &x, &erroroffset, NULL);
+}
+
+/* End of em_init.c */
diff --git a/exim_monitor/em_log.c b/exim_monitor/em_log.c
new file mode 100644
index 0000000..8d85c13
--- /dev/null
+++ b/exim_monitor/em_log.c
@@ -0,0 +1,411 @@
+/*************************************************
+* Exim Monitor *
+*************************************************/
+
+/* Copyright (c) University of Cambridge 1995 - 2018 */
+/* Copyright (c) The Exim Maintainters 2021 - 2022 */
+/* See the file NOTICE for conditions of use and distribution. */
+
+/* This module contains code for scanning the main 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, ...) PRINTF_FUNCTION(1,2);
+
+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)
+ {
+ if (fseek(LOG, log_position, SEEK_SET))
+ {
+ perror("logfile fseek");
+ exit(1);
+ }
+
+ while (Ufgets(buffer, log_buffer_len, LOG) != NULL)
+ {
+ uschar *id;
+ uschar *p = buffer;
+ rmark reset_point;
+ int length = Ustrlen(buffer);
+ pcre2_match_data * md = pcre2_match_data_create(1, NULL);
+
+ /* 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_mark();
+
+ /* 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 (int i = stripchart_varstart; i < stripchart_number; i++)
+ if (pcre2_match(stripchart_regex[i], (PCRE2_SPTR)buffer, length,
+ 0, PCRE_EOPT, md, NULL) >= 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.
+ Take note of the presence of [pid]. */
+
+ if (pcre2_match(yyyymmdd_regex, (PCRE2_SPTR) buffer, length, 0, PCRE_EOPT,
+ md, NULL) >= 0)
+ {
+ int pidlength = 0;
+ if ( (buffer[20] == '+' || buffer[20] == '-')
+ && isdigit(buffer[21]) && buffer[25] == ' ')
+ memmove(buffer + 20, buffer + 26, Ustrlen(buffer + 26) + 1);
+ if (buffer[20] == '[')
+ while (Ustrchr("[]0123456789", buffer[20+pidlength++]) != NULL)
+ ;
+ id = string_copyn(buffer + 20 + pidlength, MESSAGE_ID_LENGTH);
+ show_log("%s", buffer+11);
+ }
+ else
+ {
+ id = US"";
+ show_log("%s", buffer);
+ }
+ pcre2_match_data_free(md);
+
+ /* Deal with frozen and unfrozen messages */
+
+ if (strstric(buffer, US"frozen", FALSE) != NULL)
+ {
+ queue_item *qq = find_queue(id, queue_noop, 0);
+ if (qq)
+ qq->frozen = strstric(buffer, US"unfrozen", FALSE) == NULL;
+ }
+
+ /* 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)
+ {
+ 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];
+ /* Do *not* use "%s" here, we need the %D datestamp in the log_file string to
+ be expanded. The trailing NULL arg is to quieten preprocessors that need at
+ least one arg for a variadic set in a macro. */
+ string_format(log_file_wanted, sizeof(log_file_wanted), CS log_file, NULL);
+ 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;
+ if (fstat(fileno(LOG), &statdata))
+ {
+ fprintf(stderr, "fstat %s: %s\n", log_file_open, strerror(errno));
+ exit(1);
+ }
+ log_inode = statdata.st_ino;
+ }
+ }
+
+/* Save the position we have got to in the log. */
+
+if (LOG != NULL) log_position = ftell(LOG);
+}
+
+/* End of em_log.c */
diff --git a/exim_monitor/em_main.c b/exim_monitor/em_main.c
new file mode 100644
index 0000000..5714b99
--- /dev/null
+++ b/exim_monitor/em_main.c
@@ -0,0 +1,953 @@
+/*************************************************
+* Exim Monitor *
+*************************************************/
+
+/* Copyright (c) University of Cambridge 1995 - 2018 */
+/* Copyright (c) The Exim Maintainers 2021 - 2022 */
+/* 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, const char *format, ...)
+{
+va_list ap;
+va_start(ap, format);
+vfprintf(stderr, format, ap);
+fprintf(stderr, "\n");
+va_end(ap);
+}
+
+
+
+
+/*************************************************
+* 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_address_extract_port(uschar * address)
+{
+int port = 0;
+uschar *endptr;
+
+/* Handle the "bracketed with colon on the end" format */
+
+if (*address == '[')
+ {
+ uschar *rb = address + 1;
+ while (*rb != 0 && *rb != ']') rb++;
+ if (*rb++ == 0) return 0; /* Missing ]; leave invalid address */
+ if (*rb == ':')
+ {
+ port = Ustrtol(rb + 1, &endptr, 10);
+ if (*endptr != 0) return 0; /* Invalid port; leave invalid address */
+ }
+ else if (*rb != 0) return 0; /* Bad syntax; leave invalid address */
+ memmove(address, address + 1, rb - address - 2);
+ rb[-2] = 0;
+ }
+
+/* Handle the "dot on the end" format */
+
+else
+ {
+ 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;
+ port = Ustrtol(address + 1, &endptr, 10);
+ if (*endptr != 0) return 0; /* Invalid port; leave invalid address */
+ *address = 0;
+ }
+
+return port;
+}
+
+
+
+
+/*************************************************
+* 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)
+{
+scan_spool_input(TRUE);
+queue_display();
+tick_queue_accumulator = 0;
+}
+
+void hideAction(Widget w, XtPointer client_data, XtPointer 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;
+
+while (sk)
+ {
+ 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)
+{
+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);
+
+/* 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();
+
+/* 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. */
+
+store_init();
+big_buffer = store_get(big_buffer_size, GET_UNTAINTED);
+
+/* 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 = US 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 or %M 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)
+ {
+ /* Do *not* use "%s" here, we need the %D datestamp in the log_file to
+ be expanded! */
+ (void)string_format(log_file_open, sizeof(log_file_open), CS log_file, NULL);
+ 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);
+ if (fstat(fileno(LOG), &statdata))
+ {
+ perror("log file fstat");
+ fclose(LOG);
+ LOG=NULL;
+ }
+ else
+ log_inode = statdata.st_ino;
+ }
+ }
+else
+ {
+ printf("*** eximon warning: no log file available to tail\n");
+ }
+
+/* Now initialize the X world and create the top-level widget */
+
+toplevel_widget = XtAppInitialize(&X_appcon, "Eximon", NULL, 0, &argc, argv,
+ fallback_resources, NULL, 0);
+X_display = XtDisplay(toplevel_widget);
+xs_SetValues(toplevel_widget, 4,
+ "title", window_title,
+ "iconName", window_title,
+ "minWidth", min_width,
+ "minHeight", min_height);
+
+
+/* Create the action for setting up the menu in the queue display
+window, and register the action for positioning the menu. */
+
+XtAppAddActions(X_appcon, menu_action_table, 1);
+XawSimpleMenuAddGlobalActions(X_appcon);
+
+/* Set up translation tables for the text widgets we use. We don't
+want all the generality of editing, etc. that the defaults provide.
+This cannot be done before initializing X - the parser complains
+about unknown events, modifiers, etc. in an unhelpful way... The
+queue text widget has a different table which includes the button
+for popping up the menu. Note that the order of things in these
+tables is significant. Shift<thing> must come before <thing> as
+otherwise it isn't noticed. */
+
+/*
+ <FocusIn>: display-caret(on)\n\
+ <FocusOut>: display-caret(off)\n\
+*/
+
+/* The translation manager sets up passive grabs for the menu popups as a
+result of MenuPopup(), but the grabs match only the exact modifiers listed,
+hence combinations with and without caps-lock and num-lock must be given,
+rather than just one "Shift<Btn1Down>" (or whatever menu_event is set to),
+despite the fact that that notation (without a leading !) should ignore the
+state of other modifiers. Thanks to Kevin Ryde for this information, and for
+the function above that discovers which modifier is Num Lock, because it turns
+out that it varies from server to server. */
+
+sprintf(CS big_buffer,
+ "!%s: menu-create() XawPositionSimpleMenu(menu) MenuPopup(menu)\n\
+ !Lock %s: menu-create() XawPositionSimpleMenu(menu) MenuPopup(menu)\n\
+ ", menu_event, menu_event);
+
+numlock = numlock_modifiers(X_display, modbuf); /* Get Num Lock modifier(s) */
+
+if (numlock != NULL) sprintf(CS big_buffer + Ustrlen(big_buffer),
+ "!%s %s: menu-create() XawPositionSimpleMenu(menu) MenuPopup(menu)\n\
+ !Lock %s %s: menu-create() XawPositionSimpleMenu(menu) MenuPopup(menu)\n\
+ ", numlock, menu_event, numlock, menu_event);
+
+sprintf(CS big_buffer + Ustrlen(big_buffer),
+ "<Btn1Down>: select-start()\n\
+ <Btn1Motion>: extend-adjust()\n\
+ <Btn1Up>: extend-end(PRIMARY,CUT_BUFFER0)\n\
+ <Btn3Down>: extend-start()\n\
+ <Btn3Motion>: extend-adjust()\n\
+ <Btn3Up>: extend-end(PRIMARY,CUT_BUFFER0)\n\
+ <Key>Up: scroll-one-line-down()\n\
+ <Key>Down: scroll-one-line-up()\n\
+ Ctrl<Key>R: search(backward)\n\
+ Ctrl<Key>S: search(forward)\n\
+ ");
+
+queue_trans = XtParseTranslationTable(CS big_buffer);
+
+text_trans = XtParseTranslationTable(
+ "<Btn1Down>: select-start()\n\
+ <Btn1Motion>: extend-adjust()\n\
+ <Btn1Up>: extend-end(PRIMARY,CUT_BUFFER0)\n\
+ <Btn3Down>: extend-start()\n\
+ <Btn3Motion>: extend-adjust()\n\
+ <Btn3Up>: extend-end(PRIMARY,CUT_BUFFER0)\n\
+ <Key>Up: scroll-one-line-down()\n\
+ <Key>Down: scroll-one-line-up()\n\
+ Ctrl<Key>R: search(backward)\n\
+ Ctrl<Key>S: search(forward)\n\
+ ");
+
+
+/* Create a toplevel form widget to hold all the other things */
+
+outer_form_widget = XtCreateManagedWidget("form", formWidgetClass,
+ toplevel_widget, NULL, 0);
+
+/* Now create an inner form to hold the stripcharts */
+
+stripchart_form_widget = XtCreateManagedWidget("form", formWidgetClass,
+ outer_form_widget, NULL, 0);
+xs_SetValues(stripchart_form_widget, 5,
+ "defaultDistance", 8,
+ "left", XawChainLeft,
+ "right", XawChainLeft,
+ "top", XawChainTop,
+ "bottom", XawChainTop);
+
+/* Create the queue count stripchart and its label. */
+
+create_stripchart(stripchart_form_widget, queue_stripchart_name);
+
+/* If configured, create the size monitoring stripchart, but
+only if the OS supports statfs(). */
+
+if (size_stripchart != NULL)
+ {
+#ifdef HAVE_STATFS
+ if (size_stripchart_name == NULL)
+ {
+ size_stripchart_name = size_stripchart + Ustrlen(size_stripchart) - 1;
+ while (size_stripchart_name > size_stripchart &&
+ *size_stripchart_name == '/') size_stripchart_name--;
+ while (size_stripchart_name > size_stripchart &&
+ *size_stripchart_name != '/') size_stripchart_name--;
+ }
+ create_stripchart(stripchart_form_widget, size_stripchart_name);
+#else
+ printf("Can't create size stripchart: statfs() function not available\n");
+#endif
+ }
+
+/* Now create the configured input/output stripcharts; note
+the total number includes the queue stripchart. */
+
+for (i = stripchart_varstart; i < stripchart_number; i++)
+ create_stripchart(stripchart_form_widget, stripchart_title[i]);
+
+/* Next in vertical order come the Resize & Quit buttons */
+
+quit_args[0].value = (XtArgVal) stripchart_form_widget;
+quit_widget = XtCreateManagedWidget("quit", commandWidgetClass,
+ outer_form_widget, quit_args, XtNumber(quit_args));
+XtAddCallback(quit_widget, "callback", quitAction, NULL);
+
+resize_args[0].value = (XtArgVal) stripchart_form_widget;
+resize_args[1].value = (XtArgVal) quit_widget;
+resize_widget = XtCreateManagedWidget("resize", commandWidgetClass,
+ outer_form_widget, resize_args, XtNumber(resize_args));
+XtAddCallback(resize_widget, "callback", resizeAction, NULL);
+
+/* In the absence of log tailing, the quit widget is the one above the
+queue listing. */
+
+above_queue_widget = quit_widget;
+
+/* Create an Ascii text widget for the log tail display if we are tailing a
+log. Skip it if not. */
+
+if (log_file[0] != 0)
+ {
+ log_args[0].value = (XtArgVal) quit_widget;
+ log_widget = XtCreateManagedWidget("log", asciiTextWidgetClass,
+ outer_form_widget, log_args, XtNumber(log_args));
+ XawTextDisplayCaret(log_widget, TRUE);
+ xs_SetValues(log_widget, 6,
+ "editType", XawtextEdit,
+ "translations", text_trans,
+ "string", log_display_buffer,
+ "length", log_buffer_size,
+ "height", log_depth,
+ "width", log_width);
+
+ if (log_font != NULL)
+ {
+ XFontStruct *f = XLoadQueryFont(X_display, CS log_font);
+ if (f != NULL) xs_SetValues(log_widget, 1, "font", f);
+ }
+
+ above_queue_widget = log_widget;
+ }
+
+/* The update button */
+
+update_args[0].value = (XtArgVal) above_queue_widget;
+update_widget = XtCreateManagedWidget("update", commandWidgetClass,
+ outer_form_widget, update_args, XtNumber(update_args));
+XtAddCallback(update_widget, "callback", updateAction, NULL);
+
+/* The hide button */
+
+hide_args[0].value = (XtArgVal) above_queue_widget;
+hide_args[1].value = (XtArgVal) update_widget;
+hide_widget = XtCreateManagedWidget("hide", commandWidgetClass,
+ outer_form_widget, hide_args, XtNumber(hide_args));
+XtAddCallback(hide_widget, "callback", hideAction, NULL);
+
+/* Create an Ascii text widget for the queue display. */
+
+queue_args[0].value = (XtArgVal) update_widget;
+queue_widget = XtCreateManagedWidget("queue", asciiTextWidgetClass,
+ outer_form_widget, queue_args, XtNumber(queue_args));
+XawTextDisplayCaret(queue_widget, TRUE);
+
+xs_SetValues(queue_widget, 4,
+ "editType", XawtextEdit,
+ "height", queue_depth,
+ "width", queue_width,
+ "translations", queue_trans);
+
+if (queue_font != NULL)
+ {
+ XFontStruct *f = XLoadQueryFont(X_display, CS queue_font);
+ if (f != NULL) xs_SetValues(queue_widget, 1, "font", f);
+ }
+
+/* Call the ticker function to get the initial data set up. It
+arranges to have itself recalled every 2 seconds. */
+
+ticker(NULL, NULL);
+
+/* Everything is now set up; this flag is used by the regerror
+function and also by the queue reader. */
+
+eximon_initialized = TRUE;
+printf("\nExim Monitor running\n");
+
+/* Realize the toplevel and thereby get things displayed */
+
+XtRealizeWidget(toplevel_widget);
+
+/* Find out the size of the initial window, and set that as its
+maximum. While we are at it, get the initial position. */
+
+sizepos_args[0].value = (XtArgVal)(&maxwidth);
+sizepos_args[1].value = (XtArgVal)(&maxheight);
+sizepos_args[2].value = (XtArgVal)(&original_x);
+sizepos_args[3].value = (XtArgVal)(&original_y);
+XtGetValues(toplevel_widget, sizepos_args, 4);
+
+xs_SetValues(toplevel_widget, 2,
+ "maxWidth", maxwidth,
+ "maxHeight", maxheight);
+
+/* Set up the size of the screen */
+
+screenwidth = XDisplayWidth(X_display, 0);
+screenheight= XDisplayHeight(X_display,0);
+
+/* Register the action table */
+
+XtAppAddActions(X_appcon, actionTable, actionTableSize);
+
+/* Reduce the window to the small size if this is wanted */
+
+if (start_small) resizeAction(NULL, NULL, NULL);
+
+/* Enter the application loop which handles things from here
+onwards. The return statement is never obeyed, but is needed to
+keep pedantic ANSI compilers happy. */
+
+XtAppMainLoop(X_appcon);
+
+return 0;
+}
+
+/* End of em_main.c */
+
diff --git a/exim_monitor/em_menu.c b/exim_monitor/em_menu.c
new file mode 100644
index 0000000..881f374
--- /dev/null
+++ b/exim_monitor/em_menu.c
@@ -0,0 +1,930 @@
+/*************************************************
+* Exim Monitor *
+*************************************************/
+
+/* Copyright (c) University of Cambridge 1995 - 2018 */
+/* Copyright (c) The Exim Maintainers 2021 */
+/* 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)
+{
+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)
+{
+Widget text = text_create(US client_data, text_depth);
+uschar * fname = NULL;
+FILE * f = NULL;
+
+/* End up with the split version, so message looks right when non-exist */
+
+for (int i = 0; i < (spool_is_split ? 2:1); i++)
+ {
+ message_subdir[0] = i != 0 ? (US client_data)[5] : 0;
+ fname = spool_fname(US"msglog", message_subdir, US client_data, US"");
+ if ((f = fopen(CS fname, "r")))
+ break;
+ }
+
+if (!f)
+ text_showf(text, "%s: %s\n", fname, strerror(errno));
+else
+ {
+ uschar buffer[256];
+ while (Ufgets(buffer, sizeof(buffer), f) != NULL) text_show(text, buffer);
+ fclose(f);
+ }
+}
+
+
+
+/*************************************************
+* Display the message body *
+*************************************************/
+
+static void
+bodyAction(Widget w, XtPointer client_data, XtPointer call_data)
+{
+Widget text = text_create(US client_data, text_depth);
+FILE *f = NULL;
+
+for (int i = 0; i < (spool_is_split? 2:1); i++)
+ {
+ uschar * fname;
+ message_subdir[0] = i != 0 ? (US client_data)[5] : 0;
+ fname = spool_fname(US"input", message_subdir, US client_data, US"-D");
+ if ((f = fopen(CS fname, "r")))
+ break;
+ }
+
+if (!f)
+ text_showf(text, "Failed to open file: %s\n", strerror(errno));
+else
+ {
+ uschar buffer[256];
+ int count = 0;
+
+ while (Ufgets(buffer, sizeof(buffer), 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;
+ }
+
+if ( fcntl(pipe_fd[0], F_SETFL, O_NONBLOCK)
+ || fcntl(pipe_fd[1], F_SETFL, O_NONBLOCK))
+ {
+ perror("set nonblocking on pipe");
+ exit(1);
+ }
+
+/* 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)
+ {
+ if (q->sender) 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(errno)); 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)
+{
+ActOnMessage(US client_data, US"-v -M", US"");
+}
+
+/*************************************************
+* Cause a message to be Frozen *
+*************************************************/
+
+static void
+freezeAction(Widget w, XtPointer client_data, XtPointer call_data)
+{
+ActOnMessage(US client_data, US"-Mf", US"");
+}
+
+/*************************************************
+* Cause a message to be thawed *
+*************************************************/
+
+static void
+thawAction(Widget w, XtPointer client_data, XtPointer call_data)
+{
+ActOnMessage(US 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);
+
+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)
+{
+Ustrncpy(actioned_message, client_data, 24);
+actioned_message[23] = '\0';
+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)
+{
+Ustrncpy(actioned_message, client_data, 24);
+actioned_message[23] = '\0';
+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)
+{
+ActOnMessage(US 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;
+
+Ustrncpy(actioned_message, client_data, 24);
+actioned_message[23] = '\0';
+q = find_queue(actioned_message, queue_noop, 0);
+sender = !q ? 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)
+{
+ActOnMessage(US client_data, US"-v -Mg", US"");
+}
+
+/*************************************************
+* Cause a message to be cancelled *
+*************************************************/
+
+static void
+removeAction(Widget w, XtPointer client_data, XtPointer call_data)
+{
+ActOnMessage(US client_data, US"-Mrm", US"");
+}
+
+/*************************************************
+* Display a message's headers *
+*************************************************/
+
+static void
+headersAction(Widget w, XtPointer client_data, XtPointer call_data)
+{
+uschar buffer[256];
+Widget text = text_create(US client_data, text_depth);
+rmark reset_point;
+
+/* 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_mark();
+
+sprintf(CS buffer, "%s-H", US 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=%lu\n", buffer,
+ (unsigned long)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)
+ text_showf(text, "%s sender: <%s>\n", f.sender_local ? "Local" : "Remote",
+ sender_address);
+
+if (recipients_list)
+ {
+ text_show(text, US"Recipients:\n");
+ for (int i = 0; i < recipients_count; i++)
+ text_showf(text, " %s %s\n",
+ tree_search(tree_nonrecipients, recipients_list[i].address)
+ ? "*" : " ",
+ recipients_list[i].address);
+ text_show(text, US"\n");
+ }
+
+for (header_line * next, * h = header_list; h; 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)
+{
+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. */
+
+for (pipe_item * p = pipe_chain; p; p = p->next)
+ for (Widget pp = p->widget; pp; pp = XtParent(pp))
+ if (pp == (Widget)client_data) { p->widget = NULL; return; }
+}
+
+
+
+/*************************************************
+* 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\
+ ");
+
+/* Get the sink and source and the current text pointer */
+
+queue_get_arg[0].value = (XtArgVal)(&queue_text_sink);
+queue_get_arg[1].value = (XtArgVal)(&src);
+queue_get_arg[2].value = (XtArgVal)(&s);
+XtGetValues(w, queue_get_arg, 3);
+
+/* Find the line number of the pointer in the window, and the
+character offset of the top lefthand of the window. */
+
+line = (event->xbutton).y / XawTextSinkMaxHeight(queue_text_sink, 1);
+p = XawTextTopPosition(w);
+
+/* Find the start of the line on which the button was clicked. */
+
+i = line;
+while (i-- > 0)
+ {
+ while (s[p] != 0 && s[p++] != '\n');
+ }
+
+/* Now pointing either at 0 or 1st uschar after \n, or very 1st uschar.
+If 0, the click was beyond the end of the data; just set up a dummy
+menu. (Not easy to ignore as several actions are specified for the
+mouse click and it expects this one to set up a menu.) If on a
+continuation line, move back to the main line. */
+
+if (s[p] == 0)
+ {
+ menushell_arg[0].value = (XtArgVal)"No message selected";
+ menushell = XtCreatePopupShell("menu", simpleMenuWidgetClass,
+ queue_widget, menushell_arg, XtNumber(menushell_arg));
+ XtAddCallback(menushell, "popdownCallback", popdownAction, NULL);
+ xs_SetValues(menushell, 2,
+ "cursor", XCreateFontCursor(X_display, XC_arrow),
+ "translations", menu_trans);
+
+ /* To keep the widgets in XFree86 happy, we have to create at least one menu
+ item, it seems. (Openwindows doesn't mind a menu with no items.) Otherwise
+ there's a complaint about a zero width menu, and a crash. */
+
+ menu_line = XtCreateManagedWidget("line", smeLineObjectClass, menushell,
+ NULL, 0);
+
+ item_99_arg[0].value = (XtArgVal)menu_line;
+ (void)XtCreateManagedWidget("item99", smeBSBObjectClass, menushell,
+ item_99_arg, XtNumber(item_99_arg));
+
+ highlighted_x = -1;
+ return;
+ }
+
+while (p > 0 && s[p+11] == ' ')
+ {
+ line--;
+ p--;
+ while (p > 0 && s[p-1] != '\n') p--;
+ }
+
+/* Now pointing at first character of a main line. */
+
+Ustrncpy(message_id, s+p+11, MESSAGE_ID_LENGTH);
+message_id[MESSAGE_ID_LENGTH] = 0;
+
+/* Highlight the line being menued, and save its parameters so that it
+can be de-highlighted at popdown. */
+
+highlighted_start = highlighted_end = p;
+while (s[highlighted_end] != '\n') highlighted_end++;
+highlighted_x = 17;
+highlighted_y = line * XawTextSinkMaxHeight(queue_text_sink, 1) + 2;
+
+XawTextSinkDisplayText(queue_text_sink,
+ highlighted_x, highlighted_y,
+ highlighted_start, highlighted_end, 1);
+
+/* Create the popup shell and the other widgets that comprise the menu.
+Set the translations and pointer shape, and add the callback pointers. */
+
+menushell_arg[0].value = (XtArgVal)message_id;
+menushell = XtCreatePopupShell("menu", simpleMenuWidgetClass,
+ queue_widget, menushell_arg, XtNumber(menushell_arg));
+XtAddCallback(menushell, "popdownCallback", popdownAction, NULL);
+
+xs_SetValues(menushell, 2,
+ "cursor", XCreateFontCursor(X_display, XC_arrow),
+ "translations", menu_trans);
+
+menu_line = XtCreateManagedWidget("line", smeLineObjectClass, menushell,
+ NULL, 0);
+
+item_1_arg[0].value = (XtArgVal)menu_line;
+item_1 = XtCreateManagedWidget("item1", smeBSBObjectClass, menushell,
+ item_1_arg, XtNumber(item_1_arg));
+XtAddCallback(item_1, "callback", msglogAction, (XtPointer)message_id);
+
+item_2_arg[0].value = (XtArgVal)item_1;
+item_2 = XtCreateManagedWidget("item2", smeBSBObjectClass, menushell,
+ item_2_arg, XtNumber(item_2_arg));
+XtAddCallback(item_2, "callback", headersAction, (XtPointer)message_id);
+
+item_3_arg[0].value = (XtArgVal)item_2;
+item_3 = XtCreateManagedWidget("item3", smeBSBObjectClass, menushell,
+ item_3_arg, XtNumber(item_3_arg));
+XtAddCallback(item_3, "callback", bodyAction, (XtPointer)message_id);
+
+item_4_arg[0].value = (XtArgVal)item_3;
+item_4 = XtCreateManagedWidget("item4", smeBSBObjectClass, menushell,
+ item_4_arg, XtNumber(item_4_arg));
+XtAddCallback(item_4, "callback", deliverAction, (XtPointer)message_id);
+
+item_5_arg[0].value = (XtArgVal)item_4;
+item_5 = XtCreateManagedWidget("item5", smeBSBObjectClass, menushell,
+ item_5_arg, XtNumber(item_5_arg));
+XtAddCallback(item_5, "callback", freezeAction, (XtPointer)message_id);
+
+item_6_arg[0].value = (XtArgVal)item_5;
+item_6 = XtCreateManagedWidget("item6", smeBSBObjectClass, menushell,
+ item_6_arg, XtNumber(item_6_arg));
+XtAddCallback(item_6, "callback", thawAction, (XtPointer)message_id);
+
+item_7_arg[0].value = (XtArgVal)item_6;
+item_7 = XtCreateManagedWidget("item7", smeBSBObjectClass, menushell,
+ item_7_arg, XtNumber(item_7_arg));
+XtAddCallback(item_7, "callback", giveupAction, (XtPointer)message_id);
+
+item_8_arg[0].value = (XtArgVal)item_7;
+item_8 = XtCreateManagedWidget("item8", smeBSBObjectClass, menushell,
+ item_8_arg, XtNumber(item_8_arg));
+XtAddCallback(item_8, "callback", removeAction, (XtPointer)message_id);
+
+item_9_arg[0].value = (XtArgVal)item_8;
+item_9 = XtCreateManagedWidget("item9", smeBSBObjectClass, menushell,
+ item_9_arg, XtNumber(item_9_arg));
+
+item_10_arg[0].value = (XtArgVal)item_9;
+item_10 = XtCreateManagedWidget("item10", smeBSBObjectClass, menushell,
+ item_10_arg, XtNumber(item_10_arg));
+XtAddCallback(item_10, "callback", addrecipAction, (XtPointer)message_id);
+
+item_11_arg[0].value = (XtArgVal)item_10;
+item_11 = XtCreateManagedWidget("item11", smeBSBObjectClass, menushell,
+ item_11_arg, XtNumber(item_11_arg));
+XtAddCallback(item_11, "callback", markdelAction, (XtPointer)message_id);
+
+item_12_arg[0].value = (XtArgVal)item_11;
+item_12 = XtCreateManagedWidget("item12", smeBSBObjectClass, menushell,
+ item_12_arg, XtNumber(item_12_arg));
+XtAddCallback(item_12, "callback", markalldelAction, (XtPointer)message_id);
+
+item_13_arg[0].value = (XtArgVal)item_12;
+item_13 = XtCreateManagedWidget("item13", smeBSBObjectClass, menushell,
+ item_13_arg, XtNumber(item_13_arg));
+XtAddCallback(item_13, "callback", editsenderAction, (XtPointer)message_id);
+
+/* Arrange that the menu pops up with the first item selected. */
+
+xs_SetValues(menushell, 1, "popupOnEntry", item_1);
+
+/* Flag that the menu is up to suppress queue updates. */
+
+menu_is_up = TRUE;
+}
+
+/* End of em_menu.c */
diff --git a/exim_monitor/em_queue.c b/exim_monitor/em_queue.c
new file mode 100644
index 0000000..9badd24
--- /dev/null
+++ b/exim_monitor/em_queue.c
@@ -0,0 +1,828 @@
+/*************************************************
+* Exim Monitor *
+*************************************************/
+
+/* Copyright (c) University of Cambridge 1995 - 2018 */
+/* Copyright (c) The Exim Maintainers 2020 - 2022 */
+/* 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 an ACL variable *
+*************************************************/
+
+/* The spool_read_header() function calls acl_var_create() when it reads in an
+ACL variable. We know that in this case, the variable will be new, not re-used,
+so this is a cut-down version, to save including the whole acl.c module (which
+would need conditional compilation to cut most of it out). */
+
+tree_node *
+acl_var_create(uschar *name)
+{
+tree_node *node, **root;
+root = name[0] == 'c' ? &acl_var_c : &acl_var_m;
+node = store_get(sizeof(tree_node) + Ustrlen(name), GET_UNTAINTED);
+Ustrcpy(node->name, name);
+node->data.ptr = NULL;
+(void)tree_insertnode(root, node);
+return node;
+}
+
+
+
+/*************************************************
+* Set up new queue item *
+*************************************************/
+
+static queue_item *
+set_up(uschar *name, int dir_char)
+{
+int i, rc, save_errno;
+struct stat statdata;
+rmark 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;
+Ustrncpy(q->name, name, sizeof(q->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_mark();
+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.tv_sec;
+ 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 = " OFF_T_FMT " ***",
+ statbuf.st_size);
+ else msg = US"*** Format error in spool file ***";
+ }
+ else msg = US"*** Cannot read spool file ***";
+
+ if (rc == spool_read_hdrerror)
+ {
+ (void)find_dest(q, msg, dest_add, FALSE);
+ }
+ else
+ {
+ f.deliver_freeze = FALSE;
+ sender_address = msg;
+ recipients_count = 0;
+ }
+ }
+
+/* Now set up the remaining data. */
+
+q->frozen = f.deliver_freeze;
+
+if (f.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;
+
+snprintf(CS buffer, sizeof(buffer), "%s/input/%s/%s/%s-D",
+ spool_directory, queue_name, 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;
+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 */
+ DIR *dd;
+ struct dirent *ent;
+
+ if (subdirchar != 0)
+ {
+ input_dir[subptr] = '/';
+ input_dir[subptr+1] = subdirchar;
+ }
+
+ if (!(dd = exim_opendir(input_dir))) continue;
+
+ while ((ent = readdir(dd)))
+ {
+ 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 + 1];
+ 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!*/
+
+for (p = queue_index[0]; p; )
+ if (!p->seen)
+ {
+ queue_item * next = p->next;
+ if (p->prev)
+ p->prev->next = next;
+ else
+ queue_index[0] = next;
+ if (next)
+ next->prev = p->prev;
+ else
+ {
+ 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;
+ }
+ 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;
+rmark reset_point;
+struct stat statdata;
+uschar buffer[1024];
+
+message_subdir[0] = p->dir_char;
+
+snprintf(CS buffer, sizeof(buffer), "%s/input/%s/%s/%s-J",
+ spool_directory, queue_name, message_subdir, p->name);
+
+if (!(jread = fopen(CS buffer, "r")))
+ {
+ snprintf(CS buffer, sizeof(buffer), "%s/input/%s/%s/%s-H",
+ spool_directory, queue_name, 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_mark();
+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)
+ for (i = 0; i < recipients_count; i++)
+ {
+ uschar * pp;
+ uschar * r = recipients_list[i].address;
+ tree_node * node;
+
+ if (!(node = tree_search(tree_nonrecipients, r)))
+ node = tree_search(tree_nonrecipients, string_copylc(r));
+
+ if ((pp = strstric(r+1, qualify_domain, FALSE)) && *(--pp) == '@')
+ *pp = 0;
+ if (!node)
+ (void)find_dest(p, r, dest_add, FALSE);
+ else
+ (void)find_dest(p, r, dest_remove, FALSE);
+ }
+
+/* We also need to scan the tree of non-recipients, which might
+contain child addresses that are not in the recipients list, but
+which may have got onto the address list as a result of eximon
+noticing an == line in the log. Then remember the update time,
+recover the dynamic store, and we are done. */
+
+scan_tree(p, tree_nonrecipients);
+p->update_time = statdata.st_mtime;
+store_reset(reset_point);
+}
+
+
+
+/*************************************************
+* Display queue data *
+*************************************************/
+
+/* The present implementation simple re-writes the entire information each
+time. Take some care to keep the scrolled position as it previously was, but,
+if it was at the bottom, keep it at the bottom. Take note of any hide list, and
+time out the entries as appropriate. */
+
+void
+queue_display(void)
+{
+int now = (int)time(NULL);
+queue_item *p = queue_index[0];
+
+if (menu_is_up) return; /* Avoid nasty interactions */
+
+text_empty(queue_widget);
+
+while (p != NULL)
+ {
+ int count = 1;
+ dest_item *dd, *ddd;
+ uschar u = 'm';
+ int t = (now - p->input_time)/60; /* minutes on queue */
+
+ if (t > 90)
+ {
+ u = 'h';
+ t = (t + 30)/60;
+ if (t > 72)
+ {
+ u = 'd';
+ t = (t + 12)/24;
+ if (t > 99) /* someone had > 99 days */
+ {
+ u = 'w';
+ t = (t + 3)/7;
+ if (t > 99) /* so, just in case */
+ {
+ u = 'y';
+ t = (t + 26)/52;
+ }
+ }
+ }
+ }
+
+ update_recipients(p); /* update destinations */
+
+ /* Can't set this earlier, as header data may change things. */
+
+ dd = p->destinations;
+
+ /* Check to see if this message is on the hide list; if any hide
+ item has timed out, remove it from the list. Hide if all destinations
+ are on the hide list. */
+
+ for (ddd = dd; ddd != NULL; ddd = ddd->next)
+ {
+ skip_item *sk;
+ skip_item **skp;
+ int len_address;
+
+ if (ddd->address[0] == '*') break;
+ len_address = Ustrlen(ddd->address);
+
+ for (skp = &queue_skip; ; skp = &(sk->next))
+ {
+ int len_skip;
+
+ sk = *skp;
+ while (sk != NULL && now >= sk->reveal)
+ {
+ *skp = sk->next;
+ store_free(sk);
+ sk = *skp;
+ if (queue_skip == NULL)
+ {
+ XtDestroyWidget(unhide_widget);
+ unhide_widget = NULL;
+ }
+ }
+ if (sk == NULL) break;
+
+ /* If this address matches the skip item, break (sk != NULL) */
+
+ len_skip = Ustrlen(sk->text);
+ if (len_skip <= len_address &&
+ Ustrcmp(ddd->address + len_address - len_skip, sk->text) == 0)
+ break;
+ }
+
+ if (sk == NULL) break;
+ }
+
+ /* Don't use more than one call of anon() in one statement - it uses
+ a fixed static buffer. */
+
+ if (ddd != NULL || dd == NULL)
+ {
+ text_showf(queue_widget, "%c%2d%c %s %s %-8s ",
+ (p->frozen)? '*' : ' ',
+ t, u,
+ string_format_size(p->size, big_buffer),
+ p->name,
+ (p->sender == NULL)? US" " :
+ (p->sender[0] == 0)? US"<> " : anon(p->sender));
+
+ text_showf(queue_widget, "%s%s%s",
+ (dd == NULL || dd->address[0] == '*')? "" : "<",
+ (dd == NULL)? US"" : anon(dd->address),
+ (dd == NULL || dd->address[0] == '*')? "" : ">");
+
+ if (dd != NULL && dd->parent != NULL && dd->parent->address[0] != '*')
+ text_showf(queue_widget, " parent <%s>", anon(dd->parent->address));
+
+ text_show(queue_widget, US"\n");
+
+ if (dd != NULL) dd = dd->next;
+ while (dd != NULL && count++ < queue_max_addresses)
+ {
+ text_showf(queue_widget, " <%s>",
+ anon(dd->address));
+ if (dd->parent != NULL && dd->parent->address[0] != '*')
+ text_showf(queue_widget, " parent <%s>", anon(dd->parent->address));
+ text_show(queue_widget, US"\n");
+ dd = dd->next;
+ }
+ if (dd != NULL)
+ text_showf(queue_widget, " ...\n");
+ }
+
+ p = p->next;
+ }
+}
+
+/* End of em_queue.c */
diff --git a/exim_monitor/em_strip.c b/exim_monitor/em_strip.c
new file mode 100644
index 0000000..03864d2
--- /dev/null
+++ b/exim_monitor/em_strip.c
@@ -0,0 +1,266 @@
+/*************************************************
+* Exim Monitor *
+*************************************************/
+
+/* Copyright (c) University of Cambridge 1995 - 2009 */
+/* 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 previous 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 = (long)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, US"%");
+ 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)(long)stripchart_count);
+
+stripchart_last_total[stripchart_count] = 0;
+stripchart_max[stripchart_count] = 10;
+stripchart_midmax[stripchart_count] = 0;
+stripchart_name[stripchart_count] = title;
+stripchart_prev_label = stripchart_label[stripchart_count] = label;
+stripchart_prev_chart = chart;
+stripchart_total[stripchart_count] = 0;
+stripchart_count++;
+}
+
+/* End of em_strip.c */
diff --git a/exim_monitor/em_text.c b/exim_monitor/em_text.c
new file mode 100644
index 0000000..3a36829
--- /dev/null
+++ b/exim_monitor/em_text.c
@@ -0,0 +1,73 @@
+/*************************************************
+* Exim Monitor *
+*************************************************/
+
+/* Copyright (c) University of Cambridge 1995 - 2012 */
+/* 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, ...) PRINTF_FUNCTION(2,3);
+
+void text_showf(Widget w, char *s, ...)
+{
+va_list ap;
+uschar buffer[1024];
+va_start(ap, s);
+vsprintf(CS buffer, s, ap);
+va_end(ap);
+text_show(w, buffer);
+}
+
+/* End of em_text.c */
diff --git a/exim_monitor/em_version.c b/exim_monitor/em_version.c
new file mode 100644
index 0000000..b627a6e
--- /dev/null
+++ b/exim_monitor/em_version.c
@@ -0,0 +1,65 @@
+/*************************************************
+* Exim Monitor *
+*************************************************/
+
+/* Copyright (c) University of Cambridge 1995 - 2018 */
+/* Copyright (c) The Exim Maintainers 2020 - 2021 */
+/* See the file NOTICE for conditions of use and distribution. */
+
+#define EM_VERSION_C
+
+/* Needed by macros.h */
+/* Some systems have PATH_MAX and some have MAX_PATH_LEN. */
+
+#ifndef PATH_MAX
+# ifdef MAX_PATH_LEN
+# define PATH_MAX MAX_PATH_LEN
+# else
+# define PATH_MAX 1024
+# endif
+#endif
+
+#include "mytypes.h"
+#include "store.h"
+#include "macros.h"
+#include <string.h>
+#include <stdlib.h>
+
+#include "version.h"
+
+extern uschar *version_string;
+extern uschar *version_date;
+
+void
+version_init(void)
+{
+int i = 0;
+uschar today[20];
+
+version_string = US"2.06";
+
+#ifdef EXIM_BUILD_DATE_OVERRIDE
+/* Reproducible build support; build tooling should have given us something looking like
+ * "25-Feb-2017 20:15:40" in EXIM_BUILD_DATE_OVERRIDE based on $SOURCE_DATE_EPOCH in environ
+ * per <https://reproducible-builds.org/specs/source-date-epoch/>
+ */
+version_date = US malloc(32);
+version_date[0] = 0;
+Ustrncat(version_date, EXIM_BUILD_DATE_OVERRIDE, 31);
+
+#else
+Ustrcpy(today, US __DATE__);
+if (today[4] == ' ') i = 1;
+today[3] = today[6] = '-';
+
+version_date = US 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, US" ");
+Ustrcat(version_date, US __TIME__);
+#endif
+}
+
+/* End of em_version.c */
diff --git a/exim_monitor/em_xs.c b/exim_monitor/em_xs.c
new file mode 100644
index 0000000..ee91f7c
--- /dev/null
+++ b/exim_monitor/em_xs.c
@@ -0,0 +1,45 @@
+/*************************************************
+* Exim Monitor *
+*************************************************/
+
+/* Copyright (c) University of Cambridge, 1995 - 2016 */
+/* 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)? store_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);
+ }
+va_end(ap);
+XtSetValues(w, aa, num_args);
+if (num_args > 15) store_free(aa);
+}
+
+/* End of em_xs.c */