Blob


1 #include <u.h>
2 #include <libc.h>
3 #include <bio.h>
4 #include <thread.h>
5 #include <9pclient.h>
6 #include <plumb.h>
7 #include <ctype.h>
8 #include "dat.h"
10 char *maildir = "Mail/"; /* mountpoint of mail file system */
11 char *mboxname = "mbox"; /* mailboxdir/mboxname is mail spool file */
12 char *mailboxdir = nil; /* nil == /mail/box/$user */
13 char *fsname; /* filesystem for mailboxdir/mboxname is at maildir/fsname */
14 char *user;
15 char *outgoing;
16 char *srvname;
18 Window *wbox;
19 Message mbox;
20 Message replies;
21 char *home;
22 CFid *plumbsendfd;
23 CFid *plumbseemailfd;
24 CFid *plumbshowmailfd;
25 CFid *plumbsendmailfd;
26 Channel *cplumb;
27 Channel *cplumbshow;
28 Channel *cplumbsend;
29 int wctlfd;
30 void mainctl(void*);
31 void plumbproc(void*);
32 void plumbshowproc(void*);
33 void plumbsendproc(void*);
34 void plumbthread(void);
35 void plumbshowthread(void*);
36 void plumbsendthread(void*);
38 int shortmenu;
40 CFsys *mailfs;
41 CFsys *acmefs;
43 void
44 usage(void)
45 {
46 fprint(2, "usage: Mail [-sS] [-n srvname] [-o outgoing] [mailboxname [directoryname]]\n");
47 threadexitsall("usage");
48 }
50 void
51 removeupasfs(void)
52 {
53 char buf[256];
55 if(strcmp(mboxname, "mbox") == 0)
56 return;
57 snprint(buf, sizeof buf, "close %s", mboxname);
58 fswrite(mbox.ctlfd, buf, strlen(buf));
59 }
61 int
62 ismaildir(char *s)
63 {
64 Dir *d;
65 int ret;
67 d = fsdirstat(mailfs, s);
68 if(d == nil)
69 return 0;
70 ret = d->qid.type & QTDIR;
71 free(d);
72 return ret;
73 }
75 void
76 threadmain(int argc, char *argv[])
77 {
78 char *s, *name;
79 char err[ERRMAX], *cmd;
80 int i, newdir;
81 Fmt fmt;
83 doquote = needsrcquote;
84 quotefmtinstall();
86 /* open these early so we won't miss notification of new mail messages while we read mbox */
87 plumbsendfd = plumbopenfid("send", OWRITE|OCEXEC);
88 plumbseemailfd = plumbopenfid("seemail", OREAD|OCEXEC);
89 plumbshowmailfd = plumbopenfid("showmail", OREAD|OCEXEC);
91 shortmenu = 0;
92 srvname = "mail";
93 ARGBEGIN{
94 case 's':
95 shortmenu = 1;
96 break;
97 case 'S':
98 shortmenu = 2;
99 break;
100 case 'o':
101 outgoing = EARGF(usage());
102 break;
103 case 'm':
104 smprint(maildir, "%s/", EARGF(usage()));
105 break;
106 case 'n':
107 srvname = EARGF(usage());
108 break;
109 default:
110 usage();
111 }ARGEND
113 acmefs = nsmount("acme",nil);
114 if(acmefs == nil)
115 error("cannot mount acme: %r");
116 mailfs = nsmount(srvname, nil);
117 if(mailfs == nil)
118 error("cannot mount %s: %r", srvname);
120 name = "mbox";
122 newdir = 1;
123 if(argc > 0){
124 i = strlen(argv[0]);
125 if(argc>2 || i==0)
126 usage();
127 /* see if the name is that of an existing /mail/fs directory */
128 if(argc==1 && argv[0][0] != '/' && ismaildir(argv[0])){
129 name = argv[0];
130 mboxname = estrdup(name);
131 newdir = 0;
132 }else{
133 if(argv[0][i-1] == '/')
134 argv[0][i-1] = '\0';
135 s = strrchr(argv[0], '/');
136 if(s == nil)
137 mboxname = estrdup(argv[0]);
138 else{
139 *s++ = '\0';
140 if(*s == '\0')
141 usage();
142 mailboxdir = argv[0];
143 mboxname = estrdup(s);
145 if(argc > 1)
146 name = argv[1];
147 else
148 name = mboxname;
152 user = getenv("user");
153 if(user == nil)
154 user = "none";
155 home = getenv("home");
156 if(home == nil)
157 home = getenv("HOME");
158 if(home == nil)
159 error("can't find $home");
160 if(mailboxdir == nil)
161 mailboxdir = estrstrdup(home, "/mail");
162 if(outgoing == nil)
163 outgoing = estrstrdup(mailboxdir, "/outgoing");
165 mbox.ctlfd = fsopen(mailfs, estrstrdup(mboxname, "/ctl"), OWRITE);
166 if(mbox.ctlfd == nil)
167 error("can't open %s: %r", estrstrdup(mboxname, "/ctl"));
169 fsname = estrdup(name);
170 if(newdir && argc > 0){
171 s = emalloc(5+strlen(mailboxdir)+strlen(mboxname)+strlen(name)+10+1);
172 for(i=0; i<10; i++){
173 sprint(s, "open %s/%s %s", mailboxdir, mboxname, fsname);
174 if(fswrite(mbox.ctlfd, s, strlen(s)) >= 0)
175 break;
176 err[0] = '\0';
177 errstr(err, sizeof err);
178 if(strstr(err, "mbox name in use") == nil)
179 error("can't create directory %s for mail: %s", name, err);
180 free(fsname);
181 fsname = emalloc(strlen(name)+10);
182 sprint(fsname, "%s-%d", name, i);
184 if(i == 10)
185 error("can't open %s/%s: %r", mailboxdir, mboxname);
186 free(s);
189 s = estrstrdup(fsname, "/");
190 mbox.name = estrstrdup(maildir, s);
191 mbox.level= 0;
192 readmbox(&mbox, maildir, s);
193 home = getenv("home");
194 if(home == nil)
195 home = "/";
197 wbox = newwindow();
198 winname(wbox, mbox.name);
199 wintagwrite(wbox, "Put Mail Delmesg ", 3+1+4+1+7+1);
200 threadcreate(mainctl, wbox, STACK);
202 fmtstrinit(&fmt);
203 fmtprint(&fmt, "Mail");
204 if(shortmenu)
205 fmtprint(&fmt, " -%c", "sS"[shortmenu-1]);
206 if(outgoing)
207 fmtprint(&fmt, " -o %s", outgoing);
208 fmtprint(&fmt, " %s", name);
209 cmd = fmtstrflush(&fmt);
210 if(cmd == nil)
211 sysfatal("out of memory");
212 winsetdump(wbox, "/acme/mail", cmd);
213 mbox.w = wbox;
215 mesgmenu(wbox, &mbox);
216 winclean(wbox);
218 /* wctlfd = open("/dev/wctl", OWRITE|OCEXEC); /* for acme window */
219 wctlfd = -1;
220 cplumb = chancreate(sizeof(Plumbmsg*), 0);
221 cplumbshow = chancreate(sizeof(Plumbmsg*), 0);
222 if(strcmp(name, "mbox") == 0){
223 /*
224 * Avoid creating multiple windows to send mail by only accepting
225 * sendmail plumb messages if we're reading the main mailbox.
226 */
227 plumbsendmailfd = plumbopenfid("sendmail", OREAD|OCEXEC);
228 cplumbsend = chancreate(sizeof(Plumbmsg*), 0);
229 proccreate(plumbsendproc, nil, STACK);
230 threadcreate(plumbsendthread, nil, STACK);
232 /* start plumb reader as separate proc ... */
233 proccreate(plumbproc, nil, STACK);
234 proccreate(plumbshowproc, nil, STACK);
235 threadcreate(plumbshowthread, nil, STACK);
236 fswrite(mbox.ctlfd, "refresh", 7);
237 /* ... and use this thread to read the messages */
238 plumbthread();
241 void
242 plumbproc(void* v)
244 Plumbmsg *m;
246 threadsetname("plumbproc");
247 for(;;){
248 m = plumbrecvfid(plumbseemailfd);
249 sendp(cplumb, m);
250 if(m == nil)
251 threadexits(nil);
255 void
256 plumbshowproc(void* v)
258 Plumbmsg *m;
260 threadsetname("plumbshowproc");
261 for(;;){
262 m = plumbrecvfid(plumbshowmailfd);
263 sendp(cplumbshow, m);
264 if(m == nil)
265 threadexits(nil);
269 void
270 plumbsendproc(void* v)
272 Plumbmsg *m;
274 threadsetname("plumbsendproc");
275 for(;;){
276 m = plumbrecvfid(plumbsendmailfd);
277 sendp(cplumbsend, m);
278 if(m == nil)
279 threadexits(nil);
283 void
284 newmesg(char *name, char *digest)
286 Dir *d;
288 if(strncmp(name, mbox.name, strlen(mbox.name)) != 0)
289 return; /* message is about another mailbox */
290 if(mesglookupfile(&mbox, name, digest) != nil)
291 return;
292 if(strncmp(name, "Mail/", 5) == 0)
293 name += 5;
294 d = fsdirstat(mailfs, name);
295 if(d == nil)
296 return;
297 if(mesgadd(&mbox, mbox.name, d, digest))
298 mesgmenunew(wbox, &mbox);
299 free(d);
302 void
303 showmesg(char *name, char *digest)
305 char *n;
306 char *mb;
308 mb = mbox.name;
309 if(strncmp(name, mb, strlen(mb)) != 0)
310 return; /* message is about another mailbox */
311 n = estrdup(name+strlen(mb));
312 if(n[strlen(n)-1] != '/')
313 n = egrow(n, "/", nil);
314 mesgopen(&mbox, mbox.name, name+strlen(mb), nil, 1, digest);
315 free(n);
318 void
319 delmesg(char *name, char *digest, int dodel, char *save)
321 Message *m;
323 m = mesglookupfile(&mbox, name, digest);
324 if(m != nil){
325 if(save)
326 mesgcommand(m, estrstrdup("Save ", save));
327 if(dodel)
328 mesgmenumarkdel(wbox, &mbox, m, 1);
329 else{
330 fprint(2, "message is gone...\n");
331 /* notification came from plumber - message is gone */
332 mesgmenudel(wbox, &mbox, m);
333 if(!m->opened)
334 mesgdel(&mbox, m);
339 void
340 plumbthread(void)
342 Plumbmsg *m;
343 Plumbattr *a;
344 char *type, *digest;
346 threadsetname("plumbthread");
347 while((m = recvp(cplumb)) != nil){
348 a = m->attr;
349 digest = plumblookup(a, "digest");
350 type = plumblookup(a, "mailtype");
351 if(type == nil)
352 fprint(2, "Mail: plumb message with no mailtype attribute\n");
353 else if(strcmp(type, "new") == 0)
354 newmesg(m->data, digest);
355 else if(strcmp(type, "delete") == 0)
356 delmesg(m->data, digest, 0, nil);
357 else
358 fprint(2, "Mail: unknown plumb attribute %s\n", type);
359 plumbfree(m);
361 threadexits(nil);
364 void
365 plumbshowthread(void *v)
367 Plumbmsg *m;
369 USED(v);
370 threadsetname("plumbshowthread");
371 while((m = recvp(cplumbshow)) != nil){
372 showmesg(m->data, plumblookup(m->attr, "digest"));
373 plumbfree(m);
375 threadexits(nil);
378 void
379 plumbsendthread(void *v)
381 Plumbmsg *m;
383 USED(v);
384 threadsetname("plumbsendthread");
385 while((m = recvp(cplumbsend)) != nil){
386 mkreply(nil, "Mail", m->data, m->attr, nil);
387 plumbfree(m);
389 threadexits(nil);
392 int
393 mboxcommand(Window *w, char *s)
395 char *args[10], **targs, *save;
396 Message *m, *next;
397 int ok, nargs, i, j;
398 char buf[128];
400 nargs = tokenize(s, args, nelem(args));
401 if(nargs == 0)
402 return 0;
403 if(strcmp(args[0], "Mail") == 0){
404 if(nargs == 1)
405 mkreply(nil, "Mail", "", nil, nil);
406 else
407 mkreply(nil, "Mail", args[1], nil, nil);
408 return 1;
410 if(strcmp(s, "Del") == 0){
411 if(mbox.dirty){
412 mbox.dirty = 0;
413 fprint(2, "mail: mailbox not written\n");
414 return 1;
416 ok = 1;
417 for(m=mbox.head; m!=nil; m=next){
418 next = m->next;
419 if(m->w){
420 if(windel(m->w, 0))
421 m->w = nil;
422 else
423 ok = 0;
426 for(m=replies.head; m!=nil; m=next){
427 next = m->next;
428 if(m->w){
429 if(windel(m->w, 0))
430 m->w = nil;
431 else
432 ok = 0;
435 if(ok){
436 windel(w, 1);
437 removeupasfs();
438 threadexitsall(nil);
440 return 1;
442 if(strcmp(s, "Put") == 0){
443 rewritembox(wbox, &mbox);
444 return 1;
446 if(strcmp(s, "Get") == 0){
447 fswrite(mbox.ctlfd, "refresh", 7);
448 return 1;
450 if(strcmp(s, "Delmesg") == 0){
451 save = nil;
452 if(nargs > 1)
453 save = args[1];
454 s = winselection(w);
455 if(s == nil)
456 return 1;
457 nargs = 1;
458 for(i=0; s[i]; i++)
459 if(s[i] == '\n')
460 nargs++;
461 targs = emalloc(nargs*sizeof(char*)); /* could be too many for a local array */
462 nargs = getfields(s, targs, nargs, 1, "\n");
463 for(i=0; i<nargs; i++){
464 if(!isdigit(targs[i][0]))
465 continue;
466 j = atoi(targs[i]); /* easy way to parse the number! */
467 if(j == 0)
468 continue;
469 snprint(buf, sizeof buf, "%s%d", mbox.name, j);
470 delmesg(buf, nil, 1, save);
472 free(s);
473 free(targs);
474 return 1;
476 return 0;
479 void
480 mainctl(void *v)
482 Window *w;
483 Event *e, *e2, *eq, *ea;
484 int na, nopen;
485 char *s, *t, *buf;
487 w = v;
488 winincref(w);
489 proccreate(wineventproc, w, STACK);
491 for(;;){
492 e = recvp(w->cevent);
493 switch(e->c1){
494 default:
495 Unknown:
496 print("unknown message %c%c\n", e->c1, e->c2);
497 break;
499 case 'E': /* write to body; can't affect us */
500 break;
502 case 'F': /* generated by our actions; ignore */
503 break;
505 case 'K': /* type away; we don't care */
506 break;
508 case 'M':
509 switch(e->c2){
510 case 'x':
511 case 'X':
512 ea = nil;
513 e2 = nil;
514 if(e->flag & 2)
515 e2 = recvp(w->cevent);
516 if(e->flag & 8){
517 ea = recvp(w->cevent);
518 na = ea->nb;
519 recvp(w->cevent);
520 }else
521 na = 0;
522 s = e->b;
523 /* if it's a known command, do it */
524 if((e->flag&2) && e->nb==0)
525 s = e2->b;
526 if(na){
527 t = emalloc(strlen(s)+1+na+1);
528 sprint(t, "%s %s", s, ea->b);
529 s = t;
531 /* if it's a long message, it can't be for us anyway */
532 if(!mboxcommand(w, s)) /* send it back */
533 winwriteevent(w, e);
534 if(na)
535 free(s);
536 break;
538 case 'l':
539 case 'L':
540 buf = nil;
541 eq = e;
542 if(e->flag & 2){
543 e2 = recvp(w->cevent);
544 eq = e2;
546 s = eq->b;
547 if(eq->q1>eq->q0 && eq->nb==0){
548 buf = emalloc((eq->q1-eq->q0)*UTFmax+1);
549 winread(w, eq->q0, eq->q1, buf);
550 s = buf;
552 nopen = 0;
553 do{
554 /* skip 'deleted' string if present' */
555 if(strncmp(s, deleted, strlen(deleted)) == 0)
556 s += strlen(deleted);
557 /* skip mail box name if present */
558 if(strncmp(s, mbox.name, strlen(mbox.name)) == 0)
559 s += strlen(mbox.name);
560 nopen += mesgopen(&mbox, mbox.name, s, nil, 0, nil);
561 while(*s!='\0' && *s++!='\n')
563 }while(*s);
564 if(nopen == 0) /* send it back */
565 winwriteevent(w, e);
566 free(buf);
567 break;
569 case 'I': /* modify away; we don't care */
570 case 'D':
571 case 'd':
572 case 'i':
573 break;
575 default:
576 goto Unknown;