Plan 9 from Bell Labs’s /usr/web/sources/contrib/nemo/root/sys/src/cmd/mail2fs/upasfs/planb.c

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


/*
 * Plan B (mail2fs) mail box format.
 *
 * BUG: this does not reconstruct the
 * raw text for attachments. So imap and others
 * will be unable to access any attachment using upas/fs.
 * unless all message has been saved into the mailbox by
 * using -r flag with mail2fs.
 *
 * As an aid, we add the path to the message directory
 * to the message body, so the user could build the path
 * for any attachment and open it.
 */

#include "common.h"
#include <ctype.h>
#include <plumb.h>
#include <libsec.h>
#include "dat.h"

static int
hasbody(char *m)
{
	char *p;
	int i;

	p = strstr(m, "\n\n");
	if(p == nil)
		return 0;
	p += 2;
	for(i = 0; p[i] != 0 && i < 5; i++)
		if(p[i] != '\n')
			return 1;
	return 0;
}

static int
readmessage(Message *m, char *msg)
{
	int i, n;
	int fd;
	char sdigest[SHA1dlen*2+1];
	Dir *d;
	char *raw;
	char *buf;
	char *name;
	char *p;

	buf = nil;
	d = nil;
	raw = nil;
	name = smprint("%s/raw", msg);
	if(name == nil)
		return -1;
	if(m->filename != nil)
		s_free(m->filename);
	m->filename = s_copy(name);
	fd = open(name, OREAD);
	if(fd < 0)
		goto Fail;
	d = dirfstat(fd);
	if(d == nil)
		goto Fail;
	raw = malloc(d->length + 1);
	if(raw == nil)
		goto Fail;
	n = readn(fd, raw, d->length);
	free(d);
	d = nil;
	if(n <= 0)
		goto Fail;
	raw[n] = 0;
	close(fd);
	fd = -1;
	if(hasbody(raw)){	/* assume raw has everything */
		m->start = raw;
		m->lim = m->end = raw+n;
	}else{				/* assume raw has just headers */
		p = strchr(raw, '\n');
		if(p != nil)
			*++p = 0;
		if(strncmp(raw, "From ", 5) != 0)
			goto Fail;
		free(name);
		name = smprint("%s/text", msg);
		if(name == nil)
			goto Fail;
		fd = open(name, OREAD);
		if(fd < 0)
			goto Fail;
		d = dirfstat(fd);
		if(d == nil)
			goto Fail;
		/* allocate a few extra chars */
		buf = malloc(strlen(raw) + d->length + strlen(msg) + 80);
		if(buf == nil)
			goto Fail;
		strcpy(buf, raw);
		p = buf+strlen(raw);
		n = readn(fd, p, d->length);
		if(n < 0)
			goto Fail;
		sprint(p+n, "\n[%s]\n", msg);
		n += 2 + strlen(msg) + 2;
		close(fd);
		free(raw);
		free(name);
		free(d);
		free(m->start);
		m->start = buf;
		m->lim = m->end = p+n;
	}
	*m->end = 0;
	m->bend = m->rbend = m->end;
	sha1((uchar*)m->start, m->end - m->start, m->digest, nil);
	for(i = 0; i < SHA1dlen; i++)
		sprint(sdigest+2*i, "%2.2ux", m->digest[i]);
	m->sdigest = s_copy(sdigest);
	return 0;
Fail:
	if(fd >= 0)
		close(fd);
	free(raw);
	free(name);
	free(buf);
	free(d);
	return -1;
}

/*
 * Deleted messages are kept as spam instead.
 */
static void
archive(Message *m)
{
	char *dir;
	char *p;
	char *nname;
	Dir d;

	dir = strdup(s_to_c(m->filename));
	nname = nil;
	if(dir == nil)
		return;
	p = strrchr(dir, '/');
	if(p == nil)
		goto Fail;
	*p = 0;
	p = strrchr(dir, '/');
	if(p == nil)
		goto Fail;
	p++;
	if(*p < '0' || *p > '9')
		goto Fail;
	nname = smprint("s.%s", p);
	if(nname == nil)
		goto Fail;
	nulldir(&d);
	d.name = nname;
	dirwstat(dir, &d);
Fail:
	free(dir);
	free(nname);
}

