Exemplo n.º 1
0
int detect_mitm(isieve_t *obj, char *mechlist)
{
    char *new_mechlist;
    int ch, r = 0;

    /* wait and probe for possible automatic capability response */
    usleep(250000);
    prot_NONBLOCK(obj->pin);
    if ((ch = prot_getc(obj->pin)) != EOF) {
	/* automatic capability response */
	prot_ungetc(ch, obj->pin);
    } else {
	/* manually ask for capabilities */
	prot_printf(obj->pout, "CAPABILITY\r\n");
	prot_flush(obj->pout);
    }
    prot_BLOCK(obj->pin);

    if ((new_mechlist = read_capability(obj))) {
	/* if the server still advertises SASL mechs, compare lists */
	r = strcmp(new_mechlist, mechlist);
	free(new_mechlist);
    }

    return r;
}
Exemplo n.º 2
0
/* 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;
}
Exemplo n.º 3
0
HIDDEN int parse_backup_line(struct protstream *in, time_t *ts,
                             struct buf *cmd, struct dlist **kin)
{
    struct dlist *dl = NULL;
    struct buf buf = BUF_INITIALIZER;
    int64_t t;
    int c;

    c = prot_getc(in);
    if (c == '#')
        eatline(in, c);
    else
        prot_ungetc(c, in);

    c = getint64(in, &t);
    if (c == EOF)
        goto fail;

    c = getword(in, &buf);
    if (c == EOF)
        goto fail;

    c = dlist_parse(&dl, /*parsekeys*/ 1, 1, in);

    if (!dl) {
        fprintf(stderr, "\ndidn't parse dlist, error %i\n", c);
        goto fail;
    }

    if (c == '\r') c = prot_getc(in);
    if (c != '\n') {
        fprintf(stderr, "expected newline, got '%c'\n", c);
        eatline(in, c);
        goto fail;
    }

    if (kin) *kin = dl;
    if (cmd) buf_copy(cmd, &buf);
    if (ts) *ts = (time_t) t;
    buf_free(&buf);
    return c;

