fix: proxy-protocol (CVE-2023-41227) Bug 3031
[exim.git] / src / exim_monitor / em_main.c
1 /*************************************************
2 *                  Exim Monitor                  *
3 *************************************************/
4
5 /* Copyright (c) University of Cambridge 1995 - 2018 */
6 /* Copyright (c) The Exim Maintainers 2021 - 2022 */
7 /* See the file NOTICE for conditions of use and distribution. */
8
9
10 #include "em_hdr.h"
11
12 /* This module contains the main program of the Exim monitor, which
13 sets up the world and then lets the XtAppMainLoop function
14 run things off X events. */
15
16
17 /*************************************************
18 *               Static variables                 *
19 *************************************************/
20
21 /* Fallback resources */
22
23 static String fallback_resources[] = {"eximon.geometry: +150+0", NULL};
24
25 /* X11 fixed argument lists */
26
27 static Arg quit_args[] = {
28   {XtNfromVert, (XtArgVal) NULL},         /* must be first */
29   {XtNlabel,    (XtArgVal) " Quit "},
30   {"left",      XawChainLeft},
31   {"right",     XawChainLeft},
32   {"top",       XawChainTop},
33   {"bottom",    XawChainTop}
34 };
35
36 static Arg resize_args[] = {
37   {XtNfromVert, (XtArgVal) NULL},         /* must be first */
38   {XtNfromHoriz,(XtArgVal) NULL},         /* must be second */
39   {XtNlabel,    (XtArgVal) " Size "},
40   {"left",      XawChainLeft},
41   {"right",     XawChainLeft},
42   {"top",       XawChainTop},
43   {"bottom",    XawChainTop}
44 };
45
46 static Arg update_args[] = {
47   {XtNfromVert, (XtArgVal) NULL},         /* must be first */
48   {XtNlabel,    (XtArgVal) " Update "},
49   {"left",      XawChainLeft},
50   {"right",     XawChainLeft},
51   {"top",       XawChainTop},
52   {"bottom",    XawChainTop}
53 };
54
55 static Arg hide_args[] = {
56   {XtNfromVert, (XtArgVal) NULL},         /* must be first */
57   {XtNfromHoriz,(XtArgVal) NULL},         /* must be second */
58   {XtNlabel,    (XtArgVal) " Hide "},
59   {"left",      XawChainLeft},
60   {"right",     XawChainLeft},
61   {"top",       XawChainTop},
62   {"bottom",    XawChainTop}
63 };
64
65 static Arg unhide_args[] = {
66   {XtNfromVert, (XtArgVal) NULL},         /* must be first */
67   {XtNfromHoriz,(XtArgVal) NULL},         /* must be second */
68   {XtNlabel,    (XtArgVal) " Unhide "},
69   {"left",      XawChainLeft},
70   {"right",     XawChainLeft},
71   {"top",       XawChainTop},
72   {"bottom",    XawChainTop}
73 };
74
75 static Arg log_args[] = {
76   {XtNfromVert, (XtArgVal) NULL},         /* must be first */
77   {"editType",  XawtextEdit},
78   {"useStringInPlace", (XtArgVal)TRUE},
79   {"string",    (XtArgVal)""},            /* dummy to get it going */
80   {"scrollVertical", XawtextScrollAlways},
81   {"scrollHorizontal", XawtextScrollAlways},
82   {"right",     XawChainRight},
83   {"top",       XawChainTop},
84   {"bottom",    XawChainTop}
85 };
86
87 static Arg queue_args[] = {
88   {XtNfromVert, (XtArgVal) NULL},         /* must be first */
89   {"editType",  XawtextEdit},
90   {"string",    (XtArgVal)""},            /* dummy to get it going */
91   {"scrollVertical", XawtextScrollAlways},
92   {"right",     XawChainRight},
93   {"top",       XawChainTop},
94   {"bottom",    XawChainBottom}
95 };
96
97 static Arg sizepos_args[] = {
98   {"width",     (XtArgVal)NULL},
99   {"height",    (XtArgVal)NULL},
100   {"x",         (XtArgVal)NULL},
101   {"y",         (XtArgVal)NULL}
102 };
103
104 XtActionsRec menu_action_table[] = {
105   { "menu-create",  menu_create } };
106
107 /* Types of non-message dialog action */
108
109 enum { da_hide };
110
111 /* Miscellaneous local variables */
112
113 static int dialog_action;
114 static int tick_stripchart_accumulator = 999999;
115 static int tick_interval = 2;
116 static int maxposset = 0;
117 static int minposset = 0;
118 static int x_adjustment = -1;
119 static int y_adjustment = -1;
120 static Dimension screenwidth, screenheight;
121 static Dimension original_x, original_y;
122 static Dimension maxposx, maxposy;
123 static Dimension minposx, minposy;
124 static Dimension maxwidth, maxheight;
125 static Widget outer_form_widget;
126 static Widget hide_widget;
127 static Widget above_queue_widget;
128
129
130
131
132 #ifdef STRERROR_FROM_ERRLIST
133 /*************************************************
134 *     Provide strerror() for non-ANSI libraries  *
135 *************************************************/
136
137 /* Some old-fashioned systems still around (e.g. SunOS4) don't have strerror()
138 in their libraries, but can provide the same facility by this simple
139 alternative function. */
140
141 uschar *
142 strerror(int n)
143 {
144 if (n < 0 || n >= sys_nerr) return "unknown error number";
145 return sys_errlist[n];
146 }
147 #endif /* STRERROR_FROM_ERRLIST */
148
149
150
151 /*************************************************
152 *         Handle attempts to write the log       *
153 *************************************************/
154
155 /* The message gets written to stderr when log_write() is called from a
156 utility. The message always gets '\n' added on the end of it. These calls come
157 from modules such as store.c when things go drastically wrong (e.g. malloc()
158 failing). In normal use they won't get obeyed.
159
160 Arguments:
161   selector  not relevant when running a utility
162   flags     not relevant when running a utility
163   format    a printf() format
164   ...       arguments for format
165
166 Returns:    nothing
167 */
168
169 void
170 log_write(unsigned int selector, int flags, const char *format, ...)
171 {
172 va_list ap;
173 va_start(ap, format);
174 vfprintf(stderr, format, ap);
175 fprintf(stderr, "\n");
176 va_end(ap);
177 }
178
179
180
181
182 /*************************************************
183 *        Extract port from address string        *
184 *************************************************/
185
186 /* In the spool file, a host plus port is given as an IP address followed by a
187 dot and a port number. This function decodes this. It is needed by the
188 spool-reading function, and copied here to avoid having to include the whole
189 host.c module. One day the interaction between exim and eximon with regard to
190 included code MUST be tidied up!
191
192 Argument:
193   address    points to the string; if there is a port, the '.' in the string
194              is overwritten with zero to terminate the address
195
196 Returns:     0 if there is no port, else the port number.
197 */
198
199 int
200 host_address_extract_port(uschar * address)
201 {
202 int port = 0;
203 uschar *endptr;
204
205 /* Handle the "bracketed with colon on the end" format */
206
207 if (*address == '[')
208   {
209   uschar *rb = address + 1;
210   while (*rb != 0 && *rb != ']') rb++;
211   if (*rb++ == 0) return 0;             /* Missing ]; leave invalid address */
212   if (*rb == ':')
213     {
214     port = Ustrtol(rb + 1, &endptr, 10);
215     if (*endptr != 0) return 0;         /* Invalid port; leave invalid address */
216     }
217   else if (*rb != 0) return 0;          /* Bad syntax; leave invalid address */
218   memmove(address, address + 1, rb - address - 2);
219   rb[-2] = 0;
220   }
221
222 /* Handle the "dot on the end" format */
223
224 else
225   {
226   int skip = -3;                        /* Skip 3 dots in IPv4 addresses */
227   address--;
228   while (*(++address) != 0)
229     {
230     int ch = *address;
231     if (ch == ':') skip = 0;            /* Skip 0 dots in IPv6 addresses */
232       else if (ch == '.' && skip++ >= 0) break;
233     }
234   if (*address == 0) return 0;
235   port = Ustrtol(address + 1, &endptr, 10);
236   if (*endptr != 0) return 0;           /* Invalid port; leave invalid address */
237   *address = 0;
238   }
239
240 return port;
241 }
242
243
244
245
246 /*************************************************
247 *                SIGCHLD handler                 *
248 *************************************************/
249
250 /* Operations on messages are done in subprocesses; this handler
251 just catches them when they finish. It causes a queue display update
252 unless configured not to. */
253
254 static void sigchld_handler(int sig)
255 {
256 while (waitpid(-1, NULL, WNOHANG) > 0);
257 signal(sig, sigchld_handler);
258 if (action_queue_update) tick_queue_accumulator = 999999;
259 }
260
261
262
263 /*************************************************
264 *             Callback routines                  *
265 *************************************************/
266
267
268 void updateAction(Widget w, XtPointer client_data, XtPointer call_data)
269 {
270 scan_spool_input(TRUE);
271 queue_display();
272 tick_queue_accumulator = 0;
273 }
274
275 void hideAction(Widget w, XtPointer client_data, XtPointer call_data)
276 {
277 actioned_message[0] = 0;
278 dialog_ref_widget = w;
279 dialog_action = da_hide;
280 create_dialog(US"Hide addresses ending with", US"");
281 }
282
283 void unhideAction(Widget w, XtPointer client_data, XtPointer call_data)
284 {
285 skip_item *sk = queue_skip;
286
287 while (sk)
288   {
289   skip_item *next = sk->next;
290   store_free(sk);
291   sk = next;
292   }
293 queue_skip = NULL;
294
295 XtDestroyWidget(unhide_widget);
296 unhide_widget = NULL;
297
298 scan_spool_input(TRUE);
299 queue_display();
300 tick_queue_accumulator = 0;
301 }
302
303 void quitAction(Widget w, XtPointer client_data, XtPointer call_data)
304 {
305 exit(0);
306 }
307
308
309 /* Action when the "Size" button is pressed. This is a kludged up mess
310 that I made work after much messing around. Reading the position of the
311 toplevel widget gets the absolute position of the data portion of the window,
312 excluding the window manager's furniture. However, positioning the toplevel
313 widget's window seems to position the top corner of the furniture under the twm
314 window manager, but not under fwvm and others. The two cases are distinguished
315 by the values of x_adjustment and y_adjustment.
316
317 For twm (adjustment >= 0), one has to fudge the miminizing function to ensure
318 that we go back to exactly the same position as before.
319
320 For fwvm (adjustment < 0), one has to fudge the "top left hand corner"
321 positioning to ensure that the window manager's furniture gets displayed on the
322 screen. I haven't found a way of discovering the thickness of the furniture, so
323 some screwed-in values are used.
324
325 This is all ad hoc, developed by floundering around as I haven't found any
326 documentation that tells me what I really should do. */
327
328 void resizeAction(Widget button, XtPointer client_data, XtPointer call_data)
329 {
330 Dimension x, y;
331 Dimension width, height;
332 XWindowAttributes a;
333 Window w = XtWindow(toplevel_widget);
334
335 /* Get the position and size of the top level widget. */
336
337 sizepos_args[0].value = (XtArgVal)(&width);
338 sizepos_args[1].value = (XtArgVal)(&height);
339 sizepos_args[2].value = (XtArgVal)(&x);
340 sizepos_args[3].value = (XtArgVal)(&y);
341 XtGetValues(toplevel_widget, sizepos_args, 4);
342
343 /* Get the position of the widget's window relative to its parent; this
344 gives the thickness of the window manager's furniture. At least it does
345 in twm. For fwvm it gives zero. The size/movement function uses this data.
346 I tried doing this before entering the main loop, but it didn't always
347 work properly with twm. Running it every time seems to be OK. */
348
349 XGetWindowAttributes(X_display, XtWindow(toplevel_widget), &a);
350 if (a.x != 0) x_adjustment = a.x;
351 if (a.y != 0) y_adjustment = a.y;
352
353 /* If at maximum size, reduce to minimum and move back to where it was
354 when maximized, if that value is set, allowing for the furniture in cases
355 where the positioning includes the furniture. */
356
357 if (width == maxwidth && height == maxheight)
358   {
359   maxposx = x;
360   maxposy = y;
361   maxposset = 1;
362
363   if (minposset)
364     xs_SetValues(toplevel_widget, 4,
365       "width",     min_width,
366       "height",    min_height,
367       "x",         minposx - ((x_adjustment >= 0)? x_adjustment : 0),
368       "y",         minposy - ((y_adjustment >= 0)? y_adjustment : 0));
369   else
370     xs_SetValues(toplevel_widget, 2,
371       "width",     min_width,
372       "height",    min_height);
373   }
374
375 /* Else always expand to maximum. If currently at minimum size, remember where
376 it was for coming back. If we don't have a value for the thickness of the
377 furniture, the implication is that the coordinates position the application
378 window, so we can't use (0,0) because that loses the furniture. Use screwed in
379 values that seem to work with fvwm. */
380
381 else
382   {
383   int xx = x;
384   int yy = y;
385
386   if (width == min_width && height == min_height)
387     {
388     minposx = x;
389     minposy = y;
390     minposset = 1;
391     }
392
393   if ((int)(x + maxwidth) > (int)screenwidth ||
394       (int)(y + maxheight + 10) > (int)screenheight)
395     {
396     if (maxposset)
397       {
398       xx = maxposx - ((x_adjustment >= 0)? x_adjustment : 0);
399       yy = maxposy - ((y_adjustment >= 0)? y_adjustment : 0);
400       }
401     else
402       {
403       if ((int)(x + maxwidth) > (int)screenwidth)
404         xx = (x_adjustment >= 0)? 0 : 4;
405       if ((int)(y + maxheight + 10) > (int)screenheight)
406         yy = (y_adjustment >= 0)? 0 : 21;
407       }
408
409     xs_SetValues(toplevel_widget, 4,
410       "width",     maxwidth,
411       "height",    maxheight,
412       "x",         xx,
413       "y",         yy);
414     }
415
416   else xs_SetValues(toplevel_widget, 2,
417         "width",     maxwidth,
418         "height",    maxheight);
419   }
420
421 /* Ensure the window is at the top */
422
423 XRaiseWindow(X_display, w);
424 }
425
426
427
428
429 /*************************************************
430 *          Handle input from non-msg dialogue    *
431 *************************************************/
432
433 /* The various cases here are: hide domain, (no more yet) */
434
435 void NonMessageDialogue(uschar *s)
436 {
437 skip_item *sk;
438
439 switch(dialog_action)
440   {
441   case da_hide:
442
443   /* Create the unhide button if not present */
444
445   if (unhide_widget == NULL)
446     {
447     unhide_args[0].value = (XtArgVal) above_queue_widget;
448     unhide_args[1].value = (XtArgVal) hide_widget;
449     unhide_widget = XtCreateManagedWidget("unhide", commandWidgetClass,
450       outer_form_widget, unhide_args, XtNumber(unhide_args));
451     XtAddCallback(unhide_widget, "callback",  unhideAction, NULL);
452     }
453
454   /* Add item to skip queue */
455
456   sk = (skip_item *)store_malloc(sizeof(skip_item) + Ustrlen(s));
457   sk->next = queue_skip;
458   queue_skip = sk;
459   Ustrcpy(sk->text, s);
460   sk->reveal = time(NULL) + 60 * 60;
461   scan_spool_input(TRUE);
462   queue_display();
463   tick_queue_accumulator = 0;
464   break;
465   }
466 }
467
468
469
470 /*************************************************
471 *              Ticker function                   *
472 *************************************************/
473
474 /* This function is called initially to set up the starting data
475 values; it then sets a timeout so that it continues to be called
476 every 2 seconds. */
477
478 static void ticker(XtPointer pt, XtIntervalId *i)
479 {
480 pipe_item **pp = &pipe_chain;
481 pipe_item *p = pipe_chain;
482 tick_queue_accumulator += tick_interval;
483 tick_stripchart_accumulator += tick_interval;
484 read_log();
485
486 /* If we have passed the queue update time, we must do a full
487 scan of the queue, checking for new arrivals, etc. This will
488 as a by-product set the count of items for use by the stripchart
489 display. On some systems, SIGCHLD signals can get lost at busy times,
490 so just in case, clean up any completed children here. */
491
492 if (tick_queue_accumulator >= queue_update)
493   {
494   scan_spool_input(TRUE);
495   queue_display();
496   tick_queue_accumulator = 0;
497   if (tick_stripchart_accumulator >= stripchart_update)
498     tick_stripchart_accumulator = 0;
499   while (waitpid(-1, NULL, WNOHANG) > 0);
500   }
501
502 /* Otherwise, if we have exceeded the stripchart interval,
503 do a reduced queue scan that simply provides the count for
504 the stripchart. */
505
506 else if (tick_stripchart_accumulator >= stripchart_update)
507   {
508   scan_spool_input(FALSE);
509   tick_stripchart_accumulator = 0;
510   }
511
512 /* Scan any pipes that are set up for listening to delivery processes,
513 and display their output if their windows are still open. */
514
515 while (p != NULL)
516   {
517   int count;
518   uschar buffer[256];
519
520   while ((count = read(p->fd, buffer, 254)) > 0)
521     {
522     buffer[count] = 0;
523     if (p->widget != NULL) text_show(p->widget, buffer);
524     }
525
526   if (count == 0)
527     {
528     close(p->fd);
529     *pp = p->next;
530     store_free(p);
531     /* If configured, cause display update */
532     if (action_queue_update) tick_queue_accumulator = 999999;
533     }
534
535   else pp = &(p->next);
536
537   p = *pp;
538   }
539
540 /* Reset the timer for next time */
541
542 XtAppAddTimeOut(X_appcon, tick_interval * 1000, ticker, 0);
543 }
544
545
546
547 /*************************************************
548 *             Find Num Lock modifiers            *
549 *************************************************/
550
551 /* Return a string with the modifiers generated by XK_Num_Lock, or return
552 NULL if XK_Num_Lock doesn't generate any modifiers. This is needed because Num
553 Lock isn't always the same modifier on all servers.
554
555 Arguments:
556   display   the Display
557   buf       a buffer in which to put the answers (long enough to hold 5)
558
559 Returns:    points to the buffer, or NULL
560 */
561
562 static uschar *
563 numlock_modifiers(Display *display, uschar *buf)
564 {
565 XModifierKeymap *m;
566 int i, j;
567 uschar *ret = NULL;
568
569 m = XGetModifierMapping(display);
570 if (m == NULL)
571   {
572   printf("Not enough memory\n");
573   exit (EXIT_FAILURE);
574   }
575
576 /* Look at Mod1 through Mod5, and fill in the buffer as necessary. */
577
578 buf[0] = 0;
579 for (i = 3; i < 8; i++)
580   {
581   for (j = 0; j < m->max_keypermod; j++)
582     {
583     if (XKeycodeToKeysym(display, m->modifiermap [i*m->max_keypermod + j], 0)
584         == XK_Num_Lock)
585       {
586       sprintf(CS(buf+Ustrlen(buf)), " Mod%d", i-2);
587       ret = buf;
588       }
589     }
590   }
591
592 XFreeModifiermap(m);
593 return ret;
594 }
595
596
597
598 /*************************************************
599 *               Initialize                       *
600 *************************************************/
601
602 int
603 main(int argc, char **argv)
604 {
605 int i;
606 struct stat statdata;
607 uschar modbuf[] = " Mod1 Mod2 Mod3 Mod4 Mod5";
608 uschar *numlock;
609 Widget stripchart_form_widget,
610        update_widget,
611        quit_widget,
612        resize_widget;
613
614 /* The exim global message_id needs to get set */
615
616 message_id_external = message_id_option + 1;
617 message_id = message_id_external + 1;
618 message_subdir[1] = 0;
619
620 /* Some store needs getting for big_buffer, which is used for
621 constructing file names and things. This call will initialize
622 the store_get() function. */
623
624 store_init();
625 big_buffer = store_get(big_buffer_size, GET_UNTAINTED);
626
627 /* Set up the version string and date and output them */
628
629 version_init();
630 printf("\nExim Monitor version %s (compiled %s) initializing\n",
631   version_string, version_date);
632
633 /* Initialize various things from the environment and arguments. */
634
635 init(argc, USS argv);
636
637 /* Set up the SIGCHLD handler */
638
639 signal(SIGCHLD, sigchld_handler);
640
641 /* Get the buffer for storing the string for the log display. */
642
643 log_display_buffer = US store_malloc(log_buffer_size);
644 log_display_buffer[0] = 0;
645
646 /* Initialize the data structures for the stripcharts */
647
648 stripchart_init();
649
650 /* If log_file contains the empty string, then Exim is running using syslog
651 only, and we can't tail the log. If not, open the log file and position to the
652 end of it. Before doing so, we have to detect whether the log files are
653 datestamped, and if so, sort out the name. The string in log_file already has
654 %s replaced by "main"; if datestamping is occurring, %D or %M will be present.
655 In fact, we don't need to test explicitly - just process the string with
656 string_format.
657
658 Once opened, save the file's inode so that we can detect when the file is
659 switched to another one for non-datestamped files. However, allow the monitor
660 to start up without a log file (can happen if no messages have been sent
661 today.) */
662
663 if (log_file[0] != 0)
664   {
665   /* Do *not* use "%s" here, we need the %D datestamp in the log_file to
666   be expanded! */
667   (void)string_format(log_file_open, sizeof(log_file_open), CS log_file, NULL);
668   log_datestamping = string_datestamp_offset >= 0;
669
670   LOG = fopen(CS log_file_open, "r");
671
672   if (LOG == NULL)
673     {
674     printf("*** eximon warning: can't open log file %s - will try "
675       "periodically\n", log_file_open);
676     }
677   else
678     {
679     fseek(LOG, 0, SEEK_END);
680     log_position = ftell(LOG);
681     if (fstat(fileno(LOG), &statdata))
682       {
683       perror("log file fstat");
684       fclose(LOG);
685       LOG=NULL;
686       }
687     else
688       log_inode = statdata.st_ino;
689     }
690   }
691 else
692   {
693   printf("*** eximon warning: no log file available to tail\n");
694   }
695
696 /* Now initialize the X world and create the top-level widget */
697
698 toplevel_widget = XtAppInitialize(&X_appcon, "Eximon", NULL, 0, &argc, argv,
699   fallback_resources, NULL, 0);
700 X_display = XtDisplay(toplevel_widget);
701 xs_SetValues(toplevel_widget, 4,
702   "title",     window_title,
703   "iconName",  window_title,
704   "minWidth",  min_width,
705   "minHeight", min_height);
706
707
708 /* Create the action for setting up the menu in the queue display
709 window, and register the action for positioning the menu. */
710
711 XtAppAddActions(X_appcon, menu_action_table, 1);
712 XawSimpleMenuAddGlobalActions(X_appcon);
713
714 /* Set up translation tables for the text widgets we use. We don't
715 want all the generality of editing, etc. that the defaults provide.
716 This cannot be done before initializing X - the parser complains
717 about unknown events, modifiers, etc. in an unhelpful way... The
718 queue text widget has a different table which includes the button
719 for popping up the menu. Note that the order of things in these
720 tables is significant. Shift<thing> must come before <thing> as
721 otherwise it isn't noticed. */
722
723 /*
724    <FocusIn>:      display-caret(on)\n\
725    <FocusOut>:     display-caret(off)\n\
726 */
727
728 /* The translation manager sets up passive grabs for the menu popups as a
729 result of MenuPopup(), but the grabs match only the exact modifiers listed,
730 hence combinations with and without caps-lock and num-lock must be given,
731 rather than just one "Shift<Btn1Down>" (or whatever menu_event is set to),
732 despite the fact that that notation (without a leading !) should ignore the
733 state of other modifiers. Thanks to Kevin Ryde for this information, and for
734 the function above that discovers which modifier is Num Lock, because it turns
735 out that it varies from server to server. */
736
737 sprintf(CS big_buffer,
738   "!%s:            menu-create() XawPositionSimpleMenu(menu) MenuPopup(menu)\n\
739    !Lock %s:       menu-create() XawPositionSimpleMenu(menu) MenuPopup(menu)\n\
740   ", menu_event, menu_event);
741
742 numlock = numlock_modifiers(X_display, modbuf); /* Get Num Lock modifier(s) */
743
744 if (numlock != NULL) sprintf(CS big_buffer + Ustrlen(big_buffer),
745   "!%s %s:         menu-create() XawPositionSimpleMenu(menu) MenuPopup(menu)\n\
746    !Lock %s %s:    menu-create() XawPositionSimpleMenu(menu) MenuPopup(menu)\n\
747   ", numlock, menu_event, numlock, menu_event);
748
749 sprintf(CS big_buffer + Ustrlen(big_buffer),
750   "<Btn1Down>:     select-start()\n\
751    <Btn1Motion>:   extend-adjust()\n\
752    <Btn1Up>:       extend-end(PRIMARY,CUT_BUFFER0)\n\
753    <Btn3Down>:     extend-start()\n\
754    <Btn3Motion>:   extend-adjust()\n\
755    <Btn3Up>:       extend-end(PRIMARY,CUT_BUFFER0)\n\
756    <Key>Up:        scroll-one-line-down()\n\
757    <Key>Down:      scroll-one-line-up()\n\
758    Ctrl<Key>R:     search(backward)\n\
759    Ctrl<Key>S:     search(forward)\n\
760   ");
761
762 queue_trans = XtParseTranslationTable(CS big_buffer);
763
764 text_trans = XtParseTranslationTable(
765   "<Btn1Down>:     select-start()\n\
766    <Btn1Motion>:   extend-adjust()\n\
767    <Btn1Up>:       extend-end(PRIMARY,CUT_BUFFER0)\n\
768    <Btn3Down>:     extend-start()\n\
769    <Btn3Motion>:   extend-adjust()\n\
770    <Btn3Up>:       extend-end(PRIMARY,CUT_BUFFER0)\n\
771    <Key>Up:        scroll-one-line-down()\n\
772    <Key>Down:      scroll-one-line-up()\n\
773    Ctrl<Key>R:     search(backward)\n\
774    Ctrl<Key>S:     search(forward)\n\
775   ");
776
777
778 /* Create a toplevel form widget to hold all the other things */
779
780 outer_form_widget = XtCreateManagedWidget("form", formWidgetClass,
781   toplevel_widget, NULL, 0);
782
783 /* Now create an inner form to hold the stripcharts */
784
785 stripchart_form_widget = XtCreateManagedWidget("form", formWidgetClass,
786   outer_form_widget, NULL, 0);
787 xs_SetValues(stripchart_form_widget, 5,
788   "defaultDistance", 8,
789   "left",            XawChainLeft,
790   "right",           XawChainLeft,
791   "top",             XawChainTop,
792   "bottom",          XawChainTop);
793
794 /* Create the queue count stripchart and its label. */
795
796 create_stripchart(stripchart_form_widget, queue_stripchart_name);
797
798 /* If configured, create the size monitoring stripchart, but
799 only if the OS supports statfs(). */
800
801 if (size_stripchart != NULL)
802   {
803 #ifdef HAVE_STATFS
804   if (size_stripchart_name == NULL)
805     {
806     size_stripchart_name = size_stripchart + Ustrlen(size_stripchart) - 1;
807     while (size_stripchart_name > size_stripchart &&
808       *size_stripchart_name == '/') size_stripchart_name--;
809     while (size_stripchart_name > size_stripchart &&
810       *size_stripchart_name != '/') size_stripchart_name--;
811     }
812   create_stripchart(stripchart_form_widget, size_stripchart_name);
813 #else
814   printf("Can't create size stripchart: statfs() function not available\n");
815 #endif
816   }
817
818 /* Now create the configured input/output stripcharts; note
819 the total number includes the queue stripchart. */
820
821 for (i = stripchart_varstart; i < stripchart_number; i++)
822   create_stripchart(stripchart_form_widget, stripchart_title[i]);
823
824 /* Next in vertical order come the Resize & Quit buttons */
825
826 quit_args[0].value = (XtArgVal) stripchart_form_widget;
827 quit_widget = XtCreateManagedWidget("quit", commandWidgetClass,
828   outer_form_widget, quit_args, XtNumber(quit_args));
829 XtAddCallback(quit_widget, "callback",  quitAction, NULL);
830
831 resize_args[0].value = (XtArgVal) stripchart_form_widget;
832 resize_args[1].value = (XtArgVal) quit_widget;
833 resize_widget = XtCreateManagedWidget("resize", commandWidgetClass,
834   outer_form_widget, resize_args, XtNumber(resize_args));
835 XtAddCallback(resize_widget, "callback",  resizeAction, NULL);
836
837 /* In the absence of log tailing, the quit widget is the one above the
838 queue listing. */
839
840 above_queue_widget = quit_widget;
841
842 /* Create an Ascii text widget for the log tail display if we are tailing a
843 log. Skip it if not. */
844
845 if (log_file[0] != 0)
846   {
847   log_args[0].value = (XtArgVal) quit_widget;
848   log_widget = XtCreateManagedWidget("log", asciiTextWidgetClass,
849     outer_form_widget, log_args, XtNumber(log_args));
850   XawTextDisplayCaret(log_widget, TRUE);
851   xs_SetValues(log_widget, 6,
852     "editType",  XawtextEdit,
853     "translations", text_trans,
854     "string",    log_display_buffer,
855     "length",    log_buffer_size,
856     "height",    log_depth,
857     "width",     log_width);
858
859   if (log_font != NULL)
860     {
861     XFontStruct *f = XLoadQueryFont(X_display, CS log_font);
862     if (f != NULL) xs_SetValues(log_widget, 1, "font", f);
863     }
864
865   above_queue_widget = log_widget;
866   }
867
868 /* The update button */
869
870 update_args[0].value = (XtArgVal) above_queue_widget;
871 update_widget = XtCreateManagedWidget("update", commandWidgetClass,
872   outer_form_widget, update_args, XtNumber(update_args));
873 XtAddCallback(update_widget, "callback",  updateAction, NULL);
874
875 /* The hide button */
876
877 hide_args[0].value = (XtArgVal) above_queue_widget;
878 hide_args[1].value = (XtArgVal) update_widget;
879 hide_widget = XtCreateManagedWidget("hide", commandWidgetClass,
880   outer_form_widget, hide_args, XtNumber(hide_args));
881 XtAddCallback(hide_widget, "callback",  hideAction, NULL);
882
883 /* Create an Ascii text widget for the queue display. */
884
885 queue_args[0].value = (XtArgVal) update_widget;
886 queue_widget = XtCreateManagedWidget("queue", asciiTextWidgetClass,
887   outer_form_widget, queue_args, XtNumber(queue_args));
888 XawTextDisplayCaret(queue_widget, TRUE);
889
890 xs_SetValues(queue_widget, 4,
891   "editType",  XawtextEdit,
892   "height",    queue_depth,
893   "width",     queue_width,
894   "translations", queue_trans);
895
896 if (queue_font != NULL)
897   {
898   XFontStruct *f = XLoadQueryFont(X_display, CS queue_font);
899   if (f != NULL) xs_SetValues(queue_widget, 1, "font", f);
900   }
901
902 /* Call the ticker function to get the initial data set up. It
903 arranges to have itself recalled every 2 seconds. */
904
905 ticker(NULL, NULL);
906
907 /* Everything is now set up; this flag is used by the regerror
908 function and also by the queue reader. */
909
910 eximon_initialized = TRUE;
911 printf("\nExim Monitor running\n");
912
913 /* Realize the toplevel and thereby get things displayed */
914
915 XtRealizeWidget(toplevel_widget);
916
917 /* Find out the size of the initial window, and set that as its
918 maximum. While we are at it, get the initial position. */
919
920 sizepos_args[0].value = (XtArgVal)(&maxwidth);
921 sizepos_args[1].value = (XtArgVal)(&maxheight);
922 sizepos_args[2].value = (XtArgVal)(&original_x);
923 sizepos_args[3].value = (XtArgVal)(&original_y);
924 XtGetValues(toplevel_widget, sizepos_args, 4);
925
926 xs_SetValues(toplevel_widget, 2,
927   "maxWidth",  maxwidth,
928   "maxHeight", maxheight);
929
930 /* Set up the size of the screen */
931
932 screenwidth = XDisplayWidth(X_display, 0);
933 screenheight= XDisplayHeight(X_display,0);
934
935 /* Register the action table */
936
937 XtAppAddActions(X_appcon, actionTable, actionTableSize);
938
939 /* Reduce the window to the small size if this is wanted */
940
941 if (start_small) resizeAction(NULL, NULL, NULL);
942
943 /* Enter the application loop which handles things from here
944 onwards. The return statement is never obeyed, but is needed to
945 keep pedantic ANSI compilers happy. */
946
947 XtAppMainLoop(X_appcon);
948
949 return 0;
950 }
951
952 /* End of em_main.c */
953