/* * Generate periodic summaries of shim-logged mail. * * Generates and outputs a summary of everything logged since last time * it was run (everything in the save.list file). */ #include #include #include #include #include #include #include #include #include extern const char *__progname; static const char *workdir = "/home/mouse/hacks/mailshim+"; static const char *listfile = "save/.list"; static const char *listtmp = "save/.list.TMP"; typedef struct stats STATS; typedef struct node NODE; typedef struct leaf LEAF; typedef struct buf BUF; typedef struct crlfmap_private CRLFMAP_PRIVATE; typedef struct rsp RSP; typedef struct ctkey CTKEY; struct ctkey { const char *key; char **valp; } ; struct rsp { const char *buf; int len; int inx; } ; struct crlfmap_private { FILE *f; int *errp; int err; } ; struct buf { char *b; int l; int a; } ; struct leaf { unsigned int addr; unsigned int count; } ; struct node { void *x[2]; } ; struct stats { unsigned int msgcount; unsigned int cantopen; unsigned int unxeof; unsigned int no_body; unsigned int why_badrcpt; unsigned int why_dnsl; unsigned int why_fromrej; unsigned int why_bodyrej; unsigned int why_other; } ; static FILE *listf; static STATS stats; static void *iproot; static unsigned int maxcount; static int countwidth; static int nleaves; static LEAF **leaves; static int leafx; static void setup(void) { if (chdir(workdir) < 0) { fprintf(stderr,"%s: chdir to %s: %s\n",__progname,workdir,strerror(errno)); exit(1); } } static void grab_list_file(void) { int fd; if (rename(listfile,listtmp) < 0) { if (errno == ENOENT) exit(0); fprintf(stderr,"%s: rename %s -> %s: %s\n",__progname,listfile,listtmp,strerror(errno)); exit(1); } fd = open(listtmp,O_RDONLY|O_EXLOCK,0); if (fd < 0) { fprintf(stderr,"%s: open/lock %s: %s\n",__progname,listtmp,strerror(errno)); exit(1); } unlink(listtmp); /* ignore errors */ listf = fdopen(fd,"r"); } static void stats_init(void) { stats.msgcount = 0; stats.cantopen = 0; stats.unxeof = 0; stats.no_body = 0; stats.why_badrcpt = 0; stats.why_dnsl = 0; stats.why_fromrej = 0; stats.why_bodyrej = 0; stats.why_other = 0; iproot = 0; maxcount = 0; nleaves = 0; } static void save_ip(unsigned int a) { void **np; NODE *n; LEAF *l; int bit; np = &iproot; for (bit=31;bit>=0;bit--) { n = *np; if (n == 0) { *np = n = malloc(sizeof(NODE)); n->x[0] = 0; n->x[1] = 0; } np = &n->x[(a>>bit)&1]; } l = *np; if (l == 0) { *np = l = malloc(sizeof(LEAF)); l->addr = a; l->count = 0; nleaves ++; } l->count ++; if (l->count > maxcount) maxcount = l->count; } static void walk_tree(void *p, unsigned int v, int bits) { if (p == 0) return; if (bits == 0) { leaves[leafx++] = p; return; } walk_tree(((NODE *)p)->x[0],v<<1,bits-1); walk_tree(((NODE *)p)->x[1],(v<<1)+1,bits-1); } static void sort_leaves(void) { int i; int m; LEAF *t; static void rh(int x, int n) { int r; int l; int s; LEAF *t; t = leaves[x]; while (1) { l = x + x + 1; r = l + 1; #define LT(a,bl) \ ( (leaves[a]->count < bl->count) || \ ( (leaves[a]->count == bl->count) && \ (leaves[a]->addr > bl->addr) ) ) if ((l < n) && LT(l,t)) { if ((r < n) && LT(r,t)) { s = LT(l,leaves[r]) ? l : r; } else { s = l; } } else { if ((r < n) && LT(r,t)) { s = r; } else { leaves[x] = t; return; } } leaves[x] = leaves[s]; x = s; #undef LT } } for (i=(nleaves-2)>>1;i>=0;i--) rh(i,nleaves); for (m=nleaves-1;m>0;m--) { t = leaves[m]; leaves[m] = leaves[0]; leaves[0] = t; rh(0,m); } } static void print_leaves(void) { int i; for (i=0;icount, leaves[i]->addr>>24, (leaves[i]->addr>>16)&0xff, (leaves[i]->addr>>8)&0xff, leaves[i]->addr&0xff ); } } static void dump_ips(void) { leaves = malloc(nleaves*sizeof(LEAF *)); leafx = 0; walk_tree(iproot,0,32); sort_leaves(); print_leaves(); } static void buf_init(BUF *b) { b->b = malloc(b->a=8); b->b[0] = '\0'; b->l = 0; } static void buf_empty(BUF *b) { b->l = 0; } static int buf_len(BUF *b) { return(b->l); } static int buf_char(BUF *b, int x) { if ((x < 0) || (x > b->l)) abort(); return((unsigned char)(b->b[x])); } static void buf_addc(BUF *b, int c) { if (b->l+1 >= b->a) b->b = realloc(b->b,b->a=b->l+32); b->b[b->l++] = c; b->b[b->l] = '\0'; } static void buf_unadd(BUF *b, int n) { if (b->l < n) abort(); b->b[b->l-=n] = '\0'; } static int buf_strneq(BUF *b, int off, const char *s, int len) { if ((off < 0) || (off > b->l)) abort(); return(!strncmp(b->b+off,s,len)); } static int buf_strncaseeq(BUF *b, int off, const char *s, int len) { if ((off < 0) || (off > b->l)) abort(); return(!strncasecmp(b->b+off,s,len)); } static int buf_streq(BUF *b, int off, const char *s) { if ((off < 0) || (off > b->l)) abort(); return(!strcmp(b->b+off,s)); } static char *buf_copypart(BUF *b, int off, int len) { char *s; if ((len < 0) || (off < 0) || (off > b->l) || (len > b->l-off)) abort(); s = malloc(len+1); bcopy(b->b+off,s,len); s[len] = '\0'; return(s); } static char *buf_ptr(BUF *b, int off) { if ((off < 0) || (off > b->l)) abort(); return(b->b+off); } static int crlfmap_r(void *pv, char *buf, int len) { CRLFMAP_PRIVATE *p; int c; int rv; p = pv; rv = 0; while (rv < len) { c = getc(p->f); switch (c) { case '\r': c = getc(p->f); if (c != '\n') { if (c != EOF) ungetc(c,p->f); { p->err = 1; /* CR not followed by LF */ c = '\r'; } } break; case '\n': p->err = 1; /* unadorned LF */ break; case EOF: return(rv); break; } *buf++ = c; rv ++; } return(rv); } static int crlfmap_w(void *pv, const char *buf, int len) { CRLFMAP_PRIVATE *p; int i; p = pv; for (i=0;if); putc(buf[i],p->f); } fflush(p->f); return(len); } static int crlfmap_c(void *pv) { CRLFMAP_PRIVATE *p; p = pv; if (p->errp) *p->errp = p->err; free(p); return(0); } static FILE *fopen_crlfmap(FILE *f, int *errp) { CRLFMAP_PRIVATE *p; p = malloc(sizeof(CRLFMAP_PRIVATE)); p->f = f; p->errp = errp; p->err = 0; return(funopen(p,&crlfmap_r,&crlfmap_w,0,&crlfmap_c)); } static int getline_simple(FILE *f, BUF *l) { int c; buf_empty(l); while (1) { c = getc(f); switch (c) { case '\n': buf_addc(l,'\n'); return(1); break; case EOF: return(0); break; } buf_addc(l,c); } } static int getline_folded(FILE *f, BUF *l) { int c; buf_empty(l); while (1) { c = getc(f); switch (c) { case '\n': c = getc(f); switch (c) { case ' ': case '\t': buf_addc(l,'\n'); break; default: ungetc(c,f); /* fall through */ case EOF: buf_addc(l,'\n'); return(1); break; } break; case EOF: return(0); break; } buf_addc(l,c); } } static int rs_read(void *pv, char *buf, int len) { RSP *p; p = pv; if (p->inx+len > p->len) len = p->len - p->inx; if (len > 0) bcopy(p->buf+p->inx,buf,len); p->inx += len; return(len); } static int rs_close(void *pv) { free(pv); return(0); } static FILE *fropenstr(const void *buf, int len) { RSP *p; FILE *f; p = malloc(sizeof(RSP)); if (p == 0) return(0); f = funopen(p,rs_read,0,0,rs_close); if (f == 0) { free(p); return(0); } p->buf = buf; p->len = (len < 0) ? strlen(buf) : len; p->inx = 0; return(f); } static char *get_1521_token(FILE *f) { int bq; int dq; int cl; int c; char *s; int a; int l; bq = 0; dq = 0; cl = 0; s = malloc(16); a = 15; l = 0; while (1) { c = getc(f); if (c == EOF) { if (l == 0) { free(s); return(0); } s[l] = '\0'; return(s); } if (cl) { if (bq) { bq = 0; } else { switch (c) { case '(': cl ++; break; case ')': cl --; break; case '\\': bq = 1; break; } } continue; } else if (bq) { bq = 0; } else if (dq) { switch (c) { case '"': s[l] = '\0'; return(s); break; case '\\': bq = 1; continue; break; /* By my reading of 1521, we should also ignore whitespace here, but that turns out to be the Wrong Thing in practice. Too many people put spaces in MIME boundary= strings and expect them to "work". case ' ': case '\t': case '\r': case '\n': continue; break; */ } } else { switch (c) { case 'a': case 'b': case 'c': case 'd': case 'e': case 'f': case 'g': case 'h': case 'i': case 'j': case 'k': case 'l': case 'm': case 'n': case 'o': case 'p': case 'q': case 'r': case 's': case 't': case 'u': case 'v': case 'w': case 'x': case 'y': case 'z': case '0': case '1': case '2': case '3': case '4': case '5': case '6': case '7': case '8': case '9': case '!': case '#': case '$': case '%': case '&': case '*': case '+': case '-': case '.': case '^': case '_': case '`': case '|': case '~': case '{': case '}': case '\'': case 'A': case 'B': case 'C': case 'D': case 'E': case 'F': case 'G': case 'H': case 'I': case 'J': case 'K': case 'L': case 'M': case 'N': case 'O': case 'P': case 'Q': case 'R': case 'S': case 'T': case 'U': case 'V': case 'W': case 'X': case 'Y': case 'Z': /* This list is based on tspecials in 1521, not the lists in 822 (which would, among other things, exclude . and include / and =, which is Not Right for our purposes.) */ break; case '"': if (l == 0) { dq = 1; continue; } else { ungetc(c,f); s[l] = '\0'; return(s); } break; case ' ': case '\t': case '\r': case '\n': if (l == 0) continue; s[l] = '\0'; return(s); break; case '(': if (l == 0) { cl = 1; continue; } else { ungetc(c,f); s[l] = '\0'; return(s); } break; default: if (l == 0) { s[0] = c; s[1] = '\0'; return(s); } else { ungetc(c,f); s[l] = '\0'; return(s); } break; } } if (l >= a) s = realloc(s,(a=l+15)+1); s[l++] = c; } } static int rfc1521_equivalent(const char *s1, const char *s2) { FILE *f1; FILE *f2; char *t1; char *t2; f1 = fropenstr(s1,-1); f2 = fropenstr(s2,-1); while (1) { t1 = get_1521_token(f1); t2 = get_1521_token(f2); if (!t1 && !t2) { fclose(f1); fclose(f2); return(1); } if (!t1 || !t2 || strcasecmp(t1,t2)) { fclose(f1); fclose(f2); free(t1); free(t2); return(0); } free(t1); free(t2); } } static void crack_content_type(const char *ct, int dofrees, char **tp, char **stp, const CTKEY *keys) { int i; FILE *f; char *t; char *type; char *subtype; if (dofrees) { if (tp) free(*tp); if (stp) free(*stp); if (keys) for (i=0;keys[i].key;i++) free(*keys[i].valp); } if (tp) *tp = 0; if (stp) *stp = 0; if (keys) for (i=0;keys[i].key;i++) *keys[i].valp = 0; f = fropenstr(ct,-1); type = get_1521_token(f); if (type) { t = get_1521_token(f); if (t) { if (!strcmp(t,"/")) { free(t); subtype = get_1521_token(f); if (subtype) { if (keys) { while (1) { char *pn; char *pv; t = get_1521_token(f); if (! t) break; if (strcmp(t,";")) { free(t); break; } free(t); pn = get_1521_token(f); if (! pn) break; t = get_1521_token(f); if (! t) { free(pn); break; } if (strcmp(t,"=")) { free(t); free(pn); break; } free(t); pv = get_1521_token(f); if (! pv) { free(pn); break; } for (i=0;keys[i].key;i++) { if (!strcasecmp(pn,keys[i].key)) { free(*keys[i].valp); *keys[i].valp = pv; break; } } if (! keys[i].key) free(pv); free(pn); } } if (stp) *stp = subtype; else free(subtype); } } else { free(t); } } if (tp) *tp = type; else free(type); } fclose(f); } static int recursive_content( FILE *rf, int (*part)( FILE *, int (*)(FILE *, void *, void *), const char * ), int (*err)(int) ) /* If (*err) returns nonzero, an error is thrown up and out, with recursive_content returning nonzero. If (*err) returns zero, the error is ignored, with recovery as specified for the error in question. */ /* Content-Transfer-Encoding sans encoding. Recovery: ignore the header. */ #define RCERR_EMPTY_CTE 1 /* Unknown Content-Transfer-Encoding value. Recovery: ignore the header. */ #define RCERR_UNK_CTE 2 /* Multipart body without boundary specifier. Recovery: ignore the part. */ #define RCERR_NO_BOUNDARY 3 /* Multipart body with non-nil encoding. Recovery: decode it anyway. */ #define RCERR_ENC_MULTIPART 4 /* Missing closing multipart delimiter. Recovery: pretend it was there. */ #define RCERR_UNCLOSED 5 { char hdr[1024]; char *type; char *boundary; char *t; int rv; int (*decodefn)(FILE *, void *, void *); char *encname; const CTKEY keys[] = { { "boundary", &boundary }, { 0 } }; type = 0; boundary = 0; decodefn = &decode_default; encname = strdup("default"); do <"free_ret_rv"> { do <"free_ret_err"> { while <"hdr"> (get_unfolded_header(rf,&hdr[0],sizeof(hdr),0)) { if (!strncasecmp(&hdr[0],"content-type:",13)) { crack_content_type(&hdr[13],1,&type,0,&keys[0]); } else if (!strncasecmp(&hdr[0],"content-transfer-encoding:",26)) { FILE *f; int (*newfn)(FILE *, void *, void *); f = fropenstr(&hdr[26],-1); t = get_1521_token(f); fclose(f); if (! t) { if ((*err)(RCERR_EMPTY_CTE)) break <"free_ret_err">; continue <"hdr">; } newfn = 0; pick_decode_fn(t,&newfn,0); free(encname); encname = t; if (! newfn) { if ((*err)(RCERR_UNK_CTE)) break <"free_ret_err">; continue <"hdr">; } decodefn = newfn; } } if (type && !strcasecmp(type,"multipart")) { int partno; if (! boundary) { if ((*err)(RCERR_NO_BOUNDARY)) break <"free_ret_err">; rv = 0; break <"free_ret_rv">; } else { if ( (decodefn != &decode_default) && (decodefn != &decode_7bit) && (*err)(RCERR_ENC_MULTIPART) ) break <"free_ret_err">; partno = 0; while (1) { switch (skip_to_boundary(rf,boundary)) { case BD_OK: { FILE *f; partno ++; f = fopen_wrap(rf,&boundbreak,boundary); setvbuf(f,0,_IONBF,0); rv = recursive_content(f,part,err); fclose(f); if (rv) break <"free_ret_rv">; } break; case BD_EOF: if ((*err)(RCERR_UNCLOSED)) break <"free_ret_err">; /* fall through */ case BD_EOM: rv = 0; break <"free_ret_rv">; } } } } else { rv = (*part)(rf,decodefn,encname); break <"free_ret_rv">; } } while (0); rv = 1; } while (0); free(type); free(boundary); free(encname); return(rv); } static void process_file(const char *fn) { __label__ closeret; FILE *f; FILE *g; long int hdrstart; int ll; int dqx; int i; char *slash; int nrecv; unsigned int havebits; #define HAVE_MIME_VERSION 0x00000001 #define HAVE_CONTENT_TYPE 0x00000002 char *ct_type; char *ct_subtype; struct in_addr a; BUF line; static void malformatted(const char *, ...) __attribute__((__format__(__printf__,1,2))); static void malformatted(const char *fmt, ...) { va_list ap; printf("%s: malformatted: ",fn); va_start(ap,fmt); vprintf(fmt,ap); va_end(ap); printf("\n"); goto closeret; } static stats.msgcount ++; g = 0; f = fopen(fn,"r"); if (f == 0) { stats.cantopen ++; return; } havebits = 0; ct_type = 0; ct_subtype = 0; buf_init(&line); /* Gobble the logged-message pseudo-headers. */ while (1) { if (! getline_simple(f,&line)) { stats.unxeof ++; goto closeret; } ll = buf_len(&line); if ((ll > 0) && (buf_char(&line,ll-1) == '\n')) { buf_unadd(&line,1); ll --; } else { malformatted("missing trailing newline"); } if (ll == 0) break; if (buf_strneq(&line,0,"reason: ",8)) { if (buf_streq(&line,8,"bad recipients")) { stats.why_badrcpt ++; } else if (buf_strneq(&line,8,"DNSL reject: ",13)) { stats.why_dnsl ++; } else if (buf_strneq(&line,8,"server rejected MAIL ",21)) { stats.why_fromrej ++; } else if (buf_strneq(&line,8,"server rejected body: ",22)) { stats.why_bodyrej ++; } else { printf("%s: %.*s\n",fn,ll,buf_ptr(&line,0)); stats.why_other ++; } } } hdrstart = ftell(f); g = fopen_crlfmap(f,0); nrecv = 0; /* Gobble the logged message's headers. */ while (1) { if (! getline_folded(g,&line)) { stats.no_body ++; goto closeret; } ll = buf_len(&line); if ((ll > 0) && (buf_char(&line,ll-1) == '\n')) { buf_unadd(&line,1); ll --; } else { malformatted("missing trailing newline"); } if (ll < 1) break; if (buf_strncaseeq(&line,0,"received:",9)) { if (nrecv == 0) { static char *s = 0; if (! buf_strneq(&line,9," from ",6)) malformatted("our Received: isn't"); dqx = 15; for (i=dqx;(i= ll) malformatted("no space after sender IP"); free(s); s = buf_copypart(&line,dqx,i-dqx); if (! inet_aton(s,&a)) malformatted("sender IP [%s] isn't",s); save_ip(ntohl(a.s_addr)); } nrecv ++; } else if (buf_strncaseeq(&line,0,"mime-version:",13)) { if (rfc1521_equivalent(buf_ptr(&line,13),"1.0")) { havebits |= HAVE_MIME_VERSION; } } else if (buf_strncaseeq(&line,0,"content-type:",13)) { if (havebits & HAVE_CONTENT_TYPE) malformatted("multiple Content-Type: headers"); havebits |= HAVE_CONTENT_TYPE; crack_content_type(buf_ptr(&line,13),1,&ct_type,&ct_subtype,0); } } if ( (havebits & (HAVE_MIME_VERSION|HAVE_CONTENT_TYPE)) == (HAVE_MIME_VERSION|HAVE_CONTENT_TYPE) ) { fclose(g); fseek(f,hdrstart,SEEK_SET); g = fopen_crlfmap(f,0); recursive_content(g,&part,&err); } closeret:; unlink(fn); while ((slash=rindex(fn,'/'))) { *slash = '\0'; if (rmdir(fn) < 0) break; } free(ct_type); free(ct_subtype); if (g) fclose(g); fclose(f); } static void scan_list(void) { int fnl; char fn[256]; while (fgets(&fn[0],sizeof(fn),listf)) { fnl = strlen(&fn[0]); if ((fnl > 0) && (fn[fnl-1] == '\n')) fn[--fnl] = '\0'; process_file(&fn[0]); } } static void print_report(void) { unsigned int c; countwidth = 1; c = maxcount; while (c >= 10) { countwidth ++; c /= 10; } if (countwidth < 5) countwidth = 5; /* 5 = strlen("Count"); */ printf("Total messages: %d\n",stats.msgcount); if (stats.cantopen) printf("Can't open message file: %d\n",stats.cantopen); if (stats.unxeof) printf("Unexpected EOF: %d\n",stats.unxeof); if (stats.no_body) printf("No body: %d\n",stats.unxeof); printf("Rejection reasons:\n"); printf("\tBad recipients: %d\n",stats.why_badrcpt); printf("\tDNSL reject: %d\n",stats.why_dnsl); printf("\tServer rejected MAIL: %d\n",stats.why_fromrej); printf("\tServer rejected body: %d\n",stats.why_bodyrej); if (stats.why_other) printf("\tOther: %d\n",stats.why_other); printf("Incoming peer IPs:\n%*s Address\n",countwidth,"Count"); dump_ips(); } int main(void); int main(void) { setup(); grab_list_file(); stats_init(); scan_list(); print_report(); exit(0); }