Plan 9 from Bell Labs’s /usr/web/sources/contrib/nemo/sys/src/cmd/omail/mail.c

Copyright © 2021 Plan 9 Foundation.
Distributed under the MIT License.
Download the Plan 9 distribution.


#include <u.h>
#include <libc.h>
#include <bio.h>
#include <thread.h>
#include <plumb.h>
#include <ctype.h>
#include <omero.h>
#include "dat.h"

int	debug;
char	*maildir = "/mail/fs/";			/* mountpoint of mail file system */
char	*mailtermdir = "/mnt/term/mail/fs/";	/* alternate mountpoint */
char *mboxname = "mbox";			/* mailboxdir/mboxname is mail spool file */
char	*mailboxdir = nil;				/* nil == /mail/box/$user */
char *fsname;						/* filesystem for mailboxdir/mboxname is at maildir/fsname */
char	*user;
char	*outgoing;

Window	*wbox;
Message	mbox;
Message	replies;
char		*home;
int		plumbsendfd;
int		plumbseemailfd;
int		plumbshowmailfd;
int		plumbsendmailfd;
Channel	*cplumb;
Channel	*cplumbshow;
Channel	*cplumbsend;
int		wctlfd;
void		mainctl(void*);
void		plumbproc(void*);
void		plumbshowproc(void*);
void		plumbsendproc(void*);
void		plumbthread(void);
void		plumbshowthread(void*);
void		plumbsendthread(void*);

int			shortmenu;

void
omerogone(void)
{
	threadexitsall(nil);
}

void
usage(void)
{
	fprint(2, "usage:\tomail [-dsS] [-m maildir] [mailboxname]\n");
	fprint(2, "   or:\tomail -n [-o outgoing] [addr [subject]]\n");
	threadexitsall("usage");
}

void
removeupasfs(void)
{
	char buf[256];

	if(strcmp(mboxname, "mbox") == 0)
		return;
	snprint(buf, sizeof buf, "close %s", mboxname);
	write(mbox.ctlfd, buf, strlen(buf));
}

int
ismaildir(char *s)
{
	char buf[256];
	Dir *d;
	int ret;

	snprint(buf, sizeof buf, "%s%s", maildir, s);
	d = dirstat(buf);
	if(d == nil)
		return 0;
	ret = d->qid.type & QTDIR;
	free(d);
	return ret;
}

