Page Menu
Home
Phorge
Search
Configure Global Search
Log In
Files
F117753697
No One
Temporary
Actions
View File
Edit File
Delete File
View Transforms
Subscribe
Flag For Later
Award Token
Authored By
Unknown
Size
141 KB
Referenced Files
None
Subscribers
None
View Options
diff --git a/imap/proxyd.c b/imap/proxyd.c
index 6424f7cbd..6e1c18e0b 100644
--- a/imap/proxyd.c
+++ b/imap/proxyd.c
@@ -1,5348 +1,5349 @@
/* proxyd.c -- IMAP server proxy for Cyrus Murder
*
* Copyright (c) 1998-2000 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 other legal
* details, please contact
* Office of Technology Transfer
* Carnegie Mellon University
* 5000 Forbes Avenue
* Pittsburgh, PA 15213-3890
* (412) 268-4387, fax: (412) 268-7395
* tech-transfer@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.
*/
-/* $Id: proxyd.c,v 1.142 2002/11/06 20:43:23 rjs3 Exp $ */
+/* $Id: proxyd.c,v 1.143 2002/11/12 16:40:51 leg Exp $ */
#undef PROXY_IDLE
#include <config.h>
#include <stdio.h>
#include <string.h>
#include <time.h>
#include <signal.h>
#ifdef HAVE_UNISTD_H
#include <unistd.h>
#endif
#include <fcntl.h>
#include <sys/time.h>
#include <sys/types.h>
#include <sys/param.h>
#include <sys/stat.h>
#include <sys/un.h>
#include <syslog.h>
#include <com_err.h>
#include <netdb.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <assert.h>
#include <ctype.h>
#include <errno.h>
#include <sasl/sasl.h>
#include <sasl/saslutil.h>
#include "prot.h"
#include "acl.h"
#include "annotate.h"
#include "util.h"
#include "auth.h"
#include "map.h"
#include "imapconf.h"
#include "tls.h"
#include "version.h"
#include "charset.h"
#include "imparse.h"
#include "iptostring.h"
#include "mkgmtime.h"
#include "exitcodes.h"
#include "imap_err.h"
#include "mboxname.h"
#include "mailbox.h"
#include "mupdate-client.h"
#include "xmalloc.h"
#include "mboxlist.h"
#include "imapurl.h"
#include "pushstats.h"
#include "telemetry.h"
#include "backend.h"
/* PROXY STUFF */
/* we want a list of our outgoing connections here and which one we're
currently piping */
#define IDLE_TIMEOUT (5 * 60)
static const int ultraparanoid = 1; /* should we kick after every operation? */
static unsigned int proxyd_cmdcnt;
static int referral_kick = 0; /* kick after next command recieved, for
referrals that are likely to change the
mailbox list */
/* all subscription commands go to the backend server containing the
user's inbox */
struct backend *backend_inbox = NULL;
/* the current server most commands go to */
struct backend *backend_current = NULL;
/* our cached connections */
struct backend **backend_cached = NULL;
/* has the client issued an RLIST or RLSUB? */
static int supports_referrals;
/* -------- from imapd ---------- */
extern int optind;
extern char *optarg;
/* global state */
static char shutdownfilename[1024];
static int imaps = 0;
static sasl_ssf_t extprops_ssf = 0;
/* per-user session state */
struct protstream *proxyd_out = NULL;
struct protstream *proxyd_in = NULL;
static char proxyd_clienthost[250] = "[local]";
static int proxyd_logfd = -1;
time_t proxyd_logtime;
char *proxyd_userid = NULL;
struct auth_state *proxyd_authstate = 0;
int proxyd_userisadmin;
sasl_conn_t *proxyd_saslconn; /* the sasl connection context to the client */
int proxyd_starttls_done = 0; /* have we done a successful starttls yet? */
#ifdef HAVE_SSL
static SSL *tls_conn;
#endif /* HAVE_SSL */
/* current namespace */
static struct namespace proxyd_namespace;
void shutdown_file(int fd);
void motd_file(int fd);
void shut_down(int code);
void fatal(const char *s, int code);
void proxyd_downserver(struct backend *s);
void cmdloop(void);
void cmd_login(char *tag, char *user);
void cmd_authenticate(char *tag, char *authtype);
void cmd_noop(char *tag, char *cmd);
void cmd_capability(char *tag);
void cmd_append(char *tag, char *name);
void cmd_select(char *tag, char *cmd, char *name);
void cmd_close(char *tag);
void cmd_fetch(char *tag, char *sequence, int usinguid);
void cmd_partial(char *tag, char *msgno, char *data,
char *start, char *count);
void cmd_store(char *tag, char *sequence, char *operation, int usinguid);
void cmd_search(char *tag, int usinguid);
void cmd_sort(char *tag, int usinguid);
void cmd_thread(char *tag, int usinguid);
void cmd_copy(char *tag, char *sequence, char *name, int usinguid);
void cmd_expunge(char *tag, char *sequence);
void cmd_create(char *tag, char *name, char *partition);
void cmd_delete(char *tag, char *name);
void cmd_rename(char *tag, char *oldname, char *newname, char *partition);
void cmd_find(char *tag, char *namespace, char *pattern);
void cmd_list(char *tag, int subscribed, char *reference, char *pattern);
void cmd_changesub(char *tag, char *namespace, char *name, int add);
void cmd_getacl(char *tag, char *name, int oldform);
void cmd_listrights(char *tag, char *name, char *identifier);
void cmd_myrights(char *tag, char *name, int oldform);
void cmd_setacl(char *tag, char *name, char *identifier, char *rights);
void cmd_getquota(char *tag, char *name);
void cmd_getquotaroot(char *tag, char *name);
void cmd_setquota(char *tag, char *quotaroot);
void cmd_status(char *tag, char *name);
void cmd_getuids(char *tag, char *startuid);
void cmd_unselect(char* tag);
void cmd_namespace(char* tag);
void cmd_reconstruct(char *tag, char *name);
void cmd_id(char* tag);
struct idparamlist {
char *field;
char *value;
struct idparamlist *next;
};
extern void id_getcmdline(int argc, char **argv);
extern void id_response(struct protstream *pout);
void id_appendparamlist(struct idparamlist **l, char *field, char *value);
void id_freeparamlist(struct idparamlist *l);
void cmd_idle(char* tag);
char idle_nomailbox(char* tag, int idle_period, struct buf *arg);
char idle_passthrough(char* tag, int idle_period, struct buf *arg);
char idle_simulate(char* tag, int idle_period, struct buf *arg);
void cmd_starttls(char *tag, int imaps);
#ifdef ENABLE_X_NETSCAPE_HACK
void cmd_netscape (char* tag);
#endif
#ifdef ENABLE_ANNOTATEMORE
void cmd_getannotation(char* tag);
void cmd_setannotation(char* tag);
int getannotatefetchdata(char *tag,
struct strlist **entries, struct strlist **attribs);
int getannotatestoredata(char *tag, struct entryattlist **entryatts);
void annotate_response(struct entryattlist *l);
void appendstrlist(struct strlist **l, char *s);
void freestrlist(struct strlist *l);
void appendattvalue(struct attvaluelist **l, char *attrib, char *value);
void freeattvalues(struct attvaluelist *l);
#endif /* ENABLE_ANNOTATEMORE */
void printstring (const char *s);
void printastring (const char *s);
static int mailboxdata(char *name, int matchlen, int maycreate, void *rock);
static int listdata(char *name, int matchlen, int maycreate, void *rock);
static void mstringdata(char *cmd, char *name, int matchlen, int maycreate);
static int mlookup(const char *name, char **pathp,
char **aclp, void *tid);
extern int saslserver(sasl_conn_t *conn, const char *mech,
const char *init_resp, const char *continuation,
struct protstream *pin, struct protstream *pout,
int *sasl_result, char **success_data);
/* Enable the resetting of a sasl_conn_t */
static int reset_saslconn(sasl_conn_t **conn);
static struct
{
char *ipremoteport;
char *iplocalport;
sasl_ssf_t ssf;
char *authid;
} saslprops = {NULL,NULL,0,NULL};
#define BUFGROWSIZE 100
/* proxy support functions */
enum {
PROXY_NOCONNECTION = -1,
PROXY_OK = 0,
PROXY_NO = 1,
PROXY_BAD = 2
};
static void proxyd_gentag(char *tag)
{
sprintf(tag, "PROXY%d", proxyd_cmdcnt++);
}
/* pipe_until_tag() reads from 's->in' until the tagged response
starting with 'tag' appears. it returns the result of the
tagged command, and sets 's->last_result' with the tagged line. */
/* 's->last_result' assumes that tagged responses are:
a) short
b) don't contain literals
IMAP grammar allows both, unfortunately */
/* 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_until_tag(struct backend *s, char *tag, int force_notfatal)
{
char buf[2048];
char eol[128];
int sl;
int cont = 0, last = 0, r = -1;
int taglen = strlen(tag);
s->timeout->mark = time(NULL) + IDLE_TIMEOUT;
/* the only complication here are literals */
while (!last || cont) {
/* 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, and we shouldn't echo it. */
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", EC_UNAVAILABLE);
proxyd_downserver(s);
return PROXY_NOCONNECTION;
}
if (!cont && buf[taglen] == ' ' && !strncmp(tag, buf, taglen)) {
strlcpy(s->last_result, buf + taglen + 1, sizeof(s->last_result));
/* guarantee that 's->last_result' has \r\n\0 at the end */
s->last_result[LAST_RESULT_LEN - 3] = '\r';
s->last_result[LAST_RESULT_LEN - 2] = '\n';
s->last_result[LAST_RESULT_LEN - 1] = '\0';
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",
EC_UNAVAILABLE);
proxyd_downserver(s);
r = PROXY_NOCONNECTION;
break;
}
last = 1;
}
sl = strlen(buf);
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) prot_write(proxyd_out, buf, sl);
cont = 1;
continue;
} else { /* we got the end of the line */
int i;
int litlen = 0, islit = 0;
if (!last) prot_write(proxyd_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 (eol[i-1] == '\n' && eol[i-2] == '\r' && eol[i-3] == '}') {
/* possible literal */
i -= 4;
while (i > 0 && eol[i] != '{' && isdigit((int) 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 > sizeof(buf) ? sizeof(buf) : litlen);
j = prot_read(s->in, buf, j);
if(!j) {
/* EOF or other error */
return -1;
}
if (!last) prot_write(proxyd_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;
}
return r;
}
static int pipe_including_tag(struct backend *s, char *tag, int force_notfatal)
{
int r;
r = pipe_until_tag(s, tag, force_notfatal);
switch (r) {
case PROXY_OK:
case PROXY_NO:
case PROXY_BAD:
prot_printf(proxyd_out, "%s %s", tag, s->last_result);
break;
case PROXY_NOCONNECTION:
/* don't have to worry about downing the server, since
* pipe_until_tag does that for us */
prot_printf(proxyd_out, "%s NO %s\r\n", tag,
error_message(IMAP_SERVER_UNAVAILABLE));
break;
}
return r;
}
static int pipe_to_end_of_response(struct backend *s, int force_notfatal)
{
char buf[2048];
char eol[128];
int sl;
int cont = 1, r = PROXY_OK;
s->timeout->mark = time(NULL) + IDLE_TIMEOUT;
eol[0]='\0';
/* the only complication here are literals */
while (cont) {
/* if 'cont' is set, we're looking at the continuation to a very
long line. */
if (!prot_fgets(buf, sizeof(buf), s->in)) {
/* uh oh */
if(s == backend_current && !force_notfatal)
fatal("Lost connection to selected backend", EC_UNAVAILABLE);
proxyd_downserver(s);
return PROXY_NOCONNECTION;
}
sl = strlen(buf);
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 */
prot_write(proxyd_out, buf, sl);
cont = 1;
continue;
} else { /* we got the end of the line */
int i;
int litlen = 0, islit = 0;
prot_write(proxyd_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 (eol[i-1] == '\n' && eol[i-2] == '\r' && eol[i-3] == '}') {
/* possible literal */
i -= 4;
while (i > 0 && eol[i] != '{' && isdigit((int) 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 > sizeof(buf) ? sizeof(buf) : litlen);
j = prot_read(s->in, buf, j);
if(!j) {
/* EOF or other error */
return -1;
}
prot_write(proxyd_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, if we're here, we're done */
cont = 0;
}
return r;
}
/* 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 */
static 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), proxyd_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 (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] != '{' && isdigit((int) 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(proxyd_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(proxyd_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 > sizeof(buf) ? sizeof(buf) : litlen);
j = prot_read(proxyd_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;
}
}
}
}
/* 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 FIND MAILBOXES, for that we do an
* LSUB on the backend anyway, because the semantics of FIND do not allow
* it to return nonexistant mailboxes (RFC1176), but we need to really dumb
* down the response when this is the case.
*/
static int pipe_lsub(struct backend *s, char *tag, int force_notfatal,
int for_find)
{
int taglen = strlen(tag);
int c;
int r = PROXY_OK;
int exist_r;
char mailboxname[MAX_MAILBOX_PATH + 1];
static struct buf tagb, cmd, sep, name;
int cur_flags_size = 64;
char *flags = xmalloc(cur_flags_size);
const char *end_strip_flags[] = { " \\NonExistent)", "\\NonExistent)",
NULL };
const char *mid_strip_flags[] = { "\\NonExistent ",
NULL
};
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", EC_UNAVAILABLE);
proxyd_downserver(s);
free(flags);
return PROXY_NOCONNECTION;
}
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",
EC_UNAVAILABLE);
proxyd_downserver(s);
free(flags);
return PROXY_NOCONNECTION;
}
/* Got the end of the response */
strlcpy(s->last_result, buf, sizeof(s->last_result));
/* guarantee that 's->last_result' has \r\n\0 at the end */
s->last_result[LAST_RESULT_LEN - 3] = '\r';
s->last_result[LAST_RESULT_LEN - 2] = '\n';
s->last_result[LAST_RESULT_LEN - 1] = '\0';
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",
EC_UNAVAILABLE);
proxyd_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", EC_UNAVAILABLE);
proxyd_downserver(s);
free(flags);
return PROXY_NOCONNECTION;
}
if(strncasecmp("LSUB", cmd.s, 4)) {
prot_printf(proxyd_out, "%s %s ", tagb.s, cmd.s);
r = pipe_to_end_of_response(s, force_notfatal);
if(r != PROXY_OK) {
free(flags);
return r;
}
} else {
/* build up the response bit by bit */
int i = 0;
char *p;
c = prot_getc(s->in);
while(c != ')' && c != EOF) {
flags[i++] = c;
if(i == cur_flags_size) {
/* expand our buffer */
cur_flags_size *= 2;
flags = xrealloc(flags, cur_flags_size);
}
c = prot_getc(s->in);
}
if(c != EOF) {
/* terminate string */
flags[i++] = ')';
if(i == cur_flags_size) {
/* expand our buffer */
cur_flags_size *= 2;
flags = xrealloc(flags, cur_flags_size);
}
flags[i] = '\0';
/* get the next character */
c = prot_getc(s->in);
}
if(c != ' ') {
if(s == backend_current && !force_notfatal)
fatal("Bad LSUB response from selected backend",
EC_UNAVAILABLE);
proxyd_downserver(s);
free(flags);
return PROXY_NOCONNECTION;
}
/* Check for flags that we should remove
* (e.g. NoSelect, NonExistent) */
for(i=0; end_strip_flags[i]; i++) {
p = strstr(flags, end_strip_flags[i]);
if(p) {
*p = ')';
*(p+1) = '\0';
}
}
for(i=0; mid_strip_flags[i]; i++) {
int mid_strip_len = strlen(mid_strip_flags[i]);
p = strstr(flags, mid_strip_flags[i]);
while(p) {
strcpy(p, p + mid_strip_len);
p = strstr(flags, mid_strip_flags[i]);
}
}
/* Get separator */
c = getastring(s->in, s->out, &sep);
if(c != ' ') {
if(s == backend_current && !force_notfatal)
fatal("Bad LSUB response from selected backend",
EC_UNAVAILABLE);
proxyd_downserver(s);
free(flags);
return PROXY_NOCONNECTION;
}
/* Get name */
c = getastring(s->in, s->out, &name);
if(c == '\r') c = prot_getc(s->in);
if(c != '\n') {
if(s == backend_current && !force_notfatal)
fatal("Bad LSUB response from selected backend",
EC_UNAVAILABLE);
proxyd_downserver(s);
free(flags);
return PROXY_NOCONNECTION;
}
/* lookup name */
exist_r = 1;
r = (*proxyd_namespace.mboxname_tointernal)(&proxyd_namespace,
name.s,
proxyd_userid,
mailboxname);
if (!r) {
int mbtype;
exist_r = mboxlist_detail(mailboxname, &mbtype,
NULL, NULL, NULL, NULL);
if(!exist_r && (mbtype & MBTYPE_RESERVE))
exist_r = IMAP_MAILBOX_RESERVED;
} else {
/* skip this one */
syslog(LOG_ERR, "could not convert %s to internal form",
name.s);
continue;
}
/* send our response */
/* we need to set \Noselect if it's not in our mailboxes.db */
if(!for_find) {
if(!exist_r) {
prot_printf(proxyd_out, "* LSUB %s \"%s\" ",
flags, sep.s);
} else {
prot_printf(proxyd_out, "* LSUB (\\Noselect) \"%s\" ",
sep.s);
}
printstring(name.s);
prot_printf(proxyd_out, "\r\n");
} else if(for_find && !exist_r) {
/* Note that it has to exist for a find response */
/* xxx what happens if name.s isn't an atom? */
prot_printf(proxyd_out, "* MAILBOX %s\r\n",name.s);
}
}
} /* while(1) */
free(flags);
return r;
}
void proxyd_downserver(struct backend *s)
{
if (!s || !s->timeout) {
/* already disconnected */
return;
}
/* need to logout of server */
downserver(s);
if(s == backend_inbox) backend_inbox = NULL;
if(s == backend_current) backend_current = NULL;
/* remove the timeout */
prot_removewaitevent(proxyd_in, s->timeout);
s->timeout = NULL;
}
struct prot_waitevent *backend_timeout(struct protstream *s,
struct prot_waitevent *ev, void *rock)
{
struct backend *be = (struct backend *) rock;
if (be != backend_current) {
/* server is not our current server, and idle too long.
* down the backend server (removes the event as a side-effect)
*/
proxyd_downserver(be);
return NULL;
}
else {
/* it will timeout in IDLE_TIMEOUT seconds from now */
ev->mark = time(NULL) + IDLE_TIMEOUT;
return ev;
}
}
/* return the connection to the server */
struct backend *proxyd_findserver(char *server)
{
int i = 0;
struct backend *ret = NULL;
while (backend_cached && backend_cached[i]) {
if (!strcmp(server, backend_cached[i]->hostname)) {
/* xxx do we want to ping/noop the server here? */
ret = backend_cached[i];
break;
}
i++;
}
if (!ret || !ret->timeout) {
/* need to (re)establish connection to server or create one */
ret = findserver(ret, server, proxyd_userid);
if(!ret) return NULL;
/* add the timeout */
ret->timeout = prot_addwaitevent(proxyd_in, time(NULL) + IDLE_TIMEOUT,
backend_timeout, ret);
}
ret->timeout->mark = time(NULL) + IDLE_TIMEOUT;
/* insert server in list of cached connections */
if (!backend_cached[i]) {
backend_cached = (struct backend **)
xrealloc(backend_cached, (i + 2) * sizeof(struct backend *));
backend_cached[i] = ret;
backend_cached[i + 1] = NULL;
}
return ret;
}
static void kick_mupdate(void)
{
char buf[2048];
struct sockaddr_un srvaddr;
int s, r;
int len;
s = socket(AF_UNIX, SOCK_STREAM, 0);
if (s == -1) {
syslog(LOG_ERR, "socket: %m");
return;
}
strncpy(buf, config_dir, sizeof(buf));
strncat(buf, FNAME_MUPDATE_TARGET_SOCK, sizeof(buf));
memset((char *)&srvaddr, 0, sizeof(srvaddr));
srvaddr.sun_family = AF_UNIX;
strcpy(srvaddr.sun_path, buf);
len = sizeof(srvaddr.sun_family) + strlen(srvaddr.sun_path) + 1;
r = connect(s, (struct sockaddr *)&srvaddr, len);
if (r == -1) {
syslog(LOG_ERR, "kick_mupdate: can't connect to target: %m");
close(s);
return;
}
r = read(s, &buf, sizeof(buf));
if (r <= 0) {
syslog(LOG_ERR, "kick_mupdate: can't read from target: %m");
close(s);
return;
}
/* if we got here, it's been kicked */
close(s);
return;
}
/* proxy mboxlist_lookup; on misses, it asks the listener for this
machine to make a roundtrip to the master mailbox server to make
sure it's up to date */
static int mlookup(const char *name, char **pathp,
char **aclp, void *tid)
{
int r;
if(pathp) *pathp = NULL;
r = mboxlist_lookup(name, pathp, aclp, tid);
if (r == IMAP_MAILBOX_NONEXISTENT) {
kick_mupdate();
r = mboxlist_lookup(name, pathp, aclp, tid);
}
/* xxx hide the fact that we are storing partitions */
if(pathp && *pathp) {
char *c;
c = strchr(*pathp, '!');
if(c) *c = '\0';
}
return r;
}
static struct backend *proxyd_findinboxserver(void)
{
char inbox[MAX_MAILBOX_NAME];
int r;
char *server = NULL;
struct backend *s = NULL;
if (strlen(proxyd_userid) > MAX_MAILBOX_NAME - 30) return NULL;
strcpy(inbox, "user.");
strcat(inbox, proxyd_userid);
r = mlookup(inbox, &server, NULL, NULL);
if (!r) {
s = proxyd_findserver(server);
}
return s;
}
/* proxyd_refer() issues a referral to the client. */
static void proxyd_refer(const char *tag,
const char *server,
const char *mailbox)
{
char url[MAX_MAILBOX_PATH];
if(!strcmp(proxyd_userid, "anonymous")) {
imapurl_toURL(url, server, mailbox, "ANONYMOUS");
} else {
imapurl_toURL(url, server, mailbox, "*");
}
prot_printf(proxyd_out, "%s NO [REFERRAL %s] Remote mailbox.\r\n",
tag, url);
}
/*
* acl_ok() checks to see if the the inbox for 'user' grants the 'a'
* right to the principal 'auth_identity'. Returns 1 if so, 0 if not.
*/
static int acl_ok(const char *user, const char *auth_identity)
{
char *acl;
char inboxname[1024];
int r;
struct auth_state *authstate;
if (strchr(user, '.') || strlen(user)+6 >= sizeof(inboxname)) return 0;
strcpy(inboxname, "user.");
strcat(inboxname, user);
if (!(authstate = auth_newstate(auth_identity, (char *)0)) ||
mlookup(inboxname, (char **)0, &acl, NULL)) {
r = 0; /* Failed so assume no proxy access */
}
else {
r = (cyrus_acl_myrights(authstate, acl) & ACL_ADMIN) != 0;
}
if (authstate) auth_freestate(authstate);
return r;
}
/* should we allow users to proxy? return SASL_OK if yes,
SASL_BADAUTH otherwise */
static int mysasl_authproc(sasl_conn_t *conn,
void *context,
const char *requested_user, unsigned rlen,
const char *auth_identity, unsigned alen,
const char *def_realm, unsigned urlen,
struct propctx *propctx)
{
const char *val;
char *realm;
/* check if remote realm */
if ((realm = strchr(auth_identity, '@'))!=NULL) {
realm++;
val = config_getstring("loginrealms", "");
while (*val) {
if (!strncasecmp(val, realm, strlen(realm)) &&
(!val[strlen(realm)] || isspace((int) val[strlen(realm)]))) {
break;
}
/* not this realm, try next one */
while (*val && !isspace((int) *val)) val++;
while (*val && isspace((int) *val)) val++;
}
if (!*val) {
sasl_seterror(conn, 0, "cross-realm login %s denied",
auth_identity);
return SASL_BADAUTH;
}
}
proxyd_authstate = auth_newstate(auth_identity, NULL);
/* ok, is auth_identity an admin? */
proxyd_userisadmin = authisa(proxyd_authstate, "imap", "admins");
if (strcmp(auth_identity, requested_user)) {
/* we want to authenticate as a different user; we'll allow this
if we're an admin or if we've allowed ACL proxy logins */
int use_acl = config_getswitch("loginuseacl", 0);
if (proxyd_userisadmin ||
(use_acl && acl_ok(requested_user, auth_identity)) ||
authisa(proxyd_authstate, "imap", "proxyservers")) {
/* proxy ok! */
proxyd_userisadmin = 0; /* no longer admin */
auth_freestate(proxyd_authstate);
proxyd_authstate = auth_newstate(requested_user, NULL);
} else {
sasl_seterror(conn, 0, "user %s is not allowed to proxy", auth_identity);
auth_freestate(proxyd_authstate);
return SASL_BADAUTH;
}
}
return SASL_OK;
}
static struct sasl_callback mysasl_cb[] = {
{ SASL_CB_GETOPT, &mysasl_config, NULL },
{ SASL_CB_PROXY_POLICY, &mysasl_authproc, NULL },
{ SASL_CB_CANON_USER, &mysasl_canon_user, NULL },
{ SASL_CB_LIST_END, NULL, NULL }
};
extern void setproctitle_init(int argc, char **argv, char **envp);
extern int proc_register(const char *progname, const char *clienthost,
const char *userid, const char *mailbox);
extern void proc_cleanup(void);
/*
* run once when process is forked;
* MUST NOT exit directly; must return with non-zero error code
*/
int service_init(int argc, char **argv, char **envp)
{
int opt;
int r;
config_changeident("proxyd");
if (geteuid() == 0) fatal("must run as the Cyrus user", EC_USAGE);
setproctitle_init(argc, argv, envp);
/* set signal handlers */
signals_set_shutdown(&shut_down);
signals_add_handlers();
signal(SIGPIPE, SIG_IGN);
/* set the SASL allocation functions */
sasl_set_alloc((sasl_malloc_t *) &xmalloc,
(sasl_calloc_t *) &calloc,
(sasl_realloc_t *) &xrealloc,
(sasl_free_t *) &free);
/* load the SASL plugins */
if ((r = sasl_server_init(mysasl_cb, "Cyrus")) != SASL_OK) {
syslog(LOG_ERR, "SASL failed initializing: sasl_server_init(): %s",
sasl_errstring(r, NULL, NULL));
return EC_SOFTWARE;
}
if ((r = sasl_client_init(NULL)) != SASL_OK) {
syslog(LOG_ERR, "SASL failed initializing: sasl_client_init(): %s",
sasl_errstring(r, NULL, NULL));
return EC_SOFTWARE;
}
/* open the mboxlist, we'll need it for real work */
mboxlist_init(0);
mboxlist_open(NULL);
while ((opt = getopt(argc, argv, "sp:")) != EOF) {
switch (opt) {
case 's': /* imaps (do starttls right away) */
imaps = 1;
if (!tls_enabled("imap")) {
syslog(LOG_ERR, "imaps: required OpenSSL options not present");
fatal("imaps: required OpenSSL options not present",
EC_CONFIG);
}
break;
case 'p': /* external protection */
extprops_ssf = atoi(optarg);
break;
default:
break;
}
}
return 0;
}
static void proxyd_reset(void)
{
int i;
proc_cleanup();
/* close backend connections */
i = 0;
while (backend_cached[i]) {
proxyd_downserver(backend_cached[i]);
free(backend_cached[i]);
i++;
}
free(backend_cached);
backend_cached = NULL;
backend_inbox = backend_current = NULL;
/* Cleanup file descriptors. note: after last call to proxyd_downserver */
if(proxyd_in) {
prot_NONBLOCK(proxyd_in);
prot_fill(proxyd_in);
prot_free(proxyd_in);
}
if(proxyd_out) {
prot_flush(proxyd_out);
prot_free(proxyd_out);
}
proxyd_in = proxyd_out = NULL;
close(0);
close(1);
close(2);
/* Cleanup Globals */
proxyd_cmdcnt = 0;
supports_referrals = 0;
proxyd_userisadmin = 0;
proxyd_starttls_done = 0;
proxyd_logtime = 0;
strcpy(proxyd_clienthost, "[local]");
if(proxyd_logfd != -1) {
close(proxyd_logfd);
proxyd_logfd = -1;
}
if(proxyd_userid) {
free(proxyd_userid);
proxyd_userid = NULL;
}
if(proxyd_authstate) {
auth_freestate(proxyd_authstate);
proxyd_authstate = NULL;
}
/* Cleanup SASL */
if(proxyd_saslconn) {
sasl_dispose(&proxyd_saslconn);
proxyd_saslconn = NULL;
}
if(saslprops.iplocalport) {
free(saslprops.iplocalport);
saslprops.iplocalport = NULL;
}
if(saslprops.ipremoteport) {
free(saslprops.ipremoteport);
saslprops.ipremoteport = NULL;
}
if(saslprops.authid) {
free(saslprops.authid);
saslprops.authid = NULL;
}
saslprops.ssf = 0;
#ifdef HAVE_SSL
if (tls_conn) {
if (tls_reset_servertls(&tls_conn) == -1) {
fatal("tls_reset() failed", EC_TEMPFAIL);
}
tls_conn = NULL;
}
#endif
}
int service_main(int argc, char **argv, char **envp)
{
socklen_t salen;
struct hostent *hp;
struct sockaddr_in proxyd_localaddr, proxyd_remoteaddr;
char localip[60], remoteip[60];
int timeout;
int proxyd_haveaddr = 0;
sasl_security_properties_t *secprops = NULL;
signals_poll();
#ifdef ID_SAVE_CMDLINE
/* get command line args for use in ID before getopt mangles them */
id_getcmdline(argc, argv);
#endif
proxyd_in = prot_new(0, 0);
proxyd_out = prot_new(1, 1);
/* Find out name of client host */
salen = sizeof(proxyd_remoteaddr);
if (getpeername(0, (struct sockaddr *)&proxyd_remoteaddr, &salen) == 0 &&
proxyd_remoteaddr.sin_family == AF_INET) {
hp = gethostbyaddr((char *)&proxyd_remoteaddr.sin_addr,
sizeof(proxyd_remoteaddr.sin_addr), AF_INET);
if (hp != NULL) {
strncpy(proxyd_clienthost,hp->h_name,sizeof(proxyd_clienthost)-30);
proxyd_clienthost[sizeof(proxyd_clienthost)-30] = '\0';
} else {
proxyd_clienthost[0] = '\0';
}
strcat(proxyd_clienthost, "[");
strcat(proxyd_clienthost, inet_ntoa(proxyd_remoteaddr.sin_addr));
strcat(proxyd_clienthost, "]");
salen = sizeof(proxyd_localaddr);
if (getsockname(0, (struct sockaddr *)&proxyd_localaddr,
&salen) == 0) {
if(iptostring((struct sockaddr *)&proxyd_remoteaddr,
sizeof(struct sockaddr_in), remoteip, 60) == 0
&& iptostring((struct sockaddr *)&proxyd_localaddr,
sizeof(struct sockaddr_in), localip, 60) == 0) {
proxyd_haveaddr = 1;
}
}
}
/* create the SASL connection */
/* Make a SASL connection and setup some properties for it */
/* other params should be filled in */
if (sasl_server_new("imap", config_servername,
NULL,
(proxyd_haveaddr ? localip : NULL),
(proxyd_haveaddr ? remoteip : NULL),
NULL, 0,
&proxyd_saslconn) != SASL_OK) {
fatal("SASL failed initializing: sasl_server_new()", EC_TEMPFAIL);
}
if(proxyd_haveaddr) {
saslprops.ipremoteport = xstrdup(remoteip);
saslprops.iplocalport = xstrdup(localip);
}
secprops = mysasl_secprops(SASL_SEC_NOPLAINTEXT);
sasl_setprop(proxyd_saslconn, SASL_SEC_PROPS, secprops);
sasl_setprop(proxyd_saslconn, SASL_SSF_EXTERNAL, &extprops_ssf);
proc_register("proxyd", proxyd_clienthost, (char *)0, (char *)0);
/* Set inactivity timer */
timeout = config_getint("timeout", 30);
if (timeout < 30) timeout = 30;
prot_settimeout(proxyd_in, timeout*60);
prot_setflushonread(proxyd_in, proxyd_out);
/* setup the cache */
backend_cached = xmalloc(sizeof(struct backend *));
backend_cached[0] = NULL;
/* we were connected on imaps port so we should do
TLS negotiation immediately */
if (imaps == 1) cmd_starttls(NULL, 1);
cmdloop();
/* cleanup */
prot_flush(proxyd_out);
proxyd_reset();
/* return to service another connection */
return 0;
}
/* called if 'service_init()' was called but not 'service_main()' */
void service_abort(int error)
{
shut_down(error);
}
/*
* found a motd file; spit out message and return
*/
void motd_file(int fd)
{
struct protstream *motd_in;
char buf[1024];
char *p;
motd_in = prot_new(fd, 0);
prot_fgets(buf, sizeof(buf), motd_in);
if ((p = strchr(buf, '\r')) != NULL) *p = 0;
if ((p = strchr(buf, '\n')) != NULL) *p = 0;
for(p = buf; *p == '['; p++); /* can't have [ be first char, sigh */
prot_printf(proxyd_out, "* OK [ALERT] %s\r\n", p);
}
/*
* Found a shutdown file: Spit out an untagged BYE and shut down
*/
void shutdown_file(int fd)
{
struct protstream *shutdown_in;
char buf[1024];
char *p;
shutdown_in = prot_new(fd, 0);
prot_fgets(buf, sizeof(buf), shutdown_in);
if ((p = strchr(buf, '\r')) != NULL) *p = 0;
if ((p = strchr(buf, '\n')) != NULL) *p = 0;
for (p = buf; *p == '['; p++); /* can't have [ be first char, sigh */
prot_printf(proxyd_out, "* BYE [ALERT] %s\r\n", p);
shut_down(0);
}
/*
* Cleanly shut down and exit
*/
void shut_down(int code) __attribute__((noreturn));
void shut_down(int code)
{
int i;
proc_cleanup();
i = 0;
while (backend_cached && backend_cached[i]) {
proxyd_downserver(backend_cached[i]);
free(backend_cached[i]);
i++;
}
mboxlist_close();
mboxlist_done();
if (proxyd_in) {
prot_NONBLOCK(proxyd_in);
prot_fill(proxyd_in);
prot_free(proxyd_in);
}
if (proxyd_out) {
prot_flush(proxyd_out);
prot_free(proxyd_out);
}
#ifdef HAVE_SSL
tls_shutdown_serverengine();
#endif
exit(code);
}
void fatal(const char *s, int code)
{
static int recurse_code = 0;
if (recurse_code) {
/* We were called recursively. Just give up */
proc_cleanup();
exit(recurse_code);
}
recurse_code = code;
if (proxyd_out) {
prot_printf(proxyd_out, "* BYE Fatal error: %s\r\n", s);
prot_flush(proxyd_out);
}
shut_down(code);
}
/*
* Top-level command loop parsing
*/
void cmdloop()
{
int fd;
char motdfilename[1024];
char hostname[MAXHOSTNAMELEN+1];
int c;
int usinguid, havepartition, havenamespace, oldform;
static struct buf tag, cmd, arg1, arg2, arg3, arg4;
char *p;
const char *err;
snprintf(shutdownfilename, sizeof(shutdownfilename),
"%s/msg/shutdown", config_dir);
gethostname(hostname, sizeof(hostname));
prot_printf(proxyd_out,
"* OK %s Cyrus IMAP4 Murder %s server ready\r\n", hostname,
CYRUS_VERSION);
snprintf(motdfilename, sizeof(motdfilename), "%s/msg/motd", config_dir);
if ((fd = open(motdfilename, O_RDONLY, 0)) != -1) {
motd_file(fd);
close(fd);
}
for (;;) {
if (! proxyd_userisadmin &&
(fd = open(shutdownfilename, O_RDONLY, 0)) != -1) {
shutdown_file(fd);
}
signals_poll();
/* Parse tag */
c = getword(proxyd_in, &tag);
if (c == EOF) {
err = prot_error(proxyd_in);
if (err) {
syslog(LOG_WARNING, "PROTERR: %s", err);
prot_printf(proxyd_out, "* BYE %s\r\n", err);
}
return;
}
if (c != ' ' || !imparse_isatom(tag.s) ||
(tag.s[0] == '*' && !tag.s[1])) {
prot_printf(proxyd_out, "* BAD Invalid tag\r\n");
eatline(proxyd_in, c);
continue;
}
/* Parse command name */
c = getword(proxyd_in, &cmd);
if (!cmd.s[0]) {
prot_printf(proxyd_out, "%s BAD Null command\r\n", tag.s);
eatline(proxyd_in, c);
continue;
}
if (islower((unsigned char) cmd.s[0])) cmd.s[0] = toupper(cmd.s[0]);
for (p = &cmd.s[1]; *p; p++) {
if (isupper((unsigned char) *p)) *p = tolower(*p);
}
/* if we need to force a kick, do so */
if(referral_kick) {
kick_mupdate();
referral_kick = 0;
}
/* Only Authenticate/Login/Logout/Noop/Starttls
allowed when not logged in */
if (!proxyd_userid && !strchr("ALNCIS", cmd.s[0])) goto nologin;
switch (cmd.s[0]) {
case 'A':
if (!strcmp(cmd.s, "Authenticate")) {
if (c != ' ') goto missingargs;
c = getword(proxyd_in, &arg1);
if (!imparse_isatom(arg1.s)) {
prot_printf(proxyd_out,
"%s BAD Invalid authenticate mechanism\r\n",
tag.s);
eatline(proxyd_in, c);
continue;
}
if (c == '\r') c = prot_getc(proxyd_in);
if (c != '\n') goto extraargs;
if (proxyd_userid) {
prot_printf(proxyd_out,
"%s BAD Already authenticated\r\n", tag.s);
continue;
}
cmd_authenticate(tag.s, arg1.s);
}
else if (!proxyd_userid) goto nologin;
else if (!strcmp(cmd.s, "Append")) {
if (c != ' ') goto missingargs;
c = getastring(proxyd_in, proxyd_out, &arg1);
if (c != ' ') goto missingargs;
cmd_append(tag.s, arg1.s);
}
else goto badcmd;
break;
case 'B':
if (!strcmp(cmd.s, "Bboard")) {
if (c != ' ') goto missingargs;
c = getastring(proxyd_in, proxyd_out, &arg1);
if (c == EOF) goto missingargs;
if (c == '\r') c = prot_getc(proxyd_in);
if (c != '\n') goto extraargs;
cmd_select(tag.s, cmd.s, arg1.s);
}
else goto badcmd;
break;
case 'C':
if (!strcmp(cmd.s, "Capability")) {
if (c == '\r') c = prot_getc(proxyd_in);
if (c != '\n') goto extraargs;
cmd_capability(tag.s);
}
else if (!proxyd_userid) goto nologin;
else if (!strcmp(cmd.s, "Check")) {
if (!backend_current) goto nomailbox;
if (c == '\r') c = prot_getc(proxyd_in);
if (c != '\n') goto extraargs;
cmd_noop(tag.s, cmd.s);
}
else if (!strcmp(cmd.s, "Copy")) {
if (!backend_current) goto nomailbox;
usinguid = 0;
if (c != ' ') goto missingargs;
copy:
c = getword(proxyd_in, &arg1);
if (c == '\r') goto missingargs;
if (c != ' ' || !imparse_issequence(arg1.s)) goto badsequence;
c = getastring(proxyd_in, proxyd_out, &arg2);
if (c == EOF) goto missingargs;
if (c == '\r') c = prot_getc(proxyd_in);
if (c != '\n') goto extraargs;
cmd_copy(tag.s, arg1.s, arg2.s, usinguid);
}
else if (!strcmp(cmd.s, "Create")) {
havepartition = 0;
if (c != ' ') goto missingargs;
c = getastring(proxyd_in, proxyd_out, &arg1);
if (c == EOF) goto missingargs;
if (c == ' ') {
havepartition = 1;
c = getword(proxyd_in, &arg2);
if (!imparse_isatom(arg2.s)) goto badpartition;
}
if (c == '\r') c = prot_getc(proxyd_in);
if (c != '\n') goto extraargs;
cmd_create(tag.s, arg1.s, havepartition ? arg2.s : 0);
}
else if (!strcmp(cmd.s, "Close")) {
if (!backend_current) goto nomailbox;
if (c == '\r') c = prot_getc(proxyd_in);
if (c != '\n') goto extraargs;
cmd_close(tag.s);
}
else goto badcmd;
break;
case 'D':
if (!strcmp(cmd.s, "Delete")) {
if (c != ' ') goto missingargs;
c = getastring(proxyd_in, proxyd_out, &arg1);
if (c == EOF) goto missingargs;
if (c == '\r') c = prot_getc(proxyd_in);
if (c != '\n') goto extraargs;
cmd_delete(tag.s, arg1.s);
}
else if (!strcmp(cmd.s, "Deleteacl")) {
if (c != ' ') goto missingargs;
c = getastring(proxyd_in, proxyd_out, &arg1);
if (!strcasecmp(arg1.s, "mailbox")) {
if (c != ' ') goto missingargs;
c = getastring(proxyd_in, proxyd_out, &arg1);
}
if (c != ' ') goto missingargs;
c = getastring(proxyd_in, proxyd_out, &arg2);
if (c == EOF) goto missingargs;
if (c == '\r') c = prot_getc(proxyd_in);
if (c != '\n') goto extraargs;
cmd_setacl(tag.s, arg1.s, arg2.s, (char *)0);
}
else goto badcmd;
break;
case 'E':
if (!strcmp(cmd.s, "Expunge")) {
if (!backend_current) goto nomailbox;
if (c == '\r') c = prot_getc(proxyd_in);
if (c != '\n') goto extraargs;
cmd_expunge(tag.s, 0);
}
else if (!strcmp(cmd.s, "Examine")) {
if (c != ' ') goto missingargs;
c = getastring(proxyd_in, proxyd_out, &arg1);
if (c == EOF) goto missingargs;
if (c == '\r') c = prot_getc(proxyd_in);
if (c != '\n') goto extraargs;
cmd_select(tag.s, cmd.s, arg1.s);
}
else goto badcmd;
break;
case 'F':
if (!strcmp(cmd.s, "Fetch")) {
if (!backend_current) goto nomailbox;
usinguid = 0;
if (c != ' ') goto missingargs;
fetch:
c = getword(proxyd_in, &arg1);
if (c == '\r') goto missingargs;
if (c != ' ' || !imparse_issequence(arg1.s)) goto badsequence;
cmd_fetch(tag.s, arg1.s, usinguid);
}
else if (!strcmp(cmd.s, "Find")) {
c = getword(proxyd_in, &arg1);
if (c != ' ') goto missingargs;
c = getastring(proxyd_in, proxyd_out, &arg2);
if (c == EOF) goto missingargs;
if (c == '\r') c = prot_getc(proxyd_in);
if (c != '\n') goto extraargs;
cmd_find(tag.s, arg1.s, arg2.s);
}
else goto badcmd;
break;
case 'G':
if (!strcmp(cmd.s, "Getacl")) {
oldform = 0;
if (c != ' ') goto missingargs;
c = getastring(proxyd_in, proxyd_out, &arg1);
if (!strcasecmp(arg1.s, "mailbox")) {
oldform = 1;
if (c != ' ') goto missingargs;
c = getastring(proxyd_in, proxyd_out, &arg1);
}
if (c == EOF) goto missingargs;
if (c == '\r') c = prot_getc(proxyd_in);
if (c != '\n') goto extraargs;
cmd_getacl(tag.s, arg1.s, oldform);
}
#ifdef ENABLE_ANNOTATEMORE
else if (!strcmp(cmd.s, "Getannotation")) {
if (c != ' ') goto missingargs;
cmd_getannotation(tag.s);
}
#endif
else if (!strcmp(cmd.s, "Getquota")) {
if (c != ' ') goto missingargs;
c = getastring(proxyd_in, proxyd_out, &arg1);
if (c == EOF) goto missingargs;
if (c == '\r') c = prot_getc(proxyd_in);
if (c != '\n') goto extraargs;
cmd_getquota(tag.s, arg1.s);
}
else if (!strcmp(cmd.s, "Getquotaroot")) {
if (c != ' ') goto missingargs;
c = getastring(proxyd_in, proxyd_out, &arg1);
if (c == EOF) goto missingargs;
if (c == '\r') c = prot_getc(proxyd_in);
if (c != '\n') goto extraargs;
cmd_getquotaroot(tag.s, arg1.s);
}
else goto badcmd;
break;
case 'I':
if (!strcmp(cmd.s, "Id")) {
if (c != ' ') goto missingargs;
cmd_id(tag.s);
}
else if (!proxyd_userid) goto nologin;
else if (!strcmp(cmd.s, "Idle")) {
if (c == '\r') c = prot_getc(proxyd_in);
if (c != '\n') goto extraargs;
cmd_idle(tag.s);
}
else goto badcmd;
break;
case 'L':
if (!strcmp(cmd.s, "Login")) {
if (c != ' ') goto missingargs;
c = getastring(proxyd_in, proxyd_out, &arg1);
if(c != ' ') goto missingargs;
cmd_login(tag.s, arg1.s);
}
else if (!strcmp(cmd.s, "Logout")) {
if (c == '\r') c = prot_getc(proxyd_in);
if (c != '\n') goto extraargs;
prot_printf(proxyd_out,
"* BYE %s\r\n", error_message(IMAP_BYE_LOGOUT));
prot_printf(proxyd_out, "%s OK %s\r\n",
tag.s, error_message(IMAP_OK_COMPLETED));
return;
}
else if (!proxyd_userid) goto nologin;
else if (!strcmp(cmd.s, "List")) {
c = getastring(proxyd_in, proxyd_out, &arg1);
if (c != ' ') goto missingargs;
c = getastring(proxyd_in, proxyd_out, &arg2);
if (c == '\r') c = prot_getc(proxyd_in);
if (c != '\n') goto extraargs;
cmd_list(tag.s, 0, arg1.s, arg2.s);
}
else if (!strcmp(cmd.s, "Lsub")) {
c = getastring(proxyd_in, proxyd_out, &arg1);
if (c != ' ') goto missingargs;
c = getastring(proxyd_in, proxyd_out, &arg2);
if (c == '\r') c = prot_getc(proxyd_in);
if (c != '\n') goto extraargs;
cmd_list(tag.s, 1, arg1.s, arg2.s);
}
else if (!strcmp(cmd.s, "Listrights")) {
c = getastring(proxyd_in, proxyd_out, &arg1);
if (c != ' ') goto missingargs;
c = getastring(proxyd_in, proxyd_out, &arg2);
if (c == '\r') c = prot_getc(proxyd_in);
if (c != '\n') goto extraargs;
cmd_listrights(tag.s, arg1.s, arg2.s);
}
else goto badcmd;
break;
case 'M':
if (!strcmp(cmd.s, "Myrights")) {
oldform = 0;
if (c != ' ') goto missingargs;
c = getastring(proxyd_in, proxyd_out, &arg1);
if (!strcasecmp(arg1.s, "mailbox")) {
oldform = 1;
if (c != ' ') goto missingargs;
c = getastring(proxyd_in, proxyd_out, &arg1);
}
if (c == EOF) goto missingargs;
if (c == '\r') c = prot_getc(proxyd_in);
if (c != '\n') goto extraargs;
cmd_myrights(tag.s, arg1.s, oldform);
}
else goto badcmd;
break;
case 'N':
if (!strcmp(cmd.s, "Noop")) {
if (c == '\r') c = prot_getc(proxyd_in);
if (c != '\n') goto extraargs;
cmd_noop(tag.s, cmd.s);
}
#ifdef ENABLE_X_NETSCAPE_HACK
else if (!strcmp(cmd.s, "Netscape")) {
if (c == '\r') c = prot_getc(proxyd_in);
if (c != '\n') goto extraargs;
cmd_netscape(tag.s);
}
#endif
else if (!proxyd_userid) goto nologin;
else if (!strcmp(cmd.s, "Namespace")) {
if (c == '\r') c = prot_getc(proxyd_in);
if (c != '\n') goto extraargs;
cmd_namespace(tag.s);
}
else goto badcmd;
break;
case 'P':
if (!strcmp(cmd.s, "Partial")) {
if (!backend_current) goto nomailbox;
if (c != ' ') goto missingargs;
c = getword(proxyd_in, &arg1);
if (c != ' ') goto missingargs;
c = getword(proxyd_in, &arg2);
if (c != ' ') goto missingargs;
c = getword(proxyd_in, &arg3);
if (c != ' ') goto missingargs;
c = getword(proxyd_in, &arg4);
if (c == '\r') c = prot_getc(proxyd_in);
if (c != '\n') goto extraargs;
cmd_partial(tag.s, arg1.s, arg2.s, arg3.s, arg4.s);
}
else goto badcmd;
break;
case 'R':
if (!strcmp(cmd.s, "Rename")) {
havepartition = 0;
if (c != ' ') goto missingargs;
c = getastring(proxyd_in, proxyd_out, &arg1);
if (c != ' ') goto missingargs;
c = getastring(proxyd_in, proxyd_out, &arg2);
if (c == EOF) goto missingargs;
if (c == ' ') {
havepartition = 1;
c = getword(proxyd_in, &arg3);
if (!imparse_isatom(arg3.s)) goto badpartition;
}
if (c == '\r') c = prot_getc(proxyd_in);
if (c != '\n') goto extraargs;
cmd_rename(tag.s, arg1.s, arg2.s, havepartition ? arg3.s : 0);
}
else if (!strcmp(cmd.s, "Rlist")) {
supports_referrals = 1;
c = getastring(proxyd_in, proxyd_out, &arg1);
if (c != ' ') goto missingargs;
c = getastring(proxyd_in, proxyd_out, &arg2);
if (c == '\r') c = prot_getc(proxyd_in);
if (c != '\n') goto extraargs;
cmd_list(tag.s, 0, arg1.s, arg2.s);
}
else if (!strcmp(cmd.s, "Rlsub")) {
supports_referrals = 1;
c = getastring(proxyd_in, proxyd_out, &arg1);
if (c != ' ') goto missingargs;
c = getastring(proxyd_in, proxyd_out, &arg2);
if (c == '\r') c = prot_getc(proxyd_in);
if (c != '\n') goto extraargs;
cmd_list(tag.s, 1, arg1.s, arg2.s);
} else if(!strcmp(cmd.s, "Reconstruct")) {
if (c != ' ') goto missingargs;
c = getastring(proxyd_in, proxyd_out, &arg1);
if(c == ' ') {
/* Optional RECURSEIVE argument */
c = getword(proxyd_in, &arg2);
if(!imparse_isatom(arg2.s))
goto extraargs;
else if(strcasecmp(arg2.s, "RECURSIVE"))
goto extraargs;
/* we ignore the argument, because proxyd does not care */
}
if(c == '\r') c = prot_getc(proxyd_in);
if(c != '\n') goto extraargs;
cmd_reconstruct(tag.s, arg1.s);
}
else goto badcmd;
break;
case 'S':
if (!strcmp(cmd.s, "Starttls")) {
if (!tls_enabled("imap")) {
/* we don't support starttls */
goto badcmd;
}
if (c == '\r') c = prot_getc(proxyd_in);
if (c != '\n') goto extraargs;
/* if we've already done SASL fail */
if (proxyd_userid != NULL) {
prot_printf(proxyd_out,
"%s BAD Can't Starttls after authentication\r\n", tag.s);
continue;
}
/* check if already did a successful tls */
if (proxyd_starttls_done == 1) {
prot_printf(proxyd_out,
"%s BAD Already did a successful Starttls\r\n",
tag.s);
continue;
}
cmd_starttls(tag.s, 0);
continue;
}
if (!proxyd_userid) {
goto nologin;
} else if (!strcmp(cmd.s, "Store")) {
if (!backend_current) goto nomailbox;
usinguid = 0;
if (c != ' ') goto missingargs;
store:
c = getword(proxyd_in, &arg1);
if (c != ' ' || !imparse_issequence(arg1.s)) goto badsequence;
c = getword(proxyd_in, &arg2);
if (c != ' ') goto badsequence;
cmd_store(tag.s, arg1.s, arg2.s, usinguid);
}
else if (!strcmp(cmd.s, "Select")) {
if (c != ' ') goto missingargs;
c = getastring(proxyd_in, proxyd_out, &arg1);
if (c == EOF) goto missingargs;
if (c == '\r') c = prot_getc(proxyd_in);
if (c != '\n') goto extraargs;
cmd_select(tag.s, cmd.s, arg1.s);
}
else if (!strcmp(cmd.s, "Search")) {
if (!backend_current) goto nomailbox;
usinguid = 0;
if (c != ' ') goto missingargs;
search:
cmd_search(tag.s, usinguid);
}
else if (!strcmp(cmd.s, "Subscribe")) {
if (c != ' ') goto missingargs;
havenamespace = 0;
c = getastring(proxyd_in, proxyd_out, &arg1);
if (c == ' ') {
havenamespace = 1;
c = getastring(proxyd_in, proxyd_out, &arg2);
}
if (c == EOF) goto missingargs;
if (c == '\r') c = prot_getc(proxyd_in);
if (c != '\n') goto extraargs;
if (havenamespace) {
cmd_changesub(tag.s, arg1.s, arg2.s, 1);
}
else {
cmd_changesub(tag.s, (char *)0, arg1.s, 1);
}
}
else if (!strcmp(cmd.s, "Setacl")) {
if (c != ' ') goto missingargs;
c = getastring(proxyd_in, proxyd_out, &arg1);
if (!strcasecmp(arg1.s, "mailbox")) {
if (c != ' ') goto missingargs;
c = getastring(proxyd_in, proxyd_out, &arg1);
}
if (c != ' ') goto missingargs;
c = getastring(proxyd_in, proxyd_out, &arg2);
if (c != ' ') goto missingargs;
c = getastring(proxyd_in, proxyd_out, &arg3);
if (c == EOF) goto missingargs;
if (c == '\r') c = prot_getc(proxyd_in);
if (c != '\n') goto extraargs;
cmd_setacl(tag.s, arg1.s, arg2.s, arg3.s);
}
#ifdef ENABLE_ANNOTATEMORE
else if (!strcmp(cmd.s, "Setannotation")) {
if (c != ' ') goto missingargs;
cmd_setannotation(tag.s);
}
#endif
else if (!strcmp(cmd.s, "Setquota")) {
if (c != ' ') goto missingargs;
c = getastring(proxyd_in, proxyd_out, &arg1);
if (c != ' ') goto missingargs;
cmd_setquota(tag.s, arg1.s);
}
else if (!strcmp(cmd.s, "Sort")) {
if (!backend_current) goto nomailbox;
usinguid = 0;
if (c != ' ') goto missingargs;
sort:
cmd_sort(tag.s, usinguid);
}
else if (!strcmp(cmd.s, "Status")) {
if (c != ' ') goto missingargs;
c = getastring(proxyd_in, proxyd_out, &arg1);
if (c != ' ') goto missingargs;
cmd_status(tag.s, arg1.s);
}
else goto badcmd;
break;
case 'T':
if (!strcmp(cmd.s, "Thread")) {
if (!backend_current) goto nomailbox;
usinguid = 0;
if (c != ' ') goto missingargs;
thread:
cmd_thread(tag.s, usinguid);
}
else goto badcmd;
break;
case 'U':
if (!strcmp(cmd.s, "Uid")) {
if (!backend_current) goto nomailbox;
usinguid = 1;
if (c != ' ') goto missingargs;
c = getword(proxyd_in, &arg1);
if (c != ' ') goto missingargs;
lcase(arg1.s);
if (!strcmp(arg1.s, "fetch")) {
goto fetch;
}
else if (!strcmp(arg1.s, "store")) {
goto store;
}
else if (!strcmp(arg1.s, "search")) {
goto search;
}
else if (!strcmp(arg1.s, "sort")) {
goto sort;
}
else if (!strcmp(arg1.s, "thread")) {
goto thread;
}
else if (!strcmp(arg1.s, "copy")) {
goto copy;
}
else if (!strcmp(arg1.s, "expunge")) {
c = getword(proxyd_in, &arg1);
if (!imparse_issequence(arg1.s)) goto badsequence;
if (c == '\r') c = prot_getc(proxyd_in);
if (c != '\n') goto extraargs;
cmd_expunge(tag.s, arg1.s);
}
else {
prot_printf(proxyd_out,
"%s BAD Unrecognized UID subcommand\r\n",
tag.s);
eatline(proxyd_in, c);
}
}
else if (!strcmp(cmd.s, "Unsubscribe")) {
if (c != ' ') goto missingargs;
havenamespace = 0;
c = getastring(proxyd_in, proxyd_out, &arg1);
if (c == ' ') {
havenamespace = 1;
c = getastring(proxyd_in, proxyd_out, &arg2);
}
if (c == EOF) goto missingargs;
if (c == '\r') c = prot_getc(proxyd_in);
if (c != '\n') goto extraargs;
if (havenamespace) {
cmd_changesub(tag.s, arg1.s, arg2.s, 0);
}
else {
cmd_changesub(tag.s, (char *)0, arg1.s, 0);
}
}
else if (!strcmp(cmd.s, "Unselect")) {
if (!backend_current) goto nomailbox;
if (c == '\r') c = prot_getc(proxyd_in);
if (c != '\n') goto extraargs;
cmd_unselect(tag.s);
}
else goto badcmd;
break;
default:
badcmd:
prot_printf(proxyd_out, "%s BAD Unrecognized command\r\n", tag.s);
eatline(proxyd_in, c);
}
continue;
nologin:
prot_printf(proxyd_out, "%s BAD Please login first\r\n", tag.s);
eatline(proxyd_in, c);
continue;
nomailbox:
prot_printf(proxyd_out, "%s BAD Please select a mailbox first\r\n",
tag.s);
eatline(proxyd_in, c);
continue;
missingargs:
prot_printf(proxyd_out, "%s BAD Missing required argument to %s\r\n",
tag.s, cmd.s);
eatline(proxyd_in, c);
continue;
extraargs:
prot_printf(proxyd_out, "%s BAD Unexpected extra arguments to %s\r\n",
tag.s, cmd.s);
eatline(proxyd_in, c);
continue;
badsequence:
prot_printf(proxyd_out, "%s BAD Invalid sequence in %s\r\n",
tag.s, cmd.s);
eatline(proxyd_in, c);
continue;
badpartition:
prot_printf(proxyd_out, "%s BAD Invalid partition name in %s\r\n",
tag.s, cmd.s);
eatline(proxyd_in, c);
continue;
}
}
/*
* Perform a LOGIN command
*/
void cmd_login(char *tag, char *user)
{
char *canon_user;
char c;
struct buf passwdbuf;
char *passwd;
char *reply = 0;
int plaintextloginpause;
int r;
if (proxyd_userid) {
eatline(proxyd_in, ' ');
prot_printf(proxyd_out, "%s BAD Already logged in\r\n", tag);
return;
}
canon_user = auth_canonifyid(user, 0);
if (!canon_user) {
syslog(LOG_NOTICE, "badlogin: %s plaintext %s invalid user",
proxyd_clienthost, beautify_string(user));
prot_printf(proxyd_out, "%s NO %s\r\n", tag,
error_message(IMAP_INVALID_USER));
return;
}
/* possibly disallow login */
if ((proxyd_starttls_done == 0) &&
(config_getswitch("allowplaintext", 1) == 0) &&
strcmp(canon_user, "anonymous") != 0) {
eatline(proxyd_in, ' ');
prot_printf(proxyd_out, "%s NO Login only available under a layer\r\n",
tag);
return;
}
memset(&passwdbuf,0,sizeof(struct buf));
c = getastring(proxyd_in, proxyd_out, &passwdbuf);
if(c == '\r') c = prot_getc(proxyd_in);
if (c != '\n') {
freebuf(&passwdbuf);
prot_printf(proxyd_out,
"%s BAD Unexpected extra arguments to LOGIN\r\n",
tag);
eatline(proxyd_in, c);
return;
}
passwd = passwdbuf.s;
if (!strcmp(canon_user, "anonymous")) {
if (config_getswitch("allowanonymouslogin", 0)) {
passwd = beautify_string(passwd);
if (strlen(passwd) > 500) passwd[500] = '\0';
syslog(LOG_NOTICE, "login: %s anonymous %s",
proxyd_clienthost, passwd);
reply = "Anonymous access granted";
proxyd_userid = xstrdup("anonymous");
}
else {
syslog(LOG_NOTICE, "badlogin: %s anonymous login refused",
proxyd_clienthost);
prot_printf(proxyd_out, "%s NO %s\r\n", tag,
error_message(IMAP_ANONYMOUS_NOT_PERMITTED));
freebuf(&passwdbuf);
return;
}
}
else if ((r = sasl_checkpass(proxyd_saslconn,
canon_user,
strlen(canon_user),
passwd,
strlen(passwd)))!=SASL_OK) {
const char *errorstring = sasl_errstring(r, NULL, NULL);
if (reply) {
syslog(LOG_NOTICE, "badlogin: %s plaintext %s %s",
proxyd_clienthost, canon_user, reply);
}
/* Apply penalty only if not under layer */
if (proxyd_starttls_done == 0)
sleep(3);
if (errorstring) {
prot_printf(proxyd_out, "%s NO Login failed: %s\r\n",
tag, errorstring);
} else {
prot_printf(proxyd_out, "%s NO Login failed.", tag);
}
freebuf(&passwdbuf);
return;
}
else {
proxyd_userid = xstrdup(canon_user);
syslog(LOG_NOTICE, "login: %s %s plaintext%s %s", proxyd_clienthost,
canon_user, proxyd_starttls_done ? "+TLS" : "",
reply ? reply : "");
plaintextloginpause = config_getint("plaintextloginpause", 0);
if (plaintextloginpause) {
/* Apply penalty only if not under layer */
if (proxyd_starttls_done == 0)
sleep(plaintextloginpause);
}
}
proxyd_authstate = auth_newstate(canon_user, (char *)0);
proxyd_userisadmin = authisa(proxyd_authstate, "imap", "admins");
if (!reply) reply = "User logged in";
prot_printf(proxyd_out, "%s OK %s\r\n", tag, reply);
/* Create telemetry log */
proxyd_logfd = telemetry_log(proxyd_userid, proxyd_in, proxyd_out);
/* Set namespace */
if ((r = mboxname_init_namespace(&proxyd_namespace, proxyd_userisadmin)) != 0) {
syslog(LOG_ERR, error_message(r));
fatal(error_message(r), EC_CONFIG);
}
freebuf(&passwdbuf);
return;
}
/*
* Perform an AUTHENTICATE command
*/
void cmd_authenticate(char *tag, char *authtype)
{
int sasl_result;
const char *userid_buf;
const int *ssfp;
char *ssfmsg=NULL;
int r;
r = saslserver(proxyd_saslconn, authtype, NULL, "+ ",
proxyd_in, proxyd_out, &sasl_result, NULL);
if (r) {
const char *errorstring = NULL;
switch (r) {
case IMAP_SASL_CANCEL:
prot_printf(proxyd_out,
"%s NO Client canceled authentication\r\n", tag);
break;
case IMAP_SASL_PROTERR:
errorstring = prot_error(proxyd_in);
prot_printf(proxyd_out,
"%s NO Error reading client response: %s\r\n",
tag, errorstring ? errorstring : "");
break;
default:
/* failed authentication */
errorstring = sasl_errstring(sasl_result, NULL, NULL);
syslog(LOG_NOTICE, "badlogin: %s %s [%s]",
proxyd_clienthost, authtype, sasl_errdetail(proxyd_saslconn));
snmp_increment_args(AUTHENTICATION_NO, 1,
VARIABLE_AUTH, 0, /* hash_simple(authtype) */
VARIABLE_LISTEND);
sleep(3);
if (errorstring) {
prot_printf(proxyd_out, "%s NO %s\r\n", tag, errorstring);
} else {
prot_printf(proxyd_out, "%s NO Error authenticating\r\n", tag);
}
}
reset_saslconn(&proxyd_saslconn);
return;
}
/* successful authentication */
/* get the userid from SASL --- already canonicalized from
* mysasl_authproc()
*/
sasl_result = sasl_getprop(proxyd_saslconn, SASL_USERNAME,
(const void **)&userid_buf);
proxyd_userid = xstrdup(userid_buf);
if (sasl_result!=SASL_OK)
{
prot_printf(proxyd_out, "%s NO weird SASL error %d SASL_USERNAME\r\n",
tag, sasl_result);
syslog(LOG_ERR, "weird SASL error %d getting SASL_USERNAME",
sasl_result);
reset_saslconn(&proxyd_saslconn);
return;
}
proc_register("proxyd", proxyd_clienthost, proxyd_userid, (char *)0);
syslog(LOG_NOTICE, "login: %s %s %s%s %s",
proxyd_clienthost, proxyd_userid,
authtype, proxyd_starttls_done ? "+TLS" : "", "User logged in");
sasl_getprop(proxyd_saslconn, SASL_SSF, (const void **) &ssfp);
if (proxyd_starttls_done) {
switch(*ssfp) {
case 0: ssfmsg = "tls protection"; break;
case 1: ssfmsg = "tls plus integrity protection"; break;
default: ssfmsg = "tls plus privacy protection"; break;
}
} else {
switch(*ssfp) {
case 0: ssfmsg="no protection"; break;
case 1: ssfmsg="integrity protection"; break;
default: ssfmsg="privacy protection"; break;
}
}
prot_printf(proxyd_out, "%s OK Success (%s)\r\n", tag,ssfmsg);
prot_flush(proxyd_out);
prot_setsasl(proxyd_in, proxyd_saslconn);
prot_setsasl(proxyd_out, proxyd_saslconn);
/* Create telemetry log */
proxyd_logfd = telemetry_log(proxyd_userid, proxyd_in, proxyd_out);
/* Set namespace */
if ((r = mboxname_init_namespace(&proxyd_namespace, proxyd_userisadmin)) != 0) {
syslog(LOG_ERR, error_message(r));
fatal(error_message(r), EC_CONFIG);
}
return;
}
/*
* Perform a NOOP command
*/
void cmd_noop(char *tag, char *cmd)
{
if (backend_current) {
prot_printf(backend_current->out, "%s %s\r\n", tag, cmd);
pipe_including_tag(backend_current, tag, 0);
} else {
prot_printf(proxyd_out, "%s OK %s\r\n", tag,
error_message(IMAP_OK_COMPLETED));
}
}
/*
* Parse and perform an ID command.
*
* the command has been parsed up to the parameter list.
*
* we only allow one ID in non-authenticated state from a given client.
* we only allow MAXIDFAILED consecutive failed IDs from a given client.
* we only record MAXIDLOG ID responses from a given client.
*/
void cmd_id(char *tag)
{
static int did_id = 0;
static int failed_id = 0;
static int logged_id = 0;
int error = 0;
int c = EOF, npair = 0;
static struct buf arg, field;
struct idparamlist *params = 0;
/* check if we've already had an ID in non-authenticated state */
if (!proxyd_userid && did_id) {
prot_printf(proxyd_out,
"%s NO Only one Id allowed in non-authenticated state\r\n",
tag);
eatline(proxyd_in, c);
return;
}
/* check if we've had too many failed IDs in a row */
if (failed_id >= MAXIDFAILED) {
prot_printf(proxyd_out, "%s NO Too many (%u) invalid Id commands\r\n",
tag, failed_id);
eatline(proxyd_in, c);
return;
}
/* ok, accept parameter list */
c = getword(proxyd_in, &arg);
/* check for "NIL" or start of parameter list */
if (strcasecmp(arg.s, "NIL") && c != '(') {
prot_printf(proxyd_out, "%s BAD Invalid parameter list in Id\r\n", tag);
eatline(proxyd_in, c);
failed_id++;
return;
}
/* parse parameter list */
if (c == '(') {
for (;;) {
if (c == ')') {
/* end of string/value pairs */
break;
}
/* get field name */
c = getstring(proxyd_in, proxyd_out, &field);
if (c != ' ') {
prot_printf(proxyd_out,
"%s BAD Invalid/missing field name in Id\r\n",
tag);
error = 1;
break;
}
/* get field value */
c = getnstring(proxyd_in, proxyd_out, &arg);
if (c != ' ' && c != ')') {
prot_printf(proxyd_out,
"%s BAD Invalid/missing value in Id\r\n",
tag);
error = 1;
break;
}
/* ok, we're anal, but we'll still process the ID command */
if (strlen(field.s) > MAXIDFIELDLEN) {
prot_printf(proxyd_out,
"%s BAD field longer than %u octets in Id\r\n",
tag, MAXIDFIELDLEN);
error = 1;
break;
}
if (strlen(arg.s) > MAXIDVALUELEN) {
prot_printf(proxyd_out,
"%s BAD value longer than %u octets in Id\r\n",
tag, MAXIDVALUELEN);
error = 1;
break;
}
if (++npair > MAXIDPAIRS) {
prot_printf(proxyd_out,
"%s BAD too many (%u) field-value pairs in ID\r\n",
tag, MAXIDPAIRS);
error = 1;
break;
}
/* ok, we're happy enough */
id_appendparamlist(¶ms, field.s, arg.s);
}
if (error || c != ')') {
/* erp! */
eatline(proxyd_in, c);
id_freeparamlist(params);
failed_id++;
return;
}
c = prot_getc(proxyd_in);
}
/* check for CRLF */
if (c == '\r') c = prot_getc(proxyd_in);
if (c != '\n') {
prot_printf(proxyd_out,
"%s BAD Unexpected extra arguments to Id\r\n", tag);
eatline(proxyd_in, c);
id_freeparamlist(params);
failed_id++;
return;
}
/* log the client's ID string.
eventually this should be a callback or something. */
if (npair && logged_id < MAXIDLOG) {
char logbuf[MAXIDLOGLEN + 1] = "";
struct idparamlist *pptr;
for (pptr = params; pptr; pptr = pptr->next) {
/* should we check for and format literals here ??? */
snprintf(logbuf + strlen(logbuf), MAXIDLOGLEN - strlen(logbuf),
" \"%s\" ", pptr->field);
if (!strcmp(pptr->value, "NIL"))
snprintf(logbuf + strlen(logbuf), MAXIDLOGLEN - strlen(logbuf),
"NIL");
else
snprintf(logbuf + strlen(logbuf), MAXIDLOGLEN - strlen(logbuf),
"\"%s\"", pptr->value);
}
syslog(LOG_INFO, "client id:%s", logbuf);
logged_id++;
}
id_freeparamlist(params);
/* spit out our ID string.
eventually this might be configurable. */
if (config_getswitch("imapidresponse", 1)) {
id_response(proxyd_out);
/* add info about the backend */
if (backend_current)
prot_printf(proxyd_out, " \"backend-url\" \"imap://%s\"",
backend_current->hostname);
else
prot_printf(proxyd_out, " \"backend-url\" NIL");
prot_printf(proxyd_out, ")\r\n");
}
else
prot_printf(proxyd_out, "* ID NIL\r\n");
prot_printf(proxyd_out, "%s OK %s\r\n", tag,
error_message(IMAP_OK_COMPLETED));
failed_id = 0;
did_id = 1;
}
/*
* Append the 'field' / 'value' pair to the idparamlist 'l'.
*/
void id_appendparamlist(struct idparamlist **l, char *field, char *value)
{
struct idparamlist **tail = l;
while (*tail) tail = &(*tail)->next;
*tail = (struct idparamlist *)xmalloc(sizeof(struct idparamlist));
(*tail)->field = xstrdup(field);
(*tail)->value = xstrdup(value);
(*tail)->next = 0;
}
/*
* Free the idparamlist 'l'
*/
void id_freeparamlist(struct idparamlist *l)
{
struct idparamlist *n;
while (l) {
n = l->next;
free(l->field);
free(l->value);
l = n;
}
}
/*
* Perform an IDLE command
*/
void cmd_idle(char *tag)
{
#ifdef PROXY_IDLE
static int idle_period = -1;
int c;
static struct buf arg;
/* get polling period */
if (idle_period == -1) {
idle_period = config_getint("imapidlepoll", 60);
if (idle_period < 1) idle_period = 1;
}
if (!backend_current) {
c = idle_nomailbox(tag, idle_period, &arg);
} else if (CAPA(backend_current, IDLE)) {
c = idle_passthrough(tag, idle_period, &arg);
} else {
c = idle_simulate(tag, idle_period, &arg);
}
if (c != EOF) {
if (!strcasecmp(arg.s, "Done") &&
(c = (c == '\r') ? prot_getc(proxyd_in) : c) == '\n') {
prot_printf(proxyd_out, "%s OK %s\r\n", tag,
error_message(IMAP_OK_COMPLETED));
}
else {
prot_printf(proxyd_out,
"%s BAD Invalid Idle continuation\r\n", tag);
eatline(proxyd_in, c);
}
}
#else
prot_printf(proxyd_out, "%s NO idle disabled\r\n", tag);
#endif
}
/* Check for alerts */
struct prot_waitevent *idle_getalerts(struct protstream *s,
struct prot_waitevent *ev, void *rock)
{
int idle_period = *((int *) rock);
int fd;
if (! proxyd_userisadmin &&
(fd = open(shutdownfilename, O_RDONLY, 0)) != -1) {
/* if we're actually running IDLE on the be, terminate it */
if (backend_current && CAPA(backend_current, IDLE))
prot_printf(backend_current->out, "DONE\r\n");
shutdown_file(fd);
}
ev->mark = time(NULL) + idle_period;
return ev;
}
/* Run IDLE in the authenticated state (no mailbox) */
char idle_nomailbox(char *tag, int idle_period, struct buf *arg)
{
struct prot_waitevent *idle_event;
int c;
/* Tell client we are idling and waiting for end of command */
prot_printf(proxyd_out, "+ go ahead\r\n");
prot_flush(proxyd_out);
/* Setup an event function to check for alerts */
idle_event = prot_addwaitevent(proxyd_in,
time(NULL) + idle_period,
idle_getalerts, &idle_period);
/* Get continuation data */
c = getword(proxyd_in, arg);
/* Remove the event function */
prot_removewaitevent(proxyd_in, idle_event);
return c;
}
/* Get unsolicited untagged responses from the backend
assumes that untagged responses are:
a) short
b) don't contain literals
*/
struct prot_waitevent *idle_getresp(struct protstream *s,
struct prot_waitevent *ev, void *rock)
{
char buf[2048];
prot_NONBLOCK(backend_current->in);
while (prot_fgets(buf, sizeof(buf), backend_current->in)) {
prot_write(proxyd_out, buf, strlen(buf));
prot_flush(proxyd_out);
}
prot_BLOCK(backend_current->in);
return idle_getalerts(s, ev, rock);
}
/* Run IDLE on the backend */
char idle_passthrough(char *tag, int idle_period, struct buf *arg)
{
struct prot_waitevent *idle_event;
int c;
char buf[2048];
/* Start IDLE on backend */
prot_printf(backend_current->out, "%s IDLE\r\n", tag);
if (!prot_fgets(buf, sizeof(buf), backend_current->in)) {
/* if we received nothing from the backend, fail */
prot_printf(proxyd_out, "%s NO %s\r\n", tag,
error_message(IMAP_SERVER_UNAVAILABLE));
return EOF;
}
if (buf[0] != '+') {
/* if we received anything but a continuation response,
spit out what we received and quit */
prot_write(proxyd_out, buf, strlen(buf));
return EOF;
}
/* Tell client we are idling and waiting for end of command */
prot_printf(proxyd_out, "+ go ahead\r\n");
prot_flush(proxyd_out);
/* Setup an event function to get responses from the idling backend */
idle_event = prot_addwaitevent(proxyd_in,
time(NULL) + idle_period,
idle_getresp, &idle_period);
/* Get continuation data */
c = getword(proxyd_in, arg);
/* Remove the event function */
prot_removewaitevent(backend_current->in, idle_event);
/* Either the client timed out, or gave us a continuation.
In either case we're done, so terminate IDLE on backend */
prot_printf(backend_current->out, "DONE\r\n");
pipe_until_tag(backend_current, tag, 0);
return c;
}
/* Poll the backend for updates */
static struct prot_waitevent *idle_poll(struct protstream *s,
struct prot_waitevent *ev, void *rock)
{
char mytag[128];
proxyd_gentag(mytag);
prot_printf(backend_current->out, "%s Noop\r\n", mytag);
pipe_until_tag(backend_current, mytag, 0);
prot_flush(proxyd_out);
return idle_getalerts(s, ev, rock);
}
/* Simulate IDLE by polling the backend */
char idle_simulate(char *tag, int idle_period, struct buf *arg)
{
struct prot_waitevent *idle_event;
int c;
/* Tell client we are idling and waiting for end of command */
prot_printf(proxyd_out, "+ go ahead\r\n");
prot_flush(proxyd_out);
/* Setup an event function to poll the backend for updates */
idle_event = prot_addwaitevent(proxyd_in,
time(NULL) + idle_period,
idle_poll, &idle_period);
/* Get continuation data */
c = getword(proxyd_in, arg);
/* Remove the event function */
prot_removewaitevent(proxyd_in, idle_event);
return c;
}
/*
* Perform a CAPABILITY command
*/
void cmd_capability(char *tag)
{
const char *sasllist; /* the list of SASL mechanisms */
unsigned mechcount;
if (backend_current) {
char mytag[128];
proxyd_gentag(mytag);
/* do i want to do a NOOP for every operation? */
prot_printf(backend_current->out, "%s Noop\r\n", mytag);
pipe_until_tag(backend_current, mytag, 0);
}
prot_printf(proxyd_out, "* CAPABILITY ");
prot_printf(proxyd_out, CAPABILITY_STRING);
#ifdef PROXY_IDLE
prot_printf(proxyd_out, " IDLE");
#endif
if (tls_enabled("imap")) {
prot_printf(proxyd_out, " STARTTLS");
}
if (!proxyd_starttls_done && !config_getswitch("allowplaintext", 1)) {
prot_printf(proxyd_out, " LOGINDISABLED");
}
if (sasl_listmech(proxyd_saslconn, NULL,
"AUTH=", " AUTH=", "",
&sasllist,
NULL, &mechcount) == SASL_OK && mechcount > 0) {
prot_printf(proxyd_out, " %s", sasllist);
} else {
/* else don't show anything */
}
#ifdef ENABLE_ANNOTATEMORE
prot_printf(proxyd_out, " ANNOTATEMORE");
#endif
#ifdef ENABLE_X_NETSCAPE_HACK
prot_printf(proxyd_out, " X-NETSCAPE");
#endif
prot_printf(proxyd_out, "\r\n");
prot_printf(proxyd_out, "%s OK %s\r\n", tag,
error_message(IMAP_OK_COMPLETED));
}
/*
* Parse and perform an APPEND command.
* The command has been parsed up to and including
* the mailbox name.
*/
void cmd_append(char *tag, char *name)
{
int r;
char mailboxname[MAX_MAILBOX_PATH + 1];
char *newserver;
struct backend *s = NULL;
/* we want to pipeline this whole command through to the server that
has name on it, and then do a noop on our current server */
r = (*proxyd_namespace.mboxname_tointernal)(&proxyd_namespace, name,
proxyd_userid, mailboxname);
if (!r) {
r = mlookup(mailboxname, &newserver, NULL, NULL);
}
if (!r && supports_referrals) {
proxyd_refer(tag, newserver, mailboxname);
/* Eat the argument */
eatline(proxyd_in, prot_getc(proxyd_in));
return;
}
if (!r) {
s = proxyd_findserver(newserver);
if (!s) r = IMAP_SERVER_UNAVAILABLE;
}
if (!r) {
prot_printf(s->out, "%s Append {%d+}\r\n%s ", tag, strlen(name), name);
if (!pipe_command(s, 16384)) {
pipe_until_tag(s, tag, 0);
}
} else {
eatline(proxyd_in, prot_getc(proxyd_in));
}
if (backend_current && backend_current != s) {
char mytag[128];
proxyd_gentag(mytag);
prot_printf(backend_current->out, "%s Noop\r\n", mytag);
pipe_until_tag(backend_current, mytag, 0);
}
if (r) {
prot_printf(proxyd_out, "%s NO %s\r\n", tag, error_message(r));
} else {
/* we're allowed to reference last_result since the noop, if
sent, went to a different server */
prot_printf(proxyd_out, "%s %s", tag, s->last_result);
}
}
/*
* Perform a SELECT/EXAMINE/BBOARD command
*/
void cmd_select(char *tag, char *cmd, char *name)
{
char mailboxname[MAX_MAILBOX_NAME+1];
int r = 0;
char *newserver;
struct backend *backend_next = NULL;
if (cmd[0] == 'B') {
/* BBoard namespace is empty */
r = IMAP_MAILBOX_NONEXISTENT;
}
else {
r = (*proxyd_namespace.mboxname_tointernal)(&proxyd_namespace, name,
proxyd_userid, mailboxname);
}
if (!r) r = mlookup(mailboxname, &newserver, NULL, NULL);
if (!r && supports_referrals) {
proxyd_refer(tag, newserver, mailboxname);
return;
}
if (!r) {
backend_next = proxyd_findserver(newserver);
if (!backend_next) r = IMAP_SERVER_UNAVAILABLE;
}
if (backend_current && backend_current != backend_next) {
char mytag[128];
/* switching servers; flush old server output */
proxyd_gentag(mytag);
prot_printf(backend_current->out, "%s Unselect\r\n", mytag);
/* do not fatal() here, because we don't really care about this
* server anymore anyway */
pipe_until_tag(backend_current, mytag, 1);
}
backend_current = backend_next;
if (r) {
prot_printf(proxyd_out, "%s NO %s\r\n", tag, error_message(r));
return;
}
prot_printf(backend_current->out, "%s %s {%d+}\r\n%s\r\n", tag, cmd,
strlen(name), name);
switch (pipe_including_tag(backend_current, tag, 0)) {
case PROXY_OK:
proc_register("proxyd", proxyd_clienthost, proxyd_userid, mailboxname);
syslog(LOG_DEBUG, "open: user %s opened %s on %s", proxyd_userid, name,
newserver);
break;
default:
syslog(LOG_DEBUG, "open: user %s failed to open %s", proxyd_userid,
name);
/* not successfully selected */
backend_current = NULL;
break;
}
}
/*
* Perform a CLOSE command
*/
void cmd_close(char *tag)
{
assert(backend_current != NULL);
prot_printf(backend_current->out, "%s Close\r\n", tag);
/* xxx do we want this to say OK if the connection is gone?
* saying NO is clearly wrong, hense the fatal request. */
pipe_including_tag(backend_current, tag, 0);
backend_current = NULL;
}
/*
* Perform an UNSELECT command -- for some support of IMAP proxy.
* Just like close except no expunge.
*/
void cmd_unselect(char *tag)
{
assert(backend_current != NULL);
prot_printf(backend_current->out, "%s Unselect\r\n", tag);
/* xxx do we want this to say OK if the connection is gone?
* saying NO is clearly wrong, hense the fatal request. */
pipe_including_tag(backend_current, tag, 0);
backend_current = NULL;
}
/*
* Parse and perform a FETCH/UID FETCH command
* The command has been parsed up to and including
* the sequence
*/
void cmd_fetch(char *tag, char *sequence, int usinguid)
{
char *cmd = usinguid ? "UID Fetch" : "Fetch";
assert(backend_current != NULL);
prot_printf(backend_current->out, "%s %s %s ", tag, cmd, sequence);
if (!pipe_command(backend_current, 65536)) {
pipe_including_tag(backend_current, tag, 0);
}
}
/*
* Perform a PARTIAL command
*/
void cmd_partial(char *tag, char *msgno, char *data, char *start, char *count)
{
assert(backend_current != NULL);
prot_printf(backend_current->out, "%s Partial %s %s %s %s\r\n",
tag, msgno, data, start, count);
pipe_including_tag(backend_current, tag, 0);
}
/*
* Parse and perform a STORE/UID STORE command
* The command has been parsed up to and including
* the FLAGS/+FLAGS/-FLAGS
*/
void cmd_store(char *tag, char *sequence, char *operation, int usinguid)
{
const char *cmd = usinguid ? "UID Store" : "Store";
assert(backend_current != NULL);
prot_printf(backend_current->out, "%s %s %s %s ",
tag, cmd, sequence, operation);
if (!pipe_command(backend_current, 65536)) {
pipe_including_tag(backend_current, tag, 0);
}
}
void cmd_search(char *tag, int usinguid)
{
const char *cmd = usinguid ? "UID Search" : "Search";
assert(backend_current != NULL);
prot_printf(backend_current->out, "%s %s ", tag, cmd);
if (!pipe_command(backend_current, 65536)) {
pipe_including_tag(backend_current, tag, 0);
}
}
void cmd_sort(char *tag, int usinguid)
{
char *cmd = usinguid ? "UID Sort" : "Sort";
assert(backend_current != NULL);
prot_printf(backend_current->out, "%s %s ", tag, cmd);
if (!pipe_command(backend_current, 65536)) {
pipe_including_tag(backend_current, tag, 0);
}
}
void cmd_thread(char *tag, int usinguid)
{
char *cmd = usinguid ? "UID Thread" : "Thread";
assert(backend_current != NULL);
prot_printf(backend_current->out, "%s %s ", tag, cmd);
if (!pipe_command(backend_current, 65536)) {
pipe_including_tag(backend_current, tag, 0);
}
}
static int chomp(struct protstream *p, 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;
}
/* 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;
}
/*
* Perform a COPY/UID COPY command
*/
void cmd_copy(char *tag, char *sequence, char *name, int usinguid)
{
char *server;
char *cmd = usinguid ? "UID Copy" : "Copy";
struct backend *s = NULL;
char mailboxname[MAX_MAILBOX_NAME+1];
int r;
assert(backend_current != NULL);
r = (*proxyd_namespace.mboxname_tointernal)(&proxyd_namespace, name,
proxyd_userid, mailboxname);
if (!r) r = mlookup(mailboxname, &server, NULL, NULL);
if (!r) s = proxyd_findserver(server);
if (!s) {
/* no such mailbox or other problem */
r = mboxlist_createmailboxcheck(mailboxname, 0, 0, proxyd_userisadmin,
proxyd_userid, proxyd_authstate,
NULL, NULL);
if(!r && server) {
char *c;
c = strchr(server, '!');
if(c) *c = '\0';
}
prot_printf(proxyd_out, "%s NO %s%s\r\n", tag,
r == 0 ? "[TRYCREATE] " : "", error_message(r));
} else if (s == backend_current) {
/* this is the easy case */
prot_printf(backend_current->out, "%s %s %s {%d+}\r\n%s\r\n",
tag, cmd, sequence, strlen(name), name);
pipe_including_tag(backend_current, tag, 0);
} else {
char mytag[128];
struct d {
char *idate;
char *flags;
- int seqno, uid;
+ unsigned int seqno, uid;
struct d *next;
} *head, *p, *q;
int c;
/* this is the hard case; we have to fetch the messages and append
them to the other mailbox */
/* find out what the flags & internaldate for this message are */
proxyd_gentag(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 */;;) {
- int seqno = 0, uidno = 0;
+ 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; }
/* read seqno */
seqno = 0;
while (isdigit(c = prot_getc(backend_current->in))) {
seqno *= 10;
seqno += c - '0';
}
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(proxyd_out, "* %d EXISTS\r\n", seqno);
continue;
}
}
if (c == EOF) {
c = chomp(backend_current->in, "recent\r");
if (c == '\n') { /* got RECENT response */
prot_printf(proxyd_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 {
uidno = 0;
while (isdigit(c = prot_getc(backend_current->in))) {
uidno *= 10;
uidno += c - '0';
}
}
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; break; }
/* if we're missing something, we should echo */
if (!flags || !idate) {
char sep = '(';
prot_printf(proxyd_out, "* %d FETCH ", seqno);
if (uidno) {
prot_printf(proxyd_out, "%cUID %d", sep, uidno);
sep = ' ';
}
if (flags) {
prot_printf(proxyd_out, "%cFLAGS %s", sep, flags);
sep = ' ';
}
if (idate) {
prot_printf(proxyd_out, "%cINTERNALDATE %s", sep, flags);
sep = ' ';
}
prot_printf(proxyd_out, ")\r\n");
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 %s", tag, name);
prot_printf(backend_current->out, "%s %s %s (Rfc822.peek)\r\n",
mytag, usinguid ? "Uid Fetch" : "Fetch", sequence);
for (/* each FETCH response */;;) {
- int seqno = 0, uidno = 0;
+ 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; }
/* read seqno */
seqno = 0;
while (isdigit(c = prot_getc(backend_current->in))) {
seqno *= 10;
seqno += c - '0';
}
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(proxyd_out, "* %d EXISTS\r\n", seqno);
continue;
}
}
if (c == EOF) {
c = chomp(backend_current->in, "recent\r");
if (c == '\n') { /* got RECENT response */
prot_printf(proxyd_out, "* %d RECENT\r\n", seqno);
continue;
}
}
/* huh, don't get this response */
if (c == EOF) 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 {
uidno = 0;
while (isdigit(c = prot_getc(backend_current->in))) {
uidno *= 10;
uidno += c - '0';
}
}
break;
case 'r': case 'R':
c = chomp(backend_current->in, "fc822");
if (c == ' ') c = prot_getc(backend_current->in);
if (c != '{') c = EOF;
else {
sz = 0;
while (isdigit(c = prot_getc(backend_current->in))) {
sz *= 10;
sz += c - '0';
+ /* xxx overflow */
}
}
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 > sizeof(buf) ? 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) {
appenduid = strchr(s->last_result, '[');
/* skip over APPENDUID */
appenduid += strlen("[appenduid ");
b = strchr(appenduid, ']');
*b = '\0';
prot_printf(proxyd_out, "%s OK [COPYUID %s] %s\r\n", tag,
appenduid, error_message(IMAP_OK_COMPLETED));
} else {
prot_printf(proxyd_out, "%s %s", tag, s->last_result);
}
} else {
/* abort the append */
prot_printf(s->out, " {0}\r\n");
pipe_until_tag(backend_current, mytag, 0);
pipe_until_tag(s, tag, 0);
/* report failure */
prot_printf(proxyd_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);
}
}
}
/*
* Perform an EXPUNGE command
* sequence == NULL if this isn't a UID EXPUNGE
*/
void cmd_expunge(char *tag, char *sequence)
{
assert(backend_current != NULL);
if (sequence) {
prot_printf(backend_current->out, "%s UID Expunge %s\r\n", tag,
sequence);
} else {
prot_printf(backend_current->out, "%s Expunge\r\n", tag);
}
pipe_including_tag(backend_current, tag, 0);
}
/*
* Perform a CREATE command
*/
void cmd_create(char *tag, char *name, char *server)
{
struct backend *s = NULL;
char mailboxname[MAX_MAILBOX_NAME+1];
int r = 0, res;
char *acl = NULL;
if (server && !proxyd_userisadmin) {
r = IMAP_PERMISSION_DENIED;
}
if (name[0] && name[strlen(name)-1] == proxyd_namespace.hier_sep) {
/* We don't care about trailing hierarchy delimiters. */
name[strlen(name)-1] = '\0';
}
if (!r)
r = (*proxyd_namespace.mboxname_tointernal)(&proxyd_namespace, name,
proxyd_userid, mailboxname);
if (!r && !server) {
r = mboxlist_createmailboxcheck(mailboxname, 0, 0, proxyd_userisadmin,
proxyd_userid, proxyd_authstate,
&acl, &server);
if(!r && server) {
char *c;
c = strchr(server, '!');
if(c) *c = '\0';
}
}
if (!r && server) {
s = proxyd_findserver(server);
if (!s) r = IMAP_SERVER_UNAVAILABLE;
}
if (!r) {
if (!CAPA(s, MUPDATE)) {
#if 0
acapmbox_handle_t *acaphandle = acapmbox_get_handle();
/* reserve mailbox on ACAP */
acapmbox_new(&mboxdata, s->hostname, mailboxname);
r = acapmbox_create(acaphandle, &mboxdata);
if (r) {
syslog(LOG_ERR, "ACAP: unable to reserve %s: %s\n",
name, error_message(r));
}
#endif
}
}
if (!r) {
/* ok, send the create to that server */
prot_printf(s->out, "%s CREATE {%d+}\r\n%s\r\n",
tag, strlen(name), name);
res = pipe_including_tag(s, tag, 0);
tag = "*"; /* can't send another tagged response */
if (!CAPA(s, MUPDATE)) {
#if 0
acapmbox_handle_t *acaphandle = acapmbox_get_handle();
switch (res) {
case PROXY_OK:
/* race condition here, but not much we can do about it */
mboxdata.acl = acl;
/* commit mailbox */
r = acapmbox_markactive(acaphandle, &mboxdata);
if (r) {
syslog(LOG_ERR, "ACAP: unable to commit %s: %s\n",
mailboxname, error_message(r));
}
break;
default:
/* abort mailbox */
r = acapmbox_delete(acaphandle, mailboxname);
if (r) {
syslog(LOG_ERR, "ACAP: unable to unreserve %s: %s\n",
mailboxname, error_message(r));
}
break;
}
#endif
}
/* make sure we've seen the update */
if (ultraparanoid && res == PROXY_OK) kick_mupdate();
}
if (r) prot_printf(proxyd_out, "%s NO %s\r\n", tag, error_message(r));
}
/*
* Perform a DELETE command
*/
void cmd_delete(char *tag, char *name)
{
int r, res;
char *server;
struct backend *s = NULL;
char mailboxname[MAX_MAILBOX_NAME+1];
r = (*proxyd_namespace.mboxname_tointernal)(&proxyd_namespace, name,
proxyd_userid, mailboxname);
if (!r) r = mlookup(mailboxname, &server, NULL, NULL);
if (!r && supports_referrals) {
proxyd_refer(tag, server, mailboxname);
referral_kick = 1;
return;
}
if (!r) {
s = proxyd_findserver(server);
if (!s) r = IMAP_SERVER_UNAVAILABLE;
}
if (!r) {
prot_printf(s->out, "%s DELETE {%d+}\r\n%s\r\n",
tag, strlen(name), name);
res = pipe_including_tag(s, tag, 0);
tag = "*"; /* can't send another tagged response */
if (!CAPA(s, MUPDATE) && res == PROXY_OK) {
#if 0
acapmbox_handle_t *acaphandle = acapmbox_get_handle();
/* delete mailbox from acap server */
r = acapmbox_delete(acaphandle, mailboxname);
if (r) {
syslog(LOG_ERR,
"ACAP: can't delete mailbox entry %s: %s",
name, error_message(r));
}
#endif
}
/* make sure we've seen the update */
if (ultraparanoid && res == PROXY_OK) kick_mupdate();
}
if (r) prot_printf(proxyd_out, "%s NO %s\r\n", tag, error_message(r));
}
/*
* Perform a RECONSTRUCT command
*/
void cmd_reconstruct(char *tag, char *name)
{
int r = 0;
char mailboxname[MAX_MAILBOX_NAME+1];
char *server = NULL;
if(!proxyd_userisadmin) r = IMAP_PERMISSION_DENIED;
else {
r = (*proxyd_namespace.mboxname_tointernal)(&proxyd_namespace,
name,
proxyd_userid,
mailboxname);
}
if(!r)
r = mlookup(mailboxname, &server, NULL, NULL);
if(!r) {
proxyd_refer(tag, server, mailboxname);
} else {
prot_printf(proxyd_out, "%s NO %s\r\n", tag, error_message(r));
}
}
/*
* Perform a RENAME command
*/
void cmd_rename(char *tag, char *oldname, char *newname, char *partition)
{
int r = 0, res;
char *server;
char oldmailboxname[MAX_MAILBOX_NAME+1];
char newmailboxname[MAX_MAILBOX_NAME+1];
struct backend *s = NULL;
char *acl = NULL;
r = (*proxyd_namespace.mboxname_tointernal)(&proxyd_namespace, oldname,
proxyd_userid, oldmailboxname);
if (!r) (*proxyd_namespace.mboxname_tointernal)(&proxyd_namespace, newname,
proxyd_userid, newmailboxname);
if (!r) r = mlookup(oldmailboxname, &server, &acl, NULL);
if (!r) {
s = proxyd_findserver(server);
if (!s) r = IMAP_SERVER_UNAVAILABLE;
}
/* Cross Server Rename */
if (!r && partition) {
char *destpart;
if(strcmp(oldname, newname)) {
prot_printf(s->out,
"%s NO Cross-server or cross-partition move w/rename not supported\r\n",
tag);
return;
}
/* dest partition? */
destpart = strchr(partition,'!');
if(destpart) {
strcpy(newmailboxname,partition);
newmailboxname[destpart-partition]='\0';
destpart++;
if(!strcmp(server, newmailboxname)) {
/* Same Server, different partition */
/* xxx this would require administrative access to the
* backend, which we won't get */
prot_printf(proxyd_out,
"%s NO Can't move across partitions via a proxy\r\n",
tag);
return;
} else {
/* Cross Server */
/* <tag> XFER <name> <dest server> <dest partition> */
prot_printf(s->out,
"%s XFER {%d+}\r\n%s {%d+}\r\n%s {%d+}\r\n%s\r\n",
tag, strlen(oldname), oldname,
strlen(newmailboxname), newmailboxname,
strlen(destpart), destpart);
}
} else {
/* <tag> XFER <name> <dest server> */
prot_printf(s->out, "%s XFER {%d+}\r\n%s {%d+}\r\n%s\r\n",
tag, strlen(oldname), oldname,
strlen(partition), partition);
}
res = pipe_including_tag(s, tag, 0);
/* make sure we've seen the update */
if (ultraparanoid && res == PROXY_OK) kick_mupdate();
return;
}
if (!r) {
if (!CAPA(s, MUPDATE)) {
#if 0
acapmbox_handle_t *acaphandle = acapmbox_get_handle();
/* reserve new mailbox */
acapmbox_new(&mboxdata, s->hostname, newmailboxname);
r = acapmbox_create(acaphandle, &mboxdata);
if (r) {
syslog(LOG_ERR, "ACAP: unable to reserve %s: %s\n",
newmailboxname, error_message(r));
}
#endif
}
prot_printf(s->out, "%s RENAME {%d+}\r\n%s {%d+}\r\n%s\r\n",
tag, strlen(oldname), oldname,
strlen(newname), newname);
res = pipe_including_tag(s, tag, 0);
tag = "*"; /* can't send another tagged response */
if (!CAPA(s, MUPDATE)) {
#if 0
acapmbox_handle_t *acaphandle = acapmbox_get_handle();
switch (res) {
case PROXY_OK:
/* commit new mailbox */
mboxdata.acl = acl;
r = acapmbox_markactive(acaphandle, &mboxdata);
if (r) {
syslog(LOG_ERR, "ACAP: unable to commit %s: %s\n",
oldmailboxname, error_message(r));
}
/* delete old mailbox */
r = acapmbox_delete(acaphandle, oldmailboxname);
if (r) {
syslog(LOG_ERR, "ACAP: unable to delete %s: %s\n",
oldmailboxname, error_message(r));
}
break;
default:
/* abort new mailbox */
r = acapmbox_delete(acaphandle, newmailboxname);
if (r) {
syslog(LOG_ERR, "ACAP: unable to unreserve %s: %s\n",
newmailboxname, error_message(r));
}
break;
}
#endif
}
/* make sure we've seen the update */
if (res == PROXY_OK) kick_mupdate();
}
if (r) prot_printf(proxyd_out, "%s NO %s\r\n", tag, error_message(r));
}
/*
* Perform a FIND command
*/
void cmd_find(char *tag, char *namespace, char *pattern)
{
char *p;
lcase(namespace);
for (p = pattern; *p; p++) {
if (*p == '%') *p = '?';
}
/* Translate any separators in pattern */
mboxname_hiersep_tointernal(&proxyd_namespace, pattern);
if (!strcasecmp(namespace, "mailboxes")) {
if (!backend_inbox) {
backend_inbox = proxyd_findinboxserver();
}
if (backend_inbox) {
prot_printf(backend_inbox->out,
"%s Lsub \"\" {%d+}\r\n%s\r\n",
tag, strlen(pattern), pattern);
pipe_lsub(backend_inbox, tag, 0, 1);
} else { /* user doesn't have an INBOX */
/* noop */
}
} else if (!strcasecmp(namespace, "all.mailboxes")) {
(*proxyd_namespace.mboxlist_findall)(&proxyd_namespace, pattern,
proxyd_userisadmin, proxyd_userid,
proxyd_authstate, mailboxdata,
NULL);
} else if (!strcasecmp(namespace, "bboards")
|| !strcasecmp(namespace, "all.bboards")) {
;
} else {
prot_printf(proxyd_out, "%s BAD Invalid FIND subcommand\r\n", tag);
return;
}
if (backend_current) {
char mytag[128];
proxyd_gentag(mytag);
prot_printf(backend_current->out, "%s Noop\r\n", mytag);
pipe_until_tag(backend_current, mytag, 0);
}
prot_printf(proxyd_out, "%s OK %s\r\n", tag,
error_message(IMAP_OK_COMPLETED));
}
/*
* Perform a LIST or LSUB command
* LISTs we do locally
* LSUBs we farm out
*/
void cmd_list(char *tag, int subscribed, char *reference, char *pattern)
{
char *buf = NULL;
int patlen = 0;
int reflen = 0;
static int ignorereference = -1;
/* Ignore the reference argument?
(the behavior in 1.5.10 & older) */
if (ignorereference == -1) {
ignorereference = config_getswitch("ignorereference", 0);
}
/* Reset state in mstringdata */
mstringdata(NULL, NULL, 0, 0);
if (!pattern[0] && !subscribed) {
/* Special case: query top-level hierarchy separator */
prot_printf(proxyd_out, "* LIST (\\Noselect) \"%c\" \"\"\r\n",
proxyd_namespace.hier_sep);
} else if (subscribed) { /* do an LSUB command; contact our INBOX */
if (!backend_inbox) {
backend_inbox = proxyd_findinboxserver();
}
if (backend_inbox) {
prot_printf(backend_inbox->out,
"%s Lsub {%d+}\r\n%s {%d+}\r\n%s\r\n",
tag, strlen(reference), reference,
strlen(pattern), pattern);
pipe_lsub(backend_inbox, tag, 0, 0);
} else { /* user doesn't have an INBOX */
/* noop */
}
} else { /* do a LIST locally */
/* Do we need to concatenate fields? */
if (!ignorereference || pattern[0] == proxyd_namespace.hier_sep) {
/* Either
* - name begins with dot
* - we're configured to honor the reference argument */
/* Allocate a buffer, figure out how to stick the arguments
together, do it, then do that instead of using pattern. */
patlen = strlen(pattern);
reflen = strlen(reference);
buf = xmalloc(patlen + reflen + 1);
buf[0] = '\0';
if (*reference) {
/* check for LIST A. .B, change to LIST "" A.B */
if (reference[reflen-1] == proxyd_namespace.hier_sep &&
pattern[0] == proxyd_namespace.hier_sep) {
reference[--reflen] = '\0';
}
strcpy(buf, reference);
}
strcat(buf, pattern);
pattern = buf;
}
/* Translate any separators in pattern */
mboxname_hiersep_tointernal(&proxyd_namespace, pattern);
(*proxyd_namespace.mboxlist_findall)(&proxyd_namespace, pattern,
proxyd_userisadmin, proxyd_userid,
proxyd_authstate, listdata, NULL);
listdata((char *)0, 0, 0, 0);
if (buf) free(buf);
}
if (backend_current && (!subscribed || backend_current != backend_inbox)) {
/* our Lsub would've done this if
backend_current == backend_inbox */
char mytag[128];
proxyd_gentag(mytag);
prot_printf(backend_current->out, "%s Noop\r\n", mytag);
pipe_until_tag(backend_current, mytag, 0);
}
prot_printf(proxyd_out, "%s OK %s\r\n", tag,
error_message(IMAP_OK_COMPLETED));
}
/*
* Perform a SUBSCRIBE (add is nonzero) or
* UNSUBSCRIBE (add is zero) command
*/
void cmd_changesub(char *tag, char *namespace, char *name, int add)
{
char *cmd = add ? "Subscribe" : "Unsubscribe";
int r = 0;
if (!backend_inbox) {
backend_inbox = proxyd_findinboxserver();
}
if (backend_inbox) {
char mailboxname[MAX_MAILBOX_NAME+1];
if (add) {
r = (*proxyd_namespace.mboxname_tointernal)(&proxyd_namespace,
name, proxyd_userid,
mailboxname);
if(!r) r = mlookup(mailboxname, NULL, NULL, NULL);
/* Doesn't exist on murder */
if(r) goto done;
}
if (namespace) {
prot_printf(backend_inbox->out,
"%s %s {%d+}\r\n%s {%d+}\r\n%s\r\n",
tag, cmd,
strlen(namespace), namespace,
strlen(name), name);
} else {
prot_printf(backend_inbox->out, "%s %s {%d+}\r\n%s\r\n",
tag, cmd,
strlen(name), name);
}
pipe_including_tag(backend_inbox, tag, 0);
} else {
r = IMAP_SERVER_UNAVAILABLE;
}
done:
if(r) {
prot_printf(proxyd_out, "%s NO %s: %s\r\n", tag,
add ? "Subscribe" : "Unsubscribe", error_message(r));
}
}
/*
* Perform a GETACL command
*/
void cmd_getacl(char *tag, char *name, int oldform)
{
char mailboxname[MAX_MAILBOX_NAME+1];
int r, access;
char *acl;
char *rights, *nextid;
r = (*proxyd_namespace.mboxname_tointernal)(&proxyd_namespace, name,
proxyd_userid, mailboxname);
if (!r) r = mlookup(mailboxname, NULL, &acl, NULL);
if (!r) {
access = cyrus_acl_myrights(proxyd_authstate, acl);
if (!(access & (ACL_READ|ACL_ADMIN)) &&
!proxyd_userisadmin &&
!mboxname_userownsmailbox(proxyd_userid, mailboxname)) {
r = (access & ACL_LOOKUP) ?
IMAP_PERMISSION_DENIED : IMAP_MAILBOX_NONEXISTENT;
}
}
if (r) {
prot_printf(proxyd_out, "%s NO %s\r\n", tag, error_message(r));
return;
}
if (oldform) {
while (acl) {
rights = strchr(acl, '\t');
if (!rights) break;
*rights++ = '\0';
nextid = strchr(rights, '\t');
if (!nextid) break;
*nextid++ = '\0';
prot_printf(proxyd_out, "* ACL MAILBOX ");
printastring(name);
prot_printf(proxyd_out, " ");
printastring(acl);
prot_printf(proxyd_out, " ");
printastring(rights);
prot_printf(proxyd_out, "\r\n");
acl = nextid;
}
}
else {
prot_printf(proxyd_out, "* ACL ");
printastring(name);
while (acl) {
rights = strchr(acl, '\t');
if (!rights) break;
*rights++ = '\0';
nextid = strchr(rights, '\t');
if (!nextid) break;
*nextid++ = '\0';
prot_printf(proxyd_out, " ");
printastring(acl);
prot_printf(proxyd_out, " ");
printastring(rights);
acl = nextid;
}
prot_printf(proxyd_out, "\r\n");
}
prot_printf(proxyd_out, "%s OK %s\r\n", tag,
error_message(IMAP_OK_COMPLETED));
}
/*
* Perform a LISTRIGHTS command
*/
void cmd_listrights(char *tag, char *name, char *identifier)
{
char mailboxname[MAX_MAILBOX_NAME+1];
int r, rights;
char *canon_identifier;
int canonidlen = 0;
char *acl;
char *rightsdesc;
r = (*proxyd_namespace.mboxname_tointernal)(&proxyd_namespace, name,
proxyd_userid, mailboxname);
if (!r) {
r = mlookup(mailboxname, (char **)0, &acl, NULL);
}
if (!r) {
rights = cyrus_acl_myrights(proxyd_authstate, acl);
if (!rights && !proxyd_userisadmin &&
!mboxname_userownsmailbox(proxyd_userid, mailboxname)) {
r = IMAP_MAILBOX_NONEXISTENT;
}
}
if (!r) {
canon_identifier = auth_canonifyid(identifier, 0);
if (canon_identifier) canonidlen = strlen(canon_identifier);
if (!canon_identifier) {
rightsdesc = "\"\"";
}
else if (!strncmp(mailboxname, "user.", 5) &&
!strchr(canon_identifier, '.') &&
!strncmp(mailboxname+5, canon_identifier, canonidlen) &&
(mailboxname[5+canonidlen] == '\0' ||
mailboxname[5+canonidlen] == '.')) {
rightsdesc = "lca r s w i p d 0 1 2 3 4 5 6 7 8 9";
}
else {
rightsdesc = "\"\" l r s w i p c d a 0 1 2 3 4 5 6 7 8 9";
}
prot_printf(proxyd_out, "* LISTRIGHTS ");
printastring(name);
prot_putc(' ', proxyd_out);
printastring(identifier);
prot_printf(proxyd_out, " %s", rightsdesc);
prot_printf(proxyd_out, "\r\n%s OK %s\r\n", tag,
error_message(IMAP_OK_COMPLETED));
return;
}
prot_printf(proxyd_out, "%s NO %s\r\n", tag, error_message(r));
}
/*
* Perform a MYRIGHTS command
*/
void cmd_myrights(char *tag, char *name, int oldform)
{
char mailboxname[MAX_MAILBOX_NAME+1];
int r, rights = 0;
char *acl;
char str[ACL_MAXSTR];
r = (*proxyd_namespace.mboxname_tointernal)(&proxyd_namespace, name,
proxyd_userid, mailboxname);
if (!r) {
r = mlookup(mailboxname, (char **)0, &acl, NULL);
}
if (!r) {
rights = cyrus_acl_myrights(proxyd_authstate, acl);
/* Add in implicit rights */
if (proxyd_userisadmin ||
mboxname_userownsmailbox(proxyd_userid, mailboxname)) {
rights |= ACL_LOOKUP|ACL_ADMIN;
}
if (!rights) {
r = IMAP_MAILBOX_NONEXISTENT;
}
}
if (r) {
prot_printf(proxyd_out, "%s NO %s\r\n", tag, error_message(r));
return;
}
prot_printf(proxyd_out, "* MYRIGHTS ");
if (oldform) prot_printf(proxyd_out, "MAILBOX ");
printastring(name);
prot_printf(proxyd_out, " ");
printastring(cyrus_acl_masktostr(rights, str));
prot_printf(proxyd_out, "\r\n%s OK %s\r\n", tag,
error_message(IMAP_OK_COMPLETED));
}
/*
* Perform a SETACL command
*/
void cmd_setacl(char *tag, char *name, char *identifier, char *rights)
{
int r, res;
char mailboxname[MAX_MAILBOX_NAME+1];
char *server;
struct backend *s = NULL;
char *acl = NULL;
r = (*proxyd_namespace.mboxname_tointernal)(&proxyd_namespace, name,
proxyd_userid, mailboxname);
if (!r) r = mlookup(mailboxname, &server, &acl, NULL);
if (!r) {
s = proxyd_findserver(server);
if (!s) r = IMAP_SERVER_UNAVAILABLE;
}
if (!r && proxyd_userisadmin && supports_referrals) {
/* They aren't an admin remotely, so let's refer them */
proxyd_refer(tag, server, name);
referral_kick = 1;
return;
} else if (!r) {
if (rights) {
prot_printf(s->out,
"%s Setacl {%d+}\r\n%s {%d+}\r\n%s {%d+}\r\n%s\r\n",
tag, strlen(name), name,
strlen(identifier), identifier,
strlen(rights), rights);
} else {
prot_printf(s->out,
"%s Deleteacl {%d+}\r\n%s {%d+}\r\n%s\r\n",
tag, strlen(name), name,
strlen(identifier), identifier);
}
res = pipe_including_tag(s, tag, 0);
tag = "*"; /* can't send another tagged response */
if (!CAPA(s, MUPDATE) && res == PROXY_OK) {
#if 0
acapmbox_handle_t *acaphandle = acapmbox_get_handle();
int mode;
/* calculate new ACL; race conditions here */
if (rights) {
mode = ACL_MODE_SET;
if (*rights == '+') {
rights++;
mode = ACL_MODE_ADD;
} else if (*rights == '-') {
rights++;
mode = ACL_MODE_REMOVE;
}
if (cyrus_acl_set(&acl, identifier, mode, cyrus_acl_strtomask(rights),
NULL, proxyd_userid)) {
r = IMAP_INVALID_IDENTIFIER;
}
} else {
if (cyrus_acl_remove(&acl, identifier, NULL, proxyd_userid)) {
r = IMAP_INVALID_IDENTIFIER;
}
}
/* change the ACAP server */
r = acapmbox_setproperty_acl(acaphandle, mailboxname, acl);
if (r) {
syslog(LOG_ERR, "ACAP: unable to change ACL on %s: %s\n",
mailboxname, error_message(r));
}
#endif
}
/* make sure we've seen the update */
if (ultraparanoid && res == PROXY_OK) kick_mupdate();
}
if (r) prot_printf(proxyd_out, "%s NO %s\r\n", tag, error_message(r));
}
/*
* Callback for (get|set)quota, to ensure that all of the
* submailboxes are on the same server.
*/
static int quota_cb(char *name, int matchlen, int maycreate, void *rock)
{
int r;
char *this_server;
const char *servername = (const char *)rock;
r = mlookup(name, &this_server, NULL, NULL);
if(r) return r;
if(strcmp(servername, this_server)) {
/* Not on same server as the root */
return IMAP_NOT_SINGULAR_ROOT;
} else {
return PROXY_OK;
}
}
/*
* Perform a GETQUOTA command
*/
void cmd_getquota(char *tag, char *name)
{
int r;
char *server_rock = NULL, *server_rock_tmp = NULL;
char mailboxname[MAX_MAILBOX_NAME+1];
char quotarootbuf[MAX_MAILBOX_NAME + 3];
if(!proxyd_userisadmin) r = IMAP_PERMISSION_DENIED;
else {
r = (*proxyd_namespace.mboxname_tointernal)(&proxyd_namespace,
name,
proxyd_userid,
mailboxname);
}
if(!r)
r = mlookup(mailboxname, &server_rock_tmp, NULL, NULL);
if(!r) {
server_rock = xstrdup(server_rock_tmp);
snprintf(quotarootbuf, sizeof(quotarootbuf), "%s.*", mailboxname);
r = mboxlist_findall(&proxyd_namespace, quotarootbuf,
proxyd_userisadmin, proxyd_userid,
proxyd_authstate, quota_cb, server_rock);
}
if (!r) {
/* Do the referral */
proxyd_refer(tag, server_rock, mailboxname);
free(server_rock);
} else {
if(server_rock) free(server_rock);
prot_printf(proxyd_out, "%s NO %s\r\n", tag, error_message(r));
}
}
/*
* Perform a GETQUOTAROOT command
*/
void cmd_getquotaroot(char *tag, char *name)
{
char mailboxname[MAX_MAILBOX_NAME+1];
char *server;
int r;
struct backend *s = NULL;
r = (*proxyd_namespace.mboxname_tointernal)(&proxyd_namespace, name,
proxyd_userid, mailboxname);
if (!r) r = mlookup(mailboxname, &server, NULL, NULL);
if(proxyd_userisadmin && supports_referrals) {
/* If they are an admin, they won't be on the backend, so we
* should refer them if we can. */
proxyd_refer(tag, server, name);
} else {
if (!r) s = proxyd_findserver(server);
if (s) {
prot_printf(s->out, "%s Getquotaroot {%d+}\r\n%s\r\n",
tag, strlen(name), name);
pipe_including_tag(s, tag, 0);
} else {
r = IMAP_SERVER_UNAVAILABLE;
}
if (r) {
prot_printf(proxyd_out, "%s NO %s\r\n", tag, error_message(r));
return;
}
}
}
/*
* Parse and perform a SETQUOTA command
* The command has been parsed up to the resource list
*/
void cmd_setquota(char *tag, char *quotaroot)
{
int r;
char c;
char *p;
static struct buf arg;
int badresource = 0;
char *server_rock = NULL, *server_rock_tmp = NULL;
char mailboxname[MAX_MAILBOX_NAME+1];
char quotarootbuf[MAX_MAILBOX_NAME + 3];
/* First ensure the validity of the command */
c = prot_getc(proxyd_in);
if (c != '(') goto badlist;
/* xxx maybe we don't want to be this stringant on what types
* of quota we allow to be set, since we will just be doing a referral
* anyway... */
c = getword(proxyd_in, &arg);
if (c != ')' || arg.s[0] != '\0') {
for (;;) {
if (c != ' ') goto badlist;
if (strcasecmp(arg.s, "storage") != 0) badresource = 1;
c = getword(proxyd_in, &arg);
if (c != ' ' && c != ')') goto badlist;
if (arg.s[0] == '\0') goto badlist;
/* We are just syntax checking here, no need to save the value */
for (p = arg.s; *p; p++) {
if (!isdigit((int) *p)) goto badlist;
}
if (c == ')') break;
}
}
c = prot_getc(proxyd_in);
if (c == '\r') c = prot_getc(proxyd_in);
if (c != '\n') {
prot_printf(proxyd_out,
"%s BAD Unexpected extra arguments to SETQUOTA\r\n", tag);
eatline(proxyd_in, c);
return;
}
if(badresource) r = IMAP_UNSUPPORTED_QUOTA;
else if(!proxyd_userisadmin) r = IMAP_PERMISSION_DENIED;
else {
r = (*proxyd_namespace.mboxname_tointernal)(&proxyd_namespace,
quotaroot,
proxyd_userid,
mailboxname);
}
if(!r)
r = mlookup(mailboxname, &server_rock_tmp, NULL, NULL);
if(!r) {
server_rock = xstrdup(server_rock_tmp);
snprintf(quotarootbuf, sizeof(quotarootbuf), "%s.*", mailboxname);
r = mboxlist_findall(&proxyd_namespace, quotarootbuf,
proxyd_userisadmin, proxyd_userid,
proxyd_authstate, quota_cb, server_rock);
}
if (!r) {
/* Do the referral */
proxyd_refer(tag, server_rock, mailboxname);
free(server_rock);
} else {
if(server_rock) free(server_rock);
prot_printf(proxyd_out, "%s NO %s\r\n", tag, error_message(r));
}
return;
badlist:
prot_printf(proxyd_out, "%s BAD Invalid quota list in Setquota\r\n", tag);
eatline(proxyd_in, c);
}
#ifdef HAVE_SSL
/*
* this implements the STARTTLS command, as described in RFC 2595.
* one caveat: it assumes that no external layer is currently present.
* if a client executes this command, information about the external
* layer that was passed on the command line is disgarded. this should
* be fixed.
*/
/* imaps - weather this is an imaps transaction or not */
void cmd_starttls(char *tag, int imaps)
{
int result;
int *layerp;
sasl_ssf_t ssf;
char *auth_id;
/* SASL and openssl have different ideas about whether ssf is signed */
layerp = (int *) &(ssf);
if (proxyd_starttls_done == 1)
{
prot_printf(proxyd_out, "%s NO %s\r\n", tag,
"TLS already active");
return;
}
result=tls_init_serverengine("imap",
5, /* depth to verify */
!imaps, /* can client auth? */
!imaps); /* TLSv1 only? */
if (result == -1) {
syslog(LOG_ERR, "error initializing TLS");
if (imaps == 0)
prot_printf(proxyd_out, "%s NO %s\r\n",
tag, "Error initializing TLS");
else
fatal("tls_init() failed", EC_CONFIG);
return;
}
if (imaps == 0)
{
prot_printf(proxyd_out, "%s OK %s\r\n", tag,
"Begin TLS negotiation now");
/* must flush our buffers before starting tls */
prot_flush(proxyd_out);
}
result=tls_start_servertls(0, /* read */
1, /* write */
layerp,
&auth_id,
&tls_conn);
/* if error */
if (result==-1) {
if (imaps == 0) {
prot_printf(proxyd_out, "%s NO Starttls failed\r\n", tag);
syslog(LOG_NOTICE, "STARTTLS failed: %s", proxyd_clienthost);
return;
} else {
syslog(LOG_NOTICE, "imaps failed: %s", proxyd_clienthost);
fatal("tls_start_servertls() failed", EC_TEMPFAIL);
return;
}
}
/* tell SASL about the negotiated layer */
result = sasl_setprop(proxyd_saslconn, SASL_SSF_EXTERNAL, &ssf);
if (result != SASL_OK) {
fatal("sasl_setprop() failed: cmd_starttls()", EC_TEMPFAIL);
}
saslprops.ssf = ssf;
result = sasl_setprop(proxyd_saslconn, SASL_AUTH_EXTERNAL, auth_id);
if (result != SASL_OK) {
fatal("sasl_setprop() failed: cmd_starttls()", EC_TEMPFAIL);
}
if(saslprops.authid) {
free(saslprops.authid);
saslprops.authid = NULL;
}
if(auth_id)
saslprops.authid = xstrdup(auth_id);
/* tell the prot layer about our new layers */
prot_settls(proxyd_in, tls_conn);
prot_settls(proxyd_out, tls_conn);
proxyd_starttls_done = 1;
}
#else
void cmd_starttls(char *tag, int imaps)
{
fatal("cmd_starttls() executed, but starttls isn't implemented!",
EC_SOFTWARE);
}
#endif /* HAVE_SSL */
/*
* Parse and perform a STATUS command
* The command has been parsed up to the attribute list
*/
void cmd_status(char *tag, char *name)
{
char mailboxname[MAX_MAILBOX_NAME+1];
int r;
char *server;
struct backend *s = NULL;
r = (*proxyd_namespace.mboxname_tointernal)(&proxyd_namespace, name,
proxyd_userid, mailboxname);
if (!r) r = mlookup(mailboxname, &server, NULL, NULL);
if (!r && supports_referrals
&& config_getswitch("proxyd_allow_status_referral",0)) {
proxyd_refer(tag, server, mailboxname);
/* Eat the argument */
eatline(proxyd_in, prot_getc(proxyd_in));
return;
}
if (!r) s = proxyd_findserver(server);
if (!r && !s) r = IMAP_SERVER_UNAVAILABLE;
if (!r) {
prot_printf(s->out, "%s Status {%d+}\r\n%s ", tag,
strlen(name), name);
if (!pipe_command(s, 65536)) {
pipe_until_tag(s, tag, 0);
}
if (backend_current && s != backend_current) {
char mytag[128];
proxyd_gentag(mytag);
prot_printf(backend_current->out, "%s Noop\r\n", mytag);
pipe_until_tag(backend_current, mytag, 0);
}
} else {
eatline(proxyd_in, prot_getc(proxyd_in));
}
if (!r) {
prot_printf(proxyd_out, "%s %s", tag, s->last_result);
} else {
prot_printf(proxyd_out, "%s NO %s\r\n", tag, error_message(r));
}
}
#ifdef ENABLE_X_NETSCAPE_HACK
/*
* Reply to Netscape's crock with a crock of my own
*/
void
cmd_netscape(tag)
char *tag;
{
const char *url;
/* so tempting, and yet ... */
/* url = "http://random.yahoo.com/ryl/"; */
url = config_getstring("netscapeurl",
"http://andrew2.andrew.cmu.edu/cyrus/imapd/netscape-admin.html");
/* I only know of three things to reply with: */
prot_printf(proxyd_out,
"* OK [NETSCAPE] Carnegie Mellon Cyrus IMAP proxy\r\n* VERSION %s\r\n",
CYRUS_VERSION);
prot_printf(proxyd_out,
"* ACCOUNT-URL %s\r\n%s OK %s\r\n",
url, tag, error_message(IMAP_OK_COMPLETED));
/* no tagged response?!? */
}
#endif /* ENABLE_X_NETSCAPE_HACK */
/* Callback for cmd_namespace to be passed to mboxlist_findall.
* For each top-level mailbox found, print a bit of the response
* if it is a shared namespace. The rock is used as an integer in
* order to ensure the namespace response is correct on a server with
* no shared namespace.
*/
static int namespacedata(name, matchlen, maycreate, rock)
char* name;
int matchlen;
int maycreate;
void* rock;
{
int* sawone = (int*) rock;
if (!name) {
return 0;
}
if (!(strncmp(name, "INBOX.", 6))) {
/* The user has a "personal" namespace. */
sawone[NAMESPACE_INBOX] = 1;
} else if (!(strncmp(name, "user.", 5))) {
/* The user can see the "other users" namespace. */
sawone[NAMESPACE_USER] = 1;
} else {
/* The user can see the "shared" namespace. */
sawone[NAMESPACE_SHARED] = 1;
}
return 0;
}
/*
* Print out a response to the NAMESPACE command defined by
* RFC 2342.
*/
void cmd_namespace(tag)
char* tag;
{
int sawone[3] = {0, 0, 0};
char pattern[2] = {'%','\0'};
/* now find all the exciting toplevel namespaces -
* we're using internal names here
*/
mboxlist_findall(NULL, pattern, proxyd_userisadmin, proxyd_userid,
proxyd_authstate, namespacedata, (void*) sawone);
prot_printf(proxyd_out, "* NAMESPACE");
if (sawone[NAMESPACE_INBOX]) {
prot_printf(proxyd_out, " ((\"%s\" \"%c\"))",
proxyd_namespace.prefix[NAMESPACE_INBOX],
proxyd_namespace.hier_sep);
} else {
prot_printf(proxyd_out, " NIL");
}
if (sawone[NAMESPACE_USER]) {
prot_printf(proxyd_out, " ((\"%s\" \"%c\"))",
proxyd_namespace.prefix[NAMESPACE_USER],
proxyd_namespace.hier_sep);
} else {
prot_printf(proxyd_out, " NIL");
}
if (sawone[NAMESPACE_SHARED]) {
prot_printf(proxyd_out, " ((\"%s\" \"%c\"))",
proxyd_namespace.prefix[NAMESPACE_SHARED],
proxyd_namespace.hier_sep);
} else {
prot_printf(proxyd_out, " NIL");
}
prot_printf(proxyd_out, "\r\n");
prot_printf(proxyd_out, "%s OK %s\r\n", tag,
error_message(IMAP_OK_COMPLETED));
}
/*
* Print 's' as a quoted-string or literal (but not an atom)
*/
void
printstring(s)
const char *s;
{
const char *p;
int len = 0;
/* Look for any non-QCHAR characters */
for (p = s; *p && len < 1024; p++) {
len++;
if (*p & 0x80 || *p == '\r' || *p == '\n'
|| *p == '\"' || *p == '%' || *p == '\\') break;
}
/* if it's too long, literal it */
if (*p || len >= 1024) {
prot_printf(proxyd_out, "{%u}\r\n%s", strlen(s), s);
} else {
prot_printf(proxyd_out, "\"%s\"", s);
}
}
/*
* Print 's' as an atom, quoted-string, or literal
*/
void printastring(const char *s)
{
const char *p;
int len = 0;
if (imparse_isatom(s)) {
prot_printf(proxyd_out, "%s", s);
return;
}
/* Look for any non-QCHAR characters */
for (p = s; *p && len < 1024; p++) {
len++;
if (*p & 0x80 || *p == '\r' || *p == '\n'
|| *p == '\"' || *p == '%' || *p == '\\') break;
}
/* if it's too long, literal it */
if (*p || len >= 1024) {
prot_printf(proxyd_out, "{%u}\r\n%s", strlen(s), s);
} else {
prot_printf(proxyd_out, "\"%s\"", s);
}
}
/*
* Issue a MAILBOX untagged response
*/
static int mailboxdata(char *name,
int matchlen,
int maycreate,
void* rock)
{
char mboxname[MAX_MAILBOX_PATH+1];
(*proxyd_namespace.mboxname_toexternal)(&proxyd_namespace, name,
proxyd_userid, mboxname);
prot_printf(proxyd_out, "* MAILBOX %s\r\n", mboxname);
return 0;
}
/*
* Issue a LIST or LSUB untagged response
*/
static void mstringdata(cmd, name, matchlen, maycreate)
char *cmd;
char *name;
int matchlen;
int maycreate;
{
static char lastname[MAX_MAILBOX_PATH];
static int lastnamedelayed = 0;
static int lastnamenoinferiors = 0;
static int sawuser = 0;
int lastnamehassub = 0;
int c;
char mboxname[MAX_MAILBOX_PATH+1];
/* We have to reset the sawuser flag before each list command.
* Handle it as a dirty hack.
*/
if (cmd == NULL) {
sawuser = 0;
return;
}
if (lastnamedelayed) {
if (name && strncmp(lastname, name, strlen(lastname)) == 0 &&
name[strlen(lastname)] == '.') {
lastnamehassub = 1;
}
prot_printf(proxyd_out, "* %s (%s) \"%c\" ", cmd,
lastnamenoinferiors ? "\\Noinferiors" :
lastnamehassub ? "\\HasChildren" : "\\HasNoChildren",
proxyd_namespace.hier_sep);
(*proxyd_namespace.mboxname_toexternal)(&proxyd_namespace, lastname,
proxyd_userid, mboxname);
printstring(mboxname);
prot_printf(proxyd_out, "\r\n");
lastnamedelayed = lastnamenoinferiors = 0;
}
/* Special-case to flush any final state */
if (!name) {
lastname[0] = '\0';
return;
}
/* Suppress any output of a partial match */
if (name[matchlen] && strncmp(lastname, name, matchlen) == 0) {
return;
}
/*
* We can get a partial match for "user" multiple times with
* other matches inbetween. Handle it as a special case
*/
if (matchlen == 4 && strncasecmp(name, "user", 4) == 0) {
if (sawuser) return;
sawuser = 1;
}
strcpy(lastname, name);
lastname[matchlen] = '\0';
if (!name[matchlen]) {
lastnamedelayed = 1;
if (!maycreate) lastnamenoinferiors = 1;
return;
}
c = name[matchlen];
if (c) name[matchlen] = '\0';
prot_printf(proxyd_out, "* %s (%s) \"%c\" ", cmd,
c ? "\\HasChildren \\Noselect" : "",
proxyd_namespace.hier_sep);
(*proxyd_namespace.mboxname_toexternal)(&proxyd_namespace, name,
proxyd_userid, mboxname);
printstring(mboxname);
prot_printf(proxyd_out, "\r\n");
if (c) name[matchlen] = c;
return;
}
/*
* Issue a LIST untagged response
*/
static int listdata(char *name, int matchlen, int maycreate, void *rock)
{
mstringdata("LIST", name, matchlen, maycreate);
return 0;
}
#ifdef ENABLE_ANNOTATEMORE
/*
* Parse annotate fetch data.
*
* This is a generic routine which parses just the annotation data.
* Any surrounding command text must be parsed elsewhere, ie,
* GETANNOTATION, FETCH.
*/
int getannotatefetchdata(char *tag,
struct strlist **entries, struct strlist **attribs)
{
int c;
static struct buf arg;
*entries = *attribs = NULL;
c = prot_getc(proxyd_in);
if (c == EOF) {
prot_printf(proxyd_out,
"%s BAD Missing annotation entry\r\n", tag);
goto baddata;
}
else if (c == '(') {
/* entry list */
do {
c = getqstring(proxyd_in, proxyd_out, &arg);
if (c == EOF) {
prot_printf(proxyd_out,
"%s BAD Missing annotation entry\r\n", tag);
goto baddata;
}
/* add the entry to the list */
appendstrlist(entries, arg.s);
} while (c == ' ');
if (c != ')') {
prot_printf(proxyd_out,
"%s BAD Missing close paren in annotation entry list \r\n",
tag);
goto baddata;
}
c = prot_getc(proxyd_in);
}
else {
/* single entry -- add it to the list */
prot_ungetc(c, proxyd_in);
c = getqstring(proxyd_in, proxyd_out, &arg);
if (c == EOF) {
prot_printf(proxyd_out,
"%s BAD Missing annotation entry\r\n", tag);
goto baddata;
}
appendstrlist(entries, arg.s);
}
if (c != ' ' || (c = prot_getc(proxyd_in)) == EOF) {
prot_printf(proxyd_out,
"%s BAD Missing annotation attribute(s)\r\n", tag);
goto baddata;
}
if (c == '(') {
/* attrib list */
do {
c = getnstring(proxyd_in, proxyd_out, &arg);
if (c == EOF) {
prot_printf(proxyd_out,
"%s BAD Missing annotation attribute(s)\r\n", tag);
goto baddata;
}
/* add the attrib to the list */
appendstrlist(attribs, arg.s);
} while (c == ' ');
if (c != ')') {
prot_printf(proxyd_out,
"%s BAD Missing close paren in "
"annotation attribute list\r\n", tag);
goto baddata;
}
c = prot_getc(proxyd_in);
}
else {
/* single attrib */
prot_ungetc(c, proxyd_in);
c = getqstring(proxyd_in, proxyd_out, &arg);
if (c == EOF) {
prot_printf(proxyd_out,
"%s BAD Missing annotation attribute\r\n", tag);
goto baddata;
}
appendstrlist(attribs, arg.s);
}
return c;
baddata:
if (c != EOF) prot_ungetc(c, proxyd_in);
return EOF;
}
/*
* Parse annotate store data.
*
* This is a generic routine which parses just the annotation data.
* Any surrounding command text must be parsed elsewhere, ie,
* SETANNOTATION, STORE, APPEND.
*/
int getannotatestoredata(char *tag, struct entryattlist **entryatts)
{
int c;
static struct buf entry, attrib, value;
struct attvaluelist *attvalues = NULL;
*entryatts = NULL;
do {
/* get entry */
c = getqstring(proxyd_in, proxyd_out, &entry);
if (c == EOF) {
prot_printf(proxyd_out,
"%s BAD Missing annotation entry\r\n", tag);
goto baddata;
}
/* parse att-value list */
if (c != ' ' || (c = prot_getc(proxyd_in)) != '(') {
prot_printf(proxyd_out,
"%s BAD Missing annotation attribute-values list\r\n",
tag);
goto baddata;
}
do {
/* get attrib */
c = getqstring(proxyd_in, proxyd_out, &attrib);
if (c == EOF) {
prot_printf(proxyd_out,
"%s BAD Missing annotation attribute\r\n", tag);
goto baddata;
}
/* get value */
if (c != ' ' ||
(c = getnstring(proxyd_in, proxyd_out, &value)) == EOF) {
prot_printf(proxyd_out,
"%s BAD Missing annotation value\r\n", tag);
goto baddata;
}
/* add the attrib-value pair to the list */
appendattvalue(&attvalues, attrib.s, value.s);
} while (c == ' ');
if (c != ')') {
prot_printf(proxyd_out,
"%s BAD Missing close paren in annotation "
"attribute-values list\r\n", tag);
goto baddata;
}
/* add the entry to the list */
appendentryatt(entryatts, entry.s, attvalues);
attvalues = NULL;
c = prot_getc(proxyd_in);
} while (c == ' ');
return c;
baddata:
if (attvalues) freeattvalues(attvalues);
if (c != EOF) prot_ungetc(c, proxyd_in);
return EOF;
}
/*
* Output an entry/attribute-value list response.
*
* This is a generic routine which outputs just the annotation data.
* Any surrounding response text must be output elsewhere, ie,
* GETANNOTATION, FETCH.
*/
void annotate_response(struct entryattlist *l)
{
int islist; /* do we have more than one entry? */
if (!l) return;
islist = (l->next != NULL);
if (islist) prot_printf(proxyd_out, "(");
while (l) {
prot_printf(proxyd_out, "\"%s\"", l->entry);
/* do we have attributes? solicited vs. unsolicited */
if (l->attvalues) {
struct attvaluelist *av = l->attvalues;
prot_printf(proxyd_out, " (");
while (av) {
prot_printf(proxyd_out, "\"%s\" ", av->attrib);
if (!strcasecmp(av->value, "NIL"))
prot_printf(proxyd_out, "NIL");
else
prot_printf(proxyd_out, "\"%s\"", av->value);
if ((av = av->next) == NULL)
prot_printf(proxyd_out, ")");
else
prot_printf(proxyd_out, " ");
}
}
if ((l = l->next) != NULL)
prot_printf(proxyd_out, " ");
}
if (islist) prot_printf(proxyd_out, ")");
}
/*
* Perform a GETANNOTATION command
*
* The command has been parsed up to the entries
*/
void cmd_getannotation(char *tag)
{
int c, r = 0;
struct strlist *entries = NULL, *attribs = NULL;
struct entryattlist *entryatts = NULL;
c = getannotatefetchdata(tag, &entries, &attribs);
if (c == EOF) {
eatline(proxyd_in, c);
return;
}
/* check for CRLF */
if (c == '\r') c = prot_getc(proxyd_in);
if (c != '\n') {
prot_printf(proxyd_out,
"%s BAD Unexpected extra arguments to Getannotation\r\n",
tag);
eatline(proxyd_in, c);
goto freeargs;
}
r = annotatemore_fetch(entries, attribs, &proxyd_namespace,
proxyd_userisadmin, proxyd_userid,
proxyd_authstate, &entryatts);
if (r) {
prot_printf(proxyd_out, "%s NO %s\r\n", tag, error_message(r));
}
else if (entryatts) {
prot_printf(proxyd_out, "* ANNOTATION ");
annotate_response(entryatts);
prot_printf(proxyd_out, "\r\n");
prot_printf(proxyd_out, "%s OK %s\r\n",
tag, error_message(IMAP_OK_COMPLETED));
}
else
prot_printf(proxyd_out, "%s NO %s\r\n", tag,
error_message(IMAP_NO_NOSUCHANNOTATION));
freeargs:
if (entries) freestrlist(entries);
if (attribs) freestrlist(attribs);
if (entryatts) freeentryatts(entryatts);
return;
}
/*
* Perform a SETANNOTATION command
*
* The command has been parsed up to the entry-att list
*/
void cmd_setannotation(char *tag)
{
int c;
struct entryattlist *entryatts = NULL;
c = getannotatestoredata(tag, &entryatts);
if (c == EOF) {
eatline(proxyd_in, c);
return;
}
/* check for CRLF */
if (c == '\r') c = prot_getc(proxyd_in);
if (c != '\n') {
prot_printf(proxyd_out,
"%s BAD Unexpected extra arguments to Setannotation\r\n",
tag);
eatline(proxyd_in, c);
goto freeargs;
}
prot_printf(proxyd_out, "%s NO setting annotations not supported\r\n", tag);
freeargs:
if (entryatts) freeentryatts(entryatts);
return;
}
/*
* Append 's' to the strlist 'l'.
*/
void
appendstrlist(l, s)
struct strlist **l;
char *s;
{
struct strlist **tail = l;
while (*tail) tail = &(*tail)->next;
*tail = (struct strlist *)xmalloc(sizeof(struct strlist));
(*tail)->s = xstrdup(s);
(*tail)->p = 0;
(*tail)->next = 0;
}
/*
* Free the strlist 'l'
*/
void
freestrlist(l)
struct strlist *l;
{
struct strlist *n;
while (l) {
n = l->next;
free(l->s);
if (l->p) charset_freepat(l->p);
free((char *)l);
l = n;
}
}
/*
* Append the 'attrib'/'value' pair to the attvaluelist 'l'.
*/
void appendattvalue(struct attvaluelist **l, char *attrib, char *value)
{
struct attvaluelist **tail = l;
while (*tail) tail = &(*tail)->next;
*tail = (struct attvaluelist *)xmalloc(sizeof(struct attvaluelist));
(*tail)->attrib = xstrdup(attrib);
(*tail)->value = xstrdup(value);
(*tail)->next = 0;
}
/*
* Free the attvaluelist 'l'
*/
void freeattvalues(struct attvaluelist *l)
{
struct attvaluelist *n;
while (l) {
n = l->next;
free(l->attrib);
free(l->value);
l = n;
}
}
#endif /* ENABLE_ANNOTATEMORE */
/* Reset the given sasl_conn_t to a sane state */
static int reset_saslconn(sasl_conn_t **conn)
{
int ret;
sasl_security_properties_t *secprops = NULL;
sasl_dispose(conn);
/* do initialization typical of service_main */
ret = sasl_server_new("imap", config_servername,
NULL, NULL, NULL,
NULL, 0, conn);
if(ret != SASL_OK) return ret;
if(saslprops.ipremoteport)
ret = sasl_setprop(*conn, SASL_IPREMOTEPORT,
saslprops.ipremoteport);
if(ret != SASL_OK) return ret;
if(saslprops.iplocalport)
ret = sasl_setprop(*conn, SASL_IPLOCALPORT,
saslprops.iplocalport);
if(ret != SASL_OK) return ret;
secprops = mysasl_secprops(SASL_SEC_NOPLAINTEXT);
ret = sasl_setprop(*conn, SASL_SEC_PROPS, secprops);
if(ret != SASL_OK) return ret;
/* end of service_main initialization excepting SSF */
/* If we have TLS/SSL info, set it */
if(saslprops.ssf) {
ret = sasl_setprop(*conn, SASL_SSF_EXTERNAL, &saslprops.ssf);
} else {
ret = sasl_setprop(*conn, SASL_SSF_EXTERNAL, &extprops_ssf);
}
if(ret != SASL_OK) return ret;
if(saslprops.authid) {
ret = sasl_setprop(*conn, SASL_AUTH_EXTERNAL, saslprops.authid);
if(ret != SASL_OK) return ret;
}
/* End TLS/SSL Info */
return SASL_OK;
}
File Metadata
Details
Attached
Mime Type
text/x-diff
Expires
Sat, Apr 4, 5:57 AM (1 w, 1 d ago)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
18822771
Default Alt Text
(141 KB)
Attached To
Mode
R111 cyrus-imapd
Attached
Detach File
Event Timeline