Blob


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 <9pclient.h> /* jpc */
8 #include "dat.h"
10 char *maildir = "/mail/fs/"; /* mountpoint of mail file system */
11 char *mailtermdir = "/mnt/term/mail/fs/"; /* alternate mountpoint */
12 char *mboxname = "mbox"; /* mailboxdir/mboxname is mail spool file */
13 char *mailboxdir = nil; /* nil == /mail/box/$user */
14 char *fsname; /* filesystem for mailboxdir/mboxname is at maildir/fsname */
15 char *user;
16 char *outgoing;
18 Window *wbox;
19 Message mbox;
20 Message replies;
21 char *home;
22 int plumbsendfd;
23 int plumbseemailfd;
24 int plumbshowmailfd;
25 int 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 *upasfs; /*jpc */
41 CFsys *acmefs; /*jpc */
43 void
44 usage(void)
45 {
46 fprint(2, "usage: Mail [-sS] [-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 write(mbox.ctlfd, buf, strlen(buf));
59 }
61 int
62 ismaildir(char *s)
63 {
64 char buf[256];
65 Dir *d;
66 int ret;
68 snprint(buf, sizeof buf, "%s%s", maildir, s);
69 d = dirstat(buf);
70 if(d == nil)
71 return 0;
72 ret = d->qid.type & QTDIR;
73 free(d);
74 return ret;
75 }
77 void
78 threadmain(int argc, char *argv[])
79 {
80 char *s, *name;
81 char err[ERRMAX], *cmd;
82 int i, newdir;
83 Fmt fmt;
85 doquote = needsrcquote;
86 quotefmtinstall();
88 /* open these early so we won't miss notification of new mail messages while we read mbox */
89 plumbsendfd = plumbopen("send", OWRITE|OCEXEC);
90 plumbseemailfd = plumbopen("seemail", OREAD|OCEXEC);
91 plumbshowmailfd = plumbopen("showmail", OREAD|OCEXEC);
93 /* jpc */
94 acmefs = nsmount("acme",nil);
95 upasfs = nsmount("upasfs", nil);
96 /* jpc end */
98 shortmenu = 0;
99 ARGBEGIN{
100 case 's':
101 shortmenu = 1;
102 break;
103 case 'S':
104 shortmenu = 2;
105 break;
106 case 'o':
107 outgoing = EARGF(usage());
108 break;
109 case 'm':
110 smprint(maildir, "%s/", EARGF(usage()));
111 break;
112 default:
113 usage();
114 }ARGEND
116 name = "mbox";
118 /* bind the terminal /mail/fs directory over the local one */
119 if(access(maildir, 0)<0 && access(mailtermdir, 0)==0) {
120 /* jpc - bind(mailtermdir, maildir, MAFTER); */
121 fprint(2,"jpc: trying to bind(mailtermdir, maildir, MAFTER)\n");
124 newdir = 1;
125 if(argc > 0){
126 i = strlen(argv[0]);
127 if(argc>2 || i==0)
128 usage();
129 /* see if the name is that of an existing /mail/fs directory */
130 if(argc==1 && strchr(argv[0], '/')==0 && ismaildir(argv[0])){
131 name = argv[0];
132 mboxname = eappend(estrdup(maildir), "", name);
133 newdir = 0;
134 }else{
135 if(argv[0][i-1] == '/')
136 argv[0][i-1] = '\0';
137 s = strrchr(argv[0], '/');
138 if(s == nil)
139 mboxname = estrdup(argv[0]);
140 else{
141 *s++ = '\0';
142 if(*s == '\0')
143 usage();
144 mailboxdir = argv[0];
145 mboxname = estrdup(s);
147 if(argc > 1)
148 name = argv[1];
149 else
150 name = mboxname;
154 user = getenv("user");
155 if(user == nil)
156 user = "none";
157 if(mailboxdir == nil)
158 mailboxdir = estrstrdup(unsharp("#9/mail/box/"), user);
159 if(outgoing == nil)
160 outgoing = estrstrdup(mailboxdir, "/outgoing");
162 // s = estrstrdup(maildir, "ctl");
163 mbox.ctlfd = fsopenfd(upasfs,"ctl", ORDWR|OCEXEC);
164 if(mbox.ctlfd < 0)
165 error("can't open %s: %r\n", s);
167 fsname = estrdup(name);
168 if(newdir && argc > 0){
169 s = emalloc(5+strlen(mailboxdir)+strlen(mboxname)+strlen(name)+10+1);
170 for(i=0; i<10; i++){
171 sprint(s, "open %s/%s %s", mailboxdir, mboxname, fsname);
172 if(write(mbox.ctlfd, s, strlen(s)) >= 0)
173 break;
174 err[0] = '\0';
175 errstr(err, sizeof err);
176 if(strstr(err, "mbox name in use") == nil)
177 error("can't create directory %s for mail: %s\n", name, err);
178 free(fsname);
179 fsname = emalloc(strlen(name)+10);
180 sprint(fsname, "%s-%d", name, i);
182 if(i == 10)
183 error("can't open %s/%s: %r", mailboxdir, mboxname);
184 free(s);
187 s = estrstrdup(fsname, "/");
188 mbox.name = estrstrdup(maildir, s);
189 // mbox.name = "/mail/fs/mbox/";
190 mbox.level= 0;
191 readmbox(&mbox, maildir, s);
192 home = getenv("home");
193 if(home == nil)
194 home = "/";
196 wbox = newwindow();
197 winname(wbox, mbox.name);
198 wintagwrite(wbox, "Put Mail Delmesg ", 3+1+4+1+7+1);
199 threadcreate(mainctl, wbox, STACK);
201 fmtstrinit(&fmt);
202 fmtprint(&fmt, "Mail");
203 if(shortmenu)
204 fmtprint(&fmt, " -%c", "sS"[shortmenu-1]);
205 if(outgoing)
206 fmtprint(&fmt, " -o %s", outgoing);
207 fmtprint(&fmt, " %s", name);
208 cmd = fmtstrflush(&fmt);
209 if(cmd == nil)
210 sysfatal("out of memory");
211 winsetdump(wbox, "/acme/mail", cmd);
212 mbox.w = wbox;
214 mesgmenu(wbox, &mbox);
215 // sleep(100);
216 winclean(wbox);
218 wctlfd = open("/dev/wctl", OWRITE|OCEXEC); /* for acme window */
219 cplumb = chancreate(sizeof(Plumbmsg*), 0);
220 cplumbshow = chancreate(sizeof(Plumbmsg*), 0);
221 if(strcmp(name, "mbox") == 0){
222 /*
223 * Avoid creating multiple windows to send mail by only accepting
224 * sendmail plumb messages if we're reading the main mailbox.
225 */
226 plumbsendmailfd = plumbopen("sendmail", OREAD|OCEXEC);
227 cplumbsend = chancreate(sizeof(Plumbmsg*), 0);
228 proccreate(plumbsendproc, nil, STACK);
229 threadcreate(plumbsendthread, nil, STACK);
231 /* start plumb reader as separate proc ... */
232 proccreate(plumbproc, nil, STACK);
233 proccreate(plumbshowproc, nil, STACK);
234 threadcreate(plumbshowthread, nil, STACK);
235 /* ... and use this thread to read the messages */
236 plumbthread();
239 void
240 plumbproc(void* v)
242 Plumbmsg *m;
244 threadsetname("plumbproc");
245 for(;;){
246 m = plumbrecv(plumbseemailfd);
247 sendp(cplumb, m);
248 if(m == nil)
249 threadexits(nil);
253 void
254 plumbshowproc(void* v)
256 Plumbmsg *m;
258 threadsetname("plumbshowproc");
259 for(;;){
260 m = plumbrecv(plumbshowmailfd);
261 sendp(cplumbshow, m);
262 if(m == nil)
263 threadexits(nil);
267 void
268 plumbsendproc(void* v)
270 Plumbmsg *m;
272 threadsetname("plumbsendproc");
273 for(;;){
274 m = plumbrecv(plumbsendmailfd);
275 sendp(cplumbsend, m);
276 if(m == nil)
277 threadexits(nil);
281 void
282 newmesg(char *name, char *digest)
284 Dir *d;
285 char* tmp;
287 if(strncmp(name, mbox.name, strlen(mbox.name)) != 0) {
288 return; /* message is about another mailbox */
290 if(mesglookupfile(&mbox, name, digest) != nil) {
291 return;
293 if (strncmp(name,"/mail/fs/",strlen("/mail/fs/"))==0) {
294 tmp = name+strlen("/mail/fs/");
296 d = fsdirstat(upasfs,tmp);
297 if(d == nil) {
298 return;
300 if(mesgadd(&mbox, mbox.name, d, digest)) {
301 mesgmenunew(wbox, &mbox);
303 free(d);
306 void
307 showmesg(char *name, char *digest)
309 char *n;
311 if(strncmp(name, mbox.name, strlen(mbox.name)) != 0)
312 return; /* message is about another mailbox */
313 n = estrdup(name+strlen(mbox.name));
314 if(n[strlen(n)-1] != '/')
315 n = egrow(n, "/", nil);
316 mesgopen(&mbox, mbox.name, name+strlen(mbox.name), nil, 1, digest);
317 free(n);
320 void
321 delmesg(char *name, char *digest, int dodel)
323 Message *m;
325 m = mesglookupfile(&mbox, name, digest);
326 if(m != nil){
327 mesgmenumarkdel(wbox, &mbox, m, 0);
328 if(dodel)
329 m->writebackdel = 1;
333 void
334 plumbthread(void)
336 Plumbmsg *m;
337 Plumbattr *a;
338 char *type, *digest;
340 threadsetname("plumbthread");
341 while((m = recvp(cplumb)) != nil){
342 a = m->attr;
343 digest = plumblookup(a, "digest");
344 type = plumblookup(a, "mailtype");
345 if(type == nil)
346 fprint(2, "Mail: plumb message with no mailtype attribute\n");
347 else if(strcmp(type, "new") == 0)
348 newmesg(m->data, digest);
349 else if(strcmp(type, "delete") == 0)
350 delmesg(m->data, digest, 0);
351 else
352 fprint(2, "Mail: unknown plumb attribute %s\n", type);
353 plumbfree(m);
355 threadexits(nil);
358 void
359 plumbshowthread(void* v)
361 Plumbmsg *m;
363 threadsetname("plumbshowthread");
364 while((m = recvp(cplumbshow)) != nil){
365 showmesg(m->data, plumblookup(m->attr, "digest"));
366 plumbfree(m);
368 threadexits(nil);
371 void
372 plumbsendthread(void* v)
374 Plumbmsg *m;
376 threadsetname("plumbsendthread");
377 while((m = recvp(cplumbsend)) != nil){
378 mkreply(nil, "Mail", m->data, m->attr, nil);
379 plumbfree(m);
381 threadexits(nil);
384 int
385 mboxcommand(Window *w, char *s)
387 char *args[10], **targs;
388 Message *m, *next;
389 int ok, nargs, i, j;
390 char buf[128];
392 nargs = tokenize(s, args, nelem(args));
393 if(nargs == 0)
394 return 0;
395 if(strcmp(args[0], "Mail") == 0){
396 if(nargs == 1)
397 mkreply(nil, "Mail", "", nil, nil);
398 else
399 mkreply(nil, "Mail", args[1], nil, nil);
400 return 1;
402 if(strcmp(s, "Del") == 0){
403 if(mbox.dirty){
404 mbox.dirty = 0;
405 fprint(2, "mail: mailbox not written\n");
406 return 1;
408 ok = 1;
409 for(m=mbox.head; m!=nil; m=next){
410 next = m->next;
411 if(m->w){
412 if(windel(m->w, 0))
413 m->w = nil;
414 else
415 ok = 0;
418 for(m=replies.head; m!=nil; m=next){
419 next = m->next;
420 if(m->w){
421 if(windel(m->w, 0))
422 m->w = nil;
423 else
424 ok = 0;
427 if(ok){
428 windel(w, 1);
429 removeupasfs();
430 threadexitsall(nil);
432 return 1;
434 if(strcmp(s, "Put") == 0){
435 rewritembox(wbox, &mbox);
436 return 1;
438 if(strcmp(s, "Delmesg") == 0){
439 if(nargs > 1){
440 for(i=1; i<nargs; i++){
441 snprint(buf, sizeof buf, "%s%s", mbox.name, args[i]);
442 delmesg(buf, nil, 1);
445 s = winselection(w);
446 if(s == nil)
447 return 1;
448 nargs = 1;
449 for(i=0; s[i]; i++)
450 if(s[i] == '\n')
451 nargs++;
452 targs = emalloc(nargs*sizeof(char*)); /* could be too many for a local array */
453 nargs = getfields(s, targs, nargs, 1, "\n");
454 for(i=0; i<nargs; i++){
455 if(!isdigit(targs[i][0]))
456 continue;
457 j = atoi(targs[i]); /* easy way to parse the number! */
458 if(j == 0)
459 continue;
460 snprint(buf, sizeof buf, "%s%d", mbox.name, j);
461 delmesg(buf, nil, 1);
463 free(s);
464 free(targs);
465 return 1;
467 return 0;
470 void
471 mainctl(void *v)
473 Window *w;
474 Event *e, *e2, *eq, *ea;
475 int na, nopen;
476 char *s, *t, *buf;
478 w = v;
479 proccreate(wineventproc, w, STACK);
481 for(;;){
482 e = recvp(w->cevent);
483 switch(e->c1){
484 default:
485 Unknown:
486 print("unknown message %c%c\n", e->c1, e->c2);
487 break;
489 case 'E': /* write to body; can't affect us */
490 break;
492 case 'F': /* generated by our actions; ignore */
493 break;
495 case 'K': /* type away; we don't care */
496 break;
498 case 'M':
499 switch(e->c2){
500 case 'x':
501 case 'X':
502 ea = nil;
503 e2 = nil;
504 if(e->flag & 2)
505 e2 = recvp(w->cevent);
506 if(e->flag & 8){
507 ea = recvp(w->cevent);
508 na = ea->nb;
509 recvp(w->cevent);
510 }else
511 na = 0;
512 s = e->b;
513 /* if it's a known command, do it */
514 if((e->flag&2) && e->nb==0)
515 s = e2->b;
516 if(na){
517 t = emalloc(strlen(s)+1+na+1);
518 sprint(t, "%s %s", s, ea->b);
519 s = t;
521 /* if it's a long message, it can't be for us anyway */
522 if(!mboxcommand(w, s)) /* send it back */
523 winwriteevent(w, e);
524 if(na)
525 free(s);
526 break;
528 case 'l':
529 case 'L':
530 buf = nil;
531 eq = e;
532 if(e->flag & 2){
533 e2 = recvp(w->cevent);
534 eq = e2;
536 s = eq->b;
537 if(eq->q1>eq->q0 && eq->nb==0){
538 buf = emalloc((eq->q1-eq->q0)*UTFmax+1);
539 winread(w, eq->q0, eq->q1, buf);
540 s = buf;
542 nopen = 0;
543 do{
544 /* skip 'deleted' string if present' */
545 if(strncmp(s, deleted, strlen(deleted)) == 0)
546 s += strlen(deleted);
547 /* skip mail box name if present */
548 if(strncmp(s, mbox.name, strlen(mbox.name)) == 0)
549 s += strlen(mbox.name);
550 nopen += mesgopen(&mbox, mbox.name, s, nil, 0, nil);
551 while(*s!='\0' && *s++!='\n')
553 }while(*s);
554 if(nopen == 0) /* send it back */
555 winwriteevent(w, e);
556 free(buf);
557 break;
559 case 'I': /* modify away; we don't care */
560 case 'D':
561 case 'd':
562 case 'i':
563 break;
565 default:
566 goto Unknown;