tmail.c - plan9port - [fork] Plan 9 from user space
HTML git clone git://src.adamsgaard.dk/plan9port
DIR Log
DIR Files
DIR Refs
DIR README
DIR LICENSE
---
tmail.c (13406B)
---
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"
9
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;
17
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*);
37
38 int shortmenu;
39
40 CFsys *mailfs;
41 CFsys *acmefs;
42
43 void
44 usage(void)
45 {
46 fprint(2, "usage: Mail [-sS] [-n srvname] [-o outgoing] [mailboxname [directoryname]]\n");
47 threadexitsall("usage");
48 }
49
50 void
51 removeupasfs(void)
52 {
53 char buf[256];
54
55 if(strcmp(mboxname, "mbox") == 0)
56 return;
57 snprint(buf, sizeof buf, "close %s", mboxname);
58 fswrite(mbox.ctlfd, buf, strlen(buf));
59 }
60
61 int
62 ismaildir(char *s)
63 {
64 Dir *d;
65 int ret;
66
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 }
74
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;
82
83 doquote = needsrcquote;
84 quotefmtinstall();
85
86 /* open these early so we won't miss notification of new mail messages while we read mbox */
87 if((plumbsendfd = plumbopenfid("send", OWRITE|OCEXEC)) == nil)
88 fprint(2, "warning: open plumb/send: %r\n");
89 if((plumbseemailfd = plumbopenfid("seemail", OREAD|OCEXEC)) == nil)
90 fprint(2, "warning: open plumb/seemail: %r\n");
91 if((plumbshowmailfd = plumbopenfid("showmail", OREAD|OCEXEC)) == nil)
92 fprint(2, "warning: open plumb/showmail: %r\n");
93
94 shortmenu = 0;
95 srvname = "mail";
96 ARGBEGIN{
97 case 's':
98 shortmenu = 1;
99 break;
100 case 'S':
101 shortmenu = 2;
102 break;
103 case 'o':
104 outgoing = EARGF(usage());
105 break;
106 case 'm':
107 smprint(maildir, "%s/", EARGF(usage()));
108 break;
109 case 'n':
110 srvname = EARGF(usage());
111 break;
112 default:
113 usage();
114 }ARGEND
115
116 acmefs = nsmount("acme",nil);
117 if(acmefs == nil)
118 error("cannot mount acme: %r");
119 mailfs = nsmount(srvname, nil);
120 if(mailfs == nil)
121 error("cannot mount %s: %r", srvname);
122
123 name = "mbox";
124
125 newdir = 1;
126 if(argc > 0){
127 i = strlen(argv[0]);
128 if(argc>2 || i==0)
129 usage();
130 /* see if the name is that of an existing /mail/fs directory */
131 if(argc==1 && argv[0][0] != '/' && ismaildir(argv[0])){
132 name = argv[0];
133 mboxname = estrdup(name);
134 newdir = 0;
135 }else{
136 if(argv[0][i-1] == '/')
137 argv[0][i-1] = '\0';
138 s = strrchr(argv[0], '/');
139 if(s == nil)
140 mboxname = estrdup(argv[0]);
141 else{
142 *s++ = '\0';
143 if(*s == '\0')
144 usage();
145 mailboxdir = argv[0];
146 mboxname = estrdup(s);
147 }
148 if(argc > 1)
149 name = argv[1];
150 else
151 name = mboxname;
152 }
153 }
154
155 user = getenv("user");
156 if(user == nil)
157 user = "none";
158 home = getenv("home");
159 if(home == nil)
160 home = getenv("HOME");
161 if(home == nil)
162 error("can't find $home");
163 if(mailboxdir == nil)
164 mailboxdir = estrstrdup(home, "/mail");
165 if(outgoing == nil)
166 outgoing = estrstrdup(mailboxdir, "/outgoing");
167
168 mbox.ctlfd = fsopen(mailfs, estrstrdup(mboxname, "/ctl"), OWRITE);
169 if(mbox.ctlfd == nil)
170 error("can't open %s: %r", estrstrdup(mboxname, "/ctl"));
171
172 fsname = estrdup(name);
173 if(newdir && argc > 0){
174 s = emalloc(5+strlen(mailboxdir)+strlen(mboxname)+strlen(name)+10+1);
175 for(i=0; i<10; i++){
176 sprint(s, "open %s/%s %s", mailboxdir, mboxname, fsname);
177 if(fswrite(mbox.ctlfd, s, strlen(s)) >= 0)
178 break;
179 err[0] = '\0';
180 errstr(err, sizeof err);
181 if(strstr(err, "mbox name in use") == nil)
182 error("can't create directory %s for mail: %s", name, err);
183 free(fsname);
184 fsname = emalloc(strlen(name)+10);
185 sprint(fsname, "%s-%d", name, i);
186 }
187 if(i == 10)
188 error("can't open %s/%s: %r", mailboxdir, mboxname);
189 free(s);
190 }
191
192 s = estrstrdup(fsname, "/");
193 mbox.name = estrstrdup(maildir, s);
194 mbox.level= 0;
195 readmbox(&mbox, maildir, s);
196 home = getenv("home");
197 if(home == nil)
198 home = "/";
199
200 wbox = newwindow();
201 winname(wbox, mbox.name);
202 wintagwrite(wbox, "Put Mail Delmesg ", 3+1+4+1+7+1);
203 threadcreate(mainctl, wbox, STACK);
204
205 fmtstrinit(&fmt);
206 fmtprint(&fmt, "Mail");
207 if(shortmenu)
208 fmtprint(&fmt, " -%c", "sS"[shortmenu-1]);
209 if(outgoing)
210 fmtprint(&fmt, " -o %s", outgoing);
211 fmtprint(&fmt, " %s", name);
212 cmd = fmtstrflush(&fmt);
213 if(cmd == nil)
214 sysfatal("out of memory");
215 winsetdump(wbox, "/acme/mail", cmd);
216 mbox.w = wbox;
217
218 mesgmenu(wbox, &mbox);
219 winclean(wbox);
220
221 /* wctlfd = open("/dev/wctl", OWRITE|OCEXEC); /* for acme window */
222 wctlfd = -1;
223 cplumb = chancreate(sizeof(Plumbmsg*), 0);
224 cplumbshow = chancreate(sizeof(Plumbmsg*), 0);
225 if(strcmp(name, "mbox") == 0){
226 /*
227 * Avoid creating multiple windows to send mail by only accepting
228 * sendmail plumb messages if we're reading the main mailbox.
229 */
230 plumbsendmailfd = plumbopenfid("sendmail", OREAD|OCEXEC);
231 cplumbsend = chancreate(sizeof(Plumbmsg*), 0);
232 proccreate(plumbsendproc, nil, STACK);
233 threadcreate(plumbsendthread, nil, STACK);
234 }
235 /* start plumb reader as separate proc ... */
236 proccreate(plumbproc, nil, STACK);
237 proccreate(plumbshowproc, nil, STACK);
238 threadcreate(plumbshowthread, nil, STACK);
239 fswrite(mbox.ctlfd, "refresh", 7);
240 /* ... and use this thread to read the messages */
241 plumbthread();
242 }
243
244 void
245 plumbproc(void* v)
246 {
247 Plumbmsg *m;
248
249 threadsetname("plumbproc");
250 for(;;){
251 m = plumbrecvfid(plumbseemailfd);
252 sendp(cplumb, m);
253 if(m == nil)
254 threadexits(nil);
255 }
256 }
257
258 void
259 plumbshowproc(void* v)
260 {
261 Plumbmsg *m;
262
263 threadsetname("plumbshowproc");
264 for(;;){
265 m = plumbrecvfid(plumbshowmailfd);
266 sendp(cplumbshow, m);
267 if(m == nil)
268 threadexits(nil);
269 }
270 }
271
272 void
273 plumbsendproc(void* v)
274 {
275 Plumbmsg *m;
276
277 threadsetname("plumbsendproc");
278 for(;;){
279 m = plumbrecvfid(plumbsendmailfd);
280 sendp(cplumbsend, m);
281 if(m == nil)
282 threadexits(nil);
283 }
284 }
285
286 void
287 newmesg(char *name, char *digest)
288 {
289 Dir *d;
290
291 if(strncmp(name, mbox.name, strlen(mbox.name)) != 0)
292 return; /* message is about another mailbox */
293 if(mesglookupfile(&mbox, name, digest) != nil)
294 return;
295 if(strncmp(name, "Mail/", 5) == 0)
296 name += 5;
297 d = fsdirstat(mailfs, name);
298 if(d == nil)
299 return;
300 if(mesgadd(&mbox, mbox.name, d, digest))
301 mesgmenunew(wbox, &mbox);
302 free(d);
303 }
304
305 void
306 showmesg(char *name, char *digest)
307 {
308 char *n;
309 char *mb;
310
311 mb = mbox.name;
312 if(strncmp(name, mb, strlen(mb)) != 0)
313 return; /* message is about another mailbox */
314 n = estrdup(name+strlen(mb));
315 if(n[strlen(n)-1] != '/')
316 n = egrow(n, "/", nil);
317 mesgopen(&mbox, mbox.name, name+strlen(mb), nil, 1, digest);
318 free(n);
319 }
320
321 void
322 delmesg(char *name, char *digest, int dodel, char *save)
323 {
324 Message *m;
325
326 m = mesglookupfile(&mbox, name, digest);
327 if(m != nil){
328 if(save)
329 mesgcommand(m, estrstrdup("Save ", save));
330 if(dodel)
331 mesgmenumarkdel(wbox, &mbox, m, 1);
332 else{
333 /* notification came from plumber - message is gone */
334 mesgmenudel(wbox, &mbox, m);
335 if(!m->opened)
336 mesgdel(&mbox, m);
337 }
338 }
339 }
340
341 void
342 plumbthread(void)
343 {
344 Plumbmsg *m;
345 Plumbattr *a;
346 char *type, *digest;
347
348 threadsetname("plumbthread");
349 while((m = recvp(cplumb)) != nil){
350 a = m->attr;
351 digest = plumblookup(a, "digest");
352 type = plumblookup(a, "mailtype");
353 if(type == nil)
354 fprint(2, "Mail: plumb message with no mailtype attribute\n");
355 else if(strcmp(type, "new") == 0)
356 newmesg(m->data, digest);
357 else if(strcmp(type, "delete") == 0)
358 delmesg(m->data, digest, 0, nil);
359 else
360 fprint(2, "Mail: unknown plumb attribute %s\n", type);
361 plumbfree(m);
362 }
363 threadexits(nil);
364 }
365
366 void
367 plumbshowthread(void *v)
368 {
369 Plumbmsg *m;
370
371 USED(v);
372 threadsetname("plumbshowthread");
373 while((m = recvp(cplumbshow)) != nil){
374 showmesg(m->data, plumblookup(m->attr, "digest"));
375 plumbfree(m);
376 }
377 threadexits(nil);
378 }
379
380 void
381 plumbsendthread(void *v)
382 {
383 Plumbmsg *m;
384
385 USED(v);
386 threadsetname("plumbsendthread");
387 while((m = recvp(cplumbsend)) != nil){
388 mkreply(nil, "Mail", m->data, m->attr, nil);
389 plumbfree(m);
390 }
391 threadexits(nil);
392 }
393
394 int
395 mboxcommand(Window *w, char *s)
396 {
397 char *args[10], **targs, *save;
398 Window *sbox;
399 Message *m, *next;
400 int ok, nargs, i, j;
401 CFid *searchfd;
402 char buf[128], *res;
403
404 nargs = tokenize(s, args, nelem(args));
405 if(nargs == 0)
406 return 0;
407 if(strcmp(args[0], "Mail") == 0){
408 if(nargs == 1)
409 mkreply(nil, "Mail", "", nil, nil);
410 else
411 mkreply(nil, "Mail", args[1], nil, nil);
412 return 1;
413 }
414 if(strcmp(s, "Del") == 0){
415 if(mbox.dirty){
416 mbox.dirty = 0;
417 fprint(2, "mail: mailbox not written\n");
418 return 1;
419 }
420 if(w != mbox.w){
421 windel(w, 1);
422 return 1;
423 }
424 ok = 1;
425 for(m=mbox.head; m!=nil; m=next){
426 next = m->next;
427 if(m->w){
428 if(windel(m->w, 0))
429 m->w = nil;
430 else
431 ok = 0;
432 }
433 }
434 for(m=replies.head; m!=nil; m=next){
435 next = m->next;
436 if(m->w){
437 if(windel(m->w, 0))
438 m->w = nil;
439 else
440 ok = 0;
441 }
442 }
443 if(ok){
444 windel(w, 1);
445 removeupasfs();
446 threadexitsall(nil);
447 }
448 return 1;
449 }
450 if(strcmp(s, "Put") == 0){
451 rewritembox(wbox, &mbox);
452 return 1;
453 }
454 if(strcmp(s, "Get") == 0){
455 fswrite(mbox.ctlfd, "refresh", 7);
456 return 1;
457 }
458 if(strcmp(s, "Delmesg") == 0){
459 save = nil;
460 if(nargs > 1)
461 save = args[1];
462 s = winselection(w);
463 if(s == nil)
464 return 1;
465 nargs = 1;
466 for(i=0; s[i]; i++)
467 if(s[i] == '\n')
468 nargs++;
469 targs = emalloc(nargs*sizeof(char*)); /* could be too many for a local array */
470 nargs = getfields(s, targs, nargs, 1, "\n");
471 for(i=0; i<nargs; i++){
472 if(!isdigit(targs[i][0]))
473 continue;
474 j = atoi(targs[i]); /* easy way to parse the number! */
475 if(j == 0)
476 continue;
477 snprint(buf, sizeof buf, "%s%d", mbox.name, j);
478 delmesg(buf, nil, 1, save);
479 }
480 free(s);
481 free(targs);
482 return 1;
483 }
484 if(strcmp(s, "Search") == 0){
485 if(nargs <= 1)
486 return 1;
487 s = estrstrdup(mboxname, "/search");
488 searchfd = fsopen(mailfs, s, ORDWR);
489 if(searchfd == nil)
490 return 1;
491 save = estrdup(args[1]);
492 for(i=2; i<nargs; i++)
493 save = eappend(save, " ", args[i]);
494 fswrite(searchfd, save, strlen(save));
495 fsseek(searchfd, 0, 0);
496 j = fsread(searchfd, buf, sizeof buf - 1);
497 if(j == 0){
498 fprint(2, "[%s] search %s: no results found\n", mboxname, save);
499 fsclose(searchfd);
500 free(save);
501 return 1;
502 }
503 free(save);
504 buf[j] = '\0';
505 res = estrdup(buf);
506 j = fsread(searchfd, buf, sizeof buf - 1);
507 for(; j != 0; j = fsread(searchfd, buf, sizeof buf - 1), buf[j] = '\0')
508 res = eappend(res, "", buf);
509 fsclose(searchfd);
510
511 sbox = newwindow();
512 winname(sbox, s);
513 free(s);
514 threadcreate(mainctl, sbox, STACK);
515 winopenbody(sbox, OWRITE);
516
517 /* show results in reverse order */
518 m = mbox.tail;
519 save = nil;
520 for(s=strrchr(res, ' '); s!=nil || save!=res; s=strrchr(res, ' ')){
521 if(s != nil){
522 save = s+1;
523 *s = '\0';
524 }
525 else save = res;
526 save = estrstrdup(save, "/");
527 for(; m && strcmp(save, m->name) != 0; m=m->prev);
528 free(save);
529 if(m == nil)
530 break;
531 fsprint(sbox->body, "%s%s\n", m->name, info(m, 0, 0));
532 m = m->prev;
533 }
534 free(res);
535 winclean(sbox);
536 winclosebody(sbox);
537 return 1;
538 }
539 return 0;
540 }
541
542 void
543 mainctl(void *v)
544 {
545 Window *w;
546 Event *e, *e2, *eq, *ea;
547 int na, nopen;
548 char *s, *t, *buf;
549
550 w = v;
551 winincref(w);
552 proccreate(wineventproc, w, STACK);
553
554 for(;;){
555 e = recvp(w->cevent);
556 switch(e->c1){
557 default:
558 Unknown:
559 print("unknown message %c%c\n", e->c1, e->c2);
560 break;
561
562 case 'E': /* write to body; can't affect us */
563 break;
564
565 case 'F': /* generated by our actions; ignore */
566 break;
567
568 case 'K': /* type away; we don't care */
569 break;
570
571 case 'M':
572 switch(e->c2){
573 case 'x':
574 case 'X':
575 ea = nil;
576 e2 = nil;
577 if(e->flag & 2)
578 e2 = recvp(w->cevent);
579 if(e->flag & 8){
580 ea = recvp(w->cevent);
581 na = ea->nb;
582 recvp(w->cevent);
583 }else
584 na = 0;
585 s = e->b;
586 /* if it's a known command, do it */
587 if((e->flag&2) && e->nb==0)
588 s = e2->b;
589 if(na){
590 t = emalloc(strlen(s)+1+na+1);
591 sprint(t, "%s %s", s, ea->b);
592 s = t;
593 }
594 /* if it's a long message, it can't be for us anyway */
595 if(!mboxcommand(w, s)) /* send it back */
596 winwriteevent(w, e);
597 if(na)
598 free(s);
599 break;
600
601 case 'l':
602 case 'L':
603 buf = nil;
604 eq = e;
605 if(e->flag & 2){
606 e2 = recvp(w->cevent);
607 eq = e2;
608 }
609 s = eq->b;
610 if(eq->q1>eq->q0 && eq->nb==0){
611 buf = emalloc((eq->q1-eq->q0)*UTFmax+1);
612 winread(w, eq->q0, eq->q1, buf);
613 s = buf;
614 }
615 nopen = 0;
616 do{
617 /* skip 'deleted' string if present' */
618 if(strncmp(s, deleted, strlen(deleted)) == 0)
619 s += strlen(deleted);
620 /* skip mail box name if present */
621 if(strncmp(s, mbox.name, strlen(mbox.name)) == 0)
622 s += strlen(mbox.name);
623 nopen += mesgopen(&mbox, mbox.name, s, nil, 0, nil);
624 while(*s!='\0' && *s++!='\n')
625 ;
626 }while(*s);
627 if(nopen == 0) /* send it back */
628 winwriteevent(w, e);
629 free(buf);
630 break;
631
632 case 'I': /* modify away; we don't care */
633 case 'D':
634 case 'd':
635 case 'i':
636 break;
637
638 default:
639 goto Unknown;
640 }
641 }
642 }
643 }