[dovecot-cvs] dovecot/src/login-common .cvsignore,NONE,1.1 Makefile.am,NONE,1.1 auth-connection.c,NONE,1.1 auth-connection.h,NONE,1.1 client-common.h,NONE,1.1 common.h,NONE,1.1 main.c,NONE,1.1 master.c,NONE,1.1 master.h,NONE,1.1 ssl-proxy-gnutls.c,NONE,1.1 Message-Id: <20030128213528.5C5EA238C5@danu.procontrol.fi>
cras at procontrol.fi
cras at procontrol.fi
Tue Jan 28 23:35:28 EET 2003
- Previous message: [dovecot-cvs] dovecot/src/login .cvsignore,1.1.1.1,NONE Makefile.am,1.4,NONE auth-connection.c,1.23,NONE auth-connection.h,1.10,NONE client-authenticate.c,1.35,NONE client-authenticate.h,1.3,NONE client.c,1.30,NONE client.h,1.15,NONE common.h,1.7,NONE Message-Id: <20030128213528.3A85D238CB@danu.procontrol.fi>
- Next message: [dovecot-cvs] dovecot/src/imap .psrc,1.1.1.1,NONE
- Messages sorted by:
[ date ]
[ thread ]
[ subject ]
[ author ]
Update of /home/cvs/dovecot/src/login-common
In directory danu:/tmp/cvs-serv28480/src/login-common
Added Files:
.cvsignore Makefile.am auth-connection.c auth-connection.h
client-common.h common.h main.c master.c master.h
ssl-proxy-gnutls.c ssl-proxy-openssl.c ssl-proxy.c ssl-proxy.h
Log Message:
Moved common login process code to login-common, created pop3-login.
--- NEW FILE: .cvsignore ---
*.la
*.lo
*.o
.deps
.libs
Makefile
Makefile.in
so_locations
--- NEW FILE: Makefile.am ---
noinst_LIBRARIES = liblogin-common.a
INCLUDES = \
-I$(top_srcdir)/src/lib
liblogin_common_a_SOURCES = \
auth-connection.c \
main.c \
master.c \
ssl-proxy.c \
ssl-proxy-gnutls.c \
ssl-proxy-openssl.c
noinst_HEADERS = \
auth-connection.h \
common.h \
client-common.h \
master.h \
ssl-proxy.h
--- NEW FILE: auth-connection.c ---
/* Copyright (C) 2002 Timo Sirainen */
#include "common.h"
#include "hash.h"
#include "ioloop.h"
#include "network.h"
#include "istream.h"
#include "ostream.h"
#include "auth-connection.h"
#include <unistd.h>
#include <dirent.h>
#include <sys/stat.h>
/* Maximum size for an auth reply. 50kB should be more than enough. */
#define MAX_INBUF_SIZE (1024*50)
#define MAX_OUTBUF_SIZE \
(sizeof(struct auth_login_request_continue) + \
AUTH_LOGIN_MAX_REQUEST_DATA_SIZE)
enum auth_mech available_auth_mechs;
static int auth_reconnect;
static unsigned int request_id_counter;
static struct auth_connection *auth_connections;
static struct timeout *to;
static void auth_connection_destroy(struct auth_connection *conn);
static void auth_input(void *context);
static void auth_connect_missing(void);
static struct auth_connection *auth_connection_find(const char *path)
{
struct auth_connection *conn;
for (conn = auth_connections; conn != NULL; conn = conn->next) {
if (strcmp(conn->path, path) == 0)
return conn;
}
return NULL;
}
static struct auth_connection *auth_connection_new(const char *path)
{
struct auth_connection *conn;
struct auth_login_handshake_input handshake;
int fd;
fd = net_connect_unix(path);
if (fd == -1) {
i_error("Can't connect to imap-auth at %s: %m", path);
auth_reconnect = TRUE;
return NULL;
}
/* we depend on auth process - if it's slow, just wait */
net_set_nonblock(fd, FALSE);
conn = i_new(struct auth_connection, 1);
conn->path = i_strdup(path);
conn->fd = fd;
conn->io = io_add(fd, IO_READ, auth_input, conn);
conn->input = i_stream_create_file(fd, default_pool, MAX_INBUF_SIZE,
FALSE);
conn->output = o_stream_create_file(fd, default_pool, MAX_OUTBUF_SIZE,
IO_PRIORITY_DEFAULT, FALSE);
conn->requests = hash_create(default_pool, default_pool, 100,
NULL, NULL);
conn->next = auth_connections;
auth_connections = conn;
/* send our handshake */
memset(&handshake, 0, sizeof(handshake));
handshake.pid = login_process_uid;
if (o_stream_send(conn->output, &handshake, sizeof(handshake)) < 0) {
auth_connection_destroy(conn);
return NULL;
}
return conn;
}
static void request_destroy(struct auth_request *request)
{
hash_remove(request->conn->requests, POINTER_CAST(request->id));
i_free(request);
}
static void request_hash_destroy(void *key __attr_unused__, void *value,
void *context __attr_unused__)
{
struct auth_request *request = value;
request->callback(request, NULL, NULL, request->context);
request_destroy(request);
}
static void auth_connection_destroy(struct auth_connection *conn)
{
struct auth_connection **pos;
for (pos = &auth_connections; *pos != NULL; pos = &(*pos)->next) {
if (*pos == conn) {
*pos = conn->next;
break;
}
}
hash_foreach(conn->requests, request_hash_destroy, NULL);
hash_destroy(conn->requests);
if (close(conn->fd) < 0)
i_error("close(imap-auth) failed: %m");
io_remove(conn->io);
i_stream_unref(conn->input);
o_stream_unref(conn->output);
i_free(conn->path);
i_free(conn);
}
static struct auth_connection *
auth_connection_get(enum auth_mech mech, size_t size, const char **error)
{
struct auth_connection *conn;
int found;
found = FALSE;
for (conn = auth_connections; conn != NULL; conn = conn->next) {
if ((conn->available_auth_mechs & mech)) {
if (o_stream_have_space(conn->output, size) > 0)
return conn;
found = TRUE;
}
}
if (!found) {
if ((available_auth_mechs & mech) == 0)
*error = "Unsupported authentication mechanism";
else {
*error = "Authentication server isn't connected, "
"try again later..";
auth_reconnect = TRUE;
}
} else {
*error = "Authentication servers are busy, wait..";
i_warning("Authentication servers are busy");
}
return NULL;
}
static void update_available_auth_mechs(void)
{
struct auth_connection *conn;
available_auth_mechs = 0;
for (conn = auth_connections; conn != NULL; conn = conn->next)
available_auth_mechs |= conn->available_auth_mechs;
}
static void auth_handle_handshake(struct auth_connection *conn,
struct auth_login_handshake_output *handshake)
{
conn->pid = handshake->pid;
conn->available_auth_mechs = handshake->auth_mechanisms;
conn->handshake_received = TRUE;
update_available_auth_mechs();
}
static void auth_handle_reply(struct auth_connection *conn,
struct auth_login_reply *reply,
const unsigned char *data)
{
struct auth_request *request;
request = hash_lookup(conn->requests, POINTER_CAST(reply->id));
if (request == NULL) {
i_error("BUG: imap-auth sent us reply with unknown ID %u",
reply->id);
return;
}
request->callback(request, reply, data, request->context);
if (reply->result != AUTH_LOGIN_RESULT_CONTINUE)
request_destroy(request);
}
static void auth_input(void *context)
{
struct auth_connection *conn = context;
struct auth_login_handshake_output handshake;
const unsigned char *data;
size_t size;
switch (i_stream_read(conn->input)) {
case 0:
return;
case -1:
/* disconnected */
auth_reconnect = TRUE;
auth_connection_destroy(conn);
return;
case -2:
/* buffer full - can't happen unless imap-auth is buggy */
i_error("BUG: imap-auth sent us more than %d bytes of data",
MAX_INBUF_SIZE);
auth_connection_destroy(conn);
return;
}
if (!conn->handshake_received) {
data = i_stream_get_data(conn->input, &size);
if (size == sizeof(handshake)) {
memcpy(&handshake, data, sizeof(handshake));
i_stream_skip(conn->input, sizeof(handshake));
auth_handle_handshake(conn, &handshake);
} else if (size > sizeof(handshake)) {
i_error("BUG: imap-auth sent us too large handshake "
"(%"PRIuSIZE_T " vs %"PRIuSIZE_T")", size,
sizeof(handshake));
auth_connection_destroy(conn);
}
return;
}
if (!conn->reply_received) {
data = i_stream_get_data(conn->input, &size);
if (size < sizeof(conn->reply))
return;
memcpy(&conn->reply, data, sizeof(conn->reply));
i_stream_skip(conn->input, sizeof(conn->reply));
conn->reply_received = TRUE;
}
data = i_stream_get_data(conn->input, &size);
if (size < conn->reply.data_size)
return;
/* we've got a full reply */
conn->reply_received = FALSE;
auth_handle_reply(conn, &conn->reply, data);
i_stream_skip(conn->input, conn->reply.data_size);
}
int auth_init_request(enum auth_mech mech, auth_callback_t callback,
void *context, const char **error)
{
struct auth_connection *conn;
struct auth_request *request;
struct auth_login_request_new auth_request;
if (auth_reconnect)
auth_connect_missing();
conn = auth_connection_get(mech, sizeof(auth_request), error);
if (conn == NULL)
return FALSE;
/* create internal request structure */
request = i_new(struct auth_request, 1);
request->mech = mech;
request->conn = conn;
request->id = ++request_id_counter;
if (request->id == 0) {
/* wrapped - ID 0 not allowed */
request->id = ++request_id_counter;
}
request->callback = callback;
request->context = context;
hash_insert(conn->requests, POINTER_CAST(request->id), request);
/* send request to auth */
auth_request.type = AUTH_LOGIN_REQUEST_NEW;
auth_request.mech = request->mech;
auth_request.id = request->id;
if (o_stream_send(request->conn->output, &auth_request,
sizeof(auth_request)) < 0)
auth_connection_destroy(request->conn);
return TRUE;
}
void auth_continue_request(struct auth_request *request,
const unsigned char *data, size_t data_size)
{
struct auth_login_request_continue auth_request;
/* send continued request to auth */
auth_request.type = AUTH_LOGIN_REQUEST_CONTINUE;
auth_request.id = request->id;
auth_request.data_size = data_size;
if (o_stream_send(request->conn->output, &auth_request,
sizeof(auth_request)) < 0)
auth_connection_destroy(request->conn);
else if (o_stream_send(request->conn->output, data, data_size) < 0)
auth_connection_destroy(request->conn);
}
void auth_abort_request(struct auth_request *request)
{
request_destroy(request);
}
static void auth_connect_missing(void)
{
DIR *dirp;
struct dirent *dp;
struct stat st;
auth_reconnect = TRUE;
/* we're chrooted into */
dirp = opendir(".");
if (dirp == NULL) {
i_error("opendir(\".\") failed when trying to get list of "
"authentication servers: %m");
return;
}
while ((dp = readdir(dirp)) != NULL) {
if (dp->d_name[0] == '.')
continue;
if (auth_connection_find(dp->d_name) != NULL) {
/* already connected */
continue;
}
if (stat(dp->d_name, &st) == 0 && S_ISSOCK(st.st_mode)) {
if (auth_connection_new(dp->d_name) != NULL)
auth_reconnect = FALSE;
}
}
(void)closedir(dirp);
}
static void
auth_connect_missing_timeout(void *context __attr_unused__)
{
if (auth_reconnect)
auth_connect_missing();
}
void auth_connection_init(void)
{
auth_connections = NULL;
request_id_counter = 0;
auth_reconnect = FALSE;
auth_connect_missing();
to = timeout_add(1000, auth_connect_missing_timeout, NULL);
}
void auth_connection_deinit(void)
{
struct auth_connection *next;
while (auth_connections != NULL) {
next = auth_connections->next;
auth_connection_destroy(auth_connections);
auth_connections = next;
}
timeout_remove(to);
}
--- NEW FILE: auth-connection.h ---
#ifndef __AUTH_CONNECTION_H
#define __AUTH_CONNECTION_H
struct client;
struct auth_request;
/* reply is NULL if auth connection died */
typedef void auth_callback_t(struct auth_request *request,
struct auth_login_reply *reply,
const unsigned char *data, struct client *client);
struct auth_connection {
struct auth_connection *next;
char *path;
int fd;
struct io *io;
struct istream *input;
struct ostream *output;
unsigned int pid;
enum auth_mech available_auth_mechs;
struct auth_login_reply reply;
struct hash_table *requests;
unsigned int handshake_received:1;
unsigned int reply_received:1;
};
struct auth_request {
enum auth_mech mech;
struct auth_connection *conn;
unsigned int id;
auth_callback_t *callback;
void *context;
unsigned int init_sent:1;
};
extern enum auth_mech available_auth_mechs;
int auth_init_request(enum auth_mech mech, auth_callback_t *callback,
void *context, const char **error);
void auth_continue_request(struct auth_request *request,
const unsigned char *data, size_t data_size);
void auth_abort_request(struct auth_request *request);
void auth_connection_init(void);
void auth_connection_deinit(void);
#endif
--- NEW FILE: client-common.h ---
#ifndef __CLIENT_COMMON_H
#define __CLIENT_COMMON_H
#include "network.h"
struct client {
struct ip_addr ip;
int fd;
master_callback_t *master_callback;
/* ... */
};
struct client *client_create(int fd, struct ip_addr *ip, int ssl);
unsigned int clients_get_count(void);
void clients_destroy_all(void);
void clients_init(void);
void clients_deinit(void);
#endif
--- NEW FILE: common.h ---
#ifndef __COMMON_H
#define __COMMON_H
#include "lib.h"
#include "../auth/auth-login-interface.h"
extern int disable_plaintext_auth, process_per_connection, verbose_proctitle;
extern unsigned int max_logging_users;
extern unsigned int login_process_uid;
void main_ref(void);
void main_unref(void);
void main_close_listen(void);
#endif
--- NEW FILE: main.c ---
/* Copyright (C) 2002 Timo Sirainen */
#include "common.h"
#include "ioloop.h"
#include "lib-signals.h"
#include "restrict-access.h"
#include "process-title.h"
#include "fd-close-on-exec.h"
#include "auth-connection.h"
#include "master.h"
#include "client-common.h"
#include "ssl-proxy.h"
#include <stdlib.h>
#include <unistd.h>
#include <syslog.h>
int disable_plaintext_auth, process_per_connection, verbose_proctitle;
unsigned int max_logging_users;
unsigned int login_process_uid;
static struct ioloop *ioloop;
static struct io *io_listen, *io_listen_ssl;
static int main_refcount;
static int closing_down;
void main_ref(void)
{
main_refcount++;
}
void main_unref(void)
{
if (--main_refcount == 0) {
/* nothing to do, quit */
io_loop_stop(ioloop);
} else if (closing_down && clients_get_count() == 0) {
/* last login finished, close all communications
to master process */
master_close();
}
}
void main_close_listen(void)
{
if (closing_down)
return;
if (io_listen != NULL) {
if (close(LOGIN_LISTEN_FD) < 0)
i_fatal("can't close() IMAP listen handle");
io_remove(io_listen);
io_listen = NULL;
}
if (io_listen_ssl != NULL) {
if (close(LOGIN_SSL_LISTEN_FD) < 0)
i_fatal("can't close() IMAPS listen handle");
io_remove(io_listen_ssl);
io_listen_ssl = NULL;
}
closing_down = TRUE;
master_notify_finished();
}
static void sig_quit(int signo __attr_unused__)
{
io_loop_stop(ioloop);
}
static void login_accept(void *context __attr_unused__)
{
struct ip_addr ip;
int fd;
fd = net_accept(LOGIN_LISTEN_FD, &ip, NULL);
if (fd < 0) {
if (fd < -1)
i_fatal("accept() failed: %m");
return;
}
if (process_per_connection)
main_close_listen();
(void)client_create(fd, &ip, FALSE);
}
static void login_accept_ssl(void *context __attr_unused__)
{
struct client *client;
struct ip_addr ip;
int fd, fd_ssl;
fd = net_accept(LOGIN_SSL_LISTEN_FD, &ip, NULL);
if (fd < 0) {
if (fd < -1)
i_fatal("accept() failed: %m");
return;
}
if (process_per_connection)
main_close_listen();
fd_ssl = ssl_proxy_new(fd);
if (fd_ssl == -1)
net_disconnect(fd);
else
client = client_create(fd_ssl, &ip, TRUE);
}
static void open_logfile(const char *name)
{
if (getenv("IMAP_USE_SYSLOG") != NULL)
i_set_failure_syslog(name, LOG_NDELAY, LOG_MAIL);
else {
/* log to file or stderr */
i_set_failure_file(getenv("LOGFILE"), name);
}
if (getenv("INFOLOGFILE") != NULL)
i_set_info_file(getenv("INFOLOGFILE"));
i_set_failure_timestamp_format(getenv("LOGSTAMP"));
}
static void drop_privileges(const char *name)
{
/* Log file or syslog opening probably requires roots */
open_logfile(name);
/* Initialize SSL proxy so it can read certificate and private
key file. */
ssl_proxy_init();
/* Refuse to run as root - we should never need it and it's
dangerous with SSL. */
restrict_access_by_env(TRUE);
}
static void main_init(void)
{
const char *value;
lib_init_signals(sig_quit);
disable_plaintext_auth = getenv("DISABLE_PLAINTEXT_AUTH") != NULL;
process_per_connection = getenv("PROCESS_PER_CONNECTION") != NULL;
verbose_proctitle = getenv("VERBOSE_PROCTITLE") != NULL;
value = getenv("MAX_LOGGING_USERS");
max_logging_users = value == NULL ? 0 : strtoul(value, NULL, 10);
value = getenv("PROCESS_UID");
if (value == NULL)
i_fatal("BUG: PROCESS_UID environment not given");
login_process_uid = strtoul(value, NULL, 10);
if (login_process_uid == 0)
i_fatal("BUG: PROCESS_UID environment is 0");
closing_down = FALSE;
main_refcount = 0;
auth_connection_init();
clients_init();
io_listen = io_listen_ssl = NULL;
if (net_getsockname(LOGIN_LISTEN_FD, NULL, NULL) == 0) {
/* we're listening for imap */
io_listen = io_add(LOGIN_LISTEN_FD, IO_READ,
login_accept, NULL);
}
if (net_getsockname(LOGIN_SSL_LISTEN_FD, NULL, NULL) == 0) {
/* we're listening for imaps */
if (!ssl_initialized) {
/* this shouldn't happen, master should have
disabled the imaps socket.. */
i_fatal("BUG: SSL initialization parameters not given "
"while they should have been");
}
io_listen_ssl = io_add(LOGIN_SSL_LISTEN_FD, IO_READ,
login_accept_ssl, NULL);
}
/* initialize master last - it sends the "we're ok" notification */
master_init();
}
static void main_deinit(void)
{
if (lib_signal_kill != 0)
i_warning("Killed with signal %d", lib_signal_kill);
if (io_listen != NULL) io_remove(io_listen);
if (io_listen_ssl != NULL) io_remove(io_listen_ssl);
clients_deinit();
master_deinit();
auth_connection_deinit();
ssl_proxy_deinit();
closelog();
}
int main(int argc __attr_unused__, char *argv[], char *envp[])
{
const char *name;
#ifdef DEBUG
fd_debug_verify_leaks(3, 1024);
#endif
/* NOTE: we start rooted, so keep the code minimal until
restrict_access_by_env() is called */
lib_init();
name = strrchr(argv[0], '/');
drop_privileges(name == NULL ? argv[0] : name+1);
process_title_init(argv, envp);
ioloop = io_loop_create(system_pool);
main_init();
io_loop_run(ioloop);
main_deinit();
io_loop_destroy(ioloop);
lib_deinit();
return 0;
}
--- NEW FILE: master.c ---
/* Copyright (C) 2002 Timo Sirainen */
#include "common.h"
#include "hash.h"
#include "ioloop.h"
#include "network.h"
#include "fdpass.h"
#include "master.h"
#include "client-common.h"
#include <unistd.h>
static struct io *io_master;
static struct hash_table *master_requests;
static unsigned int master_pos;
static char master_buf[sizeof(struct master_login_reply)];
static void request_handle(struct master_login_reply *reply)
{
struct client *client;
client = hash_lookup(master_requests, POINTER_CAST(reply->tag));
if (client == NULL)
i_fatal("Master sent reply with unknown tag %u", reply->tag);
client->master_callback(client, reply->success);
hash_remove(master_requests, POINTER_CAST(reply->tag));
}
void master_request_imap(struct client *client, master_callback_t *callback,
unsigned int auth_pid, unsigned int auth_id)
{
struct master_login_request req;
memset(&req, 0, sizeof(req));
req.tag = client->fd;
req.auth_pid = auth_pid;
req.auth_id = auth_id;
req.ip = client->ip;
if (fd_send(LOGIN_MASTER_SOCKET_FD,
client->fd, &req, sizeof(req)) != sizeof(req))
i_fatal("fd_send() failed: %m");
client->master_callback = callback;
hash_insert(master_requests, POINTER_CAST(req.tag), client);
}
void master_notify_finished(void)
{
struct master_login_request req;
if (io_master == NULL)
return;
memset(&req, 0, sizeof(req));
/* sending -1 as fd does the notification */
if (fd_send(LOGIN_MASTER_SOCKET_FD,
-1, &req, sizeof(req)) != sizeof(req))
i_fatal("fd_send() failed: %m");
}
void master_close(void)
{
if (io_master == NULL)
return;
clients_destroy_all();
if (close(LOGIN_MASTER_SOCKET_FD) < 0)
i_fatal("close(master) failed: %m");
io_remove(io_master);
io_master = NULL;
main_close_listen();
main_unref();
}
static void master_input(void *context __attr_unused__)
{
int ret;
ret = net_receive(LOGIN_MASTER_SOCKET_FD, master_buf + master_pos,
sizeof(master_buf) - master_pos);
if (ret < 0) {
/* master died, kill all clients logging in */
master_close();
return;
}
master_pos += ret;
if (master_pos < sizeof(master_buf))
return;
/* reply is now read */
request_handle((struct master_login_reply *) master_buf);
master_pos = 0;
}
void master_init(void)
{
main_ref();
master_requests = hash_create(default_pool, default_pool,
0, NULL, NULL);
master_pos = 0;
io_master = io_add(LOGIN_MASTER_SOCKET_FD, IO_READ, master_input, NULL);
/* just a note to master that we're ok. if we die before,
master should shutdown itself. */
master_notify_finished();
}
void master_deinit(void)
{
hash_destroy(master_requests);
if (io_master != NULL)
io_remove(io_master);
}
--- NEW FILE: master.h ---
#ifndef __MASTER_H
#define __MASTER_H
struct client;
#include "../master/master-login-interface.h"
typedef void master_callback_t(struct client *client, int success);
void master_request_imap(struct client *client, master_callback_t *callback,
unsigned int auth_pid, unsigned int auth_id);
/* Notify master that we're not listening for new connections anymore. */
void master_notify_finished(void);
/* Close connection to master process */
void master_close(void);
void master_init(void);
void master_deinit(void);
#endif
--- NEW FILE: ssl-proxy-gnutls.c ---
/* Copyright (C) 2002 Timo Sirainen */
#include "common.h"
#include "ioloop.h"
#include "network.h"
#include "ssl-proxy.h"
#ifdef HAVE_GNUTLS
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <gcrypt.h>
#include <gnutls/gnutls.h>
struct ssl_proxy {
int refcount;
gnutls_session session;
int fd_ssl, fd_plain;
struct io *io_ssl, *io_plain;
int io_ssl_dir;
unsigned char outbuf_plain[1024];
unsigned int outbuf_pos_plain;
size_t send_left_ssl, send_left_plain;
};
const int protocol_priority[] =
{ GNUTLS_TLS1, GNUTLS_SSL3, 0 };
const int kx_priority[] =
{ GNUTLS_KX_DHE_DSS, GNUTLS_KX_RSA, GNUTLS_KX_DHE_RSA, 0 };
const int cipher_priority[] =
{ GNUTLS_CIPHER_RIJNDAEL_CBC, GNUTLS_CIPHER_3DES_CBC,
GNUTLS_CIPHER_ARCFOUR_128, GNUTLS_CIPHER_ARCFOUR_40, 0 };
const int comp_priority[] =
{ GNUTLS_COMP_LZO, GNUTLS_COMP_ZLIB, GNUTLS_COMP_NULL, 0 };
const int mac_priority[] =
{ GNUTLS_MAC_SHA, GNUTLS_MAC_MD5, 0 };
const int cert_type_priority[] =
{ GNUTLS_CRT_X509, 0 };
static gnutls_certificate_credentials x509_cred;
static gnutls_dh_params dh_params;
static gnutls_rsa_params rsa_params;
static void ssl_input(void *context);
static void plain_input(void *context);
static int ssl_proxy_destroy(struct ssl_proxy *proxy);
static const char *get_alert_text(struct ssl_proxy *proxy)
{
return gnutls_alert_get_name(gnutls_alert_get(proxy->session));
}
static int handle_ssl_error(struct ssl_proxy *proxy, int error)
{
if (!gnutls_error_is_fatal(error)) {
if (error == GNUTLS_E_WARNING_ALERT_RECEIVED) {
i_warning("Received SSL warning alert: %s",
get_alert_text(proxy));
}
return 0;
}
/* fatal error occured */
if (error == GNUTLS_E_FATAL_ALERT_RECEIVED) {
i_warning("Received SSL fatal alert: %s",
get_alert_text(proxy));
} else {
i_warning("Error reading from SSL client: %s",
gnutls_strerror(error));
}
gnutls_alert_send_appropriate(proxy->session, error);
ssl_proxy_destroy(proxy);
return -1;
}
static int proxy_recv_ssl(struct ssl_proxy *proxy, void *data, size_t size)
{
int rcvd;
rcvd = gnutls_record_recv(proxy->session, data, size);
if (rcvd > 0)
return rcvd;
if (rcvd == 0 || rcvd == GNUTLS_E_UNEXPECTED_PACKET_LENGTH) {
/* disconnected, either by nicely telling us that we'll
close the connection, or by simply killing the
connection which gives us the packet length error. */
ssl_proxy_destroy(proxy);
return -1;
}
return handle_ssl_error(proxy, rcvd);
}
static int proxy_send_ssl(struct ssl_proxy *proxy,
const void *data, size_t size)
{
int sent;
sent = gnutls_record_send(proxy->session, data, size);
if (sent >= 0)
return sent;
if (sent == GNUTLS_E_PUSH_ERROR || sent == GNUTLS_E_INVALID_SESSION) {
/* don't warn about errors related to unexpected
disconnection */
ssl_proxy_destroy(proxy);
return -1;
}
return handle_ssl_error(proxy, sent);
}
static int ssl_proxy_destroy(struct ssl_proxy *proxy)
{
if (--proxy->refcount > 0)
return TRUE;
gnutls_deinit(proxy->session);
(void)net_disconnect(proxy->fd_ssl);
(void)net_disconnect(proxy->fd_plain);
if (proxy->io_ssl != NULL)
io_remove(proxy->io_ssl);
if (proxy->io_plain != NULL)
io_remove(proxy->io_plain);
i_free(proxy);
main_unref();
return FALSE;
}
static void ssl_output(void *context)
{
struct ssl_proxy *proxy = context;
int sent;
sent = net_transmit(proxy->fd_plain,
proxy->outbuf_plain + proxy->outbuf_pos_plain,
proxy->send_left_plain);
if (sent < 0) {
/* disconnected */
ssl_proxy_destroy(proxy);
return;
}
proxy->send_left_plain -= sent;
proxy->outbuf_pos_plain += sent;
if (proxy->send_left_plain > 0)
return;
/* everything is sent, start reading again */
io_remove(proxy->io_ssl);
proxy->io_ssl = io_add(proxy->fd_ssl, IO_READ, ssl_input, proxy);
}
static void ssl_input(void *context)
{
struct ssl_proxy *proxy = context;
int rcvd, sent;
rcvd = proxy_recv_ssl(proxy, proxy->outbuf_plain,
sizeof(proxy->outbuf_plain));
if (rcvd <= 0)
return;
sent = net_transmit(proxy->fd_plain, proxy->outbuf_plain, (size_t)rcvd);
if (sent == rcvd)
return;
if (sent < 0) {
/* disconnected */
ssl_proxy_destroy(proxy);
return;
}
/* everything wasn't sent - don't read anything until we've
sent it all */
proxy->outbuf_pos_plain = 0;
proxy->send_left_plain = rcvd - sent;
io_remove(proxy->io_ssl);
proxy->io_ssl = io_add(proxy->fd_ssl, IO_WRITE, ssl_output, proxy);
}
static void plain_output(void *context)
{
struct ssl_proxy *proxy = context;
int sent;
sent = proxy_send_ssl(proxy, NULL, proxy->send_left_ssl);
if (sent <= 0)
return;
proxy->send_left_ssl -= sent;
if (proxy->send_left_ssl > 0)
return;
/* everything is sent, start reading again */
io_remove(proxy->io_plain);
proxy->io_plain = io_add(proxy->fd_plain, IO_READ, plain_input, proxy);
}
static void plain_input(void *context)
{
struct ssl_proxy *proxy = context;
char buf[1024];
ssize_t rcvd, sent;
rcvd = net_receive(proxy->fd_plain, buf, sizeof(buf));
if (rcvd < 0) {
/* disconnected */
gnutls_bye(proxy->session, 1);
ssl_proxy_destroy(proxy);
return;
}
sent = proxy_send_ssl(proxy, buf, (size_t)rcvd);
if (sent < 0 || sent == rcvd)
return;
/* everything wasn't sent - don't read anything until we've
sent it all */
proxy->send_left_ssl = rcvd - sent;
io_remove(proxy->io_plain);
proxy->io_plain = io_add(proxy->fd_ssl, IO_WRITE, plain_output, proxy);
}
static void ssl_handshake(void *context)
{
struct ssl_proxy *proxy = context;
int ret, dir;
ret = gnutls_handshake(proxy->session);
if (ret >= 0) {
/* handshake done, now we can start reading */
if (proxy->io_ssl != NULL)
io_remove(proxy->io_ssl);
proxy->io_plain = io_add(proxy->fd_plain, IO_READ,
plain_input, proxy);
proxy->io_ssl = io_add(proxy->fd_ssl, IO_READ,
ssl_input, proxy);
return;
}
if (handle_ssl_error(proxy, ret) < 0)
return;
/* i/o interrupted */
dir = gnutls_handshake_get_direction(proxy->session) == 0 ?
IO_READ : IO_WRITE;
if (proxy->io_ssl_dir != dir) {
if (proxy->io_ssl != NULL)
io_remove(proxy->io_ssl);
proxy->io_ssl = io_add(proxy->fd_ssl, dir,
ssl_handshake, proxy);
proxy->io_ssl_dir = dir;
}
}
static gnutls_session initialize_state(void)
{
gnutls_session session;
gnutls_init(&session, GNUTLS_SERVER);
gnutls_protocol_set_priority(session, protocol_priority);
gnutls_cipher_set_priority(session, cipher_priority);
gnutls_compression_set_priority(session, comp_priority);
gnutls_kx_set_priority(session, kx_priority);
gnutls_mac_set_priority(session, mac_priority);
gnutls_cert_type_set_priority(session, cert_type_priority);
gnutls_cred_set(session, GNUTLS_CRD_CERTIFICATE, x509_cred);
return session;
}
int ssl_proxy_new(int fd)
{
struct ssl_proxy *proxy;
gnutls_session session;
int sfd[2];
if (!ssl_initialized)
return -1;
session = initialize_state();
gnutls_transport_set_ptr(session, fd);
if (socketpair(AF_UNIX, SOCK_STREAM, 0, sfd) == -1) {
i_error("socketpair() failed: %m");
gnutls_deinit(session);
return -1;
}
net_set_nonblock(sfd[0], TRUE);
net_set_nonblock(sfd[1], TRUE);
proxy = i_new(struct ssl_proxy, 1);
proxy->refcount = 1;
proxy->session = session;
proxy->fd_ssl = fd;
proxy->fd_plain = sfd[0];
proxy->refcount++;
ssl_handshake(proxy);
if (!ssl_proxy_destroy(proxy))
return -1;
main_ref();
return sfd[1];
}
static void read_next_field(int fd, gnutls_datum *datum,
const char *fname, const char *field_name)
{
ssize_t ret;
/* get size */
ret = read(fd, &datum->size, sizeof(datum->size));
if (ret < 0)
i_fatal("read() failed for %s: %m", fname);
if (ret != sizeof(datum->size)) {
(void)unlink(fname);
i_fatal("Corrupted SSL parameter file %s: File too small",
fname);
}
if (datum->size > 10240) {
(void)unlink(fname);
i_fatal("Corrupted SSL parameter file %s: "
"Field '%s' too large (%u)",
fname, field_name, datum->size);
}
/* read the actual data */
datum->data = t_malloc(datum->size);
ret = read(fd, datum->data, datum->size);
if (ret < 0)
i_fatal("read() failed for %s: %m", fname);
if ((size_t)ret != datum->size) {
(void)unlink(fname);
i_fatal("Corrupted SSL parameter file %s: "
"Field '%s' not fully in file (%u < %u)",
fname, field_name, datum->size - ret, datum->size);
}
}
static void read_dh_parameters(int fd, const char *fname)
{
gnutls_datum dbits, prime, generator;
int ret, bits;
if ((ret = gnutls_dh_params_init(&dh_params)) < 0) {
i_fatal("gnutls_dh_params_init() failed: %s",
gnutls_strerror(ret));
}
/* read until bits field is 0 */
for (;;) {
read_next_field(fd, &dbits, fname, "DH bits");
if (dbits.size != sizeof(int)) {
(void)unlink(fname);
i_fatal("Corrupted SSL parameter file %s: "
"Field 'DH bits' has invalid size %u",
fname, dbits.size);
}
bits = *((int *) dbits.data);
if (bits == 0)
break;
read_next_field(fd, &prime, fname, "DH prime");
read_next_field(fd, &generator, fname, "DH generator");
ret = gnutls_dh_params_set(dh_params, prime, generator, bits);
if (ret < 0) {
i_fatal("gnutls_dh_params_set() failed: %s",
gnutls_strerror(ret));
}
}
}
static void read_rsa_parameters(int fd, const char *fname)
{
gnutls_datum m, e, d, p, q, u;
int ret;
read_next_field(fd, &m, fname, "RSA m");
read_next_field(fd, &e, fname, "RSA e");
read_next_field(fd, &d, fname, "RSA d");
read_next_field(fd, &p, fname, "RSA p");
read_next_field(fd, &q, fname, "RSA q");
read_next_field(fd, &u, fname, "RSA u");
if ((ret = gnutls_rsa_params_init(&rsa_params)) < 0) {
i_fatal("gnutls_rsa_params_init() failed: %s",
gnutls_strerror(ret));
}
/* only 512bit is allowed */
ret = gnutls_rsa_params_set(rsa_params, m, e, d, p, q, u, 512);
if (ret < 0) {
i_fatal("gnutls_rsa_params_set() failed: %s",
gnutls_strerror(ret));
}
}
static void read_parameters(const char *fname)
{
int fd;
/* we'll wait until parameter file exists */
for (;;) {
fd = open(fname, O_RDONLY);
if (fd != -1)
break;
if (errno != ENOENT)
i_fatal("Can't open SSL parameter file %s: %m", fname);
sleep(1);
}
read_dh_parameters(fd, fname);
read_rsa_parameters(fd, fname);
(void)close(fd);
}
static void gcrypt_log_handler(void *context __attr_unused__, int level,
const char *fmt, va_list args)
{
if (level == GCRY_LOG_FATAL) {
t_push();
i_error("gcrypt fatal: %s", t_strdup_vprintf(fmt, args));
t_pop();
}
}
void ssl_proxy_init(void)
{
const char *certfile, *keyfile, *paramfile;
unsigned char buf[4];
int ret;
certfile = getenv("SSL_CERT_FILE");
keyfile = getenv("SSL_KEY_FILE");
paramfile = getenv("SSL_PARAM_FILE");
if (certfile == NULL || keyfile == NULL || paramfile == NULL) {
/* SSL support is disabled */
return;
}
if ((ret = gnutls_global_init() < 0)) {
i_fatal("gnu_tls_global_init() failed: %s",
gnutls_strerror(ret));
}
/* gcrypt initialization - set log handler and make sure randomizer
opens /dev/urandom now instead of after we've chrooted */
gcry_set_log_handler(gcrypt_log_handler, NULL);
gcry_randomize(buf, sizeof(buf), GCRY_STRONG_RANDOM);
read_parameters(paramfile);
if ((ret = gnutls_certificate_allocate_cred(&x509_cred)) < 0) {
i_fatal("gnutls_certificate_allocate_cred() failed: %s",
gnutls_strerror(ret));
}
ret = gnutls_certificate_set_x509_key_file(x509_cred, certfile, keyfile,
GNUTLS_X509_FMT_PEM);
if (ret < 0) {
i_fatal("Can't load certificate files %s and %s: %s",
certfile, keyfile, gnutls_strerror(ret));
}
ret = gnutls_certificate_set_dh_params(x509_cred, dh_params);
if (ret < 0)
i_fatal("Can't set DH parameters: %s", gnutls_strerror(ret));
ret = gnutls_certificate_set_rsa_params(x509_cred, rsa_params);
if (ret < 0)
i_fatal("Can't set RSA parameters: %s", gnutls_strerror(ret));
ssl_initialized = TRUE;
}
void ssl_proxy_deinit(void)
{
if (ssl_initialized) {
gnutls_certificate_free_cred(x509_cred);
gnutls_global_deinit();
}
}
#endif
--- NEW FILE: ssl-proxy-openssl.c ---
/* Copyright (C) 2002 Timo Sirainen */
#include "common.h"
#include "ioloop.h"
#include "network.h"
#include "ssl-proxy.h"
#ifdef HAVE_OPENSSL
#include <openssl/crypto.h>
#include <openssl/x509.h>
#include <openssl/pem.h>
#include <openssl/ssl.h>
#include <openssl/err.h>
enum ssl_state {
SSL_STATE_HANDSHAKE,
SSL_STATE_READ,
SSL_STATE_WRITE
};
struct ssl_proxy {
int refcount;
SSL *ssl;
enum ssl_state state;
int fd_ssl, fd_plain;
struct io *io_ssl, *io_plain_read, *io_plain_write;
int io_ssl_dir;
unsigned char plainout_buf[1024];
unsigned int plainout_pos, plainout_size;
unsigned char sslout_buf[1024];
unsigned int sslout_pos, sslout_size;
};
static SSL_CTX *ssl_ctx;
static void plain_read(struct ssl_proxy *proxy);
static void plain_write(struct ssl_proxy *proxy);
static int ssl_proxy_destroy(struct ssl_proxy *proxy);
static void ssl_set_direction(struct ssl_proxy *proxy, int dir);
static void plain_block_input(struct ssl_proxy *proxy, int block)
{
if (block) {
if (proxy->io_plain_read != NULL) {
io_remove(proxy->io_plain_read);
proxy->io_plain_read = NULL;
}
} else {
if (proxy->io_plain_read == NULL) {
proxy->io_plain_read =
io_add(proxy->fd_plain, IO_READ,
(io_callback_t *)plain_read, proxy);
}
}
}
static void ssl_block(struct ssl_proxy *proxy, int block)
{
i_assert(proxy->state == SSL_STATE_READ);
if (block) {
if (proxy->io_ssl != NULL) {
io_remove(proxy->io_ssl);
proxy->io_ssl = NULL;
}
proxy->io_ssl_dir = -2;
} else {
proxy->io_ssl_dir = -1;
ssl_set_direction(proxy, IO_READ);
}
}
static void plain_read(struct ssl_proxy *proxy)
{
ssize_t ret;
i_assert(proxy->sslout_size == 0);
ret = net_receive(proxy->fd_plain, proxy->sslout_buf,
sizeof(proxy->sslout_buf));
if (ret < 0)
ssl_proxy_destroy(proxy);
else if (ret > 0) {
proxy->sslout_size = ret;
proxy->sslout_pos = 0;
proxy->state = SSL_STATE_WRITE;
ssl_set_direction(proxy, IO_WRITE);
plain_block_input(proxy, TRUE);
}
}
static void plain_write(struct ssl_proxy *proxy)
{
ssize_t ret;
ret = net_transmit(proxy->fd_plain,
proxy->plainout_buf + proxy->plainout_pos,
proxy->plainout_size);
if (ret < 0)
ssl_proxy_destroy(proxy);
else {
proxy->plainout_size -= ret;
proxy->plainout_pos += ret;
if (proxy->plainout_size > 0) {
ssl_block(proxy, TRUE);
if (proxy->io_plain_write == NULL) {
proxy->io_plain_write =
io_add(proxy->fd_plain, IO_WRITE,
(io_callback_t *)plain_write,
proxy);
}
} else {
proxy->plainout_pos = 0;
ssl_block(proxy, FALSE);
if (proxy->io_plain_write != NULL) {
io_remove(proxy->io_plain_write);
proxy->io_plain_write = NULL;
}
}
}
}
static const char *ssl_last_error(void)
{
unsigned long err;
char *buf;
size_t err_size = 256;
err = ERR_get_error();
if (err == 0)
return strerror(errno);
buf = t_malloc(err_size);
buf[err_size-1] = '\0';
ERR_error_string_n(err, buf, err_size-1);
return buf;
}
static void ssl_handle_error(struct ssl_proxy *proxy, int err, const char *func)
{
err = SSL_get_error(proxy->ssl, err);
switch (err) {
case SSL_ERROR_WANT_READ:
ssl_set_direction(proxy, IO_READ);
break;
case SSL_ERROR_WANT_WRITE:
ssl_set_direction(proxy, IO_WRITE);
break;
case SSL_ERROR_SYSCALL:
/* eat up the error queue */
/*i_warning("%s failed: %s", func, ssl_last_error());*/
ssl_proxy_destroy(proxy);
break;
case SSL_ERROR_ZERO_RETURN:
/* clean connection closing */
ssl_proxy_destroy(proxy);
break;
case SSL_ERROR_SSL:
/*i_warning("%s failed: %s", func, ssl_last_error());*/
ssl_proxy_destroy(proxy);
break;
default:
i_warning("%s failed: unknown failure %d (%s)",
func, err, ssl_last_error());
ssl_proxy_destroy(proxy);
break;
}
}
static void ssl_handshake_step(struct ssl_proxy *proxy)
{
int ret;
ret = SSL_accept(proxy->ssl);
if (ret != 1) {
plain_block_input(proxy, TRUE);
ssl_handle_error(proxy, ret, "SSL_accept()");
} else {
plain_block_input(proxy, FALSE);
ssl_set_direction(proxy, IO_READ);
proxy->state = SSL_STATE_READ;
}
}
static void ssl_read_step(struct ssl_proxy *proxy)
{
int ret;
i_assert(proxy->plainout_size == 0);
ret = SSL_read(proxy->ssl, proxy->plainout_buf,
sizeof(proxy->plainout_buf));
if (ret <= 0) {
plain_block_input(proxy, TRUE);
ssl_handle_error(proxy, ret, "SSL_read()");
} else {
plain_block_input(proxy, FALSE);
ssl_set_direction(proxy, IO_READ);
proxy->plainout_pos = 0;
proxy->plainout_size = ret;
plain_write(proxy);
}
}
static void ssl_write_step(struct ssl_proxy *proxy)
{
int ret;
ret = SSL_write(proxy->ssl, proxy->sslout_buf + proxy->sslout_pos,
proxy->sslout_size);
if (ret <= 0) {
plain_block_input(proxy, TRUE);
ssl_handle_error(proxy, ret, "SSL_write()");
} else {
proxy->sslout_size -= ret;
proxy->sslout_pos += ret;
if (proxy->sslout_size > 0) {
plain_block_input(proxy, TRUE);
ssl_set_direction(proxy, IO_WRITE);
proxy->state = SSL_STATE_WRITE;
} else {
plain_block_input(proxy, FALSE);
ssl_set_direction(proxy, IO_READ);
proxy->state = SSL_STATE_READ;
proxy->sslout_pos = 0;
}
}
}
static void ssl_step(void *context)
{
struct ssl_proxy *proxy = context;
switch (proxy->state) {
case SSL_STATE_HANDSHAKE:
ssl_handshake_step(proxy);
break;
case SSL_STATE_READ:
ssl_read_step(proxy);
break;
case SSL_STATE_WRITE:
ssl_write_step(proxy);
break;
}
}
static void ssl_set_direction(struct ssl_proxy *proxy, int dir)
{
i_assert(proxy->io_ssl_dir != -2);
if (proxy->io_ssl_dir == dir)
return;
if (proxy->io_ssl != NULL)
io_remove(proxy->io_ssl);
proxy->io_ssl = io_add(proxy->fd_ssl, dir, ssl_step, proxy);
}
int ssl_proxy_new(int fd)
{
struct ssl_proxy *proxy;
SSL *ssl;
int sfd[2];
if (!ssl_initialized)
return -1;
ssl = SSL_new(ssl_ctx);
if (ssl == NULL) {
i_error("SSL_new() failed: %s", ssl_last_error());
return -1;
}
SSL_set_accept_state(ssl);
if (SSL_set_fd(ssl, fd) != 1) {
i_error("SSL_set_fd() failed: %s", ssl_last_error());
return -1;
}
if (socketpair(AF_UNIX, SOCK_STREAM, 0, sfd) == -1) {
i_error("socketpair() failed: %m");
SSL_free(ssl);
return -1;
}
net_set_nonblock(sfd[0], TRUE);
net_set_nonblock(sfd[1], TRUE);
proxy = i_new(struct ssl_proxy, 1);
proxy->refcount = 1;
proxy->ssl = ssl;
proxy->fd_ssl = fd;
proxy->fd_plain = sfd[0];
proxy->state = SSL_STATE_HANDSHAKE;
ssl_set_direction(proxy, IO_READ);
proxy->refcount++;
ssl_handshake_step(proxy);
if (!ssl_proxy_destroy(proxy))
return -1;
main_ref();
return sfd[1];
}
static int ssl_proxy_destroy(struct ssl_proxy *proxy)
{
if (--proxy->refcount > 0)
return TRUE;
SSL_free(proxy->ssl);
(void)net_disconnect(proxy->fd_ssl);
(void)net_disconnect(proxy->fd_plain);
if (proxy->io_ssl != NULL)
io_remove(proxy->io_ssl);
if (proxy->io_plain_read != NULL)
io_remove(proxy->io_plain_read);
if (proxy->io_plain_write != NULL)
io_remove(proxy->io_plain_write);
i_free(proxy);
main_unref();
return FALSE;
}
void ssl_proxy_init(void)
{
const char *certfile, *keyfile, *paramfile;
int ret;
certfile = getenv("SSL_CERT_FILE");
keyfile = getenv("SSL_KEY_FILE");
paramfile = getenv("SSL_PARAM_FILE");
if (certfile == NULL || keyfile == NULL || paramfile == NULL) {
/* SSL support is disabled */
return;
}
SSL_library_init();
SSL_load_error_strings();
if ((ssl_ctx = SSL_CTX_new(SSLv23_server_method())) == NULL)
i_fatal("SSL_CTX_new() failed");
ret = SSL_CTX_use_certificate_chain_file(ssl_ctx, certfile);
if (ret != 1) {
i_fatal("Can't load certificate file %s: %s",
certfile, ssl_last_error());
}
ret = SSL_CTX_use_PrivateKey_file(ssl_ctx, keyfile, SSL_FILETYPE_PEM);
if (ret != 1) {
i_fatal("Can't load private key file %s: %s",
keyfile, ssl_last_error());
}
ssl_initialized = TRUE;
}
void ssl_proxy_deinit(void)
{
if (ssl_initialized)
SSL_CTX_free(ssl_ctx);
}
#endif
--- NEW FILE: ssl-proxy.c ---
/* Copyright (C) 2002 Timo Sirainen */
#include "lib.h"
#include "ssl-proxy.h"
int ssl_initialized = FALSE;
#ifndef HAVE_SSL
/* no SSL support */
int ssl_proxy_new(int fd __attr_unused__) { return -1; }
void ssl_proxy_init(void) {}
void ssl_proxy_deinit(void) {}
#endif
--- NEW FILE: ssl-proxy.h ---
#ifndef __SSL_PROXY_H
#define __SSL_PROXY_H
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);
void ssl_proxy_init(void);
void ssl_proxy_deinit(void);
#endif
- Previous message: [dovecot-cvs] dovecot/src/login .cvsignore,1.1.1.1,NONE Makefile.am,1.4,NONE auth-connection.c,1.23,NONE auth-connection.h,1.10,NONE client-authenticate.c,1.35,NONE client-authenticate.h,1.3,NONE client.c,1.30,NONE client.h,1.15,NONE common.h,1.7,NONE Message-Id: <20030128213528.3A85D238CB@danu.procontrol.fi>
- Next message: [dovecot-cvs] dovecot/src/imap .psrc,1.1.1.1,NONE
- Messages sorted by:
[ date ]
[ thread ]
[ subject ]
[ author ]
More information about the dovecot-cvs
mailing list