Plan 9 from Bell Labs’s /usr/web/sources/contrib/quanstro/root/sys/src/cmd/upas/fs/imap.c

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


/*
 * todo:
 * 1.	sync with imap server's flags
 * 2.	better algorithm for avoiding downloading message list.
 * 3.	get sender — eating envelope is lots of work!
 */
#include "common.h"
#include <libsec.h>
#include <auth.h>
#include "dat.h"

#define	idprint(i, ...)	if(i->flags & Fdebug) fprint(2, __VA_ARGS__); else {}
#pragma varargck argpos	imap4cmd	2
#pragma varargck	type	"Z"		char*
#pragma varargck	type	"U"		uvlong
#pragma varargck	type	"U"		vlong

static char	confused[]	= "confused about fetch response";
static char	qsep[]		= " \t\r\n";
static char	Eimap4ctl[]	= "bad imap4 control message";

enum{
	/* cap */
	Cnolog	= 1<<0,
	Ccram	= 1<<1,
	Cntlm	= 1<<2,

	/* flags */
	Fssl	= 1<<0,
	Fdebug	= 1<<1,
	Fgmail	= 1<<2,
};

typedef struct {
	uvlong	uid;
	ulong	sizes;
	ulong	dates;
} Fetchi;

typedef struct Imap Imap;
struct Imap {
	long	lastread;

	char	*mbox;
	/* free this to free the strings below */
	char	*freep;
	char	*host;
	char	*user;

	int	refreshtime;
	uchar	cap;
	uchar	flags;

	ulong	tag;
	ulong	validity;
	int	nmsg;
	int	size;

	Fetchi	*f;
	int	nuid;
	int	muid;

	Thumbprint *thumb;

	/* open network connection */
	Biobuf	bin;
	Biobuf	bout;
	int	binit;
	int	fd;
};

enum
{
	Qok = 0,
	Qquote,
	Qbackslash,
};

static int
needtoquote(Rune r)
{
	if(r >= Runeself)
		return Qquote;
	if(r <= ' ')
		return Qquote;
	if(r == '\\' || r == '"')
		return Qbackslash;
	return Qok;
}

static int
Zfmt(Fmt *f)
{
	char *s, *t;
	int w, quotes;
	Rune r;

	s = va_arg(f->args, char*);
	if(s == 0 || *s == 0)
		return fmtstrcpy(f, "\"\"");

	quotes = 0;
	for(t = s; *t; t += w){
		w = chartorune(&r, t);
		quotes |= needtoquote(r);
	}
	if(quotes == 0)
		return fmtstrcpy(f, s);

	fmtrune(f, '"');
	for(t = s; *t; t += w){
		w = chartorune(&r, t);
		if(needtoquote(r) == Qbackslash)
			fmtrune(f, '\\');
		fmtrune(f, r);
	}
	return fmtrune(f, '"');
}

static int
Ufmt(Fmt *f)
{
	char buf[20*2 + 2];
	ulong a, b;
	uvlong u;

	u = va_arg(f->args, uvlong);
	if(u == 1)
		return fmtstrcpy(f, "nil");
	if(u == 0)
		return fmtstrcpy(f, "-");
	a = u>>32;
	b = u;
	snprint(buf, sizeof buf, "%lud:%lud", a, b);
	return fmtstrcpy(f, buf);
}

static void
imap4cmd(Imap *imap, char *fmt, ...)
{
	char buf[256], *p;
	va_list va;

	va_start(va, fmt);
	p = buf + sprint(buf, "9x%lud ", imap->tag);
	vseprint(p, buf + sizeof buf, fmt, va);
	va_end(va);

	p = buf + strlen(buf);
	if(p > buf + sizeof buf - 3)
		sysfatal("imap4 command too long");
	idprint(imap, "-> %s\n", buf);
	strcpy(p, "\r\n");
	Bwrite(&imap->bout, buf, strlen(buf));
	Bflush(&imap->bout);
}

enum {
	Ok,
	No,
	Bad,
	Bye,
	Exists,
	Status,
	Fetch,
	Cap,
	Auth,

	Unknown,
};

static char *verblist[] = {
[Ok]	"ok",
[No]	"no",
[Bad]	"bad",
[Bye]	"bye",
[Exists]	"exists",
[Status]	"status",
[Fetch]	"fetch",
[Cap]	"capability",
[Auth]	"authenticate",
};

