blob: eeb35b376967a64f75dd0da76d36f7177e09f872 [file] [log] [blame]
/*****************************************************************************\
* http.c - handling HTTP
*****************************************************************************
* Copyright (C) SchedMD LLC.
*
* This file is part of Slurm, a resource management program.
* For details, see <https://slurm.schedmd.com/>.
* Please also read the included file: DISCLAIMER.
*
* Slurm 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 2 of the License, or (at your option)
* any later version.
*
* In addition, as a special exception, the copyright holders give permission
* to link the code of portions of this program with the OpenSSL library under
* certain conditions as described in each individual source file, and
* distribute linked combinations including the two. You must obey the GNU
* General Public License in all respects for all of the code used other than
* OpenSSL. If you modify file(s) with this exception, you may extend this
* exception to your version of the file(s), but you are not obligated to do
* so. If you do not wish to do so, delete this exception statement from your
* version. If you delete this exception statement from all source files in
* the program, then also delete it here.
*
* Slurm 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 Slurm; if not, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
\*****************************************************************************/
#include <limits.h>
#include <sys/socket.h>
#include <unistd.h>
#include "slurm/slurm.h"
#include "slurm/slurm_errno.h"
#include "src/common/http.h"
#include "src/common/list.h"
#include "src/common/log.h"
#include "src/common/pack.h"
#include "src/common/read_config.h"
#include "src/common/slurm_protocol_defs.h"
#include "src/common/xassert.h"
#include "src/common/xmalloc.h"
#include "src/common/xstring.h"
#include "src/interfaces/http_parser.h"
#include "src/slurmrestd/http.h"
#include "src/slurmrestd/rest_auth.h"
#define CRLF "\r\n"
#define MAGIC 0xDFAFFEEF
#define MAX_BODY_BYTES 52428800 /* 50MB */
#define MAGIC_REQUEST_T 0xdbadaaaf
/* Data to handed around by http_parser to call backs */
typedef struct {
int magic;
/* Requested URL */
url_t url;
/* Request HTTP method */
http_request_method_t method;
/* list of each header received (to be handed to callback) */
list_t *headers;
/* state tracking of last header received */
char *last_header;
/* client requested to keep_alive header or -1 to disable */
int keep_alive;
/* RFC7230-6.1 "Connection: Close" */
bool connection_close;
int expect; /* RFC7231-5.1.1 expect requested */
/* Connection context */
http_context_t *context;
/* Body of request (may be NULL) */
char *body;
/* if provided: expected body length to process or 0 */
size_t expected_body_length;
size_t body_length;
const char *body_encoding; //TODO: implement detection of this
const char *content_type;
const char *accept;
struct {
uint16_t major;
uint16_t minor;
} http_version;
} request_t;
/* default keep_alive value which appears to be implementation specific */
static int DEFAULT_KEEP_ALIVE = 5; //default to 5s to match apache2
static int _send_reject(http_context_t *context, http_status_code_t status_code,
slurm_err_t error_number);
static int _valid_http_version(uint16_t major, uint16_t minor)
{
if ((major == 0) && (minor == 0))
return SLURM_SUCCESS;
if ((major == 1) && ((minor == 0) || (minor == 1)))
return SLURM_SUCCESS;
return ESLURM_HTTP_UNSUPPORTED_VERSION;
}
extern void free_http_header(http_header_entry_t *header)
{
xfree(header->name);
xfree(header->value);
xfree(header);
}
static void _free_http_header(void *header)
{
free_http_header(header);
}
static void _request_init(http_context_t *context)
{
request_t *request = context->request;
xassert(request);
xassert(context->magic == MAGIC);
*request = (request_t) {
.magic = MAGIC_REQUEST_T,
.url = URL_INITIALIZER,
.method = HTTP_REQUEST_INVALID,
.context = context,
.keep_alive = -1,
};
request->headers = list_create(_free_http_header);
}
static void _request_free_members(http_context_t *context)
{
request_t *request = context->request;
xassert(context->magic == MAGIC);
xassert(request->magic == MAGIC_REQUEST_T);
xassert(request->context == context);
url_free_members(&request->url);
FREE_NULL_LIST(request->headers);
xfree(request->last_header);
xfree(request->content_type);
xfree(request->accept);
xfree(request->body);
xfree(request->body_encoding);
}
/* reset state of request */
static void _request_reset(http_context_t *context)
{
xassert(context->magic == MAGIC);
FREE_NULL_REST_AUTH(context->auth);
_request_free_members(context);
_request_init(context);
}
static int _on_request(const http_parser_request_t *req, void *arg)
{
request_t *request = arg;
http_context_t *context = request->context;
int rc = EINVAL;
xassert(context->magic == MAGIC);
xassert(request->magic == MAGIC_REQUEST_T);
request->http_version.major = req->http_version.major;
request->http_version.minor = req->http_version.minor;
request->method = req->method;
url_copy_members(&request->url, req->url);
/* Default to http if none given */
if (request->url.scheme == URL_SCHEME_INVALID)
request->url.scheme = URL_SCHEME_HTTP;
if (!request->url.path) {
error("%s: [%s] Rejecting request with empty URL path",
__func__, conmgr_con_get_name(context->ref));
return _send_reject(context, HTTP_STATUS_CODE_ERROR_NOT_FOUND,
ESLURM_URL_INVALID_PATH);
}
if (req->method == HTTP_REQUEST_INVALID)
return _send_reject(context,
HTTP_STATUS_CODE_ERROR_METHOD_NOT_ALLOWED,
ESLURM_HTTP_INVALID_METHOD);
if ((rc = _valid_http_version(req->http_version.major,
req->http_version.minor))) {
error("%s: [%s] rejecting unsupported HTTP %hu.%hu version: %s",
__func__, conmgr_con_get_name(context->ref),
req->http_version.major, req->http_version.minor,
slurm_strerror(rc));
return rc;
}
if ((request->url.scheme != URL_SCHEME_HTTP) &&
(request->url.scheme != URL_SCHEME_HTTPS)) {
error("%s: [%s] URL scheme not supported: %s",
__func__, conmgr_con_get_name(context->ref),
url_get_scheme_string(request->url.scheme));
return ESLURM_URL_UNSUPPORTED_SCHEME;
}
if ((request->url.scheme == URL_SCHEME_HTTPS) &&
!conmgr_fd_is_tls(context->ref)) {
error("%s: [%s] URL requested HTTPS but connection is not TLS wrapped",
__func__, conmgr_con_get_name(context->ref));
return ESLURM_TLS_REQUIRED;
}
return SLURM_SUCCESS;
}
static int _on_header(const http_parser_header_t *header, void *arg)
{
request_t *request = arg;
http_context_t *context = request->context;
http_header_entry_t *entry = NULL;
xassert(context->magic == MAGIC);
xassert(request->magic == MAGIC_REQUEST_T);
log_flag(NET, "%s: [%s] Header: %s Value: %s",
__func__, conmgr_con_get_name(context->ref), header->name,
header->value);
/* Add copy to list of headers */
entry = xmalloc(sizeof(*entry));
entry->name = xstrdup(header->name);
entry->value = xstrdup(header->value);
list_append(request->headers, entry);
/* Watch for connection headers */
if (!xstrcasecmp(header->name, "Connection")) {
if (!xstrcasecmp(header->value, "Keep-Alive")) {
if (request->keep_alive == -1)
request->keep_alive = DEFAULT_KEEP_ALIVE;
} else if (!xstrcasecmp(header->value, "Close")) {
request->connection_close = true;
} else {
warning("%s: [%s] ignoring unsupported header request: Connection: %s",
__func__,
conmgr_con_get_name(context->ref),
header->value);
}
} else if (!xstrcasecmp(header->name, "Keep-Alive")) {
int ibuffer = atoi(header->value);
if (ibuffer > 1) {
request->keep_alive = ibuffer;
} else {
error("%s: [%s] invalid Keep-Alive value %s",
__func__,
conmgr_fd_get_name(request->context->con),
header->value);
return _send_reject(
context, HTTP_STATUS_CODE_ERROR_NOT_ACCEPTABLE,
ESLURM_HTTP_UNSUPPORTED_KEEP_ALIVE);
}
} else if (!xstrcasecmp(header->name, "Content-Type")) {
xfree(request->content_type);
request->content_type = xstrdup(header->value);
} else if (!xstrcasecmp(header->name, "Content-Type")) {
xfree(request->content_type);
request->content_type = xstrdup(header->value);
} else if (!xstrcasecmp(header->name, "Content-Length")) {
/* Use signed buffer to catch if negative length is provided */
ssize_t cl;
if ((sscanf(header->value, "%zd", &cl) != 1) || (cl < 0))
return _send_reject(
context, HTTP_STATUS_CODE_ERROR_NOT_ACCEPTABLE,
ESLURM_HTTP_INVALID_CONTENT_LENGTH);
request->expected_body_length = cl;
} else if (!xstrcasecmp(header->name, "Accept")) {
xfree(request->accept);
request->accept = xstrdup(header->value);
} else if (!xstrcasecmp(header->name, "Expect")) {
if (!xstrcasecmp(header->value, "100-continue"))
request->expect = 100;
else
return _send_reject(
context,
HTTP_STATUS_CODE_ERROR_EXPECTATION_FAILED,
ESLURM_HTTP_UNSUPPORTED_EXPECT);
} else if (!xstrcasecmp(header->name, "Transfer-Encoding")) {
/* Transfer encoding is not allowed */
return _send_reject(context,
HTTP_STATUS_CODE_ERROR_NOT_ACCEPTABLE,
ESLURM_HTTP_INVALID_TRANSFER_ENCODING);
} else if (!xstrcasecmp(header->name, "Content-Encoding")) {
/* Content encoding is not allowed */
return _send_reject(context,
HTTP_STATUS_CODE_ERROR_NOT_ACCEPTABLE,
ESLURM_HTTP_INVALID_CONTENT_ENCODING);
} else if (!xstrcasecmp(header->name, "Upgrade")) {
/* Upgrades are not allowed */
return _send_reject(context,
HTTP_STATUS_CODE_ERROR_NOT_ACCEPTABLE,
ESLURM_HTTP_UNSUPPORTED_UPGRADE);
}
return SLURM_SUCCESS;
}
static int _on_headers_complete(void *arg)
{
request_t *request = arg;
http_context_t *context = request->context;
xassert(context->magic == MAGIC);
xassert(request->magic == MAGIC_REQUEST_T);
if ((request->http_version.major == 1) &&
(request->http_version.minor == 0)) {
log_flag(NET, "%s: [%s] HTTP/1.0 connection",
__func__, conmgr_con_get_name(context->ref));
/* 1.0 defaults to close w/o keep_alive */
if (!request->keep_alive)
request->connection_close = true;
} else if ((request->http_version.major == 1) &&
(request->http_version.minor == 1)) {
log_flag(NET, "%s: [%s] HTTP/1.1 connection",
__func__, conmgr_con_get_name(context->ref));
/* keep alive is assumed for 1.1 */
if (request->keep_alive == -1)
request->keep_alive = DEFAULT_KEEP_ALIVE;
}
if (!request->http_version.major && request->http_version.minor)
return SLURM_SUCCESS;
if ((request->method == HTTP_REQUEST_POST) &&
(request->expected_body_length <= 0))
return _send_reject(context,
HTTP_STATUS_CODE_ERROR_LENGTH_REQUIRED,
ESLURM_HTTP_INVALID_CONTENT_LENGTH);
if (request->expect) {
int rc = EINVAL;
send_http_response_args_t args = {
.con = context->con,
.http_major = request->http_version.major,
.http_minor = request->http_version.minor,
.status_code = request->expect,
.body_length = 0,
};
if ((rc = send_http_response(&args)))
return rc;
}
return SLURM_SUCCESS;
}
static int _on_content(const http_parser_content_t *content, void *arg)
{
request_t *request = arg;
http_context_t *context = request->context;
const void *at = get_buf_data(content->buffer);
const size_t length = get_buf_offset(content->buffer);
xassert(context->magic == MAGIC);
xassert(request->magic == MAGIC_REQUEST_T);
log_flag_hex(NET_RAW, at, length, "%s: [%s] received HTTP content",
__func__, conmgr_con_get_name(context->ref));
if (!request->url.path) {
error("%s: [%s] rejecting missing path",
__func__, conmgr_con_get_name(context->ref));
return ESLURM_HTTP_UNEXPECTED_REQUEST;
}
if (request->body) {
size_t nlength = length + request->body_length;
xassert(request->body_length > 0);
xassert(request->body_length <= MAX_BODY_BYTES);
xassert((request->body_length + 1) == xsize(request->body));
if (nlength > MAX_BODY_BYTES)
goto no_mem;
if (request->expected_body_length &&
(nlength > request->expected_body_length))
goto no_mem;
if (!try_xrealloc(request->body, (nlength + 1)))
goto no_mem;
memmove((request->body + request->body_length), at, length);
request->body_length += length;
} else {
if ((length >= MAX_BODY_BYTES) ||
(request->expected_body_length &&
(length > request->expected_body_length)) ||
!(request->body = try_xmalloc(length + 1)))
goto no_mem;
request->body_length = length;
memmove(request->body, at, length);
}
/* final byte must in body must always be NULL terminated */
xassert(!request->body[request->body_length]);
request->body[request->body_length] = '\0';
log_flag(NET, "%s: [%s] received %zu bytes for HTTP body length %zu/%zu bytes",
__func__, conmgr_con_get_name(context->ref), length,
request->body_length, request->expected_body_length);
return 0;
no_mem:
/* total body was way too large to store */
return _send_reject(context, HTTP_STATUS_CODE_ERROR_ENTITY_TOO_LARGE,
ESLURM_HTTP_INVALID_CONTENT_LENGTH);
}
/*
* Create rfc2616 formatted header
* TODO: add more sanity checks
* IN name header name
* IN value header value
* RET formatted string (must xfree)
* */
static char *_fmt_header(const char *name, const char *value)
{
return xstrdup_printf("%s: %s"CRLF, name, value);
}
/*
* Create and write formatted header
* IN request HTTP request
* IN name header name
* IN value header value
* RET formatted string (must xfree)
* */
static int _write_fmt_header(conmgr_fd_t *con, const char *name,
const char *value)
{
char *buffer = _fmt_header(name, value);
int rc = conmgr_queue_write_data(con, buffer, strlen(buffer));
xfree(buffer);
return rc;
}
/*
* Create rfc2616 formatted numerical header
* TODO: add sanity checks
* IN name header name
* IN value header value
* RET formatted string (must xfree)
* */
static char *_fmt_header_num(const char *name, size_t value)
{
return xstrdup_printf("%s: %zu" CRLF, name, value);
}
extern int send_http_connection_close(http_context_t *ctxt)
{
return _write_fmt_header(ctxt->con, "Connection", "Close");
}
/*
* Create and write formatted numerical header
* IN request HTTP request
* IN name header name
* IN value header value
* RET formatted string (must xfree)
* */
static int _write_fmt_num_header(conmgr_fd_t *con, const char *name,
size_t value)
{
const char *buffer = _fmt_header_num(name, value);
int rc = conmgr_queue_write_data(con, buffer, strlen(buffer));
xfree(buffer);
return rc;
}
extern int send_http_response(const send_http_response_args_t *args)
{
char *buffer = NULL;
int rc = SLURM_SUCCESS;
xassert(args->status_code != HTTP_STATUS_NONE);
xassert(args->body_length == 0 || (args->body_length && args->body));
log_flag(NET, "%s: [%s] sending response %u: %s",
__func__, conmgr_fd_get_name(args->con), args->status_code,
get_http_status_code_string(args->status_code));
/* send rfc2616 response */
xstrfmtcat(buffer, "HTTP/%d.%d %d %s"CRLF,
args->http_major, args->http_minor, args->status_code,
get_http_status_code_string(args->status_code));
rc = conmgr_queue_write_data(args->con, buffer, strlen(buffer));
xfree(buffer);
if (rc)
return rc;
/* send along any requested headers */
if (args->headers) {
list_itr_t *itr = list_iterator_create(args->headers);
http_header_entry_t *header = NULL;
while ((header = list_next(itr))) {
if ((rc = _write_fmt_header(args->con, header->name,
header->value)))
break;
}
list_iterator_destroy(itr);
if (rc)
return rc;
}
if (args->body && args->body_length) {
/* RFC7230-3.3.2 limits response of Content-Length */
if ((args->status_code < 100) ||
((args->status_code >= 200) &&
(args->status_code != 204))) {
if ((rc = _write_fmt_num_header(args->con,
"Content-Length", args->body_length))) {
return rc;
}
}
if (args->body_encoding &&
(rc = _write_fmt_header(
args->con, "Content-Type", args->body_encoding)))
return rc;
if ((rc = conmgr_queue_write_data(args->con, CRLF,
strlen(CRLF))))
return rc;
log_flag(NET, "%s: [%s] rc=%s(%u) sending body:\n%s",
__func__, conmgr_fd_get_name(args->con),
get_http_status_code_string(args->status_code),
args->status_code, args->body);
if ((rc = conmgr_queue_write_data(args->con, args->body,
args->body_length)))
return rc;
} else if (((args->status_code >= 100) && (args->status_code < 200)) ||
(args->status_code == 204) ||
(args->status_code == 304)) {
/*
* RFC2616 requires empty line after headers for return code
* that "MUST NOT" include a message body
*/
if ((rc = conmgr_queue_write_data(args->con, CRLF,
strlen(CRLF))))
return rc;
}
return rc;
}
static int _send_reject(http_context_t *context, http_status_code_t status_code,
slurm_err_t error_number)
{
request_t *request = context->request;
xassert(request->magic == MAGIC_REQUEST_T);
send_http_response_args_t args = {
.con = request->context->con,
.http_major = request->http_version.major,
.http_minor = request->http_version.minor,
.status_code = status_code,
.body_length = 0,
.headers = list_create(_free_http_header),
};
xassert(context->magic == MAGIC);
/* If we don't have a requested client version, default to 0.9 */
if ((args.http_major == 0) && (args.http_minor == 0))
args.http_minor = 9;
/* Ignore response since this connection is already dead */
(void) send_http_response(&args);
FREE_NULL_LIST(args.headers);
if (request->connection_close ||
_valid_http_version(request->http_version.major,
request->http_version.minor))
send_http_connection_close(request->context);
/* ensure connection gets closed */
(void) conmgr_queue_close_fd(request->context->con);
/* reset connection to avoid any possible auth inheritance */
_request_reset(context);
return error_number;
}
static int _on_message_complete_request(request_t *request)
{
int rc = EINVAL;
http_context_t *context = request->context;
on_http_request_args_t args = {
.method = request->method,
.headers = request->headers,
.path = request->url.path,
.query = request->url.query,
.context = context,
.http_major = request->http_version.major,
.http_minor = request->http_version.minor,
.content_type = request->content_type,
.accept = request->accept,
.body = request->body,
.body_length = request->body_length,
.body_encoding = request->body_encoding
};
xassert(context->magic == MAGIC);
xassert(request->magic == MAGIC_REQUEST_T);
if ((rc = context->on_http_request(&args)))
log_flag(NET, "%s: [%s] on_http_request rejected: %s",
__func__, conmgr_con_get_name(context->ref),
slurm_strerror(rc));
return rc;
}
static int _on_content_complete(void *arg)
{
request_t *request = arg;
http_context_t *context = request->context;
int rc = EINVAL;
xassert(context->magic == MAGIC);
xassert(request->magic == MAGIC_REQUEST_T);
if ((request->expected_body_length > 0) &&
(request->expected_body_length != request->body_length)) {
error("%s: [%s] Content-Length %zu and received body length %zu mismatch",
__func__, conmgr_con_get_name(context->ref),
request->expected_body_length, request->body_length);
return _send_reject(context, HTTP_STATUS_CODE_ERROR_BAD_REQUEST,
ESLURM_HTTP_INVALID_CONTENT_LENGTH);
}
if ((rc = _on_message_complete_request(request)))
return rc;
if (request->keep_alive) {
//TODO: implement keep alive correctly
log_flag(NET, "%s: [%s] keep alive not currently implemented",
__func__, conmgr_con_get_name(context->ref));
}
if (request->connection_close) {
/* Notify client that this connection will be closed now */
if (request->connection_close)
send_http_connection_close(context);
conmgr_con_queue_close_free(&context->ref);
context->con = NULL;
}
_request_reset(context);
return SLURM_SUCCESS;
}
extern int parse_http(conmgr_fd_t *con, void *x)
{
http_context_t *context = (http_context_t *) x;
static const http_parser_callbacks_t callbacks = {
.on_request = _on_request,
.on_header = _on_header,
.on_headers_complete = _on_headers_complete,
.on_content = _on_content,
.on_content_complete = _on_content_complete,
};
int rc = SLURM_SUCCESS;
request_t *request = context->request;
ssize_t bytes_parsed = -1;
buf_t *buffer = NULL;
xassert(context->magic == MAGIC);
xassert(context->con);
xassert(context->ref);
xassert(request->magic == MAGIC_REQUEST_T);
xassert(request->context == context);
if (!context->parser &&
(rc = http_parser_g_new_parse_request(
conmgr_con_get_name(context->ref), &callbacks, request,
&context->parser))) {
log_flag(NET, "%s: [%s] Creating new HTTP parser failed: %s",
__func__, conmgr_con_get_name(context->ref),
slurm_strerror(rc));
goto cleanup;
}
if ((rc = conmgr_con_shadow_in_buffer(context->ref, &buffer))) {
log_flag(NET, "%s: [%s] Unable to get HTTP input buffer: %s",
__func__, conmgr_con_get_name(context->ref),
slurm_strerror(rc));
goto cleanup;
}
/* Set buffer as fully populated */
set_buf_offset(buffer, size_buf(buffer));
log_flag(NET, "%s: [%s] Accepted HTTP connection",
__func__, conmgr_con_get_name(context->ref));
rc = http_parser_g_parse_request(context->parser, buffer,
&bytes_parsed);
if (context->ref)
log_flag(NET, "%s: [%s] parsed %zu/%u bytes: %s",
__func__, conmgr_con_get_name(context->ref),
bytes_parsed, get_buf_offset(buffer),
slurm_strerror(rc));
if (rc) {
rc = _send_reject(context, HTTP_STATUS_CODE_SRVERR_INTERNAL,
rc);
} else if (context->ref && (bytes_parsed > 0) &&
(rc = conmgr_con_mark_consumed_input_buffer(context->ref,
bytes_parsed))) {
log_flag(NET, "%s: [%s] Input buffer became invalid after parsing: %s",
__func__, conmgr_con_get_name(context->ref),
slurm_strerror(rc));
goto cleanup;
}
cleanup:
FREE_NULL_BUFFER(buffer);
return rc;
}
static http_context_t *_http_context_new(void)
{
http_context_t *context = xmalloc(sizeof(*context));
context->magic = MAGIC;
return context;
}
/* find operator against http_header_entry_t */
static int _http_header_find_key(void *x, void *y)
{
http_header_entry_t *entry = (http_header_entry_t *)x;
const char *key = (const char *)y;
xassert(entry->name);
if (key == NULL)
return 0;
/* case insensitive compare per rfc2616:4.2 */
if (entry->name && !xstrcasecmp(entry->name, key))
return 1;
else
return 0;
}
extern const char *find_http_header(list_t *headers, const char *name)
{
http_header_entry_t *header = NULL;
if (!headers || !name)
return NULL;
header = (http_header_entry_t *)list_find_first(
headers, _http_header_find_key, (void *)name);
if (header)
return header->value;
else
return NULL;
}
extern http_context_t *setup_http_context(conmgr_fd_t *con,
on_http_request_t on_http_request)
{
http_context_t *context = _http_context_new();
xassert(context->magic == MAGIC);
xassert(!context->con);
xassert(!context->request);
context->con = con;
context->ref = conmgr_fd_new_ref(con);
context->on_http_request = on_http_request;
/* Must use type since context->request is void ptr */
context->request = xmalloc(sizeof(request_t));
_request_init(context);
return context;
}
extern void on_http_connection_finish(conmgr_fd_t *con, void *ctxt)
{
http_context_t *context = (http_context_t *) ctxt;
request_t *request = NULL;
if (!context)
return;
xassert(context->magic == MAGIC);
http_parser_g_free_parse_request(&context->parser);
/* release request */
request = context->request;
xassert(request->magic == MAGIC_REQUEST_T);
_request_free_members(context);
request->magic = ~MAGIC_REQUEST_T;
xfree(context->request);
/* auth should have been released long before now */
xassert(!context->auth);
FREE_NULL_REST_AUTH(context->auth);
conmgr_fd_free_ref(&context->ref);
context->con = NULL;
context->magic = ~MAGIC;
xfree(context);
}