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