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; }; -struct acme_ctx { - int retries; - struct acme_cfg *cfg; - struct ckch_store *store; - unsigned int state; +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; + 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; }