/* imap_proxy.c - IMAP proxy support functions * * Copyright (c) 1994-2008 Carnegie Mellon University. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in * the documentation and/or other materials provided with the * distribution. * * 3. The name "Carnegie Mellon University" must not be used to * endorse or promote products derived from this software without * prior written permission. For permission or any legal * details, please contact * Carnegie Mellon University * Center for Technology Transfer and Enterprise Creation * 4615 Forbes Avenue * Suite 302 * Pittsburgh, PA 15213 * (412) 268-7393, fax: (412) 268-7395 * innovation@andrew.cmu.edu * * 4. Redistributions of any form whatsoever must retain the following * acknowledgment: * "This product includes software developed by Computing Services * at Carnegie Mellon University (http://www.cmu.edu/computing/)." * * CARNEGIE MELLON UNIVERSITY DISCLAIMS ALL WARRANTIES WITH REGARD TO * THIS SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY * AND FITNESS, IN NO EVENT SHALL CARNEGIE MELLON UNIVERSITY BE LIABLE * FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN * AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING * OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #include #include #include #include #include #include #include #include "assert.h" #include "acl.h" #include "annotate.h" #include "backend.h" #include "global.h" #include "imap_proxy.h" #include "proxy.h" #include "mboxname.h" #include "mupdate-client.h" #include "partlist.h" #include "prot.h" #include "util.h" #include "xmalloc.h" /* generated headers are not necessarily in current directory */ #include "imap/imap_err.h" extern unsigned int proxy_cmdcnt; extern struct protstream *imapd_in, *imapd_out; extern struct backend *backend_inbox, *backend_current, **backend_cached; extern char *imapd_userid, *proxy_userid; extern struct namespace imapd_namespace; static partlist_t *server_parts = NULL; static void proxy_part_filldata(partlist_t *part_list, int idx); static void imap_postcapability(struct backend *s) { if (CAPA(s, CAPA_SASL_IR)) { /* server supports initial response in AUTHENTICATE command */ s->prot->u.std.sasl_cmd.maxlen = USHRT_MAX; } } struct protocol_t imap_protocol = { "imap", "imap", TYPE_STD, { { { 1, NULL }, { "C01 CAPABILITY", NULL, "C01 ", imap_postcapability, CAPAF_MANY_PER_LINE, { { "AUTH", CAPA_AUTH }, { "STARTTLS", CAPA_STARTTLS }, { "COMPRESS=DEFLATE", CAPA_COMPRESS }, { "IDLE", CAPA_IDLE }, { "MUPDATE", CAPA_MUPDATE }, { "MULTIAPPEND", CAPA_MULTIAPPEND }, { "METADATA", CAPA_METADATA }, { "RIGHTS=kxte", CAPA_ACLRIGHTS }, { "LIST-EXTENDED", CAPA_LISTEXTENDED }, { "SASL-IR", CAPA_SASL_IR }, { "X-REPLICATION", CAPA_REPLICATION }, { NULL, 0 } } }, { "S01 STARTTLS", "S01 OK", "S01 NO", 0 }, { "A01 AUTHENTICATE", 0, 0, "A01 OK", "A01 NO", "+ ", "*", NULL, AUTO_CAPA_AUTH_OK }, { "Z01 COMPRESS DEFLATE", "* ", "Z01 OK" }, { "N01 NOOP", "* ", "N01 OK" }, { "Q01 LOGOUT", "* ", "Q01 " } } } }; void proxy_gentag(char *tag, size_t len) { snprintf(tag, len, "PROXY%d", proxy_cmdcnt++); } struct backend *proxy_findinboxserver(const char *userid) { mbentry_t *mbentry = NULL; struct backend *s = NULL; char *inbox = mboxname_user_mbox(userid, NULL); int r = mboxlist_lookup(inbox, &mbentry, NULL); free(inbox); if (r) return NULL; if (mbentry->mbtype & MBTYPE_REMOTE) { s = proxy_findserver(mbentry->server, &imap_protocol, proxy_userid, &backend_cached, &backend_current, &backend_inbox, imapd_in); } mboxlist_entry_free(&mbentry); return s; } /* pipe_response() reads from 's->in' until either the tagged response starting with 'tag' appears, or if 'tag' is NULL, to the end of the current line. If 'include_last' is set, the last/tagged line is included in the output, otherwise the last/tagged line is stored in 's->last_result'. In either case, the result of the tagged command is returned. 's->last_result' assumes that tagged responses don't contain literals. Unfortunately, the IMAP grammar allows them force_notfatal says to not fatal() if we lose connection to backend_current even though it is in 95% of the cases, a good idea... */ static int pipe_response(struct backend *s, const char *tag, int include_last, int force_notfatal) { char buf[2048]; char eol[128]; unsigned sl; int cont = 0, last = !tag, r = PROXY_OK; size_t taglen = 0; s->timeout->mark = time(NULL) + IDLE_TIMEOUT; if (tag) { taglen = strlen(tag); if(taglen >= sizeof(buf) + 1) { fatal("tag too large",EX_TEMPFAIL); } } buf_reset(&s->last_result); /* the only complication here are literals */ do { /* if 'cont' is set, we're looking at the continuation to a very long line. if 'last' is set, we've seen the tag we're looking for, we're just reading the end of the line. */ if (!cont) eol[0] = '\0'; if (!prot_fgets(buf, sizeof(buf), s->in)) { /* uh oh */ if(s == backend_current && !force_notfatal) fatal("Lost connection to selected backend", EX_UNAVAILABLE); proxy_downserver(s); return PROXY_NOCONNECTION; } sl = strlen(buf); if (tag) { /* Check for the tagged line */ if (!cont && buf[taglen] == ' ' && !strncmp(tag, buf, taglen)) { switch (buf[taglen + 1]) { case 'O': case 'o': r = PROXY_OK; break; case 'N': case 'n': r = PROXY_NO; break; case 'B': case 'b': r = PROXY_BAD; break; default: /* huh? no result? */ if(s == backend_current && !force_notfatal) fatal("Lost connection to selected backend", EX_UNAVAILABLE); proxy_downserver(s); r = PROXY_NOCONNECTION; break; } last = 1; } if (last && !include_last) { /* Store the tagged line */ buf_appendcstr(&s->last_result, buf+taglen+1); buf_cstring(&s->last_result); } } if (sl == (sizeof(buf) - 1) && buf[sl-1] != '\n') { /* only got part of a line */ /* we save the last 64 characters in case it has important literal information */ strcpy(eol, buf + sl - 64); /* write out this part, but we have to keep reading until we hit the end of the line */ if (!last || include_last) prot_write(imapd_out, buf, sl); cont = 1; continue; } else { /* we got the end of the line */ int i; int litlen = 0, islit = 0; if (!last || include_last) prot_write(imapd_out, buf, sl); /* now we have to see if this line ends with a literal */ if (sl < 64) { strcat(eol, buf); } else { strcat(eol, buf + sl - 63); } /* eol now contains the last characters from the line; we want to see if we've hit a literal */ i = strlen(eol); if (i >= 4 && eol[i-1] == '\n' && eol[i-2] == '\r' && eol[i-3] == '}') { /* possible literal */ i -= 4; while (i > 0 && eol[i] != '{' && Uisdigit(eol[i])) { i--; } if (eol[i] == '{') { islit = 1; litlen = atoi(eol + i + 1); } } /* copy the literal over */ if (islit) { while (litlen > 0) { int j = (litlen > (int) sizeof(buf) ? (int) sizeof(buf) : litlen); j = prot_read(s->in, buf, j); if(!j) { /* EOF or other error */ return -1; } if (!last || include_last) prot_write(imapd_out, buf, j); litlen -= j; } /* none of our saved information has any relevance now */ eol[0] = '\0'; /* have to keep going for the end of the line */ cont = 1; continue; } } /* ok, let's read another line */ cont = 0; } while (!last || cont); return r; } int pipe_until_tag(struct backend *s, const char *tag, int force_notfatal) { return pipe_response(s, tag, 0, force_notfatal); } int pipe_including_tag(struct backend *s, const char *tag, int force_notfatal) { int r; r = pipe_response(s, tag, 1, force_notfatal); if (r == PROXY_NOCONNECTION) { /* don't have to worry about downing the server, since * pipe_until_tag does that for us */ prot_printf(imapd_out, "%s NO %s\r\n", tag, error_message(IMAP_SERVER_UNAVAILABLE)); } return r; } static int pipe_to_end_of_response(struct backend *s, int force_notfatal) { return pipe_response(s, NULL, 1, force_notfatal); } /* copy our current input to 's' until we hit a true EOL. 'optimistic_literal' is how happy we should be about assuming that a command will go through by converting synchronizing literals of size less than optimistic_literal to nonsync returns 0 on success, <0 on big failure, >0 on full command not sent */ int pipe_command(struct backend *s, int optimistic_literal) { char buf[2048]; char eol[128]; int sl; s->timeout->mark = time(NULL) + IDLE_TIMEOUT; eol[0] = '\0'; /* again, the complication here are literals */ for (;;) { if (!prot_fgets(buf, sizeof(buf), imapd_in)) { /* uh oh */ return -1; } sl = strlen(buf); if (sl == (sizeof(buf) - 1) && buf[sl-1] != '\n') { /* only got part of a line */ strcpy(eol, buf + sl - 64); /* and write this out, except for what we've saved */ prot_write(s->out, buf, sl - 64); continue; } else { int i, nonsynch = 0, islit = 0, litlen = 0; if (sl < 64) { strcat(eol, buf); } else { /* write out what we have, and copy the last 64 characters to eol */ prot_printf(s->out, "%s", eol); prot_write(s->out, buf, sl - 64); strcpy(eol, buf + sl - 64); } /* now determine if eol has a literal in it */ i = strlen(eol); if (i >= 4 && eol[i-1] == '\n' && eol[i-2] == '\r' && eol[i-3] == '}') { /* possible literal */ i -= 4; if (eol[i] == '+') { nonsynch = 1; i--; } while (i > 0 && eol[i] != '{' && Uisdigit(eol[i])) { i--; } if (eol[i] == '{') { islit = 1; litlen = atoi(eol + i + 1); } } if (islit) { if (nonsynch) { prot_write(s->out, eol, strlen(eol)); } else if (!nonsynch && (litlen <= optimistic_literal)) { prot_printf(imapd_out, "+ i am an optimist\r\n"); prot_write(s->out, eol, strlen(eol) - 3); /* need to insert a + to turn it into a nonsynch */ prot_printf(s->out, "+}\r\n"); } else { /* we do a standard synchronizing literal */ prot_write(s->out, eol, strlen(eol)); /* but here the game gets tricky... */ prot_fgets(buf, sizeof(buf), s->in); /* but for now we cheat */ prot_write(imapd_out, buf, strlen(buf)); if (buf[0] != '+' && buf[1] != ' ') { /* char *p = strchr(buf, ' '); */ /* strncpy(s->last_result, p + 1, LAST_RESULT_LEN);*/ /* stop sending command now */ return 1; } } /* gobble literal and sent it onward */ while (litlen > 0) { int j = (litlen > (int) sizeof(buf) ? (int) sizeof(buf) : litlen); j = prot_read(imapd_in, buf, j); if(!j) { /* EOF or other error */ return -1; } prot_write(s->out, buf, j); litlen -= j; } eol[0] = '\0'; /* have to keep going for the send of the command */ continue; } else { /* no literal, so we're done! */ prot_write(s->out, eol, strlen(eol)); return 0; } } } } void print_listresponse(unsigned cmd, const char *extname, char hier_sep, uint32_t attributes, struct buf *extraflags) { const struct mbox_name_attribute *attr; const char *resp, *sep; switch (cmd) { case LIST_CMD_LSUB: resp = "LSUB"; break; case LIST_CMD_XLIST: resp = "XLIST"; break; default: resp = "LIST"; break; } prot_printf(imapd_out, "* %s (", resp); for (sep = "", attr = mbox_name_attributes; attr->id; attr++) { if (attributes & attr->flag) { prot_printf(imapd_out, "%s%s", sep, attr->id); sep = " "; } } if (extraflags && buf_len(extraflags)) { prot_printf(imapd_out, "%s%s", sep, buf_cstring(extraflags)); } prot_printf(imapd_out, ") \"%c\" ", hier_sep); prot_printastring(imapd_out, extname); if (attributes & MBOX_ATTRIBUTE_CHILDINFO_SUBSCRIBED) { prot_puts(imapd_out, " (CHILDINFO ("); /* RFC 5258: * ; Note 2: The selection options are always returned * ; quoted, unlike their specification in * ; the extended LIST command. */ if (attributes & MBOX_ATTRIBUTE_CHILDINFO_SUBSCRIBED) prot_puts(imapd_out, "\"SUBSCRIBED\""); prot_puts(imapd_out, "))"); } prot_puts(imapd_out, "\r\n"); } /* add subscription flags or filter out non-subscribed mailboxes */ static int check_subs(mbentry_t *mbentry, strarray_t *subs, struct listargs *listargs, uint32_t *flags) { int i, namelen = strlen(mbentry->name); for (i = 0; i < subs->count; i++) { const char *name = strarray_nth(subs, i); if (strncmp(mbentry->name, name, namelen)) continue; else if (!name[namelen]) { /* exact match */ *flags |= MBOX_ATTRIBUTE_SUBSCRIBED; break; } else if (name[namelen] == '.' && (listargs->sel & LIST_SEL_RECURSIVEMATCH)) { *flags |= MBOX_ATTRIBUTE_CHILDINFO_SUBSCRIBED; break; } } /* check if we need to filter out this mailbox */ return (!(listargs->sel & LIST_SEL_SUBSCRIBED) || (*flags & (MBOX_ATTRIBUTE_SUBSCRIBED | MBOX_ATTRIBUTE_CHILDINFO_SUBSCRIBED))); } /* add subscribed mailbox or ancestor of subscribed mailbox to our list */ static void add_sub(strarray_t *subs, mbentry_t *mbentry, uint32_t attributes) { if (attributes & MBOX_ATTRIBUTE_SUBSCRIBED) { /* mailbox is subscribed */ strarray_append(subs, mbentry->name); } else { /* a descendent of mailbox is subscribed */ struct buf child = BUF_INITIALIZER; buf_printf(&child, "%s.", mbentry->name); strarray_appendm(subs, buf_release(&child)); } } static int is_extended_resp(const char *cmd, struct listargs *listargs) { if (!(listargs->ret & (LIST_RET_STATUS | LIST_RET_MYRIGHTS | LIST_RET_METADATA))) { /* backend won't be sending extended response data */ return 0; } else if ((listargs->ret & LIST_RET_STATUS) && !strncasecmp("STATUS", cmd, 6)) { return 1; } else if ((listargs->ret & LIST_RET_MYRIGHTS) && !strncasecmp("MYRIGHTS", cmd, 8)) { return 1; } else if ((listargs->ret & LIST_RET_METADATA) && !strncasecmp("METADATA", cmd, 8)) { return 1; } return 0; } /* This handles piping of the LSUB command, because we have to figure out * what mailboxes actually exist before passing them to the end user. * * It is also needed if we are doing a LIST-EXTENDED, to capture subscriptions * and/or return data that can only be obtained from the backends. */ int pipe_lsub(struct backend *s, const char *userid, const char *tag, int force_notfatal, struct listargs *listargs, strarray_t *subs) { int taglen = strlen(tag); int c; int r = PROXY_OK; int exist_r; static struct buf tagb, cmd, sep, name, ext; struct buf extraflags = BUF_INITIALIZER; int build_list_only = subs && !(listargs->ret & LIST_RET_SUBSCRIBED); int suppress_resp = 0; assert(s); assert(s->timeout); s->timeout->mark = time(NULL) + IDLE_TIMEOUT; while(1) { c = getword(s->in, &tagb); if(c == EOF) { if(s == backend_current && !force_notfatal) fatal("Lost connection to selected backend", EX_UNAVAILABLE); proxy_downserver(s); r = PROXY_NOCONNECTION; goto out; } if(!strncmp(tag, tagb.s, taglen)) { char buf[2048]; if(!prot_fgets(buf, sizeof(buf), s->in)) { if(s == backend_current && !force_notfatal) fatal("Lost connection to selected backend", EX_UNAVAILABLE); proxy_downserver(s); r = PROXY_NOCONNECTION; goto out; } /* Got the end of the response */ buf_appendcstr(&s->last_result, buf); buf_cstring(&s->last_result); switch (buf[0]) { case 'O': case 'o': r = PROXY_OK; break; case 'N': case 'n': r = PROXY_NO; break; case 'B': case 'b': r = PROXY_BAD; break; default: /* huh? no result? */ if(s == backend_current && !force_notfatal) fatal("Lost connection to selected backend", EX_UNAVAILABLE); proxy_downserver(s); r = PROXY_NOCONNECTION; break; } break; /* we're done */ } c = getword(s->in, &cmd); if(c == EOF) { if(s == backend_current && !force_notfatal) fatal("Lost connection to selected backend", EX_UNAVAILABLE); proxy_downserver(s); r = PROXY_NOCONNECTION; goto out; } if(strncasecmp("LSUB", cmd.s, 4) && strncasecmp("LIST", cmd.s, 4)) { if (suppress_resp && is_extended_resp(cmd.s, listargs)) { /* suppress extended return data for this mailbox */ eatline(s->in, c); } else { prot_printf(imapd_out, "%s %s ", tagb.s, cmd.s); r = pipe_to_end_of_response(s, force_notfatal); if (r != PROXY_OK) goto out; } } else { /* build up the response bit by bit */ const struct mbox_name_attribute *attr; uint32_t attributes = 0; /* Get flags */ buf_reset(&extraflags); c = prot_getc(s->in); if (c == '(') { do { c = getword(s->in, &name); for (attr = mbox_name_attributes; attr->id && strcasecmp(name.s, attr->id); attr++); if (attr->id) attributes |= attr->flag; else { if (buf_len(&extraflags)) buf_putc(&extraflags, ' '); buf_appendcstr(&extraflags, name.s); } } while (c == ' '); if (c == ')') { /* end of flags - get the next character */ c = prot_getc(s->in); } } if(c != ' ') { if(s == backend_current && !force_notfatal) fatal("Bad LSUB response from selected backend", EX_UNAVAILABLE); proxy_downserver(s); r = PROXY_NOCONNECTION; goto out; } /* Get separator */ c = getastring(s->in, s->out, &sep); if(c != ' ') { if(s == backend_current && !force_notfatal) fatal("Bad LSUB response from selected backend", EX_UNAVAILABLE); proxy_downserver(s); r = PROXY_NOCONNECTION; goto out; } /* Get name */ c = getastring(s->in, s->out, &name); /* Get extension(s) */ buf_reset(&ext); if (c == ' ') { do { buf_putc(&ext, c); c = prot_getc(s->in); } while (c != '\r' && c != '\n' && c != EOF); /* XXX Currently there are no other documented extensions */ attributes |= MBOX_ATTRIBUTE_CHILDINFO_SUBSCRIBED; } buf_cstring(&ext); if(c == '\r') c = prot_getc(s->in); if(c != '\n') { if(s == backend_current && !force_notfatal) fatal("Bad LSUB response from selected backend", EX_UNAVAILABLE); proxy_downserver(s); r = PROXY_NOCONNECTION; goto out; } /* lookup name */ exist_r = 1; char *intname = mboxname_from_external(name.s, &imapd_namespace, userid); mbentry_t *mbentry = NULL; exist_r = mboxlist_lookup(intname, &mbentry, NULL); free(intname); if(!exist_r && (mbentry->mbtype & MBTYPE_RESERVE)) exist_r = IMAP_MAILBOX_RESERVED; /* suppress responses to client if we're just building subs list */ suppress_resp = build_list_only; if (!exist_r) { /* we need to remove \Noselect if it's in our mailboxes.db */ attributes &= ~(MBOX_ATTRIBUTE_NOSELECT | MBOX_ATTRIBUTE_NONEXISTENT); if (subs) { /* process subscriptions */ if (s != backend_inbox) { /* backend server won't have subs info - add sub flags or filter out non-sub mailboxes */ if (!check_subs(mbentry, subs, listargs, &attributes)) { /* unsubscribed and we only want subscriptions */ suppress_resp = 1; } } else if (listargs->sel & LIST_SEL_SUBSCRIBED) { /* If we're just building subs list, we want ALL subscriptions added to list. Otherwise just add those NOT on Inbox server. */ if (build_list_only || strcmp(mbentry->server, backend_inbox->hostname)) { add_sub(subs, mbentry, attributes); /* suppress the response - those NOT added to the list are sent to client in subsequent requests */ suppress_resp = 1; } } } } if (!suppress_resp) { /* send response to the client */ print_listresponse(listargs->cmd, name.s, sep.s[0], attributes, &extraflags); /* send any PROXY_ONLY metadata items */ for (c = 0; c < listargs->metaitems.count; c++) { const char *entry = strarray_nth(&listargs->metaitems, c); if (mbentry && !strcmp(entry, "/shared" IMAP_ANNOT_NS "server")) { prot_puts(imapd_out, "* METADATA "); prot_printastring(imapd_out, name.s); prot_puts(imapd_out, " ("); prot_printstring(imapd_out, entry); prot_puts(imapd_out, " "); prot_printstring(imapd_out, mbentry->server); prot_puts(imapd_out, ")\r\n"); } } } mboxlist_entry_free(&mbentry); } } /* while(1) */ out: buf_free(&extraflags); return r; } /* xxx start of separate proxy-only code (remove when we move to a unified environment) */ static int chomp(struct protstream *p, const char *s) { int c = prot_getc(p); while (*s) { if (tolower(c) != tolower(*s)) { break; } s++; c = prot_getc(p); } if (*s) { if (c != EOF) prot_ungetc(c, p); c = EOF; } return c; } #define BUFGROWSIZE 100 /* read characters from 'p' until 'end' is seen */ static char *grab(struct protstream *p, char end) { int alloc = BUFGROWSIZE, cur = 0; int c = -1; char *ret = (char *) xmalloc(alloc); ret[0] = '\0'; while ((c = prot_getc(p)) != end) { if (c == EOF) break; if (cur == alloc - 1) { alloc += BUFGROWSIZE; ret = xrealloc(ret, alloc); } ret[cur++] = c; } if (cur) ret[cur] = '\0'; return ret; } /* remove \Recent from the flags */ static char *editflags(char *flags) { char *p; p = flags; while ((p = strchr(p, '\\')) != NULL) { if (!strncasecmp(p + 1, "recent", 6)) { if (p[7] == ' ') { /* shift everything over so that \recent vanishes */ char *q; q = p + 8; while (*q) { *p++ = *q++; } *p = '\0'; } else if (p[7] == '\0') { /* last flag in line */ *p = '\0'; } else { /* not really \recent, i guess */ p++; } } else { p++; } } return flags; } void proxy_copy(const char *tag, char *sequence, char *name, int myrights, int usinguid, struct backend *s) { char mytag[128]; struct d { char *idate; char *flags; unsigned int seqno, uid; struct d *next; } *head, *p, *q; int c; /* find out what the flags & internaldate for this message are */ proxy_gentag(mytag, sizeof(mytag)); prot_printf(backend_current->out, "%s %s %s (Flags Internaldate)\r\n", tag, usinguid ? "Uid Fetch" : "Fetch", sequence); head = (struct d *) xmalloc(sizeof(struct d)); head->flags = NULL; head->idate = NULL; head->seqno = head->uid = 0; head->next = NULL; p = head; /* read all the responses into the linked list */ for (/* each FETCH response */;;) { unsigned int seqno = 0, uidno = 0; char *flags = NULL, *idate = NULL; /* read a line */ c = prot_getc(backend_current->in); if (c != '*') break; c = prot_getc(backend_current->in); if (c != ' ') { /* protocol error */ c = EOF; break; } /* check for OK/NO/BAD/BYE response */ if (!isdigit(c = prot_getc(backend_current->in))) { prot_printf(imapd_out, "* %c", c); pipe_to_end_of_response(backend_current, 0); continue; } /* read seqno */ prot_ungetc(c, backend_current->in); c = getuint32(backend_current->in, &seqno); if (seqno == 0 || c != ' ') { /* we suck and won't handle this case */ c = EOF; break; } c = chomp(backend_current->in, "fetch ("); if (c == EOF) { c = chomp(backend_current->in, "exists\r"); if (c == '\n') { /* got EXISTS response */ prot_printf(imapd_out, "* %d EXISTS\r\n", seqno); continue; } } if (c == EOF) { /* XXX the "exists" check above will eat "ex" */ c = chomp(backend_current->in, "punge\r"); if (c == '\n') { /* got EXPUNGE response */ prot_printf(imapd_out, "* %d EXPUNGE\r\n", seqno); continue; } } if (c == EOF) { c = chomp(backend_current->in, "recent\r"); if (c == '\n') { /* got RECENT response */ prot_printf(imapd_out, "* %d RECENT\r\n", seqno); continue; } } /* huh, don't get this response */ if (c == EOF) break; for (/* each fetch item */;;) { /* looking at the first character in an item */ switch (c) { case 'f': case 'F': /* flags? */ c = chomp(backend_current->in, "lags"); if (c != ' ') { c = EOF; } else c = prot_getc(backend_current->in); if (c != '(') { c = EOF; } else { flags = grab(backend_current->in, ')'); c = prot_getc(backend_current->in); } break; case 'i': case 'I': /* internaldate? */ c = chomp(backend_current->in, "nternaldate"); if (c != ' ') { c = EOF; } else c = prot_getc(backend_current->in); if (c != '"') { c = EOF; } else { idate = grab(backend_current->in, '"'); c = prot_getc(backend_current->in); } break; case 'u': case 'U': /* uid */ c = chomp(backend_current->in, "id"); if (c != ' ') { c = EOF; } else c = getuint32(backend_current->in, &uidno); break; default: /* hmm, don't like the smell of it */ c = EOF; break; } /* looking at either SP separating items or a RPAREN */ if (c == ' ') { c = prot_getc(backend_current->in); } else if (c == ')') break; else { c = EOF; break; } } /* if c == EOF we have either a protocol error or a situation we can't handle, and we should die. */ if (c == ')') c = prot_getc(backend_current->in); if (c == '\r') c = prot_getc(backend_current->in); if (c != '\n') { c = EOF; free(flags); free(idate); break; } /* if we're missing something, we should echo */ if (!flags || !idate) { char sep = '('; prot_printf(imapd_out, "* %d FETCH ", seqno); if (uidno) { prot_printf(imapd_out, "%cUID %d", sep, uidno); sep = ' '; } if (flags) { prot_printf(imapd_out, "%cFLAGS %s", sep, flags); sep = ' '; } if (idate) { prot_printf(imapd_out, "%cINTERNALDATE %s", sep, idate); sep = ' '; } prot_printf(imapd_out, ")\r\n"); if (flags) free(flags); if (idate) free(idate); continue; } /* add to p->next */ p->next = xmalloc(sizeof(struct d)); p = p->next; p->idate = idate; p->flags = editflags(flags); p->uid = uidno; p->seqno = seqno; p->next = NULL; } if (c != EOF) { prot_ungetc(c, backend_current->in); /* we should be looking at the tag now */ pipe_until_tag(backend_current, tag, 0); } if (c == EOF) { /* uh oh, we're not happy */ fatal("Lost connection to selected backend", EX_UNAVAILABLE); } /* start the append */ prot_printf(s->out, "%s Append {" SIZE_T_FMT "+}\r\n%s", tag, strlen(name), name); prot_printf(backend_current->out, "%s %s %s (Rfc822.peek)\r\n", mytag, usinguid ? "Uid Fetch" : "Fetch", sequence); for (/* each FETCH response */;;) { unsigned int seqno = 0, uidno = 0; /* read a line */ c = prot_getc(backend_current->in); if (c != '*') break; c = prot_getc(backend_current->in); if (c != ' ') { /* protocol error */ c = EOF; break; } /* check for OK/NO/BAD/BYE response */ if (!isdigit(c = prot_getc(backend_current->in))) { prot_printf(imapd_out, "* %c", c); pipe_to_end_of_response(backend_current, 0); continue; } /* read seqno */ prot_ungetc(c, backend_current->in); c = getuint32(backend_current->in, &seqno); if (seqno == 0 || c != ' ') { /* we suck and won't handle this case */ c = EOF; break; } c = chomp(backend_current->in, "fetch ("); if (c == EOF) { /* not a fetch response */ c = chomp(backend_current->in, "exists\r"); if (c == '\n') { /* got EXISTS response */ prot_printf(imapd_out, "* %d EXISTS\r\n", seqno); continue; } } if (c == EOF) { /* not an exists response */ /* XXX the "exists" check above will eat "ex" */ c = chomp(backend_current->in, "punge\r"); if (c == '\n') { /* got EXPUNGE response */ prot_printf(imapd_out, "* %d EXPUNGE\r\n", seqno); continue; } } if (c == EOF) { /* not an exists response */ c = chomp(backend_current->in, "recent\r"); if (c == '\n') { /* got RECENT response */ prot_printf(imapd_out, "* %d RECENT\r\n", seqno); continue; } } if (c == EOF) { /* huh, don't get this response */ break; } /* find seqno in the list */ p = head; while (p->next && seqno != p->next->seqno) p = p->next; if (!p->next) break; q = p->next; p->next = q->next; for (/* each fetch item */;;) { int sz = 0; switch (c) { case 'u': case 'U': c = chomp(backend_current->in, "id"); if (c != ' ') { c = EOF; } else c = getuint32(backend_current->in, &uidno); break; case 'r': case 'R': c = chomp(backend_current->in, "fc822"); if (c == ' ') c = prot_getc(backend_current->in); if (c != '{') { /* NIL? */ eatline(backend_current->in, c); c = EOF; } else c = getint32(backend_current->in, &sz); if (c == '}') c = prot_getc(backend_current->in); if (c == '\r') c = prot_getc(backend_current->in); if (c != '\n') c = EOF; if (c != EOF) { /* append p to s->out */ prot_printf(s->out, " (%s) \"%s\" {%d+}\r\n", q->flags, q->idate, sz); while (sz) { char buf[2048]; int j = (sz > (int) sizeof(buf) ? (int) sizeof(buf) : sz); j = prot_read(backend_current->in, buf, j); if(!j) break; prot_write(s->out, buf, j); sz -= j; } c = prot_getc(backend_current->in); } break; /* end of case */ default: c = EOF; break; } /* looking at either SP separating items or a RPAREN */ if (c == ' ') { c = prot_getc(backend_current->in); } else if (c == ')') break; else { c = EOF; break; } } /* if c == EOF we have either a protocol error or a situation we can't handle, and we should die. */ if (c == ')') c = prot_getc(backend_current->in); if (c == '\r') c = prot_getc(backend_current->in); if (c != '\n') { c = EOF; break; } /* free q */ free(q->idate); free(q->flags); free(q); } if (c != EOF) { char *appenduid, *b; int res; /* pushback the first character of the tag we're looking at */ prot_ungetc(c, backend_current->in); /* nothing should be left in the linked list */ assert(head->next == NULL); /* ok, finish the append; we need the UIDVALIDITY and UIDs to return as part of our COPYUID response code */ prot_printf(s->out, "\r\n"); /* should be looking at 'mytag' on 'backend_current', 'tag' on 's' */ pipe_until_tag(backend_current, mytag, 0); res = pipe_until_tag(s, tag, 0); if (res == PROXY_OK) { if (myrights & ACL_READ) { appenduid = strchr(s->last_result.s, '['); /* skip over APPENDUID */ if (appenduid) { appenduid += strlen("[appenduid "); b = strchr(appenduid, ']'); if (b) *b = '\0'; prot_printf(imapd_out, "%s OK [COPYUID %s] %s\r\n", tag, appenduid, error_message(IMAP_OK_COMPLETED)); } else prot_printf(imapd_out, "%s OK %s\r\n", tag, s->last_result.s); } else { prot_printf(imapd_out, "%s OK %s\r\n", tag, error_message(IMAP_OK_COMPLETED)); } } else { prot_printf(imapd_out, "%s %s", tag, s->last_result.s); } } else { /* abort the append */ prot_printf(s->out, " {0+}\r\n\r\n"); pipe_until_tag(backend_current, mytag, 0); pipe_until_tag(s, tag, 0); /* report failure */ prot_printf(imapd_out, "%s NO inter-server COPY failed\r\n", tag); } /* free dynamic memory */ while (head) { p = head; head = head->next; if (p->idate) free(p->idate); if (p->flags) free(p->flags); free(p); } } /* xxx end of separate proxy-only code */ int proxy_catenate_url(struct backend *s, struct imapurl *url, FILE *f, unsigned long *size, const char **parseerr) { char mytag[128]; int c, r = 0, found = 0; unsigned int uidvalidity = 0; *size = 0; *parseerr = NULL; /* select the mailbox (read-only) */ proxy_gentag(mytag, sizeof(mytag)); prot_printf(s->out, "%s Examine {" SIZE_T_FMT "+}\r\n%s\r\n", mytag, strlen(url->mailbox), url->mailbox); for (/* each examine response */;;) { /* read a line */ c = prot_getc(s->in); if (c != '*') break; c = prot_getc(s->in); if (c != ' ') { /* protocol error */ c = EOF; break; } c = chomp(s->in, "ok [uidvalidity"); if (c == EOF) { /* we don't care about this response */ eatline(s->in, c); continue; } /* read uidvalidity */ c = getuint32(s->in, &uidvalidity); if (c != ']') { c = EOF; break; } eatline(s->in, c); /* we don't care about the rest of the line */ } if (c != EOF) { prot_ungetc(c, s->in); /* we should be looking at the tag now */ eatline(s->in, c); } if (c == EOF) { /* uh oh, we're not happy */ fatal("Lost connection to backend", EX_UNAVAILABLE); } if (url->uidvalidity && (uidvalidity != url->uidvalidity)) { *parseerr = "Uidvalidity of mailbox has changed"; r = IMAP_BADURL; goto unselect; } /* fetch the bodypart */ proxy_gentag(mytag, sizeof(mytag)); prot_printf(s->out, "%s Uid Fetch %lu Body.Peek[%s]\r\n", mytag, url->uid, url->section ? url->section : ""); for (/* each fetch response */;;) { unsigned int seqno; next_resp: /* read a line */ c = prot_getc(s->in); if (c != '*') break; c = prot_getc(s->in); if (c != ' ') { /* protocol error */ c = EOF; break; } /* read seqno */ c = getuint32(s->in, &seqno); if (seqno == 0 || c != ' ') { /* we suck and won't handle this case */ c = EOF; break; } c = chomp(s->in, "fetch ("); if (c == EOF) { /* not a fetch response */ eatline(s->in, c); continue; } for (/* each fetch item */;;) { unsigned uid, sz = 0; switch (c) { case 'u': case 'U': c = chomp(s->in, "id"); if (c != ' ') { c = EOF; } else { c = getuint32(s->in, &uid); if (uid != url->uid) { /* not our response */ eatline(s->in, c); goto next_resp; } } break; case 'b': case 'B': c = chomp(s->in, "ody["); while (c != ']') c = prot_getc(s->in); if (c == ']') c = prot_getc(s->in); if (c == ' ') c = prot_getc(s->in); if (c == '{') { c = getuint32(s->in, &sz); if (c == '}') c = prot_getc(s->in); if (c == '\r') c = prot_getc(s->in); if (c != '\n') c = EOF; } else if (c == 'n' || c == 'N') { c = chomp(s->in, "il"); r = IMAP_BADURL; *parseerr = "No such message part"; } if (c != EOF) { /* catenate to f */ found = 1; *size = sz; while (sz) { char buf[2048]; int j = (sz > sizeof(buf) ? sizeof(buf) : sz); j = prot_read(s->in, buf, j); if(!j) break; fwrite(buf, j, 1, f); sz -= j; } c = prot_getc(s->in); } break; /* end of case */ default: /* probably a FLAGS item */ eatline(s->in, c); goto next_resp; } /* looking at either SP separating items or a RPAREN */ if (c == ' ') { c = prot_getc(s->in); } else if (c == ')') break; else { c = EOF; break; } } /* if c == EOF we have either a protocol error or a situation we can't handle, and we should die. */ if (c == ')') c = prot_getc(s->in); if (c == '\r') c = prot_getc(s->in); if (c != '\n') { c = EOF; break; } } if (c != EOF) { prot_ungetc(c, s->in); /* we should be looking at the tag now */ eatline(s->in, c); } if (c == EOF) { /* uh oh, we're not happy */ fatal("Lost connection to backend", EX_UNAVAILABLE); } unselect: /* unselect the mailbox */ proxy_gentag(mytag, sizeof(mytag)); prot_printf(s->out, "%s Unselect\r\n", mytag); for (/* each unselect response */;;) { /* read a line */ c = prot_getc(s->in); if (c != '*') break; c = prot_getc(s->in); if (c != ' ') { /* protocol error */ c = EOF; break; } /* we don't care about this response */ eatline(s->in, c); } if (c != EOF) { prot_ungetc(c, s->in); /* we should be looking at the tag now */ eatline(s->in, c); } if (c == EOF) { /* uh oh, we're not happy */ fatal("Lost connection to backend", EX_UNAVAILABLE); } if (!r && !found) { r = IMAP_BADURL; *parseerr = "No such message in mailbox"; } return r; } /* Proxy GETMETADATA commands to backend */ int annotate_fetch_proxy(const char *server, const char *mbox_pat, const strarray_t *entry_pat, const strarray_t *attribute_pat) { struct backend *be; int i, j; char mytag[128]; assert(server && mbox_pat && entry_pat && attribute_pat); be = proxy_findserver(server, &imap_protocol, proxy_userid, &backend_cached, &backend_current, &backend_inbox, imapd_in); if (!be) return IMAP_SERVER_UNAVAILABLE; /* Send command to remote */ proxy_gentag(mytag, sizeof(mytag)); prot_printf(be->out, "%s GETANNOTATION \"%s\" (", mytag, mbox_pat); for (i = 0; i < entry_pat->count; i++) { const char *entry = strarray_nth(entry_pat, i); for (j = 0; j < attribute_pat->count; j++) { const char *scope, *attr = strarray_nth(attribute_pat, j); if (!strcmp(attr, "value.shared")) { scope = "/shared"; } else if (!strcmp(attr, "value.priv")) { scope = "/private"; } else { syslog(LOG_ERR, "won't get deprecated annotation attribute %s", attr); continue; } prot_printf(be->out, "%s%s%s", i ? " " : "", scope, entry); } } prot_printf(be->out, ")\r\n"); prot_flush(be->out); /* Pipe the results. Note that backend-current may also pipe us other messages. */ pipe_until_tag(be, mytag, 0); return 0; } /* Proxy SETMETADATA commands to backend */ int annotate_store_proxy(const char *server, const char *mbox_pat, struct entryattlist *entryatts) { struct backend *be; struct entryattlist *e; struct attvaluelist *av; char mytag[128]; struct buf entrybuf = BUF_INITIALIZER; assert(server && mbox_pat && entryatts); be = proxy_findserver(server, &imap_protocol, proxy_userid, &backend_cached, &backend_current, &backend_inbox, imapd_in); if (!be) return IMAP_SERVER_UNAVAILABLE; /* Send command to remote */ proxy_gentag(mytag, sizeof(mytag)); prot_printf(be->out, "%s SETMETADATA \"%s\" (", mytag, mbox_pat); for (e = entryatts; e; e = e->next) { for (av = e->attvalues; av; av = av->next) { assert(av->attrib); if (!strcmp(av->attrib, "value.shared")) { buf_setcstr(&entrybuf, "/shared"); } else if (!strcmp(av->attrib, "value.priv")) { buf_setcstr(&entrybuf, "/private"); } else { syslog(LOG_ERR, "won't proxy annotation with deprecated attribute %s", av->attrib); buf_free(&entrybuf); return IMAP_INTERNAL; } buf_appendcstr(&entrybuf, e->entry); /* Print the entry-value pair */ prot_printamap(be->out, entrybuf.s, entrybuf.len); prot_putc(' ', be->out); prot_printamap(be->out, av->value.s, av->value.len); if (av->next) prot_putc(' ', be->out); } if (e->next) prot_putc(' ', be->out); } prot_printf(be->out, ")\r\n"); prot_flush(be->out); /* Pipe the results. Note that backend-current may also pipe us other messages. */ pipe_until_tag(be, mytag, 0); buf_free(&entrybuf); return 0; } char *find_free_server(void) { const char *servers = config_getstring(IMAPOPT_SERVERLIST); char *server = NULL; if (servers) { if (!server_parts) { server_parts = xzmalloc(sizeof(partlist_t)); partlist_initialize( server_parts, proxy_part_filldata, NULL, servers, NULL, partlist_getmode(config_getstring(IMAPOPT_SERVERLIST_SELECT_MODE)), config_getint(IMAPOPT_SERVERLIST_SELECT_SOFT_USAGE_LIMIT), config_getint(IMAPOPT_SERVERLIST_SELECT_USAGE_REINIT) ); } server = (char *)partlist_select_value(server_parts); } return server; } static void proxy_part_filldata(partlist_t *part_list, int idx) { char mytag[128]; struct backend *be; partitem_t *item = &part_list->items[idx]; item->id = 0; item->available = 0; item->total = 0; item->quota = 0.; syslog(LOG_DEBUG, "checking free space on server '%s'", item->value); /* connect to server */ be = proxy_findserver(item->value, &imap_protocol, proxy_userid, &backend_cached, &backend_current, &backend_inbox, imapd_in); if (be) { uint64_t server_available = 0; uint64_t server_total = 0; const char *annot = (part_list->mode == PART_SELECT_MODE_FREESPACE_MOST) ? "freespace/total" : "freespace/percent/most"; struct buf cmd = BUF_INITIALIZER; int c; /* fetch annotation from remote */ proxy_gentag(mytag, sizeof(mytag)); if (CAPA(be, CAPA_METADATA)) { buf_printf(&cmd, "METADATA \"\" (\"/shared" IMAP_ANNOT_NS "%s\"", annot); } else { buf_printf(&cmd, "ANNOTATION \"\" \"" IMAP_ANNOT_NS "%s\" " "(\"value.shared\"", annot); } prot_printf(be->out, "%s GET%s)\r\n", mytag, buf_cstring(&cmd)); prot_flush(be->out); for (/* each annotation response */;;) { /* read a line */ c = prot_getc(be->in); if (c != '*') break; c = prot_getc(be->in); if (c != ' ') { /* protocol error */ c = EOF; break; } c = chomp(be->in, buf_cstring(&cmd)); if (c == ' ') c = prot_getc(be->in); if ((c == EOF) || (c != '\"')) { /* we don't care about this response */ eatline(be->in, c); continue; } /* read available */ c = getuint64(be->in, &server_available); if (c != ';') { c = EOF; break; } /* read total */ c = getuint64(be->in, &server_total); if (c != '\"') { c = EOF; break; } eatline(be->in, c); /* we don't care about the rest of the line */ } buf_free(&cmd); if (c != EOF) { prot_ungetc(c, be->in); /* we should be looking at the tag now */ eatline(be->in, c); } if (c == EOF) { /* uh oh, we're not happy */ fatal("Lost connection to backend", EX_UNAVAILABLE); } /* unique id */ item->id = idx; item->available = server_available; item->total = server_total; } }