vf.c 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980
  1. #include "common.h"
  2. #include <ctype.h>
  3. Biobuf in;
  4. Biobuf out;
  5. typedef struct Mtype Mtype;
  6. typedef struct Hdef Hdef;
  7. typedef struct Hline Hline;
  8. typedef struct Part Part;
  9. static int badfile(char *name);
  10. static int badtype(char *type);
  11. static void ctype(Part*, Hdef*, char*);
  12. static void cencoding(Part*, Hdef*, char*);
  13. static void cdisposition(Part*, Hdef*, char*);
  14. static int decquoted(char *out, char *in, char *e);
  15. static char* getstring(char *p, String *s, int dolower);
  16. static void init_hdefs(void);
  17. static int isattribute(char **pp, char *attr);
  18. static int latin1toutf(char *out, char *in, char *e);
  19. static String* mkboundary(void);
  20. static Part* part(Part *pp);
  21. static Part* passbody(Part *p, int dobound);
  22. static void passnotheader(void);
  23. static void passunixheader(void);
  24. static Part* problemchild(Part *p);
  25. static void readheader(Part *p);
  26. static Hline* readhl(void);
  27. static void readmtypes(void);
  28. static void save(Part *p);
  29. static void setfilename(Part *p, char *name);
  30. static char* skiptosemi(char *p);
  31. static char* skipwhite(char *p);
  32. static String* tokenconvert(String *t);
  33. static void writeheader(Part *p);
  34. enum
  35. {
  36. // encodings
  37. Enone= 0,
  38. Ebase64,
  39. Equoted,
  40. // disposition possibilities
  41. Dnone= 0,
  42. Dinline,
  43. Dfile,
  44. Dignore,
  45. PAD64= '=',
  46. };
  47. /*
  48. * a message part; either the whole message or a subpart
  49. */
  50. struct Part
  51. {
  52. Part *pp; /* parent part */
  53. Hline *hl; /* linked list of header lines */
  54. int disposition;
  55. int encoding;
  56. int badfile;
  57. int badtype;
  58. String *boundary; /* boundary for multiparts */
  59. int blen;
  60. String *charset; /* character set */
  61. String *type; /* content type */
  62. String *filename; /* content type */
  63. };
  64. /*
  65. * a (multi)line header
  66. */
  67. struct Hline
  68. {
  69. Hline *next;
  70. String *s;
  71. };
  72. /*
  73. * header definitions for parsing
  74. */
  75. struct Hdef
  76. {
  77. char *type;
  78. void (*f)(Part*, Hdef*, char*);
  79. int len;
  80. };
  81. Hdef hdefs[] =
  82. {
  83. { "content-type:", ctype, },
  84. { "content-transfer-encoding:", cencoding, },
  85. { "content-disposition:", cdisposition, },
  86. { 0, },
  87. };
  88. /*
  89. * acceptable content types and their extensions
  90. */
  91. struct Mtype {
  92. Mtype *next;
  93. char *ext; /* extension */
  94. char *gtype; /* generic content type */
  95. char *stype; /* specific content type */
  96. char class;
  97. };
  98. Mtype *mtypes;
  99. int justreject;
  100. char *savefile;
  101. /*
  102. * this is a filter that changes mime types and names of
  103. * suspect attachments.
  104. *
  105. */
  106. void
  107. main(int argc, char **argv)
  108. {
  109. ARGBEGIN{
  110. case 'r':
  111. justreject = 1;
  112. break;
  113. case 's':
  114. savefile = ARGF();
  115. if(savefile == nil)
  116. exits("usage");
  117. break;
  118. }ARGEND;
  119. Binit(&in, 0, OREAD);
  120. Binit(&out, 1, OWRITE);
  121. init_hdefs();
  122. readmtypes();
  123. /* pass through our standard 'From ' line */
  124. passunixheader();
  125. /* parse with the top level part */
  126. part(nil);
  127. exits(0);
  128. }
  129. /*
  130. * parse a part; returns the ancestor whose boundary terminated
  131. * this part or nil on EOF.
  132. */
  133. static Part*
  134. part(Part *pp)
  135. {
  136. Part *p, *np;
  137. p = mallocz(sizeof *p, 1);
  138. p->pp = pp;
  139. readheader(p);
  140. if(p->boundary != nil){
  141. /* the format of a multipart part is always:
  142. * header
  143. * null or ignored body
  144. * boundary
  145. * header
  146. * body
  147. * boundary
  148. * ...
  149. */
  150. writeheader(p);
  151. np = passbody(p, 1);
  152. if(np != p)
  153. return np;
  154. for(;;){
  155. np = part(p);
  156. if(np != p)
  157. return np;
  158. }
  159. } else {
  160. /* no boundary */
  161. /* may still be multipart if this is a forwarded message */
  162. if(p->type && cistrcmp(s_to_c(p->type), "message/rfc822") == 0){
  163. /* the format of forwarded message is:
  164. * header
  165. * header
  166. * body
  167. */
  168. writeheader(p);
  169. passnotheader();
  170. return part(p);
  171. } else {
  172. /* This is the meat. This may be an executable.
  173. * if so, wrap it and change its type
  174. */
  175. if(p->badtype || p->badfile){
  176. if(p->badfile == 2){
  177. if(savefile != nil)
  178. save(p);
  179. syslog(0, "vf", "vf rejected %s %s", p->type?s_to_c(p->type):"?",
  180. p->filename?s_to_c(p->filename):"?");
  181. fprint(2, "The mail contained an executable attachment.\n");
  182. fprint(2, "We refuse all mail containing such.\n");
  183. postnote(PNGROUP, getpid(), "mail refused: we don't accept executable attachments");
  184. exits("mail refused: we don't accept executable attachments");
  185. }
  186. return problemchild(p);
  187. } else {
  188. writeheader(p);
  189. return passbody(p, 1);
  190. }
  191. }
  192. }
  193. }
  194. /*
  195. * read and parse a complete header
  196. */
  197. static void
  198. readheader(Part *p)
  199. {
  200. Hline *hl, **l;
  201. Hdef *hd;
  202. l = &p->hl;
  203. for(;;){
  204. hl = readhl();
  205. if(hl == nil)
  206. break;
  207. *l = hl;
  208. l = &hl->next;
  209. for(hd = hdefs; hd->type != nil; hd++){
  210. if(cistrncmp(s_to_c(hl->s), hd->type, hd->len) == 0){
  211. (*hd->f)(p, hd, s_to_c(hl->s));
  212. break;
  213. }
  214. }
  215. }
  216. }
  217. /*
  218. * read a possibly multiline header line
  219. */
  220. static Hline*
  221. readhl(void)
  222. {
  223. Hline *hl;
  224. String *s;
  225. char *p;
  226. int n;
  227. p = Brdline(&in, '\n');
  228. if(p == nil)
  229. return nil;
  230. n = Blinelen(&in);
  231. if(memchr(p, ':', n) == nil){
  232. Bseek(&in, -n, 1);
  233. return nil;
  234. }
  235. s = s_nappend(s_new(), p, n);
  236. for(;;){
  237. p = Brdline(&in, '\n');
  238. if(p == nil)
  239. break;
  240. n = Blinelen(&in);
  241. if(*p != ' ' && *p != '\t'){
  242. Bseek(&in, -n, 1);
  243. break;
  244. }
  245. s = s_nappend(s, p, n);
  246. }
  247. hl = malloc(sizeof *hl);
  248. hl->s = s;
  249. hl->next = nil;
  250. return hl;
  251. }
  252. /*
  253. * write out a complete header
  254. */
  255. static void
  256. writeheader(Part *p)
  257. {
  258. Hline *hl, *next;
  259. for(hl = p->hl; hl != nil; hl = next){
  260. Bprint(&out, "%s", s_to_c(hl->s));
  261. s_free(hl->s);
  262. next = hl->next;
  263. free(hl);
  264. }
  265. p->hl = nil;
  266. }
  267. /*
  268. * pass a body through. return if we hit one of our ancestors'
  269. * boundaries or EOF. if we hit a boundary, return a pointer to
  270. * that ancestor. if we hit EOF, return nil.
  271. */
  272. static Part*
  273. passbody(Part *p, int dobound)
  274. {
  275. Part *pp;
  276. char *cp;
  277. for(;;){
  278. cp = Brdline(&in, '\n');
  279. if(cp == nil)
  280. return nil;
  281. for(pp = p; pp != nil; pp = pp->pp)
  282. if(pp->boundary != nil
  283. && strncmp(cp, s_to_c(pp->boundary), pp->blen) == 0){
  284. if(dobound)
  285. Bwrite(&out, cp, Blinelen(&in));
  286. else
  287. Bseek(&in, -Blinelen(&in), 1);
  288. return pp;
  289. }
  290. Bwrite(&out, cp, Blinelen(&in));
  291. }
  292. return nil;
  293. }
  294. /*
  295. * save the message somewhere
  296. */
  297. static void
  298. save(Part *p)
  299. {
  300. int fd;
  301. char *cp;
  302. Bterm(&out);
  303. memset(&out, 0, sizeof(out));
  304. fd = open(savefile, OWRITE);
  305. if(fd < 0)
  306. return;
  307. seek(fd, 0, 2);
  308. Binit(&out, fd, OWRITE);
  309. cp = ctime(time(0));
  310. cp[28] = 0;
  311. Bprint(&out, "From virusfilter %s\n", cp);
  312. writeheader(p);
  313. passbody(p, 1);
  314. Bprint(&out, "\n");
  315. Bterm(&out);
  316. close(fd);
  317. }
  318. /*
  319. * emit a multipart Part that explains the problem
  320. */
  321. static Part*
  322. problemchild(Part *p)
  323. {
  324. Part *np;
  325. Hline *hl;
  326. String *boundary;
  327. char *cp;
  328. syslog(0, "mail", "vf wrapped %s %s", p->type?s_to_c(p->type):"?",
  329. p->filename?s_to_c(p->filename):"?");
  330. boundary = mkboundary();
  331. /* print out non-mime headers */
  332. for(hl = p->hl; hl != nil; hl = hl->next)
  333. if(cistrncmp(s_to_c(hl->s), "content-", 8) != 0)
  334. Bprint(&out, "%s", s_to_c(hl->s));
  335. /* add in out own multipart headers and message */
  336. Bprint(&out, "Content-Type: multipart/mixed;\n");
  337. Bprint(&out, "\tboundary=\"%s\"\n", s_to_c(boundary));
  338. Bprint(&out, "Content-Disposition: inline\n");
  339. Bprint(&out, "\n");
  340. Bprint(&out, "This is a multi-part message in MIME format.\n");
  341. Bprint(&out, "--%s\n", s_to_c(boundary));
  342. Bprint(&out, "Content-Disposition: inline\n");
  343. Bprint(&out, "Content-Type: text/plain; charset=\"US-ASCII\"\n");
  344. Bprint(&out, "Content-Transfer-Encoding: 7bit\n");
  345. Bprint(&out, "\n");
  346. Bprint(&out, "The following attachment had content that we can't\n");
  347. Bprint(&out, "prove to be harmless. To avoid possible automatic\n");
  348. Bprint(&out, "execution, we changed the content headers.\n");
  349. Bprint(&out, "The original header was:\n\n");
  350. /* print out original header lines */
  351. for(hl = p->hl; hl != nil; hl = hl->next)
  352. if(cistrncmp(s_to_c(hl->s), "content-", 8) == 0)
  353. Bprint(&out, "\t%s", s_to_c(hl->s));
  354. Bprint(&out, "--%s\n", s_to_c(boundary));
  355. /* change file name */
  356. if(p->filename)
  357. s_append(p->filename, ".suspect");
  358. else
  359. p->filename = s_copy("file.suspect");
  360. /* print out new header */
  361. Bprint(&out, "Content-Type: application/octet-stream\n");
  362. Bprint(&out, "Content-Disposition: attachment; filename=\"%s\"\n", s_to_c(p->filename));
  363. switch(p->encoding){
  364. case Enone:
  365. break;
  366. case Ebase64:
  367. Bprint(&out, "Content-Transfer-Encoding: base64\n");
  368. break;
  369. case Equoted:
  370. Bprint(&out, "Content-Transfer-Encoding: quoted-printable\n");
  371. break;
  372. }
  373. /* pass the body */
  374. np = passbody(p, 0);
  375. /* add the new boundary and the original terminator */
  376. Bprint(&out, "--%s--\n", s_to_c(boundary));
  377. if(np && np->boundary){
  378. cp = Brdline(&in, '\n');
  379. Bwrite(&out, cp, Blinelen(&in));
  380. }
  381. return np;
  382. }
  383. static int
  384. isattribute(char **pp, char *attr)
  385. {
  386. char *p;
  387. int n;
  388. n = strlen(attr);
  389. p = *pp;
  390. if(cistrncmp(p, attr, n) != 0)
  391. return 0;
  392. p += n;
  393. while(*p == ' ')
  394. p++;
  395. if(*p++ != '=')
  396. return 0;
  397. while(*p == ' ')
  398. p++;
  399. *pp = p;
  400. return 1;
  401. }
  402. /*
  403. * parse content type header
  404. */
  405. static void
  406. ctype(Part *p, Hdef *h, char *cp)
  407. {
  408. String *s;
  409. cp += h->len;
  410. cp = skipwhite(cp);
  411. p->type = s_new();
  412. cp = getstring(cp, p->type, 1);
  413. if(badtype(s_to_c(p->type)))
  414. p->badtype = 1;
  415. while(*cp){
  416. if(isattribute(&cp, "boundary")){
  417. s = s_new();
  418. cp = getstring(cp, s, 0);
  419. p->boundary = s_reset(p->boundary);
  420. s_append(p->boundary, "--");
  421. s_append(p->boundary, s_to_c(s));
  422. p->blen = s_len(p->boundary);
  423. s_free(s);
  424. } else if(cistrncmp(cp, "multipart", 9) == 0){
  425. /*
  426. * the first unbounded part of a multipart message,
  427. * the preamble, is not displayed or saved
  428. */
  429. } else if(isattribute(&cp, "name")){
  430. setfilename(p, cp);
  431. } else if(isattribute(&cp, "charset")){
  432. if(p->charset == nil)
  433. p->charset = s_new();
  434. cp = getstring(cp, s_reset(p->charset), 0);
  435. }
  436. cp = skiptosemi(cp);
  437. }
  438. }
  439. /*
  440. * parse content encoding header
  441. */
  442. static void
  443. cencoding(Part *m, Hdef *h, char *p)
  444. {
  445. p += h->len;
  446. p = skipwhite(p);
  447. if(cistrncmp(p, "base64", 6) == 0)
  448. m->encoding = Ebase64;
  449. else if(cistrncmp(p, "quoted-printable", 16) == 0)
  450. m->encoding = Equoted;
  451. }
  452. /*
  453. * parse content disposition header
  454. */
  455. static void
  456. cdisposition(Part *p, Hdef *h, char *cp)
  457. {
  458. cp += h->len;
  459. cp = skipwhite(cp);
  460. while(*cp){
  461. if(cistrncmp(cp, "inline", 6) == 0){
  462. p->disposition = Dinline;
  463. } else if(cistrncmp(cp, "attachment", 10) == 0){
  464. p->disposition = Dfile;
  465. } else if(cistrncmp(cp, "filename=", 9) == 0){
  466. cp += 9;
  467. setfilename(p, cp);
  468. }
  469. cp = skiptosemi(cp);
  470. }
  471. }
  472. static void
  473. setfilename(Part *p, char *name)
  474. {
  475. if(p->filename == nil)
  476. p->filename = s_new();
  477. getstring(name, s_reset(p->filename), 0);
  478. p->filename = tokenconvert(p->filename);
  479. p->badfile = badfile(s_to_c(p->filename));
  480. }
  481. static char*
  482. skipwhite(char *p)
  483. {
  484. while(isspace(*p))
  485. p++;
  486. return p;
  487. }
  488. static char*
  489. skiptosemi(char *p)
  490. {
  491. while(*p && *p != ';')
  492. p++;
  493. while(*p == ';' || isspace(*p))
  494. p++;
  495. return p;
  496. }
  497. /*
  498. * parse a possibly "'d string from a header. A
  499. * ';' terminates the string.
  500. */
  501. static char*
  502. getstring(char *p, String *s, int dolower)
  503. {
  504. s = s_reset(s);
  505. p = skipwhite(p);
  506. if(*p == '"'){
  507. p++;
  508. for(;*p && *p != '"'; p++)
  509. if(dolower)
  510. s_putc(s, tolower(*p));
  511. else
  512. s_putc(s, *p);
  513. if(*p == '"')
  514. p++;
  515. s_terminate(s);
  516. return p;
  517. }
  518. for(; *p && !isspace(*p) && *p != ';'; p++)
  519. if(dolower)
  520. s_putc(s, tolower(*p));
  521. else
  522. s_putc(s, *p);
  523. s_terminate(s);
  524. return p;
  525. }
  526. static void
  527. init_hdefs(void)
  528. {
  529. Hdef *hd;
  530. static int already;
  531. if(already)
  532. return;
  533. already = 1;
  534. for(hd = hdefs; hd->type != nil; hd++)
  535. hd->len = strlen(hd->type);
  536. }
  537. /*
  538. * create a new boundary
  539. */
  540. static String*
  541. mkboundary(void)
  542. {
  543. char buf[32];
  544. int i;
  545. static int already;
  546. if(already == 0){
  547. srand((time(0)<<16)|getpid());
  548. already = 1;
  549. }
  550. strcpy(buf, "upas-");
  551. for(i = 5; i < sizeof(buf)-1; i++)
  552. buf[i] = 'a' + nrand(26);
  553. buf[i] = 0;
  554. return s_copy(buf);
  555. }
  556. /*
  557. * skip blank lines till header
  558. */
  559. static void
  560. passnotheader(void)
  561. {
  562. char *cp;
  563. int i, n;
  564. while((cp = Brdline(&in, '\n')) != nil){
  565. n = Blinelen(&in);
  566. for(i = 0; i < n-1; i++)
  567. if(cp[i] != ' ' && cp[i] != '\t' && cp[i] != '\r'){
  568. Bseek(&in, -n, 1);
  569. return;
  570. }
  571. Bwrite(&out, cp, n);
  572. }
  573. }
  574. /*
  575. * pass unix header lines
  576. */
  577. static void
  578. passunixheader(void)
  579. {
  580. char *p;
  581. int n;
  582. while((p = Brdline(&in, '\n')) != nil){
  583. n = Blinelen(&in);
  584. if(strncmp(p, "From ", 5) != 0){
  585. Bseek(&in, -n, 1);
  586. break;
  587. }
  588. Bwrite(&out, p, n);
  589. }
  590. }
  591. /*
  592. * Read mime types
  593. */
  594. static void
  595. readmtypes(void)
  596. {
  597. Biobuf *b;
  598. char *p;
  599. char *f[6];
  600. Mtype *m;
  601. Mtype **l;
  602. b = Bopen("/sys/lib/mimetype", OREAD);
  603. if(b == nil)
  604. return;
  605. l = &mtypes;
  606. while((p = Brdline(b, '\n')) != nil){
  607. if(*p == '#')
  608. continue;
  609. p[Blinelen(b)-1] = 0;
  610. if(tokenize(p, f, nelem(f)) < 5)
  611. continue;
  612. m = mallocz(sizeof *m, 1);
  613. if(m == nil)
  614. goto err;
  615. m->ext = strdup(f[0]);
  616. if(m->ext == 0)
  617. goto err;
  618. m->gtype = strdup(f[1]);
  619. if(m->gtype == 0)
  620. goto err;
  621. m->stype = strdup(f[2]);
  622. if(m->stype == 0)
  623. goto err;
  624. m->class = *f[4];
  625. *l = m;
  626. l = &(m->next);
  627. }
  628. Bterm(b);
  629. return;
  630. err:
  631. if(m == nil)
  632. return;
  633. free(m->ext);
  634. free(m->gtype);
  635. free(m->stype);
  636. free(m);
  637. Bterm(b);
  638. }
  639. /*
  640. * if the class is 'm' or 'y', accept it
  641. * if the class is 'p' check a previous extension
  642. * otherwise, filename is bad
  643. */
  644. static int
  645. badfile(char *name)
  646. {
  647. char *p;
  648. Mtype *m;
  649. int rv;
  650. p = strrchr(name, '.');
  651. if(p == nil)
  652. return 0;
  653. for(m = mtypes; m != nil; m = m->next)
  654. if(cistrcmp(p, m->ext) == 0){
  655. switch(m->class){
  656. case 'm':
  657. case 'y':
  658. return 0;
  659. case 'p':
  660. *p = 0;
  661. rv = badfile(name);
  662. *p = '.';
  663. return rv;
  664. case 'r':
  665. return 2;
  666. }
  667. }
  668. if(justreject)
  669. return 0;
  670. return 1;
  671. }
  672. /*
  673. * if the class is 'm' or 'y' or 'p', accept it
  674. * otherwise, filename is bad
  675. */
  676. static int
  677. badtype(char *type)
  678. {
  679. Mtype *m;
  680. char *s, *fix;
  681. int rv = 1;
  682. if(justreject)
  683. return 0;
  684. fix = s = strchr(type, '/');
  685. if(s != nil)
  686. *s++ = 0;
  687. else
  688. s = "-";
  689. for(m = mtypes; m != nil; m = m->next){
  690. if(cistrcmp(type, m->gtype) != 0)
  691. continue;
  692. if(cistrcmp(s, m->stype) != 0)
  693. continue;
  694. switch(m->class){
  695. case 'y':
  696. case 'p':
  697. case 'm':
  698. rv = 0;
  699. break;
  700. }
  701. break;
  702. }
  703. if(fix != nil)
  704. *fix = '/';
  705. return rv;
  706. }
  707. /* rfc2047 non-ascii */
  708. typedef struct Charset Charset;
  709. struct Charset {
  710. char *name;
  711. int len;
  712. int convert;
  713. } charsets[] =
  714. {
  715. { "us-ascii", 8, 1, },
  716. { "utf-8", 5, 0, },
  717. { "iso-8859-1", 10, 1, },
  718. };
  719. /*
  720. * convert to UTF if need be
  721. */
  722. static String*
  723. tokenconvert(String *t)
  724. {
  725. String *s;
  726. char decoded[1024];
  727. char utfbuf[2*1024];
  728. int i, len;
  729. char *e;
  730. char *token;
  731. token = s_to_c(t);
  732. len = s_len(t);
  733. if(token[0] != '=' || token[1] != '?' ||
  734. token[len-2] != '?' || token[len-1] != '=')
  735. goto err;
  736. e = token+len-2;
  737. token += 2;
  738. // bail if we don't understand the character set
  739. for(i = 0; i < nelem(charsets); i++)
  740. if(cistrncmp(charsets[i].name, token, charsets[i].len) == 0)
  741. if(token[charsets[i].len] == '?'){
  742. token += charsets[i].len + 1;
  743. break;
  744. }
  745. if(i >= nelem(charsets))
  746. goto err;
  747. // bail if it doesn't fit
  748. if(strlen(token) > sizeof(decoded)-1)
  749. goto err;
  750. // bail if we don't understand the encoding
  751. if(cistrncmp(token, "b?", 2) == 0){
  752. token += 2;
  753. len = dec64((uchar*)decoded, sizeof(decoded), token, e-token);
  754. decoded[len] = 0;
  755. } else if(cistrncmp(token, "q?", 2) == 0){
  756. token += 2;
  757. len = decquoted(decoded, token, e);
  758. if(len > 0 && decoded[len-1] == '\n')
  759. len--;
  760. decoded[len] = 0;
  761. } else
  762. goto err;
  763. s = nil;
  764. switch(charsets[i].convert){
  765. case 0:
  766. s = s_copy(decoded);
  767. break;
  768. case 1:
  769. s = s_new();
  770. latin1toutf(utfbuf, decoded, decoded+len);
  771. s_append(s, utfbuf);
  772. break;
  773. }
  774. return s;
  775. err:
  776. return s_clone(t);
  777. }
  778. /*
  779. * decode quoted
  780. */
  781. enum
  782. {
  783. Self= 1,
  784. Hex= 2,
  785. };
  786. uchar tableqp[256];
  787. static void
  788. initquoted(void)
  789. {
  790. int c;
  791. memset(tableqp, 0, 256);
  792. for(c = ' '; c <= '<'; c++)
  793. tableqp[c] = Self;
  794. for(c = '>'; c <= '~'; c++)
  795. tableqp[c] = Self;
  796. tableqp['\t'] = Self;
  797. tableqp['='] = Hex;
  798. }
  799. static int
  800. hex2int(int x)
  801. {
  802. if(x >= '0' && x <= '9')
  803. return x - '0';
  804. if(x >= 'A' && x <= 'F')
  805. return (x - 'A') + 10;
  806. if(x >= 'a' && x <= 'f')
  807. return (x - 'a') + 10;
  808. return 0;
  809. }
  810. static char*
  811. decquotedline(char *out, char *in, char *e)
  812. {
  813. int c, soft;
  814. /* dump trailing white space */
  815. while(e >= in && (*e == ' ' || *e == '\t' || *e == '\r' || *e == '\n'))
  816. e--;
  817. /* trailing '=' means no newline */
  818. if(*e == '='){
  819. soft = 1;
  820. e--;
  821. } else
  822. soft = 0;
  823. while(in <= e){
  824. c = (*in++) & 0xff;
  825. switch(tableqp[c]){
  826. case Self:
  827. *out++ = c;
  828. break;
  829. case Hex:
  830. c = hex2int(*in++)<<4;
  831. c |= hex2int(*in++);
  832. *out++ = c;
  833. break;
  834. }
  835. }
  836. if(!soft)
  837. *out++ = '\n';
  838. *out = 0;
  839. return out;
  840. }
  841. static int
  842. decquoted(char *out, char *in, char *e)
  843. {
  844. char *p, *nl;
  845. if(tableqp[' '] == 0)
  846. initquoted();
  847. p = out;
  848. while((nl = strchr(in, '\n')) != nil && nl < e){
  849. p = decquotedline(p, in, nl);
  850. in = nl + 1;
  851. }
  852. if(in < e)
  853. p = decquotedline(p, in, e-1);
  854. // make sure we end with a new line
  855. if(*(p-1) != '\n'){
  856. *p++ = '\n';
  857. *p = 0;
  858. }
  859. return p - out;
  860. }
  861. /* translate latin1 directly since it fits neatly in utf */
  862. static int
  863. latin1toutf(char *out, char *in, char *e)
  864. {
  865. Rune r;
  866. char *p;
  867. p = out;
  868. for(; in < e; in++){
  869. r = (*in) & 0xff;
  870. p += runetochar(p, &r);
  871. }
  872. *p = 0;
  873. return p - out;
  874. }