Warning: Attempt to read property "date" on null in /usr/local/www/websvn.planix.org/blame.php on line 247

Warning: Attempt to read property "msg" on null in /usr/local/www/websvn.planix.org/blame.php on line 247
WebSVN – planix.SVN – Blame – /os/branches/feature_fixcpp/sys/src/cmd/upas/vf/vf.c – Rev 2

Subversion Repositories planix.SVN

Rev

Go to most recent revision | Details | Last modification | View Log | RSS feed

Rev Author Line No. Line
2 - 1
/*
2
 *  this is a filter that changes mime types and names of
3
 *  suspect executable attachments.
4
 */
5
#include "common.h"
6
#include <ctype.h>
7
 
8
Biobuf in;
9
Biobuf out;
10
 
11
typedef struct Mtype Mtype;
12
typedef struct Hdef Hdef;
13
typedef struct Hline Hline;
14
typedef struct Part Part;
15
 
16
static int	badfile(char *name);
17
static int	badtype(char *type);
18
static void	ctype(Part*, Hdef*, char*);
19
static void	cencoding(Part*, Hdef*, char*);
20
static void	cdisposition(Part*, Hdef*, char*);
21
static int	decquoted(char *out, char *in, char *e);
22
static char*	getstring(char *p, String *s, int dolower);
23
static void	init_hdefs(void);
24
static int	isattribute(char **pp, char *attr);
25
static int	latin1toutf(char *out, char *in, char *e);
26
static String*	mkboundary(void);
27
static Part*	part(Part *pp);
28
static Part*	passbody(Part *p, int dobound);
29
static void	passnotheader(void);
30
static void	passunixheader(void);
31
static Part*	problemchild(Part *p);
32
static void	readheader(Part *p);
33
static Hline*	readhl(void);
34
static void	readmtypes(void);
35
static int	save(Part *p, char *file);
36
static void	setfilename(Part *p, char *name);
37
static char*	skiptosemi(char *p);
38
static char*	skipwhite(char *p);
39
static String*	tokenconvert(String *t);
40
static void	writeheader(Part *p, int);
41
 
42
enum
43
{
44
	/* encodings */
45
	Enone=	0,
46
	Ebase64,
47
	Equoted,
48
 
49
	/* disposition possibilities */
50
	Dnone=	0,
51
	Dinline,
52
	Dfile,
53
	Dignore,
54
 
55
	PAD64=	'=',
56
};
57
 
58
/*
59
 *  a message part; either the whole message or a subpart
60
 */
61
struct Part
62
{
63
	Part	*pp;		/* parent part */
64
	Hline	*hl;		/* linked list of header lines */
65
	int	disposition;
66
	int	encoding;
67
	int	badfile;
68
	int	badtype;
69
	String	*boundary;	/* boundary for multiparts */
70
	int	blen;
71
	String	*charset;	/* character set */
72
	String	*type;		/* content type */
73
	String	*filename;	/* file name */
74
	Biobuf	*tmpbuf;		/* diversion input buffer */
75
};
76
 
77
/*
78
 *  a (multi)line header
79
 */
80
struct Hline
81
{
82
	Hline	*next;
83
	String		*s;
84
};
85
 
86
/*
87
 *  header definitions for parsing
88
 */
89
struct Hdef
90
{
91
	char *type;
92
	void (*f)(Part*, Hdef*, char*);
93
	int len;
94
};
95
 
96
Hdef hdefs[] =
97
{
98
	{ "content-type:", ctype, },
99
	{ "content-transfer-encoding:", cencoding, },
100
	{ "content-disposition:", cdisposition, },
101
	{ 0, },
102
};
103
 
104
/*
105
 *  acceptable content types and their extensions
106
 */
107
struct Mtype {
108
	Mtype	*next;
109
	char 	*ext;		/* extension */
110
	char	*gtype;		/* generic content type */
111
	char	*stype;		/* specific content type */
112
	char	class;
113
};
114
Mtype *mtypes;
115
 
116
int justreject;
117
char *savefile;
118
 
119
void
120
usage(void)
121
{
122
	fprint(2, "usage: upas/vf [-r] [-s savefile]\n");
123
	exits("usage");
124
}
125
 
