URI:
       timap4.c - plan9port - [fork] Plan 9 from user space
  HTML git clone git://src.adamsgaard.dk/plan9port
   DIR Log
   DIR Files
   DIR Refs
   DIR README
   DIR LICENSE
       ---
       timap4.c (16518B)
       ---
            1 #include "common.h"
            2 #include <ctype.h>
            3 #include <plumb.h>
            4 #include <libsec.h>
            5 #include <auth.h>
            6 #include "dat.h"
            7 
            8 #pragma varargck argpos imap4cmd 2
            9 #pragma varargck        type        "Z"        char*
           10 
           11 int        doublequote(Fmt*);
           12 int        pipeline = 1;
           13 
           14 /* static char Eio[] = "i/o error"; jpc */
           15 
           16 typedef struct Imap Imap;
           17 struct Imap {
           18         char *freep;        /* free this to free the strings below */
           19 
           20         char *host;
           21         char *user;
           22         char *mbox;
           23 
           24         int mustssl;
           25         int refreshtime;
           26         int debug;
           27 
           28         ulong tag;
           29         ulong validity;
           30         int nmsg;
           31         int size;
           32         char *base;
           33         char *data;
           34 
           35         vlong *uid;
           36         int nuid;
           37         int muid;
           38 
           39         Thumbprint *thumb;
           40 
           41         /* open network connection */
           42         Biobuf bin;
           43         Biobuf bout;
           44         int fd;
           45 };
           46 
           47 static char*
           48 removecr(char *s)
           49 {
           50         char *r, *w;
           51 
           52         for(r=w=s; *r; r++)
           53                 if(*r != '\r')
           54                         *w++ = *r;
           55         *w = '\0';
           56         return s;
           57 }
           58 
           59 /* */
           60 /* send imap4 command */
           61 /* */
           62 static void
           63 imap4cmd(Imap *imap, char *fmt, ...)
           64 {
           65         char buf[128], *p;
           66         va_list va;
           67 
           68         va_start(va, fmt);
           69         p = buf+sprint(buf, "9X%lud ", imap->tag);
           70         vseprint(p, buf+sizeof(buf), fmt, va);
           71         va_end(va);
           72 
           73         p = buf+strlen(buf);
           74         if(p > (buf+sizeof(buf)-3))
           75                 sysfatal("imap4 command too long");
           76 
           77         if(imap->debug)
           78                 fprint(2, "-> %s\n", buf);
           79         strcpy(p, "\r\n");
           80         Bwrite(&imap->bout, buf, strlen(buf));
           81         Bflush(&imap->bout);
           82 }
           83 
           84 enum {
           85         OK,
           86         NO,
           87         BAD,
           88         BYE,
           89         EXISTS,
           90         STATUS,
           91         FETCH,
           92         UNKNOWN
           93 };
           94 
           95 static char *verblist[] = {
           96 [OK]                "OK",
           97 [NO]                "NO",
           98 [BAD]        "BAD",
           99 [BYE]        "BYE",
          100 [EXISTS]        "EXISTS",
          101 [STATUS]        "STATUS",
          102 [FETCH]        "FETCH"
          103 };
          104 
          105 static int
          106 verbcode(char *verb)
          107 {
          108         int i;
          109         char *q;
          110 
          111         if(q = strchr(verb, ' '))
          112                 *q = '\0';
          113 
          114         for(i=0; i<nelem(verblist); i++)
          115                 if(verblist[i] && strcmp(verblist[i], verb)==0){
          116                         if(q)
          117                                 *q = ' ';
          118                         return i;
          119                 }
          120         if(q)
          121                 *q = ' ';
          122         return UNKNOWN;
          123 }
          124 
          125 static void
          126 strupr(char *s)
          127 {
          128         for(; *s; s++)
          129                 if('a' <= *s && *s <= 'z')
          130                         *s += 'A'-'a';
          131 }
          132 
          133 static void
          134 imapgrow(Imap *imap, int n)
          135 {
          136         int i;
          137 
          138         if(imap->data == nil){
          139                 imap->base = emalloc(n+1);
          140                 imap->data = imap->base;
          141                 imap->size = n+1;
          142         }
          143         if(n >= imap->size){
          144                 /* friggin microsoft - reallocate */
          145                 i = imap->data - imap->base;
          146                 imap->base = erealloc(imap->base, i+n+1);
          147                 imap->data = imap->base + i;
          148                 imap->size = n+1;
          149         }
          150 }
          151 
          152 
          153 /* */
          154 /* get imap4 response line.  there might be various  */
          155 /* data or other informational lines mixed in. */
          156 /* */
          157 static char*
          158 imap4resp(Imap *imap)
          159 {
          160         char *line, *p, *ep, *op, *q, *r, *en, *verb;
          161         int i, n;
          162         static char error[256];
          163 
          164         while(p = Brdline(&imap->bin, '\n')){
          165                 ep = p+Blinelen(&imap->bin);
          166                 while(ep > p && (ep[-1]=='\n' || ep[-1]=='\r'))
          167                         *--ep = '\0';
          168 
          169                 if(imap->debug)
          170                         fprint(2, "<- %s\n", p);
          171                 strupr(p);
          172 
          173                 switch(p[0]){
          174                 case '+':
          175                         if(imap->tag == 0)
          176                                 fprint(2, "unexpected: %s\n", p);
          177                         break;
          178 
          179                 /* ``unsolicited'' information; everything happens here. */
          180                 case '*':
          181                         if(p[1]!=' ')
          182                                 continue;
          183                         p += 2;
          184                         line = p;
          185                         n = strtol(p, &p, 10);
          186                         if(*p==' ')
          187                                 p++;
          188                         verb = p;
          189 
          190                         if(p = strchr(verb, ' '))
          191                                 p++;
          192                         else
          193                                 p = verb+strlen(verb);
          194 
          195                         switch(verbcode(verb)){
          196                         case OK:
          197                         case NO:
          198                         case BAD:
          199                                 /* human readable text at p; */
          200                                 break;
          201                         case BYE:
          202                                 /* early disconnect */
          203                                 /* human readable text at p; */
          204                                 break;
          205 
          206                         /* * 32 EXISTS */
          207                         case EXISTS:
          208                                 imap->nmsg = n;
          209                                 break;
          210 
          211                         /* * STATUS Inbox (MESSAGES 2 UIDVALIDITY 960164964) */
          212                         case STATUS:
          213                                 if(q = strstr(p, "MESSAGES"))
          214                                         imap->nmsg = atoi(q+8);
          215                                 if(q = strstr(p, "UIDVALIDITY"))
          216                                         imap->validity = strtoul(q+11, 0, 10);
          217                                 break;
          218 
          219                         case FETCH:
          220                                 /* * 1 FETCH (uid 8889 RFC822.SIZE 3031 body[] {3031} */
          221                                 /* <3031 bytes of data> */
          222                                  /* ) */
          223                                 if(strstr(p, "RFC822.SIZE") && strstr(p, "BODY[]")){
          224                                         if((q = strchr(p, '{'))
          225                                         && (n=strtol(q+1, &en, 0), *en=='}')){
          226                                                 if(imap->data == nil || n >= imap->size)
          227                                                         imapgrow(imap, n);
          228                                                 if((i = Bread(&imap->bin, imap->data, n)) != n){
          229                                                         snprint(error, sizeof error,
          230                                                                 "short read %d != %d: %r\n",
          231                                                                 i, n);
          232                                                         return error;
          233                                                 }
          234                                                 if(imap->debug)
          235                                                         fprint(2, "<- read %d bytes\n", n);
          236                                                 imap->data[n] = '\0';
          237                                                 if(imap->debug)
          238                                                         fprint(2, "<- %s\n", imap->data);
          239                                                 imap->data += n;
          240                                                 imap->size -= n;
          241                                                 p = Brdline(&imap->bin, '\n');
          242                                                 if(imap->debug)
          243                                                         fprint(2, "<- ignoring %.*s\n",
          244                                                                 Blinelen(&imap->bin), p);
          245                                         }else if((q = strchr(p, '"')) && (r = strchr(q+1, '"'))){
          246                                                 *r = '\0';
          247                                                 q++;
          248                                                 n = r-q;
          249                                                 if(imap->data == nil || n >= imap->size)
          250                                                         imapgrow(imap, n);
          251                                                 memmove(imap->data, q, n);
          252                                                 imap->data[n] = '\0';
          253                                                 imap->data += n;
          254                                                 imap->size -= n;
          255                                         }else
          256                                                 return "confused about FETCH response";
          257                                         break;
          258                                 }
          259 
          260                                 /* * 1 FETCH (UID 1 RFC822.SIZE 511) */
          261                                 if(q=strstr(p, "RFC822.SIZE")){
          262                                         imap->size = atoi(q+11);
          263                                         break;
          264                                 }
          265 
          266                                 /* * 1 FETCH (UID 1 RFC822.HEADER {496} */
          267                                 /* <496 bytes of data> */
          268                                  /* ) */
          269                                 /* * 1 FETCH (UID 1 RFC822.HEADER "data") */
          270                                 if(strstr(p, "RFC822.HEADER") || strstr(p, "RFC822.TEXT")){
          271                                         if((q = strchr(p, '{'))
          272                                         && (n=strtol(q+1, &en, 0), *en=='}')){
          273                                                 if(imap->data == nil || n >= imap->size)
          274                                                         imapgrow(imap, n);
          275                                                 if((i = Bread(&imap->bin, imap->data, n)) != n){
          276                                                         snprint(error, sizeof error,
          277                                                                 "short read %d != %d: %r\n",
          278                                                                 i, n);
          279                                                         return error;
          280                                                 }
          281                                                 if(imap->debug)
          282                                                         fprint(2, "<- read %d bytes\n", n);
          283                                                 imap->data[n] = '\0';
          284                                                 if(imap->debug)
          285                                                         fprint(2, "<- %s\n", imap->data);
          286                                                 imap->data += n;
          287                                                 imap->size -= n;
          288                                                 p = Brdline(&imap->bin, '\n');
          289                                                 if(imap->debug)
          290                                                         fprint(2, "<- ignoring %.*s\n",
          291                                                                 Blinelen(&imap->bin), p);
          292                                         }else if((q = strchr(p, '"')) && (r = strchr(q+1, '"'))){
          293                                                 *r = '\0';
          294                                                 q++;
          295                                                 n = r-q;
          296                                                 if(imap->data == nil || n >= imap->size)
          297                                                         imapgrow(imap, n);
          298                                                 memmove(imap->data, q, n);
          299                                                 imap->data[n] = '\0';
          300                                                 imap->data += n;
          301                                                 imap->size -= n;
          302                                         }else
          303                                                 return "confused about FETCH response";
          304                                         break;
          305                                 }
          306 
          307                                 /* * 1 FETCH (UID 1) */
          308                                 /* * 2 FETCH (UID 6) */
          309                                 if(q = strstr(p, "UID")){
          310                                         if(imap->nuid < imap->muid)
          311                                                 imap->uid[imap->nuid++] = ((vlong)imap->validity<<32)|strtoul(q+3, nil, 10);
          312                                         break;
          313                                 }
          314                         }
          315 
          316                         if(imap->tag == 0)
          317                                 return line;
          318                         break;
          319 
          320                 case '9':                /* response to our message */
          321                         op = p;
          322                         if(p[1]=='X' && strtoul(p+2, &p, 10)==imap->tag){
          323                                 while(*p==' ')
          324                                         p++;
          325                                 imap->tag++;
          326                                 return p;
          327                         }
          328                         fprint(2, "expected %lud; got %s\n", imap->tag, op);
          329                         break;
          330 
          331                 default:
          332                         if(imap->debug || *p)
          333                                 fprint(2, "unexpected line: %s\n", p);
          334                 }
          335         }
          336         snprint(error, sizeof error, "i/o error: %r\n");
          337         return error;
          338 }
          339 
          340 static int
          341 isokay(char *resp)
          342 {
          343         return strncmp(resp, "OK", 2)==0;
          344 }
          345 
          346 /* */
          347 /* log in to IMAP4 server, select mailbox, no SSL at the moment */
          348 /* */
          349 static char*
          350 imap4login(Imap *imap)
          351 {
          352         char *s;
          353         UserPasswd *up;
          354 
          355         imap->tag = 0;
          356         s = imap4resp(imap);
          357         if(!isokay(s))
          358                 return "error in initial IMAP handshake";
          359 
          360         if(imap->user != nil)
          361                 up = auth_getuserpasswd(auth_getkey, "proto=pass role=client service=imap server=%q user=%q", imap->host, imap->user);
          362         else
          363                 up = auth_getuserpasswd(auth_getkey, "proto=pass role=client service=imap server=%q", imap->host);
          364         if(up == nil)
          365                 return "cannot find IMAP password";
          366 
          367         imap->tag = 1;
          368         imap4cmd(imap, "LOGIN %Z %Z", up->user, up->passwd);
          369         free(up);
          370         if(!isokay(s = imap4resp(imap)))
          371                 return s;
          372 
          373         imap4cmd(imap, "SELECT %Z", imap->mbox);
          374         if(!isokay(s = imap4resp(imap)))
          375                 return s;
          376 
          377         return nil;
          378 }
          379 
          380 /* */
          381 /* push tls onto a connection */
          382 /* */
          383 int
          384 mypushtls(int fd)
          385 {
          386         int p[2];
          387         char buf[10];
          388 
          389         if(pipe(p) < 0)
          390                 return -1;
          391 
          392         switch(fork()){
          393         case -1:
          394                 close(p[0]);
          395                 close(p[1]);
          396                 return -1;
          397         case 0:
          398                 close(p[1]);
          399                 dup(p[0], 0);
          400                 dup(p[0], 1);
          401                 sprint(buf, "/fd/%d", fd);
          402                 execl("/bin/tlsrelay", "tlsrelay", "-f", buf, nil);
          403                 _exits(nil);
          404         default:
          405                 break;
          406         }
          407         close(fd);
          408         close(p[0]);
          409         return p[1];
          410 }
          411 
          412 /* */
          413 /* dial and handshake with the imap server */
          414 /* */
          415 static char*
          416 imap4dial(Imap *imap)
          417 {
          418         char *err, *port;
          419         uchar digest[SHA1dlen];
          420         int sfd;
          421         TLSconn conn;
          422 
          423         if(imap->fd >= 0){
          424                 imap4cmd(imap, "noop");
          425                 if(isokay(imap4resp(imap)))
          426                         return nil;
          427                 close(imap->fd);
          428                 imap->fd = -1;
          429         }
          430 
          431         if(imap->mustssl)
          432                 port = "imaps";
          433         else
          434                 port = "imap";
          435 
          436         if((imap->fd = dial(netmkaddr(imap->host, "net", port), 0, 0, 0)) < 0)
          437                 return geterrstr();
          438 
          439         if(imap->mustssl){
          440                 memset(&conn, 0, sizeof conn);
          441                 sfd = tlsClient(imap->fd, &conn);
          442                 if(sfd < 0)
          443                         sysfatal("tlsClient: %r");
          444                 if(conn.cert==nil || conn.certlen <= 0)
          445                         sysfatal("server did not provide TLS certificate");
          446                 sha1(conn.cert, conn.certlen, digest, nil);
          447                 if(!imap->thumb || !okThumbprint(digest, imap->thumb)){
          448                         fmtinstall('H', encodefmt);
          449                         sysfatal("server certificate %.*H not recognized", SHA1dlen, digest);
          450                 }
          451                 free(conn.cert);
          452                 close(imap->fd);
          453                 imap->fd = sfd;
          454 
          455                 if(imap->debug){
          456                         char fn[128];
          457                         int fd;
          458 
          459                         snprint(fn, sizeof fn, "%s/ctl", conn.dir);
          460                         fd = open(fn, ORDWR);
          461                         if(fd < 0)
          462                                 fprint(2, "opening ctl: %r\n");
          463                         if(fprint(fd, "debug") < 0)
          464                                 fprint(2, "writing ctl: %r\n");
          465                         close(fd);
          466                 }
          467         }
          468         Binit(&imap->bin, imap->fd, OREAD);
          469         Binit(&imap->bout, imap->fd, OWRITE);
          470 
          471         if(err = imap4login(imap)) {
          472                 close(imap->fd);
          473                 return err;
          474         }
          475 
          476         return nil;
          477 }
          478 
          479 /* */
          480 /* close connection */
          481 /* */
          482 #if 0  /* jpc */
          483 static void
          484 imap4hangup(Imap *imap)
          485 {
          486         imap4cmd(imap, "LOGOUT");
          487         imap4resp(imap);
          488         close(imap->fd);
          489 }
          490 #endif
          491 
          492 /* */
          493 /* download a single message */
          494 /* */
          495 static char*
          496 imap4fetch(Mailbox *mb, Message *m)
          497 {
          498         int i;
          499         char *p, *s, sdigest[2*SHA1dlen+1];
          500         Imap *imap;
          501 
          502         imap = mb->aux;
          503 
          504         imap->size = 0;
          505 
          506         if(!isokay(s = imap4resp(imap)))
          507                 return s;
          508 
          509         p = imap->base;
          510         if(p == nil)
          511                 return "did not get message body";
          512 
          513         removecr(p);
          514         free(m->start);
          515         m->start = p;
          516         m->end = p+strlen(p);
          517         m->bend = m->rbend = m->end;
          518         m->header = m->start;
          519 
          520         imap->base = nil;
          521         imap->data = nil;
          522 
          523         parse(m, 0, mb, 1);
          524 
          525         /* digest headers */
          526         sha1((uchar*)m->start, m->end - m->start, m->digest, nil);
          527         for(i = 0; i < SHA1dlen; i++)
          528                 sprint(sdigest+2*i, "%2.2ux", m->digest[i]);
          529         m->sdigest = s_copy(sdigest);
          530 
          531         return nil;
          532 }
          533 
          534 /* */
          535 /* check for new messages on imap4 server */
          536 /* download new messages, mark deleted messages */
          537 /* */
          538 static char*
          539 imap4read(Imap *imap, Mailbox *mb, int doplumb)
          540 {
          541         char *s;
          542         int i, ignore, nnew, t;
          543         Message *m, *next, **l;
          544 
          545         imap4cmd(imap, "STATUS %Z (MESSAGES UIDVALIDITY)", imap->mbox);
          546         if(!isokay(s = imap4resp(imap)))
          547                 return s;
          548 
          549         imap->nuid = 0;
          550         imap->uid = erealloc(imap->uid, imap->nmsg*sizeof(imap->uid[0]));
          551         imap->muid = imap->nmsg;
          552 
          553         if(imap->nmsg > 0){
          554                 imap4cmd(imap, "UID FETCH 1:* UID");
          555                 if(!isokay(s = imap4resp(imap)))
          556                         return s;
          557         }
          558 
          559         l = &mb->root->part;
          560         for(i=0; i<imap->nuid; i++){
          561                 ignore = 0;
          562                 while(*l != nil){
          563                         if((*l)->imapuid == imap->uid[i]){
          564                                 ignore = 1;
          565                                 l = &(*l)->next;
          566                                 break;
          567                         }else{
          568                                 /* old mail, we don't have it anymore */
          569                                 if(doplumb)
          570                                         mailplumb(mb, *l, 1);
          571                                 (*l)->inmbox = 0;
          572                                 (*l)->deleted = 1;
          573                                 l = &(*l)->next;
          574                         }
          575                 }
          576                 if(ignore)
          577                         continue;
          578 
          579                 /* new message */
          580                 m = newmessage(mb->root);
          581                 m->mallocd = 1;
          582                 m->inmbox = 1;
          583                 m->imapuid = imap->uid[i];
          584 
          585                 /* add to chain, will download soon */
          586                 *l = m;
          587                 l = &m->next;
          588         }
          589 
          590         /* whatever is left at the end of the chain is gone */
          591         while(*l != nil){
          592                 if(doplumb)
          593                         mailplumb(mb, *l, 1);
          594                 (*l)->inmbox = 0;
          595                 (*l)->deleted = 1;
          596                 l = &(*l)->next;
          597         }
          598 
          599         /* download new messages */
          600         t = imap->tag;
          601         if(pipeline)
          602         switch(rfork(RFPROC|RFMEM)){
          603         case -1:
          604                 sysfatal("rfork: %r");
          605         default:
          606                 break;
          607         case 0:
          608                 for(m = mb->root->part; m != nil; m = m->next){
          609                         if(m->start != nil)
          610                                 continue;
          611                         if(imap->debug)
          612                                 fprint(2, "9X%d UID FETCH %lud (UID RFC822.SIZE BODY[])\r\n",
          613                                         t, (ulong)m->imapuid);
          614                         Bprint(&imap->bout, "9X%d UID FETCH %lud (UID RFC822.SIZE BODY[])\r\n",
          615                                 t++, (ulong)m->imapuid);
          616                 }
          617                 Bflush(&imap->bout);
          618                 _exits(nil);
          619         }
          620 
          621         nnew = 0;
          622         for(m=mb->root->part; m!=nil; m=next){
          623                 next = m->next;
          624                 if(m->start != nil)
          625                         continue;
          626 
          627                 if(!pipeline){
          628                         Bprint(&imap->bout, "9X%lud UID FETCH %lud (UID RFC822.SIZE BODY[])\r\n",
          629                                 (ulong)imap->tag, (ulong)m->imapuid);
          630                         Bflush(&imap->bout);
          631                 }
          632 
          633                 if(s = imap4fetch(mb, m)){
          634                         /* message disappeared?  unchain */
          635                         fprint(2, "download %lud: %s\n", (ulong)m->imapuid, s);
          636                         delmessage(mb, m);
          637                         mb->root->subname--;
          638                         continue;
          639                 }
          640                 nnew++;
          641                 if(doplumb)
          642                         mailplumb(mb, m, 0);
          643         }
          644         if(pipeline)
          645                 waitpid();
          646 
          647         if(nnew || mb->vers == 0){
          648                 mb->vers++;
          649                 henter(PATH(0, Qtop), mb->name,
          650                         (Qid){PATH(mb->id, Qmbox), mb->vers, QTDIR}, nil, mb);
          651         }
          652         return nil;
          653 }
          654 
          655 /* */
          656 /* sync mailbox */
          657 /* */
          658 static void
          659 imap4purge(Imap *imap, Mailbox *mb)
          660 {
          661         int ndel;
          662         Message *m, *next;
          663 
          664         ndel = 0;
          665         for(m=mb->root->part; m!=nil; m=next){
          666                 next = m->next;
          667                 if(m->deleted && m->refs==0){
          668                         if(m->inmbox && (ulong)(m->imapuid>>32)==imap->validity){
          669                                 imap4cmd(imap, "UID STORE %lud +FLAGS (\\Deleted)", (ulong)m->imapuid);
          670                                 if(isokay(imap4resp(imap))){
          671                                         ndel++;
          672                                         delmessage(mb, m);
          673                                 }
          674                         }else
          675                                 delmessage(mb, m);
          676                 }
          677         }
          678 
          679         if(ndel){
          680                 imap4cmd(imap, "EXPUNGE");
          681                 imap4resp(imap);
          682         }
          683 }
          684 
          685 /* */
          686 /* connect to imap4 server, sync mailbox */
          687 /* */
          688 static char*
          689 imap4sync(Mailbox *mb, int doplumb)
          690 {
          691         char *err;
          692         Imap *imap;
          693 
          694         imap = mb->aux;
          695 
          696         if(err = imap4dial(imap)){
          697                 mb->waketime = time(0) + imap->refreshtime;
          698                 return err;
          699         }
          700 
          701         if((err = imap4read(imap, mb, doplumb)) == nil){
          702                 imap4purge(imap, mb);
          703                 mb->d->atime = mb->d->mtime = time(0);
          704         }
          705         /*
          706          * don't hang up; leave connection open for next time.
          707          */
          708         /* imap4hangup(imap); */
          709         mb->waketime = time(0) + imap->refreshtime;
          710         return err;
          711 }
          712 
          713 static char Eimap4ctl[] = "bad imap4 control message";
          714 
          715 static char*
          716 imap4ctl(Mailbox *mb, int argc, char **argv)
          717 {
          718         int n;
          719         Imap *imap;
          720 
          721         imap = mb->aux;
          722         if(argc < 1)
          723                 return Eimap4ctl;
          724 
          725         if(argc==1 && strcmp(argv[0], "debug")==0){
          726                 imap->debug = 1;
          727                 return nil;
          728         }
          729 
          730         if(argc==1 && strcmp(argv[0], "nodebug")==0){
          731                 imap->debug = 0;
          732                 return nil;
          733         }
          734 
          735         if(argc==1 && strcmp(argv[0], "thumbprint")==0){
          736                 if(imap->thumb)
          737                         freeThumbprints(imap->thumb);
          738                 imap->thumb = initThumbprints("/sys/lib/tls/mail", "/sys/lib/tls/mail.exclude");
          739         }
          740         if(strcmp(argv[0], "refresh")==0){
          741                 if(argc==1){
          742                         imap->refreshtime = 60;
          743                         return nil;
          744                 }
          745                 if(argc==2){
          746                         n = atoi(argv[1]);
          747                         if(n < 15)
          748                                 return Eimap4ctl;
          749                         imap->refreshtime = n;
          750                         return nil;
          751                 }
          752         }
          753 
          754         return Eimap4ctl;
          755 }
          756 
          757 /* */
          758 /* free extra memory associated with mb */
          759 /* */
          760 static void
          761 imap4close(Mailbox *mb)
          762 {
          763         Imap *imap;
          764 
          765         imap = mb->aux;
          766         free(imap->freep);
          767         free(imap->base);
          768         free(imap->uid);
          769         if(imap->fd >= 0)
          770                 close(imap->fd);
          771         free(imap);
          772 }
          773 
          774 /* */
          775 /* open mailboxes of the form /imap/host/user */
          776 /* */
          777 char*
          778 imap4mbox(Mailbox *mb, char *path)
          779 {
          780         char *f[10];
          781         int mustssl, nf;
          782         Imap *imap;
          783 
          784         quotefmtinstall();
          785         fmtinstall('Z', doublequote);
          786         if(strncmp(path, "/imap/", 6) != 0 && strncmp(path, "/imaps/", 7) != 0)
          787                 return Enotme;
          788         mustssl = (strncmp(path, "/imaps/", 7) == 0);
          789 
          790         path = strdup(path);
          791         if(path == nil)
          792                 return "out of memory";
          793 
          794         nf = getfields(path, f, 5, 0, "/");
          795         if(nf < 3){
          796                 free(path);
          797                 return "bad imap path syntax /imap[s]/system[/user[/mailbox]]";
          798         }
          799 
          800         imap = emalloc(sizeof(*imap));
          801         imap->fd = -1;
          802         imap->debug = debug;
          803         imap->freep = path;
          804         imap->mustssl = mustssl;
          805         imap->host = f[2];
          806         if(nf < 4)
          807                 imap->user = nil;
          808         else
          809                 imap->user = f[3];
          810         if(nf < 5)
          811                 imap->mbox = "Inbox";
          812         else
          813                 imap->mbox = f[4];
          814         imap->thumb = initThumbprints("/sys/lib/tls/mail", "/sys/lib/tls/mail.exclude");
          815 
          816         mb->aux = imap;
          817         mb->sync = imap4sync;
          818         mb->close = imap4close;
          819         mb->ctl = imap4ctl;
          820         mb->d = emalloc(sizeof(*mb->d));
          821         /*mb->fetch = imap4fetch; */
          822 
          823         return nil;
          824 }
          825 
          826 /* */
          827 /* Formatter for %" */
          828 /* Use double quotes to protect white space, frogs, \ and " */
          829 /* */
          830 enum
          831 {
          832         Qok = 0,
          833         Qquote,
          834         Qbackslash
          835 };
          836 
          837 static int
          838 needtoquote(Rune r)
          839 {
          840         if(r >= Runeself)
          841                 return Qquote;
          842         if(r <= ' ')
          843                 return Qquote;
          844         if(r=='\\' || r=='"')
          845                 return Qbackslash;
          846         return Qok;
          847 }
          848 
          849 int
          850 doublequote(Fmt *f)
          851 {
          852         char *s, *t;
          853         int w, quotes;
          854         Rune r;
          855 
          856         s = va_arg(f->args, char*);
          857         if(s == nil || *s == '\0')
          858                 return fmtstrcpy(f, "\"\"");
          859 
          860         quotes = 0;
          861         for(t=s; *t; t+=w){
          862                 w = chartorune(&r, t);
          863                 quotes |= needtoquote(r);
          864         }
          865         if(quotes == 0)
          866                 return fmtstrcpy(f, s);
          867 
          868         fmtrune(f, '"');
          869         for(t=s; *t; t+=w){
          870                 w = chartorune(&r, t);
          871                 if(needtoquote(r) == Qbackslash)
          872                         fmtrune(f, '\\');
          873                 fmtrune(f, r);
          874         }
          875         return fmtrune(f, '"');
          876 }