static int
verbcode(char *verb)
{
	int i;
	char *q;

	if(q = strchr(verb, ' '))
		*q = '\0';
	for(i = 0; i < nelem(verblist) - 1; i++)
		if(strcmp(verblist[i], verb) == 0)
			break;
	if(q)
		*q = ' ';
	return i;
}

static vlong
mkuid(Imap *i, char *id)
{
	vlong v;

	v = (vlong)i->validity<<32;
	return v | strtoul(id, 0, 10);
}

static vlong
xnum(char *s, int a, int b)
{
	vlong v;

	if(*s != a)
		return -1;
	v = strtoull(s + 1, &s, 10);
	if(*s != b)
		return -1;
	return v;
}

static struct{
	char	*flag;
	int	e;
} ftab[] = {
	"Answered",	Fanswered,
	"\\Deleted",	Fdeleted,
	"\\Draft",		Fdraft,
	"\\Flagged",	Fflagged,
	"\\Recent",	Frecent,
	"\\Seen",		Fseen,
	"\\Stored",	Fstored,
};

static void
parseflags(Message *m, char *s)
{
	char *f[10];
	int i, j, j0, n;

	n = tokenize(s, f, nelem(f));
	qsort(f, n, sizeof *f, (int (*)(void*,void*))strcmp);
	j = 0;
	for(i = 0; i < n; i++)
		for(j0 = j;; j++){
			if(j == nelem(ftab)){
				j = j0;		/* restart search */
				break;
			}
			if(strcmp(f[i], ftab[j].flag) == 0){
				m->flags |= ftab[j].e;
				break;
			}
		}
}

/* "17-Jul-1996 02:44:25 -0700" */
long
internaltounix(char *s)
{
	Tm tm;
	if(strlen(s) < 20 || s[2] != '-' || s[6] != '-')
		return -1;
	s[2] = ' ';
	s[6] = ' ';
	if(strtotm(s, &tm) == -1)
		return -1;
	return tm2sec(&tm);
}
	
static char*
qtoken(char *s, char *sep)
{
	int quoting;
	char *t;

	quoting = 0;
	t = s;	/* s is output string, t is input string */
	while(*t!='\0' && (quoting || utfrune(sep, *t)==nil)){
		if(*t != '"' && *t  != '(' && *t != ')'){
			*s++ = *t++;
			continue;
		}
		/* *t is a quote */
		if(!quoting || *t == '('){
			quoting++;
			t++;
			continue;
		}
		/* quoting and we're on a quote */
		if(t[1] != '"'){
			/* end of quoted section; absorb closing quote */
			t++;
			if(quoting > 0)
				quoting--;
			continue;
		}
		/* doubled quote; fold one quote into two */
		t++;
		*s++ = *t++;
	}
	if(*s != '\0'){
		*s = '\0';
		if(t == s)
			t++;
	}
	return t;
}

int
imaptokenize(char *s, char **args, int maxargs)
{
	int nargs;

	for(nargs=0; nargs < maxargs; nargs++){
		while(*s!='\0' && utfrune(qsep, *s)!=nil)
			s++;
		if(*s == '\0')
			break;
		args[nargs] = s;
		s = qtoken(s, qsep);
	}

	return nargs;
}