fail:
    if (dl) dlist_free(&dl);
    buf_free(&buf);
    return c;
}
Exemplo n.º 4
0
int yylex(lexstate_t * lvalp, void * client)
{
  int ch;
  char buffer[ACAP_MAX_QSTR_LEN];	/* big enough for everything */

  char *buff_ptr = buffer; /* ptr into the buffer */
  char *buff_end = buffer + ACAP_MAX_QSTR_LEN -1;

  unsigned long count=0;

  int result = SIEVE_OK;

  int synchronizing;  /* wheather we are in the process of reading a
			 synchronizing string or not */

  struct protstream *stream=(struct protstream *) client;
  
  while (1)
  {

    /* get a character
       this may block on a read if there is nothing
       in the buffer */

    ch = prot_getc(stream);

    if (ch == -1)
	return SIEVE_FAIL;

    switch (lexer_state)
    {
    

    case LEXER_STATE_RECOVER:
      if (ch == '\r')
	lexer_state=LEXER_STATE_RECOVER_CR;
      break;
    case LEXER_STATE_RECOVER_CR:
      if (ch == '\n')
	lexer_state=LEXER_STATE_NORMAL;
      return EOL;
    case LEXER_STATE_CR:
      if (ch == '\n') {
	lexer_state=LEXER_STATE_NORMAL;
	return EOL;
      }
      /* otherwise, life is bad */
      ERR_PUSHBACK();
    case LEXER_STATE_QSTR:
      if (ch == '\"') {
	/* End of the string */
	lvalp->str = NULL;
	result = string_allocate(buff_ptr - buffer, buffer, &lvalp->str);
	if (result != SIEVE_OK)
	    ERR_PUSHBACK();
	lexer_state=LEXER_STATE_NORMAL;
	return STRING;
      }
      if (ch == '\0'
	  || 0x7F < ((unsigned char)ch))
	ERR_PUSHBACK();
      /* Otherwise, we're appending a character */
      if (buff_end <= buff_ptr)
	ERR_PUSHBACK();		/* too long! */
      if (ch == '\\') {
	ch=prot_getc(stream);

	if (result != SIEVE_OK)
	  ERR();
	if (ch != '\"' && ch != '\\')
	  ERR_PUSHBACK();
      }
      *buff_ptr++ = ch;
      break;
    case LEXER_STATE_LITERAL:
      if (('0' <= ch) && (ch <= '9')) {
	unsigned long   newcount = count * 10 + (ch - '0');

	if (newcount < count)
	  ERR_PUSHBACK();	/* overflow */
	/*
	 * XXX This should be fatal if non-synchronizing.
	 */
	count = newcount;
	break;
      }
      synchronizing = FALSE;

      if (ch != '}')
	ERR_PUSHBACK();
      ch=prot_getc(stream);
      if (ch < 0)
	ERR();
      if (ch != '\r')
	ERR_PUSHBACK();
      ch=prot_getc(stream);
      if (ch < 0)
	ERR();
      if (ch != '\n')
	ERR_PUSHBACK();

      lvalp->str = NULL;
      result = string_allocate(count, NULL, &lvalp->str);
      if (result != SIEVE_OK)
	ERR_PUSHBACK();

      /* there is a literal string on the wire. let's read it */
      {
	char           *it = string_DATAPTR(lvalp->str),
	               *end = it + count;

	while (it < end) {
	  *it=prot_getc(stream);
	  it++;
	}
	*it = '\0';
      }
      lexer_state=LEXER_STATE_NORMAL;
      return STRING;
    case LEXER_STATE_NUMBER:
      if (('0' <= ch) && (ch <= '9')) {
	unsigned long   newcount = count * 10 + (ch - '0');

	if (newcount < count)
	  ERR_PUSHBACK();	/* overflow */
	count = newcount;
      } else {
	lvalp->number = count;
	lexer_state=LEXER_STATE_NORMAL;
	prot_ungetc(ch, stream);
	return NUMBER;
      }
      break;
    case LEXER_STATE_NORMAL:
      if (isalpha((unsigned char) ch)) {
	lexer_state=LEXER_STATE_ATOM;
	*buff_ptr++ = tolower(ch);
	break;
      }
      switch (ch) {
      case '(':
	return '(';
      case ')':
	return ')';
      case ' ':
	return ' ';
      case '\"':
	lexer_state=LEXER_STATE_QSTR;
	break;
      case '*':
	return '*';
      case '0': /* fall through all numbers */
      case '1':
      case '2':
      case '3':
      case '4':
      case '5':
      case '6':
      case '7':
      case '8':
      case '9':
	count = ch - '0';
	lexer_state=LEXER_STATE_NUMBER;
	break;
      case '{':
	count = 0;
	synchronizing = TRUE;
	lexer_state=LEXER_STATE_LITERAL;
	break;
      case '\r':
	lexer_state=LEXER_STATE_CR;
	break;
      case '\n':
	lexer_state=LEXER_STATE_NORMAL;
	return EOL;
	break;
      default:
	ERR_PUSHBACK();
      }
      break;
    case LEXER_STATE_ATOM:
      if (!isalpha((unsigned char) ch)) {
	int token;

	buffer[ buff_ptr - buffer] = '\0';

	/* We've got the atom. */
	token = token_lookup((char *) buffer, (int) (buff_ptr - buffer));

	if (token!=-1) {
	  lexer_state=LEXER_STATE_NORMAL;
	  prot_ungetc(ch, stream);

	  return token;
	} else
	  ERR_PUSHBACK();
      }
      if (buff_end <= buff_ptr)
	ERR_PUSHBACK();		/* atom too long */
      *buff_ptr++ = tolower(ch);
      break;
    }

  } /* while (1) */

  /* never reached */
}
Exemplo n.º 5
0
int timlex(mystring_t **outstr, unsigned long *outnum,  struct protstream *stream)
{

  int ch;

  char *buff_ptr;
  char *buff_end;
  unsigned long tmpnum = 0;
  unsigned long count=0;

  int result = TIMSIEVE_OK;


  buff_ptr = buffer; /* ptr into the buffer */
  buff_end = buffer + maxscriptsize - 10; /* ptr to end of buffer */

  
  while (1)
  {

    /* get a character
       this may block on a read if there is nothing
       in the buffer */

    ch=prot_getc(stream);

    if (ch==EOF) {
	/* Lost connection */
	return EOF;
    }

    switch (lexer_state)
    {
    

    case LEXER_STATE_RECOVER:
      if (ch == '\n') {
	lexer_state=LEXER_STATE_NORMAL;
      }
      if (ch == '\r') 
	lexer_state=LEXER_STATE_RECOVER_CR;
      break;
    case LEXER_STATE_RECOVER_CR:
      if (ch == '\n')
	lexer_state=LEXER_STATE_NORMAL;
      break;
    case LEXER_STATE_CR:
      if (ch == '\n') {
	lexer_state=LEXER_STATE_NORMAL;
	return EOL;
      }
      /* otherwise, life is bad */
      ERR_PUSHBACK();
    case LEXER_STATE_QSTR:
      if (ch == '\"') {
	/* End of the string */
	if (outstr!=NULL)
	{
	  *outstr = NULL;
	  result = string_allocate(buff_ptr - buffer, buffer, outstr);
	  if (result != TIMSIEVE_OK)
	    ERR_PUSHBACK();
	}
	  /*} */
	lexer_state=LEXER_STATE_NORMAL;
	return STRING;
      }
      /* illegal character */
      if (ch == '\0'
	  || ch == '\r'
	  || ch == '\n'
	  || 0x7F < ((unsigned char)ch))
      {
	ERR_PUSHBACK();
      }

      /* Otherwise, we're appending a character */
      if (buff_end <= buff_ptr)
	ERR_PUSHBACK();		/* too long! */
      if (ch == '\\') {
	ch=prot_getc(stream);

	if (result != TIMSIEVE_OK)
	  ERR();
	if (ch != '\"' && ch != '\\')
	  ERR_PUSHBACK();
      }
      *buff_ptr++ = ch;
      break;
    case LEXER_STATE_LITERAL:
      if (('0' <= ch) && (ch <= '9')) {
	unsigned long   newcount = count * 10 + (ch - '0');

	if (newcount < count)
	  ERR_PUSHBACK();	/* overflow */
	/*
	 * XXX This should be fatal if non-synchronizing.
	 */
	count = newcount;
	break;
      }
      if (ch != '+')
	ERR_PUSHBACK();
      ch=prot_getc(stream);
      if (ch != '}')
	ERR_PUSHBACK();
      ch=prot_getc(stream);
      if (ch < 0)
	ERR();
      if (ch != '\r')
	ERR_PUSHBACK();
      ch=prot_getc(stream);
      if (ch < 0)
	ERR();
      if (ch != '\n')
	ERR_PUSHBACK();

      if (count > maxscriptsize) {
	  /* too big, eat the input */
	  for(;count > 0;count--) {
	      if(prot_getc(stream)==EOF)
		  break;
	  }
	  
	  ERR();
      }

      if (outstr!=NULL)
      {
	*outstr = NULL;
	result = string_allocate(count, NULL, outstr);
	if (result != TIMSIEVE_OK)
	  ERR_PUSHBACK();
      }

      /* there is a literal string on the wire. let's read it */
      if (outstr!=NULL) {
	char           *it = string_DATAPTR(*outstr),
	               *end = it + count;

	while (it < end) {
	  *it=prot_getc(stream);
	  it++;
	}
      } else {
	/* just read the chars and throw them away */
	unsigned long lup;

	for (lup=0;lup<count;lup++)
	  (void)prot_getc(stream);
      }
      lexer_state=LEXER_STATE_NORMAL;
      return STRING;
    case LEXER_STATE_NUMBER:

	if (Uisdigit(ch)) {
	    unsigned long   newcount = tmpnum * 10 + (ch - '0');

	    if (newcount < tmpnum)
		ERR_PUSHBACK();	/* overflow */
	    tmpnum = newcount;
	} else {
	    lexer_state=LEXER_STATE_NORMAL;
	    prot_ungetc(ch, stream);

	    if (outnum) *outnum = tmpnum;

	    return NUMBER;
	}
	
	break;
    case LEXER_STATE_NORMAL:
      if (Uisalpha(ch)) {
	lexer_state=LEXER_STATE_ATOM;
	*buff_ptr++ = tolower(ch);
	break;
      }
      if (Uisdigit(ch)) {
	lexer_state=LEXER_STATE_NUMBER;
	tmpnum = ch -'0';
	break;
      }
      switch (ch) {
      case '(':
	return '(';
      case ')':
	return ')';
      case ' ':
	return ' ';
      case '\"':
	lexer_state=LEXER_STATE_QSTR;
	break;
      case '*':
	return '*';
      case '{':
	count = 0;
	lexer_state=LEXER_STATE_LITERAL;
	break;
      case '\r':
	lexer_state=LEXER_STATE_CR;
	break;
      case '\n':
	lexer_state=LEXER_STATE_NORMAL;
	return EOL;
	break;
      default:
	return ch;
      }
      break;
    case LEXER_STATE_ATOM:
      if (!Uisalpha(ch)) {
	int token;

	buffer[ buff_ptr - buffer] = '\0';

	/* We've got the atom. */
	token = token_lookup((char *) buffer, (int) (buff_ptr - buffer));

	if (token!=-1) {
	  lexer_state=LEXER_STATE_NORMAL;
	  prot_ungetc(ch, stream);

	  return token;
	} else
	  ERR_PUSHBACK();
      }
      if (buff_end <= buff_ptr)
	ERR_PUSHBACK();		/* atom too long */
      *buff_ptr++ = tolower(ch);
      break;
    }

  } /* while (1) */

  /* never reached */
}
Exemplo n.º 6
0
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 seperating 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, flags);
                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", EC_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 seperating 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);
    }
}
Exemplo n.º 7
0
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", EC_UNAVAILABLE);
        }

        /* unique id */
        item->id = idx;
        item->available = server_available;
        item->total = server_total;
    }
}
Exemplo n.º 8
0
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", EC_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", EC_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", EC_UNAVAILABLE);
    }

    if (!r && !found) {
        r = IMAP_BADURL;
        *parseerr = "No such message in mailbox";
    }

    return r;
}
Exemplo n.º 9
0
struct backend *backend_connect(struct backend *ret_backend, const char *server,
				struct protocol_t *prot, const char *userid,
				sasl_callback_t *cb, const char **auth_status)
{
    /* need to (re)establish connection to server or create one */
    int sock = -1;
    int r;
    int err = -1;
    int ask = 1; /* should we explicitly ask for capabilities? */
    struct addrinfo hints, *res0 = NULL, *res;
    struct sockaddr_un sunsock;
    char buf[2048];
    struct sigaction action;
    struct backend *ret;
    char rsessionid[MAX_SESSIONID_SIZE];

    if (!ret_backend) {
	ret = xzmalloc(sizeof(struct backend));
	strlcpy(ret->hostname, server, sizeof(ret->hostname));
	ret->timeout = NULL;
    }
    else
	ret = ret_backend;

    if (server[0] == '/') { /* unix socket */
	res0 = &hints;
	memset(res0, 0, sizeof(struct addrinfo));
	res0->ai_family = PF_UNIX;
	res0->ai_socktype = SOCK_STREAM;

 	res0->ai_addr = (struct sockaddr *) &sunsock;
 	res0->ai_addrlen = sizeof(sunsock.sun_family) + strlen(server) + 1;
#ifdef SIN6_LEN
 	res0->ai_addrlen += sizeof(sunsock.sun_len);
 	sunsock.sun_len = res0->ai_addrlen;
#endif
	sunsock.sun_family = AF_UNIX;
	strlcpy(sunsock.sun_path, server, sizeof(sunsock.sun_path));

	/* XXX set that we are preauthed */

	/* change hostname to 'config_servername' */
	strlcpy(ret->hostname, config_servername, sizeof(ret->hostname));
    }
    else { /* inet socket */
	memset(&hints, 0, sizeof(hints));
	hints.ai_family = PF_UNSPEC;
	hints.ai_socktype = SOCK_STREAM;
	err = getaddrinfo(server, prot->service, &hints, &res0);
	if (err) {
	    syslog(LOG_ERR, "getaddrinfo(%s) failed: %s",
		   server, gai_strerror(err));
	    goto error;
	}
    }

    /* Setup timeout */
    timedout = 0;
    action.sa_flags = 0;
    action.sa_handler = timed_out;
    sigemptyset(&action.sa_mask);
    if(sigaction(SIGALRM, &action, NULL) < 0) 
    {
	syslog(LOG_ERR, "Setting timeout in backend_connect failed: sigaction: %m");
	/* continue anyway */
    }
    
    for (res = res0; res; res = res->ai_next) {
	sock = socket(res->ai_family, res->ai_socktype, res->ai_protocol);
	if (sock < 0)
	    continue;
	alarm(config_getint(IMAPOPT_CLIENT_TIMEOUT));
	if (connect(sock, res->ai_addr, res->ai_addrlen) >= 0)
	    break;
	if(errno == EINTR && timedout == 1)
	    errno = ETIMEDOUT;
	close(sock);
	sock = -1;
    }

    /* Remove timeout code */
    alarm(0);
    signal(SIGALRM, SIG_IGN);
    
    if (sock < 0) {
	if (res0 != &hints)
	    freeaddrinfo(res0);
	syslog(LOG_ERR, "connect(%s) failed: %m", server);
	goto error;
    }
    memcpy(&ret->addr, res->ai_addr, res->ai_addrlen);
    if (res0 != &hints)
	freeaddrinfo(res0);

    ret->in = prot_new(sock, 0);
    ret->out = prot_new(sock, 1);
    ret->sock = sock;
    prot_setflushonread(ret->in, ret->out);
    ret->prot = prot;

    /* use literal+ to send literals */
    prot_setisclient(ret->in, 1);
    prot_setisclient(ret->out, 1);
    
    if (prot->banner.auto_capa) {
	/* try to get the capabilities from the banner */
	r = ask_capability(ret, /*dobanner*/1, AUTO_CAPA_BANNER);
	if (r) {
	    /* found capabilities in banner -> don't ask */
	    ask = 0;
	}
    }
    else {
	do { /* read the initial greeting */
	    if (!prot_fgets(buf, sizeof(buf), ret->in)) {
		syslog(LOG_ERR,
		       "backend_connect(): couldn't read initial greeting: %s",
		       ret->in->error ? ret->in->error : "(null)");
		goto error;
	    }
	} while (strncasecmp(buf, prot->banner.resp,
			     strlen(prot->banner.resp)));
	strncpy(ret->banner, buf, 2048);
    }

    if (ask) {
	/* get the capabilities */
	ask_capability(ret, /*dobanner*/0, AUTO_CAPA_NO);
    }

    /* now need to authenticate to backend server,
       unless we're doing LMTP/CSYNC on a UNIX socket (deliver/sync_client) */
    if ((server[0] != '/') ||
	(strcmp(prot->sasl_service, "lmtp") &&
	 strcmp(prot->sasl_service, "csync"))) {
	char *old_mechlist = backend_get_cap_params(ret, CAPA_AUTH);
	const char *my_status;

	if ((r = backend_authenticate(ret, userid, cb, &my_status))) {
	    syslog(LOG_ERR, "couldn't authenticate to backend server: %s",
		   sasl_errstring(r, NULL, NULL));
	    free(old_mechlist);
	    goto error;
	}
	else {
	    const void *ssf;

	    sasl_getprop(ret->saslconn, SASL_SSF, &ssf);
	    if (*((sasl_ssf_t *) ssf)) {
		/* if we have a SASL security layer, compare SASL mech lists
		   before/after AUTH to check for a MITM attack */
		char *new_mechlist;
		int auto_capa = (prot->sasl_cmd.auto_capa == AUTO_CAPA_AUTH_SSF);

		if (!strcmp(prot->service, "sieve")) {
		    /* XXX  Hack to handle ManageSieve servers.
		     * No way to tell from protocol if server will
		     * automatically send capabilities, so we treat it
		     * as optional.
		     */
		    char ch;

		    /* wait and probe for possible auto-capability response */
		    usleep(250000);
		    prot_NONBLOCK(ret->in);
		    if ((ch = prot_getc(ret->in)) != EOF) {
			prot_ungetc(ch, ret->in);
		    } else {
			auto_capa = AUTO_CAPA_AUTH_NO;
		    }
		    prot_BLOCK(ret->in);
		}

		ask_capability(ret, /*dobanner*/0, auto_capa);
		new_mechlist = backend_get_cap_params(ret, CAPA_AUTH);
		if (new_mechlist &&
		    old_mechlist &&
		    strcmp(new_mechlist, old_mechlist)) {
		    syslog(LOG_ERR, "possible MITM attack:"
			   "list of available SASL mechanisms changed");
		    free(new_mechlist);
		    free(old_mechlist);
		    goto error;
		}
		free(new_mechlist);
	    }
	    else if (prot->sasl_cmd.auto_capa == AUTO_CAPA_AUTH_OK) {
		/* try to get the capabilities from the AUTH success response */
		forget_capabilities(ret);
		parse_capability(ret, my_status);
		post_parse_capability(ret);
	    }

	    if (!(strcmp(prot->service, "imap") &&
		 (strcmp(prot->service, "pop3")))) {
		parse_sessionid(my_status, rsessionid);
		syslog(LOG_NOTICE, "proxy %s sessionid=<%s> remote=<%s>", userid, session_id(), rsessionid);
	    }
	}

	if (auth_status) *auth_status = my_status;
	free(old_mechlist);
    }

    /* start compression if requested and both client/server support it */
    if (config_getswitch(IMAPOPT_PROXY_COMPRESS) && ret &&
	CAPA(ret, CAPA_COMPRESS) &&
	prot->compress_cmd.cmd &&
	do_compress(ret, &prot->compress_cmd)) {

	syslog(LOG_ERR, "couldn't enable compression on backend server");
	goto error;
    }

    return ret;

error:
    forget_capabilities(ret);
    if (ret->in) {
	prot_free(ret->in);
	ret->in = NULL;
    }
    if (ret->out) {
	prot_free(ret->out);
	ret->out = NULL;
    }
    if (sock >= 0)
	close(sock);
    if (ret->saslconn) {
	sasl_dispose(&ret->saslconn);
	ret->saslconn = NULL;
    }
    if (!ret_backend)
	free(ret);
    return NULL;
}
Exemplo n.º 10
0
static int backend_login(struct backend *ret, const char *userid,
			 sasl_callback_t *cb, const char **auth_status,
			 int noauth)
{
    int r = 0;
    int ask = 1; /* should we explicitly ask for capabilities? */
    char buf[2048];
    struct protocol_t *prot = ret->prot;

    if (prot->type != TYPE_STD) return -1;

    if (prot->u.std.banner.auto_capa) {
	/* try to get the capabilities from the banner */
	r = ask_capability(ret, /*dobanner*/1, AUTO_CAPA_BANNER);
	if (r) {
	    /* found capabilities in banner -> don't ask */
	    ask = 0;
	}
    }
    else {
	do { /* read the initial greeting */
	    if (!prot_fgets(buf, sizeof(buf), ret->in)) {
		syslog(LOG_ERR,
		       "backend_login(): couldn't read initial greeting: %s",
		       ret->in->error ? ret->in->error : "(null)");
		return -1;
	    }
	} while (strncasecmp(buf, prot->u.std.banner.resp,
			     strlen(prot->u.std.banner.resp)));
	xstrncpy(ret->banner, buf, 2048);
    }

    if (ask) {
	/* get the capabilities */
	ask_capability(ret, /*dobanner*/0, AUTO_CAPA_NO);
    }

    /* now need to authenticate to backend server,
       unless we're doing LMTP/CSYNC on a UNIX socket (deliver/sync_client) */
    if (!noauth) {
	char *old_mechlist = backend_get_cap_params(ret, CAPA_AUTH);
	const char *my_status;

	if ((r = backend_authenticate(ret, userid, cb, &my_status))) {
	    syslog(LOG_ERR, "couldn't authenticate to backend server: %s",
		   sasl_errstring(r, NULL, NULL));
	    free(old_mechlist);
	    return -1;
	}
	else {
	    const void *ssf;

	    sasl_getprop(ret->saslconn, SASL_SSF, &ssf);
	    if (*((sasl_ssf_t *) ssf)) {
		/* if we have a SASL security layer, compare SASL mech lists
		   before/after AUTH to check for a MITM attack */
		char *new_mechlist;
		int auto_capa = (prot->u.std.sasl_cmd.auto_capa == AUTO_CAPA_AUTH_SSF);

		if (!strcmp(prot->service, "sieve")) {
		    /* XXX  Hack to handle ManageSieve servers.
		     * No way to tell from protocol if server will
		     * automatically send capabilities, so we treat it
		     * as optional.
		     */
		    char ch;

		    /* wait and probe for possible auto-capability response */
		    usleep(250000);
		    prot_NONBLOCK(ret->in);
		    if ((ch = prot_getc(ret->in)) != EOF) {
			prot_ungetc(ch, ret->in);
		    } else {
			auto_capa = AUTO_CAPA_AUTH_NO;
		    }
		    prot_BLOCK(ret->in);
		}

		ask_capability(ret, /*dobanner*/0, auto_capa);
		new_mechlist = backend_get_cap_params(ret, CAPA_AUTH);
		if (new_mechlist &&
		    old_mechlist &&
		    strcmp(new_mechlist, old_mechlist)) {
		    syslog(LOG_ERR, "possible MITM attack:"
			   "list of available SASL mechanisms changed");

		    if (new_mechlist) free(new_mechlist);
		    if (old_mechlist) free(old_mechlist);
		    return -1;
		}
		free(new_mechlist);
	    }
	    else if (prot->u.std.sasl_cmd.auto_capa == AUTO_CAPA_AUTH_OK) {
		/* try to get the capabilities from the AUTH success response */
		forget_capabilities(ret);
		parse_capability(ret, my_status);
		post_parse_capability(ret);
	    }

	    if (!(strcmp(prot->service, "imap") &&
		 (strcmp(prot->service, "pop3")))) {
		char rsessionid[MAX_SESSIONID_SIZE];
		parse_sessionid(my_status, rsessionid);
		syslog(LOG_NOTICE, "auditlog: proxy %s sessionid=<%s> remote=<%s>", userid, session_id(), rsessionid);
	    }
	}

	if (auth_status) *auth_status = my_status;
	free(old_mechlist);
    }

    /* start compression if requested and both client/server support it */
    if (config_getswitch(IMAPOPT_PROXY_COMPRESS) &&
	CAPA(ret, CAPA_COMPRESS) &&
	prot->u.std.compress_cmd.cmd) {
	r = do_compress(ret, &prot->u.std.compress_cmd);
	if (r) {
	    syslog(LOG_NOTICE, "couldn't enable compression on backend server: %s", error_message(r));
	    r = 0; /* not a fail-level error */
	}
    }

    return 0;
}
Exemplo n.º 11
0
EXPORTED int dlist_parse(struct dlist **dlp, int parsekey,
                          struct protstream *in, const char *alt_reserve_base)
{
    struct dlist *dl = NULL;
    static struct buf kbuf;
    static struct buf vbuf;
    int c;

