From a8d1a344e580f29699aed9b88d87fc3c6f5d113b Mon Sep 17 00:00:00 2001 From: Simo Sorce Date: Thu, 7 Jan 2016 10:17:35 -0500 Subject: Secrets: Add initial responder code for secrets service Start implementing the Secrets Service Reponder core. This commit implements stratup and basic conenction handling and HTTP parsing (using the http-parser library). Signed-off-by: Simo Sorce Related: https://fedorahosted.org/sssd/ticket/2913 Reviewed-by: Jakub Hrozek --- src/responder/common/responder.h | 6 + src/responder/common/responder_common.c | 17 +- src/responder/secrets/secsrv.c | 192 ++++++++++++ src/responder/secrets/secsrv.h | 40 +++ src/responder/secrets/secsrv_cmd.c | 507 ++++++++++++++++++++++++++++++++ 5 files changed, 750 insertions(+), 12 deletions(-) create mode 100644 src/responder/secrets/secsrv.c create mode 100644 src/responder/secrets/secsrv.h create mode 100644 src/responder/secrets/secsrv_cmd.c (limited to 'src/responder') diff --git a/src/responder/common/responder.h b/src/responder/common/responder.h index 8db9e9894..23e7d46f8 100644 --- a/src/responder/common/responder.h +++ b/src/responder/common/responder.h @@ -319,6 +319,12 @@ bool sss_utf8_check(const uint8_t *s, size_t n); void responder_set_fd_limit(rlim_t fd_limit); +errno_t reset_idle_timer(struct cli_ctx *cctx); +void idle_handler(struct tevent_context *ev, + struct tevent_timer *te, + struct timeval current_time, + void *data); + #define GET_DOMAINS_DEFAULT_TIMEOUT 60 struct tevent_req *sss_dp_get_domains_send(TALLOC_CTX *mem_ctx, diff --git a/src/responder/common/responder_common.c b/src/responder/common/responder_common.c index 6fb2ed365..f519b8488 100644 --- a/src/responder/common/responder_common.c +++ b/src/responder/common/responder_common.c @@ -347,8 +347,6 @@ static void client_recv(struct cli_ctx *cctx) return; } -static errno_t reset_idle_timer(struct cli_ctx *cctx); - static void client_fd_handler(struct tevent_context *ev, struct tevent_fd *fde, uint16_t flags, void *ptr) @@ -381,11 +379,6 @@ struct accept_fd_ctx { connection_setup_t connection_setup; }; -static void idle_handler(struct tevent_context *ev, - struct tevent_timer *te, - struct timeval current_time, - void *data); - static void accept_fd_handler(struct tevent_context *ev, struct tevent_fd *fde, uint16_t flags, void *ptr) @@ -521,7 +514,7 @@ static void accept_fd_handler(struct tevent_context *ev, return; } -static errno_t reset_idle_timer(struct cli_ctx *cctx) +errno_t reset_idle_timer(struct cli_ctx *cctx) { struct timeval tv = tevent_timeval_current_ofs(cctx->rctx->client_idle_timeout, 0); @@ -538,10 +531,10 @@ static errno_t reset_idle_timer(struct cli_ctx *cctx) return EOK; } -static void idle_handler(struct tevent_context *ev, - struct tevent_timer *te, - struct timeval current_time, - void *data) +void idle_handler(struct tevent_context *ev, + struct tevent_timer *te, + struct timeval current_time, + void *data) { /* This connection is idle. Terminate it */ struct cli_ctx *cctx = diff --git a/src/responder/secrets/secsrv.c b/src/responder/secrets/secsrv.c new file mode 100644 index 000000000..523aa6469 --- /dev/null +++ b/src/responder/secrets/secsrv.c @@ -0,0 +1,192 @@ +/* + SSSD + + Secrets Responder + + Copyright (C) Simo Sorce 2016 + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#include "util/util.h" +#include +#include +#include + +#include "responder/common/responder.h" +#include "responder/secrets/secsrv.h" + +#define DEFAULT_SEC_FD_LIMIT 2048 + +static int sec_get_config(struct sec_ctx *sctx) +{ + int ret; + + ret = confdb_get_int(sctx->rctx->cdb, + CONFDB_SEC_CONF_ENTRY, + CONFDB_SERVICE_FD_LIMIT, + DEFAULT_SEC_FD_LIMIT, + &sctx->fd_limit); + if (ret != EOK) { + DEBUG(SSSDBG_FATAL_FAILURE, + "Failed to get file descriptors limit\n"); + goto fail; + } + + ret = confdb_get_int(sctx->rctx->cdb, sctx->rctx->confdb_service_path, + CONFDB_RESPONDER_CLI_IDLE_TIMEOUT, + CONFDB_RESPONDER_CLI_IDLE_DEFAULT_TIMEOUT, + &sctx->rctx->client_idle_timeout); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, + "Cannot get the client idle timeout [%d]: %s\n", + ret, strerror(ret)); + goto fail; + } + + /* Ensure that the client timeout is at least ten seconds */ + if (sctx->rctx->client_idle_timeout < 10) { + sctx->rctx->client_idle_timeout = 10; + } + + ret = EOK; + +fail: + return ret; +} + +static int sec_responder_ctx_destructor(void *ptr) +{ + struct resp_ctx *rctx = talloc_get_type(ptr, struct resp_ctx); + + /* mark that we are shutting down the responder, so it is propagated + * into underlying contexts that are freed right before rctx */ + DEBUG(SSSDBG_TRACE_FUNC, "Responder is being shut down\n"); + rctx->shutting_down = true; + + return 0; +} + +static int sec_process_init(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct confdb_ctx *cdb) +{ + struct resp_ctx *rctx; + struct sec_ctx *sctx; + int ret; + + rctx = talloc_zero(mem_ctx, struct resp_ctx); + if (!rctx) { + DEBUG(SSSDBG_FATAL_FAILURE, "fatal error initializing resp_ctx\n"); + return ENOMEM; + } + rctx->ev = ev; + rctx->cdb = cdb; + rctx->sock_name = SSS_SEC_SOCKET_NAME; + rctx->confdb_service_path = CONFDB_SEC_CONF_ENTRY; + rctx->shutting_down = false; + + talloc_set_destructor((TALLOC_CTX*)rctx, sec_responder_ctx_destructor); + + sctx = talloc_zero(rctx, struct sec_ctx); + if (!sctx) { + DEBUG(SSSDBG_FATAL_FAILURE, "fatal error initializing sec_ctx\n"); + ret = ENOMEM; + goto fail; + } + + sctx->rctx = rctx; + sctx->rctx->pvt_ctx = sctx; + + ret = sec_get_config(sctx); + if (ret != EOK) { + DEBUG(SSSDBG_FATAL_FAILURE, "fatal error getting secrets config\n"); + goto fail; + } + + /* Set up file descriptor limits */ + responder_set_fd_limit(sctx->fd_limit); + + ret = activate_unix_sockets(rctx, sec_connection_setup); + if (ret != EOK) goto fail; + + DEBUG(SSSDBG_TRACE_FUNC, "Secrets Initialization complete\n"); + + return EOK; + +fail: + talloc_free(rctx); + return ret; +} + +int main(int argc, const char *argv[]) +{ + int opt; + poptContext pc; + struct main_context *main_ctx; + int ret; + uid_t uid; + gid_t gid; + + struct poptOption long_options[] = { + POPT_AUTOHELP + SSSD_MAIN_OPTS + SSSD_SERVER_OPTS(uid, gid) + POPT_TABLEEND + }; + + /* Set debug level to invalid value so we can deside if -d 0 was used. */ + debug_level = SSSDBG_INVALID; + + umask(DFL_RSP_UMASK); + + pc = poptGetContext(argv[0], argc, argv, long_options, 0); + while((opt = poptGetNextOpt(pc)) != -1) { + switch(opt) { + default: + fprintf(stderr, "\nInvalid option %s: %s\n\n", + poptBadOption(pc, 0), poptStrerror(opt)); + poptPrintUsage(pc, stderr, 0); + return 1; + } + } + + poptFreeContext(pc); + + DEBUG_INIT(debug_level); + + /* set up things like debug, signals, daemonization, etc... */ + debug_log_file = "sssd_secrets"; + + ret = server_setup("sssd[secrets]", 0, uid, gid, CONFDB_SEC_CONF_ENTRY, + &main_ctx); + if (ret != EOK) return 2; + + ret = die_if_parent_died(); + if (ret != EOK) { + /* This is not fatal, don't return */ + DEBUG(SSSDBG_OP_FAILURE, + "Could not set up to exit when parent process does\n"); + } + + ret = sec_process_init(main_ctx, + main_ctx->event_ctx, + main_ctx->confdb_ctx); + if (ret != EOK) return 3; + + /* loop on main */ + server_loop(main_ctx); + + return 0; +} diff --git a/src/responder/secrets/secsrv.h b/src/responder/secrets/secsrv.h new file mode 100644 index 000000000..91535ae2b --- /dev/null +++ b/src/responder/secrets/secsrv.h @@ -0,0 +1,40 @@ +/* + SSSD + + Secrets Responder, header file + + Copyright (C) Simo Sorce 2016 + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#ifndef __SECSRV_H__ +#define __SECSRV_H__ + +#include "config.h" + +#include +#include +#include +#include +#include + +struct sec_ctx { + struct resp_ctx *rctx; + int fd_limit; +}; + +int sec_connection_setup(struct cli_ctx *cctx); + +#endif /* __SECSRV_H__ */ diff --git a/src/responder/secrets/secsrv_cmd.c b/src/responder/secrets/secsrv_cmd.c new file mode 100644 index 000000000..e96610017 --- /dev/null +++ b/src/responder/secrets/secsrv_cmd.c @@ -0,0 +1,507 @@ +/* + SSSD + + Secrets Responder + + Copyright (C) Simo Sorce 2016 + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#include "util/util.h" +#include "responder/common/responder.h" +#include "responder/secrets/secsrv.h" +#include "confdb/confdb.h" +#include + +#define SEC_REQUEST_MAX_SIZE 65536 +#define SEC_PACKET_MAX_RECV_SIZE 8192 + + +struct sec_kvp { + char *name; + char *value; +}; + +struct sec_data { + char *data; + size_t length; +}; + +struct sec_proto_ctx { + http_parser_settings callbacks; + http_parser parser; +}; + +struct sec_req_ctx { + struct cli_ctx *cctx; + bool complete; + + size_t total_size; + + char *request_url; + struct sec_kvp *headers; + int num_headers; + struct sec_data body; + + struct sec_data reply; +}; + + +/* ##### Request Handling ##### */ + +static int error_403_reply(TALLOC_CTX *mem_ctx, + struct sec_data *reply) +{ + reply->data = talloc_asprintf(mem_ctx, + "HTTP/1.1 403 Forbidden\r\n" + "Content-Length: 0\r\n" + "\r\n" + "\r\n" + "\r\n\r\n403 Forbidden\r\n" + "\r\n