static char*
fetchrsp(Imap *imap, char *p, Mailbox *, Message *m)
{
	char *f[15], *s, *q;
	int i, n, a;
	ulong o, l;
	uvlong v;
	static char error[256];
	extern void msgrealloc(Message*, ulong);

redux:
	n = imaptokenize(p, f, nelem(f));
	if(n%2)
		return confused;
	for(i = 0; i < n; i += 2){
		if(strcmp(f[i], "internaldate") == 0){
			l = internaltounix(f[i + 1]);
			if(l < 418319360)
				abort();
			if(imap->nuid < imap->muid)
				imap->f[imap->nuid].dates = l;
		}else if(strcmp(f[i], "rfc822.size") == 0){
			l = strtoul(f[i + 1], 0, 0);
			if(m)
				m->size = l;
			else if(imap->nuid < imap->muid)
				imap->f[imap->nuid].sizes = l;
		}else if(strcmp(f[i], "uid") == 0){
			v = mkuid(imap, f[1]);
			if(m)
				m->imapuid = v;
			if(imap->nuid < imap->muid)
				imap->f[imap->nuid].uid = v;
		}else if(strcmp(f[i], "flags") == 0)
			parseflags(m, f[i + 1]);
		else if(strncmp(f[i], "body[]", 6) == 0){
			s = f[i]+6;
			o = 0;
			if(*s == '<')
				o = xnum(s, '<', '>');
			if(o == -1)
				return confused;
			l = xnum(f[i + 1], '{', '}');
			a = o + l - m->ibadchars - m->size;
			if(a > 0){
				assert(imap->flags & Fgmail);
				m->size = o + l;
				msgrealloc(m, m->size);
				m->size -= m->ibadchars;
			}
			if(Bread(&imap->bin, m->start + o, l) != l){
				snprint(error, sizeof error, "read: %r");
				return error;
			}
			if(Bgetc(&imap->bin) == ')'){
				while(Bgetc(&imap->bin) != '\n')
					;
				return 0;
			}
			/* evil */
			if(!(p = Brdline(&imap->bin, '\n')))
				return 0;
			q = p + Blinelen(&imap->bin);
			while(q > p && (q[-1] == '\n' || q[-1] == '\r'))
				q--;
			*q = 0;
			lowercase(p);
			idprint(imap, "<- %s\n", p);

			goto redux;
		}else
			return confused;
	}
	return 0;
}

void
parsecap(Imap *imap, char *s)
{
	char *t[32], *p;
	int n, i;

	s = strdup(s);
	n = getfields(s, t, nelem(t), 0, " ");
	for(i = 0; i < n; i++){
		if(strncmp(t[i], "auth=", 5) == 0){
			p = t[i] + 5;
			if(strcmp(p, "cram-md5") == 0)
				imap->cap |= Ccram;
			if(strcmp(p, "ntlm") == 0)
				imap->cap |= Cntlm;
		}else if(strcmp(t[i], "logindisabled") == 0)
			imap->cap |= Cnolog;
	}
	free(s);
}

/*
 *  get imap4 response line.  there might be various
 *  data or other informational lines mixed in.
 */
static char*
imap4resp0(Imap *imap, Mailbox *mb, Message *m)
{
	char *e, *line, *p, *ep, *op, *q, *verb;
	int n, unexp;
	static char error[256];

	unexp = 0;
	while(p = Brdline(&imap->bin, '\n')){
		ep = p + Blinelen(&imap->bin);
		while(ep > p && (ep[-1] == '\n' || ep[-1] == '\r'))
			*--ep = '\0';
		idprint(imap, "<- %s\n", p);
		if(unexp && p[0] != '9' && p[1] != 'x')
		if(strtoul(p + 2, &p, 10) != imap->tag)
			continue;
		if(p[0] != '+')
			lowercase(p);		/* botch */

		switch(p[0]){
		case '+':				/* cram challenge */
			if(ep - p > 2)
				return p + 2;
			break;
		case '*':
			if(p[1] != ' ')
				continue;
			p += 2;
			line = p;
			n = strtol(p, &p, 10);
			if(*p == ' ')
				p++;
			verb = p;
	
			if(p = strchr(verb, ' '))
				p++;
			else
				p = verb + strlen(verb);

			switch(verbcode(verb)){
			case Bye:
				/* early disconnect */
				snprint(error, sizeof error, "%s", p);
				return error;
			case Ok:
			case No:
			case Bad:
				/* human readable text at p; */
				break;
			case Exists:
				imap->nmsg = n;
				break;
			case Cap:
				parsecap(imap, p);
				break;
			case Status:
				/* * status inbox (messages 2 uidvalidity 960164964) */
				if(q = strstr(p, "messages"))
					imap->nmsg = strtoul(q + 8, 0, 10);
				if(q = strstr(p, "uidvalidity"))
					imap->validity = strtoul(q + 11, 0, 10);
				break;
			case Fetch:
				if(*p == '('){
					p++;
					if(ep[-1] == ')')
						*--ep = 0;
				}
				if(e = fetchrsp(imap, p, mb, m))
					eprint("imap: fetchrsp: %s\n", e);
				imap->nuid++;
				break;
			case Auth:
				break;
			}
			if(imap->tag == 0)
				return line;
			break;
		case '9':		/* response to our message */
			op = p;
			if(p[1] == 'x' && strtoul(p + 2, &p, 10) == imap->tag){
				while(*p == ' ')
					p++;
				imap->tag++;
				return p;
			}
			eprint("imap: expected %lud; got %s\n", imap->tag, op);
			break;
		default:
			if(imap->flags&Fdebug || *p){
				eprint("imap: unexpected line: %s\n", p);
				unexp = 1;
			}
		}
	}
	snprint(error, sizeof error, "i/o error: %r\n");
	return error;
}

