Line data Source code
1 : /*
2 : This file is part of TALER
3 : Copyright (C) 2014-2022 Taler Systems SA
4 :
5 : TALER is free software; you can redistribute it and/or modify it under the
6 : terms of the GNU General Public License as published by the Free Software
7 : Foundation; either version 3, or (at your option) any later version.
8 :
9 : TALER is distributed in the hope that it will be useful, but WITHOUT ANY
10 : WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
11 : A PARTICULAR PURPOSE. See the GNU General Public License for more details.
12 :
13 : You should have received a copy of the GNU General Public License along with
14 : TALER; see the file COPYING. If not, see
15 : <http://www.gnu.org/licenses/>
16 : */
17 : /**
18 : * @file lib/auditor_api_handle.c
19 : * @brief Implementation of the "handle" component of the auditor's HTTP API
20 : * @author Sree Harsha Totakura <sreeharsha@totakura.in>
21 : * @author Christian Grothoff
22 : */
23 : #include "platform.h"
24 : #include <microhttpd.h>
25 : #include <gnunet/gnunet_curl_lib.h>
26 : #include "taler_json_lib.h"
27 : #include "taler_auditor_service.h"
28 : #include "taler_signatures.h"
29 : #include "auditor_api_handle.h"
30 : #include "auditor_api_curl_defaults.h"
31 : #include "backoff.h"
32 :
33 : /**
34 : * Which revision of the Taler auditor protocol is implemented
35 : * by this library? Used to determine compatibility.
36 : */
37 : #define TALER_PROTOCOL_CURRENT 0
38 :
39 : /**
40 : * How many revisions back are we compatible to?
41 : */
42 : #define TALER_PROTOCOL_AGE 0
43 :
44 :
45 : /**
46 : * Log error related to CURL operations.
47 : *
48 : * @param type log level
49 : * @param function which function failed to run
50 : * @param code what was the curl error code
51 : */
52 : #define CURL_STRERROR(type, function, code) \
53 : GNUNET_log (type, "Curl function `%s' has failed at `%s:%d' with error: %s", \
54 : function, __FILE__, __LINE__, curl_easy_strerror (code));
55 :
56 : /**
57 : * Stages of initialization for the `struct TALER_AUDITOR_Handle`
58 : */
59 : enum AuditorHandleState
60 : {
61 : /**
62 : * Just allocated.
63 : */
64 : MHS_INIT = 0,
65 :
66 : /**
67 : * Obtained the auditor's versioning data and version.
68 : */
69 : MHS_VERSION = 1,
70 :
71 : /**
72 : * Failed to initialize (fatal).
73 : */
74 : MHS_FAILED = 2
75 : };
76 :
77 :
78 : /**
79 : * Handle to the auditor
80 : */
81 : struct TALER_AUDITOR_Handle
82 : {
83 : /**
84 : * The context of this handle
85 : */
86 : struct GNUNET_CURL_Context *ctx;
87 :
88 : /**
89 : * The URL of the auditor (i.e. "http://auditor.taler.net/")
90 : */
91 : char *url;
92 :
93 : /**
94 : * Function to call with the auditor's certification data,
95 : * NULL if this has already been done.
96 : */
97 : TALER_AUDITOR_VersionCallback version_cb;
98 :
99 : /**
100 : * Closure to pass to @e version_cb.
101 : */
102 : void *version_cb_cls;
103 :
104 : /**
105 : * Data for the request to get the /version of a auditor,
106 : * NULL once we are past stage #MHS_INIT.
107 : */
108 : struct GNUNET_CURL_Job *vr;
109 :
110 : /**
111 : * The url for the @e vr job.
112 : */
113 : char *vr_url;
114 :
115 : /**
116 : * Task for retrying /version request.
117 : */
118 : struct GNUNET_SCHEDULER_Task *retry_task;
119 :
120 : /**
121 : * /version data of the auditor, only valid if
122 : * @e handshake_complete is past stage #MHS_VERSION.
123 : */
124 : char *version;
125 :
126 : /**
127 : * Version information for the callback.
128 : */
129 : struct TALER_AUDITOR_VersionInformation vi;
130 :
131 : /**
132 : * Retry /version frequency.
133 : */
134 : struct GNUNET_TIME_Relative retry_delay;
135 :
136 : /**
137 : * Stage of the auditor's initialization routines.
138 : */
139 : enum AuditorHandleState state;
140 :
141 : };
142 :
143 :
144 : /* ***************** Internal /version fetching ************* */
145 :
146 : /**
147 : * Decode the JSON in @a resp_obj from the /version response and store the data
148 : * in the @a key_data.
149 : *
150 : * @param[in] resp_obj JSON object to parse
151 : * @param[in,out] auditor where to store the results we decoded
152 : * @param[out] vc where to store version compatibility data
153 : * @return #TALER_EC_NONE on success
154 : */
155 : static enum TALER_ErrorCode
156 0 : decode_version_json (const json_t *resp_obj,
157 : struct TALER_AUDITOR_Handle *auditor,
158 : enum TALER_AUDITOR_VersionCompatibility *vc)
159 : {
160 0 : struct TALER_AUDITOR_VersionInformation *vi = &auditor->vi;
161 : unsigned int age;
162 : unsigned int revision;
163 : unsigned int current;
164 : char dummy;
165 : const char *ver;
166 : struct GNUNET_JSON_Specification spec[] = {
167 0 : GNUNET_JSON_spec_string ("version",
168 : &ver),
169 0 : GNUNET_JSON_spec_fixed_auto ("auditor_public_key",
170 : &vi->auditor_pub),
171 0 : GNUNET_JSON_spec_end ()
172 : };
173 :
174 0 : if (JSON_OBJECT != json_typeof (resp_obj))
175 : {
176 0 : GNUNET_break_op (0);
177 0 : return TALER_EC_GENERIC_JSON_INVALID;
178 : }
179 : /* check the version */
180 0 : if (GNUNET_OK !=
181 0 : GNUNET_JSON_parse (resp_obj,
182 : spec,
183 : NULL, NULL))
184 : {
185 0 : GNUNET_break_op (0);
186 0 : return TALER_EC_GENERIC_JSON_INVALID;
187 : }
188 0 : if (3 != sscanf (ver,
189 : "%u:%u:%u%c",
190 : ¤t,
191 : &revision,
192 : &age,
193 : &dummy))
194 : {
195 0 : GNUNET_break_op (0);
196 0 : return TALER_EC_GENERIC_VERSION_MALFORMED;
197 : }
198 0 : GNUNET_free (auditor->version);
199 0 : auditor->version = GNUNET_strdup (ver);
200 0 : vi->version = auditor->version;
201 0 : *vc = TALER_AUDITOR_VC_MATCH;
202 0 : if (TALER_PROTOCOL_CURRENT < current)
203 : {
204 0 : *vc |= TALER_AUDITOR_VC_NEWER;
205 0 : if (TALER_PROTOCOL_CURRENT < current - age)
206 0 : *vc |= TALER_AUDITOR_VC_INCOMPATIBLE;
207 : }
208 : if (TALER_PROTOCOL_CURRENT > current)
209 : {
210 : *vc |= TALER_AUDITOR_VC_OLDER;
211 : if (TALER_PROTOCOL_CURRENT - TALER_PROTOCOL_AGE > current)
212 : *vc |= TALER_AUDITOR_VC_INCOMPATIBLE;
213 : }
214 0 : return TALER_EC_NONE;
215 : }
216 :
217 :
218 : /**
219 : * Initiate download of /version from the auditor.
220 : *
221 : * @param cls auditor where to download /version from
222 : */
223 : static void
224 : request_version (void *cls);
225 :
226 :
227 : /**
228 : * Callback used when downloading the reply to a /version request
229 : * is complete.
230 : *
231 : * @param cls the `struct TALER_AUDITOR_Handle`
232 : * @param response_code HTTP response code, 0 on error
233 : * @param gresp_obj parsed JSON result, NULL on error, must be a `const json_t *`
234 : */
235 : static void
236 0 : version_completed_cb (void *cls,
237 : long response_code,
238 : const void *gresp_obj)
239 : {
240 0 : struct TALER_AUDITOR_Handle *auditor = cls;
241 0 : const json_t *resp_obj = gresp_obj;
242 : enum TALER_AUDITOR_VersionCompatibility vc;
243 0 : struct TALER_AUDITOR_HttpResponse hr = {
244 : .reply = resp_obj,
245 0 : .http_status = (unsigned int) response_code
246 : };
247 :
248 0 : auditor->vr = NULL;
249 0 : GNUNET_log (GNUNET_ERROR_TYPE_INFO,
250 : "Received version from URL `%s' with status %ld.\n",
251 : auditor->url,
252 : response_code);
253 0 : vc = TALER_AUDITOR_VC_PROTOCOL_ERROR;
254 0 : switch (response_code)
255 : {
256 0 : case 0:
257 : case MHD_HTTP_INTERNAL_SERVER_ERROR:
258 : /* NOTE: this design is debatable. We MAY want to throw this error at the
259 : client. We may then still additionally internally re-try. */
260 0 : GNUNET_assert (NULL == auditor->retry_task);
261 0 : auditor->retry_delay = EXCHANGE_LIB_BACKOFF (auditor->retry_delay);
262 0 : auditor->retry_task = GNUNET_SCHEDULER_add_delayed (auditor->retry_delay,
263 : &request_version,
264 : auditor);
265 0 : return;
266 0 : case MHD_HTTP_OK:
267 0 : if (NULL == resp_obj)
268 : {
269 0 : GNUNET_break_op (0);
270 0 : TALER_LOG_WARNING ("NULL body for a 200-OK /version\n");
271 0 : hr.http_status = 0;
272 0 : hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE;
273 0 : break;
274 : }
275 0 : hr.ec = decode_version_json (resp_obj,
276 : auditor,
277 : &vc);
278 0 : if (TALER_EC_NONE != hr.ec)
279 : {
280 0 : GNUNET_break_op (0);
281 0 : hr.http_status = 0;
282 0 : break;
283 : }
284 0 : auditor->retry_delay = GNUNET_TIME_UNIT_ZERO; /* restart quickly */
285 0 : break;
286 0 : default:
287 0 : hr.ec = TALER_JSON_get_error_code (resp_obj);
288 0 : hr.hint = TALER_JSON_get_error_hint (resp_obj);
289 0 : GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
290 : "Unexpected response code %u/%d\n",
291 : (unsigned int) response_code,
292 : (int) hr.ec);
293 0 : break;
294 : }
295 0 : if (MHD_HTTP_OK != response_code)
296 : {
297 0 : GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
298 : "/version failed for auditor %s: %u!\n",
299 : auditor->url,
300 : (unsigned int) response_code);
301 0 : auditor->state = MHS_FAILED;
302 : /* notify application that we failed */
303 0 : auditor->version_cb (auditor->version_cb_cls,
304 : &hr,
305 : NULL,
306 : vc);
307 0 : return;
308 : }
309 0 : TALER_LOG_DEBUG ("Switching auditor state to 'version'\n");
310 0 : auditor->state = MHS_VERSION;
311 0 : GNUNET_log (GNUNET_ERROR_TYPE_INFO,
312 : "Auditor %s is now READY!\n",
313 : auditor->url);
314 : /* notify application about the key information */
315 0 : auditor->version_cb (auditor->version_cb_cls,
316 : &hr,
317 0 : &auditor->vi,
318 : vc);
319 : }
320 :
321 :
322 : /**
323 : * Initiate download of /version from the auditor.
324 : *
325 : * @param cls auditor where to download /version from
326 : */
327 : static void
328 0 : request_version (void *cls)
329 : {
330 0 : struct TALER_AUDITOR_Handle *auditor = cls;
331 : CURL *eh;
332 :
333 0 : auditor->retry_task = NULL;
334 0 : GNUNET_assert (NULL == auditor->vr);
335 0 : GNUNET_log (GNUNET_ERROR_TYPE_INFO,
336 : "Requesting auditor version with URL `%s'.\n",
337 : auditor->vr_url);
338 0 : eh = TALER_AUDITOR_curl_easy_get_ (auditor->vr_url);
339 0 : if (NULL == eh)
340 : {
341 0 : GNUNET_break (0);
342 0 : auditor->retry_delay = EXCHANGE_LIB_BACKOFF (auditor->retry_delay);
343 0 : auditor->retry_task = GNUNET_SCHEDULER_add_delayed (auditor->retry_delay,
344 : &request_version,
345 : auditor);
346 0 : return;
347 : }
348 0 : GNUNET_break (CURLE_OK ==
349 : curl_easy_setopt (eh,
350 : CURLOPT_TIMEOUT,
351 : (long) 300));
352 0 : auditor->vr = GNUNET_CURL_job_add (auditor->ctx,
353 : eh,
354 : &version_completed_cb,
355 : auditor);
356 : }
357 :
358 :
359 : /* ********************* library internal API ********* */
360 :
361 :
362 : struct GNUNET_CURL_Context *
363 0 : TALER_AUDITOR_handle_to_context_ (struct TALER_AUDITOR_Handle *h)
364 : {
365 0 : return h->ctx;
366 : }
367 :
368 :
369 : enum GNUNET_GenericReturnValue
370 0 : TALER_AUDITOR_handle_is_ready_ (struct TALER_AUDITOR_Handle *h)
371 : {
372 0 : GNUNET_log (GNUNET_ERROR_TYPE_INFO,
373 : "Checking if auditor at `%s` is now ready: %s\n",
374 : h->url,
375 : (MHD_VERSION == h->state) ? "yes" : "no");
376 0 : return (MHS_VERSION == h->state) ? GNUNET_YES : GNUNET_NO;
377 : }
378 :
379 :
380 : char *
381 0 : TALER_AUDITOR_path_to_url_ (struct TALER_AUDITOR_Handle *h,
382 : const char *path)
383 : {
384 0 : GNUNET_assert ('/' == path[0]);
385 0 : return TALER_url_join (h->url,
386 : path + 1,
387 : NULL);
388 : }
389 :
390 :
391 : /* ********************* public API ******************* */
392 :
393 :
394 : struct TALER_AUDITOR_Handle *
395 0 : TALER_AUDITOR_connect (struct GNUNET_CURL_Context *ctx,
396 : const char *url,
397 : TALER_AUDITOR_VersionCallback version_cb,
398 : void *version_cb_cls)
399 : {
400 : struct TALER_AUDITOR_Handle *auditor;
401 :
402 0 : auditor = GNUNET_new (struct TALER_AUDITOR_Handle);
403 0 : auditor->version_cb = version_cb;
404 0 : auditor->version_cb_cls = version_cb_cls;
405 0 : auditor->retry_delay = GNUNET_TIME_UNIT_SECONDS; /* start slowly */
406 0 : auditor->ctx = ctx;
407 0 : auditor->url = GNUNET_strdup (url);
408 0 : auditor->vr_url = TALER_AUDITOR_path_to_url_ (auditor,
409 : "/version");
410 0 : if (NULL == auditor->vr_url)
411 : {
412 0 : GNUNET_break (0);
413 0 : GNUNET_free (auditor->url);
414 0 : GNUNET_free (auditor);
415 0 : return NULL;
416 : }
417 0 : auditor->retry_task = GNUNET_SCHEDULER_add_now (&request_version,
418 : auditor);
419 0 : GNUNET_log (GNUNET_ERROR_TYPE_INFO,
420 : "Connecting to auditor at URL `%s'.\n",
421 : url);
422 0 : return auditor;
423 : }
424 :
425 :
426 : void
427 0 : TALER_AUDITOR_disconnect (struct TALER_AUDITOR_Handle *auditor)
428 : {
429 0 : GNUNET_log (GNUNET_ERROR_TYPE_INFO,
430 : "Disconnecting from auditor at URL `%s'.\n",
431 : auditor->url);
432 0 : if (NULL != auditor->vr)
433 : {
434 0 : GNUNET_CURL_job_cancel (auditor->vr);
435 0 : auditor->vr = NULL;
436 : }
437 0 : if (NULL != auditor->retry_task)
438 : {
439 0 : GNUNET_SCHEDULER_cancel (auditor->retry_task);
440 0 : auditor->retry_task = NULL;
441 : }
442 0 : GNUNET_free (auditor->version);
443 0 : GNUNET_free (auditor->vr_url);
444 0 : GNUNET_free (auditor->url);
445 0 : GNUNET_free (auditor);
446 0 : }
447 :
448 :
449 : /* end of auditor_api_handle.c */
|