126
void
127
main(int argc, char **argv)
128
{
129
	ARGBEGIN{
130
	case 'r':
131
		justreject = 1;
132
		break;
133
	case 's':
134
		savefile = EARGF(usage());
135
		break;
136
	default:
137
		usage();
138
	}ARGEND
139
 
140
	if(argc)
141
		usage();
142
 
143
	Binit(&in, 0, OREAD);
144
	Binit(&out, 1, OWRITE);
145
 
146
	init_hdefs();
147
	readmtypes();
148
 
149
	/* pass through our standard 'From ' line */
150
	passunixheader();
151
 
152
	/* parse with the top level part */
153
	part(nil);
154
 
155
	exits(0);
156
}
157
 
158
void
159
refuse(char *reason)
160
{
161
	char *full;
162
	static char msg[] =
163
		"mail refused: we don't accept executable attachments";
164
 
165
	full = smprint("%s: %s", msg, reason);
166
	postnote(PNGROUP, getpid(), full);
167
	exits(full);
168
}
169
 
170
 
171
/*
172
 *  parse a part; returns the ancestor whose boundary terminated
173
 *  this part or nil on EOF.
174
 */
175
static Part*
176
part(Part *pp)
177
{
178
	Part *p, *np;
179
 
180
	p = mallocz(sizeof *p, 1);
181
	p->pp = pp;
182
	readheader(p);
183
 
184
	if(p->boundary != nil){
185
		/* the format of a multipart part is always:
186
		 *   header
187
		 *   null or ignored body
188
		 *   boundary
189
		 *   header
190
		 *   body
191
		 *   boundary
192
		 *   ...
193
		 */
194
		writeheader(p, 1);
195
		np = passbody(p, 1);
196
		if(np != p)
197
			return np;
198
		for(;;){
199
			np = part(p);
200
			if(np != p)
201
				return np;
202
		}
203
	} else {
204
		/* no boundary */
205
		/* may still be multipart if this is a forwarded message */
206
		if(p->type && cistrcmp(s_to_c(p->type), "message/rfc822") == 0){
207
			/* the format of forwarded message is:
208
			 *   header
209
			 *   header
210
			 *   body
211
			 */
212
			writeheader(p, 1);
213
			passnotheader();
214
			return part(p);
215
		} else {
216
			/*
217
			 * This is the meat.  This may be an executable.
218
			 * if so, wrap it and change its type
219
			 */
220
			if(p->badtype || p->badfile){
221
				if(p->badfile == 2){
222
					if(savefile != nil)
223
						save(p, savefile);
224
					syslog(0, "vf", "vf rejected %s %s",
225
						p->type? s_to_c(p->type): "?",
226
						p->filename?s_to_c(p->filename):"?");
227
					fprint(2, "The mail contained an executable attachment.\n");
228
					fprint(2, "We refuse all mail containing such.\n");
229
					refuse(nil);
230
				}
231
				np = problemchild(p);
232
				if(np != p)
233
					return np;
234
				/* if problemchild returns p, it turns out p is okay: fall thru */
235
			}
236
			writeheader(p, 1);
237
			return passbody(p, 1);
238
		}
239
	}
240
}
241
 
242
/*
243
 *  read and parse a complete header
244
 */
245
static void
246
readheader(Part *p)
247
{
248
	Hline *hl, **l;
249
	Hdef *hd;
250
 
251
	l = &p->hl;
252
	for(;;){
253
		hl = readhl();
254
		if(hl == nil)
255
			break;
256
		*l = hl;
257
		l = &hl->next;
258
 
259
		for(hd = hdefs; hd->type != nil; hd++){
260
			if(cistrncmp(s_to_c(hl->s), hd->type, hd->len) == 0){
261
				(*hd->f)(p, hd, s_to_c(hl->s));
262
				break;
263
			}
264
		}
265
	}
266
}
267
 
268
/*
269
 *  read a possibly multiline header line
270
 */
