1 /* $Cambridge: exim/src/exim_monitor/em_queue.c,v 1.4 2006/02/14 14:26:15 ph10 Exp $ */
3 /*************************************************
5 *************************************************/
7 /* Copyright (c) University of Cambridge 1995 - 2006 */
8 /* See the file NOTICE for conditions of use and distribution. */
14 /* This module contains functions to do with scanning exim's
15 queue and displaying the data therefrom. */
18 /* If we are anonymizing for screen shots, define a function to anonymize
19 addresses. Otherwise, define a macro that does nothing. */
22 static uschar *anon(uschar *s)
24 static uschar anon_result[256];
25 uschar *ss = anon_result;
26 for (; *s != 0; s++) *ss++ = (*s == '@' || *s == '.')? *s : 'x';
35 /*************************************************
37 *************************************************/
39 static int queue_total = 0; /* number of items in queue */
41 /* Table for turning base-62 numbers into binary */
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 */
52 /* Index for quickly finding things in the ordered queue. */
54 static queue_item *queue_index[queue_index_size];
58 /*************************************************
59 * Find/Create/Delete a destination *
60 *************************************************/
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. */
68 dest_item *find_dest(queue_item *q, uschar *name, int action, BOOL caseless)
71 dest_item **d = &(q->destinations);
75 if ((caseless? strcmpic(name,(*d)->address) : Ustrcmp(name,(*d)->address))
80 if (action != dest_remove) return *d;
85 /* Unset any parent pointers that were to this address */
87 for (ddd = q->destinations; ddd != NULL; ddd = ddd->next)
89 if (ddd->parent == dd) ddd->parent = NULL;
97 if (action != dest_add) return NULL;
99 dd = (dest_item *)store_malloc(sizeof(dest_item) + Ustrlen(name));
100 Ustrcpy(dd->address, name);
109 /*************************************************
110 * Clean up a dead queue item *
111 *************************************************/
113 static void clean_up(queue_item *p)
115 dest_item *dd = p->destinations;
118 dest_item *next = dd->next;
122 if (p->sender != NULL) store_free(p->sender);
127 /*************************************************
128 * Set up new queue item *
129 *************************************************/
131 static queue_item *set_up(uschar *name, int dir_char)
133 int i, rc, save_errno;
134 struct stat statdata;
137 queue_item *q = (queue_item *)store_malloc(sizeof(queue_item));
140 /* Initialize the block */
142 q->next = q->prev = NULL;
143 q->destinations = NULL;
144 Ustrcpy(q->name, name);
147 q->dir_char = dir_char;
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
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. */
161 reset_point = store_get(0);
163 message_subdir[0] = dir_char;
164 sprintf(CS buffer, "%s-H", name);
165 rc = spool_read_header(buffer, FALSE, TRUE);
168 /* If we failed to read the envelope, compute the input time by
169 interpreting the id as a base-62 number. */
171 if (rc != spool_read_OK && rc != spool_read_hdrerror)
174 for (i = 0; i < 6; i++) t = t * 62 + tab62[name[i] - '0'];
175 q->update_time = q->input_time = t;
178 /* Envelope read; get input time and remove qualify_domain from sender address,
183 q->update_time = q->input_time = received_time;
184 if ((p = strstric(sender_address+1, qualify_domain, FALSE)) != NULL &&
185 *(--p) == '@') *p = 0;
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. */
192 if (rc != spool_read_OK)
196 if (save_errno == ERRNO_SPOOLFORMAT)
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 ***",
203 else msg = string_sprintf("*** Format error in spool file ***");
205 else msg = string_sprintf("*** Cannot read spool file ***");
207 if (rc == spool_read_hdrerror)
209 (void)find_dest(q, msg, dest_add, FALSE);
213 deliver_freeze = FALSE;
214 sender_address = msg;
215 recipients_count = 0;
219 /* Now set up the remaining data. */
221 q->frozen = deliver_freeze;
223 if (sender_set_untrusted)
225 if (sender_address[0] == 0)
227 q->sender = store_malloc(Ustrlen(originator_login) + 6);
228 sprintf(CS q->sender, "<> (%s)", originator_login);
232 q->sender = store_malloc(Ustrlen(sender_address) +
233 Ustrlen(originator_login) + 4);
234 sprintf(CS q->sender, "%s (%s)", sender_address, originator_login);
239 q->sender = store_malloc(Ustrlen(sender_address) + 1);
240 Ustrcpy(q->sender, sender_address);
243 sender_address = NULL;
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;
249 /* Scan and process the recipients list, skipping any that have already
250 been delivered, and removing visible names. */
252 if (recipients_list != NULL)
254 for (i = 0; i < recipients_count; i++)
256 uschar *r = recipients_list[i].address;
257 if (tree_search(tree_nonrecipients, r) == NULL)
259 if ((p = strstric(r+1, qualify_domain, FALSE)) != NULL &&
260 *(--p) == '@') *p = 0;
261 (void)find_dest(q, r, dest_add, FALSE);
266 /* Recover the dynamic store used by spool_read_header(). */
268 store_reset(reset_point);
274 /*************************************************
275 * Find/Create a queue item *
276 *************************************************/
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. */
286 static void debug_queue(void)
291 printf("\nqueue_total=%d\n", queue_total);
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);
297 printf("Queue is:\n");
302 for (i = 0; i < queue_index_size; i++)
304 if (queue_index[i] == p) printf("count=%d index=%d\n", count, (int)p);
306 printf("%d %d %d %s\n", (int)p, (int)p->next, (int)p->prev, p->name);
314 queue_item *find_queue(uschar *name, int action, int dir_char)
317 int last = queue_index_size - 1;
318 int middle = (first + last)/2;
319 queue_item *p, *q, *qq;
321 /* Handle the empty queue as a special case. */
323 if (queue_total == 0)
325 if (action != queue_add) return NULL;
326 if ((qq = set_up(name, dir_char)) != NULL)
329 for (i = 0; i < queue_index_size; i++) queue_index[i] = qq;
336 /* Also handle insertion at the start or end of the queue
339 if (Ustrcmp(name, (queue_index[0])->name) < 0)
341 if (action != queue_add) return NULL;
342 if ((qq = set_up(name, dir_char)) != NULL)
344 qq->next = queue_index[0];
345 (queue_index[0])->prev = qq;
353 if (Ustrcmp(name, (queue_index[queue_index_size-1])->name) > 0)
355 if (action != queue_add) return NULL;
356 if ((qq = set_up(name, dir_char)) != NULL)
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;
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. */
370 while (middle > first)
372 if (Ustrcmp(name, (queue_index[middle])->name) >= 0) first = middle;
374 middle = (first + last)/2;
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. */
381 p = queue_index[first];
382 q = queue_index[last];
386 int c = Ustrcmp(name, p->name);
388 /* Already on queue; mark seen if required. */
392 if (action == queue_add) p->seen = TRUE;
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
403 if (action == queue_add)
405 if ((qq = set_up(name, dir_char)) != NULL)
418 /* Control should not reach here if p == q, because the name
419 is supposed to be <= the name of the bottom item. */
421 if (p == q) return NULL;
423 /* Else might be further down the queue; continue */
428 /* Control should never reach here. */
433 /*************************************************
434 * Scan the exim spool directory *
435 *************************************************/
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"
442 void scan_spool_input(int full)
452 uschar input_dir[256];
456 stripchart_total[0] = 0;
458 sprintf(CS input_dir, "%s/input", spool_directory);
459 subptr = Ustrlen(input_dir);
460 input_dir[subptr+2] = 0; /* terminator for lengthened name */
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. */
466 for (i = 0; i < subdir_max; i++)
468 int subdirchar = subdirs[i]; /* 0 for main directory */
471 input_dir[subptr] = '/';
472 input_dir[subptr+1] = subdirchar;
475 dd = opendir(CS input_dir);
476 if (dd == NULL) continue;
478 while ((ent = readdir(dd)) != NULL)
480 uschar *name = US ent->d_name;
481 int len = Ustrlen(name);
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. */
487 if (i == 0 && len == 1 && isalnum(*name))
489 subdirs[subdir_max++] = *name;
490 spool_is_split = TRUE;
494 /* Otherwise, if it is a header spool file, add it to the list */
496 if (len == SPOOL_NAME_LENGTH &&
497 name[SPOOL_NAME_LENGTH - 2] == '-' &&
498 name[SPOOL_NAME_LENGTH - 1] == 'H')
500 uschar basename[SPOOL_NAME_LENGTH + 1];
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);
511 /* If simply counting the number, we are done; same if there are no
512 items in the in-store queue. */
514 if (!full || queue_total == 0) return;
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!*/
527 queue_item *next = p->next;
528 if (p->prev == NULL) queue_index[0] = next;
529 else p->prev->next = next;
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;
537 else next->prev = p->prev;
544 if (++count > (queue_total * indexptr)/(queue_index_size-1))
546 queue_index[indexptr++] = p;
548 p->seen = FALSE; /* for next time */
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
557 while (indexptr < queue_index_size - 1)
559 queue_index[indexptr++] = queue_index[queue_index_size-1];
566 /*************************************************
567 * Update the recipients list for a message *
568 *************************************************/
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. */
573 /* First, a local subroutine to scan the non-recipients tree and
574 remove any of them from the address list */
577 scan_tree(queue_item *p, tree_node *tn)
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);
587 /* The main function */
589 static void update_recipients(queue_item *p)
594 struct stat statdata;
597 message_subdir[0] = p->dir_char;
599 sprintf(CS buffer, "%s/input/%s/%s-J", spool_directory, message_subdir, p->name);
600 jread = fopen(CS buffer, "r");
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)
608 /* Get the contents of the header file; if any problem, just give up.
609 Arrange to recover the dynamic store afterwards. */
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)
615 store_reset(reset_point);
616 if (jread != NULL) fclose(jread);
620 /* If there's a journal file, add its contents to the non-recipients tree */
624 while (Ufgets(big_buffer, big_buffer_size, jread) != NULL)
626 int n = Ustrlen(big_buffer);
628 tree_add_nonrecipient(big_buffer);
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. */
637 if (recipients_list != NULL)
639 for (i = 0; i < recipients_count; i++)
642 uschar *r = recipients_list[i].address;
643 tree_node *node = tree_search(tree_nonrecipients, r);
650 while (*rr != 0 && *rr != '@') rr++;
651 while (*rr != 0) { *rr = tolower(*rr); rr++; }
652 node = tree_search(tree_nonrecipients, temp);
655 if ((pp = strstric(r+1, qualify_domain, FALSE)) != NULL &&
656 *(--pp) == '@') *pp = 0;
658 (void)find_dest(p, r, dest_add, FALSE);
660 (void)find_dest(p, r, dest_remove, FALSE);
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. */
670 scan_tree(p, tree_nonrecipients);
671 p->update_time = statdata.st_mtime;
672 store_reset(reset_point);
677 /*************************************************
678 * Display queue data *
679 *************************************************/
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. */
689 int now = (int)time(NULL);
690 queue_item *p = queue_index[0];
692 if (menu_is_up) return; /* Avoid nasty interactions */
694 text_empty(queue_widget);
701 int t = (now - p->input_time)/60; /* minutes on queue */
711 if (t > 99) /* someone had > 99 days */
715 if (t > 99) /* so, just in case */
724 update_recipients(p); /* update destinations */
726 /* Can't set this earlier, as header data may change things. */
728 dd = p->destinations;
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. */
734 for (ddd = dd; ddd != NULL; ddd = ddd->next)
740 if (ddd->address[0] == '*') break;
741 len_address = Ustrlen(ddd->address);
743 for (skp = &queue_skip; ; skp = &(sk->next))
748 while (sk != NULL && now >= sk->reveal)
753 if (queue_skip == NULL)
755 XtDestroyWidget(unhide_widget);
756 unhide_widget = NULL;
759 if (sk == NULL) break;
761 /* If this address matches the skip item, break (sk != NULL) */
763 len_skip = Ustrlen(sk->text);
764 if (len_skip <= len_address &&
765 Ustrcmp(ddd->address + len_address - len_skip, sk->text) == 0)
769 if (sk == NULL) break;
772 /* Don't use more than one call of anon() in one statement - it uses
773 a fixed static buffer. */
775 if (ddd != NULL || dd == NULL)
777 text_showf(queue_widget, "%c%2d%c %s %s %-8s ",
778 (p->frozen)? '*' : ' ',
780 string_format_size(p->size, big_buffer),
782 (p->sender == NULL)? US" " :
783 (p->sender[0] == 0)? US"<> " : anon(p->sender));
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] == '*')? "" : ">");
790 if (dd != NULL && dd->parent != NULL && dd->parent->address[0] != '*')
791 text_showf(queue_widget, " parent <%s>", anon(dd->parent->address));
793 text_show(queue_widget, US"\n");
795 if (dd != NULL) dd = dd->next;
796 while (dd != NULL && count++ < queue_max_addresses)
798 text_showf(queue_widget, " <%s>",
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");
806 text_showf(queue_widget, " ...\n");
813 /* End of em_queue.c */