/* Copyright (c) 2010-2018 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 "str-sanitize.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>
enum client_request_parse_state {
};
struct client_request_http {
int method_err;
char *method_id;
bool first_row;
bool value_is_array;
};
struct client_connection_http {
};
struct doveadm_http_server_mount {
const char *verb;
const char *path;
bool auth;
};
/*
* API
*/
static void doveadm_http_server_options_handler(struct client_request_http *);
static void doveadm_http_server_print_mounts(struct client_request_http *);
static void doveadm_http_server_send_api_v1(struct client_request_http *);
static void doveadm_http_server_read_request_v1(struct client_request_http *);
{
.verb = "OPTIONS",
},{
.verb = "GET",
.path = "/",
},{
.verb = "GET",
},{
.verb = "POST",
}
};
{
str_truncate(escaped,0);
str_truncate(escaped,0);
}
}
{
}
}
static void
{
const char *user;
/* final preflight check */
if (req->method_err == 0 &&
if (req->method_err != 0) {
} else {
}
return;
}
// create iostream
/* then call it */
ioloop = io_loop_create();
doveadm_exit_code = 0;
else
if (o_stream_finish(doveadm_print_ostream) < 0) {
i_info("Error writing output in command %s: %s",
}
else
if (doveadm_exit_code != 0) {
} else {
}
i_stream_unref(&is);
}
static int
{
const char *value;
int ret;
return ret;
if (type != JSON_TYPE_ARRAY) {
/* request must be a JSON array */
400, "Bad Request",
"Request must be a JSON array");
return -1;
}
/* next: parse the next command */
return 1;
}
static int
{
const char *value;
int ret;
return ret;
if (type == JSON_TYPE_ARRAY_END) {
/* end of command list */
return 1;
}
if (type != JSON_TYPE_ARRAY) {
/* command must be an array */
400, "Bad Request",
"Command must be a JSON array");
return -1;
}
req->method_err = 0;
/* next: parse the command name */
return 1;
}
static int
{
const char *value;
bool found;
return ret;
if (type != JSON_TYPE_STRING) {
/* command name must be a string */
400, "Bad Request",
"Command name must be a string");
return -1;
}
/* see if we can find it */
break;
}
}
if (!found) {
/* command not found; skip to the command ID */
return 1;
}
/* initialize pargv */
}
/* next: parse the command parameters */
return 1;
}
static int
{
const char *value;
int ret;
return ret;
if (type == JSON_TYPE_OBJECT_END) {
/* empty command parameters object; parse command ID next */
return 1;
}
if (type != JSON_TYPE_OBJECT) {
/* parameters must be contained in an object */
400, "Bad Request",
"Parameters must be contained in a JSON object");
return -1;
}
/* next: parse parameter key */
return 1;
}
static int
{
const char *value;
bool found;
int ret;
return ret;
if (type == JSON_TYPE_OBJECT_END) {
/* end of parameters; parse command ID next */
return 1;
}
/* find the parameter */
break;
}
}
/* it's already set, cannot have same key twice in json */
400, "Bad Request",
"Parameter `%s' is duplicated",
return -1;
}
/* skip remaining parameters if error has already occurred */
return 1;
}
/* next: parse parameter value */
return 1;
}
static int
{
const char *value;
int ret;
/* read the value as a stream */
if (ret <= 0)
return ret;
IO_BLOCK_SIZE, "/tmp/doveadm.");
i_stream_unref(&is[0]);
/* read the seekable stream to its end so that the underlying
json istream is read to its conclusion. */
return 1;
}
return ret;
const char *tmp;
/* expects either a singular value or an array of values */
if (type == JSON_TYPE_ARRAY) {
/* start of array */
return 1;
}
/* singular value */
if (type != JSON_TYPE_STRING) {
/* FIXME: should handle other than string too */
400, "Bad Request",
"Parameter `%s' must be string or array",
return -1;
}
/* next: continue with the next parameter */
return 1;
}
/* expects just a value */
case CMD_PARAM_BOOL:
break;
case CMD_PARAM_INT64:
}
break;
case CMD_PARAM_IP:
}
break;
case CMD_PARAM_STR:
break;
default:
break;
}
/* next: continue with the next parameter */
return 1;
}
static int
{
const char *value;
int ret;
return ret;
if (type == JSON_TYPE_ARRAY_END) {
/* end of array: continue with next parameter */
return 1;
}
if (type != JSON_TYPE_STRING) {
/* array items must be string */
400, "Bad Request",
"Command parameter array can only contain"
"string values");
return -1;
}
/* record entry */
/* next: continue with the next array item */
return 1;
}
static int
{
const unsigned char *data;
/* more to read */
return 0;
}
if (v_input->stream_errno != 0) {
i_error("read(%s) failed: %s",
400, "Bad Request",
"Failed to read command parameter data");
}
return -1;
}
/* next: continue with the next parameter */
return 1;
}
static int
{
const char *value;
int ret;
return ret;
if (type != JSON_TYPE_STRING) {
/* command ID must be a string */
400, "Bad Request",
"Command ID must be a string");
return -1;
}
/* next: parse end of command */
return 1;
}
static int
{
const char *value;
int ret;
return ret;
if (type != JSON_TYPE_ARRAY_END) {
/* command array must end here */
400, "Bad Request",
"Unexpected JSON element at end of command");
return -1;
}
/* execute command */
/* next: parse next command */
return 1;
}
static int
{
const char *value;
int ret;
return ret;
/* only gets here when there is spurious additional JSON */
400, "Bad Request",
"Unexpected JSON element in input");
return -1;
}
static int
{
/* parser state machine */
switch (req->parse_state) {
/* command list: '[' */
return request_json_parse_init(req);
/* command begin: '[' */
case CLIENT_REQUEST_PARSE_CMD:
return request_json_parse_cmd(req);
/* command name: string */
return request_json_parse_cmd_name(req);
/* command parameters: '{' */
return request_json_parse_cmd_params(req);
/* parameter key: string */
return request_json_parse_cmd_param_key(req);
/* parameter value */
return request_json_parse_param_value(req);
/* parameter array value */
return request_json_parse_param_array(req);
/* parameter istream value */
return request_json_parse_param_istream(req);
/* command ID: string */
return request_json_parse_cmd_id(req);
/* command end: ']' */
return request_json_parse_cmd_done(req);
/* finished parsing request (seen final ']') */
return request_json_parse_done(req);
default:
break;
}
i_unreached();
}
static void
{
const char *error;
int ret;
}
/* already responded */
return;
}
return;
400, "Client disconnected");
i_info("read(%s) failed: %s",
return;
}
400, "Bad Request",
"JSON parse error: %s", error);
return;
}
}
{
size_t i, k;
i++;
} else {
}
}
str_truncate(value, k);
}
static void
{
unsigned int i, k;
bool sent;
for (i = 0; i < array_count(&doveadm_cmds_ver2); i++) {
if (i > 0)
str_truncate(tmp, 0);
continue;
if (sent)
else
case CMD_PARAM_BOOL:
break;
case CMD_PARAM_INT64:
break;
case CMD_PARAM_ARRAY:
break;
case CMD_PARAM_IP:
case CMD_PARAM_ISTREAM:
case CMD_PARAM_STR:
}
}
if (k > 0)
str_truncate(tmp, 0);
}
}
static void
{
"Access-Control-Allow-Origin", "*");
"Access-Control-Allow-Methods", "GET, POST, OPTIONS");
"Access-Control-Allow-Request-Headers",
"Content-Type, X-API-Key, Authorization");
"Access-Control-Allow-Headers",
"Content-Type, WWW-Authenticate");
}
static void
{
unsigned int i;
for (i = 0; i < N_ELEMENTS(doveadm_http_server_mounts); i++) {
if (i > 0)
else {
}
else {
}
}
}
/*
* Request
*/
{
i_info("error writing output: %s",
500, "Internal server error");
return;
}
}
"application/json; charset=utf-8");
}
}
static void
{
int status;
i_info("doveadm: %s %s %s \"%s %s "
}
// we've already failed, ignore error
}
}
static bool
const struct http_auth_credentials *creds)
{
char *value;
i_error("Invalid authentication attempt to HTTP API: "
"Basic authentication scheme not enabled");
return FALSE;
}
return TRUE;
i_error("Invalid authentication attempt to HTTP API "
"(using Basic authentication scheme)");
return FALSE;
}
static bool
const struct http_auth_credentials *creds)
{
i_error("Invalid authentication attempt to HTTP API: "
"X-Dovecot-API authentication scheme not enabled");
return FALSE;
}
return TRUE;
i_error("Invalid authentication attempt to HTTP API "
"(using X-Dovecot-API authentication scheme)");
return FALSE;
}
static bool
const struct http_auth_credentials *creds)
{
/* see if the mech is supported */
i_error("Unsupported authentication scheme to HTTP API: %s",
return FALSE;
}
static bool
{
/* no authentication specified */
500, "Internal Server Error");
i_error("No authentication defined in configuration. "
"Add API key or password");
return FALSE;
}
if (!auth) {
401, "Authentication required");
"WWW-Authenticate", "X-Dovecot-API"
);
}
"WWW-Authenticate", "Basic Realm=\"doveadm\""
);
}
}
return auth;
}
static void
struct http_server_request *http_sreq)
{
unsigned int i;
/* no pipelining possible due to synchronous handling of requests */
for (i = 0; i < N_ELEMENTS(doveadm_http_server_mounts); i++) {
doveadm_http_server_mounts[i].verb) == 0) {
doveadm_http_server_mounts[i].path) == 0) {
ep = &doveadm_http_server_mounts[i];
break;
}
}
}
404, "Path Not Found");
return;
}
return;
/* handle request */
} else {
}
}
/*
* Connection
*/
static void doveadm_http_server_connection_destroy(void *context,
const char *reason);
};
static void
{
(struct client_connection_http *)_conn;
callback. */
"Server shutting down");
}
}
struct client_connection *
{
return NULL;
}
}
static void
const char *reason ATTR_UNUSED)
{
(struct client_connection_http *)context;
/* already destroying client directly */
return;
}
/* HTTP connection is destroyed already now */
/* destroy the connection itself */
}
/*
* Server
*/
void doveadm_http_server_init(void)
{
};
}
void doveadm_http_server_deinit(void)
{
}