1763 lines
31 KiB
C
1763 lines
31 KiB
C
/*
|
|
* Locking here is not quite right.
|
|
* Calling qlock(&z->lk) can block the proc,
|
|
* and when it comes back, boxes and msgs might have been freed
|
|
* (if the refresh proc was holding the lock and in the middle of a
|
|
* redial). I've tried to be careful about not assuming boxes continue
|
|
* to exist across imap commands, but maybe this isn't really tenable.
|
|
* Maybe instead we should ref count the boxes and messages.
|
|
*/
|
|
|
|
#include "a.h"
|
|
#include <libsec.h>
|
|
|
|
struct Imap
|
|
{
|
|
int connected;
|
|
int autoreconnect;
|
|
int ticks; /* until boom! */
|
|
char* server;
|
|
char* root;
|
|
int mode;
|
|
int fd;
|
|
Biobuf b;
|
|
Ioproc* io;
|
|
QLock lk;
|
|
QLock rlk;
|
|
Rendez r;
|
|
|
|
Box* inbox;
|
|
Box* box;
|
|
Box* nextbox;
|
|
|
|
/* SEARCH results */
|
|
uint *uid;
|
|
uint nuid;
|
|
};
|
|
|
|
static struct {
|
|
char *name;
|
|
int flag;
|
|
} flagstab[] =
|
|
{
|
|
"Junk", FlagJunk,
|
|
"NonJunk", FlagNonJunk,
|
|
"\\Answered", FlagReplied,
|
|
"\\Flagged", FlagFlagged,
|
|
"\\Deleted", FlagDeleted,
|
|
"\\Draft", FlagDraft,
|
|
"\\Recent", FlagRecent,
|
|
"\\Seen", FlagSeen,
|
|
"\\NoInferiors", FlagNoInferiors,
|
|
"\\NoSelect", FlagNoSelect,
|
|
"\\Marked", FlagMarked,
|
|
"\\UnMarked", FlagUnMarked
|
|
};
|
|
|
|
int chattyimap;
|
|
|
|
static char *tag = "#";
|
|
|
|
static void checkbox(Imap*, Box*);
|
|
static char* copyaddrs(Sx*);
|
|
static void freeup(UserPasswd*);
|
|
static int getbox(Imap*, Box*);
|
|
static int getboxes(Imap*);
|
|
static char* gsub(char*, char*, char*);
|
|
static int imapcmd(Imap*, Box*, char*, ...);
|
|
static Sx* imapcmdsx(Imap*, Box*, char*, ...);
|
|
static Sx* imapcmdsx0(Imap*, char*, ...);
|
|
static Sx* imapvcmdsx(Imap*, Box*, char*, va_list);
|
|
static Sx* imapvcmdsx0(Imap*, char*, va_list);
|
|
static int imapdial(char*, int);
|
|
static int imaplogin(Imap*);
|
|
static int imapquote(Fmt*);
|
|
static int imapreconnect(Imap*);
|
|
static void imaprefreshthread(void*);
|
|
static void imaptimerproc(void*);
|
|
static Sx* imapwaitsx(Imap*);
|
|
static int isatom(Sx *v, char *name);
|
|
static int islist(Sx *v);
|
|
static int isnil(Sx *v);
|
|
static int isnumber(Sx *sx);
|
|
static int isstring(Sx *sx);
|
|
static int ioimapdial(Ioproc*, char*, int);
|
|
static char* nstring(Sx*);
|
|
static void unexpected(Imap*, Sx*);
|
|
static Sx* zBrdsx(Imap*);
|
|
|
|
/*
|
|
* Imap connection maintenance and login.
|
|
*/
|
|
|
|
Imap*
|
|
imapconnect(char *server, int mode, char *root)
|
|
{
|
|
Imap *z;
|
|
|
|
fmtinstall('H', encodefmt);
|
|
fmtinstall('Z', imapquote);
|
|
|
|
z = emalloc(sizeof *z);
|
|
z->server = estrdup(server);
|
|
z->mode = mode;
|
|
if(root)
|
|
if(root[0] != 0 && root[strlen(root)-1] != '/')
|
|
z->root = smprint("%s/", root);
|
|
else
|
|
z->root = root;
|
|
else
|
|
z->root = "";
|
|
z->fd = -1;
|
|
z->autoreconnect = 0;
|
|
z->io = ioproc();
|
|
|
|
qlock(&z->lk);
|
|
if(imapreconnect(z) < 0){
|
|
free(z);
|
|
return nil;
|
|
}
|
|
|
|
z->r.l = &z->rlk;
|
|
z->autoreconnect = 1;
|
|
qunlock(&z->lk);
|
|
|
|
proccreate(imaptimerproc, z, STACK);
|
|
mailthread(imaprefreshthread, z);
|
|
|
|
return z;
|
|
}
|
|
|
|
void
|
|
imaphangup(Imap *z, int ticks)
|
|
{
|
|
z->ticks = ticks;
|
|
if(ticks == 0){
|
|
close(z->fd);
|
|
z->fd = -1;
|
|
}
|
|
}
|
|
|
|
static int
|
|
imapreconnect(Imap *z)
|
|
{
|
|
Sx *sx;
|
|
|
|
z->autoreconnect = 0;
|
|
z->box = nil;
|
|
z->inbox = nil;
|
|
|
|
if(z->fd >= 0){
|
|
close(z->fd);
|
|
z->fd = -1;
|
|
}
|
|
|
|
if(chattyimap)
|
|
fprint(2, "dial %s...\n", z->server);
|
|
if((z->fd = ioimapdial(z->io, z->server, z->mode)) < 0)
|
|
return -1;
|
|
z->connected = 1;
|
|
Binit(&z->b, z->fd, OREAD);
|
|
if((sx = zBrdsx(z)) == nil){
|
|
werrstr("no greeting");
|
|
goto err;
|
|
}
|
|
if(chattyimap)
|
|
fprint(2, "<I %#$\n", sx);
|
|
if(sx->nsx >= 2 && isatom(sx->sx[0], "*") && isatom(sx->sx[1], "PREAUTH")){
|
|
freesx(sx);
|
|
goto preauth;
|
|
}
|
|
if(!oksx(sx)){
|
|
werrstr("bad greeting - %#$", sx);
|
|
goto err;
|
|
}
|
|
freesx(sx);
|
|
sx = nil;
|
|
if(imaplogin(z) < 0)
|
|
goto err;
|
|
preauth:
|
|
if(getboxes(z) < 0 || getbox(z, z->inbox) < 0)
|
|
goto err;
|
|
z->autoreconnect = 1;
|
|
return 0;
|
|
|
|
err:
|
|
if(z->fd >= 0){
|
|
close(z->fd);
|
|
z->fd = -1;
|
|
}
|
|
if(sx)
|
|
freesx(sx);
|
|
z->autoreconnect = 1;
|
|
z->connected = 0;
|
|
return -1;
|
|
}
|
|
|
|
static int
|
|
imaplogin(Imap *z)
|
|
{
|
|
Sx *sx;
|
|
UserPasswd *up;
|
|
|
|
if((up = auth_getuserpasswd(auth_getkey, "proto=pass role=client service=imap server=%q", z->server)) == nil){
|
|
werrstr("getuserpasswd - %r");
|
|
return -1;
|
|
}
|
|
|
|
sx = imapcmdsx(z, nil, "LOGIN %Z %Z", up->user, up->passwd);
|
|
freeup(up);
|
|
if(sx == nil)
|
|
return -1;
|
|
if(!oksx(sx)){
|
|
freesx(sx);
|
|
werrstr("login rejected - %#$", sx);
|
|
return -1;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static int
|
|
getboxes(Imap *z)
|
|
{
|
|
int i;
|
|
Box **r, **w, **e;
|
|
|
|
for(i=0; i<nboxes; i++){
|
|
boxes[i]->mark = 1;
|
|
boxes[i]->exists = 0;
|
|
boxes[i]->maxseen = 0;
|
|
}
|
|
if(imapcmd(z, nil, "LIST %Z *", z->root) < 0)
|
|
return -1;
|
|
if(z->root != nil && imapcmd(z, nil, "LIST %Z INBOX", "") < 0)
|
|
return -1;
|
|
if(z->nextbox && z->nextbox->mark)
|
|
z->nextbox = nil;
|
|
for(r=boxes, w=boxes, e=boxes+nboxes; r<e; r++){
|
|
if((*r)->mark)
|
|
{fprint(2, "*** free box %s %s\n", (*r)->name, (*r)->imapname);
|
|
boxfree(*r);
|
|
}
|
|
else
|
|
*w++ = *r;
|
|
}
|
|
nboxes = w - boxes;
|
|
return 0;
|
|
}
|
|
|
|
static int
|
|
getbox(Imap *z, Box *b)
|
|
{
|
|
int i;
|
|
Msg **r, **w, **e;
|
|
|
|
if(b == nil)
|
|
return 0;
|
|
|
|
for(i=0; i<b->nmsg; i++)
|
|
b->msg[i]->imapid = 0;
|
|
if(imapcmd(z, b, "UID FETCH 1:* FLAGS") < 0)
|
|
return -1;
|
|
for(r=b->msg, w=b->msg, e=b->msg+b->nmsg; r<e; r++){
|
|
if((*r)->imapid == 0)
|
|
msgfree(*r);
|
|
else{
|
|
(*r)->ix = w-b->msg;
|
|
*w++ = *r;
|
|
}
|
|
}
|
|
b->nmsg = w - b->msg;
|
|
b->imapinit = 1;
|
|
checkbox(z, b);
|
|
return 0;
|
|
}
|
|
|
|
static void
|
|
freeup(UserPasswd *up)
|
|
{
|
|
memset(up->user, 0, strlen(up->user));
|
|
memset(up->passwd, 0, strlen(up->passwd));
|
|
free(up);
|
|
}
|
|
|
|
static void
|
|
imaptimerproc(void *v)
|
|
{
|
|
Imap *z;
|
|
|
|
z = v;
|
|
for(;;){
|
|
sleep(60*1000);
|
|
qlock(z->r.l);
|
|
rwakeup(&z->r);
|
|
qunlock(z->r.l);
|
|
}
|
|
}
|
|
|
|
static void
|
|
checkbox(Imap *z, Box *b)
|
|
{
|
|
if(imapcmd(z, b, "NOOP") >= 0){
|
|
if(!b->imapinit)
|
|
getbox(z, b);
|
|
if(!b->imapinit)
|
|
return;
|
|
if(b==z->box && b->exists > b->maxseen){
|
|
imapcmd(z, b, "UID FETCH %d:* FULL",
|
|
b->uidnext);
|
|
}
|
|
}
|
|
}
|
|
|
|
static void
|
|
imaprefreshthread(void *v)
|
|
{
|
|
Imap *z;
|
|
|
|
z = v;
|
|
for(;;){
|
|
qlock(z->r.l);
|
|
rsleep(&z->r);
|
|
qunlock(z->r.l);
|
|
|
|
qlock(&z->lk);
|
|
if(z->inbox)
|
|
checkbox(z, z->inbox);
|
|
qunlock(&z->lk);
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Run a single command and return the Sx. Does NOT redial.
|
|
*/
|
|
static Sx*
|
|
imapvcmdsx0(Imap *z, char *fmt, va_list arg)
|
|
{
|
|
char *s;
|
|
Fmt f;
|
|
int prefix, len;
|
|
Sx *sx;
|
|
|
|
if(canqlock(&z->lk))
|
|
abort();
|
|
|
|
if(z->fd < 0 || !z->connected)
|
|
return nil;
|
|
|
|
prefix = strlen(tag)+1;
|
|
fmtstrinit(&f);
|
|
fmtprint(&f, "%s ", tag);
|
|
fmtvprint(&f, fmt, arg);
|
|
fmtprint(&f, "\r\n");
|
|
s = fmtstrflush(&f);
|
|
len = strlen(s);
|
|
s[len-2] = 0;
|
|
if(chattyimap)
|
|
fprint(2, "I> %s\n", s);
|
|
s[len-2] = '\r';
|
|
if(iowrite(z->io, z->fd, s, len) < 0){
|
|
z->connected = 0;
|
|
free(s);
|
|
return nil;
|
|
}
|
|
sx = imapwaitsx(z);
|
|
free(s);
|
|
return sx;
|
|
}
|
|
|
|
static Sx*
|
|
imapcmdsx0(Imap *z, char *fmt, ...)
|
|
{
|
|
va_list arg;
|
|
Sx *sx;
|
|
|
|
va_start(arg, fmt);
|
|
sx = imapvcmdsx0(z, fmt, arg);
|
|
va_end(arg);
|
|
return sx;
|
|
}
|
|
|
|
/*
|
|
* Run a single command on box b. Does redial.
|
|
*/
|
|
static Sx*
|
|
imapvcmdsx(Imap *z, Box *b, char *fmt, va_list arg)
|
|
{
|
|
int tries;
|
|
Sx *sx;
|
|
|
|
tries = 0;
|
|
z->nextbox = b;
|
|
|
|
if(z->fd < 0 || !z->connected){
|
|
reconnect:
|
|
if(!z->autoreconnect)
|
|
return nil;
|
|
if(imapreconnect(z) < 0)
|
|
return nil;
|
|
if(b && z->nextbox == nil) /* box disappeared on reconnect */
|
|
return nil;
|
|
}
|
|
|
|
if(b && b != z->box){
|
|
if(z->box)
|
|
z->box->imapinit = 0;
|
|
z->box = b;
|
|
if((sx=imapcmdsx0(z, "SELECT %Z", b->imapname)) == nil){
|
|
z->box = nil;
|
|
if(tries++ == 0 && (z->fd < 0 || !z->connected))
|
|
goto reconnect;
|
|
return nil;
|
|
}
|
|
freesx(sx);
|
|
}
|
|
|
|
if((sx=imapvcmdsx0(z, fmt, arg)) == nil){
|
|
if(tries++ == 0 && (z->fd < 0 || !z->connected))
|
|
goto reconnect;
|
|
return nil;
|
|
}
|
|
return sx;
|
|
}
|
|
|
|
static int
|
|
imapcmd(Imap *z, Box *b, char *fmt, ...)
|
|
{
|
|
Sx *sx;
|
|
va_list arg;
|
|
|
|
va_start(arg, fmt);
|
|
sx = imapvcmdsx(z, b, fmt, arg);
|
|
va_end(arg);
|
|
if(sx == nil)
|
|
return -1;
|
|
if(sx->nsx < 2 || !isatom(sx->sx[1], "OK")){
|
|
werrstr("%$", sx);
|
|
freesx(sx);
|
|
return -1;
|
|
}
|
|
freesx(sx);
|
|
return 0;
|
|
}
|
|
|
|
static Sx*
|
|
imapcmdsx(Imap *z, Box *b, char *fmt, ...)
|
|
{
|
|
Sx *sx;
|
|
va_list arg;
|
|
|
|
va_start(arg, fmt);
|
|
sx = imapvcmdsx(z, b, fmt, arg);
|
|
va_end(arg);
|
|
return sx;
|
|
}
|
|
|
|
static Sx*
|
|
imapwaitsx(Imap *z)
|
|
{
|
|
Sx *sx;
|
|
|
|
while((sx = zBrdsx(z)) != nil){
|
|
if(chattyimap)
|
|
fprint(2, "<| %#$\n", sx);
|
|
if(sx->nsx >= 1 && sx->sx[0]->type == SxAtom && cistrcmp(sx->sx[0]->data, tag) == 0)
|
|
return sx;
|
|
if(sx->nsx >= 1 && sx->sx[0]->type == SxAtom && strcmp(sx->sx[0]->data, "*") == 0)
|
|
unexpected(z, sx);
|
|
if(sx->type == SxList && sx->nsx == 0){
|
|
freesx(sx);
|
|
break;
|
|
}
|
|
freesx(sx);
|
|
}
|
|
z->connected = 0;
|
|
return nil;
|
|
}
|
|
|
|
/*
|
|
* Imap interface to mail file system.
|
|
*/
|
|
|
|
static void
|
|
_bodyname(char *buf, char *ebuf, Part *p, char *extra)
|
|
{
|
|
if(buf >= ebuf){
|
|
fprint(2, "***** BUFFER TOO SMALL\n");
|
|
return;
|
|
}
|
|
*buf = 0;
|
|
if(p->parent){
|
|
_bodyname(buf, ebuf, p->parent, "");
|
|
buf += strlen(buf);
|
|
seprint(buf, ebuf, ".%d", p->pix+1);
|
|
}
|
|
buf += strlen(buf);
|
|
seprint(buf, ebuf, "%s", extra);
|
|
}
|
|
|
|
static char*
|
|
bodyname(Part *p, char *extra)
|
|
{
|
|
static char buf[256];
|
|
memset(buf, 0, sizeof buf); /* can't see why this is necessary, but it is */
|
|
_bodyname(buf, buf+sizeof buf, p, extra);
|
|
return buf+1; /* buf[0] == '.' */
|
|
}
|
|
|
|
static void
|
|
fetch1(Imap *z, Part *p, char *s)
|
|
{
|
|
qlock(&z->lk);
|
|
imapcmd(z, p->msg->box, "UID FETCH %d BODY[%s]",
|
|
p->msg->imapuid, bodyname(p, s));
|
|
qunlock(&z->lk);
|
|
}
|
|
|
|
void
|
|
imapfetchrawheader(Imap *z, Part *p)
|
|
{
|
|
fetch1(z, p, ".HEADER");
|
|
}
|
|
|
|
void
|
|
imapfetchrawmime(Imap *z, Part *p)
|
|
{
|
|
fetch1(z, p, ".MIME");
|
|
}
|
|
|
|
void
|
|
imapfetchrawbody(Imap *z, Part *p)
|
|
{
|
|
fetch1(z, p, ".TEXT");
|
|
}
|
|
|
|
void
|
|
imapfetchraw(Imap *z, Part *p)
|
|
{
|
|
fetch1(z, p, "");
|
|
}
|
|
|
|
static int
|
|
imaplistcmd(Imap *z, Box *box, char *before, Msg **m, uint nm, char *after)
|
|
{
|
|
int i, r;
|
|
char *cmd;
|
|
Fmt fmt;
|
|
|
|
if(nm == 0)
|
|
return 0;
|
|
|
|
fmtstrinit(&fmt);
|
|
fmtprint(&fmt, "%s ", before);
|
|
for(i=0; i<nm; i++){
|
|
if(i > 0)
|
|
fmtrune(&fmt, ',');
|
|
fmtprint(&fmt, "%ud", m[i]->imapuid);
|
|
}
|
|
fmtprint(&fmt, " %s", after);
|
|
cmd = fmtstrflush(&fmt);
|
|
|
|
r = 0;
|
|
if(imapcmd(z, box, "%s", cmd) < 0)
|
|
r = -1;
|
|
free(cmd);
|
|
return r;
|
|
}
|
|
|
|
int
|
|
imapcopylist(Imap *z, char *nbox, Msg **m, uint nm)
|
|
{
|
|
int rv;
|
|
char *name, *p;
|
|
|
|
if(nm == 0)
|
|
return 0;
|
|
|
|
qlock(&z->lk);
|
|
if(strcmp(nbox, "mbox") == 0)
|
|
name = estrdup("INBOX");
|
|
else{
|
|
p = esmprint("%s%s", z->root, nbox);
|
|
name = esmprint("%Z", p);
|
|
free(p);
|
|
}
|
|
rv = imaplistcmd(z, m[0]->box, "UID COPY", m, nm, name);
|
|
free(name);
|
|
qunlock(&z->lk);
|
|
return rv;
|
|
}
|
|
|
|
int
|
|
imapremovelist(Imap *z, Msg **m, uint nm)
|
|
{
|
|
int rv;
|
|
|
|
if(nm == 0)
|
|
return 0;
|
|
|
|
qlock(&z->lk);
|
|
rv = imaplistcmd(z, m[0]->box, "UID STORE", m, nm, "+FLAGS.SILENT (\\Deleted)");
|
|
/* careful - box might be gone; use z->box instead */
|
|
if(rv == 0 && z->box)
|
|
rv = imapcmd(z, z->box, "EXPUNGE");
|
|
qunlock(&z->lk);
|
|
return rv;
|
|
}
|
|
|
|
int
|
|
imapflaglist(Imap *z, int op, int flag, Msg **m, uint nm)
|
|
{
|
|
char *mod, *s, *sep;
|
|
int i, rv;
|
|
Fmt fmt;
|
|
|
|
if(op > 0)
|
|
mod = "+";
|
|
else if(op == 0)
|
|
mod = "";
|
|
else
|
|
mod = "-";
|
|
|
|
fmtstrinit(&fmt);
|
|
fmtprint(&fmt, "%sFLAGS (", mod);
|
|
sep = "";
|
|
for(i=0; i<nelem(flagstab); i++){
|
|
if(flagstab[i].flag & flag){
|
|
fmtprint(&fmt, "%s%s", sep, flagstab[i].name);
|
|
sep = " ";
|
|
}
|
|
}
|
|
fmtprint(&fmt, ")");
|
|
s = fmtstrflush(&fmt);
|
|
|
|
qlock(&z->lk);
|
|
rv = imaplistcmd(z, m[0]->box, "UID STORE", m, nm, s);
|
|
qunlock(&z->lk);
|
|
free(s);
|
|
return rv;
|
|
}
|
|
|
|
int
|
|
imapsearchbox(Imap *z, Box *b, char *search, Msg ***mm)
|
|
{
|
|
uint *uid;
|
|
int i, nuid;
|
|
Msg **m;
|
|
int nm;
|
|
|
|
qlock(&z->lk);
|
|
if(imapcmd(z, b, "UID SEARCH CHARSET UTF-8 TEXT %Z", search) < 0){
|
|
qunlock(&z->lk);
|
|
return -1;
|
|
}
|
|
|
|
uid = z->uid;
|
|
nuid = z->nuid;
|
|
z->uid = nil;
|
|
z->nuid = 0;
|
|
qunlock(&z->lk);
|
|
|
|
m = emalloc(nuid*sizeof m[0]);
|
|
nm = 0;
|
|
for(i=0; i<nuid; i++)
|
|
if((m[nm] = msgbyimapuid(b, uid[i], 0)) != nil)
|
|
nm++;
|
|
*mm = m;
|
|
free(uid);
|
|
return nm;
|
|
}
|
|
|
|
void
|
|
imapcheckbox(Imap *z, Box *b)
|
|
{
|
|
if(b == nil)
|
|
return;
|
|
qlock(&z->lk);
|
|
checkbox(z, b);
|
|
qunlock(&z->lk);
|
|
}
|
|
|
|
/*
|
|
* Imap utility routines
|
|
*/
|
|
static long
|
|
_ioimapdial(va_list *arg)
|
|
{
|
|
char *server;
|
|
int mode;
|
|
|
|
server = va_arg(*arg, char*);
|
|
mode = va_arg(*arg, int);
|
|
return imapdial(server, mode);
|
|
}
|
|
static int
|
|
ioimapdial(Ioproc *io, char *server, int mode)
|
|
{
|
|
return iocall(io, _ioimapdial, server, mode);
|
|
}
|
|
|
|
static long
|
|
_ioBrdsx(va_list *arg)
|
|
{
|
|
Biobuf *b;
|
|
Sx **sx;
|
|
|
|
b = va_arg(*arg, Biobuf*);
|
|
sx = va_arg(*arg, Sx**);
|
|
*sx = Brdsx(b);
|
|
if((*sx) && (*sx)->type == SxList && (*sx)->nsx == 0){
|
|
freesx(*sx);
|
|
*sx = nil;
|
|
}
|
|
return 0;
|
|
}
|
|
static Sx*
|
|
ioBrdsx(Ioproc *io, Biobuf *b)
|
|
{
|
|
Sx *sx;
|
|
|
|
iocall(io, _ioBrdsx, b, &sx);
|
|
return sx;
|
|
}
|
|
|
|
static Sx*
|
|
zBrdsx(Imap *z)
|
|
{
|
|
if(z->ticks && --z->ticks==0){
|
|
close(z->fd);
|
|
z->fd = -1;
|
|
return nil;
|
|
}
|
|
return ioBrdsx(z->io, &z->b);
|
|
}
|
|
|
|
static int
|
|
imapdial(char *server, int mode)
|
|
{
|
|
int p[2];
|
|
int fd[3];
|
|
char *tmp;
|
|
|
|
switch(mode){
|
|
default:
|
|
case Unencrypted:
|
|
return dial(netmkaddr(server, "tcp", "143"), nil, nil, nil);
|
|
|
|
case Starttls:
|
|
werrstr("starttls not supported");
|
|
return -1;
|
|
|
|
case Tls:
|
|
if(pipe(p) < 0)
|
|
return -1;
|
|
fd[0] = dup(p[0], -1);
|
|
fd[1] = dup(p[0], -1);
|
|
fd[2] = dup(2, -1);
|
|
tmp = esmprint("%s:993", server);
|
|
if(threadspawnl(fd, "tlsclient", "tlsclient", tmp, nil) < 0
|
|
&& threadspawnl(fd, "/usr/sbin/stunnel", "stunnel", "-c", "-r", tmp, nil) < 0
|
|
&& threadspawnl(fd, "/usr/bin/stunnel", "stunnel", "-c", "-r", tmp, nil) < 0){
|
|
free(tmp);
|
|
close(p[0]);
|
|
close(p[1]);
|
|
close(fd[0]);
|
|
close(fd[1]);
|
|
close(fd[2]);
|
|
return -1;
|
|
}
|
|
free(tmp);
|
|
close(p[0]);
|
|
return p[1];
|
|
|
|
case Cmd:
|
|
if(pipe(p) < 0)
|
|
return -1;
|
|
fd[0] = dup(p[0], -1);
|
|
fd[1] = dup(p[0], -1);
|
|
fd[2] = dup(2, -1);
|
|
if(threadspawnl(fd, "/usr/local/plan9/bin/rc", "rc", "-c", server, nil) < 0){
|
|
close(p[0]);
|
|
close(p[1]);
|
|
close(fd[0]);
|
|
close(fd[1]);
|
|
close(fd[2]);
|
|
return -1;
|
|
}
|
|
close(p[0]);
|
|
return p[1];
|
|
}
|
|
}
|
|
|
|
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
|
|
imapquote(Fmt *f)
|
|
{
|
|
char *s, *t;
|
|
int w, quotes;
|
|
Rune r;
|
|
|
|
s = va_arg(f->args, char*);
|
|
if(s == nil || *s == '\0')
|
|
return fmtstrcpy(f, "\"\"");
|
|
|
|
quotes = 0;
|
|
if(f->flags&FmtSharp)
|
|
quotes = 1;
|
|
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
|
|
fmttype(char c)
|
|
{
|
|
switch(c){
|
|
case 'A':
|
|
return SxAtom;
|
|
case 'L':
|
|
return SxList;
|
|
case 'N':
|
|
return SxNumber;
|
|
case 'S':
|
|
return SxString;
|
|
default:
|
|
return -1;
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Check S expression against format string.
|
|
*/
|
|
static int
|
|
sxmatch(Sx *sx, char *fmt)
|
|
{
|
|
int i;
|
|
|
|
for(i=0; fmt[i]; i++){
|
|
if(fmt[i] == '*')
|
|
fmt--; /* like i-- but better */
|
|
if(i == sx->nsx && fmt[i+1] == '*')
|
|
return 1;
|
|
if(i >= sx->nsx)
|
|
return 0;
|
|
if(sx->sx[i] == nil)
|
|
return 0;
|
|
if(sx->sx[i]->type == SxAtom && strcmp(sx->sx[i]->data, "NIL") == 0){
|
|
if(fmt[i] == 'L'){
|
|
free(sx->sx[i]->data);
|
|
sx->sx[i]->data = nil;
|
|
sx->sx[i]->type = SxList;
|
|
sx->sx[i]->sx = nil;
|
|
sx->sx[i]->nsx = 0;
|
|
}
|
|
else if(fmt[i] == 'S'){
|
|
free(sx->sx[i]->data);
|
|
sx->sx[i]->data = nil;
|
|
sx->sx[i]->type = SxString;
|
|
}
|
|
}
|
|
if(sx->sx[i]->type == SxAtom && fmt[i]=='S')
|
|
sx->sx[i]->type = SxString;
|
|
if(sx->sx[i]->type != fmttype(fmt[i])){
|
|
fprint(2, "sxmatch: %$ not %c\n", sx->sx[i], fmt[i]);
|
|
return 0;
|
|
}
|
|
}
|
|
if(i != sx->nsx)
|
|
return 0;
|
|
return 1;
|
|
}
|
|
|
|
/*
|
|
* Check string against format string.
|
|
*/
|
|
static int
|
|
stringmatch(char *fmt, char *s)
|
|
{
|
|
for(; *fmt && *s; fmt++, s++){
|
|
switch(*fmt){
|
|
case '0':
|
|
if(*s == ' ')
|
|
break;
|
|
/* fall through */
|
|
case '1':
|
|
if(*s < '0' || *s > '9')
|
|
return 0;
|
|
break;
|
|
case 'A':
|
|
if(*s < 'A' || *s > 'Z')
|
|
return 0;
|
|
break;
|
|
case 'a':
|
|
if(*s < 'a' || *s > 'z')
|
|
return 0;
|
|
break;
|
|
case '+':
|
|
if(*s != '-' && *s != '+')
|
|
return 0;
|
|
break;
|
|
default:
|
|
if(*s != *fmt)
|
|
return 0;
|
|
break;
|
|
}
|
|
}
|
|
if(*fmt || *s)
|
|
return 0;
|
|
return 1;
|
|
}
|
|
|
|
/*
|
|
* Parse simple S expressions and IMAP elements.
|
|
*/
|
|
static int
|
|
isatom(Sx *v, char *name)
|
|
{
|
|
int n;
|
|
|
|
if(v == nil || v->type != SxAtom)
|
|
return 0;
|
|
n = strlen(name);
|
|
if(cistrncmp(v->data, name, n) == 0)
|
|
if(v->data[n] == 0 || (n>0 && v->data[n-1] == '['))
|
|
return 1;
|
|
return 0;
|
|
}
|
|
|
|
static int
|
|
isstring(Sx *sx)
|
|
{
|
|
if(sx->type == SxAtom)
|
|
sx->type = SxString;
|
|
return sx->type == SxString;
|
|
}
|
|
|
|
static int
|
|
isnumber(Sx *sx)
|
|
{
|
|
return sx->type == SxNumber;
|
|
}
|
|
|
|
static int
|
|
isnil(Sx *v)
|
|
{
|
|
return v == nil ||
|
|
(v->type==SxList && v->nsx == 0) ||
|
|
(v->type==SxAtom && strcmp(v->data, "NIL") == 0);
|
|
}
|
|
|
|
static int
|
|
islist(Sx *v)
|
|
{
|
|
return isnil(v) || v->type==SxList;
|
|
}
|
|
|
|
static uint
|
|
parseflags(Sx *v)
|
|
{
|
|
int f, i, j;
|
|
|
|
if(v->type != SxList){
|
|
warn("malformed flags: %$", v);
|
|
return 0;
|
|
}
|
|
f = 0;
|
|
for(i=0; i<v->nsx; i++){
|
|
if(v->sx[i]->type != SxAtom)
|
|
continue;
|
|
for(j=0; j<nelem(flagstab); j++)
|
|
if(cistrcmp(v->sx[i]->data, flagstab[j].name) == 0)
|
|
f |= flagstab[j].flag;
|
|
}
|
|
return f;
|
|
}
|
|
|
|
static char months[] = "JanFebMarAprMayJunJulAugSepOctNovDec";
|
|
static int
|
|
parsemon(char *s)
|
|
{
|
|
int i;
|
|
|
|
for(i=0; months[i]; i+=3)
|
|
if(memcmp(s, months+i, 3) == 0)
|
|
return i/3;
|
|
return -1;
|
|
}
|
|
|
|
static uint
|
|
parsedate(Sx *v)
|
|
{
|
|
Tm tm;
|
|
uint t;
|
|
int delta;
|
|
char *p;
|
|
|
|
if(v->type != SxString || !stringmatch("01-Aaa-1111 01:11:11 +1111", v->data)){
|
|
bad:
|
|
warn("bad date: %$", v);
|
|
return 0;
|
|
}
|
|
|
|
/* cannot use atoi because 09 is malformed octal! */
|
|
memset(&tm, 0, sizeof tm);
|
|
p = v->data;
|
|
tm.mday = strtol(p, 0, 10);
|
|
tm.mon = parsemon(p+3);
|
|
if(tm.mon == -1)
|
|
goto bad;
|
|
tm.year = strtol(p+7, 0, 10) - 1900;
|
|
tm.hour = strtol(p+12, 0, 10);
|
|
tm.min = strtol(p+15, 0, 10);
|
|
tm.sec = strtol(p+18, 0, 10);
|
|
strcpy(tm.zone, "GMT");
|
|
|
|
t = tm2sec(&tm);
|
|
delta = ((p[22]-'0')*10+p[23]-'0')*3600 + ((p[24]-'0')*10+p[25]-'0')*60;
|
|
if(p[21] == '-')
|
|
delta = -delta;
|
|
|
|
t -= delta;
|
|
return t;
|
|
}
|
|
|
|
static uint
|
|
parsenumber(Sx *v)
|
|
{
|
|
if(v->type != SxNumber)
|
|
return 0;
|
|
return v->number;
|
|
}
|
|
|
|
static void
|
|
hash(DigestState *ds, char *tag, char *val)
|
|
{
|
|
if(val == nil)
|
|
val = "";
|
|
md5((uchar*)tag, strlen(tag)+1, nil, ds);
|
|
md5((uchar*)val, strlen(val)+1, nil, ds);
|
|
}
|
|
|
|
static Hdr*
|
|
parseenvelope(Sx *v)
|
|
{
|
|
Hdr *hdr;
|
|
uchar digest[16];
|
|
DigestState ds;
|
|
|
|
if(v->type != SxList || !sxmatch(v, "SSLLLLLLSS")){
|
|
warn("bad envelope: %$", v);
|
|
return nil;
|
|
}
|
|
|
|
hdr = emalloc(sizeof *hdr);
|
|
hdr->date = nstring(v->sx[0]);
|
|
hdr->subject = unrfc2047(nstring(v->sx[1]));
|
|
hdr->from = copyaddrs(v->sx[2]);
|
|
hdr->sender = copyaddrs(v->sx[3]);
|
|
hdr->replyto = copyaddrs(v->sx[4]);
|
|
hdr->to = copyaddrs(v->sx[5]);
|
|
hdr->cc = copyaddrs(v->sx[6]);
|
|
hdr->bcc = copyaddrs(v->sx[7]);
|
|
hdr->inreplyto = unrfc2047(nstring(v->sx[8]));
|
|
hdr->messageid = unrfc2047(nstring(v->sx[9]));
|
|
|
|
memset(&ds, 0, sizeof ds);
|
|
hash(&ds, "date", hdr->date);
|
|
hash(&ds, "subject", hdr->subject);
|
|
hash(&ds, "from", hdr->from);
|
|
hash(&ds, "sender", hdr->sender);
|
|
hash(&ds, "replyto", hdr->replyto);
|
|
hash(&ds, "to", hdr->to);
|
|
hash(&ds, "cc", hdr->cc);
|
|
hash(&ds, "bcc", hdr->bcc);
|
|
hash(&ds, "inreplyto", hdr->inreplyto);
|
|
hash(&ds, "messageid", hdr->messageid);
|
|
md5(0, 0, digest, &ds);
|
|
hdr->digest = esmprint("%.16H", digest);
|
|
|
|
return hdr;
|
|
}
|
|
|
|
static void
|
|
strlwr(char *s)
|
|
{
|
|
char *t;
|
|
|
|
if(s == nil)
|
|
return;
|
|
for(t=s; *t; t++)
|
|
if('A' <= *t && *t <= 'Z')
|
|
*t += 'a' - 'A';
|
|
}
|
|
|
|
static void
|
|
nocr(char *s)
|
|
{
|
|
char *r, *w;
|
|
|
|
if(s == nil)
|
|
return;
|
|
for(r=w=s; *r; r++)
|
|
if(*r != '\r')
|
|
*w++ = *r;
|
|
*w = 0;
|
|
}
|
|
|
|
/*
|
|
* substitute all occurrences of a with b in s.
|
|
*/
|
|
static char*
|
|
gsub(char *s, char *a, char *b)
|
|
{
|
|
char *p, *t, *w, *last;
|
|
int n;
|
|
|
|
n = 0;
|
|
for(p=s; (p=strstr(p, a)) != nil; p+=strlen(a))
|
|
n++;
|
|
if(n == 0)
|
|
return s;
|
|
t = emalloc(strlen(s)+n*strlen(b)+1);
|
|
w = t;
|
|
for(p=s; last=p, (p=strstr(p, a)) != nil; p+=strlen(a)){
|
|
memmove(w, last, p-last);
|
|
w += p-last;
|
|
memmove(w, b, strlen(b));
|
|
w += strlen(b);
|
|
}
|
|
strcpy(w, last);
|
|
free(s);
|
|
return t;
|
|
}
|
|
|
|
/*
|
|
* Table-driven IMAP "unexpected response" parser.
|
|
* All the interesting data is in the unexpected responses.
|
|
*/
|
|
static void xlist(Imap*, Sx*);
|
|
static void xrecent(Imap*, Sx*);
|
|
static void xexists(Imap*, Sx*);
|
|
static void xok(Imap*, Sx*);
|
|
static void xflags(Imap*, Sx*);
|
|
static void xfetch(Imap*, Sx*);
|
|
static void xexpunge(Imap*, Sx*);
|
|
static void xbye(Imap*, Sx*);
|
|
static void xsearch(Imap*, Sx*);
|
|
|
|
static struct {
|
|
int num;
|
|
char *name;
|
|
char *fmt;
|
|
void (*fn)(Imap*, Sx*);
|
|
} unextab[] = {
|
|
0, "BYE", nil, xbye,
|
|
0, "FLAGS", "AAL", xflags,
|
|
0, "LIST", "AALSS", xlist,
|
|
0, "OK", nil, xok,
|
|
0, "SEARCH", "AAN*", xsearch,
|
|
|
|
1, "EXISTS", "ANA", xexists,
|
|
1, "EXPUNGE", "ANA", xexpunge,
|
|
1, "FETCH", "ANAL", xfetch,
|
|
1, "RECENT", "ANA", xrecent
|
|
};
|
|
|
|
static void
|
|
unexpected(Imap *z, Sx *sx)
|
|
{
|
|
int i, num;
|
|
char *name;
|
|
|
|
if(sx->nsx >= 3 && sx->sx[1]->type == SxNumber && sx->sx[2]->type == SxAtom){
|
|
num = 1;
|
|
name = sx->sx[2]->data;
|
|
}else if(sx->nsx >= 2 && sx->sx[1]->type == SxAtom){
|
|
num = 0;
|
|
name = sx->sx[1]->data;
|
|
}else
|
|
return;
|
|
|
|
for(i=0; i<nelem(unextab); i++){
|
|
if(unextab[i].num == num && cistrcmp(unextab[i].name, name) == 0){
|
|
if(unextab[i].fmt && !sxmatch(sx, unextab[i].fmt)){
|
|
warn("malformed %s: %$", name, sx);
|
|
continue;
|
|
}
|
|
unextab[i].fn(z, sx);
|
|
}
|
|
}
|
|
}
|
|
|
|
static int
|
|
alldollars(char *s)
|
|
{
|
|
for(; *s; s++)
|
|
if(*s != '$')
|
|
return 0;
|
|
return 1;
|
|
}
|
|
|
|
static void
|
|
xlist(Imap *z, Sx *sx)
|
|
{
|
|
int inbox;
|
|
char *s, *t;
|
|
Box *box;
|
|
|
|
s = estrdup(sx->sx[4]->data);
|
|
if(sx->sx[3]->data && strcmp(sx->sx[3]->data, "/") != 0){
|
|
s = gsub(s, "/", "_");
|
|
s = gsub(s, sx->sx[3]->data, "/");
|
|
}
|
|
|
|
/*
|
|
* INBOX is the special imap name for the main mailbox.
|
|
* All other mailbox names have the root prefix removed, if applicable.
|
|
*/
|
|
inbox = 0;
|
|
if(cistrcmp(s, "INBOX") == 0){
|
|
inbox = 1;
|
|
free(s);
|
|
s = estrdup("mbox");
|
|
} else if(z->root && strstr(s, z->root) == s) {
|
|
t = estrdup(s+strlen(z->root));
|
|
free(s);
|
|
s = t;
|
|
}
|
|
|
|
/*
|
|
* Plan 9 calls the main mailbox mbox.
|
|
* Rename any existing mbox by appending a $.
|
|
*/
|
|
if(!inbox && strncmp(s, "mbox", 4) == 0 && alldollars(s+4)){
|
|
t = emalloc(strlen(s)+2);
|
|
strcpy(t, s);
|
|
strcat(t, "$");
|
|
free(s);
|
|
s = t;
|
|
}
|
|
|
|
box = boxcreate(s);
|
|
if(box == nil)
|
|
return;
|
|
box->imapname = estrdup(sx->sx[4]->data);
|
|
if(inbox)
|
|
z->inbox = box;
|
|
box->mark = 0;
|
|
box->flags = parseflags(sx->sx[2]);
|
|
}
|
|
|
|
static void
|
|
xrecent(Imap *z, Sx *sx)
|
|
{
|
|
if(z->box)
|
|
z->box->recent = sx->sx[1]->number;
|
|
}
|
|
|
|
static void
|
|
xexists(Imap *z, Sx *sx)
|
|
{
|
|
if(z->box){
|
|
z->box->exists = sx->sx[1]->number;
|
|
if(z->box->exists < z->box->maxseen)
|
|
z->box->maxseen = z->box->exists;
|
|
}
|
|
}
|
|
|
|
static void
|
|
xflags(Imap *z, Sx *sx)
|
|
{
|
|
/*
|
|
* This response contains in sx->sx[2] the list of flags
|
|
* that can be validly attached to messages in z->box.
|
|
* We don't have any use for this list, since we
|
|
* use only the standard flags.
|
|
*/
|
|
}
|
|
|
|
static void
|
|
xbye(Imap *z, Sx *sx)
|
|
{
|
|
close(z->fd);
|
|
z->fd = -1;
|
|
z->connected = 0;
|
|
}
|
|
|
|
static void
|
|
xexpunge(Imap *z, Sx *sx)
|
|
{
|
|
int i, n;
|
|
Box *b;
|
|
|
|
if((b=z->box) == nil)
|
|
return;
|
|
n = sx->sx[1]->number;
|
|
for(i=0; i<b->nmsg; i++){
|
|
if(b->msg[i]->imapid == n){
|
|
msgplumb(b->msg[i], 1);
|
|
msgfree(b->msg[i]);
|
|
b->nmsg--;
|
|
memmove(b->msg+i, b->msg+i+1, (b->nmsg-i)*sizeof b->msg[0]);
|
|
i--;
|
|
b->maxseen--;
|
|
b->exists--;
|
|
continue;
|
|
}
|
|
if(b->msg[i]->imapid > n)
|
|
b->msg[i]->imapid--;
|
|
b->msg[i]->ix = i;
|
|
}
|
|
}
|
|
|
|
static void
|
|
xsearch(Imap *z, Sx *sx)
|
|
{
|
|
int i;
|
|
|
|
free(z->uid);
|
|
z->uid = emalloc((sx->nsx-2)*sizeof z->uid[0]);
|
|
z->nuid = sx->nsx-2;
|
|
for(i=0; i<z->nuid; i++)
|
|
z->uid[i] = sx->sx[i+2]->number;
|
|
}
|
|
|
|
/*
|
|
* Table-driven FETCH message info parser.
|
|
*/
|
|
static void xmsgflags(Msg*, Sx*, Sx*);
|
|
static void xmsgdate(Msg*, Sx*, Sx*);
|
|
static void xmsgrfc822size(Msg*, Sx*, Sx*);
|
|
static void xmsgenvelope(Msg*, Sx*, Sx*);
|
|
static void xmsgbody(Msg*, Sx*, Sx*);
|
|
static void xmsgbodydata(Msg*, Sx*, Sx*);
|
|
|
|
static struct {
|
|
char *name;
|
|
void (*fn)(Msg*, Sx*, Sx*);
|
|
} msgtab[] = {
|
|
"FLAGS", xmsgflags,
|
|
"INTERNALDATE", xmsgdate,
|
|
"RFC822.SIZE", xmsgrfc822size,
|
|
"ENVELOPE", xmsgenvelope,
|
|
"BODY", xmsgbody,
|
|
"BODY[", xmsgbodydata
|
|
};
|
|
|
|
static void
|
|
xfetch(Imap *z, Sx *sx)
|
|
{
|
|
int i, j, n, uid;
|
|
Msg *msg;
|
|
|
|
if(z->box == nil){
|
|
warn("FETCH but no open box: %$", sx);
|
|
return;
|
|
}
|
|
|
|
/* * 152 FETCH (UID 185 FLAGS () ...) */
|
|
if(sx->sx[3]->nsx%2){
|
|
warn("malformed FETCH: %$", sx);
|
|
return;
|
|
}
|
|
|
|
n = sx->sx[1]->number;
|
|
sx = sx->sx[3];
|
|
for(i=0; i<sx->nsx; i+=2){
|
|
if(isatom(sx->sx[i], "UID")){
|
|
if(sx->sx[i+1]->type == SxNumber){
|
|
uid = sx->sx[i+1]->number;
|
|
goto haveuid;
|
|
}
|
|
}
|
|
}
|
|
/* This happens: too bad.
|
|
warn("FETCH without UID: %$", sx);
|
|
*/
|
|
return;
|
|
|
|
haveuid:
|
|
msg = msgbyimapuid(z->box, uid, 1);
|
|
if(msg->imapid && msg->imapid != n)
|
|
warn("msg id mismatch: want %d have %d", msg->id, n);
|
|
msg->imapid = n;
|
|
for(i=0; i<sx->nsx; i+=2){
|
|
for(j=0; j<nelem(msgtab); j++)
|
|
if(isatom(sx->sx[i], msgtab[j].name))
|
|
msgtab[j].fn(msg, sx->sx[i], sx->sx[i+1]);
|
|
}
|
|
}
|
|
|
|
static void
|
|
xmsgflags(Msg *msg, Sx *k, Sx *v)
|
|
{
|
|
USED(k);
|
|
msg->flags = parseflags(v);
|
|
}
|
|
|
|
static void
|
|
xmsgdate(Msg *msg, Sx *k, Sx *v)
|
|
{
|
|
USED(k);
|
|
msg->date = parsedate(v);
|
|
}
|
|
|
|
static void
|
|
xmsgrfc822size(Msg *msg, Sx *k, Sx *v)
|
|
{
|
|
USED(k);
|
|
msg->size = parsenumber(v);
|
|
}
|
|
|
|
static char*
|
|
nstring(Sx *v)
|
|
{
|
|
char *p;
|
|
|
|
if(isnil(v))
|
|
return estrdup("");
|
|
p = v->data;
|
|
v->data = nil;
|
|
return p;
|
|
}
|
|
|
|
static char*
|
|
copyaddrs(Sx *v)
|
|
{
|
|
char *s, *sep;
|
|
char *name, *email, *host, *mbox;
|
|
int i;
|
|
Fmt fmt;
|
|
|
|
if(v->nsx == 0)
|
|
return nil;
|
|
|
|
fmtstrinit(&fmt);
|
|
sep = "";
|
|
for(i=0; i<v->nsx; i++){
|
|
if(!sxmatch(v->sx[i], "SSSS"))
|
|
warn("bad address: %$", v->sx[i]);
|
|
name = unrfc2047(nstring(v->sx[i]->sx[0]));
|
|
/* ignore sx[1] - route */
|
|
mbox = unrfc2047(nstring(v->sx[i]->sx[2]));
|
|
host = unrfc2047(nstring(v->sx[i]->sx[3]));
|
|
if(mbox == nil || host == nil){ /* rfc822 group syntax */
|
|
free(name);
|
|
free(mbox);
|
|
free(host);
|
|
continue;
|
|
}
|
|
email = esmprint("%s@%s", mbox, host);
|
|
free(mbox);
|
|
free(host);
|
|
fmtprint(&fmt, "%s%q %q", sep, name ? name : "", email ? email : "");
|
|
free(name);
|
|
free(email);
|
|
sep = " ";
|
|
}
|
|
s = fmtstrflush(&fmt);
|
|
if(s == nil)
|
|
sysfatal("out of memory");
|
|
return s;
|
|
}
|
|
|
|
static void
|
|
xmsgenvelope(Msg *msg, Sx *k, Sx *v)
|
|
{
|
|
hdrfree(msg->part[0]->hdr);
|
|
msg->part[0]->hdr = parseenvelope(v);
|
|
msgplumb(msg, 0);
|
|
}
|
|
|
|
static struct {
|
|
char *name;
|
|
int offset;
|
|
} paramtab[] = {
|
|
"charset", offsetof(Part, charset),
|
|
"name", offsetof(Part, filename)
|
|
};
|
|
|
|
static void
|
|
parseparams(Part *part, Sx *v)
|
|
{
|
|
int i, j;
|
|
char *s, *t, **p;
|
|
|
|
if(isnil(v))
|
|
return;
|
|
if(v->nsx%2){
|
|
warn("bad message params: %$", v);
|
|
return;
|
|
}
|
|
for(i=0; i<v->nsx; i+=2){
|
|
s = nstring(v->sx[i]);
|
|
t = nstring(v->sx[i+1]);
|
|
for(j=0; j<nelem(paramtab); j++){
|
|
if(cistrcmp(paramtab[j].name, s) == 0){
|
|
p = (char**)((char*)part+paramtab[j].offset);
|
|
free(*p);
|
|
*p = t;
|
|
t = nil;
|
|
break;
|
|
}
|
|
}
|
|
free(s);
|
|
free(t);
|
|
}
|
|
}
|
|
|
|
static void
|
|
parsestructure(Part *part, Sx *v)
|
|
{
|
|
int i;
|
|
char *s, *t;
|
|
|
|
if(isnil(v))
|
|
return;
|
|
if(v->type != SxList){
|
|
bad:
|
|
warn("bad structure: %$", v);
|
|
return;
|
|
}
|
|
if(islist(v->sx[0])){
|
|
/* multipart */
|
|
for(i=0; i<v->nsx && islist(v->sx[i]); i++)
|
|
parsestructure(partcreate(part->msg, part), v->sx[i]);
|
|
free(part->type);
|
|
if(i != v->nsx-1 || !isstring(v->sx[i])){
|
|
warn("bad multipart structure: %$", v);
|
|
part->type = estrdup("multipart/mixed");
|
|
return;
|
|
}
|
|
s = nstring(v->sx[i]);
|
|
strlwr(s);
|
|
part->type = esmprint("multipart/%s", s);
|
|
free(s);
|
|
return;
|
|
}
|
|
/* single part */
|
|
if(!isstring(v->sx[0]) || v->nsx < 2)
|
|
goto bad;
|
|
s = nstring(v->sx[0]);
|
|
t = nstring(v->sx[1]);
|
|
strlwr(s);
|
|
strlwr(t);
|
|
free(part->type);
|
|
part->type = esmprint("%s/%s", s, t);
|
|
if(v->nsx < 7 || !islist(v->sx[2]) || !isstring(v->sx[3])
|
|
|| !isstring(v->sx[4]) || !isstring(v->sx[5]) || !isnumber(v->sx[6]))
|
|
goto bad;
|
|
parseparams(part, v->sx[2]);
|
|
part->idstr = nstring(v->sx[3]);
|
|
part->desc = nstring(v->sx[4]);
|
|
part->encoding = nstring(v->sx[5]);
|
|
part->size = v->sx[6]->number;
|
|
if(strcmp(s, "message") == 0 && strcmp(t, "rfc822") == 0){
|
|
if(v->nsx < 10 || !islist(v->sx[7]) || !islist(v->sx[8]) || !isnumber(v->sx[9]))
|
|
goto bad;
|
|
part->hdr = parseenvelope(v->sx[7]);
|
|
parsestructure(partcreate(part->msg, part), v->sx[8]);
|
|
part->lines = v->sx[9]->number;
|
|
}
|
|
if(strcmp(s, "text") == 0){
|
|
if(v->nsx < 8 || !isnumber(v->sx[7]))
|
|
goto bad;
|
|
part->lines = v->sx[7]->number;
|
|
}
|
|
}
|
|
|
|
static void
|
|
xmsgbody(Msg *msg, Sx *k, Sx *v)
|
|
{
|
|
if(v->type != SxList){
|
|
warn("bad body: %$", v);
|
|
return;
|
|
}
|
|
/*
|
|
* To follow the structure exactly we should
|
|
* be doing this to partcreate(msg, msg->part[0]),
|
|
* and we should leave msg->part[0] with type message/rfc822,
|
|
* but the extra layer is redundant - what else would be in a mailbox?
|
|
*/
|
|
parsestructure(msg->part[0], v);
|
|
if(msg->box->maxseen < msg->imapid)
|
|
msg->box->maxseen = msg->imapid;
|
|
if(msg->imapuid >= msg->box->uidnext)
|
|
msg->box->uidnext = msg->imapuid+1;
|
|
}
|
|
|
|
static void
|
|
xmsgbodydata(Msg *msg, Sx *k, Sx *v)
|
|
{
|
|
int i;
|
|
char *name, *p;
|
|
Part *part;
|
|
|
|
name = k->data;
|
|
name += 5; /* body[ */
|
|
p = strchr(name, ']');
|
|
if(p)
|
|
*p = 0;
|
|
|
|
/* now name is something like 1 or 3.2.MIME - walk down parts from root */
|
|
part = msg->part[0];
|
|
|
|
|
|
while('1' <= name[0] && name[0] <= '9'){
|
|
i = strtol(name, &p, 10);
|
|
if(*p == '.')
|
|
p++;
|
|
else if(*p != 0){
|
|
warn("bad body name: %$", k);
|
|
return;
|
|
}
|
|
if((part = subpart(part, i-1)) == nil){
|
|
warn("unknown body part: %$", k);
|
|
return;
|
|
}
|
|
name = p;
|
|
}
|
|
|
|
|
|
if(cistrcmp(name, "") == 0){
|
|
free(part->raw);
|
|
part->raw = nstring(v);
|
|
nocr(part->raw);
|
|
}else if(cistrcmp(name, "HEADER") == 0){
|
|
free(part->rawheader);
|
|
part->rawheader = nstring(v);
|
|
nocr(part->rawheader);
|
|
}else if(cistrcmp(name, "MIME") == 0){
|
|
free(part->mimeheader);
|
|
part->mimeheader = nstring(v);
|
|
nocr(part->mimeheader);
|
|
}else if(cistrcmp(name, "TEXT") == 0){
|
|
free(part->rawbody);
|
|
part->rawbody = nstring(v);
|
|
nocr(part->rawbody);
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Table-driven OK info parser.
|
|
*/
|
|
static void xokuidvalidity(Imap*, Sx*);
|
|
static void xokpermflags(Imap*, Sx*);
|
|
static void xokunseen(Imap*, Sx*);
|
|
static void xokreadwrite(Imap*, Sx*);
|
|
static void xokreadonly(Imap*, Sx*);
|
|
|
|
struct {
|
|
char *name;
|
|
char fmt;
|
|
void (*fn)(Imap*, Sx*);
|
|
} oktab[] = {
|
|
"UIDVALIDITY", 'N', xokuidvalidity,
|
|
"PERMANENTFLAGS", 'L', xokpermflags,
|
|
"UNSEEN", 'N', xokunseen,
|
|
"READ-WRITE", 0, xokreadwrite,
|
|
"READ-ONLY", 0, xokreadonly
|
|
};
|
|
|
|
static void
|
|
xok(Imap *z, Sx *sx)
|
|
{
|
|
int i;
|
|
char *name;
|
|
Sx *arg;
|
|
|
|
if(sx->nsx >= 4 && sx->sx[2]->type == SxAtom && sx->sx[2]->data[0] == '['){
|
|
if(sx->sx[3]->type == SxAtom && sx->sx[3]->data[0] == ']')
|
|
arg = nil;
|
|
else if(sx->sx[4]->type == SxAtom && sx->sx[4]->data[0] == ']')
|
|
arg = sx->sx[3];
|
|
else{
|
|
warn("cannot parse OK: %$", sx);
|
|
return;
|
|
}
|
|
name = sx->sx[2]->data+1;
|
|
for(i=0; i<nelem(oktab); i++){
|
|
if(cistrcmp(name, oktab[i].name) == 0){
|
|
if(oktab[i].fmt && (arg==nil || arg->type != fmttype(oktab[i].fmt))){
|
|
warn("malformed %s: %$", name, arg);
|
|
continue;
|
|
}
|
|
oktab[i].fn(z, arg);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
static void
|
|
xokuidvalidity(Imap *z, Sx *sx)
|
|
{
|
|
int i;
|
|
Box *b;
|
|
|
|
if((b=z->box) == nil)
|
|
return;
|
|
if(b->validity != sx->number){
|
|
b->validity = sx->number;
|
|
b->uidnext = 1;
|
|
for(i=0; i<b->nmsg; i++)
|
|
msgfree(b->msg[i]);
|
|
free(b->msg);
|
|
b->msg = nil;
|
|
b->nmsg = 0;
|
|
}
|
|
}
|
|
|
|
static void
|
|
xokpermflags(Imap *z, Sx *sx)
|
|
{
|
|
/* z->permflags = parseflags(sx); */
|
|
}
|
|
|
|
static void
|
|
xokunseen(Imap *z, Sx *sx)
|
|
{
|
|
/* z->unseen = sx->number; */
|
|
}
|
|
|
|
static void
|
|
xokreadwrite(Imap *z, Sx *sx)
|
|
{
|
|
/* z->boxmode = ORDWR; */
|
|
}
|
|
|
|
static void
|
|
xokreadonly(Imap *z, Sx *sx)
|
|
{
|
|
/* z->boxmode = OREAD; */
|
|
}
|
|
|