/* ** Modular Logfile Analyzer ** Copyright 2000 Jan Kneschke ** ** Homepage: http://www.modlogan.org ** This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version, and provided that the above copyright and permission notice is included with all distributed copies of this or derived software. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA ** ** $Id: parse.c,v 1.14 2003/07/18 11:38:10 ostborn Exp $ */ #include #include #include #include #include #include #include "mlocale.h" #include "mplugins.h" #include "mrecord.h" #include "mdatatypes.h" #include "misc.h" #include "plugin_config.h" #define DEBUG_PCRE typedef struct { int type; pcre *match; } Matches; typedef struct { int id; int qid; time_t tstamp_start; time_t tstamp_end; char *receipent; int status_dsn; /*< RFC1893 - Section 2 */ int status_smtp; /*< RFC2821 - Section 4.2.2*/ char *status_text; /*< full string */ } qrecp; typedef struct { int size; int maxsize; qrecp **recp; } qrecplist; typedef struct { int id; time_t tstamp_start; time_t tstamp_end; int size; char *sender; } qqueue; typedef struct { int size; int maxsize; qqueue **queue; } qqueuelist; static qqueuelist ql = {0, 0, NULL}; static qrecplist qr = {0, 0, NULL}; /** * create a delivery queue * * @param */ int create_queue(mconfig *ext_conf, const char * id, time_t tstamp) { int i; /* setup the array */ if (ql.maxsize == 0) { /* max queue size for qmail-queue */ ql.maxsize = 128; ql.queue = malloc(sizeof(qqueue *) * ql.maxsize); for (i = 0; i < ql.maxsize; i++) { ql.queue[i] = NULL; } } /* search for an empty slot */ for (i = 0; i < ql.maxsize; i++) { if (ql.queue[i] == NULL) { /* found */ ql.queue[i] = malloc(sizeof(qqueue)); ql.queue[i]->id = strtol(id, NULL, 10); ql.queue[i]->sender = NULL; ql.queue[i]->size = 0; ql.queue[i]->tstamp_start = tstamp; ql.queue[i]->tstamp_end = 0; /* fprintf(stderr, "%s.%d: opened queue '%d'\n", __FILE__, __LINE__, ql.queue[i]->id); */ ql.size++; break; } } if (i == ql.maxsize) { fprintf(stderr, "%s.%d: create_queue: ql is full - resizing to %d entries\n", __FILE__, __LINE__, ql.maxsize + 128); ql.maxsize += 128; ql.queue = realloc(ql.queue, sizeof(qqueue *) * ql.maxsize); for (i = ql.maxsize - 128; i < ql.maxsize; i++) { ql.queue[i] = NULL; } fprintf(stderr, "%s.%d: create_queue: ql.queue = %p\n", __FILE__, __LINE__, ql.queue); /* search for an empty slot */ for (i = 0; i < ql.maxsize; i++) { if (ql.queue[i] == NULL) { /* found */ ql.queue[i] = malloc(sizeof(qqueue)); ql.queue[i]->id = strtol(id, NULL, 10); ql.queue[i]->sender = NULL; ql.queue[i]->size = 0; ql.queue[i]->tstamp_start = tstamp; ql.queue[i]->tstamp_end = 0; ql.size++; break; } } if (i == ql.maxsize) { fprintf(stderr, "%s.%d: create_queue: ql is full\n", __FILE__, __LINE__); return -1; } } return 0; } int remove_queue(mconfig *ext_conf, const char * id) { int i; int nid = strtol(id, NULL, 10); for (i = 0; i < ql.maxsize; i++) { if (ql.queue[i] != NULL && ql.queue[i]->id == nid) { /* fprintf(stderr, "%s.%d: closing queue '%d'\n", __FILE__, __LINE__, ql.queue[i]->id); */ free(ql.queue[i]->sender); free(ql.queue[i]); ql.queue[i] = NULL; ql.size--; break; } } if (i == ql.maxsize) { fprintf(stderr, "%s.%d: remove_queue: id '%d' (%s) not found\n", __FILE__, __LINE__, nid, id); return -1; } return 0; } int set_sender_size(mconfig *ext_conf, const char * id, const char *sender, const char *size) { int i; int nid = strtol(id, NULL, 10); int nsize = strtol(size, NULL, 10); for (i = 0; i < ql.maxsize; i++) { if (ql.queue[i] != NULL && ql.queue[i]->id == nid) { /* fprintf(stderr, "%s.%d: queue '%d': setting sender '%s', size '%d'\n", __FILE__, __LINE__, ql.queue[i]->id, sender, nsize); */ ql.queue[i]->sender = malloc(strlen(sender) + 1); strcpy( ql.queue[i]->sender, sender); ql.queue[i]->size = nsize; break; } } if (i == ql.maxsize) { fprintf(stderr, "%s.%d: set_sender_size: queue id '%d' not found\n", __FILE__, __LINE__, nid); return -1; } return 0; } int create_delivery(mconfig *ext_conf, const char * id, const char *did, const char *receipent, time_t nts) { int j, i; int nid = strtol(id, NULL, 10); int ndid = strtol(did, NULL, 10); /* fprintf(stderr, "%s.%d: queue '%d': starting delivery to '%s', ['%d']\n", __FILE__, __LINE__, nid, receipent, ndid); */ if (qr.maxsize == 0) { /* max send queue send */ qr.maxsize = 128; qr.recp = malloc(sizeof(qrecp *) * qr.maxsize); for (j = 0; j < qr.maxsize; j++) { qr.recp[j] = NULL; } } for (j = 0; j < qr.maxsize; j++) { if (qr.recp[j] == NULL) { qr.recp[j] = malloc(sizeof(qrecp)); qr.recp[j]->receipent = malloc(strlen(receipent) + 1); strcpy( qr.recp[j]->receipent, receipent); qr.recp[j]->id = ndid; qr.recp[j]->qid = nid; qr.recp[j]->status_dsn = 0; qr.recp[j]->status_smtp = 0; qr.recp[j]->status_text = NULL; qr.recp[j]->tstamp_start = nts; qr.recp[j]->tstamp_end = 0; qr.size++; break; } } if (j == qr.maxsize) { fprintf(stderr, "%s.%d: create_delivery: qr is full\n", __FILE__, __LINE__); qr.maxsize += 128; qr.recp = realloc(qr.recp, sizeof(qrecp *) * qr.maxsize); for (i = ql.maxsize - 128; i < ql.maxsize; i++) { qr.recp[i] = NULL; } fprintf(stderr, "%s.%d: create_delivery: qr.recp = %p\n", __FILE__, __LINE__, qr.recp); /* search for an empty slot */ for (i = 0; i < qr.maxsize; i++) { if (qr.recp[j] == NULL) { qr.recp[j] = malloc(sizeof(qrecp)); qr.recp[j]->receipent = malloc(strlen(receipent) + 1); strcpy( qr.recp[j]->receipent, receipent); qr.recp[j]->id = ndid; qr.recp[j]->qid = nid; qr.recp[j]->status_dsn = 0; qr.recp[j]->status_smtp = 0; qr.recp[j]->status_text = NULL; qr.recp[j]->tstamp_start = nts; qr.recp[j]->tstamp_end = 0; qr.size++; break; } } if (i == qr.maxsize) { fprintf(stderr, "%s.%d: create_delivery: qr is full\n", __FILE__, __LINE__); return -1; } return -1; } return 0; } int set_delivery_status(mconfig *ext_conf, const char *did, const char *status, time_t nts, const char *status_text) { int j; int ndid = strtol(did, NULL, 10); #define N 20 + 1 const char **list; int ovector[3 * N], n; config_input *conf = ext_conf->plugin_conf; for (j = 0; j < qr.maxsize; j++) { if (qr.recp[j] != NULL && qr.recp[j]->id == ndid) { /* fprintf(stderr, "%s.%d: queue '%d': finished delivery ['%d']: status: %s\n", __FILE__, __LINE__, qr.recp[j]->qid, qr.recp[j]->id, status);*/ /* search for _250_ (status-smtp) */ if ((n = pcre_exec(conf->match_qmail_status_smtp, NULL, status_text, strlen(status_text), 0, 0, ovector, 3 * N)) < 0) { if (n != PCRE_ERROR_NOMATCH) { fprintf(stderr, "%s.%d: execution error while matching: %d\n", __FILE__, __LINE__, n); return M_RECORD_HARD_ERROR; } } else { pcre_get_substring_list(status_text, ovector, n, &list); qr.recp[j]->status_smtp = strtol(list[1], NULL, 10); pcre_free(list); } /* search for _2.0.0_ (status-dsn) */ if ((n = pcre_exec(conf->match_qmail_status_dsn, NULL, status_text, strlen(status_text), 0, 0, ovector, 3 * N)) < 0) { if (n != PCRE_ERROR_NOMATCH) { fprintf(stderr, "%s.%d: execution error while matching: %d\n", __FILE__, __LINE__, n); return M_RECORD_HARD_ERROR; } } else { char dsn[4]; pcre_get_substring_list(status_text, ovector, n, &list); dsn[0] = *list[1]; dsn[1] = *list[2]; dsn[2] = *list[3]; dsn[3] = '\0'; qr.recp[j]->status_dsn = strtol(list[1], NULL, 10); pcre_free(list); } qr.recp[j]->status_text = malloc(strlen(status_text) + 1); strcpy( qr.recp[j]->status_text, status_text); qr.recp[j]->tstamp_end = nts; break; } } if (j == qr.maxsize) { fprintf(stderr, "%s.%d: set_delivery_status: did not found\n", __FILE__, __LINE__); return -1; } return 0; #undef N } int remove_delivery(mconfig *ext_conf, const char *did) { int j; int ndid = strtol(did, NULL, 10); for (j = 0; j < qr.maxsize; j++) { if (qr.recp[j] != NULL && qr.recp[j]->id == ndid) { free(qr.recp[j]->status_text); free(qr.recp[j]->receipent); free(qr.recp[j]); qr.recp[j] = NULL; qr.size--; break; } } if (j == qr.maxsize) { fprintf(stderr, "%s.%d: remove_delivery: did not found\n", __FILE__, __LINE__); return -1; } return 0; } int set_outgoing_mail_record(mconfig *ext_conf, const char *did, mlogrec *rec) { int j, i; int ndid = strtol(did, NULL, 10); mlogrec_mail * recmail = rec->ext; /* check the recepient queue */ for (j = 0; j < qr.maxsize; j++) { /* search for destination ID */ if (qr.recp[j] != NULL && qr.recp[j]->id == ndid) { recmail->receipient = malloc(strlen(qr.recp[j]->receipent) + 1); strcpy(recmail->receipient, qr.recp[j]->receipent); recmail->duration = qr.recp[j]->tstamp_end - qr.recp[j]->tstamp_start; recmail->bytes_in = 0; recmail->status_text = malloc(strlen(qr.recp[j]->status_text) + 1); strcpy(recmail->status_text, qr.recp[j]->status_text); recmail->status_dsn = qr.recp[j]->status_dsn; recmail->status_smtp = qr.recp[j]->status_smtp; /* query sender queue */ for (i = 0; i < ql.maxsize; i++) { if (ql.queue[i] != NULL && ql.queue[i]->id == qr.recp[j]->qid) { recmail->sender = malloc(strlen(ql.queue[i]->sender) + 1); strcpy(recmail->sender, ql.queue[i]->sender); recmail->bytes_out = ql.queue[i]->size; break; } } break; } } if (j == qr.maxsize) { fprintf(stderr, "%s.%d: set_outgoing_mail_record: did not found\n", __FILE__, __LINE__); return -1; } return 0; } int set_incoming_mail_record(mconfig *ext_conf, const char *qid, mlogrec *rec) { int i; int nid = strtol(qid, NULL, 10); mlogrec_mail * recmail = rec->ext; for (i = 0; i < ql.maxsize; i++) { if (ql.queue[i] != NULL && ql.queue[i]->id == nid) { recmail->sender = malloc(strlen(ql.queue[i]->sender) + 1); strcpy(recmail->sender, ql.queue[i]->sender); recmail->bytes_in = ql.queue[i]->size; break; } } if (i == ql.maxsize) { fprintf(stderr, "%s.%d: set_incoming_mail_record: qid not found\n", __FILE__, __LINE__); return -1; } return 0; } /** * convert tai64(na) to time_t * * */ time_t parse_tai64n(const char *s) { time_t t = 0; int i; /* only the first 16 bytes are relevant for us * the first byte is skipped as we assume it to be a 4 */ if (s[0] != '4') return 0; for (i = 1; i < 16 && s[i]; i++) { int shift = ( 4 * ( 15 - i) ); t += hex2int(s[i]) << shift; } return t; } int parse_record_pcre(mconfig *ext_conf, mlogrec *record, buffer *b) { #define N 20 + 1 const char **list; int ovector[3 * N], n; int i, match; time_t timestamp = 0; int str_offset = 0; enum {M_QMAIL_NEW, M_QMAIL_START, M_QMAIL_END, M_QMAIL_DELIVERY, M_QMAIL_STATUS, M_QMAIL_INFO, M_QMAIL_BOUNCE, M_QMAIL_TRIPLE_BOUNCE}; config_input *conf = ext_conf->plugin_conf; const Matches matches [] = { { M_QMAIL_NEW, conf->match_qmail_new }, { M_QMAIL_START, conf->match_qmail_starting }, { M_QMAIL_END, conf->match_qmail_end }, { M_QMAIL_DELIVERY, conf->match_qmail_delivery }, { M_QMAIL_STATUS, conf->match_qmail_status }, { M_QMAIL_INFO, conf->match_qmail_info }, { M_QMAIL_BOUNCE, conf->match_qmail_bounce }, { M_QMAIL_TRIPLE_BOUNCE, conf->match_qmail_triple_bounce }, { 0, NULL } }; /* * We have to handle * * Syslog: * Mar 10 17:21:31 weigon qmail: 984241291.435179 status: local 0/10 remote 0/2 * * and * * Multilog: * @400000003d0fa0fa0a48aae4 status: local 1/10 remote 0/2 * */ /* try to match the syslog prefix */ if (*b->ptr == '@') { /* multilog, tai64n */ if ((n = pcre_exec(conf->match_qmail_tai64n_timestamp, NULL, b->ptr, b->used - 1, 0, 0, ovector, 3 * N)) < 0) { if (n == PCRE_ERROR_NOMATCH) { #if 0 fprintf(stderr, "%s.%d: tia64n doesn't match: %s\n", __FILE__, __LINE__, b->ptr); #endif return M_RECORD_IGNORED; } else { fprintf(stderr, "%s.%d: execution error while matching: %d\n", __FILE__, __LINE__, n); return M_RECORD_HARD_ERROR; } } pcre_get_substring_list(b->ptr, ovector, n, &list); timestamp = parse_tai64n(list[1]); str_offset = strlen(list[0]); pcre_free(list); } else { /* propably syslog */ if ((n = pcre_exec(conf->match_qmail_syslog_pre, NULL, b->ptr, b->used - 1, 0, 0, ovector, 3 * N)) < 0) { if (n == PCRE_ERROR_NOMATCH) { #if 0 fprintf(stderr, "%s.%d: syslog prefix doesn't match: %s\n", __FILE__, __LINE__, b->ptr); #endif return M_RECORD_IGNORED; } else { fprintf(stderr, "%s.%d: execution error while matching: %d\n", __FILE__, __LINE__, n); return M_RECORD_HARD_ERROR; } } pcre_get_substring_list(b->ptr, ovector, n, &list); str_offset = strlen(list[0]); pcre_free(list); /* external timestamp */ if ((n = pcre_exec(conf->match_qmail_external_syslog_timestamp, NULL, b->ptr + str_offset, strlen(b->ptr + str_offset), 0, 0, ovector, 3 * N)) < 0) { if (n == PCRE_ERROR_NOMATCH) { #if 0 fprintf(stderr, "%s.%d: tia64n doesn't match: %s\n", __FILE__, __LINE__, b->ptr); #endif return M_RECORD_IGNORED; } else { fprintf(stderr, "%s.%d: execution error while matching: %d\n", __FILE__, __LINE__, n); return M_RECORD_HARD_ERROR; } } pcre_get_substring_list(b->ptr + str_offset, ovector, n, &list); timestamp = strtol(list[1], NULL, 10); str_offset += strlen(list[0]); pcre_free(list); } /* the rest should be qmail specific */ if (n) { i = 0; match = -1; while ( matches[i].match != NULL ) { /* find the corresponding match */ if ((n = pcre_exec(matches[i].match, NULL, b->ptr + str_offset, strlen(b->ptr + str_offset), 0, 0, ovector, 3 * N)) < 0) { if (n == PCRE_ERROR_NOMATCH) { i++; continue; } else { fprintf(stderr, "%s.%d: execution error while matching: %d\n", __FILE__, __LINE__, n); return M_RECORD_HARD_ERROR; } } else { match = matches[i].type; break; } } if (n > 1 && match != -1) { pcre_get_substring_list(b->ptr + str_offset, ovector, n, &list); record->timestamp = timestamp; switch (match) { case M_QMAIL_STATUS: { /* * 0: status: local 0/10 remote 9/10 * 1: 0 - current * 2: 10 - max * 3: 9 - current * 4: 10 - max */ if (n == 5) { mlogrec_mail_qmail_status * qmstatus = mrecord_init_mail_qmail_status(); mlogrec_mail * recmail = mrecord_init_mail(); record->ext_type = M_RECORD_TYPE_MAIL; record->ext = recmail; recmail->ext_type = M_RECORD_TYPE_MAIL_QMAIL_STATUS; recmail->ext = qmstatus; qmstatus->local_cur = strtol(list[1], NULL, 10); qmstatus->local_max = strtol(list[2], NULL, 10); qmstatus->remote_cur = strtol(list[3], NULL, 10); qmstatus->remote_max = strtol(list[4], NULL, 10); qmstatus->queue_cur = ql.size; qmstatus->deliver_cur = qr.size; } else if (n == 3) { /* status: exiting */ } else { for (i = 0; i < n; i++) printf("%d: %s\n", i, list[i]); fprintf(stderr, "%s.%d: murks wurks: %d\n", __FILE__, __LINE__, n); } break; } case M_QMAIL_DELIVERY: /* * 0: delivery 170128: success: 193.98.110.1_accepted_message./Remote_host_said:_250_RAA06738_Message_accepted_for_delivery/ * 1: 170128 - deliver-id * 2: success - state * 3: 193.98.110.1_accepted_message./Remote_host_said:_250_RAA06738_Message_accepted_for_delivery * - response */ { mlogrec_mail * recmail = mrecord_init_mail(); /* clean delivery and return record */ if (0 != set_delivery_status(ext_conf, list[1], list[2], timestamp, list[3])) { free(list); return M_RECORD_HARD_ERROR; } record->ext_type = M_RECORD_TYPE_MAIL; record->ext = recmail; if (0 != set_outgoing_mail_record(ext_conf, list[1], record)) { free(list); return M_RECORD_HARD_ERROR; } if (0 != remove_delivery(ext_conf, list[1])) { free(list); return M_RECORD_HARD_ERROR; } } break; case M_QMAIL_START: /* * 0: starting delivery 170140: msg 4141 to remote Ronny.Moreas@mech.kuleuven.ac.be * 1: 170140 - deliver-id * 2: 4141 - queue-id * 3: remote - local / remote queue * 4: Ronny.More... * - destination * */ { /* - register delivery with id 'list[1]' at queue with id 'list[2]' * - set receipent to 'list[4]' */ if (0 != create_delivery(ext_conf, list[2], list[1], list[4], timestamp)) { free(list); return M_RECORD_HARD_ERROR; } } break; case M_QMAIL_INFO: /* * 0: info msg 4140: bytes 1344 from qp 4506 uid 901 * 1: 4140 - queue-id * 2: 1344 - size * 3: varin@aichinet.com * - sender * 4: 4506 - ?? * 5: 901 - userid (qmailq ?) */ { /* - set sender 'list[3]' * - set size 'list[2]' * on queue 'list[1]' */ if (0 == set_sender_size(ext_conf, list[1], list[3], list[2])) { mlogrec_mail *recmail = mrecord_init_mail(); record->ext_type = M_RECORD_TYPE_MAIL; record->ext = recmail; if (0 != set_incoming_mail_record(ext_conf, list[1], record)) { free(list); return M_RECORD_HARD_ERROR; } } else { /* how to handle it ? */ } } break; case M_QMAIL_NEW: /* * 0: new msg 4140 * 1: 4140 - queue-id */ { /* create queue entry with id 'list[1]' */ if (0 != create_queue(ext_conf, list[1], timestamp)) { free(list); return M_RECORD_HARD_ERROR; } } break; case M_QMAIL_END: /* * 0: end msg 4140 * 1: 4140 - queue-id */ { /* remove queue entry with id 'list[1]' */ if (0 != remove_queue(ext_conf, list[1])) { /* return M_RECORD_HARD_ERROR; */ } } break; case M_QMAIL_BOUNCE: /* * 0: bounce msg 4140 qp 3454 * 1: 4140 - queue-id * 2: 3454 - bounce-id */ { } break; case M_QMAIL_TRIPLE_BOUNCE: /* * 0: triple bounce: discarding bounce/143777 * 1: 143777 - queue-id */ { } break; default: #if 1 for (i = 0; i < n; i++) { printf("%d: %s\n", i, list[i]); } #endif } free(list); return record->ext ? M_RECORD_NO_ERROR : M_RECORD_IGNORED; } else { fprintf(stderr, "%s.%d: was das ?? '%s'\n", __FILE__, __LINE__, b->ptr + str_offset); return M_RECORD_CORRUPT; } } else { return M_RECORD_CORRUPT; } return M_RECORD_IGNORED; #undef N } int mplugins_input_qmail_get_next_record(mconfig *ext_conf, mlogrec *record) { int ret = 0; config_input *conf = ext_conf->plugin_conf; if (record == NULL) return M_RECORD_HARD_ERROR; /* fill the line buffer */ if (NULL == mgets(&(conf->inputfile), conf->buf)) { /* the last record, clean up qr und ql */ int j; for (j = 0; j < qr.maxsize; j++) { if (qr.recp[j] != NULL) { if (qr.recp[j]->status_text) free(qr.recp[j]->status_text); if (qr.recp[j]->receipent) free(qr.recp[j]->receipent); free(qr.recp[j]); } } if (qr.recp) free(qr.recp); for (j = 0; j < ql.maxsize; j++) { if (ql.queue[j] != NULL) { if (ql.queue[j]->sender) free(ql.queue[j]->sender); free(ql.queue[j]); } } if (ql.queue) free(ql.queue); return M_RECORD_EOF; } ret = parse_record_pcre (ext_conf, record, conf->buf); if (ret == M_RECORD_CORRUPT) { M_DEBUG1(ext_conf->debug_level, M_DEBUG_SECTION_PARSING, M_DEBUG_LEVEL_WARNINGS, "affected Record: %s\n", conf->buf->ptr ); } return ret; }