mail.c 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559
  1. /*
  2. * This file is part of the UCB release of Plan 9. It is subject to the license
  3. * terms in the LICENSE file found in the top-level directory of this
  4. * distribution and at http://akaros.cs.berkeley.edu/files/Plan9License. No
  5. * part of the UCB release of Plan 9, including this file, may be copied,
  6. * modified, propagated, or distributed except according to the terms contained
  7. * in the LICENSE file.
  8. */
  9. #include <u.h>
  10. #include <libc.h>
  11. #include <bio.h>
  12. #include <thread.h>
  13. #include <plumb.h>
  14. #include <ctype.h>
  15. #include "dat.h"
  16. char *maildir = "/mail/fs/"; /* mountpoint of mail file system */
  17. char *mailtermdir = "/mnt/term/mail/fs/"; /* alternate mountpoint */
  18. char *mboxname = "mbox"; /* mailboxdir/mboxname is mail spool file */
  19. char *mailboxdir = nil; /* nil == /mail/box/$user */
  20. char *fsname; /* filesystem for mailboxdir/mboxname is at maildir/fsname */
  21. char *user;
  22. char *outgoing;
  23. Window *wbox;
  24. Message mbox;
  25. Message replies;
  26. char *home;
  27. int plumbsendfd;
  28. int plumbseemailfd;
  29. int plumbshowmailfd;
  30. int plumbsendmailfd;
  31. Channel *cplumb;
  32. Channel *cplumbshow;
  33. Channel *cplumbsend;
  34. int wctlfd;
  35. void mainctl(void*);
  36. void plumbproc(void*);
  37. void plumbshowproc(void*);
  38. void plumbsendproc(void*);
  39. void plumbthread(void);
  40. void plumbshowthread(void*);
  41. void plumbsendthread(void*);
  42. int shortmenu;
  43. void
  44. usage(void)
  45. {
  46. fprint(2, "usage: Mail [-sS] [-o outgoing] [mailboxname [directoryname]]\n");
  47. threadexitsall("usage");
  48. }
  49. void
  50. removeupasfs(void)
  51. {
  52. char buf[256];
  53. if(strcmp(mboxname, "mbox") == 0)
  54. return;
  55. snprint(buf, sizeof buf, "close %s", mboxname);
  56. write(mbox.ctlfd, buf, strlen(buf));
  57. }
  58. int
  59. ismaildir(char *s)
  60. {
  61. char buf[256];
  62. Dir *d;
  63. int ret;
  64. snprint(buf, sizeof buf, "%s%s", maildir, s);
  65. d = dirstat(buf);
  66. if(d == nil)
  67. return 0;
  68. ret = d->qid.type & QTDIR;
  69. free(d);
  70. return ret;
  71. }
  72. void
  73. threadmain(int argc, char *argv[])
  74. {
  75. char *s, *name;
  76. char err[ERRMAX], *cmd;
  77. int i, newdir;
  78. Fmt fmt;
  79. doquote = needsrcquote;
  80. quotefmtinstall();
  81. /* open these early so we won't miss notification of new mail messages while we read mbox */
  82. plumbsendfd = plumbopen("send", OWRITE|OCEXEC);
  83. plumbseemailfd = plumbopen("seemail", OREAD|OCEXEC);
  84. plumbshowmailfd = plumbopen("showmail", OREAD|OCEXEC);
  85. shortmenu = 0;
  86. ARGBEGIN{
  87. case 's':
  88. shortmenu = 1;
  89. break;
  90. case 'S':
  91. shortmenu = 2;
  92. break;
  93. case 'o':
  94. outgoing = EARGF(usage());
  95. break;
  96. case 'm':
  97. smprint(maildir, "%s/", EARGF(usage()));
  98. break;
  99. default:
  100. usage();
  101. }ARGEND
  102. name = "mbox";
  103. /* bind the terminal /mail/fs directory over the local one */
  104. if(access(maildir, 0)<0 && access(mailtermdir, 0)==0)
  105. bind(mailtermdir, maildir, MAFTER);
  106. newdir = 1;
  107. if(argc > 0){
  108. i = strlen(argv[0]);
  109. if(argc>2 || i==0)
  110. usage();
  111. /* see if the name is that of an existing /mail/fs directory */
  112. if(argc==1 && strchr(argv[0], '/')==0 && ismaildir(argv[0])){
  113. name = argv[0];
  114. mboxname = eappend(estrdup(maildir), "", name);
  115. newdir = 0;
  116. }else{
  117. if(argv[0][i-1] == '/')
  118. argv[0][i-1] = '\0';
  119. s = strrchr(argv[0], '/');
  120. if(s == nil)
  121. mboxname = estrdup(argv[0]);
  122. else{
  123. *s++ = '\0';
  124. if(*s == '\0')
  125. usage();
  126. mailboxdir = argv[0];
  127. mboxname = estrdup(s);
  128. }
  129. if(argc > 1)
  130. name = argv[1];
  131. else
  132. name = mboxname;
  133. }
  134. }
  135. user = getenv("user");
  136. if(user == nil)
  137. user = "none";
  138. if(mailboxdir == nil)
  139. mailboxdir = estrstrdup("/mail/box/", user);
  140. if(outgoing == nil)
  141. outgoing = estrstrdup(mailboxdir, "/outgoing");
  142. s = estrstrdup(maildir, "ctl");
  143. mbox.ctlfd = open(s, ORDWR|OCEXEC);
  144. if(mbox.ctlfd < 0)
  145. error("can't open %s: %r", s);
  146. fsname = estrdup(name);
  147. if(newdir && argc > 0){
  148. s = emalloc(5+strlen(mailboxdir)+strlen(mboxname)+strlen(name)+10+1);
  149. for(i=0; i<10; i++){
  150. sprint(s, "open %s/%s %s", mailboxdir, mboxname, fsname);
  151. if(write(mbox.ctlfd, s, strlen(s)) >= 0)
  152. break;
  153. err[0] = '\0';
  154. errstr(err, sizeof err);
  155. if(strstr(err, "mbox name in use") == nil)
  156. error("can't create directory %s for mail: %s", name, err);
  157. free(fsname);
  158. fsname = emalloc(strlen(name)+10);
  159. sprint(fsname, "%s-%d", name, i);
  160. }
  161. if(i == 10)
  162. error("can't open %s/%s: %r", mailboxdir, mboxname);
  163. free(s);
  164. }
  165. s = estrstrdup(fsname, "/");
  166. mbox.name = estrstrdup(maildir, s);
  167. mbox.level= 0;
  168. readmbox(&mbox, maildir, s);
  169. home = getenv("home");
  170. if(home == nil)
  171. home = "/";
  172. wbox = newwindow();
  173. winname(wbox, mbox.name);
  174. wintagwrite(wbox, "Put Mail Delmesg ", 3+1+4+1+7+1);
  175. threadcreate(mainctl, wbox, STACK);
  176. fmtstrinit(&fmt);
  177. fmtprint(&fmt, "Mail");
  178. if(shortmenu)
  179. fmtprint(&fmt, " -%c", "sS"[shortmenu-1]);
  180. if(outgoing)
  181. fmtprint(&fmt, " -o %s", outgoing);
  182. fmtprint(&fmt, " %s", name);
  183. cmd = fmtstrflush(&fmt);
  184. if(cmd == nil)
  185. sysfatal("out of memory");
  186. winsetdump(wbox, "/acme/mail", cmd);
  187. mbox.w = wbox;
  188. mesgmenu(wbox, &mbox);
  189. winclean(wbox);
  190. wctlfd = open("/dev/wctl", OWRITE|OCEXEC); /* for acme window */
  191. cplumb = chancreate(sizeof(Plumbmsg*), 0);
  192. cplumbshow = chancreate(sizeof(Plumbmsg*), 0);
  193. if(strcmp(name, "mbox") == 0){
  194. /*
  195. * Avoid creating multiple windows to send mail by only accepting
  196. * sendmail plumb messages if we're reading the main mailbox.
  197. */
  198. plumbsendmailfd = plumbopen("sendmail", OREAD|OCEXEC);
  199. cplumbsend = chancreate(sizeof(Plumbmsg*), 0);
  200. proccreate(plumbsendproc, nil, STACK);
  201. threadcreate(plumbsendthread, nil, STACK);
  202. }
  203. /* start plumb reader as separate proc ... */
  204. proccreate(plumbproc, nil, STACK);
  205. proccreate(plumbshowproc, nil, STACK);
  206. threadcreate(plumbshowthread, nil, STACK);
  207. /* ... and use this thread to read the messages */
  208. plumbthread();
  209. }
  210. void
  211. plumbproc(void*)
  212. {
  213. Plumbmsg *m;
  214. threadsetname("plumbproc");
  215. for(;;){
  216. m = plumbrecv(plumbseemailfd);
  217. sendp(cplumb, m);
  218. if(m == nil)
  219. threadexits(nil);
  220. }
  221. }
  222. void
  223. plumbshowproc(void*)
  224. {
  225. Plumbmsg *m;
  226. threadsetname("plumbshowproc");
  227. for(;;){
  228. m = plumbrecv(plumbshowmailfd);
  229. sendp(cplumbshow, m);
  230. if(m == nil)
  231. threadexits(nil);
  232. }
  233. }
  234. void
  235. plumbsendproc(void*)
  236. {
  237. Plumbmsg *m;
  238. threadsetname("plumbsendproc");
  239. for(;;){
  240. m = plumbrecv(plumbsendmailfd);
  241. sendp(cplumbsend, m);
  242. if(m == nil)
  243. threadexits(nil);
  244. }
  245. }
  246. void
  247. newmesg(char *name, char *digest)
  248. {
  249. Dir *d;
  250. if(strncmp(name, mbox.name, strlen(mbox.name)) != 0)
  251. return; /* message is about another mailbox */
  252. if(mesglookupfile(&mbox, name, digest) != nil)
  253. return;
  254. d = dirstat(name);
  255. if(d == nil)
  256. return;
  257. if(mesgadd(&mbox, mbox.name, d, digest))
  258. mesgmenunew(wbox, &mbox);
  259. free(d);
  260. }
  261. void
  262. showmesg(char *name, char *digest)
  263. {
  264. char *n;
  265. if(strncmp(name, mbox.name, strlen(mbox.name)) != 0)
  266. return; /* message is about another mailbox */
  267. n = estrdup(name+strlen(mbox.name));
  268. if(n[strlen(n)-1] != '/')
  269. n = egrow(n, "/", nil);
  270. mesgopen(&mbox, mbox.name, name+strlen(mbox.name), nil, 1, digest);
  271. free(n);
  272. }
  273. void
  274. delmesg(char *name, char *digest, int dodel)
  275. {
  276. Message *m;
  277. m = mesglookupfile(&mbox, name, digest);
  278. if(m != nil){
  279. mesgmenumarkdel(wbox, &mbox, m, 0);
  280. if(dodel)
  281. m->writebackdel = 1;
  282. }
  283. }
  284. void
  285. plumbthread(void)
  286. {
  287. Plumbmsg *m;
  288. Plumbattr *a;
  289. char *type, *digest;
  290. threadsetname("plumbthread");
  291. while((m = recvp(cplumb)) != nil){
  292. a = m->attr;
  293. digest = plumblookup(a, "digest");
  294. type = plumblookup(a, "mailtype");
  295. if(type == nil)
  296. fprint(2, "Mail: plumb message with no mailtype attribute\n");
  297. else if(strcmp(type, "new") == 0)
  298. newmesg(m->data, digest);
  299. else if(strcmp(type, "delete") == 0)
  300. delmesg(m->data, digest, 0);
  301. else
  302. fprint(2, "Mail: unknown plumb attribute %s\n", type);
  303. plumbfree(m);
  304. }
  305. threadexits(nil);
  306. }
  307. void
  308. plumbshowthread(void*)
  309. {
  310. Plumbmsg *m;
  311. threadsetname("plumbshowthread");
  312. while((m = recvp(cplumbshow)) != nil){
  313. showmesg(m->data, plumblookup(m->attr, "digest"));
  314. plumbfree(m);
  315. }
  316. threadexits(nil);
  317. }
  318. void
  319. plumbsendthread(void*)
  320. {
  321. Plumbmsg *m;
  322. threadsetname("plumbsendthread");
  323. while((m = recvp(cplumbsend)) != nil){
  324. mkreply(nil, "Mail", m->data, m->attr, nil);
  325. plumbfree(m);
  326. }
  327. threadexits(nil);
  328. }
  329. int
  330. mboxcommand(Window *w, char *s)
  331. {
  332. char *args[10], **targs;
  333. Message *m, *next;
  334. int ok, nargs, i, j;
  335. char buf[128];
  336. nargs = tokenize(s, args, nelem(args));
  337. if(nargs == 0)
  338. return 0;
  339. if(strcmp(args[0], "Mail") == 0){
  340. if(nargs == 1)
  341. mkreply(nil, "Mail", "", nil, nil);
  342. else
  343. mkreply(nil, "Mail", args[1], nil, nil);
  344. return 1;
  345. }
  346. if(strcmp(s, "Del") == 0){
  347. if(mbox.dirty){
  348. mbox.dirty = 0;
  349. fprint(2, "mail: mailbox not written\n");
  350. return 1;
  351. }
  352. ok = 1;
  353. for(m=mbox.head; m!=nil; m=next){
  354. next = m->next;
  355. if(m->w){
  356. if(windel(m->w, 0))
  357. m->w = nil;
  358. else
  359. ok = 0;
  360. }
  361. }
  362. for(m=replies.head; m!=nil; m=next){
  363. next = m->next;
  364. if(m->w){
  365. if(windel(m->w, 0))
  366. m->w = nil;
  367. else
  368. ok = 0;
  369. }
  370. }
  371. if(ok){
  372. windel(w, 1);
  373. removeupasfs();
  374. threadexitsall(nil);
  375. }
  376. return 1;
  377. }
  378. if(strcmp(s, "Put") == 0){
  379. rewritembox(wbox, &mbox);
  380. return 1;
  381. }
  382. if(strcmp(s, "Delmesg") == 0){
  383. if(nargs > 1){
  384. for(i=1; i<nargs; i++){
  385. snprint(buf, sizeof buf, "%s%s", mbox.name, args[i]);
  386. delmesg(buf, nil, 1);
  387. }
  388. }
  389. s = winselection(w);
  390. if(s == nil)
  391. return 1;
  392. nargs = 1;
  393. for(i=0; s[i]; i++)
  394. if(s[i] == '\n')
  395. nargs++;
  396. targs = emalloc(nargs*sizeof(char*)); /* could be too many for a local array */
  397. nargs = getfields(s, targs, nargs, 1, "\n");
  398. for(i=0; i<nargs; i++){
  399. if(!isdigit(targs[i][0]))
  400. continue;
  401. j = atoi(targs[i]); /* easy way to parse the number! */
  402. if(j == 0)
  403. continue;
  404. snprint(buf, sizeof buf, "%s%d", mbox.name, j);
  405. delmesg(buf, nil, 1);
  406. }
  407. free(s);
  408. free(targs);
  409. return 1;
  410. }
  411. return 0;
  412. }
  413. void
  414. mainctl(void *v)
  415. {
  416. Window *w;
  417. Event *e, *e2, *eq, *ea;
  418. int na, nopen;
  419. char *s, *t, *buf;
  420. w = v;
  421. proccreate(wineventproc, w, STACK);
  422. for(;;){
  423. e = recvp(w->cevent);
  424. switch(e->c1){
  425. default:
  426. Unknown:
  427. print("unknown message %c%c\n", e->c1, e->c2);
  428. break;
  429. case 'E': /* write to body; can't affect us */
  430. break;
  431. case 'F': /* generated by our actions; ignore */
  432. break;
  433. case 'K': /* type away; we don't care */
  434. break;
  435. case 'M':
  436. switch(e->c2){
  437. case 'x':
  438. case 'X':
  439. ea = nil;
  440. e2 = nil;
  441. if(e->flag & 2)
  442. e2 = recvp(w->cevent);
  443. if(e->flag & 8){
  444. ea = recvp(w->cevent);
  445. na = ea->nb;
  446. recvp(w->cevent);
  447. }else
  448. na = 0;
  449. s = e->b;
  450. /* if it's a known command, do it */
  451. if((e->flag&2) && e->nb==0)
  452. s = e2->b;
  453. if(na){
  454. t = emalloc(strlen(s)+1+na+1);
  455. sprint(t, "%s %s", s, ea->b);
  456. s = t;
  457. }
  458. /* if it's a long message, it can't be for us anyway */
  459. if(!mboxcommand(w, s)) /* send it back */
  460. winwriteevent(w, e);
  461. if(na)
  462. free(s);
  463. break;
  464. case 'l':
  465. case 'L':
  466. buf = nil;
  467. eq = e;
  468. if(e->flag & 2){
  469. e2 = recvp(w->cevent);
  470. eq = e2;
  471. }
  472. s = eq->b;
  473. if(eq->q1>eq->q0 && eq->nb==0){
  474. buf = emalloc((eq->q1-eq->q0)*UTFmax+1);
  475. winread(w, eq->q0, eq->q1, buf);
  476. s = buf;
  477. }
  478. nopen = 0;
  479. do{
  480. /* skip 'deleted' string if present' */
  481. if(strncmp(s, deleted, strlen(deleted)) == 0)
  482. s += strlen(deleted);
  483. /* skip mail box name if present */
  484. if(strncmp(s, mbox.name, strlen(mbox.name)) == 0)
  485. s += strlen(mbox.name);
  486. nopen += mesgopen(&mbox, mbox.name, s, nil, 0, nil);
  487. while(*s!='\0' && *s++!='\n')
  488. ;
  489. }while(*s);
  490. if(nopen == 0) /* send it back */
  491. winwriteevent(w, e);
  492. free(buf);
  493. break;
  494. case 'I': /* modify away; we don't care */
  495. case 'D':
  496. case 'd':
  497. case 'i':
  498. break;
  499. default:
  500. goto Unknown;
  501. }
  502. }
  503. }
  504. }