1 /*************************************************
3 *************************************************/
5 /* Copyright (c) University of Cambridge 1995 - 2009 */
6 /* See the file NOTICE for conditions of use and distribution. */
12 /* This module contains functions to do with scanning exim's
13 queue and displaying the data therefrom. */
16 /* If we are anonymizing for screen shots, define a function to anonymize
17 addresses. Otherwise, define a macro that does nothing. */
20 static uschar *anon(uschar *s)
22 static uschar anon_result[256];
23 uschar *ss = anon_result;
24 for (; *s != 0; s++) *ss++ = (*s == '@' || *s == '.')? *s : 'x';
33 /*************************************************
35 *************************************************/
37 static int queue_total = 0; /* number of items in queue */
39 /* Table for turning base-62 numbers into binary */
41 static 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 */
50 /* Index for quickly finding things in the ordered queue. */
52 static queue_item *queue_index[queue_index_size];
56 /*************************************************
57 * Find/Create/Delete a destination *
58 *************************************************/
60 /* If the action is dest_noop, then just return item or NULL;
61 if it is dest_add, then add if not present, and return item;
62 if it is dest_remove, remove if present and return NULL. The
63 address is lowercased to start with, unless it begins with
64 "*", which it does for error messages. */
66 dest_item *find_dest(queue_item *q, uschar *name, int action, BOOL caseless)
69 dest_item **d = &(q->destinations);
73 if ((caseless? strcmpic(name,(*d)->address) : Ustrcmp(name,(*d)->address))
78 if (action != dest_remove) return *d;
83 /* Unset any parent pointers that were to this address */
85 for (ddd = q->destinations; ddd != NULL; ddd = ddd->next)
87 if (ddd->parent == dd) ddd->parent = NULL;
95 if (action != dest_add) return NULL;
97 dd = (dest_item *)store_malloc(sizeof(dest_item) + Ustrlen(name));
98 Ustrcpy(dd->address, name);
107 /*************************************************
108 * Clean up a dead queue item *
109 *************************************************/
111 static void clean_up(queue_item *p)
113 dest_item *dd = p->destinations;
116 dest_item *next = dd->next;
120 if (p->sender != NULL) store_free(p->sender);
125 /*************************************************
126 * Set up an ACL variable *
127 *************************************************/
129 /* The spool_read_header() function calls acl_var_create() when it reads in an
130 ACL variable. We know that in this case, the variable will be new, not re-used,
131 so this is a cut-down version, to save including the whole acl.c module (which
132 would need conditional compilation to cut most of it out). */
135 acl_var_create(uschar *name)
137 tree_node *node, **root;
138 root = (name[0] == 'c')? &acl_var_c : &acl_var_m;
139 node = store_get(sizeof(tree_node) + Ustrlen(name));
140 Ustrcpy(node->name, name);
141 node->data.ptr = NULL;
142 (void)tree_insertnode(root, node);
148 /*************************************************
149 * Set up new queue item *
150 *************************************************/
152 static queue_item *set_up(uschar *name, int dir_char)
154 int i, rc, save_errno;
155 struct stat statdata;
158 queue_item *q = (queue_item *)store_malloc(sizeof(queue_item));
161 /* Initialize the block */
163 q->next = q->prev = NULL;
164 q->destinations = NULL;
165 Ustrcpy(q->name, name);
168 q->dir_char = dir_char;
172 /* Read the header file from the spool; if there is a failure it might mean
173 inaccessibility as a result of protections. A successful read will have caused
174 sender_address to get set and the recipients fields to be initialized. If
175 there's a format error in the headers, we can still display info from the
178 Before reading the header remember the position in the dynamic store so that
179 we can recover the store into which the header is read. All data read by
180 spool_read_header that is to be preserved is copied into malloc store. */
182 reset_point = store_get(0);
184 message_subdir[0] = dir_char;
185 sprintf(CS buffer, "%s-H", name);
186 rc = spool_read_header(buffer, FALSE, TRUE);
189 /* If we failed to read the envelope, compute the input time by
190 interpreting the id as a base-62 number. */
192 if (rc != spool_read_OK && rc != spool_read_hdrerror)
195 for (i = 0; i < 6; i++) t = t * 62 + tab62[name[i] - '0'];
196 q->update_time = q->input_time = t;
199 /* Envelope read; get input time and remove qualify_domain from sender address,
204 q->update_time = q->input_time = received_time;
205 if ((p = strstric(sender_address+1, qualify_domain, FALSE)) != NULL &&
206 *(--p) == '@') *p = 0;
209 /* If we didn't read the whole header successfully, generate an error
210 message. If the envelope was read, this appears as a first recipient;
211 otherwise it sets set up in the sender field. */
213 if (rc != spool_read_OK)
217 if (save_errno == ERRNO_SPOOLFORMAT)
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 ***",
224 else msg = string_sprintf("*** Format error in spool file ***");
226 else msg = string_sprintf("*** Cannot read spool file ***");
228 if (rc == spool_read_hdrerror)
230 (void)find_dest(q, msg, dest_add, FALSE);
234 deliver_freeze = FALSE;
235 sender_address = msg;
236 recipients_count = 0;
240 /* Now set up the remaining data. */
242 q->frozen = deliver_freeze;
244 if (sender_set_untrusted)
246 if (sender_address[0] == 0)
248 q->sender = store_malloc(Ustrlen(originator_login) + 6);
249 sprintf(CS q->sender, "<> (%s)", originator_login);
253 q->sender = store_malloc(Ustrlen(sender_address) +
254 Ustrlen(originator_login) + 4);
255 sprintf(CS q->sender, "%s (%s)", sender_address, originator_login);
260 q->sender = store_malloc(Ustrlen(sender_address) + 1);
261 Ustrcpy(q->sender, sender_address);
264 sender_address = NULL;
266 sprintf(CS buffer, "%s/input/%s/%s-D", spool_directory, message_subdir, name);
267 if (Ustat(buffer, &statdata) == 0)
268 q->size = message_size + statdata.st_size - SPOOL_DATA_START_OFFSET + 1;
270 /* Scan and process the recipients list, skipping any that have already
271 been delivered, and removing visible names. */
273 if (recipients_list != NULL)
275 for (i = 0; i < recipients_count; i++)
277 uschar *r = recipients_list[i].address;
278 if (tree_search(tree_nonrecipients, r) == NULL)
280 if ((p = strstric(r+1, qualify_domain, FALSE)) != NULL &&
281 *(--p) == '@') *p = 0;
282 (void)find_dest(q, r, dest_add, FALSE);
287 /* Recover the dynamic store used by spool_read_header(). */
289 store_reset(reset_point);
295 /*************************************************
296 * Find/Create a queue item *
297 *************************************************/
299 /* The queue is kept as a doubly-linked list, sorted by name. However,
300 to speed up searches, an index into the list is used. This is maintained
301 by the scan_spool_input function when it goes down the list throwing
302 out entries that are no longer needed. When the action is "add" and
303 we don't need to add, mark the found item as seen. */
307 static void debug_queue(void)
312 printf("\nqueue_total=%d\n", queue_total);
314 for (i = 0; i < queue_index_size; i++)
315 printf("index %d = %d %s\n", i, (int)(queue_index[i]),
316 (queue_index[i])->name);
318 printf("Queue is:\n");
323 for (i = 0; i < queue_index_size; i++)
325 if (queue_index[i] == p) printf("count=%d index=%d\n", count, (int)p);
327 printf("%d %d %d %s\n", (int)p, (int)p->next, (int)p->prev, p->name);
335 queue_item *find_queue(uschar *name, int action, int dir_char)
338 int last = queue_index_size - 1;
339 int middle = (first + last)/2;
340 queue_item *p, *q, *qq;
342 /* Handle the empty queue as a special case. */
344 if (queue_total == 0)
346 if (action != queue_add) return NULL;
347 if ((qq = set_up(name, dir_char)) != NULL)
350 for (i = 0; i < queue_index_size; i++) queue_index[i] = qq;
357 /* Also handle insertion at the start or end of the queue
360 if (Ustrcmp(name, (queue_index[0])->name) < 0)
362 if (action != queue_add) return NULL;
363 if ((qq = set_up(name, dir_char)) != NULL)
365 qq->next = queue_index[0];
366 (queue_index[0])->prev = qq;
374 if (Ustrcmp(name, (queue_index[queue_index_size-1])->name) > 0)
376 if (action != queue_add) return NULL;
377 if ((qq = set_up(name, dir_char)) != NULL)
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;
388 /* Use binary chopping on the index to get a range of the queue to search
389 when the name is somewhere in the middle, if present. */
391 while (middle > first)
393 if (Ustrcmp(name, (queue_index[middle])->name) >= 0) first = middle;
395 middle = (first + last)/2;
398 /* Now search down the part of the queue in which the item must
399 lie if it exists. Both end points are inclusive - though in fact
400 the bottom one can only be = if it is the original bottom. */
402 p = queue_index[first];
403 q = queue_index[last];
407 int c = Ustrcmp(name, p->name);
409 /* Already on queue; mark seen if required. */
413 if (action == queue_add) p->seen = TRUE;
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
424 if (action == queue_add)
426 if ((qq = set_up(name, dir_char)) != NULL)
439 /* Control should not reach here if p == q, because the name
440 is supposed to be <= the name of the bottom item. */
442 if (p == q) return NULL;
444 /* Else might be further down the queue; continue */
449 /* Control should never reach here. */
454 /*************************************************
455 * Scan the exim spool directory *
456 *************************************************/
458 /* If we discover that there are subdirectories, set a flag so that the menu
459 code knows to look for them. We count the entries to set the value for the
460 queue stripchart, and set up data for the queue display window if the "full"
463 void scan_spool_input(int full)
473 uschar input_dir[256];
477 stripchart_total[0] = 0;
479 sprintf(CS input_dir, "%s/input", spool_directory);
480 subptr = Ustrlen(input_dir);
481 input_dir[subptr+2] = 0; /* terminator for lengthened name */
483 /* Loop for each spool file on the queue - searching any subdirectories that
484 may exist. When initializing eximon, every file will have to be read. To show
485 there is progress, output a dot for each one to the standard output. */
487 for (i = 0; i < subdir_max; i++)
489 int subdirchar = subdirs[i]; /* 0 for main directory */
492 input_dir[subptr] = '/';
493 input_dir[subptr+1] = subdirchar;
496 dd = opendir(CS input_dir);
497 if (dd == NULL) continue;
499 while ((ent = readdir(dd)) != NULL)
501 uschar *name = US ent->d_name;
502 int len = Ustrlen(name);
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. */
508 if (i == 0 && len == 1 && isalnum(*name))
510 subdirs[subdir_max++] = *name;
511 spool_is_split = TRUE;
515 /* Otherwise, if it is a header spool file, add it to the list */
517 if (len == SPOOL_NAME_LENGTH &&
518 name[SPOOL_NAME_LENGTH - 2] == '-' &&
519 name[SPOOL_NAME_LENGTH - 1] == 'H')
521 uschar basename[SPOOL_NAME_LENGTH + 1];
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);
532 /* If simply counting the number, we are done; same if there are no
533 items in the in-store queue. */
535 if (!full || queue_total == 0) return;
537 /* Now scan the queue and remove any items that were not in the directory. At
538 the same time, set up the index pointers into the queue. Because we are
539 removing items, the total that we are comparing against isn't actually correct,
540 but in a long queue it won't make much difference, and in a short queue it
541 doesn't matter anyway!*/
548 queue_item *next = p->next;
549 if (p->prev == NULL) queue_index[0] = next;
550 else p->prev->next = next;
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;
558 else next->prev = p->prev;
565 if (++count > (queue_total * indexptr)/(queue_index_size-1))
567 queue_index[indexptr++] = p;
569 p->seen = FALSE; /* for next time */
574 /* If a lot of messages have been removed at the bottom, we may not
575 have got the index all filled in yet. Make sure all the pointers
578 while (indexptr < queue_index_size - 1)
580 queue_index[indexptr++] = queue_index[queue_index_size-1];
587 /*************************************************
588 * Update the recipients list for a message *
589 *************************************************/
591 /* We read the spool file only if its update time differs from last time,
592 or if there is a journal file in existence. */
594 /* First, a local subroutine to scan the non-recipients tree and
595 remove any of them from the address list */
598 scan_tree(queue_item *p, tree_node *tn)
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);
608 /* The main function */
610 static void update_recipients(queue_item *p)
615 struct stat statdata;
618 message_subdir[0] = p->dir_char;
620 sprintf(CS buffer, "%s/input/%s/%s-J", spool_directory, message_subdir, p->name);
621 jread = fopen(CS buffer, "r");
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)
629 /* Get the contents of the header file; if any problem, just give up.
630 Arrange to recover the dynamic store afterwards. */
632 reset_point = store_get(0);
633 sprintf(CS buffer, "%s-H", p->name);
634 if (spool_read_header(buffer, FALSE, TRUE) != spool_read_OK)
636 store_reset(reset_point);
637 if (jread != NULL) fclose(jread);
641 /* If there's a journal file, add its contents to the non-recipients tree */
645 while (Ufgets(big_buffer, big_buffer_size, jread) != NULL)
647 int n = Ustrlen(big_buffer);
649 tree_add_nonrecipient(big_buffer);
654 /* Scan and process the recipients list, removing any that have already
655 been delivered, and removing visible names. In the nonrecipients tree,
656 domains are lower cased. */
658 if (recipients_list != NULL)
660 for (i = 0; i < recipients_count; i++)
663 uschar *r = recipients_list[i].address;
664 tree_node *node = tree_search(tree_nonrecipients, r);
671 while (*rr != 0 && *rr != '@') rr++;
672 while (*rr != 0) { *rr = tolower(*rr); rr++; }
673 node = tree_search(tree_nonrecipients, temp);
676 if ((pp = strstric(r+1, qualify_domain, FALSE)) != NULL &&
677 *(--pp) == '@') *pp = 0;
679 (void)find_dest(p, r, dest_add, FALSE);
681 (void)find_dest(p, r, dest_remove, FALSE);
685 /* We also need to scan the tree of non-recipients, which might
686 contain child addresses that are not in the recipients list, but
687 which may have got onto the address list as a result of eximon
688 noticing an == line in the log. Then remember the update time,
689 recover the dynamic store, and we are done. */
691 scan_tree(p, tree_nonrecipients);
692 p->update_time = statdata.st_mtime;
693 store_reset(reset_point);
698 /*************************************************
699 * Display queue data *
700 *************************************************/
702 /* The present implementation simple re-writes the entire information each
703 time. Take some care to keep the scrolled position as it previously was, but,
704 if it was at the bottom, keep it at the bottom. Take note of any hide list, and
705 time out the entries as appropriate. */
710 int now = (int)time(NULL);
711 queue_item *p = queue_index[0];
713 if (menu_is_up) return; /* Avoid nasty interactions */
715 text_empty(queue_widget);
722 int t = (now - p->input_time)/60; /* minutes on queue */
732 if (t > 99) /* someone had > 99 days */
736 if (t > 99) /* so, just in case */
745 update_recipients(p); /* update destinations */
747 /* Can't set this earlier, as header data may change things. */
749 dd = p->destinations;
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. */
755 for (ddd = dd; ddd != NULL; ddd = ddd->next)
761 if (ddd->address[0] == '*') break;
762 len_address = Ustrlen(ddd->address);
764 for (skp = &queue_skip; ; skp = &(sk->next))
769 while (sk != NULL && now >= sk->reveal)
774 if (queue_skip == NULL)
776 XtDestroyWidget(unhide_widget);
777 unhide_widget = NULL;
780 if (sk == NULL) break;
782 /* If this address matches the skip item, break (sk != NULL) */
784 len_skip = Ustrlen(sk->text);
785 if (len_skip <= len_address &&
786 Ustrcmp(ddd->address + len_address - len_skip, sk->text) == 0)
790 if (sk == NULL) break;
793 /* Don't use more than one call of anon() in one statement - it uses
794 a fixed static buffer. */
796 if (ddd != NULL || dd == NULL)
798 text_showf(queue_widget, "%c%2d%c %s %s %-8s ",
799 (p->frozen)? '*' : ' ',
801 string_format_size(p->size, big_buffer),
803 (p->sender == NULL)? US" " :
804 (p->sender[0] == 0)? US"<> " : anon(p->sender));
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] == '*')? "" : ">");
811 if (dd != NULL && dd->parent != NULL && dd->parent->address[0] != '*')
812 text_showf(queue_widget, " parent <%s>", anon(dd->parent->address));
814 text_show(queue_widget, US"\n");
816 if (dd != NULL) dd = dd->next;
817 while (dd != NULL && count++ < queue_max_addresses)
819 text_showf(queue_widget, " <%s>",
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");
827 text_showf(queue_widget, " ...\n");
834 /* End of em_queue.c */