Mercurial > dovecot > original-hg > dovecot-1.2
view src/imap-login/client-authenticate.c @ 9642:e7721f67688a HEAD
imap/pop3-login: If AUTHENTICATE is aborted, don't do "auth failed" delay.
author | Timo Sirainen <tss@iki.fi> |
---|---|
date | Sat, 12 Mar 2011 16:05:57 +0200 |
parents | a6d0fa17ddee |
children |
line wrap: on
line source
/* Copyright (c) 2002-2010 Dovecot authors, see the included COPYING file */ #include "common.h" #include "base64.h" #include "buffer.h" #include "ioloop.h" #include "istream.h" #include "ostream.h" #include "safe-memset.h" #include "str.h" #include "str-sanitize.h" #include "imap-resp-code.h" #include "imap-parser.h" #include "auth-client.h" #include "client.h" #include "client-authenticate.h" #include "imap-proxy.h" #include <stdlib.h> #define AUTH_FAILURE_DELAY_INCREASE_MSECS 5000 #define IMAP_SERVICE_NAME "imap" const char *client_authenticate_get_capabilities(bool secured) { const struct auth_mech_desc *mech; unsigned int i, count; string_t *str; str = t_str_new(128); mech = auth_client_get_available_mechs(auth_client, &count); for (i = 0; i < count; i++) { /* a) transport is secured b) auth mechanism isn't plaintext c) we allow insecure authentication */ if ((mech[i].flags & MECH_SEC_PRIVATE) == 0 && (secured || !disable_plaintext_auth || (mech[i].flags & MECH_SEC_PLAINTEXT) == 0)) { str_append_c(str, ' '); str_append(str, "AUTH="); str_append(str, mech[i].name); } } return str_c(str); } static void client_auth_input(struct imap_client *client) { char *line; if (!client_read(client)) return; if (client->skip_line) { if (i_stream_next_line(client->common.input) == NULL) return; client->skip_line = FALSE; } /* @UNSAFE */ line = i_stream_next_line(client->common.input); if (line == NULL) return; if (strcmp(line, "*") == 0) sasl_server_auth_abort(&client->common); else { client_set_auth_waiting(client); auth_client_request_continue(client->common.auth_request, line); io_remove(&client->io); /* clear sensitive data */ safe_memset(line, 0, strlen(line)); } } static void client_authfail_delay_timeout(struct imap_client *client) { timeout_remove(&client->to_authfail_delay); /* get back to normal client input. */ i_assert(client->io == NULL); client->io = io_add(client->common.fd, IO_READ, client_input, client); client_input(client); } void client_auth_failed(struct imap_client *client, bool nodelay) { unsigned int delay_msecs; client->common.auth_command_tag = NULL; if (client->auth_initializing) return; if (client->io != NULL) io_remove(&client->io); if (nodelay) { client->io = io_add(client->common.fd, IO_READ, client_input, client); client_input(client); return; } /* increase the timeout after each unsuccessful attempt, but don't increase it so high that the idle timeout would be triggered */ delay_msecs = client->common.auth_attempts * AUTH_FAILURE_DELAY_INCREASE_MSECS; if (delay_msecs > CLIENT_LOGIN_IDLE_TIMEOUT_MSECS) delay_msecs = CLIENT_LOGIN_IDLE_TIMEOUT_MSECS - 1000; i_assert(client->to_authfail_delay == NULL); client->to_authfail_delay = timeout_add(delay_msecs, client_authfail_delay_timeout, client); } static bool client_handle_args(struct imap_client *client, const char *const *args, bool success, bool *nodelay_r) { const char *reason = NULL, *host = NULL, *destuser = NULL, *pass = NULL; const char *master_user = NULL; const char *key, *value, *p; enum login_proxy_ssl_flags ssl_flags = 0; string_t *reply; unsigned int port = 0; unsigned int proxy_timeout_msecs = 0; bool proxy = FALSE, temp = FALSE, nologin = !success; bool authz_failure = FALSE; *nodelay_r = FALSE; for (; *args != NULL; args++) { p = strchr(*args, '='); if (p == NULL) { key = *args; value = ""; } else { key = t_strdup_until(*args, p); value = p + 1; } if (strcmp(key, "nologin") == 0) nologin = TRUE; else if (strcmp(key, "nodelay") == 0) *nodelay_r = TRUE; else if (strcmp(key, "proxy") == 0) proxy = TRUE; else if (strcmp(key, "temp") == 0) temp = TRUE; else if (strcmp(key, "authz") == 0) authz_failure = TRUE; else if (strcmp(key, "reason") == 0) reason = value; else if (strcmp(key, "host") == 0) host = value; else if (strcmp(key, "port") == 0) port = atoi(value); else if (strcmp(key, "destuser") == 0) destuser = value; else if (strcmp(key, "pass") == 0) pass = value; else if (strcmp(key, "proxy_timeout") == 0) proxy_timeout_msecs = 1000*atoi(value); else if (strcmp(key, "master") == 0) master_user = value; else if (strcmp(key, "ssl") == 0) { if (strcmp(value, "yes") == 0) { ssl_flags |= PROXY_SSL_FLAG_YES; if (port == 0) port = 993; } else if (strcmp(value, "any-cert") == 0) { ssl_flags |= PROXY_SSL_FLAG_YES | PROXY_SSL_FLAG_ANY_CERT; } } else if (strcmp(key, "starttls") == 0) { ssl_flags |= PROXY_SSL_FLAG_STARTTLS; } else if (strcmp(key, "user") == 0) { /* already handled in login-common */ } else if (auth_debug) { i_info("Ignoring unknown passdb extra field: %s", key); } } if (port == 0) port = 143; if (destuser == NULL) destuser = client->common.virtual_user; if (proxy) { /* we want to proxy the connection to another server. don't do this unless authentication succeeded. with master user proxying we can get FAIL with proxy still set. proxy host=.. [port=..] [destuser=..] pass=.. */ if (!success) return FALSE; if (imap_proxy_new(client, host, port, destuser, master_user, pass, ssl_flags, proxy_timeout_msecs) < 0) { if (!client->destroyed) client_auth_failed(client, TRUE); } return TRUE; } if (host != NULL) { /* IMAP referral [nologin] referral host=.. [port=..] [destuser=..] [reason=..] NO [REFERRAL imap://destuser;AUTH=..@host:port/] Can't login. OK [...] Logged in, but you should use this server instead. .. [REFERRAL ..] (Reason from auth server) */ reply = t_str_new(128); str_append(reply, nologin ? "NO " : "OK "); str_printfa(reply, "[REFERRAL imap://%s;AUTH=%s@%s", destuser, client->common.auth_mech_name, host); if (port != 143) str_printfa(reply, ":%u", port); str_append(reply, "/] "); if (reason != NULL) str_append(reply, reason); else if (nologin) str_append(reply, "Try this server instead."); else { str_append(reply, "Logged in, but you should use " "this server instead."); } client_send_tagline(client, str_c(reply)); if (!nologin) { client_destroy_success(client, "Login with referral"); return TRUE; } } else if (nologin) { /* Authentication went ok, but for some reason user isn't allowed to log in. Shouldn't probably happen. */ reply = t_str_new(128); if (reason != NULL) str_printfa(reply, "NO [ALERT] %s", reason); else if (temp) { str_append(reply, "NO ["IMAP_RESP_CODE_UNAVAILABLE"] " AUTH_TEMP_FAILED_MSG); } else if (authz_failure) { str_append(reply, "NO "IMAP_AUTHZ_FAILED_MSG); } else { str_append(reply, "NO "IMAP_AUTH_FAILED_MSG); } client_send_tagline(client, str_c(reply)); } else { /* normal login/failure */ return FALSE; } i_assert(nologin); if (!client->destroyed) client_auth_failed(client, *nodelay_r); return TRUE; } static void sasl_callback(struct client *_client, enum sasl_server_reply reply, const char *data, const char *const *args) { struct imap_client *client = (struct imap_client *)_client; struct const_iovec iov[3]; const char *msg; size_t data_len; bool nodelay; i_assert(!client->destroyed || reply == SASL_SERVER_REPLY_AUTH_ABORTED || reply == SASL_SERVER_REPLY_MASTER_FAILED); switch (reply) { case SASL_SERVER_REPLY_SUCCESS: if (client->to_auth_waiting != NULL) timeout_remove(&client->to_auth_waiting); if (args != NULL) { if (client_handle_args(client, args, TRUE, &nodelay)) break; } client_destroy_success(client, "Login"); break; case SASL_SERVER_REPLY_AUTH_FAILED: case SASL_SERVER_REPLY_AUTH_ABORTED: if (client->to_auth_waiting != NULL) timeout_remove(&client->to_auth_waiting); if (args != NULL) { if (client_handle_args(client, args, FALSE, &nodelay)) break; } if (reply == SASL_SERVER_REPLY_AUTH_ABORTED) { msg = "BAD Authentication aborted by client."; nodelay = TRUE; } else if (data == NULL) msg = "NO "IMAP_AUTH_FAILED_MSG; else msg = t_strconcat("NO [ALERT] ", data, NULL); client_send_tagline(client, msg); if (!client->destroyed) client_auth_failed(client, nodelay); break; case SASL_SERVER_REPLY_MASTER_FAILED: if (data == NULL) client_destroy_internal_failure(client); else { client_send_tagline(client, t_strconcat("NO ", data, NULL)); /* authentication itself succeeded, we just hit some internal failure. */ client_destroy_success(client, data); } break; case SASL_SERVER_REPLY_CONTINUE: data_len = strlen(data); iov[0].iov_base = "+ "; iov[0].iov_len = 2; iov[1].iov_base = data; iov[1].iov_len = data_len; iov[2].iov_base = "\r\n"; iov[2].iov_len = 2; /* don't check return value here. it gets tricky if we try to call client_destroy() in here. */ (void)o_stream_sendv(client->output, iov, 3); if (client->to_auth_waiting != NULL) timeout_remove(&client->to_auth_waiting); i_assert(client->io == NULL); client->io = io_add(client->common.fd, IO_READ, client_auth_input, client); client_auth_input(client); return; } client_unref(client); } static int client_auth_begin(struct imap_client *client, const char *mech_name, const char *init_resp) { client->common.auth_command_tag = client->cmd_tag; client_ref(client); client->auth_initializing = TRUE; sasl_server_auth_begin(&client->common, IMAP_SERVICE_NAME, mech_name, init_resp, sasl_callback); client->auth_initializing = FALSE; if (!client->common.authenticating) return 1; /* don't handle input until we get the initial auth reply */ if (client->io != NULL) io_remove(&client->io); client_set_auth_waiting(client); return 0; } int cmd_authenticate(struct imap_client *client, const struct imap_arg *args) { const char *mech_name, *init_resp = NULL; /* we want only one argument: authentication mechanism name */ if (args[0].type != IMAP_ARG_ATOM && args[0].type != IMAP_ARG_STRING) return -1; if (args[1].type != IMAP_ARG_EOL) { /* optional SASL initial response */ if (args[1].type != IMAP_ARG_ATOM || args[2].type != IMAP_ARG_EOL) return -1; init_resp = IMAP_ARG_STR(&args[1]); } if (!client->common.secured && ssl_required) { if (verbose_auth) { client_syslog(&client->common, "Login failed: " "SSL required for authentication"); } client->common.auth_attempts++; client_send_tagline(client, "NO ["IMAP_RESP_CODE_PRIVACYREQUIRED"] " "Authentication not allowed until SSL/TLS is enabled."); return 1; } mech_name = IMAP_ARG_STR(&args[0]); if (*mech_name == '\0') return -1; return client_auth_begin(client, mech_name, init_resp); } int cmd_login(struct imap_client *client, const struct imap_arg *args) { const char *user, *pass; string_t *plain_login, *base64; /* two arguments: username and password */ if (args[0].type != IMAP_ARG_ATOM && args[0].type != IMAP_ARG_STRING) return -1; if (args[1].type != IMAP_ARG_ATOM && args[1].type != IMAP_ARG_STRING) return -1; if (args[2].type != IMAP_ARG_EOL) return -1; user = IMAP_ARG_STR(&args[0]); pass = IMAP_ARG_STR(&args[1]); if (!client->common.secured && disable_plaintext_auth) { if (verbose_auth) { client_syslog(&client->common, "Login failed: " "Plaintext authentication disabled"); } client->common.auth_tried_disabled_plaintext = TRUE; client->common.auth_attempts++; client_send_line(client, "* BAD [ALERT] Plaintext authentication not allowed " "without SSL/TLS, but your client did it anyway. " "If anyone was listening, the password was exposed."); client_send_tagline(client, "NO ["IMAP_RESP_CODE_CLIENTBUG"] " AUTH_PLAINTEXT_DISABLED_MSG); return 1; } /* authorization ID \0 authentication ID \0 pass */ plain_login = buffer_create_dynamic(pool_datastack_create(), 64); buffer_append_c(plain_login, '\0'); buffer_append(plain_login, user, strlen(user)); buffer_append_c(plain_login, '\0'); buffer_append(plain_login, pass, strlen(pass)); base64 = buffer_create_dynamic(pool_datastack_create(), MAX_BASE64_ENCODED_SIZE(plain_login->used)); base64_encode(plain_login->data, plain_login->used, base64); return client_auth_begin(client, "PLAIN", str_c(base64)); }