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