nedmail.c 39 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592159315941595159615971598159916001601160216031604160516061607160816091610161116121613161416151616161716181619162016211622162316241625162616271628162916301631163216331634163516361637163816391640164116421643164416451646164716481649165016511652165316541655165616571658165916601661166216631664166516661667166816691670167116721673167416751676167716781679168016811682168316841685168616871688168916901691169216931694169516961697169816991700170117021703170417051706170717081709171017111712171317141715171617171718171917201721172217231724172517261727172817291730173117321733173417351736173717381739174017411742174317441745174617471748174917501751175217531754175517561757175817591760176117621763176417651766176717681769177017711772177317741775177617771778177917801781178217831784178517861787178817891790179117921793179417951796179717981799180018011802180318041805180618071808180918101811181218131814181518161817181818191820182118221823182418251826182718281829183018311832183318341835183618371838183918401841184218431844184518461847184818491850185118521853185418551856185718581859186018611862186318641865186618671868186918701871187218731874187518761877187818791880188118821883188418851886188718881889189018911892189318941895189618971898189919001901190219031904190519061907190819091910191119121913191419151916191719181919192019211922192319241925192619271928192919301931193219331934193519361937193819391940194119421943194419451946194719481949195019511952195319541955195619571958195919601961196219631964196519661967196819691970197119721973197419751976197719781979198019811982198319841985198619871988198919901991199219931994199519961997199819992000200120022003200420052006200720082009201020112012201320142015201620172018201920202021202220232024202520262027202820292030203120322033203420352036203720382039204020412042204320442045204620472048204920502051205220532054205520562057205820592060206120622063206420652066206720682069207020712072207320742075207620772078207920802081208220832084208520862087208820892090209120922093209420952096209720982099210021012102210321042105210621072108210921102111211221132114211521162117211821192120212121222123212421252126212721282129213021312132213321342135213621372138213921402141214221432144214521462147214821492150215121522153215421552156215721582159216021612162216321642165216621672168216921702171217221732174217521762177217821792180218121822183218421852186218721882189219021912192219321942195219621972198219922002201220222032204220522062207220822092210221122122213221422152216221722182219222022212222222322242225222622272228222922302231223222332234223522362237223822392240224122422243224422452246224722482249225022512252225322542255
  1. #include "common.h"
  2. #include <ctype.h>
  3. #include <plumb.h>
  4. typedef struct Message Message;
  5. typedef struct Ctype Ctype;
  6. typedef struct Cmd Cmd;
  7. char root[Pathlen];
  8. char mbname[Elemlen];
  9. int rootlen;
  10. int didopen;
  11. char *user;
  12. char wd[2048];
  13. String *mbpath;
  14. int natural;
  15. int interrupted;
  16. struct Message {
  17. Message *next;
  18. Message *prev;
  19. Message *cmd;
  20. Message *child;
  21. Message *parent;
  22. String *path;
  23. int id;
  24. int len;
  25. int fileno; // number of directory
  26. String *info;
  27. char *from;
  28. char *to;
  29. char *cc;
  30. char *replyto;
  31. char *date;
  32. char *subject;
  33. char *type;
  34. char *disposition;
  35. char *filename;
  36. char deleted;
  37. char stored;
  38. };
  39. Message top;
  40. struct Ctype {
  41. char *type;
  42. char *ext;
  43. int display;
  44. char *plumbdest;
  45. };
  46. Ctype ctype[] = {
  47. { "text/plain", "txt", 1, 0 },
  48. { "text/html", "htm", 1, 0 },
  49. { "text/html", "html", 1, 0 },
  50. { "text/tab-separated-values", "tsv", 1, 0 },
  51. { "text/richtext", "rtx", 1, 0 },
  52. { "text", "txt", 1, 0 },
  53. { "message/rfc822", "msg", 0, 0 },
  54. { "image/jpeg", "jpg", 0, "image" },
  55. { "image/gif", "gif", 0, "image" },
  56. { "application/octet-stream", "bin", 0, 0 },
  57. { "application/pdf", "pdf", 0, "" },
  58. { "application/postscript", "ps", 0, "" },
  59. { "application/", 0, 0, 0 },
  60. { "image/", 0, 0, 0 },
  61. { "multipart/", "mul", 0, 0 },
  62. { "", 0, 0, 0 },
  63. };
  64. Message* acmd(Cmd*, Message*);
  65. Message* bcmd(Cmd*, Message*);
  66. Message* dcmd(Cmd*, Message*);
  67. Message* eqcmd(Cmd*, Message*);
  68. Message* hcmd(Cmd*, Message*);
  69. Message* Hcmd(Cmd*, Message*);
  70. Message* helpcmd(Cmd*, Message*);
  71. Message* icmd(Cmd*, Message*);
  72. Message* pcmd(Cmd*, Message*);
  73. Message* qcmd(Cmd*, Message*);
  74. Message* rcmd(Cmd*, Message*);
  75. Message* scmd(Cmd*, Message*);
  76. Message* ucmd(Cmd*, Message*);
  77. Message* wcmd(Cmd*, Message*);
  78. Message* xcmd(Cmd*, Message*);
  79. Message* pipecmd(Cmd*, Message*);
  80. Message* bangcmd(Cmd*, Message*);
  81. Message* Pcmd(Cmd*, Message*);
  82. Message* mcmd(Cmd*, Message*);
  83. Message* fcmd(Cmd*, Message*);
  84. Message* quotecmd(Cmd*, Message*);
  85. struct {
  86. char *cmd;
  87. int args;
  88. Message* (*f)(Cmd*, Message*);
  89. char *help;
  90. } cmdtab[] = {
  91. { "a", 1, acmd, "a reply to sender and recipients" },
  92. { "A", 1, acmd, "A reply to sender and recipients with copy" },
  93. { "b", 0, bcmd, "b print the next 10 headers" },
  94. { "d", 0, dcmd, "d mark for deletion" },
  95. { "f", 0, fcmd, "f file message by from address" },
  96. { "h", 0, hcmd, "h print elided message summary (,h for all)" },
  97. { "help", 0, helpcmd, "help print this info" },
  98. { "H", 0, Hcmd, "H print message's MIME structure " },
  99. { "i", 0, icmd, "i incorporate new mail" },
  100. { "m", 1, mcmd, "m addr forward mail" },
  101. { "M", 1, mcmd, "M addr forward mail with message" },
  102. { "p", 0, pcmd, "p print the processed message" },
  103. { "P", 0, Pcmd, "P print the raw message" },
  104. { "\"", 0, quotecmd, "\" print a quoted version of msg" },
  105. { "q", 0, qcmd, "q exit and remove all deleted mail" },
  106. { "r", 1, rcmd, "r [addr] reply to sender plus any addrs specified" },
  107. { "rf", 1, rcmd, "rf [addr]file message and reply" },
  108. { "R", 1, rcmd, "R [addr] reply including copy of message" },
  109. { "Rf", 1, rcmd, "Rf [addr]file message and reply with copy" },
  110. { "s", 1, scmd, "s file append raw message to file" },
  111. { "u", 0, ucmd, "u remove deletion mark" },
  112. { "w", 1, wcmd, "w file store message contents as file" },
  113. { "x", 0, xcmd, "x exit with mailbox unchanged" },
  114. { "=", 1, eqcmd, "= print current message number" },
  115. { "|", 1, pipecmd, "|cmd pipe raw message to a command" },
  116. { "!", 1, bangcmd, "!cmd run a command" },
  117. { nil, 0, nil, nil },
  118. };
  119. enum
  120. {
  121. NARG= 32,
  122. };
  123. struct Cmd {
  124. Message *msgs;
  125. Message *(*f)(Cmd*, Message*);
  126. int an;
  127. char *av[NARG];
  128. int delete;
  129. };
  130. Biobuf out;
  131. int startedfs;
  132. int reverse;
  133. String* file2string(String*, char*);
  134. int dir2message(Message*, int);
  135. int filelen(String*, char*);
  136. String* extendpath(String*, char*);
  137. void snprintheader(char*, int, Message*);
  138. void cracktime(char*, char*, int);
  139. int cistrncmp(char*, char*, int);
  140. int cistrcmp(char*, char*);
  141. Reprog* parsesearch(char**);
  142. char* parseaddr(char**, Message*, Message*, Message*, Message**);
  143. char* parsecmd(char*, Cmd*, Message*, Message*);
  144. char* readline(char*, char*, int);
  145. void messagecount(Message*);
  146. void system(char*, char**, int);
  147. void mkid(String*, Message*);
  148. int switchmb(char*, char*);
  149. void closemb(void);
  150. int lineize(char*, char**, int);
  151. int rawsearch(Message*, Reprog*);
  152. Message* dosingleton(Message*, char*);
  153. String* rooted(String*);
  154. int plumb(Message*, Ctype*);
  155. String* addrecolon(char*);
  156. void exitfs(char*);
  157. void
  158. usage(void)
  159. {
  160. fprint(2, "usage: %s [-cr] [-f mboxdir] [-s singleton]\n", argv0);
  161. exits("usage");
  162. }
  163. void
  164. catchnote(void*, char *note)
  165. {
  166. if(strstr(note, "interrupt") != nil){
  167. interrupted = 1;
  168. noted(NCONT);
  169. }
  170. noted(NDFLT);
  171. }
  172. void
  173. main(int argc, char **argv)
  174. {
  175. Message *cur, *m, *x;
  176. char cmdline[4*1024];
  177. Cmd cmd;
  178. char *err;
  179. int n, cflag;
  180. char *av[4];
  181. String *prompt;
  182. char *file, *singleton;
  183. Binit(&out, 1, OWRITE);
  184. file = nil;
  185. singleton = nil;
  186. reverse = 1;
  187. cflag = 0;
  188. ARGBEGIN {
  189. case 'c':
  190. cflag = 1;
  191. break;
  192. case 'f':
  193. file = EARGF(usage());
  194. break;
  195. case 's':
  196. singleton = EARGF(usage());
  197. break;
  198. case 'r':
  199. reverse = 0;
  200. break;
  201. case 'n':
  202. natural = 1;
  203. reverse = 0;
  204. break;
  205. default:
  206. usage();
  207. break;
  208. } ARGEND;
  209. user = getlog();
  210. if(user == nil || *user == 0)
  211. sysfatal("can't read user name");
  212. if(cflag){
  213. if(argc > 0)
  214. creatembox(user, argv[0]);
  215. else
  216. creatembox(user, nil);
  217. exits(0);
  218. }
  219. if(argc)
  220. usage();
  221. if(access("/mail/fs/ctl", 0) < 0){
  222. startedfs = 1;
  223. av[0] = "fs";
  224. av[1] = "-p";
  225. av[2] = 0;
  226. system("/bin/upas/fs", av, -1);
  227. }
  228. switchmb(file, singleton);
  229. top.path = s_copy(root);
  230. if(singleton != nil){
  231. cur = dosingleton(&top, singleton);
  232. if(cur == nil){
  233. Bprint(&out, "no message\n");
  234. exitfs(0);
  235. }
  236. pcmd(nil, cur);
  237. } else {
  238. cur = &top;
  239. n = dir2message(&top, reverse);
  240. if(n < 0)
  241. sysfatal("can't read %s", s_to_c(top.path));
  242. Bprint(&out, "%d messages\n", n);
  243. }
  244. notify(catchnote);
  245. prompt = s_new();
  246. for(;;){
  247. s_reset(prompt);
  248. if(cur == &top)
  249. s_append(prompt, ": ");
  250. else {
  251. mkid(prompt, cur);
  252. s_append(prompt, ": ");
  253. }
  254. if(readline(s_to_c(prompt), cmdline, sizeof(cmdline)) == nil)
  255. break;
  256. err = parsecmd(cmdline, &cmd, top.child, cur);
  257. if(err != nil){
  258. Bprint(&out, "!%s\n", err);
  259. continue;
  260. }
  261. if(singleton != nil && cmd.f == icmd){
  262. Bprint(&out, "!illegal command\n");
  263. continue;
  264. }
  265. interrupted = 0;
  266. if(cmd.msgs == nil || cmd.msgs == &top){
  267. x = (*cmd.f)(&cmd, &top);
  268. if(x != nil)
  269. cur = x;
  270. } else for(m = cmd.msgs; m != nil; m = m->cmd){
  271. x = m;
  272. if(cmd.delete){
  273. dcmd(&cmd, x);
  274. // dp acts differently than all other commands
  275. // since its an old lesk idiom that people love.
  276. // it deletes the current message, moves the current
  277. // pointer ahead one and prints.
  278. if(cmd.f == pcmd){
  279. if(x->next == nil){
  280. Bprint(&out, "!address\n");
  281. cur = x;
  282. break;
  283. } else
  284. x = x->next;
  285. }
  286. }
  287. x = (*cmd.f)(&cmd, x);
  288. if(x != nil)
  289. cur = x;
  290. if(interrupted)
  291. break;
  292. if(singleton != nil && (cmd.delete || cmd.f == dcmd))
  293. qcmd(nil, nil);
  294. }
  295. }
  296. qcmd(nil, nil);
  297. }
  298. //
  299. // read the message info
  300. //
  301. Message*
  302. file2message(Message *parent, char *name)
  303. {
  304. Message *m;
  305. String *path;
  306. char *f[10];
  307. m = mallocz(sizeof(Message), 1);
  308. if(m == nil)
  309. return nil;
  310. m->path = path = extendpath(parent->path, name);
  311. m->fileno = atoi(name);
  312. m->info = file2string(path, "info");
  313. lineize(s_to_c(m->info), f, nelem(f));
  314. m->from = f[0];
  315. m->to = f[1];
  316. m->cc = f[2];
  317. m->replyto = f[3];
  318. m->date = f[4];
  319. m->subject = f[5];
  320. m->type = f[6];
  321. m->disposition = f[7];
  322. m->filename = f[8];
  323. m->len = filelen(path, "raw");
  324. if(strstr(m->type, "multipart") != nil || strcmp(m->type, "message/rfc822") == 0)
  325. dir2message(m, 0);
  326. m->parent = parent;
  327. return m;
  328. }
  329. //
  330. // read a directory into a list of messages
  331. //
  332. int
  333. dir2message(Message *parent, int reverse)
  334. {
  335. int i, n, fd, highest, newmsgs;
  336. Dir *d;
  337. Message *first, *last, *m;
  338. fd = open(s_to_c(parent->path), OREAD);
  339. if(fd < 0)
  340. return -1;
  341. // count current entries
  342. first = parent->child;
  343. highest = newmsgs = 0;
  344. for(last = parent->child; last != nil && last->next != nil; last = last->next)
  345. if(last->fileno > highest)
  346. highest = last->fileno;
  347. if(last != nil)
  348. if(last->fileno > highest)
  349. highest = last->fileno;
  350. n = dirreadall(fd, &d);
  351. for(i = 0; i < n; i++){
  352. if((d[i].qid.type & QTDIR) == 0)
  353. continue;
  354. if(atoi(d[i].name) <= highest)
  355. continue;
  356. m = file2message(parent, d[i].name);
  357. if(m == nil)
  358. break;
  359. newmsgs++;
  360. if(reverse){
  361. m->next = first;
  362. if(first != nil)
  363. first->prev = m;
  364. first = m;
  365. } else {
  366. if(first == nil)
  367. first = m;
  368. else
  369. last->next = m;
  370. m->prev = last;
  371. last = m;
  372. }
  373. }
  374. free(d);
  375. close(fd);
  376. parent->child = first;
  377. // renumber
  378. i = 1;
  379. for(m = first; m != nil; m = m->next)
  380. m->id = natural ? m->fileno : i++;
  381. return newmsgs;
  382. }
  383. //
  384. // point directly to a message
  385. //
  386. Message*
  387. dosingleton(Message *parent, char *path)
  388. {
  389. char *p, *np;
  390. Message *m;
  391. // walk down to message and read it
  392. if(strlen(path) < rootlen)
  393. return nil;
  394. if(path[rootlen] != '/')
  395. return nil;
  396. p = path+rootlen+1;
  397. np = strchr(p, '/');
  398. if(np != nil)
  399. *np = 0;
  400. m = file2message(parent, p);
  401. if(m == nil)
  402. return nil;
  403. parent->child = m;
  404. m->id = 1;
  405. // walk down to requested component
  406. while(np != nil){
  407. *np = '/';
  408. np = strchr(np+1, '/');
  409. if(np != nil)
  410. *np = 0;
  411. for(m = m->child; m != nil; m = m->next)
  412. if(strcmp(path, s_to_c(m->path)) == 0)
  413. return m;
  414. if(m == nil)
  415. return nil;
  416. }
  417. return m;
  418. }
  419. //
  420. // read a file into a string
  421. //
  422. String*
  423. file2string(String *dir, char *file)
  424. {
  425. String *s;
  426. int fd, n, m;
  427. s = extendpath(dir, file);
  428. fd = open(s_to_c(s), OREAD);
  429. s_grow(s, 512); /* avoid multiple reads on info files */
  430. s_reset(s);
  431. if(fd < 0)
  432. return s;
  433. for(;;){
  434. n = s->end - s->ptr;
  435. if(n == 0){
  436. s_grow(s, 128);
  437. continue;
  438. }
  439. m = read(fd, s->ptr, n);
  440. if(m <= 0)
  441. break;
  442. s->ptr += m;
  443. if(m < n)
  444. break;
  445. }
  446. s_terminate(s);
  447. close(fd);
  448. return s;
  449. }
  450. //
  451. // get the length of a file
  452. //
  453. int
  454. filelen(String *dir, char *file)
  455. {
  456. String *path;
  457. Dir *d;
  458. int rv;
  459. path = extendpath(dir, file);
  460. d = dirstat(s_to_c(path));
  461. if(d == nil){
  462. s_free(path);
  463. return -1;
  464. }
  465. s_free(path);
  466. rv = d->length;
  467. free(d);
  468. return rv;
  469. }
  470. //
  471. // walk the path name an element
  472. //
  473. String*
  474. extendpath(String *dir, char *name)
  475. {
  476. String *path;
  477. if(strcmp(s_to_c(dir), ".") == 0)
  478. path = s_new();
  479. else {
  480. path = s_copy(s_to_c(dir));
  481. s_append(path, "/");
  482. }
  483. s_append(path, name);
  484. return path;
  485. }
  486. int
  487. cistrncmp(char *a, char *b, int n)
  488. {
  489. while(n-- > 0){
  490. if(tolower(*a++) != tolower(*b++))
  491. return -1;
  492. }
  493. return 0;
  494. }
  495. int
  496. cistrcmp(char *a, char *b)
  497. {
  498. for(;;){
  499. if(tolower(*a) != tolower(*b++))
  500. return -1;
  501. if(*a++ == 0)
  502. break;
  503. }
  504. return 0;
  505. }
  506. char*
  507. nosecs(char *t)
  508. {
  509. char *p;
  510. p = strchr(t, ':');
  511. if(p == nil)
  512. return t;
  513. p = strchr(p+1, ':');
  514. if(p != nil)
  515. *p = 0;
  516. return t;
  517. }
  518. char *months[12] =
  519. {
  520. "jan", "feb", "mar", "apr", "may", "jun",
  521. "jul", "aug", "sep", "oct", "nov", "dec"
  522. };
  523. int
  524. month(char *m)
  525. {
  526. int i;
  527. for(i = 0; i < 12; i++)
  528. if(cistrcmp(m, months[i]) == 0)
  529. return i+1;
  530. return 1;
  531. }
  532. enum
  533. {
  534. Yearsecs= 365*24*60*60
  535. };
  536. void
  537. cracktime(char *d, char *out, int len)
  538. {
  539. char in[64];
  540. char *f[6];
  541. int n;
  542. Tm tm;
  543. long now, then;
  544. char *dtime;
  545. *out = 0;
  546. if(d == nil)
  547. return;
  548. strncpy(in, d, sizeof(in));
  549. in[sizeof(in)-1] = 0;
  550. n = getfields(in, f, 6, 1, " \t\r\n");
  551. if(n != 6){
  552. // unknown style
  553. snprint(out, 16, "%10.10s", d);
  554. return;
  555. }
  556. now = time(0);
  557. memset(&tm, 0, sizeof tm);
  558. if(strchr(f[0], ',') != nil && strchr(f[4], ':') != nil){
  559. // 822 style
  560. tm.year = atoi(f[3])-1900;
  561. tm.mon = month(f[2]);
  562. tm.mday = atoi(f[1]);
  563. dtime = nosecs(f[4]);
  564. then = tm2sec(&tm);
  565. } else if(strchr(f[3], ':') != nil){
  566. // unix style
  567. tm.year = atoi(f[5])-1900;
  568. tm.mon = month(f[1]);
  569. tm.mday = atoi(f[2]);
  570. dtime = nosecs(f[3]);
  571. then = tm2sec(&tm);
  572. } else {
  573. then = now;
  574. tm = *localtime(now);
  575. dtime = "";
  576. }
  577. if(now - then < Yearsecs/2)
  578. snprint(out, len, "%2d/%2.2d %s", tm.mon, tm.mday, dtime);
  579. else
  580. snprint(out, len, "%2d/%2.2d %4d", tm.mon, tm.mday, tm.year+1900);
  581. }
  582. Ctype*
  583. findctype(char *t)
  584. {
  585. Ctype *cp;
  586. for(cp = ctype; ; cp++)
  587. if(strncmp(cp->type, t, strlen(cp->type)) == 0)
  588. break;
  589. return cp;
  590. }
  591. void
  592. mkid(String *s, Message *m)
  593. {
  594. char buf[32];
  595. if(m->parent != &top){
  596. mkid(s, m->parent);
  597. s_append(s, ".");
  598. }
  599. sprint(buf, "%d", m->id);
  600. s_append(s, buf);
  601. }
  602. void
  603. snprintheader(char *buf, int len, Message *m)
  604. {
  605. char timebuf[32];
  606. String *id;
  607. // create id
  608. id = s_new();
  609. mkid(id, m);
  610. if(*m->from == 0){
  611. // no from
  612. snprint(buf, len, "%-3s %s %5d %s",
  613. s_to_c(id),
  614. m->type,
  615. m->len,
  616. m->filename);
  617. } else if(*m->subject){
  618. cracktime(m->date, timebuf, sizeof(timebuf));
  619. snprint(buf, len, "%-3s %c%c%c %5d %11.11s %s %s",
  620. s_to_c(id),
  621. m->child ? 'H' : ' ',
  622. m->deleted ? 'd' : ' ',
  623. m->stored ? 's' : ' ',
  624. m->len,
  625. timebuf,
  626. m->from,
  627. m->subject);
  628. } else {
  629. cracktime(m->date, timebuf, sizeof(timebuf));
  630. snprint(buf, len, "%-3s %c%c%c %5d %11.11s %s",
  631. s_to_c(id),
  632. m->child ? 'H' : ' ',
  633. m->deleted ? 'd' : ' ',
  634. m->stored ? 's' : ' ',
  635. m->len,
  636. timebuf,
  637. m->from);
  638. }
  639. s_free(id);
  640. }
  641. char *spaces = " ";
  642. void
  643. snprintHeader(char *buf, int len, int indent, Message *m)
  644. {
  645. String *id;
  646. char typeid[64];
  647. char *p, *e;
  648. // create id
  649. id = s_new();
  650. mkid(id, m);
  651. e = buf + len;
  652. snprint(typeid, sizeof typeid, "%s %s", s_to_c(id), m->type);
  653. if(indent < 6)
  654. p = seprint(buf, e, "%-32s %-6d ", typeid, m->len);
  655. else
  656. p = seprint(buf, e, "%-64s %-6d ", typeid, m->len);
  657. if(m->filename && *m->filename)
  658. p = seprint(p, e, "(file,%s)", m->filename);
  659. if(m->from && *m->from)
  660. p = seprint(p, e, "(from,%s)", m->from);
  661. if(m->subject && *m->subject)
  662. seprint(p, e, "(subj,%s)", m->subject);
  663. s_free(id);
  664. }
  665. char sstring[256];
  666. // cmd := range cmd ' ' arg-list ;
  667. // range := address
  668. // | address ',' address
  669. // | 'g' search ;
  670. // address := msgno
  671. // | search ;
  672. // msgno := number
  673. // | number '/' msgno ;
  674. // search := '/' string '/'
  675. // | '%' string '%' ;
  676. //
  677. Reprog*
  678. parsesearch(char **pp)
  679. {
  680. char *p, *np;
  681. int c, n;
  682. p = *pp;
  683. c = *p++;
  684. np = strchr(p, c);
  685. if(np != nil){
  686. *np++ = 0;
  687. *pp = np;
  688. } else {
  689. n = strlen(p);
  690. *pp = p + n;
  691. }
  692. if(*p == 0)
  693. p = sstring;
  694. else{
  695. strncpy(sstring, p, sizeof(sstring));
  696. sstring[sizeof(sstring)-1] = 0;
  697. }
  698. return regcomp(p);
  699. }
  700. char*
  701. parseaddr(char **pp, Message *first, Message *cur, Message *unspec, Message **mp)
  702. {
  703. int n;
  704. Message *m;
  705. char *p;
  706. Reprog *prog;
  707. int c, sign;
  708. char buf[256];
  709. *mp = nil;
  710. p = *pp;
  711. if(*p == '+'){
  712. sign = 1;
  713. p++;
  714. *pp = p;
  715. } else if(*p == '-'){
  716. sign = -1;
  717. p++;
  718. *pp = p;
  719. } else
  720. sign = 0;
  721. switch(*p){
  722. default:
  723. if(sign){
  724. n = 1;
  725. goto number;
  726. }
  727. *mp = unspec;
  728. break;
  729. case '0': case '1': case '2': case '3': case '4':
  730. case '5': case '6': case '7': case '8': case '9':
  731. n = strtoul(p, pp, 10);
  732. if(n == 0){
  733. if(sign)
  734. *mp = cur;
  735. else
  736. *mp = &top;
  737. break;
  738. }
  739. number:
  740. m = nil;
  741. switch(sign){
  742. case 0:
  743. for(m = first; m != nil; m = m->next)
  744. if(m->id == n)
  745. break;
  746. break;
  747. case -1:
  748. if(cur != &top)
  749. for(m = cur; m != nil && n > 0; n--)
  750. m = m->prev;
  751. break;
  752. case 1:
  753. if(cur == &top){
  754. n--;
  755. cur = first;
  756. }
  757. for(m = cur; m != nil && n > 0; n--)
  758. m = m->next;
  759. break;
  760. }
  761. if(m == nil)
  762. return "address";
  763. *mp = m;
  764. break;
  765. case '%':
  766. case '/':
  767. case '?':
  768. c = *p;
  769. prog = parsesearch(pp);
  770. if(prog == nil)
  771. return "badly formed regular expression";
  772. m = nil;
  773. switch(c){
  774. case '%':
  775. for(m = cur == &top ? first : cur->next; m != nil; m = m->next){
  776. if(rawsearch(m, prog))
  777. break;
  778. }
  779. break;
  780. case '/':
  781. for(m = cur == &top ? first : cur->next; m != nil; m = m->next){
  782. snprintheader(buf, sizeof(buf), m);
  783. if(regexec(prog, buf, nil, 0))
  784. break;
  785. }
  786. break;
  787. case '?':
  788. for(m = cur == &top ? nil : cur->prev; m != nil; m = m->prev){
  789. snprintheader(buf, sizeof(buf), m);
  790. if(regexec(prog, buf, nil, 0))
  791. break;
  792. }
  793. break;
  794. }
  795. if(m == nil)
  796. return "search";
  797. *mp = m;
  798. free(prog);
  799. break;
  800. case '$':
  801. for(m = first; m != nil && m->next != nil; m = m->next)
  802. ;
  803. *mp = m;
  804. *pp = p+1;
  805. break;
  806. case '.':
  807. *mp = cur;
  808. *pp = p+1;
  809. break;
  810. case ',':
  811. *mp = first;
  812. *pp = p;
  813. break;
  814. }
  815. if(*mp != nil && **pp == '.'){
  816. (*pp)++;
  817. if((*mp)->child == nil)
  818. return "no sub parts";
  819. return parseaddr(pp, (*mp)->child, (*mp)->child, (*mp)->child, mp);
  820. }
  821. if(**pp == '+' || **pp == '-' || **pp == '/' || **pp == '%')
  822. return parseaddr(pp, first, *mp, *mp, mp);
  823. return nil;
  824. }
  825. //
  826. // search a message for a regular expression match
  827. //
  828. int
  829. rawsearch(Message *m, Reprog *prog)
  830. {
  831. char buf[4096+1];
  832. int i, fd, rv;
  833. String *path;
  834. path = extendpath(m->path, "raw");
  835. fd = open(s_to_c(path), OREAD);
  836. if(fd < 0)
  837. return 0;
  838. // march through raw message 4096 bytes at a time
  839. // with a 128 byte overlap to chain the re search.
  840. rv = 0;
  841. for(;;){
  842. i = read(fd, buf, sizeof(buf)-1);
  843. if(i <= 0)
  844. break;
  845. buf[i] = 0;
  846. if(regexec(prog, buf, nil, 0)){
  847. rv = 1;
  848. break;
  849. }
  850. if(i < sizeof(buf)-1)
  851. break;
  852. if(seek(fd, -128LL, 1) < 0)
  853. break;
  854. }
  855. close(fd);
  856. s_free(path);
  857. return rv;
  858. }
  859. char*
  860. parsecmd(char *p, Cmd *cmd, Message *first, Message *cur)
  861. {
  862. Reprog *prog;
  863. Message *m, *s, *e, **l, *last;
  864. char buf[256];
  865. char *err;
  866. int i, c;
  867. static char errbuf[Errlen];
  868. cmd->delete = 0;
  869. l = &cmd->msgs;
  870. *l = nil;
  871. // eat white space
  872. while(*p == ' ')
  873. p++;
  874. // null command is a special case (advance and print)
  875. if(*p == 0){
  876. if(cur == &top){
  877. // special case
  878. m = first;
  879. } else {
  880. // walk to the next message even if we have to go up
  881. m = cur->next;
  882. while(m == nil && cur->parent != nil){
  883. cur = cur->parent;
  884. m = cur->next;
  885. }
  886. }
  887. if(m == nil)
  888. return "address";
  889. *l = m;
  890. m->cmd = nil;
  891. cmd->an = 0;
  892. cmd->f = pcmd;
  893. return nil;
  894. }
  895. // global search ?
  896. if(*p == 'g'){
  897. p++;
  898. // no search string means all messages
  899. if(*p != '/' && *p != '%'){
  900. for(m = first; m != nil; m = m->next){
  901. *l = m;
  902. l = &m->cmd;
  903. *l = nil;
  904. }
  905. } else {
  906. // mark all messages matching this search string
  907. c = *p;
  908. prog = parsesearch(&p);
  909. if(prog == nil)
  910. return "badly formed regular expression";
  911. if(c == '%'){
  912. for(m = first; m != nil; m = m->next){
  913. if(rawsearch(m, prog)){
  914. *l = m;
  915. l = &m->cmd;
  916. *l = nil;
  917. }
  918. }
  919. } else {
  920. for(m = first; m != nil; m = m->next){
  921. snprintheader(buf, sizeof(buf), m);
  922. if(regexec(prog, buf, nil, 0)){
  923. *l = m;
  924. l = &m->cmd;
  925. *l = nil;
  926. }
  927. }
  928. }
  929. free(prog);
  930. }
  931. } else {
  932. // parse an address
  933. s = e = nil;
  934. err = parseaddr(&p, first, cur, cur, &s);
  935. if(err != nil)
  936. return err;
  937. if(*p == ','){
  938. // this is an address range
  939. if(s == &top)
  940. s = first;
  941. p++;
  942. for(last = s; last != nil && last->next != nil; last = last->next)
  943. ;
  944. err = parseaddr(&p, first, cur, last, &e);
  945. if(err != nil)
  946. return err;
  947. // select all messages in the range
  948. for(; s != nil; s = s->next){
  949. *l = s;
  950. l = &s->cmd;
  951. *l = nil;
  952. if(s == e)
  953. break;
  954. }
  955. if(s == nil)
  956. return "null address range";
  957. } else {
  958. // single address
  959. if(s != &top){
  960. *l = s;
  961. s->cmd = nil;
  962. }
  963. }
  964. }
  965. cmd->an = getfields(p, cmd->av, nelem(cmd->av) - 1, 1, " \t\r\n");
  966. if(cmd->an == 0 || *cmd->av[0] == 0)
  967. cmd->f = pcmd;
  968. else {
  969. // hack to avoid space after '|' or '!'
  970. if((*p == '!' || *p == '|') && *(p+1) != 0){
  971. for(i = cmd->an; i > 0 ; i--)
  972. cmd->av[i] = cmd->av[i-1];
  973. cmd->av[0] = *p == '!' ? "!" : "|";
  974. cmd->av[1]++;
  975. cmd->an++;
  976. }
  977. // hack to allow all messages to start with 'd'
  978. if(*(cmd->av[0]) == 'd' && *(cmd->av[0]+1) != 0){
  979. cmd->delete = 1;
  980. cmd->av[0]++;
  981. }
  982. // search command table
  983. for(i = 0; cmdtab[i].cmd != nil; i++)
  984. if(strcmp(cmd->av[0], cmdtab[i].cmd) == 0)
  985. break;
  986. if(cmdtab[i].cmd == nil)
  987. return "illegal command";
  988. if(cmdtab[i].args == 0 && cmd->an > 1){
  989. snprint(errbuf, sizeof(errbuf), "%s doesn't take an argument", cmdtab[i].cmd);
  990. return errbuf;
  991. }
  992. cmd->f = cmdtab[i].f;
  993. }
  994. return nil;
  995. }
  996. // inefficient read from standard input
  997. char*
  998. readline(char *prompt, char *line, int len)
  999. {
  1000. char *p, *e;
  1001. int n;
  1002. retry:
  1003. interrupted = 0;
  1004. Bprint(&out, "%s", prompt);
  1005. Bflush(&out);
  1006. e = line + len;
  1007. for(p = line; p < e; p++){
  1008. n = read(0, p, 1);
  1009. if(n < 0){
  1010. if(interrupted)
  1011. goto retry;
  1012. return nil;
  1013. }
  1014. if(n == 0)
  1015. return nil;
  1016. if(*p == '\n')
  1017. break;
  1018. }
  1019. *p = 0;
  1020. return line;
  1021. }
  1022. void
  1023. messagecount(Message *m)
  1024. {
  1025. int i;
  1026. i = 0;
  1027. for(; m != nil; m = m->next)
  1028. i++;
  1029. Bprint(&out, "%d messages\n", i);
  1030. }
  1031. Message*
  1032. aichcmd(Message *m, int indent)
  1033. {
  1034. char hdr[256];
  1035. if(m == &top)
  1036. return nil;
  1037. snprintHeader(hdr, sizeof(hdr), indent, m);
  1038. Bprint(&out, "%s\n", hdr);
  1039. for(m = m->child; m != nil; m = m->next)
  1040. aichcmd(m, indent+1);
  1041. return nil;
  1042. }
  1043. Message*
  1044. Hcmd(Cmd*, Message *m)
  1045. {
  1046. if(m == &top)
  1047. return nil;
  1048. aichcmd(m, 0);
  1049. return nil;
  1050. }
  1051. Message*
  1052. hcmd(Cmd*, Message *m)
  1053. {
  1054. char hdr[256];
  1055. if(m == &top)
  1056. return nil;
  1057. snprintheader(hdr, sizeof(hdr), m);
  1058. Bprint(&out, "%s\n", hdr);
  1059. return nil;
  1060. }
  1061. Message*
  1062. bcmd(Cmd*, Message *m)
  1063. {
  1064. int i;
  1065. Message *om = m;
  1066. if(m == &top)
  1067. m = top.child;
  1068. for(i = 0; i < 10 && m != nil; i++){
  1069. hcmd(nil, m);
  1070. om = m;
  1071. m = m->next;
  1072. }
  1073. return om;
  1074. }
  1075. Message*
  1076. ncmd(Cmd*, Message *m)
  1077. {
  1078. if(m == &top)
  1079. return m->child;
  1080. return m->next;
  1081. }
  1082. int
  1083. printpart(String *s, char *part)
  1084. {
  1085. char buf[4096];
  1086. int n, fd, tot;
  1087. String *path;
  1088. path = extendpath(s, part);
  1089. fd = open(s_to_c(path), OREAD);
  1090. s_free(path);
  1091. if(fd < 0){
  1092. fprint(2, "!message dissappeared\n");
  1093. return 0;
  1094. }
  1095. tot = 0;
  1096. while((n = read(fd, buf, sizeof(buf))) > 0){
  1097. if(interrupted)
  1098. break;
  1099. if(Bwrite(&out, buf, n) <= 0)
  1100. break;
  1101. tot += n;
  1102. }
  1103. close(fd);
  1104. return tot;
  1105. }
  1106. int
  1107. printhtml(Message *m)
  1108. {
  1109. Cmd c;
  1110. c.an = 3;
  1111. c.av[1] = "/bin/htmlfmt";
  1112. c.av[2] = "-cutf-8";
  1113. Bprint(&out, "!%s\n", c.av[1]);
  1114. Bflush(&out);
  1115. pipecmd(&c, m);
  1116. return 0;
  1117. }
  1118. Message*
  1119. Pcmd(Cmd*, Message *m)
  1120. {
  1121. if(m == &top)
  1122. return &top;
  1123. if(m->parent == &top)
  1124. printpart(m->path, "unixheader");
  1125. printpart(m->path, "raw");
  1126. return m;
  1127. }
  1128. void
  1129. compress(char *p)
  1130. {
  1131. char *np;
  1132. int last;
  1133. last = ' ';
  1134. for(np = p; *p; p++){
  1135. if(*p != ' ' || last != ' '){
  1136. last = *p;
  1137. *np++ = last;
  1138. }
  1139. }
  1140. *np = 0;
  1141. }
  1142. Message*
  1143. pcmd(Cmd*, Message *m)
  1144. {
  1145. Message *nm;
  1146. Ctype *cp;
  1147. String *s;
  1148. char buf[128];
  1149. if(m == &top)
  1150. return &top;
  1151. if(m->parent == &top)
  1152. printpart(m->path, "unixheader");
  1153. if(printpart(m->path, "header") > 0)
  1154. Bprint(&out, "\n");
  1155. cp = findctype(m->type);
  1156. if(cp->display){
  1157. if(strcmp(m->type, "text/html") == 0)
  1158. printhtml(m);
  1159. else
  1160. printpart(m->path, "body");
  1161. } else if(strcmp(m->type, "multipart/alternative") == 0){
  1162. for(nm = m->child; nm != nil; nm = nm->next){
  1163. cp = findctype(nm->type);
  1164. if(cp->ext != nil && strncmp(cp->ext, "txt", 3) == 0)
  1165. break;
  1166. }
  1167. if(nm == nil)
  1168. for(nm = m->child; nm != nil; nm = nm->next){
  1169. cp = findctype(nm->type);
  1170. if(cp->display)
  1171. break;
  1172. }
  1173. if(nm != nil)
  1174. pcmd(nil, nm);
  1175. else
  1176. hcmd(nil, m);
  1177. } else if(strncmp(m->type, "multipart/", 10) == 0){
  1178. nm = m->child;
  1179. if(nm != nil){
  1180. // always print first part
  1181. pcmd(nil, nm);
  1182. for(nm = nm->next; nm != nil; nm = nm->next){
  1183. s = rooted(s_clone(nm->path));
  1184. cp = findctype(nm->type);
  1185. snprintHeader(buf, sizeof buf, -1, nm);
  1186. compress(buf);
  1187. if(strcmp(nm->disposition, "inline") == 0){
  1188. if(cp->ext != nil)
  1189. Bprint(&out, "\n--- %s %s/body.%s\n\n",
  1190. buf, s_to_c(s), cp->ext);
  1191. else
  1192. Bprint(&out, "\n--- %s %s/body\n\n",
  1193. buf, s_to_c(s));
  1194. pcmd(nil, nm);
  1195. } else {
  1196. if(cp->ext != nil)
  1197. Bprint(&out, "\n!--- %s %s/body.%s\n",
  1198. buf, s_to_c(s), cp->ext);
  1199. else
  1200. Bprint(&out, "\n!--- %s %s/body\n",
  1201. buf, s_to_c(s));
  1202. }
  1203. s_free(s);
  1204. }
  1205. } else {
  1206. hcmd(nil, m);
  1207. }
  1208. } else if(strcmp(m->type, "message/rfc822") == 0){
  1209. pcmd(nil, m->child);
  1210. } else if(plumb(m, cp) >= 0)
  1211. Bprint(&out, "\n!--- using plumber to display message of type %s\n", m->type);
  1212. else
  1213. Bprint(&out, "\n!--- cannot display messages of type %s\n", m->type);
  1214. return m;
  1215. }
  1216. void
  1217. printpartindented(String *s, char *part, char *indent)
  1218. {
  1219. char *p;
  1220. String *path;
  1221. Biobuf *b;
  1222. path = extendpath(s, part);
  1223. b = Bopen(s_to_c(path), OREAD);
  1224. s_free(path);
  1225. if(b == nil){
  1226. fprint(2, "!message dissappeared\n");
  1227. return;
  1228. }
  1229. while((p = Brdline(b, '\n')) != nil){
  1230. if(interrupted)
  1231. break;
  1232. p[Blinelen(b)-1] = 0;
  1233. if(Bprint(&out, "%s%s\n", indent, p) <= 0)
  1234. break;
  1235. }
  1236. Bprint(&out, "\n");
  1237. Bterm(b);
  1238. }
  1239. Message*
  1240. quotecmd(Cmd*, Message *m)
  1241. {
  1242. Message *nm;
  1243. Ctype *cp;
  1244. if(m == &top)
  1245. return &top;
  1246. Bprint(&out, "\n");
  1247. if(m->from != nil && *m->from)
  1248. Bprint(&out, "On %s, %s wrote:\n", m->date, m->from);
  1249. cp = findctype(m->type);
  1250. if(cp->display){
  1251. printpartindented(m->path, "body", "> ");
  1252. } else if(strcmp(m->type, "multipart/alternative") == 0){
  1253. for(nm = m->child; nm != nil; nm = nm->next){
  1254. cp = findctype(nm->type);
  1255. if(cp->ext != nil && strncmp(cp->ext, "txt", 3) == 0)
  1256. break;
  1257. }
  1258. if(nm == nil)
  1259. for(nm = m->child; nm != nil; nm = nm->next){
  1260. cp = findctype(nm->type);
  1261. if(cp->display)
  1262. break;
  1263. }
  1264. if(nm != nil)
  1265. quotecmd(nil, nm);
  1266. } else if(strncmp(m->type, "multipart/", 10) == 0){
  1267. nm = m->child;
  1268. if(nm != nil){
  1269. cp = findctype(nm->type);
  1270. if(cp->display || strncmp(m->type, "multipart/", 10) == 0)
  1271. quotecmd(nil, nm);
  1272. }
  1273. }
  1274. return m;
  1275. }
  1276. Message*
  1277. qcmd(Cmd*, Message*)
  1278. {
  1279. Message *m;
  1280. char buf[1024], *p, *e, *msg;
  1281. int deld, n, fd;
  1282. deld = 0;
  1283. // really delete messages
  1284. fd = open("/mail/fs/ctl", ORDWR);
  1285. if(fd < 0){
  1286. fprint(2, "!can't delete mail, opening /mail/fs/ctl: %r\n");
  1287. exitfs(0);
  1288. }
  1289. e = &buf[sizeof(buf)];
  1290. p = seprint(buf, e, "delete %s", mbname);
  1291. n = 0;
  1292. for(m = top.child; m != nil; m = m->next)
  1293. if(m->deleted){
  1294. deld++;
  1295. msg = strrchr(s_to_c(m->path), '/');
  1296. if(msg == nil)
  1297. msg = s_to_c(m->path);
  1298. else
  1299. msg++;
  1300. if(e-p < 10){
  1301. write(fd, buf, p-buf);
  1302. n = 0;
  1303. p = seprint(buf, e, "delete %s", mbname);
  1304. }
  1305. p = seprint(p, e, " %s", msg);
  1306. n++;
  1307. }
  1308. if(n)
  1309. write(fd, buf, p-buf);
  1310. close(fd);
  1311. if(didopen)
  1312. closemb();
  1313. switch(deld){
  1314. case 0:
  1315. break;
  1316. case 1:
  1317. Bprint(&out, "!1 message deleted\n");
  1318. break;
  1319. default:
  1320. Bprint(&out, "!%d messages deleted\n", deld);
  1321. break;
  1322. }
  1323. Bflush(&out);
  1324. exitfs(0);
  1325. return nil; // not reached
  1326. }
  1327. Message*
  1328. xcmd(Cmd*, Message*)
  1329. {
  1330. exitfs(0);
  1331. return nil; // not reached
  1332. }
  1333. Message*
  1334. eqcmd(Cmd*, Message *m)
  1335. {
  1336. if(m == &top)
  1337. Bprint(&out, "0\n");
  1338. else
  1339. Bprint(&out, "%d\n", m->id);
  1340. return nil;
  1341. }
  1342. Message*
  1343. dcmd(Cmd*, Message *m)
  1344. {
  1345. if(m == &top){
  1346. Bprint(&out, "!address\n");
  1347. return nil;
  1348. }
  1349. while(m->parent != &top)
  1350. m = m->parent;
  1351. m->deleted = 1;
  1352. return m;
  1353. }
  1354. Message*
  1355. ucmd(Cmd*, Message *m)
  1356. {
  1357. if(m == &top)
  1358. return nil;
  1359. while(m->parent != &top)
  1360. m = m->parent;
  1361. m->deleted = 0;
  1362. return m;
  1363. }
  1364. Message*
  1365. icmd(Cmd*, Message *m)
  1366. {
  1367. int n;
  1368. n = dir2message(&top, reverse);
  1369. if(n > 0)
  1370. Bprint(&out, "%d new messages\n", n);
  1371. return m;
  1372. }
  1373. Message*
  1374. helpcmd(Cmd*, Message *m)
  1375. {
  1376. int i;
  1377. Bprint(&out, "Commands are of the form [<range>] <command> [args]\n");
  1378. Bprint(&out, "<range> := <addr> | <addr>','<addr>| 'g'<search>\n");
  1379. Bprint(&out, "<addr> := '.' | '$' | '^' | <number> | <search> | <addr>'+'<addr> | <addr>'-'<addr>\n");
  1380. Bprint(&out, "<search> := '/'<regexp>'/' | '?'<regexp>'?' | '%%'<regexp>'%%'\n");
  1381. Bprint(&out, "<command> :=\n");
  1382. for(i = 0; cmdtab[i].cmd != nil; i++)
  1383. Bprint(&out, "%s\n", cmdtab[i].help);
  1384. return m;
  1385. }
  1386. int
  1387. tomailer(char **av)
  1388. {
  1389. Waitmsg *w;
  1390. int pid, i;
  1391. // start the mailer and get out of the way
  1392. switch(pid = fork()){
  1393. case -1:
  1394. fprint(2, "can't fork: %r\n");
  1395. return -1;
  1396. case 0:
  1397. Bprint(&out, "!/bin/upas/marshal");
  1398. for(i = 1; av[i]; i++){
  1399. if(strchr(av[i], ' ') != nil)
  1400. Bprint(&out, " '%s'", av[i]);
  1401. else
  1402. Bprint(&out, " %s", av[i]);
  1403. }
  1404. Bprint(&out, "\n");
  1405. Bflush(&out);
  1406. av[0] = "marshal";
  1407. chdir(wd);
  1408. exec("/bin/upas/marshal", av);
  1409. fprint(2, "couldn't exec /bin/upas/marshal\n");
  1410. exits(0);
  1411. default:
  1412. w = wait();
  1413. if(w == nil){
  1414. if(interrupted)
  1415. postnote(PNPROC, pid, "die");
  1416. waitpid();
  1417. return -1;
  1418. }
  1419. if(w->msg[0]){
  1420. fprint(2, "mailer failed: %s\n", w->msg);
  1421. free(w);
  1422. return -1;
  1423. }
  1424. free(w);
  1425. Bprint(&out, "!\n");
  1426. break;
  1427. }
  1428. return 0;
  1429. }
  1430. //
  1431. // like tokenize but obey "" quoting
  1432. //
  1433. int
  1434. tokenize822(char *str, char **args, int max)
  1435. {
  1436. int na;
  1437. int intok = 0, inquote = 0;
  1438. if(max <= 0)
  1439. return 0;
  1440. for(na=0; ;str++)
  1441. switch(*str) {
  1442. case ' ':
  1443. case '\t':
  1444. if(inquote)
  1445. goto Default;
  1446. /* fall through */
  1447. case '\n':
  1448. *str = 0;
  1449. if(!intok)
  1450. continue;
  1451. intok = 0;
  1452. if(na < max)
  1453. continue;
  1454. /* fall through */
  1455. case 0:
  1456. return na;
  1457. case '"':
  1458. inquote ^= 1;
  1459. /* fall through */
  1460. Default:
  1461. default:
  1462. if(intok)
  1463. continue;
  1464. args[na++] = str;
  1465. intok = 1;
  1466. }
  1467. return 0; /* can't get here; silence compiler */
  1468. }
  1469. Message*
  1470. rcmd(Cmd *c, Message *m)
  1471. {
  1472. char *av[128];
  1473. int i, ai = 1;
  1474. Message *nm;
  1475. char *addr;
  1476. String *path = nil;
  1477. String *rpath;
  1478. String *subject = nil;
  1479. String *from;
  1480. if(m == &top){
  1481. Bprint(&out, "!address\n");
  1482. return nil;
  1483. }
  1484. addr = nil;
  1485. for(nm = m; nm != &top; nm = nm->parent){
  1486. if(*nm->replyto != 0){
  1487. addr = nm->replyto;
  1488. break;
  1489. }
  1490. }
  1491. if(addr == nil){
  1492. Bprint(&out, "!no reply address\n");
  1493. return nil;
  1494. }
  1495. if(nm == &top){
  1496. print("!noone to reply to\n");
  1497. return nil;
  1498. }
  1499. for(nm = m; nm != &top; nm = nm->parent){
  1500. if(*nm->subject){
  1501. av[ai++] = "-s";
  1502. subject = addrecolon(nm->subject);
  1503. av[ai++] = s_to_c(subject);;
  1504. break;
  1505. }
  1506. }
  1507. av[ai++] = "-R";
  1508. rpath = rooted(s_clone(m->path));
  1509. av[ai++] = s_to_c(rpath);
  1510. if(strchr(c->av[0], 'f') != nil){
  1511. fcmd(c, m);
  1512. av[ai++] = "-F";
  1513. }
  1514. if(strchr(c->av[0], 'R') != nil){
  1515. av[ai++] = "-t";
  1516. av[ai++] = "message/rfc822";
  1517. av[ai++] = "-A";
  1518. path = rooted(extendpath(m->path, "raw"));
  1519. av[ai++] = s_to_c(path);
  1520. }
  1521. for(i = 1; i < c->an && ai < nelem(av)-1; i++)
  1522. av[ai++] = c->av[i];
  1523. from = s_copy(addr);
  1524. ai += tokenize822(s_to_c(from), &av[ai], nelem(av) - ai);
  1525. av[ai] = 0;
  1526. if(tomailer(av) < 0)
  1527. m = nil;
  1528. s_free(path);
  1529. s_free(rpath);
  1530. s_free(subject);
  1531. s_free(from);
  1532. return m;
  1533. }
  1534. Message*
  1535. mcmd(Cmd *c, Message *m)
  1536. {
  1537. char **av;
  1538. int i, ai;
  1539. String *path;
  1540. if(m == &top){
  1541. Bprint(&out, "!address\n");
  1542. return nil;
  1543. }
  1544. if(c->an < 2){
  1545. fprint(2, "!usage: M list-of addresses\n");
  1546. return nil;
  1547. }
  1548. ai = 1;
  1549. av = malloc(sizeof(char*)*(c->an + 8));
  1550. av[ai++] = "-t";
  1551. if(m->parent == &top)
  1552. av[ai++] = "message/rfc822";
  1553. else
  1554. av[ai++] = "mime";
  1555. av[ai++] = "-A";
  1556. path = rooted(extendpath(m->path, "raw"));
  1557. av[ai++] = s_to_c(path);
  1558. if(strchr(c->av[0], 'M') == nil)
  1559. av[ai++] = "-n";
  1560. for(i = 1; i < c->an; i++)
  1561. av[ai++] = c->av[i];
  1562. av[ai] = 0;
  1563. if(tomailer(av) < 0)
  1564. m = nil;
  1565. if(path != nil)
  1566. s_free(path);
  1567. free(av);
  1568. return m;
  1569. }
  1570. Message*
  1571. acmd(Cmd *c, Message *m)
  1572. {
  1573. char *av[128];
  1574. int i, ai;
  1575. String *from, *to, *cc, *path = nil, *subject = nil;
  1576. if(m == &top){
  1577. Bprint(&out, "!address\n");
  1578. return nil;
  1579. }
  1580. ai = 1;
  1581. if(*m->subject){
  1582. av[ai++] = "-s";
  1583. subject = addrecolon(m->subject);
  1584. av[ai++] = s_to_c(subject);
  1585. }
  1586. if(strchr(c->av[0], 'A') != nil){
  1587. av[ai++] = "-t";
  1588. av[ai++] = "message/rfc822";
  1589. av[ai++] = "-A";
  1590. path = rooted(extendpath(m->path, "raw"));
  1591. av[ai++] = s_to_c(path);
  1592. }
  1593. for(i = 1; i < c->an && ai < nelem(av)-1; i++)
  1594. av[ai++] = c->av[i];
  1595. from = s_copy(m->from);
  1596. ai += tokenize822(s_to_c(from), &av[ai], nelem(av) - ai);
  1597. to = s_copy(m->to);
  1598. ai += tokenize822(s_to_c(to), &av[ai], nelem(av) - ai);
  1599. cc = s_copy(m->cc);
  1600. ai += tokenize822(s_to_c(cc), &av[ai], nelem(av) - ai);
  1601. av[ai] = 0;
  1602. if(tomailer(av) < 0)
  1603. return nil;
  1604. s_free(from);
  1605. s_free(to);
  1606. s_free(cc);
  1607. s_free(subject);
  1608. s_free(path);
  1609. return m;
  1610. }
  1611. String *
  1612. relpath(char *path, String *to)
  1613. {
  1614. if (*path=='/' || strncmp(path, "./", 2) == 0
  1615. || strncmp(path, "../", 3) == 0) {
  1616. to = s_append(to, path);
  1617. } else if(mbpath) {
  1618. to = s_append(to, s_to_c(mbpath));
  1619. to->ptr = strrchr(to->base, '/')+1;
  1620. s_append(to, path);
  1621. }
  1622. return to;
  1623. }
  1624. int
  1625. appendtofile(Message *m, char *part, char *base, int mbox)
  1626. {
  1627. String *file, *h;
  1628. int n, in, out, rv;
  1629. char buf[4096];
  1630. file = extendpath(m->path, part);
  1631. in = open(s_to_c(file), OREAD);
  1632. if(in < 0){
  1633. fprint(2, "!message disappeared\n");
  1634. return -1;
  1635. }
  1636. s_reset(file);
  1637. relpath(base, file);
  1638. if(mbox)
  1639. out = open(s_to_c(file), OWRITE);
  1640. else
  1641. out = open(s_to_c(file), OWRITE|OTRUNC);
  1642. if(out < 0){
  1643. out = create(s_to_c(file), OWRITE, 0666);
  1644. if(out < 0){
  1645. fprint(2, "!can't open %s: %r\n", s_to_c(file));
  1646. close(in);
  1647. return -1;
  1648. }
  1649. }
  1650. if(mbox)
  1651. seek(out, 0, 2);
  1652. // put on a 'From ' line
  1653. if(mbox){
  1654. while(m->parent != &top)
  1655. m = m->parent;
  1656. h = file2string(m->path, "unixheader");
  1657. fprint(out, "%s", s_to_c(h));
  1658. s_free(h);
  1659. }
  1660. rv = 0;
  1661. for(;;){
  1662. n = read(in, buf, sizeof(buf));
  1663. if(n < 0){
  1664. fprint(2, "!error reading file: %r\n");
  1665. rv = -1;
  1666. break;
  1667. }
  1668. if(n == 0)
  1669. break;
  1670. if(write(out, buf, n) != n){
  1671. fprint(2, "!error writing file: %r\n");
  1672. rv = -1;
  1673. break;
  1674. }
  1675. }
  1676. if(mbox)
  1677. write(out, "\n", 1);
  1678. close(in);
  1679. close(out);
  1680. if(rv >= 0)
  1681. print("!saved in %s\n", s_to_c(file));
  1682. s_free(file);
  1683. return rv;
  1684. }
  1685. Message*
  1686. scmd(Cmd *c, Message *m)
  1687. {
  1688. char *file;
  1689. if(m == &top){
  1690. Bprint(&out, "!address\n");
  1691. return nil;
  1692. }
  1693. switch(c->an){
  1694. case 1:
  1695. file = "stored";
  1696. break;
  1697. case 2:
  1698. file = c->av[1];
  1699. break;
  1700. default:
  1701. fprint(2, "!usage: s filename\n");
  1702. return nil;
  1703. }
  1704. if(appendtofile(m, "raw", file, 1) < 0)
  1705. return nil;
  1706. m->stored = 1;
  1707. return m;
  1708. }
  1709. Message*
  1710. wcmd(Cmd *c, Message *m)
  1711. {
  1712. char *file;
  1713. if(m == &top){
  1714. Bprint(&out, "!address\n");
  1715. return nil;
  1716. }
  1717. switch(c->an){
  1718. case 2:
  1719. file = c->av[1];
  1720. break;
  1721. case 1:
  1722. if(*m->filename == 0){
  1723. fprint(2, "!usage: w filename\n");
  1724. return nil;
  1725. }
  1726. file = strrchr(m->filename, '/');
  1727. if(file != nil)
  1728. file++;
  1729. else
  1730. file = m->filename;
  1731. break;
  1732. default:
  1733. fprint(2, "!usage: w filename\n");
  1734. return nil;
  1735. }
  1736. if(appendtofile(m, "body", file, 0) < 0)
  1737. return nil;
  1738. m->stored = 1;
  1739. return m;
  1740. }
  1741. // find the recipient account name
  1742. static void
  1743. foldername(char *folder, char *rcvr)
  1744. {
  1745. char *p;
  1746. char *e = folder+Elemlen-1;
  1747. p = strrchr(rcvr, '!');
  1748. if(p != nil)
  1749. rcvr = p+1;
  1750. while(folder < e && *rcvr && *rcvr != '@')
  1751. *folder++ = *rcvr++;
  1752. *folder = 0;
  1753. }
  1754. Message*
  1755. fcmd(Cmd *c, Message *m)
  1756. {
  1757. char folder[Elemlen];
  1758. if(c->an > 1){
  1759. fprint(2, "!usage: f takes no arguments\n");
  1760. return nil;
  1761. }
  1762. if(m == &top){
  1763. Bprint(&out, "!address\n");
  1764. return nil;
  1765. }
  1766. foldername(folder, m->from);
  1767. if(appendtofile(m, "raw", folder, 1) < 0)
  1768. return nil;
  1769. m->stored = 1;
  1770. return m;
  1771. }
  1772. void
  1773. system(char *cmd, char **av, int in)
  1774. {
  1775. int pid;
  1776. switch(pid=fork()){
  1777. case -1:
  1778. return;
  1779. case 0:
  1780. if(in >= 0){
  1781. close(0);
  1782. dup(in, 0);
  1783. close(in);
  1784. }
  1785. if(wd[0] != 0)
  1786. chdir(wd);
  1787. exec(cmd, av);
  1788. fprint(2, "!couldn't exec %s\n", cmd);
  1789. exits(0);
  1790. default:
  1791. if(in >= 0)
  1792. close(in);
  1793. while(waitpid() < 0){
  1794. if(!interrupted)
  1795. break;
  1796. postnote(PNPROC, pid, "die");
  1797. continue;
  1798. }
  1799. break;
  1800. }
  1801. }
  1802. Message*
  1803. bangcmd(Cmd *c, Message *m)
  1804. {
  1805. char cmd[4*1024];
  1806. char *p, *e;
  1807. char *av[4];
  1808. int i;
  1809. cmd[0] = 0;
  1810. p = cmd;
  1811. e = cmd+sizeof(cmd);
  1812. for(i = 1; i < c->an; i++)
  1813. p = seprint(p, e, "%s ", c->av[i]);
  1814. av[0] = "rc";
  1815. av[1] = "-c";
  1816. av[2] = cmd;
  1817. av[3] = 0;
  1818. system("/bin/rc", av, -1);
  1819. Bprint(&out, "!\n");
  1820. return m;
  1821. }
  1822. Message*
  1823. pipecmd(Cmd *c, Message *m)
  1824. {
  1825. char cmd[128];
  1826. char *p, *e;
  1827. char *av[4];
  1828. String *path;
  1829. int i, fd;
  1830. if(c->an < 2){
  1831. Bprint(&out, "!usage: | cmd\n");
  1832. return nil;
  1833. }
  1834. if(m == &top){
  1835. Bprint(&out, "!address\n");
  1836. return nil;
  1837. }
  1838. path = extendpath(m->path, "body");
  1839. fd = open(s_to_c(path), OREAD);
  1840. s_free(path);
  1841. if(fd < 0){
  1842. fprint(2, "!message disappeared\n");
  1843. return nil;
  1844. }
  1845. p = cmd;
  1846. e = cmd+sizeof(cmd);
  1847. cmd[0] = 0;
  1848. for(i = 1; i < c->an; i++)
  1849. p = seprint(p, e, "%s ", c->av[i]);
  1850. av[0] = "rc";
  1851. av[1] = "-c";
  1852. av[2] = cmd;
  1853. av[3] = 0;
  1854. system("/bin/rc", av, fd); /* system closes fd */
  1855. Bprint(&out, "!\n");
  1856. return m;
  1857. }
  1858. void
  1859. closemb(void)
  1860. {
  1861. int fd;
  1862. fd = open("/mail/fs/ctl", ORDWR);
  1863. if(fd < 0)
  1864. sysfatal("can't open /mail/fs/ctl: %r");
  1865. // close current mailbox
  1866. if(*mbname && strcmp(mbname, "mbox") != 0)
  1867. fprint(fd, "close %s", mbname);
  1868. close(fd);
  1869. }
  1870. int
  1871. switchmb(char *file, char *singleton)
  1872. {
  1873. char *p;
  1874. int n, fd;
  1875. String *path;
  1876. char buf[256];
  1877. // if the user didn't say anything and there
  1878. // is an mbox mounted already, use that one
  1879. // so that the upas/fs -fdefault default is honored.
  1880. if(file
  1881. || (singleton && access(singleton, 0)<0)
  1882. || (!singleton && access("/mail/fs/mbox", 0)<0)){
  1883. if(file == nil)
  1884. file = "mbox";
  1885. // close current mailbox
  1886. closemb();
  1887. didopen = 1;
  1888. fd = open("/mail/fs/ctl", ORDWR);
  1889. if(fd < 0)
  1890. sysfatal("can't open /mail/fs/ctl: %r");
  1891. path = s_new();
  1892. // get an absolute path to the mail box
  1893. if(strncmp(file, "./", 2) == 0){
  1894. // resolve path here since upas/fs doesn't know
  1895. // our working directory
  1896. if(getwd(buf, sizeof(buf)-strlen(file)) == nil){
  1897. fprint(2, "!can't get working directory: %s\n", buf);
  1898. return -1;
  1899. }
  1900. s_append(path, buf);
  1901. s_append(path, file+1);
  1902. } else {
  1903. mboxpath(file, user, path, 0);
  1904. }
  1905. // make up a handle to use when talking to fs
  1906. p = strrchr(file, '/');
  1907. if(p == nil){
  1908. // if its in the mailbox directory, just use the name
  1909. strncpy(mbname, file, sizeof(mbname));
  1910. mbname[sizeof(mbname)-1] = 0;
  1911. } else {
  1912. // make up a mailbox name
  1913. p = strrchr(s_to_c(path), '/');
  1914. p++;
  1915. if(*p == 0){
  1916. fprint(2, "!bad mbox name");
  1917. return -1;
  1918. }
  1919. strncpy(mbname, p, sizeof(mbname));
  1920. mbname[sizeof(mbname)-1] = 0;
  1921. n = strlen(mbname);
  1922. if(n > Elemlen-12)
  1923. n = Elemlen-12;
  1924. sprint(mbname+n, "%ld", time(0));
  1925. }
  1926. if(fprint(fd, "open %s %s", s_to_c(path), mbname) < 0){
  1927. fprint(2, "!can't 'open %s %s': %r\n", file, mbname);
  1928. s_free(path);
  1929. return -1;
  1930. }
  1931. close(fd);
  1932. }else{
  1933. path = s_reset(nil);
  1934. mboxpath("mbox", user, path, 0);
  1935. strcpy(mbname, "mbox");
  1936. }
  1937. sprint(root, "/mail/fs/%s", mbname);
  1938. if(getwd(wd, sizeof(wd)) == 0)
  1939. wd[0] = 0;
  1940. if(singleton == nil && chdir(root) >= 0)
  1941. strcpy(root, ".");
  1942. rootlen = strlen(root);
  1943. if(mbpath != nil)
  1944. s_free(mbpath);
  1945. mbpath = path;
  1946. return 0;
  1947. }
  1948. // like tokenize but for into lines
  1949. int
  1950. lineize(char *s, char **f, int n)
  1951. {
  1952. int i;
  1953. for(i = 0; *s && i < n; i++){
  1954. f[i] = s;
  1955. s = strchr(s, '\n');
  1956. if(s == nil)
  1957. break;
  1958. *s++ = 0;
  1959. }
  1960. return i;
  1961. }
  1962. String*
  1963. rooted(String *s)
  1964. {
  1965. static char buf[256];
  1966. if(strcmp(root, ".") != 0)
  1967. return s;
  1968. snprint(buf, sizeof(buf), "/mail/fs/%s/%s", mbname, s_to_c(s));
  1969. s_free(s);
  1970. return s_copy(buf);
  1971. }
  1972. int
  1973. plumb(Message *m, Ctype *cp)
  1974. {
  1975. String *s;
  1976. Plumbmsg *pm;
  1977. static int fd = -2;
  1978. if(cp->plumbdest == nil)
  1979. return -1;
  1980. if(fd < -1)
  1981. fd = plumbopen("send", OWRITE);
  1982. if(fd < 0)
  1983. return -1;
  1984. pm = mallocz(sizeof(Plumbmsg), 1);
  1985. pm->src = strdup("mail");
  1986. if(*cp->plumbdest)
  1987. pm->dst = strdup(cp->plumbdest);
  1988. pm->wdir = nil;
  1989. pm->type = strdup("text");
  1990. pm->ndata = -1;
  1991. s = rooted(extendpath(m->path, "body"));
  1992. if(cp->ext != nil){
  1993. s_append(s, ".");
  1994. s_append(s, cp->ext);
  1995. }
  1996. pm->data = strdup(s_to_c(s));
  1997. s_free(s);
  1998. plumbsend(fd, pm);
  1999. plumbfree(pm);
  2000. return 0;
  2001. }
  2002. void
  2003. regerror(char*)
  2004. {
  2005. }
  2006. String*
  2007. addrecolon(char *s)
  2008. {
  2009. String *str;
  2010. if(cistrncmp(s, "re:", 3) != 0){
  2011. str = s_copy("Re: ");
  2012. s_append(str, s);
  2013. } else
  2014. str = s_copy(s);
  2015. return str;
  2016. }
  2017. void
  2018. exitfs(char *rv)
  2019. {
  2020. if(startedfs)
  2021. unmount(nil, "/mail/fs");
  2022. chdir("/sys/src/cmd/upas/ned");
  2023. exits(rv);
  2024. }