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