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>
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+
74107int 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 );
109174out :
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 }
269334out :
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