#pragma prototyped
#include "mailx.h"
#if _PACKAGE_ast
#include <css.h>
#include <tm.h>
#include <vmalloc.h>
#define imapfree(sp,op) ((op)->state=(op)->retain=0)
#define IMAP ((Imap_t*)state.msg.imap.state)
#define IMAP_VERSION 4
#define IMAP_REVISION 1
#define IMAP_data 1
#define IMAP_list 2
#define IMAP_name 3
#define IMAP_number 4
#define IMAP_string 5
struct Imaparg_s; typedef struct Imaparg_s Imaparg_t;
struct Imapblock_s; typedef struct Imapblock_s Imapblock_t;
struct Imappart_s; typedef struct Imappart_s Imappart_t;
typedef struct {
char* buffer;
size_t length;
} Imapdata_t;
typedef struct {
Imaparg_t* head;
Imaparg_t* tail;
} Imaplist_t;
typedef union {
Imapdata_t data;
Imaplist_t list;
long number;
char* name;
char* string;
} Imapvalue_t;
struct Imaparg_s {
Imaparg_t* next;
int type;
Imapvalue_t value;
};
typedef struct {
const char* name;
int code;
int flag;
} Imapflag_t;
typedef struct {
const char* name;
int code;
} Imaplex_t;
typedef struct {
char* from;
char* date;
char* subject;
Imappart_t* parts;
int attachments;
} Imapmsg_t;
struct Imappart_s {
Imappart_t* next;
short content;
short attachment;
int lines;
size_t size;
char* id;
char* type;
char* encoding;
char* name;
char* options;
Imapmsg_t* msg;
};
typedef struct {
Imappart_t* lastpart;
int prestack;
int idstack[64];
int* id;
} Imapbody_t;
struct Imapblock_s {
Imapblock_t* next;
size_t length;
char* data;
};
typedef struct {
int state;
int code;
int count;
int retain;
char msg[32];
Vmalloc_t* vm;
Imaplist_t args;
Imapblock_t* blocks;
} Imapop_t;
typedef struct
{
Imapop_t op[8];
Sfio_t* mp;
Sfio_t* np;
Sfio_t* rp;
Sfio_t* sp;
Sfio_t* tp;
Vmdisc_t vmdisc;
Vmalloc_t* gm;
Vmalloc_t* vm;
int auth;
int authenticated;
int connected;
int exiting;
char* host;
Msg_t* index;
char* meth;
int revision;
int selected;
int tag;
char* user;
int version;
struct
{
Sfio_t* fp;
char* prefix;
short prefixlen;
short emptylen;
} copy;
struct
{
int delimiter;
int exists;
int recent;
int read_only;
int trycreate;
int uidnext;
int uidvalidity;
int unseen;
} mailbox;
} Imap_t;
#define IMAP_DUMP_FLAGS 1
#define IMAP_DUMP_FOLDER 2
#define IMAP_DUMP_MESSAGE 3
#define IMAP_DUMP_QUEUE 4
#define IMAP_DUMP_STATE 5
static const Imaplex_t imapdump[] =
{
"fl[ags]", IMAP_DUMP_FLAGS,
"f[older]", IMAP_DUMP_FOLDER,
"m[essage]", IMAP_DUMP_MESSAGE,
"q[ueue]", IMAP_DUMP_QUEUE,
"s[tate]", IMAP_DUMP_STATE,
};
#define IMAP_STATE_free 0
#define IMAP_STATE_sent 1
#define IMAP_STATE_more 2
#define IMAP_STATE_done 3
static const Imaplex_t imapstate[] =
{
"free", IMAP_STATE_free,
"sent", IMAP_STATE_sent,
"more", IMAP_STATE_more,
"done", IMAP_STATE_done,
};
#define IMAP_CONTENT_data 0
#define IMAP_CONTENT_attachment 1
#define IMAP_CONTENT_header 2
#define IMAP_CONTENT_message 3
#define IMAP_CONTENT_multipart 4
#define IMAP_CONTENT_text 5
static const Imaplex_t imapcontent[] =
{
"attachment", IMAP_CONTENT_attachment,
"data", IMAP_CONTENT_data,
"header", IMAP_CONTENT_header,
"message", IMAP_CONTENT_message,
"multipart", IMAP_CONTENT_multipart,
"text", IMAP_CONTENT_text,
};
static const Imaplex_t imapmflags[] =
{
"DELETE", MDELETE,
"INIT", MINIT,
"MARK", MMARK,
"MBOX", MBOX,
"MODIFY", MODIFY,
"NEW", MNEW,
"NONE", MNONE,
"PRESERVE", MPRESERVE,
"READ", MREAD,
"SAVE", MSAVE,
"SCAN", MSCAN,
"SPAM", MSPAM,
"STATUS", MSTATUS,
"TOUCH", MTOUCH,
"USED", MUSED,
"ZOMBIE", MZOMBIE,
};
#define IMAP_UNKNOWN 0
#define IMAP_OK 1
#define IMAP_NO 2
#define IMAP_BAD 3
#define IMAP_BYE 4
#define IMAP_CAPABILITY 5
#define IMAP_COPY 6
#define IMAP_EXISTS 7
#define IMAP_EXPUNGE 8
#define IMAP_FETCH 9
#define IMAP_FLAGS 10
#define IMAP_LIST 11
#define IMAP_LSUB 12
#define IMAP_MORE 13
#define IMAP_PREAUTH 14
#define IMAP_RECENT 15
#define IMAP_SEARCH 16
static const Imaplex_t imapresponse[] =
{
"+", IMAP_MORE,
"BAD", IMAP_BAD,
"BYE", IMAP_BYE,
"CAPABILITY", IMAP_CAPABILITY,
"COPY", IMAP_COPY,
"EXISTS", IMAP_EXISTS,
"EXPUNGE", IMAP_EXPUNGE,
"FETCH", IMAP_FETCH,
"FLAGS", IMAP_FLAGS,
"LIST", IMAP_LIST,
"LSUB", IMAP_LSUB,
"NO", IMAP_NO,
"OK", IMAP_OK,
"PREAUTH", IMAP_PREAUTH,
"RECENT", IMAP_RECENT,
"SEARCH", IMAP_SEARCH,
"STORE", IMAP_FETCH,
"UNKNOWN", IMAP_UNKNOWN,
};
#define IMAP_AUTH_ANONYMOUS (1<<0)
#define IMAP_AUTH_KERBEROS_V4 (1<<1)
#define IMAP_AUTH_LOGIN (1<<2)
#define IMAP_AUTH_XFILE (1<<3)
static const Imaplex_t imapauth[] =
{
"ANONYMOUS", IMAP_AUTH_ANONYMOUS,
"KERBEROS_V4", IMAP_AUTH_KERBEROS_V4,
"LOGIN", IMAP_AUTH_LOGIN,
"XFILE", IMAP_AUTH_XFILE,
};
#define IMAP_STATUS_ALERT 1
#define IMAP_STATUS_NEWNAME 2
#define IMAP_STATUS_PARSE 3
#define IMAP_STATUS_PERMANENTFLAGS 4
#define IMAP_STATUS_READ_ONLY 5
#define IMAP_STATUS_READ_WRITE 6
#define IMAP_STATUS_TRYCREATE 7
#define IMAP_STATUS_UIDNEXT 8
#define IMAP_STATUS_UIDVALIDITY 9
#define IMAP_STATUS_UNSEEN 10
static const Imaplex_t imapstatus[] =
{
"ALERT", IMAP_STATUS_ALERT,
"NEWNAME", IMAP_STATUS_NEWNAME,
"PARSE", IMAP_STATUS_PARSE,
"PERMANENTFLAGS", IMAP_STATUS_PERMANENTFLAGS,
"READ-ONLY", IMAP_STATUS_READ_ONLY,
"READ-WRITE", IMAP_STATUS_READ_WRITE,
"TRYCREATE", IMAP_STATUS_TRYCREATE,
"UIDNEXT", IMAP_STATUS_UIDNEXT,
"UIDVALIDITY", IMAP_STATUS_UIDVALIDITY,
"UNSEEN", IMAP_STATUS_UNSEEN,
};
#define IMAP_FETCH_BODY 1
#define IMAP_FETCH_BODYSTRUCTURE 2
#define IMAP_FETCH_ENVELOPE 3
#define IMAP_FETCH_FLAGS 4
#define IMAP_FETCH_INTERNALDATE 5
#define IMAP_FETCH_RFC822_SIZE 6
#define IMAP_FETCH_UID 7
static const Imaplex_t imapfetch[] =
{
"BODY", IMAP_FETCH_BODY,
"BODYSTRUCTURE", IMAP_FETCH_BODYSTRUCTURE,
"ENVELOPE", IMAP_FETCH_ENVELOPE,
"FLAGS", IMAP_FETCH_FLAGS,
"INTERNALDATE", IMAP_FETCH_INTERNALDATE,
"RFC822.SIZE", IMAP_FETCH_RFC822_SIZE,
"UID", IMAP_FETCH_UID,
};
#define IMAP_FLAG_APPLICABLE (1<<0)
#define IMAP_FLAG_PERMANENT (1<<1)
#define IMAP_FLAG_ANSWERED (1<<2)
#define IMAP_FLAG_DELETED (1<<3)
#define IMAP_FLAG_DRAFT (1<<4)
#define IMAP_FLAG_FLAGGED (1<<5)
#define IMAP_FLAG_RECENT (1<<6)
#define IMAP_FLAG_SCAN (1<<7)
#define IMAP_FLAG_SEEN (1<<8)
#define IMAP_FLAG_SPAM (1<<9)
#define IMAP_FLAG_LOCAL (IMAP_FLAG_SCAN|IMAP_FLAG_SPAM)
static Imapflag_t imapflags[] =
{
"/Scan", IMAP_FLAG_SCAN, MSCAN,
"/Spam", IMAP_FLAG_SPAM, MSPAM,
"\\*", IMAP_FLAG_LOCAL, 0,
"\\Answered", IMAP_FLAG_ANSWERED, 0,
"\\Deleted", IMAP_FLAG_DELETED, MDELETE|MZOMBIE,
"\\Draft", IMAP_FLAG_DRAFT, 0,
"\\Flagged", IMAP_FLAG_FLAGGED, 0,
"\\Recent", IMAP_FLAG_RECENT, MNEW,
"\\Seen", IMAP_FLAG_SEEN, MREAD,
};
static int imapconnect(Imap_t*);
static char*
imapname(register const Imaplex_t* tab, int n, int code)
{
register const Imaplex_t* end;
for (end = tab + n; tab < end; tab++)
if (code == tab->code)
return (char*)tab->name;
return "[ERROR]";
}
static char*
imapflagnames(register Sfio_t* sp, register const Imaplex_t* tab, int n, register int flags)
{
register const Imaplex_t* end;
register int sep;
sep = 0;
for (end = tab + n; tab < end; tab++)
if (flags & tab->code)
{
if (sep)
sfputc(sp, ' ');
else
sep = 1;
sfputr(sp, tab->name, -1);
}
if (!sep)
sfputc(sp, '0');
return struse(sp);
}
static void
imapdumparg(Imap_t* imap, Imaparg_t* ap, int level)
{
switch (ap->type)
{
case IMAP_data:
sfprintf(sfstdout, " {%d}%.*s", ap->value.data.length, ap->value.data.length, ap->value.data.buffer);
break;
case IMAP_list:
if (ap = ap->value.list.head)
{
sfprintf(sfstdout, " (");
do {
imapdumparg(imap, ap, level + 1);
} while (ap = ap->next);
sfprintf(sfstdout, " )");
}
else
sfprintf(sfstdout, " NIL");
break;
case IMAP_name:
sfprintf(sfstdout, " %s", ap->value.name);
break;
case IMAP_number:
sfprintf(sfstdout, " %ld", ap->value.number);
break;
case IMAP_string:
sfprintf(sfstdout, " \"%s\"", ap->value.string);
break;
}
if (!level)
sfprintf(sfstdout, "\n");
}
static void
imapset(Imap_t* imap, register Imaparg_t* ap, int code)
{
register int i;
register Imapflag_t* fp;
for (i = 0; i < elementsof(imapflags); i++)
imapflags[i].code &= ~code;
for (; ap; ap = ap->next)
if (ap->type == IMAP_name && (fp = (Imapflag_t*)strsearch(imapflags, elementsof(imapflags), sizeof(imapflags[0]), stracmp, ap->value.name, NiL)))
{
if (fp->code == IMAP_FLAG_LOCAL)
for (i = 0; i < elementsof(imapflags); i++)
if (imapflags[i].code & IMAP_FLAG_LOCAL)
imapflags[i].code |= code;
fp->code |= code;
}
}
static char*
imapgetarg(Imap_t* imap, register Imapop_t* op, register Imaplist_t* lp, int* ep, register char* s)
{
register int c;
register char* b;
register Imaparg_t* ap;
register int eol;
int m;
char* e;
eol = ep ? *ep : 0;
for (;;)
{
for (; isspace(*s); s++);
if (*s)
{
if (*s == eol)
{
*ep = 0;
return s + 1;
}
if (!(ap = vmnewof(op->vm, 0, Imaparg_t, 1, 0)))
note(FATAL|SYSTEM, "Out of space [imap token]");
if (lp->tail)
lp->tail->next = ap;
else
lp->head = ap;
lp->tail = ap;
if (!eol && op->code <= IMAP_BYE && *s != '[')
{
ap->type = IMAP_string;
ap->value.string = s;
return 0;
}
switch (*s)
{
case '{':
ap->type = IMAP_data;
ap->value.data.length = strtol(s + 1, &e, 10);
if (*e != '}' || !op->blocks || !(op->blocks = op->blocks->next))
goto name;
ap->value.data.buffer = op->blocks->data;
return e + 1;
case '"':
for (b = ++s; *s && *s != '"'; s++);
if (*s)
*s++ = 0;
ap->type = IMAP_string;
ap->value.string = b;
return s;
case '[':
m = ']';
goto list;
case '(':
m = ')';
list:
ap->type = IMAP_list;
ap->value.list.head = ap->value.list.tail = 0;
s++;
while ((s = imapgetarg(imap, op, &ap->value.list, &m, s)) && m);
return s;
case '0':
case '1':
case '2':
case '3':
case '4':
case '5':
case '6':
case '7':
case '8':
case '9':
ap->type = IMAP_number;
ap->value.number = strtol(s, &e, 10);
return e;
case 'N':
if (*(s + 1) == 'I' && *(s + 2) == 'L' && !isalnum(*(s + 3)))
{
ap->type = IMAP_list;
ap->value.list.head = 0;
return s + 3;
}
default:
name:
m = 0;
for (b = s; (c = *s); s++)
if (c == '[')
m = c;
else if (c == ']')
m = 0;
else if (m == 0)
{
if (c == eol)
{
*ep = 0;
break;
}
else if (isspace(c))
break;
}
if (c)
*s++ = 0;
ap->type = IMAP_name;
ap->value.name = b;
return s;
}
}
if (!op->blocks || !(op->blocks = op->blocks->next))
break;
s = op->blocks->data;
}
return 0;
}
static void
imapputarg(register Imap_t* imap, register Imaparg_t* ap)
{
register char* e;
register char* s;
register char* t;
register int n;
switch (ap->type)
{
case IMAP_data:
t = s = ap->value.data.buffer;
e = s + ap->value.data.length;
while (t = (char*)memchr(t, '\r', e - t))
{
if (++t >= e)
break;
if (*t != '\n')
continue;
*(t - 1) = '\n';
n = t - s;
if (imap->copy.prefix)
sfwrite(imap->copy.fp, imap->copy.prefix, n > 1 ? imap->copy.prefixlen : imap->copy.emptylen);
sfwrite(imap->copy.fp, s, n);
if ((s = ++t) >= e)
break;
}
if ((n = e - s) > 0)
{
if (imap->copy.prefix)
sfwrite(imap->copy.fp, imap->copy.prefix, n > 1 ? imap->copy.prefixlen : imap->copy.emptylen);
sfwrite(imap->copy.fp, s, e - s);
}
break;
case IMAP_string:
sfputr(imap->copy.fp, ap->value.string, '\n');
break;
}
}
static Imapop_t*
imapop(register Imap_t* imap)
{
register char* s;
register char* z;
register size_t n;
register Imapop_t* op;
register Imapblock_t* bp;
register Imapblock_t* bt;
char* b;
char* e;
Imapblock_t* bh;
Imaplex_t* xp;
if (!(s = sfgetr(imap->rp, '\n', 1)))
return 0;
if ((z = s + sfvalue(imap->rp)) > s && --z > s && *(z - 1) == '\r')
*--z = 0;
if (TRACING('m'))
note(ERROR, "imap: mesg %s", s);
for (; isspace(*s); s++);
if (*s == '*')
{
for (; *++s && !isspace(*s););
op = imap->op;
op->state = IMAP_STATE_sent;
}
else
{
n = (int)strtol(s, &e, 10);
s = e;
op = imap->op + n;
}
if (op->state != IMAP_STATE_sent)
note(ERROR, "imap: %d: internal error -- unknown operation", n);
if (op->vm)
vmclear(op->vm);
else if (!(op->vm = vmopen(&imap->vmdisc, Vmlast, 0)))
note(FATAL|SYSTEM, "Out of space [imap vm]");
for (; isspace(*s); s++);
if (isdigit(*s))
{
n = (int)strtol(s, &e, 10);
for (s = e; isspace(*s); s++);
}
else
n = 0;
for (b = s; *s && !isspace(*s); s++);
if (*s)
for (*s++ = 0; isspace(*s); s++);
op->code = (xp = (Imaplex_t*)strsearch(imapresponse, elementsof(imapresponse), sizeof(Imaplex_t), stracmp, b, NiL)) ? xp->code : IMAP_UNKNOWN;
op->count = n;
if (TRACING('r'))
note(ERROR, "imap: recv %d %s %d %s", op - imap->op, imapname(imapresponse, elementsof(imapresponse), op->code), op->count, s);
bt = 0;
while (z > s && *(z - 1) == '}')
{
n = z - s + 1;
if (!(bp = vmnewof(op->vm, 0, Imapblock_t, 1, n)))
note(FATAL|SYSTEM, "Out of space [imap block]");
memcpy(bp->data = (char*)(bp + 1), s, n);
if (bt)
bt->next = bp;
else
bh = bp;
bt = bp;
for (z -= 2; z > s && *z != '{'; z--);
n = (size_t)strtol(z + 1, NiL, 10);
if (!(bp = vmnewof(op->vm, 0, Imapblock_t, 1, n)))
note(FATAL|SYSTEM, "Out of space [imap block]");
if ((bp->length = n) && sfread(imap->rp, bp->data = (char*)(bp + 1), n) != n)
return 0;
bt = bt->next = bp;
if (!(s = sfgetr(imap->rp, '\n', 1)))
return 0;
if ((z = s + sfvalue(imap->rp)) > s && --z > s && *(z - 1) == '\r')
*--z = 0;
if (TRACING('m'))
note(ERROR, "imap: mesg %s", s);
}
if (bt)
{
if (!(bp = vmnewof(op->vm, 0, Imapblock_t, 1, n)))
note(FATAL|SYSTEM, "Out of space [imap block]");
bp->length = z - s;
bp->data = s;
bt->next = bp;
s = bh->data;
}
else
bh = 0;
op->blocks = bh;
op->args.head = op->args.tail = 0;
while (s = imapgetarg(imap, op, &op->args, NiL, s));
return op;
}
static void
imap_EXPUNGE(Imap_t* imap, register Imapop_t* op)
{
}
static void
imap_ENVELOPE(register Imap_t* imap, register Imaparg_t* vp, Msg_t* mp)
{
register Imapmsg_t* ip;
register char* s;
ip = (Imapmsg_t*)mp->m_info;
if (!vp)
return;
if (s = vp->value.string)
{
s = fmttime("%a %b %e %H:%M %Z %Y", tmdate(s, NiL, NiL));
if (ip->date)
strcpy(ip->date, s);
else
ip->date = vmstrdup(imap->vm, s);
}
if (!(vp = vp->next))
return;
if (s = vp->value.string)
ip->subject = vmstrdup(imap->vm, s);
if (!(vp = vp->next) ||
!(vp = vp->value.list.head) ||
!(vp = vp->value.list.head) ||
!(vp = vp->next) ||
!(vp = vp->next))
return;
ip->from = vp->value.string;
if (!(vp = vp->next))
return;
if ((s = vp->value.string) && state.var.local)
s = localize(s);
if (s && *s)
{
sfprintf(imap->tp, "%s@%s", ip->from, s);
ip->from = struse(imap->tp);
}
ip->from = vmstrdup(imap->vm, ip->from);
}
static void
imap_BODYSTRUCTURE(register Imap_t* imap, register Imaparg_t* ap, register Msg_t* mp, register Imapbody_t* bp)
{
register char* s;
register Imappart_t* pp;
register Imaparg_t* vp;
int* id;
if (!ap)
return;
if (!(pp = vmnewof(imap->vm, 0, Imappart_t, 1, 0)))
note(FATAL, "out of space [imap part]");
if (bp->lastpart)
bp->lastpart->next = pp;
else
((Imapmsg_t*)mp->m_info)->parts = pp;
bp->lastpart = pp;
(*bp->id)++;
if (ap->type == IMAP_list)
{
pp->content = IMAP_CONTENT_multipart;
if (bp->id >= &bp->idstack[elementsof(bp->idstack) - 1])
note(FATAL, "imap: multipart nesting too deep -- %d max", elementsof(bp->idstack));
*++bp->id = 0;
do {
imap_BODYSTRUCTURE(imap, ap->value.list.head, mp, bp);
} while ((ap = ap->next) && ap->type == IMAP_list);
bp->id--;
sfputr(imap->tp, "multipart", -1);
if (ap && ap->type == IMAP_string)
sfprintf(imap->tp, "/%s", strlower(ap->value.string));
pp->type = vmstrdup(imap->vm, struse(imap->tp));
pp->id = "";
}
else
{
id = bp->idstack;
sfprintf(imap->tp, "%d", *id);
while (++id <= bp->id)
sfprintf(imap->tp, ".%d", *id);
pp->id = vmstrdup(imap->vm, struse(imap->tp));
if (s = ap->value.string)
{
s = strlower(s);
if (streq(s, "text"))
pp->content = IMAP_CONTENT_text;
else if (streq(s, "message"))
pp->content = IMAP_CONTENT_message;
else
pp->content = IMAP_CONTENT_data;
sfputr(imap->tp, s, -1);
}
if ((ap = ap->next) && (s = ap->value.string))
sfprintf(imap->tp, "/%s", strlower(s));
pp->type = vmstrdup(imap->vm, struse(imap->tp));
if (!ap || !(ap = ap->next))
return;
if (vp = ap->value.list.head)
do {
s = vp->value.string;
if (!(vp = vp->next))
break;
if (s && streq(s, "NAME") && vp->value.string)
{
pp->name = vmstrdup(imap->vm, vp->value.string);
break;
}
} while (vp = vp->next);
if (!(ap = ap->next) ||
!(ap = ap->next) ||
!(ap = ap->next))
return;
if (ap->type == IMAP_string && ap->value.string)
pp->encoding = vmstrdup(imap->vm, strlower(ap->value.string));
if (!(ap = ap->next))
return;
pp->size = ap->value.number;
if (!(ap = ap->next))
return;
switch (pp->content)
{
case IMAP_CONTENT_message:
if (bp->id >= &bp->idstack[elementsof(bp->idstack) - 1])
note(FATAL, "imap: message nesting too deep -- %d max", elementsof(bp->idstack));
*++bp->id = 0;
imap_ENVELOPE(imap, ap->value.list.head, mp);
ap = ap->next;
imap_BODYSTRUCTURE(imap, ap->value.list.head, mp, bp);
bp->id--;
break;
case IMAP_CONTENT_text:
pp->lines = ap->value.number;
if (!pp->name)
{
if (!(ap = ap->next) || !(ap = ap->next))
return;
if (ap->type == IMAP_list &&
(ap = ap->value.list.head) &&
(s = ap->value.string) &&
streq(s, "ATTACHMENT") &&
(ap = ap->next) &&
(ap = ap->value.list.head))
while ((s = ap->value.name) &&
(ap = ap->next))
{
if (streq(s, "FILENAME"))
{
pp->name = vmstrdup(imap->vm, ap->value.string);
break;
}
if (!(ap = ap->next))
break;
}
}
break;
case IMAP_CONTENT_data:
if (!pp->name)
{
sfprintf(imap->tp, "%d.att", ((Imapmsg_t*)mp->m_info)->attachments + 1);
pp->name = vmstrdup(imap->vm, struse(imap->tp));
}
break;
}
if (pp->name)
{
mp->m_lines += 2;
pp->content = IMAP_CONTENT_attachment;
pp->attachment = ++((Imapmsg_t*)mp->m_info)->attachments;
}
else
mp->m_lines += pp->lines;
}
}
static void
imap_FETCH(Imap_t* imap, register Imapop_t* op)
{
register Msg_t* mp;
register Imaparg_t* ap;
register Imaparg_t* vp;
register Imapmsg_t* ip;
Imaplex_t* xp;
Imapflag_t* fp;
char* s;
Imapbody_t body;
if (op->count > state.msg.count)
note(FATAL, "imap: %d: unknown message -- expected %d max", op->count, state.msg.count);
mp = state.msg.list + op->count - 1;
ip = (Imapmsg_t*)mp->m_info;
if (ap = op->args.head)
{
if (ap->type == IMAP_list)
ap = ap->value.list.head;
while (ap)
{
if (ap->type != IMAP_name)
xp = 0;
else if (!(xp = (Imaplex_t*)strsearch(imapfetch, elementsof(imapfetch), sizeof(Imaplex_t), stracmp, ap->value.name, NiL)))
{
if (s = strchr(ap->value.name, '['))
{
*s = 0;
xp = (Imaplex_t*)strsearch(imapfetch, elementsof(imapfetch), sizeof(Imaplex_t), stracmp, ap->value.name, NiL);
*s = '[';
}
if ((TRACING('u')) && !xp)
note(ERROR, "imap: %s: unknown response", ap->value.name);
}
if (ap = ap->next)
{
if (xp) switch (xp->code)
{
case IMAP_FETCH_BODY:
imapputarg(imap, ap);
break;
case IMAP_FETCH_BODYSTRUCTURE:
memset(&body, 0, sizeof(body));
body.id = body.idstack - 1;
*body.idstack = 1;
imap_BODYSTRUCTURE(imap, ap->value.list.head, mp, &body);
break;
case IMAP_FETCH_ENVELOPE:
imap_ENVELOPE(imap, ap->value.list.head, mp);
break;
case IMAP_FETCH_FLAGS:
for (vp = ap->value.list.head; vp; vp = vp->next)
if (vp->type == IMAP_name && (fp = (Imapflag_t*)strsearch(imapflags, elementsof(imapflags), sizeof(imapflags[0]), stracmp, vp->value.name, NiL)))
mp->m_flag |= fp->flag;
break;
case IMAP_FETCH_INTERNALDATE:
if (!ip->date && (s = ap->value.string))
ip->date = vmstrdup(imap->vm, fmttime("%a %b %e %H:%M %Z %Y", tmdate(s, NiL, NiL)));
break;
case IMAP_FETCH_RFC822_SIZE:
mp->m_size = ap->value.number;
break;
}
else if (TRACING('u'))
note(ERROR, "imap: %s: unknown FETCH response", ap->value.name);
ap = ap->next;
}
}
}
}
static void
imapsync(register Imap_t* imap)
{
register Msg_t* mp;
int dot;
dot = state.msg.dot ? (state.msg.dot - state.msg.list) : 0;
while (state.msg.count < imap->mailbox.exists)
{
mp = newmsg(0);
if (state.msg.count < imap->mailbox.unseen || !imap->mailbox.unseen)
mp->m_flag = MUSED|MREAD;
}
if (imap->mailbox.unseen < 0)
imap->mailbox.unseen = 1;
else if (imap->mailbox.unseen > state.msg.count)
imap->mailbox.unseen = state.msg.count;
state.msg.dot = state.msg.list + dot;
}
static Imapop_t*
imaprecv(register Imap_t* imap, register Imapop_t* wp)
{
register int n;
register Imapop_t* op;
register Imaparg_t* ap;
register Imaparg_t* sp;
Imaplex_t* xp;
int to;
int i;
char* s;
if (!wp || wp == imap->op)
{
for (op = imap->op + 1;; op++)
if (op >= &imap->op[elementsof(imap->op)])
return 0;
else if (op->state == IMAP_STATE_sent)
break;
}
if (!wp)
to = 0;
else if (wp->state != IMAP_STATE_sent && wp != imap->op)
{
if (wp->code)
return wp;
note(ERROR, "imap: %d: operation never sent", wp - imap->op);
return 0;
}
else
to = -1;
for (;;)
{
switch (sfpoll(&imap->rp, 1, to))
{
case 0:
if (wp)
note(ERROR, "imap: %d: operation not completed", wp - imap->op);
break;
case 1:
if (op = imapop(imap))
{
if (TRACING('a'))
{
note(ERROR, "imap: exec op %d count %d", op->code, op->count);
for (ap = op->args.head; ap; ap = ap->next)
imapdumparg(imap, ap, 0);
}
switch (op->code)
{
case IMAP_BAD:
case IMAP_NO:
n = 1;
goto status;
case IMAP_BYE:
n = !imap->exiting;
imap->connected = 0;
wp = op;
goto status;
case IMAP_CAPABILITY:
for (ap = op->args.head; ap; ap = ap->next)
if (ap->type == IMAP_name)
{
s = ap->value.name;
if (strneq(s, "IMAP", 4))
{
s += 4;
if ((i = (int)strtol(s, &s, 10)) >= imap->version)
{
if (i > imap->version)
imap->revision = 0;
imap->version = i;
for (; *s && !isdigit(*s); s++);
i = (int)strtol(s, NiL, 10);
if (i > imap->revision)
imap->revision = i;
}
}
else if (strneq(s, "AUTH=", 5))
if (xp = (Imaplex_t*)strsearch(imapauth, elementsof(imapauth), sizeof(Imaplex_t), stracmp, s + 5, NiL))
imap->auth |= xp->code;
}
break;
case IMAP_EXISTS:
if (imap->mailbox.exists < 0)
imap->mailbox.exists = op->count;
else
{
n = op->count - imap->mailbox.exists;
if (n > 0)
note(ERROR, "(NOTE %d new message%s incorporated)", n, n == 1 ? "" : "s");
else if (n < 0)
{
n = -n;
note(ERROR, "(NOTE %d message%s delected by another session)", n, n == 1 ? "" : "s");
vmclear(imap->vm);
state.msg.count = 0;
}
imap->mailbox.exists = op->count;
imapsync(imap);
}
break;
case IMAP_EXPUNGE:
imap_EXPUNGE(imap, op);
break;
case IMAP_FETCH:
imap_FETCH(imap, op);
break;
case IMAP_FLAGS:
if ((ap = op->args.head) && ap->type == IMAP_list)
imapset(imap, ap->value.list.head, IMAP_FLAG_APPLICABLE);
break;
case IMAP_LIST:
ap = op->args.head;
ap = ap->next;
ap = ap->next;
sfprintf(sfstdout, "@%s\n", ap->value.string);
break;
case IMAP_OK:
n = 0;
status:
if (ap = op->args.head)
{
if (ap->type == IMAP_list && (sp = ap->value.list.head) && sp->type == IMAP_name && (xp = (Imaplex_t*)strsearch(imapstatus, elementsof(imapstatus), sizeof(Imaplex_t), stracmp, sp->value.name, NiL)))
{
ap = ap->next;
sp = sp->next;
switch (xp->code)
{
case IMAP_STATUS_ALERT:
case IMAP_STATUS_NEWNAME:
case IMAP_STATUS_PARSE:
n = 1;
break;
case IMAP_STATUS_PERMANENTFLAGS:
if (sp && sp->type == IMAP_list)
imapset(imap, sp->value.list.head, IMAP_FLAG_PERMANENT);
break;
case IMAP_STATUS_READ_ONLY:
imap->mailbox.read_only = 1;
break;
case IMAP_STATUS_READ_WRITE:
imap->mailbox.read_only = 0;
break;
case IMAP_STATUS_TRYCREATE:
imap->mailbox.trycreate = 1;
break;
case IMAP_STATUS_UIDNEXT:
imap->mailbox.uidnext = sp && sp->type == IMAP_number ? sp->value.number : 0;
break;
case IMAP_STATUS_UIDVALIDITY:
imap->mailbox.uidvalidity = sp && sp->type == IMAP_number ? sp->value.number : 0;
break;
case IMAP_STATUS_UNSEEN:
imap->mailbox.unseen = sp && sp->type == IMAP_number ? sp->value.number : 0;
break;
}
}
if (n && ap && ap->type == IMAP_string)
note(ERROR, "imap: %s", ap->value.string);
}
break;
case IMAP_PREAUTH:
imap->authenticated = 1;
break;
case IMAP_RECENT:
imap->mailbox.recent = op->count;
break;
case IMAP_SEARCH:
for (ap = op->args.head; ap; ap = ap->next)
if (ap->type == IMAP_number)
{
imap->index->m_index = ap->value.number;
imap->index++;
}
break;
}
if (!op->retain)
imapfree(imap, op);
if (wp == op || wp == imap->op)
return op;
}
continue;
default:
note(ERROR, "imap: lost server connection");
break;
}
break;
}
return 0;
}
static Imapop_t*
imapvsend(register Imap_t* imap, int retain, const char* fmt, va_list ap)
{
register int i;
register int c;
register int q;
register char* s;
va_list oap;
va_copy(oap, ap);
if (!imap->connected && imapconnect(imap))
return 0;
if (!(i = imap->tag))
i = 1;
c = i;
while (imap->op[i].state)
{
if (++i >= elementsof(imap->op))
i = 1;
if (i == c)
imaprecv(imap, imap->op);
}
imap->tag = i;
sfprintf(imap->np, "%d ", i);
sfvprintf(imap->np, fmt, ap);
s = struse(imap->np);
q = 0;
for (;;)
{
switch (c = *s++)
{
case 0:
break;
case '"':
case '\'':
sfputc(imap->tp, c);
if (!q)
q = c;
continue;
case ' ':
case '\n':
case '\r':
case '\t':
if (!q)
{
for (; (c = *s) && (c == ' ' || c == '\n' || c == '\r' || c == '\t'); s++);
if (c && c != ')')
sfputc(imap->tp, ' ');
}
else
sfputc(imap->tp, c);
continue;
case '(':
sfputc(imap->tp, c);
if (!q)
for (; (c = *s) && (c == ' ' || c == '\n' || c == '\r' || c == '\t'); s++);
continue;
case ')':
sfputc(imap->tp, c);
if (!q)
{
for (; (c = *s) && (c == ' ' || c == '\n' || c == '\r' || c == '\t'); s++);
if (c && c != ']')
sfputc(imap->tp, ' ');
}
continue;
default:
sfputc(imap->tp, c);
continue;
}
break;
}
s = struse(imap->tp);
if (TRACING('s'))
note(ERROR, "imap: send %s", s);
if (sfprintf(imap->sp, "%s\r\n", s) < 0 || sfsync(imap->sp) < 0)
{
note(ERROR|SYSTEM, "imap: server send error");
i = 0;
}
if (!i)
return 0;
imap->op[i].state = IMAP_STATE_sent;
imap->op[i].retain = retain;
sfvsprintf(imap->op[i].msg, sizeof(imap->op[i].msg) - 1, fmt, oap);
return imap->op + i;
}
static Imapop_t*
imapsend(Imap_t* imap, int retain, const char* fmt, ...)
{
Imapop_t* op;
va_list ap;
va_start(ap, fmt);
op = imapvsend(imap, retain, fmt, ap);
va_end(ap);
return op;
}
static int
imapexec(Imap_t* imap, const char* fmt, ...)
{
register Imapop_t* op;
va_list ap;
va_start(ap, fmt);
op = imapvsend(imap, 1, fmt, ap);
va_end(ap);
if (!op || !imaprecv(imap, op))
return 0;
imapfree(imap, op);
return op->code == IMAP_OK ? 0 : -1;
}
static int
imapconnect(register Imap_t* imap)
{
register char* svc;
int fd;
if (imap->connected < 0)
{
note(FATAL, "imap: connection/authentication error");
return -1;
}
if (imap->sp)
{
sfclose(imap->sp);
imap->sp = 0;
}
if (imap->rp)
{
sfclose(imap->rp);
imap->rp = 0;
}
if (!imap->host || !imap->user || !imap->meth)
{
register char* host;
register char* user;
register char* meth;
Sfio_t* fp;
if (fp = fileopen(state.var.imap, "IRXr"))
{
while (host = sfgetr(fp, '\n', 1))
{
for (; isspace(*host); host++);
if (!*host || *host == '#')
continue;
for (user = host; *user && !isspace(*user); user++);
if (*user)
*user++ = 0;
if (!imap->host || streq(imap->host, host))
{
for (; isspace(*user); user++);
for (meth = user; *meth && !isspace(*meth); meth++);
if (*meth)
*meth++ = 0;
if (!imap->user || streq(imap->user, user))
{
for (; isspace(*meth); meth++);
goto match;
}
}
}
host = 0;
user = 0;
meth = 0;
}
else
{
host = imap->host;
user = imap->user;
meth = imap->meth;
}
match:
if (host && (!*host || streq(host, "*")))
host = 0;
if (!imap->host || host && !streq(host, imap->host))
imap->host = host ? vmstrdup(imap->gm, host) : "local";
if (user && (!*user || streq(user, "*")))
user = 0;
if (!imap->user || user && !streq(user, imap->user))
imap->user = user ? vmstrdup(imap->gm, user) : state.var.user;
if (meth && (!*meth || streq(meth, "*")))
meth = 0;
if (!imap->meth || meth && !streq(meth, imap->meth))
{
if (!meth)
{
if (streq(imap->user, "anonymous"))
{
sfprintf(imap->tp, "LOGIN ANONYMOUS %s", state.var.user);
if (state.var.domain)
sfprintf(imap->tp, "@%s", state.var.domain);
meth = struse(imap->tp);
}
else
meth = "AUTHENTICATE XFILE";
}
imap->meth = vmstrdup(imap->gm, meth);
}
if (fp)
sfclose(fp);
}
sfprintf(imap->tp, "/dev/tcp/%s/inet.imap", imap->host);
svc = struse(imap->tp);
if ((fd = csopen(&cs, svc, 0)) < 0)
{
note(ERROR|SYSTEM, "imap: %s: cannot connect to service", svc);
return -1;
}
if (!(imap->sp = sfnew(NiL, NiL, SF_UNBOUND, fd, SF_WRITE)) ||
!(imap->rp = sfnew(NiL, NiL, SF_UNBOUND, fd, SF_READ)))
{
if (imap->sp)
{
sfclose(imap->sp);
imap->sp = 0;
}
else
close(fd);
note(ERROR|SYSTEM, "imap: %s: cannot buffer service", svc);
return -1;
}
imap->connected = -1;
if (imapexec(imap, "CAPABILITY") || imap->version < IMAP_VERSION || imap->version == IMAP_VERSION && imap->revision < IMAP_REVISION)
{
if (imap->version)
note(ERROR|SYSTEM, "imap: %s: service version %d.%d must be at least %d.%d", svc, imap->version, imap->revision, IMAP_VERSION, IMAP_REVISION);
else
note(ERROR|SYSTEM, "imap: %s: service connect error", svc);
goto drop;
}
if (!imap->authenticated)
{
imap->authenticated = -1;
if (imapexec(imap, "%s", imap->meth))
{
note(ERROR|SYSTEM, "imap: %s: service authentication error", svc);
goto drop;
}
imap->authenticated = 1;
}
imap->connected = 1;
return 0;
drop:
if (imap->sp)
{
sfclose(imap->sp);
imap->sp = 0;
}
if (imap->rp)
{
sfclose(imap->rp);
imap->rp = 0;
}
imap->connected = 0;
return -1;
}
static int
vmexcept(Vmalloc_t* region, int type, void* obj, Vmdisc_t* disc)
{
Vmstat_t st;
switch (type)
{
case VM_BADADDR:
note(ERROR, "imap: invalid pointer %p passed to free or realloc", obj);
imap_exit(1);
return -1;
case VM_NOMEM:
vmstat(region, &st);
note(ERROR, "imap: storage allocator out of space on %lu byte request ( region %lu segments %lu busy %lu:%lu:%lu free %lu:%lu:%lu )", (size_t)obj, st.extent, st.n_seg, st.n_busy, st.s_busy, st.m_busy, st.n_free, st.s_free, st.m_free);
imap_exit(1);
return -1;
}
return 0;
}
static Imap_t*
imapinit(void)
{
register Imap_t* imap;
if (!(imap = newof(0, Imap_t, 1, 0)))
{
note(ERROR|SYSTEM, "Out of space [imap]");
return 0;
}
imap->vmdisc = *Vmdcheap;
imap->vmdisc.exceptf = vmexcept;
if (!(imap->gm = vmopen(&imap->vmdisc, Vmlast, 0)) || !(imap->vm = vmopen(&imap->vmdisc, Vmlast, 0)))
{
note(ERROR|SYSTEM, "Out of space [imap state vm]");
if (imap->gm)
vmclose(imap->gm);
free(imap);
return 0;
}
if (!(imap->mp = sfstropen()) || !(imap->tp = sfstropen()) || !(imap->np = sfstropen()))
{
note(ERROR|SYSTEM, "Out of space [imap tmp string stream]");
vmclose(imap->gm);
vmclose(imap->vm);
free(imap);
return 0;
}
imap->copy.fp = sfstdout;
return state.msg.imap.state = (void*)imap;
}
static int
imapclose(register Imap_t* imap)
{
register Msg_t* mp;
register int n;
while (imaprecv(imap, imap->op));
imap->selected = 0;
if (state.folder == FIMAP)
{
n = 0;
for (mp = state.msg.list; mp < state.msg.list + state.msg.count; mp++)
if (!(mp->m_flag & MDELETE))
n++;
if (n)
note(ERROR, "Held %d message%s in %s", n, n == 1 ? "" : "s", state.path.mail);
else
note(ERROR, "No more messages in %s", state.path.mail);
}
return imapexec(imap, "CLOSE");
}
int
imap_setptr(char* name, int isedit)
{
register Imap_t* imap;
register char* s;
register char* t;
if (!(imap = IMAP) && (!name || !(imap = imapinit())))
return -1;
if (imap->selected && imapclose(imap))
return -1;
if (!name)
return 0;
vmclear(imap->vm);
state.folder = FIMAP;
memset(&imap->mailbox, sizeof(imap->mailbox), 0);
imap->mailbox.exists = -1;
if (streq(name, "@"))
name = "@inbox";
strncopy(state.path.prev, state.path.mail, sizeof(state.path.prev));
if (name != state.path.mail)
strncopy(state.path.mail, name, sizeof(state.path.mail));
if (*name == '@')
name++;
else if (strneq(name, "imap://", 7))
{
sfprintf(imap->tp, "%s", name + 7);
s = struse(imap->tp);
if (!(t = strchr(s, '/')))
{
note(ERROR, "imap: %s: user name expected", name);
return -1;
}
*t++ = 0;
if (!imap->host || !streq(imap->host, s))
{
imap->host = vmstrdup(imap->gm, s);
imap->connected = 0;
imap->meth = 0;
}
if (s = strchr(t, '/'))
*s++ = 0;
if (!imap->user || !streq(imap->user, t))
{
imap->user = vmstrdup(imap->gm, t);
imap->connected = 0;
imap->meth = 0;
}
if (!s || !*s)
name = "inbox";
else
name += (s - sfstrbase(imap->tp)) + 7;
}
if (!imap->connected && imapconnect(imap))
return -1;
if (imapexec(imap, "%s %s", state.readonly ? "EXAMINE" : "SELECT", name))
return -1;
imap->selected = 1;
state.msg.count = 0;
imapsync(imap);
state.msg.dot = state.msg.list + imap->mailbox.unseen - 1;
return 0;
}
static Msg_t*
imap_msg(int m)
{
register Imap_t* imap;
register Msg_t* mp;
mp = state.msg.list + m - 1;
if (!mp->m_info)
{
imap = IMAP;
if (!(mp->m_info = (void*)vmnewof(imap->vm, 0, Imapmsg_t, 1, 0)))
note(FATAL, "out of space [imap msg info]");
if (imapexec(imap, "FETCH %d (RFC822.SIZE FLAGS ENVELOPE BODYSTRUCTURE INTERNALDATE)", m))
note(FATAL, "imap: %d: cannot fetch message info", m);
if (state.var.spam && !(mp->m_flag & (MSCAN|MSPAM)))
setinput(mp);
if (TRACING('b'))
{
register Imapmsg_t* ip;
register Imappart_t* pp;
ip = (Imapmsg_t*)mp->m_info;
for (pp = ip->parts; pp; pp = pp->next)
note(ERROR, "imap: message %d id=%s content=%s type=%s encoding=%s name=%s size=%d/%d", m, pp->id, imapname(imapcontent, elementsof(imapcontent), pp->content), pp->type, pp->encoding, pp->name, pp->lines, pp->size);
}
}
return mp;
}
Sfio_t*
imap_setinput(register Msg_t* mp)
{
register Imap_t* imap = IMAP;
int m = mp - state.msg.list + 1;
imap->copy.fp = imap->mp;
sfstrseek(imap->mp, 0, SEEK_SET);
if (imapexec(imap, "FETCH %d (BODY.PEEK[HEADER])", m))
note(FATAL, "imap: %d: cannot fetch message header", m);
imap->copy.fp = sfstdout;
imap->mp->_endb = imap->mp->_next;
sfseek(imap->mp, (Sfoff_t)0, SEEK_SET);
return imap->mp;
}
int
imap_command(char* s)
{
register Imap_t* imap = IMAP;
register int n;
int items;
char* e;
Imaplex_t* xp;
unsigned long trace;
if (strneq(s, "dump", 4) && (!(n = *(s + 4)) || isspace(n)))
{
for (s += 4; isspace(*s); s++);
if (!*s)
{
list:
sfprintf(sfstdout, "dump items are:");
for (n = 0; n < elementsof(imapdump); n++)
sfprintf(sfstdout, " %s", imapdump[n].name);
sfprintf(sfstdout, "\n");
}
else for (items = 0;;)
{
for (; isspace(*s); s++);
if (!*s)
break;
if (items++)
sfprintf(sfstdout, "\n");
if (!(xp = (Imaplex_t*)strpsearch(imapdump, elementsof(imapdump), sizeof(imapdump[0]), s, &e)))
{
note(ERROR, "%s: unknown dump item", s);
goto list;
}
for (s = e; isspace(*s); s++);
if (isdigit(*s))
{
n = strtol(s, &e, 0);
s = e;
}
else if (*s == '*')
{
s++;
n = -1;
}
else
n = 0;
switch (xp->code)
{
case IMAP_DUMP_FLAGS:
for (n = 0; n < elementsof(imapflags); n++)
sfprintf(sfstdout, "%15s%s%s\n", imapflags[n].name, (imapflags[n].code & IMAP_FLAG_APPLICABLE) ? " APPLICABLE" : "", (imapflags[n].code & IMAP_FLAG_PERMANENT) ? " PERMANENT" : "");
break;
case IMAP_DUMP_FOLDER:
sfprintf(sfstdout, " name %s\n", state.path.mail);
sfprintf(sfstdout, " delimiter %s\n", imap->mailbox.delimiter);
sfprintf(sfstdout, " exists %d\n", imap->mailbox.exists);
sfprintf(sfstdout, " recent %d\n", imap->mailbox.recent);
sfprintf(sfstdout, " read_only %d\n", imap->mailbox.recent);
sfprintf(sfstdout, " trycreate %d\n", imap->mailbox.trycreate);
sfprintf(sfstdout, " uidnext %d\n", imap->mailbox.uidnext);
sfprintf(sfstdout, " uidvalidity %d\n", imap->mailbox.uidvalidity);
sfprintf(sfstdout, " unseen %d\n", imap->mailbox.unseen);
break;
case IMAP_DUMP_MESSAGE:
{
register Msg_t* mp;
register Msg_t* ep;
register Imapmsg_t* ip;
register Imappart_t* pp;
if (n > state.msg.count)
{
sfprintf(sfstdout, "%d: invalid message", n);
break;
}
if (n < 0)
{
mp = state.msg.list;
ep = state.msg.list + state.msg.count;
}
else if (n > 0)
{
ep = state.msg.list + n;
mp = ep - 1;
}
else
{
mp = state.msg.dot;
ep = mp + 1;
}
for (; mp < ep; mp++)
{
sfprintf(sfstdout, "message %4d %s\n", mp - state.msg.list + 1, imapflagnames(imap->tp, imapmflags, elementsof(imapmflags), mp->m_flag));
if (ip = (Imapmsg_t*)mp->m_info)
for (pp = ip->parts; pp; pp = pp->next)
sfprintf(sfstdout, "%12s content=%s type=%s encoding=%s name=%s size=%d/%d\n", pp->id, imapname(imapcontent, elementsof(imapcontent), pp->content), pp->type, pp->encoding, pp->name, pp->lines, pp->size);
}
break;
}
case IMAP_DUMP_QUEUE:
{
register Imapop_t* op;
for (op = imap->op + 1; op < &imap->op[elementsof(imap->op)]; op++)
sfprintf(sfstdout, " [%d] %s %s %d %s%s\n", op - imap->op, imapname(imapstate, elementsof(imapstate), op->state), imapname(imapresponse, elementsof(imapresponse), op->code), op->count, op->msg, op->retain ? " retain" : "");
break;
}
case IMAP_DUMP_STATE:
sfprintf(sfstdout, " host %s\n", imap->host);
sfprintf(sfstdout, " user %s\n", imap->user);
sfprintf(sfstdout, " auth %s\n", imapflagnames(imap->tp, imapauth, elementsof(imapauth), imap->auth));
sfprintf(sfstdout, " authenticated %d\n", imap->authenticated);
sfprintf(sfstdout, " connected %d\n", imap->connected);
sfprintf(sfstdout, " exiting %d\n", imap->exiting);
sfprintf(sfstdout, " selected %d\n", imap->selected);
sfprintf(sfstdout, " tag %d\n", imap->tag);
sfprintf(sfstdout, " version %d.%d\n", imap->version, imap->revision);
break;
}
}
n = 0;
}
else
{
trace = state.trace;
TRACE('r');
n = imapexec(imap, "%s", s);
state.trace = trace;
}
return n;
}
int
imap_copy(register struct msg* mp, Sfio_t* op, Dt_t** ignore, char* prefix, unsigned long flags)
{
register Imap_t* imap = IMAP;
register Imapmsg_t* ip;
register Imappart_t* pp;
register char* s;
register int i;
struct name* np;
i = mp - state.msg.list + 1;
mp = imap_msg(i);
ip = (Imapmsg_t*)mp->m_info;
imap->copy.fp = op;
if (imap->copy.prefix = prefix)
{
imap->copy.prefixlen = strlen(prefix);
s = prefix + imap->copy.prefixlen;
while (--s >= prefix && isspace(*s));
imap->copy.emptylen = (s + 1) - prefix;
sfputr(op, imap->copy.prefix, -1);
}
sfprintf(op, "From %s %s\n", ip->from, ip->date);
sfprintf(imap->tp, "FETCH %d (BODY[HEADER", i);
if (ignore && *ignore)
{
if (ignore == &state.ignoreall)
sfprintf(imap->tp, ".FIELDS NIL");
else
{
if (dictflags(ignore) & RETAIN)
{
i = RETAIN;
sfprintf(imap->tp, ".FIELDS (");
s = "";
}
else
{
i = IGNORE;
sfprintf(imap->tp, ".FIELDS.NOT (From");
s = " ";
}
for (np = (struct name*)dtfirst(*ignore); np; np = (struct name*)dtnext(*ignore, np))
if (np->flags & i)
{
sfprintf(imap->tp, "%s%s", s, np->name);
s = " ";
}
sfprintf(imap->tp, ")");
}
sfprintf(imap->tp, "]");
for (pp = ip->parts; pp; pp = pp->next)
if (pp->content == IMAP_CONTENT_text)
sfprintf(imap->tp, " BODY[%s]", pp->id);
}
else
sfprintf(imap->tp, "] BODY[TEXT]");
sfprintf(imap->tp, ")");
if (imapexec(IMAP, struse(imap->tp)))
note(FATAL, "imap: %d: cannot fetch message info", i);
imap->copy.fp = sfstdout;
imap->copy.prefix = 0;
if (ignore && *ignore)
for (pp = ip->parts; pp; pp = pp->next)
if (pp->content == IMAP_CONTENT_attachment)
sfprintf(op, "\n(attachment %3d %s %18s \"%s\")\n", pp->attachment, counts(1, pp->lines, pp->size), pp->type, pp->name);
sfprintf(op, "\n");
return 0;
}
int
imap_mkdir(char* s)
{
if (*s == '@')
s++;
return imapexec(IMAP, "CREATE %s", s);
}
int
imap_rename(char* f, char* t)
{
if (*f == '@')
f++;
if (*t == '@')
t++;
return imapexec(IMAP, "RENAME %s %s", f, t);
}
int
imap_rmdir(char* s)
{
if (*s == '@')
s++;
return imapexec(IMAP, "DELETE %s", s);
}
void
imap_exit(int code)
{
register Imap_t* imap = IMAP;
register Msg_t* mp;
if (state.folder == FIMAP)
for (mp = state.msg.list; mp < state.msg.list + state.msg.count; mp++)
if ((mp->m_flag & (MDELETE|MZOMBIE)) == MDELETE)
imap_msgflags(mp, 0, MDELETE);
imap->selected = 0;
if (imap->connected)
imap_quit();
exit(code);
}
int
imap_folders(void)
{
return imapexec(IMAP, "LIST \"\" *");
}
static int
imap_getatt(Msg_t* mp, register Imappart_t* pp, register char* name, unsigned long flags, off_t* lines, off_t* chars)
{
register Imap_t* imap = IMAP;
register char* s;
register int n;
char* cmd;
if (!(cmd = iscmd(name)))
{
if (state.var.attachments && name == pp->name)
{
sfprintf(state.path.temp, "%s/%s", state.var.attachments, name);
name = struse(state.path.temp);
}
if (!(name = expand(name, 1)))
return 1;
}
if (pp->encoding && mime(1) && mimeview(state.part.mime, "encoding", name, pp->type, pp->options))
pp->encoding = pp->type;
if (pp->encoding && !isdigit(pp->encoding[0]))
{
sfprintf(state.path.temp, "uudecode -h -x %s", pp->encoding);
if (!mimecmp("text", pp->type, NiL))
sfprintf(state.path.temp, " -t");
if (cmd)
sfprintf(state.path.temp, " -o - | %s", cmd);
else if (filestd(name, "w"))
sfprintf(state.path.temp, " | %s", state.var.pager);
else
{
sfprintf(state.path.temp, " -o ", name);
shquote(state.path.temp, name);
}
s = struse(state.path.temp);
n = 1;
}
else if (cmd)
{
s = cmd;
n = -1;
}
else if (filestd(name, "w"))
{
s = state.var.pager;
n = -1;
}
else
{
s = name;
n = 0;
}
if (state.var.debug)
{
note(DEBUG, "%s `%s'", n ? "pipe" : "file", s);
if ((flags & GMIME) &&
mime(1) &&
(s = mimeview(state.part.mime, NiL, name, pp->type, pp->options)))
note(DEBUG, "mimeview `%s'", s);
return 0;
}
if (!(imap->copy.fp = n ? pipeopen(s, "w") : fileopen(s, "ERw"))) {
imap->copy.fp = sfstdout;
return 1;
}
if (lines)
*lines = pp->lines;
if (chars)
*chars = pp->size;
n = mp - state.msg.list + 1;
if (imapexec(imap, "FETCH %d (BODY[%s])", n, pp->id))
note(FATAL, "imap: %d: cannot fetch message info", n);
imap->copy.fp = sfstdout;
fileclose(imap->copy.fp);
if (flags & GDISPLAY)
note(ERROR, "\"%s\" %s", name, counts(1, pp->lines, pp->size));
if ((flags & GMIME) &&
mime(1) &&
(s = mimeview(state.part.mime, NiL, name, pp->type, pp->options)) &&
(n = start_command(state.var.shell, SIG_REG_EXEC, -1, -1, "-c", s, NiL)) >= 0)
wait_command(n);
return 0;
}
int
imap_get1(char** argv, unsigned long flags)
{
register Imappart_t* pp;
register int i;
register char* s;
Msg_t* mp;
Imapmsg_t* ip;
char* name;
char* a;
char* e;
int n;
int r;
if (state.msg.dot < state.msg.list || state.msg.dot >= state.msg.list + state.msg.count)
{
note(ERROR, "No current message");
return 1;
}
mp = imap_msg(state.msg.dot - state.msg.list + 1);
ip = (Imapmsg_t*)mp->m_info;
if (!ip->attachments || !(pp = ip->parts))
{
note(ERROR, "No attachments in current message");
return 1;
}
if (!*argv)
{
do {
if (pp->content == IMAP_CONTENT_attachment)
sfprintf(sfstdout, "(attachment %3d %s %18s \"%s\")\n", pp->attachment, counts(1, pp->lines, pp->size), pp->type, pp->name);
} while (pp = pp->next);
return 0;
}
if (!(a = newof(0, char, ip->attachments, 1)))
note(PANIC, "Out of space [imap attachments]");
s = *argv++;
r = 0;
for (;;)
{
while (isspace(*s))
s++;
if (!*s)
break;
else if (*s == ',')
{
s++;
r = 0;
}
else if (*s == '*')
{
if (!r)
r = 1;
for (i = r; i <= ip->attachments; i++)
a[i] = 1;
r = 0;
}
else if (*s == '-')
{
s++;
r = 1;
}
else
{
n = strtol(s, &e, 0);
if (n > 0 && n <= ip->attachments)
{
if (r)
{
for (i = r; i <= n; i++)
a[i] = 1;
r = 0;
}
else
a[n] = 1;
}
else
{
note(ERROR, "%s: invalid attachment number", s);
while (*e && !isspace(*e))
e++;
}
s = e;
if (*s == '-')
{
s++;
r = n;
}
}
}
r = 0;
for (i = 1; i <= ip->attachments; i++)
if (a[i])
{
while (pp->attachment != i)
if (!(pp = pp->next))
{
note(ERROR, "%d: attachment number out of range", i);
r = 1;
goto done;
}
if (pp->content != IMAP_CONTENT_attachment)
{
note(ERROR, "%d: not an attachment", i);
continue;
}
if (name = *argv)
argv++;
else
name = pp->name;
if (imap_getatt(mp, pp, name, flags, NiL, NiL))
r = 1;
}
done:
free(a);
return r;
}
void
imap_printhead(int m, int who)
{
register Msg_t* mp;
register Imapmsg_t* ip;
char* sizes;
int subjlen;
int current;
int disposition;
mp = imap_msg(m);
if (mp->m_flag & (MDELETE|MNONE))
return;
ip = (Imapmsg_t*)mp->m_info;
current = state.msg.dot == mp && !state.var.justheaders ? '>' : ' ';
if (mp->m_flag & MBOX)
disposition = 'M';
else if (mp->m_flag & MPRESERVE)
disposition = 'P';
else if (mp->m_flag & MSAVE)
disposition = '*';
else if (mp->m_flag & MSPAM)
disposition = 'X';
else if (!(mp->m_flag & (MREAD|MNEW)))
disposition = 'U';
else if ((mp->m_flag & (MREAD|MNEW)) == MNEW)
disposition = 'N';
else
disposition = who ? 'R' : ' ';
if (who)
{
if (!state.var.domain || strchr(ip->from, '@'))
printf("%c %s\n", disposition, ip->from);
else
printf("%c %s@%s\n", disposition, ip->from, state.var.domain);
}
else
{
sizes = counts(!!state.var.news, mp->m_lines, mp->m_size);
subjlen = state.screenwidth - 50 - strlen(sizes);
if (ip->subject && subjlen >= 0)
printf("%c%c%3d %-20.20s %16.16s %s %.*s\n",
current, disposition, m, ip->from, ip->date, sizes,
subjlen, ip->subject);
else
printf("%c%c%3d %-20.20s %16.16s %s\n",
current, disposition, m, ip->from, ip->date, sizes);
}
}
static void
imap_flags(register Imap_t* imap, Msg_t* mp, register int flags, char* op)
{
register int i;
register int c;
if (TRACING('z'))
note(ERROR, "IMAP: smap_flags msg=%d flags=0x%04x op=%s", mp - state.msg.list + 1, flags, op);
sfprintf(imap->tp, "STORE %d %sFLAGS.SILENT ", mp - state.msg.list + 1, op);
c = '(';
for (i = 0; i < elementsof(imapflags); i++)
if ((imapflags[i].flag & flags) && (imapflags[i].code & IMAP_FLAG_PERMANENT))
{
sfprintf(imap->tp, "%c%s", c, imapflags[i].name);
c = ' ';
}
if (c == ' ')
{
sfprintf(imap->tp, ")");
if (!imapsend(imap, 0, "%s", struse(imap->tp)))
note(ERROR, "%d: message flags not updated", mp - state.msg.list + 1);
}
else
sfstrseek(imap->tp, 0, SEEK_SET);
}
void
imap_msgflags(register Msg_t* mp, int set, int clr)
{
register Imap_t* imap = IMAP;
register int flags;
if (mp->m_flag & ~clr & MDELETE)
return;
if (flags = mp->m_flag & clr)
{
mp->m_flag &= ~clr;
if (flags & (MDELETE|MREAD|MSCAN|MSPAM))
imap_flags(imap, mp, flags, "-");
}
if (flags = ~mp->m_flag & set)
{
mp->m_flag |= set;
if (flags & (MDELETE|MREAD|MSCAN|MSPAM))
imap_flags(imap, mp, flags, "+");
}
}
int
imap_msglist(register char* s)
{
register Imap_t* imap = IMAP;
register char* t;
for (; isspace(*s); s++);
if (*s != '(')
{
note(ERROR, "imap: %s: invalid SEARCH expression", s);
return 0;
}
s++;
t = s + strlen(s);
for (; t > s && isspace(*--t) && *t != ')';);
imap->index = state.msg.list;
if (imapexec(imap, "SEARCH %*.*s", t - s, t - s, s))
return 0;
imap->index->m_index = 0;
return imap->index - state.msg.list;
}
void
imap_quit(void)
{
register Imap_t* imap = IMAP;
register int i;
state.msg.imap.state = 0;
if (imap->selected)
imapclose(imap);
imap->exiting = 1;
imapexec(imap, "LOGOUT");
for (i = 0; i < elementsof(imap->op); i++)
if (imap->op[i].vm)
vmclose(imap->op[i].vm);
if (imap->gm)
vmclose(imap->gm);
if (imap->vm)
vmclose(imap->vm);
if (imap->np)
sfclose(imap->np);
if (imap->sp)
sfclose(imap->sp);
if (imap->rp)
sfclose(imap->rp);
if (imap->mp)
sfclose(imap->mp);
if (imap->tp)
sfclose(imap->tp);
}
int
imap_save(register Msg_t* mp, char* folder)
{
return imapexec(IMAP, "COPY %d %s", mp - state.msg.list + 1, folder + 1);
}
#else
int
imap_setptr(char* name, int isedit)
{
note(ERROR, "imap: support not enabled");
return -1;
}
#endif