1 /* $Cambridge: exim/src/exim_monitor/em_queue.c,v 1.5 2006/09/19 11:28:45 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 an ACL variable *
129 *************************************************/
131 /* The spool_read_header() function calls acl_var_create() when it reads in an
132 ACL variable. We know that in this case, the variable will be new, not re-used,
133 so this is a cut-down version, to save including the whole acl.c module (which
134 would need conditional compilation to cut most of it out). */
137 acl_var_create(uschar *name)
139 tree_node *node, **root;
140 root = (name[0] == 'c')? &acl_var_c : &acl_var_m;
141 node = store_get(sizeof(tree_node) + Ustrlen(name));
142 Ustrcpy(node->name, name);
143 node->data.ptr = NULL;
144 (void)tree_insertnode(root, node);
150 /*************************************************
151 * Set up new queue item *
152 *************************************************/
154 static queue_item *set_up(uschar *name, int dir_char)
156 int i, rc, save_errno;
157 struct stat statdata;
160 queue_item *q = (queue_item *)store_malloc(sizeof(queue_item));
163 /* Initialize the block */
165 q->next = q->prev = NULL;
166 q->destinations = NULL;
167 Ustrcpy(q->name, name);
170 q->dir_char = dir_char;
174 /* Read the header file from the spool; if there is a failure it might mean
175 inaccessibility as a result of protections. A successful read will have caused
176 sender_address to get set and the recipients fields to be initialized. If
177 there's a format error in the headers, we can still display info from the
180 Before reading the header remember the position in the dynamic store so that
181 we can recover the store into which the header is read. All data read by
182 spool_read_header that is to be preserved is copied into malloc store. */
184 reset_point = store_get(0);
186 message_subdir[0] = dir_char;
187 sprintf(CS buffer, "%s-H", name);
188 rc = spool_read_header(buffer, FALSE, TRUE);
191 /* If we failed to read the envelope, compute the input time by
192 interpreting the id as a base-62 number. */
194 if (rc != spool_read_OK && rc != spool_read_hdrerror)
197 for (i = 0; i < 6; i++) t = t * 62 + tab62[name[i] - '0'];
198 q->update_time = q->input_time = t;
201 /* Envelope read; get input time and remove qualify_domain from sender address,
206 q->update_time = q->input_time = received_time;
207 if ((p = strstric(sender_address+1, qualify_domain, FALSE)) != NULL &&
208 *(--p) == '@') *p = 0;
211 /* If we didn't read the whole header successfully, generate an error
212 message. If the envelope was read, this appears as a first recipient;
213 otherwise it sets set up in the sender field. */
215 if (rc != spool_read_OK)
219 if (save_errno == ERRNO_SPOOLFORMAT)
222 sprintf(CS big_buffer, "%s/input/%s", spool_directory, buffer);
223 if (Ustat(big_buffer, &statbuf) == 0)
224 msg = string_sprintf("*** Format error in spool file: size = %d ***",
226 else msg = string_sprintf("*** Format error in spool file ***");
228 else msg = string_sprintf("*** Cannot read spool file ***");
230 if (rc == spool_read_hdrerror)
232 (void)find_dest(q, msg, dest_add, FALSE);
236 deliver_freeze = FALSE;
237 sender_address = msg;
238 recipients_count = 0;
242 /* Now set up the remaining data. */
244 q->frozen = deliver_freeze;
246 if (sender_set_untrusted)
248 if (sender_address[0] == 0)
250 q->sender = store_malloc(Ustrlen(originator_login) + 6);
251 sprintf(CS q->sender, "<> (%s)", originator_login);
255 q->sender = store_malloc(Ustrlen(sender_address) +
256 Ustrlen(originator_login) + 4);
257 sprintf(CS q->sender, "%s (%s)", sender_address, originator_login);
262 q->sender = store_malloc(Ustrlen(sender_address) + 1);
263 Ustrcpy(q->sender, sender_address);
266 sender_address = NULL;
268 sprintf(CS buffer, "%s/input/%s/%s-D", spool_directory, message_subdir, name);
269 if (Ustat(buffer, &statdata) == 0)
270 q->size = message_size + statdata.st_size - SPOOL_DATA_START_OFFSET + 1;
272 /* Scan and process the recipients list, skipping any that have already
273 been delivered, and removing visible names. */
275 if (recipients_list != NULL)
277 for (i = 0; i < recipients_count; i++)
279 uschar *r = recipients_list[i].address;
280 if (tree_search(tree_nonrecipients, r) == NULL)
282 if ((p = strstric(r+1, qualify_domain, FALSE)) != NULL &&
283 *(--p) == '@') *p = 0;
284 (void)find_dest(q, r, dest_add, FALSE);
289 /* Recover the dynamic store used by spool_read_header(). */
291 store_reset(reset_point);
297 /*************************************************
298 * Find/Create a queue item *
299 *************************************************/
301 /* The queue is kept as a doubly-linked list, sorted by name. However,
302 to speed up searches, an index into the list is used. This is maintained
303 by the scan_spool_input function when it goes down the list throwing
304 out entries that are no longer needed. When the action is "add" and
305 we don't need to add, mark the found item as seen. */
309 static void debug_queue(void)
314 printf("\nqueue_total=%d\n", queue_total);
316 for (i = 0; i < queue_index_size; i++)
317 printf("index %d = %d %s\n", i, (int)(queue_index[i]),
318 (queue_index[i])->name);
320 printf("Queue is:\n");
325 for (i = 0; i < queue_index_size; i++)
327 if (queue_index[i] == p) printf("count=%d index=%d\n", count, (int)p);
329 printf("%d %d %d %s\n", (int)p, (int)p->next, (int)p->prev, p->name);
337 queue_item *find_queue(uschar *name, int action, int dir_char)
340 int last = queue_index_size - 1;
341 int middle = (first + last)/2;
342 queue_item *p, *q, *qq;
344 /* Handle the empty queue as a special case. */
346 if (queue_total == 0)
348 if (action != queue_add) return NULL;
349 if ((qq = set_up(name, dir_char)) != NULL)
352 for (i = 0; i < queue_index_size; i++) queue_index[i] = qq;
359 /* Also handle insertion at the start or end of the queue
362 if (Ustrcmp(name, (queue_index[0])->name) < 0)
364 if (action != queue_add) return NULL;
365 if ((qq = set_up(name, dir_char)) != NULL)
367 qq->next = queue_index[0];
368 (queue_index[0])->prev = qq;
376 if (Ustrcmp(name, (queue_index[queue_index_size-1])->name) > 0)
378 if (action != queue_add) return NULL;
379 if ((qq = set_up(name, dir_char)) != NULL)
381 qq->prev = queue_index[queue_index_size-1];
382 (queue_index[queue_index_size-1])->next = qq;
383 queue_index[queue_index_size-1] = qq;
390 /* Use binary chopping on the index to get a range of the queue to search
391 when the name is somewhere in the middle, if present. */
393 while (middle > first)
395 if (Ustrcmp(name, (queue_index[middle])->name) >= 0) first = middle;
397 middle = (first + last)/2;
400 /* Now search down the part of the queue in which the item must
401 lie if it exists. Both end points are inclusive - though in fact
402 the bottom one can only be = if it is the original bottom. */
404 p = queue_index[first];
405 q = queue_index[last];
409 int c = Ustrcmp(name, p->name);
411 /* Already on queue; mark seen if required. */
415 if (action == queue_add) p->seen = TRUE;
419 /* Not on the queue; add an entry if required. Note that set-up might
420 fail (the file might vanish under our feet). Note also that we know
421 there is always a previous item to p because the end points are
426 if (action == queue_add)
428 if ((qq = set_up(name, dir_char)) != NULL)
441 /* Control should not reach here if p == q, because the name
442 is supposed to be <= the name of the bottom item. */
444 if (p == q) return NULL;
446 /* Else might be further down the queue; continue */
451 /* Control should never reach here. */
456 /*************************************************
457 * Scan the exim spool directory *
458 *************************************************/
460 /* If we discover that there are subdirectories, set a flag so that the menu
461 code knows to look for them. We count the entries to set the value for the
462 queue stripchart, and set up data for the queue display window if the "full"
465 void scan_spool_input(int full)
475 uschar input_dir[256];
479 stripchart_total[0] = 0;
481 sprintf(CS input_dir, "%s/input", spool_directory);
482 subptr = Ustrlen(input_dir);
483 input_dir[subptr+2] = 0; /* terminator for lengthened name */
485 /* Loop for each spool file on the queue - searching any subdirectories that
486 may exist. When initializing eximon, every file will have to be read. To show
487 there is progress, output a dot for each one to the standard output. */
489 for (i = 0; i < subdir_max; i++)
491 int subdirchar = subdirs[i]; /* 0 for main directory */
494 input_dir[subptr] = '/';
495 input_dir[subptr+1] = subdirchar;
498 dd = opendir(CS input_dir);
499 if (dd == NULL) continue;
501 while ((ent = readdir(dd)) != NULL)
503 uschar *name = US ent->d_name;
504 int len = Ustrlen(name);
506 /* If we find a single alphameric sub-directory on the first
507 pass, add it to the list for subsequent scans, and remember that
508 we are dealing with a split directory. */
510 if (i == 0 && len == 1 && isalnum(*name))
512 subdirs[subdir_max++] = *name;
513 spool_is_split = TRUE;
517 /* Otherwise, if it is a header spool file, add it to the list */
519 if (len == SPOOL_NAME_LENGTH &&
520 name[SPOOL_NAME_LENGTH - 2] == '-' &&
521 name[SPOOL_NAME_LENGTH - 1] == 'H')
523 uschar basename[SPOOL_NAME_LENGTH + 1];
524 stripchart_total[0]++;
525 if (!eximon_initialized) { printf("."); fflush(stdout); }
526 Ustrcpy(basename, name);
527 basename[SPOOL_NAME_LENGTH - 2] = 0;
528 if (full) find_queue(basename, queue_add, subdirchar);
534 /* If simply counting the number, we are done; same if there are no
535 items in the in-store queue. */
537 if (!full || queue_total == 0) return;
539 /* Now scan the queue and remove any items that were not in the directory. At
540 the same time, set up the index pointers into the queue. Because we are
541 removing items, the total that we are comparing against isn't actually correct,
542 but in a long queue it won't make much difference, and in a short queue it
543 doesn't matter anyway!*/
550 queue_item *next = p->next;
551 if (p->prev == NULL) queue_index[0] = next;
552 else p->prev->next = next;
556 queue_item *q = queue_index[queue_index_size-1];
557 for (i = queue_index_size - 1; i >= 0; i--)
558 if (queue_index[i] == q) queue_index[i] = p->prev;
560 else next->prev = p->prev;
567 if (++count > (queue_total * indexptr)/(queue_index_size-1))
569 queue_index[indexptr++] = p;
571 p->seen = FALSE; /* for next time */
576 /* If a lot of messages have been removed at the bottom, we may not
577 have got the index all filled in yet. Make sure all the pointers
580 while (indexptr < queue_index_size - 1)
582 queue_index[indexptr++] = queue_index[queue_index_size-1];
589 /*************************************************
590 * Update the recipients list for a message *
591 *************************************************/
593 /* We read the spool file only if its update time differs from last time,
594 or if there is a journal file in existence. */
596 /* First, a local subroutine to scan the non-recipients tree and
597 remove any of them from the address list */
600 scan_tree(queue_item *p, tree_node *tn)
604 if (tn->left != NULL) scan_tree(p, tn->left);
605 if (tn->right != NULL) scan_tree(p, tn->right);
606 (void)find_dest(p, tn->name, dest_remove, FALSE);
610 /* The main function */
612 static void update_recipients(queue_item *p)
617 struct stat statdata;
620 message_subdir[0] = p->dir_char;
622 sprintf(CS buffer, "%s/input/%s/%s-J", spool_directory, message_subdir, p->name);
623 jread = fopen(CS buffer, "r");
626 sprintf(CS buffer, "%s/input/%s/%s-H", spool_directory, message_subdir, p->name);
627 if (Ustat(buffer, &statdata) < 0 || p->update_time == statdata.st_mtime)
631 /* Get the contents of the header file; if any problem, just give up.
632 Arrange to recover the dynamic store afterwards. */
634 reset_point = store_get(0);
635 sprintf(CS buffer, "%s-H", p->name);
636 if (spool_read_header(buffer, FALSE, TRUE) != spool_read_OK)
638 store_reset(reset_point);
639 if (jread != NULL) fclose(jread);
643 /* If there's a journal file, add its contents to the non-recipients tree */
647 while (Ufgets(big_buffer, big_buffer_size, jread) != NULL)
649 int n = Ustrlen(big_buffer);
651 tree_add_nonrecipient(big_buffer);
656 /* Scan and process the recipients list, removing any that have already
657 been delivered, and removing visible names. In the nonrecipients tree,
658 domains are lower cased. */
660 if (recipients_list != NULL)
662 for (i = 0; i < recipients_count; i++)
665 uschar *r = recipients_list[i].address;
666 tree_node *node = tree_search(tree_nonrecipients, r);
673 while (*rr != 0 && *rr != '@') rr++;
674 while (*rr != 0) { *rr = tolower(*rr); rr++; }
675 node = tree_search(tree_nonrecipients, temp);
678 if ((pp = strstric(r+1, qualify_domain, FALSE)) != NULL &&
679 *(--pp) == '@') *pp = 0;
681 (void)find_dest(p, r, dest_add, FALSE);
683 (void)find_dest(p, r, dest_remove, FALSE);
687 /* We also need to scan the tree of non-recipients, which might
688 contain child addresses that are not in the recipients list, but
689 which may have got onto the address list as a result of eximon
690 noticing an == line in the log. Then remember the update time,
691 recover the dynamic store, and we are done. */
693 scan_tree(p, tree_nonrecipients);
694 p->update_time = statdata.st_mtime;
695 store_reset(reset_point);
700 /*************************************************
701 * Display queue data *
702 *************************************************/
704 /* The present implementation simple re-writes the entire information each
705 time. Take some care to keep the scrolled position as it previously was, but,
706 if it was at the bottom, keep it at the bottom. Take note of any hide list, and
707 time out the entries as appropriate. */
712 int now = (int)time(NULL);
713 queue_item *p = queue_index[0];
715 if (menu_is_up) return; /* Avoid nasty interactions */
717 text_empty(queue_widget);
724 int t = (now - p->input_time)/60; /* minutes on queue */
734 if (t > 99) /* someone had > 99 days */
738 if (t > 99) /* so, just in case */
747 update_recipients(p); /* update destinations */
749 /* Can't set this earlier, as header data may change things. */
751 dd = p->destinations;
753 /* Check to see if this message is on the hide list; if any hide
754 item has timed out, remove it from the list. Hide if all destinations
755 are on the hide list. */
757 for (ddd = dd; ddd != NULL; ddd = ddd->next)
763 if (ddd->address[0] == '*') break;
764 len_address = Ustrlen(ddd->address);
766 for (skp = &queue_skip; ; skp = &(sk->next))
771 while (sk != NULL && now >= sk->reveal)
776 if (queue_skip == NULL)
778 XtDestroyWidget(unhide_widget);
779 unhide_widget = NULL;
782 if (sk == NULL) break;
784 /* If this address matches the skip item, break (sk != NULL) */
786 len_skip = Ustrlen(sk->text);
787 if (len_skip <= len_address &&
788 Ustrcmp(ddd->address + len_address - len_skip, sk->text) == 0)
792 if (sk == NULL) break;
795 /* Don't use more than one call of anon() in one statement - it uses
796 a fixed static buffer. */
798 if (ddd != NULL || dd == NULL)
800 text_showf(queue_widget, "%c%2d%c %s %s %-8s ",
801 (p->frozen)? '*' : ' ',
803 string_format_size(p->size, big_buffer),
805 (p->sender == NULL)? US" " :
806 (p->sender[0] == 0)? US"<> " : anon(p->sender));
808 text_showf(queue_widget, "%s%s%s",
809 (dd == NULL || dd->address[0] == '*')? "" : "<",
810 (dd == NULL)? US"" : anon(dd->address),
811 (dd == NULL || dd->address[0] == '*')? "" : ">");
813 if (dd != NULL && dd->parent != NULL && dd->parent->address[0] != '*')
814 text_showf(queue_widget, " parent <%s>", anon(dd->parent->address));
816 text_show(queue_widget, US"\n");
818 if (dd != NULL) dd = dd->next;
819 while (dd != NULL && count++ < queue_max_addresses)
821 text_showf(queue_widget, " <%s>",
823 if (dd->parent != NULL && dd->parent->address[0] != '*')
824 text_showf(queue_widget, " parent <%s>", anon(dd->parent->address));
825 text_show(queue_widget, US"\n");
829 text_showf(queue_widget, " ...\n");
836 /* End of em_queue.c */