271
static Hline*
272
readhl(void)
273
{
274
	Hline *hl;
275
	String *s;
276
	char *p;
277
	int n;
278
 
279
	p = Brdline(&in, '\n');
280
	if(p == nil)
281
		return nil;
282
	n = Blinelen(&in);
283
	if(memchr(p, ':', n) == nil){
284
		Bseek(&in, -n, 1);
285
		return nil;
286
	}
287
	s = s_nappend(s_new(), p, n);
288
	for(;;){
289
		p = Brdline(&in, '\n');
290
		if(p == nil)
291
			break;
292
		n = Blinelen(&in);
293
		if(*p != ' ' && *p != '\t'){
294
			Bseek(&in, -n, 1);
295
			break;
296
		}
297
		s = s_nappend(s, p, n);
298
	}
299
	hl = malloc(sizeof *hl);
300
	hl->s = s;
301
	hl->next = nil;
302
	return hl;
303
}
304
 
305
/*
306
 *  write out a complete header
307
 */
308
static void
309
writeheader(Part *p, int xfree)
310
{
311
	Hline *hl, *next;
312
 
313
	for(hl = p->hl; hl != nil; hl = next){
314
		Bprint(&out, "%s", s_to_c(hl->s));
315
		if(xfree)
316
			s_free(hl->s);
317
		next = hl->next;
318
		if(xfree)
319
			free(hl);
320
	}
321
	if(xfree)
322
		p->hl = nil;
323
}
324
 
325
/*
326
 *  pass a body through.  return if we hit one of our ancestors'
327
 *  boundaries or EOF.  if we hit a boundary, return a pointer to
328
 *  that ancestor.  if we hit EOF, return nil.
329
 */
330
static Part*
331
passbody(Part *p, int dobound)
332
{
333
	Part *pp;
334
	Biobuf *b;
335
	char *cp;
336
 
337
	for(;;){
338
		if(p->tmpbuf){
339
			b = p->tmpbuf;
340
			cp = Brdline(b, '\n');
341
			if(cp == nil){
342
				Bterm(b);
343
				p->tmpbuf = nil;
344
				goto Stdin;
345
			}
346
		}else{
347
		Stdin:
348
			b = &in;
349
			cp = Brdline(b, '\n');
350
		}
351
		if(cp == nil)
352
			return nil;
353
		for(pp = p; pp != nil; pp = pp->pp)
354
			if(pp->boundary != nil
355
			&& strncmp(cp, s_to_c(pp->boundary), pp->blen) == 0){
356
				if(dobound)
357
					Bwrite(&out, cp, Blinelen(b));
358
				else
359
					Bseek(b, -Blinelen(b), 1);
360
				return pp;
361
			}
362
		Bwrite(&out, cp, Blinelen(b));
363
	}
364
}
365
 
366
/*
367
 *  save the message somewhere
368
 */
369
static vlong bodyoff;	/* clumsy hack */
370
 
371
static int
372
save(Part *p, char *file)
373
{
374
	int fd;
375
	char *cp;
376
 
377
	Bterm(&out);
378
	memset(&out, 0, sizeof(out));
379
 
380
	fd = open(file, OWRITE);
381
	if(fd < 0)
382
		return -1;
383
	seek(fd, 0, 2);
384
	Binit(&out, fd, OWRITE);
385
	cp = ctime(time(0));
386
	cp[28] = 0;
387
	Bprint(&out, "From virusfilter %s\n", cp);
388
	writeheader(p, 0);
389
	bodyoff = Boffset(&out);
390
	passbody(p, 1);
391
	Bprint(&out, "\n");
392
	Bterm(&out);
393
	close(fd);
394
 
395
	memset(&out, 0, sizeof out);
396
	Binit(&out, 1, OWRITE);
397
	return 0;
398
}
399
 
400
/*
401
 * write to a file but save the fd for passbody.
402
 */
403
static char*
404
savetmp(Part *p)
405
{
406
	char *name;
407
	int fd;
408
 
409
	name = mktemp(smprint("%s/vf.XXXXXXXXXXX", UPASTMP));
410
	if((fd = create(name, OWRITE|OEXCL, 0666)) < 0){
411
		fprint(2, "%s: error creating temporary file: %r\n", argv0);
412
		refuse("can't create temporary file");
413
	}
414
	close(fd);
415
	if(save(p, name) < 0){
416
		fprint(2, "%s: error saving temporary file: %r\n", argv0);
417
		refuse("can't write temporary file");
418
	}
419
	if(p->tmpbuf){
420
		fprint(2, "%s: error in savetmp: already have tmp file!\n",
421
			argv0);
422
		refuse("already have temporary file");
423
	}
424
	p->tmpbuf = Bopen(name, OREAD|ORCLOSE);
425
	if(p->tmpbuf == nil){
426
		fprint(2, "%s: error reading temporary file: %r\n", argv0);
427
		refuse("error reading temporary file");
428
	}
429
	Bseek(p->tmpbuf, bodyoff, 0);
430
	return name;
431
}
432
 
