📄 vf.c
字号:
/* * this is a filter that changes mime types and names of * suspect executable attachments. */#include "common.h"#include <ctype.h>Biobuf in;Biobuf out;typedef struct Mtype Mtype;typedef struct Hdef Hdef;typedef struct Hline Hline;typedef struct Part Part;static int badfile(char *name);static int badtype(char *type);static void ctype(Part*, Hdef*, char*);static void cencoding(Part*, Hdef*, char*);static void cdisposition(Part*, Hdef*, char*);static int decquoted(char *out, char *in, char *e);static char* getstring(char *p, String *s, int dolower);static void init_hdefs(void);static int isattribute(char **pp, char *attr);static int latin1toutf(char *out, char *in, char *e);static String* mkboundary(void);static Part* part(Part *pp);static Part* passbody(Part *p, int dobound);static void passnotheader(void);static void passunixheader(void);static Part* problemchild(Part *p);static void readheader(Part *p);static Hline* readhl(void);static void readmtypes(void);static int save(Part *p, char *file);static void setfilename(Part *p, char *name);static char* skiptosemi(char *p);static char* skipwhite(char *p);static String* tokenconvert(String *t);static void writeheader(Part *p, int);enum{ // encodings Enone= 0, Ebase64, Equoted, // disposition possibilities Dnone= 0, Dinline, Dfile, Dignore, PAD64= '=',};/* * a message part; either the whole message or a subpart */struct Part{ Part *pp; /* parent part */ Hline *hl; /* linked list of header lines */ int disposition; int encoding; int badfile; int badtype; String *boundary; /* boundary for multiparts */ int blen; String *charset; /* character set */ String *type; /* content type */ String *filename; /* file name */ Biobuf *tmpbuf; /* diversion input buffer */};/* * a (multi)line header */struct Hline{ Hline *next; String *s;};/* * header definitions for parsing */struct Hdef{ char *type; void (*f)(Part*, Hdef*, char*); int len;};Hdef hdefs[] ={ { "content-type:", ctype, }, { "content-transfer-encoding:", cencoding, }, { "content-disposition:", cdisposition, }, { 0, },};/* * acceptable content types and their extensions */struct Mtype { Mtype *next; char *ext; /* extension */ char *gtype; /* generic content type */ char *stype; /* specific content type */ char class;};Mtype *mtypes;int justreject;char *savefile;voidusage(void){ fprint(2, "usage: upas/vf [-r] [-s savefile]\n"); exits("usage");}voidmain(int argc, char **argv){ ARGBEGIN{ case 'r': justreject = 1; break; case 's': savefile = EARGF(usage()); break; default: usage(); }ARGEND if(argc) usage(); Binit(&in, 0, OREAD); Binit(&out, 1, OWRITE); init_hdefs(); readmtypes(); /* pass through our standard 'From ' line */ passunixheader(); /* parse with the top level part */ part(nil); exits(0);}voidrefuse(void){ postnote(PNGROUP, getpid(), "mail refused: we don't accept executable attachments"); exits("mail refused: we don't accept executable attachments");}/* * parse a part; returns the ancestor whose boundary terminated * this part or nil on EOF. */static Part*part(Part *pp){ Part *p, *np; p = mallocz(sizeof *p, 1); p->pp = pp; readheader(p); if(p->boundary != nil){ /* the format of a multipart part is always: * header * null or ignored body * boundary * header * body * boundary * ... */ writeheader(p, 1); np = passbody(p, 1); if(np != p) return np; for(;;){ np = part(p); if(np != p) return np; } } else { /* no boundary */ /* may still be multipart if this is a forwarded message */ if(p->type && cistrcmp(s_to_c(p->type), "message/rfc822") == 0){ /* the format of forwarded message is: * header * header * body */ writeheader(p, 1); passnotheader(); return part(p); } else { /* * This is the meat. This may be an executable. * if so, wrap it and change its type */ if(p->badtype || p->badfile){ if(p->badfile == 2){ if(savefile != nil) save(p, savefile); syslog(0, "vf", "vf rejected %s %s", p->type?s_to_c(p->type):"?", p->filename?s_to_c(p->filename):"?"); fprint(2, "The mail contained an executable attachment.\n"); fprint(2, "We refuse all mail containing such.\n"); refuse(); } np = problemchild(p); if(np != p) return np; /* if problemchild returns p, it turns out p is okay: fall thru */ } writeheader(p, 1); return passbody(p, 1); } }}/* * read and parse a complete header */static voidreadheader(Part *p){ Hline *hl, **l; Hdef *hd; l = &p->hl; for(;;){ hl = readhl(); if(hl == nil) break; *l = hl; l = &hl->next; for(hd = hdefs; hd->type != nil; hd++){ if(cistrncmp(s_to_c(hl->s), hd->type, hd->len) == 0){ (*hd->f)(p, hd, s_to_c(hl->s)); break; } } }}/* * read a possibly multiline header line */static Hline*readhl(void){ Hline *hl; String *s; char *p; int n; p = Brdline(&in, '\n'); if(p == nil) return nil; n = Blinelen(&in); if(memchr(p, ':', n) == nil){ Bseek(&in, -n, 1); return nil; } s = s_nappend(s_new(), p, n); for(;;){ p = Brdline(&in, '\n'); if(p == nil) break; n = Blinelen(&in); if(*p != ' ' && *p != '\t'){ Bseek(&in, -n, 1); break; } s = s_nappend(s, p, n); } hl = malloc(sizeof *hl); hl->s = s; hl->next = nil; return hl;}/* * write out a complete header */static voidwriteheader(Part *p, int xfree){ Hline *hl, *next; for(hl = p->hl; hl != nil; hl = next){ Bprint(&out, "%s", s_to_c(hl->s)); if(xfree) s_free(hl->s); next = hl->next; if(xfree) free(hl); } if(xfree) p->hl = nil;}/* * pass a body through. return if we hit one of our ancestors' * boundaries or EOF. if we hit a boundary, return a pointer to * that ancestor. if we hit EOF, return nil. */static Part*passbody(Part *p, int dobound){ Part *pp; Biobuf *b; char *cp; for(;;){ if(p->tmpbuf){ b = p->tmpbuf; cp = Brdline(b, '\n'); if(cp == nil){ Bterm(b); p->tmpbuf = nil; goto Stdin; } }else{ Stdin: b = ∈ cp = Brdline(b, '\n'); } if(cp == nil) return nil; for(pp = p; pp != nil; pp = pp->pp) if(pp->boundary != nil && strncmp(cp, s_to_c(pp->boundary), pp->blen) == 0){ if(dobound) Bwrite(&out, cp, Blinelen(b)); else Bseek(b, -Blinelen(b), 1); return pp; } Bwrite(&out, cp, Blinelen(b)); }}/* * save the message somewhere */static vlong bodyoff; /* clumsy hack */static intsave(Part *p, char *file){ int fd; char *cp; Bterm(&out); memset(&out, 0, sizeof(out)); fd = open(file, OWRITE); if(fd < 0) return -1; seek(fd, 0, 2); Binit(&out, fd, OWRITE); cp = ctime(time(0)); cp[28] = 0; Bprint(&out, "From virusfilter %s\n", cp); writeheader(p, 0); bodyoff = Boffset(&out); passbody(p, 1); Bprint(&out, "\n"); Bterm(&out); close(fd); memset(&out, 0, sizeof out); Binit(&out, 1, OWRITE); return 0;}/* * write to a file but save the fd for passbody. */static char*savetmp(Part *p){ char buf[40], *name; int fd; strcpy(buf, "/tmp/vf.XXXXXXXXXXX"); name = mktemp(buf); if((fd = create(name, OWRITE|OEXCL, 0666)) < 0){ fprint(2, "error creating temporary file: %r\n"); refuse(); } close(fd); if(save(p, name) < 0){ fprint(2, "error saving temporary file: %r\n"); refuse(); } if(p->tmpbuf){ fprint(2, "error in savetmp: already have tmp file!\n"); refuse(); } p->tmpbuf = Bopen(name, OREAD|ORCLOSE); if(p->tmpbuf == nil){ fprint(2, "error reading tempoary file: %r\n"); refuse(); } Bseek(p->tmpbuf, bodyoff, 0); return strdup(name);}/* * Run the external checker to do content-based checks. */static intrunchecker(Part *p){ int pid; char *name; Waitmsg *w; if(access("/mail/lib/validateattachment", AEXEC) < 0) return 0; name = savetmp(p); fprint(2, "run checker %s\n", name); switch(pid = fork()){ case -1: sysfatal("fork: %r"); case 0: dup(2, 1); execl("/mail/lib/validateattachment", "validateattachment", name, nil); _exits("exec failed"); } /* * Okay to return on error - will let mail through but wrapped. */ w = wait(); if(w == nil){ syslog(0, "mail", "vf wait failed: %r"); return 0; } if(w->pid != pid){ syslog(0, "mail", "vf wrong pid %d != %d", w->pid, pid); return 0; } if(p->filename) name = s_to_c(p->filename); if(strstr(w->msg, "discard")){ syslog(0, "mail", "vf validateattachment rejected %s", name); refuse(); } if(strstr(w->msg, "accept")){ syslog(0, "mail", "vf validateattachment accepted %s", name); return 1; } free(w); return 0;}/* * emit a multipart Part that explains the problem */static Part*problemchild(Part *p){ Part *np; Hline *hl; String *boundary; char *cp; /* * We don't know whether the attachment is okay. * If there's an external checker, let it have a crack at it. */ if(runchecker(p) > 0) return p; if(justreject) return p;fprint(2, "x\n"); syslog(0, "mail", "vf wrapped %s %s", p->type?s_to_c(p->type):"?", p->filename?s_to_c(p->filename):"?");fprint(2, "x\n"); boundary = mkboundary();fprint(2, "x\n"); /* print out non-mime headers */ for(hl = p->hl; hl != nil; hl = hl->next) if(cistrncmp(s_to_c(hl->s), "content-", 8) != 0) Bprint(&out, "%s", s_to_c(hl->s));fprint(2, "x\n"); /* add in our own multipart headers and message */ Bprint(&out, "Content-Type: multipart/mixed;\n"); Bprint(&out, "\tboundary=\"%s\"\n", s_to_c(boundary)); Bprint(&out, "Content-Disposition: inline\n"); Bprint(&out, "\n"); Bprint(&out, "This is a multi-part message in MIME format.\n"); Bprint(&out, "--%s\n", s_to_c(boundary)); Bprint(&out, "Content-Disposition: inline\n"); Bprint(&out, "Content-Type: text/plain; charset=\"US-ASCII\"\n"); Bprint(&out, "Content-Transfer-Encoding: 7bit\n"); Bprint(&out, "\n"); Bprint(&out, "from postmaster@%s:\n", sysname()); Bprint(&out, "The following attachment had content that we can't\n"); Bprint(&out, "prove to be harmless. To avoid possible automatic\n"); Bprint(&out, "execution, we changed the content headers.\n"); Bprint(&out, "The original header was:\n\n"); /* print out original header lines */ for(hl = p->hl; hl != nil; hl = hl->next) if(cistrncmp(s_to_c(hl->s), "content-", 8) == 0) Bprint(&out, "\t%s", s_to_c(hl->s)); Bprint(&out, "--%s\n", s_to_c(boundary)); /* change file name */ if(p->filename) s_append(p->filename, ".suspect"); else p->filename = s_copy("file.suspect"); /* print out new header */ Bprint(&out, "Content-Type: application/octet-stream\n"); Bprint(&out, "Content-Disposition: attachment; filename=\"%s\"\n", s_to_c(p->filename)); switch(p->encoding){ case Enone: break; case Ebase64: Bprint(&out, "Content-Transfer-Encoding: base64\n"); break; case Equoted: Bprint(&out, "Content-Transfer-Encoding: quoted-printable\n"); break; }fprint(2, "z\n"); /* pass the body */ np = passbody(p, 0);fprint(2, "w\n"); /* add the new boundary and the original terminator */ Bprint(&out, "--%s--\n", s_to_c(boundary));
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -