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