]> git.ipfire.org Git - thirdparty/dovecot/core.git/commitdiff
doveadm: Implement HTTP server API, slightly based on JMAP API
authorAki Tuomi <aki.tuomi@dovecot.fi>
Fri, 19 Feb 2016 12:58:41 +0000 (14:58 +0200)
committerTimo Sirainen <timo.sirainen@dovecot.fi>
Fri, 19 Feb 2016 13:31:44 +0000 (15:31 +0200)
src/doveadm/Makefile.am
src/doveadm/client-connection-http.c [new file with mode: 0644]
src/doveadm/client-connection-private.h [new file with mode: 0644]
src/doveadm/client-connection.c
src/doveadm/client-connection.h
src/doveadm/doveadm-cmd.h
src/doveadm/doveadm-settings.c
src/doveadm/doveadm-settings.h
src/doveadm/main.c

index 80658ca4a7f5eabe07c88264b6303400f3bff782..75794ddcbdfe33b65e2e3550ade4fe9b8f632fcd 100644 (file)
@@ -20,6 +20,7 @@ AM_CPPFLAGS = \
        -I$(top_srcdir)/src/lib-imap \
        -I$(top_srcdir)/src/lib-index \
        -I$(top_srcdir)/src/lib-storage \
+       -I$(top_srcdir)/src/lib-http \
        -I$(top_srcdir)/src/auth \
        -DMODULEDIR=\""$(moduledir)"\" \
        -DAUTH_MODULE_DIR=\""$(moduledir)/auth"\" \
@@ -134,6 +135,7 @@ doveadm_SOURCES = \
 doveadm_server_SOURCES = \
        $(common) \
        client-connection.c \
+       client-connection-http.c \
        doveadm-print-server.c \
        doveadm-print-json.c \
        main.c
