Swap gsasl GSSAPI $auth1/$auth2
[exim.git] / src / exim_monitor / em_log.c
CommitLineData
059ec3d9
PH
1/*************************************************
2* Exim Monitor *
3*************************************************/
4
0a49a7a4 5/* Copyright (c) University of Cambridge 1995 - 2009 */
059ec3d9
PH
6/* See the file NOTICE for conditions of use and distribution. */
7
f3f065bb 8/* This module contains code for scanning the main log,
059ec3d9
PH
9extracting information from it, and displaying a "tail". */
10
11#include "em_hdr.h"
12
13#define log_buffer_len 4096 /* For each log entry */
14
15/* If anonymizing, don't alter these strings (this is all an ad hoc hack). */
16
17#ifdef ANONYMIZE
18static char *oklist[] = {
19 "Completed",
20 "defer",
21 "from",
22 "Connection timed out",
23 "Start queue run: pid=",
24 "End queue run: pid=",
25 "host lookup did not complete",
26 "unexpected disconnection while reading SMTP command from",
27 "verify failed for SMTP recipient",
28 "H=",
29 "U=",
30 "id=",
31 "<",
32 ">",
33 "(",
34 ")",
35 "[",
36 "]",
37 "@",
38 "=",
39 "*",
40 ".",
41 "-",
42 "\"",
43 " ",
44 "\n"};
45static int oklist_size = sizeof(oklist) / sizeof(uschar *);
46#endif
47
48
49
50/*************************************************
51* Write to the log display *
52*************************************************/
53
54static int visible = 0;
55static int scrolled = FALSE;
56static int size = 0;
57static int top = 0;
58
59static void show_log(char *s, ...)
60{
61int length, newtop;
62va_list ap;
63XawTextBlock b;
64uschar buffer[log_buffer_len + 24];
65
66/* Do nothing if not tailing a log */
67
68if (log_widget == NULL) return;
69
70/* Initialize the text block structure */
71
72b.firstPos = 0;
73b.ptr = CS buffer;
74b.format = FMT8BIT;
75
76/* We want to know whether the window has been scrolled back or not,
77so that we can cease automatically scrolling with new text. This turns
78out to be tricky with the text widget. We can detect whether the
79scroll bar has been operated by checking on the "top" value, but it's
80harder to detect that it has been returned to the bottom. The following
81heuristic does its best. */
82
83newtop = XawTextTopPosition(log_widget);
84if (newtop != top)
85 {
86 if (!scrolled)
87 {
88 visible = size - top; /* save size of window */
89 scrolled = newtop < top;
90 }
91 else if (newtop > size - visible) scrolled = FALSE;
92 top = newtop;
93 }
94
95/* Format the text that is to be written. */
96
97va_start(ap, s);
98vsprintf(CS buffer, s, ap);
99va_end(ap);
100length = Ustrlen(buffer);
101
102/* If we are anonymizing for screen shots, flatten various things. */
103
104#ifdef ANONYMIZE
105 {
106 uschar *p = buffer + 9;
107 if (p[6] == '-' && p[13] == '-') p += 17;
108
109 while (p < buffer + length)
110 {
111 int i;
112
113 /* Check for strings to be left alone */
114
115 for (i = 0; i < oklist_size; i++)
116 {
117 int len = Ustrlen(oklist[i]);
118 if (Ustrncmp(p, oklist[i], len) == 0)
119 {
120 p += len;
121 break;
122 }
123 }
124 if (i < oklist_size) continue;
125
126 /* Leave driver names, size, protocol, alone */
127
128 if ((*p == 'D' || *p == 'P' || *p == 'T' || *p == 'S' || *p == 'R') &&
129 p[1] == '=')
130 {
131 p += 2;
132 while (*p != ' ' && *p != 0) p++;
133 continue;
134 }
135
136 /* Leave C= text alone */
137
138 if (Ustrncmp(p, "C=\"", 3) == 0)
139 {
140 p += 3;
141 while (*p != 0 && *p != '"') p++;
142 continue;
143 }
144
145 /* Flatten remaining chars */
146
147 if (isdigit(*p)) *p++ = 'x';
148 else if (isalpha(*p)) *p++ = 'x';
149 else *p++ = '$';
150 }
151 }
152#endif
153
154/* If this would overflow the buffer, throw away 50% of the
155current stuff in the buffer. Code defensively against odd
156extreme cases that shouldn't actually arise. */
157
158if (size + length > log_buffer_size)
159 {
160 if (size == 0) length = log_buffer_size/2; else
161 {
162 int cutcount = log_buffer_size/2;
163 if (cutcount > size) cutcount = size; else
164 {
165 while (cutcount < size && log_display_buffer[cutcount] != '\n')
166 cutcount++;
167 cutcount++;
168 }
169 b.length = 0;
170 XawTextReplace(log_widget, 0, cutcount, &b);
171 size -= cutcount;
172 top -= cutcount;
173 if (top < 0) top = 0;
174 if (top < cutcount) XawTextInvalidate(log_widget, 0, 999999);
175 xs_SetValues(log_widget, 1, "displayPosition", top);
176 }
177 }
178
179/* Insert the new text at the end of the buffer. */
180
181b.length = length;
182XawTextReplace(log_widget, 999999, 999999, &b);
183size += length;
184
185/* When not scrolled back, we want to keep the bottom line
186always visible. Put the insert point at the start of it because
187this stops left/right scrolling with some X libraries. */
188
189if (!scrolled)
190 {
191 XawTextSetInsertionPoint(log_widget, size - length);
192 top = XawTextTopPosition(log_widget);
193 }
194}
195
196
197
198
199/*************************************************
200* Function to read the log *
201*************************************************/
202
203/* We read any new log entries, and use their data to
204updated total counts for the configured stripcharts.
205The count for the queue chart is handled separately.
206We also munge the log entries and display a one-line
207version in the log window. */
208
209void read_log(void)
210{
211struct stat statdata;
212uschar buffer[log_buffer_len];
213
214/* If log is not yet open, skip all of this. */
215
216if (LOG != NULL)
217 {
218 fseek(LOG, log_position, SEEK_SET);
219
220 while (Ufgets(buffer, log_buffer_len, LOG) != NULL)
221 {
222 uschar *id;
223 uschar *p = buffer;
224 void *reset_point;
225 int length = Ustrlen(buffer);
226 int i;
227
228 /* Skip totally blank lines (paranoia: there shouldn't be any) */
229
230 while (*p == ' ' || *p == '\t') p++;
231 if (*p == '\n') continue;
232
233 /* We should now have a complete log entry in the buffer; check
234 it for various regular expression matches and take appropriate
235 action. Get the current store point so we can reset to it. */
236
237 reset_point = store_get(0);
238
239 /* First, update any stripchart data values, noting that the zeroth
240 stripchart is the queue length, which is handled elsewhere, and the
241 1st may the a size monitor. */
242
243 for (i = stripchart_varstart; i < stripchart_number; i++)
244 {
245 if (pcre_exec(stripchart_regex[i], NULL, CS buffer, length, 0, PCRE_EOPT,
246 NULL, 0) >= 0)
247 stripchart_total[i]++;
248 }
249
250 /* Munge the log entry and display shortened form on one line.
f3f065bb
PH
251 We omit the date and show only the time. Remove any time zone offset.
252 Take note of the presence of [pid]. */
059ec3d9
PH
253
254 if (pcre_exec(yyyymmdd_regex,NULL,CS buffer,length,0,PCRE_EOPT,NULL,0) >= 0)
255 {
f3f065bb 256 int pidlength = 0;
059ec3d9
PH
257 if ((buffer[20] == '+' || buffer[20] == '-') &&
258 isdigit(buffer[21]) && buffer[25] == ' ')
259 memmove(buffer + 20, buffer + 26, Ustrlen(buffer + 26) + 1);
f3f065bb
PH
260 if (buffer[20] == '[')
261 {
262 while (Ustrchr("[]0123456789", buffer[20+pidlength++]) != NULL);
263 }
264 id = string_copyn(buffer + 20 + pidlength, MESSAGE_ID_LENGTH);
059ec3d9
PH
265 show_log("%s", buffer+11);
266 }
267 else
268 {
269 id = US"";
270 show_log("%s", buffer);
271 }
272
273 /* Deal with frozen and unfrozen messages */
274
275 if (strstric(buffer, US"frozen", FALSE) != NULL)
276 {
277 queue_item *qq = find_queue(id, queue_noop, 0);
278 if (qq != NULL)
279 {
280 if (strstric(buffer, US"unfrozen", FALSE) != NULL)
281 qq->frozen = FALSE;
282 else qq->frozen = TRUE;
283 }
284 }
285
286 /* Notice defer messages, and add the destination if it
287 isn't already on the list for this message, with a pointer
288 to the parent if we can. */
289
290 if ((p = Ustrstr(buffer, "==")) != NULL)
291 {
292 queue_item *qq = find_queue(id, queue_noop, 0);
293 if (qq != NULL)
294 {
295 dest_item *d;
296 uschar *q, *r;
297 p += 2;
298 while (isspace(*p)) p++;
299 q = p;
300 while (*p != 0 && !isspace(*p))
301 {
302 if (*p++ != '\"') continue;
303 while (*p != 0)
304 {
305 if (*p == '\\') p += 2;
306 else if (*p++ == '\"') break;
307 }
308 }
309 *p++ = 0;
310 if ((r = strstric(q, qualify_domain, FALSE)) != NULL &&
311 *(--r) == '@') *r = 0;
312
313 /* If we already have this destination, as tested case-insensitively,
314 do not add it to the destinations list. */
315
316 d = find_dest(qq, q, dest_add, TRUE);
317
318 if (d->parent == NULL)
319 {
320 while (isspace(*p)) p++;
321 if (*p == '<')
322 {
323 dest_item *dd;
324 q = ++p;
325 while (*p != 0 && *p != '>') p++;
326 *p = 0;
327 if ((p = strstric(q, qualify_domain, FALSE)) != NULL &&
328 *(--p) == '@') *p = 0;
329 dd = find_dest(qq, q, dest_noop, FALSE);
330 if (dd != NULL && dd != d) d->parent = dd;
331 }
332 }
333 }
334 }
335
336 store_reset(reset_point);
337 }
338 }
339
340
341/* We have to detect when the log file is changed, and switch to the new file.
342In practice, for non-datestamped files, this means that some deliveries might
343go unrecorded, since they'll be written to the old file, but this usually
344happens in the middle of the night, and I don't think the hassle of keeping
345track of two log files is worth it.
346
347First we check the datestamped name of the log file if necessary; if it is
348different to the file we currently have open, go for the new file. As happens
349in Exim itself, we leave in the following inode check, even when datestamping
350because it does no harm and will cope should a file actually be renamed for
351some reason.
352
353The test for a changed log file is to look up the inode of the file by name and
354compare it with the saved inode of the file we currently are processing. This
355accords with the usual interpretation of POSIX and other Unix specs that imply
356"one file, one inode". However, it appears that on some Digital systems, if an
357open file is unlinked, a new file may be created with the same inode while the
358old file remains in existence. This can happen if the old log file is renamed,
359processed in some way, and then deleted. To work round this, also test for a
360link count of zero on the currently open file. */
361
362if (log_datestamping)
363 {
364 uschar log_file_wanted[256];
365 string_format(log_file_wanted, sizeof(log_file_wanted), CS log_file);
366 if (Ustrcmp(log_file_wanted, log_file_open) != 0)
367 {
368 if (LOG != NULL)
369 {
370 fclose(LOG);
371 LOG = NULL;
372 }
373 Ustrcpy(log_file_open, log_file_wanted);
374 }
375 }
376
377if (LOG == NULL ||
378 (fstat(fileno(LOG), &statdata) == 0 && statdata.st_nlink == 0) ||
379 (Ustat(log_file, &statdata) == 0 && log_inode != statdata.st_ino))
380 {
381 FILE *TEST;
382
383 /* Experiment shows that sometimes you can't immediately open
384 the new log file - presumably immediately after the old one
385 is renamed and before the new one exists. Therefore do a
386 trial open first to be sure. */
387
388 if ((TEST = fopen(CS log_file_open, "r")) != NULL)
389 {
390 if (LOG != NULL) fclose(LOG);
391 LOG = TEST;
392 fstat(fileno(LOG), &statdata);
393 log_inode = statdata.st_ino;
394 }
395 }
396
397/* Save the position we have got to in the log. */
398
399if (LOG != NULL) log_position = ftell(LOG);
400}
401
402/* End of em_log.c */