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