433
/*
434
 * Run the external checker to do content-based checks.
435
 */
436
static int
437
runchecker(Part *p)
438
{
439
	int pid;
440
	char *name;
441
	Waitmsg *w;
442
 
443
	if(access("/mail/lib/validateattachment", AEXEC) < 0)
444
		return 0;
445
 
446
	name = savetmp(p);
447
	fprint(2, "run checker %s\n", name);
448
	switch(pid = fork()){
449
	case -1:
450
		sysfatal("fork: %r");
451
	case 0:
452
		dup(2, 1);
453
		execl("/mail/lib/validateattachment", "validateattachment",
454
			name, nil);
455
		_exits("exec failed");
456
	}
457
 
458
	/*
459
	 * Okay to return on error - will let mail through but wrapped.
460
	 */
461
	w = wait();
462
	if(w == nil){
463
		syslog(0, "mail", "vf wait failed: %r");
464
		return 0;
465
	}
466
	if(w->pid != pid){
467
		syslog(0, "mail", "vf wrong pid %d != %d", w->pid, pid);
468
		return 0;
469
	}
470
	if(p->filename) {
471
		free(name);
472
		name = strdup(s_to_c(p->filename));
473
	}
474
	if(strstr(w->msg, "discard")){
475
		syslog(0, "mail", "vf validateattachment rejected %s", name);
476
		refuse("rejected by validateattachment");
477
	}
478
	if(strstr(w->msg, "accept")){
479
		syslog(0, "mail", "vf validateattachment accepted %s", name);
480
		return 1;
481
	}
482
	free(w);
483
	free(name);
484
	return 0;
485
}
486
 
487
/*
488
 *  emit a multipart Part that explains the problem
489
 */
490
static Part*
491
problemchild(Part *p)
492
{
493
	Part *np;
494
	Hline *hl;
495
	String *boundary;
496
	char *cp;
497
 
498
	/*
499
	 * We don't know whether the attachment is okay.
500
	 * If there's an external checker, let it have a crack at it.
501
	 */
502
	if(runchecker(p) > 0)
503
		return p;
504
 
505
	if(justreject)
506
		return p;
507
 
508
fprint(2, "x\n");
509
	syslog(0, "mail", "vf wrapped %s %s", p->type?s_to_c(p->type):"?",
510
		p->filename?s_to_c(p->filename):"?");
511
fprint(2, "x\n");
512
 
513
	boundary = mkboundary();
514
fprint(2, "x\n");
515
	/* print out non-mime headers */
516
	for(hl = p->hl; hl != nil; hl = hl->next)
517
		if(cistrncmp(s_to_c(hl->s), "content-", 8) != 0)
518
			Bprint(&out, "%s", s_to_c(hl->s));
519
 
520
fprint(2, "x\n");
521
	/* add in our own multipart headers and message */
522
	Bprint(&out, "Content-Type: multipart/mixed;\n");
523
	Bprint(&out, "\tboundary=\"%s\"\n", s_to_c(boundary));
524
	Bprint(&out, "Content-Disposition: inline\n");
525
	Bprint(&out, "\n");
526
	Bprint(&out, "This is a multi-part message in MIME format.\n");
527
	Bprint(&out, "--%s\n", s_to_c(boundary));
528
	Bprint(&out, "Content-Disposition: inline\n");
529
	Bprint(&out, "Content-Type: text/plain; charset=\"US-ASCII\"\n");
530
	Bprint(&out, "Content-Transfer-Encoding: 7bit\n");
531
	Bprint(&out, "\n");
532
	Bprint(&out, "from postmaster@%s:\n", sysname());
533
	Bprint(&out, "The following attachment had content that we can't\n");
534
	Bprint(&out, "prove to be harmless.  To avoid possible automatic\n");
535
	Bprint(&out, "execution, we changed the content headers.\n");
536
	Bprint(&out, "The original header was:\n\n");
537
 
538
	/* print out original header lines */
539
	for(hl = p->hl; hl != nil; hl = hl->next)
540
		if(cistrncmp(s_to_c(hl->s), "content-", 8) == 0)
541
			Bprint(&out, "\t%s", s_to_c(hl->s));
542
	Bprint(&out, "--%s\n", s_to_c(boundary));
543
 
544
	/* change file name */
545
	if(p->filename)
546
		s_append(p->filename, ".suspect");
547
	else
548
		p->filename = s_copy("file.suspect");
549
 
550
	/* print out new header */
551
	Bprint(&out, "Content-Type: application/octet-stream\n");
552
	Bprint(&out, "Content-Disposition: attachment; filename=\"%s\"\n", s_to_c(p->filename));
553
	switch(p->encoding){
554
	case Enone:
555
		break;
556
	case Ebase64:
557
		Bprint(&out, "Content-Transfer-Encoding: base64\n");
558
		break;
559
	case Equoted:
560
		Bprint(&out, "Content-Transfer-Encoding: quoted-printable\n");
561
		break;
562
	}
563
 
564
fprint(2, "z\n");
565
	/* pass the body */
566
	np = passbody(p, 0);
567
 
568
fprint(2, "w\n");
569
	/* add the new boundary and the original terminator */
570
	Bprint(&out, "--%s--\n", s_to_c(boundary));
571
	if(np && np->boundary){
572
		cp = Brdline(&in, '\n');
573
		Bwrite(&out, cp, Blinelen(&in));
574
	}
575
 
576
fprint(2, "a %p\n", np);
577
	return np;
578
}
579
 
