aboutsummaryrefslogtreecommitdiff
path: root/src/restclient/anastasis_api_policy_store.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/restclient/anastasis_api_policy_store.c')
-rw-r--r--src/restclient/anastasis_api_policy_store.c527
1 files changed, 527 insertions, 0 deletions
diff --git a/src/restclient/anastasis_api_policy_store.c b/src/restclient/anastasis_api_policy_store.c
new file mode 100644
index 0000000..7c6c244
--- /dev/null
+++ b/src/restclient/anastasis_api_policy_store.c
@@ -0,0 +1,527 @@
1/*
2 This file is part of ANASTASIS
3 Copyright (C) 2014-2021 Anastasis SARL
4
5 ANASTASIS is free software; you can redistribute it and/or modify
6 it under the terms of the GNU Lesser General Public License as
7 published by the Free Software Foundation; either version 2.1,
8 or (at your option) any later version.
9
10 ANASTASIS is distributed in the hope that it will be useful, but
11 WITHOUT ANY WARRANTY; without even the implied warranty of
12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 GNU Lesser General Public License for more details.
14
15 You should have received a copy of the GNU Lesser General Public
16 License along with ANASTASIS; see the file COPYING.LGPL. If not,
17 see <http://www.gnu.org/licenses/>
18*/
19
20/**
21 * @file lib/anastasis_api_policy_store.c
22 * @brief Implementation of the /policy GET and POST
23 * @author Christian Grothoff
24 * @author Dennis Neufeld
25 * @author Dominik Meister
26 */
27#include "platform.h"
28#include <curl/curl.h>
29#include <microhttpd.h> /* just for HTTP status codes */
30#include "anastasis_service.h"
31#include "anastasis_api_curl_defaults.h"
32#include <taler/taler_signatures.h>
33#include <taler/taler_merchant_service.h>
34#include <taler/taler_json_lib.h>
35
36
37struct ANASTASIS_PolicyStoreOperation
38{
39 /**
40 * Complete URL where the backend offers /policy
41 */
42 char *url;
43
44 /**
45 * Handle for the request.
46 */
47 struct GNUNET_CURL_Job *job;
48
49 /**
50 * The CURL context to connect to the backend
51 */
52 struct GNUNET_CURL_Context *ctx;
53
54 /**
55 * The callback to pass the backend response to
56 */
57 ANASTASIS_PolicyStoreCallback cb;
58
59 /**
60 * Closure for @e cb.
61 */
62 void *cb_cls;
63
64 /**
65 * Payment URI we received from the service, or NULL.
66 */
67 char *pay_uri;
68
69 /**
70 * Policy version we received from the service, or NULL.
71 */
72 char *policy_version;
73
74 /**
75 * Policy expiration we received from the service, or NULL.
76 */
77 char *policy_expiration;
78
79 /**
80 * Copy of the uploaded data. Needed by curl.
81 */
82 void *postcopy;
83
84 /**
85 * Hash of the data we are uploading.
86 */
87 struct GNUNET_HashCode new_upload_hash;
88};
89
90
91void
92ANASTASIS_policy_store_cancel (
93 struct ANASTASIS_PolicyStoreOperation *pso)
94{
95 if (NULL != pso->job)
96 {
97 GNUNET_CURL_job_cancel (pso->job);
98 pso->job = NULL;
99 }
100 GNUNET_free (pso->policy_version);
101 GNUNET_free (pso->policy_expiration);
102 GNUNET_free (pso->pay_uri);
103 GNUNET_free (pso->url);
104 GNUNET_free (pso->postcopy);
105 GNUNET_free (pso);
106}
107
108
109/**
110 * Callback to process POST /policy response
111 *
112 * @param cls the `struct ANASTASIS_PolicyStoreOperation`
113 * @param response_code HTTP response code, 0 on error
114 * @param data response body
115 * @param data_size number of bytes in @a data
116 */
117static void
118handle_policy_store_finished (void *cls,
119 long response_code,
120 const void *data,
121 size_t data_size)
122{
123 struct ANASTASIS_PolicyStoreOperation *pso = cls;
124 struct ANASTASIS_UploadDetails ud;
125
126 pso->job = NULL;
127 memset (&ud, 0, sizeof (ud));
128 ud.http_status = response_code;
129 ud.ec = TALER_EC_NONE;
130
131 GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
132 "Policy store finished with HTTP status %u\n",
133 (unsigned int) response_code);
134 switch (response_code)
135 {
136 case 0:
137 ud.us = ANASTASIS_US_SERVER_ERROR;
138 ud.ec = TALER_EC_GENERIC_INVALID_RESPONSE;
139 break;
140 case MHD_HTTP_NO_CONTENT:
141 case MHD_HTTP_NOT_MODIFIED:
142 {
143 unsigned long long version;
144 unsigned long long expiration;
145 char dummy;
146
147 if (1 != sscanf (pso->policy_version,
148 "%llu%c",
149 &version,
150 &dummy))
151 {
152 ud.ec = TALER_EC_GENERIC_REPLY_MALFORMED;
153 ud.us = ANASTASIS_US_SERVER_ERROR;
154 break;
155 }
156 if (1 != sscanf (pso->policy_expiration,
157 "%llu%c",
158 &expiration,
159 &dummy))
160 {
161 ud.ec = TALER_EC_GENERIC_REPLY_MALFORMED;
162 ud.us = ANASTASIS_US_SERVER_ERROR;
163 break;
164 }
165 ud.us = ANASTASIS_US_SUCCESS;
166 ud.details.success.curr_backup_hash = &pso->new_upload_hash;
167 ud.details.success.policy_expiration
168 = GNUNET_TIME_absolute_add (
169 GNUNET_TIME_UNIT_ZERO_ABS,
170 GNUNET_TIME_relative_multiply (
171 GNUNET_TIME_UNIT_SECONDS,
172 expiration));
173 ud.details.success.policy_version = version;
174 }
175 break;
176 case MHD_HTTP_BAD_REQUEST:
177 GNUNET_break (0);
178 ud.us = ANASTASIS_US_CLIENT_ERROR;
179 ud.ec = TALER_JSON_get_error_code2 (data,
180 data_size);
181 break;
182 case MHD_HTTP_PAYMENT_REQUIRED:
183 {
184 struct TALER_MERCHANT_PayUriData pd;
185
186 if ( (NULL == pso->pay_uri) ||
187 (GNUNET_OK !=
188 TALER_MERCHANT_parse_pay_uri (pso->pay_uri,
189 &pd)) )
190 {
191 GNUNET_break_op (0);
192 ud.ec = TALER_EC_ANASTASIS_GENERIC_INVALID_PAYMENT_REQUEST;
193 break;
194 }
195 GNUNET_log (GNUNET_ERROR_TYPE_INFO,
196 "Policy store operation requires payment `%s'\n",
197 pso->pay_uri);
198 if (GNUNET_OK !=
199 GNUNET_STRINGS_string_to_data (
200 pd.order_id,
201 strlen (pd.order_id),
202 &ud.details.payment.ps,
203 sizeof (ud.details.payment.ps)))
204 {
205 GNUNET_break (0);
206 ud.ec = TALER_EC_ANASTASIS_GENERIC_INVALID_PAYMENT_REQUEST;
207 TALER_MERCHANT_parse_pay_uri_free (&pd);
208 break;
209 }
210 TALER_MERCHANT_parse_pay_uri_free (&pd);
211 }
212 ud.us = ANASTASIS_US_PAYMENT_REQUIRED;
213 ud.details.payment.payment_request = pso->pay_uri;
214 break;
215 case MHD_HTTP_PAYLOAD_TOO_LARGE:
216 ud.us = ANASTASIS_US_CLIENT_ERROR;
217 ud.ec = TALER_EC_GENERIC_UPLOAD_EXCEEDS_LIMIT;
218 break;
219 case MHD_HTTP_LENGTH_REQUIRED:
220 GNUNET_break (0);
221 ud.ec = TALER_JSON_get_error_code2 (data,
222 data_size);
223 ud.us = ANASTASIS_US_SERVER_ERROR;
224 break;
225 case MHD_HTTP_INTERNAL_SERVER_ERROR:
226 ud.ec = TALER_JSON_get_error_code2 (data,
227 data_size);
228 ud.us = ANASTASIS_US_SERVER_ERROR;
229 break;
230 default:
231 ud.ec = TALER_JSON_get_error_code2 (data,
232 data_size);
233 ud.us = ANASTASIS_US_SERVER_ERROR;
234 break;
235 }
236 pso->cb (pso->cb_cls,
237 &ud);
238 pso->cb = NULL;
239 ANASTASIS_policy_store_cancel (pso);
240}
241
242
243/**
244 * Handle HTTP header received by curl.
245 *
246 * @param buffer one line of HTTP header data
247 * @param size size of an item
248 * @param nitems number of items passed
249 * @param userdata our `struct ANASTASIS_StorePolicyOperation *`
250 * @return `size * nitems`
251 */
252static size_t
253handle_header (char *buffer,
254 size_t size,
255 size_t nitems,
256 void *userdata)
257{
258 struct ANASTASIS_PolicyStoreOperation *pso = userdata;
259 size_t total = size * nitems;
260 char *ndup;
261 const char *hdr_type;
262 char *hdr_val;
263 char *sp;
264
265 ndup = GNUNET_strndup (buffer,
266 total);
267 hdr_type = strtok_r (ndup,
268 ":",
269 &sp);
270 if (NULL == hdr_type)
271 {
272 GNUNET_free (ndup);
273 return total;
274 }
275 hdr_val = strtok_r (NULL,
276 "",
277 &sp);
278 if (NULL == hdr_val)
279 {
280 GNUNET_free (ndup);
281 return total;
282 }
283 if (' ' == *hdr_val)
284 hdr_val++;
285 if (0 == strcasecmp (hdr_type,
286 "Taler"))
287 {
288 size_t len;
289
290 /* found payment URI we care about! */
291 GNUNET_free (pso->pay_uri); /* In case of duplicate header */
292 pso->pay_uri = GNUNET_strdup (hdr_val);
293 len = strlen (pso->pay_uri);
294 while ( (len > 0) &&
295 ( ('\n' == pso->pay_uri[len - 1]) ||
296 ('\r' == pso->pay_uri[len - 1]) ) )
297 {
298 len--;
299 pso->pay_uri[len] = '\0';
300 }
301 }
302
303 if (0 == strcasecmp (hdr_type,
304 ANASTASIS_HTTP_HEADER_POLICY_VERSION))
305 {
306 size_t len;
307
308 /* found policy version we care about! */
309 GNUNET_free (pso->policy_version); /* In case of duplicate header */
310 pso->policy_version = GNUNET_strdup (hdr_val);
311 len = strlen (pso->policy_version);
312 while ( (len > 0) &&
313 ( ('\n' == pso->policy_version[len - 1]) ||
314 ('\r' == pso->policy_version[len - 1]) ) )
315 {
316 len--;
317 pso->policy_version[len] = '\0';
318 }
319 }
320
321 if (0 == strcasecmp (hdr_type,
322 ANASTASIS_HTTP_HEADER_POLICY_EXPIRATION))
323 {
324 size_t len;
325
326 /* found policy expiration we care about! */
327 GNUNET_free (pso->policy_expiration); /* In case of duplicate header */
328 pso->policy_expiration = GNUNET_strdup (hdr_val);
329 len = strlen (pso->policy_expiration);
330 while ( (len > 0) &&
331 ( ('\n' == pso->policy_expiration[len - 1]) ||
332 ('\r' == pso->policy_expiration[len - 1]) ) )
333 {
334 len--;
335 pso->policy_expiration[len] = '\0';
336 }
337 }
338
339 GNUNET_free (ndup);
340 return total;
341}
342
343
344struct ANASTASIS_PolicyStoreOperation *
345ANASTASIS_policy_store (
346 struct GNUNET_CURL_Context *ctx,
347 const char *backend_url,
348 const struct ANASTASIS_CRYPTO_AccountPrivateKeyP *anastasis_priv,
349 const void *recovery_data,
350 size_t recovery_data_size,
351 uint32_t payment_years_requested,
352 const struct ANASTASIS_PaymentSecretP *payment_secret,
353 struct GNUNET_TIME_Relative payment_timeout,
354 ANASTASIS_PolicyStoreCallback cb,
355 void *cb_cls)
356{
357 struct ANASTASIS_PolicyStoreOperation *pso;
358 struct ANASTASIS_AccountSignatureP account_sig;
359 unsigned long long tms;
360 CURL *eh;
361 struct curl_slist *job_headers;
362 struct ANASTASIS_UploadSignaturePS usp = {
363 .purpose.purpose = htonl (TALER_SIGNATURE_ANASTASIS_POLICY_UPLOAD),
364 .purpose.size = htonl (sizeof (usp))
365 };
366
367 tms = (unsigned long long) (payment_timeout.rel_value_us
368 / GNUNET_TIME_UNIT_MILLISECONDS.rel_value_us);
369 GNUNET_CRYPTO_hash (recovery_data,
370 recovery_data_size,
371 &usp.new_recovery_data_hash);
372 GNUNET_CRYPTO_eddsa_sign (&anastasis_priv->priv,
373 &usp,
374 &account_sig.eddsa_sig);
375 /* setup our HTTP headers */
376 job_headers = NULL;
377 {
378 struct curl_slist *ext;
379 char *val;
380 char *hdr;
381
382 /* Set Anastasis-Policy-Signature header */
383 val = GNUNET_STRINGS_data_to_string_alloc (&account_sig,
384 sizeof (account_sig));
385 GNUNET_asprintf (&hdr,
386 "%s: %s",
387 ANASTASIS_HTTP_HEADER_POLICY_SIGNATURE,
388 val);
389 GNUNET_free (val);
390 ext = curl_slist_append (job_headers,
391 hdr);
392 GNUNET_free (hdr);
393 if (NULL == ext)
394 {
395 GNUNET_break (0);
396 curl_slist_free_all (job_headers);
397 return NULL;
398 }
399 job_headers = ext;
400
401 /* set Etag header */
402 val = GNUNET_STRINGS_data_to_string_alloc (&usp.new_recovery_data_hash,
403 sizeof (struct GNUNET_HashCode));
404 GNUNET_asprintf (&hdr,
405 "%s: %s",
406 MHD_HTTP_HEADER_IF_NONE_MATCH,
407 val);
408 GNUNET_free (val);
409 ext = curl_slist_append (job_headers,
410 hdr);
411 GNUNET_free (hdr);
412 if (NULL == ext)
413 {
414 GNUNET_break (0);
415 curl_slist_free_all (job_headers);
416 return NULL;
417 }
418 job_headers = ext;
419
420 /* Setup Payment-Identifier header */
421 if (NULL != payment_secret)
422 {
423 char *paid_order_id;
424
425 paid_order_id = GNUNET_STRINGS_data_to_string_alloc (
426 payment_secret,
427 sizeof (*payment_secret));
428 GNUNET_log (GNUNET_ERROR_TYPE_INFO,
429 "Beginning policy store operation with payment secret `%s'\n",
430 paid_order_id);
431 GNUNET_asprintf (&hdr,
432 "%s: %s",
433 ANASTASIS_HTTP_HEADER_PAYMENT_IDENTIFIER,
434 paid_order_id);
435 GNUNET_free (paid_order_id);
436 ext = curl_slist_append (job_headers,
437 hdr);
438 GNUNET_free (hdr);
439 if (NULL == ext)
440 {
441 GNUNET_break (0);
442 curl_slist_free_all (job_headers);
443 return NULL;
444 }
445 job_headers = ext;
446 }
447 else
448 {
449 GNUNET_log (GNUNET_ERROR_TYPE_INFO,
450 "Beginning policy store operation without payment secret\n");
451 }
452 }
453 /* Finished setting up headers */
454 pso = GNUNET_new (struct ANASTASIS_PolicyStoreOperation);
455 pso->postcopy = GNUNET_memdup (recovery_data,
456 recovery_data_size);
457 pso->new_upload_hash = usp.new_recovery_data_hash;
458 {
459 char *acc_pub_str;
460 char *path;
461 struct ANASTASIS_CRYPTO_AccountPublicKeyP pub;
462 char timeout_ms[32];
463 char pyrs[32];
464
465 GNUNET_snprintf (timeout_ms,
466 sizeof (timeout_ms),
467 "%llu",
468 tms);
469 GNUNET_snprintf (pyrs,
470 sizeof (pyrs),
471 "%u",
472 (unsigned int) payment_years_requested);
473 GNUNET_CRYPTO_eddsa_key_get_public (&anastasis_priv->priv,
474 &pub.pub);
475 acc_pub_str
476 = GNUNET_STRINGS_data_to_string_alloc (&pub,
477 sizeof (pub));
478 GNUNET_asprintf (&path,
479 "policy/%s",
480 acc_pub_str);
481 GNUNET_free (acc_pub_str);
482 pso->url = TALER_url_join (backend_url,
483 path,
484 "storage_duration",
485 (0 != payment_years_requested)
486 ? pyrs
487 : NULL,
488 "timeout_ms",
489 (0 != payment_timeout.rel_value_us)
490 ? timeout_ms
491 : NULL,
492 NULL);
493 GNUNET_free (path);
494 }
495 pso->ctx = ctx;
496 pso->cb = cb;
497 pso->cb_cls = cb_cls;
498 eh = ANASTASIS_curl_easy_get_ (pso->url);
499 if (0 != tms)
500 GNUNET_assert (CURLE_OK ==
501 curl_easy_setopt (eh,
502 CURLOPT_TIMEOUT_MS,
503 (long) (tms + 5000)));
504 GNUNET_assert (CURLE_OK ==
505 curl_easy_setopt (eh,
506 CURLOPT_POSTFIELDS,
507 pso->postcopy));
508 GNUNET_assert (CURLE_OK ==
509 curl_easy_setopt (eh,
510 CURLOPT_POSTFIELDSIZE,
511 (long) recovery_data_size));
512 GNUNET_assert (CURLE_OK ==
513 curl_easy_setopt (eh,
514 CURLOPT_HEADERFUNCTION,
515 &handle_header));
516 GNUNET_assert (CURLE_OK ==
517 curl_easy_setopt (eh,
518 CURLOPT_HEADERDATA,
519 pso));
520 pso->job = GNUNET_CURL_job_add_raw (ctx,
521 eh,
522 job_headers,
523 &handle_policy_store_finished,
524 pso);
525 curl_slist_free_all (job_headers);
526 return pso;
527}