static char*
imap4resp(Imap *i)
{
	return imap4resp0(i, 0, 0);
}

static int
isokay(char *resp)
{
	return cistrncmp(resp, "OK", 2) == 0;
}

static char*
findflag(int idx)
{
	int i;

	for(i = 0; i < nelem(ftab); i++)
		if(ftab[i].e == 1<<idx)
			return ftab[i].flag;
	return nil;
}

static void
imap4modflags(Mailbox *mb, Message *m, int flags)
{
	char buf[128], *p, *e, *fs;
	int i, f;
	Imap *imap;

	imap = mb->aux;
	e = buf + sizeof buf;
	p = buf;
	f = flags & ~Frecent;
	for(i = 0; i < Nflags; i++)
		if(f & 1<<i && (fs = findflag(i)))
			p = seprint(p, e, "%s ", fs);
	if(p > buf){
		p[-1] = 0;
		imap4cmd(imap, "uid store %lud flags (%s)", (ulong)m->imapuid, buf);
		imap4resp(imap);
	}
}

static char*
imap4cram(Imap *imap)
{
	char *s, *p, ch[128], usr[64], rbuf[128], ubuf[128], ebuf[192];
	int i, n, l;

	fmtinstall('[', encodefmt);

	imap4cmd(imap, "authenticate cram-md5");
	p = imap4resp(imap);
	if(p == nil)
		return "no challenge";
	l = dec64((uchar*)ch, sizeof ch, p, strlen(p));
	if(l == -1)
		return "bad base64";
	ch[l] = 0;
	idprint(imap, "challenge [%s]\n", ch);

	if(imap->user == nil)
		imap->user = getlog();
	n = auth_respond(ch, l, usr, sizeof usr, rbuf, sizeof rbuf, auth_getkey,
		"proto=cram role=client server=%q user=%s", imap->host, imap->user);
	if(n == -1)
		return "cannot find IMAP password";
	for(i = 0; i < n; i++)
		if(rbuf[i] >= 'A' && rbuf[i] <= 'Z')
			rbuf[i] += 'a' - 'A';
	l = snprint(ubuf, sizeof ubuf, "%s %.*s", usr, n, rbuf);
	idprint(imap, "raw cram [%s]\n", ubuf);
	snprint(ebuf, sizeof ebuf, "%.*[", l, ubuf);

	imap->tag = 1;
	idprint(imap, "-> %s\n", ebuf);
	Bprint(&imap->bout, "%s\r\n", ebuf);
	Bflush(&imap->bout);

	if(!isokay(s = imap4resp(imap)))
		return s;
	return nil;
}

/*
 *  authenticate to IMAP4 server using NTLM (untested)
 * 
 *  http://davenport.sourceforge.net/ntlm.html#ntlmImapAuthentication
 *  http://msdn.microsoft.com/en-us/library/cc236621%28PROT.13%29.aspx
 */
static uchar*
psecb(uchar *p, uint o, int n)
{
	putle(p, n, 2);
	putle(p+2, n, 2);
	putle(p+4, o, 4);
	return p+8;
}

static uchar*
psecq(uchar *q, char *s, int n)
{
	memcpy(q, s, n);
	return q+n;
}

