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 snprintf(CS buffer, sizeof(buffer), "%s/input/%s/%s/%s-D",
267 spool_directory, queue_name, message_subdir, name);
268 if (Ustat(buffer, &statdata) == 0)
269 q->size = message_size + statdata.st_size - SPOOL_DATA_START_OFFSET + 1;
271 /* Scan and process the recipients list, skipping any that have already
272 been delivered, and removing visible names. */
274 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);
286 /* Recover the dynamic store used by spool_read_header(). */
288 store_reset(reset_point);
294 /*************************************************
295 * Find/Create a queue item *
296 *************************************************/
298 /* The queue is kept as a doubly-linked list, sorted by name. However,
299 to speed up searches, an index into the list is used. This is maintained
300 by the scan_spool_input function when it goes down the list throwing
301 out entries that are no longer needed. When the action is "add" and
302 we don't need to add, mark the found item as seen. */
306 static void debug_queue(void)
311 printf("\nqueue_total=%d\n", queue_total);
313 for (i = 0; i < queue_index_size; i++)
314 printf("index %d = %d %s\n", i, (int)(queue_index[i]),
315 (queue_index[i])->name);
317 printf("Queue is:\n");
322 for (i = 0; i < queue_index_size; i++)
324 if (queue_index[i] == p) printf("count=%d index=%d\n", count, (int)p);
326 printf("%d %d %d %s\n", (int)p, (int)p->next, (int)p->prev, p->name);
334 queue_item *find_queue(uschar *name, int action, int dir_char)
337 int last = queue_index_size - 1;
338 int middle = (first + last)/2;
339 queue_item *p, *q, *qq;
341 /* Handle the empty queue as a special case. */
343 if (queue_total == 0)
345 if (action != queue_add) return NULL;
346 if ((qq = set_up(name, dir_char)) != NULL)
349 for (i = 0; i < queue_index_size; i++) queue_index[i] = qq;
356 /* Also handle insertion at the start or end of the queue
359 if (Ustrcmp(name, (queue_index[0])->name) < 0)
361 if (action != queue_add) return NULL;
362 if ((qq = set_up(name, dir_char)) != NULL)
364 qq->next = queue_index[0];
365 (queue_index[0])->prev = qq;
373 if (Ustrcmp(name, (queue_index[queue_index_size-1])->name) > 0)
375 if (action != queue_add) return NULL;
376 if ((qq = set_up(name, dir_char)) != NULL)
378 qq->prev = queue_index[queue_index_size-1];
379 (queue_index[queue_index_size-1])->next = qq;
380 queue_index[queue_index_size-1] = qq;
387 /* Use binary chopping on the index to get a range of the queue to search
388 when the name is somewhere in the middle, if present. */
390 while (middle > first)
392 if (Ustrcmp(name, (queue_index[middle])->name) >= 0) first = middle;
394 middle = (first + last)/2;
397 /* Now search down the part of the queue in which the item must
398 lie if it exists. Both end points are inclusive - though in fact
399 the bottom one can only be = if it is the original bottom. */
401 p = queue_index[first];
402 q = queue_index[last];
406 int c = Ustrcmp(name, p->name);
408 /* Already on queue; mark seen if required. */
412 if (action == queue_add) p->seen = TRUE;
416 /* Not on the queue; add an entry if required. Note that set-up might
417 fail (the file might vanish under our feet). Note also that we know
418 there is always a previous item to p because the end points are
423 if (action == queue_add)
425 if ((qq = set_up(name, dir_char)) != NULL)
438 /* Control should not reach here if p == q, because the name
439 is supposed to be <= the name of the bottom item. */
441 if (p == q) return NULL;
443 /* Else might be further down the queue; continue */
448 /* Control should never reach here. */
453 /*************************************************
454 * Scan the exim spool directory *
455 *************************************************/
457 /* If we discover that there are subdirectories, set a flag so that the menu
458 code knows to look for them. We count the entries to set the value for the
459 queue stripchart, and set up data for the queue display window if the "full"
462 void scan_spool_input(int full)
472 uschar input_dir[256];
476 stripchart_total[0] = 0;
478 sprintf(CS input_dir, "%s/input", spool_directory);
479 subptr = Ustrlen(input_dir);
480 input_dir[subptr+2] = 0; /* terminator for lengthened name */
482 /* Loop for each spool file on the queue - searching any subdirectories that
483 may exist. When initializing eximon, every file will have to be read. To show
484 there is progress, output a dot for each one to the standard output. */
486 for (i = 0; i < subdir_max; i++)
488 int subdirchar = subdirs[i]; /* 0 for main directory */
491 input_dir[subptr] = '/';
492 input_dir[subptr+1] = subdirchar;
495 dd = opendir(CS input_dir);
496 if (dd == NULL) continue;
498 while ((ent = readdir(dd)) != NULL)
500 uschar *name = US ent->d_name;
501 int len = Ustrlen(name);
503 /* If we find a single alphameric sub-directory on the first
504 pass, add it to the list for subsequent scans, and remember that
505 we are dealing with a split directory. */
507 if (i == 0 && len == 1 && isalnum(*name))
509 subdirs[subdir_max++] = *name;
510 spool_is_split = TRUE;
514 /* Otherwise, if it is a header spool file, add it to the list */
516 if (len == SPOOL_NAME_LENGTH &&
517 name[SPOOL_NAME_LENGTH - 2] == '-' &&
518 name[SPOOL_NAME_LENGTH - 1] == 'H')
520 uschar basename[SPOOL_NAME_LENGTH + 1];
521 stripchart_total[0]++;
522 if (!eximon_initialized) { printf("."); fflush(stdout); }
523 Ustrcpy(basename, name);
524 basename[SPOOL_NAME_LENGTH - 2] = 0;
525 if (full) find_queue(basename, queue_add, subdirchar);
531 /* If simply counting the number, we are done; same if there are no
532 items in the in-store queue. */
534 if (!full || queue_total == 0) return;
536 /* Now scan the queue and remove any items that were not in the directory. At
537 the same time, set up the index pointers into the queue. Because we are
538 removing items, the total that we are comparing against isn't actually correct,
539 but in a long queue it won't make much difference, and in a short queue it
540 doesn't matter anyway!*/
547 queue_item *next = p->next;
548 if (p->prev == NULL) queue_index[0] = next;
549 else p->prev->next = next;
553 queue_item *q = queue_index[queue_index_size-1];
554 for (i = queue_index_size - 1; i >= 0; i--)
555 if (queue_index[i] == q) queue_index[i] = p->prev;
557 else next->prev = p->prev;
564 if (++count > (queue_total * indexptr)/(queue_index_size-1))
566 queue_index[indexptr++] = p;
568 p->seen = FALSE; /* for next time */
573 /* If a lot of messages have been removed at the bottom, we may not
574 have got the index all filled in yet. Make sure all the pointers
577 while (indexptr < queue_index_size - 1)
579 queue_index[indexptr++] = queue_index[queue_index_size-1];
586 /*************************************************
587 * Update the recipients list for a message *
588 *************************************************/
590 /* We read the spool file only if its update time differs from last time,
591 or if there is a journal file in existence. */
593 /* First, a local subroutine to scan the non-recipients tree and
594 remove any of them from the address list */
597 scan_tree(queue_item *p, tree_node *tn)
601 if (tn->left != NULL) scan_tree(p, tn->left);
602 if (tn->right != NULL) scan_tree(p, tn->right);
603 (void)find_dest(p, tn->name, dest_remove, FALSE);
607 /* The main function */
609 static void update_recipients(queue_item *p)
614 struct stat statdata;
617 message_subdir[0] = p->dir_char;
619 snprintf(CS buffer, sizeof(buffer), "%s/input/%s/%s/%s-J",
620 spool_directory, queue_name, message_subdir, p->name);
622 if (!(jread = fopen(CS buffer, "r")))
624 snprintf(CS buffer, sizeof(buffer), "%s/input/%s/%s/%s-H",
625 spool_directory, queue_name, message_subdir, p->name);
626 if (Ustat(buffer, &statdata) < 0 || p->update_time == statdata.st_mtime)
630 /* Get the contents of the header file; if any problem, just give up.
631 Arrange to recover the dynamic store afterwards. */
633 reset_point = store_get(0);
634 sprintf(CS buffer, "%s-H", p->name);
635 if (spool_read_header(buffer, FALSE, TRUE) != spool_read_OK)
637 store_reset(reset_point);
638 if (jread != NULL) fclose(jread);
642 /* If there's a journal file, add its contents to the non-recipients tree */
646 while (Ufgets(big_buffer, big_buffer_size, jread) != NULL)
648 int n = Ustrlen(big_buffer);
650 tree_add_nonrecipient(big_buffer);
655 /* Scan and process the recipients list, removing any that have already
656 been delivered, and removing visible names. In the nonrecipients tree,
657 domains are lower cased. */
659 if (recipients_list != NULL)
661 for (i = 0; i < recipients_count; i++)
664 uschar *r = recipients_list[i].address;
665 tree_node *node = tree_search(tree_nonrecipients, r);
672 while (*rr != 0 && *rr != '@') rr++;
673 while (*rr != 0) { *rr = tolower(*rr); rr++; }
674 node = tree_search(tree_nonrecipients, temp);
677 if ((pp = strstric(r+1, qualify_domain, FALSE)) != NULL &&
678 *(--pp) == '@') *pp = 0;
680 (void)find_dest(p, r, dest_add, FALSE);
682 (void)find_dest(p, r, dest_remove, FALSE);
686 /* We also need to scan the tree of non-recipients, which might
687 contain child addresses that are not in the recipients list, but
688 which may have got onto the address list as a result of eximon
689 noticing an == line in the log. Then remember the update time,
690 recover the dynamic store, and we are done. */
692 scan_tree(p, tree_nonrecipients);
693 p->update_time = statdata.st_mtime;
694 store_reset(reset_point);
699 /*************************************************
700 * Display queue data *
701 *************************************************/
703 /* The present implementation simple re-writes the entire information each
704 time. Take some care to keep the scrolled position as it previously was, but,
705 if it was at the bottom, keep it at the bottom. Take note of any hide list, and
706 time out the entries as appropriate. */
711 int now = (int)time(NULL);
712 queue_item *p = queue_index[0];
714 if (menu_is_up) return; /* Avoid nasty interactions */
716 text_empty(queue_widget);
723 int t = (now - p->input_time)/60; /* minutes on queue */
733 if (t > 99) /* someone had > 99 days */
737 if (t > 99) /* so, just in case */
746 update_recipients(p); /* update destinations */
748 /* Can't set this earlier, as header data may change things. */
750 dd = p->destinations;
752 /* Check to see if this message is on the hide list; if any hide
753 item has timed out, remove it from the list. Hide if all destinations
754 are on the hide list. */
756 for (ddd = dd; ddd != NULL; ddd = ddd->next)
762 if (ddd->address[0] == '*') break;
763 len_address = Ustrlen(ddd->address);
765 for (skp = &queue_skip; ; skp = &(sk->next))
770 while (sk != NULL && now >= sk->reveal)
775 if (queue_skip == NULL)
777 XtDestroyWidget(unhide_widget);
778 unhide_widget = NULL;
781 if (sk == NULL) break;
783 /* If this address matches the skip item, break (sk != NULL) */
785 len_skip = Ustrlen(sk->text);
786 if (len_skip <= len_address &&
787 Ustrcmp(ddd->address + len_address - len_skip, sk->text) == 0)
791 if (sk == NULL) break;
794 /* Don't use more than one call of anon() in one statement - it uses
795 a fixed static buffer. */
797 if (ddd != NULL || dd == NULL)
799 text_showf(queue_widget, "%c%2d%c %s %s %-8s ",
800 (p->frozen)? '*' : ' ',
802 string_format_size(p->size, big_buffer),
804 (p->sender == NULL)? US" " :
805 (p->sender[0] == 0)? US"<> " : anon(p->sender));
807 text_showf(queue_widget, "%s%s%s",
808 (dd == NULL || dd->address[0] == '*')? "" : "<",
809 (dd == NULL)? US"" : anon(dd->address),
810 (dd == NULL || dd->address[0] == '*')? "" : ">");
812 if (dd != NULL && dd->parent != NULL && dd->parent->address[0] != '*')
813 text_showf(queue_widget, " parent <%s>", anon(dd->parent->address));
815 text_show(queue_widget, US"\n");
817 if (dd != NULL) dd = dd->next;
818 while (dd != NULL && count++ < queue_max_addresses)
820 text_showf(queue_widget, " <%s>",
822 if (dd->parent != NULL && dd->parent->address[0] != '*')
823 text_showf(queue_widget, " parent <%s>", anon(dd->parent->address));
824 text_show(queue_widget, US"\n");
828 text_showf(queue_widget, " ...\n");
835 /* End of em_queue.c */