Check localhost_number expansion for failure.
[exim.git] / src / exim_monitor / em_queue.c
CommitLineData
059ec3d9
PH
1/*************************************************
2* Exim Monitor *
3*************************************************/
4
0a49a7a4 5/* Copyright (c) University of Cambridge 1995 - 2009 */
059ec3d9
PH
6/* See the file NOTICE for conditions of use and distribution. */
7
8
9#include "em_hdr.h"
10
11
12/* This module contains functions to do with scanning exim's
13queue and displaying the data therefrom. */
14
15
16/* If we are anonymizing for screen shots, define a function to anonymize
17addresses. Otherwise, define a macro that does nothing. */
18
19#ifdef ANONYMIZE
20static uschar *anon(uschar *s)
21{
22static uschar anon_result[256];
23uschar *ss = anon_result;
24for (; *s != 0; s++) *ss++ = (*s == '@' || *s == '.')? *s : 'x';
25*ss = 0;
26return anon_result;
27}
28#else
29#define anon(x) x
30#endif
31
32
33/*************************************************
34* Static variables *
35*************************************************/
36
37static int queue_total = 0; /* number of items in queue */
38
39/* Table for turning base-62 numbers into binary */
40
41static uschar tab62[] =
42 {0,1,2,3,4,5,6,7,8,9,0,0,0,0,0,0, /* 0-9 */
43 0,10,11,12,13,14,15,16,17,18,19,20, /* A-K */
44 21,22,23,24,25,26,27,28,29,30,31,32, /* L-W */
45 33,34,35, 0, 0, 0, 0, 0, /* X-Z */
46 0,36,37,38,39,40,41,42,43,44,45,46, /* a-k */
47 47,48,49,50,51,52,53,54,55,56,57,58, /* l-w */
48 59,60,61}; /* x-z */
49
50/* Index for quickly finding things in the ordered queue. */
51
52static queue_item *queue_index[queue_index_size];
53
54
55
56/*************************************************
57* Find/Create/Delete a destination *
58*************************************************/
59
60/* If the action is dest_noop, then just return item or NULL;
61if it is dest_add, then add if not present, and return item;
62if it is dest_remove, remove if present and return NULL. The
63address is lowercased to start with, unless it begins with
64"*", which it does for error messages. */
65
66dest_item *find_dest(queue_item *q, uschar *name, int action, BOOL caseless)
67{
68dest_item *dd;
69dest_item **d = &(q->destinations);
70
71while (*d != NULL)
72 {
73 if ((caseless? strcmpic(name,(*d)->address) : Ustrcmp(name,(*d)->address))
74 == 0)
75 {
76 dest_item *ddd;
77
78 if (action != dest_remove) return *d;
79 dd = *d;
80 *d = dd->next;
81 store_free(dd);
82
83 /* Unset any parent pointers that were to this address */
84
85 for (ddd = q->destinations; ddd != NULL; ddd = ddd->next)
86 {
87 if (ddd->parent == dd) ddd->parent = NULL;
88 }
89
90 return NULL;
91 }
92 d = &((*d)->next);
93 }
94
95if (action != dest_add) return NULL;
96
97dd = (dest_item *)store_malloc(sizeof(dest_item) + Ustrlen(name));
98Ustrcpy(dd->address, name);
99dd->next = NULL;
100dd->parent = NULL;
101*d = dd;
102return dd;
103}
104
105
106
107/*************************************************
108* Clean up a dead queue item *
109*************************************************/
110
111static void clean_up(queue_item *p)
112{
113dest_item *dd = p->destinations;
114while (dd != NULL)
115 {
116 dest_item *next = dd->next;
117 store_free(dd);
118 dd = next;
119 }
120if (p->sender != NULL) store_free(p->sender);
121store_free(p);
122}
123
124
38a0a95f
PH
125/*************************************************
126* Set up an ACL variable *
127*************************************************/
128
129/* The spool_read_header() function calls acl_var_create() when it reads in an
130ACL variable. We know that in this case, the variable will be new, not re-used,
131so this is a cut-down version, to save including the whole acl.c module (which
132would need conditional compilation to cut most of it out). */
133
134tree_node *
135acl_var_create(uschar *name)
136{
137tree_node *node, **root;
138root = (name[0] == 'c')? &acl_var_c : &acl_var_m;
139node = store_get(sizeof(tree_node) + Ustrlen(name));
140Ustrcpy(node->name, name);
141node->data.ptr = NULL;
142(void)tree_insertnode(root, node);
143return node;
144}
145
146
147
059ec3d9
PH
148/*************************************************
149* Set up new queue item *
150*************************************************/
151
152static queue_item *set_up(uschar *name, int dir_char)
153{
154int i, rc, save_errno;
155struct stat statdata;
156void *reset_point;
157uschar *p;
158queue_item *q = (queue_item *)store_malloc(sizeof(queue_item));
159uschar buffer[256];
160
161/* Initialize the block */
162
163q->next = q->prev = NULL;
164q->destinations = NULL;
165Ustrcpy(q->name, name);
166q->seen = TRUE;
167q->frozen = FALSE;
168q->dir_char = dir_char;
169q->sender = NULL;
170q->size = 0;
171
172/* Read the header file from the spool; if there is a failure it might mean
173inaccessibility as a result of protections. A successful read will have caused
174sender_address to get set and the recipients fields to be initialized. If
175there's a format error in the headers, we can still display info from the
176envelope.
177
178Before reading the header remember the position in the dynamic store so that
179we can recover the store into which the header is read. All data read by
180spool_read_header that is to be preserved is copied into malloc store. */
181
182reset_point = store_get(0);
183message_size = 0;
184message_subdir[0] = dir_char;
185sprintf(CS buffer, "%s-H", name);
186rc = spool_read_header(buffer, FALSE, TRUE);
187save_errno = errno;
188
189/* If we failed to read the envelope, compute the input time by
190interpreting the id as a base-62 number. */
191
192if (rc != spool_read_OK && rc != spool_read_hdrerror)
193 {
194 int t = 0;
195 for (i = 0; i < 6; i++) t = t * 62 + tab62[name[i] - '0'];
196 q->update_time = q->input_time = t;
197 }
198
199/* Envelope read; get input time and remove qualify_domain from sender address,
200if it's there. */
201
202else
203 {
204 q->update_time = q->input_time = received_time;
205 if ((p = strstric(sender_address+1, qualify_domain, FALSE)) != NULL &&
206 *(--p) == '@') *p = 0;
207 }
208
209/* If we didn't read the whole header successfully, generate an error
210message. If the envelope was read, this appears as a first recipient;
211otherwise it sets set up in the sender field. */
212
213if (rc != spool_read_OK)
214 {
215 uschar *msg;
216
217 if (save_errno == ERRNO_SPOOLFORMAT)
218 {
219 struct stat statbuf;
220 sprintf(CS big_buffer, "%s/input/%s", spool_directory, buffer);
221 if (Ustat(big_buffer, &statbuf) == 0)
222 msg = string_sprintf("*** Format error in spool file: size = %d ***",
223 statbuf.st_size);
224 else msg = string_sprintf("*** Format error in spool file ***");
225 }
226 else msg = string_sprintf("*** Cannot read spool file ***");
227
228 if (rc == spool_read_hdrerror)
229 {
230 (void)find_dest(q, msg, dest_add, FALSE);
231 }
232 else
233 {
234 deliver_freeze = FALSE;
235 sender_address = msg;
236 recipients_count = 0;
237 }
238 }
239
240/* Now set up the remaining data. */
241
242q->frozen = deliver_freeze;
243
244if (sender_set_untrusted)
245 {
246 if (sender_address[0] == 0)
247 {
248 q->sender = store_malloc(Ustrlen(originator_login) + 6);
249 sprintf(CS q->sender, "<> (%s)", originator_login);
250 }
251 else
252 {
253 q->sender = store_malloc(Ustrlen(sender_address) +
254 Ustrlen(originator_login) + 4);
255 sprintf(CS q->sender, "%s (%s)", sender_address, originator_login);
256 }
257 }
258else
259 {
260 q->sender = store_malloc(Ustrlen(sender_address) + 1);
261 Ustrcpy(q->sender, sender_address);
262 }
263
264sender_address = NULL;
265
266sprintf(CS buffer, "%s/input/%s/%s-D", spool_directory, message_subdir, name);
267if (Ustat(buffer, &statdata) == 0)
268 q->size = message_size + statdata.st_size - SPOOL_DATA_START_OFFSET + 1;
269
270/* Scan and process the recipients list, skipping any that have already
271been delivered, and removing visible names. */
272
273if (recipients_list != NULL)
274 {
275 for (i = 0; i < recipients_count; i++)
276 {
277 uschar *r = recipients_list[i].address;
278 if (tree_search(tree_nonrecipients, r) == NULL)
279 {
280 if ((p = strstric(r+1, qualify_domain, FALSE)) != NULL &&
281 *(--p) == '@') *p = 0;
282 (void)find_dest(q, r, dest_add, FALSE);
283 }
284 }
285 }
286
287/* Recover the dynamic store used by spool_read_header(). */
288
289store_reset(reset_point);
290return q;
291}
292
293
294
295/*************************************************
296* Find/Create a queue item *
297*************************************************/
298
299/* The queue is kept as a doubly-linked list, sorted by name. However,
300to speed up searches, an index into the list is used. This is maintained
301by the scan_spool_input function when it goes down the list throwing
302out entries that are no longer needed. When the action is "add" and
303we don't need to add, mark the found item as seen. */
304
305
306#ifdef never
307static void debug_queue(void)
308{
309int i;
310int count = 0;
311queue_item *p;
312printf("\nqueue_total=%d\n", queue_total);
313
314for (i = 0; i < queue_index_size; i++)
315 printf("index %d = %d %s\n", i, (int)(queue_index[i]),
316 (queue_index[i])->name);
317
318printf("Queue is:\n");
319p = queue_index[0];
320while (p != NULL)
321 {
322 count++;
323 for (i = 0; i < queue_index_size; i++)
324 {
325 if (queue_index[i] == p) printf("count=%d index=%d\n", count, (int)p);
326 }
327 printf("%d %d %d %s\n", (int)p, (int)p->next, (int)p->prev, p->name);
328 p = p->next;
329 }
330}
331#endif
332
333
334
335queue_item *find_queue(uschar *name, int action, int dir_char)
336{
337int first = 0;
338int last = queue_index_size - 1;
339int middle = (first + last)/2;
340queue_item *p, *q, *qq;
341
342/* Handle the empty queue as a special case. */
343
344if (queue_total == 0)
345 {
346 if (action != queue_add) return NULL;
347 if ((qq = set_up(name, dir_char)) != NULL)
348 {
349 int i;
350 for (i = 0; i < queue_index_size; i++) queue_index[i] = qq;
351 queue_total++;
352 return qq;
353 }
354 return NULL;
355 }
356
357/* Also handle insertion at the start or end of the queue
358as special cases. */
359
360if (Ustrcmp(name, (queue_index[0])->name) < 0)
361 {
362 if (action != queue_add) return NULL;
363 if ((qq = set_up(name, dir_char)) != NULL)
364 {
365 qq->next = queue_index[0];
366 (queue_index[0])->prev = qq;
367 queue_index[0] = qq;
368 queue_total++;
369 return qq;
370 }
371 return NULL;
372 }
373
374if (Ustrcmp(name, (queue_index[queue_index_size-1])->name) > 0)
375 {
376 if (action != queue_add) return NULL;
377 if ((qq = set_up(name, dir_char)) != NULL)
378 {
379 qq->prev = queue_index[queue_index_size-1];
380 (queue_index[queue_index_size-1])->next = qq;
381 queue_index[queue_index_size-1] = qq;
382 queue_total++;
383 return qq;
384 }
385 return NULL;
386 }
387
388/* Use binary chopping on the index to get a range of the queue to search
389when the name is somewhere in the middle, if present. */
390
391while (middle > first)
392 {
393 if (Ustrcmp(name, (queue_index[middle])->name) >= 0) first = middle;
394 else last = middle;
395 middle = (first + last)/2;
396 }
397
398/* Now search down the part of the queue in which the item must
399lie if it exists. Both end points are inclusive - though in fact
400the bottom one can only be = if it is the original bottom. */
401
402p = queue_index[first];
403q = queue_index[last];
404
405for (;;)
406 {
407 int c = Ustrcmp(name, p->name);
408
409 /* Already on queue; mark seen if required. */
410
411 if (c == 0)
412 {
413 if (action == queue_add) p->seen = TRUE;
414 return p;
415 }
416
417 /* Not on the queue; add an entry if required. Note that set-up might
418 fail (the file might vanish under our feet). Note also that we know
419 there is always a previous item to p because the end points are
420 inclusive. */
421
422 else if (c < 0)
423 {
424 if (action == queue_add)
425 {
426 if ((qq = set_up(name, dir_char)) != NULL)
427 {
428 qq->next = p;
429 qq->prev = p->prev;
430 p->prev->next = qq;
431 p->prev = qq;
432 queue_total++;
433 return qq;
434 }
435 }
436 return NULL;
437 }
438
439 /* Control should not reach here if p == q, because the name
440 is supposed to be <= the name of the bottom item. */
441
442 if (p == q) return NULL;
443
444 /* Else might be further down the queue; continue */
445
446 p = p->next;
447 }
448
449/* Control should never reach here. */
450}
451
452
453
454/*************************************************
455* Scan the exim spool directory *
456*************************************************/
457
458/* If we discover that there are subdirectories, set a flag so that the menu
459code knows to look for them. We count the entries to set the value for the
460queue stripchart, and set up data for the queue display window if the "full"
461option is given. */
462
463void scan_spool_input(int full)
464{
465int i;
466int subptr;
467int subdir_max = 1;
468int count = 0;
469int indexptr = 1;
470queue_item *p;
471struct dirent *ent;
472DIR *dd;
473uschar input_dir[256];
474uschar subdirs[64];
475
476subdirs[0] = 0;
477stripchart_total[0] = 0;
478
479sprintf(CS input_dir, "%s/input", spool_directory);
480subptr = Ustrlen(input_dir);
481input_dir[subptr+2] = 0; /* terminator for lengthened name */
482
483/* Loop for each spool file on the queue - searching any subdirectories that
484may exist. When initializing eximon, every file will have to be read. To show
485there is progress, output a dot for each one to the standard output. */
486
487for (i = 0; i < subdir_max; i++)
488 {
489 int subdirchar = subdirs[i]; /* 0 for main directory */
490 if (subdirchar != 0)
491 {
492 input_dir[subptr] = '/';
493 input_dir[subptr+1] = subdirchar;
494 }
495
496 dd = opendir(CS input_dir);
497 if (dd == NULL) continue;
498
499 while ((ent = readdir(dd)) != NULL)
500 {
501 uschar *name = US ent->d_name;
502 int len = Ustrlen(name);
503
504 /* If we find a single alphameric sub-directory on the first
505 pass, add it to the list for subsequent scans, and remember that
506 we are dealing with a split directory. */
507
508 if (i == 0 && len == 1 && isalnum(*name))
509 {
510 subdirs[subdir_max++] = *name;
511 spool_is_split = TRUE;
512 continue;
513 }
514
515 /* Otherwise, if it is a header spool file, add it to the list */
516
517 if (len == SPOOL_NAME_LENGTH &&
518 name[SPOOL_NAME_LENGTH - 2] == '-' &&
519 name[SPOOL_NAME_LENGTH - 1] == 'H')
520 {
0d46a8c8 521 uschar basename[SPOOL_NAME_LENGTH + 1];
059ec3d9
PH
522 stripchart_total[0]++;
523 if (!eximon_initialized) { printf("."); fflush(stdout); }
524 Ustrcpy(basename, name);
525 basename[SPOOL_NAME_LENGTH - 2] = 0;
526 if (full) find_queue(basename, queue_add, subdirchar);
527 }
528 }
529 closedir(dd);
530 }
531
532/* If simply counting the number, we are done; same if there are no
533items in the in-store queue. */
534
535if (!full || queue_total == 0) return;
536
537/* Now scan the queue and remove any items that were not in the directory. At
538the same time, set up the index pointers into the queue. Because we are
539removing items, the total that we are comparing against isn't actually correct,
540but in a long queue it won't make much difference, and in a short queue it
541doesn't matter anyway!*/
542
543p = queue_index[0];
544while (p != NULL)
545 {
546 if (!p->seen)
547 {
548 queue_item *next = p->next;
549 if (p->prev == NULL) queue_index[0] = next;
550 else p->prev->next = next;
551 if (next == NULL)
552 {
553 int i;
554 queue_item *q = queue_index[queue_index_size-1];
555 for (i = queue_index_size - 1; i >= 0; i--)
556 if (queue_index[i] == q) queue_index[i] = p->prev;
557 }
558 else next->prev = p->prev;
559 clean_up(p);
560 queue_total--;
561 p = next;
562 }
563 else
564 {
565 if (++count > (queue_total * indexptr)/(queue_index_size-1))
566 {
567 queue_index[indexptr++] = p;
568 }
569 p->seen = FALSE; /* for next time */
570 p = p->next;
571 }
572 }
573
574/* If a lot of messages have been removed at the bottom, we may not
575have got the index all filled in yet. Make sure all the pointers
576are legal. */
577
578while (indexptr < queue_index_size - 1)
579 {
580 queue_index[indexptr++] = queue_index[queue_index_size-1];
581 }
582}
583
584
585
586
587/*************************************************
588* Update the recipients list for a message *
589*************************************************/
590
591/* We read the spool file only if its update time differs from last time,
592or if there is a journal file in existence. */
593
594/* First, a local subroutine to scan the non-recipients tree and
595remove any of them from the address list */
596
597static void
598scan_tree(queue_item *p, tree_node *tn)
599{
600if (tn != NULL)
601 {
602 if (tn->left != NULL) scan_tree(p, tn->left);
603 if (tn->right != NULL) scan_tree(p, tn->right);
604 (void)find_dest(p, tn->name, dest_remove, FALSE);
605 }
606}
607
608/* The main function */
609
610static void update_recipients(queue_item *p)
611{
612int i;
613FILE *jread;
614void *reset_point;
615struct stat statdata;
616uschar buffer[1024];
617
618message_subdir[0] = p->dir_char;
619
620sprintf(CS buffer, "%s/input/%s/%s-J", spool_directory, message_subdir, p->name);
621jread = fopen(CS buffer, "r");
622if (jread == NULL)
623 {
624 sprintf(CS buffer, "%s/input/%s/%s-H", spool_directory, message_subdir, p->name);
625 if (Ustat(buffer, &statdata) < 0 || p->update_time == statdata.st_mtime)
626 return;
627 }
628
629/* Get the contents of the header file; if any problem, just give up.
630Arrange to recover the dynamic store afterwards. */
631
632reset_point = store_get(0);
633sprintf(CS buffer, "%s-H", p->name);
634if (spool_read_header(buffer, FALSE, TRUE) != spool_read_OK)
635 {
636 store_reset(reset_point);
637 if (jread != NULL) fclose(jread);
638 return;
639 }
640
641/* If there's a journal file, add its contents to the non-recipients tree */
642
643if (jread != NULL)
644 {
645 while (Ufgets(big_buffer, big_buffer_size, jread) != NULL)
646 {
647 int n = Ustrlen(big_buffer);
648 big_buffer[n-1] = 0;
649 tree_add_nonrecipient(big_buffer);
650 }
651 fclose(jread);
652 }
653
654/* Scan and process the recipients list, removing any that have already
655been delivered, and removing visible names. In the nonrecipients tree,
656domains are lower cased. */
657
658if (recipients_list != NULL)
659 {
660 for (i = 0; i < recipients_count; i++)
661 {
662 uschar *pp;
663 uschar *r = recipients_list[i].address;
664 tree_node *node = tree_search(tree_nonrecipients, r);
665
666 if (node == NULL)
667 {
668 uschar temp[256];
669 uschar *rr = temp;
670 Ustrcpy(temp, r);
671 while (*rr != 0 && *rr != '@') rr++;
672 while (*rr != 0) { *rr = tolower(*rr); rr++; }
673 node = tree_search(tree_nonrecipients, temp);
674 }
675
676 if ((pp = strstric(r+1, qualify_domain, FALSE)) != NULL &&
677 *(--pp) == '@') *pp = 0;
678 if (node == NULL)
679 (void)find_dest(p, r, dest_add, FALSE);
680 else
681 (void)find_dest(p, r, dest_remove, FALSE);
682 }
683 }
684
685/* We also need to scan the tree of non-recipients, which might
686contain child addresses that are not in the recipients list, but
687which may have got onto the address list as a result of eximon
688noticing an == line in the log. Then remember the update time,
689recover the dynamic store, and we are done. */
690
691scan_tree(p, tree_nonrecipients);
692p->update_time = statdata.st_mtime;
693store_reset(reset_point);
694}
695
696
697
698/*************************************************
699* Display queue data *
700*************************************************/
701
702/* The present implementation simple re-writes the entire information each
703time. Take some care to keep the scrolled position as it previously was, but,
704if it was at the bottom, keep it at the bottom. Take note of any hide list, and
705time out the entries as appropriate. */
706
707void
708queue_display(void)
709{
710int now = (int)time(NULL);
711queue_item *p = queue_index[0];
712
713if (menu_is_up) return; /* Avoid nasty interactions */
714
715text_empty(queue_widget);
716
717while (p != NULL)
718 {
719 int count = 1;
720 dest_item *dd, *ddd;
721 uschar u = 'm';
722 int t = (now - p->input_time)/60; /* minutes on queue */
723
724 if (t > 90)
725 {
726 u = 'h';
727 t = (t + 30)/60;
728 if (t > 72)
729 {
730 u = 'd';
731 t = (t + 12)/24;
732 if (t > 99) /* someone had > 99 days */
733 {
734 u = 'w';
735 t = (t + 3)/7;
736 if (t > 99) /* so, just in case */
737 {
738 u = 'y';
739 t = (t + 26)/52;
740 }
741 }
742 }
743 }
744
745 update_recipients(p); /* update destinations */
746
747 /* Can't set this earlier, as header data may change things. */
748
749 dd = p->destinations;
750
751 /* Check to see if this message is on the hide list; if any hide
752 item has timed out, remove it from the list. Hide if all destinations
753 are on the hide list. */
754
755 for (ddd = dd; ddd != NULL; ddd = ddd->next)
756 {
757 skip_item *sk;
758 skip_item **skp;
759 int len_address;
760
761 if (ddd->address[0] == '*') break;
762 len_address = Ustrlen(ddd->address);
763
764 for (skp = &queue_skip; ; skp = &(sk->next))
765 {
766 int len_skip;
767
768 sk = *skp;
769 while (sk != NULL && now >= sk->reveal)
770 {
771 *skp = sk->next;
772 store_free(sk);
773 sk = *skp;
774 if (queue_skip == NULL)
775 {
776 XtDestroyWidget(unhide_widget);
777 unhide_widget = NULL;
778 }
779 }
780 if (sk == NULL) break;
781
782 /* If this address matches the skip item, break (sk != NULL) */
783
784 len_skip = Ustrlen(sk->text);
785 if (len_skip <= len_address &&
786 Ustrcmp(ddd->address + len_address - len_skip, sk->text) == 0)
787 break;
788 }
789
790 if (sk == NULL) break;
791 }
792
793 /* Don't use more than one call of anon() in one statement - it uses
794 a fixed static buffer. */
795
796 if (ddd != NULL || dd == NULL)
797 {
798 text_showf(queue_widget, "%c%2d%c %s %s %-8s ",
799 (p->frozen)? '*' : ' ',
800 t, u,
801 string_format_size(p->size, big_buffer),
802 p->name,
803 (p->sender == NULL)? US" " :
804 (p->sender[0] == 0)? US"<> " : anon(p->sender));
805
806 text_showf(queue_widget, "%s%s%s",
807 (dd == NULL || dd->address[0] == '*')? "" : "<",
808 (dd == NULL)? US"" : anon(dd->address),
809 (dd == NULL || dd->address[0] == '*')? "" : ">");
810
811 if (dd != NULL && dd->parent != NULL && dd->parent->address[0] != '*')
812 text_showf(queue_widget, " parent <%s>", anon(dd->parent->address));
813
814 text_show(queue_widget, US"\n");
815
816 if (dd != NULL) dd = dd->next;
817 while (dd != NULL && count++ < queue_max_addresses)
818 {
819 text_showf(queue_widget, " <%s>",
820 anon(dd->address));
821 if (dd->parent != NULL && dd->parent->address[0] != '*')
822 text_showf(queue_widget, " parent <%s>", anon(dd->parent->address));
823 text_show(queue_widget, US"\n");
824 dd = dd->next;
825 }
826 if (dd != NULL)
827 text_showf(queue_widget, " ...\n");
828 }
829
830 p = p->next;
831 }
832}
833
834/* End of em_queue.c */