static char*
imap4ntlm(Imap *imap)
{
	char *s, ruser[64], enc[256];
	uchar buf[128], *p, *ep, *q, *eq, *chal;
	int n;
	MSchapreply mcr;

	imap4cmd(imap, "authenticate ntlm");
	imap4resp(imap);

	/* simple NtLmNegotiate blob with NTLM+OEM flags */
	imap4cmd(imap, "TlRMTVNTUAABAAAAAgIAAA==");
	s = imap4resp(imap);
	n = dec64(buf, sizeof buf, s, strlen(s));
	if(n < 32 || memcmp(buf, "NTLMSSP", 8) != 0)
		return "bad NtLmChallenge";
	chal = buf+24;

	if(auth_respond(chal, 8, ruser, sizeof ruser,
			&mcr, sizeof mcr, auth_getkey,
			"proto=mschap role=client service=imap server=%q user?",
			imap->host) < 0)
		return "auth_respond failed";

	/* prepare NtLmAuthenticate blob */

	memset(buf, sizeof buf, 0);
	p = buf;
	ep = p + 8 + 6*8 + 2*4;
	q = ep;
	eq = buf + sizeof buf;


	memcpy(p, "NTLMSSP", 8);	/* magic */
	p += 8;

	putle(p, 3, 4);			/* type */
	p += 4;

	p = psecb(p, q-buf, 24);		/* LMresp */
	q = psecq(q, mcr.LMresp, 24);

	p = psecb(p, q-buf, 24);		/* NTresp */
	q = psecq(q, mcr.NTresp, 24);

	p = psecb(p, q-buf, 0);		/* realm */

	n = strlen(ruser);
	p = psecb(p, q-buf, n);		/* user name */
	q = psecq(q, ruser, n);

	p = psecb(p, q-buf, 0);		/* workstation name */
	p = psecb(p, q-buf, 0);		/* session key */

	putle(p, 0x0202, 4);		/* flags: oem(2)|ntlm(0x200) */
	p += 4;

	if(p > ep || q > eq)
		return "error creating NtLmAuthenticate";
	enc64(enc, sizeof enc, buf, q-buf);

	imap4cmd(imap, enc);
	if(!isokay(s = imap4resp(imap)))
		return s;
	return nil;
}

static char*
imap4passwd(Imap *imap)
{
	char *s;
	UserPasswd *up;

	if(imap->user != nil)
		up = auth_getuserpasswd(auth_getkey, "proto=pass service=imap server=%q user=%q", imap->host, imap->user);
	else
		up = auth_getuserpasswd(auth_getkey, "proto=pass service=imap server=%q", imap->host);
	if(up == nil)
		return "cannot find IMAP password";

	imap->tag = 1;
	imap4cmd(imap, "login %Z %Z", up->user, up->passwd);
	free(up);
	if(!isokay(s = imap4resp(imap)))
		return s;
	return nil;
}

static char*
imap4login(Imap *imap)
{
	char *e;

	if(imap->cap & Ccram)
		e = imap4cram(imap);
	else if(imap->cap & Cntlm)
		e = imap4ntlm(imap);
	else
		e = imap4passwd(imap);
	if(e)
		return e;
	imap4cmd(imap, "select %Z", imap->mbox);
	if(!isokay(e = imap4resp(imap)))
		return e;
	return nil;
}

static char*
imaperrstr(char *host, char *port)
{
	char err[ERRMAX];
	static char buf[256];

	err[0] = 0;
	errstr(err, sizeof err);
	snprint(buf, sizeof buf, "%s/%s:%s", host, port, err);
	return buf;
}

static int
starttls(Imap *imap, TLSconn *tls)
{
	char buf[Pathlen];
	uchar digest[SHA1dlen];
	int sfd, fd;

	memset(tls, 0, sizeof *tls);
	sfd = tlsClient(imap->fd, tls);
	if(sfd < 0){
		werrstr("tlsClient: %r");
		return -1;
	}
	if(tls->cert == nil || tls->certlen <= 0){
		close(sfd);
		werrstr("server did not provide TLS certificate");
		return -1;
	}
	sha1(tls->cert, tls->certlen, digest, nil);
	if(!imap->thumb || !okThumbprint(digest, imap->thumb)){
		close(sfd);
		werrstr("server certificate %.*H not recognized",
			SHA1dlen, digest);
		return -1;
	}
	close(imap->fd);
	imap->fd = sfd;

	if(imap->flags & Fdebug){
		snprint(buf, sizeof buf, "%s/ctl", tls->dir);
		fd = open(buf, OWRITE);
		fprint(fd, "debug");
		close(fd);
	}

	return 1;
}

static void
imap4disconnect(Imap *imap)
{
	if(imap->binit){
		Bterm(&imap->bin);
		Bterm(&imap->bout);
		imap->binit = 0;
	}
	close(imap->fd);
	imap->fd = -1;
}

char*
capabilties(Imap *imap)
{
	char * err;

	imap4cmd(imap, "capability");
	imap4resp(imap);
	err = imap4resp(imap);
	if(isokay(err))
		err = 0;
	return err;
}