    /* handle the key if wanted */
    if (parsekey) {
        c = getastring(in, NULL, &kbuf);
        c = next_nonspace(in, c);
    }
    else {
        buf_setcstr(&kbuf, "");
        c = prot_getc(in);
    }

    /* connection dropped? */
    if (c == EOF) goto fail;

    /* check what sort of value we have */
    if (c == '(') {
        dl = dlist_newlist(NULL, kbuf.s);
        c = next_nonspace(in, ' ');
        while (c != ')') {
            struct dlist *di = NULL;
            prot_ungetc(c, in);
            c = dlist_parse(&di, 0, in, alt_reserve_base);
            if (di) dlist_stitch(dl, di);
            c = next_nonspace(in, c);
            if (c == EOF) goto fail;
        }
        c = prot_getc(in);
    }
    else if (c == '%') {
        /* no whitespace allowed here */
        c = prot_getc(in);
        if (c == '(') {
            dl = dlist_newkvlist(NULL, kbuf.s);
            c = next_nonspace(in, ' ');
            while (c != ')') {
                struct dlist *di = NULL;
                prot_ungetc(c, in);
                c = dlist_parse(&di, 1, in, alt_reserve_base);
                if (di) dlist_stitch(dl, di);
                c = next_nonspace(in, c);
                if (c == EOF) goto fail;
            }
        }
        else if (c == '{') {
            struct message_guid tmp_guid;
            static struct buf pbuf, gbuf;
            unsigned size = 0;
            const char *fname;
            const char *part;
            c = getastring(in, NULL, &pbuf);
            if (c != ' ') goto fail;
            c = getastring(in, NULL, &gbuf);
            if (c != ' ') goto fail;
            c = getuint32(in, &size);
            if (c != '}') goto fail;
            c = prot_getc(in);
            if (c == '\r') c = prot_getc(in);
            if (c != '\n') goto fail;
            if (!message_guid_decode(&tmp_guid, gbuf.s)) goto fail;
            part = alt_reserve_base ? alt_reserve_base : pbuf.s;
            if (reservefile(in, part, &tmp_guid, size, &fname)) goto fail;
            dl = dlist_setfile(NULL, kbuf.s, pbuf.s, &tmp_guid, size, fname);
            /* file literal */
        }
        else {
            /* unknown percent type */
            goto fail;
        }
        c = prot_getc(in);
    }
    else if (c == '{') {
        prot_ungetc(c, in);
        /* could be binary in a literal */
        c = getbastring(in, NULL, &vbuf);
        dl = dlist_setmap(NULL, kbuf.s, vbuf.s, vbuf.len);
    }
    else if (c == '\\') { /* special case for flags */
        prot_ungetc(c, in);
        c = getastring(in, NULL, &vbuf);
        dl = dlist_setflag(NULL, kbuf.s, vbuf.s);
    }
    else {
        prot_ungetc(c, in);
        c = getnastring(in, NULL, &vbuf);
        dl = dlist_setatom(NULL, kbuf.s, vbuf.s);
    }

