diff options
Diffstat (limited to '')
-rw-r--r-- | src/doveadm/doveadm-zlib.c | 297 |
1 files changed, 297 insertions, 0 deletions
diff --git a/src/doveadm/doveadm-zlib.c b/src/doveadm/doveadm-zlib.c new file mode 100644 index 0000000..f9bb233 --- /dev/null +++ b/src/doveadm/doveadm-zlib.c @@ -0,0 +1,297 @@ +/* Copyright (c) 2010-2018 Dovecot authors, see the included COPYING file */ + +#include "lib.h" +#include "net.h" +#include "istream.h" +#include "ostream.h" +#include "istream-zlib.h" +#include "ostream-zlib.h" +#include "module-dir.h" +#include "master-service.h" +#include "compression.h" +#include "doveadm-dump.h" + +#include <stdio.h> +#include <fcntl.h> +#include <unistd.h> + +static bool test_dump_imapzlib(const char *path) +{ + const char *p; + char buf[4096]; + int fd, ret; + bool match = FALSE; + + p = strrchr(path, '.'); + if (p == NULL || (strcmp(p, ".in") != 0 && strcmp(p, ".out") != 0)) + return FALSE; + + fd = open(path, O_RDONLY); + if (fd == -1) + return FALSE; + + ret = read(fd, buf, sizeof(buf)-1); + if (ret > 0) { + buf[ret] = '\0'; + (void)str_lcase(buf); + match = strstr(buf, " ok begin compression.") != NULL || + strstr(buf, " compress deflate") != NULL; + } + i_close_fd(&fd); + return match; +} + +#ifdef HAVE_ZLIB +static void +cmd_dump_imapzlib(const char *path, const char *const *args ATTR_UNUSED) +{ + struct istream *input, *input2; + const unsigned char *data; + size_t size; + const char *line; + int fd; + + fd = open(path, O_RDONLY); + if (fd < 0) + i_fatal("open(%s) failed: %m", path); + input = i_stream_create_fd_autoclose(&fd, 1024*32); + while ((line = i_stream_read_next_line(input)) != NULL) { + /* skip tag */ + printf("%s\r\n", line); + while (*line != ' ' && *line != '\0') line++; + if (*line == '\0') + continue; + line++; + + if (str_begins(line, "OK Begin compression") || + strcasecmp(line, "COMPRESS DEFLATE") == 0) + break; + } + + input2 = i_stream_create_deflate(input); + i_stream_unref(&input); + + while (i_stream_read_more(input2, &data, &size) != -1) { + if (fwrite(data, 1, size, stdout) != size) + break; + i_stream_skip(input2, size); + } + if (input2->stream_errno != 0) + i_error("read(%s) failed: %s", path, i_stream_get_error(input)); + i_stream_unref(&input2); + fflush(stdout); +} + +struct client { + int fd; + struct io *io_client, *io_server; + struct istream *input, *stdin_input; + struct ostream *output; + const struct compression_handler *handler; + char *algorithm; + bool compressed; + bool compress_waiting; +}; + +static bool +client_input_get_compress_algorithm(struct client *client, const char *line) +{ + /* skip tag */ + while (*line != ' ' && *line != '\0') + line++; + if (strncasecmp(line, " COMPRESS ", 10) != 0) + return FALSE; + + if (compression_lookup_handler(t_str_lcase(line+10), + &client->handler) <= 0) + i_fatal("Unsupported compression mechanism: %s", line+10); + return TRUE; +} + +static bool client_input_uncompressed(struct client *client) +{ + const char *line; + + if (client->compress_waiting) { + /* just read all the pipelined input for now */ + (void)i_stream_read(client->stdin_input); + return TRUE; + } + + while ((line = i_stream_read_next_line(client->stdin_input)) != NULL) { + o_stream_nsend_str(client->output, line); + o_stream_nsend(client->output, "\n", 1); + if (client_input_get_compress_algorithm(client, line)) + return TRUE; + } + return FALSE; +} + +static void client_input(struct client *client) +{ + const unsigned char *data; + size_t size; + + if (!client->compressed && + client_input_uncompressed(client)) { + /* stop until server has sent reply to COMPRESS command. */ + client->compress_waiting = TRUE; + return; + } + if (client->compressed) { + if (i_stream_read_more(client->stdin_input, &data, &size) > 0) { + o_stream_nsend(client->output, data, size); + i_stream_skip(client->stdin_input, size); + } + if (o_stream_flush(client->output) < 0) { + i_fatal("write() failed: %s", + o_stream_get_error(client->output)); + } + } + if (client->stdin_input->eof) { + if (client->stdin_input->stream_errno != 0) { + i_fatal("read(stdin) failed: %s", + i_stream_get_error(client->stdin_input)); + } + master_service_stop(master_service); + } +} + +static bool server_input_is_compress_reply(const char *line) +{ + /* skip tag */ + while (*line != ' ' && *line != '\0') + line++; + return str_begins(line, " OK Begin compression"); +} + +static bool server_input_uncompressed(struct client *client) +{ + const char *line; + + while ((line = i_stream_read_next_line(client->input)) != NULL) { + if (write(STDOUT_FILENO, line, strlen(line)) < 0) + i_fatal("write(stdout) failed: %m"); + if (write(STDOUT_FILENO, "\n", 1) < 0) + i_fatal("write(stdout) failed: %m"); + if (server_input_is_compress_reply(line)) + return TRUE; + } + return FALSE; +} + +static void server_input(struct client *client) +{ + const unsigned char *data; + size_t size; + + if (i_stream_read(client->input) == -1) { + if (client->input->stream_errno != 0) { + i_fatal("read(server) failed: %s", + i_stream_get_error(client->input)); + } + + i_info("Server disconnected"); + master_service_stop(master_service); + return; + } + + if (!client->compressed && server_input_uncompressed(client)) { + /* start compression */ + struct istream *input; + struct ostream *output; + + i_info("<Compression started>"); + input = client->handler->create_istream(client->input); + output = client->handler->create_ostream(client->output, 6); + i_stream_unref(&client->input); + o_stream_unref(&client->output); + client->input = input; + client->output = output; + client->compressed = TRUE; + client->compress_waiting = FALSE; + i_stream_set_input_pending(client->stdin_input, TRUE); + } + + data = i_stream_get_data(client->input, &size); + if (write(STDOUT_FILENO, data, size) < 0) + i_fatal("write(stdout) failed: %m"); + i_stream_skip(client->input, size); +} + +static void cmd_zlibconnect(struct doveadm_cmd_context *cctx) +{ + struct client client; + const char *host; + struct ip_addr *ips; + unsigned int ips_count; + int64_t port_int64; + in_port_t port = 143; + int fd, ret; + + if (!doveadm_cmd_param_str(cctx, "host", &host)) + help_ver2(&doveadm_cmd_zlibconnect); + if (doveadm_cmd_param_int64(cctx, "port", &port_int64)) { + if (port_int64 == 0 || port_int64 > 65535) + i_fatal("Invalid port: %"PRId64, port_int64); + port = (in_port_t)port_int64; + } + + ret = net_gethostbyname(host, &ips, &ips_count); + if (ret != 0) { + i_fatal("Host %s lookup failed: %s", host, + net_gethosterror(ret)); + } + + if ((fd = net_connect_ip(&ips[0], port, NULL)) == -1) + i_fatal("connect(%s, %u) failed: %m", host, port); + + i_info("Connected to %s port %u.", net_ip2addr(&ips[0]), port); + + i_zero(&client); + client.fd = fd; + fd_set_nonblock(STDIN_FILENO, TRUE); + client.stdin_input = i_stream_create_fd(STDIN_FILENO, SIZE_MAX); + client.input = i_stream_create_fd(fd, SIZE_MAX); + client.output = o_stream_create_fd(fd, 0); + o_stream_set_no_error_handling(client.output, TRUE); + client.io_client = io_add_istream(client.stdin_input, client_input, &client); + client.io_server = io_add_istream(client.input, server_input, &client); + master_service_run(master_service, NULL); + io_remove(&client.io_client); + io_remove(&client.io_server); + i_stream_unref(&client.stdin_input); + i_stream_unref(&client.input); + o_stream_unref(&client.output); + if (close(fd) < 0) + i_fatal("close() failed: %m"); +} +#else +static void +cmd_dump_imapzlib(const char *path ATTR_UNUSED, + const char *const *args ATTR_UNUSED) +{ + i_fatal("Dovecot compiled without zlib support"); +} + +static void cmd_zlibconnect(struct doveadm_cmd_context *cctx ATTR_UNUSED) +{ + i_fatal("Dovecot compiled without zlib support"); +} +#endif + +struct doveadm_cmd_dump doveadm_cmd_dump_zlib = { + "imapzlib", + test_dump_imapzlib, + cmd_dump_imapzlib +}; + +struct doveadm_cmd_ver2 doveadm_cmd_zlibconnect = { + .name = "zlibconnect", + .cmd = cmd_zlibconnect, + .usage = "<host> [<port>]", +DOVEADM_CMD_PARAMS_START +DOVEADM_CMD_PARAM('\0', "host", CMD_PARAM_STR, CMD_PARAM_FLAG_POSITIONAL) +DOVEADM_CMD_PARAM('\0', "port", CMD_PARAM_INT64, CMD_PARAM_FLAG_POSITIONAL) +DOVEADM_CMD_PARAMS_END +}; |