Page MenuHomePhorge

No OneTemporary

Authored By
Unknown
Size
63 KB
Referenced Files
None
Subscribers
None
diff --git a/imap/mupdate-client.c b/imap/mupdate-client.c
index 084ae91f3..a67de3f9a 100644
--- a/imap/mupdate-client.c
+++ b/imap/mupdate-client.c
@@ -1,797 +1,792 @@
/* mupdate-client.c -- cyrus murder database clients
*
- * $Id: mupdate-client.c,v 1.22 2002/02/05 05:23:56 leg Exp $
+ * $Id: mupdate-client.c,v 1.23 2002/02/15 20:09:32 rjs3 Exp $
* Copyright (c) 2001 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.
*/
#include <config.h>
#include <stdio.h>
#include <errno.h>
#include <string.h>
#include <signal.h>
#include <ctype.h>
#include <sasl/sasl.h>
#include <sasl/saslutil.h>
#include <syslog.h>
#ifdef __STDC__
#include <stdarg.h>
#else
#include <varargs.h>
#endif
#ifdef HAVE_UNISTD_H
#include <unistd.h>
#endif
#include <sys/types.h>
#include <sys/time.h>
#include <netinet/in.h>
#ifdef HAVE_SYS_SELECT_H
#include <sys/select.h>
#endif
#include "prot.h"
#include "xmalloc.h"
#include "imapconf.h"
#include "assert.h"
#include "imparse.h"
#include "iptostring.h"
#include "mupdate.h"
#include "mupdate_err.h"
#include "exitcodes.h"
const char service_name[] = "mupdate";
extern sasl_callback_t *mysasl_callbacks(const char *username,
const char *authname,
const char *realm,
const char *password);
static sasl_security_properties_t *make_secprops(int min, int max)
{
sasl_security_properties_t *ret =
(sasl_security_properties_t *) xzmalloc(sizeof(sasl_security_properties_t));
ret->maxbufsize = 4000; /* xxx */
ret->min_ssf = config_getint("sasl_minimum_layer", min);
ret->max_ssf = config_getint("sasl_maximum_layer", max);
return ret;
}
static int mupdate_authenticate(mupdate_handle *h,
const char *mechlist)
{
int saslresult;
sasl_security_properties_t *secprops=NULL;
socklen_t addrsize;
struct sockaddr_in saddr_l;
struct sockaddr_in saddr_r;
char localip[60], remoteip[60];
const char *out;
unsigned int outlen;
const char *mechusing;
int ch;
char buf[4096];
/* Why do this again? */
if (h->saslcompleted) {
return 1;
}
secprops = make_secprops(0, 256);
if(!secprops) return 1;
saslresult=sasl_setprop(h->saslconn, SASL_SEC_PROPS, secprops);
if(saslresult != SASL_OK) return 1;
free(secprops);
addrsize=sizeof(struct sockaddr_in);
if (getpeername(h->sock,(struct sockaddr *)&saddr_r,&addrsize)!=0)
return 1;
addrsize=sizeof(struct sockaddr_in);
if (getsockname(h->sock,(struct sockaddr *)&saddr_l,&addrsize)!=0)
return 1;
if(iptostring((const struct sockaddr *)&saddr_l, sizeof(struct sockaddr_in),
localip, 60) != 0)
return 1;
if(iptostring((const struct sockaddr *)&saddr_r, sizeof(struct sockaddr_in),
remoteip, 60) != 0)
return 1;
saslresult=sasl_setprop(h->saslconn, SASL_IPREMOTEPORT, remoteip);
if (saslresult!=SASL_OK) return 1;
saslresult=sasl_setprop(h->saslconn, SASL_IPLOCALPORT, localip);
if (saslresult!=SASL_OK) return 1;
/* We shouldn't get sasl_interact's,
* because we provide explicit callbacks */
saslresult = sasl_client_start(h->saslconn, mechlist,
NULL, &out, &outlen, &mechusing);
if(saslresult != SASL_OK && saslresult != SASL_CONTINUE) return 1;
if(out) {
int r = sasl_encode64(out, outlen,
buf, sizeof(buf), NULL);
if(r != SASL_OK) return 1;
/* it's always ok to send the mechname quoted */
prot_printf(h->pout, "A01 AUTHENTICATE \"%s\" {%d+}\r\n%s\r\n",
mechusing, strlen(buf), buf);
} else {
prot_printf(h->pout, "A01 AUTHENTICATE \"%s\"\r\n", mechusing);
}
while(saslresult == SASL_CONTINUE) {
char *p, *in;
unsigned int len, inlen;
if(!prot_fgets(buf, sizeof(buf)-1, h->pin)) {
/* Connection Dropped */
return 1;
}
p = buf + strlen(buf) - 1;
if(p >= buf && *p == '\n') *p-- = '\0';
if(p >= buf && *p == '\r') *p-- = '\0';
len = strlen(buf);
in = xmalloc(len);
saslresult = sasl_decode64(buf, len, in, len, &inlen);
if(saslresult != SASL_OK) {
free(in);
/* CANCEL */
syslog(LOG_ERR, "couldn't base64 decode: aborted authentication");
/* If we haven't already canceled due to bad authentication,
* then we should */
if(strncmp(buf, "A01 NO ", 7)) prot_printf(h->pout, "*");
else {
syslog(LOG_ERR,
"Authentication to master failed (%s)", buf+7);
}
return 1;
}
saslresult = sasl_client_step(h->saslconn, in, inlen, NULL,
&out, &outlen);
free(in);
if((saslresult == SASL_OK || saslresult == SASL_CONTINUE) && out) {
int r = sasl_encode64(out, outlen,
buf, sizeof(buf), NULL);
if(r != SASL_OK) return 1;
prot_printf(h->pout, "%s\r\n", buf);
}
}
if(saslresult != SASL_OK) {
syslog(LOG_ERR, "bad authentication: %s",
sasl_errdetail(h->saslconn));
prot_printf(h->pout, "*");
return 1;
}
/* Check Result */
ch = getword(h->pin, &(h->tag));
if(ch != ' ') return 1; /* need an OK or NO */
ch = getword(h->pin, &(h->cmd));
if(!strncmp(h->cmd.s, "NO", 2)) {
if(ch != ' ') return 1; /* no reason really necessary, but we failed */
ch = getstring(h->pin, h->pout, &(h->arg1));
syslog(LOG_ERR, "authentication failed: %s", h->arg1.s);
return 1;
}
prot_setsasl(h->pin, h->saslconn);
prot_setsasl(h->pout, h->saslconn);
h->saslcompleted = 1;
return 0; /* SUCCESS */
}
int mupdate_connect(const char *server, const char *port,
mupdate_handle **handle,
sasl_callback_t *cbs)
{
mupdate_handle *h;
struct hostent *hp;
struct servent *sp;
struct sockaddr_in addr;
int s, saslresult;
char buf[4096];
- char *mechlist;
+ char *mechlist = NULL;
if(!handle)
return MUPDATE_BADPARAM;
/* open connection to 'server' */
if(!server) {
server = config_getstring("mupdate_server", NULL);
if (server == NULL) {
fatal("couldn't get mupdate server name", EC_UNAVAILABLE);
}
}
if(!port) {
port = config_getstring("mupdate_port",NULL);
}
hp = gethostbyname(server);
if (!hp) {
syslog(LOG_ERR, "mupdate-client: gethostbyname %s failed: %m", server);
return MUPDATE_NOCONN;
}
s = socket(AF_INET, SOCK_STREAM, 0);
if (s == -1) {
syslog(LOG_ERR, "mupdate-client: socket(): %m");
return MUPDATE_NOCONN;
}
addr.sin_family = AF_INET;
memcpy(&addr.sin_addr, hp->h_addr, sizeof(addr.sin_addr));
if (port && imparse_isnumber(port)) {
addr.sin_port = htons(atoi(port));
} else if (port) {
sp = getservbyname(port, "tcp");
if (!sp) {
syslog(LOG_ERR, "mupdate-client: getservbyname(tcp, %s): %m",
port);
}
addr.sin_port = sp->s_port;
} else if((sp = getservbyname("mupdate", "tcp")) != NULL) {
addr.sin_port = sp->s_port;
} else {
addr.sin_port = htons(2004);
}
h = xzmalloc(sizeof(mupdate_handle));
*handle = h;
h->sock = s;
if (connect(s, (struct sockaddr *)&addr, sizeof(addr)) == -1) {
syslog(LOG_ERR, "mupdate-client: connect(%s): %m", server);
return MUPDATE_NOCONN;
}
if(!cbs) {
cbs = mysasl_callbacks(config_getstring("mupdate_username",""),
config_getstring("mupdate_authname",NULL),
config_getstring("mupdate_realm",NULL),
config_getstring("mupdate_password",NULL));
}
saslresult = sasl_client_new(service_name,
server,
NULL, NULL,
cbs,
0,
&(h->saslconn));
/* create protstream */
h->pin = prot_new(h->sock, 0);
h->pout = prot_new(h->sock, 1);
prot_setflushonread(h->pin, h->pout);
prot_settimeout(h->pin, 30*60);
- /* Read the banner */
- if(!prot_fgets(buf, sizeof(buf)-1, h->pin)) {
- goto noconn;
- }
-
- if(strncmp(buf, "* OK MUPDATE", 12)) {
- syslog(LOG_ERR,
- "mupdate-client: invalid banner from remote server: %s", buf);
- mupdate_disconnect(handle);
- return MUPDATE_PROTOCOL_ERROR;
- }
+ /* Read the mechlist & other capabilities */
+ while(1) {
+ if (!prot_fgets(buf, sizeof(buf)-1, h->pin)) {
+ goto noconn;
+ }
- /* Read the mechlist */
- if (!prot_fgets(buf, sizeof(buf)-1, h->pin)) {
- goto noconn;
+ if(!strncmp(buf, "* AUTH", 6)) {
+ mechlist = xstrdup(buf + 6);
+ } else if(!strncmp(buf, "* OK MUPDATE", 12)) {
+ break;
+ }
}
- if(strncmp(buf, "* AUTH", 6)) {
- syslog(LOG_ERR,
- "mupdate-client: remote server did not send AUTH banner: %s",
- buf);
+ if(!mechlist) {
+ syslog(LOG_ERR, "no AUTH banner from remote");
mupdate_disconnect(handle);
- return MUPDATE_PROTOCOL_ERROR;
+ return MUPDATE_NOAUTH;
}
-
- mechlist = buf + 6;
if (mupdate_authenticate(h, mechlist)) {
syslog(LOG_ERR, "authentication to remote mupdate server failed");
+ free(mechlist);
mupdate_disconnect(handle);
return MUPDATE_NOAUTH;
}
+ free(mechlist);
/* SUCCESS */
return 0;
noconn:
+ if(mechlist) free(mechlist);
syslog(LOG_ERR, "mupdate-client: connection to server closed: %s",
prot_error(h->pin));
mupdate_disconnect(handle);
return MUPDATE_NOCONN;
}
void mupdate_disconnect(mupdate_handle **hp)
{
mupdate_handle *h;
if(!hp || !(*hp)) return;
h = *hp;
prot_printf(h->pout, "L01 LOGOUT\r\n");
prot_flush(h->pout);
freebuf(&(h->tag));
freebuf(&(h->cmd));
freebuf(&(h->arg1));
freebuf(&(h->arg2));
freebuf(&(h->arg3));
prot_free(h->pin);
prot_free(h->pout);
sasl_dispose(&(h->saslconn));
close(h->sock);
if(h->acl_buf) free(h->acl_buf);
free(h);
*hp = NULL;
}
/* We're really only looking for an OK or NO or BAD here */
static int mupdate_scarf_one(struct mupdate_mailboxdata *mdata __attribute__((unused)),
const char *cmd,
void *context)
{
int *called = context;
if(*called) {
/* Only want to be called once per command */
return -1;
}
*called = 1;
/*only accept OK, NO and BAD */
if(strncmp(cmd, "OK", 2)) {
return 0;
} else if (strncmp(cmd, "NO", 2) || strncmp(cmd, "BAD", 3)) {
return -1;
} else {
return 1;
}
}
int mupdate_activate(mupdate_handle *handle,
const char *mailbox, const char *server,
const char *acl)
{
int ret;
int called = 0;
enum mupdate_cmd_response response;
if (!handle) return MUPDATE_BADPARAM;
if (!mailbox || !server || !acl) return MUPDATE_BADPARAM;
if (!handle->saslcompleted) return MUPDATE_NOAUTH;
prot_printf(handle->pout,
"X%u ACTIVATE {%d+}\r\n%s {%d+}\r\n%s {%d+}\r\n%s\r\n",
handle->tagn++, strlen(mailbox), mailbox,
strlen(server), server, strlen(acl), acl);
ret = mupdate_scarf(handle, mupdate_scarf_one, &called, 1, &response);
if (ret) {
return ret;
} else if (response != MUPDATE_OK) {
return MUPDATE_FAIL;
} else {
return 0;
}
}
int mupdate_reserve(mupdate_handle *handle,
const char *mailbox, const char *server)
{
int ret;
int called = 0;
enum mupdate_cmd_response response;
if (!handle) return MUPDATE_BADPARAM;
if (!mailbox || !server) return MUPDATE_BADPARAM;
if (!handle->saslcompleted) return MUPDATE_NOAUTH;
prot_printf(handle->pout,
"X%u RESERVE {%d+}\r\n%s {%d+}\r\n%s\r\n",
handle->tagn++, strlen(mailbox), mailbox,
strlen(server), server);
ret = mupdate_scarf(handle, mupdate_scarf_one, &called, 1, &response);
if (ret) {
return ret;
} else if (response != MUPDATE_OK) {
return MUPDATE_FAIL;
} else {
return 0;
}
}
int mupdate_delete(mupdate_handle *handle,
const char *mailbox)
{
int ret;
int called = 0;
enum mupdate_cmd_response response;
if (!handle) return MUPDATE_BADPARAM;
if (!mailbox) return MUPDATE_BADPARAM;
if (!handle->saslcompleted) return MUPDATE_NOAUTH;
prot_printf(handle->pout,
"X%u DELETE {%d+}\r\n%s\r\n", handle->tagn++,
strlen(mailbox), mailbox);
ret = mupdate_scarf(handle, mupdate_scarf_one, &called, 1, &response);
if (ret) {
return ret;
} else if (response != MUPDATE_OK) {
return MUPDATE_FAIL;
} else {
return 0;
}
}
static int mupdate_find_cb(struct mupdate_mailboxdata *mdata,
const char *cmd, void *context)
{
struct mupdate_handle_s *h = (struct mupdate_handle_s *)context;
if(!h || !cmd || !mdata) return 1;
/* coyp the data to the handle storage */
/* xxx why can't we just point to the 'mdata' buffers? */
strlcpy(h->mailbox_buf, mdata->mailbox, MAX_MAILBOX_NAME);
strlcpy(h->server_buf, mdata->server, MAX_MAILBOX_NAME);
if(!strncmp(cmd, "MAILBOX", 7)) {
int len = strlen(mdata->acl) + 1;
h->mailboxdata_buf.t = ACTIVE;
if(len > h->acl_buf_len) {
/* we want to at least double the buffer */
if (len < 2 * h->acl_buf_len) {
len = 2 * h->acl_buf_len;
}
h->acl_buf = xrealloc(h->acl_buf, len);
strcpy(h->acl_buf, mdata->acl);
}
} else if (!strncmp(cmd, "RESERVE", 7)) {
h->mailboxdata_buf.t = RESERVE;
if(!h->acl_buf) {
h->acl_buf = xstrdup("");
h->acl_buf_len = 1;
} else {
h->acl_buf[0] = '\0';
}
} else {
/* Bad command */
return 1;
}
h->mailboxdata_buf.mailbox = h->mailbox_buf;
h->mailboxdata_buf.server = h->server_buf;
h->mailboxdata_buf.acl = h->acl_buf;
return 0;
}
int mupdate_find(mupdate_handle *handle, const char *mailbox,
struct mupdate_mailboxdata **target)
{
int ret;
enum mupdate_cmd_response response;
if(!handle || !mailbox || !target) return MUPDATE_BADPARAM;
prot_printf(handle->pout,
"X%u FIND {%d+}\r\n%s\r\n", handle->tagn++,
strlen(mailbox), mailbox);
memset(&(handle->mailboxdata_buf), 0, sizeof(handle->mailboxdata_buf));
ret = mupdate_scarf(handle, mupdate_find_cb, handle, 1, &response);
if (!ret && response == MUPDATE_OK) {
*target = &(handle->mailboxdata_buf);
return 0;
} else {
*target = NULL;
return ret ? ret : MUPDATE_FAIL;
}
}
int mupdate_list(mupdate_handle *handle, mupdate_callback callback,
const char *prefix, void *context)
{
int ret;
enum mupdate_cmd_response response;
if(!handle || !callback) return MUPDATE_BADPARAM;
if(prefix) {
prot_printf(handle->pout,
"X%u LIST {%d+}\r\n%s\r\n", handle->tagn++,
strlen(prefix), prefix);
} else {
prot_printf(handle->pout,
"X%u LIST\r\n", handle->tagn++);
}
ret = mupdate_scarf(handle, callback, context, 1, &response);
if (ret) {
return ret;
} else if (response != MUPDATE_OK) {
return MUPDATE_FAIL;
} else {
return 0;
}
}
#define CHECKNEWLINE(c, ch) do { if ((ch) == '\r') (ch)=prot_getc((c)->pin); \
if ((ch) != '\n') { syslog(LOG_ERR, \
"extra arguments recieved, aborting connection");\
r = MUPDATE_PROTOCOL_ERROR;\
goto done; }} while(0)
/* Scarf up the incoming data and perform the requested operations */
int mupdate_scarf(mupdate_handle *handle,
mupdate_callback callback,
void *context,
int wait_for_ok,
enum mupdate_cmd_response *response)
{
struct mupdate_mailboxdata box;
int r = 0;
if (!handle || !callback) return MUPDATE_BADPARAM;
/* keep going while we have input or if we're waiting for an OK */
while (!r) {
int ch;
unsigned char *p;
if (wait_for_ok) {
prot_BLOCK(handle->pin);
} else {
prot_NONBLOCK(handle->pin);
}
ch = getword(handle->pin, &(handle->tag));
if (ch == EOF && errno == EAGAIN) {
/* this was just "no input" we return 0 */
goto done;
} else if (ch == EOF) {
/* this was a fatal error */
r = MUPDATE_NOCONN;
goto done;
}
/* set it blocking so we don't get half a line */
prot_BLOCK(handle->pin);
if(ch != ' ') {
/* We always have a command */
syslog(LOG_ERR, "Protocol error from master: no tag",
handle->tag.s, ch);
r = MUPDATE_PROTOCOL_ERROR;
goto done;
}
ch = getword(handle->pin, &(handle->cmd));
if(ch != ' ') {
/* We always have an argument */
syslog(LOG_ERR, "Protocol error from master: no keyword");
r = MUPDATE_PROTOCOL_ERROR;
break;
}
if (islower((unsigned char) handle->cmd.s[0])) {
handle->cmd.s[0] = toupper((unsigned char) handle->cmd.s[0]);
}
for (p = &(handle->cmd.s[1]); *p; p++) {
if (islower((unsigned char) *p))
*p = toupper((unsigned char) *p);
}
switch(handle->cmd.s[0]) {
case 'B':
if(!strncmp(handle->cmd.s, "BAD", 6)) {
ch = getstring(handle->pin, handle->pout, &(handle->arg1));
CHECKNEWLINE(handle, ch);
syslog(LOG_DEBUG, "mupdate BAD response: %s", handle->arg1.s);
if (wait_for_ok && response) {
*response = MUPDATE_BAD;
}
goto done;
}
goto badcmd;
case 'D':
if(!strncmp(handle->cmd.s, "DELETE", 6)) {
ch = getstring(handle->pin, handle->pout, &(handle->arg1));
CHECKNEWLINE(handle, ch);
memset(&box, 0, sizeof(box));
box.mailbox = handle->arg1.s;
/* Handle delete command */
r = callback(&box, handle->cmd.s, context);
if (r) {
syslog(LOG_ERR,
"error deleting mailbox: callback returned %d", r);
goto done;
}
break;
}
goto badcmd;
case 'M':
if(!strncmp(handle->cmd.s, "MAILBOX", 7)) {
/* Mailbox Name */
ch = getstring(handle->pin, handle->pout, &(handle->arg1));
if(ch != ' ') {
r = MUPDATE_PROTOCOL_ERROR;
goto done;
}
/* Server */
ch = getstring(handle->pin, handle->pout, &(handle->arg2));
if(ch != ' ') {
r = MUPDATE_PROTOCOL_ERROR;
goto done;
}
/* ACL */
ch = getstring(handle->pin, handle->pout, &(handle->arg3));
CHECKNEWLINE(handle, ch);
/* Handle mailbox command */
memset(&box, 0, sizeof(box));
box.mailbox = handle->arg1.s;
box.server = handle->arg2.s;
box.acl = handle->arg3.s;
r = callback(&box, handle->cmd.s, context);
if (r) { /* callback error ? */
syslog(LOG_ERR,
"error activating mailbox: callback returned %d", r);
goto done;
}
break;
}
goto badcmd;
case 'N':
if(!strncmp(handle->cmd.s, "NO", 6)) {
ch = getstring(handle->pin, handle->pout, &(handle->arg1));
CHECKNEWLINE(handle, ch);
syslog(LOG_DEBUG, "mupdate NO response: %s", handle->arg1.s);
if (wait_for_ok) {
if (response) *response = MUPDATE_NO;
goto done;
}
break;
}
goto badcmd;
case 'O':
if(!strncmp(handle->cmd.s, "OK", 2)) {
/* It's all good, grab the attached string and move on */
ch = getstring(handle->pin, handle->pout, &(handle->arg1));
CHECKNEWLINE(handle, ch);
if (wait_for_ok) {
if (response) *response = MUPDATE_OK;
goto done;
}
break;
}
goto badcmd;
case 'R':
if(!strncmp(handle->cmd.s, "RESERVE", 7)) {
/* Mailbox Name */
ch = getstring(handle->pin, handle->pout, &(handle->arg1));
if(ch != ' ') {
r = MUPDATE_PROTOCOL_ERROR;
goto done;
}
/* Server */
ch = getstring(handle->pin, handle->pout, &(handle->arg2));
CHECKNEWLINE(handle, ch);
/* Handle reserve command */
memset(&box, 0, sizeof(box));
box.mailbox = handle->arg1.s;
box.server = handle->arg2.s;
r = callback(&box, handle->cmd.s, context);
if (r) { /* callback error ? */
syslog(LOG_ERR,
"error reserving mailbox: callback returned %d", r);
goto done;
}
break;
}
goto badcmd;
default:
badcmd:
/* Bad Command */
syslog(LOG_ERR, "bad/unexpected command from master: %s",
handle->cmd.s);
r = MUPDATE_PROTOCOL_ERROR;
goto done;
}
}
done:
/* reset blocking */
prot_NONBLOCK(handle->pin);
return r;
}
diff --git a/imap/mupdate.c b/imap/mupdate.c
index e030e4785..9db4d23b3 100644
--- a/imap/mupdate.c
+++ b/imap/mupdate.c
@@ -1,1587 +1,1587 @@
/* mupdate.c -- cyrus murder database master
*
- * $Id: mupdate.c,v 1.48 2002/02/12 16:37:02 rjs3 Exp $
+ * $Id: mupdate.c,v 1.49 2002/02/15 20:09:32 rjs3 Exp $
* Copyright (c) 2001 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.
*/
#include <config.h>
#include <sys/time.h>
#ifdef HAVE_UNISTD_H
#include <unistd.h>
#endif
#include <stdio.h>
#include <string.h>
#include <ctype.h>
#include <signal.h>
#include <time.h>
#include <stdlib.h>
#include <assert.h>
#include <syslog.h>
#include <errno.h>
#include <netdb.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <pthread.h>
#include <sasl/sasl.h>
#include <sasl/saslutil.h>
#include "mupdate.h"
#include "mupdate-client.h"
#include "xmalloc.h"
#include "iptostring.h"
#include "mailbox.h"
#include "mboxlist.h"
#include "exitcodes.h"
#include "prot.h"
#include "imapconf.h"
#include "version.h"
#include "mpool.h"
static int masterp = 0;
enum {
poll_interval = 1,
update_wait = 5
};
struct pending {
enum settype t;
struct pending *next;
char mailbox[MAX_MAILBOX_NAME];
};
struct conn {
int fd;
struct protstream *pin;
struct protstream *pout;
sasl_conn_t *saslconn;
char *userid;
const char *clienthost;
struct
{
char *ipremoteport;
char *iplocalport;
} saslprops;
/* pending changes to send, in reverse order */
const char *streaming; /* tag */
pthread_mutex_t m;
pthread_cond_t cond;
struct pending *plist;
struct conn *updatelist_next;
struct prot_waitevent *ev; /* invoked every 'update_wait' seconds
to send out updates */
/* Prefix for list commands */
const char *list_prefix;
size_t list_prefix_len;
struct conn *next;
};
int ready_for_connections = 0;
pthread_cond_t ready_for_connections_cond = PTHREAD_COND_INITIALIZER;
pthread_mutex_t ready_for_connections_mutex = PTHREAD_MUTEX_INITIALIZER;
int synced = 0;
pthread_cond_t synced_cond = PTHREAD_COND_INITIALIZER;
pthread_mutex_t synced_mutex = PTHREAD_MUTEX_INITIALIZER;
pthread_mutex_t connlist_mutex = PTHREAD_MUTEX_INITIALIZER;
struct conn *connlist = NULL;
/* ---- database access ---- */
pthread_mutex_t mailboxes_mutex = PTHREAD_MUTEX_INITIALIZER;
struct conn *updatelist = NULL;
/* --- prototypes --- */
void cmd_authenticate(struct conn *C,
const char *tag, const char *mech,
const char *clientstart);
void cmd_set(struct conn *C,
const char *tag, const char *mailbox,
const char *server, const char *acl, enum settype t);
void cmd_find(struct conn *C, const char *tag, const char *mailbox,
int dook);
void cmd_list(struct conn *C, const char *tag, const char *host_prefix);
void cmd_startupdate(struct conn *C, const char *tag);
void shut_down(int code);
static int reset_saslconn(struct conn *c);
void database_init();
void sendupdates(struct conn *C, int flushnow);
/* --- prototypes in mupdate-client.c */
void *mupdate_client_start(void *rock);
/* --- mutex wrapper functions for SASL */
void *my_mutex_new(void)
{
pthread_mutex_t *ret = (pthread_mutex_t *)xmalloc(sizeof(pthread_mutex_t));
pthread_mutex_init(ret, NULL);
return ret;
}
int my_mutex_destroy(pthread_mutex_t *m)
{
if(!m) return SASL_BADPARAM;
if(pthread_mutex_destroy(m)) return SASL_FAIL;
free(m);
return SASL_OK;
}
/* end mutex wrapper functions */
static struct conn *conn_new(int fd)
{
struct conn *C = xzmalloc(sizeof(struct conn));
C->fd = fd;
pthread_mutex_lock(&connlist_mutex); /* LOCK */
C->next = connlist;
connlist = C;
pthread_mutex_unlock(&connlist_mutex); /* UNLOCK */
return C;
}
static void conn_free(struct conn *C)
{
if (C->streaming) { /* remove from updatelist */
struct conn *upc;
pthread_mutex_lock(&mailboxes_mutex);
if (C == updatelist) {
/* first thing in updatelist */
updatelist = C->updatelist_next;
} else {
/* find in update list */
for (upc = updatelist; upc->updatelist_next != NULL;
upc = upc->updatelist_next) {
if (upc->updatelist_next == C) break;
}
/* must find it ! */
assert(upc->updatelist_next == C);
upc->updatelist_next = C->updatelist_next;
}
pthread_mutex_unlock(&mailboxes_mutex);
}
pthread_mutex_lock(&connlist_mutex); /* LOCK */
/* remove from connlist */
if (C == connlist) {
connlist = connlist->next;
} else {
struct conn *t;
for (t = connlist; t->next != NULL; t = t->next) {
if (t->next == C) break;
}
assert(t != NULL);
t->next = C->next;
}
pthread_mutex_unlock(&connlist_mutex); /* UNLOCK */
if (C->ev) prot_removewaitevent(C->pin, C->ev);
if (C->pin) prot_free(C->pin);
if (C->pout) prot_free(C->pout);
close(C->fd);
if (C->saslconn) sasl_dispose(&C->saslconn);
/* free update list */
free(C);
}
/* 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;
int allowed=0;
struct auth_state *authstate;
/* 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;
}
}
/* ok, is auth_identity an admin?
* for now only admins can do mupdate from another machine
*/
authstate = auth_newstate(auth_identity, NULL);
allowed = authisa(authstate, "mupdate", "admins");
auth_freestate(authstate);
if (!allowed) {
sasl_seterror(conn, 0, "only admins may authenticate");
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_LIST_END, NULL, NULL }
};
/*
* 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 r;
int opt;
config_changeident("mupdate");
if (geteuid() == 0) fatal("must run as the Cyrus user", EC_USAGE);
/* 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);
/* set the SASL mutex functions */
sasl_set_mutex((sasl_mutex_alloc_t *) &my_mutex_new,
(sasl_mutex_lock_t *) &pthread_mutex_lock,
(sasl_mutex_unlock_t *) &pthread_mutex_unlock,
(sasl_mutex_free_t *) &my_mutex_destroy);
/* 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;
}
/* see if we're the master or a slave */
while ((opt = getopt(argc, argv, "C:m")) != EOF) {
switch (opt) {
case 'C':
break;
case 'm':
masterp = 1;
break;
default:
break;
}
}
/* Slave Skiplist Fast Resync? */
if(!masterp && config_getswitch("mupdate_slave_fast_resync",0)) {
char fname[4096];
if(snprintf(fname,4096,"%s%s",config_dir,FNAME_MBOXLIST) == -1)
fatal("mboxlist database filename too large",EC_TEMPFAIL);
putenv("CYRUS_SKIPLIST_UNSAFE=1");
unlink(fname);
}
database_init();
if (!masterp) {
pthread_t t;
r = pthread_create(&t, NULL, &mupdate_client_start, NULL);
if(r == 0) {
pthread_detach(t);
} else {
syslog(LOG_ERR, "could not start client thread");
return EC_SOFTWARE;
}
/* Wait until they sync the database */
pthread_mutex_lock(&synced_mutex);
if(!synced)
pthread_cond_wait(&synced_cond, &synced_mutex);
pthread_mutex_unlock(&synced_mutex);
} else {
mupdate_ready();
}
return 0;
}
/* called if 'service_init()' was called but not 'service_main()' */
void service_abort(int error)
{
exit(error);
}
void fatal(const char *s, int code)
{
syslog(LOG_ERR, "%s", s);
exit(code);
}
#define CHECKNEWLINE(c, ch) do { if ((ch) == '\r') (ch)=prot_getc((c)->pin); \
if ((ch) != '\n') goto extraargs; } while (0)
void cmdloop(struct conn *c)
{
struct buf tag, cmd, arg1, arg2, arg3;
const char *mechs;
int ret;
unsigned int mechcount;
syslog(LOG_DEBUG, "starting cmdloop() on fd %d", c->fd);
/* zero out struct bufs */
/* Note that since they live on the stack for the duration of the
* connection we don't need to fill up the struct conn with them */
memset(&tag, 0, sizeof(struct buf));
memset(&cmd, 0, sizeof(struct buf));
memset(&arg1, 0, sizeof(struct buf));
memset(&arg2, 0, sizeof(struct buf));
memset(&arg3, 0, sizeof(struct buf));
- ret=sasl_listmech(c->saslconn, NULL, "\r\n* AUTH ", " ", "", &mechs,
- NULL, &mechcount);
+ ret=sasl_listmech(c->saslconn, NULL, "* AUTH \"", "\" \"", "\"",
+ &mechs, NULL, &mechcount);
- /* AUTH banner is mandatory, even if empty */
+ /* AUTH banner is mandatory */
prot_printf(c->pout,
- "* OK MUPDATE \"%s\" \"Cyrus Murder\" \"%s\" \"%s\"%s\r\n",
- config_servername,
- CYRUS_VERSION, masterp ? "(master)" : "(slave)",
- (ret == SASL_OK && mechcount > 0) ? mechs : "* AUTH");
+ "%s\r\n* OK MUPDATE \"%s\" \"Cyrus Murder\" \"%s\" \"%s\"\r\n",
+ (ret == SASL_OK && mechcount > 0) ? mechs : "* AUTH",
+ config_servername,
+ CYRUS_VERSION, masterp ? "(master)" : "(slave)");
for (;;) {
int ch;
char *p;
signals_poll();
ch = getword(c->pin, &tag);
if (ch == EOF && errno == EAGAIN) {
/* streaming and no input from client */
continue;
}
if (ch == EOF) {
const char *err;
if ((err = prot_error(c->pin)) != NULL) {
syslog(LOG_WARNING, "%s, closing connection", err);
prot_printf(c->pout, "* BYE \"%s\"\r\n", err);
}
goto done;
}
/* send out any updates we have pending */
if (c->streaming) {
sendupdates(c, 0); /* don't flush pout though */
}
if (ch != ' ') {
prot_printf(c->pout, "%s BAD \"Need command\"\r\n", tag.s);
eatline(c->pin, ch);
continue;
}
/* parse command name */
ch = getword(c->pin, &cmd);
if (!cmd.s[0]) {
prot_printf(c->pout, "%s BAD \"Null command\"\r\n", tag.s);
eatline(c->pin, ch);
continue;
}
if (islower((unsigned char) cmd.s[0])) {
cmd.s[0] = toupper((unsigned char) cmd.s[0]);
}
for (p = &cmd.s[1]; *p; p++) {
if (isupper((unsigned char) *p)) *p = tolower((unsigned char) *p);
}
switch (cmd.s[0]) {
case 'A':
if (!strcmp(cmd.s, "Authenticate")) {
int opt = 0;
if (ch != ' ') goto missingargs;
ch = getstring(c->pin, c->pout, &arg1);
if (ch == ' ') {
ch = getstring(c->pin, c->pout, &arg2);
opt = 1;
}
CHECKNEWLINE(c, ch);
if (c->userid) {
prot_printf(c->pout,
"%s BAD \"already authenticated\"\r\n",
tag.s);
continue;
}
cmd_authenticate(c, tag.s, arg1.s, opt ? arg2.s : NULL);
}
else if (!c->userid) goto nologin;
else if (!strcmp(cmd.s, "Activate")) {
if (ch != ' ') goto missingargs;
ch = getstring(c->pin, c->pout, &arg1);
if (ch != ' ') goto missingargs;
ch = getstring(c->pin, c->pout, &arg2);
if (ch != ' ') goto missingargs;
ch = getstring(c->pin, c->pout, &arg3);
CHECKNEWLINE(c, ch);
if (c->streaming) goto notwhenstreaming;
if (!masterp) goto masteronly;
cmd_set(c, tag.s, arg1.s, arg2.s, arg3.s, SET_ACTIVE);
}
else goto badcmd;
break;
case 'D':
if (!c->userid) goto nologin;
else if (!strcmp(cmd.s, "Delete")) {
if (ch != ' ') goto missingargs;
ch = getstring(c->pin, c->pout, &arg1);
CHECKNEWLINE(c, ch);
if (c->streaming) goto notwhenstreaming;
if (!masterp) goto masteronly;
cmd_set(c, tag.s, arg1.s, NULL, NULL, SET_DELETE);
}
else goto badcmd;
break;
case 'F':
if (!c->userid) goto nologin;
else if (!strcmp(cmd.s, "Find")) {
if (ch != ' ') goto missingargs;
ch = getstring(c->pin, c->pout, &arg1);
CHECKNEWLINE(c, ch);
if (c->streaming) goto notwhenstreaming;
cmd_find(c, tag.s, arg1.s, 1);
}
else goto badcmd;
break;
case 'L':
if (!strcmp(cmd.s, "Logout")) {
CHECKNEWLINE(c, ch);
prot_printf(c->pout, "%s OK \"bye-bye\"\r\n", tag.s);
goto done;
}
else if (!c->userid) goto nologin;
else if (!strcmp(cmd.s, "List")) {
int opt = 0;
if (ch == ' ') {
/* Optional partition/host prefix parameter */
ch = getstring(c->pin, c->pout, &arg1);
opt = 1;
}
CHECKNEWLINE(c, ch);
if (c->streaming) goto notwhenstreaming;
cmd_list(c, tag.s, opt ? arg1.s : NULL);
prot_printf(c->pout, "%s OK \"list complete\"\r\n", tag.s);
}
else goto badcmd;
break;
case 'N':
if (!c->userid) goto nologin;
else if (!strcmp(cmd.s, "Noop")) {
CHECKNEWLINE(c, ch);
prot_printf(c->pout, "%s OK \"Noop done\"\r\n", tag.s);
}
else goto badcmd;
break;
case 'R':
if (!c->userid) goto nologin;
else if (!strcmp(cmd.s, "Reserve")) {
if (ch != ' ') goto missingargs;
ch = getstring(c->pin, c->pout, &arg1);
if (ch != ' ') goto missingargs;
ch = getstring(c->pin, c->pout, &arg2);
CHECKNEWLINE(c, ch);
if (c->streaming) goto notwhenstreaming;
if (!masterp) goto masteronly;
cmd_set(c, tag.s, arg1.s, arg2.s, NULL, SET_RESERVE);
}
else goto badcmd;
break;
case 'U':
if (!c->userid) goto nologin;
else if (!strcmp(cmd.s, "Update")) {
CHECKNEWLINE(c, ch);
if (c->streaming) goto notwhenstreaming;
cmd_startupdate(c, tag.s);
}
else goto badcmd;
break;
default:
badcmd:
prot_printf(c->pout, "%s BAD \"Unrecognized command\"\r\n", tag.s);
eatline(c->pin, ch);
continue;
extraargs:
prot_printf(c->pout, "%s BAD \"Extra arguments\"\r\n", tag.s);
eatline(c->pin, ch);
continue;
missingargs:
prot_printf(c->pout, "%s BAD \"Missing arguments\"\r\n", tag.s);
eatline(c->pin, ch);
continue;
notwhenstreaming:
prot_printf(c->pout, "%s BAD \"not legal when streaming\"\r\n",
tag.s);
continue;
masteronly:
prot_printf(c->pout,
"%s BAD \"read-only session\"\r\n",
tag.s);
continue;
}
continue;
nologin:
prot_printf(c->pout, "%s BAD Please login first\r\n", tag.s);
eatline(c->pin, ch);
continue;
}
done:
prot_flush(c->pout);
/* free struct bufs */
freebuf(&tag);
freebuf(&cmd);
freebuf(&arg1);
freebuf(&arg2);
freebuf(&arg3);
syslog(LOG_DEBUG, "ending cmdloop() on fd %d", c->fd);
}
void *start(void *rock)
{
struct conn *c = (struct conn *) rock;
struct sockaddr_in localaddr, remoteaddr;
int haveaddr = 0;
int salen;
int secflags, plaintext_result;
sasl_security_properties_t *secprops = NULL;
char localip[60], remoteip[60];
char clienthost[250];
struct hostent *hp;
pthread_mutex_lock(&ready_for_connections_mutex);
/* are we ready to take connections? */
while(!ready_for_connections) {
pthread_cond_wait(&ready_for_connections_cond,
&ready_for_connections_mutex);
}
pthread_mutex_unlock(&ready_for_connections_mutex);
c->pin = prot_new(c->fd, 0);
c->pout = prot_new(c->fd, 1);
c->clienthost = clienthost;
prot_setflushonread(c->pin, c->pout);
prot_settimeout(c->pin, 180*60);
/* Find out name of client host */
salen = sizeof(remoteaddr);
if (getpeername(c->fd, (struct sockaddr *)&remoteaddr, &salen) == 0 &&
remoteaddr.sin_family == AF_INET) {
hp = gethostbyaddr((char *)&remoteaddr.sin_addr,
sizeof(remoteaddr.sin_addr), AF_INET);
if (hp != NULL) {
strncpy(clienthost, hp->h_name, sizeof(clienthost)-30);
clienthost[sizeof(clienthost)-30] = '\0';
} else {
clienthost[0] = '\0';
}
strcat(clienthost, "[");
strcat(clienthost, inet_ntoa(remoteaddr.sin_addr));
strcat(clienthost, "]");
salen = sizeof(localaddr);
if (getsockname(c->fd, (struct sockaddr *)&localaddr, &salen) == 0
&& iptostring((struct sockaddr *)&remoteaddr,
sizeof(struct sockaddr_in), remoteip, 60) == 0
&& iptostring((struct sockaddr *)&localaddr,
sizeof(struct sockaddr_in), localip, 60) == 0) {
haveaddr = 1;
}
}
if(haveaddr) {
c->saslprops.ipremoteport = remoteip;
c->saslprops.iplocalport = localip;
}
/* create sasl connection */
if (sasl_server_new("mupdate",
config_servername, NULL,
(haveaddr ? localip : NULL),
(haveaddr ? remoteip : NULL),
NULL, 0,
&c->saslconn) != SASL_OK) {
fatal("SASL failed initializing: sasl_server_new()", EC_TEMPFAIL);
}
/* set my allowable security properties */
secflags = SASL_SEC_NOANONYMOUS;
plaintext_result = config_getswitch("allowplaintext",1);
if (!config_getswitch("mupdate_allowplaintext", plaintext_result)) {
secflags |= SASL_SEC_NOPLAINTEXT;
}
secprops = mysasl_secprops(secflags);
sasl_setprop(c->saslconn, SASL_SEC_PROPS, secprops);
cmdloop(c);
close(c->fd);
conn_free(c);
return NULL;
}
/*
* run for each accepted connection
*/
int service_main_fd(int fd, int argc, char **argv, char **envp)
{
/* spawn off a thread to handle this connection */
pthread_t t;
struct conn *c = conn_new(fd);
int r;
r = pthread_create(&t, NULL, &start, c);
if (r == 0) {
pthread_detach(t);
}
return 0;
}
/* read from disk database must be unlocked. */
void database_init()
{
pthread_mutex_lock(&mailboxes_mutex); /* LOCK */
mboxlist_init(0);
mboxlist_open(NULL);
pthread_mutex_unlock(&mailboxes_mutex); /* UNLOCK */
}
static const char *translate(enum settype t)
{
switch (t) {
case SET_ACTIVE: return "ACTIVE";
case SET_RESERVE: return "RESERVE";
case SET_DELETE: return "DELETE";
default: abort();
}
}
/* log change to database. database must be locked. */
void database_log(const struct mbent *mb)
{
switch (mb->t) {
case SET_ACTIVE:
mboxlist_insertremote(mb->mailbox, 0, mb->server, mb->acl, NULL);
break;
case SET_RESERVE:
mboxlist_insertremote(mb->mailbox, MBTYPE_RESERVE, mb->server,
"", NULL);
break;
case SET_DELETE:
mboxlist_deletemailbox(mb->mailbox, 1, "", NULL, 0);
break;
}
}
/* lookup in database. database must be locked */
/* This could probabally be more efficient and avoid some copies */
/* passing in a NULL pool implies that we should use regular xmalloc,
* a non-null pool implies we should use the mpool functionality */
struct mbent *database_lookup(const char *name, struct mpool *pool)
{
char *path, *acl;
int type;
struct mbent *out;
if(!name) return NULL;
if(mboxlist_detail(name, &type, &path, NULL, &acl, NULL))
return NULL;
if(type & MBTYPE_RESERVE) {
if(!pool) out = xmalloc(sizeof(struct mbent) + 1);
else out = mpool_malloc(pool, sizeof(struct mbent) + 1);
out->t = SET_RESERVE;
out->acl[0] = '\0';
} else {
if(!pool) out = xmalloc(sizeof(struct mbent) + strlen(acl));
else out = mpool_malloc(pool, sizeof(struct mbent) + strlen(acl));
out->t = SET_ACTIVE;
strcpy(out->acl, acl);
}
out->mailbox = (pool) ? mpool_strdup(pool, name) : xstrdup(name);
out->server = (pool) ? mpool_strdup(pool, path) : xstrdup(path);
return out;
}
void cmd_authenticate(struct conn *C,
const char *tag, const char *mech,
const char *clientstart)
{
int r;
char *in = NULL;
const char *out = NULL;
unsigned int inlen = 0, outlen = 0;
if(clientstart && clientstart[0]) {
unsigned len = strlen(clientstart);
in = xmalloc(len);
r = sasl_decode64(clientstart, len, in, len, &inlen);
if(r != SASL_OK) {
prot_printf(C->pout, "%s NO \"cannot base64 decode\"\r\n",tag);
free(in);
return;
}
}
r = sasl_server_start(C->saslconn, mech, in, inlen, &out, &outlen);
free(in); in=NULL;
if(r == SASL_NOMECH) {
prot_printf(C->pout,
"%s NO \"unknown authentication mechanism\"\r\n",tag);
return;
}
while(r == SASL_CONTINUE) {
char buf[4096];
char inbase64[4096];
char *p;
unsigned len;
if(out) {
r = sasl_encode64(out, outlen,
inbase64, sizeof(inbase64), NULL);
if(r != SASL_OK) break;
/* send out */
prot_printf(C->pout, "%s\r\n", inbase64);
prot_flush(C->pout);
}
/* read a line */
if(!prot_fgets(buf, sizeof(buf)-1, C->pin))
return;
p = buf + strlen(buf) - 1;
if(p >= buf && *p == '\n') *p-- = '\0';
if(p >= buf && *p == '\r') *p-- = '\0';
if(buf[0] == '*') {
prot_printf(C->pout,
"%s NO \"client canceled authentication\"\r\n",
tag);
reset_saslconn(C);
return;
}
len = strlen(buf);
in = xmalloc(len);
r = sasl_decode64(buf, len, in, len, &inlen);
if(r != SASL_OK) {
prot_printf(C->pout, "%s NO \"cannot base64 decode\"\r\n",tag);
free(in);
reset_saslconn(C);
return;
}
r = sasl_server_step(C->saslconn, in, inlen,
&out, &outlen);
free(in); in=NULL;
}
if(r != SASL_OK) {
sleep(3);
syslog(LOG_ERR, "badlogin: %s %s %s",
C->clienthost,
mech, sasl_errdetail(C->saslconn));
prot_printf(C->pout, "%s NO \"%s\"\r\n", tag,
sasl_errstring((r == SASL_NOUSER ? SASL_BADAUTH : r),
NULL, NULL));
reset_saslconn(C);
return;
}
/* Successful Authentication */
r = sasl_getprop(C->saslconn, SASL_USERNAME, (const void **)&C->userid);
if(r != SASL_OK) {
prot_printf(C->pout, "%s NO \"SASL Error\"\r\n", tag);
reset_saslconn(C);
return;
}
syslog(LOG_NOTICE, "login: %s from %s",
C->userid, C->clienthost);
prot_printf(C->pout, "%s OK \"Authenticated\"\r\n", tag);
prot_setsasl(C->pin, C->saslconn);
prot_setsasl(C->pout, C->saslconn);
return;
}
void cmd_set(struct conn *C,
const char *tag, const char *mailbox,
const char *server, const char *acl, enum settype t)
{
struct mbent *m;
struct conn *upc;
syslog(LOG_DEBUG, "cmd_set(fd:%d, %s)", C->fd, mailbox);
pthread_mutex_lock(&mailboxes_mutex); /* LOCK */
m = database_lookup(mailbox, NULL);
if (m && t == SET_RESERVE) {
/* failed; mailbox already exists */
prot_printf(C->pout, "%s NO \"mailbox already exists\"\r\n", tag);
goto done;
}
if (t == SET_DELETE) {
if (!m) {
/* failed; mailbox doesn't exist */
prot_printf(C->pout, "%s NO \"mailbox doesn't exist\"\r\n", tag);
goto done;
}
/* do the deletion */
m->t = SET_DELETE;
} else {
if (m && (!acl || strlen(acl) < strlen(m->acl))) {
/* change what's already there */
strcpy(m->server, server);
if (acl) strcpy(m->acl, acl);
else m->acl[0] = '\0';
m->t = t;
} else {
struct mbent *newm;
/* allocate new mailbox */
if (acl) {
newm = xrealloc(m, sizeof(struct mbent) + strlen(acl));
} else {
newm = xrealloc(m, sizeof(struct mbent) + 1);
}
strcpy(newm->mailbox, mailbox);
strcpy(newm->server, server);
if (acl) {
strcpy(newm->acl, acl);
} else {
newm->acl[0] = '\0';
}
newm->t = t;
/* re-scope */
m = newm;
}
}
/* write to disk */
database_log(m);
/* post pending changes */
for (upc = updatelist; upc != NULL; upc = upc->updatelist_next) {
/* for each connection, add to pending list */
struct pending *p = (struct pending *) xmalloc(sizeof(struct pending));
strcpy(p->mailbox, mailbox);
p->t = t;
pthread_mutex_lock(&upc->m);
p->next = upc->plist;
upc->plist = p;
pthread_cond_signal(&upc->cond);
pthread_mutex_unlock(&upc->m);
}
prot_printf(C->pout, "%s OK \"done\"\r\n", tag);
done:
free_mbent(m);
pthread_mutex_unlock(&mailboxes_mutex); /* UNLOCK */
}
void cmd_find(struct conn *C, const char *tag, const char *mailbox, int dook)
{
struct mbent *m;
syslog(LOG_DEBUG, "cmd_find(fd:%d, %s)", C->fd, mailbox);
pthread_mutex_lock(&mailboxes_mutex); /* LOCK */
m = database_lookup(mailbox, NULL);
if (m && m->t == SET_ACTIVE) {
prot_printf(C->pout, "%s MAILBOX {%d+}\r\n%s {%d+}\r\n%s {%d+}\r\n%s\r\n",
tag,
strlen(m->mailbox), m->mailbox,
strlen(m->server), m->server,
strlen(m->acl), m->acl);
} else if (m && m->t == SET_RESERVE) {
prot_printf(C->pout, "%s RESERVE {%d+}\r\n%s {%d+}\r\n%s\r\n",
tag,
strlen(m->mailbox), m->mailbox,
strlen(m->server), m->server);
} else {
/* no output: not found */
}
free_mbent(m);
pthread_mutex_unlock(&mailboxes_mutex); /* UNLOCK */
if (dook) {
prot_printf(C->pout, "%s OK \"Search completed\"\r\n", tag);
}
}
/* Callback for cmd_startupdate to be passed to mboxlist_findall. */
/* Requires that C->streaming be set to the tag to respond with */
static int sendupdate(char *name, int matchlen, int maycreate, void *rock)
{
struct conn *C = (struct conn *)rock;
struct mbent *m;
if(!C) return -1;
m = database_lookup(name, NULL);
if(!m) return -1;
if(!C->list_prefix ||
!strncmp(m->server, C->list_prefix, C->list_prefix_len)) {
/* Either there is not a prefix to test, or we matched it */
switch (m->t) {
case SET_ACTIVE:
prot_printf(C->pout,
"%s MAILBOX {%d+}\r\n%s {%d+}\r\n%s {%d+}\r\n%s\r\n",
C->streaming,
strlen(m->mailbox), m->mailbox,
strlen(m->server), m->server,
strlen(m->acl), m->acl);
break;
case SET_RESERVE:
prot_printf(C->pout, "%s RESERVE {%d+}\r\n%s {%d+}\r\n%s\r\n",
C->streaming,
strlen(m->mailbox), m->mailbox,
strlen(m->server), m->server);
break;
case SET_DELETE:
/* deleted item in the list !?! */
abort();
}
}
free_mbent(m);
return 0;
}
void cmd_list(struct conn *C, const char *tag, const char *host_prefix)
{
char pattern[2] = {'*','\0'};
/* indicate interest in updates */
pthread_mutex_lock(&mailboxes_mutex); /* LOCK */
/* since this isn't valid when streaming, just use the same callback */
C->streaming = tag;
C->list_prefix = host_prefix;
if(C->list_prefix) C->list_prefix_len = strlen(C->list_prefix);
else C->list_prefix_len = 0;
mboxlist_findall(NULL, pattern, 1, NULL,
NULL, sendupdate, (void*)C);
C->streaming = NULL;
pthread_mutex_unlock(&mailboxes_mutex); /* UNLOCK */
}
/*
* we've registered this connection for streaming, and every X seconds
* this will be invoked. note that we always send out updates as soon
* as we get a noop: that resets this counter back */
struct prot_waitevent *sendupdates_evt(struct protstream *s,
struct prot_waitevent *ev,
void *rock)
{
struct conn *C = (struct conn *) rock;
sendupdates(C, 1);
/* 'sendupdates()' will update when we next trigger */
return ev;
}
void cmd_startupdate(struct conn *C, const char *tag)
{
char pattern[2] = {'*','\0'};
/* initialize my condition variable */
pthread_cond_init(&C->cond, NULL);
/* indicate interest in updates */
pthread_mutex_lock(&mailboxes_mutex); /* LOCK */
C->updatelist_next = updatelist;
updatelist = C;
C->streaming = xstrdup(tag);
/* dump initial list */
mboxlist_findall(NULL, pattern, 1, NULL,
NULL, sendupdate, (void*)C);
pthread_mutex_unlock(&mailboxes_mutex); /* UNLOCK */
prot_printf(C->pout, "%s OK \"streaming starts\"\r\n", tag);
/* schedule our first update */
C->ev = prot_addwaitevent(C->pin, time(NULL) + update_wait,
sendupdates_evt, C);
}
/* send out any pending updates.
if 'flushnow' is set, flush the output buffer */
void sendupdates(struct conn *C, int flushnow)
{
struct pending *p, *q;
pthread_mutex_lock(&C->m);
/* just grab the update list and release the lock */
p = C->plist;
C->plist = NULL;
pthread_mutex_unlock(&C->m);
while (p != NULL) {
/* send update */
q = p;
p = p->next;
if (q->t == SET_DELETE) {
prot_printf(C->pout, "%s DELETE {%d+}\r\n%s\r\n",
C->streaming, strlen(q->mailbox), q->mailbox);
} else {
/* notify just like a FIND */
cmd_find(C, C->streaming, q->mailbox, 0);
}
free(q);
}
/* reschedule event for 'update_wait' seconds */
C->ev->mark = time(NULL) + update_wait;
if (flushnow) {
prot_flush(C->pout);
}
}
void shut_down(int code) __attribute__((noreturn));
void shut_down(int code)
{
exit(code);
}
/* Reset the given sasl_conn_t to a sane state */
static int reset_saslconn(struct conn *c)
{
int ret, secflags, plaintext_result;
sasl_security_properties_t *secprops = NULL;
sasl_dispose(&c->saslconn);
/* do initialization typical of service_main */
ret = sasl_server_new("mupdate", config_servername,
NULL, NULL, NULL,
NULL, 0, &c->saslconn);
if(ret != SASL_OK) return ret;
if(c->saslprops.ipremoteport)
ret = sasl_setprop(c->saslconn, SASL_IPREMOTEPORT,
c->saslprops.ipremoteport);
if(ret != SASL_OK) return ret;
if(c->saslprops.iplocalport)
ret = sasl_setprop(c->saslconn, SASL_IPLOCALPORT,
c->saslprops.iplocalport);
if(ret != SASL_OK) return ret;
secflags = SASL_SEC_NOANONYMOUS;
plaintext_result = config_getswitch("allowplaintext",1);
if (!config_getswitch("mupdate_allowplaintext", plaintext_result)) {
secflags |= SASL_SEC_NOPLAINTEXT;
}
secprops = mysasl_secprops(secflags);
ret = sasl_setprop(c->saslconn, SASL_SEC_PROPS, secprops);
if(ret != SASL_OK) return ret;
/* end of service_main initialization excepting SSF */
return SASL_OK;
}
int cmd_change(struct mupdate_mailboxdata *mdata,
const char *rock, void *context)
{
struct mbent *m = NULL;
struct conn *upc = NULL;
enum settype t = -1;
int ret = 0;
if(!mdata || !rock || !mdata->mailbox) return 1;
pthread_mutex_lock(&mailboxes_mutex); /* LOCK */
if(!strncmp(rock, "DELETE", 6)) {
m = database_lookup(mdata->mailbox, NULL);
if(!m) {
syslog(LOG_DEBUG, "attempt to delete unknown mailbox %s",
mdata->mailbox);
/* Mailbox doesn't exist */
ret = -1;
goto done;
}
m->t = t = SET_DELETE;
} else {
m = database_lookup(mdata->mailbox, NULL);
if (m && (!mdata->acl || strlen(mdata->acl) < strlen(m->acl))) {
/* change what's already there */
free(m->server);
m->server = xstrdup(mdata->server);
if (mdata->acl) strcpy(m->acl, mdata->acl);
else m->acl[0] = '\0';
if(!strncmp(rock, "MAILBOX", 6)) {
m->t = t = SET_ACTIVE;
} else if(!strncmp(rock, "RESERVE", 7)) {
m->t = t = SET_RESERVE;
} else {
syslog(LOG_DEBUG,
"bad mupdate command in cmd_change: %s", rock);
ret = 1;
goto done;
}
} else {
struct mbent *newm;
if(m) {
free(m->mailbox);
free(m->server);
}
/* allocate new mailbox */
if (mdata->acl) {
newm = xrealloc(m, sizeof(struct mbent) + strlen(mdata->acl));
} else {
newm = xrealloc(m, sizeof(struct mbent) + 1);
}
newm->mailbox = xstrdup(mdata->mailbox);
newm->server = xstrdup(mdata->server);
if (mdata->acl) {
strcpy(newm->acl, mdata->acl);
} else {
newm->acl[0] = '\0';
}
if(!strncmp(rock, "MAILBOX", 6)) {
newm->t = t = SET_ACTIVE;
} else if(!strncmp(rock, "RESERVE", 7)) {
newm->t = t = SET_RESERVE;
} else {
syslog(LOG_DEBUG,
"bad mupdate command in cmd_change: %s", rock);
ret = 1;
goto done;
}
/* Bring it back into scope */
m = newm;
}
}
/* write to disk */
database_log(m);
/* post pending changes to anyone we are talking to */
for (upc = updatelist; upc != NULL; upc = upc->updatelist_next) {
/* for each connection, add to pending list */
struct pending *p = (struct pending *) xmalloc(sizeof(struct pending));
strcpy(p->mailbox, mdata->mailbox);
p->t = t;
pthread_mutex_lock(&upc->m);
p->next = upc->plist;
upc->plist = p;
pthread_cond_signal(&upc->cond);
pthread_mutex_unlock(&upc->m);
}
done:
free_mbent(m);
pthread_mutex_unlock(&mailboxes_mutex); /* UNLOCK */
return ret;
}
struct sync_rock
{
struct mpool *pool;
struct mbent_queue *boxes;
};
/* Read a series of MAILBOX and RESERVE commands and tack them onto a
* queue */
int cmd_resync(struct mupdate_mailboxdata *mdata,
const char *rock, void *context)
{
struct sync_rock *r = (struct sync_rock *)context;
struct mbent_queue *remote_boxes = r->boxes;
struct mbent *newm = NULL;
if(!mdata || !rock || !mdata->mailbox || !remote_boxes) return 1;
/* allocate new mailbox */
if (mdata->acl) {
newm = mpool_malloc(r->pool,sizeof(struct mbent) + strlen(mdata->acl));
} else {
newm = mpool_malloc(r->pool,sizeof(struct mbent) + 1);
}
newm->mailbox = mpool_strdup(r->pool, mdata->mailbox);
newm->server = mpool_strdup(r->pool, mdata->server);
if (mdata->acl) {
strcpy(newm->acl, mdata->acl);
} else {
newm->acl[0] = '\0';
}
if(!strncmp(rock, "MAILBOX", 6)) {
newm->t = SET_ACTIVE;
} else if(!strncmp(rock, "RESERVE", 7)) {
newm->t = SET_RESERVE;
} else {
syslog(LOG_NOTICE,
"bad mupdate command in cmd_resync: %s", rock);
return 1;
}
/* Insert onto queue */
newm->next = NULL;
*(remote_boxes->tail) = newm;
remote_boxes->tail = &(newm->next);
return 0;
}
/* Callback for mupdate_synchronize to be passed to mboxlist_findall. */
static int sync_findall_cb(char *name, int matchlen, int maycreate, void *rock)
{
struct sync_rock *r = (struct sync_rock *)rock;
struct mbent_queue *local_boxes = (struct mbent_queue *)r->boxes;
struct mbent *m;
if(!local_boxes) return 1;
m = database_lookup(name, r->pool);
/* If it doesn't exist, fine... */
if(!m) return 0;
m->next = NULL;
*(local_boxes->tail) = m;
local_boxes->tail = &(m->next);
return 0;
}
int mupdate_synchronize(mupdate_handle *handle)
{
struct mbent_queue local_boxes;
struct mbent_queue remote_boxes;
struct mbent *l,*r;
struct mpool *pool;
struct sync_rock rock;
char pattern[] = { '*', '\0' };
if(!handle || !handle->saslcompleted) return 1;
pool = new_mpool(131072); /* Arbitrary, but large (128k) */
rock.pool = pool;
/* ask for updates and set nonblocking */
prot_printf(handle->pout, "U01 UPDATE\r\n");
/* Note that this prevents other people from running an UPDATE against
* us for the duration. this is a GOOD THING */
pthread_mutex_lock(&mailboxes_mutex); /* LOCK */
syslog(LOG_NOTICE,
"synchronizing mailbox list with master mupdate server");
local_boxes.head = NULL;
local_boxes.tail = &(local_boxes.head);
remote_boxes.head = NULL;
remote_boxes.tail = &(remote_boxes.head);
rock.boxes = &remote_boxes;
/* If there is a fatal error, die, other errors ignore */
if (mupdate_scarf(handle, cmd_resync, &rock, 1, NULL) != 0) {
struct mbent *p=remote_boxes.head, *p_next=NULL;
while(p) {
p_next = p->next;
p = p_next;
}
pthread_mutex_unlock(&mailboxes_mutex);
free_mpool(pool);
return 1;
}
/* Make socket nonblocking now */
prot_NONBLOCK(handle->pin);
rock.boxes = &local_boxes;
mboxlist_findall(NULL, pattern, 1, NULL,
NULL, sync_findall_cb, (void*)&rock);
/* Traverse both lists, compare the names */
/* If they match, ensure that server and acl are correct, if so,
move on, if not, fix them */
/* If the local is before the next remote, delete it */
/* If the next remote is before theis local, insert it and try again */
for(l = local_boxes.head, r = remote_boxes.head; l && r;
l = local_boxes.head, r = remote_boxes.head)
{
int ret = strcmp(l->mailbox, r->mailbox);
if(!ret) {
/* Match */
if(l->t != r->t ||
strcmp(l->server, r->server) ||
strcmp(l->acl,r->acl)) {
/* Something didn't match, replace it */
mboxlist_insertremote(r->mailbox,
(r->t == SET_RESERVE ?
MBTYPE_RESERVE : 0),
r->server, r->acl, NULL);
}
/* Okay, dump these two */
local_boxes.head = l->next;
remote_boxes.head = r->next;
} else if (ret < 0) {
/* Local without corresponding remote, delete it */
mboxlist_deletemailbox(l->mailbox, 1, "", NULL, 0);
local_boxes.head = l->next;
} else /* (ret > 0) */ {
/* Remote without corresponding local, insert it */
mboxlist_insertremote(r->mailbox,
(r->t == SET_RESERVE ?
MBTYPE_RESERVE : 0),
r->server, r->acl, NULL);
remote_boxes.head = r->next;
}
}
if(l && !r) {
/* we have more deletes to do */
while(l) {
mboxlist_deletemailbox(l->mailbox, 1, "", NULL, 0);
local_boxes.head = l->next;
l = local_boxes.head;
}
} else if (r && !l) {
/* we have more inserts to do */
while(r) {
mboxlist_insertremote(r->mailbox,
(r->t == SET_RESERVE ?
MBTYPE_RESERVE : 0),
r->server, r->acl, NULL);
remote_boxes.head = r->next;
r = remote_boxes.head;
}
}
/* All up to date! */
syslog(LOG_NOTICE, "mailbox list synchronization complete");
pthread_mutex_unlock(&mailboxes_mutex); /* UNLOCK */
free_mpool(pool);
return 0;
}
void mupdate_signal_db_synced(void)
{
pthread_mutex_lock(&synced_mutex);
synced = 1;
pthread_cond_broadcast(&synced_cond);
pthread_mutex_unlock(&synced_mutex);
}
void mupdate_ready(void)
{
if(ready_for_connections) {
syslog(LOG_CRIT, "mupdate_ready called when already ready");
fatal("mupdate_ready called when already ready", EC_TEMPFAIL);
}
pthread_mutex_lock(&ready_for_connections_mutex);
ready_for_connections = 1;
pthread_cond_broadcast(&ready_for_connections_cond);
pthread_mutex_unlock(&ready_for_connections_mutex);
}
void mupdate_unready(void)
{
pthread_mutex_lock(&ready_for_connections_mutex);
syslog(LOG_NOTICE, "unready for connections");
/* close all connections to us */
while(connlist) conn_free(connlist);
ready_for_connections = 0;
pthread_mutex_unlock(&ready_for_connections_mutex);
}
/* Used to free malloc'd mbent's */
void free_mbent(struct mbent *p)
{
if(!p) return;
free(p->server);
free(p->mailbox);
free(p);
}

File Metadata

Mime Type
text/x-diff
Expires
Sat, Apr 4, 7:00 AM (1 w, 4 d ago)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
18822945
Default Alt Text
(63 KB)

Event Timeline