mbox.c 28 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592159315941595
  1. #include "common.h"
  2. #include <ctype.h>
  3. #include <plumb.h>
  4. #include <libsec.h>
  5. #include "dat.h"
  6. typedef struct Header Header;
  7. struct Header {
  8. char *type;
  9. void (*f)(Message*, Header*, char*);
  10. int len;
  11. };
  12. /* headers */
  13. static void ctype(Message*, Header*, char*);
  14. static void cencoding(Message*, Header*, char*);
  15. static void cdisposition(Message*, Header*, char*);
  16. static void date822(Message*, Header*, char*);
  17. static void from822(Message*, Header*, char*);
  18. static void to822(Message*, Header*, char*);
  19. static void sender822(Message*, Header*, char*);
  20. static void replyto822(Message*, Header*, char*);
  21. static void subject822(Message*, Header*, char*);
  22. static void inreplyto822(Message*, Header*, char*);
  23. static void cc822(Message*, Header*, char*);
  24. static void bcc822(Message*, Header*, char*);
  25. static void messageid822(Message*, Header*, char*);
  26. static void mimeversion(Message*, Header*, char*);
  27. static void nullsqueeze(Message*);
  28. enum
  29. {
  30. Mhead= 11, /* offset of first mime header */
  31. };
  32. Header head[] =
  33. {
  34. { "date:", date822, },
  35. { "from:", from822, },
  36. { "to:", to822, },
  37. { "sender:", sender822, },
  38. { "reply-to:", replyto822, },
  39. { "subject:", subject822, },
  40. { "cc:", cc822, },
  41. { "bcc:", bcc822, },
  42. { "in-reply-to:", inreplyto822, },
  43. { "mime-version:", mimeversion, },
  44. { "message-id:", messageid822, },
  45. [Mhead] { "content-type:", ctype, },
  46. { "content-transfer-encoding:", cencoding, },
  47. { "content-disposition:", cdisposition, },
  48. { 0, },
  49. };
  50. static void fatal(char *fmt, ...);
  51. static void initquoted(void);
  52. static void startheader(Message*);
  53. static void startbody(Message*);
  54. static char* skipwhite(char*);
  55. static char* skiptosemi(char*);
  56. static char* getstring(char*, String*, int);
  57. static void setfilename(Message*, char*);
  58. static char* lowercase(char*);
  59. static int is8bit(Message*);
  60. static int headerline(char**, String*);
  61. static void initheaders(void);
  62. static void parseattachments(Message*, Mailbox*);
  63. int debug;
  64. char *Enotme = "path not served by this file server";
  65. enum
  66. {
  67. Chunksize = 1024,
  68. };
  69. Mailboxinit *boxinit[] = {
  70. imap4mbox,
  71. pop3mbox,
  72. plan9mbox,
  73. };
  74. char*
  75. syncmbox(Mailbox *mb, int doplumb)
  76. {
  77. return (*mb->sync)(mb, doplumb);
  78. }
  79. /* create a new mailbox */
  80. char*
  81. newmbox(char *path, char *name, int std)
  82. {
  83. Mailbox *mb, **l;
  84. char *p, *rv;
  85. int i;
  86. initheaders();
  87. mb = emalloc(sizeof(*mb));
  88. strncpy(mb->path, path, sizeof(mb->path)-1);
  89. if(name == nil){
  90. p = strrchr(path, '/');
  91. if(p == nil)
  92. p = path;
  93. else
  94. p++;
  95. if(*p == 0){
  96. free(mb);
  97. return "bad mbox name";
  98. }
  99. strncpy(mb->name, p, sizeof(mb->name)-1);
  100. } else {
  101. strncpy(mb->name, name, sizeof(mb->name)-1);
  102. }
  103. rv = nil;
  104. // check for a mailbox type
  105. for(i=0; i<nelem(boxinit); i++)
  106. if((rv = (*boxinit[i])(mb, path)) != Enotme)
  107. break;
  108. if(i == nelem(boxinit)){
  109. free(mb);
  110. return "bad path";
  111. }
  112. // on error, give up
  113. if(rv){
  114. free(mb);
  115. return rv;
  116. }
  117. // make sure name isn't taken
  118. qlock(&mbllock);
  119. for(l = &mbl; *l != nil; l = &(*l)->next){
  120. if(strcmp((*l)->name, mb->name) == 0){
  121. if(strcmp(path, (*l)->path) == 0)
  122. rv = nil;
  123. else
  124. rv = "mbox name in use";
  125. if(mb->close)
  126. (*mb->close)(mb);
  127. free(mb);
  128. qunlock(&mbllock);
  129. return rv;
  130. }
  131. }
  132. // always try locking
  133. mb->dolock = 1;
  134. mb->refs = 1;
  135. mb->next = nil;
  136. mb->id = newid();
  137. mb->root = newmessage(nil);
  138. mb->std = std;
  139. *l = mb;
  140. qunlock(&mbllock);
  141. qlock(mb);
  142. if(mb->ctl){
  143. henter(PATH(mb->id, Qmbox), "ctl",
  144. (Qid){PATH(mb->id, Qmboxctl), 0, QTFILE}, nil, mb);
  145. }
  146. rv = syncmbox(mb, 0);
  147. qunlock(mb);
  148. return rv;
  149. }
  150. // close the named mailbox
  151. void
  152. freembox(char *name)
  153. {
  154. Mailbox **l, *mb;
  155. qlock(&mbllock);
  156. for(l=&mbl; *l != nil; l=&(*l)->next){
  157. if(strcmp(name, (*l)->name) == 0){
  158. mb = *l;
  159. *l = mb->next;
  160. mboxdecref(mb);
  161. break;
  162. }
  163. }
  164. hfree(PATH(0, Qtop), name);
  165. qunlock(&mbllock);
  166. }
  167. static void
  168. initheaders(void)
  169. {
  170. Header *h;
  171. static int already;
  172. if(already)
  173. return;
  174. already = 1;
  175. for(h = head; h->type != nil; h++)
  176. h->len = strlen(h->type);
  177. }
  178. /*
  179. * parse a Unix style header
  180. */
  181. void
  182. parseunix(Message *m)
  183. {
  184. char *p;
  185. String *h;
  186. h = s_new();
  187. for(p = m->start + 5; *p && *p != '\r' && *p != '\n'; p++)
  188. s_putc(h, *p);
  189. s_terminate(h);
  190. s_restart(h);
  191. m->unixfrom = s_parse(h, s_reset(m->unixfrom));
  192. m->unixdate = s_append(s_reset(m->unixdate), h->ptr);
  193. s_free(h);
  194. }
  195. /*
  196. * parse a message
  197. */
  198. void
  199. parseheaders(Message *m, int justmime, Mailbox *mb, int addfrom)
  200. {
  201. String *hl;
  202. Header *h;
  203. char *p, *q;
  204. int i;
  205. if(m->whole == m->whole->whole){
  206. henter(PATH(mb->id, Qmbox), m->name,
  207. (Qid){PATH(m->id, Qdir), 0, QTDIR}, m, mb);
  208. } else {
  209. henter(PATH(m->whole->id, Qdir), m->name,
  210. (Qid){PATH(m->id, Qdir), 0, QTDIR}, m, mb);
  211. }
  212. for(i = 0; i < Qmax; i++)
  213. henter(PATH(m->id, Qdir), dirtab[i],
  214. (Qid){PATH(m->id, i), 0, QTFILE}, m, mb);
  215. // parse mime headers
  216. p = m->header;
  217. hl = s_new();
  218. while(headerline(&p, hl)){
  219. if(justmime)
  220. h = &head[Mhead];
  221. else
  222. h = head;
  223. for(; h->type; h++){
  224. if(cistrncmp(s_to_c(hl), h->type, h->len) == 0){
  225. (*h->f)(m, h, s_to_c(hl));
  226. break;
  227. }
  228. }
  229. s_reset(hl);
  230. }
  231. s_free(hl);
  232. // the blank line isn't really part of the body or header
  233. if(justmime){
  234. m->mhend = p;
  235. m->hend = m->header;
  236. } else {
  237. m->hend = p;
  238. }
  239. if(*p == '\n')
  240. p++;
  241. m->rbody = m->body = p;
  242. // if type is text, get any nulls out of the body. This is
  243. // for the two seans and imap clients that get confused.
  244. if(strncmp(s_to_c(m->type), "text/", 5) == 0)
  245. nullsqueeze(m);
  246. //
  247. // cobble together Unix-style from line
  248. // for local mailbox messages, we end up recreating the
  249. // original header.
  250. // for pop3 messages, the best we can do is
  251. // use the From: information and the RFC822 date.
  252. //
  253. if(m->unixdate == nil || strcmp(s_to_c(m->unixdate), "???") == 0
  254. || strcmp(s_to_c(m->unixdate), "Thu Jan 1 00:00:00 GMT 1970") == 0){
  255. if(m->unixdate){
  256. s_free(m->unixdate);
  257. m->unixdate = nil;
  258. }
  259. // look for the date in the first Received: line.
  260. // it's likely to be the right time zone (it's
  261. // the local system) and in a convenient format.
  262. if(cistrncmp(m->header, "received:", 9)==0){
  263. if((q = strchr(m->header, ';')) != nil){
  264. p = q;
  265. while((p = strchr(p, '\n')) != nil){
  266. if(p[1] != ' ' && p[1] != '\t' && p[1] != '\n')
  267. break;
  268. p++;
  269. }
  270. if(p){
  271. *p = '\0';
  272. m->unixdate = date822tounix(q+1);
  273. *p = '\n';
  274. }
  275. }
  276. }
  277. // fall back on the rfc822 date
  278. if(m->unixdate==nil && m->date822)
  279. m->unixdate = date822tounix(s_to_c(m->date822));
  280. }
  281. if(m->unixheader != nil)
  282. s_free(m->unixheader);
  283. // only fake header for top-level messages for pop3 and imap4
  284. // clients (those protocols don't include the unix header).
  285. // adding the unix header all the time screws up mime-attached
  286. // rfc822 messages.
  287. if(!addfrom && !m->unixfrom){
  288. m->unixheader = nil;
  289. return;
  290. }
  291. m->unixheader = s_copy("From ");
  292. if(m->unixfrom && strcmp(s_to_c(m->unixfrom), "???") != 0)
  293. s_append(m->unixheader, s_to_c(m->unixfrom));
  294. else if(m->from822)
  295. s_append(m->unixheader, s_to_c(m->from822));
  296. else
  297. s_append(m->unixheader, "???");
  298. s_append(m->unixheader, " ");
  299. if(m->unixdate)
  300. s_append(m->unixheader, s_to_c(m->unixdate));
  301. else
  302. s_append(m->unixheader, "Thu Jan 1 00:00:00 GMT 1970");
  303. s_append(m->unixheader, "\n");
  304. }
  305. String*
  306. promote(String **sp)
  307. {
  308. String *s;
  309. if(*sp != nil)
  310. s = s_clone(*sp);
  311. else
  312. s = nil;
  313. return s;
  314. }
  315. void
  316. parsebody(Message *m, Mailbox *mb)
  317. {
  318. Message *nm;
  319. // recurse
  320. if(strncmp(s_to_c(m->type), "multipart/", 10) == 0){
  321. parseattachments(m, mb);
  322. } else if(strcmp(s_to_c(m->type), "message/rfc822") == 0){
  323. decode(m);
  324. parseattachments(m, mb);
  325. nm = m->part;
  326. // promote headers
  327. if(m->replyto822 == nil && m->from822 == nil && m->sender822 == nil){
  328. m->from822 = promote(&nm->from822);
  329. m->to822 = promote(&nm->to822);
  330. m->date822 = promote(&nm->date822);
  331. m->sender822 = promote(&nm->sender822);
  332. m->replyto822 = promote(&nm->replyto822);
  333. m->subject822 = promote(&nm->subject822);
  334. m->unixdate = promote(&nm->unixdate);
  335. }
  336. }
  337. }
  338. void
  339. parse(Message *m, int justmime, Mailbox *mb, int addfrom)
  340. {
  341. parseheaders(m, justmime, mb, addfrom);
  342. parsebody(m, mb);
  343. }
  344. static void
  345. parseattachments(Message *m, Mailbox *mb)
  346. {
  347. Message *nm, **l;
  348. char *p, *x;
  349. // if there's a boundary, recurse...
  350. if(m->boundary != nil){
  351. p = m->body;
  352. nm = nil;
  353. l = &m->part;
  354. for(;;){
  355. x = strstr(p, s_to_c(m->boundary));
  356. /* no boundary, we're done */
  357. if(x == nil){
  358. if(nm != nil)
  359. nm->rbend = nm->bend = nm->end = m->bend;
  360. break;
  361. }
  362. /* boundary must be at the start of a line */
  363. if(x != m->body && *(x-1) != '\n'){
  364. p = x+1;
  365. continue;
  366. }
  367. if(nm != nil)
  368. nm->rbend = nm->bend = nm->end = x;
  369. x += strlen(s_to_c(m->boundary));
  370. /* is this the last part? ignore anything after it */
  371. if(strncmp(x, "--", 2) == 0)
  372. break;
  373. p = strchr(x, '\n');
  374. if(p == nil)
  375. break;
  376. nm = newmessage(m);
  377. nm->start = nm->header = nm->body = nm->rbody = ++p;
  378. nm->mheader = nm->header;
  379. *l = nm;
  380. l = &nm->next;
  381. }
  382. for(nm = m->part; nm != nil; nm = nm->next)
  383. parse(nm, 1, mb, 0);
  384. return;
  385. }
  386. // if we've got an rfc822 message, recurse...
  387. if(strcmp(s_to_c(m->type), "message/rfc822") == 0){
  388. nm = newmessage(m);
  389. m->part = nm;
  390. nm->start = nm->header = nm->body = nm->rbody = m->body;
  391. nm->end = nm->bend = nm->rbend = m->bend;
  392. parse(nm, 0, mb, 0);
  393. }
  394. }
  395. /*
  396. * pick up a header line
  397. */
  398. static int
  399. headerline(char **pp, String *hl)
  400. {
  401. char *p, *x;
  402. s_reset(hl);
  403. p = *pp;
  404. x = strpbrk(p, ":\n");
  405. if(x == nil || *x == '\n')
  406. return 0;
  407. for(;;){
  408. x = strchr(p, '\n');
  409. if(x == nil)
  410. x = p + strlen(p);
  411. s_nappend(hl, p, x-p);
  412. p = x;
  413. if(*p != '\n' || *++p != ' ' && *p != '\t')
  414. break;
  415. while(*p == ' ' || *p == '\t')
  416. p++;
  417. s_putc(hl, ' ');
  418. }
  419. *pp = p;
  420. return 1;
  421. }
  422. static String*
  423. addr822(char *p)
  424. {
  425. String *s, *list;
  426. int incomment, addrdone, inanticomment, quoted;
  427. int n;
  428. int c;
  429. list = s_new();
  430. s = s_new();
  431. quoted = incomment = addrdone = inanticomment = 0;
  432. n = 0;
  433. for(; *p; p++){
  434. c = *p;
  435. // whitespace is ignored
  436. if(!quoted && isspace(c) || c == '\r')
  437. continue;
  438. // strings are always treated as atoms
  439. if(!quoted && c == '"'){
  440. if(!addrdone && !incomment)
  441. s_putc(s, c);
  442. for(p++; *p; p++){
  443. if(!addrdone && !incomment)
  444. s_putc(s, *p);
  445. if(!quoted && *p == '"')
  446. break;
  447. if(*p == '\\')
  448. quoted = 1;
  449. else
  450. quoted = 0;
  451. }
  452. if(*p == 0)
  453. break;
  454. quoted = 0;
  455. continue;
  456. }
  457. // ignore everything in an expicit comment
  458. if(!quoted && c == '('){
  459. incomment = 1;
  460. continue;
  461. }
  462. if(incomment){
  463. if(!quoted && c == ')')
  464. incomment = 0;
  465. quoted = 0;
  466. continue;
  467. }
  468. // anticomments makes everything outside of them comments
  469. if(!quoted && c == '<' && !inanticomment){
  470. inanticomment = 1;
  471. s = s_reset(s);
  472. continue;
  473. }
  474. if(!quoted && c == '>' && inanticomment){
  475. addrdone = 1;
  476. inanticomment = 0;
  477. continue;
  478. }
  479. // commas separate addresses
  480. if(!quoted && c == ',' && !inanticomment){
  481. s_terminate(s);
  482. addrdone = 0;
  483. if(n++ != 0)
  484. s_append(list, " ");
  485. s_append(list, s_to_c(s));
  486. s = s_reset(s);
  487. continue;
  488. }
  489. // what's left is part of the address
  490. s_putc(s, c);
  491. // quoted characters are recognized only as characters
  492. if(c == '\\')
  493. quoted = 1;
  494. else
  495. quoted = 0;
  496. }
  497. if(*s_to_c(s) != 0){
  498. s_terminate(s);
  499. if(n++ != 0)
  500. s_append(list, " ");
  501. s_append(list, s_to_c(s));
  502. }
  503. s_free(s);
  504. if(n == 0){
  505. s_free(list);
  506. return nil;
  507. }
  508. return list;
  509. }
  510. static void
  511. to822(Message *m, Header *h, char *p)
  512. {
  513. p += strlen(h->type);
  514. s_free(m->to822);
  515. m->to822 = addr822(p);
  516. }
  517. static void
  518. cc822(Message *m, Header *h, char *p)
  519. {
  520. p += strlen(h->type);
  521. s_free(m->cc822);
  522. m->cc822 = addr822(p);
  523. }
  524. static void
  525. bcc822(Message *m, Header *h, char *p)
  526. {
  527. p += strlen(h->type);
  528. s_free(m->bcc822);
  529. m->bcc822 = addr822(p);
  530. }
  531. static void
  532. from822(Message *m, Header *h, char *p)
  533. {
  534. p += strlen(h->type);
  535. s_free(m->from822);
  536. m->from822 = addr822(p);
  537. }
  538. static void
  539. sender822(Message *m, Header *h, char *p)
  540. {
  541. p += strlen(h->type);
  542. s_free(m->sender822);
  543. m->sender822 = addr822(p);
  544. }
  545. static void
  546. replyto822(Message *m, Header *h, char *p)
  547. {
  548. p += strlen(h->type);
  549. s_free(m->replyto822);
  550. m->replyto822 = addr822(p);
  551. }
  552. static void
  553. mimeversion(Message *m, Header *h, char *p)
  554. {
  555. p += strlen(h->type);
  556. s_free(m->mimeversion);
  557. m->mimeversion = addr822(p);
  558. }
  559. static void
  560. killtrailingwhite(char *p)
  561. {
  562. char *e;
  563. e = p + strlen(p) - 1;
  564. while(e > p && isspace(*e))
  565. *e-- = 0;
  566. }
  567. static void
  568. date822(Message *m, Header *h, char *p)
  569. {
  570. p += strlen(h->type);
  571. p = skipwhite(p);
  572. s_free(m->date822);
  573. m->date822 = s_copy(p);
  574. p = s_to_c(m->date822);
  575. killtrailingwhite(p);
  576. }
  577. static void
  578. subject822(Message *m, Header *h, char *p)
  579. {
  580. p += strlen(h->type);
  581. p = skipwhite(p);
  582. s_free(m->subject822);
  583. m->subject822 = s_copy(p);
  584. p = s_to_c(m->subject822);
  585. killtrailingwhite(p);
  586. }
  587. static void
  588. inreplyto822(Message *m, Header *h, char *p)
  589. {
  590. p += strlen(h->type);
  591. p = skipwhite(p);
  592. s_free(m->inreplyto822);
  593. m->inreplyto822 = s_copy(p);
  594. p = s_to_c(m->inreplyto822);
  595. killtrailingwhite(p);
  596. }
  597. static void
  598. messageid822(Message *m, Header *h, char *p)
  599. {
  600. p += strlen(h->type);
  601. p = skipwhite(p);
  602. s_free(m->messageid822);
  603. m->messageid822 = s_copy(p);
  604. p = s_to_c(m->messageid822);
  605. killtrailingwhite(p);
  606. }
  607. static int
  608. isattribute(char **pp, char *attr)
  609. {
  610. char *p;
  611. int n;
  612. n = strlen(attr);
  613. p = *pp;
  614. if(cistrncmp(p, attr, n) != 0)
  615. return 0;
  616. p += n;
  617. while(*p == ' ')
  618. p++;
  619. if(*p++ != '=')
  620. return 0;
  621. while(*p == ' ')
  622. p++;
  623. *pp = p;
  624. return 1;
  625. }
  626. static void
  627. ctype(Message *m, Header *h, char *p)
  628. {
  629. String *s;
  630. p += h->len;
  631. p = skipwhite(p);
  632. p = getstring(p, m->type, 1);
  633. while(*p){
  634. if(isattribute(&p, "boundary")){
  635. s = s_new();
  636. p = getstring(p, s, 0);
  637. m->boundary = s_reset(m->boundary);
  638. s_append(m->boundary, "--");
  639. s_append(m->boundary, s_to_c(s));
  640. s_free(s);
  641. } else if(cistrncmp(p, "multipart", 9) == 0){
  642. /*
  643. * the first unbounded part of a multipart message,
  644. * the preamble, is not displayed or saved
  645. */
  646. } else if(isattribute(&p, "name")){
  647. if(m->filename == nil)
  648. setfilename(m, p);
  649. } else if(isattribute(&p, "charset")){
  650. p = getstring(p, s_reset(m->charset), 0);
  651. }
  652. p = skiptosemi(p);
  653. }
  654. }
  655. static void
  656. cencoding(Message *m, Header *h, char *p)
  657. {
  658. p += h->len;
  659. p = skipwhite(p);
  660. if(cistrncmp(p, "base64", 6) == 0)
  661. m->encoding = Ebase64;
  662. else if(cistrncmp(p, "quoted-printable", 16) == 0)
  663. m->encoding = Equoted;
  664. }
  665. static void
  666. cdisposition(Message *m, Header *h, char *p)
  667. {
  668. p += h->len;
  669. p = skipwhite(p);
  670. while(*p){
  671. if(cistrncmp(p, "inline", 6) == 0){
  672. m->disposition = Dinline;
  673. } else if(cistrncmp(p, "attachment", 10) == 0){
  674. m->disposition = Dfile;
  675. } else if(cistrncmp(p, "filename=", 9) == 0){
  676. p += 9;
  677. setfilename(m, p);
  678. }
  679. p = skiptosemi(p);
  680. }
  681. }
  682. ulong msgallocd, msgfreed;
  683. Message*
  684. newmessage(Message *parent)
  685. {
  686. static int id;
  687. Message *m;
  688. msgallocd++;
  689. m = emalloc(sizeof(*m));
  690. memset(m, 0, sizeof(*m));
  691. m->disposition = Dnone;
  692. m->type = s_copy("text/plain");
  693. m->charset = s_copy("iso-8859-1");
  694. m->id = newid();
  695. if(parent)
  696. sprint(m->name, "%d", ++(parent->subname));
  697. if(parent == nil)
  698. parent = m;
  699. m->whole = parent;
  700. m->hlen = -1;
  701. return m;
  702. }
  703. // delete a message from a mailbox
  704. void
  705. delmessage(Mailbox *mb, Message *m)
  706. {
  707. Message **l;
  708. int i;
  709. mb->vers++;
  710. msgfreed++;
  711. if(m->whole != m){
  712. // unchain from parent
  713. for(l = &m->whole->part; *l && *l != m; l = &(*l)->next)
  714. ;
  715. if(*l != nil)
  716. *l = m->next;
  717. // clear out of name lookup hash table
  718. if(m->whole->whole == m->whole)
  719. hfree(PATH(mb->id, Qmbox), m->name);
  720. else
  721. hfree(PATH(m->whole->id, Qdir), m->name);
  722. for(i = 0; i < Qmax; i++)
  723. hfree(PATH(m->id, Qdir), dirtab[i]);
  724. }
  725. /* recurse through sub-parts */
  726. while(m->part)
  727. delmessage(mb, m->part);
  728. /* free memory */
  729. if(m->mallocd)
  730. free(m->start);
  731. if(m->hallocd)
  732. free(m->header);
  733. if(m->ballocd)
  734. free(m->body);
  735. s_free(m->unixfrom);
  736. s_free(m->unixdate);
  737. s_free(m->unixheader);
  738. s_free(m->from822);
  739. s_free(m->sender822);
  740. s_free(m->to822);
  741. s_free(m->bcc822);
  742. s_free(m->cc822);
  743. s_free(m->replyto822);
  744. s_free(m->date822);
  745. s_free(m->inreplyto822);
  746. s_free(m->subject822);
  747. s_free(m->messageid822);
  748. s_free(m->addrs);
  749. s_free(m->mimeversion);
  750. s_free(m->sdigest);
  751. s_free(m->boundary);
  752. s_free(m->type);
  753. s_free(m->charset);
  754. s_free(m->filename);
  755. free(m);
  756. }
  757. // mark messages (identified by path) for deletion
  758. void
  759. delmessages(int ac, char **av)
  760. {
  761. Mailbox *mb;
  762. Message *m;
  763. int i, needwrite;
  764. qlock(&mbllock);
  765. for(mb = mbl; mb != nil; mb = mb->next)
  766. if(strcmp(av[0], mb->name) == 0){
  767. qlock(mb);
  768. break;
  769. }
  770. qunlock(&mbllock);
  771. if(mb == nil)
  772. return;
  773. needwrite = 0;
  774. for(i = 1; i < ac; i++){
  775. for(m = mb->root->part; m != nil; m = m->next)
  776. if(strcmp(m->name, av[i]) == 0){
  777. if(!m->deleted){
  778. mailplumb(mb, m, 1);
  779. needwrite = 1;
  780. m->deleted = 1;
  781. logmsg("deleting", m);
  782. }
  783. break;
  784. }
  785. }
  786. if(needwrite)
  787. syncmbox(mb, 1);
  788. qunlock(mb);
  789. }
  790. /*
  791. * the following are called with the mailbox qlocked
  792. */
  793. void
  794. msgincref(Message *m)
  795. {
  796. m->refs++;
  797. }
  798. void
  799. msgdecref(Mailbox *mb, Message *m)
  800. {
  801. m->refs--;
  802. if(m->refs == 0 && m->deleted)
  803. syncmbox(mb, 1);
  804. }
  805. /*
  806. * the following are called with mbllock'd
  807. */
  808. void
  809. mboxincref(Mailbox *mb)
  810. {
  811. assert(mb->refs > 0);
  812. mb->refs++;
  813. }
  814. void
  815. mboxdecref(Mailbox *mb)
  816. {
  817. assert(mb->refs > 0);
  818. qlock(mb);
  819. mb->refs--;
  820. if(mb->refs == 0){
  821. delmessage(mb, mb->root);
  822. if(mb->ctl)
  823. hfree(PATH(mb->id, Qmbox), "ctl");
  824. if(mb->close)
  825. (*mb->close)(mb);
  826. free(mb);
  827. } else
  828. qunlock(mb);
  829. }
  830. int
  831. cistrncmp(char *a, char *b, int n)
  832. {
  833. while(n-- > 0){
  834. if(tolower(*a++) != tolower(*b++))
  835. return -1;
  836. }
  837. return 0;
  838. }
  839. int
  840. cistrcmp(char *a, char *b)
  841. {
  842. for(;;){
  843. if(tolower(*a) != tolower(*b++))
  844. return -1;
  845. if(*a++ == 0)
  846. break;
  847. }
  848. return 0;
  849. }
  850. static char*
  851. skipwhite(char *p)
  852. {
  853. while(isspace(*p))
  854. p++;
  855. return p;
  856. }
  857. static char*
  858. skiptosemi(char *p)
  859. {
  860. while(*p && *p != ';')
  861. p++;
  862. while(*p == ';' || isspace(*p))
  863. p++;
  864. return p;
  865. }
  866. static char*
  867. getstring(char *p, String *s, int dolower)
  868. {
  869. s = s_reset(s);
  870. p = skipwhite(p);
  871. if(*p == '"'){
  872. p++;
  873. for(;*p && *p != '"'; p++)
  874. if(dolower)
  875. s_putc(s, tolower(*p));
  876. else
  877. s_putc(s, *p);
  878. if(*p == '"')
  879. p++;
  880. s_terminate(s);
  881. return p;
  882. }
  883. for(; *p && !isspace(*p) && *p != ';'; p++)
  884. if(dolower)
  885. s_putc(s, tolower(*p));
  886. else
  887. s_putc(s, *p);
  888. s_terminate(s);
  889. return p;
  890. }
  891. static void
  892. setfilename(Message *m, char *p)
  893. {
  894. m->filename = s_reset(m->filename);
  895. getstring(p, m->filename, 0);
  896. for(p = s_to_c(m->filename); *p; p++)
  897. if(*p == ' ' || *p == '\t' || *p == ';')
  898. *p = '_';
  899. }
  900. //
  901. // undecode message body
  902. //
  903. void
  904. decode(Message *m)
  905. {
  906. int i, len;
  907. char *x;
  908. if(m->decoded)
  909. return;
  910. switch(m->encoding){
  911. case Ebase64:
  912. len = m->bend - m->body;
  913. i = (len*3)/4+1; // room for max chars + null
  914. x = emalloc(i);
  915. len = dec64((uchar*)x, i, m->body, len);
  916. if(m->ballocd)
  917. free(m->body);
  918. m->body = x;
  919. m->bend = x + len;
  920. m->ballocd = 1;
  921. break;
  922. case Equoted:
  923. len = m->bend - m->body;
  924. x = emalloc(len+2); // room for null and possible extra nl
  925. len = decquoted(x, m->body, m->bend, 0);
  926. if(m->ballocd)
  927. free(m->body);
  928. m->body = x;
  929. m->bend = x + len;
  930. m->ballocd = 1;
  931. break;
  932. default:
  933. break;
  934. }
  935. m->decoded = 1;
  936. }
  937. // convert latin1 to utf
  938. void
  939. convert(Message *m)
  940. {
  941. int len;
  942. char *x;
  943. // don't convert if we're not a leaf, not text, or already converted
  944. if(m->converted)
  945. return;
  946. if(m->part != nil)
  947. return;
  948. if(cistrncmp(s_to_c(m->type), "text", 4) != 0)
  949. return;
  950. if(cistrcmp(s_to_c(m->charset), "us-ascii") == 0 ||
  951. cistrcmp(s_to_c(m->charset), "iso-8859-1") == 0){
  952. len = is8bit(m);
  953. if(len > 0){
  954. len = 2*len + m->bend - m->body + 1;
  955. x = emalloc(len);
  956. len = latin1toutf(x, m->body, m->bend);
  957. if(m->ballocd)
  958. free(m->body);
  959. m->body = x;
  960. m->bend = x + len;
  961. m->ballocd = 1;
  962. }
  963. } else if(cistrcmp(s_to_c(m->charset), "iso-8859-2") == 0){
  964. len = xtoutf("8859-2", &x, m->body, m->bend);
  965. if(len != 0){
  966. if(m->ballocd)
  967. free(m->body);
  968. m->body = x;
  969. m->bend = x + len;
  970. m->ballocd = 1;
  971. }
  972. } else if(cistrcmp(s_to_c(m->charset), "iso-8859-15") == 0){
  973. len = xtoutf("8859-15", &x, m->body, m->bend);
  974. if(len != 0){
  975. if(m->ballocd)
  976. free(m->body);
  977. m->body = x;
  978. m->bend = x + len;
  979. m->ballocd = 1;
  980. }
  981. } else if(cistrcmp(s_to_c(m->charset), "big5") == 0){
  982. len = xtoutf("big5", &x, m->body, m->bend);
  983. if(len != 0){
  984. if(m->ballocd)
  985. free(m->body);
  986. m->body = x;
  987. m->bend = x + len;
  988. m->ballocd = 1;
  989. }
  990. } else if(cistrcmp(s_to_c(m->charset), "iso-2022-jp") == 0){
  991. len = xtoutf("jis", &x, m->body, m->bend);
  992. if(len != 0){
  993. if(m->ballocd)
  994. free(m->body);
  995. m->body = x;
  996. m->bend = x + len;
  997. m->ballocd = 1;
  998. }
  999. } else if(cistrcmp(s_to_c(m->charset), "windows-1257") == 0
  1000. || cistrcmp(s_to_c(m->charset), "windows-1252") == 0){
  1001. len = is8bit(m);
  1002. if(len > 0){
  1003. len = 2*len + m->bend - m->body + 1;
  1004. x = emalloc(len);
  1005. len = windows1257toutf(x, m->body, m->bend);
  1006. if(m->ballocd)
  1007. free(m->body);
  1008. m->body = x;
  1009. m->bend = x + len;
  1010. m->ballocd = 1;
  1011. }
  1012. } else if(cistrcmp(s_to_c(m->charset), "windows-1251") == 0){
  1013. len = xtoutf("cp1251", &x, m->body, m->bend);
  1014. if(len != 0){
  1015. if(m->ballocd)
  1016. free(m->body);
  1017. m->body = x;
  1018. m->bend = x + len;
  1019. m->ballocd = 1;
  1020. }
  1021. } else if(cistrcmp(s_to_c(m->charset), "koi8-r") == 0){
  1022. len = xtoutf("koi8", &x, m->body, m->bend);
  1023. if(len != 0){
  1024. if(m->ballocd)
  1025. free(m->body);
  1026. m->body = x;
  1027. m->bend = x + len;
  1028. m->ballocd = 1;
  1029. }
  1030. }
  1031. m->converted = 1;
  1032. }
  1033. enum
  1034. {
  1035. Self= 1,
  1036. Hex= 2,
  1037. };
  1038. uchar tableqp[256];
  1039. static void
  1040. initquoted(void)
  1041. {
  1042. int c;
  1043. memset(tableqp, 0, 256);
  1044. for(c = ' '; c <= '<'; c++)
  1045. tableqp[c] = Self;
  1046. for(c = '>'; c <= '~'; c++)
  1047. tableqp[c] = Self;
  1048. tableqp['\t'] = Self;
  1049. tableqp['='] = Hex;
  1050. }
  1051. static int
  1052. hex2int(int x)
  1053. {
  1054. if(x >= '0' && x <= '9')
  1055. return x - '0';
  1056. if(x >= 'A' && x <= 'F')
  1057. return (x - 'A') + 10;
  1058. if(x >= 'a' && x <= 'f')
  1059. return (x - 'a') + 10;
  1060. return 0;
  1061. }
  1062. static char*
  1063. decquotedline(char *out, char *in, char *e, int uscores)
  1064. {
  1065. int c, soft;
  1066. /* dump trailing white space */
  1067. while(e >= in && (*e == ' ' || *e == '\t' || *e == '\r' || *e == '\n'))
  1068. e--;
  1069. /* trailing '=' means no newline */
  1070. if(*e == '='){
  1071. soft = 1;
  1072. e--;
  1073. } else
  1074. soft = 0;
  1075. while(in <= e){
  1076. c = (*in++) & 0xff;
  1077. switch(tableqp[c]){
  1078. case Self:
  1079. if(uscores && c == '_')
  1080. c = ' ';
  1081. *out++ = c;
  1082. break;
  1083. case Hex:
  1084. c = hex2int(*in++)<<4;
  1085. c |= hex2int(*in++);
  1086. *out++ = c;
  1087. break;
  1088. }
  1089. }
  1090. if(!soft)
  1091. *out++ = '\n';
  1092. *out = 0;
  1093. return out;
  1094. }
  1095. int
  1096. decquoted(char *out, char *in, char *e, int uscores)
  1097. {
  1098. char *p, *nl;
  1099. if(tableqp[' '] == 0)
  1100. initquoted();
  1101. p = out;
  1102. while((nl = strchr(in, '\n')) != nil && nl < e){
  1103. p = decquotedline(p, in, nl, uscores);
  1104. in = nl + 1;
  1105. }
  1106. if(in < e)
  1107. p = decquotedline(p, in, e-1, uscores);
  1108. // make sure we end with a new line
  1109. if(*(p-1) != '\n'){
  1110. *p++ = '\n';
  1111. *p = 0;
  1112. }
  1113. return p - out;
  1114. }
  1115. static char*
  1116. lowercase(char *p)
  1117. {
  1118. char *op;
  1119. int c;
  1120. for(op = p; c = *p; p++)
  1121. if(isupper(c))
  1122. *p = tolower(c);
  1123. return op;
  1124. }
  1125. /*
  1126. * return number of 8 bit characters
  1127. */
  1128. static int
  1129. is8bit(Message *m)
  1130. {
  1131. int count = 0;
  1132. char *p;
  1133. for(p = m->body; p < m->bend; p++)
  1134. if(*p & 0x80)
  1135. count++;
  1136. return count;
  1137. }
  1138. // translate latin1 directly since it fits neatly in utf
  1139. int
  1140. latin1toutf(char *out, char *in, char *e)
  1141. {
  1142. Rune r;
  1143. char *p;
  1144. p = out;
  1145. for(; in < e; in++){
  1146. r = (*in) & 0xff;
  1147. p += runetochar(p, &r);
  1148. }
  1149. *p = 0;
  1150. return p - out;
  1151. }
  1152. // translate any thing else using the tcs program
  1153. int
  1154. xtoutf(char *charset, char **out, char *in, char *e)
  1155. {
  1156. char *av[4];
  1157. int totcs[2];
  1158. int fromtcs[2];
  1159. int n, len, sofar;
  1160. char *p;
  1161. len = e-in+1;
  1162. sofar = 0;
  1163. *out = p = malloc(len+1);
  1164. if(p == nil)
  1165. return 0;
  1166. av[0] = charset;
  1167. av[1] = "-f";
  1168. av[2] = charset;
  1169. av[3] = 0;
  1170. if(pipe(totcs) < 0)
  1171. return 0;
  1172. if(pipe(fromtcs) < 0){
  1173. close(totcs[0]); close(totcs[1]);
  1174. return 0;
  1175. }
  1176. switch(rfork(RFPROC|RFFDG|RFNOWAIT)){
  1177. case -1:
  1178. close(fromtcs[0]); close(fromtcs[1]);
  1179. close(totcs[0]); close(totcs[1]);
  1180. return 0;
  1181. case 0:
  1182. close(fromtcs[0]); close(totcs[1]);
  1183. dup(fromtcs[1], 1);
  1184. dup(totcs[0], 0);
  1185. close(fromtcs[1]); close(totcs[0]);
  1186. dup(open("/dev/null", OWRITE), 2);
  1187. exec("/bin/tcs", av);
  1188. _exits(0);
  1189. default:
  1190. close(fromtcs[1]); close(totcs[0]);
  1191. switch(rfork(RFPROC|RFFDG|RFNOWAIT)){
  1192. case -1:
  1193. close(fromtcs[0]); close(totcs[1]);
  1194. return 0;
  1195. case 0:
  1196. close(fromtcs[0]);
  1197. while(in < e){
  1198. n = write(totcs[1], in, e-in);
  1199. if(n <= 0)
  1200. break;
  1201. in += n;
  1202. }
  1203. close(totcs[1]);
  1204. _exits(0);
  1205. default:
  1206. close(totcs[1]);
  1207. for(;;){
  1208. n = read(fromtcs[0], &p[sofar], len-sofar);
  1209. if(n <= 0)
  1210. break;
  1211. sofar += n;
  1212. p[sofar] = 0;
  1213. if(sofar == len){
  1214. len += 1024;
  1215. *out = p = realloc(p, len+1);
  1216. if(p == nil)
  1217. return 0;
  1218. }
  1219. }
  1220. close(fromtcs[0]);
  1221. break;
  1222. }
  1223. break;
  1224. }
  1225. return sofar;
  1226. }
  1227. enum {
  1228. Winstart= 0x7f,
  1229. Winend= 0x9f,
  1230. };
  1231. Rune winchars[] = {
  1232. L'•',
  1233. L'•', L'•', L'‚', L'ƒ', L'„', L'…', L'†', L'‡',
  1234. L'ˆ', L'‰', L'Š', L'‹', L'Œ', L'•', L'•', L'•',
  1235. L'•', L'‘', L'’', L'“', L'”', L'•', L'–', L'—',
  1236. L'˜', L'™', L'š', L'›', L'œ', L'•', L'•', L'Ÿ',
  1237. };
  1238. int
  1239. windows1257toutf(char *out, char *in, char *e)
  1240. {
  1241. Rune r;
  1242. char *p;
  1243. p = out;
  1244. for(; in < e; in++){
  1245. r = (*in) & 0xff;
  1246. if(r >= 0x7f && r <= 0x9f)
  1247. r = winchars[r-0x7f];
  1248. p += runetochar(p, &r);
  1249. }
  1250. *p = 0;
  1251. return p - out;
  1252. }
  1253. void *
  1254. emalloc(ulong n)
  1255. {
  1256. void *p;
  1257. p = mallocz(n, 1);
  1258. if(!p){
  1259. fprint(2, "%s: out of memory alloc %lud\n", argv0, n);
  1260. exits("out of memory");
  1261. }
  1262. setmalloctag(p, getcallerpc(&n));
  1263. return p;
  1264. }
  1265. void *
  1266. erealloc(void *p, ulong n)
  1267. {
  1268. if(n == 0)
  1269. n = 1;
  1270. p = realloc(p, n);
  1271. if(!p){
  1272. fprint(2, "%s: out of memory realloc %lud\n", argv0, n);
  1273. exits("out of memory");
  1274. }
  1275. setrealloctag(p, getcallerpc(&p));
  1276. return p;
  1277. }
  1278. void
  1279. mailplumb(Mailbox *mb, Message *m, int delete)
  1280. {
  1281. Plumbmsg p;
  1282. Plumbattr a[7];
  1283. char buf[256];
  1284. int ai;
  1285. char lenstr[10], *from, *subject, *date;
  1286. static int fd = -1;
  1287. if(m->subject822 == nil)
  1288. subject = "";
  1289. else
  1290. subject = s_to_c(m->subject822);
  1291. if(m->from822 != nil)
  1292. from = s_to_c(m->from822);
  1293. else if(m->unixfrom != nil)
  1294. from = s_to_c(m->unixfrom);
  1295. else
  1296. from = "";
  1297. if(m->unixdate != nil)
  1298. date = s_to_c(m->unixdate);
  1299. else
  1300. date = "";
  1301. sprint(lenstr, "%ld", m->end-m->start);
  1302. if(biffing && !delete)
  1303. print("[ %s / %s / %s ]\n", from, subject, lenstr);
  1304. if(!plumbing)
  1305. return;
  1306. if(fd < 0)
  1307. fd = plumbopen("send", OWRITE);
  1308. if(fd < 0)
  1309. return;
  1310. p.src = "mailfs";
  1311. p.dst = "seemail";
  1312. p.wdir = "/mail/fs";
  1313. p.type = "text";
  1314. ai = 0;
  1315. a[ai].name = "filetype";
  1316. a[ai].value = "mail";
  1317. a[++ai].name = "sender";
  1318. a[ai].value = from;
  1319. a[ai-1].next = &a[ai];
  1320. a[++ai].name = "length";
  1321. a[ai].value = lenstr;
  1322. a[ai-1].next = &a[ai];
  1323. a[++ai].name = "mailtype";
  1324. a[ai].value = delete?"delete":"new";
  1325. a[ai-1].next = &a[ai];
  1326. a[++ai].name = "date";
  1327. a[ai].value = date;
  1328. a[ai-1].next = &a[ai];
  1329. if(m->sdigest){
  1330. a[++ai].name = "digest";
  1331. a[ai].value = s_to_c(m->sdigest);
  1332. a[ai-1].next = &a[ai];
  1333. }
  1334. a[ai].next = nil;
  1335. p.attr = a;
  1336. snprint(buf, sizeof(buf), "%s/%s/%s",
  1337. mntpt, mb->name, m->name);
  1338. p.ndata = strlen(buf);
  1339. p.data = buf;
  1340. plumbsend(fd, &p);
  1341. }
  1342. //
  1343. // count the number of lines in the body (for imap4)
  1344. //
  1345. void
  1346. countlines(Message *m)
  1347. {
  1348. int i;
  1349. char *p;
  1350. i = 0;
  1351. for(p = strchr(m->rbody, '\n'); p != nil && p < m->rbend; p = strchr(p+1, '\n'))
  1352. i++;
  1353. sprint(m->lines, "%d", i);
  1354. }
  1355. char *LOG = "fs";
  1356. void
  1357. logmsg(char *s, Message *m)
  1358. {
  1359. int pid;
  1360. if(!logging)
  1361. return;
  1362. pid = getpid();
  1363. if(m == nil)
  1364. syslog(0, LOG, "%s.%d: %s", user, pid, s);
  1365. else
  1366. syslog(0, LOG, "%s.%d: %s msg from %s digest %s",
  1367. user, pid, s,
  1368. m->from822 ? s_to_c(m->from822) : "?",
  1369. s_to_c(m->sdigest));
  1370. }
  1371. /*
  1372. * squeeze nulls out of the body
  1373. */
  1374. static void
  1375. nullsqueeze(Message *m)
  1376. {
  1377. char *p, *q;
  1378. q = memchr(m->body, 0, m->end-m->body);
  1379. if(q == nil)
  1380. return;
  1381. for(p = m->body; q < m->end; q++){
  1382. if(*q == 0)
  1383. continue;
  1384. *p++ = *q;
  1385. }
  1386. m->bend = m->rbend = m->end = p;
  1387. }
  1388. //
  1389. // convert an RFC822 date into a Unix style date
  1390. // for when the Unix From line isn't there (e.g. POP3).
  1391. // enough client programs depend on having a Unix date
  1392. // that it's easiest to write this conversion code once, right here.
  1393. //
  1394. // people don't follow RFC822 particularly closely,
  1395. // so we use strtotm, which is a bunch of heuristics.
  1396. //
  1397. extern int strtotm(char*, Tm*);
  1398. String*
  1399. date822tounix(char *s)
  1400. {
  1401. char *p, *q;
  1402. Tm tm;
  1403. if(strtotm(s, &tm) < 0)
  1404. return nil;
  1405. p = asctime(&tm);
  1406. if(q = strchr(p, '\n'))
  1407. *q = '\0';
  1408. return s_copy(p);
  1409. }