int
purgembox(Mailbox *mb, int virtual)
{
	Message *m, *next;
	int newdels;

	// forget about what's no longer in the mailbox
	newdels = 0;
	for(m = mb->root->part; m != nil; m = next){
		next = m->next;
		if(m->deleted > 0 && m->refs == 0){
			if(m->inmbox){
				newdels++;
				/* virtual folders are
				 * virtual, we do not archive
				 */
				if(virtual == 0)
					archive(m);
			}
			delmessage(mb, m);
		}
	}
	return newdels;
}

static int
mustshow(char* name)
{
	if(isdigit(name[0]))
		return 1;
	if(0 && name[0] == 'a' && name[1] == '.')
		return 1;
	if(0 && name[0] == 's' && name[1] == '.')
		return 1;
	return 0;
}

static int
readpbmessage(Mailbox *mb, char *msg, int doplumb)
{
	Message *m, **l;
	char *x;

	m = newmessage(mb->root);
	m->mallocd = 1;
	m->inmbox = 1;
	if(readmessage(m, msg) < 0){
		delmessage(mb, m);
		mb->root->subname--;
		return -1;
	}
	for(l = &mb->root->part; *l != nil; l = &(*l)->next)
		if(strcmp(s_to_c((*l)->filename), s_to_c(m->filename)) == 0)
		if(*l != m){
			if((*l)->deleted < 0)
				(*l)->deleted = 0;
			delmessage(mb, m);
			mb->root->subname--;
			return -1;
		}
	x = strchr(m->start, '\n');
	if(x == nil)
		m->header = m->end;
	else
		m->header = x + 1;
	m->mheader = m->mhend = m->header;
	parseunix(m);
	parse(m, 0, mb, 0);
	logmsg("new", m);

	/* chain in */
	*l = m;
	if(doplumb)
		mailplumb(mb, m, 0);
	return 0;
}

static int
dcmp(Dir *a, Dir *b)
{
	char *an;
	char *bn;

	an = a->name;
	bn = b->name;
	if(an[0] != 0 && an[1] == '.')
		an += 2;
	if(bn[0] != 0 && bn[1] == '.')
		bn += 2;
	return strcmp(an, bn);
}

static void
readpbvmbox(Mailbox *mb, int doplumb)
{
	Dir *d;
	char *data;
	long sz;
	char *ln, *p, *nln;
	char *msg;
	int fd;
	int nr;

	fd = open(mb->path, OREAD);
	if(fd < 0){
		fprint(2, "%s: %s: %r\n", argv0, mb->path);
		return;
	}
	d = dirfstat(fd);
	if(d == nil){
		fprint(2, "%s: %s: %r\n", argv0, mb->path);
		close(fd);
		return;
	}
	sz = d->length;
	free(d);
	if(sz > 32 * 1024 * 1024){
		sz = 32 * 1024 * 1024;
		fprint(2, "%s: %s: bug: folder too big (>32M)\n", argv0, mb->path);
	}
	data = malloc(sz+1);
	if(data == nil){
		close(fd);
		fprint(2, "%s: no memory\n", argv0);
		return;
	}
	nr = readn(fd, data, sz);
	close(fd);
	if(nr < 0){
		fprint(2, "%s: %s: %r\n", argv0, mb->path);
		free(data);
		return;
	}
	data[nr] = 0;

	for(ln = data; *ln != 0; ln = nln){
		nln = strchr(ln, '\n');
		if(nln != nil)
			*nln++ = 0;
		else
			nln = ln + strlen(ln);
		p = strchr(ln , ' ');
		if(p != nil)
			*p = 0;
		p = strchr(ln, '\t');
		if(p != nil)
			*p = 0;
		p = strstr(ln, "/text");
		if(p != nil)
			*p = 0;
		msg = smprint("/mail/box/%s/msgs/%s", user, ln);
		if(msg == nil){
			fprint(2, "%s: no memory\n", argv0);
			continue;
		}
		readpbmessage(mb, msg, doplumb);
		free(msg);
	}
	free(data);		
}

