Mercurial > dovecot > core-2.2
changeset 14840:6a0954d0ce09
Merged changes from v2.1 tree.
line wrap: on
line diff
--- a/.hgsigs Fri Aug 10 04:56:56 2012 +0300 +++ b/.hgsigs Fri Aug 10 05:24:07 2012 +0300 @@ -47,3 +47,5 @@ 469cee314d9c54d2d7101ec9e38579fdc9610eaf 0 iEYEABECAAYFAk+VWqkACgkQyUhSUUBVislnXACfVjPqMmPUvYtXQXwqff0h7N76mZUAn02lPeUCyuyr1TF9e1hGM/sKgmko 7c249e2a82a9cd33ae15ead6443c3499e16da623 0 iEYEABECAAYFAk+nX2sACgkQyUhSUUBVisn7uwCbBD3boxBOGEJ8OYsIJ57n5Cr09FAAoIvhxL6EHRB15AMOw4sPaALJ3/bB c92fb8b928f69ca01681a2c2976304b7e4bc3afc 0 iEYEABECAAYFAk/FIeIACgkQyUhSUUBVisk4IgCfUiXVXntqzPjJcALYRpqw4Zc7a/0An3HKWwgb6PBCbmvxBfTezNkqjqVK +7e5f36fd989d27a2fb48250adbab8fa54ddb6083 0 iEYEABECAAYFAk/yVakACgkQyUhSUUBVismekwCfSEVQjd6fwdChjd53LSt03b4UWKoAoIxd/IjLatTISlHm44iiQwzRKByo +bc86680293d256d5a8009690caeb73ab2e34e359 0 iEYEABECAAYFAlAZaTUACgkQyUhSUUBVisnTAACfU1pB34RrXEyLnpnL4Ee/oeNBYcoAnRWxTqx870Efjwf+eBPzafO0C/NU
--- a/.hgtags Fri Aug 10 04:56:56 2012 +0300 +++ b/.hgtags Fri Aug 10 05:24:07 2012 +0300 @@ -84,3 +84,5 @@ 469cee314d9c54d2d7101ec9e38579fdc9610eaf 2.1.5 7c249e2a82a9cd33ae15ead6443c3499e16da623 2.1.6 c92fb8b928f69ca01681a2c2976304b7e4bc3afc 2.1.7 +7e5f36fd989d27a2fb48250adbab8fa54ddb6083 2.1.8 +bc86680293d256d5a8009690caeb73ab2e34e359 2.1.9
--- a/NEWS Fri Aug 10 04:56:56 2012 +0300 +++ b/NEWS Fri Aug 10 05:24:07 2012 +0300 @@ -1,3 +1,42 @@ +v2.1.9 2012-08-01 Timo Sirainen <tss@iki.fi> + + * mail-log plugin: Log mailbox names with UTF-8 everywhere + (instead of mUTF-7 in some places and UTF-8 in other places) + * director: Changed director_username_hash setting's default from %u + to %Lu (= lowercase usernames). This doesn't break any existing + installations, but might fix some of them. + + + doveadm: Added "auth cache flush [<username>]" command. + + Implemented dict passdb/userdb + + Implemented Redis and memcached dict backends, which can be used as + auth backends. Redis can also be used as dict-quota backend. + + Added plugin { quota_ignore_save_errors=yes } setting to allow saving + a mail when quota lookup fails with temporary failure. + - Full text search indexing might have failed for some messages, + always causing indexer-worker process to run out of memory. + - fts-lucene: Fixed handling SEARCH HEADER FROM/TO/SUBJECT/CC/BCC when + the header wasn't lowercased. + - fts-squat: Fixed crash when searching a virtual mailbox. + - pop3: Fixed assert crash when doing UIDL on empty mailbox on some + setups. + - auth: GSSAPI RFC compliancy and error handling fixes. + - Various fixes related to handling shared namespaces + +v2.1.8 2012-07-03 Timo Sirainen <tss@iki.fi> + + + pop3c: Added pop3c_master_user setting. + - imap: Mailbox names were accidentally sent as UTF-8 instead of mUTF-7 + in previous v2.1.x releases for STATUS, MYRIGHTS and GETQUOTAROOT + commands. + - lmtp proxy: Don't timeout connections too early when mail has a lot + of RCPT TOs. + - director: Don't crash if the director is working alone. + - shared mailboxes: Avoid doing "@domain" userdb lookups. + - doveadm: Fixed crash with proxying some commands. + - fts-squat: Fixed handling multiple SEARCH parameters. + - imapc: Fixed a crash when message had more than 8 keywords. + - imapc: Don't crash on APPEND/COPY if server doesn't support UIDPLUS. + v2.1.7 2012-05-29 Timo Sirainen <tss@iki.fi> * LDAP: Compatibility fix for v2.0: ldap: If attributes contain
--- a/configure.in Fri Aug 10 04:56:56 2012 +0300 +++ b/configure.in Fri Aug 10 05:24:07 2012 +0300 @@ -2675,6 +2675,11 @@ AC_CHECK_LIB(textcat, special_textcat_Init, [ have_lucene_textcat=yes AC_DEFINE(HAVE_LUCENE_TEXTCAT,, Define if you want textcat support for CLucene) + ], [ + AC_CHECK_LIB(exttextcat, special_textcat_Init, [ + have_lucene_exttextcat=yes + AC_DEFINE(HAVE_LUCENE_EXTTEXTCAT,, Define if you want textcat (Debian version) support for CLucene) + ]) ]) ], [ if test $want_stemmer = yes; then @@ -2687,6 +2692,7 @@ fi AM_CONDITIONAL(BUILD_LUCENE_STEMMER, test "$have_lucene_stemmer" = "yes") AM_CONDITIONAL(BUILD_LUCENE_TEXTCAT, test "$have_lucene_textcat" = "yes") +AM_CONDITIONAL(BUILD_LUCENE_EXTTEXTCAT, test "$have_lucene_exttextcat" = "yes") if test $have_lucene = no; then not_fts="$not_fts lucene"
--- a/doc/Makefile.am Fri Aug 10 04:56:56 2012 +0300 +++ b/doc/Makefile.am Fri Aug 10 05:24:07 2012 +0300 @@ -7,7 +7,10 @@ docfiles = \ documentation.txt \ securecoding.txt \ - thread-refs.txt + thread-refs.txt \ + mkcert.sh \ + dovecot-openssl.cnf \ + solr-schema.xml if BUILD_DOCS doc_DATA = $(docfiles) @@ -15,7 +18,4 @@ EXTRA_DIST = \ dovecot-initd.sh \ - mkcert.sh \ - dovecot-openssl.cnf \ - solr-schema.xml \ $(docfiles)
--- a/doc/example-config/conf.d/10-director.conf Fri Aug 10 04:56:56 2012 +0300 +++ b/doc/example-config/conf.d/10-director.conf Fri Aug 10 05:24:07 2012 +0300 @@ -25,6 +25,11 @@ # If you enable this, you'll also need to add inet_listener for the port. #director_doveadm_port = 0 +# How the username is translated before being hashed. Useful values include +# %Ln if user can log in with or without @domain, %Ld if mailboxes are shared +# within domain. +#director_username_hash = %Lu + # To enable director service, uncomment the modes and assign a port. service director { unix_listener login/director {
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/doc/example-config/conf.d/auth-dict.conf.ext Fri Aug 10 05:24:07 2012 +0300 @@ -0,0 +1,16 @@ +# Authentication via dict backend. Included from auth.conf. +# +# <doc/wiki/AuthDatabase.Dict.txt> + +passdb { + driver = dict + + # Path for dict configuration file, see + # example-config/dovecot-dict-auth.conf.ext + args = /etc/dovecot/dovecot-dict-auth.conf.ext +} + +userdb { + driver = dict + args = /etc/dovecot/dovecot-dict-auth.conf.ext +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/doc/example-config/dovecot-dict-auth.conf.ext Fri Aug 10 05:24:07 2012 +0300 @@ -0,0 +1,22 @@ +# Dictionary URI +#uri = + +# Key for passdb lookups +password_key = dovecot/passdb/%u + +# Key for userdb lookups +user_key = dovecot/userdb/%u + +# How to parse the value for key=value pairs. Currently we support only JSON +# format with { "key": "value", ... } object. +#value_format = json + +# Username iteration prefix. Keys under this are assumed to contain usernames. +iterate_prefix = dovecot/userdb/ + +# Should iteration be disabled for this userdb? If this userdb acts only as a +# cache there's no reason to try to iterate the (partial & duplicate) users. +#iterate_disable = no + +# Default password scheme +default_pass_scheme = MD5
--- a/src/auth/Makefile.am Fri Aug 10 04:56:56 2012 +0300 +++ b/src/auth/Makefile.am Fri Aug 10 05:24:07 2012 +0300 @@ -25,6 +25,7 @@ -I$(top_srcdir)/src/lib \ -I$(top_srcdir)/src/lib-auth \ -I$(top_srcdir)/src/lib-test \ + -I$(top_srcdir)/src/lib-dict \ -I$(top_srcdir)/src/lib-dns \ -I$(top_srcdir)/src/lib-sql \ -I$(top_srcdir)/src/lib-settings \ @@ -73,6 +74,7 @@ auth-worker-client.c \ auth-worker-server.c \ db-checkpassword.c \ + db-dict.c \ db-sql.c \ db-passwd-file.c \ main.c \ @@ -96,6 +98,7 @@ passdb-bsdauth.c \ passdb-cache.c \ passdb-checkpassword.c \ + passdb-dict.c \ passdb-passwd.c \ passdb-passwd-file.c \ passdb-pam.c \ @@ -108,6 +111,7 @@ userdb.c \ userdb-blocking.c \ userdb-checkpassword.c \ + userdb-dict.c \ userdb-nss.c \ userdb-passwd.c \ userdb-passwd-file.c \ @@ -134,6 +138,7 @@ auth-stream.h \ auth-worker-client.h \ auth-worker-server.h \ + db-dict.h \ db-ldap.h \ db-sql.h \ db-passwd-file.h \
--- a/src/auth/auth-cache.c Fri Aug 10 04:56:56 2012 +0300 +++ b/src/auth/auth-cache.c Fri Aug 10 05:24:07 2012 +0300 @@ -23,36 +23,72 @@ unsigned long long pos_size, neg_size; }; -static const struct var_expand_table * -auth_request_var_expand_tab_find(const char *key, unsigned int size) +static bool +auth_request_var_expand_tab_find(const char *key, unsigned int size, + unsigned int *idx_r) { const struct var_expand_table *tab = auth_request_var_expand_static_tab; unsigned int i; for (i = 0; tab[i].key != '\0' || tab[i].long_key != NULL; i++) { if (size == 1) { - if (key[0] == tab[i].key) - return &tab[i]; + if (key[0] == tab[i].key) { + *idx_r = i; + return TRUE; + } } else if (tab[i].long_key != NULL) { if (strncmp(key, tab[i].long_key, size) == 0 && - tab[i].long_key[size] == '\0') - return &tab[i]; + tab[i].long_key[size] == '\0') { + *idx_r = i; + return TRUE; + } } } - return NULL; + return FALSE; +} + +static void +auth_cache_key_add_var(string_t *str, const char *data, unsigned int len) +{ + if (str_len(str) > 0) + str_append_c(str, '\t'); + str_append_c(str, '%'); + if (len == 1) + str_append_c(str, data[0]); + else { + str_append_c(str, '{'); + str_append_n(str, data, len); + str_append_c(str, '}'); + } +} + +static void auth_cache_key_add_tab_idx(string_t *str, unsigned int i) +{ + const struct var_expand_table *tab = + &auth_request_var_expand_static_tab[i]; + + if (str_len(str) > 0) + str_append_c(str, '\t'); + str_append_c(str, '%'); + if (tab->key != '\0') + str_append_c(str, tab->key); + else { + str_append_c(str, '{'); + str_append(str, tab->long_key); + str_append_c(str, '}'); + } } char *auth_cache_parse_key(pool_t pool, const char *query) { - const struct var_expand_table *tab; string_t *str; - bool key_seen[100]; - unsigned int idx, size, tab_idx; - bool add_key; + bool key_seen[AUTH_REQUEST_VAR_TAB_COUNT]; + const char *extra_vars; + unsigned int i, idx, size, tab_idx; memset(key_seen, 0, sizeof(key_seen)); - str = str_new(pool, 32); + str = t_str_new(32); for (; *query != '\0'; ) { if (*query != '%') { query++; @@ -66,34 +102,45 @@ } query += idx; - tab = auth_request_var_expand_tab_find(query, size); - if (tab == NULL) { + if (!auth_request_var_expand_tab_find(query, size, &tab_idx)) { /* just add the key. it would be nice to prevent duplicates here as well, but that's just too much trouble and probably very rare. */ - add_key = TRUE; + auth_cache_key_add_var(str, query, size); } else { - tab_idx = tab - auth_request_var_expand_static_tab; i_assert(tab_idx < N_ELEMENTS(key_seen)); - /* @UNSAFE */ - add_key = !key_seen[tab_idx]; key_seen[tab_idx] = TRUE; } - if (add_key) { - if (str_len(str) != 0) - str_append_c(str, '\t'); - str_append_c(str, '%'); - if (size == 1) - str_append_c(str, query[0]); - else { - str_append_c(str, '{'); - str_append_n(str, query, size); - str_append_c(str, '}'); - } - } query += size; } - return str_free_without_data(&str); + + if (key_seen[AUTH_REQUEST_VAR_TAB_USERNAME_IDX] && + key_seen[AUTH_REQUEST_VAR_TAB_DOMAIN_IDX]) { + /* %n and %d both used -> replace with %u */ + key_seen[AUTH_REQUEST_VAR_TAB_USER_IDX] = TRUE; + key_seen[AUTH_REQUEST_VAR_TAB_USERNAME_IDX] = FALSE; + key_seen[AUTH_REQUEST_VAR_TAB_DOMAIN_IDX] = FALSE; + } + + /* we rely on these being at the beginning */ + i_assert(AUTH_REQUEST_VAR_TAB_USER_IDX == 0); + i_assert(AUTH_REQUEST_VAR_TAB_USERNAME_IDX == 1); + i_assert(AUTH_REQUEST_VAR_TAB_DOMAIN_IDX == 2); + + extra_vars = t_strdup(str_c(str)); + str_truncate(str, 0); + for (i = 0; i < N_ELEMENTS(key_seen); i++) { + if (key_seen[i]) + auth_cache_key_add_tab_idx(str, i); + } + + if (*extra_vars != '\0') { + if (str_len(str) > 0) + str_append_c(str, '\t'); + str_append(str, extra_vars); + } + + return p_strdup(pool, str_c(str)); } static void @@ -142,8 +189,8 @@ { struct auth_cache *cache = context; - i_info("SIGHUP received, clearing cache"); - auth_cache_clear(cache); + i_info("SIGHUP received, %u cache entries flushed", + auth_cache_clear(cache)); } static void sig_auth_cache_stats(const siginfo_t *si ATTR_UNUSED, void *context) @@ -200,11 +247,69 @@ i_free(cache); } -void auth_cache_clear(struct auth_cache *cache) +unsigned int auth_cache_clear(struct auth_cache *cache) { + unsigned int ret = hash_table_count(cache->hash); + while (cache->tail != NULL) auth_cache_node_destroy(cache, cache->tail); hash_table_clear(cache->hash, FALSE); + return ret; +} + +static bool auth_cache_node_is_user(struct auth_cache_node *node, + const char *username) +{ + const char *data = node->data; + unsigned int username_len; + + /* The cache nodes begin with "P"/"U", passdb/userdb ID, "/" and + then usually followed by the username. It's too much trouble to + keep track of all the cache keys, so we'll just match it as if it + was the username. If e.g. '%n' is used in the cache key instead of + '%u', it means that cache entries can be removed only when @domain + isn't in the username parameter. */ + if (*data != 'P' && *data != 'U') + return FALSE; + data++; + + while (*data >= '0' && *data <= '9') + data++; + if (*data != '/') + return FALSE; + data++; + + username_len = strlen(username); + return strncmp(data, username, username_len) == 0 && + (data[username_len] == '\t' || data[username_len] == '\0'); +} + +static bool auth_cache_node_is_one_of_users(struct auth_cache_node *node, + const char *const *usernames) +{ + unsigned int i; + + for (i = 0; usernames[i] != NULL; i++) { + if (auth_cache_node_is_user(node, usernames[i])) + return TRUE; + } + return FALSE; +} + +unsigned int auth_cache_clear_users(struct auth_cache *cache, + const char *const *usernames) +{ + struct auth_cache_node *node, *next; + unsigned int ret = 0; + + for (node = cache->tail; node != NULL; node = next) { + next = node->next; + if (auth_cache_node_is_one_of_users(node, usernames)) { + auth_cache_node_destroy(cache, node); + ret++; + } + } + return ret; } static const char * @@ -216,12 +321,27 @@ return str_tabescape(string); } +static const char * +auth_request_expand_cache_key(const struct auth_request *request, + const char *key) +{ + string_t *str; + + /* Uniquely identify the request's passdb/userdb with the P/U prefix + and by "%!", which expands to the passdb/userdb ID number. */ + key = t_strconcat(request->userdb_lookup ? "U" : "P", "%!/", key, NULL); + + str = t_str_new(256); + var_expand(str, key, + auth_request_get_var_expand_table(request, auth_cache_escape)); + return str_c(str); +} + const char * auth_cache_lookup(struct auth_cache *cache, const struct auth_request *request, const char *key, struct auth_cache_node **node_r, bool *expired_r, bool *neg_expired_r) { - string_t *str; struct auth_cache_node *node; const char *value; unsigned int ttl_secs; @@ -230,13 +350,8 @@ *expired_r = FALSE; *neg_expired_r = FALSE; - /* %! is prepended automatically. it contains the passdb ID number. */ - str = t_str_new(256); - var_expand(str, t_strconcat(request->userdb_lookup ? "U" : "P", - "%!/", key, NULL), - auth_request_get_var_expand_table(request, auth_cache_escape)); - - node = hash_table_lookup(cache->hash, str_c(str)); + key = auth_request_expand_cache_key(request, key); + node = hash_table_lookup(cache->hash, key); if (node == NULL) { cache->miss_count++; return NULL; @@ -269,9 +384,8 @@ void auth_cache_insert(struct auth_cache *cache, struct auth_request *request, const char *key, const char *value, bool last_success) { - string_t *str; struct auth_cache_node *node; - size_t data_size, alloc_size, value_len = strlen(value); + size_t data_size, alloc_size, key_len, value_len = strlen(value); char *current_username; if (*value == '\0' && cache->neg_ttl_secs == 0) { @@ -286,15 +400,12 @@ request->requested_login_user == NULL) request->user = t_strdup_noconst(request->translated_username); - /* %! is prepended automatically. it contains the db ID number. */ - str = t_str_new(256); - var_expand(str, t_strconcat(request->userdb_lookup ? "U" : "P", - "%!/", key, NULL), - auth_request_get_var_expand_table(request, auth_cache_escape)); + key = auth_request_expand_cache_key(request, key); + key_len = strlen(key); request->user = current_username; - data_size = str_len(str) + 1 + value_len + 1; + data_size = key_len + 1 + value_len + 1; alloc_size = sizeof(struct auth_cache_node) - sizeof(node->data) + data_size; @@ -302,7 +413,7 @@ while (cache->size_left < alloc_size && cache->tail != NULL) auth_cache_node_destroy(cache, cache->tail); - node = hash_table_lookup(cache->hash, str_c(str)); + node = hash_table_lookup(cache->hash, key); if (node != NULL) { /* key is already in cache (probably expired), remove it */ auth_cache_node_destroy(cache, node); @@ -313,8 +424,8 @@ node->created = time(NULL); node->alloc_size = alloc_size; node->last_success = last_success; - memcpy(node->data, str_data(str), str_len(str)); - memcpy(node->data + str_len(str) + 1, value, value_len); + memcpy(node->data, key, key_len); + memcpy(node->data + key_len + 1, value, value_len); auth_cache_node_link_head(cache, node); @@ -331,17 +442,12 @@ } void auth_cache_remove(struct auth_cache *cache, - const struct auth_request *request, - const char *key) + const struct auth_request *request, const char *key) { - string_t *str; struct auth_cache_node *node; - str = t_str_new(256); - var_expand(str, key, - auth_request_get_var_expand_table(request, auth_cache_escape)); - - node = hash_table_lookup(cache->hash, str_c(str)); + key = auth_request_expand_cache_key(request, key); + node = hash_table_lookup(cache->hash, key); if (node == NULL) return;
--- a/src/auth/auth-cache.h Fri Aug 10 04:56:56 2012 +0300 +++ b/src/auth/auth-cache.h Fri Aug 10 05:24:07 2012 +0300 @@ -28,8 +28,11 @@ unsigned int neg_ttl_secs); void auth_cache_free(struct auth_cache **cache); -/* Clear the cache. */ -void auth_cache_clear(struct auth_cache *cache); +/* Clear the cache. Returns how many entries were removed. */ +unsigned int ATTR_NOWARN_UNUSED_RESULT +auth_cache_clear(struct auth_cache *cache); +unsigned int auth_cache_clear_users(struct auth_cache *cache, + const char *const *usernames); /* Look key from cache. key should be the same string as returned by auth_cache_parse_key(). Returned node can't be used after any other
--- a/src/auth/auth-master-connection.c Fri Aug 10 04:56:56 2012 +0300 +++ b/src/auth/auth-master-connection.c Fri Aug 10 05:24:07 2012 +0300 @@ -18,6 +18,7 @@ #include "userdb.h" #include "userdb-blocking.h" #include "master-interface.h" +#include "passdb-cache.h" #include "auth-request-handler.h" #include "auth-client-connection.h" #include "auth-master-connection.h" @@ -137,6 +138,30 @@ } static int +master_input_cache_flush(struct auth_master_connection *conn, const char *args) +{ + const char *const *list; + unsigned int count; + + /* <id> [<user> [<user> [..]] */ + list = t_strsplit_tab(args); + if (list[0] == NULL) { + i_error("BUG: doveadm sent broken CACHE-FLUSH"); + return FALSE; + } + + if (list[1] == NULL) { + /* flush the whole cache */ + count = auth_cache_clear(passdb_cache); + } else { + count = auth_cache_clear_users(passdb_cache, list+1); + } + (void)o_stream_send_str(conn->output, + t_strdup_printf("OK\t%s\t%u\n", list[0], count)); + return TRUE; +} + +static int master_input_auth_request(struct auth_master_connection *conn, const char *args, const char *cmd, struct auth_request **request_r, const char **error_r) @@ -566,6 +591,8 @@ i_assert(conn->userdb_restricted_uid == 0); if (strncmp(line, "REQUEST\t", 8) == 0) return master_input_request(conn, line + 8); + if (strncmp(line, "CACHE-FLUSH\t", 12) == 0) + return master_input_cache_flush(conn, line + 12); if (strncmp(line, "CPID\t", 5) == 0) { i_error("Authentication client trying to connect to " "master socket");
--- a/src/auth/auth-request.h Fri Aug 10 04:56:56 2012 +0300 +++ b/src/auth/auth-request.h Fri Aug 10 05:24:07 2012 +0300 @@ -126,6 +126,10 @@ typedef void auth_request_proxy_cb_t(bool success, struct auth_request *); extern unsigned int auth_request_state_count[AUTH_REQUEST_STATE_MAX]; +#define AUTH_REQUEST_VAR_TAB_USER_IDX 0 +#define AUTH_REQUEST_VAR_TAB_USERNAME_IDX 1 +#define AUTH_REQUEST_VAR_TAB_DOMAIN_IDX 2 +#define AUTH_REQUEST_VAR_TAB_COUNT 19 extern const struct var_expand_table auth_request_var_expand_static_tab[]; struct auth_request *
--- a/src/auth/auth.c Fri Aug 10 04:56:56 2012 +0300 +++ b/src/auth/auth.c Fri Aug 10 05:24:07 2012 +0300 @@ -295,6 +295,15 @@ { struct auth *const *auth; + /* sanity checks */ + i_assert(auth_request_var_expand_static_tab[AUTH_REQUEST_VAR_TAB_USER_IDX].key == 'u'); + i_assert(auth_request_var_expand_static_tab[AUTH_REQUEST_VAR_TAB_USERNAME_IDX].key == 'n'); + i_assert(auth_request_var_expand_static_tab[AUTH_REQUEST_VAR_TAB_DOMAIN_IDX].key == 'd'); + i_assert(auth_request_var_expand_static_tab[AUTH_REQUEST_VAR_TAB_COUNT].key == '\0' && + auth_request_var_expand_static_tab[AUTH_REQUEST_VAR_TAB_COUNT].long_key == NULL); + i_assert(auth_request_var_expand_static_tab[AUTH_REQUEST_VAR_TAB_COUNT-1].key != '\0' || + auth_request_var_expand_static_tab[AUTH_REQUEST_VAR_TAB_COUNT-1].long_key != NULL); + array_foreach(&auths, auth) auth_init(*auth); }
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/auth/db-dict.c Fri Aug 10 05:24:07 2012 +0300 @@ -0,0 +1,177 @@ +/* Copyright (c) 2012 Dovecot authors, see the included COPYING file */ + +#include "auth-common.h" + +#include "settings.h" +#include "dict.h" +#include "json-parser.h" +#include "str.h" +#include "auth-request.h" +#include "auth-worker-client.h" +#include "db-dict.h" + +#include <stddef.h> +#include <stdlib.h> + +#define DEF_STR(name) DEF_STRUCT_STR(name, db_dict_settings) +#define DEF_BOOL(name) DEF_STRUCT_BOOL(name, db_dict_settings) + +static struct setting_def setting_defs[] = { + DEF_STR(uri), + DEF_STR(password_key), + DEF_STR(user_key), + DEF_STR(iterate_prefix), + DEF_STR(value_format), + DEF_BOOL(iterate_disable), + DEF_STR(default_pass_scheme), + + { 0, NULL, 0 } +}; + +static struct db_dict_settings default_dict_settings = { + .uri = NULL, + .password_key = "", + .user_key = "", + .iterate_prefix = "", + .iterate_disable = FALSE, + .value_format = "json", + .default_pass_scheme = "MD5" +}; + +static struct dict_connection *connections = NULL; + +static struct dict_connection *dict_conn_find(const char *config_path) +{ + struct dict_connection *conn; + + for (conn = connections; conn != NULL; conn = conn->next) { + if (strcmp(conn->config_path, config_path) == 0) + return conn; + } + + return NULL; +} + +static const char *parse_setting(const char *key, const char *value, + struct dict_connection *conn) +{ + return parse_setting_from_defs(conn->pool, setting_defs, + &conn->set, key, value); +} + +struct dict_connection *db_dict_init(const char *config_path) +{ + struct dict_connection *conn; + pool_t pool; + + conn = dict_conn_find(config_path); + if (conn != NULL) { + conn->refcount++; + return conn; + } + + if (*config_path == '\0') + i_fatal("dict: Configuration file path not given"); + + pool = pool_alloconly_create("dict_connection", 1024); + conn = p_new(pool, struct dict_connection, 1); + conn->pool = pool; + + conn->refcount = 1; + + conn->config_path = p_strdup(pool, config_path); + conn->set = default_dict_settings; + if (!settings_read(config_path, NULL, parse_setting, + null_settings_section_callback, conn)) + exit(FATAL_DEFAULT); + + if (conn->set.uri == NULL) + i_fatal("dict %s: Empty uri setting", config_path); + if (strcmp(conn->set.value_format, "json") != 0) { + i_fatal("dict %s: Unsupported value_format %s in ", + config_path, conn->set.value_format); + } + conn->dict = dict_init(conn->set.uri, DICT_DATA_TYPE_STRING, "", + global_auth_settings->base_dir); + + conn->next = connections; + connections = conn; + return conn; +} + +void db_dict_unref(struct dict_connection **_conn) +{ + struct dict_connection *conn = *_conn; + + *_conn = NULL; + if (--conn->refcount > 0) + return; + + dict_deinit(&conn->dict); + pool_unref(&conn->pool); +} + +struct db_dict_value_iter { + struct json_parser *parser; + string_t *key; + const char *error; +}; + +struct db_dict_value_iter * +db_dict_value_iter_init(struct dict_connection *conn, const char *value) +{ + struct db_dict_value_iter *iter; + + i_assert(strcmp(conn->set.value_format, "json") == 0); + + /* hardcoded for now for JSON value. make it more modular when other + value types are supported. */ + iter = i_new(struct db_dict_value_iter, 1); + iter->key = str_new(default_pool, 64); + iter->parser = json_parser_init((const void *)value, strlen(value)); + return iter; +} + +bool db_dict_value_iter_next(struct db_dict_value_iter *iter, + const char **key_r, const char **value_r) +{ + enum json_type type; + const char *value; + + if (!json_parse_next(iter->parser, &type, &value)) + return FALSE; + if (type != JSON_TYPE_OBJECT_KEY) { + iter->error = "Object expected"; + return FALSE; + } + if (*value == '\0') { + iter->error = "Empty object key"; + return FALSE; + } + str_truncate(iter->key, 0); + str_append(iter->key, value); + + if (!json_parse_next(iter->parser, &type, &value)) { + iter->error = "Missing value"; + return FALSE; + } + *key_r = str_c(iter->key); + *value_r = value; + return TRUE; +} + +int db_dict_value_iter_deinit(struct db_dict_value_iter **_iter, + const char **error_r) +{ + struct db_dict_value_iter *iter = *_iter; + + *_iter = NULL; + + *error_r = iter->error; + if (json_parser_deinit(&iter->parser, &iter->error) < 0 && + *error_r == NULL) + *error_r = iter->error; + str_free(&iter->key); + i_free(iter); + return *error_r != NULL ? -1 : 0; +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/auth/db-dict.h Fri Aug 10 05:24:07 2012 +0300 @@ -0,0 +1,37 @@ +#ifndef DB_DICT_H +#define DB_DICT_H + +#include "sql-api.h" + +struct db_dict_settings { + const char *uri; + const char *password_key; + const char *user_key; + const char *iterate_prefix; + bool iterate_disable; + const char *value_format; + const char *default_pass_scheme; +}; + +struct dict_connection { + struct dict_connection *next; + + pool_t pool; + int refcount; + + char *config_path; + struct db_dict_settings set; + struct dict *dict; +}; + +struct dict_connection *db_dict_init(const char *config_path); +void db_dict_unref(struct dict_connection **conn); + +struct db_dict_value_iter * +db_dict_value_iter_init(struct dict_connection *conn, const char *value); +bool db_dict_value_iter_next(struct db_dict_value_iter *iter, + const char **key_r, const char **value_r); +int db_dict_value_iter_deinit(struct db_dict_value_iter **iter, + const char **error_r); + +#endif
--- a/src/auth/db-ldap.c Fri Aug 10 04:56:56 2012 +0300 +++ b/src/auth/db-ldap.c Fri Aug 10 05:24:07 2012 +0300 @@ -314,7 +314,8 @@ i_assert(request->msgid == -1); request->msgid = - ldap_search(conn->ld, srequest->base, conn->set.ldap_scope, + ldap_search(conn->ld, *srequest->base == '\0' ? NULL : + srequest->base, conn->set.ldap_scope, srequest->filter, srequest->attributes, 0); if (request->msgid == -1) { auth_request_log_error(request->auth_request, "ldap",
--- a/src/auth/db-passwd-file.c Fri Aug 10 04:56:56 2012 +0300 +++ b/src/auth/db-passwd-file.c Fri Aug 10 05:24:07 2012 +0300 @@ -7,6 +7,7 @@ #include "userdb.h" #include "db-passwd-file.h" +#include "array.h" #include "buffer.h" #include "istream.h" #include "hash.h" @@ -91,7 +92,7 @@ } if (*args == NULL) { - if (pw->db->userdb) { + if (pw->db->userdb_warn_missing) { i_error("passwd-file %s: User %s is missing " "userdb info", pw->path, username); } @@ -291,6 +292,15 @@ return NULL; } +static void db_passwd_file_set_userdb(struct db_passwd_file *db) +{ + db->userdb = TRUE; + /* warn about missing userdb fields only when there aren't any other + userdbs. */ + db->userdb_warn_missing = + array_count(&global_auth_settings->userdbs) == 1; +} + struct db_passwd_file * db_passwd_file_init(const char *path, bool userdb, bool debug) { @@ -301,13 +311,15 @@ db = db_passwd_file_find(path); if (db != NULL) { db->refcount++; - db->userdb = TRUE; + if (userdb) + db_passwd_file_set_userdb(db); return db; } db = i_new(struct db_passwd_file, 1); db->refcount = 1; - db->userdb = userdb; + if (userdb) + db_passwd_file_set_userdb(db); db->debug = debug; for (p = path; *p != '\0'; p++) {
--- a/src/auth/db-passwd-file.h Fri Aug 10 04:56:56 2012 +0300 +++ b/src/auth/db-passwd-file.h Fri Aug 10 05:24:07 2012 +0300 @@ -37,6 +37,7 @@ unsigned int vars:1; unsigned int userdb:1; + unsigned int userdb_warn_missing:1; unsigned int debug:1; };
--- a/src/auth/main.c Fri Aug 10 04:56:56 2012 +0300 +++ b/src/auth/main.c Fri Aug 10 05:24:07 2012 +0300 @@ -16,6 +16,7 @@ #include "master-service.h" #include "master-service-settings.h" #include "master-interface.h" +#include "dict.h" #include "password-scheme.h" #include "passdb-cache.h" #include "mech.h" @@ -192,6 +193,7 @@ auth_penalty = auth_penalty_init(AUTH_PENALTY_ANVIL_PATH); mech_init(global_auth_settings); mech_reg = mech_register_init(global_auth_settings); + dict_drivers_register_builtin(); auths_preinit(global_auth_settings, auth_set_pool, mech_reg, services); @@ -260,6 +262,7 @@ auth_request_handler_deinit(); /* there are no more auth requests */ auths_free(); + dict_drivers_unregister_builtin(); auth_client_connections_destroy_all(); auth_master_connections_destroy_all();
--- a/src/auth/mech-gssapi.c Fri Aug 10 04:56:56 2012 +0300 +++ b/src/auth/mech-gssapi.c Fri Aug 10 05:24:07 2012 +0300 @@ -78,6 +78,9 @@ static gss_OID_desc mech_gssapi_krb5_oid = { 9, "\x2a\x86\x48\x86\xf7\x12\x01\x02\x02" }; +static int +mech_gssapi_wrap(struct gssapi_auth_request *request, gss_buffer_desc inbuf); + static void mech_gssapi_log_error(struct auth_request *request, OM_uint32 status_value, int status_type, const char *description) @@ -214,6 +217,21 @@ return name; } +static gss_name_t +duplicate_name(struct auth_request *request, gss_name_t old) +{ + OM_uint32 major_status, minor_status; + gss_name_t new; + + major_status = gss_duplicate_name(&minor_status, old, &new); + if (GSS_ERROR(major_status)) { + mech_gssapi_log_error(request, major_status, GSS_C_GSS_CODE, + "gss_duplicate_name"); + return GSS_C_NO_NAME; + } + return new; +} + static bool data_has_nuls(const void *data, unsigned int len) { const unsigned char *c = data; @@ -328,9 +346,15 @@ } if (ret == 0) { - auth_request_handler_reply_continue(auth_request, - output_token.value, - output_token.length); + if (output_token.length > 0) { + auth_request_handler_reply_continue(auth_request, + output_token.value, + output_token.length); + } else { + /* If there is no output token, go straight to wrap, + which is expecting an empty input token. */ + ret = mech_gssapi_wrap(request, output_token); + } } (void)gss_release_buffer(&minor_status, &output_token); return ret; @@ -415,8 +439,8 @@ bool authorized = FALSE; /* Parse out the principal's username */ - if (!get_display_name(&request->auth_request, name, &name_type, - &princ_display_name) < 0) + if (get_display_name(&request->auth_request, name, &name_type, + &princ_display_name) < 0) return FALSE; if (!mech_gssapi_oid_cmp(name_type, GSS_KRB5_NT_PRINCIPAL_NAME) && @@ -574,22 +598,32 @@ /* outbuf[0] contains bitmask for selected security layer, outbuf[1..3] contains maximum output_message size */ - if (outbuf.length <= 4) { + if (outbuf.length < 4) { auth_request_log_error(auth_request, "gssapi", "Invalid response length"); return -1; } - name = (unsigned char *)outbuf.value + 4; - name_len = outbuf.length - 4; + + if (outbuf.length > 4) { + name = (unsigned char *)outbuf.value + 4; + name_len = outbuf.length - 4; - if (data_has_nuls(name, name_len)) { - auth_request_log_info(auth_request, "gssapi", - "authz_name has NULs"); - return -1; + if (data_has_nuls(name, name_len)) { + auth_request_log_info(auth_request, "gssapi", + "authz_name has NULs"); + return -1; + } + + login_user = p_strndup(auth_request->pool, name, name_len); + request->authz_name = import_name(auth_request, name, name_len); + } else { + request->authz_name = duplicate_name(auth_request, + request->authn_name); + if (get_display_name(auth_request, request->authz_name, + NULL, &login_user) < 0) + return -1; } - login_user = p_strndup(auth_request->pool, name, name_len); - request->authz_name = import_name(auth_request, name, name_len); if (request->authz_name == GSS_C_NO_NAME) { auth_request_log_info(auth_request, "gssapi", "no authz_name"); return -1;
--- a/src/auth/mech-winbind.c Fri Aug 10 04:56:56 2012 +0300 +++ b/src/auth/mech-winbind.c Fri Aug 10 05:24:07 2012 +0300 @@ -241,7 +241,7 @@ } else if (strcmp(token[0], "AF") == 0) { const char *user, *p, *error; - user = gss_spnego ? token[2] : token[1]; + user = t_strarray_join(gss_spnego ? token+2 : token+1, " "); i_assert(user != NULL); p = strchr(user, '\\');
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/auth/passdb-dict.c Fri Aug 10 05:24:07 2012 +0300 @@ -0,0 +1,197 @@ +/* Copyright (c) 2012 Dovecot authors, see the included COPYING file */ + +#include "auth-common.h" +#include "passdb.h" + +#include "str.h" +#include "var-expand.h" +#include "dict.h" +#include "password-scheme.h" +#include "auth-cache.h" +#include "db-dict.h" + +#include <stdlib.h> +#include <string.h> + +struct dict_passdb_module { + struct passdb_module module; + + struct dict_connection *conn; +}; + +struct passdb_dict_request { + struct auth_request *auth_request; + union { + verify_plain_callback_t *verify_plain; + lookup_credentials_callback_t *lookup_credentials; + } callback; +}; + +static int +dict_query_save_results(struct auth_request *auth_request, + struct dict_connection *conn, const char *result) +{ + struct db_dict_value_iter *iter; + const char *key, *value, *error; + + iter = db_dict_value_iter_init(conn, result); + while (db_dict_value_iter_next(iter, &key, &value)) { + if (value != NULL) { + auth_request_set_field(auth_request, key, value, + conn->set.default_pass_scheme); + } + } + if (db_dict_value_iter_deinit(&iter, &error) < 0) { + auth_request_log_error(auth_request, "dict", + "Value '%s' not in valid %s format: %s", + result, conn->set.value_format, error); + return -1; + } + return 0; +} + +static enum passdb_result +passdb_dict_lookup_key(struct auth_request *auth_request, + struct dict_passdb_module *module, const char *key) +{ + const char *value; + int ret; + + auth_request_log_debug(auth_request, "dict", "lookup %s", key); + ret = dict_lookup(module->conn->dict, pool_datastack_create(), + key, &value); + if (ret < 0) { + auth_request_log_error(auth_request, "dict", "Lookup failed"); + return PASSDB_RESULT_INTERNAL_FAILURE; + } else if (ret == 0) { + auth_request_log_info(auth_request, "dict", "unknown user"); + return PASSDB_RESULT_USER_UNKNOWN; + } else { + auth_request_log_debug(auth_request, "dict", + "result: %s", value); + if (dict_query_save_results(auth_request, module->conn, value) < 0) + return PASSDB_RESULT_INTERNAL_FAILURE; + + if (auth_request->passdb_password == NULL && + !auth_request->no_password) { + auth_request_log_info(auth_request, "dict", + "No password returned (and no nopassword)"); + return PASSDB_RESULT_PASSWORD_MISMATCH; + } else { + return PASSDB_RESULT_OK; + } + } +} + +static void passdb_dict_lookup_pass(struct passdb_dict_request *dict_request) +{ + struct auth_request *auth_request = dict_request->auth_request; + struct passdb_module *_module = auth_request->passdb->passdb; + struct dict_passdb_module *module = + (struct dict_passdb_module *)_module; + string_t *key; + const char *password = NULL, *scheme = NULL; + enum passdb_result passdb_result; + int ret; + + key = t_str_new(512); + str_append(key, DICT_PATH_SHARED); + var_expand(key, module->conn->set.password_key, + auth_request_get_var_expand_table(auth_request, NULL)); + + if (*module->conn->set.password_key == '\0') { + auth_request_log_error(auth_request, "dict", + "password_key not specified"); + passdb_result = PASSDB_RESULT_INTERNAL_FAILURE; + } else { + passdb_result = passdb_dict_lookup_key(auth_request, module, + str_c(key)); + } + + if (passdb_result == PASSDB_RESULT_OK) { + /* passdb_password may change on the way, + so we'll need to strdup. */ + password = t_strdup(auth_request->passdb_password); + scheme = password_get_scheme(&password); + /* auth_request_set_field() sets scheme */ + i_assert(password == NULL || scheme != NULL); + } + + if (auth_request->credentials_scheme != NULL) { + passdb_handle_credentials(passdb_result, password, scheme, + dict_request->callback.lookup_credentials, + auth_request); + } else { + if (password != NULL) { + ret = auth_request_password_verify(auth_request, + auth_request->mech_password, + password, scheme, "dict"); + passdb_result = ret > 0 ? PASSDB_RESULT_OK : + PASSDB_RESULT_PASSWORD_MISMATCH; + } + + dict_request->callback.verify_plain(passdb_result, + auth_request); + } +} + +static void dict_verify_plain(struct auth_request *request, + const char *password ATTR_UNUSED, + verify_plain_callback_t *callback) +{ + struct passdb_dict_request *dict_request; + + dict_request = p_new(request->pool, struct passdb_dict_request, 1); + dict_request->auth_request = request; + dict_request->callback.verify_plain = callback; + + passdb_dict_lookup_pass(dict_request); +} + +static void dict_lookup_credentials(struct auth_request *request, + lookup_credentials_callback_t *callback) +{ + struct passdb_dict_request *dict_request; + + dict_request = p_new(request->pool, struct passdb_dict_request, 1); + dict_request->auth_request = request; + dict_request->callback.lookup_credentials = callback; + + passdb_dict_lookup_pass(dict_request); +} + +static struct passdb_module * +passdb_dict_preinit(pool_t pool, const char *args) +{ + struct dict_passdb_module *module; + struct dict_connection *conn; + + module = p_new(pool, struct dict_passdb_module, 1); + module->conn = conn = db_dict_init(args); + + module->module.blocking = TRUE; + module->module.cache_key = + auth_cache_parse_key(pool, conn->set.password_key); + module->module.default_pass_scheme = conn->set.default_pass_scheme; + return &module->module; +} + +static void passdb_dict_deinit(struct passdb_module *_module) +{ + struct dict_passdb_module *module = + (struct dict_passdb_module *)_module; + + db_dict_unref(&module->conn); +} + +struct passdb_module_interface passdb_dict = { + "dict", + + passdb_dict_preinit, + NULL, + passdb_dict_deinit, + + dict_verify_plain, + dict_lookup_credentials, + NULL +};
--- a/src/auth/passdb.c Fri Aug 10 04:56:56 2012 +0300 +++ b/src/auth/passdb.c Fri Aug 10 05:24:07 2012 +0300 @@ -278,6 +278,7 @@ extern struct passdb_module_interface passdb_passwd; extern struct passdb_module_interface passdb_bsdauth; +extern struct passdb_module_interface passdb_dict; extern struct passdb_module_interface passdb_shadow; extern struct passdb_module_interface passdb_passwd_file; extern struct passdb_module_interface passdb_pam; @@ -294,6 +295,7 @@ i_array_init(&passdb_modules, 16); passdb_register_module(&passdb_passwd); passdb_register_module(&passdb_bsdauth); + passdb_register_module(&passdb_dict); passdb_register_module(&passdb_passwd_file); passdb_register_module(&passdb_pam); passdb_register_module(&passdb_checkpassword);
--- a/src/auth/test-auth-cache.c Fri Aug 10 04:56:56 2012 +0300 +++ b/src/auth/test-auth-cache.c Fri Aug 10 05:24:07 2012 +0300 @@ -6,6 +6,11 @@ #include "test-common.h" const struct var_expand_table auth_request_var_expand_static_tab[] = { + /* these 3 must be in this order */ + { 'u', NULL, "user" }, + { 'n', NULL, "username" }, + { 'd', NULL, "domain" }, + { 'a', NULL, NULL }, { '\0', NULL, "longb" }, { 'c', NULL, "longc" }, @@ -24,14 +29,21 @@ struct { const char *in, *out; } tests[] = { + { "%n@%d", "%u" }, + { "%{username}@%{domain}", "%u" }, + { "%n%d%u", "%u" }, + { "%n", "%n" }, + { "%d", "%d" }, + { "%a%b%u", "%u\t%a\t%b" }, + { "foo%5.5Mabar", "%a" }, { "foo%5.5M{longb}bar", "%{longb}" }, { "foo%5.5Mcbar", "%c" }, - { "foo%5.5M{longc}bar", "%{longc}" }, + { "foo%5.5M{longc}bar", "%c" }, { "%a%b", "%a\t%b" }, { "%a%{longb}%a", "%a\t%{longb}" }, - { "%{longc}%c", "%{longc}" }, - { "%c%a%{longc}%c", "%c\t%a" }, + { "%{longc}%c", "%c" }, + { "%c%a%{longc}%c", "%a\t%c" }, { "%a%{env:foo}%{env:foo}%a", "%a\t%{env:foo}\t%{env:foo}" } }; const char *cache_key;
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/auth/userdb-dict.c Fri Aug 10 05:24:07 2012 +0300 @@ -0,0 +1,207 @@ +/* Copyright (c) 2012 Dovecot authors, see the included COPYING file */ + +#include "auth-common.h" +#include "userdb.h" + +#include "ioloop.h" +#include "array.h" +#include "str.h" +#include "var-expand.h" +#include "auth-cache.h" +#include "db-dict.h" + +#include <dict.h> +#include <stdlib.h> + +struct dict_userdb_module { + struct userdb_module module; + + struct dict_connection *conn; +}; + +struct dict_userdb_iterate_context { + struct userdb_iterate_context ctx; + + userdb_callback_t *userdb_callback; + const char *key_prefix; + unsigned int key_prefix_len; + struct dict_iterate_context *iter; +}; + +static int +dict_query_save_results(struct auth_request *auth_request, + struct dict_connection *conn, const char *result) +{ + struct db_dict_value_iter *iter; + const char *key, *value, *error; + + auth_request_init_userdb_reply(auth_request); + + iter = db_dict_value_iter_init(conn, result); + while (db_dict_value_iter_next(iter, &key, &value)) { + if (value != NULL) + auth_request_set_userdb_field(auth_request, key, value); + } + if (db_dict_value_iter_deinit(&iter, &error) < 0) { + auth_request_log_error(auth_request, "dict", + "Value '%s' not in valid %s format: %s", + result, conn->set.value_format, error); + return -1; + } + return 0; +} + +static void userdb_dict_lookup(struct auth_request *auth_request, + userdb_callback_t *callback) +{ + struct userdb_module *_module = auth_request->userdb->userdb; + struct dict_userdb_module *module = + (struct dict_userdb_module *)_module; + string_t *key; + enum userdb_result userdb_result; + const char *value; + int ret; + + if (*module->conn->set.user_key == '\0') { + auth_request_log_error(auth_request, "dict", + "user_key not specified"); + callback(USERDB_RESULT_INTERNAL_FAILURE, auth_request); + return; + } + + key = t_str_new(512); + str_append(key, DICT_PATH_SHARED); + var_expand(key, module->conn->set.user_key, + auth_request_get_var_expand_table(auth_request, NULL)); + + auth_request_log_debug(auth_request, "dict", "lookup %s", str_c(key)); + ret = dict_lookup(module->conn->dict, pool_datastack_create(), + str_c(key), &value); + if (ret < 0) { + auth_request_log_error(auth_request, "dict", "Lookup failed"); + userdb_result = USERDB_RESULT_INTERNAL_FAILURE; + } else if (ret == 0) { + auth_request_log_info(auth_request, "dict", "unknown user"); + userdb_result = USERDB_RESULT_USER_UNKNOWN; + } else { + auth_request_log_debug(auth_request, "dict", + "result: %s", value); + if (dict_query_save_results(auth_request, module->conn, value) < 0) + userdb_result = USERDB_RESULT_INTERNAL_FAILURE; + else + userdb_result = USERDB_RESULT_OK; + } + callback(userdb_result, auth_request); +} + +static struct userdb_iterate_context * +userdb_dict_iterate_init(struct auth_request *auth_request, + userdb_iter_callback_t *callback, void *context) +{ + struct userdb_module *_module = auth_request->userdb->userdb; + struct dict_userdb_module *module = + (struct dict_userdb_module *)_module; + struct dict_userdb_iterate_context *ctx; + const struct var_expand_table *vars; + string_t *path; + + ctx = i_new(struct dict_userdb_iterate_context, 1); + ctx->ctx.auth_request = auth_request; + ctx->ctx.callback = callback; + ctx->ctx.context = context; + auth_request_ref(auth_request); + + if (*module->conn->set.iterate_prefix == '\0') { + if (!module->conn->set.iterate_disable) { + auth_request_log_error(auth_request, "dict", + "iterate: iterate_prefix not set"); + ctx->ctx.failed = TRUE; + } + return &ctx->ctx; + } + + path = t_str_new(128); + str_append(path, DICT_PATH_SHARED); + vars = auth_request_get_var_expand_table(auth_request, NULL); + var_expand(path, module->conn->set.iterate_prefix, vars); + ctx->key_prefix = p_strdup(auth_request->pool, str_c(path)); + ctx->key_prefix_len = strlen(ctx->key_prefix); + + ctx->iter = dict_iterate_init(module->conn->dict, ctx->key_prefix, 0); + auth_request_log_debug(auth_request, "dict", "iterate: prefix=%s", + ctx->key_prefix); + return &ctx->ctx; +} + +static const char * +userdb_dict_get_user(struct dict_userdb_iterate_context *ctx, const char *key) +{ + i_assert(strncmp(key, ctx->key_prefix, ctx->key_prefix_len) == 0); + + return key + ctx->key_prefix_len; +} + +static void userdb_dict_iterate_next(struct userdb_iterate_context *_ctx) +{ + struct dict_userdb_iterate_context *ctx = + (struct dict_userdb_iterate_context *)_ctx; + const char *key, *value; + + if (ctx->iter != NULL && dict_iterate(ctx->iter, &key, &value)) + _ctx->callback(userdb_dict_get_user(ctx, key), _ctx->context); + else + _ctx->callback(NULL, _ctx->context); +} + +static int userdb_dict_iterate_deinit(struct userdb_iterate_context *_ctx) +{ + struct dict_userdb_iterate_context *ctx = + (struct dict_userdb_iterate_context *)_ctx; + int ret = _ctx->failed ? -1 : 0; + + if (ctx->iter != NULL) { + if (dict_iterate_deinit(&ctx->iter) < 0) + ret = -1; + } + auth_request_unref(&ctx->ctx.auth_request); + i_free(ctx); + return ret; +} + +static struct userdb_module * +userdb_dict_preinit(pool_t pool, const char *args) +{ + struct dict_userdb_module *module; + struct dict_connection *conn; + + module = p_new(pool, struct dict_userdb_module, 1); + module->conn = conn = db_dict_init(args); + + module->module.blocking = TRUE; + module->module.cache_key = + auth_cache_parse_key(pool, conn->set.user_key); + return &module->module; +} + +static void userdb_dict_deinit(struct userdb_module *_module) +{ + struct dict_userdb_module *module = + (struct dict_userdb_module *)_module; + + db_dict_unref(&module->conn); +} + +struct userdb_module_interface userdb_dict = +{ + "dict", + + userdb_dict_preinit, + NULL, + userdb_dict_deinit, + + userdb_dict_lookup, + + userdb_dict_iterate_init, + userdb_dict_iterate_next, + userdb_dict_iterate_deinit +};
--- a/src/auth/userdb.c Fri Aug 10 04:56:56 2012 +0300 +++ b/src/auth/userdb.c Fri Aug 10 05:24:07 2012 +0300 @@ -230,6 +230,7 @@ extern struct userdb_module_interface userdb_sql; extern struct userdb_module_interface userdb_nss; extern struct userdb_module_interface userdb_checkpassword; +extern struct userdb_module_interface userdb_dict; void userdbs_init(void) { @@ -244,6 +245,7 @@ userdb_register_module(&userdb_sql); userdb_register_module(&userdb_nss); userdb_register_module(&userdb_checkpassword); + userdb_register_module(&userdb_dict); } void userdbs_deinit(void)
--- a/src/config/config-connection.c Fri Aug 10 04:56:56 2012 +0300 +++ b/src/config/config-connection.c Fri Aug 10 05:24:07 2012 +0300 @@ -100,7 +100,7 @@ path = master_service_get_config_path(master_service); if (config_parse_file(path, TRUE, "", &error) <= 0) { o_stream_nsend_str(conn->output, - t_strconcat("ERROR ", error, "\n", NULL)); + t_strconcat("\nERROR ", error, "\n", NULL)); config_connection_destroy(conn); return -1; }
--- a/src/config/doveconf.c Fri Aug 10 04:56:56 2012 +0300 +++ b/src/config/doveconf.c Fri Aug 10 05:24:07 2012 +0300 @@ -452,7 +452,7 @@ unsigned int len; bool dump_section = FALSE; - ctx = config_dump_human_init("", scope, TRUE); + ctx = config_dump_human_init("", scope, FALSE); config_export_by_filter(ctx->export_ctx, filter); if (config_export_finish(&ctx->export_ctx) < 0) return -1; @@ -694,6 +694,11 @@ ret2 = config_export_finish(&ctx); } else if (setting_name_filters != NULL) { ret2 = 0; + /* ignore settings-check failures in configuration. this allows + using doveconf to lookup settings for things like install or + uninstall scripts where the configuration might + (temporarily) not be fully usable */ + ret = 0; for (i = 0; setting_name_filters[i] != NULL; i++) { if (config_dump_one(&filter, hide_key, scope, setting_name_filters[i]) < 0)
--- a/src/config/old-set-parser.c Fri Aug 10 04:56:56 2012 +0300 +++ b/src/config/old-set-parser.c Fri Aug 10 05:24:07 2012 +0300 @@ -307,7 +307,7 @@ return TRUE; } p = strrchr(value, ':'); - if (p != NULL) { + if (p != NULL && listen_has_port(value)) { obsolete(ctx, "%s=..:port has been replaced by service { inet_listener { port } }", key); value = t_strdup_until(value, p++); if (config_filter_match(&old_section->filter, &imap_filter)) {
--- a/src/director/director-settings.c Fri Aug 10 04:56:56 2012 +0300 +++ b/src/director/director-settings.c Fri Aug 10 05:24:07 2012 +0300 @@ -80,7 +80,7 @@ .director_servers = "", .director_mail_servers = "", - .director_username_hash = "%u", + .director_username_hash = "%Lu", .director_user_expire = 60*15, .director_doveadm_port = 0 };
--- a/src/doveadm/client-connection.c Fri Aug 10 04:56:56 2012 +0300 +++ b/src/doveadm/client-connection.c Fri Aug 10 05:24:07 2012 +0300 @@ -21,6 +21,8 @@ #define MAX_INBUF_SIZE 1024 +static void client_connection_input(struct client_connection *conn); + struct client_connection { pool_t pool; @@ -208,6 +210,10 @@ return FALSE; } + /* make sure client_connection_input() isn't called by the ioloop that + is going to be run by doveadm_mail_cmd_server_run() */ + io_remove(&conn->io); + o_stream_cork(conn->output); ctx = doveadm_mail_cmd_server_parse(cmd_name, conn->set, &input, argc, args); if (ctx == NULL) @@ -220,6 +226,8 @@ net_set_nonblock(conn->fd, FALSE); (void)o_stream_flush(conn->output); net_set_nonblock(conn->fd, TRUE); + + conn->io = io_add(conn->fd, IO_READ, client_connection_input, conn); return TRUE; }
--- a/src/doveadm/doveadm-auth.c Fri Aug 10 04:56:56 2012 +0300 +++ b/src/doveadm/doveadm-auth.c Fri Aug 10 05:24:07 2012 +0300 @@ -27,6 +27,8 @@ bool success; }; +static void auth_cmd_help(doveadm_command_t *cmd); + static int cmd_user_input(const char *auth_socket_path, const struct authtest_input *input, const char *show_field) @@ -214,12 +216,52 @@ auth_master_deinit(&conn); } +static void cmd_auth_cache_flush(int argc, char *argv[]) +{ + const char *auth_socket_path = NULL; + struct auth_master_connection *conn; + unsigned int count; + int c; + + while ((c = getopt(argc, argv, "a:")) > 0) { + switch (c) { + case 'a': + auth_socket_path = optarg; + break; + default: + auth_cmd_help(cmd_auth_cache_flush); + } + } + argv += optind; + + if (auth_socket_path == NULL) { + auth_socket_path = t_strconcat(doveadm_settings->base_dir, + "/auth-master", NULL); + } + + conn = auth_master_init(auth_socket_path, 0); + if (auth_master_cache_flush(conn, (void *)argv, &count) < 0) { + i_error("Cache flush failed"); + doveadm_exit_code = EX_TEMPFAIL; + } else { + printf("%u cache entries flushed\n", count); + } + auth_master_deinit(&conn); +} + static void cmd_auth(int argc, char *argv[]) { const char *auth_socket_path = NULL; struct authtest_input input; int c; + if (null_strcmp(argv[1], "cache") == 0 && + null_strcmp(argv[2], "flush") == 0) { + /* kludgy: handle "doveadm auth cache" command instead */ + cmd_auth_cache_flush(argc-2, argv+2); + return; + } + memset(&input, 0, sizeof(input)); input.info.service = "doveadm"; @@ -232,12 +274,12 @@ auth_user_info_parse(&input.info, optarg); break; default: - help(&doveadm_cmd_auth); + auth_cmd_help(cmd_auth); } } if (optind == argc) - help(&doveadm_cmd_auth); + auth_cmd_help(cmd_auth); input.username = argv[optind++]; input.password = argv[optind] != NULL ? argv[optind++] : @@ -331,12 +373,12 @@ auth_user_info_parse(&input.info, optarg); break; default: - help(&doveadm_cmd_user); + auth_cmd_help(cmd_user); } } if (optind == argc) - help(&doveadm_cmd_user); + auth_cmd_help(cmd_user); have_wildcards = FALSE; for (i = optind; argv[i] != NULL; i++) { @@ -380,12 +422,30 @@ mail_storage_service_deinit(&storage_service); } -struct doveadm_cmd doveadm_cmd_auth = { - cmd_auth, "auth", - "[-a <auth socket path>] [-x <auth info>] <user> [<password>]" +struct doveadm_cmd doveadm_cmd_auth[] = { + { cmd_auth, "auth", + "[-a <auth socket path>] [-x <auth info>] <user> [<password>]" }, + { cmd_auth_cache_flush, "auth cache flush", + "[-a <master socket path>] [<user> [...]]" }, + { cmd_user, "user", + "[-a <userdb socket path>] [-x <auth info>] [-f field] [-m] <user mask> [...]" } }; -struct doveadm_cmd doveadm_cmd_user = { - cmd_user, "user", - "[-a <userdb socket path>] [-x <auth info>] [-f field] [-m] <user mask> [...]" -}; +static void auth_cmd_help(doveadm_command_t *cmd) +{ + unsigned int i; + + for (i = 0; i < N_ELEMENTS(doveadm_cmd_auth); i++) { + if (doveadm_cmd_auth[i].cmd == cmd) + help(&doveadm_cmd_auth[i]); + } + i_unreached(); +} + +void doveadm_register_auth_commands(void) +{ + unsigned int i; + + for (i = 0; i < N_ELEMENTS(doveadm_cmd_auth); i++) + doveadm_register_cmd(&doveadm_cmd_auth[i]); +}
--- a/src/doveadm/doveadm-mail-mailbox-status.c Fri Aug 10 04:56:56 2012 +0300 +++ b/src/doveadm/doveadm-mail-mailbox-status.c Fri Aug 10 05:24:07 2012 +0300 @@ -154,6 +154,7 @@ int ret = 0; memset(&ctx->total_status, 0, sizeof(ctx->total_status)); + memset(&ctx->total_metadata, 0, sizeof(ctx->total_metadata)); iter = doveadm_mailbox_list_iter_init(_ctx, user, ctx->search_args, iter_flags);
--- a/src/doveadm/doveadm.c Fri Aug 10 04:56:56 2012 +0300 +++ b/src/doveadm/doveadm.c Fri Aug 10 05:24:07 2012 +0300 @@ -271,8 +271,6 @@ &doveadm_cmd_config, &doveadm_cmd_stop, &doveadm_cmd_reload, - &doveadm_cmd_auth, - &doveadm_cmd_user, &doveadm_cmd_dump, &doveadm_cmd_pw, &doveadm_cmd_who, @@ -342,6 +340,7 @@ quick_init = TRUE; } else { quick_init = FALSE; + doveadm_register_auth_commands(); doveadm_register_director_commands(); doveadm_register_instance_commands(); doveadm_register_mount_commands();
--- a/src/doveadm/doveadm.h Fri Aug 10 04:56:56 2012 +0300 +++ b/src/doveadm/doveadm.h Fri Aug 10 05:24:07 2012 +0300 @@ -22,8 +22,6 @@ extern struct doveadm_cmd doveadm_cmd_stop; extern struct doveadm_cmd doveadm_cmd_reload; -extern struct doveadm_cmd doveadm_cmd_auth; -extern struct doveadm_cmd doveadm_cmd_user; extern struct doveadm_cmd doveadm_cmd_dump; extern struct doveadm_cmd doveadm_cmd_pw; extern struct doveadm_cmd doveadm_cmd_who; @@ -41,6 +39,7 @@ void help(const struct doveadm_cmd *cmd) ATTR_NORETURN; void doveadm_master_send_signal(int signo); +void doveadm_register_auth_commands(void); void doveadm_register_director_commands(void); void doveadm_register_proxy_commands(void); void doveadm_register_log_commands(void);
--- a/src/doveadm/main.c Fri Aug 10 04:56:56 2012 +0300 +++ b/src/doveadm/main.c Fri Aug 10 05:24:07 2012 +0300 @@ -68,9 +68,12 @@ &doveadm_setting_parser_info, NULL }; + enum master_service_flags service_flags = + MASTER_SERVICE_FLAG_KEEP_CONFIG_OPEN; const char *error; - master_service = master_service_init("doveadm", 0, &argc, &argv, ""); + master_service = master_service_init("doveadm", service_flags, + &argc, &argv, ""); if (master_getopt(master_service) > 0) return FATAL_DEFAULT;
--- a/src/imap-login/client.c Fri Aug 10 04:56:56 2012 +0300 +++ b/src/imap-login/client.c Fri Aug 10 05:24:07 2012 +0300 @@ -62,7 +62,7 @@ str_append(cap_str, imap_client->set->imap_capability + 1); } - if (ssl_initialized && !client->tls) + if (client_is_tls_enabled(client) && !client->tls) str_append(cap_str, " STARTTLS"); if (client->set->disable_plaintext_auth && !client->secured) str_append(cap_str, " LOGINDISABLED");
--- a/src/imap/cmd-delete.c Fri Aug 10 04:56:56 2012 +0300 +++ b/src/imap/cmd-delete.c Fri Aug 10 05:24:07 2012 +0300 @@ -10,6 +10,7 @@ struct mailbox *box; const char *name, *errstr; enum mail_error error; + bool disconnect = FALSE; /* <mailbox> */ if (!client_read_string_args(cmd, 1, &name)) @@ -31,6 +32,7 @@ /* deleting selected mailbox. close it first */ client_search_updates_free(client); mailbox_free(&client->mailbox); + disconnect = TRUE; } if (mailbox_delete(box) == 0) @@ -45,5 +47,10 @@ } } mailbox_free(&box); + + if (disconnect) { + client_disconnect_with_error(cmd->client, + "Selected mailbox was deleted, have to disconnect."); + } return TRUE; }
--- a/src/imap/cmd-list.c Fri Aug 10 04:56:56 2012 +0300 +++ b/src/imap/cmd-list.c Fri Aug 10 05:24:07 2012 +0300 @@ -236,17 +236,22 @@ enum imap_match_result match; const char *ns_prefix, *p; bool inboxcase; + unsigned int skip_len; + + skip_len = strlen(ctx->ref); + if (strncmp(ctx->ns->prefix, ctx->ref, skip_len) != 0) + skip_len = 0; inboxcase = strncasecmp(ctx->ns->prefix, "INBOX", 5) == 0 && ctx->ns->prefix[5] == mail_namespace_get_sep(ctx->ns); glob = imap_match_init_multiple(pool_datastack_create(), ctx->patterns, inboxcase, mail_namespace_get_sep(ctx->ns)); - ns_prefix = ctx->ns->prefix; + ns_prefix = ctx->ns->prefix + skip_len; match = imap_match(glob, ns_prefix); if (match == IMAP_MATCH_YES) { - return !ctx->cur_ns_skip_trailing_sep ? ns_prefix : - t_strndup(ns_prefix, strlen(ns_prefix)-1); + return !ctx->cur_ns_skip_trailing_sep ? ctx->ns->prefix : + t_strndup(ctx->ns->prefix, strlen(ctx->ns->prefix)-1); } while ((match & IMAP_MATCH_PARENT) != 0) { @@ -256,7 +261,8 @@ match = imap_match(glob, ns_prefix); } i_assert(match == IMAP_MATCH_YES); - return ns_prefix; + return t_strconcat(t_strndup(ctx->ns->prefix, skip_len), + ns_prefix, NULL); } static void list_reply_append_ns_sep_param(string_t *str, char sep) @@ -364,8 +370,9 @@ client_send_line(ctx->cmd->client, str_c(str)); } -static void list_send_status(struct cmd_list_context *ctx, const char *name, - enum mailbox_info_flags flags) +static void +list_send_status(struct cmd_list_context *ctx, const char *name, + const char *mutf7_name, enum mailbox_info_flags flags) { struct imap_status_result result; struct mail_namespace *ns; @@ -391,7 +398,8 @@ return; } - imap_status_send(ctx->cmd->client, name, &ctx->status_items, &result); + imap_status_send(ctx->cmd->client, mutf7_name, + &ctx->status_items, &result); } static bool list_has_empty_prefix_ns(struct mail_user *user) @@ -481,7 +489,7 @@ ret = client_send_line_next(ctx->cmd->client, str_c(str)); if (ctx->used_status) T_BEGIN { - list_send_status(ctx, name, flags); + list_send_status(ctx, name, str_c(mutf7_name), flags); } T_END; if (ret == 0) { /* buffer is full, continue later */ @@ -508,18 +516,14 @@ skip_namespace_prefix(const char **prefix, const char **pattern, bool inbox_check, char sep) { - size_t pattern_len, prefix_len; + size_t pattern_len, prefix_len, min_len; bool match; prefix_len = strlen(*prefix); pattern_len = strlen(*pattern); + min_len = I_MIN(prefix_len, pattern_len); - if (pattern_len < prefix_len) { - /* eg. namespace prefix = "INBOX.", pattern = "INBOX" */ - return; - } - - match = strncmp(*prefix, *pattern, prefix_len) == 0; + match = strncmp(*prefix, *pattern, min_len) == 0; if (!match && inbox_check) { /* try INBOX check. */ match = prefix_len >= 5 && @@ -531,8 +535,8 @@ } if (match) { - *prefix += prefix_len; - *pattern += prefix_len; + *prefix += min_len; + *pattern += min_len; } }
--- a/src/imap/cmd-status.c Fri Aug 10 04:56:56 2012 +0300 +++ b/src/imap/cmd-status.c Fri Aug 10 05:24:07 2012 +0300 @@ -13,7 +13,7 @@ struct imap_status_items items; struct imap_status_result result; struct mail_namespace *ns; - const char *mailbox, *error; + const char *mailbox, *orig_mailbox, *error; bool selected_mailbox; /* <mailbox> <status items> */ @@ -30,6 +30,7 @@ if (imap_status_parse_items(cmd, list_args, &items) < 0) return TRUE; + orig_mailbox = mailbox; ns = client_find_namespace(cmd, &mailbox); if (ns == NULL) return TRUE; @@ -42,7 +43,7 @@ return TRUE; } - imap_status_send(client, mailbox, &items, &result); + imap_status_send(client, orig_mailbox, &items, &result); if (!selected_mailbox) client_send_tagline(cmd, "OK Status completed."); else {
--- a/src/imap/imap-status.c Fri Aug 10 04:56:56 2012 +0300 +++ b/src/imap/imap-status.c Fri Aug 10 05:24:07 2012 +0300 @@ -96,7 +96,7 @@ return ret; } -void imap_status_send(struct client *client, const char *mailbox, +void imap_status_send(struct client *client, const char *mailbox_mutf7, const struct imap_status_items *items, const struct imap_status_result *result) { @@ -106,7 +106,7 @@ str = t_str_new(128); str_append(str, "* STATUS "); - imap_quote_append_string(str, mailbox, FALSE); + imap_quote_append_string(str, mailbox_mutf7, FALSE); str_append(str, " ("); prefix_len = str_len(str);
--- a/src/imap/imap-status.h Fri Aug 10 04:56:56 2012 +0300 +++ b/src/imap/imap-status.h Fri Aug 10 05:24:07 2012 +0300 @@ -18,7 +18,7 @@ struct mail_namespace *ns, const char *mailbox, const struct imap_status_items *items, struct imap_status_result *result_r, const char **error_r); -void imap_status_send(struct client *client, const char *mailbox, +void imap_status_send(struct client *client, const char *mailbox_mutf7, const struct imap_status_items *items, const struct imap_status_result *result);
--- a/src/lib-auth/auth-master.c Fri Aug 10 04:56:56 2012 +0300 +++ b/src/lib-auth/auth-master.c Fri Aug 10 05:24:07 2012 +0300 @@ -9,6 +9,7 @@ #include "istream.h" #include "ostream.h" #include "str.h" +#include "strescape.h" #include "master-interface.h" #include "auth-master.h" @@ -565,6 +566,48 @@ } static bool +auth_cache_flush_reply_callback(const char *cmd, const char *const *args, + void *context) +{ + unsigned int *countp = context; + + if (strcmp(cmd, "OK") != 0) + *countp = -1U; + else if (args[0] == NULL || str_to_uint(args[0], countp) < 0) + *countp = -1U; + + io_loop_stop(current_ioloop); + return TRUE; +} + +int auth_master_cache_flush(struct auth_master_connection *conn, + const char *const *users, unsigned int *count_r) +{ + string_t *str; + + *count_r = -1U; + + conn->reply_callback = auth_cache_flush_reply_callback; + conn->reply_context = count_r; + + str = t_str_new(128); + str_printfa(str, "CACHE-FLUSH\t%u", auth_master_next_request_id(conn)); + if (users != NULL) { + for (; *users != NULL; users++) { + str_append_c(str, '\t'); + str_tabescape_write(str, *users); + } + } + str_append_c(str, '\n'); + + conn->prefix = "auth cache flush"; + (void)auth_master_run_cmd(conn, str_c(str)); + conn->prefix = DEFAULT_USERDB_LOOKUP_PREFIX; + + return *count_r == -1U ? -1 : 0; +} + +static bool auth_user_list_reply_callback(const char *cmd, const char *const *args, void *context) {
--- a/src/lib-auth/auth-master.h Fri Aug 10 04:56:56 2012 +0300 +++ b/src/lib-auth/auth-master.h Fri Aug 10 05:24:07 2012 +0300 @@ -39,6 +39,10 @@ int auth_master_pass_lookup(struct auth_master_connection *conn, const char *user, const struct auth_user_info *info, pool_t pool, const char *const **fields_r); +/* Flush authentication cache for everyone (users=NULL) or only for specified + users. Returns number of users flushed from cache. */ +int auth_master_cache_flush(struct auth_master_connection *conn, + const char *const *users, unsigned int *count_r); /* Parse userdb extra fields into auth_user_reply structure. */ void auth_user_fields_parse(const char *const *fields, pool_t pool,
--- a/src/lib-charset/charset-iconv.c Fri Aug 10 04:56:56 2012 +0300 +++ b/src/lib-charset/charset-iconv.c Fri Aug 10 05:24:07 2012 +0300 @@ -120,9 +120,8 @@ charset_to_utf8(struct charset_translation *t, const unsigned char *src, size_t *src_size, buffer_t *dest) { - bool dtcase = (t->flags & CHARSET_FLAG_DECOMP_TITLECASE) != 0; enum charset_result result; - size_t pos, used, size, prev_pos = 0, prev_used = 0; + size_t pos, size; size_t prev_invalid_pos = (size_t)-1; bool ret; @@ -141,16 +140,6 @@ prev_invalid_pos = dest->used; } pos++; - } else if (!dtcase) { - /* force buffer to grow */ - used = dest->used; - size = buffer_get_size(dest) - used + 1; - (void)buffer_append_space_unsafe(dest, size); - buffer_set_used_size(dest, used); - } else { - i_assert(dest->used != prev_used || pos != prev_pos); - prev_pos = pos; - prev_used = dest->used; } }
--- a/src/lib-dict/Makefile.am Fri Aug 10 04:56:56 2012 +0300 +++ b/src/lib-dict/Makefile.am Fri Aug 10 05:24:07 2012 +0300 @@ -13,7 +13,9 @@ base_sources = \ dict.c \ dict-client.c \ - dict-file.c + dict-file.c \ + dict-memcached.c \ + dict-redis.c libdict_la_SOURCES = \ $(base_sources)
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/lib-dict/dict-memcached.c Fri Aug 10 05:24:07 2012 +0300 @@ -0,0 +1,376 @@ +/* Copyright (c) 2012 Dovecot authors, see the included COPYING memcached */ + +#include "lib.h" +#include "array.h" +#include "str.h" +#include "istream.h" +#include "ostream.h" +#include "connection.h" +#include "dict-private.h" + +#define MEMCACHED_DEFAULT_PORT 11211 +#define MEMCACHED_DEFAULT_LOOKUP_TIMEOUT_MSECS (1000*30) + +/* we need only very limited memcached functionality, so just define the binary + protocol ourself instead requiring protocol_binary.h */ +#define MEMCACHED_REQUEST_HDR_MAGIC 0x80 +#define MEMCACHED_REPLY_HDR_MAGIC 0x81 + +#define MEMCACHED_REQUEST_HDR_LENGTH 24 +#define MEMCACHED_REPLY_HDR_LENGTH 24 + +#define MEMCACHED_CMD_GET 0x00 + +#define MEMCACHED_DATA_TYPE_RAW 0x00 + +enum memcached_response { + MEMCACHED_RESPONSE_OK = 0x0000, + MEMCACHED_RESPONSE_NOTFOUND = 0x0001, + MEMCACHED_RESPONSE_INTERNALERROR= 0x0084, + MEMCACHED_RESPONSE_BUSY = 0x0085, + MEMCACHED_RESPONSE_TEMPFAILURE = 0x0086 +}; + +struct memcached_connection { + struct connection conn; + struct memcached_dict *dict; + + buffer_t *cmd; + struct { + const unsigned char *value; + unsigned int value_len; + enum memcached_response status; + bool reply_received; + } reply; +}; + +struct memcached_dict { + struct dict dict; + struct ip_addr ip; + char *key_prefix; + unsigned int port; + unsigned int timeout_msecs; + + struct ioloop *ioloop; + struct memcached_connection conn; + + bool connected; +}; + +static struct connection_list *memcached_connections; + +static void memcached_conn_destroy(struct connection *_conn) +{ + struct memcached_connection *conn = (struct memcached_connection *)_conn; + + conn->dict->connected = FALSE; + connection_disconnect(_conn); + + if (conn->dict->ioloop != NULL) + io_loop_stop(conn->dict->ioloop); +} + +static int memcached_input_get(struct memcached_connection *conn) +{ + const unsigned char *data; + size_t size; + uint32_t body_len, value_pos; + uint16_t key_len, key_pos, status; + uint8_t extras_len, data_type; + + data = i_stream_get_data(conn->conn.input, &size); + if (size < MEMCACHED_REPLY_HDR_LENGTH) + return 0; + + if (data[0] != MEMCACHED_REPLY_HDR_MAGIC) { + i_error("memcached: Invalid reply magic: %u != %u", + data[0], MEMCACHED_REPLY_HDR_MAGIC); + return -1; + } + memcpy(&body_len, data+8, 4); body_len = ntohl(body_len); + body_len += MEMCACHED_REPLY_HDR_LENGTH; + if (size < body_len) { + /* we haven't read the whole response yet */ + return 0; + } + + memcpy(&key_len, data+2, 2); key_len = ntohs(key_len); + extras_len = data[4]; + data_type = data[5]; + memcpy(&status, data+6, 2); status = ntohs(status); + if (data_type != MEMCACHED_DATA_TYPE_RAW) { + i_error("memcached: Unsupported data type: %u != %u", + data[0], MEMCACHED_DATA_TYPE_RAW); + return -1; + } + + key_pos = MEMCACHED_REPLY_HDR_LENGTH + extras_len; + value_pos = key_pos + key_len; + if (value_pos > body_len) { + i_error("memcached: Invalid key/extras lengths"); + return -1; + } + conn->reply.value = data + value_pos; + conn->reply.value_len = body_len - value_pos; + conn->reply.status = status; + + i_stream_skip(conn->conn.input, body_len); + conn->reply.reply_received = TRUE; + + if (conn->dict->ioloop != NULL) + io_loop_stop(conn->dict->ioloop); + return 1; +} + +static void memcached_conn_input(struct connection *_conn) +{ + struct memcached_connection *conn = (struct memcached_connection *)_conn; + + switch (i_stream_read(_conn->input)) { + case 0: + return; + case -1: + memcached_conn_destroy(_conn); + return; + default: + break; + } + + if (memcached_input_get(conn) < 0) + memcached_conn_destroy(_conn); +} + +static void memcached_conn_connected(struct connection *_conn) +{ + struct memcached_connection *conn = (struct memcached_connection *)_conn; + + if ((errno = net_geterror(_conn->fd_in)) != 0) { + i_error("memcached: connect(%s, %u) failed: %m", + net_ip2addr(&conn->dict->ip), conn->dict->port); + } else { + conn->dict->connected = TRUE; + } + if (conn->dict->ioloop != NULL) + io_loop_stop(conn->dict->ioloop); +} + +static const struct connection_settings memcached_conn_set = { + .input_max_size = (size_t)-1, + .output_max_size = (size_t)-1, + .client = TRUE +}; + +static const struct connection_vfuncs memcached_conn_vfuncs = { + .destroy = memcached_conn_destroy, + .input = memcached_conn_input, + .connected = memcached_conn_connected +}; + +static struct dict * +memcached_dict_init(struct dict *driver, const char *uri, + enum dict_data_type value_type ATTR_UNUSED, + const char *username ATTR_UNUSED, + const char *base_dir ATTR_UNUSED) +{ + struct memcached_dict *dict; + const char *const *args; + + if (memcached_connections == NULL) { + memcached_connections = + connection_list_init(&memcached_conn_set, + &memcached_conn_vfuncs); + } + + dict = i_new(struct memcached_dict, 1); + if (net_addr2ip("127.0.0.1", &dict->ip) < 0) + i_unreached(); + dict->port = MEMCACHED_DEFAULT_PORT; + dict->timeout_msecs = MEMCACHED_DEFAULT_LOOKUP_TIMEOUT_MSECS; + dict->key_prefix = i_strdup(""); + + args = t_strsplit(uri, ":"); + for (; *args != NULL; args++) { + if (strncmp(*args, "host=", 5) == 0) { + if (net_addr2ip(*args+5, &dict->ip) < 0) + i_error("Invalid IP: %s", *args+5); + } else if (strncmp(*args, "port=", 5) == 0) { + if (str_to_uint(*args+5, &dict->port) < 0) + i_error("Invalid port: %s", *args+5); + } else if (strncmp(*args, "prefix=", 7) == 0) { + i_free(dict->key_prefix); + dict->key_prefix = i_strdup(*args + 7); + } else if (strncmp(*args, "timeout_msecs=", 14) == 0) { + if (str_to_uint(*args+14, &dict->timeout_msecs) < 0) + i_error("Invalid timeout_msecs: %s", *args+14); + } else { + i_error("Unknown parameter: %s", *args); + } + } + connection_init_client_ip(memcached_connections, &dict->conn.conn, + &dict->ip, dict->port); + + dict->dict = *driver; + dict->conn.cmd = buffer_create_dynamic(default_pool, 256); + dict->conn.dict = dict; + return &dict->dict; +} + +static void memcached_dict_deinit(struct dict *_dict) +{ + struct memcached_dict *dict = (struct memcached_dict *)_dict; + + connection_deinit(&dict->conn.conn); + buffer_free(&dict->conn.cmd); + i_free(dict->key_prefix); + i_free(dict); + + if (memcached_connections->connections == NULL) + connection_list_deinit(&memcached_connections); +} + +static void memcached_dict_lookup_timeout(struct memcached_dict *dict) +{ + i_error("memcached: Lookup timed out in %u.%03u secs", + dict->timeout_msecs/1000, dict->timeout_msecs%1000); + io_loop_stop(dict->ioloop); +} + +static void memcached_add_header(buffer_t *buf, unsigned int key_len) +{ + uint32_t body_len = htonl(key_len); + + i_assert(key_len <= 0xffff); + + buffer_append_c(buf, MEMCACHED_REQUEST_HDR_MAGIC); + buffer_append_c(buf, MEMCACHED_CMD_GET); + buffer_append_c(buf, (key_len >> 8) & 0xff); + buffer_append_c(buf, key_len & 0xff); + buffer_append_c(buf, 0); /* extras length */ + buffer_append_c(buf, MEMCACHED_DATA_TYPE_RAW); + buffer_append_zero(buf, 2); /* vbucket id - we probably don't care? */ + buffer_append(buf, &body_len, sizeof(body_len)); + buffer_append_zero(buf, 4+8); /* opaque + cas */ + i_assert(buf->used == MEMCACHED_REQUEST_HDR_LENGTH); +} + +static int +memcached_dict_lookup_real(struct memcached_dict *dict, pool_t pool, + const char *key, const char **value_r) +{ + struct ioloop *prev_ioloop = current_ioloop; + struct timeout *to; + unsigned int key_len; + + if (strncmp(key, DICT_PATH_SHARED, strlen(DICT_PATH_SHARED)) == 0) + key += strlen(DICT_PATH_SHARED); + else { + i_error("memcached: Only shared keys supported currently"); + return -1; + } + if (*dict->key_prefix != '\0') + key = t_strconcat(dict->key_prefix, key, NULL); + key_len = strlen(key); + if (key_len > 0xffff) { + i_error("memcached: Key is too long (%u bytes): %s", + key_len, key); + return -1; + } + + i_assert(dict->ioloop == NULL); + + dict->ioloop = io_loop_create(); + connection_switch_ioloop(&dict->conn.conn); + + if (dict->conn.conn.fd_in == -1 && + connection_client_connect(&dict->conn.conn) < 0) { + i_error("memcached: Couldn't connect to %s:%u", + net_ip2addr(&dict->ip), dict->port); + } else { + to = timeout_add(dict->timeout_msecs, + memcached_dict_lookup_timeout, dict); + if (!dict->connected) { + /* wait for connection */ + io_loop_run(dict->ioloop); + } + + if (dict->connected) { + buffer_set_used_size(dict->conn.cmd, 0); + memcached_add_header(dict->conn.cmd, key_len); + buffer_append(dict->conn.cmd, key, key_len); + + o_stream_nsend(dict->conn.conn.output, + dict->conn.cmd->data, + dict->conn.cmd->used); + + memset(&dict->conn.reply, 0, sizeof(dict->conn.reply)); + io_loop_run(dict->ioloop); + } + timeout_remove(&to); + } + + current_ioloop = prev_ioloop; + connection_switch_ioloop(&dict->conn.conn); + current_ioloop = dict->ioloop; + io_loop_destroy(&dict->ioloop); + + if (!dict->conn.reply.reply_received) { + /* we failed in some way. make sure we disconnect since the + connection state isn't known anymore */ + memcached_conn_destroy(&dict->conn.conn); + return -1; + } + switch (dict->conn.reply.status) { + case MEMCACHED_RESPONSE_OK: + *value_r = p_strndup(pool, dict->conn.reply.value, + dict->conn.reply.value_len); + return 1; + case MEMCACHED_RESPONSE_NOTFOUND: + return 0; + case MEMCACHED_RESPONSE_INTERNALERROR: + i_error("memcached: Lookup(%s) failed: Internal error", key); + return -1; + case MEMCACHED_RESPONSE_BUSY: + i_error("memcached: Lookup(%s) failed: Busy", key); + return -1; + case MEMCACHED_RESPONSE_TEMPFAILURE: + i_error("memcached: Lookup(%s) failed: Temporary failure", key); + return -1; + } + + i_error("memcached: Lookup(%s) failed: Error code=%u", + key, dict->conn.reply.status); + return -1; +} + +static int memcached_dict_lookup(struct dict *_dict, pool_t pool, + const char *key, const char **value_r) +{ + struct memcached_dict *dict = (struct memcached_dict *)_dict; + int ret; + + if (pool->datastack_pool) + ret = memcached_dict_lookup_real(dict, pool, key, value_r); + else T_BEGIN { + ret = memcached_dict_lookup_real(dict, pool, key, value_r); + } T_END; + return ret; +} + +struct dict dict_driver_memcached = { + .name = "memcached", + { + memcached_dict_init, + memcached_dict_deinit, + NULL, + memcached_dict_lookup, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL + } +};
--- a/src/lib-dict/dict-private.h Fri Aug 10 04:56:56 2012 +0300 +++ b/src/lib-dict/dict-private.h Fri Aug 10 05:24:07 2012 +0300 @@ -51,7 +51,9 @@ unsigned int changed:1; }; +extern struct dict dict_driver_client; extern struct dict dict_driver_file; -extern struct dict dict_driver_client; +extern struct dict dict_driver_memcached; +extern struct dict dict_driver_redis; #endif
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/lib-dict/dict-redis.c Fri Aug 10 05:24:07 2012 +0300 @@ -0,0 +1,666 @@ +/* Copyright (c) 2008-2012 Dovecot authors, see the included COPYING redis */ + +#include "lib.h" +#include "array.h" +#include "str.h" +#include "istream.h" +#include "ostream.h" +#include "connection.h" +#include "dict-private.h" + +#define REDIS_DEFAULT_PORT 6379 +#define REDIS_DEFAULT_LOOKUP_TIMEOUT_MSECS (1000*30) +#define DICT_USERNAME_SEPARATOR '/' + +enum redis_input_state { + /* expecting $-1 / $<size> followed by GET reply */ + REDIS_INPUT_STATE_GET, + /* expecting +QUEUED */ + REDIS_INPUT_STATE_MULTI, + /* expecting +OK reply for DISCARD */ + REDIS_INPUT_STATE_DISCARD, + /* expecting *<nreplies> */ + REDIS_INPUT_STATE_EXEC, + /* expecting EXEC reply */ + REDIS_INPUT_STATE_EXEC_REPLY +}; + +struct redis_connection { + struct connection conn; + struct redis_dict *dict; + + string_t *last_reply; + unsigned int bytes_left; + bool value_not_found; + bool value_received; +}; + +struct redis_dict_reply { + unsigned int reply_count; + dict_transaction_commit_callback_t *callback; + void *context; +}; + +struct redis_dict { + struct dict dict; + struct ip_addr ip; + char *username, *key_prefix; + unsigned int port; + unsigned int timeout_msecs; + + struct ioloop *ioloop; + struct redis_connection conn; + + ARRAY_DEFINE(input_states, enum redis_input_state); + ARRAY_DEFINE(replies, struct redis_dict_reply); + + bool connected; + bool transaction_open; +}; + +struct redis_dict_transaction_context { + struct dict_transaction_context ctx; + unsigned int cmd_count; + bool failed; +}; + +static struct connection_list *redis_connections; + +static void +redis_input_state_add(struct redis_dict *dict, enum redis_input_state state) +{ + array_append(&dict->input_states, &state, 1); +} + +static void redis_input_state_remove(struct redis_dict *dict) +{ + array_delete(&dict->input_states, 0, 1); +} + +static void redis_conn_destroy(struct connection *_conn) +{ + struct redis_connection *conn = (struct redis_connection *)_conn; + const struct redis_dict_reply *reply; + + conn->dict->connected = FALSE; + connection_disconnect(_conn); + + array_foreach(&conn->dict->replies, reply) { + if (reply->callback != NULL) + reply->callback(-1, reply->context); + } + array_clear(&conn->dict->replies); + array_clear(&conn->dict->input_states); + + if (conn->dict->ioloop != NULL) + io_loop_stop(conn->dict->ioloop); +} + +static void redis_wait(struct redis_dict *dict) +{ + struct ioloop *prev_ioloop = current_ioloop; + + i_assert(dict->ioloop == NULL); + + dict->ioloop = io_loop_create(); + connection_switch_ioloop(&dict->conn.conn); + + do { + io_loop_run(dict->ioloop); + } while (array_count(&dict->input_states) > 0); + + current_ioloop = prev_ioloop; + connection_switch_ioloop(&dict->conn.conn); + current_ioloop = dict->ioloop; + io_loop_destroy(&dict->ioloop); +} + +static void redis_input_get(struct redis_connection *conn) +{ + const unsigned char *data; + size_t size; + const char *line; + + if (conn->bytes_left == 0) { + /* read the size first */ + line = i_stream_next_line(conn->conn.input); + if (line == NULL) + return; + if (strcmp(line, "$-1") == 0) { + conn->value_received = TRUE; + conn->value_not_found = TRUE; + if (conn->dict->ioloop != NULL) + io_loop_stop(conn->dict->ioloop); + redis_input_state_remove(conn->dict); + return; + } + if (line[0] != '$' || str_to_uint(line+1, &conn->bytes_left) < 0) { + i_error("redis: Unexpected input (wanted $size): %s", + line); + redis_conn_destroy(&conn->conn); + return; + } + conn->bytes_left += 2; /* include trailing CRLF */ + } + + data = i_stream_get_data(conn->conn.input, &size); + if (size > conn->bytes_left) + size = conn->bytes_left; + str_append_n(conn->last_reply, data, size); + + conn->bytes_left -= size; + i_stream_skip(conn->conn.input, size); + + if (conn->bytes_left == 0) { + /* reply fully read - drop trailing CRLF */ + conn->value_received = TRUE; + str_truncate(conn->last_reply, str_len(conn->last_reply)-2); + + if (conn->dict->ioloop != NULL) + io_loop_stop(conn->dict->ioloop); + redis_input_state_remove(conn->dict); + } +} + +static int redis_conn_input_more(struct redis_connection *conn) +{ + struct redis_dict *dict = conn->dict; + struct redis_dict_reply *reply; + const enum redis_input_state *states; + enum redis_input_state state; + unsigned int count, num_replies; + const char *line; + + states = array_get(&dict->input_states, &count); + if (count == 0) { + line = i_stream_next_line(conn->conn.input); + if (line == NULL) line = ""; + i_error("redis: Unexpected input (expected nothing): %s", + line); + return -1; + } + state = states[0]; + if (state == REDIS_INPUT_STATE_GET) { + redis_input_get(conn); + return 1; + } + + line = i_stream_next_line(conn->conn.input); + if (line == NULL) + return 0; + + redis_input_state_remove(dict); + switch (state) { + case REDIS_INPUT_STATE_GET: + i_unreached(); + case REDIS_INPUT_STATE_MULTI: + case REDIS_INPUT_STATE_DISCARD: + if (line[0] != '+') + break; + return 1; + case REDIS_INPUT_STATE_EXEC: + if (line[0] != '*' || str_to_uint(line+1, &num_replies) < 0) + break; + + reply = array_idx_modifiable(&dict->replies, 0); + i_assert(reply->reply_count > 0); + if (reply->reply_count != num_replies) { + i_error("redis: EXEC expected %u replies, not %u", + reply->reply_count, num_replies); + return -1; + } + return 1; + case REDIS_INPUT_STATE_EXEC_REPLY: + if (*line != '+' && *line != ':') + break; + /* success, just ignore the actual reply */ + reply = array_idx_modifiable(&dict->replies, 0); + i_assert(reply->reply_count > 0); + if (--reply->reply_count == 0) { + if (reply->callback != NULL) + reply->callback(1, reply->context); + array_delete(&dict->replies, 0, 1); + /* if we're running in a dict-ioloop, we're handling a + synchronous commit and need to stop now */ + if (array_count(&dict->replies) == 0 && + conn->dict->ioloop != NULL) + io_loop_stop(conn->dict->ioloop); + } + return 1; + } + i_error("redis: Unexpected input (state=%d): %s", state, line); + return -1; +} + +static void redis_conn_input(struct connection *_conn) +{ + struct redis_connection *conn = (struct redis_connection *)_conn; + int ret; + + switch (i_stream_read(_conn->input)) { + case 0: + return; + case -1: + redis_conn_destroy(_conn); + return; + default: + break; + } + + while ((ret = redis_conn_input_more(conn)) > 0) { + if (i_stream_get_data_size(_conn->input) == 0) + break; + } + if (ret < 0) + redis_conn_destroy(_conn); +} + +static void redis_conn_connected(struct connection *_conn) +{ + struct redis_connection *conn = (struct redis_connection *)_conn; + + if ((errno = net_geterror(_conn->fd_in)) != 0) { + i_error("redis: connect(%s, %u) failed: %m", + net_ip2addr(&conn->dict->ip), conn->dict->port); + } else { + conn->dict->connected = TRUE; + } + if (conn->dict->ioloop != NULL) + io_loop_stop(conn->dict->ioloop); +} + +static const struct connection_settings redis_conn_set = { + .input_max_size = (size_t)-1, + .output_max_size = (size_t)-1, + .client = TRUE +}; + +static const struct connection_vfuncs redis_conn_vfuncs = { + .destroy = redis_conn_destroy, + .input = redis_conn_input, + .connected = redis_conn_connected +}; + +static const char *redis_escape_username(const char *username) +{ + const char *p; + string_t *str = t_str_new(64); + + for (p = username; *p != '\0'; p++) { + switch (*p) { + case DICT_USERNAME_SEPARATOR: + str_append(str, "\\-"); + break; + case '\\': + str_append(str, "\\\\"); + break; + default: + str_append_c(str, *p); + } + } + return str_c(str); +} + +static struct dict * +redis_dict_init(struct dict *driver, const char *uri, + enum dict_data_type value_type ATTR_UNUSED, + const char *username, + const char *base_dir ATTR_UNUSED) +{ + struct redis_dict *dict; + const char *const *args; + + if (redis_connections == NULL) { + redis_connections = + connection_list_init(&redis_conn_set, + &redis_conn_vfuncs); + } + + dict = i_new(struct redis_dict, 1); + if (net_addr2ip("127.0.0.1", &dict->ip) < 0) + i_unreached(); + dict->port = REDIS_DEFAULT_PORT; + dict->timeout_msecs = REDIS_DEFAULT_LOOKUP_TIMEOUT_MSECS; + if (strchr(username, DICT_USERNAME_SEPARATOR) == NULL) + dict->username = i_strdup(username); + else { + /* escape the username */ + dict->username = i_strdup(redis_escape_username(username)); + } + dict->key_prefix = i_strdup(""); + i_array_init(&dict->input_states, 4); + i_array_init(&dict->replies, 4); + + args = t_strsplit(uri, ":"); + for (; *args != NULL; args++) { + if (strncmp(*args, "host=", 5) == 0) { + if (net_addr2ip(*args+5, &dict->ip) < 0) + i_error("Invalid IP: %s", *args+5); + } else if (strncmp(*args, "port=", 5) == 0) { + if (str_to_uint(*args+5, &dict->port) < 0) + i_error("Invalid port: %s", *args+5); + } else if (strncmp(*args, "prefix=", 7) == 0) { + i_free(dict->key_prefix); + dict->key_prefix = i_strdup(*args + 7); + } else if (strncmp(*args, "timeout_msecs=", 14) == 0) { + if (str_to_uint(*args+14, &dict->timeout_msecs) < 0) + i_error("Invalid timeout_msecs: %s", *args+14); + } else { + i_error("Unknown parameter: %s", *args); + } + } + connection_init_client_ip(redis_connections, &dict->conn.conn, + &dict->ip, dict->port); + + dict->dict = *driver; + dict->conn.last_reply = str_new(default_pool, 256); + dict->conn.dict = dict; + return &dict->dict; +} + +static void redis_dict_deinit(struct dict *_dict) +{ + struct redis_dict *dict = (struct redis_dict *)_dict; + + if (array_count(&dict->input_states) > 0) { + i_assert(dict->connected); + redis_wait(dict); + } + connection_deinit(&dict->conn.conn); + str_free(&dict->conn.last_reply); + array_free(&dict->replies); + array_free(&dict->input_states); + i_free(dict->key_prefix); + i_free(dict->username); + i_free(dict); + + if (redis_connections->connections == NULL) + connection_list_deinit(&redis_connections); +} + +static void redis_dict_lookup_timeout(struct redis_dict *dict) +{ + i_error("redis: Lookup timed out in %u.%03u secs", + dict->timeout_msecs/1000, dict->timeout_msecs%1000); + io_loop_stop(dict->ioloop); +} + +static const char * +redis_dict_get_full_key(struct redis_dict *dict, const char *key) +{ + if (strncmp(key, DICT_PATH_SHARED, strlen(DICT_PATH_SHARED)) == 0) + key += strlen(DICT_PATH_SHARED); + else if (strncmp(key, DICT_PATH_PRIVATE, strlen(DICT_PATH_PRIVATE)) == 0) { + key = t_strdup_printf("%s%c%s", dict->username, + DICT_USERNAME_SEPARATOR, + key + strlen(DICT_PATH_PRIVATE)); + } else { + i_unreached(); + } + if (*dict->key_prefix != '\0') + key = t_strconcat(dict->key_prefix, key, NULL); + return key; +} + +static int +redis_dict_lookup_real(struct redis_dict *dict, pool_t pool, + const char *key, const char **value_r) +{ + struct timeout *to; + const char *cmd; + struct ioloop *prev_ioloop = current_ioloop; + + key = redis_dict_get_full_key(dict, key); + + dict->conn.value_received = FALSE; + dict->conn.value_not_found = FALSE; + + i_assert(dict->ioloop == NULL); + + dict->ioloop = io_loop_create(); + connection_switch_ioloop(&dict->conn.conn); + + if (dict->conn.conn.fd_in == -1 && + connection_client_connect(&dict->conn.conn) < 0) { + i_error("redis: Couldn't connect to %s:%u", + net_ip2addr(&dict->ip), dict->port); + } else { + to = timeout_add(dict->timeout_msecs, + redis_dict_lookup_timeout, dict); + if (!dict->connected) { + /* wait for connection */ + io_loop_run(dict->ioloop); + } + + if (dict->connected) { + cmd = t_strdup_printf("*2\r\n$3\r\nGET\r\n$%d\r\n%s\r\n", + (int)strlen(key), key); + o_stream_nsend_str(dict->conn.conn.output, cmd); + + str_truncate(dict->conn.last_reply, 0); + redis_input_state_add(dict, REDIS_INPUT_STATE_GET); + io_loop_run(dict->ioloop); + } + timeout_remove(&to); + } + + current_ioloop = prev_ioloop; + connection_switch_ioloop(&dict->conn.conn); + current_ioloop = dict->ioloop; + io_loop_destroy(&dict->ioloop); + + if (!dict->conn.value_received) { + /* we failed in some way. make sure we disconnect since the + connection state isn't known anymore */ + redis_conn_destroy(&dict->conn.conn); + return -1; + } + if (dict->conn.value_not_found) + return 0; + + *value_r = p_strdup(pool, str_c(dict->conn.last_reply)); + return 1; +} + +static int redis_dict_lookup(struct dict *_dict, pool_t pool, + const char *key, const char **value_r) +{ + struct redis_dict *dict = (struct redis_dict *)_dict; + int ret; + + i_assert(!dict->transaction_open); + + if (pool->datastack_pool) + ret = redis_dict_lookup_real(dict, pool, key, value_r); + else T_BEGIN { + ret = redis_dict_lookup_real(dict, pool, key, value_r); + } T_END; + return ret; +} + +static struct dict_transaction_context * +redis_transaction_init(struct dict *_dict) +{ + struct redis_dict *dict = (struct redis_dict *)_dict; + struct redis_dict_transaction_context *ctx; + + i_assert(!dict->transaction_open); + dict->transaction_open = TRUE; + + ctx = i_new(struct redis_dict_transaction_context, 1); + ctx->ctx.dict = _dict; + + if (dict->conn.conn.fd_in == -1 && + connection_client_connect(&dict->conn.conn) < 0) { + i_error("redis: Couldn't connect to %s:%u", + net_ip2addr(&dict->ip), dict->port); + } else if (!dict->connected) { + /* wait for connection */ + redis_wait(dict); + } + return &ctx->ctx; +} + +static int +redis_transaction_commit(struct dict_transaction_context *_ctx, bool async, + dict_transaction_commit_callback_t *callback, + void *context) +{ + struct redis_dict_transaction_context *ctx = + (struct redis_dict_transaction_context *)_ctx; + struct redis_dict *dict = (struct redis_dict *)_ctx->dict; + struct redis_dict_reply *reply; + unsigned int i; + int ret = 1; + + i_assert(dict->transaction_open); + dict->transaction_open = FALSE; + + if (ctx->failed) { + /* make sure we're disconnected */ + redis_conn_destroy(&dict->conn.conn); + ret = -1; + } else if (_ctx->changed) { + i_assert(ctx->cmd_count > 0); + + o_stream_nsend_str(dict->conn.conn.output, + "*1\r\n$4\r\nEXEC\r\n"); + reply = array_append_space(&dict->replies); + reply->callback = callback; + reply->context = context; + reply->reply_count = ctx->cmd_count; + redis_input_state_add(dict, REDIS_INPUT_STATE_EXEC); + for (i = 0; i < ctx->cmd_count; i++) + redis_input_state_add(dict, REDIS_INPUT_STATE_EXEC_REPLY); + if (async) + return 1; + redis_wait(dict); + } + if (callback != NULL) + callback(ret, context); + i_free(ctx); + return ret; +} + +static void redis_transaction_rollback(struct dict_transaction_context *_ctx) +{ + struct redis_dict_transaction_context *ctx = + (struct redis_dict_transaction_context *)_ctx; + struct redis_dict *dict = (struct redis_dict *)_ctx->dict; + struct redis_dict_reply *reply; + + i_assert(dict->transaction_open); + dict->transaction_open = FALSE; + + if (ctx->failed) { + /* make sure we're disconnected */ + redis_conn_destroy(&dict->conn.conn); + } else if (_ctx->changed) { + o_stream_nsend_str(dict->conn.conn.output, + "*1\r\n$7\r\nDISCARD\r\n"); + reply = array_append_space(&dict->replies); + reply->reply_count = 1; + redis_input_state_add(dict, REDIS_INPUT_STATE_DISCARD); + } + i_free(ctx); +} + +static int redis_check_transaction(struct redis_dict_transaction_context *ctx) +{ + struct redis_dict *dict = (struct redis_dict *)ctx->ctx.dict; + + if (ctx->failed) + return -1; + if (ctx->ctx.changed) + return 0; + + redis_input_state_add(dict, REDIS_INPUT_STATE_MULTI); + if (o_stream_send_str(dict->conn.conn.output, + "*1\r\n$5\r\nMULTI\r\n") < 0) { + ctx->failed = TRUE; + return -1; + } + return 0; +} + +static void redis_set(struct dict_transaction_context *_ctx, + const char *key, const char *value) +{ + struct redis_dict_transaction_context *ctx = + (struct redis_dict_transaction_context *)_ctx; + struct redis_dict *dict = (struct redis_dict *)_ctx->dict; + const char *cmd; + + if (redis_check_transaction(ctx) < 0) + return; + + key = redis_dict_get_full_key(dict, key); + cmd = t_strdup_printf("*3\r\n$3\r\nSET\r\n$%u\r\n%s\r\n$%u\r\n%s\r\n", + (unsigned int)strlen(key), key, + (unsigned int)strlen(value), value); + if (o_stream_send_str(dict->conn.conn.output, cmd) < 0) + ctx->failed = TRUE; + redis_input_state_add(dict, REDIS_INPUT_STATE_MULTI); + ctx->cmd_count++; +} + +static void redis_unset(struct dict_transaction_context *_ctx, + const char *key) +{ + struct redis_dict_transaction_context *ctx = + (struct redis_dict_transaction_context *)_ctx; + struct redis_dict *dict = (struct redis_dict *)_ctx->dict; + const char *cmd; + + if (redis_check_transaction(ctx) < 0) + return; + + key = redis_dict_get_full_key(dict, key); + cmd = t_strdup_printf("*2\r\n$3\r\nDEL\r\n$%u\r\n%s\r\n", + (unsigned int)strlen(key), key); + if (o_stream_send_str(dict->conn.conn.output, cmd) < 0) + ctx->failed = TRUE; + redis_input_state_add(dict, REDIS_INPUT_STATE_MULTI); + ctx->cmd_count++; +} + +static void redis_atomic_inc(struct dict_transaction_context *_ctx, + const char *key, long long diff) +{ + struct redis_dict_transaction_context *ctx = + (struct redis_dict_transaction_context *)_ctx; + struct redis_dict *dict = (struct redis_dict *)_ctx->dict; + const char *cmd, *diffstr; + + if (redis_check_transaction(ctx) < 0) + return; + + key = redis_dict_get_full_key(dict, key); + diffstr = t_strdup_printf("%lld", diff); + cmd = t_strdup_printf("*3\r\n$6\r\nINCRBY\r\n$%u\r\n%s\r\n$%u\r\n%s\r\n", + (unsigned int)strlen(key), key, + (unsigned int)strlen(diffstr), diffstr); + if (o_stream_send_str(dict->conn.conn.output, cmd) < 0) + ctx->failed = TRUE; + redis_input_state_add(dict, REDIS_INPUT_STATE_MULTI); + ctx->cmd_count++; +} + +struct dict dict_driver_redis = { + .name = "redis", + { + redis_dict_init, + redis_dict_deinit, + NULL, + redis_dict_lookup, + NULL, + NULL, + NULL, + redis_transaction_init, + redis_transaction_commit, + redis_transaction_rollback, + redis_set, + redis_unset, + redis_atomic_inc + } +};
--- a/src/lib-dict/dict.c Fri Aug 10 04:56:56 2012 +0300 +++ b/src/lib-dict/dict.c Fri Aug 10 05:24:07 2012 +0300 @@ -7,6 +7,7 @@ #include "dict-private.h" static ARRAY_DEFINE(dict_drivers, struct dict *); +static struct dict_iterate_context dict_iter_unsupported; static struct dict *dict_driver_lookup(const char *name) { @@ -55,12 +56,16 @@ { dict_driver_register(&dict_driver_client); dict_driver_register(&dict_driver_file); + dict_driver_register(&dict_driver_memcached); + dict_driver_register(&dict_driver_redis); } void dict_drivers_unregister_builtin(void) { dict_driver_unregister(&dict_driver_client); dict_driver_unregister(&dict_driver_file); + dict_driver_unregister(&dict_driver_memcached); + dict_driver_unregister(&dict_driver_redis); } struct dict *dict_init(const char *uri, enum dict_data_type value_type, @@ -134,13 +139,19 @@ i_assert(paths[0] != NULL); for (i = 0; paths[i] != NULL; i++) i_assert(dict_key_prefix_is_valid(paths[i])); + if (dict->v.iterate_init == NULL) { + /* not supported by backend */ + i_error("%s: dict iteration not supported", dict->name); + return &dict_iter_unsupported; + } return dict->v.iterate_init(dict, paths, flags); } bool dict_iterate(struct dict_iterate_context *ctx, const char **key_r, const char **value_r) { - return ctx->dict->v.iterate(ctx, key_r, value_r); + return ctx == &dict_iter_unsupported ? FALSE : + ctx->dict->v.iterate(ctx, key_r, value_r); } int dict_iterate_deinit(struct dict_iterate_context **_ctx) @@ -148,7 +159,8 @@ struct dict_iterate_context *ctx = *_ctx; *_ctx = NULL; - return ctx->dict->v.iterate_deinit(ctx); + return ctx == &dict_iter_unsupported ? -1 : + ctx->dict->v.iterate_deinit(ctx); } struct dict_transaction_context *dict_transaction_begin(struct dict *dict)
--- a/src/lib-dict/test-dict.c Fri Aug 10 04:56:56 2012 +0300 +++ b/src/lib-dict/test-dict.c Fri Aug 10 05:24:07 2012 +0300 @@ -6,6 +6,8 @@ struct dict dict_driver_client; struct dict dict_driver_file; +struct dict dict_driver_memcached; +struct dict dict_driver_redis; static void test_dict_escape(void) {
--- a/src/lib-imap-client/imapc-client.c Fri Aug 10 04:56:56 2012 +0300 +++ b/src/lib-imap-client/imapc-client.c Fri Aug 10 05:24:07 2012 +0300 @@ -270,7 +270,7 @@ /* reopen the mailbox */ box->reopen_callback(box->reopen_context); } else { - imapc_connection_abort_commands(box->conn, TRUE, FALSE); + imapc_connection_abort_commands(box->conn, NULL, FALSE); } }
--- a/src/lib-imap-client/imapc-connection.c Fri Aug 10 04:56:56 2012 +0300 +++ b/src/lib-imap-client/imapc-connection.c Fri Aug 10 05:24:07 2012 +0300 @@ -213,6 +213,7 @@ static void imapc_connection_abort_commands_array(ARRAY_TYPE(imapc_command) *cmd_array, ARRAY_TYPE(imapc_command) *dest_array, + struct imapc_client_mailbox *only_box, bool keep_retriable) { struct imapc_command *const *cmdp, *cmd; @@ -222,8 +223,10 @@ cmdp = array_idx(cmd_array, i); cmd = *cmdp; - if (keep_retriable && - (cmd->flags & IMAPC_COMMAND_FLAG_RETRIABLE) != 0) { + if (cmd->box != only_box && only_box != NULL) + i++; + else if (keep_retriable && + (cmd->flags & IMAPC_COMMAND_FLAG_RETRIABLE) != 0) { cmd->send_pos = 0; cmd->wait_for_literal = 0; i++; @@ -235,22 +238,20 @@ } void imapc_connection_abort_commands(struct imapc_connection *conn, - bool disconnected, bool keep_retriable) + struct imapc_client_mailbox *only_box, + bool keep_retriable) { struct imapc_command *const *cmdp, *cmd; ARRAY_TYPE(imapc_command) tmp_array; struct imapc_command_reply reply; t_array_init(&tmp_array, 8); - if (disconnected) { - imapc_connection_abort_commands_array(&conn->cmd_wait_list, - &tmp_array, - keep_retriable); - } - imapc_connection_abort_commands_array(&conn->cmd_send_queue, - &tmp_array, keep_retriable); + imapc_connection_abort_commands_array(&conn->cmd_wait_list, &tmp_array, + only_box, keep_retriable); + imapc_connection_abort_commands_array(&conn->cmd_send_queue, &tmp_array, + only_box, keep_retriable); - if (array_count(&conn->cmd_wait_list) > 0 && disconnected) { + if (array_count(&conn->cmd_wait_list) > 0 && only_box == NULL) { /* need to move all the waiting commands to send queue */ array_append_array(&conn->cmd_wait_list, &conn->cmd_send_queue); @@ -371,13 +372,13 @@ conn->fd = -1; imapc_connection_set_state(conn, IMAPC_CONNECTION_STATE_DISCONNECTED); - imapc_connection_abort_commands(conn, TRUE, reconnecting); + imapc_connection_abort_commands(conn, NULL, reconnecting); } static void imapc_connection_set_disconnected(struct imapc_connection *conn) { imapc_connection_set_state(conn, IMAPC_CONNECTION_STATE_DISCONNECTED); - imapc_connection_abort_commands(conn, TRUE, FALSE); + imapc_connection_abort_commands(conn, NULL, FALSE); } static void imapc_connection_reconnect(struct imapc_connection *conn) @@ -1857,7 +1858,7 @@ conn->selecting_box = NULL; } imapc_connection_send_idle_done(conn); - imapc_connection_abort_commands(conn, FALSE, FALSE); + imapc_connection_abort_commands(conn, box, FALSE); } struct imapc_client_mailbox *
--- a/src/lib-imap-client/imapc-connection.h Fri Aug 10 04:56:56 2012 +0300 +++ b/src/lib-imap-client/imapc-connection.h Fri Aug 10 05:24:07 2012 +0300 @@ -26,7 +26,8 @@ void *login_context) ATTR_NULL(2, 3); void imapc_connection_disconnect(struct imapc_connection *conn); void imapc_connection_abort_commands(struct imapc_connection *conn, - bool disconnected, bool keep_retriable); + struct imapc_client_mailbox *only_box, + bool keep_retriable) ATTR_NULL(2); void imapc_connection_ioloop_changed(struct imapc_connection *conn); void imapc_connection_input_pending(struct imapc_connection *conn);
--- a/src/lib-mail/mail-user-hash.c Fri Aug 10 04:56:56 2012 +0300 +++ b/src/lib-mail/mail-user-hash.c Fri Aug 10 05:24:07 2012 +0300 @@ -21,6 +21,12 @@ if (strcmp(format, "%u") == 0) { /* fast path */ md5_get_digest(username, strlen(username), md5); + } else if (strcmp(format, "%Lu") == 0) { + /* almost as fast path */ + T_BEGIN { + md5_get_digest(t_str_lcase(username), + strlen(username), md5); + } T_END; } else T_BEGIN { string_t *str = t_str_new(128);
--- a/src/lib-master/master-service-settings-cache.c Fri Aug 10 04:56:56 2012 +0300 +++ b/src/lib-master/master-service-settings-cache.c Fri Aug 10 05:24:07 2012 +0300 @@ -145,6 +145,8 @@ } if (entry != NULL) { + DLLIST2_REMOVE(&cache->oldest, &cache->newest, entry); + DLLIST2_APPEND(&cache->oldest, &cache->newest, entry); *parser_r = entry->parser; return TRUE; } @@ -166,10 +168,11 @@ settings_parser_deinit(&entry->parser); } -static void cache_add(struct master_service_settings_cache *cache, - const struct master_service_settings_input *input, - const struct master_service_settings_output *output, - struct setting_parser_context *parser) +static struct setting_parser_context * +cache_add(struct master_service_settings_cache *cache, + const struct master_service_settings_input *input, + const struct master_service_settings_output *output, + struct setting_parser_context *parser) { struct settings_entry *entry; pool_t pool; @@ -185,41 +188,39 @@ } if (cache->service_uses_remote) { /* for now we don't try to handle caching remote IPs */ - return; + return parser; } if (input->local_name == NULL && input->local_ip.family == 0) - return; + return parser; if (!output->used_local) { /* use global settings, but add local_ip/host to hash tables so we'll find them */ pool = pool_alloconly_create("settings global entry", 256); - entry = p_new(pool, struct settings_entry, 1); } else if (cache->cache_malloc_size >= cache->max_cache_size) { /* free the oldest and reuse its pool */ - entry = cache->oldest; - pool = entry->pool; - setting_entry_detach(cache, entry); - p_clear(pool); + pool = cache->oldest->pool; + setting_entry_detach(cache, cache->oldest); + p_clear(pool); /* note: frees also entry */ } else { pool_size = cache->approx_entry_pool_size != 0 ? cache->approx_entry_pool_size : CACHE_INITIAL_ENTRY_POOL_SIZE; pool = pool_alloconly_create("settings entry", pool_size); - entry = p_new(pool, struct settings_entry, 1); } + entry = p_new(pool, struct settings_entry, 1); entry->pool = pool; entry_local_name = p_strdup(pool, input->local_name); entry->local_name = entry_local_name; entry->local_ip = input->local_ip; if (!output->used_local) { entry->parser = cache->global_parser; - DLLIST2_PREPEND(&cache->oldest_global, &cache->newest_global, - entry); + DLLIST2_APPEND(&cache->oldest_global, &cache->newest_global, + entry); } else { entry->parser = settings_parser_dup(parser, entry->pool); - DLLIST2_PREPEND(&cache->oldest, &cache->newest, entry); + DLLIST2_APPEND(&cache->oldest, &cache->newest, entry); pool_size = pool_alloconly_get_total_used_size(pool); if (pool_size > cache->approx_entry_pool_size) { @@ -249,6 +250,7 @@ hash_table_insert(cache->local_ip_hash, &entry->local_ip, entry); } + return entry->parser; } int master_service_settings_cache_read(struct master_service_settings_cache *cache, @@ -294,7 +296,7 @@ return -1; } - cache_add(cache, &new_input, &output, cache->service->set_parser); - *parser_r = cache->service->set_parser; + *parser_r = cache_add(cache, &new_input, &output, + cache->service->set_parser); return 0; }
--- a/src/lib-master/mountpoint-list.c Fri Aug 10 04:56:56 2012 +0300 +++ b/src/lib-master/mountpoint-list.c Fri Aug 10 05:24:07 2012 +0300 @@ -55,6 +55,8 @@ "/media", "/sys", "/proc", + "/var/run", + "/run", NULL };
--- a/src/lib-settings/settings-parser.c Fri Aug 10 04:56:56 2012 +0300 +++ b/src/lib-settings/settings-parser.c Fri Aug 10 05:24:07 2012 +0300 @@ -946,6 +946,8 @@ switch (ret) { case -1: + if (ctx->error != NULL) + break; if (input->stream_errno != 0) { ctx->error = p_strdup_printf(ctx->parser_pool, "read() failed: %m");
--- a/src/lib-sql/sql-db-cache.c Fri Aug 10 04:56:56 2012 +0300 +++ b/src/lib-sql/sql-db-cache.c Fri Aug 10 05:24:07 2012 +0300 @@ -75,6 +75,7 @@ db = cache->unused_tail; ctx = SQL_DB_CACHE_CONTEXT(db); sql_db_cache_unlink(ctx); + hash_table_remove(cache->dbs, ctx->key); i_free(ctx->key); ctx->orig_deinit(db);
--- a/src/lib-ssl-iostream/iostream-openssl.c Fri Aug 10 04:56:56 2012 +0300 +++ b/src/lib-ssl-iostream/iostream-openssl.c Fri Aug 10 05:24:07 2012 +0300 @@ -24,9 +24,9 @@ i_warning("%s: SSL failed: where=0x%x: %s", ssl_io->source, where, SSL_state_string_long(ssl)); } else { - i_warning("%s: SSL: where=0x%x, ret=%d: %s", - ssl_io->source, where, ret, - SSL_state_string_long(ssl)); + i_debug("%s: SSL: where=0x%x, ret=%d: %s", + ssl_io->source, where, ret, + SSL_state_string_long(ssl)); } }
--- a/src/lib-storage/index/imapc/imapc-save.c Fri Aug 10 04:56:56 2012 +0300 +++ b/src/lib-storage/index/imapc/imapc-save.c Fri Aug 10 05:24:07 2012 +0300 @@ -165,7 +165,8 @@ uint32_t uid = 0; if (reply->state == IMAPC_COMMAND_STATE_OK) { - if (strcasecmp(reply->resp_text_key, "APPENDUID") == 0) + if (reply->resp_text_key != NULL && + strcasecmp(reply->resp_text_key, "APPENDUID") == 0) imapc_save_appenduid(ctx->ctx, reply, &uid); imapc_save_add_to_index(ctx->ctx, uid); ctx->ret = 0; @@ -353,7 +354,8 @@ uint32_t uid = 0; if (reply->state == IMAPC_COMMAND_STATE_OK) { - if (strcasecmp(reply->resp_text_key, "COPYUID") == 0) + if (reply->resp_text_key != NULL && + strcasecmp(reply->resp_text_key, "COPYUID") == 0) imapc_save_copyuid(ctx->ctx, reply, &uid); imapc_save_add_to_index(ctx->ctx, uid); ctx->ret = 0;
--- a/src/lib-storage/index/index-mail.c Fri Aug 10 04:56:56 2012 +0300 +++ b/src/lib-storage/index/index-mail.c Fri Aug 10 05:24:07 2012 +0300 @@ -758,7 +758,8 @@ uint32_t t; dates[0] = mail->data.received_date; - dates[1] = mail->data.save_date; + dates[1] = mail->mail.mail.saving ? ioloop_time : + mail->data.save_date; for (i = 0; i < N_ELEMENTS(date_fields); i++) { if (dates[i] != (time_t)-1 &&
--- a/src/lib-storage/index/index-storage.c Fri Aug 10 04:56:56 2012 +0300 +++ b/src/lib-storage/index/index-storage.c Fri Aug 10 05:24:07 2012 +0300 @@ -629,6 +629,7 @@ struct mailbox_transaction_context *dest_trans = ctx->transaction; const struct mail_cache_field *dest_field; unsigned int src_field_idx, dest_field_idx; + uint32_t t; src_field_idx = mail_cache_register_lookup(src_mail->box->cache, name); i_assert(src_field_idx != -1U); @@ -647,8 +648,16 @@ } buffer_set_used_size(buf, 0); - if (mail_cache_lookup_field(src_mail->transaction->cache_view, buf, - src_mail->seq, src_field_idx) > 0) { + if (strcmp(name, "date.save") == 0) { + /* save date must update when mail is copied */ + t = ioloop_time; + buffer_append(buf, &t, sizeof(t)); + } else { + if (mail_cache_lookup_field(src_mail->transaction->cache_view, buf, + src_mail->seq, src_field_idx) <= 0) + buffer_set_used_size(buf, 0); + } + if (buf->used > 0) { mail_cache_add(dest_trans->cache_trans, dest_seq, dest_field_idx, buf->data, buf->used); }
--- a/src/lib-storage/index/pop3c/pop3c-client.c Fri Aug 10 04:56:56 2012 +0300 +++ b/src/lib-storage/index/pop3c/pop3c-client.c Fri Aug 10 05:24:07 2012 +0300 @@ -85,7 +85,7 @@ client->set.debug = set->debug; client->set.host = p_strdup(pool, set->host); client->set.port = set->port; - client->set.master_user = p_strdup(pool, set->master_user); + client->set.master_user = p_strdup_empty(pool, set->master_user); client->set.username = p_strdup(pool, set->username); client->set.password = p_strdup(pool, set->password); client->set.dns_client_socket_path = @@ -352,8 +352,13 @@ client->state = POP3C_CLIENT_STATE_CAPA; break; case POP3C_CLIENT_STATE_CAPA: - if (strncasecmp(line, "-ERR", 4) == 0 || - strcmp(line, ".") == 0) { + if (strncasecmp(line, "-ERR", 4) == 0) { + /* CAPA command not supported. some commands still + support UIDL though. */ + client->capabilities |= POP3C_CAPABILITY_UIDL; + pop3c_client_login_finished(client); + break; + } else if (strcmp(line, ".") == 0) { pop3c_client_login_finished(client); break; }
--- a/src/lib-storage/index/pop3c/pop3c-mail.c Fri Aug 10 04:56:56 2012 +0300 +++ b/src/lib-storage/index/pop3c/pop3c-mail.c Fri Aug 10 05:24:07 2012 +0300 @@ -110,10 +110,12 @@ if (mail->data.stream == NULL) { capa = pop3c_client_get_capabilities(mbox->client); - if (get_body || (capa & POP3C_CAPABILITY_TOP) == 0) + if (get_body || (capa & POP3C_CAPABILITY_TOP) == 0) { cmd = t_strdup_printf("RETR %u\r\n", _mail->seq); - else + get_body = TRUE; + } else { cmd = t_strdup_printf("TOP %u 0\r\n", _mail->seq); + } if (pop3c_client_cmd_stream(mbox->client, cmd, &input, &error) < 0) { mail_storage_set_error(mbox->box.storage, @@ -130,7 +132,8 @@ } } i_stream_set_name(mail->data.stream, t_strcut(cmd, '\r')); - pop3c_mail_cache_size(mail); + if (get_body) + pop3c_mail_cache_size(mail); } return index_mail_init_stream(mail, hdr_size, body_size, stream_r); }
--- a/src/lib-storage/index/pop3c/pop3c-settings.c Fri Aug 10 04:56:56 2012 +0300 +++ b/src/lib-storage/index/pop3c/pop3c-settings.c Fri Aug 10 05:24:07 2012 +0300 @@ -18,6 +18,7 @@ DEF(SET_UINT, pop3c_port), DEF(SET_STR_VARS, pop3c_user), + DEF(SET_STR_VARS, pop3c_master_user), DEF(SET_STR, pop3c_password), DEF(SET_ENUM, pop3c_ssl), @@ -35,6 +36,7 @@ .pop3c_port = 110, .pop3c_user = "%u", + .pop3c_master_user = "", .pop3c_password = "", .pop3c_ssl = "no:pop3s:starttls",
--- a/src/lib-storage/index/pop3c/pop3c-settings.h Fri Aug 10 04:56:56 2012 +0300 +++ b/src/lib-storage/index/pop3c/pop3c-settings.h Fri Aug 10 05:24:07 2012 +0300 @@ -6,6 +6,7 @@ unsigned int pop3c_port; const char *pop3c_user; + const char *pop3c_master_user; const char *pop3c_password; const char *pop3c_ssl;
--- a/src/lib-storage/index/pop3c/pop3c-storage.c Fri Aug 10 04:56:56 2012 +0300 +++ b/src/lib-storage/index/pop3c/pop3c-storage.c Fri Aug 10 05:24:07 2012 +0300 @@ -60,6 +60,7 @@ client_set.host = set->pop3c_host; client_set.port = set->pop3c_port; client_set.username = set->pop3c_user; + client_set.master_user = set->pop3c_master_user; client_set.password = set->pop3c_password; client_set.dns_client_socket_path = t_strconcat(user->set->base_dir, "/",
--- a/src/lib-storage/index/shared/shared-list.c Fri Aug 10 04:56:56 2012 +0300 +++ b/src/lib-storage/index/shared/shared-list.c Fri Aug 10 05:24:07 2012 +0300 @@ -136,7 +136,8 @@ else ns_ref = NULL; - if (ns_ref != NULL && shared_storage_get_namespace(&ns, &ns_ref) == 0) + if (ns_ref != NULL && *ns_ref != '\0' && + shared_storage_get_namespace(&ns, &ns_ref) == 0) return mailbox_list_join_refpattern(ns->list, ref, pattern); /* fallback to default behavior */
--- a/src/lib-storage/list/mailbox-list-delete.c Fri Aug 10 04:56:56 2012 +0300 +++ b/src/lib-storage/list/mailbox-list-delete.c Fri Aug 10 05:24:07 2012 +0300 @@ -340,11 +340,12 @@ if (errno == ENOENT) { mailbox_list_set_error(list, MAIL_ERROR_NOTFOUND, T_MAIL_ERR_MAILBOX_NOT_FOUND(name)); - } else if (errno == EISDIR) { + } else if (errno == EISDIR || + errno == EPERM) { /* Solaris */ mailbox_list_set_error(list, MAIL_ERROR_NOTPOSSIBLE, "Mailbox isn't a symlink"); } else { - mailbox_list_set_critical(list, "stat(%s) failed: %m", path); + mailbox_list_set_critical(list, "unlink(%s) failed: %m", path); } return -1; }
--- a/src/lib-storage/list/mailbox-list-fs-iter.c Fri Aug 10 04:56:56 2012 +0300 +++ b/src/lib-storage/list/mailbox-list-fs-iter.c Fri Aug 10 05:24:07 2012 +0300 @@ -404,10 +404,18 @@ we just want to see its contents (not the INBOX's children). */ root = ""; - } else if (*prefix_vname == '\0') { - /* we need to handle "" explicitly here, because getting - storage name with mail_shared_explicit_inbox=no - would return root=INBOX. */ + } else if ((ns->flags & NAMESPACE_FLAG_INBOX_ANY) != 0 && + ns->type == NAMESPACE_SHARED && + !ctx->ctx.list->mail_set->mail_shared_explicit_inbox && + (prefix_vname[0] == '\0' || + (strncmp(ns->prefix, prefix_vname, ns->prefix_len-1) == 0 && + prefix_vname[ns->prefix_len-1] == '\0'))) { + /* we need to handle ns prefix explicitly here, because + getting storage name with + mail_shared_explicit_inbox=no would return + root=INBOX. (e.g. LIST "" shared/user/box has to + return the box when it doesn't exist but + shared/user/box/child exists) */ root = ""; } else { root = mailbox_list_get_storage_name(ctx->ctx.list,
--- a/src/lib-storage/mail-namespace.c Fri Aug 10 04:56:56 2012 +0300 +++ b/src/lib-storage/mail-namespace.c Fri Aug 10 05:24:07 2012 +0300 @@ -249,15 +249,15 @@ return FALSE; } if (list_sep == '\0') { - *error_r = "no list=yes namespaces"; + *error_r = "list=yes namespace missing"; return FALSE; } if (!visible_namespaces) { - *error_r = "no hidden=no namespaces"; + *error_r = "hidden=no namespace missing"; return FALSE; } if (subscriptions_count == 0) { - *error_r = "no subscriptions=yes namespaces"; + *error_r = "subscriptions=yes namespace missing"; return FALSE; } return TRUE;
--- a/src/lib-storage/mail-storage.c Fri Aug 10 04:56:56 2012 +0300 +++ b/src/lib-storage/mail-storage.c Fri Aug 10 05:24:07 2012 +0300 @@ -818,17 +818,18 @@ return 0; } -static void mailbox_autocreate(struct mailbox *box) +static int mailbox_autocreate(struct mailbox *box) { const char *errstr; enum mail_error error; if (mailbox_create(box, NULL, FALSE) < 0) { errstr = mailbox_get_last_error(box, &error); - if (error != MAIL_ERROR_NOTFOUND && !box->inbox_user) { + if (error != MAIL_ERROR_NOTFOUND) { mail_storage_set_critical(box->storage, "Failed to autocreate mailbox %s: %s", box->vname, errstr); + return -1; } } else if (box->set != NULL && strcmp(box->set->autocreate, @@ -837,15 +838,18 @@ mail_storage_set_critical(box->storage, "Failed to autosubscribe to mailbox %s: %s", box->vname, mailbox_get_last_error(box, NULL)); + return -1; } } + return 0; } static int mailbox_autocreate_and_reopen(struct mailbox *box) { int ret; - mailbox_autocreate(box); + if (mailbox_autocreate(box) < 0) + return -1; mailbox_close(box); ret = box->v.open(box);
--- a/src/lib-storage/mail-user.c Fri Aug 10 04:56:56 2012 +0300 +++ b/src/lib-storage/mail-user.c Fri Aug 10 05:24:07 2012 +0300 @@ -201,7 +201,9 @@ }; struct var_expand_table *tab; - if (user->var_expand_table != NULL) + /* use a cached table, unless home directory has been set afterwards */ + if (user->var_expand_table != NULL && + user->var_expand_table[4].value == user->_home) return user->var_expand_table; tab = p_malloc(user->pool, sizeof(static_tab));
--- a/src/lib/Makefile.am Fri Aug 10 04:56:56 2012 +0300 +++ b/src/lib/Makefile.am Fri Aug 10 05:24:07 2012 +0300 @@ -77,6 +77,7 @@ ioloop-select.c \ ioloop-epoll.c \ ioloop-kqueue.c \ + json-parser.c \ lib.c \ lib-signals.c \ md4.c \ @@ -190,6 +191,7 @@ ioloop-iolist.h \ ioloop-private.h \ ioloop-notify-fd.h \ + json-parser.h \ lib.h \ lib-signals.h \ llist.h \
--- a/src/lib/file-set-size.c Fri Aug 10 04:56:56 2012 +0300 +++ b/src/lib/file-set-size.c Fri Aug 10 05:24:07 2012 +0300 @@ -100,7 +100,7 @@ fs.fst_bytesalloc = 0; if (fcntl(fd, F_PREALLOCATE, &fs) < 0) return -1; - return 0; + return fs.fst_bytesalloc > 0 ? 1 : 0; #else return 0; #endif
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/lib/json-parser.c Fri Aug 10 05:24:07 2012 +0300 @@ -0,0 +1,272 @@ +/* Copyright (c) 2012 Dovecot authors, see the included COPYING file */ + +#include "lib.h" +#include "str.h" +#include "hex-dec.h" +#include "unichar.h" +#include "json-parser.h" + +enum json_state { + JSON_STATE_ROOT = 0, + JSON_STATE_OBJECT_KEY, + JSON_STATE_OBJECT_COLON, + JSON_STATE_OBJECT_VALUE, + JSON_STATE_OBJECT_VALUE_NEXT, + JSON_STATE_DONE +}; + +struct json_parser { + const unsigned char *data, *end; + const char *error; + string_t *value; + + enum json_state state; +}; + +struct json_parser * +json_parser_init(const unsigned char *data, unsigned int len) +{ + struct json_parser *parser; + + parser = i_new(struct json_parser, 1); + parser->data = data; + parser->end = data + len; + parser->value = str_new(default_pool, 128); + return parser; +} + +int json_parser_deinit(struct json_parser **_parser, const char **error_r) +{ + struct json_parser *parser = *_parser; + + *_parser = NULL; + + if (parser->error == NULL && parser->data == parser->end && + parser->state != JSON_STATE_ROOT && + parser->state != JSON_STATE_DONE) + parser->error = "Missing '}'"; + + *error_r = parser->error; + str_free(&parser->value); + i_free(parser); + return *error_r != NULL ? -1 : 0; +} + +static bool json_parse_whitespace(struct json_parser *parser) +{ + for (; parser->data != parser->end; parser->data++) { + switch (*parser->data) { + case ' ': + case '\t': + case '\r': + case '\n': + break; + default: + return TRUE; + } + } + return FALSE; +} + +static int json_parse_string(struct json_parser *parser, const char **value_r) +{ + const unsigned char *p; + + if (*parser->data != '"') + return -1; + + str_truncate(parser->value, 0); + for (p = parser->data + 1; p < parser->end; p++) { + if (*p == '"') { + parser->data = p + 1; + *value_r = str_c(parser->value); + return 0; + } + if (*p != '\\') + str_append_c(parser->value, *p); + else { + switch (*++p) { + case '"': + case '\\': + case '/': + str_append_c(parser->value, *p); + break; + case 'b': + str_append_c(parser->value, '\b'); + break; + case 'f': + str_append_c(parser->value, '\f'); + break; + case 'n': + str_append_c(parser->value, '\n'); + break; + case 'r': + str_append_c(parser->value, '\r'); + break; + case 't': + str_append_c(parser->value, '\t'); + break; + case 'u': + if (parser->end - p < 4) + return -1; + uni_ucs4_to_utf8_c(hex2dec(p, 4), + parser->value); + p += 3; + break; + default: + return -1; + } + } + } + return -1; +} + +static int +json_parse_digits(struct json_parser *parser, const unsigned char **_p) +{ + const unsigned char *p = *_p; + + if (p >= parser->end || *p < '0' || *p > '9') + return -1; + + for (; p < parser->end && *p >= '0' && *p <= '9'; p++) + str_append_c(parser->value, *p++); + *_p = p; + return 0; +} + +static int json_parse_int(struct json_parser *parser, const unsigned char **_p) +{ + const unsigned char *p = *_p; + + if (*p == '-') { + str_append_c(parser->value, *p++); + if (p == parser->end) + return -1; + } + if (*p == '0') + str_append_c(parser->value, *p++); + else { + if (json_parse_digits(parser, &p) < 0) + return -1; + } + *_p = p; + return 0; +} + +static int json_parse_number(struct json_parser *parser, const char **value_r) +{ + const unsigned char *p = parser->data; + + str_truncate(parser->value, 0); + if (json_parse_int(parser, &p) < 0) + return -1; + if (p < parser->end && *p == '.') { + /* frac */ + str_append_c(parser->value, *p++); + if (json_parse_digits(parser, &p) < 0) + return -1; + } + if (p < parser->end && (*p == 'e' || *p == 'E')) { + /* exp */ + str_append_c(parser->value, *p++); + if (p == parser->end) + return -1; + if (*p == '+' || *p == '-') + str_append_c(parser->value, *p++); + if (json_parse_digits(parser, &p) < 0) + return -1; + } + *value_r = str_c(parser->value); + return 0; +} + +static int json_parse_atom(struct json_parser *parser, const char *atom) +{ + unsigned int len = strlen(atom); + + if (parser->end - parser->data < len) + return -1; + if (memcmp(parser->data, atom, len) != 0) + return -1; + parser->data += len; + return 0; +} + +bool json_parse_next(struct json_parser *parser, enum json_type *type_r, + const char **value_r) +{ + *value_r = NULL; + + if (!json_parse_whitespace(parser) || parser->error != NULL) + return FALSE; + + switch (parser->state) { + case JSON_STATE_ROOT: + if (*parser->data == '{') { + parser->data++; + parser->state = JSON_STATE_OBJECT_KEY; + return json_parse_next(parser, type_r, value_r); + } + /* fall through */ + case JSON_STATE_OBJECT_VALUE: + if (json_parse_string(parser, value_r) == 0) + *type_r = JSON_TYPE_STRING; + else if (json_parse_number(parser, value_r) == 0) + *type_r = JSON_TYPE_NUMBER; + else if (json_parse_atom(parser, "true") == 0) { + *type_r = JSON_TYPE_TRUE; + *value_r = "true"; + } else if (json_parse_atom(parser, "false") == 0) { + *type_r = JSON_TYPE_FALSE; + *value_r = "false"; + } else if (json_parse_atom(parser, "null") == 0) { + *type_r = JSON_TYPE_NULL; + *value_r = NULL; + } else if (*parser->data == '[') { + parser->error = "Arrays not supported"; + return FALSE; + } else if (*parser->data == '{') { + parser->error = "Nested objects not supported"; + return FALSE; + } else { + parser->error = "Invalid data as value"; + return FALSE; + } + parser->state = parser->state == JSON_STATE_ROOT ? + JSON_STATE_DONE : + JSON_STATE_OBJECT_VALUE_NEXT; + break; + case JSON_STATE_OBJECT_KEY: + *type_r = JSON_TYPE_OBJECT_KEY; + if (json_parse_string(parser, value_r) < 0) { + parser->error = "Expected string as object key"; + return FALSE; + } + parser->state = JSON_STATE_OBJECT_COLON; + break; + case JSON_STATE_OBJECT_COLON: + if (*parser->data != ':') { + parser->error = "Expected ':' after key"; + return FALSE; + } + parser->data++; + parser->state = JSON_STATE_OBJECT_VALUE; + return json_parse_next(parser, type_r, value_r); + case JSON_STATE_OBJECT_VALUE_NEXT: + if (*parser->data == ',') + parser->state = JSON_STATE_OBJECT_KEY; + else if (*parser->data == '}') + parser->state = JSON_STATE_DONE; + else { + parser->error = "Expected ',' or '}' after object value"; + return FALSE; + } + parser->data++; + return json_parse_next(parser, type_r, value_r); + case JSON_STATE_DONE: + parser->error = "Unexpected data at the end"; + return FALSE; + } + return TRUE; +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/lib/json-parser.h Fri Aug 10 05:24:07 2012 +0300 @@ -0,0 +1,22 @@ +#ifndef JSON_PARSER_H +#define JSON_PARSER_H + +enum json_type { + JSON_TYPE_OBJECT_KEY, + JSON_TYPE_STRING, + JSON_TYPE_NUMBER, + JSON_TYPE_TRUE, + JSON_TYPE_FALSE, + JSON_TYPE_NULL +}; + +/* A really simple JSON parser, which for now only needs to be able to parse + a single { key: value, .. } object. */ +struct json_parser * +json_parser_init(const unsigned char *data, unsigned int len); +int json_parser_deinit(struct json_parser **parser, const char **error_r); + +bool json_parse_next(struct json_parser *parser, enum json_type *type_r, + const char **value_r); + +#endif
--- a/src/lmtp/commands.c Fri Aug 10 04:56:56 2012 +0300 +++ b/src/lmtp/commands.c Fri Aug 10 05:24:07 2012 +0300 @@ -398,6 +398,69 @@ } } +static void lmtp_address_translate(struct client *client, const char **address) +{ + const char *transpos = client->lmtp_set->lmtp_address_translate; + const char *p, *nextstr, *addrpos = *address; + unsigned int len; + string_t *username, *domain, *dest = NULL; + + if (*transpos == '\0') + return; + + username = t_str_new(64); + domain = t_str_new(64); + + /* check that string matches up to the first '%' */ + p = strchr(transpos, '%'); + if (p == NULL) + len = strlen(transpos); + else + len = p-transpos; + if (strncmp(transpos, addrpos, len) != 0) + return; + transpos += len; + addrpos += len; + + while (*transpos != '\0') { + switch (transpos[1]) { + case 'n': + case 'u': + dest = username; + break; + case 'd': + dest = domain; + break; + default: + return; + } + transpos += 2; + + /* find where the next string starts */ + if (*transpos == '\0') { + str_append(dest, addrpos); + break; + } + p = strchr(transpos, '%'); + if (p == NULL) + nextstr = transpos; + else + nextstr = t_strdup_until(transpos, p); + p = strstr(addrpos, nextstr); + if (p == NULL) + return; + str_append_n(dest, addrpos, p-addrpos); + + len = strlen(nextstr); + transpos += len; + addrpos = p + len; + } + str_append_c(username, '@'); + if (domain != NULL) + str_append_str(username, domain); + *address = str_c(username); +} + int cmd_rcpt(struct client *client, const char *args) { struct mail_recipient rcpt; @@ -467,6 +530,8 @@ return 0; } + lmtp_address_translate(client, &address); + rcpt.address = p_strdup(client->state_pool, address); rcpt.detail = p_strdup(client->state_pool, detail); array_append(&client->state.rcpt_to, &rcpt, 1); @@ -789,6 +854,9 @@ struct istream *input; bool ret = TRUE; + /* stop handling client input until saving/proxying is finished */ + if (client->to_idle != NULL) + timeout_remove(&client->to_idle); io_remove(&client->io); i_stream_destroy(&client->dot_input);
--- a/src/lmtp/lmtp-proxy.c Fri Aug 10 04:56:56 2012 +0300 +++ b/src/lmtp/lmtp-proxy.c Fri Aug 10 05:24:07 2012 +0300 @@ -236,6 +236,10 @@ i_assert(!rcpt->rcpt_to_failed); i_assert(rcpt->reply != NULL); + /* reset timeout in case there are a lot of RCPT TOs */ + if (conn->to != NULL) + timeout_reset(conn->to); + rcpt->reply = p_strdup(conn->proxy->pool, reply); rcpt->data_reply_received = TRUE;
--- a/src/lmtp/lmtp-settings.c Fri Aug 10 04:56:56 2012 +0300 +++ b/src/lmtp/lmtp-settings.c Fri Aug 10 05:24:07 2012 +0300 @@ -59,6 +59,7 @@ static const struct setting_define lmtp_setting_defines[] = { DEF(SET_BOOL, lmtp_proxy), DEF(SET_BOOL, lmtp_save_to_detail_mailbox), + DEF(SET_STR, lmtp_address_translate), DEF(SET_STR_VARS, login_greeting), DEF(SET_STR, login_trusted_networks), @@ -68,6 +69,7 @@ static const struct lmtp_settings lmtp_default_settings = { .lmtp_proxy = FALSE, .lmtp_save_to_detail_mailbox = FALSE, + .lmtp_address_translate = "", .login_greeting = PACKAGE_NAME" ready.", .login_trusted_networks = "" };
--- a/src/lmtp/lmtp-settings.h Fri Aug 10 04:56:56 2012 +0300 +++ b/src/lmtp/lmtp-settings.h Fri Aug 10 05:24:07 2012 +0300 @@ -7,6 +7,7 @@ struct lmtp_settings { bool lmtp_proxy; bool lmtp_save_to_detail_mailbox; + const char *lmtp_address_translate; const char *login_greeting; const char *login_trusted_networks; };
--- a/src/login-common/client-common.c Fri Aug 10 04:56:56 2012 +0300 +++ b/src/login-common/client-common.c Fri Aug 10 05:24:07 2012 +0300 @@ -15,6 +15,7 @@ #include "var-expand.h" #include "master-interface.h" #include "master-service.h" +#include "master-service-ssl-settings.h" #include "master-auth.h" #include "auth-client.h" #include "login-proxy.h" @@ -379,7 +380,7 @@ return; } - if (!ssl_initialized) { + if (!client_is_tls_enabled(client)) { client->v.notify_starttls(client, FALSE, "TLS support isn't enabled."); return; } @@ -599,6 +600,11 @@ } T_END; } +bool client_is_tls_enabled(struct client *client) +{ + return ssl_initialized && strcmp(client->ssl_set->ssl, "no") != 0; +} + const char *client_get_extra_disconnect_reason(struct client *client) { unsigned int auth_secs = client->auth_first_started == 0 ? 0 :
--- a/src/login-common/client-common.h Fri Aug 10 04:56:56 2012 +0300 +++ b/src/login-common/client-common.h Fri Aug 10 05:24:07 2012 +0300 @@ -188,6 +188,7 @@ void client_auth_respond(struct client *client, const char *response); void client_auth_abort(struct client *client); +bool client_is_tls_enabled(struct client *client); void client_auth_fail(struct client *client, const char *text); const char *client_get_session_id(struct client *client);
--- a/src/login-common/ssl-proxy-openssl.c Fri Aug 10 04:56:56 2012 +0300 +++ b/src/login-common/ssl-proxy-openssl.c Fri Aug 10 05:24:07 2012 +0300 @@ -856,9 +856,9 @@ where, SSL_state_string_long(ssl), net_ip2addr(&proxy->ip)); } else { - i_warning("SSL: where=0x%x, ret=%d: %s [%s]", - where, ret, SSL_state_string_long(ssl), - net_ip2addr(&proxy->ip)); + i_debug("SSL: where=0x%x, ret=%d: %s [%s]", + where, ret, SSL_state_string_long(ssl), + net_ip2addr(&proxy->ip)); } } @@ -1036,6 +1036,17 @@ } } +static const char *ssl_key_load_error(void) +{ + unsigned long err = ERR_peek_error(); + + if (ERR_GET_LIB(err) == ERR_LIB_X509 && + ERR_GET_REASON(err) == X509_R_KEY_VALUES_MISMATCH) + return "Key is for a different cert than ssl_cert"; + else + return ssl_last_error(); +} + static EVP_PKEY * ATTR_NULL(2) ssl_proxy_load_key(const char *key, const char *password) { @@ -1050,23 +1061,14 @@ dup_password = t_strdup_noconst(password); pkey = PEM_read_bio_PrivateKey(bio, NULL, pem_password_callback, dup_password); - if (pkey == NULL) - i_fatal("Couldn't parse private ssl_key"); + if (pkey == NULL) { + i_fatal("Couldn't parse private ssl_key: %s", + ssl_key_load_error()); + } BIO_free(bio); return pkey; } -static const char *ssl_key_load_error(void) -{ - unsigned long err = ERR_peek_error(); - - if (ERR_GET_LIB(err) == ERR_LIB_X509 && - ERR_GET_REASON(err) == X509_R_KEY_VALUES_MISMATCH) - return "Key is for a different cert than ssl_cert"; - else - return ssl_last_error(); -} - static void ssl_proxy_ctx_use_key(SSL_CTX *ctx, const struct master_service_ssl_settings *set)
--- a/src/master/main.c Fri Aug 10 04:56:56 2012 +0300 +++ b/src/master/main.c Fri Aug 10 05:24:07 2012 +0300 @@ -299,8 +299,7 @@ while ((rec = mountpoint_list_iter_next(iter)) != NULL) { if (MOUNTPOINT_WRONGLY_NOT_MOUNTED(rec)) { i_warning("%s is no longer mounted. " - "If this is intentional, " - "remove it with doveadm mount", + "See http://wiki2.dovecot.org/Mountpoints", rec->mount_path); } }
--- a/src/plugins/fts-lucene/Makefile.am Fri Aug 10 04:56:56 2012 +0300 +++ b/src/plugins/fts-lucene/Makefile.am Fri Aug 10 05:24:07 2012 +0300 @@ -21,6 +21,10 @@ endif if BUILD_LUCENE_TEXTCAT TEXTCAT_LIBS = -ltextcat +else +if BUILD_LUCENE_EXTTEXTCAT +TEXTCAT_LIBS = -lexttextcat +endif endif lib21_fts_lucene_plugin_la_LIBADD = \
--- a/src/plugins/fts-lucene/lucene-wrapper.cc Fri Aug 10 04:56:56 2012 +0300 +++ b/src/plugins/fts-lucene/lucene-wrapper.cc Fri Aug 10 05:24:07 2012 +0300 @@ -18,6 +18,10 @@ #include <sys/stat.h> #ifdef HAVE_LUCENE_TEXTCAT # include <libtextcat/textcat.h> +#else +#ifdef HAVE_LUCENE_EXTTEXTCAT +# include <libexttextcat/textcat.h> +#endif #endif }; #include <CLucene.h> @@ -1094,7 +1098,7 @@ return false; q = lucene_get_query(index, - t_lucene_utf8_to_tchar(index, arg->hdr_field_name, FALSE), + t_lucene_utf8_to_tchar(index, t_str_lcase(arg->hdr_field_name), FALSE), arg); break; default: @@ -1137,7 +1141,7 @@ if (*arg->value.str == '\0') { /* checking potential existence of the header name */ q = lucene_get_query_str(index, _T("hdr"), - arg->hdr_field_name, FALSE); + t_str_lcase(arg->hdr_field_name), FALSE); break; }
--- a/src/plugins/fts-solr/fts-backend-solr.c Fri Aug 10 04:56:56 2012 +0300 +++ b/src/plugins/fts-solr/fts-backend-solr.c Fri Aug 10 05:24:07 2012 +0300 @@ -38,6 +38,7 @@ struct solr_connection_post *post; uint32_t prev_uid; string_t *cmd, *cur_value, *cur_value2; + string_t *cmd_expunge; ARRAY_DEFINE(fields, struct solr_fts_field); uint32_t last_indexed_uid; @@ -240,7 +241,6 @@ ctx = i_new(struct solr_fts_backend_update_context, 1); ctx->ctx.backend = _backend; - ctx->cmd = str_new(default_pool, SOLR_CMDBUF_SIZE); i_array_init(&ctx->fields, 16); return &ctx->ctx; } @@ -326,6 +326,15 @@ return solr_connection_post_end(ctx->post); } +static void +fts_backend_solr_expunge_flush(struct solr_fts_backend_update_context *ctx) +{ + str_append(ctx->cmd_expunge, "</delete>"); + (void)solr_connection_post(solr_conn, str_c(ctx->cmd_expunge)); + str_truncate(ctx->cmd_expunge, 0); + str_append(ctx->cmd_expunge, "<delete>"); +} + static int fts_backend_solr_update_deinit(struct fts_backend_update_context *_ctx) { @@ -341,6 +350,8 @@ if (ctx->documents_added || ctx->expunges) { /* commit and wait until the documents we just indexed are visible to the following search */ + if (ctx->expunges) + fts_backend_solr_expunge_flush(ctx); str = t_strdup_printf("<commit waitFlush=\"false\" " "waitSearcher=\"%s\"/>", ctx->documents_added ? "true" : "false"); @@ -348,7 +359,10 @@ ret = -1; } - str_free(&ctx->cmd); + if (ctx->cmd != NULL) + str_free(&ctx->cmd); + if (ctx->cmd_expunge != NULL) + str_free(&ctx->cmd_expunge); array_foreach_modifiable(&ctx->fields, field) { str_free(&field->value); i_free(field->key); @@ -404,18 +418,18 @@ highly unlikely to be indexed at this time. */ return; } - ctx->expunges = TRUE; - - T_BEGIN { - string_t *cmd; + if (!ctx->expunges) { + ctx->expunges = TRUE; + ctx->cmd_expunge = str_new(default_pool, 1024); + str_append(ctx->cmd_expunge, "<delete>"); + } - cmd = t_str_new(256); - str_append(cmd, "<delete><id>"); - xml_encode_id(ctx, cmd, uid); - str_append(cmd, "</id></delete>"); + if (str_len(ctx->cmd_expunge) >= SOLR_CMDBUF_FLUSH_SIZE) + fts_backend_solr_expunge_flush(ctx); - (void)solr_connection_post(solr_conn, str_c(cmd)); - } T_END; + str_append(ctx->cmd_expunge, "<id>"); + xml_encode_id(ctx, ctx->cmd_expunge, uid); + str_append(ctx->cmd_expunge, "</id>"); } static void @@ -425,6 +439,7 @@ if (ctx->post == NULL) { i_assert(ctx->prev_uid == 0); + ctx->cmd = str_new(default_pool, SOLR_CMDBUF_SIZE); ctx->post = solr_connection_post_begin(solr_conn); str_append(ctx->cmd, "<add>"); } else {
--- a/src/plugins/fts-solr/solr-connection.c Fri Aug 10 04:56:56 2012 +0300 +++ b/src/plugins/fts-solr/solr-connection.c Fri Aug 10 05:24:07 2012 +0300 @@ -88,7 +88,7 @@ const void *data, size_t size, bool done) { enum XML_Error err; - int line; + int line, col; if (conn->xml_failed) return -1; @@ -99,8 +99,10 @@ err = XML_GetErrorCode(conn->xml_parser); if (err != XML_ERROR_FINISHED) { line = XML_GetCurrentLineNumber(conn->xml_parser); - i_error("fts_solr: Invalid XML input at line %d: %s", - line, XML_ErrorString(err)); + col = XML_GetCurrentColumnNumber(conn->xml_parser); + i_error("fts_solr: Invalid XML input at %d:%d: %s " + "(near: %.*s)", line, col, XML_ErrorString(err), + (int)I_MIN(size, 128), (const char *)data); conn->xml_failed = TRUE; return -1; }
--- a/src/plugins/fts/fts-api.c Fri Aug 10 04:56:56 2012 +0300 +++ b/src/plugins/fts/fts-api.c Fri Aug 10 05:24:07 2012 +0300 @@ -318,9 +318,30 @@ struct mail_search_arg *args, bool and_args, struct fts_multi_result *result) { + unsigned int i; + i_assert(boxes[0] != NULL); - return backend->v.lookup_multi(backend, boxes, args, and_args, result); + if (backend->v.lookup_multi != NULL) { + if (backend->v.lookup_multi(backend, boxes, args, + and_args, result) < 0) + return -1; + if (result->box_results == NULL) { + result->box_results = p_new(result->pool, + struct fts_result, 1); + } + return 0; + } + + for (i = 0; boxes[i] != NULL; i++) ; + result->box_results = p_new(result->pool, struct fts_result, i+1); + + for (i = 0; boxes[i] != NULL; i++) { + if (backend->v.lookup(backend, boxes[i], args, + and_args, &result->box_results[i]) < 0) + return -1; + } + return 0; } void fts_backend_lookup_done(struct fts_backend *backend)
--- a/src/plugins/fts/fts-search.c Fri Aug 10 04:56:56 2012 +0300 +++ b/src/plugins/fts/fts-search.c Fri Aug 10 05:24:07 2012 +0300 @@ -199,13 +199,15 @@ struct mail_search_arg *args, bool and_args) { - if (!fctx->virtual_mailbox) { - if (fts_search_lookup_level_single(fctx, args, and_args) < 0) - return -1; - } else T_BEGIN { - if (fts_search_lookup_level_multi(fctx, args, and_args) < 0) - return -1; + int ret; + + T_BEGIN { + ret = !fctx->virtual_mailbox ? + fts_search_lookup_level_single(fctx, args, and_args) : + fts_search_lookup_level_multi(fctx, args, and_args); } T_END; + if (ret < 0) + return -1; for (; args != NULL; args = args->next) { if (args->type != SEARCH_OR && args->type != SEARCH_SUB)
--- a/src/plugins/imap-acl/imap-acl-plugin.c Fri Aug 10 04:56:56 2012 +0300 +++ b/src/plugins/imap-acl/imap-acl-plugin.c Fri Aug 10 05:24:07 2012 +0300 @@ -309,12 +309,13 @@ { struct mail_namespace *ns; struct mailbox *box; - const char *mailbox; + const char *mailbox, *orig_mailbox; const char *const *rights; string_t *str; if (!client_read_string_args(cmd, 1, &mailbox)) return FALSE; + orig_mailbox = mailbox; if (ACL_USER_CONTEXT(cmd->client->user) == NULL) { client_send_command_error(cmd, "ACLs disabled."); @@ -346,7 +347,7 @@ str = t_str_new(128); str_append(str, "* MYRIGHTS "); - imap_quote_append_string(str, mailbox, FALSE); + imap_quote_append_string(str, orig_mailbox, FALSE); str_append_c(str,' '); imap_acl_write_rights_list(str, rights);
--- a/src/plugins/imap-quota/imap-quota-plugin.c Fri Aug 10 04:56:56 2012 +0300 +++ b/src/plugins/imap-quota/imap-quota-plugin.c Fri Aug 10 05:24:07 2012 +0300 @@ -74,12 +74,13 @@ struct mailbox *box; struct quota_root_iter *iter; struct quota_root *root; - const char *mailbox, *name; + const char *mailbox, *orig_mailbox, *name; string_t *quotaroot_reply, *quota_reply; /* <mailbox> */ if (!client_read_string_args(cmd, 1, &mailbox)) return FALSE; + orig_mailbox = mailbox; ns = client_find_namespace(cmd, &mailbox); if (ns == NULL) @@ -101,7 +102,7 @@ quotaroot_reply = t_str_new(128); quota_reply = t_str_new(256); str_append(quotaroot_reply, "* QUOTAROOT "); - imap_quote_append_string(quotaroot_reply, mailbox, FALSE); + imap_quote_append_string(quotaroot_reply, orig_mailbox, FALSE); iter = quota_root_iter_init(box); while ((root = quota_root_iter_next(iter)) != NULL) {
--- a/src/plugins/mail-log/mail-log-plugin.c Fri Aug 10 04:56:56 2012 +0300 +++ b/src/plugins/mail-log/mail-log-plugin.c Fri Aug 10 05:24:07 2012 +0300 @@ -363,7 +363,7 @@ const char *desc; desc = t_strdup_printf("copy from %s", - str_sanitize(mailbox_get_name(src->box), + str_sanitize(mailbox_get_vname(src->box), MAILBOX_NAME_LOG_LEN)); mail_log_append_mail_message(ctx, dst, MAIL_LOG_EVENT_COPY, desc); @@ -466,7 +466,7 @@ return; i_info("Mailbox created: %s", - str_sanitize(box->name, MAILBOX_NAME_LOG_LEN)); + str_sanitize(mailbox_get_vname(box), MAILBOX_NAME_LOG_LEN)); } static void @@ -478,7 +478,7 @@ return; i_info("Mailbox deleted: %s", - str_sanitize(box->name, MAILBOX_NAME_LOG_LEN)); + str_sanitize(mailbox_get_vname(box), MAILBOX_NAME_LOG_LEN)); } static void @@ -491,8 +491,8 @@ return; i_info("Mailbox renamed: %s -> %s", - str_sanitize(src->name, MAILBOX_NAME_LOG_LEN), - str_sanitize(dest->name, MAILBOX_NAME_LOG_LEN)); + str_sanitize(mailbox_get_vname(src), MAILBOX_NAME_LOG_LEN), + str_sanitize(mailbox_get_vname(dest), MAILBOX_NAME_LOG_LEN)); } static const struct notify_vfuncs mail_log_vfuncs = {
--- a/src/plugins/pop3-migration/pop3-migration-plugin.c Fri Aug 10 04:56:56 2012 +0300 +++ b/src/plugins/pop3-migration/pop3-migration-plugin.c Fri Aug 10 05:24:07 2012 +0300 @@ -472,8 +472,13 @@ if (pop3_mailbox_open(box->storage) < 0) return -1; + /* the POP3 server isn't connected to yet. handle all IMAP traffic + first before connecting, so POP3 server won't disconnect us due to + idling. */ + if (imap_map_read(box) < 0) + return -1; - if (pop3_map_read(box->storage) < 0 || imap_map_read(box) < 0) + if (pop3_map_read(box->storage) < 0) return -1; if (!pop3_uidl_assign_by_size(box)) {
--- a/src/plugins/quota/quota-private.h Fri Aug 10 04:56:56 2012 +0300 +++ b/src/plugins/quota/quota-private.h Fri Aug 10 05:24:07 2012 +0300 @@ -25,6 +25,7 @@ uoff_t size, bool *too_large_r); const char *quota_exceeded_msg; + unsigned int ignore_save_errors:1; unsigned int debug:1; }; @@ -101,7 +102,10 @@ /* this quota root applies only to this namespace. it may also be a public namespace without an owner. */ struct mail_namespace *ns; - /* this is set in quota init(), because namespaces aren't known yet */ + /* this is set in quota init(), because namespaces aren't known yet. + when accessing shared users the ns_prefix may be non-NULL but + ns=NULL, so when checking if quota root applies only to a specific + namespace use the ns_prefix!=NULL check. */ const char *ns_prefix; /* initially the same as set->default_rule.*_limit, but some backends
--- a/src/plugins/quota/quota-storage.c Fri Aug 10 04:56:56 2012 +0300 +++ b/src/plugins/quota/quota-storage.c Fri Aug 10 05:24:07 2012 +0300 @@ -147,7 +147,7 @@ } else { mail_storage_set_critical(t->box->storage, "Internal quota calculation error"); - return -1; + return qt->quota->set->ignore_save_errors ? 0 : -1; } } @@ -213,7 +213,8 @@ } else if (ret < 0) { mail_storage_set_critical(t->box->storage, "Internal quota calculation error"); - return -1; + if (!qt->quota->set->ignore_save_errors) + return -1; } }
--- a/src/plugins/quota/quota.c Fri Aug 10 04:56:56 2012 +0300 +++ b/src/plugins/quota/quota.c Fri Aug 10 05:24:07 2012 +0300 @@ -195,6 +195,8 @@ quota_set->debug = user->mail_debug; quota_set->quota_exceeded_msg = mail_user_plugin_getenv(user, "quota_exceeded_message"); + quota_set->ignore_save_errors = + mail_user_plugin_getenv(user, "quota_ignore_save_errors") != NULL; if (quota_set->quota_exceeded_msg == NULL) quota_set->quota_exceeded_msg = DEFAULT_QUOTA_EXCEEDED_MSG; @@ -770,7 +772,7 @@ (storage->class_flags & MAIL_STORAGE_CLASS_FLAG_NOQUOTA) != 0) return FALSE; - if (root->ns != NULL) { + if (root->ns_prefix != NULL) { if (root->ns != ns) return FALSE; } else {
--- a/src/pop3-login/client-authenticate.c Fri Aug 10 04:56:56 2012 +0300 +++ b/src/pop3-login/client-authenticate.c Fri Aug 10 05:24:07 2012 +0300 @@ -31,7 +31,7 @@ str_append(str, "+OK\r\n"); str_append(str, capability_string); - if (ssl_initialized && !client->common.tls) + if (client_is_tls_enabled(&client->common) && !client->common.tls) str_append(str, "STLS\r\n"); if (!client->common.set->disable_plaintext_auth || client->common.secured)
--- a/src/pop3/pop3-commands.c Fri Aug 10 04:56:56 2012 +0300 +++ b/src/pop3/pop3-commands.c Fri Aug 10 05:24:07 2012 +0300 @@ -756,7 +756,7 @@ str_hash, (hash_cmp_callback_t *)strcmp); client->uidl_pool = pool_alloconly_create("message uidls", 1024); client->message_uidls = p_new(client->uidl_pool, const char *, - client->messages_count); + client->messages_count+1); str = t_str_new(128); msgnum = 0; while (mailbox_search_next(search_ctx, &mail)) {
--- a/src/util/script.c Fri Aug 10 04:56:56 2012 +0300 +++ b/src/util/script.c Fri Aug 10 05:24:07 2012 +0300 @@ -133,8 +133,8 @@ } alarm(0); - /* drop the last LF */ - buffer_set_used_size(input, scanpos-1); + /* drop the last two LFs */ + buffer_set_used_size(input, scanpos-2); args = t_strsplit(str_c(input), "\n"); script_verify_version(*args); args++; @@ -148,7 +148,7 @@ exec_child(conn, args + 1); i_unreached(); } - if (*args == '\0') + if (**args == '\0') i_fatal("empty options"); args++; }