diff options
Diffstat (limited to 'src/imap-urlauth/imap-urlauth-client.c')
-rw-r--r-- | src/imap-urlauth/imap-urlauth-client.c | 380 |
1 files changed, 380 insertions, 0 deletions
diff --git a/src/imap-urlauth/imap-urlauth-client.c b/src/imap-urlauth/imap-urlauth-client.c new file mode 100644 index 0000000..73f5b18 --- /dev/null +++ b/src/imap-urlauth/imap-urlauth-client.c @@ -0,0 +1,380 @@ +/* Copyright (c) 2013-2018 Dovecot authors, see the included COPYING file */ + +#include "imap-urlauth-common.h" +#include "array.h" +#include "ioloop.h" +#include "net.h" +#include "fdpass.h" +#include "istream.h" +#include "ostream.h" +#include "str.h" +#include "strescape.h" +#include "eacces-error.h" +#include "llist.h" +#include "hostpid.h" +#include "execv-const.h" +#include "env-util.h" +#include "var-expand.h" +#include "restrict-access.h" +#include "master-service.h" +#include "master-interface.h" + +#include <unistd.h> +#include <sys/wait.h> + +#define IMAP_URLAUTH_PROTOCOL_MAJOR_VERSION 1 +#define IMAP_URLAUTH_PROTOCOL_MINOR_VERSION 0 + +#define IMAP_URLAUTH_WORKER_SOCKET "imap-urlauth-worker" + +/* max. length of input lines (URLs) */ +#define MAX_INBUF_SIZE 2048 + +/* Disconnect client after idling this many milliseconds */ +#define CLIENT_IDLE_TIMEOUT_MSECS (10*60*1000) + +#define USER_EXECUTABLE "imap-urlauth-worker" + +#define IS_STANDALONE() \ + (getenv(MASTER_IS_PARENT_ENV) == NULL) + +static struct event_category event_category_urlauth = { + .name = "imap-urlauth", +}; + +struct client *imap_urlauth_clients; +unsigned int imap_urlauth_client_count; + +static int client_worker_connect(struct client *client); +static void client_worker_disconnect(struct client *client); +static void client_worker_input(struct client *client); + +int client_create(const char *service, const char *username, + int fd_in, int fd_out, const struct imap_urlauth_settings *set, + struct client **client_r) +{ + struct client *client; + const char *app; + + /* always use nonblocking I/O */ + net_set_nonblock(fd_in, TRUE); + net_set_nonblock(fd_out, TRUE); + + client = i_new(struct client, 1); + client->fd_in = fd_in; + client->fd_out = fd_out; + client->fd_ctrl = -1; + client->set = set; + + client->event = event_create(NULL); + event_set_forced_debug(client->event, set->mail_debug); + event_add_category(client->event, &event_category_urlauth); + event_set_append_log_prefix(client->event, t_strdup_printf( + "user %s: ", username)); + + if (client_worker_connect(client) < 0) { + event_unref(&client->event); + i_free(client); + return -1; + } + + /* determine user's special privileges */ + i_array_init(&client->access_apps, 4); + if (username != NULL) { + if (set->imap_urlauth_submit_user != NULL && + strcmp(set->imap_urlauth_submit_user, username) == 0) { + e_debug(client->event, "User has URLAUTH submit access"); + app = "submit+"; + array_push_back(&client->access_apps, &app); + } + if (set->imap_urlauth_stream_user != NULL && + strcmp(set->imap_urlauth_stream_user, username) == 0) { + e_debug(client->event, "User has URLAUTH stream access"); + app = "stream"; + array_push_back(&client->access_apps, &app); + } + } + + client->username = i_strdup(username); + client->service = i_strdup(service); + + client->output = o_stream_create_fd(fd_out, SIZE_MAX); + + imap_urlauth_client_count++; + DLLIST_PREPEND(&imap_urlauth_clients, client); + + imap_urlauth_refresh_proctitle(); + *client_r = client; + return 0; +} + +void client_send_line(struct client *client, const char *fmt, ...) +{ + va_list va; + ssize_t ret; + + if (client->output->closed) + return; + + va_start(va, fmt); + + T_BEGIN { + string_t *str; + + str = t_str_new(256); + str_vprintfa(str, fmt, va); + str_append(str, "\n"); + + ret = o_stream_send(client->output, + str_data(str), str_len(str)); + i_assert(ret < 0 || (size_t)ret == str_len(str)); + } T_END; + + va_end(va); +} + +static int client_worker_connect(struct client *client) +{ + static const char handshake[] = "VERSION\timap-urlauth-worker\t2\t0\n"; + const char *socket_path; + ssize_t ret; + unsigned char data; + + socket_path = t_strconcat(client->set->base_dir, + "/"IMAP_URLAUTH_WORKER_SOCKET, NULL); + + e_debug(client->event, "Connecting to worker socket %s", socket_path); + + client->fd_ctrl = net_connect_unix_with_retries(socket_path, 1000); + if (client->fd_ctrl < 0) { + if (errno == EACCES) { + e_error(client->event, "imap-urlauth-client: %s", + eacces_error_get("net_connect_unix", + socket_path)); + } else { + e_error(client->event, "imap-urlauth-client: " + "net_connect_unix(%s) failed: %m", + socket_path); + } + return -1; + } + + /* transfer one or two fds */ + data = (client->fd_in == client->fd_out ? '0' : '1'); + ret = fd_send(client->fd_ctrl, client->fd_in, &data, sizeof(data)); + if (ret > 0 && client->fd_in != client->fd_out) { + data = '0'; + ret = fd_send(client->fd_ctrl, client->fd_out, + &data, sizeof(data)); + } + + if (ret <= 0) { + if (ret < 0) { + e_error(client->event, "fd_send(%s, %d) failed: %m", + socket_path, client->fd_ctrl); + } else { + e_error(client->event, "fd_send(%s, %d) failed to send byte", + socket_path, client->fd_ctrl); + } + client_worker_disconnect(client); + return -1; + } + + client->ctrl_output = o_stream_create_fd(client->fd_ctrl, SIZE_MAX); + + /* send protocol version handshake */ + if (o_stream_send_str(client->ctrl_output, handshake) < 0) { + e_error(client->event, + "Error sending handshake to imap-urlauth worker: %m"); + client_worker_disconnect(client); + return -1; + } + + client->ctrl_input = + i_stream_create_fd(client->fd_ctrl, MAX_INBUF_SIZE); + client->ctrl_io = + io_add(client->fd_ctrl, IO_READ, client_worker_input, client); + return 0; +} + +void client_worker_disconnect(struct client *client) +{ + client->worker_state = IMAP_URLAUTH_WORKER_STATE_INACTIVE; + + io_remove(&client->ctrl_io); + o_stream_destroy(&client->ctrl_output); + i_stream_destroy(&client->ctrl_input); + if (client->fd_ctrl >= 0) { + net_disconnect(client->fd_ctrl); + client->fd_ctrl = -1; + } +} + +static int +client_worker_input_line(struct client *client, const char *response) +{ + const char *const *apps; + unsigned int count, i; + bool restart; + string_t *str; + int ret; + + switch (client->worker_state) { + case IMAP_URLAUTH_WORKER_STATE_INACTIVE: + if (strcasecmp(response, "OK") != 0) { + client_disconnect(client, "Worker handshake failed"); + return -1; + } + client->worker_state = IMAP_URLAUTH_WORKER_STATE_CONNECTED; + + str = t_str_new(256); + str_append(str, "ACCESS\t"); + if (client->username != NULL) + str_append_tabescaped(str, client->username); + str_append(str, "\t"); + str_append_tabescaped(str, client->service); + if (client->set->mail_debug) + str_append(str, "\tdebug"); + if (array_count(&client->access_apps) > 0) { + str_append(str, "\tapps="); + apps = array_get(&client->access_apps, &count); + str_append(str, apps[0]); + for (i = 1; i < count; i++) { + str_append_c(str, ','); + str_append_tabescaped(str, apps[i]); + } + } + str_append(str, "\n"); + + ret = o_stream_send(client->ctrl_output, + str_data(str), str_len(str)); + i_assert(ret < 0 || (size_t)ret == str_len(str)); + if (ret < 0) { + client_disconnect(client, + "Failed to send ACCESS control command to worker"); + return -1; + } + break; + + case IMAP_URLAUTH_WORKER_STATE_CONNECTED: + if (strcasecmp(response, "OK") != 0) { + client_disconnect(client, + "Failed to negotiate access parameters"); + return -1; + } + client->worker_state = IMAP_URLAUTH_WORKER_STATE_ACTIVE; + break; + + case IMAP_URLAUTH_WORKER_STATE_ACTIVE: + restart = TRUE; + if (strcasecmp(response, "DISCONNECTED") == 0) { + /* worker detected client disconnect */ + restart = FALSE; + } else if (strcasecmp(response, "FINISHED") != 0) { + /* unknown response */ + client_disconnect(client, + "Worker finished with unknown response"); + return -1; + } + + e_debug(client->event, "Worker finished successfully"); + + if (restart) { + /* connect to new worker for accessing different user */ + client_worker_disconnect(client); + if (client_worker_connect(client) < 0) { + client_disconnect(client, + "Failed to connect to new worker"); + return -1; + } + + /* indicate success of "END" command */ + client_send_line(client, "OK"); + } else { + client_disconnect(client, "Client disconnected"); + } + return -1; + default: + i_unreached(); + } + return 0; +} + +void client_worker_input(struct client *client) +{ + struct istream *input = client->ctrl_input; + const char *line; + + if (input->closed) { + /* disconnected */ + client_disconnect(client, "Worker disconnected unexpectedly"); + return; + } + + switch (i_stream_read(input)) { + case -1: + /* disconnected */ + client_disconnect(client, "Worker disconnected unexpectedly"); + return; + case -2: + /* input buffer full */ + client_disconnect(client, "Worker sent too large input"); + return; + } + + while ((line = i_stream_next_line(input)) != NULL) { + if (client_worker_input_line(client, line) < 0) + return; + } +} + +void client_destroy(struct client *client, const char *reason) +{ + i_assert(reason != NULL || client->disconnected); + + if (!client->disconnected) + e_info(client->event, "Disconnected: %s", reason); + + imap_urlauth_client_count--; + DLLIST_REMOVE(&imap_urlauth_clients, client); + + timeout_remove(&client->to_idle); + + client_worker_disconnect(client); + + o_stream_destroy(&client->output); + + fd_close_maybe_stdio(&client->fd_in, &client->fd_out); + + event_unref(&client->event); + + i_free(client->username); + i_free(client->service); + array_free(&client->access_apps); + i_free(client); + + master_service_client_connection_destroyed(master_service); + imap_urlauth_refresh_proctitle(); +} + +static void client_destroy_timeout(struct client *client) +{ + client_destroy(client, NULL); +} + +void client_disconnect(struct client *client, const char *reason) +{ + if (client->disconnected) + return; + + client->disconnected = TRUE; + e_info(client->event, "Disconnected: %s", reason); + + client->to_idle = timeout_add(0, client_destroy_timeout, client); +} + +void clients_destroy_all(void) +{ + while (imap_urlauth_clients != NULL) + client_destroy(imap_urlauth_clients, "Server shutting down."); +} |