From: Timo Sirainen Date: Sun, 9 Jun 2013 03:03:34 +0000 (+0300) Subject: imap/pop3-login: Use libsasl for authenticating to remote IMAP/POP3 server. X-Git-Tag: 2.2.3~36 X-Git-Url: http://git.ipfire.org/?a=commitdiff_plain;h=1093de32efb2a231949566d4bd8aa55a8f43fb70;p=thirdparty%2Fdovecot%2Fcore.git imap/pop3-login: Use libsasl for authenticating to remote IMAP/POP3 server. Also passdb lookup can return "proxy_mech" extra field to specify which SASL mechanism to use. --- diff --git a/src/imap-login/Makefile.am b/src/imap-login/Makefile.am index ec95a05101..5cc5e8c889 100644 --- a/src/imap-login/Makefile.am +++ b/src/imap-login/Makefile.am @@ -6,6 +6,7 @@ AM_CPPFLAGS = \ -I$(top_srcdir)/src/lib \ -I$(top_srcdir)/src/lib-settings \ -I$(top_srcdir)/src/lib-auth \ + -I$(top_srcdir)/src/lib-sasl \ -I$(top_srcdir)/src/lib-imap \ -I$(top_srcdir)/src/lib-master \ -I$(top_srcdir)/src/login-common diff --git a/src/imap-login/client.h b/src/imap-login/client.h index 555d7b77d4..887c0c2177 100644 --- a/src/imap-login/client.h +++ b/src/imap-login/client.h @@ -19,7 +19,6 @@ struct imap_client { unsigned int cmd_finished:1; unsigned int proxy_sasl_ir:1; unsigned int proxy_seen_banner:1; - unsigned int proxy_wait_auth_continue:1; unsigned int skip_line:1; unsigned int id_logged:1; unsigned int client_ignores_capability_resp_code:1; diff --git a/src/imap-login/imap-proxy.c b/src/imap-login/imap-proxy.c index 71ca799238..e283d48a1f 100644 --- a/src/imap-login/imap-proxy.c +++ b/src/imap-login/imap-proxy.c @@ -9,6 +9,7 @@ #include "str.h" #include "str-sanitize.h" #include "safe-memset.h" +#include "sasl-client.h" #include "client.h" #include "client-authenticate.h" #include "imap-resp-code.h" @@ -55,42 +56,55 @@ static void proxy_free_password(struct client *client) i_free_and_null(client->proxy_password); } -static void get_plain_auth(struct client *client, string_t *dest) +static int proxy_write_login(struct imap_client *client, string_t *str) { - string_t *str; - - str = t_str_new(128); - str_append(str, client->proxy_user); - str_append_c(str, '\0'); - str_append(str, client->proxy_master_user); - str_append_c(str, '\0'); - str_append(str, client->proxy_password); - base64_encode(str_data(str), str_len(str), dest); -} + struct sasl_client_settings sasl_set; + const unsigned char *output; + unsigned int len; + const char *mech_name, *error; -static void proxy_write_login(struct imap_client *client, string_t *str) -{ str_append(str, "C CAPABILITY\r\n"); - if (client->common.proxy_master_user == NULL) { + if (client->common.proxy_mech == NULL) { /* logging in normally - use LOGIN command */ str_append(str, "L LOGIN "); imap_append_string(str, client->common.proxy_user); str_append_c(str, ' '); imap_append_string(str, client->common.proxy_password); + str_append(str, "\r\n"); proxy_free_password(&client->common); - } else if (client->proxy_sasl_ir) { - /* master user login with SASL initial response support */ - str_append(str, "L AUTHENTICATE PLAIN "); - get_plain_auth(&client->common, str); - proxy_free_password(&client->common); - } else { - /* master user login without SASL initial response */ - str_append(str, "L AUTHENTICATE PLAIN"); - client->proxy_wait_auth_continue = TRUE; + return 0; + } + + i_assert(client->common.proxy_sasl_client == NULL); + memset(&sasl_set, 0, sizeof(sasl_set)); + sasl_set.authid = client->common.proxy_user; + sasl_set.authzid = client->common.proxy_master_user; + sasl_set.password = client->common.proxy_password; + client->common.proxy_sasl_client = + sasl_client_new(client->common.proxy_mech, &sasl_set); + mech_name = sasl_client_mech_get_name(client->common.proxy_mech); + + str_append(str, "L AUTHENTICATE "); + str_append(str, mech_name); + if (client->proxy_sasl_ir) { + if (sasl_client_output(client->common.proxy_sasl_client, + &output, &len, &error) < 0) { + client_log_err(&client->common, t_strdup_printf( + "proxy: SASL mechanism %s init failed: %s", + mech_name, error)); + return -1; + } + str_append_c(str, ' '); + if (len == 0) + str_append_c(str, '='); + else + base64_encode(output, len, str); } str_append(str, "\r\n"); + proxy_free_password(&client->common); + return 0; } static int proxy_input_banner(struct imap_client *client, @@ -126,7 +140,8 @@ static int proxy_input_banner(struct imap_client *client, } str_append(str, "S STARTTLS\r\n"); } else { - proxy_write_login(client, str); + if (proxy_write_login(client, str) < 0) + return -1; } o_stream_nsend(output, str_data(str), str_len(str)); @@ -173,6 +188,10 @@ int imap_proxy_parse_line(struct client *client, const char *line) struct imap_client *imap_client = (struct imap_client *)client; struct ostream *output; string_t *str; + const unsigned char *data; + unsigned int data_len; + const char *error; + int ret; i_assert(!client->destroyed); @@ -188,17 +207,37 @@ int imap_proxy_parse_line(struct client *client, const char *line) return 0; } else if (*line == '+') { /* AUTHENTICATE started. finish it. */ - if (!imap_client->proxy_wait_auth_continue) { + if (client->proxy_sasl_client == NULL) { /* used literals with LOGIN command, just ignore. */ return 0; } client->proxy_state = IMAP_PROXY_STATE_AUTH_CONTINUE; - imap_client->proxy_wait_auth_continue = FALSE; str = t_str_new(128); - get_plain_auth(client, str); + if (line[1] != ' ' || + base64_decode(line+2, strlen(line+2), NULL, str) < 0) { + client_log_err(client, + "proxy: Server sent invalid base64 data in AUTHENTICATE response"); + client_proxy_failed(client, TRUE); + return -1; + } + ret = sasl_client_input(client->proxy_sasl_client, + str_data(str), str_len(str), &error); + if (ret == 0) { + ret = sasl_client_output(client->proxy_sasl_client, + &data, &data_len, &error); + } + if (ret < 0) { + client_log_err(client, t_strdup_printf( + "proxy: Server sent invalid authentication data: %s", + error)); + client_proxy_failed(client, TRUE); + return -1; + } + + str_truncate(str, 0); + base64_encode(data, data_len, str); str_append(str, "\r\n"); - proxy_free_password(client); o_stream_nsend(output, str_data(str), str_len(str)); return 0; @@ -220,7 +259,10 @@ int imap_proxy_parse_line(struct client *client, const char *line) /* i/ostreams changed. */ output = login_proxy_get_ostream(client->login_proxy); str = t_str_new(128); - proxy_write_login(imap_client, str); + if (proxy_write_login(imap_client, str) < 0) { + client_proxy_failed(client, TRUE); + return -1; + } o_stream_nsend(output, str_data(str), str_len(str)); return 1; } else if (strncmp(line, "L OK ", 5) == 0) { @@ -305,7 +347,6 @@ void imap_proxy_reset(struct client *client) imap_client->proxy_sasl_ir = FALSE; imap_client->proxy_seen_banner = FALSE; - imap_client->proxy_wait_auth_continue = FALSE; client->proxy_state = IMAP_PROXY_STATE_NONE; } diff --git a/src/login-common/Makefile.am b/src/login-common/Makefile.am index 6db44e7c58..402ae742d3 100644 --- a/src/login-common/Makefile.am +++ b/src/login-common/Makefile.am @@ -4,6 +4,7 @@ AM_CPPFLAGS = \ -I$(top_srcdir)/src/lib \ -I$(top_srcdir)/src/lib-settings \ -I$(top_srcdir)/src/lib-auth \ + -I$(top_srcdir)/src/lib-sasl \ -I$(top_srcdir)/src/lib-master \ -I$(top_srcdir)/src/lib-ssl-iostream \ -I$(top_srcdir)/src/lib-mail \ diff --git a/src/login-common/client-common-auth.c b/src/login-common/client-common-auth.c index 99c7f34732..604c1648f3 100644 --- a/src/login-common/client-common-auth.c +++ b/src/login-common/client-common-auth.c @@ -9,6 +9,7 @@ #include "time-util.h" #include "login-proxy.h" #include "auth-client.h" +#include "sasl-client.h" #include "master-service-ssl-settings.h" #include "client-common.h" @@ -104,6 +105,8 @@ static void client_auth_parse_args(struct client *client, reply_r->proxy_timeout_msecs = 1000*atoi(value); else if (strcmp(key, "proxy_refresh") == 0) reply_r->proxy_refresh_secs = atoi(value); + else if (strcmp(key, "proxy_mech") == 0) + reply_r->proxy_mech = value; else if (strcmp(key, "master") == 0) reply_r->master_user = value; else if (strcmp(key, "ssl") == 0) { @@ -198,6 +201,8 @@ void client_proxy_failed(struct client *client, bool send_line) client_proxy_error(client, PROXY_FAILURE_MSG); } + if (client->proxy_sasl_client != NULL) + sasl_client_free(&client->proxy_sasl_client); login_proxy_free(&client->login_proxy); proxy_free_password(client); i_free_and_null(client->proxy_user); @@ -270,10 +275,13 @@ static int proxy_start(struct client *client, const struct client_auth_reply *reply) { struct login_proxy_settings proxy_set; + const struct sasl_client_mech *sasl_mech = NULL; i_assert(reply->destuser != NULL); i_assert(!client->destroyed); + i_assert(client->proxy_sasl_client == NULL); + client->proxy_mech = NULL; client->v.proxy_reset(client); if (reply->password == NULL) { @@ -287,6 +295,20 @@ static int proxy_start(struct client *client, return -1; } + if (reply->proxy_mech != NULL) { + sasl_mech = sasl_client_mech_find(reply->proxy_mech); + if (sasl_mech == NULL) { + client_log_err(client, t_strdup_printf( + "proxy: Unsupported SASL mechanism %s", + reply->proxy_mech)); + client_proxy_error(client, PROXY_FAILURE_MSG); + return -1; + } + } else if (reply->master_user != NULL) { + /* have to use PLAIN authentication with master user logins */ + sasl_mech = &sasl_client_mech_plain; + } + i_assert(client->refcount > 1); if (client->destroyed) { @@ -318,6 +340,7 @@ static int proxy_start(struct client *client, return -1; } + 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); diff --git a/src/login-common/client-common.c b/src/login-common/client-common.c index e117bea268..454b28eae0 100644 --- a/src/login-common/client-common.c +++ b/src/login-common/client-common.c @@ -18,6 +18,7 @@ #include "master-service-ssl-settings.h" #include "master-auth.h" #include "auth-client.h" +#include "sasl-client.h" #include "login-proxy.h" #include "ssl-proxy.h" #include "client-common.h" @@ -209,6 +210,8 @@ void client_destroy(struct client *client, const char *reason) i_free_and_null(client->proxy_password); } + if (client->proxy_sasl_client != NULL) + sasl_client_free(&client->proxy_sasl_client); if (client->login_proxy != NULL) login_proxy_free(&client->login_proxy); if (client->v.destroy != NULL) diff --git a/src/login-common/client-common.h b/src/login-common/client-common.h index 2758808375..a73e9faab8 100644 --- a/src/login-common/client-common.h +++ b/src/login-common/client-common.h @@ -55,7 +55,7 @@ enum client_auth_result { struct client_auth_reply { const char *master_user, *reason; /* for proxying */ - const char *host, *hostip, *destuser, *password; + const char *host, *hostip, *destuser, *password, *proxy_mech; unsigned int port; unsigned int proxy_timeout_msecs; unsigned int proxy_refresh_secs; @@ -122,6 +122,8 @@ struct client { struct login_proxy *login_proxy; char *proxy_user, *proxy_master_user, *proxy_password; + const struct sasl_client_mech *proxy_mech; + struct sasl_client *proxy_sasl_client; unsigned int proxy_state; unsigned int proxy_ttl; diff --git a/src/login-common/main.c b/src/login-common/main.c index 0c95949188..24b83ede37 100644 --- a/src/login-common/main.c +++ b/src/login-common/main.c @@ -13,6 +13,7 @@ #include "access-lookup.h" #include "anvil-client.h" #include "auth-client.h" +#include "sasl-client.h" #include "master-service-ssl-settings.h" #include "ssl-proxy.h" #include "login-proxy.h" @@ -281,6 +282,7 @@ static void main_preinit(bool allow_core_dumps) /* Initialize SSL proxy so it can read certificate and private key file. */ ssl_proxy_init(); + sasl_clients_init(); /* set the number of fds we want to use. it may get increased or decreased. leave a couple of extra fds for auth sockets and such. @@ -356,6 +358,7 @@ static void main_deinit(void) anvil_client_deinit(&anvil); if (auth_client_to != NULL) timeout_remove(&auth_client_to); + sasl_clients_deinit(); login_settings_deinit(); } diff --git a/src/pop3-login/Makefile.am b/src/pop3-login/Makefile.am index 6d2ad73639..a3f831c7d8 100644 --- a/src/pop3-login/Makefile.am +++ b/src/pop3-login/Makefile.am @@ -6,6 +6,7 @@ AM_CPPFLAGS = \ -I$(top_srcdir)/src/lib \ -I$(top_srcdir)/src/lib-settings \ -I$(top_srcdir)/src/lib-auth \ + -I$(top_srcdir)/src/lib-sasl \ -I$(top_srcdir)/src/lib-master \ -I$(top_srcdir)/src/login-common diff --git a/src/pop3-login/pop3-proxy.c b/src/pop3-login/pop3-proxy.c index e0d3f25f5c..dd5c7dc966 100644 --- a/src/pop3-login/pop3-proxy.c +++ b/src/pop3-login/pop3-proxy.c @@ -8,6 +8,7 @@ #include "safe-memset.h" #include "str.h" #include "str-sanitize.h" +#include "sasl-client.h" #include "client.h" #include "pop3-proxy.h" @@ -20,21 +21,12 @@ static void proxy_free_password(struct client *client) i_free_and_null(client->proxy_password); } -static void get_plain_auth(struct client *client, string_t *dest) -{ - string_t *str; - - str = t_str_new(128); - str_append(str, client->proxy_user); - str_append_c(str, '\0'); - str_append(str, client->proxy_master_user); - str_append_c(str, '\0'); - str_append(str, client->proxy_password); - base64_encode(str_data(str), str_len(str), dest); -} - -static void proxy_send_login(struct pop3_client *client, struct ostream *output) +static int proxy_send_login(struct pop3_client *client, struct ostream *output) { + struct sasl_client_settings sasl_set; + const unsigned char *sasl_output; + unsigned int len; + const char *mech_name, *error; string_t *str; i_assert(client->common.proxy_ttl > 1); @@ -52,16 +44,79 @@ static void proxy_send_login(struct pop3_client *client, struct ostream *output) } str = t_str_new(128); - if (client->common.proxy_master_user == NULL) { + if (client->common.proxy_mech == NULL) { /* send USER command */ str_append(str, "USER "); str_append(str, client->common.proxy_user); str_append(str, "\r\n"); - } else { - /* master user login - use AUTH PLAIN. */ - str_append(str, "AUTH PLAIN\r\n"); + o_stream_nsend(output, str_data(str), str_len(str)); + return 0; } + + i_assert(client->common.proxy_sasl_client == NULL); + memset(&sasl_set, 0, sizeof(sasl_set)); + sasl_set.authid = client->common.proxy_user; + sasl_set.authzid = client->common.proxy_master_user; + sasl_set.password = client->common.proxy_password; + client->common.proxy_sasl_client = + sasl_client_new(client->common.proxy_mech, &sasl_set); + mech_name = sasl_client_mech_get_name(client->common.proxy_mech); + + str_printfa(str, "AUTH %s ", mech_name); + if (sasl_client_output(client->common.proxy_sasl_client, + &sasl_output, &len, &error) < 0) { + client_log_err(&client->common, t_strdup_printf( + "proxy: SASL mechanism %s init failed: %s", + mech_name, error)); + return -1; + } + if (len == 0) + str_append_c(str, '='); + else + base64_encode(sasl_output, len, str); + str_append(str, "\r\n"); o_stream_nsend(output, str_data(str), str_len(str)); + + proxy_free_password(&client->common); + if (client->common.proxy_state != POP3_PROXY_XCLIENT) + client->common.proxy_state = POP3_PROXY_LOGIN2; + return 0; +} + +static int +pop3_proxy_continue_sasl_auth(struct client *client, struct ostream *output, + const char *line) +{ + string_t *str; + const unsigned char *data; + unsigned int data_len; + const char *error; + int ret; + + str = t_str_new(128); + if (base64_decode(line, strlen(line), NULL, str) < 0) { + client_log_err(client, "proxy: Server sent invalid base64 data in AUTH response"); + return -1; + } + ret = sasl_client_input(client->proxy_sasl_client, + str_data(str), str_len(str), &error); + if (ret == 0) { + ret = sasl_client_output(client->proxy_sasl_client, + &data, &data_len, &error); + } + if (ret < 0) { + client_log_err(client, t_strdup_printf( + "proxy: Server sent invalid authentication data: %s", + error)); + return -1; + } + + str_truncate(str, 0); + base64_encode(data, data_len, str); + str_append(str, "\r\n"); + + o_stream_nsend(output, str_data(str), str_len(str)); + return 0; } int pop3_proxy_parse_line(struct client *client, const char *line) @@ -69,7 +124,6 @@ int pop3_proxy_parse_line(struct client *client, const char *line) struct pop3_client *pop3_client = (struct pop3_client *)client; struct ostream *output; enum login_proxy_ssl_flags ssl_flags; - string_t *str; i_assert(!client->destroyed); @@ -89,7 +143,10 @@ int pop3_proxy_parse_line(struct client *client, const char *line) ssl_flags = login_proxy_get_ssl_flags(client->login_proxy); if ((ssl_flags & PROXY_SSL_FLAG_STARTTLS) == 0) { - proxy_send_login(pop3_client, output); + if (proxy_send_login(pop3_client, output) < 0) { + client_proxy_failed(client, TRUE); + return -1; + } } else { o_stream_nsend_str(output, "STLS\r\n"); client->proxy_state = POP3_PROXY_STARTTLS; @@ -109,7 +166,10 @@ int pop3_proxy_parse_line(struct client *client, const char *line) } /* i/ostreams changed. */ output = login_proxy_get_ostream(client->login_proxy); - proxy_send_login(pop3_client, output); + if (proxy_send_login(pop3_client, output) < 0) { + client_proxy_failed(client, TRUE); + return -1; + } return 1; case POP3_PROXY_XCLIENT: if (strncmp(line, "+OK", 3) != 0) { @@ -119,30 +179,31 @@ int pop3_proxy_parse_line(struct client *client, const char *line) client_proxy_failed(client, TRUE); return -1; } - client->proxy_state = POP3_PROXY_LOGIN1; + client->proxy_state = client->proxy_sasl_client == NULL ? + POP3_PROXY_LOGIN1 : POP3_PROXY_LOGIN2; return 0; case POP3_PROXY_LOGIN1: - str = t_str_new(128); - if (client->proxy_master_user == NULL) { - if (strncmp(line, "+OK", 3) != 0) - break; - - /* USER successful, send PASS */ - str_append(str, "PASS "); - str_append(str, client->proxy_password); - str_append(str, "\r\n"); - } else { - if (*line != '+') - break; - /* AUTH successful, send the authentication data */ - get_plain_auth(client, str); - str_append(str, "\r\n"); - } - o_stream_nsend(output, str_data(str), str_len(str)); + i_assert(client->proxy_sasl_client == NULL); + if (strncmp(line, "+OK", 3) != 0) + break; + + /* USER successful, send PASS */ + o_stream_nsend_str(output, t_strdup_printf( + "PASS %s\r\n", client->proxy_password)); proxy_free_password(client); client->proxy_state = POP3_PROXY_LOGIN2; return 0; case POP3_PROXY_LOGIN2: + if (strncmp(line, "+ ", 2) == 0 && + client->proxy_sasl_client != NULL) { + /* continue SASL authentication */ + if (pop3_proxy_continue_sasl_auth(client, output, + line+2) < 0) { + client_proxy_failed(client, TRUE); + return -1; + } + return 0; + } if (strncmp(line, "+OK", 3) != 0) break;