/*
* 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;
}
|