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