static char*
imap4dial(Imap *imap)
{
	char *err, *port;
	TLSconn conn;

	if(imap->fd >= 0){
		imap4cmd(imap, "noop");
		if(isokay(imap4resp(imap)))
			return nil;
		imap4disconnect(imap);
	}
	if(imap->flags & Fssl)
		port = "imaps";
	else
		port = "imap";
	if((imap->fd = dial(netmkaddr(imap->host, "net", port), 0, 0, 0)) < 0)
		return imaperrstr(imap->host, port);
	if(imap->flags & Fssl && starttls(imap, &conn) == -1){
		err = imaperrstr(imap->host, port);
		free(conn.cert);
		imap4disconnect(imap);
		return err;
	}
	assert(imap->binit == 0);
	Binit(&imap->bin, imap->fd, OREAD);
	Binit(&imap->bout, imap->fd, OWRITE);
	imap->binit = 1;

	imap->tag = 0;
	err = imap4resp(imap);
	if(!isokay(err))
		return "error in initial IMAP handshake";

	if((err = capabilties(imap)) || (err = imap4login(imap))){
		eprint("imap: err is %s\n", err);
		imap4disconnect(imap);
		return err;
	}
	return nil;
}

static void
imap4hangup(Imap *imap)
{
	imap4cmd(imap, "logout");
	imap4resp(imap);
	imap4disconnect(imap);
}

/* gmail lies about message sizes */
static ulong
gmaildiscount(Message *m, uvlong o, ulong l)
{
	if((m->cstate&Cidx) == 0)
	if(o + l == m->size)
		return l + 100 + (o + l)/5;
	return l;
}

static int
imap4fetch(Mailbox *mb, Message *m, uvlong o, ulong l)
{
	Imap *imap;

	imap = mb->aux;
	if(imap->flags & Fgmail)
		l = gmaildiscount(m, o, l);
	idprint(imap, "uid fetch %lud (body.peek[]<%llud.%lud>)\n", (ulong)m->imapuid, o, l);
	imap4cmd(imap, "uid fetch %lud (body.peek[]<%llud.%lud>)", (ulong)m->imapuid, o, l);
	if(!isokay(imap4resp0(imap, mb, m))){
		eprint("imap: imap fetch failed\n");
		return -1;
	}
	return 0;
}

static uvlong
datesec(Imap *imap, int i)
{
	int j;
	uvlong v;
	Fetchi *f;

	f = imap->f;
	v = (uvlong)f[i].dates << 8;

	/* shifty; these sequences should be stable. */
	for(j = i; j-- > 0; )
		if(f[i].dates != f[j].dates)
			break;
	v |= i - (j + 1);
	return v;
}

static void
markdel(Mailbox *mb, Message *m, int doplumb)
{
	if(doplumb)
		mailplumb(mb, m, 1);
	m->inmbox = 0;
	m->deleted = Disappear;
}

static int
vcmp(vlong a, vlong b)
{
	a -= b;
	if(a > 0)
		return 1;
	if(a < 0)
		return -1;
	return 0;
}

static int
fetchicmp(Fetchi *f1, Fetchi *f2)
{
	return vcmp(f1->uid, f2->uid);
}

static int
setsize(Mailbox *, Message *m, Fetchi *f)
{
	if(f->sizes >= Maxmsg)
		return -1;
//	if(!gmailmbox(mb))
	return m->size = f->sizes;
}