void
threadmain(int argc, char *argv[])
{
	char *s, *name;
	char err[ERRMAX];
	int i, newdir;
	int	sendonly;
	char*	subj;

	doquote = needsrcquote;
	quotefmtinstall();

	sendonly = 0;
	shortmenu = 0;
	ARGBEGIN{
	case 'n':
		sendonly = 1;
		break;
	case 'd':
		debug++;
		break;
	case 's':
		shortmenu = 1;
		break;
	case 'S':
		shortmenu = 2;
		break;
	case 'o':
		outgoing = EARGF(usage());
		break;
	case 'm':
		smprint(maildir, "%s/", EARGF(usage()));
		break;
	default:
		usage();
	}ARGEND
	if (sendonly){
		mbox.name = "/mail/";
		outgoing = smprint("/mail/box/%s/outgoing", getuser());
		if (argc > 1)
			subj = argv[1];
		else
			subj = nil;
		mkreply(nil, subj, "Mail", (argc ? argv[0] : ""), nil, nil);
		threadexits(nil);
	}
	/* open these early so we won't miss notification of new mail messages while we read mbox */
	plumbsendfd = plumbopen("send", OWRITE|OCEXEC);
	plumbseemailfd = plumbopen("seemail", OREAD|OCEXEC);
	plumbshowmailfd = plumbopen("showmail", OREAD|OCEXEC);

	name = "mbox";

	/* bind the terminal /mail/fs directory over the local one */
	if(access(maildir, 0)<0 && access(mailtermdir, 0)==0)
		bind(mailtermdir, maildir, MAFTER);

	newdir = 1;
	if(argc > 0){
		i = strlen(argv[0]);
		if(argc>2 || i==0)
			usage();
		/* see if the name is that of an existing /mail/fs directory */
		if(argc==1 && strchr(argv[0], '/')==0 && ismaildir(argv[0])){
			name = argv[0];
			mboxname = eappend(estrdup(maildir), "", name);
			newdir = 0;
		}else{
			if(argv[0][i-1] == '/')
				argv[0][i-1] = '\0';
			s = strrchr(argv[0], '/');
			if(s == nil)
				mboxname = estrdup(argv[0]);
			else{
				*s++ = '\0';
				if(*s == '\0')
					usage();
				mailboxdir = argv[0];
				mboxname = estrdup(s);
			}
			if(argc > 1)
				name = argv[1];
			else
				name = mboxname;
		}
	}

	user = getenv("user");
	if(user == nil)
		user = "none";
	if(mailboxdir == nil)
		mailboxdir = estrstrdup("/mail/box/", user);
	if(outgoing == nil)
		outgoing = estrstrdup(mailboxdir, "/outgoing");

	s = estrstrdup(maildir, "ctl");
	mbox.ctlfd = open(s, ORDWR|OCEXEC);
	if(mbox.ctlfd < 0)
		error("Mail: can't open %s: %r\n", s);

	fsname = estrdup(name);
	if(newdir && argc > 0){
		s = emalloc(5+strlen(mailboxdir)+strlen(mboxname)+strlen(name)+10+1);
		for(i=0; i<10; i++){
			sprint(s, "open %s/%s %s", mailboxdir, mboxname, fsname);
			if(write(mbox.ctlfd, s, strlen(s)) >= 0)
				break;
			err[0] = '\0';
			errstr(err, sizeof err);
			if(strstr(err, "mbox name in use") == nil)
				error("Mail: can't create directory %s for mail: %s\n", name, err);
			free(fsname);
			fsname = emalloc(strlen(name)+10);
			sprint(fsname, "%s-%d", name, i);
		}
		if(i == 10)
			error("Mail: can't open %s/%s: %r", mailboxdir, mboxname);
		free(s);
	}

	s = estrstrdup(fsname, "/");
	mbox.name = estrstrdup(maildir, s);
	mbox.level= 0;
	readmbox(&mbox, maildir, s);
	home = getenv("home");
	if(home == nil)
		home = "/";

	wbox = newwindow(mbox.name);
	wintagwrite(wbox, "Put Mail rm ", 3+1+4+1+7+1);
	threadcreate(mainctl, wbox, STACK);
	mbox.w = wbox;

	mesgmenu(wbox, &mbox);
	winclean(wbox);

	cplumb = chancreate(sizeof(Plumbmsg*), 0);
	cplumbshow = chancreate(sizeof(Plumbmsg*), 0);
	if(strcmp(name, "mbox") == 0){
		/*
		 * Avoid creating multiple windows to send mail by only accepting
		 * sendmail plumb messages if we're reading the main mailbox.
		 */
		plumbsendmailfd = plumbopen("sendmail", OREAD|OCEXEC);
		cplumbsend = chancreate(sizeof(Plumbmsg*), 0);
		proccreate(plumbsendproc, nil, STACK);
		threadcreate(plumbsendthread, nil, STACK);
	}
	/* start plumb reader as separate proc ... */
	proccreate(plumbproc, nil, STACK);
	proccreate(plumbshowproc, nil, STACK);
	threadcreate(plumbshowthread, nil, STACK);
	/* ... and use this thread to read the messages */
	plumbthread();
}

void
plumbproc(void*)
{
	Plumbmsg *m;

	threadsetname("plumbproc");
	for(;;){
		m = plumbrecv(plumbseemailfd);
		sendp(cplumb, m);
		if(m == nil)
			threadexits(nil);
	}
}

void
plumbshowproc(void*)
{
	Plumbmsg *m;

	threadsetname("plumbshowproc");
	for(;;){
		m = plumbrecv(plumbshowmailfd);
		sendp(cplumbshow, m);
		if(m == nil)
			threadexits(nil);
	}
}

void
plumbsendproc(void*)
{
	Plumbmsg *m;

	threadsetname("plumbsendproc");
	for(;;){
		m = plumbrecv(plumbsendmailfd);
		sendp(cplumbsend, m);
		if(m == nil)
			threadexits(nil);
	}
}

void
newmesg(char *name, char *digest)
{
	Dir *d;

	if(strncmp(name, mbox.name, strlen(mbox.name)) != 0)
		return;	/* message is about another mailbox */
	if(mesglookupfile(&mbox, name, digest) != nil)
		return;
	d = dirstat(name);
	if(d == nil)
		return;
	if(mesgadd(&mbox, mbox.name, d, digest))
		mesgmenu(wbox, &mbox);
	free(d);
}

void
showmesg(char *name, char *digest)
{
	char *n;

	if(strncmp(name, mbox.name, strlen(mbox.name)) != 0)
		return;	/* message is about another mailbox */
	n = estrdup(name+strlen(mbox.name));
	if(n[strlen(n)-1] != '/')
		n = egrow(n, "/", nil);
	mesgopen(&mbox, mbox.name, name+strlen(mbox.name), nil, digest);
	free(n);
}

