Line data Source code
1 : /*
2 : This file is part of TALER
3 : (C) 2014-2025 Taler Systems SA
4 :
5 : TALER is free software; you can redistribute it and/or modify it under the
6 : terms of the GNU Affero 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 <http://www.gnu.org/licenses/>
15 : */
16 : /**
17 : * @file taler-merchant-httpd.c
18 : * @brief HTTP serving layer intended to perform crypto-work and
19 : * communication with the exchange
20 : * @author Marcello Stanisci
21 : * @author Christian Grothoff
22 : * @author Florian Dold
23 : * @author Priscilla HUANG
24 : */
25 : #include "platform.h"
26 : #include <taler/taler_dbevents.h>
27 : #include <taler/taler_bank_service.h>
28 : #include <taler/taler_mhd_lib.h>
29 : #include <taler/taler_templating_lib.h>
30 : #include <taler/taler_exchange_service.h>
31 : #include "taler_merchant_util.h"
32 : #include "taler-merchant-httpd_config.h"
33 : #include "taler-merchant-httpd_exchanges.h"
34 : #include "taler-merchant-httpd_get-orders-ID.h"
35 : #include "taler-merchant-httpd_get-templates-ID.h"
36 : #include "taler-merchant-httpd_helper.h"
37 : #include "taler-merchant-httpd_mhd.h"
38 : #include "taler-merchant-httpd_private-delete-account-ID.h"
39 : #include "taler-merchant-httpd_private-delete-categories-ID.h"
40 : #include "taler-merchant-httpd_private-delete-instances-ID.h"
41 : #include "taler-merchant-httpd_private-delete-instances-ID-token.h"
42 : #include "taler-merchant-httpd_private-delete-products-ID.h"
43 : #include "taler-merchant-httpd_private-delete-orders-ID.h"
44 : #include "taler-merchant-httpd_private-delete-otp-devices-ID.h"
45 : #include "taler-merchant-httpd_private-delete-templates-ID.h"
46 : #include "taler-merchant-httpd_private-delete-token-families-SLUG.h"
47 : #include "taler-merchant-httpd_private-delete-transfers-ID.h"
48 : #include "taler-merchant-httpd_private-delete-webhooks-ID.h"
49 : #include "taler-merchant-httpd_private-get-accounts.h"
50 : #include "taler-merchant-httpd_private-get-accounts-ID.h"
51 : #include "taler-merchant-httpd_private-get-categories.h"
52 : #include "taler-merchant-httpd_private-get-categories-ID.h"
53 : #include "taler-merchant-httpd_private-get-incoming.h"
54 : #include "taler-merchant-httpd_private-get-instances.h"
55 : #include "taler-merchant-httpd_private-get-instances-ID.h"
56 : #include "taler-merchant-httpd_private-get-instances-ID-kyc.h"
57 : #include "taler-merchant-httpd_private-get-instances-ID-tokens.h"
58 : #include "taler-merchant-httpd_private-get-pos.h"
59 : #include "taler-merchant-httpd_private-get-products.h"
60 : #include "taler-merchant-httpd_private-get-products-ID.h"
61 : #include "taler-merchant-httpd_private-get-orders.h"
62 : #include "taler-merchant-httpd_private-get-orders-ID.h"
63 : #include "taler-merchant-httpd_private-get-otp-devices.h"
64 : #include "taler-merchant-httpd_private-get-otp-devices-ID.h"
65 : #include "taler-merchant-httpd_private-get-statistics-amount-SLUG.h"
66 : #include "taler-merchant-httpd_private-get-statistics-counter-SLUG.h"
67 : #include "taler-merchant-httpd_private-get-templates.h"
68 : #include "taler-merchant-httpd_private-get-templates-ID.h"
69 : #include "taler-merchant-httpd_private-get-token-families.h"
70 : #include "taler-merchant-httpd_private-get-token-families-SLUG.h"
71 : #include "taler-merchant-httpd_private-get-transfers.h"
72 : #include "taler-merchant-httpd_private-get-webhooks.h"
73 : #include "taler-merchant-httpd_private-get-webhooks-ID.h"
74 : #include "taler-merchant-httpd_private-patch-accounts-ID.h"
75 : #include "taler-merchant-httpd_private-patch-categories-ID.h"
76 : #include "taler-merchant-httpd_private-patch-instances-ID.h"
77 : #include "taler-merchant-httpd_private-patch-orders-ID-forget.h"
78 : #include "taler-merchant-httpd_private-patch-otp-devices-ID.h"
79 : #include "taler-merchant-httpd_private-patch-products-ID.h"
80 : #include "taler-merchant-httpd_private-patch-templates-ID.h"
81 : #include "taler-merchant-httpd_private-patch-token-families-SLUG.h"
82 : #include "taler-merchant-httpd_private-patch-webhooks-ID.h"
83 : #include "taler-merchant-httpd_private-post-account.h"
84 : #include "taler-merchant-httpd_private-post-categories.h"
85 : #include "taler-merchant-httpd_private-post-instances.h"
86 : #include "taler-merchant-httpd_private-post-instances-ID-auth.h"
87 : #include "taler-merchant-httpd_private-post-instances-ID-token.h"
88 : #include "taler-merchant-httpd_private-post-otp-devices.h"
89 : #include "taler-merchant-httpd_private-post-orders.h"
90 : #include "taler-merchant-httpd_private-post-orders-ID-refund.h"
91 : #include "taler-merchant-httpd_private-post-products.h"
92 : #include "taler-merchant-httpd_private-post-products-ID-lock.h"
93 : #include "taler-merchant-httpd_private-post-templates.h"
94 : #include "taler-merchant-httpd_private-post-token-families.h"
95 : #include "taler-merchant-httpd_private-post-transfers.h"
96 : #include "taler-merchant-httpd_private-post-webhooks.h"
97 : #include "taler-merchant-httpd_post-orders-ID-abort.h"
98 : #include "taler-merchant-httpd_post-orders-ID-claim.h"
99 : #include "taler-merchant-httpd_post-orders-ID-paid.h"
100 : #include "taler-merchant-httpd_post-orders-ID-pay.h"
101 : #include "taler-merchant-httpd_post-using-templates.h"
102 : #include "taler-merchant-httpd_post-orders-ID-refund.h"
103 : #include "taler-merchant-httpd_spa.h"
104 : #include "taler-merchant-httpd_statics.h"
105 :
106 :
107 : /**
108 : * Fixme: document.
109 : */
110 : #define INSTANCE_STALENESS GNUNET_TIME_UNIT_MINUTES
111 :
112 : /**
113 : * Backlog for listen operation on unix-domain sockets.
114 : */
115 : #define UNIX_BACKLOG 500
116 :
117 : /**
118 : * Default maximum upload size permitted. Can be overridden
119 : * per handler.
120 : */
121 : #define DEFAULT_MAX_UPLOAD_SIZE (16 * 1024)
122 :
123 : /**
124 : * Which currency do we use?
125 : */
126 : char *TMH_currency;
127 :
128 : /**
129 : * What is the base URL for this merchant backend? NULL if it is not
130 : * configured and is to be determined from HTTP headers (X-Forwarded-Host and
131 : * X-Forwarded-Port and X-Forwarded-Prefix) of the reverse proxy.
132 : */
133 : char *TMH_base_url;
134 :
135 : /**
136 : * Inform the auditor for all deposit confirmations (global option)
137 : */
138 : int TMH_force_audit;
139 :
140 : /**
141 : * Connection handle to the our database
142 : */
143 : struct TALER_MERCHANTDB_Plugin *TMH_db;
144 :
145 : /**
146 : * Event handler for instance settings changes.
147 : */
148 : static struct GNUNET_DB_EventHandler *instance_eh;
149 :
150 : /**
151 : * Hashmap pointing at merchant instances by 'id'. An 'id' is
152 : * just a string that identifies a merchant instance. When a frontend
153 : * needs to specify an instance to the backend, it does so by 'id'
154 : */
155 : struct GNUNET_CONTAINER_MultiHashMap *TMH_by_id_map;
156 :
157 : /**
158 : * #GNUNET_YES if protocol version 19 is strictly enforced.
159 : * (Default is #GNUNET_NO)
160 : */
161 : int TMH_strict_v19;
162 :
163 : /**
164 : * #GNUNET_YES if authentication is disabled (For testing only!!).
165 : * (Default is #GNUNET_NO)
166 : */
167 : int TMH_auth_disabled;
168 :
169 : /**
170 : * How long do we need to keep information on paid contracts on file for tax
171 : * or other legal reasons? Used to block deletions for younger transaction
172 : * data.
173 : */
174 : struct GNUNET_TIME_Relative TMH_legal_expiration;
175 :
176 : /**
177 : * Length of the TMH_cspecs array.
178 : */
179 : unsigned int TMH_num_cspecs;
180 :
181 : /**
182 : * Rendering specs for currencies.
183 : */
184 : struct TALER_CurrencySpecification *TMH_cspecs;
185 :
186 : /**
187 : * True if we started any HTTP daemon.
188 : */
189 : static bool have_daemons;
190 :
191 : /**
192 : * Should a "Connection: close" header be added to each HTTP response?
193 : */
194 : static int merchant_connection_close;
195 :
196 : /**
197 : * Context for all exchange operations (useful to the event loop).
198 : */
199 : struct GNUNET_CURL_Context *TMH_curl_ctx;
200 :
201 : /**
202 : * Context for integrating #TMH_curl_ctx with the
203 : * GNUnet event loop.
204 : */
205 : static struct GNUNET_CURL_RescheduleContext *merchant_curl_rc;
206 :
207 : /**
208 : * Global return code
209 : */
210 : static int global_ret;
211 :
212 : /**
213 : * Our configuration.
214 : */
215 : static const struct GNUNET_CONFIGURATION_Handle *cfg;
216 :
217 : /**
218 : * Maximum length of a permissions string of a scope
219 : */
220 : #define TMH_MAX_SCOPE_PERMISSIONS_LEN 4096
221 :
222 : /**
223 : * Maximum length of a name of a scope
224 : */
225 : #define TMH_MAX_NAME_LEN 255
226 :
227 : /**
228 : * Represents a hard-coded set of default scopes with their
229 : * permissions and names
230 : */
231 : struct ScopePermissionMap
232 : {
233 : /**
234 : * The scope enum value
235 : */
236 : enum TMH_AuthScope as;
237 :
238 : /**
239 : * The scope name
240 : */
241 : char name[TMH_MAX_NAME_LEN];
242 :
243 : /**
244 : * The scope permissions string.
245 : * Comma-separated.
246 : */
247 : char permissions[TMH_MAX_SCOPE_PERMISSIONS_LEN];
248 : };
249 :
250 : /**
251 : * The default scopes array for merchant
252 : */
253 : struct ScopePermissionMap scope_permissions[] = {
254 : /* Deprecated since v19 */
255 : {
256 : .as = TMH_AS_ALL,
257 : .name = "write",
258 : .permissions = "*"
259 : },
260 : /* Full access for SPA */
261 : {
262 : .as = TMH_AS_ALL,
263 : .name = "all",
264 : .permissions = "*"
265 : },
266 : /* Full access for SPA */
267 : {
268 : .as = TMH_AS_SPA,
269 : .name = "spa",
270 : .permissions = "*"
271 : },
272 : /* Read-only access */
273 : {
274 : .as = TMH_AS_READ_ONLY,
275 : .name = "readonly",
276 : .permissions = "*-read"
277 : },
278 : /* Simple order management */
279 : {
280 : .as = TMH_AS_ORDER_SIMPLE,
281 : .name = "order-simple",
282 : .permissions = "orders-read,orders-write"
283 : },
284 : /* Simple order management for PoS, also allows inventory locking */
285 : {
286 : .as = TMH_AS_ORDER_POS,
287 : .name = "order-pos",
288 : .permissions = "orders-read,orders-write,inventory-lock"
289 : },
290 : /* Simple order management, also allows refunding */
291 : {
292 : .as = TMH_AS_ORDER_MGMT,
293 : .name = "order-mgmt",
294 : .permissions = "orders-read,orders-write,orders-refund"
295 : },
296 : /* Full order management, allows inventory locking and refunds */
297 : {
298 : .as = TMH_AS_ORDER_FULL,
299 : .name = "order-full",
300 : .permissions = "orders-read,orders-write,inventory-lock,orders-refund"
301 : },
302 : /* No permissions, dummy scope */
303 : {
304 : .as = TMH_AS_NONE,
305 : }
306 : };
307 :
308 :
309 : /**
310 : * Get permissions string for scope.
311 : * Also extracts the leftmost bit into the @a refreshable
312 : * output parameter.
313 : *
314 : * @param as the scope to get the permissions string from
315 : * @param[out] refreshable true if the token associated with this scope is refreshable.
316 : * @return the permissions string, or NULL if no such scope found
317 : */
318 : static const char*
319 508 : get_scope_permissions (enum TMH_AuthScope as,
320 : bool *refreshable)
321 : {
322 508 : *refreshable = as & TMH_AS_REFRESHABLE;
323 709 : for (unsigned int i = 0; TMH_AS_NONE != scope_permissions[i].as; i++)
324 : {
325 : /* We ignore the TMH_AS_REFRESHABLE bit */
326 691 : if ( (as & ~TMH_AS_REFRESHABLE) ==
327 691 : (scope_permissions[i].as & ~TMH_AS_REFRESHABLE) )
328 490 : return scope_permissions[i].permissions;
329 : }
330 18 : return NULL;
331 : }
332 :
333 :
334 : /**
335 : * Checks if @a permission_required is in permissions of
336 : * @a scope.
337 : *
338 : * @param permission_required the permission to check.
339 : * @param scope the scope to check.
340 : * @return true if @a permission_required is in the permissions set of @a scope.
341 : */
342 : static bool
343 478 : permission_in_scope (const char *permission_required,
344 : enum TMH_AuthScope scope)
345 : {
346 : char *permissions;
347 : const char *perms_tmp;
348 478 : bool is_read_perm = false;
349 478 : bool is_write_perm = false;
350 : bool refreshable;
351 : const char *last_dash;
352 :
353 478 : perms_tmp = get_scope_permissions (scope,
354 : &refreshable);
355 478 : if (NULL == perms_tmp)
356 : {
357 18 : GNUNET_break_op (0);
358 18 : return false;
359 : }
360 460 : last_dash = strrchr (permission_required,
361 : '-');
362 460 : if (NULL != last_dash)
363 : {
364 459 : is_write_perm = (0 == strcmp (last_dash,
365 : "-write"));
366 459 : is_read_perm = (0 == strcmp (last_dash,
367 : "-read"));
368 : }
369 :
370 460 : if (0 == strcmp ("token-refresh",
371 : permission_required))
372 16 : return refreshable;
373 444 : permissions = GNUNET_strdup (perms_tmp);
374 : {
375 444 : const char *perm = strtok (permissions,
376 : ",");
377 :
378 444 : if (NULL == perm)
379 : {
380 0 : GNUNET_free (permissions);
381 0 : return false;
382 : }
383 445 : while (NULL != perm)
384 : {
385 444 : if (0 == strcmp ("*",
386 : perm))
387 : {
388 442 : GNUNET_free (permissions);
389 442 : return true;
390 : }
391 2 : if ( (0 == strcmp ("*-write",
392 0 : perm)) &&
393 : (is_write_perm) )
394 : {
395 0 : GNUNET_free (permissions);
396 0 : return true;
397 : }
398 2 : if ( (0 == strcmp ("*-read",
399 2 : perm)) &&
400 : (is_read_perm) )
401 : {
402 1 : GNUNET_free (permissions);
403 1 : return true;
404 : }
405 1 : if (0 == strcmp (permission_required,
406 : perm))
407 : {
408 0 : GNUNET_free (permissions);
409 0 : return true;
410 : }
411 1 : perm = strtok (NULL,
412 : ",");
413 : }
414 : }
415 1 : GNUNET_free (permissions);
416 1 : return false;
417 : }
418 :
419 :
420 : bool
421 15 : TMH_scope_is_subset (enum TMH_AuthScope as,
422 : enum TMH_AuthScope candidate)
423 : {
424 : const char *as_perms;
425 : const char *candidate_perms;
426 : char *permissions;
427 : bool as_refreshable;
428 : bool cand_refreshable;
429 :
430 15 : as_perms = get_scope_permissions (as,
431 : &as_refreshable);
432 15 : candidate_perms = get_scope_permissions (candidate,
433 : &cand_refreshable);
434 15 : if (! as_refreshable && cand_refreshable)
435 0 : return false;
436 15 : if ( (NULL == as_perms) &&
437 : (NULL != candidate_perms) )
438 0 : return false;
439 15 : if ( (NULL == candidate_perms) ||
440 15 : (0 == strcmp ("*",
441 : as_perms)))
442 14 : return true;
443 1 : permissions = GNUNET_strdup (candidate_perms);
444 : {
445 : const char *perm;
446 :
447 1 : perm = strtok (permissions,
448 : ",");
449 1 : if (NULL == perm)
450 : {
451 0 : GNUNET_free (permissions);
452 0 : return true;
453 : }
454 1 : while (NULL != perm)
455 : {
456 1 : if (! permission_in_scope (perm,
457 : as))
458 : {
459 1 : GNUNET_free (permissions);
460 1 : return false;
461 : }
462 0 : perm = strtok (NULL,
463 : ",");
464 : }
465 : }
466 0 : GNUNET_free (permissions);
467 0 : return true;
468 : }
469 :
470 :
471 : enum TMH_AuthScope
472 15 : TMH_get_scope_by_name (const char *name)
473 : {
474 36 : for (unsigned int i = 0; TMH_AS_NONE != scope_permissions[i].as; i++)
475 : {
476 36 : if (0 == strcasecmp (scope_permissions[i].name,
477 : name))
478 15 : return scope_permissions[i].as;
479 : }
480 0 : return TMH_AS_NONE;
481 : }
482 :
483 :
484 : const char*
485 2 : TMH_get_name_by_scope (enum TMH_AuthScope scope, bool *refreshable)
486 : {
487 2 : *refreshable = scope & TMH_AS_REFRESHABLE;
488 8 : for (unsigned int i = 0; TMH_AS_NONE != scope_permissions[i].as; i++)
489 : {
490 : /* We ignore the TMH_AS_REFRESHABLE bit */
491 8 : if ( (scope & ~TMH_AS_REFRESHABLE) ==
492 8 : (scope_permissions[i].as & ~TMH_AS_REFRESHABLE) )
493 2 : return scope_permissions[i].name;
494 : }
495 0 : return NULL;
496 : }
497 :
498 :
499 : enum GNUNET_GenericReturnValue
500 23 : TMH_check_auth (const char *password,
501 : struct TALER_MerchantAuthenticationSaltP *salt,
502 : struct TALER_MerchantAuthenticationHashP *hash)
503 : {
504 : struct TALER_MerchantAuthenticationHashP val;
505 :
506 23 : if (GNUNET_is_zero (hash))
507 0 : return GNUNET_OK;
508 23 : if (NULL == password)
509 0 : return GNUNET_SYSERR;
510 23 : GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
511 : "Checking against token with salt %s\n",
512 : TALER_B2S (salt));
513 23 : TALER_merchant_instance_auth_hash_with_salt (&val,
514 : salt,
515 : password);
516 : return (0 ==
517 23 : GNUNET_memcmp (&val,
518 : hash))
519 : ? GNUNET_OK
520 23 : : GNUNET_SYSERR;
521 : }
522 :
523 :
524 : /**
525 : * Check if @a userpass grants access to @a instance.
526 : *
527 : * @param userpass base64 encoded "$USERNAME:$PASSWORD" value
528 : * from HTTP Basic "Authentication" header
529 : * @param instances the access controlled instance
530 : */
531 : static enum GNUNET_GenericReturnValue
532 14 : check_auth_instance (const char *userpass,
533 : struct TMH_MerchantInstance *instance)
534 : {
535 : char *tmp;
536 : char *colon;
537 : const char *instance_name;
538 : const char *password;
539 14 : const char *target_instance = "admin";
540 : enum GNUNET_GenericReturnValue ret;
541 :
542 : /* implicitly a zeroed out hash means no authentication */
543 14 : if (GNUNET_is_zero (&instance->auth.auth_hash))
544 0 : return GNUNET_OK;
545 14 : if (NULL == userpass)
546 0 : return GNUNET_SYSERR;
547 14 : if (0 ==
548 14 : GNUNET_STRINGS_base64_decode (userpass,
549 : strlen (userpass),
550 : (void**) &tmp))
551 : {
552 0 : return GNUNET_SYSERR;
553 : }
554 14 : colon = strchr (tmp,
555 : ':');
556 14 : if (NULL == colon)
557 : {
558 0 : GNUNET_free (tmp);
559 0 : return GNUNET_SYSERR;
560 : }
561 14 : *colon = '\0';
562 14 : instance_name = tmp;
563 14 : password = colon + 1;
564 : /* instance->settings.id can be NULL if there is no instance yet */
565 14 : if (NULL != instance->settings.id)
566 14 : target_instance = instance->settings.id;
567 14 : if (0 != strcmp (instance_name,
568 : target_instance))
569 : {
570 0 : GNUNET_free (tmp);
571 0 : return GNUNET_SYSERR;
572 : }
573 14 : ret = TMH_check_auth (password,
574 : &instance->auth.auth_salt,
575 : &instance->auth.auth_hash);
576 14 : GNUNET_free (tmp);
577 14 : return ret;
578 : }
579 :
580 :
581 : void
582 13 : TMH_compute_auth (const char *token,
583 : struct TALER_MerchantAuthenticationSaltP *salt,
584 : struct TALER_MerchantAuthenticationHashP *hash)
585 : {
586 13 : GNUNET_CRYPTO_random_block (GNUNET_CRYPTO_QUALITY_NONCE,
587 : salt,
588 : sizeof (*salt));
589 13 : GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
590 : "Computing initial auth using token with salt %s\n",
591 : TALER_B2S (salt));
592 13 : TALER_merchant_instance_auth_hash_with_salt (hash,
593 : salt,
594 : token);
595 13 : }
596 :
597 :
598 : void
599 52 : TMH_wire_method_free (struct TMH_WireMethod *wm)
600 : {
601 52 : GNUNET_free (wm->payto_uri.full_payto);
602 52 : GNUNET_free (wm->wire_method);
603 52 : GNUNET_free (wm->credit_facade_url);
604 52 : json_decref (wm->credit_facade_credentials);
605 52 : GNUNET_free (wm);
606 52 : }
607 :
608 :
609 : void
610 718 : TMH_instance_decref (struct TMH_MerchantInstance *mi)
611 : {
612 : struct TMH_WireMethod *wm;
613 :
614 718 : mi->rc--;
615 718 : if (0 != mi->rc)
616 621 : return;
617 97 : TMH_force_get_orders_resume (mi);
618 149 : while (NULL != (wm = (mi->wm_head)))
619 : {
620 52 : GNUNET_CONTAINER_DLL_remove (mi->wm_head,
621 : mi->wm_tail,
622 : wm);
623 52 : TMH_wire_method_free (wm);
624 : }
625 :
626 97 : GNUNET_free (mi->settings.id);
627 97 : GNUNET_free (mi->settings.name);
628 97 : GNUNET_free (mi->settings.email);
629 97 : GNUNET_free (mi->settings.website);
630 97 : GNUNET_free (mi->settings.logo);
631 97 : json_decref (mi->settings.address);
632 97 : json_decref (mi->settings.jurisdiction);
633 97 : GNUNET_free (mi);
634 : }
635 :
636 :
637 : enum GNUNET_GenericReturnValue
638 97 : TMH_instance_free_cb (void *cls,
639 : const struct GNUNET_HashCode *key,
640 : void *value)
641 : {
642 97 : struct TMH_MerchantInstance *mi = value;
643 :
644 : (void) cls;
645 : (void) key;
646 97 : GNUNET_assert (GNUNET_OK ==
647 : GNUNET_CONTAINER_multihashmap_remove (TMH_by_id_map,
648 : &mi->h_instance,
649 : mi));
650 97 : TMH_instance_decref (mi);
651 97 : return GNUNET_YES;
652 : }
653 :
654 :
655 : /**
656 : * Shutdown task (invoked when the application is being
657 : * terminated for any reason)
658 : *
659 : * @param cls NULL
660 : */
661 : static void
662 14 : do_shutdown (void *cls)
663 : {
664 : (void) cls;
665 14 : TALER_MHD_daemons_halt ();
666 14 : TMH_force_orders_resume ();
667 14 : TMH_force_ac_resume ();
668 14 : TMH_force_pc_resume ();
669 14 : TMH_force_kyc_resume ();
670 14 : TMH_force_gorc_resume ();
671 14 : TMH_force_wallet_get_order_resume ();
672 14 : TMH_force_wallet_refund_order_resume ();
673 14 : TALER_MHD_daemons_destroy ();
674 14 : if (NULL != instance_eh)
675 : {
676 14 : TMH_db->event_listen_cancel (instance_eh);
677 14 : instance_eh = NULL;
678 : }
679 14 : TMH_EXCHANGES_done ();
680 14 : if (NULL != TMH_db)
681 : {
682 14 : TALER_MERCHANTDB_plugin_unload (TMH_db);
683 14 : TMH_db = NULL;
684 : }
685 14 : if (NULL != TMH_by_id_map)
686 : {
687 14 : GNUNET_CONTAINER_multihashmap_iterate (TMH_by_id_map,
688 : &TMH_instance_free_cb,
689 : NULL);
690 14 : GNUNET_CONTAINER_multihashmap_destroy (TMH_by_id_map);
691 14 : TMH_by_id_map = NULL;
692 : }
693 14 : TALER_TEMPLATING_done ();
694 14 : if (NULL != TMH_curl_ctx)
695 : {
696 14 : GNUNET_CURL_fini (TMH_curl_ctx);
697 14 : TMH_curl_ctx = NULL;
698 : }
699 14 : if (NULL != merchant_curl_rc)
700 : {
701 14 : GNUNET_CURL_gnunet_rc_destroy (merchant_curl_rc);
702 14 : merchant_curl_rc = NULL;
703 : }
704 14 : }
705 :
706 :
707 : /**
708 : * Function called whenever MHD is done with a request. If the
709 : * request was a POST, we may have stored a `struct Buffer *` in the
710 : * @a con_cls that might still need to be cleaned up. Call the
711 : * respective function to free the memory.
712 : *
713 : * @param cls client-defined closure
714 : * @param connection connection handle
715 : * @param con_cls value as set by the last call to
716 : * the #MHD_AccessHandlerCallback
717 : * @param toe reason for request termination
718 : * @see #MHD_OPTION_NOTIFY_COMPLETED
719 : * @ingroup request
720 : */
721 : static void
722 661 : handle_mhd_completion_callback (void *cls,
723 : struct MHD_Connection *connection,
724 : void **con_cls,
725 : enum MHD_RequestTerminationCode toe)
726 : {
727 661 : struct TMH_HandlerContext *hc = *con_cls;
728 :
729 : (void) cls;
730 661 : if (NULL == hc)
731 0 : return;
732 661 : GNUNET_SCHEDULER_begin_async_scope (&hc->async_scope_id);
733 : {
734 : #if MHD_VERSION >= 0x00097304
735 : const union MHD_ConnectionInfo *ci;
736 661 : unsigned int http_status = 0;
737 :
738 661 : ci = MHD_get_connection_info (connection,
739 : MHD_CONNECTION_INFO_HTTP_STATUS);
740 661 : if (NULL != ci)
741 661 : http_status = ci->http_status;
742 661 : GNUNET_log (GNUNET_ERROR_TYPE_INFO,
743 : "Request for `%s' completed with HTTP status %u (%d)\n",
744 : hc->url,
745 : http_status,
746 : toe);
747 : #else
748 : (void) connection;
749 : GNUNET_log (GNUNET_ERROR_TYPE_INFO,
750 : "Finished handling request for `%s' with MHD termination code %d\n",
751 : hc->url,
752 : (int) toe);
753 : #endif
754 : }
755 661 : if (NULL != hc->cc)
756 242 : hc->cc (hc->ctx);
757 661 : TALER_MHD_parse_post_cleanup_callback (hc->json_parse_context);
758 661 : GNUNET_free (hc->infix);
759 661 : if (NULL != hc->request_body)
760 380 : json_decref (hc->request_body);
761 661 : if (NULL != hc->instance)
762 621 : TMH_instance_decref (hc->instance);
763 661 : TMH_db->preflight (TMH_db->cls);
764 661 : GNUNET_free (hc->full_url);
765 661 : GNUNET_free (hc);
766 661 : *con_cls = NULL;
767 : }
768 :
769 :
770 : struct TMH_MerchantInstance *
771 915 : TMH_lookup_instance (const char *instance_id)
772 : {
773 : struct GNUNET_HashCode h_instance;
774 :
775 915 : if (NULL == instance_id)
776 578 : instance_id = "admin";
777 915 : GNUNET_CRYPTO_hash (instance_id,
778 : strlen (instance_id),
779 : &h_instance);
780 915 : GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
781 : "Looking for by-id key %s of '%s' in hashmap\n",
782 : GNUNET_h2s (&h_instance),
783 : instance_id);
784 : /* We're fine if that returns NULL, the calling routine knows how
785 : to handle that */
786 915 : return GNUNET_CONTAINER_multihashmap_get (TMH_by_id_map,
787 : &h_instance);
788 : }
789 :
790 :
791 : /**
792 : * Add instance definition to our active set of instances.
793 : *
794 : * @param[in,out] mi merchant instance details to define
795 : * @return #GNUNET_OK on success, #GNUNET_NO if the same ID is in use already
796 : */
797 : enum GNUNET_GenericReturnValue
798 97 : TMH_add_instance (struct TMH_MerchantInstance *mi)
799 : {
800 : const char *id;
801 : int ret;
802 :
803 97 : id = mi->settings.id;
804 97 : if (NULL == id)
805 0 : id = "admin";
806 97 : GNUNET_CRYPTO_hash (id,
807 : strlen (id),
808 : &mi->h_instance);
809 97 : GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
810 : "Looking for by-id key %s of `%s' in hashmap\n",
811 : GNUNET_h2s (&mi->h_instance),
812 : id);
813 97 : ret = GNUNET_CONTAINER_multihashmap_put (TMH_by_id_map,
814 97 : &mi->h_instance,
815 : mi,
816 : GNUNET_CONTAINER_MULTIHASHMAPOPTION_UNIQUE_ONLY);
817 97 : if (GNUNET_OK == ret)
818 : {
819 97 : GNUNET_assert (mi->rc < UINT_MAX);
820 97 : mi->rc++;
821 : }
822 97 : return ret;
823 : }
824 :
825 :
826 : /**
827 : * Handle a OPTIONS "*" request.
828 : *
829 : * @param rh context of the handler
830 : * @param connection the MHD connection to handle
831 : * @param[in,out] hc context with further information about the request
832 : * @return MHD result code
833 : */
834 : static MHD_RESULT
835 0 : handle_server_options (const struct TMH_RequestHandler *rh,
836 : struct MHD_Connection *connection,
837 : struct TMH_HandlerContext *hc)
838 : {
839 : (void) rh;
840 : (void) hc;
841 0 : return TALER_MHD_reply_cors_preflight (connection);
842 : }
843 :
844 :
845 : /**
846 : * Generates the response for "/", redirecting the
847 : * client to the "/webui/" from where we serve the SPA.
848 : *
849 : * @param rh request handler
850 : * @param connection MHD connection
851 : * @param hc handler context
852 : * @return MHD result code
853 : */
854 : static MHD_RESULT
855 1 : spa_redirect (const struct TMH_RequestHandler *rh,
856 : struct MHD_Connection *connection,
857 : struct TMH_HandlerContext *hc)
858 : {
859 1 : const char *text = "Redirecting to /webui/";
860 : struct MHD_Response *response;
861 : char *dst;
862 :
863 1 : response = MHD_create_response_from_buffer (strlen (text),
864 : (void *) text,
865 : MHD_RESPMEM_PERSISTENT);
866 1 : if (NULL == response)
867 : {
868 0 : GNUNET_break (0);
869 0 : return MHD_NO;
870 : }
871 1 : TALER_MHD_add_global_headers (response,
872 : true);
873 1 : GNUNET_break (MHD_YES ==
874 : MHD_add_response_header (response,
875 : MHD_HTTP_HEADER_CONTENT_TYPE,
876 : "text/plain"));
877 1 : if ( (NULL == hc->instance) ||
878 1 : (0 == strcmp ("admin",
879 1 : hc->instance->settings.id)) )
880 1 : dst = GNUNET_strdup ("/webui/");
881 : else
882 0 : GNUNET_asprintf (&dst,
883 : "/instances/%s/webui/",
884 0 : hc->instance->settings.id);
885 1 : if (MHD_NO ==
886 1 : MHD_add_response_header (response,
887 : MHD_HTTP_HEADER_LOCATION,
888 : dst))
889 : {
890 0 : GNUNET_break (0);
891 0 : MHD_destroy_response (response);
892 0 : GNUNET_free (dst);
893 0 : return MHD_NO;
894 : }
895 1 : GNUNET_free (dst);
896 :
897 : {
898 : MHD_RESULT ret;
899 :
900 1 : ret = MHD_queue_response (connection,
901 : MHD_HTTP_FOUND,
902 : response);
903 1 : MHD_destroy_response (response);
904 1 : return ret;
905 : }
906 : }
907 :
908 :
909 : /**
910 : * Extract the token from authorization header value @a auth.
911 : * The @a auth value can be a bearer token or a Basic
912 : * authentication header. In both cases, this function
913 : * updates @a auth to point to the actual credential,
914 : * skipping spaces.
915 : *
916 : * NOTE: We probably want to replace this function with MHD2
917 : * API calls in the future that are more robust.
918 : *
919 : * @param[in,out] auth pointer to authorization header value,
920 : * will be updated to point to the start of the token
921 : * or set to NULL if header value is invalid
922 : * @param[out] is_basic_auth will be set to true if the
923 : * authorization header uses basic authentication,
924 : * otherwise to false
925 : */
926 : static void
927 71 : extract_auth (const char **auth,
928 : bool *is_basic_auth)
929 : {
930 71 : const char *bearer = "Bearer ";
931 71 : const char *basic = "Basic ";
932 71 : const char *tok = *auth;
933 71 : size_t offset = 0;
934 71 : bool is_bearer = false;
935 :
936 71 : *is_basic_auth = false;
937 71 : if (0 == strncmp (tok,
938 : bearer,
939 : strlen (bearer)))
940 : {
941 57 : offset = strlen (bearer);
942 57 : is_bearer = true;
943 : }
944 14 : else if (0 == strncmp (tok,
945 : basic,
946 : strlen (basic)))
947 : {
948 14 : offset = strlen (basic);
949 14 : *is_basic_auth = true;
950 : }
951 : else
952 : {
953 0 : *auth = NULL;
954 0 : return;
955 : }
956 71 : tok += offset;
957 71 : while (' ' == *tok)
958 0 : tok++;
959 71 : if ( (is_bearer) &&
960 57 : (0 != strncasecmp (tok,
961 : RFC_8959_PREFIX,
962 : strlen (RFC_8959_PREFIX))) )
963 : {
964 0 : *auth = NULL;
965 0 : return;
966 : }
967 71 : *auth = tok;
968 : }
969 :
970 :
971 : /**
972 : * Checks if the @a rh matches the given (parsed) URL.
973 : *
974 : * @param rh handler to compare against
975 : * @param url the main URL (without "/private/" prefix, if any)
976 : * @param prefix_strlen length of the prefix, i.e. 8 for '/orders/' or 7 for '/config'
977 : * @param infix_url infix text, i.e. "$ORDER_ID".
978 : * @param infix_strlen length of the string in @a infix_url
979 : * @param suffix_url suffix, i.e. "/refund", including the "/"
980 : * @param suffix_strlen number of characters in @a suffix_url
981 : * @return true if @a rh matches this request
982 : */
983 : static bool
984 11903 : prefix_match (const struct TMH_RequestHandler *rh,
985 : const char *url,
986 : size_t prefix_strlen,
987 : const char *infix_url,
988 : size_t infix_strlen,
989 : const char *suffix_url,
990 : size_t suffix_strlen)
991 : {
992 11903 : if ( (prefix_strlen != strlen (rh->url_prefix)) ||
993 1827 : (0 != memcmp (url,
994 1827 : rh->url_prefix,
995 : prefix_strlen)) )
996 10625 : return false;
997 1278 : if (! rh->have_id_segment)
998 : {
999 : /* Require /$PREFIX/$SUFFIX or /$PREFIX */
1000 413 : if (NULL != suffix_url)
1001 0 : return false; /* too many segments to match */
1002 413 : if ( (NULL == infix_url) /* either or */
1003 413 : ^ (NULL == rh->url_suffix) )
1004 0 : return false; /* suffix existence mismatch */
1005 : /* If /$PREFIX/$SUFFIX, check $SUFFIX matches */
1006 413 : if ( (NULL != infix_url) &&
1007 0 : ( (infix_strlen != strlen (rh->url_suffix)) ||
1008 0 : (0 != memcmp (infix_url,
1009 0 : rh->url_suffix,
1010 : infix_strlen)) ) )
1011 0 : return false; /* cannot use infix as suffix: content mismatch */
1012 : }
1013 : else
1014 : {
1015 : /* Require /$PREFIX/$ID or /$PREFIX/$ID/$SUFFIX */
1016 865 : if (NULL == infix_url)
1017 0 : return false; /* infix existence mismatch */
1018 865 : if ( ( (NULL == suffix_url)
1019 865 : ^ (NULL == rh->url_suffix) ) )
1020 236 : return false; /* suffix existence mismatch */
1021 629 : if ( (NULL != suffix_url) &&
1022 330 : ( (suffix_strlen != strlen (rh->url_suffix)) ||
1023 226 : (0 != memcmp (suffix_url,
1024 226 : rh->url_suffix,
1025 : suffix_strlen)) ) )
1026 180 : return false; /* suffix content mismatch */
1027 : }
1028 862 : return true;
1029 : }
1030 :
1031 :
1032 : /**
1033 : * Function called first by MHD with the full URL.
1034 : *
1035 : * @param cls NULL
1036 : * @param full_url the full URL
1037 : * @param con MHD connection object
1038 : * @return our handler context
1039 : */
1040 : static void *
1041 661 : full_url_track_callback (void *cls,
1042 : const char *full_url,
1043 : struct MHD_Connection *con)
1044 : {
1045 : struct TMH_HandlerContext *hc;
1046 :
1047 661 : hc = GNUNET_new (struct TMH_HandlerContext);
1048 661 : GNUNET_async_scope_fresh (&hc->async_scope_id);
1049 661 : GNUNET_SCHEDULER_begin_async_scope (&hc->async_scope_id);
1050 661 : hc->full_url = GNUNET_strdup (full_url);
1051 661 : return hc;
1052 : }
1053 :
1054 :
1055 : /**
1056 : * Function used to process Basic authorization header value.
1057 : * Sets correct scope in the auth_scope parameter of the
1058 : * #TMH_HandlerContext.
1059 : *
1060 : * @param hc the handler context
1061 : * @param authn_s the value of the authorization header
1062 : */
1063 : static void
1064 14 : process_basic_auth (struct TMH_HandlerContext *hc, const char*authn_s)
1065 : {
1066 : /* Handle token endpoint slightly differently: Only allow
1067 : * instance password (Basic auth) to retrieve access token.
1068 : * We need to handle authorization with Basic auth here first
1069 : * The only time we need to handle authentication like this is
1070 : * for the token endpoint!
1071 : */
1072 14 : if ( (0 != strncmp (hc->rh->url_prefix,
1073 : "/token",
1074 14 : strlen ("/token"))) ||
1075 14 : (0 != strncmp (MHD_HTTP_METHOD_POST,
1076 14 : hc->rh->method,
1077 14 : strlen (MHD_HTTP_METHOD_POST))) ||
1078 14 : (NULL == hc->instance))
1079 : {
1080 0 : GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
1081 : "Called endpoint `%s' with Basic authentication. Rejecting...\n",
1082 : hc->rh->url_prefix);
1083 0 : hc->auth_scope = TMH_AS_NONE;
1084 0 : return;
1085 : }
1086 14 : if (GNUNET_OK ==
1087 14 : check_auth_instance (authn_s,
1088 : hc->instance))
1089 13 : hc->auth_scope = TMH_AS_ALL;
1090 : else
1091 1 : hc->auth_scope = TMH_AS_NONE;
1092 : }
1093 :
1094 :
1095 : /**
1096 : * Function used to process Bearer authorization header value.
1097 : * Sets correct scope in the auth_scope parameter of the
1098 : * #TMH_HandlerContext..
1099 : *
1100 : * @param hc the handler context
1101 : * @param authn_s the value of the authorization header
1102 : * @return TALER_EC_NONE on success.
1103 : */
1104 : static enum TALER_ErrorCode
1105 447 : process_bearer_auth (struct TMH_HandlerContext *hc, const char*authn_s)
1106 : {
1107 447 : if (NULL == hc->instance)
1108 : {
1109 2 : hc->auth_scope = TMH_AS_NONE;
1110 2 : return TALER_EC_NONE;
1111 : }
1112 445 : if (GNUNET_is_zero (&hc->instance->auth.auth_hash))
1113 : {
1114 : /* hash zero means no authentication for instance */
1115 402 : hc->auth_scope = TMH_AS_ALL;
1116 402 : return TALER_EC_NONE;
1117 : }
1118 : {
1119 : enum TALER_ErrorCode ec;
1120 :
1121 43 : ec = TMH_check_token (authn_s,
1122 43 : hc->instance->settings.id,
1123 : &hc->auth_scope);
1124 43 : if (TALER_EC_NONE != ec)
1125 : {
1126 : char *dec;
1127 : size_t dec_len;
1128 : const char *token;
1129 :
1130 : /* NOTE: Deprecated, remove sometime after v1.1 */
1131 9 : if (0 != strncasecmp (authn_s,
1132 : RFC_8959_PREFIX,
1133 : strlen (RFC_8959_PREFIX)))
1134 : {
1135 0 : GNUNET_break_op (0);
1136 0 : hc->auth_scope = TMH_AS_NONE;
1137 9 : return ec;
1138 : }
1139 9 : GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
1140 : "Trying deprecated secret-token:password API authN\n");
1141 9 : token = authn_s + strlen (RFC_8959_PREFIX);
1142 9 : dec_len = GNUNET_STRINGS_urldecode (token,
1143 : strlen (token),
1144 : &dec);
1145 18 : if ( (0 == dec_len) ||
1146 : (GNUNET_OK !=
1147 9 : TMH_check_auth (dec,
1148 9 : &hc->instance->auth.auth_salt,
1149 9 : &hc->instance->auth.auth_hash)) )
1150 : {
1151 9 : GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
1152 : "Login failed\n");
1153 9 : hc->auth_scope = TMH_AS_NONE;
1154 9 : GNUNET_free (dec);
1155 9 : return TALER_EC_NONE;
1156 : }
1157 0 : hc->auth_scope = TMH_AS_ALL;
1158 0 : GNUNET_free (dec);
1159 : }
1160 : }
1161 34 : return TALER_EC_NONE;
1162 : }
1163 :
1164 :
1165 : /**
1166 : * A client has requested the given url using the given method
1167 : * (#MHD_HTTP_METHOD_GET, #MHD_HTTP_METHOD_PUT,
1168 : * #MHD_HTTP_METHOD_DELETE, #MHD_HTTP_METHOD_POST, etc). The callback
1169 : * must call MHD callbacks to provide content to give back to the
1170 : * client and return an HTTP status code (i.e. #MHD_HTTP_OK,
1171 : * #MHD_HTTP_NOT_FOUND, etc.).
1172 : *
1173 : * @param cls argument given together with the function
1174 : * pointer when the handler was registered with MHD
1175 : * @param connection the MHD connection to handle
1176 : * @param url the requested url
1177 : * @param method the HTTP method used (#MHD_HTTP_METHOD_GET,
1178 : * #MHD_HTTP_METHOD_PUT, etc.)
1179 : * @param version the HTTP version string (i.e.
1180 : * #MHD_HTTP_VERSION_1_1)
1181 : * @param upload_data the data being uploaded (excluding HEADERS,
1182 : * for a POST that fits into memory and that is encoded
1183 : * with a supported encoding, the POST data will NOT be
1184 : * given in upload_data and is instead available as
1185 : * part of #MHD_get_connection_values; very large POST
1186 : * data *will* be made available incrementally in
1187 : * @a upload_data)
1188 : * @param upload_data_size set initially to the size of the
1189 : * @a upload_data provided; the method must update this
1190 : * value to the number of bytes NOT processed;
1191 : * @param con_cls pointer that the callback can set to some
1192 : * address and that will be preserved by MHD for future
1193 : * calls for this request; since the access handler may
1194 : * be called many times (i.e., for a PUT/POST operation
1195 : * with plenty of upload data) this allows the application
1196 : * to easily associate some request-specific state.
1197 : * If necessary, this state can be cleaned up in the
1198 : * global #MHD_RequestCompletedCallback (which
1199 : * can be set with the #MHD_OPTION_NOTIFY_COMPLETED).
1200 : * Initially, `*con_cls` will be set up by the
1201 : * full_url_track_callback().
1202 : * @return #MHD_YES if the connection was handled successfully,
1203 : * #MHD_NO if the socket must be closed due to a serious
1204 : * error while handling the request
1205 : */
1206 : static MHD_RESULT
1207 1794 : url_handler (void *cls,
1208 : struct MHD_Connection *connection,
1209 : const char *url,
1210 : const char *method,
1211 : const char *version,
1212 : const char *upload_data,
1213 : size_t *upload_data_size,
1214 : void **con_cls)
1215 : {
1216 : static struct TMH_RequestHandler management_handlers[] = {
1217 : /* GET /instances */
1218 : {
1219 : .url_prefix = "/instances",
1220 : .method = MHD_HTTP_METHOD_GET,
1221 : .permission = "instances-write",
1222 : .skip_instance = true,
1223 : .default_only = true,
1224 : .handler = &TMH_private_get_instances
1225 : },
1226 : /* POST /instances */
1227 : {
1228 : .url_prefix = "/instances",
1229 : .method = MHD_HTTP_METHOD_POST,
1230 : .permission = "instances-write",
1231 : .skip_instance = true,
1232 : .default_only = true,
1233 : .handler = &TMH_private_post_instances,
1234 : /* allow instance data of up to 8 MB, that should be plenty;
1235 : note that exceeding #GNUNET_MAX_MALLOC_CHECKED (40 MB)
1236 : would require further changes to the allocation logic
1237 : in the code... */
1238 : .max_upload = 1024 * 1024 * 8
1239 : },
1240 : /* GET /instances/$ID/ */
1241 : {
1242 : .url_prefix = "/instances/",
1243 : .method = MHD_HTTP_METHOD_GET,
1244 : .permission = "instances-write",
1245 : .skip_instance = true,
1246 : .default_only = true,
1247 : .have_id_segment = true,
1248 : .handler = &TMH_private_get_instances_default_ID
1249 : },
1250 : /* DELETE /instances/$ID */
1251 : {
1252 : .url_prefix = "/instances/",
1253 : .method = MHD_HTTP_METHOD_DELETE,
1254 : .permission = "instances-write",
1255 : .skip_instance = true,
1256 : .default_only = true,
1257 : .have_id_segment = true,
1258 : .handler = &TMH_private_delete_instances_default_ID
1259 : },
1260 : /* PATCH /instances/$ID */
1261 : {
1262 : .url_prefix = "/instances/",
1263 : .method = MHD_HTTP_METHOD_PATCH,
1264 : .permission = "instances-write",
1265 : .skip_instance = true,
1266 : .default_only = true,
1267 : .have_id_segment = true,
1268 : .handler = &TMH_private_patch_instances_default_ID,
1269 : /* allow instance data of up to 8 MB, that should be plenty;
1270 : note that exceeding #GNUNET_MAX_MALLOC_CHECKED (40 MB)
1271 : would require further changes to the allocation logic
1272 : in the code... */
1273 : .max_upload = 1024 * 1024 * 8
1274 : },
1275 : /* POST /auth: */
1276 : {
1277 : .url_prefix = "/instances/",
1278 : .url_suffix = "auth",
1279 : .method = MHD_HTTP_METHOD_POST,
1280 : .permission = "instances-auth-write",
1281 : .skip_instance = true,
1282 : .default_only = true,
1283 : .have_id_segment = true,
1284 : .handler = &TMH_private_post_instances_default_ID_auth,
1285 : /* Body should be pretty small. */
1286 : .max_upload = 1024 * 1024
1287 : },
1288 : /* GET /kyc: */
1289 : {
1290 : .url_prefix = "/instances/",
1291 : .url_suffix = "kyc",
1292 : .method = MHD_HTTP_METHOD_GET,
1293 : .permission = "instances-kyc-read",
1294 : .skip_instance = true,
1295 : .default_only = true,
1296 : .have_id_segment = true,
1297 : .handler = &TMH_private_get_instances_default_ID_kyc,
1298 : },
1299 : {
1300 : .url_prefix = NULL
1301 : }
1302 : };
1303 :
1304 : static struct TMH_RequestHandler private_handlers[] = {
1305 : /* GET /instances/$ID/: */
1306 : {
1307 : .url_prefix = "/",
1308 : .method = MHD_HTTP_METHOD_GET,
1309 : .permission = "instances-read",
1310 : .handler = &TMH_private_get_instances_ID
1311 : },
1312 : /* DELETE /instances/$ID/: */
1313 : {
1314 : .url_prefix = "/",
1315 : .method = MHD_HTTP_METHOD_DELETE,
1316 : .permission = "instances-write",
1317 : .allow_deleted_instance = true,
1318 : .handler = &TMH_private_delete_instances_ID
1319 : },
1320 : /* PATCH /instances/$ID/: */
1321 : {
1322 : .url_prefix = "/",
1323 : .method = MHD_HTTP_METHOD_PATCH,
1324 : .handler = &TMH_private_patch_instances_ID,
1325 : .permission = "instances-write",
1326 : .allow_deleted_instance = true,
1327 : /* allow instance data of up to 8 MB, that should be plenty;
1328 : note that exceeding #GNUNET_MAX_MALLOC_CHECKED (40 MB)
1329 : would require further changes to the allocation logic
1330 : in the code... */
1331 : .max_upload = 1024 * 1024 * 8
1332 : },
1333 : /* POST /auth: */
1334 : {
1335 : .url_prefix = "/auth",
1336 : .method = MHD_HTTP_METHOD_POST,
1337 : .handler = &TMH_private_post_instances_ID_auth,
1338 : .permission = "auth-write",
1339 : /* Body should be pretty small. */
1340 : .max_upload = 1024 * 1024,
1341 : },
1342 : /* GET /kyc: */
1343 : {
1344 : .url_prefix = "/kyc",
1345 : .method = MHD_HTTP_METHOD_GET,
1346 : .permission = "kyc-read",
1347 : .handler = &TMH_private_get_instances_ID_kyc,
1348 : },
1349 : /* GET /pos: */
1350 : {
1351 : .url_prefix = "/pos",
1352 : .method = MHD_HTTP_METHOD_GET,
1353 : .permission = "pos-read",
1354 : .handler = &TMH_private_get_pos
1355 : },
1356 : /* GET /categories: */
1357 : {
1358 : .url_prefix = "/categories",
1359 : .method = MHD_HTTP_METHOD_GET,
1360 : .permission = "categories-read",
1361 : .handler = &TMH_private_get_categories
1362 : },
1363 : /* POST /categories: */
1364 : {
1365 : .url_prefix = "/categories",
1366 : .method = MHD_HTTP_METHOD_POST,
1367 : .permission = "categories-write",
1368 : .handler = &TMH_private_post_categories,
1369 : /* allow category data of up to 8 kb, that should be plenty */
1370 : .max_upload = 1024 * 8
1371 : },
1372 : /* GET /categories/$ID: */
1373 : {
1374 : .url_prefix = "/categories/",
1375 : .method = MHD_HTTP_METHOD_GET,
1376 : .permission = "categories-read",
1377 : .have_id_segment = true,
1378 : .allow_deleted_instance = true,
1379 : .handler = &TMH_private_get_categories_ID
1380 : },
1381 : /* DELETE /categories/$ID: */
1382 : {
1383 : .url_prefix = "/categories/",
1384 : .method = MHD_HTTP_METHOD_DELETE,
1385 : .permission = "categories-write",
1386 : .have_id_segment = true,
1387 : .allow_deleted_instance = true,
1388 : .handler = &TMH_private_delete_categories_ID
1389 : },
1390 : /* PATCH /categories/$ID/: */
1391 : {
1392 : .url_prefix = "/categories/",
1393 : .method = MHD_HTTP_METHOD_PATCH,
1394 : .permission = "categories-write",
1395 : .have_id_segment = true,
1396 : .allow_deleted_instance = true,
1397 : .handler = &TMH_private_patch_categories_ID,
1398 : /* allow category data of up to 8 kb, that should be plenty */
1399 : .max_upload = 1024 * 8
1400 : },
1401 : /* GET /products: */
1402 : {
1403 : .url_prefix = "/products",
1404 : .permission = "products-read",
1405 : .method = MHD_HTTP_METHOD_GET,
1406 : .handler = &TMH_private_get_products
1407 : },
1408 : /* POST /products: */
1409 : {
1410 : .url_prefix = "/products",
1411 : .method = MHD_HTTP_METHOD_POST,
1412 : .permission = "products-write",
1413 : .handler = &TMH_private_post_products,
1414 : /* allow product data of up to 8 MB, that should be plenty;
1415 : note that exceeding #GNUNET_MAX_MALLOC_CHECKED (40 MB)
1416 : would require further changes to the allocation logic
1417 : in the code... */
1418 : .max_upload = 1024 * 1024 * 8
1419 : },
1420 : /* GET /products/$ID: */
1421 : {
1422 : .url_prefix = "/products/",
1423 : .method = MHD_HTTP_METHOD_GET,
1424 : .have_id_segment = true,
1425 : .permission = "products-read",
1426 : .allow_deleted_instance = true,
1427 : .handler = &TMH_private_get_products_ID
1428 : },
1429 : /* DELETE /products/$ID/: */
1430 : {
1431 : .url_prefix = "/products/",
1432 : .method = MHD_HTTP_METHOD_DELETE,
1433 : .have_id_segment = true,
1434 : .permission = "products-write",
1435 : .allow_deleted_instance = true,
1436 : .handler = &TMH_private_delete_products_ID
1437 : },
1438 : /* PATCH /products/$ID/: */
1439 : {
1440 : .url_prefix = "/products/",
1441 : .method = MHD_HTTP_METHOD_PATCH,
1442 : .have_id_segment = true,
1443 : .allow_deleted_instance = true,
1444 : .permission = "products-write",
1445 : .handler = &TMH_private_patch_products_ID,
1446 : /* allow product data of up to 8 MB, that should be plenty;
1447 : note that exceeding #GNUNET_MAX_MALLOC_CHECKED (40 MB)
1448 : would require further changes to the allocation logic
1449 : in the code... */
1450 : .max_upload = 1024 * 1024 * 8
1451 : },
1452 : /* POST /products/$ID/lock: */
1453 : {
1454 : .url_prefix = "/products/",
1455 : .url_suffix = "lock",
1456 : .method = MHD_HTTP_METHOD_POST,
1457 : .have_id_segment = true,
1458 : .permission = "products-lock",
1459 : .handler = &TMH_private_post_products_ID_lock,
1460 : /* the body should be pretty small, allow 1 MB of upload
1461 : to set a conservative bound for sane wallets */
1462 : .max_upload = 1024 * 1024
1463 : },
1464 : /* POST /orders: */
1465 : {
1466 : .url_prefix = "/orders",
1467 : .method = MHD_HTTP_METHOD_POST,
1468 : .permission = "orders-write",
1469 : .handler = &TMH_private_post_orders,
1470 : /* allow contracts of up to 8 MB, that should be plenty;
1471 : note that exceeding #GNUNET_MAX_MALLOC_CHECKED (40 MB)
1472 : would require further changes to the allocation logic
1473 : in the code... */
1474 : .max_upload = 1024 * 1024 * 8
1475 : },
1476 : /* GET /orders/$ID: */
1477 : {
1478 : .url_prefix = "/orders/",
1479 : .method = MHD_HTTP_METHOD_GET,
1480 : .permission = "orders-read",
1481 : .have_id_segment = true,
1482 : .allow_deleted_instance = true,
1483 : .handler = &TMH_private_get_orders_ID
1484 : },
1485 : /* GET /orders: */
1486 : {
1487 : .url_prefix = "/orders",
1488 : .method = MHD_HTTP_METHOD_GET,
1489 : .permission = "orders-read",
1490 : .allow_deleted_instance = true,
1491 : .handler = &TMH_private_get_orders
1492 : },
1493 : /* POST /orders/$ID/refund: */
1494 : {
1495 : .url_prefix = "/orders/",
1496 : .url_suffix = "refund",
1497 : .method = MHD_HTTP_METHOD_POST,
1498 : .have_id_segment = true,
1499 : .permission = "orders-refund",
1500 : .handler = &TMH_private_post_orders_ID_refund,
1501 : /* the body should be pretty small, allow 1 MB of upload
1502 : to set a conservative bound for sane wallets */
1503 : .max_upload = 1024 * 1024
1504 : },
1505 : /* PATCH /orders/$ID/forget: */
1506 : {
1507 : .url_prefix = "/orders/",
1508 : .url_suffix = "forget",
1509 : .method = MHD_HTTP_METHOD_PATCH,
1510 : .permission = "orders-write",
1511 : .have_id_segment = true,
1512 : .allow_deleted_instance = true,
1513 : .handler = &TMH_private_patch_orders_ID_forget,
1514 : /* the body should be pretty small, allow 1 MB of upload
1515 : to set a conservative bound for sane wallets */
1516 : .max_upload = 1024 * 1024
1517 : },
1518 : /* DELETE /orders/$ID: */
1519 : {
1520 : .url_prefix = "/orders/",
1521 : .method = MHD_HTTP_METHOD_DELETE,
1522 : .permission = "orders-write",
1523 : .have_id_segment = true,
1524 : .allow_deleted_instance = true,
1525 : .handler = &TMH_private_delete_orders_ID
1526 : },
1527 : /* POST /transfers: */
1528 : {
1529 : .url_prefix = "/transfers",
1530 : .method = MHD_HTTP_METHOD_POST,
1531 : .allow_deleted_instance = true,
1532 : .handler = &TMH_private_post_transfers,
1533 : .permission = "transfers-write",
1534 : /* the body should be pretty small, allow 1 MB of upload
1535 : to set a conservative bound for sane wallets */
1536 : .max_upload = 1024 * 1024
1537 : },
1538 : /* DELETE /transfers/$ID: */
1539 : {
1540 : .url_prefix = "/transfers/",
1541 : .method = MHD_HTTP_METHOD_DELETE,
1542 : .permission = "transfers-write",
1543 : .allow_deleted_instance = true,
1544 : .handler = &TMH_private_delete_transfers_ID,
1545 : .have_id_segment = true,
1546 : /* the body should be pretty small, allow 1 MB of upload
1547 : to set a conservative bound for sane wallets */
1548 : .max_upload = 1024 * 1024
1549 : },
1550 : /* GET /transfers: */
1551 : {
1552 : .url_prefix = "/transfers",
1553 : .permission = "transfers-read",
1554 : .method = MHD_HTTP_METHOD_GET,
1555 : .allow_deleted_instance = true,
1556 : .handler = &TMH_private_get_transfers
1557 : },
1558 : /* GET /incoming: */
1559 : {
1560 : .url_prefix = "/incoming",
1561 : .permission = "transfers-read",
1562 : .method = MHD_HTTP_METHOD_GET,
1563 : .allow_deleted_instance = true,
1564 : .handler = &TMH_private_get_incoming
1565 : },
1566 : /* POST /otp-devices: */
1567 : {
1568 : .url_prefix = "/otp-devices",
1569 : .permission = "otp-devices-write",
1570 : .method = MHD_HTTP_METHOD_POST,
1571 : .handler = &TMH_private_post_otp_devices
1572 : },
1573 : /* GET /otp-devices: */
1574 : {
1575 : .url_prefix = "/otp-devices",
1576 : .permission = "opt-devices-read",
1577 : .method = MHD_HTTP_METHOD_GET,
1578 : .handler = &TMH_private_get_otp_devices
1579 : },
1580 : /* GET /otp-devices/$ID/: */
1581 : {
1582 : .url_prefix = "/otp-devices/",
1583 : .method = MHD_HTTP_METHOD_GET,
1584 : .permission = "otp-devices-read",
1585 : .have_id_segment = true,
1586 : .handler = &TMH_private_get_otp_devices_ID
1587 : },
1588 : /* DELETE /otp-devices/$ID/: */
1589 : {
1590 : .url_prefix = "/otp-devices/",
1591 : .method = MHD_HTTP_METHOD_DELETE,
1592 : .permission = "otp-devices-write",
1593 : .have_id_segment = true,
1594 : .handler = &TMH_private_delete_otp_devices_ID
1595 : },
1596 : /* PATCH /otp-devices/$ID/: */
1597 : {
1598 : .url_prefix = "/otp-devices/",
1599 : .method = MHD_HTTP_METHOD_PATCH,
1600 : .permission = "otp-devices-write",
1601 : .have_id_segment = true,
1602 : .handler = &TMH_private_patch_otp_devices_ID
1603 : },
1604 : /* POST /templates: */
1605 : {
1606 : .url_prefix = "/templates",
1607 : .method = MHD_HTTP_METHOD_POST,
1608 : .permission = "templates-write",
1609 : .handler = &TMH_private_post_templates,
1610 : /* allow template data of up to 8 MB, that should be plenty;
1611 : note that exceeding #GNUNET_MAX_MALLOC_CHECKED (40 MB)
1612 : would require further changes to the allocation logic
1613 : in the code... */
1614 : .max_upload = 1024 * 1024 * 8
1615 : },
1616 : /* GET /templates: */
1617 : {
1618 : .url_prefix = "/templates",
1619 : .permission = "templates-read",
1620 : .method = MHD_HTTP_METHOD_GET,
1621 : .handler = &TMH_private_get_templates
1622 : },
1623 : /* GET /templates/$ID/: */
1624 : {
1625 : .url_prefix = "/templates/",
1626 : .method = MHD_HTTP_METHOD_GET,
1627 : .permission = "templates-read",
1628 : .have_id_segment = true,
1629 : .allow_deleted_instance = true,
1630 : .handler = &TMH_private_get_templates_ID
1631 : },
1632 : /* DELETE /templates/$ID/: */
1633 : {
1634 : .url_prefix = "/templates/",
1635 : .method = MHD_HTTP_METHOD_DELETE,
1636 : .permission = "templates-write",
1637 : .have_id_segment = true,
1638 : .allow_deleted_instance = true,
1639 : .handler = &TMH_private_delete_templates_ID
1640 : },
1641 : /* PATCH /templates/$ID/: */
1642 : {
1643 : .url_prefix = "/templates/",
1644 : .method = MHD_HTTP_METHOD_PATCH,
1645 : .permission = "templates-write",
1646 : .have_id_segment = true,
1647 : .allow_deleted_instance = true,
1648 : .handler = &TMH_private_patch_templates_ID,
1649 : /* allow template data of up to 8 MB, that should be plenty;
1650 : note that exceeding #GNUNET_MAX_MALLOC_CHECKED (40 MB)
1651 : would require further changes to the allocation logic
1652 : in the code... */
1653 : .max_upload = 1024 * 1024 * 8
1654 : },
1655 : /* GET /webhooks: */
1656 : {
1657 : .url_prefix = "/webhooks",
1658 : .permission = "webhooks-read",
1659 : .method = MHD_HTTP_METHOD_GET,
1660 : .handler = &TMH_private_get_webhooks
1661 : },
1662 : /* POST /webhooks: */
1663 : {
1664 : .url_prefix = "/webhooks",
1665 : .method = MHD_HTTP_METHOD_POST,
1666 : .permission = "webhooks-write",
1667 : .handler = &TMH_private_post_webhooks,
1668 : /* allow webhook data of up to 8 MB, that should be plenty;
1669 : note that exceeding #GNUNET_MAX_MALLOC_CHECKED (40 MB)
1670 : would require further changes to the allocation logic
1671 : in the code... */
1672 : .max_upload = 1024 * 1024 * 8
1673 : },
1674 : /* GET /webhooks/$ID/: */
1675 : {
1676 : .url_prefix = "/webhooks/",
1677 : .method = MHD_HTTP_METHOD_GET,
1678 : .permission = "webhooks-read",
1679 : .have_id_segment = true,
1680 : .allow_deleted_instance = true,
1681 : .handler = &TMH_private_get_webhooks_ID
1682 : },
1683 : /* DELETE /webhooks/$ID/: */
1684 : {
1685 : .url_prefix = "/webhooks/",
1686 : .permission = "webhooks-write",
1687 : .method = MHD_HTTP_METHOD_DELETE,
1688 : .have_id_segment = true,
1689 : .allow_deleted_instance = true,
1690 : .handler = &TMH_private_delete_webhooks_ID
1691 : },
1692 : /* PATCH /webhooks/$ID/: */
1693 : {
1694 : .url_prefix = "/webhooks/",
1695 : .method = MHD_HTTP_METHOD_PATCH,
1696 : .permission = "webhooks-write",
1697 : .have_id_segment = true,
1698 : .allow_deleted_instance = true,
1699 : .handler = &TMH_private_patch_webhooks_ID,
1700 : /* allow webhook data of up to 8 MB, that should be plenty;
1701 : note that exceeding #GNUNET_MAX_MALLOC_CHECKED (40 MB)
1702 : would require further changes to the allocation logic
1703 : in the code... */
1704 : .max_upload = 1024 * 1024 * 8
1705 : },
1706 : /* POST /accounts: */
1707 : {
1708 : .url_prefix = "/accounts",
1709 : .method = MHD_HTTP_METHOD_POST,
1710 : .permission = "accounts-write",
1711 : .handler = &TMH_private_post_account,
1712 : /* allow account details of up to 8 kb, that should be plenty */
1713 : .max_upload = 1024 * 8
1714 : },
1715 : /* PATCH /accounts/$H_WIRE: */
1716 : {
1717 : .url_prefix = "/accounts/",
1718 : .method = MHD_HTTP_METHOD_PATCH,
1719 : .permission = "accounts-write",
1720 : .handler = &TMH_private_patch_accounts_ID,
1721 : .have_id_segment = true,
1722 : /* allow account details of up to 8 kb, that should be plenty */
1723 : .max_upload = 1024 * 8
1724 : },
1725 : /* GET /accounts: */
1726 : {
1727 : .url_prefix = "/accounts",
1728 : .permission = "accounts-read",
1729 : .method = MHD_HTTP_METHOD_GET,
1730 : .handler = &TMH_private_get_accounts
1731 : },
1732 : /* GET /accounts/$H_WIRE: */
1733 : {
1734 : .url_prefix = "/accounts/",
1735 : .permission = "accounts-read",
1736 : .method = MHD_HTTP_METHOD_GET,
1737 : .have_id_segment = true,
1738 : .handler = &TMH_private_get_accounts_ID
1739 : },
1740 : /* DELETE /accounts/$H_WIRE: */
1741 : {
1742 : .url_prefix = "/accounts/",
1743 : .permission = "accounts-write",
1744 : .method = MHD_HTTP_METHOD_DELETE,
1745 : .handler = &TMH_private_delete_account_ID,
1746 : .have_id_segment = true
1747 : },
1748 : /* GET /tokens: */
1749 : {
1750 : .url_prefix = "/tokens",
1751 : .permission = "tokens-read",
1752 : .method = MHD_HTTP_METHOD_GET,
1753 : .handler = &TMH_private_get_instances_ID_tokens,
1754 : },
1755 : /* POST /token: */
1756 : {
1757 : .url_prefix = "/token",
1758 : .permission = "token-refresh",
1759 : .method = MHD_HTTP_METHOD_POST,
1760 : .handler = &TMH_private_post_instances_ID_token,
1761 : /* Body should be tiny. */
1762 : .max_upload = 1024
1763 : },
1764 : /* DELETE /tokens/$SERIAL: */
1765 : {
1766 : .url_prefix = "/tokens/",
1767 : .permission = "tokens-write",
1768 : .method = MHD_HTTP_METHOD_DELETE,
1769 : .handler = &TMH_private_delete_instances_ID_token_SERIAL,
1770 : .have_id_segment = true
1771 : },
1772 : /* DELETE /token: */
1773 : {
1774 : .url_prefix = "/token",
1775 : .method = MHD_HTTP_METHOD_DELETE,
1776 : .handler = &TMH_private_delete_instances_ID_token,
1777 : },
1778 : /* GET /tokenfamilies: */
1779 : {
1780 : .url_prefix = "/tokenfamilies",
1781 : .permission = "tokenfamilies-read",
1782 : .method = MHD_HTTP_METHOD_GET,
1783 : .handler = &TMH_private_get_tokenfamilies
1784 : },
1785 : /* POST /tokenfamilies: */
1786 : {
1787 : .url_prefix = "/tokenfamilies",
1788 : .permission = "tokenfamilies-write",
1789 : .method = MHD_HTTP_METHOD_POST,
1790 : .handler = &TMH_private_post_token_families
1791 : },
1792 : /* GET /tokenfamilies/$SLUG/: */
1793 : {
1794 : .url_prefix = "/tokenfamilies/",
1795 : .method = MHD_HTTP_METHOD_GET,
1796 : .permission = "tokenfamilies-read",
1797 : .have_id_segment = true,
1798 : .handler = &TMH_private_get_tokenfamilies_SLUG
1799 : },
1800 : /* DELETE /tokenfamilies/$SLUG/: */
1801 : {
1802 : .url_prefix = "/tokenfamilies/",
1803 : .method = MHD_HTTP_METHOD_DELETE,
1804 : .permission = "tokenfamilies-write",
1805 : .have_id_segment = true,
1806 : .handler = &TMH_private_delete_token_families_SLUG
1807 : },
1808 : /* PATCH /tokenfamilies/$SLUG/: */
1809 : {
1810 : .url_prefix = "/tokenfamilies/",
1811 : .method = MHD_HTTP_METHOD_PATCH,
1812 : .permission = "tokenfamilies-write",
1813 : .have_id_segment = true,
1814 : .handler = &TMH_private_patch_token_family_SLUG,
1815 : },
1816 : /* GET /statistics-counter/$SLUG: */
1817 : {
1818 : .url_prefix = "/statistics-counter/",
1819 : .method = MHD_HTTP_METHOD_GET,
1820 : .permission = "statistics-read",
1821 : .have_id_segment = true,
1822 : .handler = &TMH_private_get_statistics_counter_SLUG,
1823 : },
1824 : /* GET /statistics-amount/$SLUG: */
1825 : {
1826 : .url_prefix = "/statistics-amount/",
1827 : .method = MHD_HTTP_METHOD_GET,
1828 : .permission = "statistics-read",
1829 : .have_id_segment = true,
1830 : .handler = &TMH_private_get_statistics_amount_SLUG,
1831 : },
1832 : {
1833 : .url_prefix = NULL
1834 : }
1835 : };
1836 : static struct TMH_RequestHandler public_handlers[] = {
1837 : {
1838 : /* for "admin" instance, it does not even
1839 : have to exist before we give the WebUI */
1840 : .url_prefix = "/",
1841 : .method = MHD_HTTP_METHOD_GET,
1842 : .mime_type = "text/html",
1843 : .skip_instance = true,
1844 : .default_only = true,
1845 : .handler = &spa_redirect,
1846 : .response_code = MHD_HTTP_FOUND
1847 : },
1848 : {
1849 : /* for "normal" instance,s they must exist
1850 : before we give the WebUI */
1851 : .url_prefix = "/",
1852 : .method = MHD_HTTP_METHOD_GET,
1853 : .mime_type = "text/html",
1854 : .handler = &spa_redirect,
1855 : .response_code = MHD_HTTP_FOUND
1856 : },
1857 : {
1858 : .url_prefix = "/webui/",
1859 : .method = MHD_HTTP_METHOD_GET,
1860 : .mime_type = "text/html",
1861 : .skip_instance = true,
1862 : .have_id_segment = true,
1863 : .handler = &TMH_return_spa,
1864 : .response_code = MHD_HTTP_OK
1865 : },
1866 : {
1867 : .url_prefix = "/agpl",
1868 : .method = MHD_HTTP_METHOD_GET,
1869 : .skip_instance = true,
1870 : .handler = &TMH_MHD_handler_agpl_redirect
1871 : },
1872 : {
1873 : .url_prefix = "/config",
1874 : .method = MHD_HTTP_METHOD_GET,
1875 : .skip_instance = true,
1876 : .default_only = true,
1877 : .handler = &MH_handler_config
1878 : },
1879 : /* Also serve the same /config per instance */
1880 : {
1881 : .url_prefix = "/config",
1882 : .method = MHD_HTTP_METHOD_GET,
1883 : .skip_instance = false,
1884 : .allow_deleted_instance = true,
1885 : .handler = &MH_handler_config
1886 : },
1887 : /* POST /orders/$ID/abort: */
1888 : {
1889 : .url_prefix = "/orders/",
1890 : .have_id_segment = true,
1891 : .url_suffix = "abort",
1892 : .method = MHD_HTTP_METHOD_POST,
1893 : .handler = &TMH_post_orders_ID_abort,
1894 : /* wallet may give us many coins to sign, allow 1 MB of upload
1895 : to set a conservative bound for sane wallets */
1896 : .max_upload = 1024 * 1024
1897 : },
1898 : /* POST /orders/$ID/claim: */
1899 : {
1900 : .url_prefix = "/orders/",
1901 : .have_id_segment = true,
1902 : .url_suffix = "claim",
1903 : .method = MHD_HTTP_METHOD_POST,
1904 : .handler = &TMH_post_orders_ID_claim,
1905 : /* the body should be pretty small, allow 1 MB of upload
1906 : to set a conservative bound for sane wallets */
1907 : .max_upload = 1024 * 1024
1908 : },
1909 : /* POST /orders/$ID/pay: */
1910 : {
1911 : .url_prefix = "/orders/",
1912 : .have_id_segment = true,
1913 : .url_suffix = "pay",
1914 : .method = MHD_HTTP_METHOD_POST,
1915 : .handler = &TMH_post_orders_ID_pay,
1916 : /* wallet may give us many coins to sign, allow 1 MB of upload
1917 : to set a conservative bound for sane wallets */
1918 : .max_upload = 1024 * 1024
1919 : },
1920 : /* POST /orders/$ID/paid: */
1921 : {
1922 : .url_prefix = "/orders/",
1923 : .have_id_segment = true,
1924 : .allow_deleted_instance = true,
1925 : .url_suffix = "paid",
1926 : .method = MHD_HTTP_METHOD_POST,
1927 : .handler = &TMH_post_orders_ID_paid,
1928 : /* the body should be pretty small, allow 1 MB of upload
1929 : to set a conservative bound for sane wallets */
1930 : .max_upload = 1024 * 1024
1931 : },
1932 : /* POST /orders/$ID/refund: */
1933 : {
1934 : .url_prefix = "/orders/",
1935 : .have_id_segment = true,
1936 : .allow_deleted_instance = true,
1937 : .url_suffix = "refund",
1938 : .method = MHD_HTTP_METHOD_POST,
1939 : .handler = &TMH_post_orders_ID_refund,
1940 : /* the body should be pretty small, allow 1 MB of upload
1941 : to set a conservative bound for sane wallets */
1942 : .max_upload = 1024 * 1024
1943 : },
1944 : /* GET /orders/$ID: */
1945 : {
1946 : .url_prefix = "/orders/",
1947 : .method = MHD_HTTP_METHOD_GET,
1948 : .allow_deleted_instance = true,
1949 : .have_id_segment = true,
1950 : .handler = &TMH_get_orders_ID
1951 : },
1952 : /* GET /static/ *: */
1953 : {
1954 : .url_prefix = "/static/",
1955 : .method = MHD_HTTP_METHOD_GET,
1956 : .have_id_segment = true,
1957 : .handler = &TMH_return_static
1958 : },
1959 : /* GET /templates/$ID/: */
1960 : {
1961 : .url_prefix = "/templates/",
1962 : .method = MHD_HTTP_METHOD_GET,
1963 : .have_id_segment = true,
1964 : .handler = &TMH_get_templates_ID
1965 : },
1966 : /* POST /templates/$ID: */
1967 : {
1968 : .url_prefix = "/templates/",
1969 : .method = MHD_HTTP_METHOD_POST,
1970 : .have_id_segment = true,
1971 : .handler = &TMH_post_using_templates_ID,
1972 : .max_upload = 1024 * 1024
1973 : },
1974 : {
1975 : .url_prefix = "*",
1976 : .method = MHD_HTTP_METHOD_OPTIONS,
1977 : .handler = &handle_server_options
1978 : },
1979 : {
1980 : .url_prefix = NULL
1981 : }
1982 : };
1983 1794 : struct TMH_HandlerContext *hc = *con_cls;
1984 : struct TMH_RequestHandler *handlers;
1985 1794 : bool use_default = false;
1986 :
1987 : (void) cls;
1988 : (void) version;
1989 1794 : if (NULL != hc->url)
1990 : {
1991 : /* MHD calls us again for a request, for first call
1992 : see 'else' case below */
1993 1133 : GNUNET_assert (NULL != hc->rh);
1994 1133 : GNUNET_SCHEDULER_begin_async_scope (&hc->async_scope_id);
1995 1133 : if ( (hc->has_body) &&
1996 862 : (NULL == hc->request_body) )
1997 : {
1998 760 : size_t mul = hc->rh->max_upload;
1999 : enum GNUNET_GenericReturnValue res;
2000 :
2001 760 : if (0 == mul)
2002 20 : mul = DEFAULT_MAX_UPLOAD_SIZE;
2003 760 : if ( (hc->total_upload + *upload_data_size < hc->total_upload) ||
2004 760 : (hc->total_upload + *upload_data_size > mul) )
2005 : {
2006 : /* Client exceeds upload limit. Should _usually_ be checked earlier
2007 : when we look at the MHD_HTTP_HEADER_CONTENT_LENGTH, alas with
2008 : chunked encoding an uploader MAY have omitted this, and thus
2009 : not permitted us to check on time. In this case, we just close
2010 : the connection once it exceeds our limit (instead of waiting
2011 : for the upload to complete and then fail). This could theoretically
2012 : cause some clients to retry, alas broken or malicious clients
2013 : are likely to retry anyway, so little we can do about it, and
2014 : failing earlier seems the best option here. */
2015 0 : GNUNET_break_op (0);
2016 0 : return MHD_NO;
2017 : }
2018 760 : hc->total_upload += *upload_data_size;
2019 760 : res = TALER_MHD_parse_post_json (connection,
2020 : &hc->json_parse_context,
2021 : upload_data,
2022 : upload_data_size,
2023 : &hc->request_body);
2024 760 : if (GNUNET_SYSERR == res)
2025 0 : return MHD_NO;
2026 : /* A error response was already generated */
2027 760 : if ( (GNUNET_NO == res) ||
2028 : /* or, need more data to accomplish parsing */
2029 760 : (NULL == hc->request_body) )
2030 380 : return MHD_YES; /* let MHD call us *again* */
2031 : }
2032 : /* Upload complete (if any), call handler to generate reply */
2033 753 : return hc->rh->handler (hc->rh,
2034 : connection,
2035 : hc);
2036 : }
2037 661 : hc->url = url;
2038 : {
2039 : const char *correlation_id;
2040 :
2041 661 : correlation_id = MHD_lookup_connection_value (connection,
2042 : MHD_HEADER_KIND,
2043 : "Taler-Correlation-Id");
2044 661 : if ( (NULL != correlation_id) &&
2045 : (GNUNET_YES !=
2046 0 : GNUNET_CURL_is_valid_scope_id (correlation_id)) )
2047 : {
2048 0 : GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
2049 : "Illegal incoming correlation ID\n");
2050 0 : correlation_id = NULL;
2051 : }
2052 661 : if (NULL != correlation_id)
2053 0 : GNUNET_log (GNUNET_ERROR_TYPE_INFO,
2054 : "Handling request for (%s) URL '%s', correlation_id=%s\n",
2055 : method,
2056 : url,
2057 : correlation_id);
2058 : else
2059 661 : GNUNET_log (GNUNET_ERROR_TYPE_INFO,
2060 : "Handling request (%s) for URL '%s'\n",
2061 : method,
2062 : url);
2063 : }
2064 :
2065 661 : if (0 == strcasecmp (method,
2066 : MHD_HTTP_METHOD_HEAD))
2067 0 : method = MHD_HTTP_METHOD_GET; /* MHD will deal with the rest */
2068 :
2069 :
2070 : /* Find out the merchant backend instance for the request.
2071 : * If there is an instance, remove the instance specification
2072 : * from the beginning of the request URL. */
2073 : {
2074 661 : const char *instance_prefix = "/instances/";
2075 :
2076 661 : if (0 == strncmp (url,
2077 : instance_prefix,
2078 : strlen (instance_prefix)))
2079 : {
2080 : /* url starts with "/instances/" */
2081 83 : const char *istart = url + strlen (instance_prefix);
2082 83 : const char *slash = strchr (istart, '/');
2083 : char *instance_id;
2084 :
2085 83 : if (NULL == slash)
2086 0 : instance_id = GNUNET_strdup (istart);
2087 : else
2088 83 : instance_id = GNUNET_strndup (istart,
2089 : slash - istart);
2090 83 : if (0 == strcmp (instance_id,
2091 : "admin"))
2092 : {
2093 : MHD_RESULT ret;
2094 : struct MHD_Response *response;
2095 0 : const char *rstart = hc->full_url + strlen (instance_prefix);
2096 0 : const char *rslash = strchr (rstart, '/');
2097 :
2098 0 : GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
2099 : "Client used deprecated '/instances/default/' path. Redirecting to modern path\n");
2100 :
2101 : response
2102 0 : = MHD_create_response_from_buffer (0,
2103 : NULL,
2104 : MHD_RESPMEM_PERSISTENT);
2105 0 : TALER_MHD_add_global_headers (response,
2106 : true);
2107 0 : if (MHD_NO ==
2108 0 : MHD_add_response_header (response,
2109 : MHD_HTTP_HEADER_LOCATION,
2110 : NULL == rslash
2111 : ? "/"
2112 : : rslash))
2113 : {
2114 0 : GNUNET_break (0);
2115 0 : MHD_destroy_response (response);
2116 0 : GNUNET_free (instance_id);
2117 0 : return MHD_NO;
2118 : }
2119 0 : ret = MHD_queue_response (connection,
2120 : MHD_HTTP_PERMANENT_REDIRECT,
2121 : response);
2122 0 : MHD_destroy_response (response);
2123 0 : GNUNET_free (instance_id);
2124 0 : return ret;
2125 : }
2126 83 : hc->instance = TMH_lookup_instance (instance_id);
2127 83 : if ( (NULL == hc->instance) &&
2128 2 : (0 == strcmp ("admin",
2129 : instance_id)) )
2130 0 : hc->instance = TMH_lookup_instance (NULL);
2131 83 : GNUNET_free (instance_id);
2132 83 : if (NULL == slash)
2133 0 : url = "";
2134 : else
2135 83 : url = slash;
2136 : }
2137 : else
2138 : {
2139 : /* use 'default' */
2140 578 : use_default = true;
2141 578 : hc->instance = TMH_lookup_instance (NULL);
2142 : }
2143 661 : if (NULL != hc->instance)
2144 : {
2145 621 : GNUNET_assert (hc->instance->rc < UINT_MAX);
2146 621 : hc->instance->rc++;
2147 : }
2148 : }
2149 :
2150 : {
2151 661 : const char *management_prefix = "/management/";
2152 661 : const char *private_prefix = "/private/";
2153 :
2154 661 : if ( (0 == strncmp (url,
2155 : management_prefix,
2156 : strlen (management_prefix))) )
2157 : {
2158 61 : handlers = management_handlers;
2159 61 : url += strlen (management_prefix) - 1;
2160 : }
2161 600 : else if ( (0 == strncmp (url,
2162 : private_prefix,
2163 192 : strlen (private_prefix))) ||
2164 192 : (0 == strcmp (url,
2165 : "/private")) )
2166 : {
2167 423 : handlers = private_handlers;
2168 423 : if (0 == strcmp (url,
2169 : "/private"))
2170 15 : url = "/";
2171 : else
2172 408 : url += strlen (private_prefix) - 1;
2173 : }
2174 : else
2175 : {
2176 177 : handlers = public_handlers;
2177 : }
2178 : }
2179 :
2180 661 : if (0 == strcmp (url,
2181 : ""))
2182 0 : url = "/"; /* code below does not like empty string */
2183 :
2184 : {
2185 : /* Matching URL found, but maybe method doesn't match */
2186 : size_t prefix_strlen; /* i.e. 8 for "/orders/", or 7 for "/config" */
2187 661 : const char *infix_url = NULL; /* i.e. "$ORDER_ID", no '/'-es */
2188 661 : size_t infix_strlen = 0; /* number of characters in infix_url */
2189 661 : const char *suffix_url = NULL; /* i.e. "refund", excludes '/' at the beginning */
2190 661 : size_t suffix_strlen = 0; /* number of characters in suffix_url */
2191 :
2192 : /* parse the URL into the three different components */
2193 : {
2194 : const char *slash;
2195 :
2196 661 : slash = strchr (&url[1], '/');
2197 661 : if (NULL == slash)
2198 : {
2199 : /* the prefix was everything */
2200 306 : prefix_strlen = strlen (url);
2201 : }
2202 : else
2203 : {
2204 355 : prefix_strlen = slash - url + 1; /* includes both '/'-es if present! */
2205 355 : infix_url = slash + 1;
2206 355 : slash = strchr (infix_url, '/');
2207 355 : if (NULL == slash)
2208 : {
2209 : /* the infix was the rest */
2210 205 : infix_strlen = strlen (infix_url);
2211 : }
2212 : else
2213 : {
2214 150 : infix_strlen = slash - infix_url; /* excludes both '/'-es */
2215 150 : suffix_url = slash + 1; /* skip the '/' */
2216 150 : suffix_strlen = strlen (suffix_url);
2217 : }
2218 355 : hc->infix = GNUNET_strndup (infix_url,
2219 : infix_strlen);
2220 : }
2221 : }
2222 :
2223 : /* find matching handler */
2224 : {
2225 661 : bool url_found = false;
2226 :
2227 11915 : for (unsigned int i = 0; NULL != handlers[i].url_prefix; i++)
2228 : {
2229 11915 : struct TMH_RequestHandler *rh = &handlers[i];
2230 :
2231 11915 : if (rh->default_only && (! use_default))
2232 12 : continue;
2233 11903 : if (! prefix_match (rh,
2234 : url,
2235 : prefix_strlen,
2236 : infix_url,
2237 : infix_strlen,
2238 : suffix_url,
2239 : suffix_strlen))
2240 11041 : continue;
2241 862 : url_found = true;
2242 862 : if (0 == strcasecmp (method,
2243 : MHD_HTTP_METHOD_OPTIONS))
2244 : {
2245 1 : return TALER_MHD_reply_cors_preflight (connection);
2246 : }
2247 861 : if ( (rh->method != NULL) &&
2248 861 : (0 != strcasecmp (method,
2249 : rh->method)) )
2250 201 : continue;
2251 660 : hc->rh = rh;
2252 660 : break;
2253 : }
2254 : /* Handle HTTP 405: METHOD NOT ALLOWED case */
2255 660 : if ( (NULL == hc->rh) &&
2256 : (url_found) )
2257 : {
2258 : struct MHD_Response *reply;
2259 : MHD_RESULT ret;
2260 0 : char *allowed = NULL;
2261 :
2262 0 : GNUNET_break_op (0);
2263 : /* compute 'Allowed:' header (required by HTTP spec for 405 replies) */
2264 0 : for (unsigned int i = 0; NULL != handlers[i].url_prefix; i++)
2265 : {
2266 0 : struct TMH_RequestHandler *rh = &handlers[i];
2267 :
2268 0 : if (rh->default_only && (! use_default))
2269 0 : continue;
2270 0 : if (! prefix_match (rh,
2271 : url,
2272 : prefix_strlen,
2273 : infix_url,
2274 : infix_strlen,
2275 : suffix_url,
2276 : suffix_strlen))
2277 0 : continue;
2278 0 : if (NULL == allowed)
2279 : {
2280 0 : allowed = GNUNET_strdup (rh->method);
2281 : }
2282 : else
2283 : {
2284 : char *tmp;
2285 :
2286 0 : GNUNET_asprintf (&tmp,
2287 : "%s, %s",
2288 : allowed,
2289 : rh->method);
2290 0 : GNUNET_free (allowed);
2291 0 : allowed = tmp;
2292 : }
2293 0 : if (0 == strcasecmp (rh->method,
2294 : MHD_HTTP_METHOD_GET))
2295 : {
2296 : char *tmp;
2297 :
2298 0 : GNUNET_asprintf (&tmp,
2299 : "%s, %s",
2300 : allowed,
2301 : MHD_HTTP_METHOD_HEAD);
2302 0 : GNUNET_free (allowed);
2303 0 : allowed = tmp;
2304 : }
2305 : }
2306 0 : reply = TALER_MHD_make_error (TALER_EC_GENERIC_METHOD_INVALID,
2307 : method);
2308 0 : GNUNET_break (MHD_YES ==
2309 : MHD_add_response_header (reply,
2310 : MHD_HTTP_HEADER_ALLOW,
2311 : allowed));
2312 0 : GNUNET_free (allowed);
2313 0 : ret = MHD_queue_response (connection,
2314 : MHD_HTTP_METHOD_NOT_ALLOWED,
2315 : reply);
2316 0 : MHD_destroy_response (reply);
2317 0 : return ret;
2318 : }
2319 660 : if (NULL == hc->rh)
2320 : {
2321 0 : GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
2322 : "Endpoint `%s' not known\n",
2323 : hc->url);
2324 0 : return TALER_MHD_reply_with_error (connection,
2325 : MHD_HTTP_NOT_FOUND,
2326 : TALER_EC_GENERIC_ENDPOINT_UNKNOWN,
2327 : hc->url);
2328 : }
2329 : }
2330 : }
2331 : /* At this point, we must have found a handler */
2332 660 : GNUNET_assert (NULL != hc->rh);
2333 :
2334 : /* If an instance should be there, check one exists */
2335 660 : if ( (NULL == hc->instance) &&
2336 39 : (! hc->rh->skip_instance) )
2337 : {
2338 3 : GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
2339 : "Instance for `%s' not known\n",
2340 : hc->url);
2341 3 : return TALER_MHD_reply_with_error (connection,
2342 : MHD_HTTP_NOT_FOUND,
2343 : TALER_EC_MERCHANT_GENERIC_INSTANCE_UNKNOWN,
2344 3 : hc->infix);
2345 : }
2346 :
2347 : /* Access control for non-public handlers */
2348 657 : if (public_handlers != handlers)
2349 : {
2350 : const char *auth;
2351 480 : bool is_basic_auth = false;
2352 480 : bool auth_malformed = false;
2353 :
2354 480 : auth = MHD_lookup_connection_value (connection,
2355 : MHD_HEADER_KIND,
2356 : MHD_HTTP_HEADER_AUTHORIZATION);
2357 :
2358 480 : if (NULL != auth)
2359 : {
2360 71 : extract_auth (&auth,
2361 : &is_basic_auth);
2362 71 : if (NULL == auth)
2363 0 : auth_malformed = true;
2364 71 : hc->auth_token = auth;
2365 : }
2366 :
2367 : /* If we have zero configured instances (not even ones that have been
2368 : purged) or explicitly disabled authentication, THEN we accept anything
2369 : (no access control), as we then also have no data to protect. */
2370 480 : if ((0 == GNUNET_CONTAINER_multihashmap_size (TMH_by_id_map)) ||
2371 461 : (GNUNET_YES == TMH_auth_disabled))
2372 : {
2373 19 : hc->auth_scope = TMH_AS_ALL;
2374 : }
2375 461 : else if (is_basic_auth)
2376 : {
2377 14 : process_basic_auth (hc, auth);
2378 : }
2379 : else /* Check bearer token */
2380 : {
2381 : enum TALER_ErrorCode ec;
2382 447 : ec = process_bearer_auth (hc, auth);
2383 447 : if (TALER_EC_NONE != ec)
2384 : {
2385 19 : return TALER_MHD_reply_with_ec (connection,
2386 : ec,
2387 : NULL);
2388 : }
2389 : }
2390 : /* We grant access if:
2391 : - Endpoint does not require permissions
2392 : - Authorization scope of bearer token contains permissions
2393 : required by endpoint.
2394 : */
2395 480 : if ( (NULL != hc->rh->permission) &&
2396 477 : (! permission_in_scope (hc->rh->permission,
2397 : hc->auth_scope)))
2398 : {
2399 19 : if (auth_malformed &&
2400 0 : (TMH_AS_NONE == hc->auth_scope) )
2401 : {
2402 0 : GNUNET_break_op (0);
2403 0 : return TALER_MHD_reply_with_error (
2404 : connection,
2405 : MHD_HTTP_UNAUTHORIZED,
2406 : TALER_EC_GENERIC_PARAMETER_MALFORMED,
2407 : "'" RFC_8959_PREFIX
2408 : "' prefix or 'Bearer' missing in 'Authorization' header");
2409 : }
2410 19 : GNUNET_break_op (0);
2411 19 : return TALER_MHD_reply_with_error (connection,
2412 : MHD_HTTP_UNAUTHORIZED,
2413 : TALER_EC_MERCHANT_GENERIC_UNAUTHORIZED,
2414 : "Check 'Authorization' header");
2415 : }
2416 : } /* if (use_private) */
2417 :
2418 :
2419 638 : if ( (NULL == hc->instance) &&
2420 34 : (! hc->rh->skip_instance) )
2421 : {
2422 0 : GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
2423 : "Instance for URL `%s' not known\n",
2424 : url);
2425 0 : return TALER_MHD_reply_with_error (connection,
2426 : MHD_HTTP_NOT_FOUND,
2427 : TALER_EC_MERCHANT_GENERIC_INSTANCE_UNKNOWN,
2428 : url);
2429 : }
2430 638 : if ( (NULL != hc->instance) && /* make static analysis happy */
2431 604 : (! hc->rh->skip_instance) &&
2432 563 : (hc->instance->deleted) &&
2433 3 : (! hc->rh->allow_deleted_instance) )
2434 : {
2435 3 : GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
2436 : "Instance `%s' was deleted\n",
2437 : hc->instance->settings.id);
2438 3 : return TALER_MHD_reply_with_error (connection,
2439 : MHD_HTTP_NOT_FOUND,
2440 : TALER_EC_MERCHANT_GENERIC_INSTANCE_DELETED,
2441 3 : hc->instance->settings.id);
2442 : }
2443 : /* parse request body */
2444 1270 : hc->has_body = ( (0 == strcasecmp (method,
2445 922 : MHD_HTTP_METHOD_POST)) ||
2446 : /* PUT is not yet used */
2447 287 : (0 == strcasecmp (method,
2448 : MHD_HTTP_METHOD_PATCH)) );
2449 635 : if (hc->has_body)
2450 : {
2451 380 : TALER_MHD_check_content_length (connection,
2452 : 0 == hc->rh->max_upload
2453 : ? DEFAULT_MAX_UPLOAD_SIZE
2454 : : hc->rh->max_upload);
2455 380 : GNUNET_break (NULL == hc->request_body); /* can't have it already */
2456 : }
2457 635 : return MHD_YES; /* wait for MHD to call us again */
2458 : }
2459 :
2460 :
2461 : /**
2462 : * Callback invoked with information about a bank account.
2463 : *
2464 : * @param cls closure with a `struct TMH_MerchantInstance *`
2465 : * @param merchant_priv private key of the merchant instance
2466 : * @param acc details about the account
2467 : */
2468 : static void
2469 32 : add_account_cb (void *cls,
2470 : const struct TALER_MerchantPrivateKeyP *merchant_priv,
2471 : const struct TALER_MERCHANTDB_AccountDetails *acc)
2472 : {
2473 32 : struct TMH_MerchantInstance *mi = cls;
2474 : struct TMH_WireMethod *wm;
2475 :
2476 : (void) merchant_priv;
2477 32 : wm = GNUNET_new (struct TMH_WireMethod);
2478 32 : wm->h_wire = acc->h_wire;
2479 : wm->payto_uri.full_payto
2480 32 : = GNUNET_strdup (acc->payto_uri.full_payto);
2481 32 : wm->wire_salt = acc->salt;
2482 : wm->wire_method
2483 32 : = TALER_payto_get_method (acc->payto_uri.full_payto);
2484 32 : wm->active = acc->active;
2485 32 : GNUNET_CONTAINER_DLL_insert (mi->wm_head,
2486 : mi->wm_tail,
2487 : wm);
2488 32 : }
2489 :
2490 :
2491 : /**
2492 : * Function called during startup to add all known instances to our
2493 : * hash map in memory for faster lookups when we receive requests.
2494 : *
2495 : * @param cls closure, NULL, unused
2496 : * @param merchant_pub public key of the instance
2497 : * @param merchant_priv private key of the instance, NULL if not available
2498 : * @param is detailed configuration settings for the instance
2499 : * @param ias authentication settings for the instance
2500 : */
2501 : static void
2502 68 : add_instance_cb (void *cls,
2503 : const struct TALER_MerchantPublicKeyP *merchant_pub,
2504 : const struct TALER_MerchantPrivateKeyP *merchant_priv,
2505 : const struct TALER_MERCHANTDB_InstanceSettings *is,
2506 : const struct TALER_MERCHANTDB_InstanceAuthSettings *ias)
2507 : {
2508 : struct TMH_MerchantInstance *mi;
2509 : enum GNUNET_DB_QueryStatus qs;
2510 :
2511 : (void) cls;
2512 68 : mi = TMH_lookup_instance (is->id);
2513 68 : if (NULL != mi)
2514 : {
2515 : /* (outdated) entry exists, remove old entry */
2516 0 : (void) TMH_instance_free_cb (NULL,
2517 0 : &mi->h_instance,
2518 : mi);
2519 : }
2520 68 : mi = GNUNET_new (struct TMH_MerchantInstance);
2521 68 : mi->settings = *is;
2522 68 : mi->auth = *ias;
2523 68 : mi->settings.id = GNUNET_strdup (mi->settings.id);
2524 68 : mi->settings.name = GNUNET_strdup (mi->settings.name);
2525 68 : if (NULL != mi->settings.email)
2526 0 : mi->settings.email = GNUNET_strdup (mi->settings.email);
2527 68 : if (NULL != mi->settings.website)
2528 0 : mi->settings.website = GNUNET_strdup (mi->settings.website);
2529 68 : if (NULL != mi->settings.logo)
2530 0 : mi->settings.logo = GNUNET_strdup (mi->settings.logo);
2531 68 : mi->settings.address = json_incref (mi->settings.address);
2532 68 : mi->settings.jurisdiction = json_incref (mi->settings.jurisdiction);
2533 68 : if (NULL != merchant_priv)
2534 64 : mi->merchant_priv = *merchant_priv;
2535 : else
2536 4 : mi->deleted = true;
2537 68 : mi->merchant_pub = *merchant_pub;
2538 68 : qs = TMH_db->select_accounts (TMH_db->cls,
2539 68 : mi->settings.id,
2540 : &add_account_cb,
2541 : mi);
2542 68 : if (0 > qs)
2543 : {
2544 0 : GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
2545 : "Error loading accounts of `%s' from database\n",
2546 : mi->settings.id);
2547 : }
2548 68 : GNUNET_assert (GNUNET_OK ==
2549 : TMH_add_instance (mi));
2550 68 : }
2551 :
2552 :
2553 : /**
2554 : * Trigger (re)loading of instance settings from DB.
2555 : *
2556 : * @param cls NULL
2557 : * @param extra ID of the instance that changed, NULL
2558 : * to load all instances (will not handle purges!)
2559 : * @param extra_len number of bytes in @a extra
2560 : */
2561 : static void
2562 94 : load_instances (void *cls,
2563 : const void *extra,
2564 : size_t extra_len)
2565 : {
2566 : enum GNUNET_DB_QueryStatus qs;
2567 94 : const char *id = extra;
2568 :
2569 : (void) cls;
2570 94 : if ( (NULL != extra) &&
2571 80 : ( (0 == extra_len) ||
2572 80 : ('\0' != id[extra_len - 1]) ) )
2573 : {
2574 0 : GNUNET_break (0 == extra_len);
2575 0 : extra = NULL;
2576 : }
2577 94 : GNUNET_log (GNUNET_ERROR_TYPE_INFO,
2578 : "Received instance settings notification: reload `%s'\n",
2579 : id);
2580 94 : if (NULL == extra)
2581 : {
2582 14 : qs = TMH_db->lookup_instances (TMH_db->cls,
2583 : false,
2584 : &add_instance_cb,
2585 : NULL);
2586 : }
2587 : else
2588 : {
2589 : struct TMH_MerchantInstance *mi;
2590 :
2591 : /* This must be done here to handle instance
2592 : purging, as for purged instances, the DB
2593 : lookup below will otherwise do nothing */
2594 80 : mi = TMH_lookup_instance (id);
2595 80 : if (NULL != mi)
2596 : {
2597 80 : (void) TMH_instance_free_cb (NULL,
2598 80 : &mi->h_instance,
2599 : mi);
2600 : }
2601 80 : qs = TMH_db->lookup_instance (TMH_db->cls,
2602 : id,
2603 : false,
2604 : &add_instance_cb,
2605 : NULL);
2606 : }
2607 94 : if (0 > qs)
2608 : {
2609 0 : GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
2610 : "Failed initialization. Check database setup.\n");
2611 0 : global_ret = EXIT_NOPERMISSION;
2612 0 : GNUNET_SCHEDULER_shutdown ();
2613 0 : return;
2614 : }
2615 : }
2616 :
2617 :
2618 : /**
2619 : * A transaction modified an instance setting (or created/deleted/purged
2620 : * one). Notify all backends about the change.
2621 : *
2622 : * @param id ID of the instance that changed
2623 : */
2624 : void
2625 80 : TMH_reload_instances (const char *id)
2626 : {
2627 80 : struct GNUNET_DB_EventHeaderP es = {
2628 80 : .size = ntohs (sizeof (es)),
2629 80 : .type = ntohs (TALER_DBEVENT_MERCHANT_INSTANCE_SETTINGS)
2630 : };
2631 :
2632 80 : GNUNET_log (GNUNET_ERROR_TYPE_INFO,
2633 : "Generating instance settings notification: reload `%s'\n",
2634 : id);
2635 160 : TMH_db->event_notify (TMH_db->cls,
2636 : &es,
2637 : id,
2638 : (NULL == id)
2639 : ? 0
2640 80 : : strlen (id) + 1);
2641 80 : }
2642 :
2643 :
2644 : /**
2645 : * Callback invoked on every listen socket to start the
2646 : * respective MHD HTTP daemon.
2647 : *
2648 : * @param cls unused
2649 : * @param lsock the listen socket
2650 : */
2651 : static void
2652 28 : start_daemon (void *cls,
2653 : int lsock)
2654 : {
2655 : struct MHD_Daemon *mhd;
2656 :
2657 : (void) cls;
2658 28 : GNUNET_assert (-1 != lsock);
2659 28 : mhd = MHD_start_daemon (MHD_USE_SUSPEND_RESUME | MHD_USE_DUAL_STACK
2660 : | MHD_USE_AUTO,
2661 : 0 /* port */,
2662 : NULL, NULL,
2663 : &url_handler, NULL,
2664 : MHD_OPTION_LISTEN_SOCKET, lsock,
2665 : MHD_OPTION_URI_LOG_CALLBACK,
2666 : &full_url_track_callback, NULL,
2667 : MHD_OPTION_NOTIFY_COMPLETED,
2668 : &handle_mhd_completion_callback, NULL,
2669 : MHD_OPTION_CONNECTION_TIMEOUT,
2670 : (unsigned int) 10 /* 10s */,
2671 : MHD_OPTION_END);
2672 28 : if (NULL == mhd)
2673 : {
2674 0 : GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
2675 : "Failed to launch HTTP service.\n");
2676 0 : GNUNET_SCHEDULER_shutdown ();
2677 0 : return;
2678 : }
2679 28 : have_daemons = true;
2680 28 : TALER_MHD_daemon_start (mhd);
2681 : }
2682 :
2683 :
2684 : /**
2685 : * Main function that will be run by the scheduler.
2686 : *
2687 : * @param cls closure
2688 : * @param args remaining command-line arguments
2689 : * @param cfgfile name of the configuration file used (for saving, can be
2690 : * NULL!)
2691 : * @param config configuration
2692 : */
2693 : static void
2694 14 : run (void *cls,
2695 : char *const *args,
2696 : const char *cfgfile,
2697 : const struct GNUNET_CONFIGURATION_Handle *config)
2698 : {
2699 : enum TALER_MHD_GlobalOptions go;
2700 : int elen;
2701 :
2702 : (void) cls;
2703 : (void) args;
2704 : (void) cfgfile;
2705 14 : cfg = config;
2706 14 : GNUNET_log (GNUNET_ERROR_TYPE_INFO,
2707 : "Starting taler-merchant-httpd\n");
2708 14 : go = TALER_MHD_GO_NONE;
2709 14 : if (merchant_connection_close)
2710 0 : go |= TALER_MHD_GO_FORCE_CONNECTION_CLOSE;
2711 14 : TALER_MHD_setup (go);
2712 :
2713 14 : global_ret = EXIT_SUCCESS;
2714 14 : GNUNET_SCHEDULER_add_shutdown (&do_shutdown,
2715 : NULL);
2716 :
2717 : TMH_curl_ctx
2718 14 : = GNUNET_CURL_init (&GNUNET_CURL_gnunet_scheduler_reschedule,
2719 : &merchant_curl_rc);
2720 14 : if (NULL == TMH_curl_ctx)
2721 : {
2722 0 : GNUNET_break (0);
2723 0 : global_ret = EXIT_NO_RESTART;
2724 0 : GNUNET_SCHEDULER_shutdown ();
2725 0 : return;
2726 : }
2727 14 : merchant_curl_rc = GNUNET_CURL_gnunet_rc_create (TMH_curl_ctx);
2728 : /* Disable 100 continue processing */
2729 14 : GNUNET_break (GNUNET_OK ==
2730 : GNUNET_CURL_append_header (TMH_curl_ctx,
2731 : MHD_HTTP_HEADER_EXPECT ":"));
2732 14 : GNUNET_CURL_enable_async_scope_header (TMH_curl_ctx,
2733 : "Taler-Correlation-Id");
2734 :
2735 14 : if (GNUNET_SYSERR ==
2736 14 : TALER_config_get_currency (cfg,
2737 : "merchant",
2738 : &TMH_currency))
2739 : {
2740 0 : global_ret = EXIT_NOTCONFIGURED;
2741 0 : GNUNET_SCHEDULER_shutdown ();
2742 0 : return;
2743 : }
2744 14 : if (GNUNET_OK !=
2745 14 : TALER_CONFIG_parse_currencies (cfg,
2746 : TMH_currency,
2747 : &TMH_num_cspecs,
2748 : &TMH_cspecs))
2749 : {
2750 0 : global_ret = EXIT_NOTCONFIGURED;
2751 0 : GNUNET_SCHEDULER_shutdown ();
2752 0 : return;
2753 : }
2754 :
2755 14 : if (GNUNET_SYSERR ==
2756 14 : (TMH_strict_v19 = GNUNET_CONFIGURATION_get_value_yesno (cfg,
2757 : "merchant",
2758 : "STRICT_PROTOCOL_V19")))
2759 : {
2760 0 : GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR,
2761 : "merchant",
2762 : "STRICT_PROTOCOL_V19");
2763 0 : TMH_strict_v19 = GNUNET_NO;
2764 : }
2765 14 : if (GNUNET_SYSERR ==
2766 14 : (TMH_auth_disabled = GNUNET_CONFIGURATION_get_value_yesno (cfg,
2767 : "merchant",
2768 : "DISABLE_AUTHENTICATION")))
2769 : {
2770 0 : TMH_auth_disabled = GNUNET_NO;
2771 : }
2772 14 : if (GNUNET_YES == TMH_auth_disabled)
2773 : {
2774 0 : GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
2775 : "DANGEROUS: Endpoint Authentication disabled!");
2776 : }
2777 14 : if (GNUNET_OK !=
2778 14 : GNUNET_CONFIGURATION_get_value_time (cfg,
2779 : "merchant",
2780 : "LEGAL_PRESERVATION",
2781 : &TMH_legal_expiration))
2782 : {
2783 0 : GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR,
2784 : "merchant",
2785 : "LEGAL_PRESERVATION");
2786 0 : global_ret = EXIT_NOTCONFIGURED;
2787 0 : GNUNET_SCHEDULER_shutdown ();
2788 0 : return;
2789 : }
2790 14 : if (GNUNET_OK ==
2791 14 : GNUNET_CONFIGURATION_get_value_string (cfg,
2792 : "merchant",
2793 : "BASE_URL",
2794 : &TMH_base_url))
2795 : {
2796 0 : if (! TALER_is_web_url (TMH_base_url))
2797 : {
2798 0 : GNUNET_log_config_invalid (GNUNET_ERROR_TYPE_ERROR,
2799 : "merchant",
2800 : "BASE_URL",
2801 : "Needs to start with 'http://' or 'https://'");
2802 0 : global_ret = EXIT_NOTCONFIGURED;
2803 0 : GNUNET_SCHEDULER_shutdown ();
2804 0 : return;
2805 : }
2806 : }
2807 14 : if (GNUNET_YES ==
2808 14 : GNUNET_CONFIGURATION_get_value_yesno (cfg,
2809 : "merchant",
2810 : "FORCE_AUDIT"))
2811 11 : TMH_force_audit = GNUNET_YES;
2812 14 : if (GNUNET_OK !=
2813 14 : TALER_TEMPLATING_init (TALER_MERCHANT_project_data ()))
2814 : {
2815 0 : GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
2816 : "Failed to setup templates\n");
2817 0 : global_ret = EXIT_NOTINSTALLED;
2818 0 : GNUNET_SCHEDULER_shutdown ();
2819 0 : return;
2820 : }
2821 14 : if (GNUNET_OK !=
2822 14 : TMH_spa_init ())
2823 : {
2824 0 : GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
2825 : "Failed to load single page app\n");
2826 0 : global_ret = EXIT_NOTINSTALLED;
2827 0 : GNUNET_SCHEDULER_shutdown ();
2828 0 : return;
2829 : }
2830 : /* /static/ is currently not used */
2831 : /* (void) TMH_statics_init (); */
2832 14 : if (NULL ==
2833 14 : (TMH_by_id_map = GNUNET_CONTAINER_multihashmap_create (4,
2834 : GNUNET_YES)))
2835 : {
2836 0 : global_ret = EXIT_FAILURE;
2837 0 : GNUNET_SCHEDULER_shutdown ();
2838 0 : return;
2839 : }
2840 14 : if (NULL ==
2841 14 : (TMH_db = TALER_MERCHANTDB_plugin_load (cfg)))
2842 : {
2843 0 : global_ret = EXIT_NOTINSTALLED;
2844 0 : GNUNET_SCHEDULER_shutdown ();
2845 0 : return;
2846 : }
2847 14 : if (GNUNET_OK !=
2848 14 : TMH_db->connect (TMH_db->cls))
2849 : {
2850 0 : GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
2851 : "Failed to connect to database. Consider running taler-merchant-dbinit!\n");
2852 0 : global_ret = EXIT_FAILURE;
2853 0 : GNUNET_SCHEDULER_shutdown ();
2854 0 : return;
2855 : }
2856 14 : elen = TMH_EXCHANGES_init (config);
2857 14 : if (GNUNET_SYSERR == elen)
2858 : {
2859 0 : global_ret = EXIT_NOTCONFIGURED;
2860 0 : GNUNET_SCHEDULER_shutdown ();
2861 0 : return;
2862 : }
2863 14 : if (0 == elen)
2864 : {
2865 0 : GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
2866 : "Fatal: no trusted exchanges configured. Exiting.\n");
2867 0 : global_ret = EXIT_NOTCONFIGURED;
2868 0 : GNUNET_SCHEDULER_shutdown ();
2869 0 : return;
2870 : }
2871 :
2872 : {
2873 14 : struct GNUNET_DB_EventHeaderP es = {
2874 14 : .size = ntohs (sizeof (es)),
2875 14 : .type = ntohs (TALER_DBEVENT_MERCHANT_INSTANCE_SETTINGS)
2876 : };
2877 :
2878 28 : instance_eh = TMH_db->event_listen (TMH_db->cls,
2879 : &es,
2880 14 : GNUNET_TIME_UNIT_FOREVER_REL,
2881 : &load_instances,
2882 : NULL);
2883 : }
2884 14 : load_instances (NULL,
2885 : NULL,
2886 : 0);
2887 : {
2888 : enum GNUNET_GenericReturnValue ret;
2889 :
2890 14 : ret = TALER_MHD_listen_bind (cfg,
2891 : "merchant",
2892 : &start_daemon,
2893 : NULL);
2894 14 : switch (ret)
2895 : {
2896 0 : case GNUNET_SYSERR:
2897 0 : global_ret = EXIT_NOTCONFIGURED;
2898 0 : GNUNET_SCHEDULER_shutdown ();
2899 0 : return;
2900 0 : case GNUNET_NO:
2901 0 : if (! have_daemons)
2902 : {
2903 0 : global_ret = EXIT_NOTCONFIGURED;
2904 0 : GNUNET_SCHEDULER_shutdown ();
2905 0 : return;
2906 : }
2907 0 : GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
2908 : "Could not open all configured listen sockets\n");
2909 0 : break;
2910 14 : case GNUNET_OK:
2911 14 : break;
2912 : }
2913 : }
2914 14 : global_ret = EXIT_SUCCESS;
2915 : }
2916 :
2917 :
2918 : /**
2919 : * The main function of the serve tool
2920 : *
2921 : * @param argc number of arguments from the command line
2922 : * @param argv command line arguments
2923 : * @return 0 ok, non-zero on error
2924 : */
2925 : int
2926 27 : main (int argc,
2927 : char *const *argv)
2928 : {
2929 : enum GNUNET_GenericReturnValue res;
2930 27 : struct GNUNET_GETOPT_CommandLineOption options[] = {
2931 27 : GNUNET_GETOPT_option_flag ('C',
2932 : "connection-close",
2933 : "force HTTP connections to be closed after each request",
2934 : &merchant_connection_close),
2935 27 : GNUNET_GETOPT_option_timetravel ('T',
2936 : "timetravel"),
2937 27 : GNUNET_GETOPT_option_version (PACKAGE_VERSION "-" VCS_VERSION),
2938 : GNUNET_GETOPT_OPTION_END
2939 : };
2940 :
2941 27 : res = GNUNET_PROGRAM_run (
2942 : TALER_MERCHANT_project_data (),
2943 : argc, argv,
2944 : "taler-merchant-httpd",
2945 : "Taler merchant's HTTP backend interface",
2946 : options,
2947 : &run, NULL);
2948 27 : if (GNUNET_SYSERR == res)
2949 0 : return EXIT_INVALIDARGUMENT;
2950 27 : if (GNUNET_NO == res)
2951 13 : return EXIT_SUCCESS;
2952 14 : return global_ret;
2953 : }
|