diff --git a/imap/global.c b/imap/global.c index ffd286bd3..c47e71b8a 100644 --- a/imap/global.c +++ b/imap/global.c @@ -1,1132 +1,1133 @@ /* global.c -- Configuration routines * * 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. */ #include #include #include #include #include #include #include #include #include #include #include #ifdef HAVE_SSL #include #endif #if HAVE_UNISTD_H # include #endif #include "acl.h" #include "assert.h" #include "charset.h" #include "cyr_lock.h" #include "gmtoff.h" #include "iptostring.h" #include "global.h" #include "ical_support.h" #include "libconfig.h" #include "libcyr_cfg.h" #include "mboxlist.h" #include "mutex.h" #include "prot.h" /* for PROT_BUFSIZE */ #include "strarray.h" #include "userdeny.h" #include "util.h" #include "xmalloc.h" #include "xstrlcat.h" #include "xstrlcpy.h" /* generated headers are not necessarily in current directory */ #include "imap/http_err.h" #include "imap/imap_err.h" #include "imap/mupdate_err.h" static enum { NOT_RUNNING = 0, RUNNING = 1, DONE = 2 } cyrus_init_run = NOT_RUNNING; static int cyrus_init_nodb = 0; EXPORTED volatile sig_atomic_t in_shutdown = 0; EXPORTED int config_fulldirhash; /* 0 */ EXPORTED int config_implicitrights; /* "lkxa" */ EXPORTED unsigned long config_metapartition_files; /* 0 */ EXPORTED const char *config_mboxlist_db; EXPORTED const char *config_quota_db; EXPORTED const char *config_subscription_db; EXPORTED const char *config_annotation_db; EXPORTED const char *config_seenstate_db; HIDDEN const char *config_mboxkey_db; EXPORTED const char *config_duplicate_db; EXPORTED const char *config_tls_sessions_db; EXPORTED const char *config_ptscache_db; EXPORTED const char *config_statuscache_db; HIDDEN const char *config_userdeny_db; EXPORTED const char *config_zoneinfo_db; EXPORTED const char *config_conversations_db; EXPORTED const char *config_backup_db; EXPORTED int charset_flags; EXPORTED int charset_snippet_flags; EXPORTED size_t config_search_maxsize; static char session_id_buf[MAX_SESSIONID_SIZE]; static int session_id_time = 0; static int session_id_count = 0; static strarray_t *suppressed_capabilities = NULL; static int get_facility(const char *name) { if (!strcasecmp(name, "DAEMON")) return LOG_DAEMON; if (!strcasecmp(name, "MAIL")) return LOG_MAIL; if (!strcasecmp(name, "NEWS")) return LOG_NEWS; if (!strcasecmp(name, "USER")) return LOG_USER; if (!strcasecmp(name, "LOCAL0")) return LOG_LOCAL0; if (!strcasecmp(name, "LOCAL1")) return LOG_LOCAL1; if (!strcasecmp(name, "LOCAL2")) return LOG_LOCAL2; if (!strcasecmp(name, "LOCAL3")) return LOG_LOCAL3; if (!strcasecmp(name, "LOCAL4")) return LOG_LOCAL4; if (!strcasecmp(name, "LOCAL5")) return LOG_LOCAL5; if (!strcasecmp(name, "LOCAL6")) return LOG_LOCAL6; if (!strcasecmp(name, "LOCAL7")) return LOG_LOCAL7; /* fall back to the default. This will work because we already have this log open when we call out */ syslog(LOG_ERR, "config error: syslog name %s not recognised", name); return SYSLOG_FACILITY; } struct cyrus_module { void (*done)(void *rock); void *rock; }; static ptrarray_t cyrus_modules = PTRARRAY_INITIALIZER; EXPORTED void cyrus_modules_add(void (*done)(void*), void *rock) { struct cyrus_module *cm = xmalloc(sizeof(struct cyrus_module)); cm->done = done; cm->rock = rock; ptrarray_append(&cyrus_modules, cm); } static void cyrus_modules_done() { struct cyrus_module *cm; while ((cm = ptrarray_pop(&cyrus_modules))) { cm->done(cm->rock); free(cm); } } /* Called before a cyrus application starts (but after command line parameters * are read) */ EXPORTED int cyrus_init(const char *alt_config, const char *ident, unsigned flags, int config_need_data) { char *p; const char *val; const char *prefix; int umaskval = 0; int syslog_opts = LOG_PID; const char *facility; char *ident_buf = NULL; if (cyrus_init_run != NOT_RUNNING) { fatal("cyrus_init called twice!", EX_CONFIG); } else { cyrus_init_run = RUNNING; } cyrus_init_nodb = (flags & CYRUSINIT_NODB); #ifdef LOG_PERROR if ((flags & CYRUSINIT_PERROR)) syslog_opts |= LOG_PERROR; #endif + syslog_opts |= LOG_PERROR; initialize_http_error_table(); initialize_imap_error_table(); initialize_mupd_error_table(); /* various things can run our commands with only two file descriptors, e.g. old strace on FreeBSD, * or IPC::Run from Perl. Make sure we don't accidentally reuse low FD numbers */ while (1) { int fd = open("/dev/null", 0); if (fd == -1) fatal("can't open /dev/null", EX_SOFTWARE); if (fd >= 3) { close(fd); break; } } if (!ident) fatal("service name was not specified to cyrus_init", EX_SOFTWARE); config_ident = ident; /* If we have the syslog prefix in the environment, set it before reading * config, so that config read failures get logged with the right prefix! */ if ((prefix = getenv("CYRUS_SYSLOG_PREFIX"))) { ident_buf = strconcat(prefix, "/", ident, NULL); openlog(ident_buf, syslog_opts, SYSLOG_FACILITY); } else { openlog(config_ident, syslog_opts, SYSLOG_FACILITY); } /* Load configuration file. This will set config_dir when it finds it */ config_read(alt_config, config_need_data); /* changed user if needed */ if ((geteuid()) == 0 && (become_cyrus(/*is_master*/0) != 0)) { fatal("must run as the Cyrus user", EX_USAGE); } /* Now that we have config loaded, we might need to openlog again with * the configured facility and/or prefix. */ prefix = config_getstring(IMAPOPT_SYSLOG_PREFIX); facility = config_getstring(IMAPOPT_SYSLOG_FACILITY); if (prefix || facility) { int facnum = facility ? get_facility(facility) : SYSLOG_FACILITY; /* The $CYRUS_SYSLOG_PREFIX environment variable takes precedence */ if (!ident_buf) { if (prefix) ident_buf = strconcat(prefix, "/", ident, NULL); else ident_buf = xstrdup(ident); } closelog(); openlog(ident_buf, syslog_opts, facnum); } /* Do not free ident_buf, syslog needs it for the life of this process! */ /* allow debug logging */ if (!config_debug) setlogmask(~LOG_MASK(LOG_DEBUG)); /* Look up default partition */ config_defpartition = config_getstring(IMAPOPT_DEFAULTPARTITION); for (p = (char *)config_defpartition; p && *p; p++) { if (!Uisalnum(*p)) fatal("defaultpartition option contains non-alphanumeric character", EX_CONFIG); if (Uisupper(*p)) *p = tolower((unsigned char) *p); } /* Look up umask */ val = config_getstring(IMAPOPT_UMASK); while (*val) { if (*val >= '0' && *val <= '7') umaskval = umaskval*8 + *val - '0'; val++; } umask(umaskval); config_fulldirhash = config_getswitch(IMAPOPT_FULLDIRHASH); /* look up and canonify the implicit rights of mailbox owners */ cyrus_acl_strtomask(config_getstring(IMAPOPT_IMPLICIT_OWNER_RIGHTS), &config_implicitrights); /* XXX and if strtomask fails? */ config_metapartition_files = config_getbitfield(IMAPOPT_METAPARTITION_FILES); val = config_getstring(IMAPOPT_SUPPRESS_CAPABILITIES); if (val) suppressed_capabilities = strarray_split(val, NULL, 0); if (config_getswitch(IMAPOPT_SEARCH_SKIPDIACRIT)) charset_flags |= CHARSET_SKIPDIACRIT; switch (config_getenum(IMAPOPT_SEARCH_WHITESPACE)) { case IMAP_ENUM_SEARCH_WHITESPACE_MERGE: charset_flags |= CHARSET_MERGESPACE; break; case IMAP_ENUM_SEARCH_WHITESPACE_SKIP: charset_flags |= CHARSET_SKIPSPACE; break; default: break; } if (config_getswitch(IMAPOPT_SEARCH_SKIPHTML)) charset_flags |= CHARSET_SKIPHTML; if (config_getswitch(IMAPOPT_RFC2047_UTF8)) charset_flags |= CHARSET_MIME_UTF8; /* Set snippet conversion flags. */ charset_snippet_flags = CHARSET_KEEPCASE; if (config_getenum(IMAPOPT_SEARCH_ENGINE) != IMAP_ENUM_SEARCH_ENGINE_XAPIAN) { /* All search engines other than Xapian require escaped HTML */ charset_snippet_flags |= CHARSET_ESCAPEHTML; } config_search_maxsize = 1024 * config_getint(IMAPOPT_SEARCH_MAXSIZE); if (!cyrus_init_nodb) { /* lookup the database backends */ config_mboxlist_db = config_getstring(IMAPOPT_MBOXLIST_DB); config_quota_db = config_getstring(IMAPOPT_QUOTA_DB); config_subscription_db = config_getstring(IMAPOPT_SUBSCRIPTION_DB); config_annotation_db = config_getstring(IMAPOPT_ANNOTATION_DB); config_seenstate_db = config_getstring(IMAPOPT_SEENSTATE_DB); config_mboxkey_db = config_getstring(IMAPOPT_MBOXKEY_DB); config_duplicate_db = config_getstring(IMAPOPT_DUPLICATE_DB); config_tls_sessions_db = config_getstring(IMAPOPT_TLS_SESSIONS_DB); config_ptscache_db = config_getstring(IMAPOPT_PTSCACHE_DB); config_statuscache_db = config_getstring(IMAPOPT_STATUSCACHE_DB); config_userdeny_db = config_getstring(IMAPOPT_USERDENY_DB); config_zoneinfo_db = config_getstring(IMAPOPT_ZONEINFO_DB); config_conversations_db = config_getstring(IMAPOPT_CONVERSATIONS_DB); config_backup_db = config_getstring(IMAPOPT_BACKUP_DB); /* configure libcyrus as needed */ libcyrus_config_setstring(CYRUSOPT_CONFIG_DIR, config_dir); libcyrus_config_setswitch(CYRUSOPT_AUTH_UNIX_GROUP_ENABLE, config_getswitch(IMAPOPT_UNIX_GROUP_ENABLE)); libcyrus_config_setswitch(CYRUSOPT_USERNAME_TOLOWER, config_getswitch(IMAPOPT_USERNAME_TOLOWER)); libcyrus_config_setswitch(CYRUSOPT_SKIPLIST_UNSAFE, config_getswitch(IMAPOPT_SKIPLIST_UNSAFE)); libcyrus_config_setstring(CYRUSOPT_TEMP_PATH, config_getstring(IMAPOPT_TEMP_PATH)); libcyrus_config_setint(CYRUSOPT_PTS_CACHE_TIMEOUT, /* <-- n.b. still an int */ config_getduration(IMAPOPT_PTSCACHE_TIMEOUT, 's')); libcyrus_config_setswitch(CYRUSOPT_FULLDIRHASH, config_getswitch(IMAPOPT_FULLDIRHASH)); libcyrus_config_setstring(CYRUSOPT_PTSCACHE_DB, config_getstring(IMAPOPT_PTSCACHE_DB)); libcyrus_config_setstring(CYRUSOPT_PTSCACHE_DB_PATH, config_getstring(IMAPOPT_PTSCACHE_DB_PATH)); libcyrus_config_setstring(CYRUSOPT_PTLOADER_SOCK, config_getstring(IMAPOPT_PTLOADER_SOCK)); libcyrus_config_setswitch(CYRUSOPT_VIRTDOMAINS, config_getenum(IMAPOPT_VIRTDOMAINS)); libcyrus_config_setstring(CYRUSOPT_AUTH_MECH, config_getstring(IMAPOPT_AUTH_MECH)); libcyrus_config_setstring(CYRUSOPT_DELETERIGHT, config_getstring(IMAPOPT_DELETERIGHT)); libcyrus_config_setstring(CYRUSOPT_SQL_DATABASE, config_getstring(IMAPOPT_SQL_DATABASE)); libcyrus_config_setstring(CYRUSOPT_SQL_ENGINE, config_getstring(IMAPOPT_SQL_ENGINE)); libcyrus_config_setstring(CYRUSOPT_SQL_HOSTNAMES, config_getstring(IMAPOPT_SQL_HOSTNAMES)); libcyrus_config_setstring(CYRUSOPT_SQL_USER, config_getstring(IMAPOPT_SQL_USER)); libcyrus_config_setstring(CYRUSOPT_SQL_PASSWD, config_getstring(IMAPOPT_SQL_PASSWD)); libcyrus_config_setswitch(CYRUSOPT_SQL_USESSL, config_getswitch(IMAPOPT_SQL_USESSL)); libcyrus_config_setswitch(CYRUSOPT_SKIPLIST_ALWAYS_CHECKPOINT, config_getswitch(IMAPOPT_SKIPLIST_ALWAYS_CHECKPOINT)); libcyrus_config_setswitch(CYRUSOPT_ACL_ADMIN_IMPLIES_WRITE, config_getswitch(IMAPOPT_ACL_ADMIN_IMPLIES_WRITE)); /* Not until all configuration parameters are set! */ libcyrus_init(); } /* debug lock timing */ const char *locktime = config_getstring(IMAPOPT_LOCK_DEBUGTIME); if (locktime) { debug_locks_longer_than = atof(locktime); } #ifdef HAVE_ICAL /* Initialize libical */ ical_support_init(); #endif return 0; } EXPORTED void global_sasl_init(int client, int server, const sasl_callback_t *callbacks) { static int called_already = 0; assert(client || server); assert(!called_already); called_already = 1; /* set the SASL allocation functions */ sasl_set_alloc((sasl_malloc_t *) &xmalloc, (sasl_calloc_t *) &xcalloc, (sasl_realloc_t *) &xrealloc, (sasl_free_t *) &free); /* set the SASL mutex functions */ sasl_set_mutex((sasl_mutex_alloc_t *) &cyrus_mutex_alloc, (sasl_mutex_lock_t *) &cyrus_mutex_lock, (sasl_mutex_unlock_t *) &cyrus_mutex_unlock, (sasl_mutex_free_t *) &cyrus_mutex_free); if(client && sasl_client_init(callbacks)) { fatal("could not init sasl (client)", EX_SOFTWARE); } if(server && sasl_server_init(callbacks, "Cyrus")) { fatal("could not init sasl (server)", EX_SOFTWARE); } } /* this is a wrapper to call the cyrus configuration from SASL */ EXPORTED int mysasl_config(void *context __attribute__((unused)), const char *plugin_name, const char *option, const char **result, unsigned *len) { if (!strcmp(option, "srvtab")) { /* we don't transform srvtab! */ *result = config_getstring(IMAPOPT_SRVTAB); } else { *result = NULL; if (plugin_name) { /* first try it with the plugin name */ char *opt = strconcat("sasl_", plugin_name, "_", option, (char*)NULL); *result = config_getoverflowstring(opt, NULL); free(opt); } if (*result == NULL) { /* try without the plugin name */ char *opt = strconcat("sasl_", option, (char *)NULL); *result = config_getoverflowstring(opt, NULL); free(opt); } } if (*result != NULL) { if (len) { *len = strlen(*result); } return SASL_OK; } return SASL_FAIL; } /* This creates a structure that defines the allowable * security properties */ EXPORTED sasl_security_properties_t *mysasl_secprops(int flags) { static sasl_security_properties_t ret; ret.maxbufsize = PROT_BUFSIZE; ret.min_ssf = config_getint(IMAPOPT_SASL_MINIMUM_LAYER); /* minimum allowable security strength */ ret.max_ssf = config_getint(IMAPOPT_SASL_MAXIMUM_LAYER); /* maximum allowable security strength */ ret.security_flags = flags; if (!config_getswitch(IMAPOPT_ALLOWPLAINTEXT)) { ret.security_flags |= SASL_SEC_NOPLAINTEXT; } if (!config_getswitch(IMAPOPT_ALLOWANONYMOUSLOGIN)) { ret.security_flags |= SASL_SEC_NOANONYMOUS; } ret.property_names = NULL; ret.property_values = NULL; return &ret; } /* true if 'authstate' is in 'opt' */ EXPORTED int global_authisa(struct auth_state *authstate, enum imapopt opt) { char buf[1024]; const char *val = config_getstring(opt); size_t len; /* Is the option defined? */ if(!val) return 0; while (*val) { char *p; for (p = (char *) val; *p && !Uisspace(*p); p++); len = p-val; if(len >= sizeof(buf)) len = sizeof(buf) - 1; memcpy(buf, val, len); buf[len] = '\0'; if (auth_memberof(authstate, buf)) { return 1; } val = p; while (*val && Uisspace(*val)) val++; } return 0; } /* Note: This function is not idempotent! Only call it once for a given ID * or you will be unhappy (think IP hosting). */ EXPORTED const char *canonify_userid(char *user, const char *loginid, int *domain_from_ip) { char *domain = NULL; int len = strlen(user); char buf[81]; /* check for domain */ if (config_virtdomains && ((domain = strrchr(user, '@')) || (domain = strrchr(user, '%')))) { *domain = '@'; len = domain - user; } /* check for global identifiers */ if (is_userid_anonymous(user)) { return "anonymous"; } else if ((len == 7 && strncasecmp(user, "anybody", len) == 0) || (len == 6 && strncasecmp(user, "anyone", len) == 0)) { return "anyone"; } if (config_virtdomains) { if (domain) { if (config_defdomain && !strcasecmp(config_defdomain, domain+1)) { *domain = '\0'; /* trim the default domain */ } } else if (loginid) { /* used for LISTRIGHTS */ if ((domain = strrchr(loginid, '@'))) { /* append the domain from the login id */ snprintf(buf, sizeof(buf), "%s@%s", user, domain+1); user = buf; } } else if (config_virtdomains != IMAP_ENUM_VIRTDOMAINS_USERID) { socklen_t salen; int error; struct sockaddr_storage localaddr; char hbuf[NI_MAXHOST]; salen = sizeof(localaddr); if (getsockname(0, (struct sockaddr *)&localaddr, &salen) == 0) { error = getnameinfo((struct sockaddr *)&localaddr, salen, hbuf, sizeof(hbuf), NULL, 0, NI_NAMEREQD); if (error == 0 && (domain = strchr(hbuf, '.')) && !(config_defdomain && !strcasecmp(config_defdomain, domain+1))) { /* append the domain from our IP */ snprintf(buf, sizeof(buf), "%s@%s", user, domain+1); user = buf; if (domain_from_ip) *domain_from_ip = 1; } } } } return auth_canonifyid(user, 0); } EXPORTED int mysasl_canon_user(sasl_conn_t *conn, void *context, const char *user, unsigned ulen, unsigned flags __attribute__((unused)), const char *user_realm __attribute__((unused)), char *out, unsigned out_max, unsigned *out_ulen) { const char *canonuser = NULL; if (ulen+1 > out_max) { sasl_seterror(conn, 0, "buffer overflow while canonicalizing"); return SASL_BUFOVER; } if (out != user) { /* There are some paths through libsasl which result in our * being called with parameters 'user' and 'out' being the * same buffer. We can handle that case just fine, but this * memcpy() is redundant and causes a warning in Valgrind. */ memcpy(out, user, ulen); } out[ulen] = '\0'; canonuser = canonify_userid(out, NULL, (int*) context); if (!canonuser) { sasl_seterror(conn, 0, "bad userid authenticated"); return SASL_BADAUTH; } *out_ulen = strlen(canonuser); if (*out_ulen >= out_max) { sasl_seterror(conn, 0, "buffer overflow while canonicalizing"); return SASL_BUFOVER; } strcpy(out, canonuser); return SASL_OK; } EXPORTED int is_userid_anonymous(const char *user) { int len = strlen(user); const char *domain; assert(user); /* check for domain */ if (config_virtdomains && ((domain = strrchr(user, '@')) || (domain = strrchr(user, '%')))) { len = domain - user; } /* check if we are anonymous */ if (len == 9 && strncasecmp(user, "anonymous", len) == 0) { return 1; } else { return 0; } } /* * acl_ok() checks to see if the inbox for 'user' grants the 'a' * right to 'authstate'. Returns 1 if so, 0 if not. */ /* Note that we do not determine if the mailbox is remote or not */ static int acl_ok(const char *userid, struct auth_state *authstate) { mbentry_t *mbentry = NULL; char *inbox = mboxname_user_mbox(userid, NULL); int r = 0; if (authstate && !mboxlist_lookup(inbox, &mbentry, NULL)) { r = (cyrus_acl_myrights(authstate, mbentry->acl) & ACL_ADMIN) != 0; } mboxlist_entry_free(&mbentry); free(inbox); return r; } /* should we allow users to proxy? return SASL_OK if yes, SASL_BADAUTH otherwise */ EXPORTED int mysasl_proxy_policy(sasl_conn_t *conn, void *context, const char *requested_user, unsigned rlen, const char *auth_identity, unsigned alen, const char *def_realm __attribute__((unused)), unsigned urlen __attribute__((unused)), struct propctx *propctx __attribute__((unused))) { struct proxy_context *ctx = (struct proxy_context *) context; const char *val = config_getstring(IMAPOPT_LOGINREALMS); struct auth_state *authstate; int userisadmin = 0; char *realm; /* check if remote realm */ if ((!config_virtdomains || *val) && (realm = strchr(auth_identity, '@'))!=NULL) { realm++; while (*val) { if (!strncasecmp(val, realm, strlen(realm)) && (!val[strlen(realm)] || Uisspace(val[strlen(realm)]))) { break; } /* not this realm, try next one */ while (*val && !Uisspace(*val)) val++; while (*val && Uisspace(*val)) val++; } if (!*val) { sasl_seterror(conn, 0, "cross-realm login %s denied", auth_identity); return SASL_BADAUTH; } } authstate = auth_newstate(auth_identity); /* ok, is auth_identity an admin? */ userisadmin = global_authisa(authstate, IMAPOPT_ADMINS); if (!ctx) { /* for now only admins are allowed */ auth_freestate(authstate); if (!userisadmin) { syslog(LOG_ERR, "%s is not an admin", auth_identity); sasl_seterror(conn, SASL_NOLOG, "only admins may authenticate"); return SASL_BADAUTH; } return SASL_OK; } /* is requested_user denied access? authenticated admins are exempt */ if (!userisadmin && userdeny(requested_user, config_ident, NULL, 0)) { syslog(LOG_ERR, "user '%s' denied access to service '%s'", requested_user, config_ident); sasl_seterror(conn, SASL_NOLOG, "user '%s' is denied access to service '%s'", requested_user, config_ident); auth_freestate(authstate); return SASL_NOAUTHZ; } if (alen != rlen || strncmp(auth_identity, requested_user, alen)) { /* 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 = ctx->use_acl && config_getswitch(IMAPOPT_LOGINUSEACL); if (userisadmin || (use_acl && acl_ok(requested_user, authstate)) || (ctx->proxy_servers && global_authisa(authstate, IMAPOPT_PROXYSERVERS))) { /* proxy ok! */ userisadmin = 0; /* no longer admin */ auth_freestate(authstate); authstate = auth_newstate(requested_user); /* are we a proxy admin? */ if (ctx->userisproxyadmin) *(ctx->userisproxyadmin) = global_authisa(authstate, IMAPOPT_ADMINS); } else { sasl_seterror(conn, 0, "user %s is not allowed to proxy", auth_identity); auth_freestate(authstate); return SASL_BADAUTH; } } if (ctx->authstate) *(ctx->authstate) = authstate; else auth_freestate(authstate); if (ctx->userisadmin) *(ctx->userisadmin) = userisadmin; return SASL_OK; } /* call before a cyrus application exits */ EXPORTED void cyrus_done(void) { cyrus_modules_done(); if (cyrus_init_run != RUNNING) return; cyrus_init_run = DONE; if (!cyrus_init_nodb) libcyrus_done(); } /* * Returns 1 if we have a shutdown file, with the first line in buf. * Otherwise returns 0, and the contents of buf is undefined. */ EXPORTED int shutdown_file(char *buf, int size) { FILE *f; static char *shutdownfilename = NULL, *suffix; char *p; char tmpbuf[1024]; if (!shutdownfilename) { /* Create system shutdownfile name */ struct buf buf = BUF_INITIALIZER; size_t system_len; buf_printf(&buf, "%s/msg/shutdown", config_dir); system_len = buf_len(&buf); /* Add per-service suffix */ buf_printf(&buf, ".%s", config_ident); shutdownfilename = buf_release(&buf); suffix = shutdownfilename + system_len; } /* Try per-service shutdown file */ f = fopen(shutdownfilename, "r"); if (!f) { /* Trim per-service suffix and try system shutdownfile */ *suffix = '\0'; f = fopen(shutdownfilename, "r"); if (!f) { /* Restore per-service suffix */ *suffix = '.'; return 0; } } free(shutdownfilename); if (!buf) { buf = tmpbuf; size = sizeof(tmpbuf); } if (!fgets(buf, size, f)) { *buf = '\0'; syslog(LOG_DEBUG, "Shutdown file exists with no contents"); } else { if ((p = strchr(buf, '\r')) != NULL) *p = 0; if ((p = strchr(buf, '\n')) != NULL) *p = 0; syslog(LOG_DEBUG, "Shutdown file: %s, closing connection", buf); } fclose(f); return 1; } /* Set up the Session ID Buffer */ EXPORTED void session_new_id(void) { const char *base; int now = time(NULL); if (now != session_id_time) { session_id_time = now; session_id_count = 0; } ++session_id_count; base = config_getstring(IMAPOPT_SYSLOG_PREFIX); if (!base) base = config_servername; #ifdef HAVE_SSL unsigned long long random; RAND_bytes((unsigned char *) &random, sizeof(random)); snprintf(session_id_buf, MAX_SESSIONID_SIZE, "%.128s-%d-%d-%d-%llu", base, session_id_time, getpid(), session_id_count, random); #else snprintf(session_id_buf, MAX_SESSIONID_SIZE, "%.128s-%d-%d-%d", base, session_id_time, getpid(), session_id_count); #endif } /* Return the session id */ EXPORTED const char *session_id(void) { if (!session_id_count) session_new_id(); return (const char *)session_id_buf; } /* parse sessionid out of protocol answers */ EXPORTED void parse_sessionid(const char *str, char *sessionid) { char *sp, *ep; int len; if ((str) && (sp = strstr(str, "SESSIONID=<")) && (ep = strchr(sp, '>'))) { sp += 11; len = ep - sp; if (len < MAX_SESSIONID_SIZE) { strncpy(sessionid, sp, len); ep = sessionid + len; *ep = '\0'; } else strcpy(sessionid, "invalid"); } else strcpy(sessionid, "unknown"); } EXPORTED int capa_is_disabled(const char *str) { if (!suppressed_capabilities) return 0; return (strarray_find_case(suppressed_capabilities, str, 0) >= 0); } /* Find a message-id looking thingy in a string. Returns a pointer to the * alloc'd id and the remaining string is returned in the **loc parameter. * * This is a poor-man's way of finding the message-id. We simply look for * any string having the format "< ... @ ... >" and assume that the mail * client created a properly formatted message-id. */ #define MSGID_SPECIALS "<> @\\" EXPORTED char *find_msgid(char *str, char **rem) { char *msgid, *src, *dst, *cp; if (!str) return NULL; msgid = NULL; src = str; /* find the start of a msgid (don't go past the end of the header) */ while ((cp = src = strpbrk(src, "<\r")) != NULL) { /* check for fold or end of header * * Per RFC 2822 section 2.2.3, a long header may be folded by * inserting CRLF before any WSP (SP and HTAB, per section 2.2.2). * Any other CRLF is the end of the header. */ if (*cp++ == '\r') { if (*cp++ == '\n' && !(*cp == ' ' || *cp == '\t')) { /* end of header, we're done */ break; } /* skip fold (or junk) */ src++; continue; } /* see if we have (and skip) a quoted localpart */ if (*cp == '\"') { /* find the endquote, making sure it isn't escaped */ do { ++cp; cp = strchr(cp, '\"'); } while (cp && *(cp-1) == '\\'); /* no endquote, so bail */ if (!cp) { src++; continue; } } /* find the end of the msgid */ if ((cp = strchr(cp, '>')) == NULL) return NULL; /* alloc space for the msgid */ dst = msgid = (char*) xrealloc(msgid, cp - src + 2); *dst++ = *src++; /* quoted string */ if (*src == '\"') { src++; while (*src != '\"') { if (*src == '\\') { src++; } *dst++ = *src++; } src++; } /* atom */ else { while (!strchr(MSGID_SPECIALS, *src)) *dst++ = *src++; } if (*src != '@' || *(dst-1) == '<') continue; *dst++ = *src++; /* domain atom */ while (!strchr(MSGID_SPECIALS, *src)) *dst++ = *src++; if (*src != '>' || *(dst-1) == '@') continue; *dst++ = *src++; *dst = '\0'; if (rem) *rem = src; return msgid; } if (msgid) free(msgid); return NULL; } /* * Get name of client host on socket 's'. * Also returns local IP port and remote IP port on inet connections. */ EXPORTED const char *get_clienthost(int s, const char **localip, const char **remoteip) { #define IPBUF_SIZE (NI_MAXHOST+NI_MAXSERV+2) socklen_t salen; struct sockaddr_storage localaddr, remoteaddr; struct sockaddr *localsock = (struct sockaddr *)&localaddr; struct sockaddr *remotesock = (struct sockaddr *)&remoteaddr; static struct buf clientbuf = BUF_INITIALIZER; static char lipbuf[IPBUF_SIZE], ripbuf[IPBUF_SIZE]; char hbuf[NI_MAXHOST]; int niflags; buf_reset(&clientbuf); *localip = *remoteip = NULL; /* determine who we're talking to */ salen = sizeof(struct sockaddr_storage); if (getpeername(s, remotesock, &salen) == 0 && (remotesock->sa_family == AF_INET || remotesock->sa_family == AF_INET6)) { /* connected to an internet socket */ if (getnameinfo(remotesock, salen, hbuf, sizeof(hbuf), NULL, 0, NI_NAMEREQD) == 0) { buf_printf(&clientbuf, "%s ", hbuf); } niflags = NI_NUMERICHOST; #ifdef NI_WITHSCOPEID if (remotesock->sa_family == AF_INET6) niflags |= NI_WITHSCOPEID; #endif if (getnameinfo(remotesock, salen, hbuf, sizeof(hbuf), NULL, 0, niflags) != 0) { strlcpy(hbuf, "unknown", sizeof(hbuf)); } buf_printf(&clientbuf, "[%s]", hbuf); salen = sizeof(struct sockaddr_storage); if (getsockname(s, localsock, &salen) == 0) { /* set the ip addresses here */ if (iptostring(localsock, salen, lipbuf, sizeof(lipbuf)) == 0) { *localip = lipbuf; } if (iptostring(remotesock, salen, ripbuf, sizeof(ripbuf)) == 0) { *remoteip = ripbuf; } } else { fatal("can't get local addr", EX_SOFTWARE); } } else { /* we're not connected to a internet socket! */ buf_setcstr(&clientbuf, UNIX_SOCKET); } return buf_cstring(&clientbuf); } EXPORTED int cmd_cancelled(int insearch) { if (signals_cancelled()) return IMAP_CANCELLED; if (insearch && cmdtime_checksearch()) return IMAP_SEARCH_SLOW; return 0; } EXPORTED void saslprops_reset(struct saslprops_t *saslprops) { buf_reset(&saslprops->iplocalport); buf_reset(&saslprops->ipremoteport); buf_reset(&saslprops->authid); saslprops->ssf = 0; saslprops->cbinding.name = NULL; } EXPORTED void saslprops_free(struct saslprops_t *saslprops) { buf_free(&saslprops->iplocalport); buf_free(&saslprops->ipremoteport); buf_free(&saslprops->authid); } EXPORTED int saslprops_set_tls(struct saslprops_t *saslprops, sasl_conn_t *saslconn) { int r; r = sasl_setprop(saslconn, SASL_SSF_EXTERNAL, &saslprops->ssf); if (r != SASL_OK) { syslog(LOG_NOTICE, "sasl_setprop(SSF_EXTERNAL) failed"); return r; } r = sasl_setprop(saslconn, SASL_AUTH_EXTERNAL, buf_cstringnull(&saslprops->authid)); if (r != SASL_OK) { syslog(LOG_NOTICE, "sasl_setprop(AUTH_EXTERNAL) failed"); return r; } if (saslprops->cbinding.name) { r = sasl_setprop(saslconn, SASL_CHANNEL_BINDING, &saslprops->cbinding); if (r != SASL_OK) { syslog(LOG_NOTICE, "sasl_setprop(CHANNEL_BINDING) failed"); return r; } } return SASL_OK; } diff --git a/master/masterconf.c b/master/masterconf.c index 3e694885e..3a23785a6 100644 --- a/master/masterconf.c +++ b/master/masterconf.c @@ -1,351 +1,351 @@ /* masterconfig.c -- Configuration routines for master process * * 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. */ #include #include #include #include #include #include #include #include #include "util.h" #include "libconfig.h" #include "xmalloc.h" #include "xstrlcat.h" #include "xstrlcpy.h" #if HAVE_UNISTD_H # include #endif #include "masterconf.h" extern const char *MASTER_CONFIG_FILENAME; struct configlist { char *key; char *value; }; extern void fatal(const char *buf, int code) __attribute__((noreturn)); void fatalf(int code, const char *fmt, ...) { va_list args; char buf[2048]; va_start(args, fmt); vsnprintf(buf, sizeof(buf), fmt, args); va_end(args); fatal(buf, code); } int masterconf_init(const char *ident, const char *alt_config) { char *buf = NULL; const char *prefix; /* If our prefix is configured in the environment we can set it early */ if ((prefix = getenv("CYRUS_SYSLOG_PREFIX"))) { buf = strconcat(prefix, "/", ident, NULL); - openlog(buf, LOG_PID, SYSLOG_FACILITY); + openlog(buf, LOG_PID | LOG_PERROR, SYSLOG_FACILITY); } else { - openlog(ident, LOG_PID, SYSLOG_FACILITY); + openlog(ident, LOG_PID | LOG_PERROR, SYSLOG_FACILITY); } config_ident = ident; config_read(alt_config, 0); /* If we didn't already get the syslog prefix from the environment, * check config. */ if (!buf) { prefix = config_getstring(IMAPOPT_SYSLOG_PREFIX); /* XXX master ignores IMAPOPT_SYSLOG_FACILITY */ if (prefix) buf = strconcat(prefix, "/", ident, NULL); else buf = xstrdup(ident); /* Reopen the log with the new prefix */ closelog(); - openlog(buf, LOG_PID, SYSLOG_FACILITY); + openlog(buf, LOG_PID | LOG_PERROR, SYSLOG_FACILITY); } /* don't free 'buf', syslog needs it for the lifetime of the process */ /* drop debug messages locally */ if (!config_debug) setlogmask(~LOG_MASK(LOG_DEBUG)); return 0; } struct entry { #define MAXARGS 64 int nargs; struct { char *key; char *value; } args[MAXARGS]; int lineno; }; const char *masterconf_getstring(struct entry *e, const char *key, const char *def) { int i; for (i = 0 ; i < e->nargs ; i++) { if (!strcmp(key, e->args[i].key)) return e->args[i].value; } return def; } int masterconf_getint(struct entry *e, const char *key, int def) { const char *val = masterconf_getstring(e, key, NULL); if (!val) return def; if (!Uisdigit(*val) && (*val != '-' || !Uisdigit(val[1]))) { syslog(LOG_DEBUG, "value '%s' for '%s' does not look like a number.", val, key); return def; } return atoi(val); } int masterconf_getswitch(struct entry *e, const char *key, int def) { const char *val = masterconf_getstring(e, key, NULL); if (!val) return def; if (val[0] == '0' || val[0] == 'n' || (val[0] == 'o' && val[1] == 'f') || val[0] == 'f') { return 0; } else if (val[0] == '1' || val[0] == 'y' || (val[0] == 'o' && val[1] == 'n') || val[0] == 't') { return 1; } syslog(LOG_DEBUG, "cannot interpret value '%s' for key '%s'. use y/n.", val, key); return def; } static void split_args(struct entry *e, char *buf) { char *p = buf, *q; char *key, *value; for (;;) { /* skip whitespace before arg */ while (Uisspace(*p)) p++; if (!*p) return; key = p; /* parse the key */ for (q = p ; Uisalnum(*q) ; q++) ; if (*q != '=') fatalf(EX_CONFIG, "configuration file %s: " "bad character '%c' in argument on line %d", MASTER_CONFIG_FILENAME, *q, e->lineno); *q++ = '\0'; /* parse the value */ if (*q == '"') { /* quoted string */ value = ++q; q = strchr(q, '"'); if (!q) fatalf(EX_CONFIG, "configuration file %s: missing \" on line %d", MASTER_CONFIG_FILENAME, e->lineno); *q++ = '\0'; } else { /* simple word */ value = q; while (*q && !Uisspace(*q)) q++; if (*q) *q++ = '\0'; } if (e->nargs == MAXARGS) fatalf(EX_CONFIG, "configuration file %s: too many arguments on line %d", MASTER_CONFIG_FILENAME, e->lineno); e->args[e->nargs].key = key; e->args[e->nargs].value = value; e->nargs++; p = q; } } static void process_section(FILE *f, int *lnptr, masterconf_process *func, void *rock) { struct entry e; char buf[4096]; int lineno = *lnptr; while (fgets(buf, sizeof(buf), f)) { char *p, *q; lineno++; /* remove EOL character */ if (buf[strlen(buf)-1] == '\n') buf[strlen(buf)-1] = '\0'; /* remove starting whitespace */ for (p = buf; *p && Uisspace(*p); p++); /* remove comments */ q = strchr(p, '#'); if (q) *q = '\0'; /* skip empty lines or all comment lines */ if (!*p) continue; if (*p == '}') break; for (q = p; Uisalnum(*q); q++) ; if (*q) { if (q > p && !Uisspace(*q)) fatalf(EX_CONFIG, "configuration file %s: " "bad character '%c' in name on line %d", MASTER_CONFIG_FILENAME, *q, lineno); *q++ = '\0'; } if (q - p > 0) { /* there's a value on this line */ memset(&e, 0, sizeof(e)); e.lineno = lineno; split_args(&e, q); func(p, &e, rock); } /* end of section? */ if (strchr(q, '}')) break; } *lnptr = lineno; } void masterconf_getsection(const char *section, masterconf_process *f, void *rock) { FILE *infile = NULL; int seclen = strlen(section); int level = 0; int lineno = 0; char buf[4096]; const char *cyrus_path; /* try loading the copy inside CYRUS_PREFIX first */ cyrus_path = getenv("CYRUS_PREFIX"); if (cyrus_path) { strlcpy(buf, cyrus_path, sizeof(buf)); strlcat(buf, MASTER_CONFIG_FILENAME, sizeof(buf)); infile = fopen(buf, "r"); } if (!infile) infile = fopen(MASTER_CONFIG_FILENAME, "r"); if (!infile) fatalf(EX_CONFIG, "can't open configuration file %s: %m", MASTER_CONFIG_FILENAME); while (fgets(buf, sizeof(buf), infile)) { char *p, *q; lineno++; if (buf[strlen(buf)-1] == '\n') buf[strlen(buf)-1] = '\0'; for (p = buf; *p && Uisspace(*p); p++); /* remove comments */ q = strchr(p, '#'); if (q) *q = '\0'; /* skip empty lines or all comment lines */ if (!*p) continue; if (level == 0 && *p == *section && !strncasecmp(p, section, seclen) && !Uisalnum(p[seclen])) { for (p += seclen; *p; p++) { if (*p == '{') level++; if (*p == '}') level--; } /* valid opening; process the section */ if (level == 1) process_section(infile, &lineno, f, rock); continue; } for (; *p; p++) { if (*p == '{') level++; if (*p == '}') level--; } } fclose(infile); }