ref: bf645afaac246967b9590ae7463f243c11d97480
dir: /sys/src/cmd/upas/scanmail/common.c/
#include <u.h> #include <libc.h> #include <bio.h> #include <regexp.h> #include "spam.h" enum { Quanta = 8192, Minbody = 6000, HdrMax = 15, }; typedef struct keyword Keyword; typedef struct word Word; struct word{ char *string; int n; }; struct keyword{ char *string; int value; }; Word htmlcmds[] = { "html", 4, "!doctype html", 13, 0, }; Word hrefs[] = { "a href=", 7, "a title=", 8, "a target=", 9, "base href=", 10, "img src=", 8, "img border=", 11, "form action=", 12, "!--", 3, 0, }; /* * RFC822 header keywords to look for for fractured header. * all lengths must be less than HdrMax defined above. */ Word hdrwords[] = { "cc:", 3, "bcc:", 4, "to:", 3, 0, 0, }; Keyword keywords[] = { "header", HoldHeader, "line", SaveLine, "hold", Hold, "dump", Dump, "loff", Lineoff, 0, Nactions, }; Patterns patterns[] = { [Dump] { "DUMP:", 0, 0 }, [HoldHeader] { "HEADER:", 0, 0 }, [Hold] { "HOLD:", 0, 0 }, [SaveLine] { "LINE:", 0, 0 }, [Lineoff] { "LINEOFF:", 0, 0 }, [Nactions] { 0, 0, 0 }, }; static char* endofhdr(char*, char*); static int escape(char**); static int extract(char*); static int findkey(char*); static int hash(int); static int isword(Word*, char*, int); static void parsealt(Biobuf*, char*, Spat**); /* * The canonicalizer: convert input to canonical representation */ char* readmsg(Biobuf *bp, int *hsize, int *bufsize) { char *p, *buf; int n, offset, eoh, bsize, delta; buf = 0; offset = 0; if(bufsize) *bufsize = 0; if(hsize) *hsize = 0; for(;;) { buf = Realloc(buf, offset+Quanta+1); n = Bread(bp, buf+offset, Quanta); if(n < 0){ free(buf); return 0; } p = buf+offset; /* start of this chunk */ offset += n; /* end of this chunk */ buf[offset] = 0; if(n == 0){ if(offset == 0) return 0; break; } if(hsize == 0) /* don't process header */ break; if(p != buf && p[-1] == '\n') /* check for EOH across buffer split */ p--; p = endofhdr(p, buf+offset); if(p) break; if(offset >= Maxread) /* gargantuan header - just punt*/ { if(hsize) *hsize = offset; if(bufsize) *bufsize = offset; return buf; } } eoh = p-buf; /* End of header */ bsize = offset - eoh; /* amount of body already read */ /* Read at least Minbody bytes of the body */ if (bsize < Minbody){ delta = Minbody-bsize; buf = Realloc(buf, offset+delta+1); n = Bread(bp, buf+offset, delta); if(n > 0) { offset += n; buf[offset] = 0; } } if(hsize) *hsize = eoh; if(bufsize) *bufsize = offset; return buf; } static int isword(Word *wp, char *text, int len) { for(;wp->string; wp++) if(len >= wp->n && strncmp(text, wp->string, wp->n) == 0) return 1; return 0; } static char* endofhdr(char *raw, char *end) { int i; char *p, *q; char buf[HdrMax]; /* * can't use strchr to search for newlines because * there may be embedded NULL's. */ for(p = raw; p < end; p++){ if(*p != '\n' || p[1] != '\n') continue; p++; for(i = 0, q = p+1; i < sizeof(buf) && *q; q++){ buf[i++] = tolower(*q); if(*q == ':' || *q == '\n') break; } if(!isword(hdrwords, buf, i)) return p+1; } return 0; } static int htmlmatch(Word *wp, char *text, char *end, int *n) { char *cp; int i, c, lastc; char buf[MaxHtml]; /* * extract a string up to '>' */ i = lastc = 0; cp = text; while (cp < end && i < sizeof(buf)-1){ c = *cp++; if(c == '=') c = escape(&cp); switch(c){ case 0: case '\r': continue; case '>': goto out; case '\n': case ' ': case '\t': if(lastc == ' ') continue; c = ' '; break; default: c = tolower(c); break; } buf[i++] = lastc = c; } out: buf[i] = 0; if(n) *n = cp-text; return isword(wp, buf, i); } static int escape(char **msg) { int c; char *p; p = *msg; c = *p; if(c == '\n'){ p++; c = *p++; } else if(c == '2'){ c = tolower(p[1]); if(c == 'e'){ p += 2; c = '.'; }else if(c == 'f'){ p += 2; c = '/'; }else if(c == '0'){ p += 2; c = ' '; } else c = '='; } else { if(c == '3' && tolower(p[1]) == 'd') p += 2; c = '='; } *msg = p; return c; } static int htmlchk(char **msg, char *end) { int n; char *p; static int ishtml; p = *msg; if(ishtml == 0){ ishtml = htmlmatch(htmlcmds, p, end, &n); /* If not an HTML keyword, check if it's * an HTML comment (<!comment>). if so, * skip over it; otherwise copy it in. */ if(ishtml == 0 && *p != '!') /* not comment */ return '<'; /* copy it */ } else if(htmlmatch(hrefs, p, end, &n)) /* if special HTML string */ return '<'; /* copy it */ /* * this is an uninteresting HTML command; skip over it. */ p += n; *msg = p+1; return *p; } /* * decode a base 64 encode body */ void conv64(char *msg, char *end, char *buf, int bufsize) { int len, i; char *cp; len = end - msg; i = (len*3)/4+1; // room for max chars + null cp = Malloc(i); len = dec64((uchar*)cp, i, msg, len); convert(cp, cp+len, buf, bufsize, 1); free(cp); } int convert(char *msg, char *end, char *buf, int bufsize, int isbody) { char *p; int c, lastc, base64; lastc = 0; base64 = 0; while(msg < end && bufsize > 0){ c = *msg++; /* * In the body only, try to strip most HTML and * replace certain MIME escape sequences with the character */ if(isbody) { do{ p = msg; if(c == '<') c = htmlchk(&msg, end); if(c == '=') c = escape(&msg); } while(p != msg && p < end); } switch(c){ case 0: case '\r': continue; case '\t': case ' ': case '\n': if(lastc == ' ') continue; c = ' '; break; case 'C': /* check for MIME base 64 encoding in header */ case 'c': if(isbody == 0) if(msg < end-32 && *msg == 'o' && msg[1] == 'n') if(cistrncmp(msg+2, "tent-transfer-encoding: base64", 30) == 0) base64 = 1; c = 'c'; break; default: c = tolower(c); break; } *buf++ = c; lastc = c; bufsize--; } *buf = 0; return base64; } /* * The pattern parser: build data structures from the pattern file */ static int hash(int c) { return c & 127; } static int findkey(char *val) { Keyword *kp; for(kp = keywords; kp->string; kp++) if(strcmp(val, kp->string) == 0) break; return kp->value; } #define whitespace(c) ((c) == ' ' || (c) == '\t') void parsepats(Biobuf *bp) { Pattern *p, *new; char *cp, *qp; int type, action, n, h; Spat *spat; for(;;){ cp = Brdline(bp, '\n'); if(cp == 0) break; cp[Blinelen(bp)-1] = 0; while(*cp == ' ' || *cp == '\t') cp++; if(*cp == '#' || *cp == 0) continue; type = regexp; if(*cp == '*'){ type = string; cp++; } qp = strchr(cp, ':'); if(qp == 0) continue; *qp = 0; if(debug) fprint(2, "action = %s\n", cp); action = findkey(cp); if(action >= Nactions) continue; cp = qp+1; n = extract(cp); if(n <= 0 || *cp == 0) continue; qp = strstr(cp, "~~"); if(qp){ *qp = 0; n = strlen(cp); } if(debug) fprint(2, " Pattern: `%s'\n", cp); /* Hook regexps into a chain */ if(type == regexp) { new = Malloc(sizeof(Pattern)); new->action = action; new->pat = regcomp(cp); if(new->pat == 0){ free(new); continue; } new->type = regexp; new->alt = 0; new->next = 0; if(qp) parsealt(bp, qp+2, &new->alt); new->next = patterns[action].regexps; patterns[action].regexps = new; continue; } /* not a Regexp - hook strings into Pattern hash chain */ spat = Malloc(sizeof(*spat)); spat->next = 0; spat->alt = 0; spat->len = n; spat->string = Malloc(n+1); spat->c1 = cp[1]; strcpy(spat->string, cp); if(qp) parsealt(bp, qp+2, &spat->alt); p = patterns[action].strings; if(p == 0) { p = Malloc(sizeof(Pattern)); memset(p, 0, sizeof(*p)); p->action = action; p->type = string; patterns[action].strings = p; } h = hash(*spat->string); spat->next = p->spat[h]; p->spat[h] = spat; } } static void parsealt(Biobuf *bp, char *cp, Spat** head) { char *p; Spat *alt; while(cp){ if(*cp == 0){ /*escaped newline*/ do{ cp = Brdline(bp, '\n'); if(cp == 0) return; cp[Blinelen(bp)-1] = 0; } while(extract(cp) <= 0 || *cp == 0); } p = cp; cp = strstr(p, "~~"); if(cp){ *cp = 0; cp += 2; } if(strlen(p)){ alt = Malloc(sizeof(*alt)); alt->string = strdup(p); alt->next = *head; *head = alt; } } } static int extract(char *cp) { int c; char *p, *q, *r; p = q = r = cp; while(whitespace(*p)) p++; while(c = *p++){ if (c == '#') break; if(c == '"'){ while(*p && *p != '"'){ if(*p == '\\' && p[1] == '"') p++; if('A' <= *p && *p <= 'Z') *q++ = *p++ + ('a'-'A'); else *q++ = *p++; } if(*p) p++; r = q; /* never back up over a quoted string */ } else { if('A' <= c && c <= 'Z') c += ('a'-'A'); *q++ = c; } } while(q > r && whitespace(q[-1])) q--; *q = 0; return q-cp; } /* * The matching engine: compare canonical input to pattern structures */ static Spat* isalt(char *message, Spat *alt) { while(alt) { if(*cmd) if(message != cmd && strstr(cmd, alt->string)) break; if(message != header+1 && strstr(header+1, alt->string)) break; if(strstr(message, alt->string)) break; alt = alt->next; } return alt; } int matchpat(Pattern *p, char *message, Resub *m) { Spat *spat; char *s; int c, c1; if(p->type == string){ c1 = *message; for(s=message; c=c1; s++){ c1 = s[1]; for(spat=p->spat[hash(c)]; spat; spat=spat->next){ if(c1 == spat->c1) if(memcmp(s, spat->string, spat->len) == 0) if(!isalt(message, spat->alt)){ m->sp = s; m->ep = s + spat->len; return 1; } } } return 0; } m->sp = m->ep = 0; if(regexec(p->pat, message, m, 1) == 0) return 0; if(isalt(message, p->alt)) return 0; return 1; } void xprint(int fd, char *type, Resub *m) { char *p, *q; int i; if(m->sp == 0 || m->ep == 0) return; /* back up approx 30 characters to whitespace */ for(p = m->sp, i = 0; *p && i < 30; i++, p--) ; while(*p && *p != ' ') p--; p++; /* grab about 30 more chars beyond the end of the match */ for(q = m->ep, i = 0; *q && i < 30; i++, q++) ; while(*q && *q != ' ') q++; fprint(fd, "%s %.*s~%.*s~%.*s\n", type, utfnlen(p, m->sp-p), p, utfnlen(m->sp, m->ep-m->sp), m->sp, utfnlen(m->ep, q-m->ep), m->ep); } enum { INVAL= 255 }; static uchar t64d[256] = { /*00 */ INVAL, INVAL, INVAL, INVAL, INVAL, INVAL, INVAL, INVAL, INVAL, INVAL, INVAL, INVAL, INVAL, INVAL, INVAL, INVAL, /*10*/ INVAL, INVAL, INVAL, INVAL, INVAL, INVAL, INVAL, INVAL, INVAL, INVAL, INVAL, INVAL, INVAL, INVAL, INVAL, INVAL, /*20*/ INVAL, INVAL, INVAL, INVAL, INVAL, INVAL, INVAL, INVAL, INVAL, INVAL, INVAL, 62, INVAL, INVAL, INVAL, 63, /*30*/ 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, INVAL, INVAL, INVAL, INVAL, INVAL, INVAL, /*40*/ INVAL, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, /*50*/ 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, INVAL, INVAL, INVAL, INVAL, INVAL, /*60*/ INVAL, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, /*70*/ 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, INVAL, INVAL, INVAL, INVAL, INVAL, /*80*/ INVAL, INVAL, INVAL, INVAL, INVAL, INVAL, INVAL, INVAL, INVAL, INVAL, INVAL, INVAL, INVAL, INVAL, INVAL, INVAL, /*90*/ INVAL, INVAL, INVAL, INVAL, INVAL, INVAL, INVAL, INVAL, INVAL, INVAL, INVAL, INVAL, INVAL, INVAL, INVAL, INVAL, /*A0*/ INVAL, INVAL, INVAL, INVAL, INVAL, INVAL, INVAL, INVAL, INVAL, INVAL, INVAL, INVAL, INVAL, INVAL, INVAL, INVAL, /*B0*/ INVAL, INVAL, INVAL, INVAL, INVAL, INVAL, INVAL, INVAL, INVAL, INVAL, INVAL, INVAL, INVAL, INVAL, INVAL, INVAL, /*C0*/ INVAL, INVAL, INVAL, INVAL, INVAL, INVAL, INVAL, INVAL, INVAL, INVAL, INVAL, INVAL, INVAL, INVAL, INVAL, INVAL, /*D0*/ INVAL, INVAL, INVAL, INVAL, INVAL, INVAL, INVAL, INVAL, INVAL, INVAL, INVAL, INVAL, INVAL, INVAL, INVAL, INVAL, /*E0*/ INVAL, INVAL, INVAL, INVAL, INVAL, INVAL, INVAL, INVAL, INVAL, INVAL, INVAL, INVAL, INVAL, INVAL, INVAL, INVAL, /*F0*/ INVAL, INVAL, INVAL, INVAL, INVAL, INVAL, INVAL, INVAL, INVAL, INVAL, INVAL, INVAL, INVAL, INVAL, INVAL, INVAL, };