diff --git a/src/doveadm/client-connection-http.c b/src/doveadm/client-connection-http.c
new file mode 100644 (file)
index 0000000..e18cbcc
--- /dev/null
@@ -0,0 +1,637 @@
+/* Copyright (c) 2010-2016 Dovecot authors, see the included COPYING file */
+
+#include "lib.h"
+#include "compat.h"
+#include "lib-signals.h"
+#include "base64.h"
+#include "ioloop.h"
+#include "str.h"
+#include "istream.h"
+#include "ostream.h"
+#include "strescape.h"
+#include "settings-parser.h"
+#include "iostream-ssl.h"
+#include "iostream-temp.h"
+#include "istream-seekable.h"
+#include "master-service.h"
+#include "master-service-ssl.h"
+#include "master-service-settings.h"
+#include "mail-storage-service.h"
+#include "http-server.h"
+#include "http-request.h"
+#include "http-response.h"
+#include "http-url.h"
+#include "doveadm-util.h"
+#include "doveadm-server.h"
+#include "doveadm-mail.h"
+#include "doveadm-print.h"
+#include "doveadm-settings.h"
+#include "client-connection-private.h"
+#include "json-parser.h"
+
+#include <unistd.h>
+#include <ctype.h>
+
+struct client_connection_http {
+       struct client_connection client;
+       struct http_server_connection *http_client;
+       struct http_server_request *http_server_request;
+       const struct http_request *http_request;
+       struct http_server_response *http_response;
+       struct json_parser *json_parser;
+       const struct doveadm_cmd_ver2 *cmd;
+       struct doveadm_cmd_param *cmd_param;
+       ARRAY_TYPE(doveadm_cmd_param_arr_t) pargv;
+       int method_err;
+       char *method_id;
+       bool first_row;
+       bool value_is_array;
+       enum {
+               JSON_STATE_INIT,
+               JSON_STATE_COMMAND,
+               JSON_STATE_COMMAND_NAME,
+               JSON_STATE_COMMAND_PARAMETERS,
+               JSON_STATE_COMMAND_PARAMETER_KEY,
+               JSON_STATE_COMMAND_PARAMETER_VALUE,
+               JSON_STATE_COMMAND_ID,
+               JSON_STATE_COMMAND_DONE,
+               JSON_STATE_DONE
+       } json_state;
+};
+
+static struct http_server *doveadm_http_server;
+
+static void doveadm_http_server_handle_request(void *context, struct http_server_request *req);
+static void doveadm_http_server_connection_destroy(void *context, const char *reason);
+
+static const struct http_server_callbacks doveadm_http_callbacks = {
+        .connection_destroy = doveadm_http_server_connection_destroy,
+        .handle_request = doveadm_http_server_handle_request
+};
+
+static void doveadm_http_server_process_request(void *context);
+
+struct client_connection *
+client_connection_create_http(int fd, bool ssl)
+{
+       struct client_connection_http *conn;
+       pool_t pool;
+
+       pool = pool_alloconly_create("doveadm client", 1024*16);
+       conn = p_new(pool, struct client_connection_http, 1);
+       conn->client.pool = pool;
+
+       if (client_connection_init(&conn->client, fd) < 0)
+               return NULL;
+
+       conn->http_client = http_server_connection_create(doveadm_http_server,
+                       fd, fd, ssl, &doveadm_http_callbacks, conn);
+       conn->client.fd = -1;
+       return &conn->client;
+}
+
+static void
+doveadm_http_server_connection_destroy(void *context, const char *reason ATTR_UNUSED)
+{
+       struct client_connection *conn = context;
+       client_connection_destroy(&conn);
+}
+
+static void
+doveadm_http_server_request_destroy(void *context)
+{
+        struct client_connection_http *conn = context;
+       struct http_server_response *resp = 
+               http_server_request_get_response(conn->http_server_request);
+
+       if (resp != NULL) {
+               const char *agent;
+               const char *url;
+               int status;
+               const char *reason;
+               uoff_t size;
+               http_server_response_get_status(resp, &status, &reason);
+               size = http_server_response_get_total_size(resp);
+               agent = http_request_header_get(conn->http_request, "User-Agent");
+               if (agent == NULL) agent = "";
+               url = http_url_create(conn->http_request->target.url);
+               i_info("doveadm: %s %s %s \"%s %s HTTP/%d.%d\" %d %lu \"%s\" \"%s\"",
+                       net_ip2addr(&conn->client.remote_ip), "-", "-",
+                       conn->http_request->method,
+                       conn->http_request->target.url->path,
+                       conn->http_request->version_major, conn->http_request->version_minor,
+                       status, size,
+                       url,
+                       agent);
+       }
+       if (conn->json_parser != NULL) {
+               const char *error ATTR_UNUSED;
+               (void)json_parser_deinit(&conn->json_parser, &error);
+               // we've already failed, ignore error
+       }
+       http_server_request_unref(&(conn->http_server_request));
+       http_server_switch_ioloop(doveadm_http_server);
+        http_server_connection_unref(&(conn->http_client));
+       conn->http_client = NULL;
+}
+
+static void doveadm_http_server_json_error(void *context, const char *error)
+{
+       struct client_connection_http *conn = context;
+       string_t *escaped;
+       o_stream_nsend_str(conn->client.output,"[\"error\",{\"type\":\"");
+       escaped = str_new(conn->client.pool, 10);
+       json_append_escaped(escaped, error);
+       o_stream_nsend_str(conn->client.output,str_c(escaped));
+       o_stream_nsend_str(conn->client.output,"\", \"exitCode\":");
+        str_truncate(escaped,0);
+       str_printfa(escaped,"%d", doveadm_exit_code);
+       o_stream_nsend_str(conn->client.output, str_c(escaped));
+       o_stream_nsend_str(conn->client.output,"},\"");
+       str_truncate(escaped,0);
+       if (conn->method_id != NULL) {
+               json_append_escaped(escaped, conn->method_id);
+               o_stream_nsend_str(conn->client.output, str_c(escaped));
+       }
+       o_stream_nsend_str(conn->client.output,"\"]");
+}
+
+static void doveadm_http_server_json_success(void *context, struct istream *result)
+{
+       struct client_connection_http *conn = context;
+       string_t *escaped;
+       escaped = str_new(conn->client.pool, 10);
+       o_stream_nsend_str(conn->client.output,"[\"doveadmResponse\",");
+       o_stream_send_istream(conn->client.output, result);
+       o_stream_nsend_str(conn->client.output,",\"");
+       if (conn->method_id != NULL) {
+               json_append_escaped(escaped, conn->method_id);
+               o_stream_nsend_str(conn->client.output, str_c(escaped));
+       }
+       o_stream_nsend_str(conn->client.output,"\"]");
+}
+
+/**
+ * this is to ensure we can handle arrays and other special parameter types
+ */
+static int doveadm_http_server_json_parse_next(struct client_connection_http *conn, enum json_type *type, const char **value)
+{
+       int rc;
+       const char *tmp;
+       if (conn->json_state == JSON_STATE_COMMAND_PARAMETER_VALUE) {
+               if (conn->cmd_param->type == CMD_PARAM_ISTREAM) {
+                       if (conn->cmd_param->value_set == TRUE)
+                               return json_parse_next(conn->json_parser, type, value);
+                       struct istream* is[2] = {0};
+                       rc = json_parse_next_stream(conn->json_parser, &is[0]);
+                       if (rc != 1) return rc;
+                       conn->cmd_param->value_set = TRUE;
+                       conn->cmd_param->value.v_istream = i_stream_create_seekable_path(is, IO_BLOCK_SIZE, "/tmp/doveadm.");
+                       rc = json_parse_next(conn->json_parser, type, value);
+                       conn->json_state = JSON_STATE_COMMAND_PARAMETER_KEY;
+                       return rc;
+               }
+               rc = json_parse_next(conn->json_parser, type, value);
+               if (rc != 1) return rc;
+               if (*type == JSON_TYPE_ARRAY_END) {
+                       conn->json_state = JSON_STATE_COMMAND_PARAMETER_KEY;
+                       return json_parse_next(conn->json_parser, type, value);
+               }
+               /* allow array for cmd_param_array only, and only once */
+               if (*type == JSON_TYPE_ARRAY && conn->cmd_param->type != CMD_PARAM_ARRAY)
+                       return -2;
+               if (*type == JSON_TYPE_ARRAY) { /* start of array */
+                       conn->value_is_array = TRUE;
+                       rc = json_parse_next(conn->json_parser, type, value);
+                       if (rc != 1) return rc;
+               }
+               if (conn->cmd_param->type == CMD_PARAM_ARRAY) {
+                       if (*type != JSON_TYPE_STRING) {
+                               /* FIXME: should handle other than string too */
+                               return -2;
+                       }
+                       conn->cmd_param->value_set = TRUE;
+                       if (!array_is_created(&conn->cmd_param->value.v_array))
+                               p_array_init(&conn->cmd_param->value.v_array,conn->client.pool,1);
+                       if (conn->value_is_array) {
+                               while(rc == 1) {
+                                       tmp = p_strdup(conn->client.pool,*value);
+                                       array_append(&conn->cmd_param->value.v_array, &tmp, 1);
+                                       rc = json_parse_next(conn->json_parser, type, value);
+                                       if (*type == JSON_TYPE_ARRAY_END) {
+                                               conn->json_state = JSON_STATE_COMMAND_PARAMETER_KEY;
+                                               return json_parse_next(conn->json_parser, type, value);
+                                       }
+                               }
+                       } else {
+                               tmp = p_strdup(conn->client.pool,*value);
+                               array_append(&conn->cmd_param->value.v_array, &tmp, 1);
+                               conn->json_state = JSON_STATE_COMMAND_PARAMETER_KEY;
+                               return json_parse_next(conn->json_parser, type, value);
+                       }
+                       return rc;
+               } else {
+                       conn->cmd_param->value_set = TRUE;
+                       switch(conn->cmd_param->type) {
+                               case CMD_PARAM_NONE:
+                               case CMD_PARAM_BOOL:
+                                       conn->cmd_param->value.v_bool = (strcmp(*value,"true")==0); break;
+                               case CMD_PARAM_INT64:
+                                       if (str_to_int64(*value, &conn->cmd_param->value.v_int64) != 0) {
+                                               conn->method_err = 400;
+                                       }
+                                       break;
+                               case CMD_PARAM_STR:
+                                       conn->cmd_param->value.v_string = p_strdup(conn->client.pool, *value); break;
+                               default:
+                                       break;
+                       }
+               }
+               rc = json_parse_next(conn->json_parser, type, value);
+               conn->json_state = JSON_STATE_COMMAND_PARAMETER_KEY;
+               return rc;
+       } else {
+               rc = json_parse_next(conn->json_parser, type, value); /* just get next */
+               return rc;
+       }
+}
+
+static void
+doveadm_http_server_command_execute(struct client_connection_http *conn)
+{
+        const struct doveadm_cmd_param *cpar;
+       int rc;
+
+       /* final preflight check */
+       if (!doveadm_client_is_allowed_command(conn->client.set, conn->cmd->name))
+               conn->method_err = 403;
+       if (conn->method_err != 0) {
+               if (conn->method_err == 404) {
+                       doveadm_http_server_json_error(conn, "unknownMethod");
+               } else if (conn->method_err == 403) {
+                       doveadm_http_server_json_error(conn, "unAuthorized");
+               } else if (conn->method_err == 400) {
+                       doveadm_http_server_json_error(conn, "invalidRequest");
+               } else {
+                       doveadm_http_server_json_error(conn, "internalError");
+               }
+               return;
+       }
+
+       int pargc;
+       struct istream *is;
+       struct ioloop *ioloop,*prev_ioloop = current_ioloop;
+
+       // create iostream
+       doveadm_print_ostream = iostream_temp_create("/tmp/doveadm.", 0);
+
+       doveadm_print_init(DOVEADM_PRINT_TYPE_JSON);
+       /* then call it */
+       cpar = array_get(&conn->pargv, (unsigned int*)&pargc);
+       ioloop = io_loop_create();
+       lib_signals_reset_ioloop();
+       doveadm_exit_code = 0;
+       rc = conn->cmd->cmd(conn->cmd,pargc, cpar);
+
+       io_loop_set_current(prev_ioloop);
+       lib_signals_reset_ioloop();
+       o_stream_switch_ioloop(conn->client.output);
+       io_loop_set_current(ioloop);
+       io_loop_destroy(&ioloop);
+
+       doveadm_print_deinit();
+       if (o_stream_nfinish(doveadm_print_ostream)<0) {
+               i_info("doveadm(%s): Error writing output in command %s: %s",
+                      i_stream_get_name(conn->client.input), conn->cmd->name,
+                      o_stream_get_error(conn->client.output));
+               doveadm_exit_code = EX_TEMPFAIL;
+       }
+
+       is = iostream_temp_finish(&doveadm_print_ostream, 4096);
+       doveadm_print_ostream = NULL;
+
+       if (conn->first_row == TRUE) {
+               conn->first_row = FALSE;
+       } else {
+               o_stream_nsend_str(conn->client.output,",");
+       }
+
+       if (rc != 0 || doveadm_exit_code != 0) {
+               if (doveadm_exit_code == 0 || doveadm_exit_code == EX_TEMPFAIL)
+                       i_error("doveadm(%s): Command %s failed", i_stream_get_name(conn->client.input), conn->cmd->name);
+               doveadm_http_server_json_error(conn, "exitCode");
+       } else {
+               doveadm_http_server_json_success(conn, is);
+       }
+       i_stream_unref(&is);
+}
+
+static void
+doveadm_http_server_read_request(struct client_connection_http *conn)
+{
+       enum json_type type;
+       const char *value, *error;
+       const struct doveadm_cmd_ver2 *ccmd;
+       struct doveadm_cmd_param *par;
+       int rc;
+       bool found;
+
+       if (conn->json_parser == NULL)
+               conn->json_parser = json_parser_init_flags(conn->client.input, JSON_PARSER_NO_ROOT_OBJECT);
+
+       while((rc = doveadm_http_server_json_parse_next(conn, &type, &value)) == 1) {
+               if (conn->json_state == JSON_STATE_INIT) {
+                       if (type != JSON_TYPE_ARRAY) break;
+                       conn->json_state = JSON_STATE_COMMAND;
+                       conn->first_row = TRUE;
+                       o_stream_nsend_str(conn->client.output,"[");
+               } else if (conn->json_state == JSON_STATE_COMMAND) {
+                       if (type == JSON_TYPE_ARRAY_END) {
+                               conn->json_state = JSON_STATE_DONE;
+                               continue;
+                       }
+                       if (type != JSON_TYPE_ARRAY) break;
+                       conn->method_err = 0;
+                       if (conn->method_id != NULL)
+                               p_free(conn->client.pool, conn->method_id);
+                       conn->method_id = NULL;
+                       conn->cmd = NULL;
+                       array_clear(&conn->pargv);
+                       conn->json_state = JSON_STATE_COMMAND_NAME;
+               } else if (conn->json_state == JSON_STATE_COMMAND_NAME) {
+                       if (type != JSON_TYPE_STRING) break;
+                       /* see if we can find it */
+                       found = FALSE;
+                       array_foreach(&doveadm_cmds_ver2, ccmd) {
+                               if (i_strccdascmp(ccmd->name, value) == 0) {
+                                       conn->cmd = ccmd;
+                                       found = TRUE;
+                                       break;
+                               }
+                       }
+                       if (!found) {
+                               json_parse_skip_next(conn->json_parser);
+                               conn->json_state = JSON_STATE_COMMAND_ID;
+                               conn->method_err = 404;
+                       } else {
+                               const struct doveadm_cmd_param *cpar;
+                               /* initialize pargv */
+                               for(cpar = conn->cmd->parameters; cpar->name != NULL; cpar++)
+                                       array_append(&conn->pargv, cpar, 1);
+                               conn->json_state = JSON_STATE_COMMAND_PARAMETERS;
+                       }
+               } else if (conn->json_state == JSON_STATE_COMMAND_PARAMETERS) {
+                       if (type == JSON_TYPE_OBJECT_END) {
+                               conn->json_state = JSON_STATE_COMMAND_ID;
+                               continue;
+                       }
+                       if (type != JSON_TYPE_OBJECT) break;
+                       conn->json_state = JSON_STATE_COMMAND_PARAMETER_KEY;
+               } else if (conn->json_state == JSON_STATE_COMMAND_PARAMETER_KEY) {
+                        if (type == JSON_TYPE_OBJECT_END) {
+                               conn->json_state = JSON_STATE_COMMAND_ID;
+                               continue;
+                       }
+                       // can happen...
+                       if (type != JSON_TYPE_OBJECT_KEY && type != JSON_TYPE_STRING) break;
+                       /* go hunting */
+                       found = FALSE;
+                       array_foreach_modifiable(&conn->pargv, par) {
+                               if (i_strccdascmp(par->name, value) == 0) {
+                                       /* it's already set, cannot have same key twice in json */
+                                       if (par->value_set) break;
+                                       conn->cmd_param = par;
+                                       found = TRUE;
+                                       break;
+                               }
+                       }
+                       /* skip parameters if error has already occured */
+                       if (!found || conn->method_err != 0) {
+                               json_parse_skip_next(conn->json_parser);
+                               conn->json_state = JSON_STATE_COMMAND_PARAMETER_KEY;
+                               conn->method_err = 400;
+                       } else {
+                               if (conn->cmd_param->value_set) {
+                                       // FIXME: should be returned as error to client, not logged
+                                       i_info("doveadm(%s): Parameter %s already set",
+                                              i_stream_get_name(conn->client.input),
+                                              conn->cmd_param->name);
+                                       break;
+                               }
+                               conn->value_is_array = FALSE;
+                               conn->json_state = JSON_STATE_COMMAND_PARAMETER_VALUE;
+                       }
+               } else if (conn->json_state == JSON_STATE_COMMAND_ID) {
+                       if (type != JSON_TYPE_STRING) break;
+                       conn->method_id = p_strdup(conn->client.pool, value);
+                       conn->json_state = JSON_STATE_COMMAND_DONE;
+               } else if (conn->json_state == JSON_STATE_COMMAND_DONE) {
+                       /* should be end of array */
+                       if (type != JSON_TYPE_ARRAY_END) break;
+                       doveadm_http_server_command_execute(conn);
+                       conn->json_state = JSON_STATE_COMMAND;
+               } else if (conn->json_state == JSON_STATE_DONE) {
+                       // FIXME: should be returned as error to client, not logged
+                       i_info("doveadm(%s): Got unexpected elements in JSON data", i_stream_get_name(conn->client.input));
+                       continue;
+               }
+       }
+
+       if (!conn->client.input->eof && rc == 0)
+               return;
+
+       if ((rc == 1 && conn->json_state != JSON_STATE_DONE)) {
+               /* this will happen if the parser above runs into unexpected element, but JSON is OK */
+               http_server_request_fail_close(conn->http_server_request, 400, "Unexpected element in input");
+               // FIXME: should be returned as error to client, not logged
+               i_info("doveadm(%s): unexpected element", i_stream_get_name(conn->client.input));
+               return;
+       }
+
+       if (conn->client.input->stream_errno != 0) {
+               http_server_request_fail_close(conn->http_server_request, 400, "Client disconnected");
+               i_info("doveadm(%s): read(client) failed: %s",
+                      i_stream_get_name(conn->client.input),
+                      i_stream_get_error(conn->client.input));
+               return;
+       }
+
+       if (json_parser_deinit(&conn->json_parser, &error) != 0) {
+               http_server_request_fail_close(conn->http_server_request, 400, "Invalid JSON input");
+               // FIXME: should be returned as error to client, not logged
+               i_info("doveadm(%s): %s", i_stream_get_name(conn->client.input), error);
+               return;
+       }
+
+       conn->json_parser = NULL;
+
+       io_remove(&conn->client.io);
+       conn->client.io = NULL;
+       i_stream_unref(&conn->client.input);
+       conn->client.input = NULL;
+
+       if (conn->client.output != NULL)
+               o_stream_nsend_str(conn->client.output,"]");
+
+       doveadm_http_server_process_request(conn);
+}
+
+static void doveadm_http_server_camelcase_value(string_t *value)
+{
+       size_t i,k;
+       char *ptr = str_c_modifiable(value);
+       for(i=0,k=0; i < strlen(ptr);) {
+               if (ptr[i] == ' ' || ptr[i] == '-') {
+                       i++;
+                       ptr[k++] = i_toupper(ptr[i++]);
+               } else ptr[k++] = ptr[i++];
+       }
+       str_truncate(value, k);
+}
+
+static void
+doveadm_http_server_send_api(struct client_connection_http *conn)
+{
+       string_t *tmp = str_new(conn->client.pool, 8);
+       size_t i,k;
+       const struct doveadm_cmd_ver2 *cmd;
+       const struct doveadm_cmd_param *par;
+       bool sent;
+       o_stream_nsend_str(conn->client.output,"[\n");
+       for(i = 0; i < array_count(&doveadm_cmds_ver2); i++) {
+               cmd = array_idx(&doveadm_cmds_ver2, i);
+               if (i>0) o_stream_nsend_str(conn->client.output, ",\n");
+               o_stream_nsend_str(conn->client.output, "\t{\"command\":\"");
+               json_append_escaped(tmp, cmd->name);
+               doveadm_http_server_camelcase_value(tmp);
+               o_stream_nsend_str(conn->client.output, str_c(tmp));
+               o_stream_nsend_str(conn->client.output, "\", \"parameters\":[");
+               sent = FALSE;
+               for(k=0;cmd->parameters[k].name != NULL; k++) {
+                       str_truncate(tmp, 0);
+                       par = &(cmd->parameters[k]);
+                       if ((par->flags & CMD_PARAM_FLAG_DO_NOT_EXPOSE) != 0)
+                               continue;
+                       if (sent)
+                               o_stream_nsend_str(conn->client.output, ",\n");
+                       else
+                               o_stream_nsend_str(conn->client.output, "\n");
+                       sent = TRUE;
+                       o_stream_nsend_str(conn->client.output, "\t\t{\"name\":\"");
+                       json_append_escaped(tmp, par->name);
+                       doveadm_http_server_camelcase_value(tmp);
+                       o_stream_nsend_str(conn->client.output, str_c(tmp));
+                       o_stream_nsend_str(conn->client.output, "\",\"type\":\"");
+                       switch(par->type) {
+                       case CMD_PARAM_NONE:
+                       case CMD_PARAM_BOOL:
+                               o_stream_nsend_str(conn->client.output, "boolean");
+                               break;
+                       case CMD_PARAM_INT64:
+                               o_stream_nsend_str(conn->client.output, "integer");
+                               break;
+                       case CMD_PARAM_ARRAY:
+                               o_stream_nsend_str(conn->client.output, "array");
+                               break;
+                       case CMD_PARAM_ISTREAM:
+                       case CMD_PARAM_STR:
+                               o_stream_nsend_str(conn->client.output, "string");
+                       }
+                       o_stream_nsend_str(conn->client.output, "\"}");
+               }
+               if (k>0) o_stream_nsend_str(conn->client.output,"\n\t");
+               o_stream_nsend_str(conn->client.output,"]}");
+               str_truncate(tmp, 0);
+       }
+       o_stream_nsend_str(conn->client.output,"\n]");
+}
+
+static void
+doveadm_http_server_handle_request(void *context, struct http_server_request *req)
+{
+       struct client_connection_http *conn = context;
+       conn->http_server_request = req;
+       conn->http_request = http_server_request_get(req);
+       const char *path = conn->http_request->target.url->path;
+
+       http_server_connection_ref(conn->http_client);
+       http_server_request_set_destroy_callback(req, doveadm_http_server_request_destroy, conn);
+       http_server_request_ref(conn->http_server_request);
+
+       if (doveadm_settings->doveadm_api_key != NULL) {
+               const char *key;
+               key = http_request_header_get(conn->http_request, "X-API-Key");
+               if (key == NULL || strcmp(doveadm_settings->doveadm_api_key, key) != 0) {
+                       http_server_request_fail_close(req, 403, "Not authorized");
+                       return;
+               }
+       }
+
+       if (strcmp(conn->http_request->method, "GET") != 0 &&
+               strcmp(conn->http_request->method, "POST") != 0) {
+               http_server_request_fail_close(req, 405, "Method Not Allowed");
+               return;
+       }
+
+       if (strcasecmp(path, "/doveadm") != 0) {
+               http_server_request_fail_close(req, 404, "Object Not Found");
+               return;
+       }
+
+       conn->http_response = http_server_response_create(req, 200, "OK");
+       http_server_response_add_header(conn->http_response, "Content-Type",
+               "application/json; charset=utf-8");
+
+        if (strcmp(conn->http_request->method, "GET") == 0) {
+                /* print out API */
+               conn->client.output = iostream_temp_create_named("/tmp", 0, net_ip2addr(&conn->client.remote_ip));
+                doveadm_http_server_send_api(conn);
+               doveadm_http_server_process_request(context);
+        } else if (strcmp(conn->http_request->method, "POST") == 0) {
+               /* handle request */
+               conn->client.input = conn->http_request->payload;
+               i_stream_set_name(conn->client.input, net_ip2addr(&conn->client.remote_ip));
+               i_stream_ref(conn->client.input);
+               conn->client.io = io_add_istream(conn->client.input,
+                                        doveadm_http_server_read_request, conn);
+               conn->client.output = iostream_temp_create_named("/tmp", 0, net_ip2addr(&conn->client.remote_ip));
+               p_array_init(&conn->pargv, conn->client.pool, 5);
+               doveadm_http_server_read_request(conn);
+       } else {
+               i_unreached();
+       }
+}
+
+static void doveadm_http_server_process_request(void *context)
+{
+       struct client_connection_http *conn = context;
+
+       if (conn->client.output != NULL) {
+               if (o_stream_nfinish(conn->client.output) == -1) {
+                       i_info("doveadm(%s): error writing output: %s",
+                              net_ip2addr(&conn->client.remote_ip),
+                              o_stream_get_error(conn->client.output));
+                       http_server_response_update_status(conn->http_response, 500, "Internal server error");
+               } else {
+                       // read the response
+                       http_server_response_set_payload(conn->http_response, iostream_temp_finish(&conn->client.output, IO_BLOCK_SIZE));
+               }
+       }
+       // submit response
+       http_server_response_submit_close(conn->http_response);
+}
+
+static const struct http_server_settings http_server_set = {
+        .max_pipelined_requests = 0
+};
+
+void doveadm_http_server_init(void)
+{
+       doveadm_http_server = http_server_init(&http_server_set);
+       
+}
+
+void doveadm_http_server_deinit(void)
+{
+       http_server_deinit(&doveadm_http_server);
+}      
diff --git a/src/doveadm/client-connection-private.h b/src/doveadm/client-connection-private.h
new file mode 100644 (file)
index 0000000..48a1c15
--- /dev/null
@@ -0,0 +1,14 @@
+#ifndef CLIENT_CONNECTION_PRIVATE_H
+#define CLIENT_CONNECTION_PRIVATE_H
+
+#include "client-connection.h"
+
+bool doveadm_client_is_allowed_command(const struct doveadm_settings *set,
+       const char *cmd_name);
+
+int client_connection_init(struct client_connection *conn, int fd);
+
+void doveadm_http_server_init(void);
+void doveadm_http_server_deinit(void);
+
+#endif
index baad9b8d8dad347092ee9d28856e0ee28f9c2117..db0cb1953cc2b06f7f9848c3f92a0a1fc0c82a6d 100644 (file)
@@ -18,7 +18,7 @@
 #include "doveadm-mail.h"
 #include "doveadm-print.h"
 #include "doveadm-settings.h"