Forbidden

\r\n" + "

You don't have permission to access the requested " + "resource.

\r\n
\r\n"); + if (!reply->data) return ENOMEM; + + reply->length = strlen(reply->data); + + return EOK; +} + +struct sec_http_request_state { + struct tevent_context *ev; + struct sec_req_ctx *secreq; +}; + +static struct tevent_req *sec_http_request_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct sec_req_ctx *secreq) +{ + struct tevent_req *req; + struct sec_http_request_state *state; + int ret; + + req = tevent_req_create(mem_ctx, &state, struct sec_http_request_state); + if (!req) return NULL; + + state->ev = ev; + state->secreq = secreq; + + /* Take it form here */ + + /* For now, always return an error */ + ret = error_403_reply(state, &state->secreq->reply); + if (ret != EOK) goto done; + +done: + if (ret != EOK) { + tevent_req_error(req, ret); + } else { + tevent_req_done(req); + } + return tevent_req_post(req, state->ev); +} + +static int sec_http_request_recv(struct tevent_req *req) +{ + TEVENT_REQ_RETURN_ON_ERROR(req); + + return EOK; +} + +/* --- */ + +static void +sec_http_request_done(struct tevent_req *req) +{ + struct cli_ctx *cctx = tevent_req_callback_data(req, struct cli_ctx); + int ret; + + ret = sec_http_request_recv(req); + if (ret != EOK) { + DEBUG(SSSDBG_FATAL_FAILURE, + "Failed to find reply, aborting client!\n"); + talloc_free(cctx); + return; + } + + /* Turn writable on so we can write back the reply */ + TEVENT_FD_WRITEABLE(cctx->cfde); +} + +static void sec_cmd_execute(struct cli_ctx *cctx) +{ + struct sec_req_ctx *secreq; + struct tevent_req *req; + + secreq = talloc_get_type(cctx->state_ctx, struct sec_req_ctx); + + req = sec_http_request_send(secreq, cctx->ev, secreq); + if (!req) { + DEBUG(SSSDBG_CRIT_FAILURE, + "Failed to schedule secret retrieval\n."); + talloc_free(cctx); + return; + } + tevent_req_set_callback(req, sec_http_request_done, cctx); +} + + +/* ##### HTTP Parsing Callbacks ##### */ + +static void sec_append_string(TALLOC_CTX *memctx, char **dest, + const char *src, size_t len) +{ + if (*dest) { + *dest = talloc_strndup_append_buffer(*dest, src, len); + } else { + *dest = talloc_strndup(memctx, src, len); + } +} + +static bool sec_too_much_data(struct sec_req_ctx *req, size_t length) +{ + req->total_size += length; + if (req->total_size > SEC_REQUEST_MAX_SIZE) { + DEBUG(SSSDBG_FATAL_FAILURE, + "Request too big, aborting client!\n"); + return true; + } + return false; +} + +static int sec_on_message_begin(http_parser *parser) +{ + DEBUG(SSSDBG_TRACE_INTERNAL, "HTTP Message parsing begins\n"); + + return 0; +} + +static int sec_on_url(http_parser *parser, + const char *at, size_t length) +{ + struct sec_req_ctx *req = + talloc_get_type(parser->data, struct sec_req_ctx); + + if (sec_too_much_data(req, length)) return -1; + + sec_append_string(req, &req->request_url, at, length); + if (!req->request_url) { + DEBUG(SSSDBG_FATAL_FAILURE, + "Failed to store URL, aborting client!\n"); + return -1; + } + return 0; +} + +static int sec_on_header_field(http_parser *parser, + const char *at, size_t length) +{ + struct sec_req_ctx *req = + talloc_get_type(parser->data, struct sec_req_ctx); + int n = req->num_headers; + + if (sec_too_much_data(req, length)) return -1; + + if (!req->headers) { + req->headers = talloc_zero_array(req, struct sec_kvp, 10); + } else if ((n % 10 == 0) && + (req->headers[n - 1].value)) { + req->headers = talloc_realloc(req, req->headers, + struct sec_kvp, n + 10); + if (req->headers) { + memset(&req->headers[n], 0, sizeof(struct sec_kvp) * 10); + } + } + if (!req->headers) { + DEBUG(SSSDBG_FATAL_FAILURE, + "Failed to store headers, aborting client!\n"); + return -1; + } + + if (!n || req->headers[n - 1].value) { + /* new field */ + n++; + } + sec_append_string(req->headers, &req->headers[n - 1].name, at, length); + if (!req->headers[n - 1].name) { + DEBUG(SSSDBG_FATAL_FAILURE, + "Failed to store header name, aborting client!\n"); + return -1; + } + + return 0; +} + +static int sec_on_header_value(http_parser *parser, + const char *at, size_t length) +{ + struct sec_req_ctx *req = + talloc_get_type(parser->data, struct sec_req_ctx); + int n = req->num_headers; + + if (sec_too_much_data(req, length)) return -1; + + if (!req->headers) { + DEBUG(SSSDBG_FATAL_FAILURE, + "Invalid headers pointer, aborting client!\n"); + return -1; + } + + if (req->headers[n].name && !req->headers[n].value) { + /* we increment on new value */ + n = ++req->num_headers; + } + + sec_append_string(req->headers, &req->headers[n - 1].value, at, length); + if (!req->headers[n - 1].value) { + DEBUG(SSSDBG_FATAL_FAILURE, + "Failed to store header value, aborting client!\n"); + return -1; + } + + return 0; +} + +static int sec_on_headers_complete(http_parser *parser) +{ + /* TODO: if message has no body we should return 1 */ + return 0; +} + +static int sec_on_body(http_parser *parser, + const char *at, size_t length) +{ + struct sec_req_ctx *req = + talloc_get_type(parser->data, struct sec_req_ctx); + + if (sec_too_much_data(req, length)) return -1; + + sec_append_string(req, &req->body.data, at, length); + if (!req->body.data) { + DEBUG(SSSDBG_FATAL_FAILURE, + "Failed to store body, aborting client!\n"); + return -1; + } + req->body.length += length; + + return 0; +} + +static int sec_on_message_complete(http_parser *parser) +{ + struct sec_req_ctx *req = + talloc_get_type(parser->data, struct sec_req_ctx); + + req->complete = true; + + return 0; +} + + +/* ##### Communications ##### */ + +static int sec_send_data(int fd, struct sec_data *data) +{ + ssize_t len; + + errno = 0; + len = send(fd, data->data, data->length, 0); + if (len == -1) { + if (errno == EAGAIN || errno == EWOULDBLOCK || errno == EINTR) { + return EAGAIN; + } else { + return errno; + } + } + + if (len == 0) { + return EIO; + } + + data->length -= len; + data->data += len; + return EOK; +} + +static void sec_send(struct cli_ctx *cctx) +{ + struct sec_req_ctx *req; + int ret; + + req = talloc_get_type(cctx->state_ctx, struct sec_req_ctx); + + ret = sec_send_data(cctx->cfd, &req->reply); + if (ret == EAGAIN) { + /* not all data was sent, loop again */ + return; + } + if (ret != EOK) { + DEBUG(SSSDBG_FATAL_FAILURE, "Failed to send data, aborting client!\n"); + talloc_free(cctx); + return; + } + + /* ok all sent */ + TEVENT_FD_NOT_WRITEABLE(cctx->cfde); + TEVENT_FD_READABLE(cctx->cfde); + talloc_zfree(cctx->state_ctx); + return; +} + +static int sec_recv_data(int fd, struct sec_data *data) +{ + ssize_t len; + + errno = 0; + len = recv(fd, data->data, data->length, 0); + if (len == -1) { + if (errno == EAGAIN || errno == EWOULDBLOCK || errno == EINTR) { + return EAGAIN; + } else { + return errno; + } + } + + if (len == 0) { + return ENODATA; + } + + data->length = len; + return EOK; +} + +static void sec_recv(struct cli_ctx *cctx) +{ + struct sec_proto_ctx *prctx; + struct sec_req_ctx *req; + char buffer[SEC_PACKET_MAX_RECV_SIZE]; + struct sec_data data = { buffer, + SEC_PACKET_MAX_RECV_SIZE }; + size_t len; + int ret; + + prctx = talloc_get_type(cctx->protocol_ctx, struct sec_proto_ctx); + req = talloc_get_type(cctx->state_ctx, struct sec_req_ctx); + if (!req) { + /* A new request comes in, setup data structures */ + req = talloc_zero(cctx, struct sec_req_ctx); + if (!req) { + DEBUG(SSSDBG_FATAL_FAILURE, + "Failed to setup request handlers, aborting client\n"); + talloc_free(cctx); + return; + } + req->cctx = cctx; + cctx->state_ctx = req; + http_parser_init(&prctx->parser, HTTP_REQUEST); + prctx->parser.data = req; + } + + ret = sec_recv_data(cctx->cfd, &data); + switch (ret) { + case ENODATA: + DEBUG(SSSDBG_TRACE_ALL, + "Client closed connection.\n"); + talloc_free(cctx); + return; + case EAGAIN: + DEBUG(SSSDBG_TRACE_ALL, + "Interrupted before any data could be read, retry later\n"); + return; + case EOK: + /* all fine */ + break; + default: + DEBUG(SSSDBG_FATAL_FAILURE, + "Failed to receive data (%d, %s), aborting client\n", + ret, sss_strerror(ret)); + talloc_free(cctx); + return; + } + + len = http_parser_execute(&prctx->parser, &prctx->callbacks, + data.data, data.length); + if (len != data.length) { + DEBUG(SSSDBG_FATAL_FAILURE, + "Failed to parse request, aborting client!\n"); + talloc_free(cctx); + return; + } + + if (!req->complete) { + return; + } + + /* do not read anymore, client is done sending */ + TEVENT_FD_NOT_READABLE(cctx->cfde); + + sec_cmd_execute(cctx); +} + +static void sec_fd_handler(struct tevent_context *ev, + struct tevent_fd *fde, + uint16_t flags, void *ptr) +{ + errno_t ret; + struct cli_ctx *cctx = talloc_get_type(ptr, struct cli_ctx); + + /* Always reset the idle timer on any activity */ + ret = reset_idle_timer(cctx); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, + "Could not create idle timer for client. " + "This connection may not auto-terminate\n"); + /* Non-fatal, continue */ + } + + if (flags & TEVENT_FD_READ) { + sec_recv(cctx); + return; + } + if (flags & TEVENT_FD_WRITE) { + sec_send(cctx); + return; + } +} + +static http_parser_settings sec_callbacks = { + .on_message_begin = sec_on_message_begin, + .on_url = sec_on_url, + .on_header_field = sec_on_header_field, + .on_header_value = sec_on_header_value, + .on_headers_complete = sec_on_headers_complete, + .on_body = sec_on_body, + .on_message_complete = sec_on_message_complete +}; + +int sec_connection_setup(struct cli_ctx *cctx) +{ + struct sec_proto_ctx *protocol_ctx; + + protocol_ctx = talloc_zero(cctx, struct sec_proto_ctx); + if (!protocol_ctx) return ENOMEM; + protocol_ctx->callbacks = sec_callbacks; + + cctx->protocol_ctx = protocol_ctx; + cctx->cfd_handler = sec_fd_handler; + return EOK; +} + +/* Dummy, not used here but required to link to other responder files */ +struct cli_protocol_version *register_cli_protocol_version(void) +{ + return NULL; +} -- cgit