diff options
Diffstat (limited to 'exim_monitor')
-rw-r--r-- | exim_monitor/EDITME | 179 | ||||
-rw-r--r-- | exim_monitor/em_StripChart.c | 504 | ||||
-rw-r--r-- | exim_monitor/em_TextPop.c | 767 | ||||
-rw-r--r-- | exim_monitor/em_globals.c | 239 | ||||
-rw-r--r-- | exim_monitor/em_hdr.h | 329 | ||||
-rw-r--r-- | exim_monitor/em_init.c | 238 | ||||
-rw-r--r-- | exim_monitor/em_log.c | 411 | ||||
-rw-r--r-- | exim_monitor/em_main.c | 953 | ||||
-rw-r--r-- | exim_monitor/em_menu.c | 930 | ||||
-rw-r--r-- | exim_monitor/em_queue.c | 828 | ||||
-rw-r--r-- | exim_monitor/em_strip.c | 266 | ||||
-rw-r--r-- | exim_monitor/em_text.c | 73 | ||||
-rw-r--r-- | exim_monitor/em_version.c | 65 | ||||
-rw-r--r-- | exim_monitor/em_xs.c | 45 |
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 */ |