From: Timo Sirainen Date: Mon, 17 May 2004 01:32:16 +0000 (+0300) Subject: Added ssl_require_client_cert auth-specific setting. Hide X-Git-Tag: 1.1.alpha1~4079 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=8222ce68120b51353a3b31d3073b5f845d0e9f53;p=thirdparty%2Fdovecot%2Fcore.git Added ssl_require_client_cert auth-specific setting. Hide ssl_verify_client_cert from default config file as it's automatically set if needed and there's not much point in forcing it. --HG-- branch : HEAD --- diff --git a/dovecot-example.conf b/dovecot-example.conf index 715912f0fa..cbfb3daff5 100644 --- a/dovecot-example.conf +++ b/dovecot-example.conf @@ -37,7 +37,7 @@ # File containing trusted SSL certificate authorities. Usually not needed. #ssl_ca_file = -# Require client to send a valid certificate, otherwise fail the SSL handshake. +# Request client to send a certificate. #ssl_verify_client_cert = no # SSL parameter file. Master process generates this file for login processes. @@ -312,10 +312,9 @@ #umask = 0077 # Drop all privileges before exec()ing the mail process. This is mostly -# meant for debugging, otherwise you don't get core dumps. Note that setting -# this to yes means that log file is opened as the logged in user, which -# might not work. It could also be a small security risk if you use single UID -# for multiple users, as the users could ptrace() each others processes then. +# meant for debugging, otherwise you don't get core dumps. It could be a small +# security risk if you use single UID for multiple users, as the users could +# ptrace() each others processes then. #mail_drop_priv_before_exec = no # Set max. process size in megabytes. Most of the memory goes to mmap()ing @@ -437,6 +436,9 @@ auth default { # Number of authentication processes to create #count = 1 + + # Require a valid SSL client certificate or the authentication fails. + #ssl_require_client_cert = no } # PAM doesn't provide a way to get uid, gid or home directory. If you don't diff --git a/src/auth/auth-client-interface.h b/src/auth/auth-client-interface.h index 1309a759ce..5f9fbb3842 100644 --- a/src/auth/auth-client-interface.h +++ b/src/auth/auth-client-interface.h @@ -22,6 +22,10 @@ enum auth_protocol { AUTH_PROTOCOL_POP3 = 0x02 }; +enum auth_client_request_new_flags { + AUTH_CLIENT_FLAG_SSL_VALID_CLIENT_CERT = 0x01 +}; + enum auth_client_request_type { AUTH_CLIENT_REQUEST_NEW = 1, AUTH_CLIENT_REQUEST_CONTINUE @@ -51,6 +55,7 @@ struct auth_client_request_new { enum auth_mech mech; enum auth_protocol protocol; + enum auth_client_request_new_flags flags; }; /* Continue authentication request */ diff --git a/src/auth/mech.c b/src/auth/mech.c index 621b5169ed..44aad31c5b 100644 --- a/src/auth/mech.c +++ b/src/auth/mech.c @@ -22,6 +22,7 @@ const char *anonymous_username; char username_chars[256]; static int set_use_cyrus_sasl; +static int ssl_require_client_cert; static struct mech_module_list *mech_modules; static struct auth_client_request_reply failure_reply; @@ -75,6 +76,16 @@ void mech_request_new(struct auth_client_connection *conn, return; } + if (ssl_require_client_cert && + (request->flags & AUTH_CLIENT_FLAG_SSL_VALID_CLIENT_CERT) == 0) { + /* we fail without valid certificate */ + if (verbose) + i_info("Client didn't present valid SSL certificate"); + failure_reply.id = request->id; + callback(&failure_reply, NULL, conn); + return; + } + #ifdef USE_CYRUS_SASL2 if (set_use_cyrus_sasl) { auth_request = mech_cyrus_sasl_new(conn, request, callback); @@ -291,6 +302,7 @@ void mech_init(void) if (set_use_cyrus_sasl) mech_cyrus_sasl_init_lib(); #endif + ssl_require_client_cert = getenv("SSL_REQUIRE_CLIENT_CERT") != NULL; } void mech_deinit(void) diff --git a/src/auth/mech.h b/src/auth/mech.h index cb50688cba..fb8e101f23 100644 --- a/src/auth/mech.h +++ b/src/auth/mech.h @@ -43,6 +43,7 @@ extern const char *const *auth_realms; extern const char *default_realm; extern const char *anonymous_username; extern char username_chars[256]; +extern int ssl_require_client_cert; void mech_register_module(struct mech_module *module); void mech_unregister_module(struct mech_module *module); diff --git a/src/imap-login/client-authenticate.c b/src/imap-login/client-authenticate.c index b9033f0dcd..0bb91ca667 100644 --- a/src/imap-login/client-authenticate.c +++ b/src/imap-login/client-authenticate.c @@ -11,6 +11,7 @@ #include "imap-parser.h" #include "auth-client.h" #include "../auth/auth-mech-desc.h" +#include "ssl-proxy.h" #include "client.h" #include "client-authenticate.h" #include "auth-common.h" @@ -160,6 +161,17 @@ static void login_callback(struct auth_request *request, } } +static enum auth_client_request_new_flags +client_get_auth_flags(struct imap_client *client) +{ + enum auth_client_request_new_flags auth_flags = 0; + + if (client->common.proxy != NULL && + ssl_proxy_has_valid_client_cert(client->common.proxy)) + auth_flags |= AUTH_CLIENT_FLAG_SSL_VALID_CLIENT_CERT; + return auth_flags; +} + int cmd_login(struct imap_client *client, struct imap_arg *args) { const char *user, *pass, *error; @@ -193,10 +205,12 @@ int cmd_login(struct imap_client *client, struct imap_arg *args) buffer_append(client->plain_login, pass, strlen(pass)); client_ref(client); + client->common.auth_request = auth_client_request_new(auth_client, AUTH_MECH_PLAIN, - AUTH_PROTOCOL_IMAP, login_callback, - client, &error); + AUTH_PROTOCOL_IMAP, + client_get_auth_flags(client), + login_callback, client, &error); if (client->common.auth_request == NULL) { client_send_tagline(client, t_strconcat( "NO Login failed: ", error, NULL)); @@ -324,6 +338,7 @@ int cmd_authenticate(struct imap_client *client, struct imap_arg *args) client->common.auth_request = auth_client_request_new(auth_client, mech->mech, AUTH_PROTOCOL_IMAP, + client_get_auth_flags(client), authenticate_callback, client, &error); if (client->common.auth_request != NULL) { diff --git a/src/imap-login/client.c b/src/imap-login/client.c index bc1f11e505..6ef5cb1927 100644 --- a/src/imap-login/client.c +++ b/src/imap-login/client.c @@ -124,7 +124,8 @@ static int cmd_starttls(struct imap_client *client) client->common.io = NULL; } - fd_ssl = ssl_proxy_new(client->common.fd, &client->common.ip); + fd_ssl = ssl_proxy_new(client->common.fd, &client->common.ip, + &client->common.proxy); if (fd_ssl != -1) { client->tls = TRUE; client->secured = TRUE; @@ -421,6 +422,8 @@ void client_destroy(struct imap_client *client, const char *reason) client->common.fd = -1; } + if (client->common.proxy != NULL) + ssl_proxy_free(client->common.proxy); client_unref(client); } diff --git a/src/lib-auth/auth-client.h b/src/lib-auth/auth-client.h index 1883206801..830b6654b0 100644 --- a/src/lib-auth/auth-client.h +++ b/src/lib-auth/auth-client.h @@ -31,6 +31,7 @@ void auth_client_connect_missing_servers(struct auth_client *client); struct auth_request * auth_client_request_new(struct auth_client *client, enum auth_mech mech, enum auth_protocol protocol, + enum auth_client_request_new_flags flags, auth_request_callback_t *callback, void *context, const char **error_r); diff --git a/src/lib-auth/auth-server-request.c b/src/lib-auth/auth-server-request.c index bda435efd4..dd9251f6fa 100644 --- a/src/lib-auth/auth-server-request.c +++ b/src/lib-auth/auth-server-request.c @@ -11,7 +11,8 @@ struct auth_request { struct auth_server_connection *conn; enum auth_mech mech; - enum auth_protocol protocol; + enum auth_protocol protocol; + enum auth_client_request_new_flags flags; unsigned int id; @@ -35,6 +36,7 @@ static int auth_server_send_new_request(struct auth_server_connection *conn, auth_request.id = request->id; auth_request.protocol = request->protocol; auth_request.mech = request->mech; + auth_request.flags = request->flags; if (o_stream_send(conn->output, &auth_request, sizeof(auth_request)) < 0) { @@ -177,6 +179,7 @@ void auth_server_requests_remove_all(struct auth_server_connection *conn) struct auth_request * auth_client_request_new(struct auth_client *client, enum auth_mech mech, enum auth_protocol protocol, + enum auth_client_request_new_flags flags, auth_request_callback_t *callback, void *context, const char **error_r) { @@ -191,6 +194,7 @@ auth_client_request_new(struct auth_client *client, request->conn = conn; request->mech = mech; request->protocol = protocol; + request->flags = flags; request->id = ++client->request_id_counter; if (request->id == 0) { /* wrapped - ID 0 not allowed */ diff --git a/src/login-common/client-common.h b/src/login-common/client-common.h index 1d52e7e57f..97b738c0fa 100644 --- a/src/login-common/client-common.h +++ b/src/login-common/client-common.h @@ -6,6 +6,7 @@ struct client { struct ip_addr ip; + struct ssl_proxy *proxy; int fd; struct io *io; diff --git a/src/login-common/main.c b/src/login-common/main.c index 271d9126c5..7375d5a34d 100644 --- a/src/login-common/main.c +++ b/src/login-common/main.c @@ -95,6 +95,8 @@ static void login_accept(void *context __attr_unused__) static void login_accept_ssl(void *context __attr_unused__) { struct ip_addr ip; + struct client *client; + struct ssl_proxy *proxy; int fd, fd_ssl; fd = net_accept(LOGIN_SSL_LISTEN_FD, &ip, NULL); @@ -107,11 +109,13 @@ static void login_accept_ssl(void *context __attr_unused__) if (process_per_connection) main_close_listen(); - fd_ssl = ssl_proxy_new(fd, &ip); + fd_ssl = ssl_proxy_new(fd, &ip, &proxy); if (fd_ssl == -1) net_disconnect(fd); - else - (void)client_create(fd_ssl, &ip, TRUE); + else { + client = client_create(fd_ssl, &ip, TRUE); + client->proxy = proxy; + } } static void auth_connect_notify(struct auth_client *client __attr_unused__, @@ -213,6 +217,8 @@ int main(int argc __attr_unused__, char *argv[], char *envp[]) { const char *name, *group_name; struct ip_addr ip; + struct ssl_proxy *proxy = NULL; + struct client *client; int i, fd = -1, master_fd = -1; is_inetd = getenv("DOVECOT_MASTER") == NULL; @@ -258,7 +264,7 @@ int main(int argc __attr_unused__, char *argv[], char *envp[]) fd = 1; for (i = 1; i < argc; i++) { if (strcmp(argv[i], "--ssl") == 0) { - fd = ssl_proxy_new(fd, &ip); + fd = ssl_proxy_new(fd, &ip, &proxy); if (fd == -1) i_fatal("SSL initialization failed"); } else if (strncmp(argv[i], "--group=", 8) != 0) @@ -269,8 +275,10 @@ int main(int argc __attr_unused__, char *argv[], char *envp[]) closing_down = TRUE; } - if (fd != -1) - (void)client_create(fd, &ip, TRUE); + if (fd != -1) { + client = client_create(fd, &ip, TRUE); + client->proxy = proxy; + } io_loop_run(ioloop); main_deinit(); diff --git a/src/login-common/ssl-proxy-openssl.c b/src/login-common/ssl-proxy-openssl.c index db229ba74a..e416d33c6a 100644 --- a/src/login-common/ssl-proxy-openssl.c +++ b/src/login-common/ssl-proxy-openssl.c @@ -41,8 +41,11 @@ struct ssl_proxy { unsigned int handshaked:1; unsigned int destroyed:1; + unsigned int cert_received:1; + unsigned int cert_broken:1; }; +static int extdata_index; static SSL_CTX *ssl_ctx; static struct hash_table *ssl_proxies; @@ -308,12 +311,14 @@ static void ssl_step(void *context) ssl_proxy_unref(proxy); } -int ssl_proxy_new(int fd, struct ip_addr *ip) +int ssl_proxy_new(int fd, struct ip_addr *ip, struct ssl_proxy **proxy_r) { struct ssl_proxy *proxy; SSL *ssl; int sfd[2]; + *proxy_r = NULL; + if (!ssl_initialized) return -1; @@ -340,26 +345,32 @@ int ssl_proxy_new(int fd, struct ip_addr *ip) net_set_nonblock(fd, TRUE); proxy = i_new(struct ssl_proxy, 1); - proxy->refcount = 1; + proxy->refcount = 2; proxy->ssl = ssl; proxy->fd_ssl = fd; proxy->fd_plain = sfd[0]; proxy->ip = *ip; + SSL_set_ex_data(ssl, extdata_index, proxy); hash_insert(ssl_proxies, proxy, proxy); - proxy->refcount++; ssl_handshake(proxy); - if (!ssl_proxy_unref(proxy)) { - /* handshake failed. return the disconnected socket anyway - so the caller doesn't try to use the old closed fd */ - return sfd[1]; - } - main_ref(); + + *proxy_r = proxy; return sfd[1]; } +int ssl_proxy_has_valid_client_cert(struct ssl_proxy *proxy) +{ + return proxy->cert_received && !proxy->cert_broken; +} + +void ssl_proxy_free(struct ssl_proxy *proxy) +{ + ssl_proxy_unref(proxy); +} + static int ssl_proxy_unref(struct ssl_proxy *proxy) { if (--proxy->refcount > 0) @@ -401,6 +412,22 @@ static RSA *ssl_gen_rsa_key(SSL *ssl __attr_unused__, return RSA_generate_key(keylength, RSA_F4, NULL, NULL); } +static int ssl_verify_client_cert(int preverify_ok, X509_STORE_CTX *ctx) +{ + SSL *ssl; + struct ssl_proxy *proxy; + + ssl = X509_STORE_CTX_get_ex_data(ctx, + SSL_get_ex_data_X509_STORE_CTX_idx()); + proxy = SSL_get_ex_data(ssl, extdata_index); + + proxy->cert_received = TRUE; + if (!preverify_ok) + proxy->cert_broken = TRUE; + + return 1; +} + void ssl_proxy_init(void) { const char *cafile, *certfile, *keyfile, *paramfile, *cipher_list; @@ -419,6 +446,8 @@ void ssl_proxy_init(void) SSL_library_init(); SSL_load_error_strings(); + extdata_index = SSL_get_ex_new_index(0, "dovecot", NULL, NULL, NULL); + if ((ssl_ctx = SSL_CTX_new(SSLv23_server_method())) == NULL) i_fatal("SSL_CTX_new() failed"); @@ -455,8 +484,8 @@ void ssl_proxy_init(void) if (getenv("SSL_VERIFY_CLIENT_CERT") != NULL) { SSL_CTX_set_verify(ssl_ctx, SSL_VERIFY_PEER | - SSL_VERIFY_FAIL_IF_NO_PEER_CERT | - SSL_VERIFY_CLIENT_ONCE, NULL); + SSL_VERIFY_CLIENT_ONCE, + ssl_verify_client_cert); } /* PRNG initialization might want to use /dev/urandom, make sure it diff --git a/src/login-common/ssl-proxy.h b/src/login-common/ssl-proxy.h index 80ca7a5ed5..c5dabdff16 100644 --- a/src/login-common/ssl-proxy.h +++ b/src/login-common/ssl-proxy.h @@ -2,13 +2,16 @@ #define __SSL_PROXY_H struct ip_addr; +struct ssl_proxy; extern int ssl_initialized; /* establish SSL connection with the given fd, returns a new fd which you must use from now on, or -1 if error occured. Unless -1 is returned, the given fd must be simply forgotten. */ -int ssl_proxy_new(int fd, struct ip_addr *ip); +int ssl_proxy_new(int fd, struct ip_addr *ip, struct ssl_proxy **proxy_r); +int ssl_proxy_has_valid_client_cert(struct ssl_proxy *proxy); +void ssl_proxy_free(struct ssl_proxy *proxy); void ssl_proxy_init(void); void ssl_proxy_deinit(void); diff --git a/src/master/auth-process.c b/src/master/auth-process.c index 30fc950f79..3dceaf79c7 100644 --- a/src/master/auth-process.c +++ b/src/master/auth-process.c @@ -335,6 +335,8 @@ static pid_t create_auth_process(struct auth_process_group *group) env_put("USE_CYRUS_SASL=1"); if (group->set->verbose) env_put("VERBOSE=1"); + if (group->set->ssl_require_client_cert) + env_put("SSL_REQUIRE_CLIENT_CERT=1"); restrict_process_size(group->set->process_size, (unsigned int)-1); diff --git a/src/master/master-settings.c b/src/master/master-settings.c index 804cadedb1..601ca3b2f4 100644 --- a/src/master/master-settings.c +++ b/src/master/master-settings.c @@ -132,6 +132,7 @@ static struct setting_def auth_setting_defs[] = { DEF(SET_BOOL, use_cyrus_sasl), DEF(SET_BOOL, verbose), + DEF(SET_BOOL, ssl_require_client_cert), DEF(SET_INT, count), DEF(SET_INT, process_size), @@ -263,6 +264,7 @@ struct auth_settings default_auth_settings = { MEMBER(use_cyrus_sasl) FALSE, MEMBER(verbose) FALSE, + MEMBER(ssl_require_client_cert) FALSE, MEMBER(count) 1, MEMBER(process_size) 256, @@ -334,6 +336,15 @@ static int auth_settings_verify(struct auth_settings *auth) auth->chroot); return FALSE; } + + if (auth->ssl_require_client_cert) { + /* if we require valid cert, make sure we also ask for it */ + if (auth->parent->pop3 != NULL) + auth->parent->pop3->ssl_verify_client_cert = TRUE; + if (auth->parent->imap != NULL) + auth->parent->imap->ssl_verify_client_cert = TRUE; + } + return TRUE; } diff --git a/src/master/master-settings.h b/src/master/master-settings.h index a608aaa235..4cc06ae0bb 100644 --- a/src/master/master-settings.h +++ b/src/master/master-settings.h @@ -109,6 +109,7 @@ struct auth_settings { const char *anonymous_username; int use_cyrus_sasl, verbose; + int ssl_require_client_cert; unsigned int count; unsigned int process_size; diff --git a/src/pop3-login/client-authenticate.c b/src/pop3-login/client-authenticate.c index 7001a56e27..31b5cb5760 100644 --- a/src/pop3-login/client-authenticate.c +++ b/src/pop3-login/client-authenticate.c @@ -11,6 +11,7 @@ #include "auth-client.h" #include "../auth/auth-mech-desc.h" #include "../pop3/capability.h" +#include "ssl-proxy.h" #include "master.h" #include "auth-common.h" #include "client.h" @@ -132,6 +133,17 @@ static void client_send_auth_data(struct pop3_client *client, t_pop(); } +static enum auth_client_request_new_flags +client_get_auth_flags(struct pop3_client *client) +{ + enum auth_client_request_new_flags auth_flags = 0; + + if (client->common.proxy != NULL && + ssl_proxy_has_valid_client_cert(client->common.proxy)) + auth_flags |= AUTH_CLIENT_FLAG_SSL_VALID_CLIENT_CERT; + return auth_flags; +} + static void login_callback(struct auth_request *request, struct auth_client_request_reply *reply, const unsigned char *data, void *context) @@ -196,6 +208,7 @@ int cmd_pass(struct pop3_client *client, const char *args) client->common.auth_request = auth_client_request_new(auth_client, AUTH_MECH_PLAIN, AUTH_PROTOCOL_POP3, + client_get_auth_flags(client), login_callback, client, &error); if (client->common.auth_request != NULL) { /* don't read any input from client until login is finished */ @@ -305,6 +318,7 @@ int cmd_auth(struct pop3_client *client, const char *args) client->common.auth_request = auth_client_request_new(auth_client, mech->mech, AUTH_PROTOCOL_POP3, + client_get_auth_flags(client), authenticate_callback, client, &error); if (client->common.auth_request != NULL) { /* following input data will go to authentication */ diff --git a/src/pop3-login/client.c b/src/pop3-login/client.c index e0b9984578..52f4e5d685 100644 --- a/src/pop3-login/client.c +++ b/src/pop3-login/client.c @@ -80,7 +80,8 @@ static int cmd_stls(struct pop3_client *client) client->common.io = NULL; } - fd_ssl = ssl_proxy_new(client->common.fd, &client->common.ip); + fd_ssl = ssl_proxy_new(client->common.fd, &client->common.ip, + &client->common.proxy); if (fd_ssl != -1) { client->tls = TRUE; client->secured = TRUE; @@ -298,6 +299,8 @@ void client_destroy(struct pop3_client *client, const char *reason) net_disconnect(client->common.fd); client->common.fd = -1; + if (client->common.proxy != NULL) + ssl_proxy_free(client->common.proxy); client_unref(client); }