aboutsummaryrefslogtreecommitdiff
path: root/src/cmd/upas/vf
diff options
context:
space:
mode:
authorrsc <devnull@localhost>2005-10-29 16:26:44 +0000
committerrsc <devnull@localhost>2005-10-29 16:26:44 +0000
commit5cdb17983ae6e6367ad7a940cb219eab247a9304 (patch)
tree8ca1ef49af2a96e7daebe624d91fdf679814a057 /src/cmd/upas/vf
parentcd3745196389579fb78b9b01ef1daefb5a57aa71 (diff)
downloadplan9port-5cdb17983ae6e6367ad7a940cb219eab247a9304.tar.gz
plan9port-5cdb17983ae6e6367ad7a940cb219eab247a9304.tar.bz2
plan9port-5cdb17983ae6e6367ad7a940cb219eab247a9304.zip
Thanks to John Cummings.
Diffstat (limited to 'src/cmd/upas/vf')
-rw-r--r--src/cmd/upas/vf/mkfile20
-rw-r--r--src/cmd/upas/vf/vf.c1110
2 files changed, 1130 insertions, 0 deletions
diff --git a/src/cmd/upas/vf/mkfile b/src/cmd/upas/vf/mkfile
new file mode 100644
index 00000000..b17e4578
--- /dev/null
+++ b/src/cmd/upas/vf/mkfile
@@ -0,0 +1,20 @@
+<$PLAN9/src/mkhdr
+
+TARG=vf
+
+OFILES=vf.$O\
+
+LIB=../common/libcommon.a\
+
+HFILES=../common/common.h\
+ ../common/sys.h\
+
+
+BIN=$PLAN9/bin/upas
+UPDATE=\
+ mkfile\
+ $HFILES\
+ ${OFILES:%.$O=%.c}\
+
+<$PLAN9/src/mkone
+CFLAGS=$CFLAGS -I../common
diff --git a/src/cmd/upas/vf/vf.c b/src/cmd/upas/vf/vf.c
new file mode 100644
index 00000000..db4f5ce8
--- /dev/null
+++ b/src/cmd/upas/vf/vf.c
@@ -0,0 +1,1110 @@
+/*
+ * 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;
+
+void
+main(int argc, char **argv)
+{
+ ARGBEGIN{
+ case 'r':
+ justreject = 1;
+ break;
+ case 's':
+ savefile = ARGF();
+ if(savefile == nil)
+ exits("usage");
+ break;
+ }ARGEND;
+
+ 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);
+}
+
+void
+refuse(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 void
+readheader(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 void
+writeheader(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 = &in;
+ 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));
+ }
+ return nil;
+}
+
+/*
+ * save the message somewhere
+ */
+static vlong bodyoff; /* clumsy hack */
+static int
+save(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);
+}
+
+/*
+ * XXX save the decoded file, run 9 unzip -tf on it, and then
+ * look at the file list.
+ */
+static int
+runchecker(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;
+
+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));
+ if(np && np->boundary){
+ cp = Brdline(&in, '\n');
+ Bwrite(&out, cp, Blinelen(&in));
+ }
+
+fprint(2, "a %p\n", np);
+ return np;
+}
+
+static int
+isattribute(char **pp, char *attr)
+{
+ char *p;
+ int n;
+
+ n = strlen(attr);
+ p = *pp;
+ if(cistrncmp(p, attr, n) != 0)
+ return 0;
+ p += n;
+ while(*p == ' ')
+ p++;
+ if(*p++ != '=')
+ return 0;
+ while(*p == ' ')
+ p++;
+ *pp = p;
+ return 1;
+}
+
+/*
+ * parse content type header
+ */
+static void
+ctype(Part *p, Hdef *h, char *cp)
+{
+ String *s;
+
+ cp += h->len;
+ cp = skipwhite(cp);
+
+ p->type = s_new();
+ cp = getstring(cp, p->type, 1);
+ if(badtype(s_to_c(p->type)))
+ p->badtype = 1;
+
+ while(*cp){
+ if(isattribute(&cp, "boundary")){
+ s = s_new();
+ cp = getstring(cp, s, 0);
+ p->boundary = s_reset(p->boundary);
+ s_append(p->boundary, "--");
+ s_append(p->boundary, s_to_c(s));
+ p->blen = s_len(p->boundary);
+ s_free(s);
+ } else if(cistrncmp(cp, "multipart", 9) == 0){
+ /*
+ * the first unbounded part of a multipart message,
+ * the preamble, is not displayed or saved
+ */
+ } else if(isattribute(&cp, "name")){
+ setfilename(p, cp);
+ } else if(isattribute(&cp, "charset")){
+ if(p->charset == nil)
+ p->charset = s_new();
+ cp = getstring(cp, s_reset(p->charset), 0);
+ }
+
+ cp = skiptosemi(cp);
+ }
+}
+
+/*
+ * parse content encoding header
+ */
+static void
+cencoding(Part *m, Hdef *h, char *p)
+{
+ p += h->len;
+ p = skipwhite(p);
+ if(cistrncmp(p, "base64", 6) == 0)
+ m->encoding = Ebase64;
+ else if(cistrncmp(p, "quoted-printable", 16) == 0)
+ m->encoding = Equoted;
+}
+
+/*
+ * parse content disposition header
+ */
+static void
+cdisposition(Part *p, Hdef *h, char *cp)
+{
+ cp += h->len;
+ cp = skipwhite(cp);
+ while(*cp){
+ if(cistrncmp(cp, "inline", 6) == 0){
+ p->disposition = Dinline;
+ } else if(cistrncmp(cp, "attachment", 10) == 0){
+ p->disposition = Dfile;
+ } else if(cistrncmp(cp, "filename=", 9) == 0){
+ cp += 9;
+ setfilename(p, cp);
+ }
+ cp = skiptosemi(cp);
+ }
+
+}
+
+static void
+setfilename(Part *p, char *name)
+{
+ if(p->filename == nil)
+ p->filename = s_new();
+ getstring(name, s_reset(p->filename), 0);
+ p->filename = tokenconvert(p->filename);
+ p->badfile = badfile(s_to_c(p->filename));
+}
+
+static char*
+skipwhite(char *p)
+{
+ while(isspace(*p))
+ p++;
+ return p;
+}
+
+static char*
+skiptosemi(char *p)
+{
+ while(*p && *p != ';')
+ p++;
+ while(*p == ';' || isspace(*p))
+ p++;
+ return p;
+}
+
+/*
+ * parse a possibly "'d string from a header. A
+ * ';' terminates the string.
+ */
+static char*
+getstring(char *p, String *s, int dolower)
+{
+ s = s_reset(s);
+ p = skipwhite(p);
+ if(*p == '"'){
+ p++;
+ for(;*p && *p != '"'; p++)
+ if(dolower)
+ s_putc(s, tolower(*p));
+ else
+ s_putc(s, *p);
+ if(*p == '"')
+ p++;
+ s_terminate(s);
+
+ return p;
+ }
+
+ for(; *p && !isspace(*p) && *p != ';'; p++)
+ if(dolower)
+ s_putc(s, tolower(*p));
+ else
+ s_putc(s, *p);
+ s_terminate(s);
+
+ return p;
+}
+
+static void
+init_hdefs(void)
+{
+ Hdef *hd;
+ static int already;
+
+ if(already)
+ return;
+ already = 1;
+
+ for(hd = hdefs; hd->type != nil; hd++)
+ hd->len = strlen(hd->type);
+}
+
+/*
+ * create a new boundary
+ */
+static String*
+mkboundary(void)
+{
+ char buf[32];
+ int i;
+ static int already;
+
+ if(already == 0){
+ srand((time(0)<<16)|getpid());
+ already = 1;
+ }
+ strcpy(buf, "upas-");
+ for(i = 5; i < sizeof(buf)-1; i++)
+ buf[i] = 'a' + nrand(26);
+ buf[i] = 0;
+ return s_copy(buf);
+}
+
+/*
+ * skip blank lines till header
+ */
+static void
+passnotheader(void)
+{
+ char *cp;
+ int i, n;
+
+ while((cp = Brdline(&in, '\n')) != nil){
+ n = Blinelen(&in);
+ for(i = 0; i < n-1; i++)
+ if(cp[i] != ' ' && cp[i] != '\t' && cp[i] != '\r'){
+ Bseek(&in, -n, 1);
+ return;
+ }
+ Bwrite(&out, cp, n);
+ }
+}
+
+/*
+ * pass unix header lines
+ */
+static void
+passunixheader(void)
+{
+ char *p;
+ int n;
+
+ while((p = Brdline(&in, '\n')) != nil){
+ n = Blinelen(&in);
+ if(strncmp(p, "From ", 5) != 0){
+ Bseek(&in, -n, 1);
+ break;
+ }
+ Bwrite(&out, p, n);
+ }
+}
+
+/*
+ * Read mime types
+ */
+static void
+readmtypes(void)
+{
+ Biobuf *b;
+ char *p;
+ char *f[6];
+ Mtype *m;
+ Mtype **l;
+
+ b = Bopen(unsharp("#9/sys/lib/mimetype"), OREAD);
+ if(b == nil)
+ return;
+
+ l = &mtypes;
+ while((p = Brdline(b, '\n')) != nil){
+ if(*p == '#')
+ continue;
+ p[Blinelen(b)-1] = 0;
+ if(tokenize(p, f, nelem(f)) < 5)
+ continue;
+ m = mallocz(sizeof *m, 1);
+ if(m == nil)
+ goto err;
+ m->ext = strdup(f[0]);
+ if(m->ext == 0)
+ goto err;
+ m->gtype = strdup(f[1]);
+ if(m->gtype == 0)
+ goto err;
+ m->stype = strdup(f[2]);
+ if(m->stype == 0)
+ goto err;
+ m->class = *f[4];
+ *l = m;
+ l = &(m->next);
+ }
+ Bterm(b);
+ return;
+err:
+ if(m == nil)
+ return;
+ free(m->ext);
+ free(m->gtype);
+ free(m->stype);
+ free(m);
+ Bterm(b);
+}
+
+/*
+ * if the class is 'm' or 'y', accept it
+ * if the class is 'p' check a previous extension
+ * otherwise, filename is bad
+ */
+static int
+badfile(char *name)
+{
+ char *p;
+ Mtype *m;
+ int rv;
+
+ p = strrchr(name, '.');
+ if(p == nil)
+ return 0;
+
+ for(m = mtypes; m != nil; m = m->next)
+ if(cistrcmp(p, m->ext) == 0){
+ switch(m->class){
+ case 'm':
+ case 'y':
+ return 0;
+ case 'p':
+ *p = 0;
+ rv = badfile(name);
+ *p = '.';
+ return rv;
+ case 'r':
+ return 2;
+ }
+ }
+ if(justreject)
+ return 0;
+ return 1;
+}
+
+/*
+ * if the class is 'm' or 'y' or 'p', accept it
+ * otherwise, filename is bad
+ */
+static int
+badtype(char *type)
+{
+ Mtype *m;
+ char *s, *fix;
+ int rv = 1;
+
+ if(justreject)
+ return 0;
+
+ fix = s = strchr(type, '/');
+ if(s != nil)
+ *s++ = 0;
+ else
+ s = "-";
+
+ for(m = mtypes; m != nil; m = m->next){
+ if(cistrcmp(type, m->gtype) != 0)
+ continue;
+ if(cistrcmp(s, m->stype) != 0)
+ continue;
+ switch(m->class){
+ case 'y':
+ case 'p':
+ case 'm':
+ rv = 0;
+ break;
+ }
+ break;
+ }
+
+ if(fix != nil)
+ *fix = '/';
+ return rv;
+}
+
+/* rfc2047 non-ascii */
+typedef struct Charset Charset;
+struct Charset {
+ char *name;
+ int len;
+ int convert;
+} charsets[] =
+{
+ { "us-ascii", 8, 1, },
+ { "utf-8", 5, 0, },
+ { "iso-8859-1", 10, 1, },
+};
+
+/*
+ * convert to UTF if need be
+ */
+static String*
+tokenconvert(String *t)
+{
+ String *s;
+ char decoded[1024];
+ char utfbuf[2*1024];
+ int i, len;
+ char *e;
+ char *token;
+
+ token = s_to_c(t);
+ len = s_len(t);
+
+ if(token[0] != '=' || token[1] != '?' ||
+ token[len-2] != '?' || token[len-1] != '=')
+ goto err;
+ e = token+len-2;
+ token += 2;
+
+ // bail if we don't understand the character set
+ for(i = 0; i < nelem(charsets); i++)
+ if(cistrncmp(charsets[i].name, token, charsets[i].len) == 0)
+ if(token[charsets[i].len] == '?'){
+ token += charsets[i].len + 1;
+ break;
+ }
+ if(i >= nelem(charsets))
+ goto err;
+
+ // bail if it doesn't fit
+ if(strlen(token) > sizeof(decoded)-1)
+ goto err;
+
+ // bail if we don't understand the encoding
+ if(cistrncmp(token, "b?", 2) == 0){
+ token += 2;
+ len = dec64((uchar*)decoded, sizeof(decoded), token, e-token);
+ decoded[len] = 0;
+ } else if(cistrncmp(token, "q?", 2) == 0){
+ token += 2;
+ len = decquoted(decoded, token, e);
+ if(len > 0 && decoded[len-1] == '\n')
+ len--;
+ decoded[len] = 0;
+ } else
+ goto err;
+
+ s = nil;
+ switch(charsets[i].convert){
+ case 0:
+ s = s_copy(decoded);
+ break;
+ case 1:
+ s = s_new();
+ latin1toutf(utfbuf, decoded, decoded+len);
+ s_append(s, utfbuf);
+ break;
+ }
+
+ return s;
+err:
+ return s_clone(t);
+}
+
+/*
+ * decode quoted
+ */
+enum
+{
+ Self= 1,
+ Hex= 2,
+};
+uchar tableqp[256];
+
+static void
+initquoted(void)
+{
+ int c;
+
+ memset(tableqp, 0, 256);
+ for(c = ' '; c <= '<'; c++)
+ tableqp[c] = Self;
+ for(c = '>'; c <= '~'; c++)
+ tableqp[c] = Self;
+ tableqp['\t'] = Self;
+ tableqp['='] = Hex;
+}
+
+static int
+hex2int(int x)
+{
+ if(x >= '0' && x <= '9')
+ return x - '0';
+ if(x >= 'A' && x <= 'F')
+ return (x - 'A') + 10;
+ if(x >= 'a' && x <= 'f')
+ return (x - 'a') + 10;
+ return 0;
+}
+
+static char*
+decquotedline(char *out, char *in, char *e)
+{
+ int c, soft;
+
+ /* dump trailing white space */
+ while(e >= in && (*e == ' ' || *e == '\t' || *e == '\r' || *e == '\n'))
+ e--;
+
+ /* trailing '=' means no newline */
+ if(*e == '='){
+ soft = 1;
+ e--;
+ } else
+ soft = 0;
+
+ while(in <= e){
+ c = (*in++) & 0xff;
+ switch(tableqp[c]){
+ case Self:
+ *out++ = c;
+ break;
+ case Hex:
+ c = hex2int(*in++)<<4;
+ c |= hex2int(*in++);
+ *out++ = c;
+ break;
+ }
+ }
+ if(!soft)
+ *out++ = '\n';
+ *out = 0;
+
+ return out;
+}
+
+static int
+decquoted(char *out, char *in, char *e)
+{
+ char *p, *nl;
+
+ if(tableqp[' '] == 0)
+ initquoted();
+
+ p = out;
+ while((nl = strchr(in, '\n')) != nil && nl < e){
+ p = decquotedline(p, in, nl);
+ in = nl + 1;
+ }
+ if(in < e)
+ p = decquotedline(p, in, e-1);
+
+ // make sure we end with a new line
+ if(*(p-1) != '\n'){
+ *p++ = '\n';
+ *p = 0;
+ }
+
+ return p - out;
+}
+
+/* translate latin1 directly since it fits neatly in utf */
+static int
+latin1toutf(char *out, char *in, char *e)
+{
+ Rune r;
+ char *p;
+
+ p = out;
+ for(; in < e; in++){
+ r = (*in) & 0xff;
+ p += runetochar(p, &r);
+ }
+ *p = 0;
+ return p - out;
+}