]> git.proxmox.com Git - pmg-log-tracker.git/blob - pmg-log-tracker.c
3e0f92bec9a95881121fca24d0f78ad27630311a
[pmg-log-tracker.git] / pmg-log-tracker.c
1 /*
2
3 (C) 2007-2017 Proxmox Server Solutions GmbH, All Rights Reserved
4
5 Proxmox Mail Tracker
6
7 This program is free software: you can redistribute it and/or modify
8 it under the terms of the GNU Affero General Public License as published by
9 the Free Software Foundation, either version 3 of the License, or
10 (at your option) any later version.
11
12 This program is distributed in the hope that it will be useful,
13 but WITHOUT ANY WARRANTY; without even the implied warranty of
14 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 GNU Affero General Public License for more details.
16
17 You should have received a copy of the GNU Affero General Public License
18 along with this program. If not, see <http://www.gnu.org/licenses/>.
19
20 Author: Dietmar Maurer <dietmar@proxmox.com>
21
22 See http://www.proxmox.com for more information
23
24 */
25
26 #define _GNU_SOURCE
27
28 #include <stdlib.h>
29 #include <stdio.h>
30 #include <glib.h>
31 #include <string.h>
32 #include <ctype.h>
33 #include <sys/time.h>
34 #include <time.h>
35 #include <unistd.h>
36 #include <sys/types.h>
37 #include <zlib.h>
38 #include <fnmatch.h>
39
40 /*
41 We assume the syslog files belong to one host, i.e. we do not
42 consider the hostname at all
43 */
44
45 /*
46 REGEX: we do not use posix regular expressions (libc implementation
47 is too slow). fnmatch() is also slow.
48 Future versions may use our own implementation of a DFA. But currently we
49 just use strstr (no regex support at all)
50 */
51
52 //#define STRMATCH(pattern, string) (fnmatch (pattern, string, FNM_CASEFOLD) == 0)
53 #define STRMATCH(pattern, string) (strcasestr (string, pattern) != NULL)
54
55
56 //#define EPOOL_BLOCK_SIZE 512
57 //#define EPOOL_MAX_SIZE 128
58 #define EPOOL_BLOCK_SIZE 2048
59 #define EPOOL_MAX_SIZE 128
60 #define MAX_LOGFILES 31
61 //#define EPOOL_DEBUG
62 //#define DEBUG
63
64 typedef struct _SList SList;
65 struct _SList {
66 gpointer data;
67 SList *next;
68 };
69
70 typedef struct _NQList NQList;
71 struct _NQList {
72 char *from;
73 char *to;
74 char dstatus;
75 time_t ltime;
76 NQList *next;
77 };
78
79 typedef struct _TOList TOList;
80 struct _TOList {
81 char *to;
82 char *relay;
83 char dstatus;
84 time_t ltime;
85 TOList *next;
86 };
87
88 #ifdef DEBUG
89 GHashTable *smtpd_debug_alloc;
90 GHashTable *qmgr_debug_alloc;
91 GHashTable *filter_debug_alloc;
92 GHashTable *smtpd_debug_free;
93 GHashTable *qmgr_debug_free;
94 GHashTable *filter_debug_free;
95 #endif
96
97 // EPool: Entry related memory pools
98
99
100 #ifdef EPOOL_DEBUG
101 int ep_allocated;
102 int ep_maxalloc;
103 #endif
104
105 typedef struct _EPool EPool;
106 struct _EPool {
107 SList *blocks; // allocated usiing g_slice_alloc(EPOOL_BLOCK_SIZE)
108 SList *mblocks; // allocated use g_malloc
109 int cpos;
110 #ifdef EPOOL_DEBUG
111 int allocated;
112 #endif
113 };
114
115 typedef struct {
116 EPool ep;
117 GHashTable *smtpd_h;
118 GHashTable *qmgr_h;
119 GHashTable *filter_h;
120 //GHashTable *track_h;
121 gzFile stream[MAX_LOGFILES];
122 char *from;
123 char *to;
124 time_t year[MAX_LOGFILES];
125 time_t start;
126 time_t end;
127 time_t ctime;
128 char *server;
129 char *msgid;
130 char *qid;
131 char *strmatch;
132 unsigned long limit;
133 unsigned long count;
134 int verbose;
135 unsigned int exclude_greylist:1;
136 unsigned int exclude_ndrs:1;
137 } LParser;
138
139 typedef struct _SEntry SEntry;
140 typedef struct _QEntry QEntry;
141 typedef struct _FEntry FEntry;
142
143 typedef struct _LogEntry LogEntry;
144 typedef struct _LogList LogList;
145
146 struct _LogEntry {
147 const char *text;
148 LogEntry *next;
149 };
150
151 struct _LogList {
152 LogEntry *log;
153 LogEntry *logs_last; // pointer to last log (speedup add log)
154 };
155
156 // SEntry: Store SMTPD related logs
157
158 struct _SEntry {
159
160 EPool ep;
161 LogList loglist;
162
163 int pid;
164
165 SList *refs;
166
167 NQList *nqlist;
168
169 char *connect;
170
171 //unsigned int external:1; // not from local host
172 unsigned int disconnect:1;
173 unsigned int strmatch:1;
174
175 };
176
177 // QEntry: Store Queue (qmgr, smtp, lmtp) related logs
178
179 struct _QEntry {
180
181 EPool ep;
182 LogList loglist;
183
184 char *qid;
185
186 SEntry *smtpd;
187 FEntry *filter;
188
189 TOList *tolist;
190
191 unsigned int size;
192 char *from;
193 char *client;
194 char *msgid;
195
196 unsigned int cleanup:1;
197 unsigned int removed:1;
198 unsigned int filtered:1; // set when passed via lmtp to filter
199 unsigned int strmatch:1;
200 };
201
202 // FEntry: Store filter (proxprox) related logs
203
204 struct _FEntry {
205
206 EPool ep;
207 LogList loglist;
208
209 char *logid; // proxprox log id
210
211 TOList *tolist;
212
213 float ptime;
214
215 unsigned int finished:1;
216 unsigned int strmatch:1;
217 };
218
219 // Prototypes
220 void debug_error (char *msg, const char *line);
221 SList *slist_remove (SList *list, gpointer data);
222
223 EPool *epool_init (EPool *ep);
224 void epool_free (EPool *ep);
225 gpointer epool_alloc (EPool *ep, int size);
226 char *epool_strndup (EPool *ep, const char *s, int len);
227 char *epool_strndup0 (EPool *ep, const char *s, int len);
228 char *epool_strdup (EPool *ep, const char *s);
229
230 void loglist_print (LogList *loglist);
231 void loglist_add (EPool *ep, LogList *loglist, const char *text, int len);
232
233 SEntry *sentry_new (int pid);
234 SEntry *sentry_get (LParser *parser, int pid);
235 void sentry_ref_add (SEntry *sentry, QEntry *qentry);
236 int sentry_ref_del (SEntry *sentry, QEntry *qentry);
237 void sentry_ref_finalize (LParser *parser, SEntry *sentry);
238 int sentry_ref_rem_unneeded (LParser *parser, SEntry *sentry);
239 void sentry_nqlist_add (SEntry *sentry, time_t ltime, const char *from, int from_len,
240 const char *to, int to_len, char dstatus);
241 void sentry_print (LParser *parser, SEntry *sentry);
242 void sentry_set_connect (SEntry *sentry, const char *connect, int len);
243 void sentry_free_noremove (SEntry *sentry);
244 void sentry_free (LParser *parser, SEntry *sentry);
245 void sentry_cleanup_hash (gpointer key, gpointer value, gpointer user_data);
246
247
248 QEntry *qentry_new (const char *qid);
249 QEntry *qentry_get (LParser *parser, const char *qid);
250 void qentry_tolist_add (QEntry *qentry, time_t ltime, char dstatus, const char *to,
251 int to_len, const char *relay, int relay_len);
252 void qentry_set_from (QEntry *qentry, const char *from, int len);
253 void qentry_set_msgid (QEntry *qentry, const char *msgid, int len);
254 void qentry_set_client (QEntry *qentry, const char *client, int len);
255 void qentry_print (LParser *parser, QEntry *qentry);
256 void qentry_finalize (LParser *parser, QEntry *qentry);
257 void qentry_free_noremove (LParser *parser, QEntry *qentry);
258 void qentry_free (LParser *parser, QEntry *qentry);
259 void qentry_cleanup_hash (gpointer key, gpointer value, gpointer user_data);
260
261
262 FEntry *fentry_new (const char *logid);
263 FEntry *fentry_get (LParser *parser, const char *logid);
264 void fentry_tolist_add (FEntry *fentry, char dstatus, const char *to,
265 int to_len, const char *qid, int qid_len);
266 void fentry_free_noremove (LParser *parser, FEntry *fentry);
267 void fentry_free (LParser *parser, FEntry *fentry);
268 void fentry_cleanup_hash (gpointer key, gpointer value, gpointer user_data);
269
270 LParser *parser_new ();
271 void parser_free (LParser *parser);
272
273 //char *parser_track (LParser *parser, const char *qid, gboolean insert);
274
275 // Implementations
276
277 //#define LOGPATH "./log/"
278 #define LOGPATH "/var/log/"
279 //#define LOGPATH "/var/log5/"
280
281 static const char *logfiles[] = {
282 LOGPATH "syslog",
283 LOGPATH "syslog.1",
284 LOGPATH "syslog.2.gz",
285 LOGPATH "syslog.3.gz",
286 LOGPATH "syslog.4.gz",
287 LOGPATH "syslog.5.gz",
288 LOGPATH "syslog.6.gz",
289 LOGPATH "syslog.7.gz",
290 LOGPATH "syslog.8.gz",
291 LOGPATH "syslog.9.gz",
292 LOGPATH "syslog.10.gz",
293 LOGPATH "syslog.11.gz",
294 LOGPATH "syslog.12.gz",
295 LOGPATH "syslog.13.gz",
296 LOGPATH "syslog.14.gz",
297 LOGPATH "syslog.15.gz",
298 LOGPATH "syslog.16.gz",
299 LOGPATH "syslog.17.gz",
300 LOGPATH "syslog.18.gz",
301 LOGPATH "syslog.19.gz",
302 LOGPATH "syslog.20.gz",
303 LOGPATH "syslog.21.gz",
304 LOGPATH "syslog.22.gz",
305 LOGPATH "syslog.23.gz",
306 LOGPATH "syslog.24.gz",
307 LOGPATH "syslog.25.gz",
308 LOGPATH "syslog.26.gz",
309 LOGPATH "syslog.27.gz",
310 LOGPATH "syslog.28.gz",
311 LOGPATH "syslog.29.gz",
312 LOGPATH "syslog.30.gz",
313 LOGPATH "syslog.31.gz",
314 };
315
316 void
317 debug_error (char *msg, const char *line)
318 {
319 #ifdef DEBUG
320 fprintf (stderr, "ERROR: %s\n", msg);
321 if (line) fprintf (stderr, "LINE: %s\n", line);
322
323 G_BREAKPOINT();
324
325 exit (-1);
326 #endif
327 }
328
329 SList *
330 slist_remove (SList *list, gpointer data)
331 {
332 SList *p = NULL;
333 SList *l = list;
334
335 while (l) {
336 if (l->data == data) {
337 if (p)
338 p->next = l->next;
339 if (list == l)
340 list = list->next;
341
342 l->next = NULL;
343
344 break;
345 }
346
347 p = l;
348 l = l->next;
349 }
350
351 return list;
352 }
353
354 EPool *
355 epool_init (EPool *ep)
356 {
357 gpointer data;
358 SList *blocks;
359
360 data = g_slice_alloc0(EPOOL_BLOCK_SIZE);
361 blocks = (SList *)data;
362
363 #ifdef EPOOL_DEBUG
364 ep->allocated += EPOOL_BLOCK_SIZE;
365 ep_allocated += EPOOL_BLOCK_SIZE;
366 ep_maxalloc = (ep_allocated > ep_maxalloc) ? ep_allocated : ep_maxalloc;
367 #endif
368
369
370 blocks->data = data;
371 blocks->next = NULL;
372
373 ep->blocks = blocks;
374 ep->cpos = sizeof (SList);
375
376 return ep;
377 }
378
379 void
380 epool_free (EPool *ep)
381 {
382 SList *l;
383 gpointer data;
384
385 #ifdef EPOOL_DEBUG
386 ep_allocated -= ep->allocated;
387 printf ("MEM: %d\n", ep_allocated);
388 #endif
389
390 #ifdef DEBUG
391 return;
392 #endif
393
394 l = ep->mblocks;
395 while (l) {
396 data = l->data;
397 l = l->next;
398 g_free (data);
399 }
400
401 l = ep->blocks;
402 while (l) {
403 data = l->data;
404 l = l->next;
405 g_slice_free1(EPOOL_BLOCK_SIZE, data);
406 }
407 }
408
409 gpointer
410 epool_alloc (EPool *ep, int size)
411 {
412 int rs = (size + 3) & ~3;
413 int space = EPOOL_BLOCK_SIZE - sizeof (SList) - ep->cpos;
414 gpointer data;
415
416 if (size > EPOOL_MAX_SIZE) {
417 SList *blocks;
418 if (space >= sizeof (SList)) {
419 blocks = ep->blocks->data + ep->cpos;
420 ep->cpos += sizeof (SList);
421 } else {
422 blocks = (SList *)epool_alloc (ep, sizeof (SList));
423 }
424
425 data = g_malloc (size);
426 #ifdef EPOOL_DEBUG
427 ep->allocated += size;
428 ep_allocated += size;
429 ep_maxalloc = (ep_allocated > ep_maxalloc) ? ep_allocated : ep_maxalloc;
430 #endif
431 blocks->data = data;
432 blocks->next = ep->mblocks;
433
434 ep->mblocks = blocks;
435
436 return data;
437
438 } else if (space >= rs) {
439 data = ep->blocks->data + ep->cpos;
440 ep->cpos += rs;
441
442 return data;
443
444 } else {
445 SList *blocks = ep->blocks->data + ep->cpos;
446
447 data = g_slice_alloc0 (EPOOL_BLOCK_SIZE);
448 blocks->data = data;
449 blocks->next = ep->blocks;
450
451 ep->blocks = blocks;
452 ep->cpos = rs;
453
454 #ifdef EPOOL_DEBUG
455 ep->allocated += EPOOL_BLOCK_SIZE;
456 ep_allocated += EPOOL_BLOCK_SIZE;
457 ep_maxalloc = (ep_allocated > ep_maxalloc) ? ep_allocated : ep_maxalloc;
458 #endif
459
460 return data;
461 }
462 }
463
464 char *
465 epool_strndup (EPool *ep, const char *s, int len)
466 {
467 int l = len + 1;
468 char *res = epool_alloc (ep, l);
469 strncpy (res, s, l);
470 return res;
471 }
472
473 char *
474 epool_strndup0 (EPool *ep, const char *s, int len)
475 {
476 char *res = epool_alloc (ep, len + 1);
477 strncpy (res, s, len);
478 res[len] = 0;
479
480 return res;
481 }
482
483 char *
484 epool_strdup (EPool *ep, const char *s)
485 {
486 int l = strlen (s) + 1;
487 char *res = epool_alloc (ep, l);
488 strncpy (res, s, l);
489 return res;
490 }
491
492 void
493 loglist_print (LogList *loglist)
494 {
495 LogEntry *log = loglist->log;
496 while (log) {
497 printf ("%s", log->text);
498 log = log->next;
499 }
500 }
501
502
503 void
504 loglist_add (EPool *ep, LogList *loglist, const char *text, int len)
505 {
506 LogEntry *log;
507
508 #ifdef DEBUG
509 if (len != strlen (text)) {
510 debug_error ("string with wrong len", NULL);
511 }
512 #endif
513 if (text[len] != 0) {
514 debug_error ("string is not null terminated", NULL);
515 return;
516 }
517
518 log = epool_alloc (ep, sizeof (LogEntry));
519
520 log->text = epool_strndup (ep, text, len);
521 log->next = NULL;
522
523 if (loglist->logs_last) {
524 loglist->logs_last->next = log;
525 loglist->logs_last = log;
526 } else {
527 loglist->log = log;
528 loglist->logs_last = log;
529 }
530
531 return;
532 }
533
534 SEntry*
535 sentry_new (int pid)
536 {
537 SEntry *sentry;
538 SList *blocks;
539 int cpos;
540
541 sentry = (SEntry *)g_slice_alloc0(EPOOL_BLOCK_SIZE);
542 sentry->pid = pid;
543
544 #ifdef EPOOL_DEBUG
545 sentry->ep.allocated += EPOOL_BLOCK_SIZE;
546 ep_allocated += EPOOL_BLOCK_SIZE;
547 ep_maxalloc = (ep_allocated > ep_maxalloc) ? ep_allocated : ep_maxalloc;
548 #endif
549
550 #ifdef DEBUG
551 {
552 SEntry *se;
553 if ((se = g_hash_table_lookup (smtpd_debug_alloc, sentry))) {
554 debug_error ("SEntry already alloced", NULL);
555 } else {
556 g_hash_table_insert (smtpd_debug_alloc, sentry, sentry);
557 }
558 }
559 #endif
560
561 cpos = sizeof (SEntry);
562
563 blocks = (SList *)((char *)sentry + cpos);
564
565 cpos += sizeof (SList);
566
567 blocks->data = sentry;
568 blocks->next = NULL;
569
570 sentry->ep.blocks = blocks;
571 sentry->ep.cpos = cpos;
572
573 return sentry;
574 }
575
576 SEntry *
577 sentry_get (LParser *parser, int pid)
578 {
579 SEntry *sentry;
580
581 if ((sentry = g_hash_table_lookup (parser->smtpd_h, &pid))) {
582 return sentry;
583 } else {
584
585 if ((sentry = sentry_new (pid))) {
586 g_hash_table_insert (parser->smtpd_h, &sentry->pid, sentry);
587 }
588
589 return sentry;
590 }
591 }
592
593 void
594 sentry_ref_add (SEntry *sentry, QEntry *qentry)
595 {
596 SList *l;
597
598 if (qentry->smtpd) {
599 if (qentry->smtpd != sentry) {
600 debug_error ("qentry ref already set", NULL);
601 }
602 return;
603 }
604
605 l = sentry->refs;
606 while (l) {
607 if (l->data == qentry) return;
608 l = l->next;
609 }
610
611 l = epool_alloc (&sentry->ep, sizeof (SList));
612
613 qentry->smtpd = sentry;
614
615 l->data = qentry;
616 l->next = sentry->refs;
617
618 sentry->refs = l;
619 }
620
621 int
622 sentry_ref_del (SEntry *sentry, QEntry *qentry)
623 {
624 SList *l = sentry->refs;
625 int count = 0;
626
627 if (!qentry->smtpd) {
628 debug_error ("qentry does not hav a qentry ref", NULL);
629 return 0;
630 }
631
632 l = sentry->refs;
633
634 while (l) {
635 QEntry *qe = (QEntry *)l->data;
636 if (qe == qentry) {
637 l->data = NULL;
638 } else {
639 if (qe && qe->cleanup) count++;
640 }
641 l = l->next;
642 }
643
644 return count;
645 }
646
647 void
648 sentry_ref_finalize (LParser *parser, SEntry *sentry)
649 {
650 SList *l = sentry->refs;
651
652 int count = 0;
653
654 while (l) {
655 SList *cl = l;
656 QEntry *qe = l->data;
657
658 l = l->next;
659
660 if (!qe) continue;
661
662 count++;
663
664 if (!qe->removed) continue;
665
666 FEntry *fe = qe->filter;
667
668 if (fe && !fe->finished) continue;
669
670 count--;
671
672 qentry_print (parser, qe);
673
674 cl->data = NULL;
675 qe->smtpd = NULL;
676
677 qentry_free (parser, qe);
678
679 if (fe) fentry_free (parser, fe);
680
681 }
682
683 if (!count) sentry_free_noremove (sentry);
684 }
685
686 int
687 sentry_ref_rem_unneeded (LParser *parser, SEntry *sentry)
688 {
689 SList *l = sentry->refs;
690 int count = 0;
691
692 while (l) {
693 QEntry *qe = (QEntry *)l->data;
694
695 if (qe) {
696 if (!qe->cleanup) {
697 qe->smtpd = NULL;
698 qentry_free (parser, qe);
699 l->data = NULL;
700 } else {
701 count++;
702 }
703 }
704
705 l = l->next;
706 }
707
708 return count;
709 }
710
711 void
712 sentry_nqlist_add (SEntry *sentry, time_t ltime, const char *from, int from_len,
713 const char *to, int to_len, char dstatus)
714 {
715 NQList *nq = (NQList *)epool_alloc (&sentry->ep, sizeof (NQList));
716
717 nq->from = epool_strndup0 (&sentry->ep, from, from_len);
718 nq->to = epool_strndup0 (&sentry->ep, to, to_len);
719 nq->dstatus = dstatus;
720
721 nq->next = sentry->nqlist;
722 nq->ltime = ltime;
723 sentry->nqlist = nq;
724 }
725
726 void
727 sentry_print (LParser *parser, SEntry *sentry)
728 {
729 NQList *nq;
730
731 if (parser->msgid || parser->qid) return;
732
733 if (parser->server) {
734 if (!sentry->connect) return;
735 if (!strcasestr (sentry->connect, parser->server)) return;
736 }
737
738 if (parser->from || parser->to ||
739 parser->exclude_greylist || parser->exclude_ndrs) {
740 nq = sentry->nqlist;
741 int found = 0;
742 while (nq) {
743 if (parser->from) {
744 if (!*(parser->from)) {
745 if (*(nq->from)) nq->dstatus = 0;
746 } else if (!STRMATCH(parser->from, nq->from)) {
747 nq->dstatus = 0;
748 }
749 }
750
751 if (parser->exclude_greylist && nq->dstatus == 'G') nq->dstatus = 0;
752
753 if (parser->exclude_ndrs && nq->from && !*nq->from) nq->dstatus = 0;
754
755 if (parser->to && nq->to && !STRMATCH(parser->to, nq->to)) {
756 nq->dstatus = 0;
757 }
758
759 if (nq->dstatus != 0) found = 1;
760
761 nq = nq->next;
762 }
763 if (!found) return;
764 }
765
766 if (parser->strmatch && !sentry->strmatch) return;
767
768 if (parser->verbose) {
769
770 printf ("SMTPD:\n");
771
772 printf ("CTIME: %08lX\n", parser->ctime);
773
774 if (sentry->connect) { printf ("CONNECT: %s\n", sentry->connect); }
775 //printf ("EXTERNAL: %d\n", sentry->external);
776
777 }
778
779 nq = sentry->nqlist;
780 while (nq) {
781 if (nq->from && nq->to && nq->dstatus) {
782 printf ("TO:%08lX:00000000000:%c: from <%s> to <%s>\n", nq->ltime, nq->dstatus, nq->from, nq->to);
783 parser->count++;
784 }
785 nq = nq->next;
786 }
787
788 if (parser->verbose) {
789 printf ("LOGS:\n");
790 loglist_print (&sentry->loglist);
791 printf ("\n");
792 }
793
794 fflush (stdout);
795 }
796
797 void
798 sentry_set_connect (SEntry *sentry, const char *connect, int len)
799 {
800 if (sentry->connect) {
801 #ifdef DEBUG
802 if (strncmp (sentry->connect, connect, len)) {
803 debug_error ("duplicate connect", NULL);
804 }
805 #endif
806 } else {
807 sentry->connect = epool_strndup0 (&sentry->ep, connect, len);
808 }
809 }
810
811 void
812 sentry_free_noremove (SEntry *sentry)
813 {
814 SList *l;
815 gpointer data;
816
817 #ifdef EPOOL_DEBUG
818 ep_allocated -= sentry->ep.allocated;
819 printf ("MEM: %d\n", ep_allocated);
820 #endif
821
822 #ifdef DEBUG
823 {
824 SEntry *se;
825 if ((se = g_hash_table_lookup (smtpd_debug_free, sentry))) {
826 debug_error ("SEntry already freed", NULL);
827 } else {
828 g_hash_table_insert (smtpd_debug_free, sentry, sentry);
829 }
830 }
831 return;
832 #endif
833
834 l = sentry->ep.mblocks;
835 while (l) {
836 data = l->data;
837 l = l->next;
838 g_free (data);
839 }
840
841 l = sentry->ep.blocks;
842 while (l) {
843 data = l->data;
844 l = l->next;
845 g_slice_free1(EPOOL_BLOCK_SIZE, data);
846 }
847 }
848
849 void
850 sentry_free (LParser *parser, SEntry *sentry)
851 {
852 g_hash_table_remove (parser->smtpd_h, &sentry->pid);
853
854 sentry_free_noremove (sentry);
855 }
856
857 void
858 sentry_cleanup_hash (gpointer key,
859 gpointer value,
860 gpointer user_data)
861 {
862 SEntry *se = value;
863 LParser *parser = (LParser *)user_data;
864
865 sentry_print (parser, se);
866 sentry_free_noremove (se);
867 }
868
869 #ifdef DEBUG
870 void
871 sentry_debug_alloc (gpointer key,
872 gpointer value,
873 gpointer user_data)
874 {
875 LParser *parser = (LParser *)user_data;
876 SEntry *se = value;
877 SEntry *fe;
878
879 if ((fe = g_hash_table_lookup (smtpd_debug_free, se))) {
880 return;
881 }
882
883 printf ("FOUND ALLOCATED SENTRY:\n");
884 sentry_print (parser, se);
885
886 exit (-1);
887 }
888 #endif
889
890 // QEntry
891
892 QEntry*
893 qentry_new (const char *qid)
894 {
895 QEntry *qentry;
896 SList *blocks;
897 int cpos;
898 char *qid_cp;
899
900 qentry = (QEntry *)g_slice_alloc0(EPOOL_BLOCK_SIZE);
901
902 #ifdef EPOOL_DEBUG
903 qentry->ep.allocated += EPOOL_BLOCK_SIZE;
904 ep_allocated += EPOOL_BLOCK_SIZE;
905 ep_maxalloc = (ep_allocated > ep_maxalloc) ? ep_allocated : ep_maxalloc;
906 #endif
907
908 #ifdef DEBUG
909 {
910 QEntry *qe;
911 if ((qe = g_hash_table_lookup (qmgr_debug_alloc, qentry))) {
912 debug_error ("QEntry already alloced", NULL);
913 } else {
914 g_hash_table_insert (qmgr_debug_alloc, qentry, qentry);
915 }
916 }
917 #endif
918
919 cpos = sizeof (QEntry);
920
921 blocks = (SList *)((char *)qentry + cpos);
922
923 cpos += sizeof (SList);
924
925 blocks->data = qentry;
926 blocks->next = NULL;
927
928 qentry->qid = qid_cp = (char *)qentry + cpos;
929 while ((*qid_cp++ = *qid++)) cpos++;
930
931 cpos = (cpos + 4) & ~3;
932
933 qentry->ep.blocks = blocks;
934 qentry->ep.cpos = cpos;;
935
936 return qentry;
937 }
938
939 void
940 qentry_tolist_add (QEntry *qentry, time_t ltime, char dstatus, const char *to, int to_len,
941 const char *relay, int relay_len)
942 {
943 TOList *tl = (TOList *)epool_alloc (&qentry->ep, sizeof (TOList));
944
945 tl->to = epool_strndup0 (&qentry->ep, to, to_len);
946 tl->relay = epool_strndup0 (&qentry->ep, relay, relay_len);
947 tl->dstatus = dstatus;
948 tl->ltime = ltime;
949 tl->next = qentry->tolist;
950
951 qentry->tolist = tl;
952 }
953
954 void
955 qentry_set_from (QEntry *qentry, const char *from, int len)
956 {
957 if (qentry->from) {
958 #ifdef DEBUG
959 if (strncmp (qentry->from, from, len)) {
960 debug_error ("duplicate from", NULL);
961 }
962 #endif
963 } else {
964 qentry->from = epool_strndup0 (&qentry->ep, from, len);
965 }
966 }
967
968 void
969 qentry_set_msgid (QEntry *qentry, const char *msgid, int len)
970 {
971 if (qentry->msgid) {
972 #ifdef DEBUG
973 if (strncmp (qentry->msgid, msgid, len)) {
974 debug_error ("duplicate msgid", NULL);
975 }
976 #endif
977 } else {
978 qentry->msgid = epool_strndup0 (&qentry->ep, msgid, len);
979 }
980 }
981
982 void
983 qentry_set_client (QEntry *qentry, const char *client, int len)
984 {
985 if (qentry->client) {
986 #ifdef DEBUG
987 if (strncmp (qentry->client, client, len)) {
988 debug_error ("duplicate client", NULL);
989 }
990 #endif
991 } else {
992 qentry->client = epool_strndup0 (&qentry->ep, client, len);
993 }
994 }
995
996 void
997 qentry_print (LParser *parser, QEntry *qentry)
998 {
999 TOList *tl, *fl;
1000 SEntry *se = qentry->smtpd;
1001 FEntry *fe = qentry->filter;
1002
1003 if (parser->msgid) {
1004 if (!qentry->msgid) return;
1005 if (strcasecmp (parser->msgid, qentry->msgid)) return;
1006 }
1007
1008 if (parser->qid) {
1009 int found = 0;
1010 if (fe && !strcmp (fe->logid, parser->qid)) found = 1;
1011 if (!strcmp (qentry->qid, parser->qid)) found = 1;
1012
1013 if (!found) return;
1014 }
1015
1016 if (parser->server) {
1017 int found = 0;
1018 if (se && se->connect && strcasestr (se->connect, parser->server)) found = 1;
1019 if (qentry->client && strcasestr (qentry->client, parser->server)) found = 1;
1020
1021 if (!found) return;
1022 }
1023
1024 if (parser->from) {
1025 if (!qentry->from) return;
1026 if (!*(parser->from)) {
1027 if (*(qentry->from)) return;
1028 } else if (!STRMATCH(parser->from, qentry->from)) {
1029 return;
1030 }
1031 } else {
1032 if (parser->exclude_ndrs && qentry->from && !*qentry->from) return;
1033 }
1034
1035 if (parser->to) {
1036 tl = qentry->tolist;
1037 int found = 0;
1038 while (tl) {
1039 if (parser->to && !STRMATCH(parser->to, tl->to)) {
1040 tl->to = NULL;
1041 } else {
1042 found = 1;
1043 }
1044 tl = tl->next;
1045 }
1046 if (!found) return;
1047 }
1048
1049 if (parser->strmatch &&
1050 !(qentry->strmatch || (se && se->strmatch) || (fe && fe->strmatch)))
1051 return;
1052
1053
1054 if (parser->verbose) {
1055
1056 printf ("QENTRY: %s\n", qentry->qid);
1057
1058 printf ("CTIME: %08lX\n", parser->ctime);
1059 printf ("SIZE: %u\n", qentry->size);
1060
1061 if (qentry->client) { printf ("CLIENT: %s\n", qentry->client); }
1062
1063 if (qentry->msgid) { printf ("MSGID: %s\n", qentry->msgid); }
1064
1065 }
1066
1067 tl = qentry->tolist;
1068 while (tl) {
1069 if (tl->to) {
1070 fl = NULL;
1071 if (fe && tl->dstatus == '2') {
1072 fl = fe->tolist;
1073 while (fl) {
1074 if (fl->to && !strcmp (tl->to, fl->to)) {
1075 break;
1076 }
1077 fl = fl->next;
1078 }
1079 }
1080 char *to;
1081 char dstatus;
1082 char *relay;
1083
1084 if (fl) {
1085 to = fl->to;
1086 dstatus = fl->dstatus;
1087 relay = fl->relay;
1088 } else {
1089 to = tl->to;
1090 dstatus = tl->dstatus;
1091 relay = tl->relay;
1092 }
1093
1094 printf ("TO:%08lX:%s:%c: from <%s> to <%s> (%s)\n", tl->ltime, qentry->qid, dstatus, qentry->from ? qentry->from : "", to ? to : "", relay ? relay : "none");
1095
1096 parser->count++;
1097 }
1098 tl = tl->next;
1099 }
1100
1101
1102 if (!parser->verbose) { fflush (stdout); return; }
1103
1104 if (se) {
1105
1106 if (se->loglist.log) {
1107 printf ("SMTP:\n");
1108 loglist_print (&se->loglist);
1109 }
1110 }
1111
1112 if (fe) {
1113 if (fe->loglist.log) {
1114 printf ("FILTER: %s\n", fe->logid);
1115 loglist_print (&fe->loglist);
1116 }
1117 }
1118
1119 if (qentry->loglist.log) {
1120 printf ("QMGR:\n");
1121 loglist_print (&qentry->loglist);
1122 }
1123
1124 printf ("\n");
1125
1126 fflush (stdout);
1127
1128 //sleep (1);
1129 }
1130
1131 QEntry *
1132 qentry_get (LParser *parser, const char *qid)
1133 {
1134 QEntry *qentry;
1135
1136 if ((qentry = g_hash_table_lookup (parser->qmgr_h, qid))) {
1137 return qentry;
1138 } else {
1139 if ((qentry = qentry_new (qid))) {
1140 g_hash_table_insert (parser->qmgr_h, qentry->qid, qentry);
1141 }
1142
1143 return qentry;
1144 }
1145 }
1146
1147 void
1148 qentry_free_noremove (LParser *parser, QEntry *qentry)
1149 {
1150 SList *l;
1151 gpointer data;
1152 SEntry *se;
1153
1154 if ((se = qentry->smtpd)) {
1155 if (sentry_ref_del (se, qentry) == 0) {
1156 if (se->disconnect) {
1157 sentry_free_noremove (se);
1158 }
1159 }
1160 }
1161
1162 #ifdef EPOOL_DEBUG
1163 ep_allocated -= qentry->ep.allocated;
1164 printf ("MEM: %d\n", ep_allocated);
1165 #endif
1166
1167 #ifdef DEBUG
1168 {
1169 QEntry *qe;
1170 if ((qe = g_hash_table_lookup (qmgr_debug_free, qentry))) {
1171 debug_error ("QEntry already freed", NULL);
1172 } else {
1173 g_hash_table_insert (qmgr_debug_free, qentry, qentry);
1174 }
1175 }
1176 return;
1177 #endif
1178
1179 l = qentry->ep.mblocks;
1180 while (l) {
1181 data = l->data;
1182 l = l->next;
1183 g_free (data);
1184 }
1185
1186 l = qentry->ep.blocks;
1187 while (l) {
1188 data = l->data;
1189 l = l->next;
1190 g_slice_free1(EPOOL_BLOCK_SIZE, data);
1191 }
1192 }
1193
1194 void
1195 qentry_free (LParser *parser, QEntry *qentry)
1196 {
1197 g_hash_table_remove (parser->qmgr_h, qentry->qid);
1198
1199 qentry_free_noremove (parser, qentry);
1200 }
1201
1202 void
1203 qentry_cleanup_hash (gpointer key,
1204 gpointer value,
1205 gpointer user_data)
1206 {
1207 QEntry *qe = value;
1208 LParser *parser = (LParser *)user_data;
1209
1210 qentry_print (parser, qe);
1211 qentry_free_noremove (parser, qe);
1212 }
1213
1214 void
1215 qentry_finalize (LParser *parser, QEntry *qentry)
1216 {
1217 if (qentry && qentry->removed) {
1218 SEntry *se = qentry->smtpd;
1219
1220 if (se && !se->disconnect) return;
1221
1222 FEntry *fe = qentry->filter;
1223
1224 if (fe && !fe->finished) return;
1225
1226 qentry_print (parser, qentry);
1227 qentry_free (parser, qentry);
1228
1229 if (fe) fentry_free (parser, fe);
1230 }
1231 }
1232
1233 // FEntry
1234
1235 FEntry*
1236 fentry_new (const char *logid)
1237 {
1238 FEntry *fentry;
1239 SList *blocks;
1240 int cpos;
1241 char *logid_cp;
1242
1243 fentry = (FEntry *)g_slice_alloc0(EPOOL_BLOCK_SIZE);
1244
1245 #ifdef EPOOL_DEBUG
1246 fentry->ep.allocated += EPOOL_BLOCK_SIZE;
1247 ep_allocated += EPOOL_BLOCK_SIZE;
1248 ep_maxalloc = (ep_allocated > ep_maxalloc) ? ep_allocated : ep_maxalloc;
1249 #endif
1250
1251 #ifdef DEBUG
1252 {
1253 FEntry *fe;
1254 if ((fe = g_hash_table_lookup (filter_debug_alloc, fentry))) {
1255 debug_error ("FEntry already alloced", NULL);
1256 } else {
1257 g_hash_table_insert (filter_debug_alloc, fentry, fentry);
1258 }
1259 }
1260 #endif
1261
1262 cpos = sizeof (FEntry);
1263
1264 blocks = (SList *)((char *)fentry + cpos);
1265
1266 cpos += sizeof (SList);
1267
1268 blocks->data = fentry;
1269 blocks->next = NULL;
1270
1271 fentry->logid = logid_cp = (char *)fentry + cpos;
1272 while ((*logid_cp++ = *logid++)) cpos++;
1273 cpos = (cpos + 4) & ~3;
1274
1275 fentry->ep.blocks = blocks;
1276 fentry->ep.cpos = cpos;;
1277
1278 return fentry;
1279 }
1280
1281 FEntry *
1282 fentry_get (LParser *parser, const char *logid)
1283 {
1284 FEntry *fentry;
1285
1286 if ((fentry = g_hash_table_lookup (parser->filter_h, logid))) {
1287 return fentry;
1288 } else {
1289 if ((fentry = fentry_new (logid))) {
1290 g_hash_table_insert (parser->filter_h, fentry->logid, fentry);
1291 }
1292
1293 return fentry;
1294 }
1295 }
1296
1297 void
1298 fentry_tolist_add (FEntry *fentry, char dstatus, const char *to, int to_len,
1299 const char *qid, int qid_len)
1300 {
1301 TOList *tl = (TOList *)epool_alloc (&fentry->ep, sizeof (TOList));
1302
1303 tl->to = epool_strndup0 (&fentry->ep, to, to_len);
1304
1305 if (qid) {
1306 tl->relay = epool_strndup0 (&fentry->ep, qid, qid_len);
1307 } else {
1308 tl->relay = NULL;
1309 }
1310 tl->dstatus = dstatus;
1311 tl->next = fentry->tolist;
1312
1313 fentry->tolist = tl;
1314 }
1315
1316 void
1317 fentry_free_noremove (LParser *parser, FEntry *fentry)
1318 {
1319 SList *l;
1320 gpointer data;
1321
1322 #ifdef EPOOL_DEBUG
1323 ep_allocated -= fentry->ep.allocated;
1324 printf ("MEM: %d\n", ep_allocated);
1325 #endif
1326
1327 #ifdef DEBUG
1328 {
1329 FEntry *fe;
1330 if ((fe = g_hash_table_lookup (filter_debug_free, fentry))) {
1331 debug_error ("FEntry already freed", NULL);
1332 } else {
1333 g_hash_table_insert (filter_debug_free, fentry, fentry);
1334 }
1335 }
1336 return;
1337 #endif
1338
1339 l = fentry->ep.mblocks;
1340 while (l) {
1341 data = l->data;
1342 l = l->next;
1343 g_free (data);
1344 }
1345
1346 l = fentry->ep.blocks;
1347 while (l) {
1348 data = l->data;
1349 l = l->next;
1350 g_slice_free1(EPOOL_BLOCK_SIZE, data);
1351 }
1352 }
1353
1354 void
1355 fentry_free (LParser *parser, FEntry *fentry)
1356 {
1357 g_hash_table_remove (parser->filter_h, fentry->logid);
1358
1359 fentry_free_noremove (parser, fentry);
1360 }
1361
1362 void
1363 fentry_cleanup_hash (gpointer key,
1364 gpointer value,
1365 gpointer user_data)
1366 {
1367 FEntry *fe = value;
1368 LParser *parser = (LParser *)user_data;
1369
1370 fentry_free_noremove (parser, fe);
1371 }
1372
1373 // Parser
1374
1375 LParser*
1376 parser_new ()
1377 {
1378 LParser *parser = g_malloc0 (sizeof (LParser));
1379 struct timeval tv;
1380 struct tm *ltime;
1381 int i;
1382
1383 epool_init (&parser->ep);
1384
1385 if (!(parser->smtpd_h = g_hash_table_new (g_int_hash, g_int_equal))) {
1386 return NULL;
1387 }
1388
1389 if (!(parser->qmgr_h = g_hash_table_new (g_str_hash, g_str_equal))) {
1390 return NULL;
1391 }
1392
1393 if (!(parser->filter_h = g_hash_table_new (g_str_hash, g_str_equal))) {
1394 return NULL;
1395 }
1396
1397 //if (!(parser->track_h = g_hash_table_new (g_str_hash, g_str_equal))) {
1398 //return NULL;
1399 //}
1400
1401 for (i = 0; i < MAX_LOGFILES; i++) {
1402 gettimeofday(&tv, NULL);
1403 tv.tv_sec -= 3600*24*i;
1404 ltime = localtime (&tv.tv_sec);
1405 parser->year[i] = ltime->tm_year + 1900;
1406 }
1407
1408 return parser;
1409 }
1410
1411 void
1412 parser_free (LParser *parser)
1413 {
1414 int i;
1415
1416 for (i = 0; i < MAX_LOGFILES; i++) {
1417 if (parser->stream[i]) gzclose (parser->stream[i]);
1418 }
1419
1420 epool_free (&parser->ep);
1421
1422 g_hash_table_destroy (parser->smtpd_h);
1423 g_hash_table_destroy (parser->qmgr_h);
1424 g_hash_table_destroy (parser->filter_h);
1425 //g_hash_table_destroy (parser->track_h);
1426
1427 g_free (parser);
1428 }
1429
1430 #if 0
1431 char *
1432 parser_track (LParser *parser, const char *qid, gboolean insert)
1433 {
1434 char *res;
1435
1436 if ((res = g_hash_table_lookup (parser->track_h, qid))) {
1437 return res;
1438 } else {
1439 if (insert && (res = epool_strdup (&parser->ep, qid))) {
1440 g_hash_table_insert (parser->track_h, res, res);
1441 return res;
1442 }
1443 }
1444 return NULL;
1445 }
1446 #endif
1447
1448 static const int linebufsize = 8192;
1449 static int cur_year;
1450 static int cur_month = 0;
1451 static int cal_mtod[12] = {0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334};
1452
1453 static time_t
1454 mkgmtime (struct tm *tm)
1455 {
1456 time_t res;
1457
1458 int year = tm->tm_year + 1900;
1459 int mon = tm->tm_mon;
1460
1461 res = (year - 1970) * 365 + cal_mtod[mon];
1462
1463 // leap year corrections (gregorian calendar)
1464 if (mon <= 1) year -= 1;
1465 res += (year - 1968) / 4;
1466 res -= (year - 1900) / 100;
1467 res += (year - 1600) / 400;
1468
1469 res += tm->tm_mday - 1;
1470 res = res*24 + tm->tm_hour;
1471 res = res*60 + tm->tm_min;
1472 res = res*60 + tm->tm_sec;
1473
1474 return res;
1475 }
1476
1477 time_t
1478 parse_time (const char **text, int len)
1479 {
1480 time_t ltime = 0;
1481
1482 int year = cur_year;
1483
1484 int mon = 0;
1485 int mday = 0;
1486 int hour = 0;
1487 int min = 0;
1488 int sec = 0;
1489 int found;
1490
1491 const char *line = *text;
1492
1493 if (len == (linebufsize - 1)) {
1494 debug_error ("skipping long line data", line);
1495 return 0;
1496 }
1497
1498 if (len < 15) {
1499 debug_error ("skipping short line data", line);
1500 return 0;
1501 }
1502
1503 // parse month
1504 int csum = (line[0]<<16) + (line[1]<<8) + line[2];
1505
1506 switch (csum) {
1507 case 4874606: mon = 0; break;
1508 case 4613474: mon = 1; break;
1509 case 5071218: mon = 2; break;
1510 case 4288626: mon = 3; break;
1511 case 5071225: mon = 4; break;
1512 case 4879726: mon = 5; break;
1513 case 4879724: mon = 6; break;
1514 case 4289895: mon = 7; break;
1515 case 5465456: mon = 8; break;
1516 case 5202804: mon = 9; break;
1517 case 5140342: mon = 10; break;
1518 case 4482403: mon = 11; break;
1519 default:
1520 debug_error ("unable to parse month", line);
1521 return 0;
1522 }
1523
1524 // year change heuristik
1525 if (cur_month == 11 && mon == 0) {
1526 year++;
1527 }
1528 if (mon > cur_month) cur_month = mon;
1529
1530 ltime = (year - 1970)*365 + cal_mtod[mon];
1531
1532 // leap year corrections (gregorian calendar)
1533 if (mon <= 1) year -= 1;
1534 ltime += (year - 1968) / 4;
1535 ltime -= (year - 1900) / 100;
1536 ltime += (year - 1600) / 400;
1537
1538 const char *cpos = line + 3;
1539
1540 found = 0; while (isspace (*cpos)) { cpos++; found = 1; }
1541 if (!found) {
1542 debug_error ("missing spaces after month", line);
1543 return 0;
1544 }
1545
1546 found = 0; while (isdigit (*cpos)) { mday = mday*10 + *cpos - 48; cpos++; found++; }
1547 if (found < 1 || found > 2) {
1548 debug_error ("unable to parse day of month", line);
1549 return 0;
1550 }
1551
1552 ltime += mday - 1;
1553
1554 found = 0; while (isspace (*cpos)) { cpos++; found++; }
1555 if (!found) {
1556 debug_error ("missing spaces after day of month", line);
1557 return 0;
1558 }
1559
1560 found = 0; while (isdigit (*cpos)) { hour = hour*10 + *cpos - 48; cpos++; found++; }
1561 if (found < 1 || found > 2) {
1562 debug_error ("unable to parse hour", line);
1563 return 0;
1564 }
1565
1566 ltime *= 24;
1567 ltime += hour;
1568
1569 if (*cpos != ':') {
1570 debug_error ("missing collon after hour", line);
1571 return 0;
1572 }
1573 cpos++;
1574
1575 found = 0; while (isdigit (*cpos)) { min = min*10 + *cpos - 48; cpos++; found++; }
1576 if (found < 1 || found > 2) {
1577 debug_error ("unable to parse minute", line);
1578 return 0;
1579 }
1580
1581 ltime *= 60;
1582 ltime += min;
1583
1584 if (*cpos != ':') {
1585 debug_error ("missing collon after minute", line);
1586 return 0;
1587 }
1588 cpos++;
1589
1590 found = 0; while (isdigit (*cpos)) { sec = sec*10 + *cpos - 48; cpos++; found++; }
1591 if (found < 1 || found > 2) {
1592 debug_error ("unable to parse second", line);
1593 return 0;
1594 }
1595
1596 ltime *= 60;
1597 ltime += sec;
1598
1599 found = 0; while (isspace (*cpos)) { cpos++; found = 1; }
1600 if (!found) {
1601 debug_error ("missing spaces after time", line);
1602 return 0;
1603 }
1604
1605 *text = cpos;
1606
1607 return ltime;
1608 }
1609
1610
1611 int
1612 parser_count_files (LParser *parser)
1613 {
1614 int i;
1615 time_t start = parser->start;
1616 char linebuf[linebufsize];
1617 const char *line;
1618 gzFile stream;
1619
1620 for (i = 0; i < MAX_LOGFILES; i++) {
1621 cur_year = parser->year[i];
1622 cur_month = 0;
1623
1624 if ((stream = gzopen (logfiles[i], "r"))) {
1625 if ((line = gzgets (stream, linebuf, linebufsize))) {
1626 if (parse_time (&line, strlen (line)) < start) {
1627 break;
1628 }
1629 } else {
1630 return i;
1631 }
1632 gzclose (stream);
1633 } else {
1634 return i;
1635 }
1636 }
1637
1638 return i + 1;
1639 }
1640
1641 static char *
1642 parse_qid (const char **text, char *out, char delim, int maxlen)
1643 {
1644 const char *idx;
1645 char *copy = out;
1646
1647 int found = 0;
1648 idx = *text;
1649 while (isxdigit (*idx)) { *copy++ = *idx++; found++; if (found > maxlen) break; }
1650
1651 if (found > 1 && found < maxlen &&
1652 ((delim && (*idx == delim)) || (!delim && isspace (*idx)))) {
1653 *copy = 0;
1654 idx++;
1655 while (isspace (*idx)) idx++;
1656 *text = idx;
1657 return out;
1658 }
1659 return NULL;
1660 }
1661
1662 void
1663 print_usage (const char *name)
1664 {
1665 fprintf (stderr, "usage: %s [OPTIONS] [OUTPUTFILENAME]\n", name);
1666 fprintf (stderr, "\t-f SENDER mails from SENDER\n");
1667 fprintf (stderr, "\t-t RECIPIENT mails to RECIPIENT\n");
1668 fprintf (stderr, "\t-h Server Server IP or Hostname\n");
1669 fprintf (stderr, "\t-s START start time (YYYY-MM-DD HH:MM:SS)\n");
1670 fprintf (stderr, "\t or seconds since epoch\n");
1671 fprintf (stderr, "\t-e END end time (YYYY-MM-DD HH:MM:SS)\n");
1672 fprintf (stderr, "\t or seconds since epoch\n");
1673 fprintf (stderr, "\t-m MSGID message ID (exact match)\n");
1674 fprintf (stderr, "\t-q QID queue ID (exact match)\n");
1675 fprintf (stderr, "\t-x STRING search for strings\n");
1676 fprintf (stderr, "\t-l LIMIT print max limit entries\n");
1677 fprintf (stderr, "\t-v verbose output\n");
1678 }
1679
1680
1681 // gzgets is ways too slow, so we do it our own way
1682
1683 static int
1684 mygzgetc (gzFile stream)
1685 {
1686 int br;
1687 static char readbuf[16384];
1688 static char *readend = readbuf + sizeof (readbuf);
1689 static char *readpos = readbuf + sizeof (readbuf);
1690
1691 if (readpos < readend) return *readpos++;
1692
1693 if ((br = gzread (stream, readbuf, sizeof (readbuf))) <= 0) {
1694 return -1;
1695 } else {
1696 readpos = readbuf;
1697 readend = readbuf + br;
1698
1699 return *readpos++;
1700 }
1701 }
1702
1703 static char *
1704 mygzgets (gzFile stream, char *line, int bufsize)
1705 {
1706 int c=0;
1707 char *cpos;
1708
1709 cpos = line;
1710 while (--bufsize > 0 && (c = mygzgetc(stream)) != -1) {
1711 *cpos++ = c;
1712 if (c == '\n')
1713 break;
1714 }
1715 if (c == -1 && cpos == line)
1716 return NULL;
1717 *cpos++ = '\0';
1718 return line;
1719 }
1720
1721
1722 extern char *optarg;
1723 extern int optind, opterr, optopt;
1724
1725 int
1726 main (int argc, char * const argv[])
1727 {
1728 char linebuf[linebufsize];
1729 char *line;
1730 char *uniqueid = NULL;
1731
1732 const char *text;
1733 const char *idx1;
1734 const char *idx2;
1735 const char *cpos;
1736 int found = 0;
1737 int csum_prog;
1738 int lines = 0;
1739 char qidbuf[30];
1740 int i;
1741
1742 struct tm *ltime;
1743 struct timeval tv;
1744 time_t ctime, start, end;
1745
1746 LParser *parser;
1747 int opt;
1748
1749 #ifdef DEBUG
1750 smtpd_debug_alloc = g_hash_table_new (g_direct_hash, g_direct_equal);
1751 qmgr_debug_alloc = g_hash_table_new (g_direct_hash, g_direct_equal);
1752 filter_debug_alloc = g_hash_table_new (g_direct_hash, g_direct_equal);
1753 smtpd_debug_free = g_hash_table_new (g_direct_hash, g_direct_equal);
1754 qmgr_debug_free = g_hash_table_new (g_direct_hash, g_direct_equal);
1755 filter_debug_free = g_hash_table_new (g_direct_hash, g_direct_equal);
1756 #endif
1757
1758 if (!(parser = parser_new ())) {
1759 fprintf (stderr, "unable to alloc parser structure\n");
1760 exit (-1);
1761 }
1762
1763 while ((opt = getopt (argc, argv, "f:t:s:e:h:m:q:x:l:I:vgn")) != -1) {
1764 if (opt == 'f') {
1765 parser->from = epool_strdup (&parser->ep, optarg);
1766 } else if (opt == 't') {
1767 parser->to = epool_strdup (&parser->ep, optarg);
1768 } else if (opt == 'v') {
1769 parser->verbose = 1;
1770 } else if (opt == 'g') {
1771 parser->exclude_greylist = 1;
1772 } else if (opt == 'n') {
1773 parser->exclude_ndrs = 1;
1774 } else if (opt == 'I') {
1775 uniqueid = optarg;
1776 } else if (opt == 'h') {
1777 parser->server = epool_strdup (&parser->ep, optarg);
1778 } else if (opt == 'm') {
1779 parser->msgid = epool_strdup (&parser->ep, optarg);
1780 } else if (opt == 'q') {
1781 parser->qid = epool_strdup (&parser->ep, optarg);
1782 } else if (opt == 'x') {
1783 parser->strmatch = epool_strdup (&parser->ep, optarg);
1784 } else if (opt == 'l') {
1785 char *l;
1786 parser->limit = strtoul (optarg, &l, 0);
1787 if (!*optarg || *l) {
1788 fprintf (stderr, "unable to parse limit '%s'\n", optarg);
1789 exit (-1);
1790 }
1791 } else if (opt == 's') {
1792 // use strptime to convert time
1793 struct tm tm;
1794 char *res;
1795 if ((!(res = strptime (optarg, "%F %T", &tm)) &&
1796 !(res = strptime (optarg, "%s", &tm))) || *res) {
1797 fprintf (stderr, "unable to parse start time\n");
1798 exit (-1);
1799 } else {
1800 parser->start = mkgmtime (&tm);
1801 }
1802 } else if (opt == 'e') {
1803 struct tm tm;
1804 char *res;
1805 if ((!(res = strptime (optarg, "%F %T", &tm)) &&
1806 !(res = strptime (optarg, "%s", &tm))) || *res) {
1807 fprintf (stderr, "unable to parse end time\n");
1808 exit (-1);
1809 } else {
1810 parser->end = mkgmtime (&tm);
1811 }
1812 } else {
1813 print_usage (argv[0]);
1814 exit (-1);
1815 }
1816 }
1817
1818 if (optind < argc) {
1819
1820 if ((argc - optind) > 1) {
1821 print_usage (argv[0]);
1822 exit (-1);
1823 }
1824
1825 char *tmpfn = g_strdup_printf ("/tmp/.proxtrack-%08X.txt", getpid ());
1826
1827 if ((stdout = freopen (tmpfn, "w", stdout)) == NULL) {
1828 perror ("unable to open output file");
1829 exit (-1);
1830 }
1831 if (rename (tmpfn, argv[optind]) != 0) {
1832 perror ("unable to open output file");
1833 unlink (tmpfn);
1834 exit (-1);
1835 }
1836 }
1837
1838 // we use gmtime exerywhere to speedup things, this can cause problems
1839 // when daylight saving time changes
1840
1841 gettimeofday(&tv, NULL);
1842 ltime = localtime (&tv.tv_sec);
1843
1844 if (!parser->start) {
1845 ltime->tm_sec = 0;
1846 ltime->tm_min = 0;
1847 ltime->tm_hour = 0;
1848 parser->start = mkgmtime (ltime);
1849 }
1850
1851 ltime = localtime (&tv.tv_sec);
1852
1853 if (!parser->end) {
1854 parser->end = mkgmtime (ltime);
1855 }
1856
1857 if (parser->end < parser->start) {
1858 fprintf (stderr, "end time before start time\n");
1859 exit (-1);
1860 }
1861
1862 int filecount;
1863 if ((filecount = parser_count_files (parser)) <= 0) {
1864 fprintf (stderr, "unable to access log files\n");
1865 exit (-1);
1866 }
1867
1868 if (uniqueid) {
1869 printf ("# LogReader: %d %s\n", getpid(), uniqueid);
1870 } else {
1871 printf ("# LogReader: %d\n", getpid());
1872 }
1873
1874 printf ("# Query options\n");
1875 if (parser->from) printf ("# Sender: %s\n", parser->from);
1876 if (parser->to) printf ("# Recipient: %s\n", parser->to);
1877 if (parser->server) printf ("# Server: %s\n", parser->server);
1878 if (parser->msgid) printf ("# MsgID: %s\n", parser->msgid);
1879 if (parser->qid) printf ("# QID: %s\n", parser->qid);
1880 if (parser->strmatch) printf ("# Match: %s\n", parser->strmatch);
1881
1882 strftime (linebuf, 256, "%F %T", gmtime (&parser->start));
1883 printf ("# Start: %s (%lu)\n", linebuf, parser->start);
1884 strftime (linebuf, 256, "%F %T", gmtime (&parser->end));
1885 printf ("# END: %s (%lu)\n", linebuf, parser->end);
1886 printf ("# End Query Options\n\n");
1887
1888 fflush (stdout);
1889
1890 start = parser->start;
1891 end = parser->end;
1892 ctime = 0;
1893
1894 for (i = filecount - 1; i >= 0; i--) {
1895 gpointer stream;
1896
1897 // syslog files does not conain years, so we must compute then
1898 // cur_year is the assumed start year
1899
1900 cur_month = 0;
1901 cur_year = parser->year[i];
1902
1903 if (i <= 1) {
1904 if (!(stream = (gpointer) fopen (logfiles[i], "r"))) continue;
1905 } else {
1906 if (!(stream = (gpointer) gzopen (logfiles[i], "r"))) continue;
1907 }
1908
1909 while (1) {
1910
1911 if (parser->limit && (parser->count >= parser->limit)) {
1912 printf ("STATUS: aborted by limit (too many hits)\n");
1913 exit (0);
1914 }
1915
1916 if (i <= 1) {
1917 line = fgets (linebuf, linebufsize, (FILE *)stream);
1918 } else {
1919 line = mygzgets ((gzFile)stream, linebuf, linebufsize);
1920 }
1921
1922 if (!line) break;
1923
1924 int len = strlen (line);
1925 int pid = 0;
1926
1927 cpos = line;
1928 if (!(ctime = parse_time (&cpos, len))) {
1929 continue;
1930 }
1931
1932 if (ctime < start) continue;
1933 if (ctime > end) break;
1934
1935 lines++;
1936
1937 found = 0; while (!isspace (*cpos)) { cpos++; found = 1; }
1938 if (!found) {
1939 debug_error ("missing hostname", line);
1940 continue;
1941 }
1942
1943 found = 0; while (isspace (*cpos)) { cpos++; found = 1; }
1944 if (!found) {
1945 debug_error ("missing spaces after host", line);
1946 continue;
1947 }
1948
1949 if ((*cpos == 'l') && !strncmp (cpos, "last message repeated", 21)) {
1950 continue;
1951 }
1952
1953 //printf ("LINE: %s\n", line);
1954 //const char *prog = cpos;
1955
1956 csum_prog = 0;
1957 found = 0; while (*cpos && (*cpos != ':') && (*cpos != '[')) {
1958 csum_prog = (csum_prog <<8) + *cpos;
1959 cpos++;
1960 found++;
1961 }
1962
1963 //idx1 = g_strndup (prog, found);
1964 //printf ("TEST:%s:%08X\n", idx1, csum_prog);
1965 //free (idx1);
1966
1967 if (*cpos == '[') {
1968 cpos++;
1969 found = 0; while (isdigit (*cpos)) {
1970 pid = pid*10 + *cpos - 48;
1971 cpos++;
1972 found++;
1973 }
1974 if (found < 1 || found > 15 || *cpos != ']') {
1975 debug_error ("unable to parse pid", line);
1976 continue;
1977 }
1978 cpos++;
1979 }
1980
1981 if (*cpos++ != ':') {
1982 debug_error ("missing collon", line);
1983 continue;
1984 }
1985
1986
1987 if (!isspace (*cpos++)) {
1988 debug_error ("missing space after collon", line);
1989 continue;
1990 }
1991
1992 text = cpos;
1993
1994 parser->ctime = ctime;
1995
1996 int strmatch = 0;
1997
1998 if (parser->strmatch && STRMATCH(parser->strmatch, text)) {
1999 strmatch = 1;
2000 }
2001
2002 if (csum_prog == 0x6C746572) { // pmg-smtp-filter
2003
2004 if ((idx1 = parse_qid (&cpos, qidbuf, ':', 25))) {
2005
2006 FEntry *fe;
2007
2008 if (!(fe = fentry_get (parser, idx1))) {
2009 continue;
2010 }
2011
2012 loglist_add (&fe->ep, &fe->loglist, line, len);
2013
2014 if (strmatch) fe->strmatch = 1;
2015
2016 //fixme: BCC, Notify?
2017 //fixme: parse virus info
2018 //fixme: parse spam score
2019
2020 if ((*cpos == 'a') && !strncmp (cpos, "accept mail to <", 16)) {
2021
2022 const char *to_s, *to_e;
2023
2024 to_s = cpos = cpos + 16;
2025
2026 while (*cpos && (*cpos != '>')) { cpos++; }
2027
2028 if (*cpos != '>') continue;
2029
2030 to_e = cpos;
2031
2032 cpos++;
2033
2034 if ((*cpos++ != ' ') || (*cpos++ != '(')) continue;
2035
2036 if (!(idx1 = parse_qid (&cpos, qidbuf, ')', 15))) continue;
2037
2038 // parser_track (parser, idx1, 1);
2039
2040 fentry_tolist_add (fe, 'A', to_s, to_e - to_s, idx1, strlen (idx1));
2041
2042 } else if ((*cpos == 'm') && !strncmp (cpos, "moved mail for <", 16)) {
2043 const char *to_s, *to_e;
2044
2045 to_s = cpos = cpos + 16;
2046
2047 while (*cpos && (*cpos != '>')) { cpos++; }
2048
2049 to_e = cpos;
2050
2051 if (strncmp (cpos, "> to ", 5)) continue;
2052 cpos += 5;
2053
2054 if (!strncmp (cpos, "spam", 4)) {
2055 cpos += 4;
2056 } else if (!strncmp (cpos, "virus", 5)) {
2057 cpos += 5;
2058 } else {
2059 continue;
2060 }
2061
2062 if (strncmp (cpos, " quarantine - ", 14)) continue;
2063 cpos += 14;
2064
2065 if (!(idx1 = parse_qid (&cpos, qidbuf, 0, 25))) continue;
2066
2067 fentry_tolist_add (fe, 'Q', to_s, to_e - to_s, idx1, strlen (idx1));
2068
2069 } else if ((*cpos == 'b') && !strncmp (cpos, "block mail to <", 15)) {
2070
2071 const char *to_s;
2072
2073 to_s = cpos = cpos + 15;
2074
2075 while (*cpos && (*cpos != '>')) { cpos++; }
2076
2077 if (*cpos != '>') continue;
2078
2079 fentry_tolist_add (fe, 'B', to_s, cpos - to_s, NULL, 0);
2080
2081 } else if ((*cpos == 'p') && !strncmp (cpos, "processing time: ", 17)) {
2082 cpos += 17;
2083
2084 sscanf (cpos, "%f", &fe->ptime);
2085
2086 fe->finished = 1;
2087 }
2088
2089 }
2090
2091 } else if (csum_prog == 0x7265656E) { // postfix/postscreen
2092
2093 SEntry *se;
2094
2095 if (!pid) {
2096 debug_error ("no pid for postscreen", line);
2097 continue;
2098 }
2099
2100
2101 if ((*text == 'N') && !strncmp (text, "NOQUEUE: reject: RCPT from ", 27)) {
2102
2103 cpos = text + 27;
2104
2105 if (!(idx1 = strstr (cpos, "; client ["))) continue;
2106
2107 const char *client = cpos = idx1 + 10;
2108
2109 while (*cpos && (*cpos != ']')) { cpos++; }
2110
2111 const char *client_end = cpos;
2112
2113 if (!(idx1 = strstr (cpos, "; from=<"))) continue;
2114
2115 const char *from = cpos = idx1 + 8;
2116
2117 while (*cpos && (*cpos != '>')) { cpos++; }
2118 idx1 = cpos;
2119
2120 if ((*cpos == '>') && strncmp (cpos, ">, to=<", 7)) continue;
2121
2122 const char *to = cpos = cpos + 7;
2123
2124 while (*cpos && (*cpos != '>')) { cpos++; }
2125
2126 if (*cpos != '>') continue;
2127
2128 if (!(se = sentry_get (parser, pid))) {
2129 continue;
2130 }
2131
2132 if (strmatch) se->strmatch = 1;
2133
2134 loglist_add (&se->ep, &se->loglist, line, len);
2135
2136 sentry_nqlist_add (se, ctime, from, idx1 - from, to, cpos - to, 'N');
2137
2138 sentry_set_connect (se, client, client_end - client);
2139
2140 g_hash_table_remove (parser->smtpd_h, &se->pid);
2141
2142 se->disconnect = 1;
2143
2144 sentry_print (parser, se);
2145 sentry_free (parser, se);
2146 }
2147
2148 } else if (csum_prog == 0x716D6772) { // postfix/qmgr
2149
2150 if ((idx2 = text) && (idx1 = parse_qid (&idx2, qidbuf, ':', 15))) {
2151
2152 QEntry *qe;
2153
2154 if (!(qe = qentry_get (parser, idx1))) {
2155 continue;
2156 }
2157
2158 if (strmatch) qe->strmatch = 1;
2159
2160 qe->cleanup = 1;
2161
2162 loglist_add (&qe->ep, &qe->loglist, line, len);
2163
2164 if ((*idx2 == 'f') && !strncmp (idx2, "from=<", 6)) {
2165
2166 cpos = idx2 = idx2 + 6;
2167 while (*cpos && (*cpos != '>')) { cpos++; }
2168
2169 if (*cpos != '>') {
2170 debug_error ("unable to parse 'from' address", line);
2171 continue;
2172 }
2173
2174 qentry_set_from (qe, idx2, cpos - idx2);
2175
2176 cpos++;
2177
2178 if ((*cpos == ',') && !strncmp (cpos, ", size=", 7)) {
2179 int size = 0;
2180 cpos += 7;
2181
2182 while (isdigit (*cpos)) { size = size*10 + *cpos++ - 48; }
2183
2184 qe->size = size;
2185 }
2186
2187 } else if ((*idx2 == 'r') && !strncmp (idx2, "removed\n", 8)) {
2188
2189 qe->removed = 1;
2190
2191 qentry_finalize (parser, qe);
2192 }
2193 }
2194
2195 } else if ((csum_prog == 0x736D7470) || //postfix/smtp
2196 (csum_prog == 0x6C6D7470) || //postfix/lmtp
2197 (csum_prog == 0x72726F72)) { //postfix/error
2198
2199 int lmtp = (csum_prog == 0x6C6D7470);
2200
2201 if ((cpos = text) && (idx1 = parse_qid (&cpos, qidbuf, ':', 15))) {
2202
2203 QEntry *qe;
2204
2205 if (!(qe = qentry_get (parser, idx1))) {
2206 continue;
2207 }
2208
2209 if (strmatch) qe->strmatch = 1;
2210
2211 qe->cleanup = 1;
2212
2213 loglist_add (&qe->ep, &qe->loglist, line, len);
2214
2215 if (strncmp (cpos, "to=<", 4)) continue;
2216 cpos += 4;
2217
2218 const char *to_s, *to_e;
2219
2220 to_s = cpos;
2221
2222 while (*cpos && (*cpos != '>')) { cpos++; }
2223
2224 if (*cpos != '>') continue;
2225
2226 to_e = cpos;
2227
2228 cpos ++;
2229
2230 if (!(cpos = strstr (cpos, ", relay="))) continue;
2231 cpos += 8;
2232
2233 const char *relay_s, *relay_e;
2234
2235 relay_s = cpos;
2236
2237 while (*cpos && (*cpos != ',')) { cpos++; }
2238
2239 if (*cpos != ',') continue;
2240
2241 relay_e = cpos;
2242
2243 if (!(idx1 = strstr (cpos + 1, ", dsn="))) continue;
2244
2245 cpos = idx1 + 6;
2246
2247 if (!isdigit (*cpos)) continue;
2248
2249 char dstatus = *cpos;
2250
2251 qentry_tolist_add (qe, ctime, dstatus, to_s, to_e - to_s,
2252 relay_s, relay_e - relay_s);
2253
2254 if (!lmtp) continue; // filter always uses lmtp
2255
2256 if (!(idx1 = strstr (cpos + 1, "status=sent (250 2.")))
2257 continue;
2258
2259 cpos = idx1 = idx1 + 19;
2260
2261 if (*cpos == '5' && (idx1 = strstr (cpos, "5.0 OK ("))) {
2262 cpos = idx1 = idx1 + 8;
2263 } else if (*cpos == '7' && (idx1 = strstr (cpos, "7.0 BLOCKED ("))) {
2264 cpos = idx1 = idx1 + 13;
2265 } else {
2266 continue;
2267 }
2268
2269 if (!(idx1 = parse_qid (&cpos, qidbuf, ')', 25)))
2270 continue;
2271
2272 FEntry *fe;
2273
2274 qe->filtered = 1;
2275
2276 if ((fe = g_hash_table_lookup (parser->filter_h, idx1))) {
2277 qe->filter = fe;
2278 }
2279 }
2280
2281 } else if (csum_prog == 0x6D747064) { // postfix/smtpd
2282 SEntry *se;
2283
2284 if (!pid) {
2285 debug_error ("no pid for smtpd", line);
2286 continue;
2287 }
2288
2289 if (!(se = sentry_get (parser, pid))) {
2290 continue;
2291 }
2292
2293 if (strmatch) se->strmatch = 1;
2294
2295 loglist_add (&se->ep, &se->loglist, line, len);
2296
2297 if ((*text == 'c') && !strncmp (text, "connect from ", 13)) {
2298
2299 cpos = idx1 = text + 13;
2300
2301 while (*idx1 && !isspace (*idx1)) { idx1++; }
2302
2303 sentry_set_connect (se, cpos, idx1 - cpos);
2304
2305 // fixme: do we need this?
2306 //if (strcmp (se->connect, "localhost[127.0.0.1]")) {
2307 // se->external = 1;
2308 //}
2309
2310 } else if ((*text == 'd') && !strncmp (text, "disconnect from", 15)) {
2311
2312 // unlink
2313 g_hash_table_remove (parser->smtpd_h, &se->pid);
2314
2315 se->disconnect = 1;
2316
2317 if (sentry_ref_rem_unneeded (parser, se) == 0) {
2318 sentry_print (parser, se);
2319 sentry_free (parser, se);
2320 } else {
2321 sentry_ref_finalize (parser, se);
2322 }
2323
2324 } else if ((*text == 'N') && !strncmp (text, "NOQUEUE:", 8)) {
2325
2326 cpos = text + 8;
2327
2328 // parse 'whatsup' (reject:)
2329 while (*cpos && (*cpos != ':')) { cpos++; }
2330 if (*cpos != ':') continue;
2331 cpos++;
2332
2333 // parse '%s from %s:'
2334 while (*cpos && (*cpos != ':')) { cpos++; }
2335 if (*cpos != ':') continue;
2336 idx1 = cpos++;
2337
2338 // parse '%s;'
2339 while (*cpos && (*cpos != ';')) { cpos++; }
2340 if (*cpos != ';') continue;
2341
2342 // greylisting test
2343 char dstatus = 'N';
2344 *(char *)cpos = 0; // dangerous hack: delimit string
2345 if (strstr (idx1, ": Recipient address rejected: Service is unavailable (try later)")) {
2346 dstatus = 'G';
2347 }
2348 *(char *)cpos = ';'; // dangerous hack: restore line
2349
2350 if (!(idx1 = strstr (cpos, "; from=<"))) continue;
2351
2352 const char *from = cpos = idx1 + 8;
2353
2354 while (*cpos && (*cpos != '>')) { cpos++; }
2355 idx1 = cpos;
2356
2357 if ((*cpos == '>') && strncmp (cpos, "> to=<", 6)) continue;
2358
2359 const char *to = cpos = cpos + 6;
2360
2361 while (*cpos && (*cpos != '>')) { cpos++; }
2362
2363 if (*cpos != '>') continue;
2364
2365 sentry_nqlist_add (se, ctime, from, idx1 - from, to, cpos - to, dstatus);
2366
2367 } else if ((idx2 = text) && (idx1 = parse_qid (&idx2, qidbuf, ':', 15))) {
2368
2369 QEntry *qe;
2370
2371 if ((qe = qentry_get (parser, idx1))) {
2372
2373 if (strmatch) qe->strmatch = 1;
2374
2375 sentry_ref_add (se, qe);
2376
2377 if ((*idx2 == 'c') && !strncmp (idx2, "client=", 7)) {
2378 cpos = idx2 = idx2 + 7;
2379
2380 while (*cpos && !isspace (*cpos)) { cpos++; }
2381
2382 qentry_set_client (qe, idx2, cpos - idx2);
2383 }
2384 }
2385
2386 }
2387
2388 } else if (csum_prog == 0x616E7570) { // postfix/cleanup
2389
2390 QEntry *qe;
2391
2392 idx2 = text;
2393 if ((idx1 = parse_qid (&idx2, qidbuf, ':', 15))) {
2394 if ((qe = qentry_get (parser, idx1))) {
2395
2396 if (strmatch) qe->strmatch = 1;
2397
2398 loglist_add (&qe->ep, &qe->loglist, line, len);
2399
2400 if ((*idx2 == 'm') && !strncmp (idx2, "message-id=", 11)) {
2401
2402 cpos = idx2 = idx2 + 11;
2403
2404 while (*cpos && !isspace(*cpos)) { cpos++; }
2405
2406 qentry_set_msgid (qe, idx2, cpos - idx2);
2407
2408 qe->cleanup = 1;
2409 }
2410 }
2411 }
2412 }
2413 }
2414
2415 if (i <= 1) {
2416 fclose ((FILE *)stream);
2417 } else {
2418 gzclose ((gzFile)stream);
2419 }
2420
2421 if (ctime > end) break;
2422 }
2423
2424
2425 #ifdef DEBUG
2426 printf ("LINES: %d\n", lines);
2427 printf ("MEM SMTPD entries: %d\n", g_hash_table_size (parser->smtpd_h));
2428 printf ("MEM QMGR entries: %d\n", g_hash_table_size (parser->qmgr_h));
2429 printf ("MEM FILTER entries: %d\n", g_hash_table_size (parser->filter_h));
2430
2431 printf ("MEMDEB SMTPD entries: %d %d\n",
2432 g_hash_table_size (smtpd_debug_alloc),
2433 g_hash_table_size (smtpd_debug_free));
2434 printf ("MEMDEB QMGR entries: %d %d\n",
2435 g_hash_table_size (qmgr_debug_alloc),
2436 g_hash_table_size (qmgr_debug_free));
2437 printf ("MEMDEB FILTER entries: %d %d\n",
2438 g_hash_table_size (filter_debug_alloc),
2439 g_hash_table_size (filter_debug_free));
2440 #endif
2441
2442 g_hash_table_foreach (parser->qmgr_h, qentry_cleanup_hash, parser);
2443 g_hash_table_foreach (parser->smtpd_h, sentry_cleanup_hash, parser);
2444 g_hash_table_foreach (parser->filter_h, fentry_cleanup_hash, parser);
2445
2446 #ifdef DEBUG
2447 printf ("MEMDEB SMTPD entries: %d %d\n",
2448 g_hash_table_size (smtpd_debug_alloc),
2449 g_hash_table_size (smtpd_debug_free));
2450 printf ("MEMDEB QMGR entries: %d %d\n",
2451 g_hash_table_size (qmgr_debug_alloc),
2452 g_hash_table_size (qmgr_debug_free));
2453 printf ("MEMDEB FILTER entries: %d %d\n",
2454 g_hash_table_size (filter_debug_alloc),
2455 g_hash_table_size (filter_debug_free));
2456
2457 g_hash_table_foreach (smtpd_debug_alloc, sentry_debug_alloc, parser);
2458
2459 #endif
2460
2461
2462 #ifdef EPOOL_DEBUG
2463 printf ("MEMMAX %d\n", ep_maxalloc);
2464 #endif
2465
2466 parser_free (parser);
2467
2468 fclose (stdout);
2469
2470 exit (0);
2471 }