]> git.lizzy.rs Git - plan9front.git/blob - acme/mail/src/mail.c
merge
[plan9front.git] / acme / mail / src / mail.c
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
40 usage(void)
41 {
42         fprint(2, "usage: Mail [-sS] [-o outgoing] [mailboxname [directoryname]]\n");
43         threadexitsall("usage");
44 }
45
46 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
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
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
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
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
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
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
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
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
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
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
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
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
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