/*
* Copyright (c) 1994-2008 Carnegie Mellon University. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
*
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in
* the documentation and/or other materials provided with the
* distribution.
*
* 3. The name "Carnegie Mellon University" must not be used to
* endorse or promote products derived from this software without
* prior written permission. For permission or any legal
* details, please contact
* Carnegie Mellon University
* Center for Technology Transfer and Enterprise Creation
* 4615 Forbes Avenue
* Suite 302
* Pittsburgh, PA 15213
* (412) 268-7393, fax: (412) 268-7395
* innovation@andrew.cmu.edu
*
* 4. Redistributions of any form whatsoever must retain the following
* acknowledgment:
* "This product includes software developed by Computing Services
* at Carnegie Mellon University (http://www.cmu.edu/computing/)."
*
* CARNEGIE MELLON UNIVERSITY DISCLAIMS ALL WARRANTIES WITH REGARD TO
* THIS SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
* AND FITNESS, IN NO EVENT SHALL CARNEGIE MELLON UNIVERSITY BE LIABLE
* FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN
* AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING
* OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*
* $Id: smmapd.c,v 1.23 2010/01/06 17:01:40 murch Exp $
*
* smmapd.c -- sendmail socket map daemon
*
*
* From Sendmail Operations Guide:
*
* The socket map uses a simple request/reply protocol over TCP or
* UNIX domain sockets to query an external server. Both requests and
* replies are text based and encoded as netstrings, i.e., a string
* "hello there" becomes:
*
* 11:hello there,
*
* Note: neither requests nor replies end with CRLF.
*
* The request consists of the database map name and the lookup key
* separated by a space character:
*
* <mapname> ’ ’ <key>
*
* The server responds with a status indicator and the result (if any):
*
* <status> ’ ’ <result>
*
* The status indicator is one of the following upper case words:
*
* OK the key was found, result contains the looked up value
* NOTFOUND the key was not found, the result is empty
* TEMP a temporary failure occured
* TIMEOUT a timeout occured on the server side
* PERM a permanent failure occured
*
* In case of errors (status TEMP, TIMEOUT or PERM) the result field
* may contain an explanatory message.
*
*/
#include <config.h>
#ifdef HAVE_UNISTD_H
#include <unistd.h>
#endif
#include <stdio.h>
#include <string.h>
#include <syslog.h>
#include <signal.h>
#include <ctype.h>
#include "acl.h"
#include "append.h"
#include "exitcodes.h"
#include "global.h"
#include "imap_err.h"
#include "mboxlist.h"
#include "mupdate-client.h"
#include "proc.h"
#include "util.h"
#include "xmalloc.h"
#include "xstrlcpy.h"
#include "xstrlcat.h"
const char *BB;
int forcedowncase;
extern int optind;
struct protstream *map_in, *map_out;
/* current namespace */
static struct namespace map_namespace;
/* config.c info */
const int config_need_data = 0;
/* forward decls */
int begin_handling(void);
void smmapd_reset(void)
{
if (map_in) {
/* Flush the incoming buffer */
prot_NONBLOCK(map_in);
prot_fill(map_in);
prot_free(map_in);
}
if (map_out) {
/* Flush the outgoing buffer */
prot_flush(map_out);
prot_free(map_out);
}
map_in = map_out = NULL;
cyrus_reset_stdio();
}
void shut_down(int code) __attribute__((noreturn));
void shut_down(int code)
{
in_shutdown = 1;
smmapd_reset();
mboxlist_close();
mboxlist_done();
quotadb_close();
quotadb_done();
cyrus_done();
exit(code);
}
void fatal(const char* s, int code)
{
static int recurse_code = 0;
if (recurse_code) {
/* We were called recursively. Just give up */
exit(code);
}
recurse_code = code;
syslog(LOG_ERR, "Fatal error: %s", s);
abort();
shut_down(code);
}
/*
* 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;
if (geteuid() == 0) fatal("must run as the Cyrus user", EC_USAGE);
setproctitle_init(argc, argv, envp);
signals_set_shutdown(&shut_down);
signal(SIGPIPE, SIG_IGN);
BB = config_getstring(IMAPOPT_POSTUSER);
forcedowncase = config_getswitch(IMAPOPT_LMTP_DOWNCASE_RCPT);
/* so we can do mboxlist operations */
mboxlist_init(0);
mboxlist_open(NULL);
/* so we can check the quotas */
quotadb_init(0);
quotadb_open(NULL);
/* Set namespace */
if ((r = mboxname_init_namespace(&map_namespace, 1)) != 0) {
syslog(LOG_ERR, "%s", error_message(r));
fatal(error_message(r), EC_CONFIG);
}
return 0;
}
/* Called by service API to shut down the service */
void service_abort(int error)
{
shut_down(error);
}
int service_main(int argc __attribute__((unused)),
char **argv __attribute__((unused)),
char **envp __attribute__((unused)))
{
map_in = prot_new(0, 0);
map_out = prot_new(1, 1);
prot_setflushonread(map_in, map_out);
prot_settimeout(map_in, 360);
if (begin_handling() != 0) shut_down(0);
/* prepare for new connection */
smmapd_reset();
return 0;
}
int verify_user(const char *key, long quotacheck,
struct auth_state *authstate)
{
char rcpt[MAX_MAILBOX_BUFFER], namebuf[MAX_MAILBOX_BUFFER] = "";
char *user = rcpt, *domain = NULL, *mailbox = NULL;
int r = 0;
/* make a working copy of the key and split it into user/domain/mailbox */
strlcpy(rcpt, key, sizeof(rcpt));
/* find domain */
if (config_virtdomains && (domain = strrchr(rcpt, '@'))) {
*domain++ = '\0';
/* ignore default domain */
if (config_defdomain && !strcasecmp(config_defdomain, domain))
domain = NULL;
}
/* translate any separators in user & mailbox */
mboxname_hiersep_tointernal(&map_namespace, rcpt, 0);
/* find mailbox */
if ((mailbox = strchr(rcpt, '+'))) *mailbox++ = '\0';
/* downcase the rcpt, if necessary */
if (forcedowncase) {
lcase(user);
if (domain) lcase(domain);
}
/* see if its a shared mailbox address */
if (!strcmp(user, BB)) user = NULL;
/* XXX the following is borrowed from lmtpd.c:verify_user() */
if ((!user && !mailbox) ||
(domain && (strlen(domain) + 1 > sizeof(namebuf)))) {
r = IMAP_MAILBOX_NONEXISTENT;
} else {
/* construct the mailbox that we will verify */
if (domain) snprintf(namebuf, sizeof(namebuf), "%s!", domain);
if (!user) {
/* shared folder */
if (strlen(namebuf) + strlen(mailbox) > sizeof(namebuf)) {
r = IMAP_MAILBOX_NONEXISTENT;
} else {
strlcat(namebuf, mailbox, sizeof(namebuf));
}
} else {
/* ordinary user -- check INBOX */
if (strlen(namebuf) + 5 + strlen(user) > sizeof(namebuf)) {
r = IMAP_MAILBOX_NONEXISTENT;
} else {
strlcat(namebuf, "user.", sizeof(namebuf));
strlcat(namebuf, user, sizeof(namebuf));
}
}
}
if (!r) {
long aclcheck = !user ? ACL_POST : 0;
struct mboxlist_entry mbentry;
char *c;
struct hostent *hp;
char *host;
struct sockaddr_in sin,sfrom;
char buf[512];
int soc, rc;
socklen_t x;
/*
* check to see if mailbox exists and we can append to it:
*
* - must have posting privileges on shared folders
* - don't care about ACL on INBOX (always allow post)
* - don't care about message size (1 msg over quota allowed)
*/
r = mboxlist_lookup(namebuf, &mbentry, NULL);
if (r == IMAP_MAILBOX_NONEXISTENT && config_mupdate_server) {
kick_mupdate();
r = mboxlist_lookup(namebuf, &mbentry, NULL);
}
if (!r && (mbentry.mbtype & MBTYPE_REMOTE)) {
/* XXX Perhaps we should support the VRFY command in lmtpd
* and then we could do a VRFY to the correct backend which
* would also do a quotacheck.
*/
int access = cyrus_acl_myrights(authstate, mbentry.acl);
if ((access & aclcheck) != aclcheck) {
r = (access & ACL_LOOKUP) ?
IMAP_PERMISSION_DENIED : IMAP_MAILBOX_NONEXISTENT;
return r;
} else {
r = 0;
}
/* proxy the request to the real backend server to
* check the quota. if this fails, just return 0
* (asuume under quota)
*/
host = xstrdup(mbentry.partition);
c = strchr(host, '!');
if (c) *c = 0;
syslog(LOG_ERR, "verify_user(%s) proxying to host %s",
namebuf, host);
hp = gethostbyname(host);
if (hp == (struct hostent*) 0) {
syslog(LOG_ERR, "verify_user(%s) failed: can't find host %s",
namebuf, host);
return r;
}
soc = socket(PF_INET, SOCK_STREAM, 0);
if (soc < 0) {
syslog(LOG_ERR, "verify_user(%s) failed: can't connect to %s",
namebuf, host);
free(host);
return r;
}
memcpy(&sin.sin_addr.s_addr,hp->h_addr,hp->h_length);
sin.sin_family = AF_INET;
/* XXX port should be configurable */
sin.sin_port = htons(12345);
if (connect(soc,(struct sockaddr *) &sin, sizeof(sin)) < 0) {
syslog(LOG_ERR, "verify_user(%s) failed: can't connect to %s",
namebuf, host);
free(host);
return r;
}
sprintf(buf,SIZE_T_FMT ":cyrus %s,%c",strlen(key)+6,key,4);
sendto(soc,buf,strlen(buf),0,(struct sockaddr *)&sin,sizeof(sin));
x = sizeof(sfrom);
rc = recvfrom(soc,buf,512,0,(struct sockaddr *)&sfrom,&x);
close(soc);
if (rc >= 0) {
buf[rc] = '\0';
prot_printf(map_out, "%s", buf);
}
free(host);
return -1; /* tell calling function we already replied */
} else if (!r) {
r = append_check(namebuf, authstate,
aclcheck, (quotacheck < 0 )
|| config_getswitch(IMAPOPT_LMTP_STRICT_QUOTA) ?
quotacheck : 0);
}
}
if (r) syslog(LOG_DEBUG, "verify_user(%s) failed: %s", namebuf,
error_message(r));
return r;
}
/*
* begin_handling: handle requests on a single connection.
* returns non-zero if requested to stop handling new connections (SIGHUP)
*/
#define MAXREQUEST 1024 /* XXX is this reasonable? */
int begin_handling(void)
{
int c;
while ((c = prot_getc(map_in)) != EOF) {
int r = 0, len = 0, size = 0;
struct auth_state *authstate = NULL;
char request[MAXREQUEST+1];
char *mapname = NULL, *key = NULL;
const char *errstring = NULL;
if (signals_poll() == SIGHUP) {
/* caught a SIGHUP, return */
return 1;
}
prot_ungetc(c, map_in);
c = getint32(map_in, &len);
if (c == EOF) {
errstring = prot_error(map_in);
r = IMAP_IOERROR;
}
if (len == -1 || c != ':') {
errstring = "missing length";
r = IMAP_PROTOCOL_ERROR;
}
if (!r && prot_read(map_in, request, len) != len) {
errstring = "request size doesn't match length";
r = IMAP_PROTOCOL_ERROR;
}
if (!r && (c = prot_getc(map_in)) != ',') {
errstring = "missing terminator";
r = IMAP_PROTOCOL_ERROR;
}
if (!r) {
request[len] = '\0';
mapname = request;
if (!(key = strchr(request, ' '))) {
errstring = "missing key";
r = IMAP_PROTOCOL_ERROR;
}
}
if (!r) {
*key++ = '\0';
r = verify_user(key, size, authstate);
}
switch (r) {
case -1:
/* reply already sent */
break;
case 0:
prot_printf(map_out, SIZE_T_FMT ":OK %s,", 3+strlen(key), key);
break;
case IMAP_MAILBOX_NONEXISTENT:
prot_printf(map_out, SIZE_T_FMT ":NOTFOUND %s,",
9+strlen(error_message(r)), error_message(r));
break;
case IMAP_QUOTA_EXCEEDED:
if (!config_getswitch(IMAPOPT_LMTP_OVER_QUOTA_PERM_FAILURE)) {
prot_printf(map_out, SIZE_T_FMT ":TEMP %s,", strlen(error_message(r))+5,
error_message(r));
break;
}
/* fall through - permanent failure */
default:
if (errstring)
prot_printf(map_out, SIZE_T_FMT ":PERM %s (%s),",
5+strlen(error_message(r))+3+strlen(errstring),
error_message(r), errstring);
else
prot_printf(map_out, SIZE_T_FMT ":PERM %s,",
5+strlen(error_message(r)), error_message(r));
break;
}
}
return 0;
}
void printstring(const char *s __attribute__((unused)))
{
/* needed to link against annotate.o */
fatal("printstring() executed, but its not used for smmapd!",
EC_SOFTWARE);
}