void
delmesg(char *name, char *digest, int dodel)
{
	Message *m;

	m = mesglookupfile(&mbox, name, digest);
	if(m != nil){
		mesgmenumarkdel(wbox, &mbox, m, 0);
		if(dodel)
			m->writebackdel = 1;
	}
}

void
plumbthread(void)
{
	Plumbmsg *m;
	Plumbattr *a;
	char *type, *digest;

	threadsetname("plumbthread");
	while((m = recvp(cplumb)) != nil){
		a = m->attr;
		digest = plumblookup(a, "digest");
		type = plumblookup(a, "mailtype");
		if(type == nil)
			fprint(2, "Mail: plumb message with no mailtype attribute\n");
		else if(strcmp(type, "new") == 0)
			newmesg(m->data, digest);
		else if(strcmp(type, "delete") == 0)
			delmesg(m->data, digest, 0);
		else
			fprint(2, "Mail: unknown plumb attribute %s\n", type);
		plumbfree(m);
	}
	threadexits(nil);
}

void
plumbshowthread(void*)
{
	Plumbmsg *m;

	threadsetname("plumbshowthread");
	while((m = recvp(cplumbshow)) != nil){
		showmesg(m->data, plumblookup(m->attr, "digest"));
		plumbfree(m);
	}
	threadexits(nil);
}

void
plumbsendthread(void*)
{
	Plumbmsg *m;

	threadsetname("plumbsendthread");
	while((m = recvp(cplumbsend)) != nil){
		mkreply(nil,nil,  "Mail", m->data, m->attr, nil);
		plumbfree(m);
	}
	threadexits(nil);
}

int
mboxcommand(Window *w, char *s)
{
	char *args[10];
	Message *m, *next;
	int ok, nargs, i;
	char buf[128];

	s = strdup(s);
	nargs = tokenize(s, args, nelem(args));
	if(nargs == 0){
		free(s);
		return 0;
	}
	if(strcmp(args[0], "Mail") == 0){
		if(nargs == 1)
			mkreply(nil, nil, "Mail", "", nil, nil);
		else
			mkreply(nil, nil, "Mail", args[1], nil, nil);
		free(s);
		return 1;
	}
	if(strcmp(s, "Done") == 0){
		if(mbox.dirty){
			mbox.dirty = 0;
			fprint(2, "mail: mailbox not written\n");
			free(s);
			return 1;
		}
		ok = 1;
		for(m=mbox.head; m!=nil; m=next){
			next = m->next;
			if(m->w){
				if(windel(m->w))
					m->w = nil;
				else
					ok = 0;
			}
		}
		for(m=replies.head; m!=nil; m=next){
			next = m->next;
			if(m->w){
				if(windel(m->w))
					m->w = nil;
				else
					ok = 0;
			}
		}
		if(ok){
			windel(w);
			removeupasfs();
			threadexitsall(nil);
		}
		free(s);
		return 1;
	}
	if(strcmp(s, "Put") == 0){
		rewritembox(wbox, &mbox);
		mesgmenu(wbox, &mbox);
		free(s);
		return 1;
	}
	if(strcmp(s, "Rm") == 0){
		if(nargs > 1){
			for(i=1; i<nargs; i++){
				snprint(buf, sizeof buf, "%s%s", mbox.name, args[i]);
				delmesg(buf, nil, 1);
			}
		}
		free(s);
		return 1;
	}
	free(s);
	return 0;
}

void
mainctl(void *v)
{
	Window *w;
	Oev	e;
	Channel*	c;

	w = v;
	c = w->g->evc;
	while(recv(c, &e) != -1){
		dprint(2, "event %s %s\n", e.ev, e.arg);
		if (!strcmp(e.ev, "exec")){
			if(!mboxcommand(w, e.arg+12))	/* send it back */
				plumbexec(mbox.name, e.arg);
		} else if (!strcmp(e.ev, "look")){
			if (!mesgopen(&mbox, mbox.name, e.arg+12, nil, nil))
				plumblook(mbox.name, e.arg);
		} else if (!strcmp(e.ev, "exit")){
			clearoev(&e);
			break;
		}
		clearoev(&e);
	}
	chanfree(c);
	threadexits(nil);
}


Bell Labs OSI certified Powered by Plan 9

(Return to Plan 9 Home Page)

Copyright © 2021 Plan 9 Foundation. All Rights Reserved.
Comments to webmaster@9p.io.