From: William Lallemand Date: Wed, 9 Apr 2025 14:43:24 +0000 (+0200) Subject: MINOR: acme: get the ACME directory X-Git-Tag: v3.2-dev11~97 X-Git-Url: http://git.ipfire.org/?a=commitdiff_plain;h=471290458e3117ade13cf376e3d7a2c22cb63fc9;p=thirdparty%2Fhaproxy.git MINOR: acme: get the ACME directory The first request of the ACME protocol is getting the list of URLs for the next steps. This patch implements the first request and the parsing of the response. The response is a JSON object so mjson is used to parse it. --- diff --git a/include/haproxy/acme-t.h b/include/haproxy/acme-t.h index aa6892a0f..db378ff0a 100644 --- a/include/haproxy/acme-t.h +++ b/include/haproxy/acme-t.h @@ -2,6 +2,7 @@ #ifndef _ACME_T_H_ #define _ACME_T_H_ +#include #include #define ACME_RETRY 3 @@ -28,11 +29,28 @@ struct acme_cfg { struct acme_cfg *next; }; +enum acme_st { + ACME_RESSOURCES = 0, + ACME_END +}; + +enum http_st { + ACME_HTTP_REQ, + ACME_HTTP_RES, +}; + +/* acme task context */ struct acme_ctx { + enum acme_st state; + enum http_st http_state; int retries; + struct httpclient *hc; struct acme_cfg *cfg; struct ckch_store *store; - unsigned int state; + struct { + struct ist newNonce; + struct ist newAccount; + struct ist newOrder; + } ressources; }; - #endif diff --git a/src/acme.c b/src/acme.c index 86930045e..cc3ba42de 100644 --- a/src/acme.c +++ b/src/acme.c @@ -18,6 +18,14 @@ #include #include #include + +#include +#include +#include +#include +#include +#include +#include #include #include #include @@ -478,8 +486,173 @@ INITCALL1(STG_REGISTER, cfg_register_keywords, &cfg_kws_acme); REGISTER_CONFIG_SECTION("acme", cfg_parse_acme, cfg_postsection_acme); + +static void acme_httpclient_end(struct httpclient *hc) +{ + struct task *task = hc->caller; + struct acme_ctx *ctx = task->context; + + if (!task) + return; + + if (ctx->http_state == ACME_HTTP_REQ) + ctx->http_state = ACME_HTTP_RES; + + task_wakeup(task, TASK_WOKEN_MSG); +} + + +int acme_http_req(struct task *task, struct acme_ctx *ctx, struct ist url, enum http_meth_t meth) +{ + struct httpclient *hc; + + hc = httpclient_new(task, meth, url); + if (!hc) + goto error; + + if (httpclient_req_gen(hc, hc->req.url, hc->req.meth, NULL, IST_NULL) != ERR_NONE) + goto error; + + hc->ops.res_end = acme_httpclient_end; + + ctx->hc = hc; + + if (!httpclient_start(hc)) + goto error; + + return 0; +error: + httpclient_destroy(hc); + ctx->hc = NULL; + + return 1; + +} + +int acme_directory(struct task *task, struct acme_ctx *ctx, char **errmsg) +{ + struct httpclient *hc; + int ret = 0; + + hc = ctx->hc; + + if (!hc) + goto error; + + if (hc->res.status != 200) { + memprintf(errmsg, "invalid HTTP status code %d when getting directory URL", hc->res.status); + goto error; + } + + if ((ret = mjson_get_string(hc->res.buf.area, hc->res.buf.data, "$.newNonce", trash.area, trash.size)) <= 0) { + memprintf(errmsg, "couldn't get newNonce URL from the directory URL"); + goto error; + } + ctx->ressources.newNonce = istdup(ist2(trash.area, ret)); + if (!isttest(ctx->ressources.newNonce)) { + memprintf(errmsg, "couldn't get newNonce URL from the directory URL"); + goto error; + } + + if ((ret = mjson_get_string(hc->res.buf.area, hc->res.buf.data, "$.newAccount", trash.area, trash.size)) <= 0) { + memprintf(errmsg, "couldn't get newAccount URL from the directory URL"); + goto error; + } + ctx->ressources.newAccount = istdup(ist2(trash.area, ret)); + if (!isttest(ctx->ressources.newAccount)) { + memprintf(errmsg, "couldn't get newAccount URL from the directory URL"); + goto error; + } + if ((ret = mjson_get_string(hc->res.buf.area, hc->res.buf.data, "$.newOrder", trash.area, trash.size)) <= 0) { + memprintf(errmsg, "couldn't get newOrder URL from the directory URL"); + goto error; + } + ctx->ressources.newOrder = istdup(ist2(trash.area, ret)); + if (!isttest(ctx->ressources.newOrder)) { + memprintf(errmsg, "couldn't get newOrder URL from the directory URL"); + goto error; + } + + httpclient_destroy(hc); + ctx->hc = NULL; + +// fprintf(stderr, "newNonce: %s\nnewAccount: %s\nnewOrder: %s\n", +// ctx->ressources.newNonce.ptr, ctx->ressources.newAccount.ptr, ctx->ressources.newOrder.ptr); + + return 0; + +error: + httpclient_destroy(hc); + ctx->hc = NULL; + + istfree(&ctx->ressources.newNonce); + istfree(&ctx->ressources.newAccount); + istfree(&ctx->ressources.newOrder); + + return 1; +} + +/* + * Task for ACME processing: + * - when retrying after a failure, the task must be waked up + * - when calling a get function, the httpclient is waking up the task again + * once the data are ready or upon failure + */ struct task *acme_process(struct task *task, void *context, unsigned int state) { + struct acme_ctx *ctx = task->context; + enum acme_st st = ctx->state; + enum http_st http_st = ctx->http_state; + char *errmsg = NULL; + + switch (st) { + case ACME_RESSOURCES: + if (http_st == ACME_HTTP_REQ) { + if (acme_http_req(task, ctx, ist(ctx->cfg->uri), HTTP_METH_GET) != 0) + goto retry; + } + + if (http_st == ACME_HTTP_RES) { + if (acme_directory(task, ctx, &errmsg) != 0) { + http_st = ACME_HTTP_REQ; + goto retry; + } + st = ACME_END; + } + break; + + case ACME_END: + goto end; + break; + default: + break; + + } + + ctx->http_state = http_st; + ctx->state = st; + + return task; + +retry: + ctx->http_state = http_st; + ctx->state = st; + + ctx->retries--; + if (ctx->retries > 0) { + ha_notice("acme: %s, retrying (%d/%d)...\n", errmsg ? errmsg : "", ACME_RETRY-ctx->retries, ACME_RETRY); + task_wakeup(task, TASK_WOKEN_MSG); + } else { + ha_notice("acme: %s, aborting. (%d/%d)\n", errmsg ? errmsg : "", ACME_RETRY-ctx->retries, ACME_RETRY); + goto end; + } + + ha_free(&errmsg); + + return task; +end: + task_destroy(task); + task = NULL; return task; }