/* mupdate-client.c -- cyrus murder database clients * * $Id: mupdate-client.c,v 1.36 2003/06/04 03:27:52 rjs3 Exp $ * Copyright (c) 1998-2003 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 #include #include #include #include #include #include #include #include #ifdef HAVE_STDARG_H #include #else #include #endif #ifdef HAVE_UNISTD_H #include #endif #include #include #include #ifdef HAVE_SYS_SELECT_H #include #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); extern void free_callbacks(sasl_callback_t *in); 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 = PROT_BUFSIZE; 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_storage saddr_l; struct sockaddr_storage 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_storage); if (getpeername(h->sock,(struct sockaddr *)&saddr_r,&addrsize)!=0) return 1; addrsize=sizeof(struct sockaddr_storage); if (getsockname(h->sock,(struct sockaddr *)&saddr_l,&addrsize)!=0) return 1; if(iptostring((const struct sockaddr *)&saddr_l, addrsize, localip, 60) != 0) return 1; if(iptostring((const struct sockaddr *)&saddr_r, addrsize, 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)) { 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 = NULL; struct addrinfo hints, *res0, *res; int err; int local_cbs = 0; int s, saslresult; char buf[4096]; 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); } memset(&hints, 0, sizeof(hints)); hints.ai_family = PF_UNSPEC; hints.ai_socktype = SOCK_STREAM; err = getaddrinfo(server, port, &hints, &res0); if (err == EAI_SERVICE) { err = getaddrinfo(server, "mupdate", &hints, &res0); if (err == EAI_SERVICE) err = getaddrinfo(server, "2004", &hints, &res0); } if (err) { syslog(LOG_ERR, "mupdate-client: getaddrinfo(%s, %s) failed: %s", server, port, gai_strerror(err)); return MUPDATE_NOCONN; } s = -1; for (res = res0; res; res = res->ai_next) { s = socket(res->ai_family, res->ai_socktype, res->ai_protocol); if (s < 0) continue; if (connect(s, res->ai_addr, res->ai_addrlen) >= 0) break; close(s); s = -1; } freeaddrinfo(res0); if (s < 0) { syslog(LOG_ERR, "mupdate-client: connect(%s): %m", server); return MUPDATE_NOCONN; } h = xzmalloc(sizeof(mupdate_handle)); *handle = h; h->sock = s; if(!cbs) { local_cbs = 1; 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 mechlist & other capabilities */ while(1) { 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(!mechlist) { syslog(LOG_ERR, "no AUTH banner from remote"); mupdate_disconnect(handle); free_callbacks(cbs); return MUPDATE_NOAUTH; } if (mupdate_authenticate(h, mechlist)) { syslog(LOG_ERR, "authentication to remote mupdate server failed"); free(mechlist); mupdate_disconnect(handle); free_callbacks(cbs); return MUPDATE_NOAUTH; } free(mechlist); /* xxx unclear that this is correct, but it prevents a memory leak */ if(local_cbs) free_callbacks(cbs); /* 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; if(h->pout) { 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)); if(h->pin) prot_free(h->pin); if(h->pout) 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_RESERVE; } else { return 0; } } int mupdate_deactivate(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 DEACTIVATE {%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_RESERVE; } 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, sizeof(h->mailbox_buf)); strlcpy(h->server_buf, mdata->server, sizeof(h->server_buf)); 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); /* note that the response is still OK even if there was no data returned, * so we have to make sure we actually filled in the data too */ if (!ret && response == MUPDATE_OK && handle->mailboxdata_buf.mailbox) { *target = &(handle->mailboxdata_buf); return 0; } else if(!ret && response == MUPDATE_OK) { /* it looked okay, but we didn't get a mailbox */ *target = NULL; return MUPDATE_MAILBOX_UNKNOWN; } else { /* Something Bad happened */ *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; } } int mupdate_noop(mupdate_handle *handle, mupdate_callback callback, void *context) { int ret; enum mupdate_cmd_response response; if(!handle || !callback) return MUPDATE_BADPARAM; prot_printf(handle->pout, "X%u NOOP\r\n", handle->tagn++); ret = mupdate_scarf(handle, callback, context, 1, &response); if (!ret && response == MUPDATE_OK) { return 0; } else { return ret ? ret : MUPDATE_FAIL; } } #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"); 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; }