]> git.lizzy.rs Git - plan9front.git/blob - sys/src/cmd/upas/fs/imap.c
upas/fs: remove imap lastread debounding
[plan9front.git] / sys / src / cmd / upas / fs / imap.c
1 /*
2  * todo:
3  * 1.   sync with imap server's flags
4  * 2.   better algorithm for avoiding downloading message list.
5  * 3.   get sender — eating envelope is lots of work!
6  */
7 #include "common.h"
8 #include <libsec.h>
9 #include <auth.h>
10 #include "dat.h"
11
12 #define idprint(i, ...) if(i->flags & Fdebug) fprint(2, __VA_ARGS__); else {}
13 #pragma varargck argpos imap4cmd        2
14 #pragma varargck        type    "Z"             char*
15 #pragma varargck        type    "U"             uvlong
16 #pragma varargck        type    "U"             vlong
17
18 static char     confused[]      = "confused about fetch response";
19 static char     qsep[]          = " \t\r\n";
20 static char     Eimap4ctl[]     = "bad imap4 control message";
21
22 enum{
23         /* cap */
24         Cnolog  = 1<<0,
25         Ccram   = 1<<1,
26         Cntlm   = 1<<2,
27
28         /* flags */
29         Fssl    = 1<<0,
30         Fdebug  = 1<<1,
31         Fgmail  = 1<<2,
32 };
33
34 typedef struct {
35         uvlong  uid;
36         ulong   sizes;
37         ulong   dates;
38 } Fetchi;
39
40 typedef struct Imap Imap;
41 struct Imap {
42         char    *mbox;
43         /* free this to free the strings below */
44         char    *freep;
45         char    *host;
46         char    *user;
47
48         int     refreshtime;
49         uchar   cap;
50         uchar   flags;
51
52         ulong   tag;
53         ulong   validity;
54         int     nmsg;
55         int     size;
56
57         Fetchi  *f;
58         int     nuid;
59         int     muid;
60
61         /* open network connection */
62         Biobuf  bin;
63         Biobuf  bout;
64         int     binit;
65         int     fd;
66 };
67
68 enum
69 {
70         Qok = 0,
71         Qquote,
72         Qbackslash,
73 };
74
75 static int
76 needtoquote(Rune r)
77 {
78         if(r >= Runeself)
79                 return Qquote;
80         if(r <= ' ')
81                 return Qquote;
82         if(r == '\\' || r == '"')
83                 return Qbackslash;
84         return Qok;
85 }
86
87 static int
88 Zfmt(Fmt *f)
89 {
90         char *s, *t;
91         int w, quotes;
92         Rune r;
93
94         s = va_arg(f->args, char*);
95         if(s == 0 || *s == 0)
96                 return fmtstrcpy(f, "\"\"");
97
98         quotes = 0;
99         for(t = s; *t; t += w){
100                 w = chartorune(&r, t);
101                 quotes |= needtoquote(r);
102         }
103         if(quotes == 0)
104                 return fmtstrcpy(f, s);
105
106         fmtrune(f, '"');
107         for(t = s; *t; t += w){
108                 w = chartorune(&r, t);
109                 if(needtoquote(r) == Qbackslash)
110                         fmtrune(f, '\\');
111                 fmtrune(f, r);
112         }
113         return fmtrune(f, '"');
114 }
115
116 static int
117 Ufmt(Fmt *f)
118 {
119         char buf[20*2 + 2];
120         ulong a, b;
121         uvlong u;
122
123         u = va_arg(f->args, uvlong);
124         if(u == 1)
125                 return fmtstrcpy(f, "nil");
126         if(u == 0)
127                 return fmtstrcpy(f, "-");
128         a = u>>32;
129         b = u;
130         snprint(buf, sizeof buf, "%lud:%lud", a, b);
131         return fmtstrcpy(f, buf);
132 }
133
134 static void
135 imap4cmd(Imap *imap, char *fmt, ...)
136 {
137         char buf[256], *p;
138         va_list va;
139
140         va_start(va, fmt);
141         p = buf + sprint(buf, "9x%lud ", imap->tag);
142         vseprint(p, buf + sizeof buf, fmt, va);
143         va_end(va);
144
145         p = buf + strlen(buf);
146         if(p > buf + sizeof buf - 3)
147                 sysfatal("imap4 command too long");
148         idprint(imap, "-> %s\n", buf);
149         strcpy(p, "\r\n");
150         Bwrite(&imap->bout, buf, strlen(buf));
151         Bflush(&imap->bout);
152 }
153
154 enum {
155         Ok,
156         No,
157         Bad,
158         Bye,
159         Exists,
160         Status,
161         Fetch,
162         Cap,
163         Auth,
164
165         Unknown,
166 };
167
168 static char *verblist[] = {
169 [Ok]    "ok",
170 [No]    "no",
171 [Bad]   "bad",
172 [Bye]   "bye",
173 [Exists]        "exists",
174 [Status]        "status",
175 [Fetch] "fetch",
176 [Cap]   "capability",
177 [Auth]  "authenticate",
178 };
179
180 static int
181 verbcode(char *verb)
182 {
183         int i;
184         char *q;
185
186         if(q = strchr(verb, ' '))
187                 *q = '\0';
188         for(i = 0; i < nelem(verblist) - 1; i++)
189                 if(strcmp(verblist[i], verb) == 0)
190                         break;
191         if(q)
192                 *q = ' ';
193         return i;
194 }
195
196 static vlong
197 mkuid(Imap *i, char *id)
198 {
199         vlong v;
200
201         v = (vlong)i->validity<<32;
202         return v | strtoul(id, 0, 10);
203 }
204
205 static vlong
206 xnum(char *s, int a, int b)
207 {
208         vlong v;
209
210         if(*s != a)
211                 return -1;
212         v = strtoull(s + 1, &s, 10);
213         if(*s != b)
214                 return -1;
215         return v;
216 }
217
218 static struct{
219         char    *flag;
220         int     e;
221 } ftab[] = {
222         "Answered",     Fanswered,
223         "\\Deleted",    Fdeleted,
224         "\\Draft",              Fdraft,
225         "\\Flagged",    Fflagged,
226         "\\Recent",     Frecent,
227         "\\Seen",               Fseen,
228         "\\Stored",     Fstored,
229 };
230
231 static void
232 parseflags(Message *m, char *s)
233 {
234         char *f[10];
235         int i, j, j0, n;
236
237         n = tokenize(s, f, nelem(f));
238         qsort(f, n, sizeof *f, (int (*)(void*,void*))strcmp);
239         j = 0;
240         for(i = 0; i < n; i++)
241                 for(j0 = j;; j++){
242                         if(j == nelem(ftab)){
243                                 j = j0;         /* restart search */
244                                 break;
245                         }
246                         if(strcmp(f[i], ftab[j].flag) == 0){
247                                 m->flags |= ftab[j].e;
248                                 break;
249                         }
250                 }
251 }
252
253 /* "17-Jul-1996 02:44:25 -0700" */
254 long
255 internaltounix(char *s)
256 {
257         Tm tm;
258         if(strlen(s) < 20 || s[2] != '-' || s[6] != '-')
259                 return -1;
260         s[2] = ' ';
261         s[6] = ' ';
262         if(strtotm(s, &tm) == -1)
263                 return -1;
264         return tm2sec(&tm);
265 }
266         
267 static char*
268 qtoken(char *s, char *sep)
269 {
270         int quoting;
271         char *t;
272
273         quoting = 0;
274         t = s;  /* s is output string, t is input string */
275         while(*t!='\0' && (quoting || utfrune(sep, *t)==nil)){
276                 if(*t != '"' && *t  != '(' && *t != ')'){
277                         *s++ = *t++;
278                         continue;
279                 }
280                 /* *t is a quote */
281                 if(!quoting || *t == '('){
282                         quoting++;
283                         t++;
284                         continue;
285                 }
286                 /* quoting and we're on a quote */
287                 if(t[1] != '"'){
288                         /* end of quoted section; absorb closing quote */
289                         t++;
290                         if(quoting > 0)
291                                 quoting--;
292                         continue;
293                 }
294                 /* doubled quote; fold one quote into two */
295                 t++;
296                 *s++ = *t++;
297         }
298         if(*s != '\0'){
299                 *s = '\0';
300                 if(t == s)
301                         t++;
302         }
303         return t;
304 }
305
306 int
307 imaptokenize(char *s, char **args, int maxargs)
308 {
309         int nargs;
310
311         for(nargs=0; nargs < maxargs; nargs++){
312                 while(*s!='\0' && utfrune(qsep, *s)!=nil)
313                         s++;
314                 if(*s == '\0')
315                         break;
316                 args[nargs] = s;
317                 s = qtoken(s, qsep);
318         }
319
320         return nargs;
321 }
322
323 static char*
324 fetchrsp(Imap *imap, char *p, Mailbox *, Message *m)
325 {
326         char *f[15], *s, *q;
327         int i, n, a;
328         ulong o, l;
329         uvlong v;
330         static char error[256];
331         extern void msgrealloc(Message*, ulong);
332
333 redux:
334         n = imaptokenize(p, f, nelem(f));
335         if(n%2)
336                 return confused;
337         for(i = 0; i < n; i += 2){
338                 if(strcmp(f[i], "internaldate") == 0){
339                         l = internaltounix(f[i + 1]);
340                         if(l < 418319360)
341                                 abort();
342                         if(imap->nuid < imap->muid)
343                                 imap->f[imap->nuid].dates = l;
344                 }else if(strcmp(f[i], "rfc822.size") == 0){
345                         l = strtoul(f[i + 1], 0, 0);
346                         if(m)
347                                 m->size = l;
348                         else if(imap->nuid < imap->muid)
349                                 imap->f[imap->nuid].sizes = l;
350                 }else if(strcmp(f[i], "uid") == 0){
351                         v = mkuid(imap, f[1]);
352                         if(m)
353                                 m->imapuid = v;
354                         if(imap->nuid < imap->muid)
355                                 imap->f[imap->nuid].uid = v;
356                 }else if(strcmp(f[i], "flags") == 0)
357                         parseflags(m, f[i + 1]);
358                 else if(strncmp(f[i], "body[]", 6) == 0){
359                         s = f[i]+6;
360                         o = 0;
361                         if(*s == '<')
362                                 o = xnum(s, '<', '>');
363                         if(o == -1)
364                                 return confused;
365                         l = xnum(f[i + 1], '{', '}');
366                         a = o + l - m->ibadchars - m->size;
367                         if(a > 0){
368                                 assert(imap->flags & Fgmail);
369                                 m->size = o + l;
370                                 msgrealloc(m, m->size);
371                                 m->size -= m->ibadchars;
372                         }
373                         if(Bread(&imap->bin, m->start + o, l) != l){
374                                 snprint(error, sizeof error, "read: %r");
375                                 return error;
376                         }
377                         if(Bgetc(&imap->bin) == ')'){
378                                 while(Bgetc(&imap->bin) != '\n')
379                                         ;
380                                 return 0;
381                         }
382                         /* evil */
383                         if(!(p = Brdline(&imap->bin, '\n')))
384                                 return 0;
385                         q = p + Blinelen(&imap->bin);
386                         while(q > p && (q[-1] == '\n' || q[-1] == '\r'))
387                                 q--;
388                         *q = 0;
389                         lowercase(p);
390                         idprint(imap, "<- %s\n", p);
391
392                         goto redux;
393                 }else
394                         return confused;
395         }
396         return 0;
397 }
398
399 void
400 parsecap(Imap *imap, char *s)
401 {
402         char *t[32], *p;
403         int n, i;
404
405         s = strdup(s);
406         n = getfields(s, t, nelem(t), 0, " ");
407         for(i = 0; i < n; i++){
408                 if(strncmp(t[i], "auth=", 5) == 0){
409                         p = t[i] + 5;
410                         if(strcmp(p, "cram-md5") == 0)
411                                 imap->cap |= Ccram;
412                         if(strcmp(p, "ntlm") == 0)
413                                 imap->cap |= Cntlm;
414                 }else if(strcmp(t[i], "logindisabled") == 0)
415                         imap->cap |= Cnolog;
416         }
417         free(s);
418 }
419
420 /*
421  *  get imap4 response line.  there might be various
422  *  data or other informational lines mixed in.
423  */
424 static char*
425 imap4resp0(Imap *imap, Mailbox *mb, Message *m)
426 {
427         char *e, *line, *p, *ep, *op, *q, *verb;
428         int n, unexp;
429         static char error[256];
430
431         unexp = 0;
432         while(p = Brdline(&imap->bin, '\n')){
433                 ep = p + Blinelen(&imap->bin);
434                 while(ep > p && (ep[-1] == '\n' || ep[-1] == '\r'))
435                         *--ep = '\0';
436                 idprint(imap, "<- %s\n", p);
437                 if(unexp && p[0] != '9' && p[1] != 'x')
438                 if(strtoul(p + 2, &p, 10) != imap->tag)
439                         continue;
440                 if(p[0] != '+')
441                         lowercase(p);           /* botch */
442
443                 switch(p[0]){
444                 case '+':                               /* cram challenge */
445                         if(ep - p > 2)
446                                 return p + 2;
447                         break;
448                 case '*':
449                         if(p[1] != ' ')
450                                 continue;
451                         p += 2;
452                         line = p;
453                         n = strtol(p, &p, 10);
454                         if(*p == ' ')
455                                 p++;
456                         verb = p;
457         
458                         if(p = strchr(verb, ' '))
459                                 p++;
460                         else
461                                 p = verb + strlen(verb);
462
463                         switch(verbcode(verb)){
464                         case Bye:
465                                 /* early disconnect */
466                                 snprint(error, sizeof error, "%s", p);
467                                 return error;
468                         case Ok:
469                         case No:
470                         case Bad:
471                                 /* human readable text at p; */
472                                 break;
473                         case Exists:
474                                 imap->nmsg = n;
475                                 break;
476                         case Cap:
477                                 parsecap(imap, p);
478                                 break;
479                         case Status:
480                                 /* * status inbox (messages 2 uidvalidity 960164964) */
481                                 if(q = strstr(p, "messages"))
482                                         imap->nmsg = strtoul(q + 8, 0, 10);
483                                 if(q = strstr(p, "uidvalidity"))
484                                         imap->validity = strtoul(q + 11, 0, 10);
485                                 break;
486                         case Fetch:
487                                 if(*p == '('){
488                                         p++;
489                                         if(ep[-1] == ')')
490                                                 *--ep = 0;
491                                 }
492                                 if(e = fetchrsp(imap, p, mb, m))
493                                         eprint("imap: fetchrsp: %s\n", e);
494                                 imap->nuid++;
495                                 break;
496                         case Auth:
497                                 break;
498                         }
499                         if(imap->tag == 0)
500                                 return line;
501                         break;
502                 case '9':               /* response to our message */
503                         op = p;
504                         if(p[1] == 'x' && strtoul(p + 2, &p, 10) == imap->tag){
505                                 while(*p == ' ')
506                                         p++;
507                                 imap->tag++;
508                                 return p;
509                         }
510                         eprint("imap: expected %lud; got %s\n", imap->tag, op);
511                         break;
512                 default:
513                         if(imap->flags&Fdebug || *p){
514                                 eprint("imap: unexpected line: %s\n", p);
515                                 unexp = 1;
516                         }
517                 }
518         }
519         snprint(error, sizeof error, "i/o error: %r\n");
520         return error;
521 }
522
523 static char*
524 imap4resp(Imap *i)
525 {
526         return imap4resp0(i, 0, 0);
527 }
528
529 static int
530 isokay(char *resp)
531 {
532         return cistrncmp(resp, "OK", 2) == 0;
533 }
534
535 static char*
536 findflag(int idx)
537 {
538         int i;
539
540         for(i = 0; i < nelem(ftab); i++)
541                 if(ftab[i].e == 1<<idx)
542                         return ftab[i].flag;
543         return nil;
544 }
545
546 static void
547 imap4modflags(Mailbox *mb, Message *m, int flags)
548 {
549         char buf[128], *p, *e, *fs;
550         int i, f;
551         Imap *imap;
552
553         imap = mb->aux;
554         e = buf + sizeof buf;
555         p = buf;
556         f = flags & ~Frecent;
557         for(i = 0; i < Nflags; i++)
558                 if(f & 1<<i && (fs = findflag(i)))
559                         p = seprint(p, e, "%s ", fs);
560         if(p > buf){
561                 p[-1] = 0;
562                 imap4cmd(imap, "uid store %lud flags (%s)", (ulong)m->imapuid, buf);
563                 imap4resp(imap);
564         }
565 }
566
567 static char*
568 imap4cram(Imap *imap)
569 {
570         char *s, *p, ch[128], usr[64], rbuf[128], ubuf[128], ebuf[192];
571         int i, n, l;
572
573         fmtinstall('[', encodefmt);
574
575         imap4cmd(imap, "authenticate cram-md5");
576         p = imap4resp(imap);
577         if(p == nil)
578                 return "no challenge";
579         l = dec64((uchar*)ch, sizeof ch, p, strlen(p));
580         if(l == -1)
581                 return "bad base64";
582         ch[l] = 0;
583         idprint(imap, "challenge [%s]\n", ch);
584
585         if(imap->user == nil)
586                 imap->user = getlog();
587         n = auth_respond(ch, l, usr, sizeof usr, rbuf, sizeof rbuf, auth_getkey,
588                 "proto=cram role=client server=%q user=%s", imap->host, imap->user);
589         if(n == -1)
590                 return "cannot find IMAP password";
591         for(i = 0; i < n; i++)
592                 if(rbuf[i] >= 'A' && rbuf[i] <= 'Z')
593                         rbuf[i] += 'a' - 'A';
594         l = snprint(ubuf, sizeof ubuf, "%s %.*s", usr, n, rbuf);
595         idprint(imap, "raw cram [%s]\n", ubuf);
596         snprint(ebuf, sizeof ebuf, "%.*[", l, ubuf);
597
598         imap->tag = 1;
599         idprint(imap, "-> %s\n", ebuf);
600         Bprint(&imap->bout, "%s\r\n", ebuf);
601         Bflush(&imap->bout);
602
603         if(!isokay(s = imap4resp(imap)))
604                 return s;
605         return nil;
606 }
607
608 /*
609  *  authenticate to IMAP4 server using NTLM (untested)
610  * 
611  *  http://davenport.sourceforge.net/ntlm.html#ntlmImapAuthentication
612  *  http://msdn.microsoft.com/en-us/library/cc236621%28PROT.13%29.aspx
613  */
614 static uchar*
615 psecb(uchar *p, uint o, int n)
616 {
617         p[0] = n;
618         p[1] = n>>8;
619         p[2] = n;
620         p[3] = n>>8;
621         p[4] = o;
622         p[5] = o>>8;
623         p[6] = o>>16;
624         p[7] = o>>24;
625         return p+8;
626 }
627
628 static uchar*
629 psecq(uchar *q, char *s, int n)
630 {
631         memcpy(q, s, n);
632         return q+n;
633 }
634
635 static char*
636 imap4ntlm(Imap *imap)
637 {
638         char *s, ruser[64], enc[256];
639         uchar buf[128], *p, *ep, *q, *eq, *chal;
640         int n;
641         MSchapreply mcr;
642
643         imap4cmd(imap, "authenticate ntlm");
644         imap4resp(imap);
645
646         /* simple NtLmNegotiate blob with NTLM+OEM flags */
647         imap4cmd(imap, "TlRMTVNTUAABAAAAAgIAAA==");
648         s = imap4resp(imap);
649         n = dec64(buf, sizeof buf, s, strlen(s));
650         if(n < 32 || memcmp(buf, "NTLMSSP", 8) != 0)
651                 return "bad NtLmChallenge";
652         chal = buf+24;
653
654         if(auth_respond(chal, 8, ruser, sizeof ruser,
655                         &mcr, sizeof mcr, auth_getkey,
656                         "proto=mschap role=client service=imap server=%q user?",
657                         imap->host) < 0)
658                 return "auth_respond failed";
659
660         /* prepare NtLmAuthenticate blob */
661
662         memset(buf, sizeof buf, 0);
663         p = buf;
664         ep = p + 8 + 6*8 + 2*4;
665         q = ep;
666         eq = buf + sizeof buf;
667
668
669         memcpy(p, "NTLMSSP", 8);        /* magic */
670         p += 8;
671
672         *p++ = 3;
673         *p++ = 0;
674         *p++ = 0;
675         *p++ = 0;
676
677         p = psecb(p, q-buf, 24);                /* LMresp */
678         q = psecq(q, mcr.LMresp, 24);
679
680         p = psecb(p, q-buf, 24);                /* NTresp */
681         q = psecq(q, mcr.NTresp, 24);
682
683         p = psecb(p, q-buf, 0);         /* realm */
684
685         n = strlen(ruser);
686         p = psecb(p, q-buf, n);         /* user name */
687         q = psecq(q, ruser, n);
688
689         p = psecb(p, q-buf, 0);         /* workstation name */
690         p = psecb(p, q-buf, 0);         /* session key */
691
692         *p++ = 0x02;                    /* flags: oem(2)|ntlm(0x200) */
693         *p++ = 0x02;
694         *p++ = 0;
695         *p++ = 0;
696
697         if(p > ep || q > eq)
698                 return "error creating NtLmAuthenticate";
699         enc64(enc, sizeof enc, buf, q-buf);
700
701         imap4cmd(imap, enc);
702         if(!isokay(s = imap4resp(imap)))
703                 return s;
704         return nil;
705 }
706
707 static char*
708 imap4passwd(Imap *imap)
709 {
710         char *s;
711         UserPasswd *up;
712
713         if(imap->user != nil)
714                 up = auth_getuserpasswd(auth_getkey, "proto=pass service=imap server=%q user=%q", imap->host, imap->user);
715         else
716                 up = auth_getuserpasswd(auth_getkey, "proto=pass service=imap server=%q", imap->host);
717         if(up == nil)
718                 return "cannot find IMAP password";
719
720         imap->tag = 1;
721         imap4cmd(imap, "login %Z %Z", up->user, up->passwd);
722         free(up);
723         if(!isokay(s = imap4resp(imap)))
724                 return s;
725         return nil;
726 }
727
728 static char*
729 imap4login(Imap *imap)
730 {
731         char *e;
732
733         if(imap->cap & Ccram)
734                 e = imap4cram(imap);
735         else if(imap->cap & Cntlm)
736                 e = imap4ntlm(imap);
737         else
738                 e = imap4passwd(imap);
739         if(e)
740                 return e;
741         imap4cmd(imap, "select %Z", imap->mbox);
742         if(!isokay(e = imap4resp(imap)))
743                 return e;
744         return nil;
745 }
746
747 static char*
748 imaperrstr(char *host, char *port)
749 {
750         char err[ERRMAX];
751         static char buf[256];
752
753         err[0] = 0;
754         errstr(err, sizeof err);
755         snprint(buf, sizeof buf, "%s/%s:%s", host, port, err);
756         return buf;
757 }
758
759 static void
760 imap4disconnect(Imap *imap)
761 {
762         if(imap->binit){
763                 Bterm(&imap->bin);
764                 Bterm(&imap->bout);
765                 imap->binit = 0;
766         }
767         if(imap->fd >= 0){
768                 close(imap->fd);
769                 imap->fd = -1;
770         }
771 }
772
773 char*
774 capabilties(Imap *imap)
775 {
776         char * err;
777
778         imap4cmd(imap, "capability");
779         imap4resp(imap);
780         err = imap4resp(imap);
781         if(isokay(err))
782                 err = 0;
783         return err;
784 }
785
786 static char*
787 imap4dial(Imap *imap)
788 {
789         char *err, *port;
790
791         if(imap->fd >= 0){
792                 imap4cmd(imap, "noop");
793                 if(isokay(imap4resp(imap)))
794                         return nil;
795                 imap4disconnect(imap);
796         }
797         if(imap->flags & Fssl)
798                 port = "imaps";
799         else
800                 port = "imap";
801         if((imap->fd = dial(netmkaddr(imap->host, "net", port), 0, 0, 0)) < 0)
802                 return imaperrstr(imap->host, port);
803         if(imap->flags & Fssl && (imap->fd = wraptls(imap->fd, imap->host)) < 0){
804                 err = imaperrstr(imap->host, port);
805                 imap4disconnect(imap);
806                 return err;
807         }
808         assert(imap->binit == 0);
809         Binit(&imap->bin, imap->fd, OREAD);
810         Binit(&imap->bout, imap->fd, OWRITE);
811         imap->binit = 1;
812
813         imap->tag = 0;
814         err = imap4resp(imap);
815         if(!isokay(err))
816                 return "error in initial IMAP handshake";
817
818         if((err = capabilties(imap)) || (err = imap4login(imap))){
819                 eprint("imap: err is %s\n", err);
820                 imap4disconnect(imap);
821                 return err;
822         }
823         return nil;
824 }
825
826 static void
827 imap4hangup(Imap *imap)
828 {
829         imap4cmd(imap, "logout");
830         imap4resp(imap);
831         imap4disconnect(imap);
832 }
833
834 /* gmail lies about message sizes */
835 static ulong
836 gmaildiscount(Message *m, uvlong o, ulong l)
837 {
838         if((m->cstate&Cidx) == 0)
839         if(o + l == m->size)
840                 return l + 100 + (o + l)/5;
841         return l;
842 }
843
844 static int
845 imap4fetch(Mailbox *mb, Message *m, uvlong o, ulong l)
846 {
847         Imap *imap;
848
849         imap = mb->aux;
850         if(imap->flags & Fgmail)
851                 l = gmaildiscount(m, o, l);
852         idprint(imap, "uid fetch %lud (body.peek[]<%llud.%lud>)\n", (ulong)m->imapuid, o, l);
853         imap4cmd(imap, "uid fetch %lud (body.peek[]<%llud.%lud>)", (ulong)m->imapuid, o, l);
854         if(!isokay(imap4resp0(imap, mb, m))){
855                 eprint("imap: imap fetch failed\n");
856                 return -1;
857         }
858         return 0;
859 }
860
861 static uvlong
862 datesec(Imap *imap, int i)
863 {
864         int j;
865         uvlong v;
866         Fetchi *f;
867
868         f = imap->f;
869         v = (uvlong)f[i].dates << 8;
870
871         /* shifty; these sequences should be stable. */
872         for(j = i; j-- > 0; )
873                 if(f[i].dates != f[j].dates)
874                         break;
875         v |= i - (j + 1);
876         return v;
877 }
878
879 static void
880 markdel(Mailbox *mb, Message *m, int doplumb)
881 {
882         if(doplumb)
883                 mailplumb(mb, m, 1);
884         m->inmbox = 0;
885         m->deleted = Disappear;
886 }
887
888 static int
889 vcmp(vlong a, vlong b)
890 {
891         a -= b;
892         if(a > 0)
893                 return 1;
894         if(a < 0)
895                 return -1;
896         return 0;
897 }
898
899 static int
900 fetchicmp(Fetchi *f1, Fetchi *f2)
901 {
902         return vcmp(f1->uid, f2->uid);
903 }
904
905 static char*
906 imap4read(Imap *imap, Mailbox *mb, int doplumb, int *new)
907 {
908         char *s;
909         int i, n, c, nnew, ndel;
910         Fetchi *f;
911         Message *m, **ll;
912
913         *new = 0;
914         imap4cmd(imap, "status %Z (messages uidvalidity)", imap->mbox);
915         if(!isokay(s = imap4resp(imap)))
916                 return s;
917
918         imap->nuid = 0;
919         imap->muid = imap->nmsg;
920         imap->f = erealloc(imap->f, imap->nmsg*sizeof imap->f[0]);
921         f = imap->f;
922         n = imap->nmsg;
923
924         if(imap->nmsg > 0){
925                 imap4cmd(imap, "uid fetch 1:* (uid rfc822.size internaldate)");
926                 if(!isokay(s = imap4resp(imap)))
927                         return s;
928         }
929
930         qsort(f, n, sizeof f[0], (int(*)(void*, void*))fetchicmp);
931         nnew = ndel = 0;
932         ll = &mb->root->part;
933         for(i = 0; (m = *ll) != nil || i < n; ){
934                 c = -1;
935                 if(i >= n)
936                         c = 1;
937                 else if(m){
938                         if(m->imapuid == 0)
939                                 m->imapuid = strtoull(m->idxaux, 0, 0);
940                         c = vcmp(f[i].uid, m->imapuid);
941                 }
942                 idprint(imap, "consider %U and %U -> %d\n", i<n? f[i].uid: 0, m? m->imapuid: 1, c);
943                 if(c < 0){
944                         /* new message */
945                         idprint(imap, "new: %U (%U)\n", f[i].uid, m? m->imapuid: 0);
946                         if(f[i].sizes > Maxmsg){
947                                 idprint(imap, "skipping bad size: %lud\n", f[i].sizes);
948                                 i++;
949                                 continue;
950                         }
951                         nnew++;
952                         m = newmessage(mb->root);
953                         m->inmbox = 1;
954                         m->idxaux = smprint("%llud", f[i].uid);
955                         m->imapuid = f[i].uid;
956                         m->fileid = datesec(imap, i);
957                         m->size = f[i].sizes;
958                         m->next = *ll;
959                         *ll = m;
960                         ll = &m->next;
961                         newcachehash(mb, m, doplumb);
962                         putcache(mb, m);
963                         i++;
964                 }else if(c > 0){
965                         /* deleted message; */
966                         idprint(imap, "deleted: %U (%U)\n", i<n? f[i].uid: 0, m? m->imapuid: 0);
967                         ndel++;
968                         markdel(mb, m, doplumb);
969                         ll = &m->next;
970                 }else{
971                         ll = &m->next;
972                         i++;
973                 }
974         }
975
976         *new = nnew;
977         return nil;
978 }
979
980 static void
981 imap4delete(Mailbox *mb, Message *m)
982 {
983         Imap *imap;
984
985         imap = mb->aux;
986         if((ulong)(m->imapuid>>32) == imap->validity){
987                 imap4cmd(imap, "uid store %lud +flags (\\Deleted)", (ulong)m->imapuid);
988                 imap4resp(imap);
989                 imap4cmd(imap, "expunge");
990                 imap4resp(imap);
991 //              if(!isokay(imap4resp(imap))
992 //                      return -1;
993         }
994         m->inmbox = 0;
995 }
996
997 static char*
998 imap4sync(Mailbox *mb, int doplumb, int *new)
999 {
1000         char *err;
1001         Imap *imap;
1002
1003         imap = mb->aux;
1004         if(err = imap4dial(imap))
1005                 goto out;
1006         err = imap4read(imap, mb, doplumb, new);
1007 out:
1008         mb->waketime = (ulong)time(0) + imap->refreshtime;
1009         return err;
1010 }
1011
1012 static char*
1013 imap4ctl(Mailbox *mb, int argc, char **argv)
1014 {
1015         Imap *imap;
1016
1017         imap = mb->aux;
1018         if(argc < 1)
1019                 return Eimap4ctl;
1020
1021         if(argc == 1 && strcmp(argv[0], "debug") == 0){
1022                 imap->flags ^= Fdebug;
1023                 return nil;
1024         }
1025         if(argc == 2 && strcmp(argv[0], "uid") == 0){
1026                 uvlong l;
1027                 Message *m;
1028
1029                 for(m = mb->root->part; m; m = m->next)
1030                         if(strcmp(argv[1], m->name) == 0){
1031                                 l = strtoull(m->idxaux, 0, 0);
1032                                 fprint(2, "uid %s %lud %lud %lud %lud\n", m->name, (ulong)(l>>32), (ulong)l,
1033                                         (ulong)(m->imapuid>>32), (ulong)m->imapuid);
1034                         }
1035                 return nil;
1036         }
1037         if(strcmp(argv[0], "refresh") == 0)
1038                 switch(argc){
1039                 case 1:
1040                         imap->refreshtime = 60;
1041                         return nil;
1042                 case 2:
1043                         imap->refreshtime = atoi(argv[1]);
1044                         return nil;
1045                 }
1046
1047         return Eimap4ctl;
1048 }
1049
1050 static void
1051 imap4close(Mailbox *mb)
1052 {
1053         Imap *imap;
1054
1055         imap = mb->aux;
1056         imap4disconnect(imap);
1057         free(imap->f);
1058         free(imap);
1059 }
1060
1061 static char*
1062 mkmbox(Imap *imap, char *p, char *e)
1063 {
1064         p = seprint(p, e, "%s/box/%s/imap.%s", MAILROOT, getlog(), imap->host);
1065         if(imap->user && strcmp(imap->user, getlog()))
1066                 p = seprint(p, e, ".%s", imap->user);
1067         if(cistrcmp(imap->mbox, "inbox"))
1068                 p = seprint(p, e, ".%s", imap->mbox);
1069         return p;
1070 }
1071
1072 static char*
1073 findmbox(char *p)
1074 {
1075         char *f[10], path[Pathlen];
1076         int nf;
1077
1078         snprint(path, sizeof path, "%s", p);
1079         nf = getfields(path, f, 5, 0, "/");
1080         if(nf < 3)
1081                 return nil;
1082         return f[nf - 1];
1083 }
1084
1085 static char*
1086 imap4rename(Mailbox *mb, char *p2, int)
1087 {
1088         char *r, *new;
1089         Imap *imap;
1090
1091         imap = mb->aux;
1092         new = findmbox(p2);
1093         idprint(imap, "rename %s %s\n", imap->mbox, new);
1094         imap4cmd(imap, "rename %s %s", imap->mbox, new);
1095         r = imap4resp(imap);
1096         if(!isokay(r))
1097                 return r;
1098         free(imap->mbox);
1099         imap->mbox = smprint("%s", new);
1100         mkmbox(imap, mb->path, mb->path + sizeof mb->path);
1101         return 0;
1102 }
1103
1104 /*
1105  * incomplete; when we say remove we want to get subfolders, too.
1106  * so we need to to a list, and recursivly nuke folders.
1107  */
1108 static char*
1109 imap4remove(Mailbox *mb, int flags)
1110 {
1111         char *r;
1112         Imap *imap;
1113
1114         imap = mb->aux;
1115         idprint(imap, "remove %s\n", imap->mbox);
1116         imap4cmd(imap, "delete %s", imap->mbox);
1117         r = imap4resp(imap);
1118         if(!isokay(r))
1119                 return r;
1120         if(flags & Rtrunc){
1121                 imap4cmd(imap, "create %s", imap->mbox);
1122                 r = imap4resp(imap);
1123                 if(!isokay(r))
1124                         return r;
1125         }
1126         return 0;
1127 }
1128
1129 char*
1130 imap4mbox(Mailbox *mb, char *path)
1131 {
1132         char *f[10];
1133         uchar flags;
1134         int nf;
1135         Imap *imap;
1136
1137         fmtinstall('Z', Zfmt);
1138         fmtinstall('U', Ufmt);
1139         if(strncmp(path, "/imap/", 6) == 0)
1140                 flags = 0;
1141         else if(strncmp(path, "/imaps/", 7) == 0)
1142                 flags = Fssl;
1143         else
1144                 return Enotme;
1145
1146         path = strdup(path);
1147         if(path == nil)
1148                 return "out of memory";
1149
1150         nf = getfields(path, f, 5, 0, "/");
1151         if(nf < 3){
1152                 free(path);
1153                 return "bad imap path syntax /imap[s]/system[/user[/mailbox]]";
1154         }
1155
1156         imap = emalloc(sizeof *imap);
1157         imap->fd = -1;
1158         imap->freep = path;
1159         imap->flags = flags;
1160         imap->host = f[2];
1161         if(strstr(imap->host, "gmail.com"))
1162                 imap->flags |= Fgmail;
1163         imap->refreshtime = 60;
1164         if(nf < 4)
1165                 imap->user = nil;
1166         else
1167                 imap->user = f[3];
1168         if(nf < 5)
1169                 imap->mbox = strdup("inbox");
1170         else
1171                 imap->mbox = strdup(f[4]);
1172         mkmbox(imap, mb->path, mb->path + sizeof mb->path);
1173         mb->aux = imap;
1174         mb->sync = imap4sync;
1175         mb->close = imap4close;
1176         mb->ctl = imap4ctl;
1177         mb->fetch = imap4fetch;
1178         mb->delete = imap4delete;
1179         mb->rename = imap4rename;
1180 //      mb->remove = imap4remove;
1181         mb->modflags = imap4modflags;
1182         mb->addfrom = 1;
1183         return nil;
1184 }