plan9port

fork of plan9port with libvec, libstr and libsdb
Log | Files | Refs | README | LICENSE

mail.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 }