Mercurial > dovecot > core-2.2
changeset 19769:9324bdd97612
doveadm: Implement HTTP server API, slightly based on JMAP API
author | Aki Tuomi <aki.tuomi@dovecot.fi> |
---|---|
date | Fri, 19 Feb 2016 14:58:41 +0200 |
parents | 603fdee3685f |
children | 7a5ab5070433 |
files | src/doveadm/Makefile.am src/doveadm/client-connection-http.c src/doveadm/client-connection-private.h 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 |
diffstat | 9 files changed, 731 insertions(+), 31 deletions(-) [+] |
line wrap: on
line diff
--- a/src/doveadm/Makefile.am Fri Feb 19 14:40:59 2016 +0200 +++ b/src/doveadm/Makefile.am Fri Feb 19 14:58:41 2016 +0200 @@ -20,6 +20,7 @@ -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_server_SOURCES = \ $(common) \ client-connection.c \ + client-connection-http.c \ doveadm-print-server.c \ doveadm-print-json.c \ main.c
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/doveadm/client-connection-http.c Fri Feb 19 14:58:41 2016 +0200 @@ -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); +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/doveadm/client-connection-private.h Fri Feb 19 14:58:41 2016 +0200 @@ -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
--- a/src/doveadm/client-connection.c Fri Feb 19 14:40:59 2016 +0200 +++ b/src/doveadm/client-connection.c Fri Feb 19 14:58:41 2016 +0200 @@ -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 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 @@ } 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 @@ 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 @@ const struct doveadm_cmd *cmd; const struct doveadm_mail_cmd *mail_cmd; struct doveadm_mail_cmd_context *ctx; + const struct doveadm_cmd_ver2 *cmd_ver2; - 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 ((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 @@ 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 @@ /* 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 @@ } } - 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 @@ } } -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 @@ 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);
--- a/src/doveadm/client-connection.h Fri Feb 19 14:40:59 2016 +0200 +++ b/src/doveadm/client-connection.h Fri Feb 19 14:58:41 2016 +0200 @@ -21,6 +21,8 @@ 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
--- a/src/doveadm/doveadm-cmd.h Fri Feb 19 14:40:59 2016 +0200 +++ b/src/doveadm/doveadm-cmd.h Fri Feb 19 14:58:41 2016 +0200 @@ -39,6 +39,7 @@ } 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[]);
--- a/src/doveadm/doveadm-settings.c Fri Feb 19 14:40:59 2016 +0200 +++ b/src/doveadm/doveadm-settings.c Fri Feb 19 14:58:41 2016 +0200 @@ -69,6 +69,7 @@ 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 @@ .ssl_client_ca_dir = "", .ssl_client_ca_file = "", .director_username_hash = "%Lu", + .doveadm_api_key = NULL, .plugin_envs = ARRAY_INIT };
--- a/src/doveadm/doveadm-settings.h Fri Feb 19 14:40:59 2016 +0200 +++ b/src/doveadm/doveadm-settings.h Fri Feb 19 14:58:41 2016 +0200 @@ -20,6 +20,7 @@ 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; };
--- a/src/doveadm/main.c Fri Feb 19 14:40:59 2016 +0200 +++ b/src/doveadm/main.c Fri Feb 19 14:58:41 2016 +0200 @@ -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 @@ } 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 @@ 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 @@ 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 @@ doveadm_unload_modules(); doveadm_print_deinit(); doveadm_cmds_deinit(); + doveadm_http_server_deinit(); } int main(int argc, char *argv[])