static char*
imap4read(Imap *imap, Mailbox *mb, int doplumb, int *new)
{
	char *s;
	int i, n, c, nnew, ndel;
	Fetchi *f;
	Message *m, **ll;

	*new = 0;
	if(time(0) - imap->lastread < 10)
		return nil;
	imap->lastread = time(0);
	imap4cmd(imap, "status %Z (messages uidvalidity)", imap->mbox);
	if(!isokay(s = imap4resp(imap)))
		return s;

	imap->nuid = 0;
	imap->muid = imap->nmsg;
	imap->f = erealloc(imap->f, imap->nmsg*sizeof imap->f[0]);
	f = imap->f;
	n = imap->nmsg;

	if(imap->nmsg > 0){
		imap4cmd(imap, "uid fetch 1:* (uid rfc822.size internaldate)");
		if(!isokay(s = imap4resp(imap)))
			return s;
	}

	qsort(f, n, sizeof f[0], (int(*)(void*, void*))fetchicmp);
	nnew = ndel = 0;
	ll = &mb->root->part;
	for(i = 0; *ll || i < n; ){
		c = -1;
		if(i >= n)
			c = 1;
		else if(*ll){
			if((*ll)->imapuid == 0)
				(*ll)->imapuid = strtoull((*ll)->idxaux, 0, 0);
			c = vcmp(f[i].uid, (*ll)->imapuid);
		}
		idprint(imap, "consider %U and %U -> %d\n", i<n? f[i].uid: 0, *ll? (*ll)->imapuid: 1, c);
		if(c < 0){
			/* new message */
			idprint(imap, "new: %U (%U)\n", f[i].uid, *ll? (*ll)->imapuid: 0);
			m = newmessage(mb->root);
			m->inmbox = 1;
			m->idxaux = smprint("%llud", f[i].uid);
			m->imapuid = f[i].uid;
			m->fileid = datesec(imap, i);
			if(setsize(mb, m, f + i) < 0 || m->size >= Maxmsg){
				/* message disappeared?  unchain */
				idprint(imap, "deleted → %r (%U)\n", m->imapuid);
				logmsg(m, "disappeared");
				if(doplumb)
					mailplumb(mb, m, 1); /* redundant */
				unnewmessage(mb, mb->root, m);
				/* we're out of sync; here's were to signal that */
				break;
			}
			nnew++;
			logmsg(m, "new %s", m->idxaux);
			m->next = *ll;
			*ll = m;
			ll = &m->next;
			i++;
			newcachehash(mb, m, doplumb);
			putcache(mb, m);
		}else if(c > 0){
			/* deleted message; */
			idprint(imap, "deleted: %U (%U)\n", i<n? f[i].uid: 0, *ll? (*ll)->imapuid: 0);
			ndel++;
			logmsg(*ll, "deleted");
			markdel(mb, *ll, doplumb);
			ll = &(*ll)->next;
		}else{
			//logmsg(*ll, "duplicate %s", d[i].name);
			i++;
			ll = &(*ll)->next;
		}
	}

	*new = nnew;
	return nil;
}

static void
imap4delete(Mailbox *mb, Message *m)
{
	Imap *imap;

	imap = mb->aux;
	if((ulong)(m->imapuid>>32) == imap->validity){
		imap4cmd(imap, "uid store %lud +flags (\\Deleted)", (ulong)m->imapuid);
		imap4resp(imap);
		imap4cmd(imap, "expunge");
		imap4resp(imap);
//		if(!isokay(imap4resp(imap))
//			return -1;
	}
	m->inmbox = 0;
}

static char*
imap4sync(Mailbox *mb, int doplumb, int *new)
{
	char *err;
	Imap *imap;

	imap = mb->aux;
	if(err = imap4dial(imap))
		goto out;
	if((err = imap4read(imap, mb, doplumb, new)) == nil)
		mb->d->atime = mb->d->mtime = time(0);
out:
	mb->waketime = time(0) + imap->refreshtime;
	return err;
}

static char*
imap4ctl(Mailbox *mb, int argc, char **argv)
{
	char *a, *b;
	Imap *imap;

	imap = mb->aux;
	if(argc < 1)
		return Eimap4ctl;

	if(argc == 1 && strcmp(argv[0], "debug") == 0){
		imap->flags ^= Fdebug;
		return nil;
	}
	if(strcmp(argv[0], "thumbprint") == 0){
		if(imap->thumb){
			freeThumbprints(imap->thumb);
			imap->thumb = 0;
		}
		a = "/sys/lib/tls/mail";
		b = "/sys/lib/tls/mail.exclude";
		switch(argc){
		default:
			return Eimap4ctl;
		case 4:
			b = argv[2];
		case 3:
			a = argv[1];
		case 2:
			break;
		}
		imap->thumb = initThumbprints(a, b);
		return nil;
	}
	if(argc == 2 && strcmp(argv[0], "uid") == 0){
		uvlong l;
		Message *m;

		for(m = mb->root->part; m; m = m->next)
			if(strcmp(argv[1], m->name) == 0){
				l = strtoull(m->idxaux, 0, 0);
				fprint(2, "uid %s %lud %lud %lud %lud\n", m->name, (ulong)(l>>32), (ulong)l,
					(ulong)(m->imapuid>>32), (ulong)m->imapuid);
			}
		return nil;
	}
	if(strcmp(argv[0], "refresh") == 0)
		switch(argc){
		case 1:
			imap->refreshtime = 60;
			return nil;
		case 2:
			imap->refreshtime = atoi(argv[1]);
			return nil;
		}

	return Eimap4ctl;
}