-#include "client-connection.h"
+#include "client-connection-private.h"
 
 #include <unistd.h>
 
@@ -40,16 +40,11 @@ static struct {
 static void client_connection_input(struct client_connection *conn);
 
 static void
-doveadm_cmd_server_run(struct client_connection *conn,
-                      const struct doveadm_cmd *cmd, int argc, char *argv[])
+doveadm_cmd_server_post(struct client_connection *conn, const char *cmd_name)
 {
        const char *str = NULL;
        unsigned int i;
 
-       i_getopt_reset();
-       doveadm_exit_code = 0;
-       cmd->cmd(argc, argv);
-
        if (doveadm_exit_code == 0) {
                o_stream_nsend(conn->output, "\n+\n", 3);
                return;
@@ -67,10 +62,32 @@ doveadm_cmd_server_run(struct client_connection *conn,
        } else {
                o_stream_nsend_str(conn->output, "\n-\n");
                i_error("BUG: Command '%s' returned unknown error code %d",
-                       cmd->name, doveadm_exit_code);
+                       cmd_name, doveadm_exit_code);
        }
 }
 
+static void
+doveadm_cmd_server_run_ver2(struct client_connection *conn,
+                           const struct doveadm_cmd_ver2 *cmd,
+                           int argc, const char *argv[])
+{
+       i_getopt_reset();
+       doveadm_exit_code = 0;
+       if (doveadm_cmd_run_ver2(cmd, argc, argv) < 0)
+               doveadm_exit_code = EX_USAGE;
+       doveadm_cmd_server_post(conn, cmd->name);
+}
+
+static void
+doveadm_cmd_server_run(struct client_connection *conn,
+                      const struct doveadm_cmd *cmd, int argc, char *argv[])
+{
+       i_getopt_reset();
+       doveadm_exit_code = 0;
+       cmd->cmd(argc, argv);
+       doveadm_cmd_server_post(conn, cmd->name);
+}
+
 static int
 doveadm_mail_cmd_server_parse(const struct doveadm_mail_cmd *cmd,
                              const struct doveadm_settings *set,
@@ -178,8 +195,8 @@ doveadm_mail_cmd_server_run(struct client_connection *conn,
        pool_unref(&ctx->pool);
 }
 
-static bool client_is_allowed_command(const struct doveadm_settings *set,
-                                     const char *cmd_name)
+bool doveadm_client_is_allowed_command(const struct doveadm_settings *set,
+                                      const char *cmd_name)
 {
        bool ret = FALSE;
 
@@ -208,17 +225,20 @@ static int doveadm_cmd_handle(struct client_connection *conn,
        const struct doveadm_cmd *cmd;
        const struct doveadm_mail_cmd *mail_cmd;
        struct doveadm_mail_cmd_context *ctx;
-
-       cmd = doveadm_cmd_find(cmd_name, &argc, &argv);
-       if (cmd == NULL) {
-               mail_cmd = doveadm_mail_cmd_find(cmd_name);
-               if (mail_cmd == NULL) {
-                       i_error("doveadm: Client sent unknown command: %s", cmd_name);
-                       return -1;
+       const struct doveadm_cmd_ver2 *cmd_ver2;
+
+       if ((cmd_ver2 = doveadm_cmd_find_ver2(cmd_name, argc, (const char**)argv)) == NULL) {
+               cmd = doveadm_cmd_find(cmd_name, &argc, &argv);
+               if (cmd == NULL) {
+                       mail_cmd = doveadm_mail_cmd_find(cmd_name);
+                       if (mail_cmd == NULL) {
+                               i_error("doveadm: Client sent unknown command: %s", cmd_name);
+                               return -1;
+                       }
+                       if (doveadm_mail_cmd_server_parse(mail_cmd, conn->set, input,
+                                                         argc, argv, &ctx) < 0)
+                               return -1;
                }
-               if (doveadm_mail_cmd_server_parse(mail_cmd, conn->set, input,
-                                                 argc, argv, &ctx) < 0)
-                       return -1;
        }
 
        /* some commands will want to call io_loop_run(), but we're already
@@ -227,7 +247,9 @@ static int doveadm_cmd_handle(struct client_connection *conn,
        ioloop = io_loop_create();
        lib_signals_reset_ioloop();
 
-       if (cmd != NULL)
+       if (cmd_ver2 != NULL)
+               doveadm_cmd_server_run_ver2(conn, cmd_ver2, argc, (const char**)argv);
+       else if (cmd != NULL)
                doveadm_cmd_server_run(conn, cmd, argc, argv);
        else
                doveadm_mail_cmd_server_run(conn, ctx, input);
@@ -241,7 +263,7 @@ static int doveadm_cmd_handle(struct client_connection *conn,
        /* clear all headers */
        doveadm_print_deinit();
        doveadm_print_init(DOVEADM_PRINT_TYPE_SERVER);
-       return 0;
+       return doveadm_exit_code == 0 ? 0 : -1;
 }
 
 static bool client_handle_command(struct client_connection *conn, char **args)
@@ -289,7 +311,7 @@ static bool client_handle_command(struct client_connection *conn, char **args)
                }
        }
 
-       if (!client_is_allowed_command(conn->set, cmd_name)) {
+       if (!doveadm_client_is_allowed_command(conn->set, cmd_name)) {
                i_error("doveadm client isn't allowed to use command: %s",
                        cmd_name);
                return FALSE;
@@ -482,7 +504,7 @@ client_connection_send_auth_handshake(struct client_connection *
        }
 }
 
-static int client_connection_init(struct client_connection *conn, int fd)
+int client_connection_init(struct client_connection *conn, int fd)
 {
        const char *ip;
 
@@ -543,10 +565,16 @@ void client_connection_destroy(struct client_connection **_conn)
 
        if (conn->ssl_iostream != NULL)
                ssl_iostream_destroy(&conn->ssl_iostream);
-       i_stream_destroy(&conn->input);
-       o_stream_destroy(&conn->output);
-       io_remove(&conn->io);
-       if (close(conn->fd) < 0)
+
+       if (conn->output != NULL)
+               o_stream_destroy(&conn->output);
+
+       if (conn->io != NULL) {
+               i_stream_destroy(&conn->input);
+               io_remove(&conn->io);
+       }
+
+       if (conn->fd > 0 && close(conn->fd) < 0)
                i_error("close(client) failed: %m");
        pool_unref(&conn->pool);
 
index a1d134cb71d0bd7ab23febfb914453d5b5c0a0ae..1daf8fd7a86a8bad583b6824b3fce9299047daca 100644 (file)
@@ -21,6 +21,8 @@ struct client_connection {
 
 struct client_connection *
 client_connection_create(int fd, int listen_fd, bool ssl);
+struct client_connection *
+client_connection_create_http(int fd, bool ssl);
 void client_connection_destroy(struct client_connection **conn);
 
 #endif
index 372873c81a53cb474eeefc961ea22c7e6b4e5d4a..29c82cc7624b2ce793330eb3a72c963837c77b8b 100644 (file)
@@ -39,6 +39,7 @@ struct doveadm_cmd_param {
        } value;
        doveadm_cmd_param_flag_t flags;
 };
+ARRAY_DEFINE_TYPE(doveadm_cmd_param_arr_t, struct doveadm_cmd_param);
 
 typedef int doveadm_command_ver2_t(const struct doveadm_cmd_ver2* cmd,
        int argc, const struct doveadm_cmd_param[]);
index 1c4b5b0c56fd4c08b6307133b28ecd73b77a35f5..20d68818a71bcf22e899194143895a8e374af682 100644 (file)
@@ -69,6 +69,7 @@ static const struct setting_define doveadm_setting_defines[] = {
        DEF(SET_STR, ssl_client_ca_dir),
        DEF(SET_STR, ssl_client_ca_file),
        DEF(SET_STR, director_username_hash),
+       DEF(SET_STR, doveadm_api_key),
 
        { SET_STRLIST, "plugin", offsetof(struct doveadm_settings, plugin_envs), NULL },
 
@@ -92,6 +93,7 @@ const struct doveadm_settings doveadm_default_settings = {
        .ssl_client_ca_dir = "",
        .ssl_client_ca_file = "",
        .director_username_hash = "%Lu",
+       .doveadm_api_key = NULL,
 
        .plugin_envs = ARRAY_INIT
 };
index 5c3f1a2c141debf0b3b0ce8139220c5c9c677d6e..10c6e69a326b36add8a3042b5542bbc6d85d64b1 100644 (file)
@@ -20,6 +20,7 @@ struct doveadm_settings {
        const char *ssl_client_ca_dir;
        const char *ssl_client_ca_file;
        const char *director_username_hash;
+       const char *doveadm_api_key;
 
        ARRAY(const char *) plugin_envs;
 };
index f9314f16b9e5c774f4e985a6e0466d815a351636..26b3502fec44431149f03c353bb72cea922bd6db 100644 (file)
@@ -6,14 +6,17 @@
 #include "master-service-settings.h"
 #include "settings-parser.h"
 #include "client-connection.h"
+#include "client-connection-private.h"
 #include "doveadm-settings.h"
 #include "doveadm-dump.h"
 #include "doveadm-mail.h"
 #include "doveadm-print-private.h"
 #include "doveadm-server.h"
+#include "ostream.h"
 
 const struct doveadm_print_vfuncs *doveadm_print_vfuncs_all[] = {
        &doveadm_print_server_vfuncs,
+       &doveadm_print_json_vfuncs,
        NULL
 };
 
@@ -33,8 +36,12 @@ static void client_connected(struct master_service_connection *conn)
        }
 
        master_service_client_connection_accept(conn);
-       doveadm_client = client_connection_create(conn->fd, conn->listen_fd,
-                                                 conn->ssl);
+       if (strcmp(conn->name, "http") == 0) {
+               doveadm_client = client_connection_create_http(conn->fd, conn->ssl);
+       } else {
+               doveadm_client = client_connection_create(conn->fd, conn->listen_fd,
+                                                         conn->ssl);
+       }
 }
 
 void help(const struct doveadm_cmd *cmd)
@@ -43,6 +50,12 @@ void help(const struct doveadm_cmd *cmd)
                cmd->name, cmd->short_usage);
 }
 
+void help_ver2(const struct doveadm_cmd_ver2 *cmd)
+{
+       i_fatal("Client sent invalid command. Usage: %s %s",
+               cmd->name, cmd->usage);
+}
+
 static void main_preinit(void)
 {
        restrict_access_by_env(NULL, FALSE);
@@ -57,12 +70,11 @@ static void main_init(void)
                                        doveadm_settings,
                                        pool_datastack_create());
 
+       doveadm_http_server_init();
        doveadm_cmds_init();
        doveadm_dump_init();
        doveadm_mail_init();
        doveadm_load_modules();
-       doveadm_print_init(DOVEADM_PRINT_TYPE_SERVER);
-        doveadm_print_init(DOVEADM_PRINT_TYPE_JSON);
 }
 
 static void main_deinit(void)
@@ -74,6 +86,7 @@ static void main_deinit(void)
        doveadm_unload_modules();
        doveadm_print_deinit();
        doveadm_cmds_deinit();
+       doveadm_http_server_deinit();
 }
 
 int main(int argc, char *argv[])