Skip to content

Commit 0fc608d

Browse files
committed
Use Retry-After header when server sends it
1 parent 63aa9f9 commit 0fc608d

File tree

1 file changed

+131
-57
lines changed

1 file changed

+131
-57
lines changed

uacme.c

Lines changed: 131 additions & 57 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@
2626
#include <fcntl.h>
2727
#include <getopt.h>
2828
#include <libgen.h>
29+
#include <limits.h>
2930
#include <locale.h>
3031
#include <regex.h>
3132
#include <stdarg.h>
@@ -36,6 +37,7 @@
3637
#include <string.h>
3738
#include <sys/stat.h>
3839
#include <sys/wait.h>
40+
#include <time.h>
3941
#include <unistd.h>
4042

4143
#include "base64.h"
@@ -71,55 +73,105 @@ typedef struct acme {
7173
char *certprefix;
7274
} acme_t;
7375

76+
unsigned int retry_after(const char *str, unsigned int d)
77+
{
78+
long dt;
79+
char *p;
80+
81+
if (!str || !*str)
82+
return d;
83+
84+
dt = strtol(str, &p, 10);
85+
if (*p) {
86+
time_t now = time(NULL);
87+
struct tm t, tnow;
88+
89+
gmtime_r(&now, &tnow);
90+
p = strptime(str, "%a, %d %b %Y %T", &t);
91+
if (!p)
92+
p = strptime(str, "%a, %d-%b-%y %T", &t);
93+
if (!p)
94+
p = strptime(str, "%a %b %d %T %Y", &t);
95+
if (!p)
96+
return d;
97+
dt = difftime(mktime(&t), mktime(&tnow));
98+
}
99+
if (dt > UINT_MAX)
100+
return UINT_MAX;
101+
else if (dt > 0)
102+
return (unsigned int)dt;
103+
else
104+
return d;
105+
}
106+
74107
int acme_get(acme_t *a, const char *url)
75108
{
76109
int ret = 0;
77110

78-
json_free(a->json);
79-
a->json = NULL;
80-
free(a->headers);
81-
a->headers = NULL;
82-
free(a->body);
83-
a->body = NULL;
84-
free(a->type);
85-
a->type = NULL;
86-
87111
if (!url) {
88112
warnx("acme_get: invalid URL");
89113
goto out;
90114
}
91-
if (g_loglevel > 1)
92-
warnx("acme_get: url=%s", url);
93-
curldata_t *c = curl_get(url);
94-
if (!c) {
95-
warnx("acme_get: curl_get failed");
96-
goto out;
115+
116+
for (int retry = 0; retry < 3; retry++) {
117+
if (retry > 0)
118+
msg(1, "acme_get: retrying");
119+
120+
json_free(a->json);
121+
a->json = NULL;
122+
free(a->headers);
123+
a->headers = NULL;
124+
free(a->body);
125+
a->body = NULL;
126+
free(a->type);
127+
a->type = NULL;
128+
129+
if (g_loglevel > 1)
130+
warnx("acme_get: url=%s", url);
131+
curldata_t *c = curl_get(url);
132+
if (!c) {
133+
warnx("acme_get: curl_get failed");
134+
goto out;
135+
}
136+
free(a->nonce);
137+
a->nonce = find_header(c->headers, "Replay-Nonce");
138+
a->type = find_header(c->headers, "Content-Type");
139+
if (a->type && strcasestr(a->type, "json"))
140+
a->json = json_parse(c->body, c->body_len);
141+
a->headers = c->headers;
142+
c->headers = NULL;
143+
a->body = c->body;
144+
c->body = NULL;
145+
ret = c->code;
146+
curldata_free(c);
147+
if (g_loglevel > 2) {
148+
if (a->headers)
149+
warnx("acme_get: HTTP headers\n%s", a->headers);
150+
if (a->body)
151+
warnx("acme_get: HTTP body\n%s", a->body);
152+
}
153+
if (g_loglevel > 1) {
154+
if (a->json) {
155+
warnx("acme_get: return code %d, json=", ret);
156+
json_dump(stderr, a->json);
157+
} else
158+
warnx("acme_get: return code %d", ret);
159+
}
160+
if (!a->type || !a->json ||
161+
!strcasestr(a->type, "application/problem+json"))
162+
break;
163+
if (ret == 503 && json_compare_string(a->json, "type",
164+
"urn:ietf:params:acme:error:rateLimited") == 0) {
165+
char *ra = find_header(a->headers, "Retry-After");
166+
unsigned int dt = retry_after(ra, 60);
167+
free(ra);
168+
msg(1, "acme_get: server busy, waiting %u seconds", dt);
169+
sleep(dt);
170+
continue;
171+
}
172+
break;
97173
}
98-
free(a->nonce);
99-
a->nonce = find_header(c->headers, "Replay-Nonce");
100-
a->type = find_header(c->headers, "Content-Type");
101-
if (a->type && strcasestr(a->type, "json"))
102-
a->json = json_parse(c->body, c->body_len);
103-
a->headers = c->headers;
104-
c->headers = NULL;
105-
a->body = c->body;
106-
c->body = NULL;
107-
ret = c->code;
108-
curldata_free(c);
109174
out:
110-
if (g_loglevel > 2) {
111-
if (a->headers)
112-
warnx("acme_get: HTTP headers\n%s", a->headers);
113-
if (a->body)
114-
warnx("acme_get: HTTP body\n%s", a->body);
115-
}
116-
if (g_loglevel > 1) {
117-
if (a->json) {
118-
warnx("acme_get: return code %d, json=", ret);
119-
json_dump(stderr, a->json);
120-
} else
121-
warnx("acme_get: return code %d", ret);
122-
}
123175
if (!a->headers)
124176
a->headers = strdup("");
125177
if (!a->body)
@@ -185,11 +237,6 @@ int acme_post(acme_t *a, const char *url, const char *format, ...)
185237
return 0;
186238
}
187239

188-
if (!a->nonce && !acme_nonce(a)) {
189-
warnx("acme_post: no nonce available");
190-
return 0;
191-
}
192-
193240
va_list ap;
194241
va_start(ap, format);
195242
if (vasprintf(&payload, format, ap) < 0)
@@ -200,9 +247,14 @@ int acme_post(acme_t *a, const char *url, const char *format, ...)
200247
return 0;
201248
}
202249

