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