static void
imap4close(Mailbox *mb)
{
	Imap *imap;

	imap = mb->aux;
	imap4disconnect(imap);
	free(imap->f);
	free(imap);
}

static char*
mkmbox(Imap *imap, char *p, char *e)
{
	p = seprint(p, e, "%s/box/%s/imap.%s", MAILROOT, getlog(), imap->host);
	if(imap->user && strcmp(imap->user, getlog()))
		p = seprint(p, e, ".%s", imap->user);
	if(cistrcmp(imap->mbox, "inbox"))
		p = seprint(p, e, ".%s", imap->mbox);
	return p;
}

static char*
findmbox(char *p)
{
	char *f[10], path[Pathlen];
	int nf;

	snprint(path, sizeof path, "%s", p);
	nf = getfields(path, f, 5, 0, "/");
	if(nf < 3)
		return nil;
	return f[nf - 1];
}

static char*
imap4rename(Mailbox *mb, char *p2, int)
{
	char *r, *new;
	Imap *imap;

	imap = mb->aux;
	new = findmbox(p2);
	idprint(imap, "rename %s %s\n", imap->mbox, new);
	imap4cmd(imap, "rename %s %s", imap->mbox, new);
	r = imap4resp(imap);
	if(!isokay(r))
		return r;
	free(imap->mbox);
	imap->mbox = smprint("%s", new);
	mkmbox(imap, mb->path, mb->path + sizeof mb->path);
	return 0;
}

/*
 * incomplete; when we say remove we want to get subfolders, too.
 * so we need to to a list, and recursivly nuke folders.
 */
static char*
imap4remove(Mailbox *mb, int flags)
{
	char *r;
	Imap *imap;

	imap = mb->aux;
	idprint(imap, "remove %s\n", imap->mbox);
	imap4cmd(imap, "delete %s", imap->mbox);
	r = imap4resp(imap);
	if(!isokay(r))
		return r;
	if(flags & Rtrunc){
		imap4cmd(imap, "create %s", imap->mbox);
		r = imap4resp(imap);
		if(!isokay(r))
			return r;
	}
	return 0;
}

char*
imap4mbox(Mailbox *mb, char *path)
{
	char *f[10];
	uchar flags;
	int nf;
	Imap *imap;

	fmtinstall('Z', Zfmt);
	fmtinstall('U', Ufmt);
	if(strncmp(path, "/imap/", 6) == 0)
		flags = 0;
	else if(strncmp(path, "/imaps/", 7) == 0)
		flags = Fssl;
	else
		return Enotme;

	path = strdup(path);
	if(path == nil)
		return "out of memory";

	nf = getfields(path, f, 5, 0, "/");
	if(nf < 3){
		free(path);
		return "bad imap path syntax /imap[s]/system[/user[/mailbox]]";
	}

	imap = emalloc(sizeof *imap);
	imap->fd = -1;
	imap->freep = path;
	imap->flags = flags;
	imap->host = f[2];
	if(strstr(imap->host, "gmail.com"))
		imap->flags |= Fgmail;
	imap->refreshtime = 60;
	if(nf < 4)
		imap->user = nil;
	else
		imap->user = f[3];
	if(nf < 5)
		imap->mbox = strdup("inbox");
	else
		imap->mbox = strdup(f[4]);
	mkmbox(imap, mb->path, mb->path + sizeof mb->path);
	if(imap->flags & Fssl)
		imap->thumb = initThumbprints("/sys/lib/tls/mail", "/sys/lib/tls/mail.exclude");

	mb->aux = imap;
	mb->sync = imap4sync;
	mb->close = imap4close;
	mb->ctl = imap4ctl;
	mb->fetch = imap4fetch;
	mb->delete = imap4delete;
	mb->rename = imap4rename;
//	mb->remove = imap4remove;
	mb->modflags = imap4modflags;
	mb->d = emalloc(sizeof *mb->d);
	mb->addfrom = 1;
	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].