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