580
static int
581
isattribute(char **pp, char *attr)
582
{
583
	char *p;
584
	int n;
585
 
586
	n = strlen(attr);
587
	p = *pp;
588
	if(cistrncmp(p, attr, n) != 0)
589
		return 0;
590
	p += n;
591
	while(*p == ' ')
592
		p++;
593
	if(*p++ != '=')
594
		return 0;
595
	while(*p == ' ')
596
		p++;
597
	*pp = p;
598
	return 1;
599
}
600
 
601
/*
602
 *  parse content type header
603
 */
604
static void
605
ctype(Part *p, Hdef *h, char *cp)
606
{
607
	String *s;
608
 
609
	cp += h->len;
610
	cp = skipwhite(cp);
611
 
612
	p->type = s_new();
613
	cp = getstring(cp, p->type, 1);
614
	if(badtype(s_to_c(p->type)))
615
		p->badtype = 1;
616
 
617
	while(*cp){
618
		if(isattribute(&cp, "boundary")){
619
			s = s_new();
620
			cp = getstring(cp, s, 0);
621
			p->boundary = s_reset(p->boundary);
622
			s_append(p->boundary, "--");
623
			s_append(p->boundary, s_to_c(s));
624
			p->blen = s_len(p->boundary);
625
			s_free(s);
626
		} else if(cistrncmp(cp, "multipart", 9) == 0){
627
			/*
628
			 *  the first unbounded part of a multipart message,
629
			 *  the preamble, is not displayed or saved
630
			 */
631
		} else if(isattribute(&cp, "name")){
632
			setfilename(p, cp);
633
		} else if(isattribute(&cp, "charset")){
634
			if(p->charset == nil)
635
				p->charset = s_new();
636
			cp = getstring(cp, s_reset(p->charset), 0);
637
		}
638
 
639
		cp = skiptosemi(cp);
640
	}
641
}
642
 
643
/*
644
 *  parse content encoding header
645
 */
646
static void
647
cencoding(Part *m, Hdef *h, char *p)
648
{
649
	p += h->len;
650
	p = skipwhite(p);
651
	if(cistrncmp(p, "base64", 6) == 0)
652
		m->encoding = Ebase64;
653
	else if(cistrncmp(p, "quoted-printable", 16) == 0)
654
		m->encoding = Equoted;
655
}
656
 
657
/*
658
 *  parse content disposition header
659
 */