203-
for (int retry = 0; a->nonce && retry < 3; retry++) {
250+
for (int retry = 0; retry < 3; retry++) {
204251
if (retry > 0)
205-
msg(1, "acme_post: server rejected nonce, retrying");
252+
msg(1, "acme_post: retrying");
253+
254+
if (!a->nonce && !acme_nonce(a)) {
255+
warnx("acme_post: no nonce available");
256+
goto out;
257+
}
206258

207259
json_free(a->json);
208260
a->json = NULL;
@@ -260,11 +312,24 @@ int acme_post(acme_t *a, const char *url, const char *format, ...)
260312
} else
261313
warnx("acme_post: return code %d", ret);
262314
}
263-
if (ret != 400 || !a->type || !a->nonce || !a->json ||
264-
!strcasestr(a->type, "application/problem+json") ||
265-
json_compare_string(a->json, "type",
266-
"urn:ietf:params:acme:error:badNonce") != 0)
315+
if (!a->type || !a->json ||
316+
!strcasestr(a->type, "application/problem+json"))
267317
break;
318+
if (ret == 400 && json_compare_string(a->json, "type",
319+
"urn:ietf:params:acme:error:badNonce") == 0) {
320+
msg(1, "acme_post: server rejected nonce");
321+
continue;
322+
}
323+
if (ret == 503 && json_compare_string(a->json, "type",
324+
"urn:ietf:params:acme:error:rateLimited") == 0) {
325+
char *ra = find_header(a->headers, "Retry-After");
326+
unsigned int dt = retry_after(ra, 60);
327+
free(ra);
328+
msg(1, "acme_post: server busy, waiting %u seconds", dt);
329+
sleep(dt);
330+
continue;
331+
}
332+
break;
268333
}
269334
out:
270335
free(payload);
@@ -895,9 +960,12 @@ bool authorize(acme_t *a)
895960
acme_error(a);
896961
break;
897962
} else if (u < 5*512) {
898-
msg(u > 40 ? 1 : 2, "%s, waiting %u seconds",
899-
status, u);
900-
sleep(u);
963+
char *ra = find_header(a->headers, "Retry-After");
964+
unsigned int dt = retry_after(ra, u);
965+
free(ra);
966+
msg(dt > 40 ? 1 : 2, "%s, waiting %u seconds",
967+
status, dt);
968+
sleep(dt);
901969
} else {
902970
warnx("timeout while polling challenge status at %s, "
903971
"giving up", url);
@@ -999,8 +1067,11 @@ bool cert_issue(acme_t *a, char * const *names, const char *csr)
9991067
acme_error(a);
10001068
goto out;
10011069
} else if (u < 5*512) {
1002-
msg(u > 40 ? 1 : 2, "waiting %u seconds", u);
1003-
sleep(u);
1070+
char *ra = find_header(a->headers, "Retry-After");
1071+
unsigned int dt = retry_after(ra, u);
1072+
free(ra);
1073+
msg(dt > 40 ? 1 : 2, "%s, waiting %u seconds", status, dt);
1074+
sleep(dt);
10041075
} else {
10051076
warnx("timeout while polling order status at %s, giving up",
10061077
orderurl);
@@ -1042,8 +1113,11 @@ bool cert_issue(acme_t *a, char * const *names, const char *csr)
10421113
acme_error(a);
10431114
goto out;
10441115
} else if (u < 5*512) {
1045-
msg(u > 40 ? 1 : 2, "waiting %u seconds", u);
1046-
sleep(u);
1116+
char *ra = find_header(a->headers, "Retry-After");
1117+
unsigned int dt = retry_after(ra, u);
1118+
free(ra);
1119+
msg(dt > 40 ? 1 : 2, "%s, waiting %u seconds", status, dt);
1120+
sleep(dt);
10471121
} else {
10481122
warnx("timeout while polling order status at %s, giving up",
10491123
orderurl);

0 commit comments

Comments
 (0)