static void
readpbmbox(Mailbox *mb, int doplumb)
{
	int fd;
	Dir *md;
	Dir *cd;
	char *cf;
	int nd;
	Dir *d;
	int nmd;
	char *msg;
	char *month;
	int i, j;

	fd = open(mb->path, OREAD);
	if(fd < 0){
		fprint(2, "%s: %s: %r\n", argv0, mb->path);
		return;
	}
	nd = dirreadall(fd, &d);
	close(fd);
	if(nd > 0)
		qsort(d, nd, sizeof d[0], (int (*)(void*, void*))dcmp);
	for(i = 0; i < nd; i++){
		month = smprint("%s/%s", mb->path, d[i].name);
		cf = smprint("%s.l", month);
		if(month == nil || cf == nil)	/* what do we do? */
			break;
		fd = open(month, OREAD);
		if(fd < 0){
			fprint(2, "%s: %s: %r\n", argv0, month);
			free(month);
			continue;
		}
		md = dirfstat(fd);
		cd = dirstat(cf);
		if(md != nil && (md->qid.type & QTDIR) != 0){
			/*
			 * A 200909.l file is a cached listing for month 200909.
			 * use it instead if it's there and not out of date.
			 *
			 * But upas/fs is not ready for this. We can handle
			 * huge folders, but upas/fs is eager to read everything.
			 * Also, we are ignoring archived messages on the main
			 * mailbox but not on the virtual mboxes.
			 * Thus, we don't readpbvmbox for path cf.
			 */
			free(md);
			md = nil;
			nmd = dirreadall(fd, &md);
			for(j = 0; j < nmd; j++)
				if(mustshow(md[j].name)){
					msg = smprint("%s/%s",month,md[j].name);
					readpbmessage(mb, msg, doplumb);
					free(msg);
				}
		}
		close(fd);
		free(month);
		free(md);
		free(cd);
		free(cf);
		md = nil;
	}
	free(d);
}

static char*
readmbox(Mailbox *mb, int doplumb, int virt)
{
	int fd;
	Dir *d;
	static char err[128];
	Message *m;

	if(debug)
		fprint(2, "read mbox %s\n", mb->path);
	fd = open(mb->path, OREAD);
	if(fd < 0){
		errstr(err, sizeof(err));
		return err;
	}

	d = dirfstat(fd);
	if(d == nil){
		close(fd);
		errstr(err, sizeof(err));
		return err;
	}
	if(mb->d != nil){
		if(d->qid.path == mb->d->qid.path && d->qid.vers == mb->d->qid.vers){
			close(fd);
			free(d);
			return nil;
		}
		free(mb->d);
	}
	close(fd);
	mb->d = d;
	mb->vers++;
	henter(PATH(0, Qtop), mb->name,
		(Qid){PATH(mb->id, Qmbox), mb->vers, QTDIR}, nil, mb);
	snprint(err, sizeof err, "reading '%s'", mb->path);
	logmsg(err, nil);

	for(m = mb->root->part; m != nil; m = m->next)
		if(m->deleted == 0)
			m->deleted = -1;
	if(virt == 0)
		readpbmbox(mb, doplumb);
	else
		readpbvmbox(mb, doplumb);

	/*
	 * messages removed from the mbox; flag them to go.
	 */
	for(m = mb->root->part; m != nil; m = m->next)
		if(m->deleted < 0 && doplumb){
			m->inmbox = 0;
			m->deleted = 1;
			mailplumb(mb, m, 1);
		}
	logmsg("mbox read", nil);
	return nil;
}

static char*
mbsync(Mailbox *mb, int doplumb)
{
	char *rv;

	rv = readmbox(mb, doplumb, 0);
	purgembox(mb, 0);

	return rv;
}

static char*
mbvsync(Mailbox *mb, int doplumb)
{
	char *rv;

	rv = readmbox(mb, doplumb, 1);
	purgembox(mb, 1);

	return rv;
}

char*
planbmbox(Mailbox *mb, char *path)
{
	static char err[64];
	char *list;

	if(access(path, AEXIST) < 0)
		return Enotme;
	list = smprint("%s/list", path);
	if(access(list, AEXIST) < 0){
		free(list);
		return Enotme;
	}
	free(list);
	mb->sync = mbsync;
	if(debug)
		fprint(2, "planb mbox %s\n", path);
	return nil;
}

char*
planbvmbox(Mailbox *mb, char *path)
{
	static char err[64];
	char buf[64];
	int fd;
	int nr;
	int i;

	fd = open(path, OREAD);
	if(fd < 0)
		return Enotme;
	nr = read(fd, buf, sizeof(buf)-1);
	close(fd);
	if(nr < 7)
		return Enotme;
	buf[nr] = 0;
	for(i = 0; i < 6; i++)
		if(buf[i] < '0' || buf[i] > '9')
			return Enotme;
	if(buf[6] != '/')
		return Enotme;
	mb->sync = mbvsync;
	if(debug)
		fprint(2, "planb virtual mbox %s\n", path);
	return 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 [email protected].