Mercurial > dovecot > core-2.2
changeset 1049:c41787e8c3f4 HEAD
Moved common login process code to login-common, created pop3-login.
line wrap: on
line diff
--- a/configure.in Tue Jan 28 21:32:18 2003 +0200 +++ b/configure.in Tue Jan 28 23:35:25 2003 +0200 @@ -857,9 +857,11 @@ src/lib-storage/register/Makefile src/auth/Makefile src/imap/Makefile -src/login/Makefile +src/imap-login/Makefile +src/login-common/Makefile src/master/Makefile src/pop3/Makefile +src/pop3-login/Makefile stamp.h) echo
--- a/src/Makefile.am Tue Jan 28 21:32:18 2003 +0200 +++ b/src/Makefile.am Tue Jan 28 23:35:25 2003 +0200 @@ -1,1 +1,1 @@ -SUBDIRS = lib lib-charset lib-mail lib-imap lib-index lib-storage auth master login imap +SUBDIRS = lib lib-charset lib-mail lib-imap lib-index lib-storage auth master login-common imap-login imap pop3-login pop3
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/imap-login/.cvsignore Tue Jan 28 23:35:25 2003 +0200 @@ -0,0 +1,9 @@ +*.la +*.lo +*.o +.deps +.libs +Makefile +Makefile.in +so_locations +imap-login
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/imap-login/Makefile.am Tue Jan 28 23:35:25 2003 +0200 @@ -0,0 +1,22 @@ +pkglibexecdir = $(libexecdir)/dovecot + +pkglibexec_PROGRAMS = imap-login + +INCLUDES = \ + -I$(top_srcdir)/src/lib \ + -I$(top_srcdir)/src/lib-imap \ + -I$(top_srcdir)/src/login-common + +imap_login_LDADD = \ + ../login-common/liblogin-common.a \ + ../lib-imap/imap-parser.o \ + ../lib/liblib.a \ + $(SSL_LIBS) + +imap_login_SOURCES = \ + client.c \ + client-authenticate.c + +noinst_HEADERS = \ + common.h \ + client.h
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/imap-login/client-authenticate.c Tue Jan 28 23:35:25 2003 +0200 @@ -0,0 +1,355 @@ +/* Copyright (C) 2002 Timo Sirainen */ + +#include "common.h" +#include "base64.h" +#include "buffer.h" +#include "ioloop.h" +#include "istream.h" +#include "ostream.h" +#include "safe-memset.h" +#include "str.h" +#include "imap-parser.h" +#include "auth-connection.h" +#include "../auth/auth-mech-desc.h" +#include "client.h" +#include "client-authenticate.h" +#include "master.h" + +static enum auth_mech auth_mechs = 0; +static char *auth_mechs_capability = NULL; + +const char *client_authenticate_get_capabilities(void) +{ + string_t *str; + int i; + + if (auth_mechs == available_auth_mechs) + return auth_mechs_capability; + + auth_mechs = available_auth_mechs; + i_free(auth_mechs_capability); + + str = t_str_new(128); + + for (i = 0; i < AUTH_MECH_COUNT; i++) { + if ((auth_mechs & auth_mech_desc[i].mech) && + auth_mech_desc[i].name != NULL) { + str_append_c(str, ' '); + str_append(str, "AUTH="); + str_append(str, auth_mech_desc[i].name); + } + } + + auth_mechs_capability = i_strdup_empty(str_c(str)); + return auth_mechs_capability; +} + +static struct auth_mech_desc *auth_mech_find(const char *name) +{ + int i; + + for (i = 0; i < AUTH_MECH_COUNT; i++) { + if (auth_mech_desc[i].name != NULL && + strcasecmp(auth_mech_desc[i].name, name) == 0) + return &auth_mech_desc[i]; + } + + return NULL; +} + +static void client_auth_abort(struct imap_client *client, const char *msg) +{ + if (client->auth_request != NULL) { + auth_abort_request(client->auth_request); + client->auth_request = NULL; + } + + client_send_tagline(client, msg != NULL ? msg : + "NO Authentication failed."); + o_stream_flush(client->output); + + /* get back to normal client input */ + if (client->io != NULL) + io_remove(client->io); + client->io = client->common.fd == -1 ? NULL : + io_add(client->common.fd, IO_READ, client_input, client); + + client_unref(client); +} + +static void master_callback(struct client *_client, int success) +{ + struct imap_client *client = (struct imap_client *) _client; + const char *reason = NULL; + + if (success) + reason = t_strconcat("Login: ", client->virtual_user, NULL); + else { + reason = t_strconcat("Internal login failure: ", + client->virtual_user, NULL); + client_send_line(client, "* BYE Internal login failure."); + } + + client_destroy(client, reason); + client_unref(client); +} + +static void client_send_auth_data(struct imap_client *client, + const unsigned char *data, size_t size) +{ + buffer_t *buf; + + t_push(); + + buf = buffer_create_dynamic(data_stack_pool, size*2, (size_t)-1); + buffer_append(buf, "+ ", 2); + base64_encode(data, size, buf); + buffer_append(buf, "\r\n", 2); + + o_stream_send(client->output, buffer_get_data(buf, NULL), + buffer_get_used_size(buf)); + o_stream_flush(client->output); + + t_pop(); +} + +static const char *auth_login_get_str(struct auth_login_reply *reply, + const unsigned char *data, size_t idx) +{ + size_t stop; + + if (idx >= reply->data_size || idx >= reply->reply_idx) + return NULL; + + stop = reply->reply_idx < reply->data_size ? + reply->reply_idx-1 : reply->data_size; + + return t_strndup(data, stop); +} + +static int auth_callback(struct auth_request *request, + struct auth_login_reply *reply, + const unsigned char *data, void *context) +{ + struct imap_client *client = context; + const char *user, *realm; + + if (reply == NULL) { + /* failed */ + client->auth_request = NULL; + client_auth_abort(client, "NO Authentication process died."); + return FALSE; + } + + switch (reply->result) { + case AUTH_LOGIN_RESULT_CONTINUE: + client->auth_request = request; + return TRUE; + + case AUTH_LOGIN_RESULT_SUCCESS: + client->auth_request = NULL; + + user = auth_login_get_str(reply, data, reply->username_idx); + realm = auth_login_get_str(reply, data, reply->realm_idx); + + i_free(client->virtual_user); + client->virtual_user = realm == NULL ? + i_strdup(user) : i_strconcat(user, "@", realm, NULL); + + /* we should be able to log in. if we fail, just + disconnect the client. */ + client_send_tagline(client, "OK Logged in."); + + master_request_imap(&client->common, master_callback, + request->conn->pid, request->id); + + /* disable IO until we're back from master */ + if (client->io != NULL) { + io_remove(client->io); + client->io = NULL; + } + return FALSE; + + case AUTH_LOGIN_RESULT_FAILURE: + /* see if we have error message */ + client->auth_request = NULL; + + if (reply->data_size > 0 && data[reply->data_size-1] == '\0') { + client_auth_abort(client, t_strconcat( + "NO Authentication failed: ", + (const char *) data, NULL)); + } else { + /* default error message */ + client_auth_abort(client, NULL); + } + return FALSE; + } + + i_unreached(); +} + +static void login_callback(struct auth_request *request, + struct auth_login_reply *reply, + const unsigned char *data, struct client *_client) +{ + struct imap_client *client = (struct imap_client *) _client; + const void *ptr; + size_t size; + + if (auth_callback(request, reply, data, client)) { + ptr = buffer_get_data(client->plain_login, &size); + auth_continue_request(request, ptr, size); + + buffer_set_used_size(client->plain_login, 0); + } +} + +int cmd_login(struct imap_client *client, struct imap_arg *args) +{ + const char *user, *pass, *error; + + /* two arguments: username and password */ + if (args[0].type != IMAP_ARG_ATOM && args[0].type != IMAP_ARG_STRING) + return FALSE; + if (args[1].type != IMAP_ARG_ATOM && args[1].type != IMAP_ARG_STRING) + return FALSE; + if (args[2].type != IMAP_ARG_EOL) + return FALSE; + + user = IMAP_ARG_STR(&args[0]); + pass = IMAP_ARG_STR(&args[1]); + + if (!client->tls && disable_plaintext_auth) { + client_send_tagline(client, + "NO Plaintext authentication disabled."); + return TRUE; + } + + /* authorization ID \0 authentication ID \0 pass */ + buffer_set_used_size(client->plain_login, 0); + buffer_append_c(client->plain_login, '\0'); + buffer_append(client->plain_login, user, strlen(user)); + buffer_append_c(client->plain_login, '\0'); + buffer_append(client->plain_login, pass, strlen(pass)); + + client_ref(client); + if (auth_init_request(AUTH_MECH_PLAIN, login_callback, + &client->common, &error)) { + /* don't read any input from client until login is finished */ + if (client->io != NULL) { + io_remove(client->io); + client->io = NULL; + } + return TRUE; + } else { + client_send_tagline(client, t_strconcat( + "NO Login failed: ", error, NULL)); + client_unref(client); + return TRUE; + } +} + +static void authenticate_callback(struct auth_request *request, + struct auth_login_reply *reply, + const unsigned char *data, + struct client *_client) +{ + struct imap_client *client = (struct imap_client *) _client; + + if (auth_callback(request, reply, data, client)) + client_send_auth_data(client, data, reply->data_size); +} + +static void client_auth_input(void *context) +{ + struct imap_client *client = context; + buffer_t *buf; + char *line; + size_t linelen, bufsize; + + if (!client_read(client)) + return; + + if (client->skip_line) { + if (i_stream_next_line(client->input) == NULL) + return; + + client->skip_line = FALSE; + } + + /* @UNSAFE */ + line = i_stream_next_line(client->input); + if (line == NULL) + return; + + if (strcmp(line, "*") == 0) { + client_auth_abort(client, "NO Authentication aborted"); + return; + } + + linelen = strlen(line); + buf = buffer_create_static_hard(data_stack_pool, linelen); + + if (base64_decode((const unsigned char *) line, linelen, + NULL, buf) <= 0) { + /* failed */ + client_auth_abort(client, "NO Invalid base64 data"); + } else if (client->auth_request == NULL) { + client_auth_abort(client, "NO Don't send unrequested data"); + } else { + auth_continue_request(client->auth_request, + buffer_get_data(buf, NULL), + buffer_get_used_size(buf)); + } + + /* clear sensitive data */ + safe_memset(line, 0, linelen); + + bufsize = buffer_get_used_size(buf); + safe_memset(buffer_free_without_data(buf), 0, bufsize); +} + +int cmd_authenticate(struct imap_client *client, struct imap_arg *args) +{ + struct auth_mech_desc *mech; + const char *mech_name, *error; + + /* we want only one argument: authentication mechanism name */ + if (args[0].type != IMAP_ARG_ATOM && args[0].type != IMAP_ARG_STRING) + return FALSE; + if (args[1].type != IMAP_ARG_EOL) + return FALSE; + + mech_name = IMAP_ARG_STR(&args[0]); + if (*mech_name == '\0') + return FALSE; + + mech = auth_mech_find(mech_name); + if (mech == NULL) { + client_send_tagline(client, + "NO Unsupported authentication mechanism."); + return TRUE; + } + + if (!client->tls && mech->plaintext && disable_plaintext_auth) { + client_send_tagline(client, + "NO Plaintext authentication disabled."); + return TRUE; + } + + client_ref(client); + if (auth_init_request(mech->mech, authenticate_callback, + &client->common, &error)) { + /* following input data will go to authentication */ + if (client->io != NULL) + io_remove(client->io); + client->io = io_add(client->common.fd, IO_READ, + client_auth_input, client); + } else { + client_send_tagline(client, t_strconcat( + "NO Authentication failed: ", error, NULL)); + client_unref(client); + } + + return TRUE; +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/imap-login/client-authenticate.h Tue Jan 28 23:35:25 2003 +0200 @@ -0,0 +1,9 @@ +#ifndef __CLIENT_AUTHENTICATE_H +#define __CLIENT_AUTHENTICATE_H + +const char *client_authenticate_get_capabilities(void); + +int cmd_login(struct imap_client *client, struct imap_arg *args); +int cmd_authenticate(struct imap_client *client, struct imap_arg *args); + +#endif
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/imap-login/client.c Tue Jan 28 23:35:25 2003 +0200 @@ -0,0 +1,459 @@ +/* Copyright (C) 2002 Timo Sirainen */ + +#include "common.h" +#include "buffer.h" +#include "hash.h" +#include "ioloop.h" +#include "istream.h" +#include "ostream.h" +#include "process-title.h" +#include "safe-memset.h" +#include "strescape.h" +#include "imap-parser.h" +#include "client.h" +#include "client-authenticate.h" +#include "ssl-proxy.h" + +/* max. size of one parameter in line */ +#define MAX_INBUF_SIZE 512 + +/* max. number of IMAP argument elements to accept. The maximum memory usage + for command from user is around MAX_INBUF_SIZE * MAX_IMAP_ARG_ELEMENTS */ +#define MAX_IMAP_ARG_ELEMENTS 4 + +/* Disconnect client after idling this many seconds */ +#define CLIENT_LOGIN_IDLE_TIMEOUT 60 + +/* Disconnect client when it sends too many bad commands */ +#define CLIENT_MAX_BAD_COMMANDS 10 + +/* When max. number of simultaneous connections is reached, few of the + oldest connections are disconnected. Since we have to go through the whole + client hash, it's faster if we disconnect multiple clients. */ +#define CLIENT_DESTROY_OLDEST_COUNT 16 + +static struct hash_table *clients; +static struct timeout *to_idle; + +static void client_set_title(struct imap_client *client) +{ + const char *host; + + if (!verbose_proctitle || !process_per_connection) + return; + + host = net_ip2host(&client->common.ip); + if (host == NULL) + host = "??"; + + process_title_set(t_strdup_printf(client->tls ? "[%s TLS]" : "[%s]", + host)); +} + +static int cmd_capability(struct imap_client *client) +{ + const char *capability; + + capability = t_strconcat("* CAPABILITY " CAPABILITY_STRING, + ssl_initialized ? " STARTTLS" : "", + disable_plaintext_auth && !client->tls ? + " LOGINDISABLED" : "", + client_authenticate_get_capabilities(), + NULL); + client_send_line(client, capability); + client_send_tagline(client, "OK Capability completed."); + return TRUE; +} + +static int cmd_starttls(struct imap_client *client) +{ + int fd_ssl; + + if (client->tls) { + client_send_tagline(client, "BAD TLS is already active."); + return TRUE; + } + + if (!ssl_initialized) { + client_send_tagline(client, "BAD TLS support isn't enabled."); + return TRUE; + } + + client_send_tagline(client, "OK Begin TLS negotiation now."); + o_stream_flush(client->output); + + /* must be removed before ssl_proxy_new(), since it may + io_add() the same fd. */ + if (client->io != NULL) { + io_remove(client->io); + client->io = NULL; + } + + fd_ssl = ssl_proxy_new(client->common.fd); + if (fd_ssl != -1) { + client->tls = TRUE; + client_set_title(client); + + client->common.fd = fd_ssl; + + i_stream_unref(client->input); + o_stream_unref(client->output); + + client->input = i_stream_create_file(fd_ssl, default_pool, + 8192, FALSE); + client->output = o_stream_create_file(fd_ssl, default_pool, + 1024, IO_PRIORITY_DEFAULT, + FALSE); + } else { + client_send_line(client, " * BYE TLS handehake failed."); + client_destroy(client, "TLS handshake failed"); + } + + client->io = io_add(client->common.fd, IO_READ, client_input, client); + return TRUE; +} + +static int cmd_noop(struct imap_client *client) +{ + client_send_tagline(client, "OK NOOP completed."); + return TRUE; +} + +static int cmd_logout(struct imap_client *client) +{ + client_send_line(client, "* BYE Logging out"); + client_send_tagline(client, "OK Logout completed."); + client_destroy(client, "Aborted login"); + return TRUE; +} + +static int client_command_execute(struct imap_client *client, const char *cmd, + struct imap_arg *args) +{ + cmd = str_ucase(t_strdup_noconst(cmd)); + if (strcmp(cmd, "LOGIN") == 0) + return cmd_login(client, args); + if (strcmp(cmd, "AUTHENTICATE") == 0) + return cmd_authenticate(client, args); + if (strcmp(cmd, "CAPABILITY") == 0) + return cmd_capability(client); + if (strcmp(cmd, "STARTTLS") == 0) + return cmd_starttls(client); + if (strcmp(cmd, "NOOP") == 0) + return cmd_noop(client); + if (strcmp(cmd, "LOGOUT") == 0) + return cmd_logout(client); + + return FALSE; +} + +/* Skip incoming data until newline is found, + returns TRUE if newline was found. */ +static int client_skip_line(struct imap_client *client) +{ + const unsigned char *data; + size_t i, data_size; + + data = i_stream_get_data(client->input, &data_size); + + for (i = 0; i < data_size; i++) { + if (data[i] == '\n') { + i_stream_skip(client->input, i+1); + return TRUE; + } + } + + return FALSE; +} + +static void client_handle_input(struct imap_client *client) +{ + struct imap_arg *args; + + if (client->cmd_finished) { + /* clear the previous command from memory. don't do this + immediately after handling command since we need the + cmd_tag to stay some time after authentication commands. */ + client->cmd_tag = NULL; + client->cmd_name = NULL; + imap_parser_reset(client->parser); + + /* remove \r\n */ + if (client->skip_line) { + if (!client_skip_line(client)) + return; + client->skip_line = FALSE; + } + + client->cmd_finished = FALSE; + } + + if (client->cmd_tag == NULL) { + client->cmd_tag = imap_parser_read_word(client->parser); + if (client->cmd_tag == NULL) + return; /* need more data */ + } + + if (client->cmd_name == NULL) { + client->cmd_name = imap_parser_read_word(client->parser); + if (client->cmd_name == NULL) + return; /* need more data */ + } + + switch (imap_parser_read_args(client->parser, 0, 0, &args)) { + case -1: + /* error */ + client_destroy(client, NULL); + return; + case -2: + /* not enough data */ + return; + } + client->skip_line = TRUE; + + if (*client->cmd_tag == '\0' || + !client_command_execute(client, client->cmd_name, args)) { + if (++client->bad_counter >= CLIENT_MAX_BAD_COMMANDS) { + client_send_line(client, + "* BYE Too many invalid IMAP commands."); + client_destroy(client, "Disconnected: " + "Too many invalid commands"); + return; + } + client_send_tagline(client, + "BAD Error in IMAP command received by server."); + } + + client->cmd_finished = TRUE; +} + +int client_read(struct imap_client *client) +{ + switch (i_stream_read(client->input)) { + case -2: + /* buffer full */ + client_send_line(client, "* BYE Input buffer full, aborting"); + client_destroy(client, "Disconnected: Input buffer full"); + return FALSE; + case -1: + /* disconnected */ + client_destroy(client, "Disconnected"); + return FALSE; + default: + /* something was read */ + return TRUE; + } +} + +void client_input(void *context) +{ + struct imap_client *client = context; + + client->last_input = ioloop_time; + + if (!client_read(client)) + return; + + client_ref(client); + + o_stream_cork(client->output); + client_handle_input(client); + + if (client_unref(client)) + o_stream_flush(client->output); +} + +static void client_hash_destroy_oldest(void *key, void *value __attr_unused__, + void *context) +{ + struct imap_client *client = key; + struct imap_client *const *destroy_clients; + buffer_t *destroy_buf = context; + size_t i, count; + + destroy_clients = buffer_get_data(destroy_buf, &count); + count /= sizeof(struct imap_client *); + + for (i = 0; i < count; i++) { + if (destroy_clients[i]->created > client->created) { + buffer_insert(destroy_buf, + i * sizeof(struct imap_client *), + &client, sizeof(struct imap_client *)); + break; + } + } +} + +static void client_destroy_oldest(void) +{ + struct imap_client *const *destroy_clients; + buffer_t *destroy_buf; + size_t i, count; + + /* find the oldest clients and put them to destroy-buffer */ + destroy_buf = buffer_create_static_hard(data_stack_pool, + sizeof(struct imap_client *) * + CLIENT_DESTROY_OLDEST_COUNT); + hash_foreach(clients, client_hash_destroy_oldest, destroy_buf); + + /* then kill them */ + destroy_clients = buffer_get_data(destroy_buf, &count); + count /= sizeof(struct imap_client *); + + for (i = 0; i < count; i++) { + client_destroy(destroy_clients[i], + "Disconnected: Connection queue full"); + } +} + +struct client *client_create(int fd, struct ip_addr *ip, int ssl) +{ + struct imap_client *client; + + if (max_logging_users > CLIENT_DESTROY_OLDEST_COUNT && + hash_size(clients) >= max_logging_users) { + /* reached max. users count, kill few of the + oldest connections */ + client_destroy_oldest(); + } + + /* always use nonblocking I/O */ + net_set_nonblock(fd, TRUE); + + client = i_new(struct imap_client, 1); + client->created = ioloop_time; + client->refcount = 1; + client->tls = ssl; + + client->common.ip = *ip; + client->common.fd = fd; + client->io = io_add(fd, IO_READ, client_input, client); + client->input = i_stream_create_file(fd, default_pool, + MAX_INBUF_SIZE, FALSE); + client->output = o_stream_create_file(fd, default_pool, 1024, + IO_PRIORITY_DEFAULT, FALSE); + client->parser = imap_parser_create(client->input, client->output, + MAX_INBUF_SIZE, + MAX_IMAP_ARG_ELEMENTS); + client->plain_login = buffer_create_dynamic(system_pool, 128, 8192); + + client->last_input = ioloop_time; + hash_insert(clients, client, client); + + main_ref(); + + client_send_line(client, "* OK " PACKAGE " ready."); + client_set_title(client); + return &client->common; +} + +void client_destroy(struct imap_client *client, const char *reason) +{ + if (reason != NULL) + client_syslog(client, reason); + + hash_remove(clients, client); + + imap_parser_destroy(client->parser); + i_stream_close(client->input); + o_stream_close(client->output); + + if (client->io != NULL) { + io_remove(client->io); + client->io = NULL; + } + + net_disconnect(client->common.fd); + client->common.fd = -1; + + i_free(client->virtual_user); + client_unref(client); +} + +void client_ref(struct imap_client *client) +{ + client->refcount++; +} + +int client_unref(struct imap_client *client) +{ + if (--client->refcount > 0) + return TRUE; + + i_stream_unref(client->input); + o_stream_unref(client->output); + + buffer_free(client->plain_login); + i_free(client); + + main_unref(); + return FALSE; +} + +void client_send_line(struct imap_client *client, const char *line) +{ + o_stream_send_str(client->output, line); + o_stream_send(client->output, "\r\n", 2); +} + +void client_send_tagline(struct imap_client *client, const char *line) +{ + client_send_line(client, t_strconcat(client->cmd_tag, " ", line, NULL)); +} + +void client_syslog(struct imap_client *client, const char *text) +{ + const char *host; + + host = net_ip2host(&client->common.ip); + if (host == NULL) + host = "??"; + + i_info("%s [%s]", text, host); +} + +static void client_hash_check_idle(void *key, void *value __attr_unused__, + void *context __attr_unused__) +{ + struct imap_client *client = key; + + if (ioloop_time - client->last_input >= CLIENT_LOGIN_IDLE_TIMEOUT) { + client_send_line(client, "* BYE Disconnected for inactivity."); + client_destroy(client, "Disconnected: Inactivity"); + } +} + +static void idle_timeout(void *context __attr_unused__) +{ + hash_foreach(clients, client_hash_check_idle, NULL); +} + +unsigned int clients_get_count(void) +{ + return hash_size(clients); +} + +static void client_hash_destroy(void *key, void *value __attr_unused__, + void *context __attr_unused__) +{ + client_destroy(key, NULL); +} + +void clients_destroy_all(void) +{ + hash_foreach(clients, client_hash_destroy, NULL); +} + +void clients_init(void) +{ + clients = hash_create(default_pool, default_pool, 128, NULL, NULL); + to_idle = timeout_add(1000, idle_timeout, NULL); +} + +void clients_deinit(void) +{ + clients_destroy_all(); + hash_destroy(clients); + + timeout_remove(to_idle); +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/imap-login/client.h Tue Jan 28 23:35:25 2003 +0200 @@ -0,0 +1,52 @@ +#ifndef __CLIENT_H +#define __CLIENT_H + +#include "network.h" +#include "master.h" +#include "client-common.h" + +struct imap_client { + struct client common; + + time_t created; + int refcount; + + struct io *io; + struct istream *input; + struct ostream *output; + struct imap_parser *parser; + + time_t last_input; + unsigned int bad_counter; + + const char *cmd_tag, *cmd_name; + + buffer_t *plain_login; + struct auth_request *auth_request; + char *virtual_user; + + unsigned int tls:1; + unsigned int cmd_finished:1; + unsigned int skip_line:1; +}; + +struct client *client_create(int fd, struct ip_addr *ip, int ssl); +void client_destroy(struct imap_client *client, const char *reason); + +void client_ref(struct imap_client *client); +int client_unref(struct imap_client *client); + +void client_send_line(struct imap_client *client, const char *line); +void client_send_tagline(struct imap_client *client, const char *line); +void client_syslog(struct imap_client *client, const char *text); + +int client_read(struct imap_client *client); +void client_input(void *context); + +unsigned int clients_get_count(void); +void clients_destroy_all(void); + +void clients_init(void); +void clients_deinit(void); + +#endif
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/imap-login/common.h Tue Jan 28 23:35:25 2003 +0200 @@ -0,0 +1,16 @@ +#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
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/login-common/.cvsignore Tue Jan 28 23:35:25 2003 +0200 @@ -0,0 +1,8 @@ +*.la +*.lo +*.o +.deps +.libs +Makefile +Makefile.in +so_locations
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/login-common/Makefile.am Tue Jan 28 23:35:25 2003 +0200 @@ -0,0 +1,19 @@ +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
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/login-common/auth-connection.c Tue Jan 28 23:35:25 2003 +0200 @@ -0,0 +1,374 @@ +/* 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); +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/login-common/auth-connection.h Tue Jan 28 23:35:25 2003 +0200 @@ -0,0 +1,56 @@ +#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
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/login-common/client-common.h Tue Jan 28 23:35:25 2003 +0200 @@ -0,0 +1,22 @@ +#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
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/login-common/common.h Tue Jan 28 23:35:25 2003 +0200 @@ -0,0 +1,16 @@ +#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
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/login-common/main.c Tue Jan 28 23:35:25 2003 +0200 @@ -0,0 +1,237 @@ +/* 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; +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/login-common/master.c Tue Jan 28 23:35:25 2003 +0200 @@ -0,0 +1,125 @@ +/* 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); +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/login-common/master.h Tue Jan 28 23:35:25 2003 +0200 @@ -0,0 +1,22 @@ +#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
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/login-common/ssl-proxy-gnutls.c Tue Jan 28 23:35:25 2003 +0200 @@ -0,0 +1,512 @@ +/* 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
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/login-common/ssl-proxy-openssl.c Tue Jan 28 23:35:25 2003 +0200 @@ -0,0 +1,386 @@ +/* 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
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/login-common/ssl-proxy.c Tue Jan 28 23:35:25 2003 +0200 @@ -0,0 +1,16 @@ +/* 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
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/login-common/ssl-proxy.h Tue Jan 28 23:35:25 2003 +0200 @@ -0,0 +1,14 @@ +#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
--- a/src/login/.cvsignore Tue Jan 28 21:32:18 2003 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,9 +0,0 @@ -*.la -*.lo -*.o -.deps -.libs -Makefile -Makefile.in -so_locations -imap-login
--- a/src/login/Makefile.am Tue Jan 28 21:32:18 2003 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,30 +0,0 @@ -pkglibexecdir = $(libexecdir)/dovecot - -pkglibexec_PROGRAMS = imap-login - -INCLUDES = \ - -I$(top_srcdir)/src/lib \ - -I$(top_srcdir)/src/lib-imap - -imap_login_LDADD = \ - ../lib/liblib.a \ - ../lib-imap/imap-parser.o \ - $(SSL_LIBS) - -imap_login_SOURCES = \ - auth-connection.c \ - client.c \ - client-authenticate.c \ - main.c \ - master.c \ - ssl-proxy.c \ - ssl-proxy-gnutls.c \ - ssl-proxy-openssl.c - -noinst_HEADERS = \ - auth-connection.h \ - common.h \ - client.h \ - client-authenticate.h \ - master.h \ - ssl-proxy.h
--- a/src/login/auth-connection.c Tue Jan 28 21:32:18 2003 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,374 +0,0 @@ -/* 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); -}
--- a/src/login/auth-connection.h Tue Jan 28 21:32:18 2003 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,56 +0,0 @@ -#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
--- a/src/login/client-authenticate.c Tue Jan 28 21:32:18 2003 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,351 +0,0 @@ -/* Copyright (C) 2002 Timo Sirainen */ - -#include "common.h" -#include "base64.h" -#include "buffer.h" -#include "ioloop.h" -#include "istream.h" -#include "ostream.h" -#include "safe-memset.h" -#include "str.h" -#include "imap-parser.h" -#include "auth-connection.h" -#include "../auth/auth-mech-desc.h" -#include "client.h" -#include "client-authenticate.h" -#include "master.h" - -static enum auth_mech auth_mechs = 0; -static char *auth_mechs_capability = NULL; - -const char *client_authenticate_get_capabilities(void) -{ - string_t *str; - int i; - - if (auth_mechs == available_auth_mechs) - return auth_mechs_capability; - - auth_mechs = available_auth_mechs; - i_free(auth_mechs_capability); - - str = t_str_new(128); - - for (i = 0; i < AUTH_MECH_COUNT; i++) { - if ((auth_mechs & auth_mech_desc[i].mech) && - auth_mech_desc[i].name != NULL) { - str_append_c(str, ' '); - str_append(str, "AUTH="); - str_append(str, auth_mech_desc[i].name); - } - } - - auth_mechs_capability = i_strdup_empty(str_c(str)); - return auth_mechs_capability; -} - -static struct auth_mech_desc *auth_mech_find(const char *name) -{ - int i; - - for (i = 0; i < AUTH_MECH_COUNT; i++) { - if (auth_mech_desc[i].name != NULL && - strcasecmp(auth_mech_desc[i].name, name) == 0) - return &auth_mech_desc[i]; - } - - return NULL; -} - -static void client_auth_abort(struct client *client, const char *msg) -{ - if (client->auth_request != NULL) { - auth_abort_request(client->auth_request); - client->auth_request = NULL; - } - - client_send_tagline(client, msg != NULL ? msg : - "NO Authentication failed."); - o_stream_flush(client->output); - - /* get back to normal client input */ - if (client->io != NULL) - io_remove(client->io); - client->io = client->fd == -1 ? NULL : - io_add(client->fd, IO_READ, client_input, client); - - client_unref(client); -} - -static void master_callback(struct client *client, int success) -{ - const char *reason = NULL; - - if (success) - reason = t_strconcat("Login: ", client->virtual_user, NULL); - else { - reason = t_strconcat("Internal login failure: ", - client->virtual_user, NULL); - client_send_line(client, "* BYE Internal login failure."); - } - - client_destroy(client, reason); - client_unref(client); -} - -static void client_send_auth_data(struct client *client, - const unsigned char *data, size_t size) -{ - buffer_t *buf; - - t_push(); - - buf = buffer_create_dynamic(data_stack_pool, size*2, (size_t)-1); - buffer_append(buf, "+ ", 2); - base64_encode(data, size, buf); - buffer_append(buf, "\r\n", 2); - - o_stream_send(client->output, buffer_get_data(buf, NULL), - buffer_get_used_size(buf)); - o_stream_flush(client->output); - - t_pop(); -} - -static const char *auth_login_get_str(struct auth_login_reply *reply, - const unsigned char *data, size_t idx) -{ - size_t stop; - - if (idx >= reply->data_size || idx >= reply->reply_idx) - return NULL; - - stop = reply->reply_idx < reply->data_size ? - reply->reply_idx-1 : reply->data_size; - - return t_strndup(data, stop); -} - -static int auth_callback(struct auth_request *request, - struct auth_login_reply *reply, - const unsigned char *data, void *context) -{ - struct client *client = context; - const char *user, *realm; - - if (reply == NULL) { - /* failed */ - client->auth_request = NULL; - client_auth_abort(client, "NO Authentication process died."); - return FALSE; - } - - switch (reply->result) { - case AUTH_LOGIN_RESULT_CONTINUE: - client->auth_request = request; - return TRUE; - - case AUTH_LOGIN_RESULT_SUCCESS: - client->auth_request = NULL; - - user = auth_login_get_str(reply, data, reply->username_idx); - realm = auth_login_get_str(reply, data, reply->realm_idx); - - i_free(client->virtual_user); - client->virtual_user = realm == NULL ? - i_strdup(user) : i_strconcat(user, "@", realm, NULL); - - /* we should be able to log in. if we fail, just - disconnect the client. */ - client_send_tagline(client, "OK Logged in."); - - master_request_imap(client, master_callback, - request->conn->pid, request->id); - - /* disable IO until we're back from master */ - if (client->io != NULL) { - io_remove(client->io); - client->io = NULL; - } - return FALSE; - - case AUTH_LOGIN_RESULT_FAILURE: - /* see if we have error message */ - client->auth_request = NULL; - - if (reply->data_size > 0 && data[reply->data_size-1] == '\0') { - client_auth_abort(client, t_strconcat( - "NO Authentication failed: ", - (const char *) data, NULL)); - } else { - /* default error message */ - client_auth_abort(client, NULL); - } - return FALSE; - } - - i_unreached(); -} - -static void login_callback(struct auth_request *request, - struct auth_login_reply *reply, - const unsigned char *data, struct client *client) -{ - const void *ptr; - size_t size; - - if (auth_callback(request, reply, data, client)) { - ptr = buffer_get_data(client->plain_login, &size); - auth_continue_request(request, ptr, size); - - buffer_set_used_size(client->plain_login, 0); - } -} - -int cmd_login(struct client *client, struct imap_arg *args) -{ - const char *user, *pass, *error; - - /* two arguments: username and password */ - if (args[0].type != IMAP_ARG_ATOM && args[0].type != IMAP_ARG_STRING) - return FALSE; - if (args[1].type != IMAP_ARG_ATOM && args[1].type != IMAP_ARG_STRING) - return FALSE; - if (args[2].type != IMAP_ARG_EOL) - return FALSE; - - user = IMAP_ARG_STR(&args[0]); - pass = IMAP_ARG_STR(&args[1]); - - if (!client->tls && disable_plaintext_auth) { - client_send_tagline(client, - "NO Plaintext authentication disabled."); - return TRUE; - } - - /* authorization ID \0 authentication ID \0 pass */ - buffer_set_used_size(client->plain_login, 0); - buffer_append_c(client->plain_login, '\0'); - buffer_append(client->plain_login, user, strlen(user)); - buffer_append_c(client->plain_login, '\0'); - buffer_append(client->plain_login, pass, strlen(pass)); - - client_ref(client); - if (auth_init_request(AUTH_MECH_PLAIN, login_callback, - client, &error)) { - /* don't read any input from client until login is finished */ - if (client->io != NULL) { - io_remove(client->io); - client->io = NULL; - } - return TRUE; - } else { - client_send_tagline(client, t_strconcat( - "NO Login failed: ", error, NULL)); - client_unref(client); - return TRUE; - } -} - -static void authenticate_callback(struct auth_request *request, - struct auth_login_reply *reply, - const unsigned char *data, - struct client *client) -{ - if (auth_callback(request, reply, data, client)) - client_send_auth_data(client, data, reply->data_size); -} - -static void client_auth_input(void *context) -{ - struct client *client = context; - buffer_t *buf; - char *line; - size_t linelen, bufsize; - - if (!client_read(client)) - return; - - if (client->skip_line) { - if (i_stream_next_line(client->input) == NULL) - return; - - client->skip_line = FALSE; - } - - /* @UNSAFE */ - line = i_stream_next_line(client->input); - if (line == NULL) - return; - - if (strcmp(line, "*") == 0) { - client_auth_abort(client, "NO Authentication aborted"); - return; - } - - linelen = strlen(line); - buf = buffer_create_static_hard(data_stack_pool, linelen); - - if (base64_decode((const unsigned char *) line, linelen, - NULL, buf) <= 0) { - /* failed */ - client_auth_abort(client, "NO Invalid base64 data"); - } else if (client->auth_request == NULL) { - client_auth_abort(client, "NO Don't send unrequested data"); - } else { - auth_continue_request(client->auth_request, - buffer_get_data(buf, NULL), - buffer_get_used_size(buf)); - } - - /* clear sensitive data */ - safe_memset(line, 0, linelen); - - bufsize = buffer_get_used_size(buf); - safe_memset(buffer_free_without_data(buf), 0, bufsize); -} - -int cmd_authenticate(struct client *client, struct imap_arg *args) -{ - struct auth_mech_desc *mech; - const char *mech_name, *error; - - /* we want only one argument: authentication mechanism name */ - if (args[0].type != IMAP_ARG_ATOM && args[0].type != IMAP_ARG_STRING) - return FALSE; - if (args[1].type != IMAP_ARG_EOL) - return FALSE; - - mech_name = IMAP_ARG_STR(&args[0]); - if (*mech_name == '\0') - return FALSE; - - mech = auth_mech_find(mech_name); - if (mech == NULL) { - client_send_tagline(client, - "NO Unsupported authentication mechanism."); - return TRUE; - } - - if (!client->tls && mech->plaintext && disable_plaintext_auth) { - client_send_tagline(client, - "NO Plaintext authentication disabled."); - return TRUE; - } - - client_ref(client); - if (auth_init_request(mech->mech, authenticate_callback, - client, &error)) { - /* following input data will go to authentication */ - if (client->io != NULL) - io_remove(client->io); - client->io = io_add(client->fd, IO_READ, - client_auth_input, client); - } else { - client_send_tagline(client, t_strconcat( - "NO Authentication failed: ", error, NULL)); - client_unref(client); - } - - return TRUE; -}
--- a/src/login/client-authenticate.h Tue Jan 28 21:32:18 2003 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,9 +0,0 @@ -#ifndef __CLIENT_AUTHENTICATE_H -#define __CLIENT_AUTHENTICATE_H - -const char *client_authenticate_get_capabilities(void); - -int cmd_login(struct client *client, struct imap_arg *args); -int cmd_authenticate(struct client *client, struct imap_arg *args); - -#endif
--- a/src/login/client.c Tue Jan 28 21:32:18 2003 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,457 +0,0 @@ -/* Copyright (C) 2002 Timo Sirainen */ - -#include "common.h" -#include "buffer.h" -#include "hash.h" -#include "ioloop.h" -#include "istream.h" -#include "ostream.h" -#include "process-title.h" -#include "safe-memset.h" -#include "strescape.h" -#include "imap-parser.h" -#include "client.h" -#include "client-authenticate.h" -#include "ssl-proxy.h" - -/* max. size of one parameter in line */ -#define MAX_INBUF_SIZE 512 - -/* max. number of IMAP argument elements to accept. The maximum memory usage - for command from user is around MAX_INBUF_SIZE * MAX_IMAP_ARG_ELEMENTS */ -#define MAX_IMAP_ARG_ELEMENTS 4 - -/* Disconnect client after idling this many seconds */ -#define CLIENT_LOGIN_IDLE_TIMEOUT 60 - -/* Disconnect client when it sends too many bad commands */ -#define CLIENT_MAX_BAD_COMMANDS 10 - -/* When max. number of simultaneous connections is reached, few of the - oldest connections are disconnected. Since we have to go through the whole - client hash, it's faster if we disconnect multiple clients. */ -#define CLIENT_DESTROY_OLDEST_COUNT 16 - -static struct hash_table *clients; -static struct timeout *to_idle; - -static void client_set_title(struct client *client) -{ - const char *host; - - if (!verbose_proctitle || !process_per_connection) - return; - - host = net_ip2host(&client->ip); - if (host == NULL) - host = "??"; - - process_title_set(t_strdup_printf(client->tls ? "[%s TLS]" : "[%s]", - host)); -} - -static int cmd_capability(struct client *client) -{ - const char *capability; - - capability = t_strconcat("* CAPABILITY " CAPABILITY_STRING, - ssl_initialized ? " STARTTLS" : "", - disable_plaintext_auth && !client->tls ? - " LOGINDISABLED" : "", - client_authenticate_get_capabilities(), - NULL); - client_send_line(client, capability); - client_send_tagline(client, "OK Capability completed."); - return TRUE; -} - -static int cmd_starttls(struct client *client) -{ - int fd_ssl; - - if (client->tls) { - client_send_tagline(client, "BAD TLS is already active."); - return TRUE; - } - - if (!ssl_initialized) { - client_send_tagline(client, "BAD TLS support isn't enabled."); - return TRUE; - } - - client_send_tagline(client, "OK Begin TLS negotiation now."); - o_stream_flush(client->output); - - /* must be removed before ssl_proxy_new(), since it may - io_add() the same fd. */ - if (client->io != NULL) { - io_remove(client->io); - client->io = NULL; - } - - fd_ssl = ssl_proxy_new(client->fd); - if (fd_ssl != -1) { - client->tls = TRUE; - client_set_title(client); - - client->fd = fd_ssl; - - i_stream_unref(client->input); - o_stream_unref(client->output); - - client->input = i_stream_create_file(fd_ssl, default_pool, - 8192, FALSE); - client->output = o_stream_create_file(fd_ssl, default_pool, - 1024, IO_PRIORITY_DEFAULT, - FALSE); - } else { - client_send_line(client, " * BYE TLS handehake failed."); - client_destroy(client, "TLS handshake failed"); - } - - client->io = io_add(client->fd, IO_READ, client_input, client); - return TRUE; -} - -static int cmd_noop(struct client *client) -{ - client_send_tagline(client, "OK NOOP completed."); - return TRUE; -} - -static int cmd_logout(struct client *client) -{ - client_send_line(client, "* BYE Logging out"); - client_send_tagline(client, "OK Logout completed."); - client_destroy(client, "Aborted login"); - return TRUE; -} - -static int client_command_execute(struct client *client, const char *cmd, - struct imap_arg *args) -{ - cmd = str_ucase(t_strdup_noconst(cmd)); - if (strcmp(cmd, "LOGIN") == 0) - return cmd_login(client, args); - if (strcmp(cmd, "AUTHENTICATE") == 0) - return cmd_authenticate(client, args); - if (strcmp(cmd, "CAPABILITY") == 0) - return cmd_capability(client); - if (strcmp(cmd, "STARTTLS") == 0) - return cmd_starttls(client); - if (strcmp(cmd, "NOOP") == 0) - return cmd_noop(client); - if (strcmp(cmd, "LOGOUT") == 0) - return cmd_logout(client); - - return FALSE; -} - -/* Skip incoming data until newline is found, - returns TRUE if newline was found. */ -static int client_skip_line(struct client *client) -{ - const unsigned char *data; - size_t i, data_size; - - data = i_stream_get_data(client->input, &data_size); - - for (i = 0; i < data_size; i++) { - if (data[i] == '\n') { - i_stream_skip(client->input, i+1); - return TRUE; - } - } - - return FALSE; -} - -static void client_handle_input(struct client *client) -{ - struct imap_arg *args; - - if (client->cmd_finished) { - /* clear the previous command from memory. don't do this - immediately after handling command since we need the - cmd_tag to stay some time after authentication commands. */ - client->cmd_tag = NULL; - client->cmd_name = NULL; - imap_parser_reset(client->parser); - - /* remove \r\n */ - if (client->skip_line) { - if (!client_skip_line(client)) - return; - client->skip_line = FALSE; - } - - client->cmd_finished = FALSE; - } - - if (client->cmd_tag == NULL) { - client->cmd_tag = imap_parser_read_word(client->parser); - if (client->cmd_tag == NULL) - return; /* need more data */ - } - - if (client->cmd_name == NULL) { - client->cmd_name = imap_parser_read_word(client->parser); - if (client->cmd_name == NULL) - return; /* need more data */ - } - - switch (imap_parser_read_args(client->parser, 0, 0, &args)) { - case -1: - /* error */ - client_destroy(client, NULL); - return; - case -2: - /* not enough data */ - return; - } - client->skip_line = TRUE; - - if (*client->cmd_tag == '\0' || - !client_command_execute(client, client->cmd_name, args)) { - if (++client->bad_counter >= CLIENT_MAX_BAD_COMMANDS) { - client_send_line(client, - "* BYE Too many invalid IMAP commands."); - client_destroy(client, "Disconnected: " - "Too many invalid commands"); - return; - } - client_send_tagline(client, - "BAD Error in IMAP command received by server."); - } - - client->cmd_finished = TRUE; -} - -int client_read(struct client *client) -{ - switch (i_stream_read(client->input)) { - case -2: - /* buffer full */ - client_send_line(client, "* BYE Input buffer full, aborting"); - client_destroy(client, "Disconnected: Input buffer full"); - return FALSE; - case -1: - /* disconnected */ - client_destroy(client, "Disconnected"); - return FALSE; - default: - /* something was read */ - return TRUE; - } -} - -void client_input(void *context) -{ - struct client *client = context; - - client->last_input = ioloop_time; - - if (!client_read(client)) - return; - - client_ref(client); - - o_stream_cork(client->output); - client_handle_input(client); - - if (client_unref(client)) - o_stream_flush(client->output); -} - -static void client_hash_destroy_oldest(void *key, void *value __attr_unused__, - void *context) -{ - struct client *client = key; - struct client *const *destroy_clients; - buffer_t *destroy_buf = context; - size_t i, count; - - destroy_clients = buffer_get_data(destroy_buf, &count); - count /= sizeof(struct client *); - - for (i = 0; i < count; i++) { - if (destroy_clients[i]->created > client->created) { - buffer_insert(destroy_buf, i * sizeof(struct client *), - &client, sizeof(struct client *)); - break; - } - } -} - -static void client_destroy_oldest(void) -{ - struct client *const *destroy_clients; - buffer_t *destroy_buf; - size_t i, count; - - /* find the oldest clients and put them to destroy-buffer */ - destroy_buf = buffer_create_static_hard(data_stack_pool, - sizeof(struct client *) * - CLIENT_DESTROY_OLDEST_COUNT); - hash_foreach(clients, client_hash_destroy_oldest, destroy_buf); - - /* then kill them */ - destroy_clients = buffer_get_data(destroy_buf, &count); - count /= sizeof(struct client *); - - for (i = 0; i < count; i++) { - client_destroy(destroy_clients[i], - "Disconnected: Connection queue full"); - } -} - -struct client *client_create(int fd, struct ip_addr *ip, int imaps) -{ - struct client *client; - - if (max_logging_users > CLIENT_DESTROY_OLDEST_COUNT && - hash_size(clients) >= max_logging_users) { - /* reached max. users count, kill few of the - oldest connections */ - client_destroy_oldest(); - } - - /* always use nonblocking I/O */ - net_set_nonblock(fd, TRUE); - - client = i_new(struct client, 1); - client->created = ioloop_time; - client->refcount = 1; - client->tls = imaps; - - memcpy(&client->ip, ip, sizeof(struct ip_addr)); - client->fd = fd; - client->io = io_add(fd, IO_READ, client_input, client); - client->input = i_stream_create_file(fd, default_pool, 8192, FALSE); - client->output = o_stream_create_file(fd, default_pool, 1024, - IO_PRIORITY_DEFAULT, FALSE); - client->parser = imap_parser_create(client->input, client->output, - MAX_INBUF_SIZE, - MAX_IMAP_ARG_ELEMENTS); - client->plain_login = buffer_create_dynamic(system_pool, 128, 8192); - - client->last_input = ioloop_time; - hash_insert(clients, client, client); - - main_ref(); - - client_send_line(client, "* OK " PACKAGE " ready."); - client_set_title(client); - return client; -} - -void client_destroy(struct client *client, const char *reason) -{ - if (reason != NULL) - client_syslog(client, reason); - - hash_remove(clients, client); - - imap_parser_destroy(client->parser); - i_stream_close(client->input); - o_stream_close(client->output); - - if (client->io != NULL) { - io_remove(client->io); - client->io = NULL; - } - - net_disconnect(client->fd); - client->fd = -1; - - i_free(client->virtual_user); - client_unref(client); -} - -void client_ref(struct client *client) -{ - client->refcount++; -} - -int client_unref(struct client *client) -{ - if (--client->refcount > 0) - return TRUE; - - i_stream_unref(client->input); - o_stream_unref(client->output); - - buffer_free(client->plain_login); - i_free(client); - - main_unref(); - return FALSE; -} - -void client_send_line(struct client *client, const char *line) -{ - o_stream_send_str(client->output, line); - o_stream_send(client->output, "\r\n", 2); -} - -void client_send_tagline(struct client *client, const char *line) -{ - client_send_line(client, t_strconcat(client->cmd_tag, " ", line, NULL)); -} - -void client_syslog(struct client *client, const char *text) -{ - const char *host; - - host = net_ip2host(&client->ip); - if (host == NULL) - host = "??"; - - i_info("%s [%s]", text, host); -} - -static void client_hash_check_idle(void *key, void *value __attr_unused__, - void *context __attr_unused__) -{ - struct client *client = key; - - if (ioloop_time - client->last_input >= CLIENT_LOGIN_IDLE_TIMEOUT) { - client_send_line(client, "* BYE Disconnected for inactivity."); - client_destroy(client, "Disconnected: Inactivity"); - } -} - -static void idle_timeout(void *context __attr_unused__) -{ - hash_foreach(clients, client_hash_check_idle, NULL); -} - -unsigned int clients_get_count(void) -{ - return hash_size(clients); -} - -static void client_hash_destroy(void *key, void *value __attr_unused__, - void *context __attr_unused__) -{ - client_destroy(key, NULL); -} - -void clients_destroy_all(void) -{ - hash_foreach(clients, client_hash_destroy, NULL); -} - -void clients_init(void) -{ - clients = hash_create(default_pool, default_pool, 128, NULL, NULL); - to_idle = timeout_add(1000, idle_timeout, NULL); -} - -void clients_deinit(void) -{ - clients_destroy_all(); - hash_destroy(clients); - - timeout_remove(to_idle); -}
--- a/src/login/client.h Tue Jan 28 21:32:18 2003 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,53 +0,0 @@ -#ifndef __CLIENT_H -#define __CLIENT_H - -#include "network.h" -#include "master.h" - -struct client { - time_t created; - int refcount; - struct ip_addr ip; - - int fd; - struct io *io; - struct istream *input; - struct ostream *output; - struct imap_parser *parser; - - time_t last_input; - unsigned int bad_counter; - - const char *cmd_tag, *cmd_name; - - buffer_t *plain_login; - struct auth_request *auth_request; - char *virtual_user; - - master_callback_t *master_callback; - - unsigned int tls:1; - unsigned int cmd_finished:1; - unsigned int skip_line:1; -}; - -struct client *client_create(int fd, struct ip_addr *ip, int imaps); -void client_destroy(struct client *client, const char *reason); - -void client_ref(struct client *client); -int client_unref(struct client *client); - -void client_send_line(struct client *client, const char *line); -void client_send_tagline(struct client *client, const char *line); -void client_syslog(struct client *client, const char *text); - -int client_read(struct client *client); -void client_input(void *context); - -unsigned int clients_get_count(void); -void clients_destroy_all(void); - -void clients_init(void); -void clients_deinit(void); - -#endif
--- a/src/login/common.h Tue Jan 28 21:32:18 2003 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,16 +0,0 @@ -#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
--- a/src/login/main.c Tue Jan 28 21:32:18 2003 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,235 +0,0 @@ -/* 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.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_imap, *io_imaps; -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_imap != NULL) { - if (close(LOGIN_IMAP_LISTEN_FD) < 0) - i_fatal("can't close() IMAP listen handle"); - - io_remove(io_imap); - io_imap = NULL; - } - - if (io_imaps != NULL) { - if (close(LOGIN_IMAPS_LISTEN_FD) < 0) - i_fatal("can't close() IMAPS listen handle"); - - io_remove(io_imaps); - io_imaps = 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_IMAP_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 addr; - int fd, fd_ssl; - - fd = net_accept(LOGIN_IMAPS_LISTEN_FD, &addr, 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, &addr, TRUE); - client->tls = TRUE; - } -} - -static void open_logfile(void) -{ - if (getenv("IMAP_USE_SYSLOG") != NULL) - i_set_failure_syslog("imap-login", LOG_NDELAY, LOG_MAIL); - else { - /* log to file or stderr */ - i_set_failure_file(getenv("IMAP_LOGFILE"), "imap-login"); - } - - if (getenv("IMAP_INFOLOGFILE") != NULL) - i_set_info_file(getenv("IMAP_INFOLOGFILE")); - - i_set_failure_timestamp_format(getenv("IMAP_LOGSTAMP")); -} - -static void drop_privileges(void) -{ - /* Log file or syslog opening probably requires roots */ - open_logfile(); - - /* 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_imap = io_imaps = NULL; - - if (net_getsockname(LOGIN_IMAP_LISTEN_FD, NULL, NULL) == 0) { - /* we're listening for imap */ - io_imap = io_add(LOGIN_IMAP_LISTEN_FD, IO_READ, - login_accept, NULL); - } - - if (net_getsockname(LOGIN_IMAPS_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_imaps = io_add(LOGIN_IMAPS_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_imap != NULL) io_remove(io_imap); - if (io_imaps != NULL) io_remove(io_imaps); - - clients_deinit(); - master_deinit(); - auth_connection_deinit(); - - ssl_proxy_deinit(); - - closelog(); -} - -int main(int argc __attr_unused__, char *argv[], char *envp[]) -{ -#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(); - drop_privileges(); - - 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; -}
--- a/src/login/master.c Tue Jan 28 21:32:18 2003 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,125 +0,0 @@ -/* 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.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); -}
--- a/src/login/master.h Tue Jan 28 21:32:18 2003 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,22 +0,0 @@ -#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
--- a/src/login/ssl-proxy-gnutls.c Tue Jan 28 21:32:18 2003 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,512 +0,0 @@ -/* 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
--- a/src/login/ssl-proxy-openssl.c Tue Jan 28 21:32:18 2003 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,386 +0,0 @@ -/* 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
--- a/src/login/ssl-proxy.c Tue Jan 28 21:32:18 2003 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,16 +0,0 @@ -/* 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
--- a/src/login/ssl-proxy.h Tue Jan 28 21:32:18 2003 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,14 +0,0 @@ -#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
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/pop3-login/.cvsignore Tue Jan 28 23:35:25 2003 +0200 @@ -0,0 +1,9 @@ +*.la +*.lo +*.o +.deps +.libs +Makefile +Makefile.in +so_locations +pop3-login
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/pop3-login/Makefile.am Tue Jan 28 23:35:25 2003 +0200 @@ -0,0 +1,20 @@ +pkglibexecdir = $(libexecdir)/dovecot + +pkglibexec_PROGRAMS = pop3-login + +INCLUDES = \ + -I$(top_srcdir)/src/lib \ + -I$(top_srcdir)/src/login-common + +pop3_login_LDADD = \ + ../login-common/liblogin-common.a \ + ../lib/liblib.a \ + $(SSL_LIBS) + +pop3_login_SOURCES = \ + client.c \ + client-authenticate.c + +noinst_HEADERS = \ + common.h \ + client.h
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/pop3-login/client-authenticate.c Tue Jan 28 23:35:25 2003 +0200 @@ -0,0 +1,311 @@ +/* Copyright (C) 2002 Timo Sirainen */ + +#include "common.h" +#include "base64.h" +#include "buffer.h" +#include "ioloop.h" +#include "istream.h" +#include "ostream.h" +#include "safe-memset.h" +#include "str.h" +#include "imap-parser.h" +#include "auth-connection.h" +#include "../auth/auth-mech-desc.h" +#include "client.h" +#include "client-authenticate.h" +#include "master.h" + +static struct auth_mech_desc *auth_mech_find(const char *name) +{ + int i; + + for (i = 0; i < AUTH_MECH_COUNT; i++) { + if (auth_mech_desc[i].name != NULL && + strcasecmp(auth_mech_desc[i].name, name) == 0) + return &auth_mech_desc[i]; + } + + return NULL; +} + +static void client_auth_abort(struct pop3_client *client, const char *msg) +{ + if (client->auth_request != NULL) { + auth_abort_request(client->auth_request); + client->auth_request = NULL; + } + + client_send_line(client, msg != NULL ? msg : + "-ERR Authentication failed."); + o_stream_flush(client->output); + + /* get back to normal client input */ + if (client->io != NULL) + io_remove(client->io); + client->io = client->common.fd == -1 ? NULL : + io_add(client->common.fd, IO_READ, client_input, client); + + client_unref(client); +} + +static void master_callback(struct client *_client, int success) +{ + struct pop3_client *client = (struct pop3_client *) _client; + const char *reason = NULL; + + if (success) + reason = t_strconcat("Login: ", client->virtual_user, NULL); + else { + reason = t_strconcat("Internal login failure: ", + client->virtual_user, NULL); + client_send_line(client, "* BYE Internal login failure."); + } + + client_destroy(client, reason); + client_unref(client); +} + +static void client_send_auth_data(struct pop3_client *client, + const unsigned char *data, size_t size) +{ + buffer_t *buf; + + t_push(); + + buf = buffer_create_dynamic(data_stack_pool, size*2, (size_t)-1); + buffer_append(buf, "+ ", 2); + base64_encode(data, size, buf); + buffer_append(buf, "\r\n", 2); + + o_stream_send(client->output, buffer_get_data(buf, NULL), + buffer_get_used_size(buf)); + o_stream_flush(client->output); + + t_pop(); +} + +static const char *auth_login_get_str(struct auth_login_reply *reply, + const unsigned char *data, size_t idx) +{ + size_t stop; + + if (idx >= reply->data_size || idx >= reply->reply_idx) + return NULL; + + stop = reply->reply_idx < reply->data_size ? + reply->reply_idx-1 : reply->data_size; + + return t_strndup(data, stop); +} + +static int auth_callback(struct auth_request *request, + struct auth_login_reply *reply, + const unsigned char *data, void *context) +{ + struct pop3_client *client = context; + const char *user, *realm; + + if (reply == NULL) { + /* failed */ + client->auth_request = NULL; + client_auth_abort(client, "-ERR Authentication process died."); + return FALSE; + } + + switch (reply->result) { + case AUTH_LOGIN_RESULT_CONTINUE: + client->auth_request = request; + return TRUE; + + case AUTH_LOGIN_RESULT_SUCCESS: + client->auth_request = NULL; + + user = auth_login_get_str(reply, data, reply->username_idx); + realm = auth_login_get_str(reply, data, reply->realm_idx); + + i_free(client->virtual_user); + client->virtual_user = realm == NULL ? + i_strdup(user) : i_strconcat(user, "@", realm, NULL); + + /* we should be able to log in. if we fail, just + disconnect the client. */ + client_send_line(client, "+OK Logged in."); + + master_request_imap(&client->common, master_callback, + request->conn->pid, request->id); + + /* disable IO until we're back from master */ + if (client->io != NULL) { + io_remove(client->io); + client->io = NULL; + } + return FALSE; + + case AUTH_LOGIN_RESULT_FAILURE: + /* see if we have error message */ + client->auth_request = NULL; + + if (reply->data_size > 0 && data[reply->data_size-1] == '\0') { + client_auth_abort(client, t_strconcat( + "-ERR Authentication failed: ", + (const char *) data, NULL)); + } else { + /* default error message */ + client_auth_abort(client, NULL); + } + return FALSE; + } + + i_unreached(); +} + +static void login_callback(struct auth_request *request, + struct auth_login_reply *reply, + const unsigned char *data, struct client *_client) +{ + struct pop3_client *client = (struct pop3_client *) _client; + const void *ptr; + size_t size; + + if (auth_callback(request, reply, data, client)) { + ptr = buffer_get_data(client->plain_login, &size); + auth_continue_request(request, ptr, size); + + buffer_set_used_size(client->plain_login, 0); + } +} + +int cmd_user(struct pop3_client *client, const char *args) +{ + if (!client->tls && disable_plaintext_auth) { + client_send_line(client, + "-ERR Plaintext authentication disabled."); + return TRUE; + } + + /* authorization ID \0 authentication ID \0 pass */ + buffer_set_used_size(client->plain_login, 0); + buffer_append_c(client->plain_login, '\0'); + buffer_append(client->plain_login, args, strlen(args)); + + client_send_line(client, "+OK"); + return TRUE; +} + +int cmd_pass(struct pop3_client *client, const char *args) +{ + const char *error; + + if (buffer_get_used_size(client->plain_login) == 0) { + client_send_line(client, "-ERR No username given."); + return TRUE; + } + + buffer_append_c(client->plain_login, '\0'); + buffer_append(client->plain_login, args, strlen(args)); + + client_ref(client); + if (auth_init_request(AUTH_MECH_PLAIN, login_callback, + &client->common, &error)) { + /* don't read any input from client until login is finished */ + if (client->io != NULL) { + io_remove(client->io); + client->io = NULL; + } + return TRUE; + } else { + client_send_line(client, + t_strconcat("-ERR Login failed: ", error, NULL)); + client_unref(client); + return TRUE; + } +} + +static void authenticate_callback(struct auth_request *request, + struct auth_login_reply *reply, + const unsigned char *data, + struct client *_client) +{ + struct pop3_client *client = (struct pop3_client *) _client; + + if (auth_callback(request, reply, data, client)) + client_send_auth_data(client, data, reply->data_size); +} + +static void client_auth_input(void *context) +{ + struct pop3_client *client = context; + buffer_t *buf; + char *line; + size_t linelen, bufsize; + + if (!client_read(client)) + return; + + /* @UNSAFE */ + line = i_stream_next_line(client->input); + if (line == NULL) + return; + + if (strcmp(line, "*") == 0) { + client_auth_abort(client, "-ERR Authentication aborted"); + return; + } + + linelen = strlen(line); + buf = buffer_create_static_hard(data_stack_pool, linelen); + + if (base64_decode((const unsigned char *) line, linelen, + NULL, buf) <= 0) { + /* failed */ + client_auth_abort(client, "-ERR Invalid base64 data"); + } else if (client->auth_request == NULL) { + client_auth_abort(client, "-ERR Don't send unrequested data"); + } else { + auth_continue_request(client->auth_request, + buffer_get_data(buf, NULL), + buffer_get_used_size(buf)); + } + + /* clear sensitive data */ + safe_memset(line, 0, linelen); + + bufsize = buffer_get_used_size(buf); + safe_memset(buffer_free_without_data(buf), 0, bufsize); +} + +int cmd_auth(struct pop3_client *client, const char *args) +{ + struct auth_mech_desc *mech; + const char *error; + + /* we have only one argument: authentication mechanism name */ + mech = auth_mech_find(args); + if (mech == NULL) { + client_send_line(client, + "-ERR Unsupported authentication mechanism."); + return TRUE; + } + + if (!client->tls && mech->plaintext && disable_plaintext_auth) { + client_send_line(client, + "-ERR Plaintext authentication disabled."); + return TRUE; + } + + client_ref(client); + if (auth_init_request(mech->mech, authenticate_callback, + &client->common, &error)) { + /* following input data will go to authentication */ + if (client->io != NULL) + io_remove(client->io); + client->io = io_add(client->common.fd, IO_READ, + client_auth_input, client); + } else { + client_send_line(client, t_strconcat( + "-ERR Authentication failed: ", error, NULL)); + client_unref(client); + } + + return TRUE; +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/pop3-login/client-authenticate.h Tue Jan 28 23:35:25 2003 +0200 @@ -0,0 +1,8 @@ +#ifndef __CLIENT_AUTHENTICATE_H +#define __CLIENT_AUTHENTICATE_H + +int cmd_user(struct pop3_client *client, const char *args); +int cmd_pass(struct pop3_client *client, const char *args); +int cmd_auth(struct pop3_client *client, const char *args); + +#endif
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/pop3-login/client.c Tue Jan 28 23:35:25 2003 +0200 @@ -0,0 +1,354 @@ +/* Copyright (C) 2002 Timo Sirainen */ + +#include "common.h" +#include "buffer.h" +#include "hash.h" +#include "ioloop.h" +#include "istream.h" +#include "ostream.h" +#include "process-title.h" +#include "safe-memset.h" +#include "strescape.h" +#include "client.h" +#include "client-authenticate.h" +#include "ssl-proxy.h" + +/* max. length of input command line (spec says 512) */ +#define MAX_INBUF_SIZE 2048 + +/* Disconnect client after idling this many seconds */ +#define CLIENT_LOGIN_IDLE_TIMEOUT 60 + +/* Disconnect client when it sends too many bad commands */ +#define CLIENT_MAX_BAD_COMMANDS 10 + +/* When max. number of simultaneous connections is reached, few of the + oldest connections are disconnected. Since we have to go through the whole + client hash, it's faster if we disconnect multiple clients. */ +#define CLIENT_DESTROY_OLDEST_COUNT 16 + +static struct hash_table *clients; +static struct timeout *to_idle; + +static void client_set_title(struct pop3_client *client) +{ + const char *host; + + if (!verbose_proctitle || !process_per_connection) + return; + + host = net_ip2host(&client->common.ip); + if (host == NULL) + host = "??"; + + process_title_set(t_strdup_printf(client->tls ? "[%s TLS]" : "[%s]", + host)); +} + +static int cmd_stls(struct pop3_client *client) +{ + int fd_ssl; + + if (client->tls) { + client_send_line(client, "-ERR TLS is already active."); + return TRUE; + } + + if (!ssl_initialized) { + client_send_line(client, "-ERR TLS support isn't enabled."); + return TRUE; + } + + client_send_line(client, "+OK Begin TLS negotiation now."); + o_stream_flush(client->output); + + /* must be removed before ssl_proxy_new(), since it may + io_add() the same fd. */ + if (client->io != NULL) { + io_remove(client->io); + client->io = NULL; + } + + fd_ssl = ssl_proxy_new(client->common.fd); + if (fd_ssl != -1) { + client->tls = TRUE; + client_set_title(client); + + client->common.fd = fd_ssl; + + i_stream_unref(client->input); + o_stream_unref(client->output); + + client->input = i_stream_create_file(fd_ssl, default_pool, + 8192, FALSE); + client->output = o_stream_create_file(fd_ssl, default_pool, + 1024, IO_PRIORITY_DEFAULT, + FALSE); + } else { + client_send_line(client, "-ERR TLS handehake failed."); + client_destroy(client, "TLS handshake failed"); + } + + client->io = io_add(client->common.fd, IO_READ, client_input, client); + return TRUE; +} + +static int cmd_quit(struct pop3_client *client) +{ + client_send_line(client, "+OK Logging out"); + client_destroy(client, "Aborted login"); + return TRUE; +} + +static int client_command_execute(struct pop3_client *client, const char *cmd, + const char *args) +{ + cmd = str_ucase(t_strdup_noconst(cmd)); + if (strcmp(cmd, "USER") == 0) + return cmd_user(client, args); + if (strcmp(cmd, "PASS") == 0) + return cmd_pass(client, args); + if (strcmp(cmd, "AUTH") == 0) + return cmd_auth(client, args); + if (strcmp(cmd, "STLS") == 0) + return cmd_stls(client); + if (strcmp(cmd, "QUIT") == 0) + return cmd_quit(client); + + return FALSE; +} + +int client_read(struct pop3_client *client) +{ + switch (i_stream_read(client->input)) { + case -2: + /* buffer full */ + client_send_line(client, "-ERR Input line too long, aborting"); + client_destroy(client, "Disconnected: Input buffer full"); + return FALSE; + case -1: + /* disconnected */ + client_destroy(client, "Disconnected"); + return FALSE; + default: + /* something was read */ + return TRUE; + } +} + +void client_input(void *context) +{ + struct pop3_client *client = context; + char *line, *args; + + client->last_input = ioloop_time; + + if (!client_read(client)) + return; + + client_ref(client); + + o_stream_cork(client->output); + while (!client->output->closed && + (line = i_stream_next_line(client->input)) != NULL) { + args = strchr(line, ' '); + if (args == NULL) + args = ""; + else + *args++ = '\0'; + + if (client_command_execute(client, line, args)) + client->bad_counter = 0; + else if (++client->bad_counter > CLIENT_MAX_BAD_COMMANDS) { + client_send_line(client, "-ERR Too many bad commands."); + client_destroy(client, + "Disconnected: Too many bad commands"); + } + } + + if (client_unref(client)) + o_stream_flush(client->output); +} + +static void client_hash_destroy_oldest(void *key, void *value __attr_unused__, + void *context) +{ + struct pop3_client *client = key; + struct pop3_client *const *destroy_clients; + buffer_t *destroy_buf = context; + size_t i, count; + + destroy_clients = buffer_get_data(destroy_buf, &count); + count /= sizeof(struct pop3_client *); + + for (i = 0; i < count; i++) { + if (destroy_clients[i]->created > client->created) { + buffer_insert(destroy_buf, + i * sizeof(struct pop3_client *), + &client, sizeof(struct pop3_client *)); + break; + } + } +} + +static void client_destroy_oldest(void) +{ + struct pop3_client *const *destroy_clients; + buffer_t *destroy_buf; + size_t i, count; + + /* find the oldest clients and put them to destroy-buffer */ + destroy_buf = buffer_create_static_hard(data_stack_pool, + sizeof(struct pop3_client *) * + CLIENT_DESTROY_OLDEST_COUNT); + hash_foreach(clients, client_hash_destroy_oldest, destroy_buf); + + /* then kill them */ + destroy_clients = buffer_get_data(destroy_buf, &count); + count /= sizeof(struct pop3_client *); + + for (i = 0; i < count; i++) { + client_destroy(destroy_clients[i], + "Disconnected: Connection queue full"); + } +} + +struct client *client_create(int fd, struct ip_addr *ip, int ssl) +{ + struct pop3_client *client; + + if (max_logging_users > CLIENT_DESTROY_OLDEST_COUNT && + hash_size(clients) >= max_logging_users) { + /* reached max. users count, kill few of the + oldest connections */ + client_destroy_oldest(); + } + + /* always use nonblocking I/O */ + net_set_nonblock(fd, TRUE); + + client = i_new(struct pop3_client, 1); + client->created = ioloop_time; + client->refcount = 1; + client->tls = ssl; + + client->common.ip = *ip; + client->common.fd = fd; + client->io = io_add(fd, IO_READ, client_input, client); + client->input = i_stream_create_file(fd, default_pool, 8192, FALSE); + client->output = o_stream_create_file(fd, default_pool, 1024, + IO_PRIORITY_DEFAULT, FALSE); + client->plain_login = buffer_create_dynamic(system_pool, 128, 8192); + + client->last_input = ioloop_time; + hash_insert(clients, client, client); + + main_ref(); + + client_send_line(client, "+OK " PACKAGE " ready."); + client_set_title(client); + return &client->common; +} + +void client_destroy(struct pop3_client *client, const char *reason) +{ + if (reason != NULL) + client_syslog(client, reason); + + hash_remove(clients, client); + + i_stream_close(client->input); + o_stream_close(client->output); + + if (client->io != NULL) { + io_remove(client->io); + client->io = NULL; + } + + net_disconnect(client->common.fd); + client->common.fd = -1; + + i_free(client->virtual_user); + client_unref(client); +} + +void client_ref(struct pop3_client *client) +{ + client->refcount++; +} + +int client_unref(struct pop3_client *client) +{ + if (--client->refcount > 0) + return TRUE; + + i_stream_unref(client->input); + o_stream_unref(client->output); + + buffer_free(client->plain_login); + i_free(client); + + main_unref(); + return FALSE; +} + +void client_send_line(struct pop3_client *client, const char *line) +{ + o_stream_send_str(client->output, line); + o_stream_send(client->output, "\r\n", 2); +} + +void client_syslog(struct pop3_client *client, const char *text) +{ + const char *host; + + host = net_ip2host(&client->common.ip); + if (host == NULL) + host = "??"; + + i_info("%s [%s]", text, host); +} + +static void client_hash_check_idle(void *key, void *value __attr_unused__, + void *context __attr_unused__) +{ + struct pop3_client *client = key; + + if (ioloop_time - client->last_input >= CLIENT_LOGIN_IDLE_TIMEOUT) + client_destroy(client, "Disconnected: Inactivity"); +} + +static void idle_timeout(void *context __attr_unused__) +{ + hash_foreach(clients, client_hash_check_idle, NULL); +} + +unsigned int clients_get_count(void) +{ + return hash_size(clients); +} + +static void client_hash_destroy(void *key, void *value __attr_unused__, + void *context __attr_unused__) +{ + client_destroy(key, NULL); +} + +void clients_destroy_all(void) +{ + hash_foreach(clients, client_hash_destroy, NULL); +} + +void clients_init(void) +{ + clients = hash_create(default_pool, default_pool, 128, NULL, NULL); + to_idle = timeout_add(1000, idle_timeout, NULL); +} + +void clients_deinit(void) +{ + clients_destroy_all(); + hash_destroy(clients); + + timeout_remove(to_idle); +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/pop3-login/client.h Tue Jan 28 23:35:25 2003 +0200 @@ -0,0 +1,46 @@ +#ifndef __CLIENT_H +#define __CLIENT_H + +#include "network.h" +#include "master.h" +#include "client-common.h" + +struct pop3_client { + struct client common; + + time_t created; + int refcount; + + struct io *io; + struct istream *input; + struct ostream *output; + + time_t last_input; + unsigned int bad_counter; + + buffer_t *plain_login; + struct auth_request *auth_request; + char *virtual_user; + + unsigned int tls:1; +}; + +struct client *client_create(int fd, struct ip_addr *ip, int ssl); +void client_destroy(struct pop3_client *client, const char *reason); + +void client_ref(struct pop3_client *client); +int client_unref(struct pop3_client *client); + +void client_send_line(struct pop3_client *client, const char *line); +void client_syslog(struct pop3_client *client, const char *text); + +int client_read(struct pop3_client *client); +void client_input(void *context); + +unsigned int clients_get_count(void); +void clients_destroy_all(void); + +void clients_init(void); +void clients_deinit(void); + +#endif
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/pop3-login/common.h Tue Jan 28 23:35:25 2003 +0200 @@ -0,0 +1,16 @@ +#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