mail.c 11 KB

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