Mercurial > dovecot > original-hg > dovecot-2.1
changeset 14607:43b841891c77
Added "connection" API for handling client/server connections more easily.
author | Timo Sirainen <tss@iki.fi> |
---|---|
date | Sun, 08 Jul 2012 08:59:52 +0300 |
parents | 523c19238a8b |
children | 211fbc872ed4 |
files | src/lib/Makefile.am src/lib/connection.c src/lib/connection.h |
diffstat | 3 files changed, 461 insertions(+), 0 deletions(-) [+] |
line wrap: on
line diff
--- a/src/lib/Makefile.am Sun Jul 08 07:45:17 2012 +0300 +++ b/src/lib/Makefile.am Sun Jul 08 08:59:52 2012 +0300 @@ -22,6 +22,7 @@ child-wait.c \ close-keep-errno.c \ compat.c \ + connection.c \ crc32.c \ data-stack.c \ eacces-error.c \ @@ -140,6 +141,7 @@ child-wait.h \ close-keep-errno.h \ compat.h \ + connection.h \ crc32.h \ data-stack.h \ eacces-error.h \
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/lib/connection.c Sun Jul 08 08:59:52 2012 +0300 @@ -0,0 +1,341 @@ +/* Copyright (c) 2012 Dovecot authors, see the included COPYING file */ + +#include "lib.h" +#include "ioloop.h" +#include "istream.h" +#include "ostream.h" +#include "network.h" +#include "strescape.h" +#include "llist.h" +#include "connection.h" + +#include <unistd.h> + +static void connection_idle_timeout(struct connection *conn) +{ + conn->disconnect_reason = CONNECTION_DISCONNECT_IDLE_TIMEOUT; + conn->list->v.destroy(conn); +} + +static void connection_connect_timeout(struct connection *conn) +{ + conn->disconnect_reason = CONNECTION_DISCONNECT_CONNECT_TIMEOUT; + conn->list->v.destroy(conn); +} + +void connection_input_default(struct connection *conn) +{ + const char *line; + struct istream *input; + int ret = 0; + + switch (connection_input_read(conn)) { + case -1: + case 0: + return; + case 1: + break; + default: + i_unreached(); + } + + input = conn->input; + i_stream_ref(input); + while (!input->closed && (line = i_stream_next_line(input)) != NULL) { + T_BEGIN { + ret = conn->list->v.input_line(conn, line); + } T_END; + if (ret <= 0) + break; + } + if (ret < 0 && !input->closed) { + conn->disconnect_reason = CONNECTION_DISCONNECT_DEINIT; + conn->list->v.destroy(conn); + } + i_stream_unref(&input); +} + +int connection_verify_version(struct connection *conn, + const char *const *args) +{ + unsigned int recv_major_version; + + /* VERSION <tab> service_name <tab> major version <tab> minor version */ + if (str_array_length(args) != 4 || + strcmp(args[0], "VERSION") != 0 || + str_to_uint(args[2], &recv_major_version) < 0 || + str_to_uint(args[3], &conn->minor_version) < 0) { + i_error("%s didn't reply with a valid VERSION line", + conn->name); + return -1; + } + + if (strcmp(args[1], conn->list->set.service_name_in) != 0) { + i_error("%s: Connected to wrong socket type. " + "We want '%s', but received '%s'", conn->name, + conn->list->set.service_name_in, args[1]); + return -1; + } + + if (recv_major_version != conn->list->set.major_version) { + i_error("%s: Socket supports major version %u, " + "but we support only %u (mixed old and new binaries?)", + conn->name, recv_major_version, + conn->list->set.major_version); + return -1; + } + return 0; +} + +int connection_input_line_default(struct connection *conn, const char *line) +{ + const char *const *args; + + args = t_strsplit_tabescaped(line); + if (!conn->version_received) { + if (connection_verify_version(conn, args) < 0) + return -1; + conn->version_received = TRUE; + } + + return conn->list->v.input_args(conn, args); +} + +static void connection_init_streams(struct connection *conn) +{ + const struct connection_settings *set = &conn->list->set; + + i_assert(conn->input == NULL); + i_assert(conn->output == NULL); + i_assert(conn->to == NULL); + + conn->version_received = set->major_version == 0; + + if (set->input_max_size != 0) { + conn->input = i_stream_create_fd(conn->fd_in, + set->input_max_size, FALSE); + } + if (set->output_max_size != 0) { + conn->output = o_stream_create_fd(conn->fd_out, + set->output_max_size, FALSE); + } + if (set->input_idle_timeout_secs != 0) { + conn->to = timeout_add(set->input_idle_timeout_secs*1000, + connection_idle_timeout, conn); + } + if (set->major_version != 0) { + o_stream_send_str(conn->output, t_strdup_printf( + "VERSION\t%s\t%u\t%u\n", set->service_name_out, + set->major_version, set->minor_version)); + } +} + +static void connection_init_io(struct connection *conn) +{ + i_assert(conn->io == NULL); + conn->io = io_add(conn->fd_in, IO_READ, conn->list->v.input, conn); +} + +void connection_init_server(struct connection_list *list, + struct connection *conn, const char *name, + int fd_in, int fd_out) +{ + i_assert(name != NULL); + i_assert(!list->set.client); + + conn->list = list; + conn->name = i_strdup(name); + conn->fd_in = fd_in; + conn->fd_out = fd_out; + connection_init_io(conn); + connection_init_streams(conn); + + DLLIST_PREPEND(&list->connections, conn); +} + +void connection_init_client_ip(struct connection_list *list, + struct connection *conn, + const struct ip_addr *ip, unsigned int port) +{ + i_assert(list->set.client); + + conn->fd_in = conn->fd_out = -1; + conn->list = list; + conn->name = i_strdup_printf("%s:%u", net_ip2addr(ip), port); + + conn->ip = *ip; + conn->port = port; + + DLLIST_PREPEND(&list->connections, conn); +} + +void connection_init_client_unix(struct connection_list *list, + struct connection *conn, const char *path) +{ + i_assert(list->set.client); + + conn->fd_in = conn->fd_out = -1; + conn->list = list; + conn->name = i_strdup(path); + + DLLIST_PREPEND(&list->connections, conn); +} + +static void connection_connected(struct connection *conn) +{ + io_remove(&conn->io); + if (conn->to != NULL) + timeout_remove(&conn->to); + + connection_init_io(conn); +} + +int connection_client_connect(struct connection *conn) +{ + const struct connection_settings *set = &conn->list->set; + int fd; + + i_assert(conn->list->set.client); + i_assert(conn->fd_in == -1); + + if (conn->port != 0) + fd = net_connect_ip(&conn->ip, conn->port, NULL); + else + fd = net_connect_unix(conn->name); + if (fd == -1) + return -1; + conn->fd_in = conn->fd_out = fd; + + if (conn->port != 0) { + conn->io = io_add(conn->fd_out, IO_WRITE, + connection_connected, conn); + if (set->client_connect_timeout_msecs != 0) { + conn->to = timeout_add(set->client_connect_timeout_msecs, + connection_connect_timeout, conn); + } + } else { + connection_init_io(conn); + } + connection_init_streams(conn); + return 0; +} + +void connection_disconnect(struct connection *conn) +{ + if (conn->to != NULL) + timeout_remove(&conn->to); + if (conn->io != NULL) + io_remove(&conn->io); + if (conn->input != NULL) + i_stream_destroy(&conn->input); + if (conn->output != NULL) + o_stream_destroy(&conn->output); + if (conn->fd_in != -1) { + if (close(conn->fd_in) < 0) + i_error("close(%s) failed: %m", conn->name); + if (conn->fd_in != conn->fd_out) + i_error("close(%s/out) failed: %m", conn->name); + conn->fd_in = conn->fd_out = -1; + } +} + +void connection_deinit(struct connection *conn) +{ + DLLIST_REMOVE(&conn->list->connections, conn); + + connection_disconnect(conn); + i_free(conn->name); +} + +int connection_input_read(struct connection *conn) +{ + conn->last_input = ioloop_time; + if (conn->to != NULL) + timeout_reset(conn->to); + + switch (i_stream_read(conn->input)) { + case -2: + /* buffer full */ + switch (conn->list->set.input_full_behavior) { + case CONNECTION_BEHAVIOR_DESTROY: + conn->disconnect_reason = + CONNECTION_DISCONNECT_BUFFER_FULL; + conn->list->v.destroy(conn); + return -1; + case CONNECTION_BEHAVIOR_ALLOW: + return -2; + } + case -1: + /* disconnected */ + conn->disconnect_reason = + CONNECTION_DISCONNECT_CONN_CLOSED; + conn->list->v.destroy(conn); + return -1; + case 0: + /* nothing new read */ + return 0; + default: + /* something was read */ + return 1; + } +} + +const char *connection_disconnect_reason(struct connection *conn) +{ + if (conn->input != NULL && conn->input->stream_errno != 0) + errno = conn->input->stream_errno; + else if (conn->output != NULL && conn->output->stream_errno != 0) + errno = conn->output->stream_errno; + + return errno == 0 || errno == EPIPE ? "Connection closed" : + t_strdup_printf("Connection closed: %m"); +} + +void connection_switch_ioloop(struct connection *conn) +{ + if (conn->io != NULL) + conn->io = io_loop_move_io(&conn->io); + if (conn->to != NULL) + conn->to = io_loop_move_timeout(&conn->to); + if (conn->output != NULL) + o_stream_switch_ioloop(conn->output); +} + +struct connection_list * +connection_list_init(const struct connection_settings *set, + const struct connection_vfuncs *vfuncs) +{ + struct connection_list *list; + + i_assert(vfuncs->input != NULL || + set->input_full_behavior != CONNECTION_BEHAVIOR_ALLOW); + i_assert(set->major_version == 0 || + (set->service_name_in != NULL && + set->service_name_out != NULL)); + + list = i_new(struct connection_list, 1); + list->set = *set; + list->v = *vfuncs; + + if (list->v.input == NULL) + list->v.input = connection_input_default; + if (list->v.input_line == NULL) + list->v.input_line = connection_input_line_default; + + return list; +} + +void connection_list_deinit(struct connection_list **_list) +{ + struct connection_list *list = *_list; + struct connection *conn; + + *_list = NULL; + + while (list->connections != NULL) { + conn = list->connections; + list->v.destroy(conn); + i_assert(conn != list->connections); + } + i_free(list); +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/lib/connection.h Sun Jul 08 08:59:52 2012 +0300 @@ -0,0 +1,118 @@ +#ifndef CONNECTION_H +#define CONNECTION_H + +#include "network.h" + +struct connection; + +enum connection_behavior { + CONNECTION_BEHAVIOR_DESTROY = 0, + CONNECTION_BEHAVIOR_ALLOW +}; + +enum connection_disconnect_reason { + /* not disconnected yet */ + CONNECTION_DISCONNECT_NOT = 0, + /* normal requested disconnection */ + CONNECTION_DISCONNECT_DEINIT, + /* input buffer full */ + CONNECTION_DISCONNECT_BUFFER_FULL, + /* connection got disconnected */ + CONNECTION_DISCONNECT_CONN_CLOSED, + /* connect() timed out */ + CONNECTION_DISCONNECT_CONNECT_TIMEOUT, + /* remote didn't send input */ + CONNECTION_DISCONNECT_IDLE_TIMEOUT +}; + +struct connection_vfuncs { + void (*destroy)(struct connection *conn); + + /* implement one of the input*() methods. + They return 0 = ok, -1 = error, disconnect the client */ + void (*input)(struct connection *conn); + int (*input_line)(struct connection *conn, const char *line); + int (*input_args)(struct connection *conn, const char *const *args); +}; + +struct connection_settings { + const char *service_name_in; + const char *service_name_out; + unsigned int major_version, minor_version; + + unsigned int client_connect_timeout_msecs; + unsigned int input_idle_timeout_secs; + + size_t input_max_size; + size_t output_max_size; + enum connection_behavior input_full_behavior; + + bool client; +}; + +struct connection { + struct connection *prev, *next; + struct connection_list *list; + + char *name; + int fd_in, fd_out; + struct io *io; + struct istream *input; + struct ostream *output; + + struct timeout *to; + time_t last_input; + + /* for IP client: */ + struct ip_addr ip; + unsigned int port; + + /* received minor version */ + unsigned int minor_version; + + enum connection_disconnect_reason disconnect_reason; + + unsigned int version_received:1; +}; + +struct connection_list { + struct connection *connections; + + struct connection_settings set; + struct connection_vfuncs v; +}; + +void connection_init_server(struct connection_list *list, + struct connection *conn, const char *name, + int fd_in, int fd_out); +void connection_init_client_ip(struct connection_list *list, + struct connection *conn, + const struct ip_addr *ip, unsigned int port); +void connection_init_client_unix(struct connection_list *list, + struct connection *conn, const char *path); + +int connection_client_connect(struct connection *conn); + +void connection_disconnect(struct connection *conn); +void connection_deinit(struct connection *conn); + +/* Returns -1 = disconnected, 0 = nothing new, 1 = something new. + If input_full_behavior is ALLOW, may return also -2 = buffer full. */ +int connection_input_read(struct connection *conn); +/* Verify that VERSION input matches what we expect. */ +int connection_verify_version(struct connection *conn, const char *const *args); + +/* Returns human-readable reason for why connection was disconnected. */ +const char *connection_disconnect_reason(struct connection *conn); + +void connection_switch_ioloop(struct connection *conn); + +struct connection_list * +connection_list_init(const struct connection_settings *set, + const struct connection_vfuncs *vfuncs); +void connection_list_deinit(struct connection_list **list); + +void connection_input_default(struct connection *conn); +int connection_input_line_default(struct connection *conn, const char *line); + +#endif