#include <config.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
#include <syslog.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <sys/stat.h>
#if HAVE_UNISTD_H
# include <unistd.h>
#endif
#include <errno.h>
#include "acl.h"
#include "cyrusdb.h"
#include "exitcodes.h"
#include "gmtoff.h"
#include "hash.h"
#include "imap_error.h"
#include "global.h"
#include "libconfig.h"
#include "libcyr_cfg.h"
#include "mboxlist.h"
#include "mupdate_error.h"
#include "mutex.h"
#include "prot.h"
#include "util.h"
#include "xmalloc.h"
static enum {
NOT_RUNNING = 0,
RUNNING = 1,
DONE = 2
} cyrus_init_run = NOT_RUNNING;
static int cyrus_init_nodb = 0;
int config_implicitrights;
struct cyrusdb_backend *config_mboxlist_db;
struct cyrusdb_backend *config_quota_db;
struct cyrusdb_backend *config_subscription_db;
struct cyrusdb_backend *config_annotation_db;
struct cyrusdb_backend *config_seenstate_db;
struct cyrusdb_backend *config_duplicate_db;
struct cyrusdb_backend *config_tlscache_db;
#ifdef WITH_PTS
struct cyrusdb_backend *config_ptscache_db;
#endif
int cyrus_init(const char *alt_config, const char *ident, unsigned flags)
{
char *p;
const char *val;
const char *prefix;
int umaskval = 0;
if(cyrus_init_run != NOT_RUNNING) {
fatal("cyrus_init called twice!", EC_CONFIG);
} else {
cyrus_init_run = RUNNING;
}
cyrus_init_nodb = (flags & CYRUSINIT_NODB);
initialize_imap_error_table();
initialize_mupd_error_table();
add_error_table( &et_imap_error_table );
add_error_table( &et_mupd_error_table );
if(!ident)
fatal("service name was not specified to cyrus_init", EC_CONFIG);
config_ident = ident;
openlog(config_ident, LOG_PID, SYSLOG_FACILITY);
config_read(alt_config);
prefix = config_getstring(IMAPOPT_SYSLOG_PREFIX);
if(prefix) {
int size = strlen(prefix) + 1 + strlen(ident) + 1;
char *ident_buf = xmalloc(size);
strlcpy(ident_buf, prefix, size);
strlcat(ident_buf, "/", size);
strlcat(ident_buf, ident, size);
closelog();
openlog(ident_buf, LOG_PID, SYSLOG_FACILITY);
}
config_defpartition = config_getstring(IMAPOPT_DEFAULTPARTITION);
for (p = (char *)config_defpartition; *p; p++) {
if (!isalnum((unsigned char) *p))
fatal("defaultpartition option contains non-alphanumeric character",
EC_CONFIG);
if (isupper((unsigned char) *p)) *p = tolower((unsigned char) *p);
}
val = config_getstring(IMAPOPT_UMASK);
while (*val) {
if (*val >= '0' && *val <= '7') umaskval = umaskval*8 + *val - '0';
val++;
}
umask(umaskval);
config_implicitrights =
cyrus_acl_strtomask(config_getstring(IMAPOPT_IMPLICIT_OWNER_RIGHTS));
if (!cyrus_init_nodb) {
config_mboxlist_db =
cyrusdb_fromname(config_getstring(IMAPOPT_MBOXLIST_DB));
config_quota_db =
cyrusdb_fromname(config_getstring(IMAPOPT_QUOTA_DB));
config_subscription_db =
cyrusdb_fromname(config_getstring(IMAPOPT_SUBSCRIPTION_DB));
config_annotation_db =
cyrusdb_fromname(config_getstring(IMAPOPT_ANNOTATION_DB));
config_seenstate_db =
cyrusdb_fromname(config_getstring(IMAPOPT_SEENSTATE_DB));
config_duplicate_db =
cyrusdb_fromname(config_getstring(IMAPOPT_DUPLICATE_DB));
config_tlscache_db =
cyrusdb_fromname(config_getstring(IMAPOPT_TLSCACHE_DB));
#ifdef WITH_PTS
config_ptscache_db =
cyrusdb_fromname(config_getstring(IMAPOPT_PTSCACHE_DB));
#endif
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,
config_getint(IMAPOPT_PTSCACHE_TIMEOUT));
libcyrus_config_setswitch(CYRUSOPT_FULLDIRHASH,
config_getswitch(IMAPOPT_FULLDIRHASH));
libcyrus_config_setstring(CYRUSOPT_PTSCACHE_DB,
config_getstring(IMAPOPT_PTSCACHE_DB));
libcyrus_config_setstring(CYRUSOPT_PTLOADER_SOCK,
config_getstring(IMAPOPT_PTLOADER_SOCK));
libcyrus_config_setswitch(CYRUSOPT_VIRTDOMAINS,
config_getenum(IMAPOPT_VIRTDOMAINS));
libcyrus_config_setint(CYRUSOPT_BERKELEY_CACHESIZE,
config_getint(IMAPOPT_BERKELEY_CACHESIZE));
libcyrus_config_setint(CYRUSOPT_BERKELEY_LOCKS_MAX,
config_getint(IMAPOPT_BERKELEY_LOCKS_MAX));
libcyrus_config_setint(CYRUSOPT_BERKELEY_TXNS_MAX,
config_getint(IMAPOPT_BERKELEY_TXNS_MAX));
libcyrus_init();
}
return 0;
}
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;
sasl_set_alloc((sasl_malloc_t *) &xmalloc,
(sasl_calloc_t *) &calloc,
(sasl_realloc_t *) &xrealloc,
(sasl_free_t *) &free);
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)", EC_SOFTWARE);
}
if(server){
int r=sasl_server_init_alt(callbacks, "Cyrus");
if((r!=SASL_NOMECH)&&(r!=SASL_OK)){
fatal("could not init sasl (client)", EC_SOFTWARE);
}
}
}
int mysasl_config(void *context __attribute__((unused)),
const char *plugin_name,
const char *option,
const char **result,
unsigned *len)
{
char opt[1024];
if (!strcmp(option, "srvtab")) {
*result = config_getstring(IMAPOPT_SRVTAB);
} else {
*result = NULL;
if (plugin_name) {
strlcpy(opt, "sasl_", sizeof(opt));
strlcat(opt, plugin_name, sizeof(opt));
strlcat(opt, "_", sizeof(opt));
strlcat(opt, option, sizeof(opt));
*result = config_getoverflowstring(opt, NULL);
}
if (*result == NULL) {
strlcpy(opt, "sasl_", sizeof(opt));
strlcat(opt, option, sizeof(opt));
*result = config_getoverflowstring(opt, NULL);
}
}
if (*result != NULL) {
if (len) { *len = strlen(*result); }
return SASL_OK;
}
return SASL_FAIL;
}
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);
ret.max_ssf = config_getint(IMAPOPT_SASL_MAXIMUM_LAYER);
ret.security_flags = flags;
if (!config_getswitch(IMAPOPT_ALLOWANONYMOUSLOGIN)) {
ret.security_flags |= SASL_SEC_NOANONYMOUS;
}
ret.property_names = NULL;
ret.property_values = NULL;
return &ret;
}
int global_authisa(struct auth_state *authstate, enum imapopt opt)
{
char buf[1024];
const char *val = config_getstring(opt);
int len;
if(!val) return 0;
while (*val) {
char *p;
for (p = (char *) val; *p && !isspace((int) *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 && isspace((int) *val)) val++;
}
return 0;
}
char *canonify_userid(char *user, char *loginid, int *domain_from_ip)
{
char *domain = NULL;
int len = strlen(user);
char buf[81];
if (config_virtdomains &&
((domain = strrchr(user, '@')) || (domain = strrchr(user, '%')))) {
*domain = '@';
len = domain - user;
}
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';
}
}
else if (loginid) {
if ((domain = strrchr(loginid, '@'))) {
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))) {
snprintf(buf, sizeof(buf), "%s@%s", user, domain+1);
user = buf;
if (domain_from_ip) *domain_from_ip = 1;
}
}
}
}
return auth_canonifyid(user, 0);
}
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)
{
char *canonuser = NULL;
if (ulen+1 > out_max) {
sasl_seterror(conn, 0, "buffer overflow while canonicalizing");
return SASL_BUFOVER;
}
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;
}
int is_userid_anonymous(const char *user)
{
int len = strlen(user);
const char *domain;
assert(user);
if (config_virtdomains &&
((domain = strrchr(user, '@')) || (domain = strrchr(user, '%')))) {
len = domain - user;
}
if (len == 9 && strncasecmp(user, "anonymous", len) == 0) {
return 1;
} else {
return 0;
}
}
static int acl_ok(const char *user, struct auth_state *authstate)
{
struct namespace namespace;
char *acl;
char bufuser[MAX_MAILBOX_NAME], inboxname[MAX_MAILBOX_NAME];
int r;
if ((r = mboxname_init_namespace(&namespace, 0)) != 0) {
syslog(LOG_ERR, error_message(r));
fatal(error_message(r), EC_CONFIG);
}
strlcpy(bufuser, user, sizeof(bufuser));
mboxname_hiersep_tointernal(&namespace, bufuser,
config_virtdomains ?
strcspn(bufuser, "@") : 0);
if (!r)
r = (*namespace.mboxname_tointernal)(&namespace, "INBOX",
bufuser, inboxname);
if (r || !authstate ||
mboxlist_lookup(inboxname, NULL, &acl, NULL)) {
r = 0;
}
else {
r = (cyrus_acl_myrights(authstate, acl) & ACL_ADMIN) != 0;
}
return r;
}
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;
if ((!config_virtdomains || *val) &&
(realm = strchr(auth_identity, '@'))!=NULL) {
realm++;
while (*val) {
if (!strncasecmp(val, realm, strlen(realm)) &&
(!val[strlen(realm)] || isspace((int) val[strlen(realm)]))) {
break;
}
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;
}
}
authstate = auth_newstate(auth_identity);
userisadmin = global_authisa(authstate, IMAPOPT_ADMINS);
if (!ctx) {
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;
}
if (alen != rlen || strncmp(auth_identity, requested_user, alen)) {
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))) {
userisadmin = 0;
auth_freestate(authstate);
authstate = auth_newstate(requested_user);
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;
}
void cyrus_ctime(time_t date, char *datebuf)
{
struct tm *tm = localtime(&date);
long gmtoff = gmtoff_of(tm, date);
int gmtnegative = 0;
static const char *monthname[] = {
"Jan", "Feb", "Mar", "Apr", "May", "Jun",
"Jul", "Aug", "Sep", "Oct", "Nov", "Dec" };
if (date == 0 || tm->tm_year < 69) {
abort();
}
if (gmtoff < 0) {
gmtoff = -gmtoff;
gmtnegative = 1;
}
gmtoff /= 60;
sprintf(datebuf,
"%2u-%s-%u %.2u:%.2u:%.2u %c%.2lu%.2lu",
tm->tm_mday, monthname[tm->tm_mon], tm->tm_year+1900,
tm->tm_hour, tm->tm_min, tm->tm_sec,
gmtnegative ? '-' : '+', gmtoff/60, gmtoff%60);
}
void cyrus_done()
{
if(cyrus_init_run != RUNNING) return;
cyrus_init_run = DONE;
if (!cyrus_init_nodb) libcyrus_done();
}
int shutdown_file(char *buf, int size)
{
FILE *f;
static char shutdownfilename[1024] = "";
char *p;
if (!shutdownfilename[0])
snprintf(shutdownfilename, sizeof(shutdownfilename),
"%s/msg/shutdown", config_dir);
if ((f = fopen(shutdownfilename, "r")) == NULL) return 0;
fgets(buf, size, f);
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);
return 1;
}