    /* success */
    *dlp = dl;
    return c;

fail:
    dlist_free(&dl);
    return EOF;
}
Exemplo n.º 12
0
char *find_free_server(void)
{
    const char *servers = config_getstring(IMAPOPT_SERVERLIST);
    unsigned long max_avail = 0;
    char *server = NULL;

    if (servers) {
	char *tmpbuf, *cur_server, *next_server;
	char mytag[128];
	struct backend *be;

	/* make a working copy of the list */
	cur_server = tmpbuf = xstrdup(servers);

	while (cur_server) {
	    /* eat any leading whitespace */
	    while (Uisspace(*cur_server)) cur_server++;

	    if (!*cur_server) break;

	    /* find end of server */
	    if ((next_server = strchr(cur_server, ' ')) ||
		(next_server = strchr(cur_server, '\t')))
		*next_server++ = '\0';

	    syslog(LOG_DEBUG, "checking free space on server '%s'", cur_server);

	    /* connect to server */
	    be = proxy_findserver(cur_server, &imap_protocol,
				  proxy_userid, &backend_cached,
				  &backend_current, &backend_inbox, imapd_in);
	    if (be) {
		unsigned avail = 0;
		int c;

		/* fetch annotation from remote */
		proxy_gentag(mytag, sizeof(mytag));
		prot_printf(be->out,
			    "%s GETANNOTATION \"\" "
			    "\"/vendor/cmu/cyrus-imapd/freespace\" "
			    "\"value.shared\"\r\n", mytag);
		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,
			      "ANNOTATION \"\" "
			      "\"/vendor/cmu/cyrus-imapd/freespace\" "
			      "(\"value.shared\" \"");
		    if (c == EOF) {
			/* we don't care about this response */
			eatline(be->in, c);
			continue;
		    }

		    /* read uidvalidity */
		    c = getuint32(be->in, &avail);
		    if (c != '\"') { c = EOF; break; }
		    eatline(be->in, c); /* we don't care about the rest of the line */
		}
		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", EC_UNAVAILABLE);
		}
		if (avail > max_avail) {
		    server = cur_server;
		    max_avail = avail;
		}
	    }

	    /* move to next server */
	    cur_server = next_server;
	}

	if (server) server = xstrdup(server);

	free(tmpbuf);
    }

    return server;
}