660
static void
661
cdisposition(Part *p, Hdef *h, char *cp)
662
{
663
	cp += h->len;
664
	cp = skipwhite(cp);
665
	while(*cp){
666
		if(cistrncmp(cp, "inline", 6) == 0){
667
			p->disposition = Dinline;
668
		} else if(cistrncmp(cp, "attachment", 10) == 0){
669
			p->disposition = Dfile;
670
		} else if(cistrncmp(cp, "filename=", 9) == 0){
671
			cp += 9;
672
			setfilename(p, cp);
673
		}
674
		cp = skiptosemi(cp);
675
	}
676
 
677
}
678
 
679
static void
680
setfilename(Part *p, char *name)
681
{
682
	if(p->filename == nil)
683
		p->filename = s_new();
684
	getstring(name, s_reset(p->filename), 0);
685
	p->filename = tokenconvert(p->filename);
686
	p->badfile = badfile(s_to_c(p->filename));
687
}
688
 
689
static char*
690
skipwhite(char *p)
691
{
692
	while(isspace(*p))
693
		p++;
694
	return p;
695
}
696
 
697
static char*
698
skiptosemi(char *p)
699
{
700
	while(*p && *p != ';')
701
		p++;
702
	while(*p == ';' || isspace(*p))
703
		p++;
704
	return p;
705
}
706
 
707
/*
708
 *  parse a possibly "'d string from a header.  A
709
 *  ';' terminates the string.
710
 */
711
static char*
712
getstring(char *p, String *s, int dolower)
713
{
714
	s = s_reset(s);
715
	p = skipwhite(p);
716
	if(*p == '"'){
717
		p++;
718
		for(;*p && *p != '"'; p++)
719
			if(dolower)
720
				s_putc(s, tolower(*p));
721
			else
722
				s_putc(s, *p);
723
		if(*p == '"')
724
			p++;
725
		s_terminate(s);
726
 
727
		return p;
728
	}
729
 
730
	for(; *p && !isspace(*p) && *p != ';'; p++)
731
		if(dolower)
732
			s_putc(s, tolower(*p));
733
		else
734
			s_putc(s, *p);
735
	s_terminate(s);
736
 
737
	return p;
738
}
739
 
740
static void
741
init_hdefs(void)
742
{
743
	Hdef *hd;
744
	static int already;
745
 
746
	if(already)
747
		return;
748
	already = 1;
749
 
750
	for(hd = hdefs; hd->type != nil; hd++)
751
		hd->len = strlen(hd->type);
752
}
753
 
754
/*
755
 *  create a new boundary
756
 */
757
static String*
758
mkboundary(void)
759
{
760
	char buf[32];
761
	int i;
762
	static int already;
763
 
764
	if(already == 0){
765
		srand((time(0)<<16)|getpid());
766
		already = 1;
767
	}
768
	strcpy(buf, "upas-");
769
	for(i = 5; i < sizeof(buf)-1; i++)
770
		buf[i] = 'a' + nrand(26);
771
	buf[i] = 0;
772
	return s_copy(buf);
773
}
774
 
775
/*
776
 *  skip blank lines till header
777
 */
778
static void
779
passnotheader(void)
780
{
781
	char *cp;
782
	int i, n;
783
 
784
	while((cp = Brdline(&in, '\n')) != nil){
785
		n = Blinelen(&in);
786
		for(i = 0; i < n-1; i++)
787
			if(cp[i] != ' ' && cp[i] != '\t' && cp[i] != '\r'){
788
				Bseek(&in, -n, 1);
789
				return;
790
			}
791
		Bwrite(&out, cp, n);
792
	}
793
}
794
 
795
/*
796
 *  pass unix header lines
797
 */
798
static void
799
passunixheader(void)
800
{
801
	char *p;
802
	int n;
803
 
804
	while((p = Brdline(&in, '\n')) != nil){
805
		n = Blinelen(&in);
806
		if(strncmp(p, "From ", 5) != 0){
807
			Bseek(&in, -n, 1);
808
			break;
809
		}
810
		Bwrite(&out, p, n);
811
	}
812
}
813
 
814
/*
815
 *  Read mime types
816
 */
