1 /* imap_proxy.c - IMAP proxy support functions
2  *
3  * Copyright (c) 1994-2008 Carnegie Mellon University.  All rights reserved.
4  *
5  * Redistribution and use in source and binary forms, with or without
6  * modification, are permitted provided that the following conditions
7  * are met:
8  *
9  * 1. Redistributions of source code must retain the above copyright
10  *    notice, this list of conditions and the following disclaimer.
11  *
12  * 2. Redistributions in binary form must reproduce the above copyright
13  *    notice, this list of conditions and the following disclaimer in
14  *    the documentation and/or other materials provided with the
15  *    distribution.
16  *
17  * 3. The name "Carnegie Mellon University" must not be used to
18  *    endorse or promote products derived from this software without
19  *    prior written permission. For permission or any legal
20  *    details, please contact
21  *      Carnegie Mellon University
22  *      Center for Technology Transfer and Enterprise Creation
23  *      4615 Forbes Avenue
24  *      Suite 302
25  *      Pittsburgh, PA  15213
26  *      (412) 268-7393, fax: (412) 268-7395
27  *      innovation@andrew.cmu.edu
28  *
29  * 4. Redistributions of any form whatsoever must retain the following
30  *    acknowledgment:
31  *    "This product includes software developed by Computing Services
32  *     at Carnegie Mellon University (http://www.cmu.edu/computing/)."
33  *
34  * CARNEGIE MELLON UNIVERSITY DISCLAIMS ALL WARRANTIES WITH REGARD TO
35  * THIS SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
36  * AND FITNESS, IN NO EVENT SHALL CARNEGIE MELLON UNIVERSITY BE LIABLE
37  * FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
38  * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN
39  * AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING
40  * OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
41  */
42 
43 #include <config.h>
44 
45 #include <ctype.h>
46 #include <stdio.h>
47 #include <string.h>
48 #include <sysexits.h>
49 #include <syslog.h>
50 #include <sys/un.h>
51 
52 #include "assert.h"
53 #include "acl.h"
54 #include "annotate.h"
55 #include "backend.h"
56 #include "global.h"
57 #include "imap_proxy.h"
58 #include "proxy.h"
59 #include "mboxname.h"
60 #include "mupdate-client.h"
61 #include "partlist.h"
62 #include "prot.h"
63 #include "util.h"
64 #include "xmalloc.h"
65 
66 /* generated headers are not necessarily in current directory */
67 #include "imap/imap_err.h"
68 
69 extern unsigned int proxy_cmdcnt;
70 extern struct protstream *imapd_in, *imapd_out;
71 extern struct backend *backend_inbox, *backend_current, **backend_cached;
72 extern char *imapd_userid, *proxy_userid;
73 extern struct namespace imapd_namespace;
74 
75 static partlist_t *server_parts = NULL;
76 
77 static void proxy_part_filldata(partlist_t *part_list, int idx);
78 
imap_postcapability(struct backend * s)79 static void imap_postcapability(struct backend *s)
80 {
81     if (CAPA(s, CAPA_SASL_IR)) {
82         /* server supports initial response in AUTHENTICATE command */
83         s->prot->u.std.sasl_cmd.maxlen = USHRT_MAX;
84     }
85 }
86 
87 struct protocol_t imap_protocol =
88 { "imap", "imap", TYPE_STD,
89   { { { 1, NULL },
90       { "C01 CAPABILITY", NULL, "C01 ", imap_postcapability,
91         CAPAF_MANY_PER_LINE,
92         { { "AUTH", CAPA_AUTH },
93           { "STARTTLS", CAPA_STARTTLS },
94           { "COMPRESS=DEFLATE", CAPA_COMPRESS },
95           { "IDLE", CAPA_IDLE },
96           { "MUPDATE", CAPA_MUPDATE },
97           { "MULTIAPPEND", CAPA_MULTIAPPEND },
98           { "METADATA", CAPA_METADATA },
99           { "RIGHTS=kxte", CAPA_ACLRIGHTS },
100           { "LIST-EXTENDED", CAPA_LISTEXTENDED },
101           { "SASL-IR", CAPA_SASL_IR },
102           { "X-REPLICATION", CAPA_REPLICATION },
103           { NULL, 0 } } },
104       { "S01 STARTTLS", "S01 OK", "S01 NO", 0 },
105       { "A01 AUTHENTICATE", 0, 0, "A01 OK", "A01 NO", "+ ", "*",
106         NULL, AUTO_CAPA_AUTH_OK },
107       { "Z01 COMPRESS DEFLATE", "* ", "Z01 OK" },
108       { "N01 NOOP", "* ", "N01 OK" },
109       { "Q01 LOGOUT", "* ", "Q01 " } } }
110 };
111 
proxy_gentag(char * tag,size_t len)112 void proxy_gentag(char *tag, size_t len)
113 {
114     snprintf(tag, len, "PROXY%d", proxy_cmdcnt++);
115 }
116 
proxy_findinboxserver(const char * userid)117 struct backend *proxy_findinboxserver(const char *userid)
118 {
119     mbentry_t *mbentry = NULL;
120     struct backend *s = NULL;
121 
122     char *inbox = mboxname_user_mbox(userid, NULL);
123     int r = mboxlist_lookup(inbox, &mbentry, NULL);
124     free(inbox);
125 
126     if (r) return NULL;
127 
128     if (mbentry->mbtype & MBTYPE_REMOTE) {
129         s = proxy_findserver(mbentry->server, &imap_protocol,
130                              proxy_userid, &backend_cached,
131                              &backend_current, &backend_inbox, imapd_in);
132     }
133 
134     mboxlist_entry_free(&mbentry);
135 
136     return s;
137 }
138 
139 /* pipe_response() reads from 's->in' until either the tagged response
140    starting with 'tag' appears, or if 'tag' is NULL, to the end of the
141    current line.  If 'include_last' is set, the last/tagged line is included
142    in the output, otherwise the last/tagged line is stored in 's->last_result'.
143    In either case, the result of the tagged command is returned.
144 
145    's->last_result' assumes that tagged responses don't contain literals.
146    Unfortunately, the IMAP grammar allows them
147 
148    force_notfatal says to not fatal() if we lose connection to backend_current
149    even though it is in 95% of the cases, a good idea...
150 */
pipe_response(struct backend * s,const char * tag,int include_last,int force_notfatal)151 static int pipe_response(struct backend *s, const char *tag, int include_last,
152                          int force_notfatal)
153 {
154     char buf[2048];
155     char eol[128];
156     unsigned sl;
157     int cont = 0, last = !tag, r = PROXY_OK;
158     size_t taglen = 0;
159 
160     s->timeout->mark = time(NULL) + IDLE_TIMEOUT;
161 
162     if (tag) {
163         taglen = strlen(tag);
164         if(taglen >= sizeof(buf) + 1) {
165             fatal("tag too large",EX_TEMPFAIL);
166         }
167     }
168 
169     buf_reset(&s->last_result);
170 
171     /* the only complication here are literals */
172     do {
173         /* if 'cont' is set, we're looking at the continuation to a very
174            long line.
175            if 'last' is set, we've seen the tag we're looking for, we're
176            just reading the end of the line. */
177         if (!cont) eol[0] = '\0';
178 
179         if (!prot_fgets(buf, sizeof(buf), s->in)) {
180             /* uh oh */
181             if(s == backend_current && !force_notfatal)
182                 fatal("Lost connection to selected backend", EX_UNAVAILABLE);
183             proxy_downserver(s);
184             return PROXY_NOCONNECTION;
185         }
186 
187         sl = strlen(buf);
188 
189         if (tag) {
190             /* Check for the tagged line */
191             if (!cont && buf[taglen] == ' ' && !strncmp(tag, buf, taglen)) {
192 
193                 switch (buf[taglen + 1]) {
194                 case 'O': case 'o':
195                     r = PROXY_OK;
196                     break;
197                 case 'N': case 'n':
198                     r = PROXY_NO;
199                     break;
200                 case 'B': case 'b':
201                     r = PROXY_BAD;
202                     break;
203                 default: /* huh? no result? */
204                     if(s == backend_current && !force_notfatal)
205                         fatal("Lost connection to selected backend",
206                               EX_UNAVAILABLE);
207                     proxy_downserver(s);
208                     r = PROXY_NOCONNECTION;
209                     break;
210                 }
211 
212                 last = 1;
213             }
214 
215             if (last && !include_last) {
216                 /* Store the tagged line */
217                 buf_appendcstr(&s->last_result, buf+taglen+1);
218                 buf_cstring(&s->last_result);
219             }
220         }
221 
222         if (sl == (sizeof(buf) - 1) && buf[sl-1] != '\n') {
223             /* only got part of a line */
224             /* we save the last 64 characters in case it has important
225                literal information */
226             strcpy(eol, buf + sl - 64);
227 
228             /* write out this part, but we have to keep reading until we
229                hit the end of the line */
230             if (!last || include_last) prot_write(imapd_out, buf, sl);
231             cont = 1;
232             continue;
233         } else {                /* we got the end of the line */
234             int i;
235             int litlen = 0, islit = 0;
236 
237             if (!last || include_last) prot_write(imapd_out, buf, sl);
238 
239             /* now we have to see if this line ends with a literal */
240             if (sl < 64) {
241                 strcat(eol, buf);
242             } else {
243                 strcat(eol, buf + sl - 63);
244             }
245 
246             /* eol now contains the last characters from the line; we want
247                to see if we've hit a literal */
248             i = strlen(eol);
249             if (i >= 4 &&
250                 eol[i-1] == '\n' && eol[i-2] == '\r' && eol[i-3] == '}') {
251                 /* possible literal */
252                 i -= 4;
253                 while (i > 0 && eol[i] != '{' && Uisdigit(eol[i])) {
254                     i--;
255                 }
256                 if (eol[i] == '{') {
257                     islit = 1;
258                     litlen = atoi(eol + i + 1);
259                 }
260             }
261 
262             /* copy the literal over */
263             if (islit) {
264                 while (litlen > 0) {
265                     int j = (litlen > (int) sizeof(buf) ?
266                              (int) sizeof(buf) : litlen);
267 
268                     j = prot_read(s->in, buf, j);
269                     if(!j) {
270                         /* EOF or other error */
271                         return -1;
272                     }
273                     if (!last || include_last) prot_write(imapd_out, buf, j);
274                     litlen -= j;
275                 }
276 
277                 /* none of our saved information has any relevance now */
278                 eol[0] = '\0';
279 
280                 /* have to keep going for the end of the line */
281                 cont = 1;
282                 continue;
283             }
284         }
285 
286         /* ok, let's read another line */
287         cont = 0;
288 
289     } while (!last || cont);
290 
291     return r;
292 }
293 
pipe_until_tag(struct backend * s,const char * tag,int force_notfatal)294 int pipe_until_tag(struct backend *s, const char *tag, int force_notfatal)
295 {
296     return pipe_response(s, tag, 0, force_notfatal);
297 }
298 
pipe_including_tag(struct backend * s,const char * tag,int force_notfatal)299 int pipe_including_tag(struct backend *s, const char *tag, int force_notfatal)
300 {
301     int r;
302 
303     r = pipe_response(s, tag, 1, force_notfatal);
304     if (r == PROXY_NOCONNECTION) {
305         /* don't have to worry about downing the server, since
306          * pipe_until_tag does that for us */
307         prot_printf(imapd_out, "%s NO %s\r\n", tag,
308                     error_message(IMAP_SERVER_UNAVAILABLE));
309     }
310     return r;
311 }
312 
pipe_to_end_of_response(struct backend * s,int force_notfatal)313 static int pipe_to_end_of_response(struct backend *s, int force_notfatal)
314 {
315     return pipe_response(s, NULL, 1, force_notfatal);
316 }
317 
318 /* copy our current input to 's' until we hit a true EOL.
319 
320    'optimistic_literal' is how happy we should be about assuming
321    that a command will go through by converting synchronizing literals of
322    size less than optimistic_literal to nonsync
323 
324    returns 0 on success, <0 on big failure, >0 on full command not sent */
pipe_command(struct backend * s,int optimistic_literal)325 int pipe_command(struct backend *s, int optimistic_literal)
326 {
327     char buf[2048];
328     char eol[128];
329     int sl;
330 
331     s->timeout->mark = time(NULL) + IDLE_TIMEOUT;
332 
333     eol[0] = '\0';
334 
335     /* again, the complication here are literals */
336     for (;;) {
337         if (!prot_fgets(buf, sizeof(buf), imapd_in)) {
338             /* uh oh */
339             return -1;
340         }
341 
342         sl = strlen(buf);
343 
344         if (sl == (sizeof(buf) - 1) && buf[sl-1] != '\n') {
345             /* only got part of a line */
346             strcpy(eol, buf + sl - 64);
347 
348             /* and write this out, except for what we've saved */
349             prot_write(s->out, buf, sl - 64);
350             continue;
351         } else {
352             int i, nonsynch = 0, islit = 0, litlen = 0;
353 
354             if (sl < 64) {
355                 strcat(eol, buf);
356             } else {
357                 /* write out what we have, and copy the last 64 characters
358                    to eol */
359                 prot_printf(s->out, "%s", eol);
360                 prot_write(s->out, buf, sl - 64);
361                 strcpy(eol, buf + sl - 64);
362             }
363 
364             /* now determine if eol has a literal in it */
365             i = strlen(eol);
366             if (i >= 4 &&
367                 eol[i-1] == '\n' && eol[i-2] == '\r' && eol[i-3] == '}') {
368                 /* possible literal */
369                 i -= 4;
370                 if (eol[i] == '+') {
371                     nonsynch = 1;
372                     i--;
373                 }
374                 while (i > 0 && eol[i] != '{' && Uisdigit(eol[i])) {
375                     i--;
376                 }
377                 if (eol[i] == '{') {
378                     islit = 1;
379                     litlen = atoi(eol + i + 1);
380                 }
381             }
382 
383             if (islit) {
384                 if (nonsynch) {
385                     prot_write(s->out, eol, strlen(eol));
386                 } else if (!nonsynch && (litlen <= optimistic_literal)) {
387                     prot_printf(imapd_out, "+ i am an optimist\r\n");
388                     prot_write(s->out, eol, strlen(eol) - 3);
389                     /* need to insert a + to turn it into a nonsynch */
390                     prot_printf(s->out, "+}\r\n");
391                 } else {
392                     /* we do a standard synchronizing literal */
393                     prot_write(s->out, eol, strlen(eol));
394                     /* but here the game gets tricky... */
395                     prot_fgets(buf, sizeof(buf), s->in);
396                     /* but for now we cheat */
397                     prot_write(imapd_out, buf, strlen(buf));
398                     if (buf[0] != '+' && buf[1] != ' ') {
399                         /* char *p = strchr(buf, ' '); */
400                         /* strncpy(s->last_result, p + 1, LAST_RESULT_LEN);*/
401 
402                         /* stop sending command now */
403                         return 1;
404                     }
405                 }
406 
407                 /* gobble literal and sent it onward */
408                 while (litlen > 0) {
409                     int j = (litlen > (int) sizeof(buf) ?
410                              (int) sizeof(buf) : litlen);
411 
412                     j = prot_read(imapd_in, buf, j);
413                     if(!j) {
414                         /* EOF or other error */
415                         return -1;
416                     }
417                     prot_write(s->out, buf, j);
418                     litlen -= j;
419                 }
420 
421                 eol[0] = '\0';
422 
423                 /* have to keep going for the send of the command */
424                 continue;
425             } else {
426                 /* no literal, so we're done! */
427                 prot_write(s->out, eol, strlen(eol));
428 
429                 return 0;
430             }
431         }
432     }
433 }
434 
print_listresponse(unsigned cmd,const char * extname,char hier_sep,uint32_t attributes,struct buf * extraflags)435 void print_listresponse(unsigned cmd, const char *extname, char hier_sep,
436                         uint32_t attributes, struct buf *extraflags)
437 {
438     const struct mbox_name_attribute *attr;
439     const char *resp, *sep;
440 
441     switch (cmd) {
442     case LIST_CMD_LSUB:
443         resp = "LSUB";
444         break;
445     case LIST_CMD_XLIST:
446         resp = "XLIST";
447         break;
448     default:
449         resp = "LIST";
450         break;
451     }
452 
453     prot_printf(imapd_out, "* %s (", resp);
454 
455     for (sep = "", attr = mbox_name_attributes; attr->id; attr++) {
456         if (attributes & attr->flag) {
457             prot_printf(imapd_out, "%s%s", sep, attr->id);
458             sep = " ";
459         }
460     }
461 
462     if (extraflags && buf_len(extraflags)) {
463         prot_printf(imapd_out, "%s%s", sep, buf_cstring(extraflags));
464     }
465 
466     prot_printf(imapd_out, ") \"%c\" ", hier_sep);
467 
468     prot_printastring(imapd_out, extname);
469 
470     if (attributes & MBOX_ATTRIBUTE_CHILDINFO_SUBSCRIBED) {
471         prot_puts(imapd_out, " (CHILDINFO (");
472         /* RFC 5258:
473          *     ; Note 2: The selection options are always returned
474          *     ; quoted, unlike their specification in
475          *     ; the extended LIST command.
476          */
477         if (attributes & MBOX_ATTRIBUTE_CHILDINFO_SUBSCRIBED)
478             prot_puts(imapd_out, "\"SUBSCRIBED\"");
479         prot_puts(imapd_out, "))");
480     }
481 
482     prot_puts(imapd_out, "\r\n");
483 }
484 
485 /* add subscription flags or filter out non-subscribed mailboxes */
check_subs(mbentry_t * mbentry,strarray_t * subs,struct listargs * listargs,uint32_t * flags)486 static int check_subs(mbentry_t *mbentry, strarray_t *subs,
487                       struct listargs *listargs, uint32_t *flags)
488 {
489     int i, namelen = strlen(mbentry->name);
490 
491     for (i = 0; i < subs->count; i++) {
492         const char *name = strarray_nth(subs, i);
493 
494         if (strncmp(mbentry->name, name, namelen)) continue;
495         else if (!name[namelen]) { /* exact match */
496             *flags |= MBOX_ATTRIBUTE_SUBSCRIBED;
497             break;
498         }
499         else if (name[namelen] == '.' &&
500                  (listargs->sel & LIST_SEL_RECURSIVEMATCH)) {
501             *flags |= MBOX_ATTRIBUTE_CHILDINFO_SUBSCRIBED;
502             break;
503         }
504     }
505 
506     /* check if we need to filter out this mailbox */
507     return (!(listargs->sel & LIST_SEL_SUBSCRIBED) ||
508             (*flags & (MBOX_ATTRIBUTE_SUBSCRIBED |
509                        MBOX_ATTRIBUTE_CHILDINFO_SUBSCRIBED)));
510 }
511 
512 /* add subscribed mailbox or ancestor of subscribed mailbox to our list */
add_sub(strarray_t * subs,mbentry_t * mbentry,uint32_t attributes)513 static void add_sub(strarray_t *subs, mbentry_t *mbentry, uint32_t attributes)
514 {
515     if (attributes & MBOX_ATTRIBUTE_SUBSCRIBED) {
516         /* mailbox is subscribed */
517         strarray_append(subs, mbentry->name);
518     }
519     else {
520         /* a descendent of mailbox is subscribed */
521         struct buf child = BUF_INITIALIZER;
522 
523         buf_printf(&child, "%s.", mbentry->name);
524         strarray_appendm(subs, buf_release(&child));
525     }
526 }
527 
is_extended_resp(const char * cmd,struct listargs * listargs)528 static int is_extended_resp(const char *cmd, struct listargs *listargs)
529 {
530     if (!(listargs->ret &
531           (LIST_RET_STATUS | LIST_RET_MYRIGHTS | LIST_RET_METADATA))) {
532         /* backend won't be sending extended response data */
533         return 0;
534     }
535     else if ((listargs->ret & LIST_RET_STATUS) &&
536              !strncasecmp("STATUS", cmd, 6)) {
537         return 1;
538     }
539     else if ((listargs->ret & LIST_RET_MYRIGHTS) &&
540              !strncasecmp("MYRIGHTS", cmd, 8)) {
541         return 1;
542     }
543     else if ((listargs->ret & LIST_RET_METADATA) &&
544              !strncasecmp("METADATA", cmd, 8)) {
545         return 1;
546     }
547 
548     return 0;
549 }
550 
551 /* This handles piping of the LSUB command, because we have to figure out
552  * what mailboxes actually exist before passing them to the end user.
553  *
554  * It is also needed if we are doing a LIST-EXTENDED, to capture subscriptions
555  * and/or return data that can only be obtained from the backends.
556  */
pipe_lsub(struct backend * s,const char * userid,const char * tag,int force_notfatal,struct listargs * listargs,strarray_t * subs)557 int pipe_lsub(struct backend *s, const char *userid, const char *tag,
558               int force_notfatal, struct listargs *listargs, strarray_t *subs)
559 {
560     int taglen = strlen(tag);
561     int c;
562     int r = PROXY_OK;
563     int exist_r;
564     static struct buf tagb, cmd, sep, name, ext;
565     struct buf extraflags = BUF_INITIALIZER;
566     int build_list_only = subs && !(listargs->ret & LIST_RET_SUBSCRIBED);
567     int suppress_resp = 0;
568 
569     assert(s);
570     assert(s->timeout);
571 
572     s->timeout->mark = time(NULL) + IDLE_TIMEOUT;
573 
574     while(1) {
575         c = getword(s->in, &tagb);
576 
577         if(c == EOF) {
578             if(s == backend_current && !force_notfatal)
579                 fatal("Lost connection to selected backend", EX_UNAVAILABLE);
580             proxy_downserver(s);
581             r = PROXY_NOCONNECTION;
582             goto out;
583         }
584 
585         if(!strncmp(tag, tagb.s, taglen)) {
586             char buf[2048];
587             if(!prot_fgets(buf, sizeof(buf), s->in)) {
588                 if(s == backend_current && !force_notfatal)
589                     fatal("Lost connection to selected backend",
590                           EX_UNAVAILABLE);
591                 proxy_downserver(s);
592                 r = PROXY_NOCONNECTION;
593                 goto out;
594             }
595             /* Got the end of the response */
596             buf_appendcstr(&s->last_result, buf);
597             buf_cstring(&s->last_result);
598 
599             switch (buf[0]) {
600             case 'O': case 'o':
601                 r = PROXY_OK;
602                 break;
603             case 'N': case 'n':
604                 r = PROXY_NO;
605                 break;
606             case 'B': case 'b':
607                 r = PROXY_BAD;
608                 break;
609             default: /* huh? no result? */
610                 if(s == backend_current && !force_notfatal)
611                     fatal("Lost connection to selected backend",
612                           EX_UNAVAILABLE);
613                 proxy_downserver(s);
614                 r = PROXY_NOCONNECTION;
615                 break;
616             }
617             break; /* we're done */
618         }
619 
620         c = getword(s->in, &cmd);
621 
622         if(c == EOF) {
623             if(s == backend_current && !force_notfatal)
624                 fatal("Lost connection to selected backend", EX_UNAVAILABLE);
625             proxy_downserver(s);
626             r = PROXY_NOCONNECTION;
627             goto out;
628         }
629 
630         if(strncasecmp("LSUB", cmd.s, 4) && strncasecmp("LIST", cmd.s, 4)) {
631             if (suppress_resp && is_extended_resp(cmd.s, listargs)) {
632                 /* suppress extended return data for this mailbox */
633                 eatline(s->in, c);
634             }
635             else {
636                 prot_printf(imapd_out, "%s %s ", tagb.s, cmd.s);
637                 r = pipe_to_end_of_response(s, force_notfatal);
638                 if (r != PROXY_OK)
639                     goto out;
640             }
641         } else {
642             /* build up the response bit by bit */
643             const struct mbox_name_attribute *attr;
644             uint32_t attributes = 0;
645 
646             /* Get flags */
647             buf_reset(&extraflags);
648             c = prot_getc(s->in);
649             if (c == '(') {
650                 do {
651                     c = getword(s->in, &name);
652                     for (attr = mbox_name_attributes;
653                          attr->id && strcasecmp(name.s, attr->id); attr++);
654 
655                     if (attr->id) attributes |= attr->flag;
656                     else {
657                         if (buf_len(&extraflags)) buf_putc(&extraflags, ' ');
658                         buf_appendcstr(&extraflags, name.s);
659                     }
660                 } while (c == ' ');
661 
662                 if (c == ')') {
663                     /* end of flags - get the next character */
664                     c = prot_getc(s->in);
665                 }
666             }
667 
668             if(c != ' ') {
669                 if(s == backend_current && !force_notfatal)
670                     fatal("Bad LSUB response from selected backend",
671                           EX_UNAVAILABLE);
672                 proxy_downserver(s);
673                 r = PROXY_NOCONNECTION;
674                 goto out;
675             }
676 
677             /* Get separator */
678             c = getastring(s->in, s->out, &sep);
679 
680             if(c != ' ') {
681                 if(s == backend_current && !force_notfatal)
682                     fatal("Bad LSUB response from selected backend",
683                           EX_UNAVAILABLE);
684                 proxy_downserver(s);
685                 r = PROXY_NOCONNECTION;
686                 goto out;
687             }
688 
689             /* Get name */
690             c = getastring(s->in, s->out, &name);
691 
692             /* Get extension(s) */
693             buf_reset(&ext);
694             if (c == ' ') {
695                 do {
696                     buf_putc(&ext, c);
697                     c = prot_getc(s->in);
698                 } while (c != '\r' && c != '\n' && c != EOF);
699 
700                 /* XXX  Currently there are no other documented extensions */
701                 attributes |= MBOX_ATTRIBUTE_CHILDINFO_SUBSCRIBED;
702             }
703             buf_cstring(&ext);
704 
705             if(c == '\r') c = prot_getc(s->in);
706             if(c != '\n') {
707                 if(s == backend_current && !force_notfatal)
708                     fatal("Bad LSUB response from selected backend",
709                           EX_UNAVAILABLE);
710                 proxy_downserver(s);
711                 r = PROXY_NOCONNECTION;
712                 goto out;
713             }
714 
715             /* lookup name */
716             exist_r = 1;
717             char *intname =
718                 mboxname_from_external(name.s, &imapd_namespace, userid);
719             mbentry_t *mbentry = NULL;
720             exist_r = mboxlist_lookup(intname, &mbentry, NULL);
721             free(intname);
722             if(!exist_r && (mbentry->mbtype & MBTYPE_RESERVE))
723                 exist_r = IMAP_MAILBOX_RESERVED;
724 
725             /* suppress responses to client if we're just building subs list */
726             suppress_resp = build_list_only;
727 
728             if (!exist_r) {
729                 /* we need to remove \Noselect if it's in our mailboxes.db */
730                 attributes &=
731                     ~(MBOX_ATTRIBUTE_NOSELECT | MBOX_ATTRIBUTE_NONEXISTENT);
732 
733                 if (subs) {
734                     /* process subscriptions */
735                     if (s != backend_inbox) {
736                         /* backend server won't have subs info -
737                            add sub flags or filter out non-sub mailboxes */
738                         if (!check_subs(mbentry, subs, listargs, &attributes)) {
739                             /* unsubscribed and we only want subscriptions */
740                             suppress_resp = 1;
741                         }
742                     }
743                     else if (listargs->sel & LIST_SEL_SUBSCRIBED) {
744                         /* If we're just building subs list,
745                            we want ALL subscriptions added to list.
746                            Otherwise just add those NOT on Inbox server. */
747                         if (build_list_only ||
748                             strcmp(mbentry->server, backend_inbox->hostname)) {
749                             add_sub(subs, mbentry, attributes);
750                             /* suppress the response - those NOT added to the
751                                list are sent to client in subsequent requests */
752                             suppress_resp = 1;
753                         }
754                     }
755                 }
756             }
757 
758             if (!suppress_resp) {
759                 /* send response to the client */
760                 print_listresponse(listargs->cmd, name.s, sep.s[0],
761                                    attributes, &extraflags);
762 
763                 /* send any PROXY_ONLY metadata items */
764                 for (c = 0; c < listargs->metaitems.count; c++) {
765                     const char *entry = strarray_nth(&listargs->metaitems, c);
766 
767                     if (mbentry &&
768                         !strcmp(entry, "/shared" IMAP_ANNOT_NS "server")) {
769                         prot_puts(imapd_out, "* METADATA ");
770                         prot_printastring(imapd_out, name.s);
771                         prot_puts(imapd_out, " (");
772                         prot_printstring(imapd_out, entry);
773                         prot_puts(imapd_out, " ");
774                         prot_printstring(imapd_out, mbentry->server);
775                         prot_puts(imapd_out, ")\r\n");
776                     }
777                 }
778             }
779 
780             mboxlist_entry_free(&mbentry);
781         }
782     } /* while(1) */
783 
784 out:
785     buf_free(&extraflags);
786     return r;
787 }
788 
789 /* xxx  start of separate proxy-only code
790    (remove when we move to a unified environment) */
chomp(struct protstream * p,const char * s)791 static int chomp(struct protstream *p, const char *s)
792 {
793     int c = prot_getc(p);
794 
795     while (*s) {
796         if (tolower(c) != tolower(*s)) { break; }
797         s++;
798         c = prot_getc(p);
799     }
800     if (*s) {
801         if (c != EOF) prot_ungetc(c, p);
802         c = EOF;
803     }
804     return c;
805 }
806 
807 #define BUFGROWSIZE 100
808 
809 /* read characters from 'p' until 'end' is seen */
grab(struct protstream * p,char end)810 static char *grab(struct protstream *p, char end)
811 {
812     int alloc = BUFGROWSIZE, cur = 0;
813     int c = -1;
814     char *ret = (char *) xmalloc(alloc);
815 
816     ret[0] = '\0';
817     while ((c = prot_getc(p)) != end) {
818         if (c == EOF) break;
819         if (cur == alloc - 1) {
820             alloc += BUFGROWSIZE;
821             ret = xrealloc(ret, alloc);
822 
823         }
824         ret[cur++] = c;
825     }
826     if (cur) ret[cur] = '\0';
827 
828     return ret;
829 }
830 
831 /* remove \Recent from the flags */
editflags(char * flags)832 static char *editflags(char *flags)
833 {
834     char *p;
835 
836     p = flags;
837     while ((p = strchr(p, '\\')) != NULL) {
838         if (!strncasecmp(p + 1, "recent", 6)) {
839             if (p[7] == ' ') {
840                 /* shift everything over so that \recent vanishes */
841                 char *q;
842 
843                 q = p + 8;
844                 while (*q) {
845                     *p++ = *q++;
846                 }
847                 *p = '\0';
848             } else if (p[7] == '\0') {
849                 /* last flag in line */
850                 *p = '\0';
851             } else {
852                 /* not really \recent, i guess */
853                 p++;
854             }
855         } else {
856             p++;
857         }
858     }
859 
860     return flags;
861 }
862 
proxy_copy(const char * tag,char * sequence,char * name,int myrights,int usinguid,struct backend * s)863 void proxy_copy(const char *tag, char *sequence, char *name, int myrights,
864                 int usinguid, struct backend *s)
865 {
866     char mytag[128];
867     struct d {
868         char *idate;
869         char *flags;
870         unsigned int seqno, uid;
871         struct d *next;
872     } *head, *p, *q;
873     int c;
874 
875     /* find out what the flags & internaldate for this message are */
876     proxy_gentag(mytag, sizeof(mytag));
877     prot_printf(backend_current->out,
878                 "%s %s %s (Flags Internaldate)\r\n",
879                 tag, usinguid ? "Uid Fetch" : "Fetch", sequence);
880     head = (struct d *) xmalloc(sizeof(struct d));
881     head->flags = NULL; head->idate = NULL;
882     head->seqno = head->uid = 0;
883     head->next = NULL;
884     p = head;
885     /* read all the responses into the linked list */
886     for (/* each FETCH response */;;) {
887         unsigned int seqno = 0, uidno = 0;
888         char *flags = NULL, *idate = NULL;
889 
890         /* read a line */
891         c = prot_getc(backend_current->in);
892         if (c != '*') break;
893         c = prot_getc(backend_current->in);
894         if (c != ' ') { /* protocol error */ c = EOF; break; }
895 
896         /* check for OK/NO/BAD/BYE response */
897         if (!isdigit(c = prot_getc(backend_current->in))) {
898             prot_printf(imapd_out, "* %c", c);
899             pipe_to_end_of_response(backend_current, 0);
900             continue;
901         }
902 
903         /* read seqno */
904         prot_ungetc(c, backend_current->in);
905         c = getuint32(backend_current->in, &seqno);
906         if (seqno == 0 || c != ' ') {
907             /* we suck and won't handle this case */
908             c = EOF; break;
909         }
910         c = chomp(backend_current->in, "fetch (");
911         if (c == EOF) {
912             c = chomp(backend_current->in, "exists\r");
913             if (c == '\n') { /* got EXISTS response */
914                 prot_printf(imapd_out, "* %d EXISTS\r\n", seqno);
915                 continue;
916             }
917         }
918         if (c == EOF) {
919             /* XXX  the "exists" check above will eat "ex" */
920             c = chomp(backend_current->in, "punge\r");
921             if (c == '\n') { /* got EXPUNGE response */
922                 prot_printf(imapd_out, "* %d EXPUNGE\r\n", seqno);
923                 continue;
924             }
925         }
926         if (c == EOF) {
927             c = chomp(backend_current->in, "recent\r");
928             if (c == '\n') { /* got RECENT response */
929                 prot_printf(imapd_out, "* %d RECENT\r\n", seqno);
930                 continue;
931             }
932         }
933         /* huh, don't get this response */
934         if (c == EOF) break;
935         for (/* each fetch item */;;) {
936             /* looking at the first character in an item */
937             switch (c) {
938             case 'f': case 'F': /* flags? */
939                 c = chomp(backend_current->in, "lags");
940                 if (c != ' ') { c = EOF; }
941                 else c = prot_getc(backend_current->in);
942                 if (c != '(') { c = EOF; }
943                 else {
944                     flags = grab(backend_current->in, ')');
945                     c = prot_getc(backend_current->in);
946                 }
947                 break;
948             case 'i': case 'I': /* internaldate? */
949                 c = chomp(backend_current->in, "nternaldate");
950                 if (c != ' ') { c = EOF; }
951                 else c = prot_getc(backend_current->in);
952                 if (c != '"') { c = EOF; }
953                 else {
954                     idate = grab(backend_current->in, '"');
955                     c = prot_getc(backend_current->in);
956                 }
957                 break;
958             case 'u': case 'U': /* uid */
959                 c = chomp(backend_current->in, "id");
960                 if (c != ' ') { c = EOF; }
961                 else c = getuint32(backend_current->in, &uidno);
962                 break;
963             default: /* hmm, don't like the smell of it */
964                 c = EOF;
965                 break;
966             }
967             /* looking at either SP separating items or a RPAREN */
968             if (c == ' ') { c = prot_getc(backend_current->in); }
969             else if (c == ')') break;
970             else { c = EOF; break; }
971         }
972         /* if c == EOF we have either a protocol error or a situation
973            we can't handle, and we should die. */
974         if (c == ')') c = prot_getc(backend_current->in);
975         if (c == '\r') c = prot_getc(backend_current->in);
976         if (c != '\n') {
977             c = EOF;
978             free(flags);
979             free(idate);
980             break;
981         }
982 
983         /* if we're missing something, we should echo */
984         if (!flags || !idate) {
985             char sep = '(';
986             prot_printf(imapd_out, "* %d FETCH ", seqno);
987             if (uidno) {
988                 prot_printf(imapd_out, "%cUID %d", sep, uidno);
989                 sep = ' ';
990             }
991             if (flags) {
992                 prot_printf(imapd_out, "%cFLAGS %s", sep, flags);
993                 sep = ' ';
994             }
995             if (idate) {
996                 prot_printf(imapd_out, "%cINTERNALDATE %s", sep, idate);
997                 sep = ' ';
998             }
999             prot_printf(imapd_out, ")\r\n");
1000             if (flags) free(flags);
1001             if (idate) free(idate);
1002             continue;
1003         }
1004 
1005         /* add to p->next */
1006         p->next = xmalloc(sizeof(struct d));
1007         p = p->next;
1008         p->idate = idate;
1009         p->flags = editflags(flags);
1010         p->uid = uidno;
1011         p->seqno = seqno;
1012         p->next = NULL;
1013     }
1014     if (c != EOF) {
1015         prot_ungetc(c, backend_current->in);
1016 
1017         /* we should be looking at the tag now */
1018         pipe_until_tag(backend_current, tag, 0);
1019     }
1020     if (c == EOF) {
1021         /* uh oh, we're not happy */
1022         fatal("Lost connection to selected backend", EX_UNAVAILABLE);
1023     }
1024 
1025     /* start the append */
1026     prot_printf(s->out, "%s Append {" SIZE_T_FMT "+}\r\n%s",
1027                 tag, strlen(name), name);
1028     prot_printf(backend_current->out, "%s %s %s (Rfc822.peek)\r\n",
1029                 mytag, usinguid ? "Uid Fetch" : "Fetch", sequence);
1030     for (/* each FETCH response */;;) {
1031         unsigned int seqno = 0, uidno = 0;
1032 
1033         /* read a line */
1034         c = prot_getc(backend_current->in);
1035         if (c != '*') break;
1036         c = prot_getc(backend_current->in);
1037         if (c != ' ') { /* protocol error */ c = EOF; break; }
1038 
1039         /* check for OK/NO/BAD/BYE response */
1040         if (!isdigit(c = prot_getc(backend_current->in))) {
1041             prot_printf(imapd_out, "* %c", c);
1042             pipe_to_end_of_response(backend_current, 0);
1043             continue;
1044         }
1045 
1046         /* read seqno */
1047         prot_ungetc(c, backend_current->in);
1048         c = getuint32(backend_current->in, &seqno);
1049         if (seqno == 0 || c != ' ') {
1050             /* we suck and won't handle this case */
1051             c = EOF; break;
1052         }
1053         c = chomp(backend_current->in, "fetch (");
1054         if (c == EOF) { /* not a fetch response */
1055             c = chomp(backend_current->in, "exists\r");
1056             if (c == '\n') { /* got EXISTS response */
1057                 prot_printf(imapd_out, "* %d EXISTS\r\n", seqno);
1058                 continue;
1059             }
1060         }
1061         if (c == EOF) { /* not an exists response */
1062             /* XXX  the "exists" check above will eat "ex" */
1063             c = chomp(backend_current->in, "punge\r");
1064             if (c == '\n') { /* got EXPUNGE response */
1065                 prot_printf(imapd_out, "* %d EXPUNGE\r\n", seqno);
1066                 continue;
1067             }
1068         }
1069         if (c == EOF) { /* not an exists response */
1070             c = chomp(backend_current->in, "recent\r");
1071             if (c == '\n') { /* got RECENT response */
1072                 prot_printf(imapd_out, "* %d RECENT\r\n", seqno);
1073                 continue;
1074             }
1075         }
1076         if (c == EOF) {
1077             /* huh, don't get this response */
1078             break;
1079         }
1080         /* find seqno in the list */
1081         p = head;
1082         while (p->next && seqno != p->next->seqno) p = p->next;
1083         if (!p->next) break;
1084         q = p->next;
1085         p->next = q->next;
1086         for (/* each fetch item */;;) {
1087             int sz = 0;
1088 
1089             switch (c) {
1090             case 'u': case 'U':
1091                 c = chomp(backend_current->in, "id");
1092                 if (c != ' ') { c = EOF; }
1093                 else c = getuint32(backend_current->in, &uidno);
1094                 break;
1095 
1096             case 'r': case 'R':
1097                 c = chomp(backend_current->in, "fc822");
1098                 if (c == ' ') c = prot_getc(backend_current->in);
1099                 if (c != '{') {
1100                     /* NIL? */
1101                     eatline(backend_current->in, c);
1102                     c = EOF;
1103                 }
1104                 else c = getint32(backend_current->in, &sz);
1105                 if (c == '}') c = prot_getc(backend_current->in);
1106                 if (c == '\r') c = prot_getc(backend_current->in);
1107                 if (c != '\n') c = EOF;
1108 
1109                 if (c != EOF) {
1110                     /* append p to s->out */
1111                     prot_printf(s->out, " (%s) \"%s\" {%d+}\r\n",
1112                                 q->flags, q->idate, sz);
1113                     while (sz) {
1114                         char buf[2048];
1115                         int j = (sz > (int) sizeof(buf) ?
1116                                  (int) sizeof(buf) : sz);
1117 
1118                         j = prot_read(backend_current->in, buf, j);
1119                         if(!j) break;
1120                         prot_write(s->out, buf, j);
1121                         sz -= j;
1122                     }
1123                     c = prot_getc(backend_current->in);
1124                 }
1125 
1126                 break; /* end of case */
1127             default:
1128                 c = EOF;
1129                 break;
1130             }
1131             /* looking at either SP separating items or a RPAREN */
1132             if (c == ' ') { c = prot_getc(backend_current->in); }
1133             else if (c == ')') break;
1134             else { c = EOF; break; }
1135         }
1136 
1137         /* if c == EOF we have either a protocol error or a situation
1138            we can't handle, and we should die. */
1139         if (c == ')') c = prot_getc(backend_current->in);
1140         if (c == '\r') c = prot_getc(backend_current->in);
1141         if (c != '\n') { c = EOF; break; }
1142 
1143         /* free q */
1144         free(q->idate);
1145         free(q->flags);
1146         free(q);
1147     }
1148     if (c != EOF) {
1149         char *appenduid, *b;
1150         int res;
1151 
1152         /* pushback the first character of the tag we're looking at */
1153         prot_ungetc(c, backend_current->in);
1154 
1155         /* nothing should be left in the linked list */
1156         assert(head->next == NULL);
1157 
1158         /* ok, finish the append; we need the UIDVALIDITY and UIDs
1159            to return as part of our COPYUID response code */
1160         prot_printf(s->out, "\r\n");
1161 
1162         /* should be looking at 'mytag' on 'backend_current',
1163            'tag' on 's' */
1164         pipe_until_tag(backend_current, mytag, 0);
1165         res = pipe_until_tag(s, tag, 0);
1166 
1167         if (res == PROXY_OK) {
1168             if (myrights & ACL_READ) {
1169                 appenduid = strchr(s->last_result.s, '[');
1170                 /* skip over APPENDUID */
1171                 if (appenduid) {
1172                     appenduid += strlen("[appenduid ");
1173                     b = strchr(appenduid, ']');
1174                     if (b) *b = '\0';
1175                     prot_printf(imapd_out, "%s OK [COPYUID %s] %s\r\n", tag,
1176                                 appenduid, error_message(IMAP_OK_COMPLETED));
1177                 }
1178                 else
1179                     prot_printf(imapd_out, "%s OK %s\r\n", tag, s->last_result.s);
1180             }
1181             else {
1182                 prot_printf(imapd_out, "%s OK %s\r\n", tag,
1183                             error_message(IMAP_OK_COMPLETED));
1184             }
1185         } else {
1186             prot_printf(imapd_out, "%s %s", tag, s->last_result.s);
1187         }
1188     } else {
1189         /* abort the append */
1190         prot_printf(s->out, " {0+}\r\n\r\n");
1191         pipe_until_tag(backend_current, mytag, 0);
1192         pipe_until_tag(s, tag, 0);
1193 
1194         /* report failure */
1195         prot_printf(imapd_out, "%s NO inter-server COPY failed\r\n", tag);
1196     }
1197 
1198     /* free dynamic memory */
1199     while (head) {
1200         p = head;
1201         head = head->next;
1202         if (p->idate) free(p->idate);
1203         if (p->flags) free(p->flags);
1204         free(p);
1205     }
1206 }
1207 /* xxx  end of separate proxy-only code */
1208 
proxy_catenate_url(struct backend * s,struct imapurl * url,FILE * f,unsigned long * size,const char ** parseerr)1209 int proxy_catenate_url(struct backend *s, struct imapurl *url, FILE *f,
1210                        unsigned long *size, const char **parseerr)
1211 {
1212     char mytag[128];
1213     int c, r = 0, found = 0;
1214     unsigned int uidvalidity = 0;
1215 
1216     *size = 0;
1217     *parseerr = NULL;
1218 
1219     /* select the mailbox (read-only) */
1220     proxy_gentag(mytag, sizeof(mytag));
1221     prot_printf(s->out, "%s Examine {" SIZE_T_FMT "+}\r\n%s\r\n",
1222                 mytag, strlen(url->mailbox), url->mailbox);
1223     for (/* each examine response */;;) {
1224         /* read a line */
1225         c = prot_getc(s->in);
1226         if (c != '*') break;
1227         c = prot_getc(s->in);
1228         if (c != ' ') { /* protocol error */ c = EOF; break; }
1229 
1230         c = chomp(s->in, "ok [uidvalidity");
1231         if (c == EOF) {
1232             /* we don't care about this response */
1233             eatline(s->in, c);
1234             continue;
1235         }
1236 
1237         /* read uidvalidity */
1238         c = getuint32(s->in, &uidvalidity);
1239         if (c != ']') { c = EOF; break; }
1240         eatline(s->in, c); /* we don't care about the rest of the line */
1241     }
1242     if (c != EOF) {
1243         prot_ungetc(c, s->in);
1244 
1245         /* we should be looking at the tag now */
1246         eatline(s->in, c);
1247     }
1248     if (c == EOF) {
1249         /* uh oh, we're not happy */
1250         fatal("Lost connection to backend", EX_UNAVAILABLE);
1251     }
1252 
1253     if (url->uidvalidity && (uidvalidity != url->uidvalidity)) {
1254         *parseerr = "Uidvalidity of mailbox has changed";
1255         r = IMAP_BADURL;
1256         goto unselect;
1257     }
1258 
1259     /* fetch the bodypart */
1260     proxy_gentag(mytag, sizeof(mytag));
1261     prot_printf(s->out, "%s Uid Fetch %lu Body.Peek[%s]\r\n",
1262                 mytag, url->uid, url->section ? url->section : "");
1263     for (/* each fetch response */;;) {
1264         unsigned int seqno;
1265 
1266       next_resp:
1267         /* read a line */
1268         c = prot_getc(s->in);
1269         if (c != '*') break;
1270         c = prot_getc(s->in);
1271         if (c != ' ') { /* protocol error */ c = EOF; break; }
1272 
1273         /* read seqno */
1274         c = getuint32(s->in, &seqno);
1275         if (seqno == 0 || c != ' ') {
1276             /* we suck and won't handle this case */
1277             c = EOF; break;
1278         }
1279         c = chomp(s->in, "fetch (");
1280         if (c == EOF) { /* not a fetch response */
1281             eatline(s->in, c);
1282             continue;
1283         }
1284 
1285         for (/* each fetch item */;;) {
1286             unsigned uid, sz = 0;
1287 
1288             switch (c) {
1289             case 'u': case 'U':
1290                 c = chomp(s->in, "id");
1291                 if (c != ' ') { c = EOF; }
1292                 else {
1293                     c = getuint32(s->in, &uid);
1294                     if (uid != url->uid) {
1295                         /* not our response */
1296                         eatline(s->in, c);
1297                         goto next_resp;
1298                     }
1299                 }
1300                 break;
1301 
1302             case 'b': case 'B':
1303                 c = chomp(s->in, "ody[");
1304                 while (c != ']') c = prot_getc(s->in);
1305                 if (c == ']') c = prot_getc(s->in);
1306                 if (c == ' ') c = prot_getc(s->in);
1307                 if (c == '{') {
1308                     c = getuint32(s->in, &sz);
1309                     if (c == '}') c = prot_getc(s->in);
1310                     if (c == '\r') c = prot_getc(s->in);
1311                     if (c != '\n') c = EOF;
1312                 }
1313                 else if (c == 'n' || c == 'N') {
1314                     c = chomp(s->in, "il");
1315                     r = IMAP_BADURL;
1316                     *parseerr = "No such message part";
1317                 }
1318 
1319                 if (c != EOF) {
1320                     /* catenate to f */
1321                     found = 1;
1322                     *size = sz;
1323 
1324                     while (sz) {
1325                         char buf[2048];
1326                         int j = (sz > sizeof(buf) ? sizeof(buf) : sz);
1327 
1328                         j = prot_read(s->in, buf, j);
1329                         if(!j) break;
1330                         fwrite(buf, j, 1, f);
1331                         sz -= j;
1332                     }
1333                     c = prot_getc(s->in);
1334                 }
1335 
1336                 break; /* end of case */
1337             default:
1338                 /* probably a FLAGS item */
1339                 eatline(s->in, c);
1340                 goto next_resp;
1341             }
1342             /* looking at either SP separating items or a RPAREN */
1343             if (c == ' ') { c = prot_getc(s->in); }
1344             else if (c == ')') break;
1345             else { c = EOF; break; }
1346         }
1347 
1348         /* if c == EOF we have either a protocol error or a situation
1349            we can't handle, and we should die. */
1350         if (c == ')') c = prot_getc(s->in);
1351         if (c == '\r') c = prot_getc(s->in);
1352         if (c != '\n') { c = EOF; break; }
1353     }
1354     if (c != EOF) {
1355         prot_ungetc(c, s->in);
1356 
1357         /* we should be looking at the tag now */
1358         eatline(s->in, c);
1359     }
1360     if (c == EOF) {
1361         /* uh oh, we're not happy */
1362         fatal("Lost connection to backend", EX_UNAVAILABLE);
1363     }
1364 
1365   unselect:
1366     /* unselect the mailbox */
1367     proxy_gentag(mytag, sizeof(mytag));
1368     prot_printf(s->out, "%s Unselect\r\n", mytag);
1369     for (/* each unselect response */;;) {
1370         /* read a line */
1371         c = prot_getc(s->in);
1372         if (c != '*') break;
1373         c = prot_getc(s->in);
1374         if (c != ' ') { /* protocol error */ c = EOF; break; }
1375 
1376         /* we don't care about this response */
1377         eatline(s->in, c);
1378     }
1379     if (c != EOF) {
1380         prot_ungetc(c, s->in);
1381 
1382         /* we should be looking at the tag now */
1383         eatline(s->in, c);
1384     }
1385     if (c == EOF) {
1386         /* uh oh, we're not happy */
1387         fatal("Lost connection to backend", EX_UNAVAILABLE);
1388     }
1389 
1390     if (!r && !found) {
1391         r = IMAP_BADURL;
1392         *parseerr = "No such message in mailbox";
1393     }
1394 
1395     return r;
1396 }
1397 
1398 /* Proxy GETMETADATA commands to backend */
annotate_fetch_proxy(const char * server,const char * mbox_pat,const strarray_t * entry_pat,const strarray_t * attribute_pat)1399 int annotate_fetch_proxy(const char *server, const char *mbox_pat,
1400                          const strarray_t *entry_pat,
1401                          const strarray_t *attribute_pat)
1402 {
1403     struct backend *be;
1404     int i, j;
1405     char mytag[128];
1406 
1407     assert(server && mbox_pat && entry_pat && attribute_pat);
1408 
1409     be = proxy_findserver(server, &imap_protocol,
1410                           proxy_userid, &backend_cached,
1411                           &backend_current, &backend_inbox, imapd_in);
1412     if (!be) return IMAP_SERVER_UNAVAILABLE;
1413 
1414     /* Send command to remote */
1415     proxy_gentag(mytag, sizeof(mytag));
1416     prot_printf(be->out, "%s GETANNOTATION \"%s\" (", mytag, mbox_pat);
1417     for (i = 0; i < entry_pat->count; i++) {
1418         const char *entry = strarray_nth(entry_pat, i);
1419 
1420         for (j = 0; j < attribute_pat->count; j++) {
1421             const char *scope, *attr = strarray_nth(attribute_pat, j);
1422             if (!strcmp(attr, "value.shared")) {
1423                 scope = "/shared";
1424             }
1425             else if (!strcmp(attr, "value.priv")) {
1426                 scope = "/private";
1427             }
1428             else {
1429                 syslog(LOG_ERR, "won't get deprecated annotation attribute %s", attr);
1430                 continue;
1431             }
1432             prot_printf(be->out, "%s%s%s", i ? " " : "", scope, entry);
1433         }
1434     }
1435     prot_printf(be->out, ")\r\n");
1436     prot_flush(be->out);
1437 
1438     /* Pipe the results.  Note that backend-current may also pipe us other
1439        messages. */
1440     pipe_until_tag(be, mytag, 0);
1441 
1442     return 0;
1443 }
1444 
1445 /* Proxy SETMETADATA commands to backend */
annotate_store_proxy(const char * server,const char * mbox_pat,struct entryattlist * entryatts)1446 int annotate_store_proxy(const char *server, const char *mbox_pat,
1447                          struct entryattlist *entryatts)
1448 {
1449     struct backend *be;
1450     struct entryattlist *e;
1451     struct attvaluelist *av;
1452     char mytag[128];
1453     struct buf entrybuf = BUF_INITIALIZER;
1454 
1455 
1456     assert(server && mbox_pat && entryatts);
1457 
1458     be = proxy_findserver(server, &imap_protocol,
1459                           proxy_userid, &backend_cached,
1460                           &backend_current, &backend_inbox, imapd_in);
1461     if (!be) return IMAP_SERVER_UNAVAILABLE;
1462 
1463     /* Send command to remote */
1464     proxy_gentag(mytag, sizeof(mytag));
1465     prot_printf(be->out, "%s SETMETADATA \"%s\" (", mytag, mbox_pat);
1466     for (e = entryatts; e; e = e->next) {
1467         for (av = e->attvalues; av; av = av->next) {
1468             assert(av->attrib);
1469             if (!strcmp(av->attrib, "value.shared")) {
1470                 buf_setcstr(&entrybuf, "/shared");
1471             }
1472             else if (!strcmp(av->attrib, "value.priv")) {
1473                 buf_setcstr(&entrybuf, "/private");
1474             }
1475             else {
1476                 syslog(LOG_ERR,
1477                        "won't proxy annotation with deprecated attribute %s",
1478                        av->attrib);
1479                 buf_free(&entrybuf);
1480                 return IMAP_INTERNAL;
1481             }
1482 
1483             buf_appendcstr(&entrybuf, e->entry);
1484 
1485             /* Print the entry-value pair */
1486             prot_printamap(be->out, entrybuf.s, entrybuf.len);
1487             prot_putc(' ', be->out);
1488             prot_printamap(be->out, av->value.s, av->value.len);
1489 
1490             if (av->next) prot_putc(' ', be->out);
1491         }
1492         if (e->next) prot_putc(' ', be->out);
1493     }
1494     prot_printf(be->out, ")\r\n");
1495     prot_flush(be->out);
1496 
1497     /* Pipe the results.  Note that backend-current may also pipe us other
1498        messages. */
1499     pipe_until_tag(be, mytag, 0);
1500 
1501     buf_free(&entrybuf);
1502 
1503     return 0;
1504 }
1505 
1506 
find_free_server(void)1507 char *find_free_server(void)
1508 {
1509     const char *servers = config_getstring(IMAPOPT_SERVERLIST);
1510     char *server = NULL;
1511 
1512     if (servers) {
1513         if (!server_parts) {
1514             server_parts = xzmalloc(sizeof(partlist_t));
1515 
1516             partlist_initialize(
1517                     server_parts,
1518                     proxy_part_filldata,
1519                     NULL,
1520                     servers,
1521                     NULL,
1522                     partlist_getmode(config_getstring(IMAPOPT_SERVERLIST_SELECT_MODE)),
1523                     config_getint(IMAPOPT_SERVERLIST_SELECT_SOFT_USAGE_LIMIT),
1524                     config_getint(IMAPOPT_SERVERLIST_SELECT_USAGE_REINIT)
1525                 );
1526 
1527         }
1528 
1529         server = (char *)partlist_select_value(server_parts);
1530     }
1531 
1532     return server;
1533 }
1534 
1535 
proxy_part_filldata(partlist_t * part_list,int idx)1536 static void proxy_part_filldata(partlist_t *part_list, int idx)
1537 {
1538     char mytag[128];
1539     struct backend *be;
1540     partitem_t *item = &part_list->items[idx];
1541 
1542     item->id = 0;
1543     item->available = 0;
1544     item->total = 0;
1545     item->quota = 0.;
1546 
1547     syslog(LOG_DEBUG, "checking free space on server '%s'", item->value);
1548 
1549     /* connect to server */
1550     be = proxy_findserver(item->value, &imap_protocol,
1551             proxy_userid, &backend_cached,
1552             &backend_current, &backend_inbox, imapd_in);
1553 
1554     if (be) {
1555         uint64_t server_available = 0;
1556         uint64_t server_total = 0;
1557         const char *annot =
1558             (part_list->mode == PART_SELECT_MODE_FREESPACE_MOST) ?
1559             "freespace/total" : "freespace/percent/most";
1560         struct buf cmd = BUF_INITIALIZER;
1561         int c;
1562 
1563         /* fetch annotation from remote */
1564         proxy_gentag(mytag, sizeof(mytag));
1565         if (CAPA(be, CAPA_METADATA)) {
1566             buf_printf(&cmd, "METADATA \"\" (\"/shared" IMAP_ANNOT_NS "%s\"",
1567                        annot);
1568         }
1569         else {
1570             buf_printf(&cmd, "ANNOTATION \"\" \"" IMAP_ANNOT_NS "%s\" "
1571                        "(\"value.shared\"", annot);
1572         }
1573         prot_printf(be->out, "%s GET%s)\r\n", mytag, buf_cstring(&cmd));
1574         prot_flush(be->out);
1575 
1576         for (/* each annotation response */;;) {
1577             /* read a line */
1578             c = prot_getc(be->in);
1579             if (c != '*') break;
1580             c = prot_getc(be->in);
1581             if (c != ' ') { /* protocol error */ c = EOF; break; }
1582 
1583             c = chomp(be->in, buf_cstring(&cmd));
1584             if (c == ' ') c = prot_getc(be->in);
1585             if ((c == EOF) || (c != '\"')) {
1586                 /* we don't care about this response */
1587                 eatline(be->in, c);
1588                 continue;
1589             }
1590 
1591             /* read available */
1592             c = getuint64(be->in, &server_available);
1593             if (c != ';') { c = EOF; break; }
1594 
1595             /* read total */
1596             c = getuint64(be->in, &server_total);
1597             if (c != '\"') { c = EOF; break; }
1598             eatline(be->in, c); /* we don't care about the rest of the line */
1599         }
1600         buf_free(&cmd);
1601         if (c != EOF) {
1602             prot_ungetc(c, be->in);
1603 
1604             /* we should be looking at the tag now */
1605             eatline(be->in, c);
1606         }
1607         if (c == EOF) {
1608             /* uh oh, we're not happy */
1609             fatal("Lost connection to backend", EX_UNAVAILABLE);
1610         }
1611 
1612         /* unique id */
1613         item->id = idx;
1614         item->available = server_available;
1615         item->total = server_total;
1616     }
1617 }
1618