# 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.
#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
# 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
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
enum auth_mech mech;
enum auth_protocol protocol;
+ enum auth_client_request_new_flags flags;
};
/* Continue authentication request */
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;
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);
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)
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);
#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"
}
}
+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;
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));
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) {
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;
client->common.fd = -1;
}
+ if (client->common.proxy != NULL)
+ ssl_proxy_free(client->common.proxy);
client_unref(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);
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;
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) {
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)
{
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 */
struct client {
struct ip_addr ip;
+ struct ssl_proxy *proxy;
int fd;
struct io *io;
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);
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__,
{
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;
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)
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();
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;
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;
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)
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;
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");
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
#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);
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);
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),
MEMBER(use_cyrus_sasl) FALSE,
MEMBER(verbose) FALSE,
+ MEMBER(ssl_require_client_cert) FALSE,
MEMBER(count) 1,
MEMBER(process_size) 256,
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;
}
const char *anonymous_username;
int use_cyrus_sasl, verbose;
+ int ssl_require_client_cert;
unsigned int count;
unsigned int process_size;
#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"
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)
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 */
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 */
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;
net_disconnect(client->common.fd);
client->common.fd = -1;
+ if (client->common.proxy != NULL)
+ ssl_proxy_free(client->common.proxy);
client_unref(client);
}