Blob


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>
8 enum {
9 Accept = 0xA,
10 Discard = 0xD,
11 };
13 Biobuf in;
14 Biobuf out;
16 typedef struct Mtype Mtype;
17 typedef struct Hdef Hdef;
18 typedef struct Hline Hline;
19 typedef struct Part Part;
21 static int badfile(char *name);
22 static int badtype(char *type);
23 static void ctype(Part*, Hdef*, char*);
24 static void cencoding(Part*, Hdef*, char*);
25 static void cdisposition(Part*, Hdef*, char*);
26 static int decquoted(char *out, char *in, char *e);
27 static char* getstring(char *p, String *s, int dolower);
28 static void init_hdefs(void);
29 static int isattribute(char **pp, char *attr);
30 static int latin1toutf(char *out, char *in, char *e);
31 static String* mkboundary(void);
32 static Part* part(Part *pp);
33 static Part* passbody(Part *p, int dobound);
34 static void passnotheader(void);
35 static void passunixheader(void);
36 static Part* problemchild(Part *p);
37 static void readheader(Part *p);
38 static Hline* readhl(void);
39 static void readmtypes(void);
40 static int save(Part *p, char *file);
41 static void setfilename(Part *p, char *name);
42 static char* skiptosemi(char *p);
43 static char* skipwhite(char *p);
44 static String* tokenconvert(String *t);
45 static void writeheader(Part *p, int);
47 enum
48 {
49 /* encodings */
50 Enone= 0,
51 Ebase64,
52 Equoted,
54 /* disposition possibilities */
55 Dnone= 0,
56 Dinline,
57 Dfile,
58 Dignore,
60 PAD64= '='
61 };
63 /*
64 * a message part; either the whole message or a subpart
65 */
66 struct Part
67 {
68 Part *pp; /* parent part */
69 Hline *hl; /* linked list of header lines */
70 int disposition;
71 int encoding;
72 int badfile;
73 int badtype;
74 String *boundary; /* boundary for multiparts */
75 int blen;
76 String *charset; /* character set */
77 String *type; /* content type */
78 String *filename; /* file name */
79 Biobuf *tmpbuf; /* diversion input buffer */
80 };
82 /*
83 * a (multi)line header
84 */
85 struct Hline
86 {
87 Hline *next;
88 String *s;
89 };
91 /*
92 * header definitions for parsing
93 */
94 struct Hdef
95 {
96 char *type;
97 void (*f)(Part*, Hdef*, char*);
98 int len;
99 };
101 Hdef hdefs[] =
103 { "content-type:", ctype, },
104 { "content-transfer-encoding:", cencoding, },
105 { "content-disposition:", cdisposition, },
106 { 0, }
107 };
109 /*
110 * acceptable content types and their extensions
111 */
112 struct Mtype {
113 Mtype *next;
114 char *ext; /* extension */
115 char *gtype; /* generic content type */
116 char *stype; /* specific content type */
117 char class;
118 };
119 Mtype *mtypes;
121 int justreject;
122 char *savefile;
124 void
125 usage(void)
127 fprint(2, "usage: upas/vf [-r] [-s savefile]\n");
128 exits("usage");
131 void
132 main(int argc, char **argv)
134 ARGBEGIN{
135 case 'r':
136 justreject = 1;
137 break;
138 case 's':
139 savefile = EARGF(usage());
140 break;
141 default:
142 usage();
143 }ARGEND;
145 if(argc)
146 usage();
148 Binit(&in, 0, OREAD);
149 Binit(&out, 1, OWRITE);
151 init_hdefs();
152 readmtypes();
154 /* pass through our standard 'From ' line */
155 passunixheader();
157 /* parse with the top level part */
158 part(nil);
160 exits(0);
163 void
164 refuse(void)
166 postnote(PNGROUP, getpid(), "mail refused: we don't accept executable attachments");
167 exits("mail refused: we don't accept executable attachments");
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)
178 Part *p, *np;
180 p = mallocz(sizeof *p, 1);
181 p->pp = pp;
182 readheader(p);
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;
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", p->type?s_to_c(p->type):"?",
225 p->filename?s_to_c(p->filename):"?");
226 fprint(2, "The mail contained an executable attachment.\n");
227 fprint(2, "We refuse all mail containing such.\n");
228 refuse();
230 np = problemchild(p);
231 if(np != p)
232 return np;
233 /* if problemchild returns p, it turns out p is okay: fall thru */
235 writeheader(p, 1);
236 return passbody(p, 1);
241 /*
242 * read and parse a complete header
243 */
244 static void
245 readheader(Part *p)
247 Hline *hl, **l;
248 Hdef *hd;
250 l = &p->hl;
251 for(;;){
252 hl = readhl();
253 if(hl == nil)
254 break;
255 *l = hl;
256 l = &hl->next;
258 for(hd = hdefs; hd->type != nil; hd++){
259 if(cistrncmp(s_to_c(hl->s), hd->type, hd->len) == 0){
260 (*hd->f)(p, hd, s_to_c(hl->s));
261 break;
267 /*
268 * read a possibly multiline header line
269 */
270 static Hline*
271 readhl(void)
273 Hline *hl;
274 String *s;
275 char *p;
276 int n;
278 p = Brdline(&in, '\n');
279 if(p == nil)
280 return nil;
281 n = Blinelen(&in);
282 if(memchr(p, ':', n) == nil){
283 Bseek(&in, -n, 1);
284 return nil;
286 s = s_nappend(s_new(), p, n);
287 for(;;){
288 p = Brdline(&in, '\n');
289 if(p == nil)
290 break;
291 n = Blinelen(&in);
292 if(*p != ' ' && *p != '\t'){
293 Bseek(&in, -n, 1);
294 break;
296 s = s_nappend(s, p, n);
298 hl = malloc(sizeof *hl);
299 hl->s = s;
300 hl->next = nil;
301 return hl;
304 /*
305 * write out a complete header
306 */
307 static void
308 writeheader(Part *p, int xfree)
310 Hline *hl, *next;
312 for(hl = p->hl; hl != nil; hl = next){
313 Bprint(&out, "%s", s_to_c(hl->s));
314 if(xfree)
315 s_free(hl->s);
316 next = hl->next;
317 if(xfree)
318 free(hl);
320 if(xfree)
321 p->hl = nil;
324 /*
325 * pass a body through. return if we hit one of our ancestors'
326 * boundaries or EOF. if we hit a boundary, return a pointer to
327 * that ancestor. if we hit EOF, return nil.
328 */
329 static Part*
330 passbody(Part *p, int dobound)
332 Part *pp;
333 Biobuf *b;
334 char *cp;
336 for(;;){
337 if(p->tmpbuf){
338 b = p->tmpbuf;
339 cp = Brdline(b, '\n');
340 if(cp == nil){
341 Bterm(b);
342 p->tmpbuf = nil;
343 goto Stdin;
345 }else{
346 Stdin:
347 b = &in;
348 cp = Brdline(b, '\n');
350 if(cp == nil)
351 return nil;
352 for(pp = p; pp != nil; pp = pp->pp)
353 if(pp->boundary != nil
354 && strncmp(cp, s_to_c(pp->boundary), pp->blen) == 0){
355 if(dobound)
356 Bwrite(&out, cp, Blinelen(b));
357 else
358 Bseek(b, -Blinelen(b), 1);
359 return pp;
361 Bwrite(&out, cp, Blinelen(b));
363 return nil;
366 /*
367 * save the message somewhere
368 */
369 static vlong bodyoff; /* clumsy hack */
370 static int
371 save(Part *p, char *file)
373 int fd;
374 char *cp;
376 Bterm(&out);
377 memset(&out, 0, sizeof(out));
379 fd = open(file, OWRITE);
380 if(fd < 0)
381 return -1;
382 seek(fd, 0, 2);
383 Binit(&out, fd, OWRITE);
384 cp = ctime(time(0));
385 cp[28] = 0;
386 Bprint(&out, "From virusfilter %s\n", cp);
387 writeheader(p, 0);
388 bodyoff = Boffset(&out);
389 passbody(p, 1);
390 Bprint(&out, "\n");
391 Bterm(&out);
392 close(fd);
394 memset(&out, 0, sizeof out);
395 Binit(&out, 1, OWRITE);
396 return 0;
399 /*
400 * write to a file but save the fd for passbody.
401 */
402 static char*
403 savetmp(Part *p)
405 char buf[40], *name;
406 int fd;
408 strcpy(buf, "/var/tmp/vf.XXXXXXXXXXX");
409 if((fd = mkstemp(buf)) < 0){
410 fprint(2, "error creating temporary file: %r\n");
411 refuse();
413 name = buf;
414 close(fd);
415 if(save(p, name) < 0){
416 fprint(2, "error saving temporary file: %r\n");
417 refuse();
419 if(p->tmpbuf){
420 fprint(2, "error in savetmp: already have tmp file!\n");
421 refuse();
423 p->tmpbuf = Bopen(name, OREAD|ORCLOSE);
424 if(p->tmpbuf == nil){
425 fprint(2, "error reading tempoary file: %r\n");
426 refuse();
428 Bseek(p->tmpbuf, bodyoff, 0);
429 return strdup(name);
432 /*
433 * Run the external checker to do content-based checks.
434 */
435 static int
436 runchecker(Part *p)
438 int pid;
439 char *name;
440 Waitmsg *w;
441 static char *val;
443 if(val == nil)
444 val = unsharp("#9/mail/lib/validateattachment");
445 if(val == nil || access(val, AEXEC) < 0)
446 return 0;
448 name = savetmp(p);
449 fprint(2, "run checker %s\n", name);
450 switch(pid = fork()){
451 case -1:
452 sysfatal("fork: %r");
453 case 0:
454 dup(2, 1);
455 execl(val, "validateattachment", name, nil);
456 _exits("exec failed");
459 /*
460 * Okay to return on error - will let mail through but wrapped.
461 */
462 w = wait();
463 if(w == nil){
464 syslog(0, "mail", "vf wait failed: %r");
465 return 0;
467 if(w->pid != pid){
468 syslog(0, "mail", "vf wrong pid %d != %d", w->pid, pid);
469 return 0;
471 if(p->filename)
472 name = s_to_c(p->filename);
473 if(atoi(w->msg) == Discard){
474 syslog(0, "mail", "vf validateattachment rejected %s", name);
475 refuse();
477 if(atoi(w->msg) == Accept){
478 syslog(0, "mail", "vf validateattachment accepted %s", name);
479 return 1;
481 free(w);
482 return 0;
485 /*
486 * emit a multipart Part that explains the problem
487 */
488 static Part*
489 problemchild(Part *p)
491 Part *np;
492 Hline *hl;
493 String *boundary;
494 char *cp;
496 /*
497 * We don't know whether the attachment is okay.
498 * If there's an external checker, let it have a crack at it.
499 */
500 if(runchecker(p) > 0)
501 return p;
503 if(justreject)
504 return p;
506 syslog(0, "mail", "vf wrapped %s %s", p->type?s_to_c(p->type):"?",
507 p->filename?s_to_c(p->filename):"?");
509 boundary = mkboundary();
510 /* print out non-mime headers */
511 for(hl = p->hl; hl != nil; hl = hl->next)
512 if(cistrncmp(s_to_c(hl->s), "content-", 8) != 0)
513 Bprint(&out, "%s", s_to_c(hl->s));
515 /* add in our own multipart headers and message */
516 Bprint(&out, "Content-Type: multipart/mixed;\n");
517 Bprint(&out, "\tboundary=\"%s\"\n", s_to_c(boundary));
518 Bprint(&out, "Content-Disposition: inline\n");
519 Bprint(&out, "\n");
520 Bprint(&out, "This is a multi-part message in MIME format.\n");
521 Bprint(&out, "--%s\n", s_to_c(boundary));
522 Bprint(&out, "Content-Disposition: inline\n");
523 Bprint(&out, "Content-Type: text/plain; charset=\"US-ASCII\"\n");
524 Bprint(&out, "Content-Transfer-Encoding: 7bit\n");
525 Bprint(&out, "\n");
526 Bprint(&out, "from postmaster@%s:\n", sysname());
527 Bprint(&out, "The following attachment had content that we can't\n");
528 Bprint(&out, "prove to be harmless. To avoid possible automatic\n");
529 Bprint(&out, "execution, we changed the content headers.\n");
530 Bprint(&out, "The original header was:\n\n");
532 /* print out original header lines */
533 for(hl = p->hl; hl != nil; hl = hl->next)
534 if(cistrncmp(s_to_c(hl->s), "content-", 8) == 0)
535 Bprint(&out, "\t%s", s_to_c(hl->s));
536 Bprint(&out, "--%s\n", s_to_c(boundary));
538 /* change file name */
539 if(p->filename)
540 s_append(p->filename, ".suspect");
541 else
542 p->filename = s_copy("file.suspect");
544 /* print out new header */
545 Bprint(&out, "Content-Type: application/octet-stream\n");
546 Bprint(&out, "Content-Disposition: attachment; filename=\"%s\"\n", s_to_c(p->filename));
547 switch(p->encoding){
548 case Enone:
549 break;
550 case Ebase64:
551 Bprint(&out, "Content-Transfer-Encoding: base64\n");
552 break;
553 case Equoted:
554 Bprint(&out, "Content-Transfer-Encoding: quoted-printable\n");
555 break;
558 /* pass the body */
559 np = passbody(p, 0);
561 /* add the new boundary and the original terminator */
562 Bprint(&out, "--%s--\n", s_to_c(boundary));
563 if(np && np->boundary){
564 cp = Brdline(&in, '\n');
565 Bwrite(&out, cp, Blinelen(&in));
568 return np;
571 static int
572 isattribute(char **pp, char *attr)
574 char *p;
575 int n;
577 n = strlen(attr);
578 p = *pp;
579 if(cistrncmp(p, attr, n) != 0)
580 return 0;
581 p += n;
582 while(*p == ' ')
583 p++;
584 if(*p++ != '=')
585 return 0;
586 while(*p == ' ')
587 p++;
588 *pp = p;
589 return 1;
592 /*
593 * parse content type header
594 */
595 static void
596 ctype(Part *p, Hdef *h, char *cp)
598 String *s;
600 cp += h->len;
601 cp = skipwhite(cp);
603 p->type = s_new();
604 cp = getstring(cp, p->type, 1);
605 if(badtype(s_to_c(p->type)))
606 p->badtype = 1;
608 while(*cp){
609 if(isattribute(&cp, "boundary")){
610 s = s_new();
611 cp = getstring(cp, s, 0);
612 p->boundary = s_reset(p->boundary);
613 s_append(p->boundary, "--");
614 s_append(p->boundary, s_to_c(s));
615 p->blen = s_len(p->boundary);
616 s_free(s);
617 } else if(cistrncmp(cp, "multipart", 9) == 0){
618 /*
619 * the first unbounded part of a multipart message,
620 * the preamble, is not displayed or saved
621 */
622 } else if(isattribute(&cp, "name")){
623 setfilename(p, cp);
624 } else if(isattribute(&cp, "charset")){
625 if(p->charset == nil)
626 p->charset = s_new();
627 cp = getstring(cp, s_reset(p->charset), 0);
630 cp = skiptosemi(cp);
634 /*
635 * parse content encoding header
636 */
637 static void
638 cencoding(Part *m, Hdef *h, char *p)
640 p += h->len;
641 p = skipwhite(p);
642 if(cistrncmp(p, "base64", 6) == 0)
643 m->encoding = Ebase64;
644 else if(cistrncmp(p, "quoted-printable", 16) == 0)
645 m->encoding = Equoted;
648 /*
649 * parse content disposition header
650 */
651 static void
652 cdisposition(Part *p, Hdef *h, char *cp)
654 cp += h->len;
655 cp = skipwhite(cp);
656 while(*cp){
657 if(cistrncmp(cp, "inline", 6) == 0){
658 p->disposition = Dinline;
659 } else if(cistrncmp(cp, "attachment", 10) == 0){
660 p->disposition = Dfile;
661 } else if(cistrncmp(cp, "filename=", 9) == 0){
662 cp += 9;
663 setfilename(p, cp);
665 cp = skiptosemi(cp);
670 static void
671 setfilename(Part *p, char *name)
673 if(p->filename == nil)
674 p->filename = s_new();
675 getstring(name, s_reset(p->filename), 0);
676 p->filename = tokenconvert(p->filename);
677 p->badfile = badfile(s_to_c(p->filename));
680 static char*
681 skipwhite(char *p)
683 while(isspace(*p))
684 p++;
685 return p;
688 static char*
689 skiptosemi(char *p)
691 while(*p && *p != ';')
692 p++;
693 while(*p == ';' || isspace(*p))
694 p++;
695 return p;
698 /*
699 * parse a possibly "'d string from a header. A
700 * ';' terminates the string.
701 */
702 static char*
703 getstring(char *p, String *s, int dolower)
705 s = s_reset(s);
706 p = skipwhite(p);
707 if(*p == '"'){
708 p++;
709 for(;*p && *p != '"'; p++)
710 if(dolower)
711 s_putc(s, tolower(*p));
712 else
713 s_putc(s, *p);
714 if(*p == '"')
715 p++;
716 s_terminate(s);
718 return p;
721 for(; *p && !isspace(*p) && *p != ';'; p++)
722 if(dolower)
723 s_putc(s, tolower(*p));
724 else
725 s_putc(s, *p);
726 s_terminate(s);
728 return p;
731 static void
732 init_hdefs(void)
734 Hdef *hd;
735 static int already;
737 if(already)
738 return;
739 already = 1;
741 for(hd = hdefs; hd->type != nil; hd++)
742 hd->len = strlen(hd->type);
745 /*
746 * create a new boundary
747 */
748 static String*
749 mkboundary(void)
751 char buf[32];
752 int i;
753 static int already;
755 if(already == 0){
756 srand((time(0)<<16)|getpid());
757 already = 1;
759 strcpy(buf, "upas-");
760 for(i = 5; i < sizeof(buf)-1; i++)
761 buf[i] = 'a' + nrand(26);
762 buf[i] = 0;
763 return s_copy(buf);
766 /*
767 * skip blank lines till header
768 */
769 static void
770 passnotheader(void)
772 char *cp;
773 int i, n;
775 while((cp = Brdline(&in, '\n')) != nil){
776 n = Blinelen(&in);
777 for(i = 0; i < n-1; i++)
778 if(cp[i] != ' ' && cp[i] != '\t' && cp[i] != '\r'){
779 Bseek(&in, -n, 1);
780 return;
782 Bwrite(&out, cp, n);
786 /*
787 * pass unix header lines
788 */
789 static void
790 passunixheader(void)
792 char *p;
793 int n;
795 while((p = Brdline(&in, '\n')) != nil){
796 n = Blinelen(&in);
797 if(strncmp(p, "From ", 5) != 0){
798 Bseek(&in, -n, 1);
799 break;
801 Bwrite(&out, p, n);
805 /*
806 * Read mime types
807 */
808 static void
809 readmtypes(void)
811 Biobuf *b;
812 char *p;
813 char *f[6];
814 Mtype *m;
815 Mtype **l;
817 b = Bopen(unsharp("#9/lib/mimetype"), OREAD);
818 if(b == nil)
819 return;
821 l = &mtypes;
822 while((p = Brdline(b, '\n')) != nil){
823 if(*p == '#')
824 continue;
825 p[Blinelen(b)-1] = 0;
826 if(tokenize(p, f, nelem(f)) < 5)
827 continue;
828 m = mallocz(sizeof *m, 1);
829 if(m == nil)
830 goto err;
831 m->ext = strdup(f[0]);
832 if(m->ext == 0)
833 goto err;
834 m->gtype = strdup(f[1]);
835 if(m->gtype == 0)
836 goto err;
837 m->stype = strdup(f[2]);
838 if(m->stype == 0)
839 goto err;
840 m->class = *f[4];
841 *l = m;
842 l = &(m->next);
844 Bterm(b);
845 return;
846 err:
847 if(m == nil)
848 return;
849 free(m->ext);
850 free(m->gtype);
851 free(m->stype);
852 free(m);
853 Bterm(b);
856 /*
857 * if the class is 'm' or 'y', accept it
858 * if the class is 'p' check a previous extension
859 * otherwise, filename is bad
860 */
861 static int
862 badfile(char *name)
864 char *p;
865 Mtype *m;
866 int rv;
868 p = strrchr(name, '.');
869 if(p == nil)
870 return 0;
872 for(m = mtypes; m != nil; m = m->next)
873 if(cistrcmp(p, m->ext) == 0){
874 switch(m->class){
875 case 'm':
876 case 'y':
877 return 0;
878 case 'p':
879 *p = 0;
880 rv = badfile(name);
881 *p = '.';
882 return rv;
883 case 'r':
884 return 2;
887 return 1;
890 /*
891 * if the class is 'm' or 'y' or 'p', accept it
892 * otherwise, filename is bad
893 */
894 static int
895 badtype(char *type)
897 Mtype *m;
898 char *s, *fix;
899 int rv = 1;
901 fix = s = strchr(type, '/');
902 if(s != nil)
903 *s++ = 0;
904 else
905 s = "-";
907 for(m = mtypes; m != nil; m = m->next){
908 if(cistrcmp(type, m->gtype) != 0)
909 continue;
910 if(cistrcmp(s, m->stype) != 0)
911 continue;
912 switch(m->class){
913 case 'y':
914 case 'p':
915 case 'm':
916 rv = 0;
917 break;
919 break;
922 if(fix != nil)
923 *fix = '/';
924 return rv;
927 /* rfc2047 non-ascii */
928 typedef struct Charset Charset;
929 struct Charset {
930 char *name;
931 int len;
932 int convert;
933 } charsets[] =
935 { "us-ascii", 8, 1, },
936 { "utf-8", 5, 0, },
937 { "iso-8859-1", 10, 1, }
938 };
940 /*
941 * convert to UTF if need be
942 */
943 static String*
944 tokenconvert(String *t)
946 String *s;
947 char decoded[1024];
948 char utfbuf[2*1024];
949 int i, len;
950 char *e;
951 char *token;
953 token = s_to_c(t);
954 len = s_len(t);
956 if(token[0] != '=' || token[1] != '?' ||
957 token[len-2] != '?' || token[len-1] != '=')
958 goto err;
959 e = token+len-2;
960 token += 2;
962 /* bail if we don't understand the character set */
963 for(i = 0; i < nelem(charsets); i++)
964 if(cistrncmp(charsets[i].name, token, charsets[i].len) == 0)
965 if(token[charsets[i].len] == '?'){
966 token += charsets[i].len + 1;
967 break;
969 if(i >= nelem(charsets))
970 goto err;
972 /* bail if it doesn't fit */
973 if(strlen(token) > sizeof(decoded)-1)
974 goto err;
976 /* bail if we don't understand the encoding */
977 if(cistrncmp(token, "b?", 2) == 0){
978 token += 2;
979 len = dec64((uchar*)decoded, sizeof(decoded), token, e-token);
980 decoded[len] = 0;
981 } else if(cistrncmp(token, "q?", 2) == 0){
982 token += 2;
983 len = decquoted(decoded, token, e);
984 if(len > 0 && decoded[len-1] == '\n')
985 len--;
986 decoded[len] = 0;
987 } else
988 goto err;
990 s = nil;
991 switch(charsets[i].convert){
992 case 0:
993 s = s_copy(decoded);
994 break;
995 case 1:
996 s = s_new();
997 latin1toutf(utfbuf, decoded, decoded+len);
998 s_append(s, utfbuf);
999 break;
1002 return s;
1003 err:
1004 return s_clone(t);
1008 * decode quoted
1010 enum
1012 Self= 1,
1013 Hex= 2
1015 uchar tableqp[256];
1017 static void
1018 initquoted(void)
1020 int c;
1022 memset(tableqp, 0, 256);
1023 for(c = ' '; c <= '<'; c++)
1024 tableqp[c] = Self;
1025 for(c = '>'; c <= '~'; c++)
1026 tableqp[c] = Self;
1027 tableqp['\t'] = Self;
1028 tableqp['='] = Hex;
1031 static int
1032 hex2int(int x)
1034 if(x >= '0' && x <= '9')
1035 return x - '0';
1036 if(x >= 'A' && x <= 'F')
1037 return (x - 'A') + 10;
1038 if(x >= 'a' && x <= 'f')
1039 return (x - 'a') + 10;
1040 return 0;
1043 static char*
1044 decquotedline(char *out, char *in, char *e)
1046 int c, soft;
1048 /* dump trailing white space */
1049 while(e >= in && (*e == ' ' || *e == '\t' || *e == '\r' || *e == '\n'))
1050 e--;
1052 /* trailing '=' means no newline */
1053 if(*e == '='){
1054 soft = 1;
1055 e--;
1056 } else
1057 soft = 0;
1059 while(in <= e){
1060 c = (*in++) & 0xff;
1061 switch(tableqp[c]){
1062 case Self:
1063 *out++ = c;
1064 break;
1065 case Hex:
1066 c = hex2int(*in++)<<4;
1067 c |= hex2int(*in++);
1068 *out++ = c;
1069 break;
1072 if(!soft)
1073 *out++ = '\n';
1074 *out = 0;
1076 return out;
1079 static int
1080 decquoted(char *out, char *in, char *e)
1082 char *p, *nl;
1084 if(tableqp[' '] == 0)
1085 initquoted();
1087 p = out;
1088 while((nl = strchr(in, '\n')) != nil && nl < e){
1089 p = decquotedline(p, in, nl);
1090 in = nl + 1;
1092 if(in < e)
1093 p = decquotedline(p, in, e-1);
1095 /* make sure we end with a new line */
1096 if(*(p-1) != '\n'){
1097 *p++ = '\n';
1098 *p = 0;
1101 return p - out;
1104 /* translate latin1 directly since it fits neatly in utf */
1105 static int
1106 latin1toutf(char *out, char *in, char *e)
1108 Rune r;
1109 char *p;
1111 p = out;
1112 for(; in < e; in++){
1113 r = (*in) & 0xff;
1114 p += runetochar(p, &r);
1116 *p = 0;
1117 return p - out;