817
static void
818
readmtypes(void)
819
{
820
	Biobuf *b;
821
	char *p;
822
	char *f[6];
823
	Mtype *m;
824
	Mtype **l;
825
 
826
	b = Bopen("/sys/lib/mimetype", OREAD);
827
	if(b == nil)
828
		return;
829
 
830
	l = &mtypes;
831
	while((p = Brdline(b, '\n')) != nil){
832
		if(*p == '#')
833
			continue;
834
		p[Blinelen(b)-1] = 0;
835
		if(tokenize(p, f, nelem(f)) < 5)
836
			continue;
837
		m = mallocz(sizeof *m, 1);
838
		if(m == nil)
839
			goto err;
840
		m->ext = strdup(f[0]);
841
		if(m->ext == 0)
842
			goto err;
843
		m->gtype = strdup(f[1]);
844
		if(m->gtype == 0)
845
			goto err;
846
		m->stype = strdup(f[2]);
847
		if(m->stype == 0)
848
			goto err;
849
		m->class = *f[4];
850
		*l = m;
851
		l = &(m->next);
852
	}
853
	Bterm(b);
854
	return;
855
err:
856
	if(m == nil)
857
		return;
858
	free(m->ext);
859
	free(m->gtype);
860
	free(m->stype);
861
	free(m);
862
	Bterm(b);
863
}
864
 
865
/*
866
 *  if the class is 'm' or 'y', accept it
867
 *  if the class is 'p' check a previous extension
868
 *  otherwise, filename is bad
869
 */
870
static int
871
badfile(char *name)
872
{
873
	char *p;
874
	Mtype *m;
875
	int rv;
876
 
877
	p = strrchr(name, '.');
878
	if(p == nil)
879
		return 0;
880
 
881
	for(m = mtypes; m != nil; m = m->next)
882
		if(cistrcmp(p, m->ext) == 0){
883
			switch(m->class){
884
			case 'm':
885
			case 'y':
886
				return 0;
887
			case 'p':
888
				*p = 0;
889
				rv = badfile(name);
890
				*p = '.';
891
				return rv;
892
			case 'r':
893
				return 2;
894
			}
895
		}
896
	return 1;
897
}
898
 
899
/*
900
 *  if the class is 'm' or 'y' or 'p', accept it
901
 *  otherwise, filename is bad
902
 */
903
static int
904
badtype(char *type)
905
{
906
	Mtype *m;
907
	char *s, *fix;
908
	int rv = 1;
909
 
910
	fix = s = strchr(type, '/');
911
	if(s != nil)
912
		*s++ = 0;
913
	else
914
		s = "-";
915
 
916
	for(m = mtypes; m != nil; m = m->next){
917
		if(cistrcmp(type, m->gtype) != 0)
918
			continue;
919
		if(cistrcmp(s, m->stype) != 0)
920
			continue;
921
		switch(m->class){
922
		case 'y':
923
		case 'p':
924
		case 'm':
925
			rv = 0;
926
			break;
927
		}
928
		break;
929
	}
930
 
931
	if(fix != nil)
932
		*fix = '/';
933
	return rv;
934
}
935
 
936
/* rfc2047 non-ascii */
937
typedef struct Charset Charset;
938
struct Charset {
939
	char *name;
940
	int len;
941
	int convert;
942
} charsets[] =
943
{
944
	{ "us-ascii",		8,	1, },
945
	{ "utf-8",		5,	0, },
946
	{ "iso-8859-1",		10,	1, },
947
};
948
 
949
/*
950
 *  convert to UTF if need be
951
 */
