summaryrefslogtreecommitdiffstats
path: root/src/login-common/client-common-auth.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/login-common/client-common-auth.c')
-rw-r--r--src/login-common/client-common-auth.c953
1 files changed, 953 insertions, 0 deletions
diff --git a/src/login-common/client-common-auth.c b/src/login-common/client-common-auth.c
new file mode 100644
index 0000000..ec5194b
--- /dev/null
+++ b/src/login-common/client-common-auth.c
@@ -0,0 +1,953 @@
+/* Copyright (c) 2002-2018 Dovecot authors, see the included COPYING file */
+
+#include "hostpid.h"
+#include "login-common.h"
+#include "array.h"
+#include "iostream.h"
+#include "istream.h"
+#include "ostream.h"
+#include "str.h"
+#include "safe-memset.h"
+#include "time-util.h"
+#include "settings-parser.h"
+#include "login-proxy.h"
+#include "auth-client.h"
+#include "dsasl-client.h"
+#include "master-service-ssl-settings.h"
+#include "client-common.h"
+
+/* If we've been waiting auth server to respond for over this many milliseconds,
+ send a "waiting" message. */
+#define AUTH_WAITING_TIMEOUT_MSECS (30*1000)
+#define AUTH_WAITING_WARNING_TIMEOUT_MSECS (10*1000)
+
+struct client_auth_fail_code_id {
+ const char *id;
+ enum client_auth_fail_code code;
+};
+
+static const struct client_auth_fail_code_id client_auth_fail_codes[] = {
+ { AUTH_CLIENT_FAIL_CODE_AUTHZFAILED,
+ CLIENT_AUTH_FAIL_CODE_AUTHZFAILED },
+ { AUTH_CLIENT_FAIL_CODE_TEMPFAIL,
+ CLIENT_AUTH_FAIL_CODE_TEMPFAIL },
+ { AUTH_CLIENT_FAIL_CODE_USER_DISABLED,
+ CLIENT_AUTH_FAIL_CODE_USER_DISABLED },
+ { AUTH_CLIENT_FAIL_CODE_PASS_EXPIRED,
+ CLIENT_AUTH_FAIL_CODE_PASS_EXPIRED },
+ { AUTH_CLIENT_FAIL_CODE_INVALID_BASE64,
+ CLIENT_AUTH_FAIL_CODE_INVALID_BASE64 },
+ { AUTH_CLIENT_FAIL_CODE_MECH_INVALID,
+ CLIENT_AUTH_FAIL_CODE_MECH_INVALID },
+ { AUTH_CLIENT_FAIL_CODE_MECH_SSL_REQUIRED,
+ CLIENT_AUTH_FAIL_CODE_MECH_SSL_REQUIRED },
+ { AUTH_CLIENT_FAIL_CODE_ANONYMOUS_DENIED,
+ CLIENT_AUTH_FAIL_CODE_ANONYMOUS_DENIED },
+ { NULL, CLIENT_AUTH_FAIL_CODE_NONE }
+};
+
+static enum client_auth_fail_code
+client_auth_fail_code_lookup(const char *fail_code)
+{
+ const struct client_auth_fail_code_id *fail = client_auth_fail_codes;
+
+ while (fail->id != NULL) {
+ if (strcmp(fail->id, fail_code) == 0)
+ return fail->code;
+ fail++;
+ }
+
+ return CLIENT_AUTH_FAIL_CODE_NONE;
+}
+
+static void client_auth_failed(struct client *client)
+{
+ i_free_and_null(client->master_data_prefix);
+ if (client->auth_response != NULL)
+ str_truncate(client->auth_response, 0);
+
+ if (client->auth_initializing || client->destroyed)
+ return;
+
+ io_remove(&client->io);
+
+ if (!client_does_custom_io(client)) {
+ client->io = io_add_istream(client->input, client_input, client);
+ io_set_pending(client->io);
+ }
+}
+
+static void client_auth_waiting_timeout(struct client *client)
+{
+ if (!client->notified_auth_ready) {
+ e_warning(client->event, "Auth process not responding, "
+ "delayed sending initial response (greeting)");
+ }
+ client_notify_status(client, FALSE, client->master_tag == 0 ?
+ AUTH_SERVER_WAITING_MSG : AUTH_MASTER_WAITING_MSG);
+ timeout_remove(&client->to_auth_waiting);
+}
+
+void client_set_auth_waiting(struct client *client)
+{
+ i_assert(client->to_auth_waiting == NULL);
+ client->to_auth_waiting =
+ timeout_add(!client->notified_auth_ready ?
+ AUTH_WAITING_WARNING_TIMEOUT_MSECS :
+ AUTH_WAITING_TIMEOUT_MSECS,
+ client_auth_waiting_timeout, client);
+}
+
+static void alt_username_set(ARRAY_TYPE(const_string) *alt_usernames, pool_t pool,
+ const char *key, const char *value)
+{
+ char *const *fields;
+ unsigned int i, count;
+
+ fields = array_get(&global_alt_usernames, &count);
+ for (i = 0; i < count; i++) {
+ if (strcmp(fields[i], key) == 0)
+ break;
+ }
+ if (i == count) {
+ char *new_key = i_strdup(key);
+ array_push_back(&global_alt_usernames, &new_key);
+ }
+
+ value = p_strdup(pool, value);
+ if (i < array_count(alt_usernames)) {
+ array_idx_set(alt_usernames, i, &value);
+ return;
+ }
+
+ /* array is NULL-terminated, so if there are unused fields in
+ the middle set them as "" */
+ while (array_count(alt_usernames) < i) {
+ const char *empty_str = "";
+ array_push_back(alt_usernames, &empty_str);
+ }
+ array_push_back(alt_usernames, &value);
+}
+
+static void client_auth_parse_args(struct client *client, bool success,
+ const char *const *args,
+ struct client_auth_reply *reply_r)
+{
+ const char *key, *value, *p, *error;
+ ARRAY_TYPE(const_string) alt_usernames;
+
+ t_array_init(&alt_usernames, 4);
+ i_zero(reply_r);
+ reply_r->proxy_host_immediate_failure_after_secs =
+ LOGIN_PROXY_DEFAULT_HOST_IMMEDIATE_FAILURE_AFTER_SECS;
+
+ 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) {
+ reply_r->nologin = TRUE;
+ reply_r->fail_code = CLIENT_AUTH_FAIL_CODE_LOGIN_DISABLED;
+ } else if (strcmp(key, "proxy") == 0)
+ reply_r->proxy = TRUE;
+ else if (strcmp(key, "reason") == 0)
+ reply_r->reason = value;
+ else if (strcmp(key, "host") == 0)
+ reply_r->host = value;
+ else if (strcmp(key, "hostip") == 0)
+ reply_r->hostip = value;
+ else if (strcmp(key, "source_ip") == 0)
+ reply_r->source_ip = value;
+ else if (strcmp(key, "port") == 0) {
+ if (net_str2port(value, &reply_r->port) < 0) {
+ e_error(client->event,
+ "Auth service returned invalid "
+ "port number: %s", value);
+ }
+ } else if (strcmp(key, "destuser") == 0)
+ reply_r->destuser = value;
+ else if (strcmp(key, "pass") == 0)
+ reply_r->password = value;
+ else if (strcmp(key, "proxy_timeout") == 0) {
+ /* backwards compatibility: plain number is seconds */
+ if (str_to_uint(value, &reply_r->proxy_timeout_msecs) == 0)
+ reply_r->proxy_timeout_msecs *= 1000;
+ else if (settings_get_time_msecs(value,
+ &reply_r->proxy_timeout_msecs, &error) < 0) {
+ e_error(client->event,
+ "BUG: Auth service returned invalid "
+ "proxy_timeout value '%s': %s",
+ value, error);
+ }
+ } else if (strcmp(key, "proxy_host_immediate_failure_after") == 0) {
+ if (settings_get_time(value,
+ &reply_r->proxy_host_immediate_failure_after_secs,
+ &error) < 0) {
+ e_error(client->event,
+ "BUG: Auth service returned invalid "
+ "proxy_host_immediate_failure_after value '%s': %s",
+ value, error);
+ }
+ } else if (strcmp(key, "proxy_refresh") == 0) {
+ if (str_to_uint(value, &reply_r->proxy_refresh_secs) < 0) {
+ e_error(client->event,
+ "BUG: Auth service returned invalid "
+ "proxy_refresh value: %s", value);
+ }
+ } else if (strcmp(key, "proxy_mech") == 0)
+ reply_r->proxy_mech = value;
+ else if (strcmp(key, "proxy_noauth") == 0)
+ reply_r->proxy_noauth = TRUE;
+ else if (strcmp(key, "proxy_nopipelining") == 0)
+ reply_r->proxy_nopipelining = TRUE;
+ else if (strcmp(key, "proxy_not_trusted") == 0)
+ reply_r->proxy_not_trusted = TRUE;
+ else if (strcmp(key, "master") == 0) {
+ /* ignore empty master field */
+ if (*value != '\0')
+ reply_r->master_user = value;
+ } else if (strcmp(key, "ssl") == 0) {
+ reply_r->ssl_flags |= PROXY_SSL_FLAG_YES;
+ if (strcmp(value, "any-cert") == 0)
+ reply_r->ssl_flags |= PROXY_SSL_FLAG_ANY_CERT;
+ if (reply_r->port == 0)
+ reply_r->port = login_binary->default_ssl_port;
+ } else if (strcmp(key, "starttls") == 0) {
+ reply_r->ssl_flags |= PROXY_SSL_FLAG_YES |
+ PROXY_SSL_FLAG_STARTTLS;
+ if (strcmp(value, "any-cert") == 0)
+ reply_r->ssl_flags |= PROXY_SSL_FLAG_ANY_CERT;
+ } else if (strcmp(key, "code") == 0) {
+ if (reply_r->fail_code != CLIENT_AUTH_FAIL_CODE_NONE) {
+ /* code already assigned */
+ } else {
+ reply_r->fail_code = client_auth_fail_code_lookup(value);
+ }
+ } else if (strcmp(key, "user") == 0 ||
+ strcmp(key, "postlogin_socket") == 0) {
+ /* already handled in sasl-server.c */
+ } else if (str_begins(key, "user_")) {
+ if (success) {
+ alt_username_set(&alt_usernames, client->pool,
+ key, value);
+ }
+ } else if (str_begins(key, "forward_")) {
+ /* these are passed to upstream */
+ } else if (str_begins(key, "event_")) {
+ /* add name to event */
+ event_add_str(client->event, key + 6, value);
+ } else if (strcmp(key, "resp") == 0) {
+ /* ignore final response */
+ continue;
+ } else
+ e_debug(event_auth, "Ignoring unknown passdb extra field: %s", key);
+ }
+ if (array_count(&alt_usernames) > 0) {
+ const char **alt;
+
+ alt = p_new(client->pool, const char *,
+ array_count(&alt_usernames) + 1);
+ memcpy(alt, array_front(&alt_usernames),
+ sizeof(*alt) * array_count(&alt_usernames));
+ client->alt_usernames = alt;
+ }
+ if (reply_r->port == 0)
+ reply_r->port = login_binary->default_port;
+
+ if (reply_r->destuser == NULL)
+ reply_r->destuser = client->virtual_user;
+}
+
+static void proxy_free_password(struct client *client)
+{
+ if (client->proxy_password == NULL)
+ return;
+
+ safe_memset(client->proxy_password, 0, strlen(client->proxy_password));
+ i_free_and_null(client->proxy_password);
+}
+
+static void client_proxy_append_conn_info(string_t *str, struct client *client)
+{
+ const char *source_host;
+
+ source_host = login_proxy_get_source_host(client->login_proxy);
+ if (source_host[0] != '\0')
+ str_printfa(str, " from %s", source_host);
+ if (strcmp(client->virtual_user, client->proxy_user) != 0) {
+ /* remote username is different, log it */
+ str_printfa(str, " as user %s", client->proxy_user);
+ }
+ if (client->proxy_master_user != NULL)
+ str_printfa(str, " (master %s)", client->proxy_master_user);
+}
+
+void client_proxy_finish_destroy_client(struct client *client)
+{
+ string_t *str = t_str_new(128);
+
+ if (client->input->closed) {
+ /* input stream got closed in client_send_raw_data().
+ In most places we don't have to check for this explicitly,
+ but login_proxy_detach() attempts to get and use the
+ istream's fd, which is now -1. */
+ client_destroy_iostream_error(client);
+ return;
+ }
+
+ /* Include hostname in the log message in case it's different from the
+ IP address in the prefix. */
+ const char *ip_str = login_proxy_get_ip_str(client->login_proxy);
+ const char *host = login_proxy_get_host(client->login_proxy);
+ str_printfa(str, "Started proxying to <%s>",
+ login_proxy_get_ip_str(client->login_proxy));
+ if (strcmp(ip_str, host) != 0)
+ str_printfa(str, " (<%s>)", host);
+
+ client_proxy_append_conn_info(str, client);
+
+ struct event *proxy_event = login_proxy_get_event(client->login_proxy);
+ login_proxy_append_success_log_info(client->login_proxy, str);
+ struct event_passthrough *e = event_create_passthrough(proxy_event)->
+ set_name("proxy_session_established");
+ e_info(e->event(), "%s", str_c(str));
+ login_proxy_detach(client->login_proxy);
+ client_destroy_success(client, NULL);
+}
+
+const char *client_proxy_get_state(struct client *client)
+{
+ return client->v.proxy_get_state(client);
+}
+
+void client_proxy_log_failure(struct client *client, const char *line)
+{
+ string_t *str = t_str_new(128);
+
+ str_printfa(str, "Login failed");
+ client_proxy_append_conn_info(str, client);
+ str_append(str, ": ");
+ str_append(str, line);
+ e_info(login_proxy_get_event(client->login_proxy), "%s", str_c(str));
+}
+
+static void client_proxy_failed(struct client *client)
+{
+ login_proxy_free(&client->login_proxy);
+ proxy_free_password(client);
+ i_free_and_null(client->proxy_user);
+ i_free_and_null(client->proxy_master_user);
+
+ client_auth_failed(client);
+}
+
+static void proxy_input(struct client *client)
+{
+ struct istream *input;
+ struct ostream *output;
+ const char *line;
+ unsigned int duration;
+
+ input = login_proxy_get_istream(client->login_proxy);
+ switch (i_stream_read(input)) {
+ case -2:
+ login_proxy_failed(client->login_proxy,
+ login_proxy_get_event(client->login_proxy),
+ LOGIN_PROXY_FAILURE_TYPE_PROTOCOL,
+ "Too long input line");
+ return;
+ case -1:
+ line = i_stream_next_line(input);
+ duration = ioloop_time - client->created.tv_sec;
+ const char *reason = t_strdup_printf(
+ "Disconnected by server: %s "
+ "(state=%s, duration=%us)%s",
+ io_stream_get_disconnect_reason(input, NULL),
+ client_proxy_get_state(client), duration,
+ line == NULL ? "" : t_strdup_printf(
+ " - BUG: line not read: %s", line));
+ login_proxy_failed(client->login_proxy,
+ login_proxy_get_event(client->login_proxy),
+ LOGIN_PROXY_FAILURE_TYPE_CONNECT, reason);
+ return;
+ }
+
+ output = client->output;
+ /* The "line" variable is allocated from the istream, but the istream
+ may be freed by proxy_parse_line(). Keep the istream referenced to
+ make sure the line isn't freed too early. */
+ i_stream_ref(input);
+ o_stream_ref(output);
+ o_stream_cork(output);
+ while ((line = i_stream_next_line(input)) != NULL) {
+ if (client->v.proxy_parse_line(client, line) != 0)
+ break;
+ }
+ o_stream_uncork(output);
+ o_stream_unref(&output);
+ i_stream_unref(&input);
+}
+
+void client_common_proxy_failed(struct client *client,
+ enum login_proxy_failure_type type,
+ const char *reason ATTR_UNUSED,
+ bool reconnecting)
+{
+ dsasl_client_free(&client->proxy_sasl_client);
+ if (reconnecting) {
+ client->v.proxy_reset(client);
+ return;
+ }
+
+ switch (type) {
+ case LOGIN_PROXY_FAILURE_TYPE_CONNECT:
+ case LOGIN_PROXY_FAILURE_TYPE_INTERNAL:
+ case LOGIN_PROXY_FAILURE_TYPE_INTERNAL_CONFIG:
+ case LOGIN_PROXY_FAILURE_TYPE_REMOTE:
+ case LOGIN_PROXY_FAILURE_TYPE_REMOTE_CONFIG:
+ case LOGIN_PROXY_FAILURE_TYPE_PROTOCOL:
+ break;
+ case LOGIN_PROXY_FAILURE_TYPE_AUTH:
+ case LOGIN_PROXY_FAILURE_TYPE_AUTH_TEMPFAIL:
+ client->proxy_auth_failed = TRUE;
+ break;
+ }
+ client_proxy_failed(client);
+}
+
+static bool
+proxy_check_start(struct client *client, struct event *event,
+ const struct client_auth_reply *reply,
+ const struct dsasl_client_mech **sasl_mech_r,
+ struct ip_addr *ip_r)
+{
+ if (reply->password == NULL) {
+ e_error(event, "password not given");
+ return FALSE;
+ }
+ if (reply->host == NULL || *reply->host == '\0') {
+ e_error(event, "host not given");
+ return FALSE;
+ }
+
+ if (reply->hostip != NULL && reply->hostip[0] != '\0') {
+ if (net_addr2ip(reply->hostip, ip_r) < 0) {
+ e_error(event, "Invalid hostip %s", reply->hostip);
+ return FALSE;
+ }
+ } else if (net_addr2ip(reply->host, ip_r) < 0) {
+ e_error(event,
+ "BUG: host %s is not an IP (auth should have changed it)",
+ reply->host);
+ return FALSE;
+ }
+
+ if (reply->proxy_mech != NULL) {
+ *sasl_mech_r = dsasl_client_mech_find(reply->proxy_mech);
+ if (*sasl_mech_r == NULL) {
+ e_error(event, "Unsupported SASL mechanism %s",
+ reply->proxy_mech);
+ return FALSE;
+ }
+ } else if (reply->master_user != NULL) {
+ /* have to use PLAIN authentication with master user logins */
+ *sasl_mech_r = &dsasl_client_mech_plain;
+ }
+
+ if (login_proxy_is_ourself(client, reply->host, reply->port,
+ reply->destuser)) {
+ e_error(event, "Proxying loops to itself");
+ return FALSE;
+ }
+ return TRUE;
+}
+
+static int proxy_start(struct client *client,
+ const struct client_auth_reply *reply)
+{
+ struct login_proxy_settings proxy_set;
+ const struct dsasl_client_mech *sasl_mech = NULL;
+ struct ip_addr ip;
+ struct event *event;
+
+ i_assert(reply->destuser != NULL);
+ i_assert(client->refcount > 1);
+ i_assert(!client->destroyed);
+ i_assert(client->proxy_sasl_client == NULL);
+
+ client->proxy_mech = NULL;
+ client->v.proxy_reset(client);
+ event = event_create(client->event);
+ event_set_append_log_prefix(event, t_strdup_printf(
+ "proxy(%s): ", client->virtual_user));
+
+ if (!proxy_check_start(client, event, reply, &sasl_mech, &ip)) {
+ client->v.proxy_failed(client,
+ LOGIN_PROXY_FAILURE_TYPE_INTERNAL,
+ LOGIN_PROXY_FAILURE_MSG, FALSE);
+ event_unref(&event);
+ return -1;
+ }
+
+ i_zero(&proxy_set);
+ proxy_set.host = reply->host;
+ proxy_set.ip = ip;
+ if (reply->source_ip != NULL) {
+ if (net_addr2ip(reply->source_ip, &proxy_set.source_ip) < 0)
+ proxy_set.source_ip.family = 0;
+ } else if (login_source_ips_count > 0) {
+ /* select the next source IP with round robin. */
+ proxy_set.source_ip = login_source_ips[login_source_ips_idx];
+ login_source_ips_idx =
+ (login_source_ips_idx + 1) % login_source_ips_count;
+ }
+ proxy_set.port = reply->port;
+ proxy_set.connect_timeout_msecs = reply->proxy_timeout_msecs;
+ if (proxy_set.connect_timeout_msecs == 0)
+ proxy_set.connect_timeout_msecs = client->set->login_proxy_timeout;
+ proxy_set.notify_refresh_secs = reply->proxy_refresh_secs;
+ proxy_set.ssl_flags = reply->ssl_flags;
+ proxy_set.host_immediate_failure_after_secs =
+ reply->proxy_host_immediate_failure_after_secs;
+ proxy_set.rawlog_dir = client->set->login_proxy_rawlog_dir;
+
+ /* Include destination ip:port also in the log prefix */
+ event_set_append_log_prefix(event, t_strdup_printf(
+ "proxy(%s,%s:%u): ", client->virtual_user,
+ net_ip2addr(&proxy_set.ip), proxy_set.port));
+
+ client->proxy_mech = sasl_mech;
+ client->proxy_user = i_strdup(reply->destuser);
+ client->proxy_master_user = i_strdup(reply->master_user);
+ client->proxy_password = i_strdup(reply->password);
+ client->proxy_noauth = reply->proxy_noauth;
+ client->proxy_nopipelining = reply->proxy_nopipelining;
+ client->proxy_not_trusted = reply->proxy_not_trusted;
+
+ if (login_proxy_new(client, event, &proxy_set, proxy_input,
+ client->v.proxy_failed) < 0) {
+ event_unref(&event);
+ return -1;
+ }
+ event_unref(&event);
+
+ /* disable input until authentication is finished */
+ io_remove(&client->io);
+ return 0;
+}
+
+static void ATTR_NULL(3, 4)
+client_auth_result(struct client *client, enum client_auth_result result,
+ const struct client_auth_reply *reply, const char *text)
+{
+ o_stream_cork(client->output);
+ client->v.auth_result(client, result, reply, text);
+ o_stream_uncork(client->output);
+}
+
+static bool
+client_auth_handle_reply(struct client *client,
+ const struct client_auth_reply *reply, bool success)
+{
+ if (reply->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 (proxy_start(client, reply) < 0)
+ client_auth_failed(client);
+ else {
+ /* this for plugins being able th hook into auth reply
+ when proxying is used */
+ client_auth_result(client, CLIENT_AUTH_RESULT_SUCCESS,
+ reply, NULL);
+ }
+ return TRUE;
+ }
+
+ if (reply->host != NULL) {
+ const char *reason;
+
+ if (reply->reason != NULL)
+ reason = reply->reason;
+ else if (reply->nologin)
+ reason = "Try this server instead.";
+ else
+ reason = "Logged in, but you should use this server instead.";
+
+ if (reply->nologin) {
+ client_auth_result(client,
+ CLIENT_AUTH_RESULT_REFERRAL_NOLOGIN,
+ reply, reason);
+ } else {
+ client_auth_result(client,
+ CLIENT_AUTH_RESULT_REFERRAL_SUCCESS,
+ reply, reason);
+ return TRUE;
+ }
+ } else if (reply->nologin) {
+ enum client_auth_result result = CLIENT_AUTH_RESULT_AUTHFAILED;
+ const char *timestamp, *reason = reply->reason;
+
+ /* Either failed or user login is disabled */
+ switch (reply->fail_code) {
+ case CLIENT_AUTH_FAIL_CODE_AUTHZFAILED:
+ result = CLIENT_AUTH_RESULT_AUTHZFAILED;
+ if (reason == NULL)
+ reason = "Authorization failed";
+ break;
+ case CLIENT_AUTH_FAIL_CODE_TEMPFAIL:
+ result = CLIENT_AUTH_RESULT_TEMPFAIL;
+ timestamp = t_strflocaltime("%Y-%m-%d %H:%M:%S", ioloop_time);
+ reason = t_strdup_printf(AUTH_TEMP_FAILED_MSG" [%s:%s]",
+ my_hostname, timestamp);
+ break;
+ case CLIENT_AUTH_FAIL_CODE_PASS_EXPIRED:
+ result = CLIENT_AUTH_RESULT_PASS_EXPIRED;
+ break;
+ case CLIENT_AUTH_FAIL_CODE_INVALID_BASE64:
+ result = CLIENT_AUTH_RESULT_INVALID_BASE64;
+ break;
+ case CLIENT_AUTH_FAIL_CODE_MECH_INVALID:
+ result = CLIENT_AUTH_RESULT_MECH_INVALID;
+ break;
+ case CLIENT_AUTH_FAIL_CODE_MECH_SSL_REQUIRED:
+ result = CLIENT_AUTH_RESULT_MECH_SSL_REQUIRED;
+ break;
+ case CLIENT_AUTH_FAIL_CODE_ANONYMOUS_DENIED:
+ result = CLIENT_AUTH_RESULT_ANONYMOUS_DENIED;
+ break;
+ case CLIENT_AUTH_FAIL_CODE_LOGIN_DISABLED:
+ result = CLIENT_AUTH_RESULT_LOGIN_DISABLED;
+ if (reason == NULL)
+ reason = "Login disabled for this user";
+ break;
+ case CLIENT_AUTH_FAIL_CODE_USER_DISABLED:
+ default:
+ if (reason != NULL)
+ result = CLIENT_AUTH_RESULT_AUTHFAILED_REASON;
+ else
+ result = CLIENT_AUTH_RESULT_AUTHFAILED;
+ }
+
+ if (reason == NULL)
+ reason = AUTH_FAILED_MSG;
+ client_auth_result(client, result, reply, reason);
+ } else {
+ /* normal login/failure */
+ return FALSE;
+ }
+
+ i_assert(reply->nologin);
+
+ if (!client->destroyed)
+ client_auth_failed(client);
+ return TRUE;
+}
+
+void client_auth_respond(struct client *client, const char *response)
+{
+ client->auth_waiting = FALSE;
+ client_set_auth_waiting(client);
+ auth_client_request_continue(client->auth_request, response);
+ if (!client_does_custom_io(client))
+ io_remove(&client->io);
+}
+
+void client_auth_abort(struct client *client)
+{
+ sasl_server_auth_abort(client);
+}
+
+void client_auth_fail(struct client *client, const char *text)
+{
+ sasl_server_auth_failed(client, text, NULL);
+}
+
+int client_auth_read_line(struct client *client)
+{
+ const unsigned char *data;
+ size_t i, size, len;
+
+ if (i_stream_read_more(client->input, &data, &size) == -1) {
+ client_destroy_iostream_error(client);
+ return -1;
+ }
+
+ /* see if we have a full line */
+ for (i = 0; i < size; i++) {
+ if (data[i] == '\n')
+ break;
+ }
+ if (client->auth_response == NULL)
+ client->auth_response = str_new(default_pool, I_MAX(i+1, 256));
+ if (str_len(client->auth_response) + i > LOGIN_MAX_AUTH_BUF_SIZE) {
+ client_destroy(client, "Authentication response too large");
+ return -1;
+ }
+ str_append_data(client->auth_response, data, i);
+ i_stream_skip(client->input, i == size ? size : i+1);
+
+ /* drop trailing \r */
+ len = str_len(client->auth_response);
+ if (len > 0 && str_c(client->auth_response)[len-1] == '\r')
+ str_truncate(client->auth_response, len-1);
+
+ return i < size ? 1 : 0;
+}
+
+void client_auth_parse_response(struct client *client)
+{
+ if (client_auth_read_line(client) <= 0)
+ return;
+
+ /* This has to happen before * handling, otherwise
+ client can abort failed request. */
+ if (client->final_response) {
+ sasl_server_auth_delayed_final(client);
+ return;
+ }
+
+ if (strcmp(str_c(client->auth_response), "*") == 0) {
+ sasl_server_auth_abort(client);
+ return;
+ }
+
+ client_auth_respond(client, str_c(client->auth_response));
+ memset(str_c_modifiable(client->auth_response), 0,
+ str_len(client->auth_response));
+}
+
+static void client_auth_input(struct client *client)
+{
+ i_assert(client->v.auth_parse_response != NULL);
+ client->v.auth_parse_response(client);
+}
+
+void client_auth_send_challenge(struct client *client, const char *data)
+{
+ struct const_iovec iov[3];
+
+ iov[0].iov_base = "+ ";
+ iov[0].iov_len = 2;
+ iov[1].iov_base = data;
+ iov[1].iov_len = strlen(data);
+ iov[2].iov_base = "\r\n";
+ iov[2].iov_len = 2;
+
+ o_stream_nsendv(client->output, iov, 3);
+}
+
+static void
+sasl_callback(struct client *client, enum sasl_server_reply sasl_reply,
+ const char *data, const char *const *args)
+{
+ struct client_auth_reply reply;
+
+ i_assert(!client->destroyed ||
+ sasl_reply == SASL_SERVER_REPLY_AUTH_ABORTED ||
+ sasl_reply == SASL_SERVER_REPLY_MASTER_FAILED);
+
+ client->last_auth_fail = CLIENT_AUTH_FAIL_CODE_NONE;
+ i_zero(&reply);
+ switch (sasl_reply) {
+ case SASL_SERVER_REPLY_SUCCESS:
+ timeout_remove(&client->to_auth_waiting);
+ if (args != NULL) {
+ client_auth_parse_args(client, TRUE, args, &reply);
+ reply.all_fields = args;
+ client->last_auth_fail = reply.fail_code;
+ if (client_auth_handle_reply(client, &reply, TRUE))
+ break;
+ }
+ client_auth_result(client, CLIENT_AUTH_RESULT_SUCCESS,
+ &reply, NULL);
+ client_destroy_success(client, "Login");
+ break;
+ case SASL_SERVER_REPLY_AUTH_FAILED:
+ case SASL_SERVER_REPLY_AUTH_ABORTED:
+ timeout_remove(&client->to_auth_waiting);
+ if (args != NULL) {
+ client_auth_parse_args(client, FALSE, args, &reply);
+ if (reply.reason == NULL)
+ reply.reason = data;
+ client->last_auth_fail = reply.fail_code;
+ reply.nologin = TRUE;
+ reply.all_fields = args;
+ if (client_auth_handle_reply(client, &reply, FALSE))
+ break;
+ }
+
+ if (sasl_reply == SASL_SERVER_REPLY_AUTH_ABORTED) {
+ client_auth_result(client, CLIENT_AUTH_RESULT_ABORTED,
+ &reply, "Authentication aborted by client.");
+ } else if (data == NULL) {
+ client_auth_result(client,
+ CLIENT_AUTH_RESULT_AUTHFAILED, &reply,
+ AUTH_FAILED_MSG);
+ } else {
+ client_auth_result(client,
+ CLIENT_AUTH_RESULT_AUTHFAILED_REASON, &reply,
+ data);
+ }
+
+ if (!client->destroyed)
+ client_auth_failed(client);
+ break;
+ case SASL_SERVER_REPLY_MASTER_FAILED:
+ if (data != NULL) {
+ /* authentication itself succeeded, we just hit some
+ internal failure. */
+ client_auth_result(client, CLIENT_AUTH_RESULT_TEMPFAIL,
+ &reply, data);
+ }
+
+ /* the fd may still be hanging somewhere in kernel or another
+ process. make sure the client gets disconnected. */
+ if (shutdown(client->fd, SHUT_RDWR) < 0 && errno != ENOTCONN)
+ e_error(client->event, "shutdown() failed: %m");
+
+ if (data != NULL) {
+ /* e.g. mail_max_userip_connections is reached */
+ } else {
+ /* The error should have been logged already.
+ The client will only see a generic internal error. */
+ client_notify_disconnect(client, CLIENT_DISCONNECT_INTERNAL_ERROR,
+ "Internal login failure. "
+ "Refer to server log for more information.");
+ data = t_strdup_printf("Internal login failure (pid=%s id=%u)",
+ my_pid, client->master_auth_id);
+ }
+ client->no_extra_disconnect_reason = TRUE;
+ client_destroy(client, data);
+ break;
+ case SASL_SERVER_REPLY_CONTINUE:
+ i_assert(client->v.auth_send_challenge != NULL);
+ client->v.auth_send_challenge(client, data);
+
+ timeout_remove(&client->to_auth_waiting);
+
+ if (client->auth_response != NULL)
+ str_truncate(client->auth_response, 0);
+
+ i_assert(client->io == NULL);
+ client->auth_waiting = TRUE;
+ if (!client_does_custom_io(client)) {
+ client->io = io_add_istream(client->input,
+ client_auth_input, client);
+ client_auth_input(client);
+ }
+ return;
+ }
+
+ client_unref(&client);
+}
+
+static int
+client_auth_begin_common(struct client *client, const char *mech_name,
+ enum sasl_server_auth_flags auth_flags,
+ const char *init_resp)
+{
+ if (!client->secured && strcmp(client->ssl_set->ssl, "required") == 0) {
+ if (client->set->auth_verbose) {
+ e_info(client->event, "Login failed: "
+ "SSL required for authentication");
+ }
+ client->auth_attempts++;
+ client_auth_result(client, CLIENT_AUTH_RESULT_SSL_REQUIRED, NULL,
+ "Authentication not allowed until SSL/TLS is enabled.");
+ return 1;
+ }
+
+
+ client_ref(client);
+ client->auth_initializing = TRUE;
+ sasl_server_auth_begin(client, login_binary->protocol, mech_name,
+ auth_flags, init_resp, sasl_callback);
+ client->auth_initializing = FALSE;
+ if (!client->authenticating)
+ return 1;
+
+ /* don't handle input until we get the initial auth reply */
+ io_remove(&client->io);
+ client_set_auth_waiting(client);
+ return 0;
+}
+
+int client_auth_begin(struct client *client, const char *mech_name,
+ const char *init_resp)
+{
+ return client_auth_begin_common(client, mech_name, 0, init_resp);
+}
+
+int client_auth_begin_private(struct client *client, const char *mech_name,
+ const char *init_resp)
+{
+ return client_auth_begin_common(client, mech_name,
+ SASL_SERVER_AUTH_FLAG_PRIVATE,
+ init_resp);
+}
+
+int client_auth_begin_implicit(struct client *client, const char *mech_name,
+ const char *init_resp)
+{
+ return client_auth_begin_common(client, mech_name,
+ SASL_SERVER_AUTH_FLAG_IMPLICIT,
+ init_resp);
+}
+
+bool client_check_plaintext_auth(struct client *client, bool pass_sent)
+{
+ bool ssl_required = (strcmp(client->ssl_set->ssl, "required") == 0);
+
+ if (client->secured || (!client->set->disable_plaintext_auth &&
+ !ssl_required))
+ return TRUE;
+
+ if (client->set->auth_verbose) {
+ e_info(client->event, "Login failed: "
+ "Plaintext authentication disabled");
+ }
+ if (pass_sent) {
+ client_notify_status(client, TRUE,
+ "Plaintext authentication not allowed "
+ "without SSL/TLS, but your client did it anyway. "
+ "If anyone was listening, the password was exposed.");
+ }
+
+ if (ssl_required) {
+ client_auth_result(client, CLIENT_AUTH_RESULT_SSL_REQUIRED, NULL,
+ AUTH_PLAINTEXT_DISABLED_MSG);
+ } else {
+ client_auth_result(client, CLIENT_AUTH_RESULT_MECH_SSL_REQUIRED, NULL,
+ AUTH_PLAINTEXT_DISABLED_MSG);
+ }
+ client->auth_attempts++;
+ return FALSE;
+}
+
+void clients_notify_auth_connected(void)
+{
+ struct client *client, *next;
+
+ for (client = clients; client != NULL; client = next) {
+ next = client->next;
+
+ timeout_remove(&client->to_auth_waiting);
+
+ client_notify_auth_ready(client);
+
+ if (!client_does_custom_io(client) && client->input_blocked) {
+ client->input_blocked = FALSE;
+ io_set_pending(client->io);
+ }
+ }
+}