1130 lines
20 KiB
C
1130 lines
20 KiB
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;
|
|
|
|
void
|
|
usage(void)
|
|
{
|
|
fprint(2, "usage: upas/vf [-r] [-s savefile]\n");
|
|
exits("usage");
|
|
}
|
|
|
|
void
|
|
main(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);
|
|
}
|
|
|
|
void
|
|
refuse(char *reason)
|
|
{
|
|
char *full;
|
|
static char msg[] =
|
|
"mail refused: we don't accept executable attachments";
|
|
|
|
full = smprint("%s: %s", msg, reason);
|
|
postnote(PNGROUP, getpid(), full);
|
|
exits(full);
|
|
}
|
|
|
|
|
|
/*
|
|
* 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(nil);
|
|
}
|
|
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 = ∈
|
|
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 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 *name;
|
|
int fd;
|
|
|
|
name = mktemp(smprint("%s/vf.XXXXXXXXXXX", UPASTMP));
|
|
if((fd = create(name, OWRITE|OEXCL, 0666)) < 0){
|
|
fprint(2, "%s: error creating temporary file: %r\n", argv0);
|
|
refuse("can't create temporary file");
|
|
}
|
|
close(fd);
|
|
if(save(p, name) < 0){
|
|
fprint(2, "%s: error saving temporary file: %r\n", argv0);
|
|
refuse("can't write temporary file");
|
|
}
|
|
if(p->tmpbuf){
|
|
fprint(2, "%s: error in savetmp: already have tmp file!\n",
|
|
argv0);
|
|
refuse("already have temporary file");
|
|
}
|
|
p->tmpbuf = Bopen(name, OREAD|ORCLOSE);
|
|
if(p->tmpbuf == nil){
|
|
fprint(2, "%s: error reading temporary file: %r\n", argv0);
|
|
refuse("error reading temporary file");
|
|
}
|
|
Bseek(p->tmpbuf, bodyoff, 0);
|
|
return name;
|
|
}
|
|
|
|
/*
|
|
* Run the external checker to do content-based checks.
|
|
*/
|
|
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) {
|
|
free(name);
|
|
name = strdup(s_to_c(p->filename));
|
|
}
|
|
if(strstr(w->msg, "discard")){
|
|
syslog(0, "mail", "vf validateattachment rejected %s", name);
|
|
refuse("rejected by validateattachment");
|
|
}
|
|
if(strstr(w->msg, "accept")){
|
|
syslog(0, "mail", "vf validateattachment accepted %s", name);
|
|
return 1;
|
|
}
|
|
free(w);
|
|
free(name);
|
|
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));
|
|
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("/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;
|
|
}
|
|
}
|
|
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;
|
|
|
|
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);
|
|
if(len == -1)
|
|
goto err;
|
|
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;
|
|
}
|