952
static String*
953
tokenconvert(String *t)
954
{
955
	String *s;
956
	char decoded[1024];
957
	char utfbuf[2*1024];
958
	int i, len;
959
	char *e;
960
	char *token;
961
 
962
	token = s_to_c(t);
963
	len = s_len(t);
964
 
965
	if(token[0] != '=' || token[1] != '?' ||
966
	   token[len-2] != '?' || token[len-1] != '=')
967
		goto err;
968
	e = token+len-2;
969
	token += 2;
970
 
971
	/* bail if we don't understand the character set */
972
	for(i = 0; i < nelem(charsets); i++)
973
		if(cistrncmp(charsets[i].name, token, charsets[i].len) == 0)
974
		if(token[charsets[i].len] == '?'){
975
			token += charsets[i].len + 1;
976
			break;
977
		}
978
	if(i >= nelem(charsets))
979
		goto err;
980
 
981
	/* bail if it doesn't fit */
982
	if(strlen(token) > sizeof(decoded)-1)
983
		goto err;
984
 
985
	/* bail if we don't understand the encoding */
986
	if(cistrncmp(token, "b?", 2) == 0){
987
		token += 2;
988
		len = dec64((uchar*)decoded, sizeof(decoded), token, e-token);
989
		decoded[len] = 0;
990
	} else if(cistrncmp(token, "q?", 2) == 0){
991
		token += 2;
992
		len = decquoted(decoded, token, e);
993
		if(len > 0 && decoded[len-1] == '\n')
994
			len--;
995
		decoded[len] = 0;
996
	} else
997
		goto err;
998
 
999
	s = nil;
1000
	switch(charsets[i].convert){
1001
	case 0:
1002
		s = s_copy(decoded);
1003
		break;
1004
	case 1:
1005
		s = s_new();
1006
		latin1toutf(utfbuf, decoded, decoded+len);
1007
		s_append(s, utfbuf);
1008
		break;
1009
	}
1010
 
1011
	return s;
1012
err:
1013
	return s_clone(t);
1014
}
1015
 
1016
/*
1017
 *  decode quoted
1018
 */
1019
enum
1020
{
1021
	Self=	1,
1022
	Hex=	2,
1023
};
1024
uchar	tableqp[256];
1025
 
1026
static void
1027
initquoted(void)
1028
{
1029
	int c;
1030
 
1031
	memset(tableqp, 0, 256);
1032
	for(c = ' '; c <= '<'; c++)
1033
		tableqp[c] = Self;
1034
	for(c = '>'; c <= '~'; c++)
1035
		tableqp[c] = Self;
1036
	tableqp['\t'] = Self;
1037
	tableqp['='] = Hex;
1038
}
1039
 
1040
static int
1041
hex2int(int x)
1042
{
1043
	if(x >= '0' && x <= '9')
1044
		return x - '0';
1045
	if(x >= 'A' && x <= 'F')
1046
		return (x - 'A') + 10;
1047
	if(x >= 'a' && x <= 'f')
1048
		return (x - 'a') + 10;
1049
	return 0;
1050
}
1051
 
1052
static char*
1053
decquotedline(char *out, char *in, char *e)
1054
{
1055
	int c, soft;
1056
 
1057
	/* dump trailing white space */
1058
	while(e >= in && (*e == ' ' || *e == '\t' || *e == '\r' || *e == '\n'))
1059
		e--;
1060
 
1061
	/* trailing '=' means no newline */
1062
	if(*e == '='){
1063
		soft = 1;
1064
		e--;
1065
	} else
1066
		soft = 0;
1067
 
1068
	while(in <= e){
1069
		c = (*in++) & 0xff;
1070
		switch(tableqp[c]){
1071
		case Self:
1072
			*out++ = c;
1073
			break;
1074
		case Hex:
1075
			c = hex2int(*in++)<<4;
1076
			c |= hex2int(*in++);
1077
			*out++ = c;
1078
			break;
1079
		}
1080
	}
1081
	if(!soft)
1082
		*out++ = '\n';
1083
	*out = 0;
1084
 
1085
	return out;
1086
}
1087
 
1088
static int
1089
decquoted(char *out, char *in, char *e)
1090
{
1091
	char *p, *nl;
1092
 
1093
	if(tableqp[' '] == 0)
1094
		initquoted();
1095
 
1096
	p = out;
1097
	while((nl = strchr(in, '\n')) != nil && nl < e){
1098
		p = decquotedline(p, in, nl);
1099
		in = nl + 1;
1100
	}
1101
	if(in < e)
1102
		p = decquotedline(p, in, e-1);
1103
 
1104
	/* make sure we end with a new line */
1105
	if(*(p-1) != '\n'){
1106
		*p++ = '\n';
1107
		*p = 0;
1108
	}
1109
 
1110
	return p - out;
1111
}
1112
 
1113
/* translate latin1 directly since it fits neatly in utf */
1114
static int
1115
latin1toutf(char *out, char *in, char *e)
1116
{
1117
	Rune r;
1118
	char *p;
1119
 
1120
	p = out;
1121
	for(; in < e; in++){
1122
		r = (*in) & 0xff;
1123
		p += runetochar(p, &r);
1124
	}
1125
	*p = 0;
1126
	return p - out;
1127
}