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