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