1 /*************************************************
3 *************************************************/
5 /* Copyright (c) University of Cambridge 1995 - 2018 */
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. */
67 find_dest(queue_item *q, uschar *name, int action, BOOL caseless)
70 dest_item **d = &(q->destinations);
74 if ((caseless? strcmpic(name,(*d)->address) : Ustrcmp(name,(*d)->address))
79 if (action != dest_remove) return *d;
84 /* Unset any parent pointers that were to this address */
86 for (ddd = q->destinations; ddd != NULL; ddd = ddd->next)
88 if (ddd->parent == dd) ddd->parent = NULL;
96 if (action != dest_add) return NULL;
98 dd = (dest_item *)store_malloc(sizeof(dest_item) + Ustrlen(name));
99 Ustrcpy(dd->address, name);
108 /*************************************************
109 * Clean up a dead queue item *
110 *************************************************/
113 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), FALSE);
142 Ustrcpy(node->name, name);
143 node->data.ptr = NULL;
144 (void)tree_insertnode(root, node);
150 /*************************************************
151 * Set up new queue item *
152 *************************************************/
155 set_up(uschar *name, int dir_char)
157 int i, rc, save_errno;
158 struct stat statdata;
161 queue_item *q = (queue_item *)store_malloc(sizeof(queue_item));
164 /* Initialize the block */
166 q->next = q->prev = NULL;
167 q->destinations = NULL;
168 Ustrncpy(q->name, name, sizeof(q->name));
171 q->dir_char = dir_char;
175 /* Read the header file from the spool; if there is a failure it might mean
176 inaccessibility as a result of protections. A successful read will have caused
177 sender_address to get set and the recipients fields to be initialized. If
178 there's a format error in the headers, we can still display info from the
181 Before reading the header remember the position in the dynamic store so that
182 we can recover the store into which the header is read. All data read by
183 spool_read_header that is to be preserved is copied into malloc store. */
185 reset_point = store_mark();
187 message_subdir[0] = dir_char;
188 sprintf(CS buffer, "%s-H", name);
189 rc = spool_read_header(buffer, FALSE, TRUE);
192 /* If we failed to read the envelope, compute the input time by
193 interpreting the id as a base-62 number. */
195 if (rc != spool_read_OK && rc != spool_read_hdrerror)
198 for (i = 0; i < 6; i++) t = t * 62 + tab62[name[i] - '0'];
199 q->update_time = q->input_time = t;
202 /* Envelope read; get input time and remove qualify_domain from sender address,
207 q->update_time = q->input_time = received_time.tv_sec;
208 if ((p = strstric(sender_address+1, qualify_domain, FALSE)) != NULL &&
209 *(--p) == '@') *p = 0;
212 /* If we didn't read the whole header successfully, generate an error
213 message. If the envelope was read, this appears as a first recipient;
214 otherwise it sets set up in the sender field. */
216 if (rc != spool_read_OK)
220 if (save_errno == ERRNO_SPOOLFORMAT)
223 sprintf(CS big_buffer, "%s/input/%s", spool_directory, buffer);
224 if (Ustat(big_buffer, &statbuf) == 0)
225 msg = string_sprintf("*** Format error in spool file: size = %d ***",
227 else msg = US"*** Format error in spool file ***";
229 else msg = US"*** Cannot read spool file ***";
231 if (rc == spool_read_hdrerror)
233 (void)find_dest(q, msg, dest_add, FALSE);
237 f.deliver_freeze = FALSE;
238 sender_address = msg;
239 recipients_count = 0;
243 /* Now set up the remaining data. */
245 q->frozen = f.deliver_freeze;
247 if (f.sender_set_untrusted)
249 if (sender_address[0] == 0)
251 q->sender = store_malloc(Ustrlen(originator_login) + 6);
252 sprintf(CS q->sender, "<> (%s)", originator_login);
256 q->sender = store_malloc(Ustrlen(sender_address) +
257 Ustrlen(originator_login) + 4);
258 sprintf(CS q->sender, "%s (%s)", sender_address, originator_login);
263 q->sender = store_malloc(Ustrlen(sender_address) + 1);
264 Ustrcpy(q->sender, sender_address);
267 sender_address = NULL;
269 snprintf(CS buffer, sizeof(buffer), "%s/input/%s/%s/%s-D",
270 spool_directory, queue_name, message_subdir, name);
271 if (Ustat(buffer, &statdata) == 0)
272 q->size = message_size + statdata.st_size - SPOOL_DATA_START_OFFSET + 1;
274 /* Scan and process the recipients list, skipping any that have already
275 been delivered, and removing visible names. */
277 if (recipients_list != NULL)
278 for (i = 0; i < recipients_count; i++)
280 uschar *r = recipients_list[i].address;
281 if (tree_search(tree_nonrecipients, r) == NULL)
283 if ((p = strstric(r+1, qualify_domain, FALSE)) != NULL &&
284 *(--p) == '@') *p = 0;
285 (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);
338 find_queue(uschar *name, int action, int dir_char)
341 int last = queue_index_size - 1;
342 int middle = (first + last)/2;
343 queue_item *p, *q, *qq;
345 /* Handle the empty queue as a special case. */
347 if (queue_total == 0)
349 if (action != queue_add) return NULL;
350 if ((qq = set_up(name, dir_char)) != NULL)
353 for (i = 0; i < queue_index_size; i++) queue_index[i] = qq;
360 /* Also handle insertion at the start or end of the queue
363 if (Ustrcmp(name, (queue_index[0])->name) < 0)
365 if (action != queue_add) return NULL;
366 if ((qq = set_up(name, dir_char)) != NULL)
368 qq->next = queue_index[0];
369 (queue_index[0])->prev = qq;
377 if (Ustrcmp(name, (queue_index[queue_index_size-1])->name) > 0)
379 if (action != queue_add) return NULL;
380 if ((qq = set_up(name, dir_char)) != NULL)
382 qq->prev = queue_index[queue_index_size-1];
383 (queue_index[queue_index_size-1])->next = qq;
384 queue_index[queue_index_size-1] = qq;
391 /* Use binary chopping on the index to get a range of the queue to search
392 when the name is somewhere in the middle, if present. */
394 while (middle > first)
396 if (Ustrcmp(name, (queue_index[middle])->name) >= 0) first = middle;
398 middle = (first + last)/2;
401 /* Now search down the part of the queue in which the item must
402 lie if it exists. Both end points are inclusive - though in fact
403 the bottom one can only be = if it is the original bottom. */
405 p = queue_index[first];
406 q = queue_index[last];
410 int c = Ustrcmp(name, p->name);
412 /* Already on queue; mark seen if required. */
416 if (action == queue_add) p->seen = TRUE;
420 /* Not on the queue; add an entry if required. Note that set-up might
421 fail (the file might vanish under our feet). Note also that we know
422 there is always a previous item to p because the end points are
427 if (action == queue_add)
429 if ((qq = set_up(name, dir_char)) != NULL)
442 /* Control should not reach here if p == q, because the name
443 is supposed to be <= the name of the bottom item. */
445 if (p == q) return NULL;
447 /* Else might be further down the queue; continue */
452 /* Control should never reach here. */
457 /*************************************************
458 * Scan the exim spool directory *
459 *************************************************/
461 /* If we discover that there are subdirectories, set a flag so that the menu
462 code knows to look for them. We count the entries to set the value for the
463 queue stripchart, and set up data for the queue display window if the "full"
466 void scan_spool_input(int full)
476 uschar input_dir[256];
480 stripchart_total[0] = 0;
482 sprintf(CS input_dir, "%s/input", spool_directory);
483 subptr = Ustrlen(input_dir);
484 input_dir[subptr+2] = 0; /* terminator for lengthened name */
486 /* Loop for each spool file on the queue - searching any subdirectories that
487 may exist. When initializing eximon, every file will have to be read. To show
488 there is progress, output a dot for each one to the standard output. */
490 for (i = 0; i < subdir_max; i++)
492 int subdirchar = subdirs[i]; /* 0 for main directory */
495 input_dir[subptr] = '/';
496 input_dir[subptr+1] = subdirchar;
499 dd = opendir(CS input_dir);
500 if (dd == NULL) continue;
502 while ((ent = readdir(dd)) != NULL)
504 uschar *name = US ent->d_name;
505 int len = Ustrlen(name);
507 /* If we find a single alphameric sub-directory on the first
508 pass, add it to the list for subsequent scans, and remember that
509 we are dealing with a split directory. */
511 if (i == 0 && len == 1 && isalnum(*name))
513 subdirs[subdir_max++] = *name;
514 spool_is_split = TRUE;
518 /* Otherwise, if it is a header spool file, add it to the list */
520 if (len == SPOOL_NAME_LENGTH &&
521 name[SPOOL_NAME_LENGTH - 2] == '-' &&
522 name[SPOOL_NAME_LENGTH - 1] == 'H')
524 uschar basename[SPOOL_NAME_LENGTH + 1];
525 stripchart_total[0]++;
526 if (!eximon_initialized) { printf("."); fflush(stdout); }
527 Ustrcpy(basename, name);
528 basename[SPOOL_NAME_LENGTH - 2] = 0;
529 if (full) find_queue(basename, queue_add, subdirchar);
535 /* If simply counting the number, we are done; same if there are no
536 items in the in-store queue. */
538 if (!full || queue_total == 0) return;
540 /* Now scan the queue and remove any items that were not in the directory. At
541 the same time, set up the index pointers into the queue. Because we are
542 removing items, the total that we are comparing against isn't actually correct,
543 but in a long queue it won't make much difference, and in a short queue it
544 doesn't matter anyway!*/
551 queue_item *next = p->next;
552 if (p->prev == NULL) queue_index[0] = next;
553 else p->prev->next = next;
557 queue_item *q = queue_index[queue_index_size-1];
558 for (i = queue_index_size - 1; i >= 0; i--)
559 if (queue_index[i] == q) queue_index[i] = p->prev;
561 else next->prev = p->prev;
568 if (++count > (queue_total * indexptr)/(queue_index_size-1))
570 queue_index[indexptr++] = p;
572 p->seen = FALSE; /* for next time */
577 /* If a lot of messages have been removed at the bottom, we may not
578 have got the index all filled in yet. Make sure all the pointers
581 while (indexptr < queue_index_size - 1)
583 queue_index[indexptr++] = queue_index[queue_index_size-1];
590 /*************************************************
591 * Update the recipients list for a message *
592 *************************************************/
594 /* We read the spool file only if its update time differs from last time,
595 or if there is a journal file in existence. */
597 /* First, a local subroutine to scan the non-recipients tree and
598 remove any of them from the address list */
601 scan_tree(queue_item *p, tree_node *tn)
605 if (tn->left != NULL) scan_tree(p, tn->left);
606 if (tn->right != NULL) scan_tree(p, tn->right);
607 (void)find_dest(p, tn->name, dest_remove, FALSE);
611 /* The main function */
613 static void update_recipients(queue_item *p)
618 struct stat statdata;
621 message_subdir[0] = p->dir_char;
623 snprintf(CS buffer, sizeof(buffer), "%s/input/%s/%s/%s-J",
624 spool_directory, queue_name, message_subdir, p->name);
626 if (!(jread = fopen(CS buffer, "r")))
628 snprintf(CS buffer, sizeof(buffer), "%s/input/%s/%s/%s-H",
629 spool_directory, queue_name, message_subdir, p->name);
630 if (Ustat(buffer, &statdata) < 0 || p->update_time == statdata.st_mtime)
634 /* Get the contents of the header file; if any problem, just give up.
635 Arrange to recover the dynamic store afterwards. */
637 reset_point = store_mark();
638 sprintf(CS buffer, "%s-H", p->name);
639 if (spool_read_header(buffer, FALSE, TRUE) != spool_read_OK)
641 store_reset(reset_point);
642 if (jread != NULL) fclose(jread);
646 /* If there's a journal file, add its contents to the non-recipients tree */
650 while (Ufgets(big_buffer, big_buffer_size, jread) != NULL)
652 int n = Ustrlen(big_buffer);
654 tree_add_nonrecipient(big_buffer);
659 /* Scan and process the recipients list, removing any that have already
660 been delivered, and removing visible names. In the nonrecipients tree,
661 domains are lower cased. */
664 for (i = 0; i < recipients_count; i++)
667 uschar * r = recipients_list[i].address;
670 if (!(node = tree_search(tree_nonrecipients, r)))
671 node = tree_search(tree_nonrecipients, string_copylc(r));
673 if ((pp = strstric(r+1, qualify_domain, FALSE)) && *(--pp) == '@')
676 (void)find_dest(p, r, dest_add, FALSE);
678 (void)find_dest(p, r, dest_remove, FALSE);
681 /* We also need to scan the tree of non-recipients, which might
682 contain child addresses that are not in the recipients list, but
683 which may have got onto the address list as a result of eximon
684 noticing an == line in the log. Then remember the update time,
685 recover the dynamic store, and we are done. */
687 scan_tree(p, tree_nonrecipients);
688 p->update_time = statdata.st_mtime;
689 store_reset(reset_point);
694 /*************************************************
695 * Display queue data *
696 *************************************************/
698 /* The present implementation simple re-writes the entire information each
699 time. Take some care to keep the scrolled position as it previously was, but,
700 if it was at the bottom, keep it at the bottom. Take note of any hide list, and
701 time out the entries as appropriate. */
706 int now = (int)time(NULL);
707 queue_item *p = queue_index[0];
709 if (menu_is_up) return; /* Avoid nasty interactions */
711 text_empty(queue_widget);
718 int t = (now - p->input_time)/60; /* minutes on queue */
728 if (t > 99) /* someone had > 99 days */
732 if (t > 99) /* so, just in case */
741 update_recipients(p); /* update destinations */
743 /* Can't set this earlier, as header data may change things. */
745 dd = p->destinations;
747 /* Check to see if this message is on the hide list; if any hide
748 item has timed out, remove it from the list. Hide if all destinations
749 are on the hide list. */
751 for (ddd = dd; ddd != NULL; ddd = ddd->next)
757 if (ddd->address[0] == '*') break;
758 len_address = Ustrlen(ddd->address);
760 for (skp = &queue_skip; ; skp = &(sk->next))
765 while (sk != NULL && now >= sk->reveal)
770 if (queue_skip == NULL)
772 XtDestroyWidget(unhide_widget);
773 unhide_widget = NULL;
776 if (sk == NULL) break;
778 /* If this address matches the skip item, break (sk != NULL) */
780 len_skip = Ustrlen(sk->text);
781 if (len_skip <= len_address &&
782 Ustrcmp(ddd->address + len_address - len_skip, sk->text) == 0)
786 if (sk == NULL) break;
789 /* Don't use more than one call of anon() in one statement - it uses
790 a fixed static buffer. */
792 if (ddd != NULL || dd == NULL)
794 text_showf(queue_widget, "%c%2d%c %s %s %-8s ",
795 (p->frozen)? '*' : ' ',
797 string_format_size(p->size, big_buffer),
799 (p->sender == NULL)? US" " :
800 (p->sender[0] == 0)? US"<> " : anon(p->sender));
802 text_showf(queue_widget, "%s%s%s",
803 (dd == NULL || dd->address[0] == '*')? "" : "<",
804 (dd == NULL)? US"" : anon(dd->address),
805 (dd == NULL || dd->address[0] == '*')? "" : ">");
807 if (dd != NULL && dd->parent != NULL && dd->parent->address[0] != '*')
808 text_showf(queue_widget, " parent <%s>", anon(dd->parent->address));
810 text_show(queue_widget, US"\n");
812 if (dd != NULL) dd = dd->next;
813 while (dd != NULL && count++ < queue_max_addresses)
815 text_showf(queue_widget, " <%s>",
817 if (dd->parent != NULL && dd->parent->address[0] != '*')
818 text_showf(queue_widget, " parent <%s>", anon(dd->parent->address));
819 text_show(queue_widget, US"\n");
823 text_showf(queue_widget, " ...\n");
830 /* End of em_queue.c */