Line data Source code
1 : /*
2 : This file is part of TALER
3 : (C) 2014-2022 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 : */
24 : #include "platform.h"
25 : #include <taler/taler_dbevents.h>
26 : #include <taler/taler_bank_service.h>
27 : #include <taler/taler_mhd_lib.h>
28 : #include <taler/taler_exchange_service.h>
29 : #include "taler-merchant-httpd_auditors.h"
30 : #include "taler-merchant-httpd_config.h"
31 : #include "taler-merchant-httpd_exchanges.h"
32 : #include "taler-merchant-httpd_get-orders-ID.h"
33 : #include "taler-merchant-httpd_get-tips-ID.h"
34 : #include "taler-merchant-httpd_mhd.h"
35 : #include "taler-merchant-httpd_private-delete-instances-ID.h"
36 : #include "taler-merchant-httpd_private-delete-products-ID.h"
37 : #include "taler-merchant-httpd_private-delete-orders-ID.h"
38 : #include "taler-merchant-httpd_private-delete-reserves-ID.h"
39 : #include "taler-merchant-httpd_private-delete-transfers-ID.h"
40 : #include "taler-merchant-httpd_private-get-instances.h"
41 : #include "taler-merchant-httpd_private-get-instances-ID.h"
42 : #include "taler-merchant-httpd_private-get-instances-ID-kyc.h"
43 : #include "taler-merchant-httpd_private-get-products.h"
44 : #include "taler-merchant-httpd_private-get-products-ID.h"
45 : #include "taler-merchant-httpd_private-get-orders.h"
46 : #include "taler-merchant-httpd_private-get-orders-ID.h"
47 : #include "taler-merchant-httpd_private-get-reserves.h"
48 : #include "taler-merchant-httpd_private-get-reserves-ID.h"
49 : #include "taler-merchant-httpd_private-get-tips-ID.h"
50 : #include "taler-merchant-httpd_private-get-tips.h"
51 : #include "taler-merchant-httpd_private-get-transfers.h"
52 : #include "taler-merchant-httpd_private-patch-instances-ID.h"
53 : #include "taler-merchant-httpd_private-patch-orders-ID-forget.h"
54 : #include "taler-merchant-httpd_private-patch-products-ID.h"
55 : #include "taler-merchant-httpd_private-post-instances.h"
56 : #include "taler-merchant-httpd_private-post-instances-ID-auth.h"
57 : #include "taler-merchant-httpd_private-post-orders.h"
58 : #include "taler-merchant-httpd_private-post-orders-ID-refund.h"
59 : #include "taler-merchant-httpd_private-post-products.h"
60 : #include "taler-merchant-httpd_private-post-products-ID-lock.h"
61 : #include "taler-merchant-httpd_private-post-reserves.h"
62 : #include "taler-merchant-httpd_private-post-reserves-ID-authorize-tip.h"
63 : #include "taler-merchant-httpd_private-post-transfers.h"
64 : #include "taler-merchant-httpd_post-orders-ID-abort.h"
65 : #include "taler-merchant-httpd_post-orders-ID-claim.h"
66 : #include "taler-merchant-httpd_post-orders-ID-paid.h"
67 : #include "taler-merchant-httpd_post-orders-ID-pay.h"
68 : #include "taler-merchant-httpd_post-orders-ID-refund.h"
69 : #include "taler-merchant-httpd_post-tips-ID-pickup.h"
70 : #include "taler-merchant-httpd_reserves.h"
71 : #include "taler-merchant-httpd_spa.h"
72 : #include "taler-merchant-httpd_statics.h"
73 : #include "taler-merchant-httpd_templating.h"
74 :
75 : /**
76 : * Fixme: document.
77 : */
78 : #define INSTANCE_STALENESS GNUNET_TIME_UNIT_MINUTES
79 :
80 : /**
81 : * Backlog for listen operation on unix-domain sockets.
82 : */
83 : #define UNIX_BACKLOG 500
84 :
85 : /**
86 : * Default maximum upload size permitted. Can be overridden
87 : * per handler.
88 : */
89 : #define DEFAULT_MAX_UPLOAD_SIZE (16 * 1024)
90 :
91 : /**
92 : * Which currency do we use?
93 : */
94 : char *TMH_currency;
95 :
96 : /**
97 : * Inform the auditor for all deposit confirmations (global option)
98 : */
99 : int TMH_force_audit;
100 :
101 : /**
102 : * Connection handle to the our database
103 : */
104 : struct TALER_MERCHANTDB_Plugin *TMH_db;
105 :
106 : /**
107 : * Event handler for instance settings changes.
108 : */
109 : static struct GNUNET_DB_EventHandler *instance_eh;
110 :
111 : /**
112 : * Hashmap pointing at merchant instances by 'id'. An 'id' is
113 : * just a string that identifies a merchant instance. When a frontend
114 : * needs to specify an instance to the backend, it does so by 'id'
115 : */
116 : struct GNUNET_CONTAINER_MultiHashMap *TMH_by_id_map;
117 :
118 : /**
119 : * How long do we need to keep information on paid contracts on file for tax
120 : * or other legal reasons? Used to block deletions for younger transaction
121 : * data.
122 : */
123 : struct GNUNET_TIME_Relative TMH_legal_expiration;
124 :
125 : /**
126 : * The port we are running on
127 : */
128 : static uint16_t port;
129 :
130 : /**
131 : * Should a "Connection: close" header be added to each HTTP response?
132 : */
133 : static int merchant_connection_close;
134 :
135 : /**
136 : * Global return code
137 : */
138 : static int result;
139 :
140 : /**
141 : * Our configuration.
142 : */
143 : static const struct GNUNET_CONFIGURATION_Handle *cfg;
144 :
145 : /**
146 : * Initial authorization token.
147 : */
148 : char *TMH_default_auth;
149 :
150 :
151 : enum GNUNET_GenericReturnValue
152 19 : TMH_check_auth (const char *token,
153 : struct TALER_MerchantAuthenticationSaltP *salt,
154 : struct TALER_MerchantAuthenticationHashP *hash)
155 : {
156 : struct GNUNET_HashCode val;
157 : char *dec;
158 : size_t dec_len;
159 :
160 19 : if (GNUNET_is_zero (hash))
161 9 : return GNUNET_OK;
162 10 : if (NULL == token)
163 2 : return GNUNET_SYSERR;
164 8 : dec_len = GNUNET_STRINGS_urldecode (token,
165 : strlen (token),
166 : &dec);
167 8 : GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
168 : "Checking against token with salt %s\n",
169 : TALER_B2S (salt));
170 8 : GNUNET_assert (GNUNET_YES ==
171 : GNUNET_CRYPTO_kdf (&val,
172 : sizeof (val),
173 : salt,
174 : sizeof (*salt),
175 : dec,
176 : dec_len,
177 : "merchant-instance-auth",
178 : strlen ("merchant-instance-auth"),
179 : NULL,
180 : 0));
181 8 : GNUNET_free (dec);
182 8 : return (0 == GNUNET_memcmp (&val,
183 : &hash->hash))
184 : ? GNUNET_OK
185 8 : : GNUNET_SYSERR;
186 : }
187 :
188 :
189 : void
190 4 : TMH_compute_auth (const char *token,
191 : struct TALER_MerchantAuthenticationSaltP *salt,
192 : struct TALER_MerchantAuthenticationHashP *hash)
193 : {
194 4 : GNUNET_CRYPTO_random_block (GNUNET_CRYPTO_QUALITY_NONCE,
195 : salt,
196 : sizeof (*salt));
197 4 : GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
198 : "Computing initial auth using token with salt %s\n",
199 : TALER_B2S (salt));
200 4 : GNUNET_assert (GNUNET_YES ==
201 : GNUNET_CRYPTO_kdf (hash,
202 : sizeof (*hash),
203 : salt,
204 : sizeof (*salt),
205 : token,
206 : strlen (token),
207 : "merchant-instance-auth",
208 : strlen ("merchant-instance-auth"),
209 : NULL,
210 : 0));
211 4 : }
212 :
213 :
214 : void
215 40 : TMH_instance_decref (struct TMH_MerchantInstance *mi)
216 : {
217 : struct TMH_WireMethod *wm;
218 :
219 40 : mi->rc--;
220 40 : if (0 != mi->rc)
221 26 : return;
222 14 : TMH_force_get_orders_resume (mi);
223 28 : while (NULL != (wm = (mi->wm_head)))
224 : {
225 14 : GNUNET_CONTAINER_DLL_remove (mi->wm_head,
226 : mi->wm_tail,
227 : wm);
228 14 : GNUNET_free (wm->payto_uri);
229 14 : GNUNET_free (wm->wire_method);
230 14 : GNUNET_free (wm);
231 : }
232 :
233 14 : GNUNET_free (mi->settings.id);
234 14 : GNUNET_free (mi->settings.name);
235 14 : GNUNET_free (mi->settings.email);
236 14 : GNUNET_free (mi->settings.website);
237 14 : GNUNET_free (mi->settings.logo);
238 14 : json_decref (mi->settings.address);
239 14 : json_decref (mi->settings.jurisdiction);
240 14 : GNUNET_free (mi);
241 : }
242 :
243 :
244 : /**
245 : * Callback that frees an instances removing
246 : * it from the global hashmap.
247 : *
248 : * @param cls closure, NULL
249 : * @param key current key
250 : * @param value a `struct TMH_MerchantInstance`
251 : */
252 : int
253 14 : TMH_instance_free_cb (void *cls,
254 : const struct GNUNET_HashCode *key,
255 : void *value)
256 : {
257 14 : struct TMH_MerchantInstance *mi = value;
258 :
259 : (void) cls;
260 : (void) key;
261 14 : GNUNET_assert (GNUNET_OK ==
262 : GNUNET_CONTAINER_multihashmap_remove (TMH_by_id_map,
263 : &mi->h_instance,
264 : mi));
265 14 : TMH_instance_decref (mi);
266 14 : return GNUNET_YES;
267 : }
268 :
269 :
270 : /**
271 : * Shutdown task (magically invoked when the application is being
272 : * quit)
273 : *
274 : * @param cls NULL
275 : */
276 : static void
277 3 : do_shutdown (void *cls)
278 : {
279 : (void) cls;
280 3 : TMH_force_ac_resume ();
281 3 : TMH_force_pc_resume ();
282 3 : TMH_force_kyc_resume ();
283 3 : TMH_force_rc_resume ();
284 3 : TMH_force_gorc_resume ();
285 3 : TMH_force_post_transfers_resume ();
286 3 : TMH_force_tip_pickup_resume ();
287 3 : TMH_force_wallet_get_order_resume ();
288 3 : TMH_force_wallet_refund_order_resume ();
289 : {
290 : struct MHD_Daemon *mhd;
291 :
292 3 : mhd = TALER_MHD_daemon_stop ();
293 3 : if (NULL != mhd)
294 3 : MHD_stop_daemon (mhd);
295 : }
296 3 : TMH_RESERVES_done ();
297 3 : if (NULL != instance_eh)
298 : {
299 3 : TMH_db->event_listen_cancel (instance_eh);
300 3 : instance_eh = NULL;
301 : }
302 3 : if (NULL != TMH_db)
303 : {
304 3 : TALER_MERCHANTDB_plugin_unload (TMH_db);
305 3 : TMH_db = NULL;
306 : }
307 3 : TMH_EXCHANGES_done ();
308 3 : TMH_AUDITORS_done ();
309 3 : if (NULL != TMH_by_id_map)
310 : {
311 3 : GNUNET_CONTAINER_multihashmap_iterate (TMH_by_id_map,
312 : &TMH_instance_free_cb,
313 : NULL);
314 3 : GNUNET_CONTAINER_multihashmap_destroy (TMH_by_id_map);
315 3 : TMH_by_id_map = NULL;
316 : }
317 3 : }
318 :
319 :
320 : /**
321 : * Function called whenever MHD is done with a request. If the
322 : * request was a POST, we may have stored a `struct Buffer *` in the
323 : * @a con_cls that might still need to be cleaned up. Call the
324 : * respective function to free the memory.
325 : *
326 : * @param cls client-defined closure
327 : * @param connection connection handle
328 : * @param con_cls value as set by the last call to
329 : * the #MHD_AccessHandlerCallback
330 : * @param toe reason for request termination
331 : * @see #MHD_OPTION_NOTIFY_COMPLETED
332 : * @ingroup request
333 : */
334 : static void
335 38 : handle_mhd_completion_callback (void *cls,
336 : struct MHD_Connection *connection,
337 : void **con_cls,
338 : enum MHD_RequestTerminationCode toe)
339 : {
340 38 : struct TMH_HandlerContext *hc = *con_cls;
341 :
342 38 : if (NULL == hc)
343 0 : return;
344 38 : GNUNET_SCHEDULER_begin_async_scope (&hc->async_scope_id);
345 : {
346 : #if MHD_VERSION >= 0x00097304
347 : const union MHD_ConnectionInfo *ci;
348 38 : unsigned int http_status = 0;
349 :
350 38 : ci = MHD_get_connection_info (connection,
351 : MHD_CONNECTION_INFO_HTTP_STATUS);
352 38 : if (NULL != ci)
353 38 : http_status = ci->http_status;
354 38 : GNUNET_log (GNUNET_ERROR_TYPE_INFO,
355 : "Request for `%s' completed with HTTP status %u (%d)\n",
356 : hc->url,
357 : http_status,
358 : toe);
359 : #else
360 : (void) connection;
361 : GNUNET_log (GNUNET_ERROR_TYPE_INFO,
362 : "Finished handling request for `%s' with MHD termination code %d\n",
363 : hc->url,
364 : (int) toe);
365 : #endif
366 : }
367 38 : if (NULL != hc->cc)
368 1 : hc->cc (hc->ctx);
369 38 : TALER_MHD_parse_post_cleanup_callback (hc->json_parse_context);
370 38 : GNUNET_free (hc->infix);
371 38 : if (NULL != hc->request_body)
372 8 : json_decref (hc->request_body);
373 38 : if (NULL != hc->instance)
374 26 : TMH_instance_decref (hc->instance);
375 38 : GNUNET_free (hc);
376 38 : *con_cls = NULL;
377 : }
378 :
379 :
380 : struct TMH_MerchantInstance *
381 62 : TMH_lookup_instance (const char *instance_id)
382 : {
383 : struct GNUNET_HashCode h_instance;
384 :
385 62 : if (NULL == instance_id)
386 28 : instance_id = "default";
387 62 : GNUNET_CRYPTO_hash (instance_id,
388 : strlen (instance_id),
389 : &h_instance);
390 62 : GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
391 : "Looking for by-id key %s of '%s' in hashmap\n",
392 : GNUNET_h2s (&h_instance),
393 : instance_id);
394 : /* We're fine if that returns NULL, the calling routine knows how
395 : to handle that */
396 62 : return GNUNET_CONTAINER_multihashmap_get (TMH_by_id_map,
397 : &h_instance);
398 : }
399 :
400 :
401 : /**
402 : * Add instance definition to our active set of instances.
403 : *
404 : * @param[in,out] mi merchant instance details to define
405 : * @return #GNUNET_OK on success, #GNUNET_NO if the same ID is in use already
406 : */
407 : enum GNUNET_GenericReturnValue
408 14 : TMH_add_instance (struct TMH_MerchantInstance *mi)
409 : {
410 : const char *id;
411 : int ret;
412 :
413 14 : id = mi->settings.id;
414 14 : if (NULL == id)
415 0 : id = "default";
416 14 : GNUNET_CRYPTO_hash (id,
417 : strlen (id),
418 : &mi->h_instance);
419 14 : GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
420 : "Looking for by-id key %s of `%s' in hashmap\n",
421 : GNUNET_h2s (&mi->h_instance),
422 : id);
423 14 : ret = GNUNET_CONTAINER_multihashmap_put (TMH_by_id_map,
424 14 : &mi->h_instance,
425 : mi,
426 : GNUNET_CONTAINER_MULTIHASHMAPOPTION_UNIQUE_ONLY);
427 14 : if (GNUNET_OK == ret)
428 : {
429 14 : GNUNET_assert (mi->rc < UINT_MAX);
430 14 : mi->rc++;
431 : }
432 14 : return ret;
433 : }
434 :
435 :
436 : /**
437 : * Handle a OPTIONS "*" request.
438 : *
439 : * @param rh context of the handler
440 : * @param connection the MHD connection to handle
441 : * @param[in,out] hc context with further information about the request
442 : * @return MHD result code
443 : */
444 : static MHD_RESULT
445 0 : handle_server_options (const struct TMH_RequestHandler *rh,
446 : struct MHD_Connection *connection,
447 : struct TMH_HandlerContext *hc)
448 : {
449 0 : return TALER_MHD_reply_cors_preflight (connection);
450 : }
451 :
452 :
453 : /**
454 : * Extract the token from authorization header value @a auth.
455 : *
456 : * @param auth pointer to authorization header value,
457 : * will be updated to point to the start of the token
458 : * or set to NULL if header value is invalid
459 : */
460 : static void
461 10 : extract_token (const char **auth)
462 : {
463 10 : const char *bearer = "Bearer ";
464 10 : const char *tok = *auth;
465 :
466 10 : if (0 != strncmp (tok, bearer, strlen (bearer)))
467 : {
468 1 : *auth = NULL;
469 1 : return;
470 : }
471 9 : tok = tok + strlen (bearer);
472 13 : while (' ' == *tok)
473 4 : tok++;
474 9 : if (0 != strncasecmp (tok,
475 : RFC_8959_PREFIX,
476 : strlen (RFC_8959_PREFIX)))
477 : {
478 0 : *auth = NULL;
479 0 : return;
480 : }
481 9 : *auth = tok;
482 : }
483 :
484 :
485 : /**
486 : * Checks if the @a rh matches the given (parsed) URL.
487 : *
488 : * @param rh handler to compare against
489 : * @param url the main URL (without "/private/" prefix, if any)
490 : * @param prefix_strlen length of the prefix, i.e. 8 for '/orders/' or 7 for '/config'
491 : * @param infix_url infix text, i.e. "$ORDER_ID".
492 : * @param infix_strlen length of the string in @a infix_url
493 : * @param suffix_url suffix, i.e. "/refund", including the "/"
494 : * @param suffix_strlen number of characters in @a suffix_url
495 : * @return true if @a rh matches this request
496 : */
497 : static bool
498 291 : prefix_match (const struct TMH_RequestHandler *rh,
499 : const char *url,
500 : size_t prefix_strlen,
501 : const char *infix_url,
502 : size_t infix_strlen,
503 : const char *suffix_url,
504 : size_t suffix_strlen)
505 : {
506 291 : if ( (prefix_strlen != strlen (rh->url_prefix)) ||
507 72 : (0 != memcmp (url,
508 72 : rh->url_prefix,
509 : prefix_strlen)) )
510 249 : return false;
511 42 : if (! rh->have_id_segment)
512 : {
513 : /* Require /$PREFIX/$SUFFIX or /$PREFIX */
514 36 : if (NULL != suffix_url)
515 0 : return false; /* too many segments to match */
516 36 : if ( (NULL == infix_url) /* either or */
517 36 : ^ (NULL == rh->url_suffix) )
518 0 : return false; /* suffix existence mismatch */
519 : /* If /$PREFIX/$SUFFIX, check $SUFFIX matches */
520 36 : if ( (NULL != infix_url) &&
521 0 : ( (infix_strlen != strlen (rh->url_suffix)) ||
522 0 : (0 != memcmp (infix_url,
523 0 : rh->url_suffix,
524 : infix_strlen)) ) )
525 0 : return false; /* cannot use infix as suffix: content mismatch */
526 : }
527 : else
528 : {
529 : /* Require /$PREFIX/$ID or /$PREFIX/$ID/$SUFFIX */
530 6 : if (NULL == infix_url)
531 0 : return false; /* infix existence mismatch */
532 6 : if ( ( (NULL == suffix_url)
533 6 : ^ (NULL == rh->url_suffix) ) )
534 0 : return false; /* suffix existence mismatch */
535 6 : if ( (NULL != suffix_url) &&
536 0 : ( (suffix_strlen != strlen (rh->url_suffix)) ||
537 0 : (0 != memcmp (suffix_url,
538 0 : rh->url_suffix,
539 : suffix_strlen)) ) )
540 0 : return false; /* suffix content mismatch */
541 : }
542 42 : return true;
543 : }
544 :
545 :
546 : /**
547 : * A client has requested the given url using the given method
548 : * (#MHD_HTTP_METHOD_GET, #MHD_HTTP_METHOD_PUT,
549 : * #MHD_HTTP_METHOD_DELETE, #MHD_HTTP_METHOD_POST, etc). The callback
550 : * must call MHD callbacks to provide content to give back to the
551 : * client and return an HTTP status code (i.e. #MHD_HTTP_OK,
552 : * #MHD_HTTP_NOT_FOUND, etc.).
553 : *
554 : * @param cls argument given together with the function
555 : * pointer when the handler was registered with MHD
556 : * @param connection the MHD connection to handle
557 : * @param url the requested url
558 : * @param method the HTTP method used (#MHD_HTTP_METHOD_GET,
559 : * #MHD_HTTP_METHOD_PUT, etc.)
560 : * @param version the HTTP version string (i.e.
561 : * #MHD_HTTP_VERSION_1_1)
562 : * @param upload_data the data being uploaded (excluding HEADERS,
563 : * for a POST that fits into memory and that is encoded
564 : * with a supported encoding, the POST data will NOT be
565 : * given in upload_data and is instead available as
566 : * part of #MHD_get_connection_values; very large POST
567 : * data *will* be made available incrementally in
568 : * @a upload_data)
569 : * @param upload_data_size set initially to the size of the
570 : * @a upload_data provided; the method must update this
571 : * value to the number of bytes NOT processed;
572 : * @param con_cls pointer that the callback can set to some
573 : * address and that will be preserved by MHD for future
574 : * calls for this request; since the access handler may
575 : * be called many times (i.e., for a PUT/POST operation
576 : * with plenty of upload data) this allows the application
577 : * to easily associate some request-specific state.
578 : * If necessary, this state can be cleaned up in the
579 : * global #MHD_RequestCompletedCallback (which
580 : * can be set with the #MHD_OPTION_NOTIFY_COMPLETED).
581 : * Initially, `*con_cls` will be NULL.
582 : * @return #MHD_YES if the connection was handled successfully,
583 : * #MHD_NO if the socket must be closed due to a serious
584 : * error while handling the request
585 : */
586 : static MHD_RESULT
587 71 : url_handler (void *cls,
588 : struct MHD_Connection *connection,
589 : const char *url,
590 : const char *method,
591 : const char *version,
592 : const char *upload_data,
593 : size_t *upload_data_size,
594 : void **con_cls)
595 : {
596 : static struct TMH_RequestHandler management_handlers[] = {
597 : /* GET /instances */
598 : {
599 : .url_prefix = "/instances",
600 : .method = MHD_HTTP_METHOD_GET,
601 : .skip_instance = true,
602 : .default_only = true,
603 : .handler = &TMH_private_get_instances
604 : },
605 : /* POST /instances */
606 : {
607 : .url_prefix = "/instances",
608 : .method = MHD_HTTP_METHOD_POST,
609 : .skip_instance = true,
610 : .default_only = true,
611 : .handler = &TMH_private_post_instances,
612 : /* allow instance data of up to 8 MB, that should be plenty;
613 : note that exceeding #GNUNET_MAX_MALLOC_CHECKED (40 MB)
614 : would require further changes to the allocation logic
615 : in the code... */
616 : .max_upload = 1024 * 1024 * 8
617 : },
618 : /* GET /instances/$ID/ */
619 : {
620 : .url_prefix = "/instances/",
621 : .method = MHD_HTTP_METHOD_GET,
622 : .skip_instance = true,
623 : .default_only = true,
624 : .have_id_segment = true,
625 : .handler = &TMH_private_get_instances_default_ID
626 : },
627 : /* DELETE /instances/$ID */
628 : {
629 : .url_prefix = "/instances/",
630 : .method = MHD_HTTP_METHOD_DELETE,
631 : .skip_instance = true,
632 : .default_only = true,
633 : .have_id_segment = true,
634 : .handler = &TMH_private_delete_instances_default_ID
635 : },
636 : /* PATCH /instances/$ID */
637 : {
638 : .url_prefix = "/instances/",
639 : .method = MHD_HTTP_METHOD_PATCH,
640 : .skip_instance = true,
641 : .default_only = true,
642 : .have_id_segment = true,
643 : .handler = &TMH_private_patch_instances_default_ID,
644 : /* allow instance data of up to 8 MB, that should be plenty;
645 : note that exceeding #GNUNET_MAX_MALLOC_CHECKED (40 MB)
646 : would require further changes to the allocation logic
647 : in the code... */
648 : .max_upload = 1024 * 1024 * 8
649 : },
650 : /* POST /auth: */
651 : {
652 : .url_prefix = "/instances/",
653 : .url_suffix = "auth",
654 : .method = MHD_HTTP_METHOD_POST,
655 : .skip_instance = true,
656 : .default_only = true,
657 : .have_id_segment = true,
658 : .handler = &TMH_private_post_instances_default_ID_auth,
659 : /* Body should be pretty small. */
660 : .max_upload = 1024 * 1024
661 : },
662 : /* POST /kyc: */
663 : {
664 : .url_prefix = "/instances/",
665 : .url_suffix = "kyc",
666 : .method = MHD_HTTP_METHOD_GET,
667 : .skip_instance = true,
668 : .default_only = true,
669 : .have_id_segment = true,
670 : .handler = &TMH_private_get_instances_default_ID_kyc,
671 : },
672 : {
673 : NULL
674 : }
675 : };
676 :
677 : static struct TMH_RequestHandler private_handlers[] = {
678 : /* GET /instances/$ID/: */
679 : {
680 : .url_prefix = "/",
681 : .method = MHD_HTTP_METHOD_GET,
682 : .handler = &TMH_private_get_instances_ID
683 : },
684 : /* DELETE /instances/$ID/: */
685 : {
686 : .url_prefix = "/",
687 : .method = MHD_HTTP_METHOD_DELETE,
688 : .allow_deleted_instance = true,
689 : .handler = &TMH_private_delete_instances_ID
690 : },
691 : /* PATCH /instances/$ID/: */
692 : {
693 : .url_prefix = "/",
694 : .method = MHD_HTTP_METHOD_PATCH,
695 : .handler = &TMH_private_patch_instances_ID,
696 : .allow_deleted_instance = true,
697 : /* allow instance data of up to 8 MB, that should be plenty;
698 : note that exceeding #GNUNET_MAX_MALLOC_CHECKED (40 MB)
699 : would require further changes to the allocation logic
700 : in the code... */
701 : .max_upload = 1024 * 1024 * 8
702 : },
703 : /* POST /auth: */
704 : {
705 : .url_prefix = "/auth",
706 : .method = MHD_HTTP_METHOD_POST,
707 : .handler = &TMH_private_post_instances_ID_auth,
708 : /* Body should be pretty small. */
709 : .max_upload = 1024 * 1024,
710 : },
711 : /* GET /kyc: */
712 : {
713 : .url_prefix = "/kyc",
714 : .method = MHD_HTTP_METHOD_GET,
715 : .handler = &TMH_private_get_instances_ID_kyc,
716 : },
717 : /* GET /products: */
718 : {
719 : .url_prefix = "/products",
720 : .method = MHD_HTTP_METHOD_GET,
721 : .handler = &TMH_private_get_products
722 : },
723 : /* POST /products: */
724 : {
725 : .url_prefix = "/products",
726 : .method = MHD_HTTP_METHOD_POST,
727 : .handler = &TMH_private_post_products,
728 : /* allow product data of up to 8 MB, that should be plenty;
729 : note that exceeding #GNUNET_MAX_MALLOC_CHECKED (40 MB)
730 : would require further changes to the allocation logic
731 : in the code... */
732 : .max_upload = 1024 * 1024 * 8
733 : },
734 : /* GET /products/$ID/: */
735 : {
736 : .url_prefix = "/products/",
737 : .method = MHD_HTTP_METHOD_GET,
738 : .have_id_segment = true,
739 : .allow_deleted_instance = true,
740 : .handler = &TMH_private_get_products_ID
741 : },
742 : /* DELETE /products/$ID/: */
743 : {
744 : .url_prefix = "/products/",
745 : .method = MHD_HTTP_METHOD_DELETE,
746 : .have_id_segment = true,
747 : .allow_deleted_instance = true,
748 : .handler = &TMH_private_delete_products_ID
749 : },
750 : /* PATCH /products/$ID/: */
751 : {
752 : .url_prefix = "/products/",
753 : .method = MHD_HTTP_METHOD_PATCH,
754 : .have_id_segment = true,
755 : .allow_deleted_instance = true,
756 : .handler = &TMH_private_patch_products_ID,
757 : /* allow product data of up to 8 MB, that should be plenty;
758 : note that exceeding #GNUNET_MAX_MALLOC_CHECKED (40 MB)
759 : would require further changes to the allocation logic
760 : in the code... */
761 : .max_upload = 1024 * 1024 * 8
762 : },
763 : /* POST /products/$ID/lock: */
764 : {
765 : .url_prefix = "/products/",
766 : .url_suffix = "lock",
767 : .method = MHD_HTTP_METHOD_POST,
768 : .have_id_segment = true,
769 : .handler = &TMH_private_post_products_ID_lock,
770 : /* the body should be pretty small, allow 1 MB of upload
771 : to set a conservative bound for sane wallets */
772 : .max_upload = 1024 * 1024
773 : },
774 : /* POST /orders: */
775 : {
776 : .url_prefix = "/orders",
777 : .method = MHD_HTTP_METHOD_POST,
778 : .handler = &TMH_private_post_orders,
779 : /* allow contracts of up to 8 MB, that should be plenty;
780 : note that exceeding #GNUNET_MAX_MALLOC_CHECKED (40 MB)
781 : would require further changes to the allocation logic
782 : in the code... */
783 : .max_upload = 1024 * 1024 * 8
784 : },
785 : /* GET /orders/$ID: */
786 : {
787 : .url_prefix = "/orders/",
788 : .method = MHD_HTTP_METHOD_GET,
789 : .have_id_segment = true,
790 : .allow_deleted_instance = true,
791 : .handler = &TMH_private_get_orders_ID
792 : },
793 : /* GET /orders: */
794 : {
795 : .url_prefix = "/orders",
796 : .method = MHD_HTTP_METHOD_GET,
797 : .allow_deleted_instance = true,
798 : .handler = &TMH_private_get_orders
799 : },
800 : /* POST /orders/$ID/refund: */
801 : {
802 : .url_prefix = "/orders/",
803 : .url_suffix = "refund",
804 : .method = MHD_HTTP_METHOD_POST,
805 : .have_id_segment = true,
806 : .handler = &TMH_private_post_orders_ID_refund,
807 : /* the body should be pretty small, allow 1 MB of upload
808 : to set a conservative bound for sane wallets */
809 : .max_upload = 1024 * 1024
810 : },
811 : /* PATCH /orders/$ID/forget: */
812 : {
813 : .url_prefix = "/orders/",
814 : .url_suffix = "forget",
815 : .method = MHD_HTTP_METHOD_PATCH,
816 : .have_id_segment = true,
817 : .allow_deleted_instance = true,
818 : .handler = &TMH_private_patch_orders_ID_forget,
819 : /* the body should be pretty small, allow 1 MB of upload
820 : to set a conservative bound for sane wallets */
821 : .max_upload = 1024 * 1024
822 : },
823 : /* DELETE /orders/$ID: */
824 : {
825 : .url_prefix = "/orders/",
826 : .method = MHD_HTTP_METHOD_DELETE,
827 : .have_id_segment = true,
828 : .allow_deleted_instance = true,
829 : .handler = &TMH_private_delete_orders_ID
830 : },
831 : /* POST /reserves: */
832 : {
833 : .url_prefix = "/reserves",
834 : .method = MHD_HTTP_METHOD_POST,
835 : .handler = &TMH_private_post_reserves,
836 : /* the body should be pretty small, allow 1 MB of upload
837 : to set a conservative bound for sane wallets */
838 : .max_upload = 1024 * 1024
839 : },
840 : /* DELETE /reserves/$ID: */
841 : {
842 : .url_prefix = "/reserves/",
843 : .have_id_segment = true,
844 : .allow_deleted_instance = true,
845 : .method = MHD_HTTP_METHOD_DELETE,
846 : .handler = &TMH_private_delete_reserves_ID
847 : },
848 : /* POST /reserves/$ID/authorize-tip: */
849 : {
850 : .url_prefix = "/reserves/",
851 : .url_suffix = "authorize-tip",
852 : .have_id_segment = true,
853 : .method = MHD_HTTP_METHOD_POST,
854 : .handler = &TMH_private_post_reserves_ID_authorize_tip,
855 : /* the body should be pretty small, allow 1 MB of upload
856 : to set a conservative bound for sane wallets */
857 : .max_upload = 1024 * 1024
858 : },
859 : /* POST /tips: */
860 : {
861 : .url_prefix = "/tips",
862 : .method = MHD_HTTP_METHOD_POST,
863 : .handler = &TMH_private_post_tips,
864 : /* the body should be pretty small, allow 1 MB of upload
865 : to set a conservative bound for sane wallets */
866 : .max_upload = 1024 * 1024
867 : },
868 : /* GET /tips: */
869 : {
870 : .url_prefix = "/tips",
871 : .allow_deleted_instance = true,
872 : .method = MHD_HTTP_METHOD_GET,
873 : .handler = &TMH_private_get_tips
874 : },
875 : /* GET /tips/$ID: */
876 : {
877 : .url_prefix = "/tips/",
878 : .method = MHD_HTTP_METHOD_GET,
879 : .allow_deleted_instance = true,
880 : .have_id_segment = true,
881 : .handler = &TMH_private_get_tips_ID
882 : },
883 : /* GET /reserves: */
884 : {
885 : .url_prefix = "/reserves",
886 : .allow_deleted_instance = true,
887 : .method = MHD_HTTP_METHOD_GET,
888 : .handler = &TMH_private_get_reserves
889 : },
890 : /* GET /reserves/$ID: */
891 : {
892 : .url_prefix = "/reserves/",
893 : .allow_deleted_instance = true,
894 : .have_id_segment = true,
895 : .method = MHD_HTTP_METHOD_GET,
896 : .handler = &TMH_private_get_reserves_ID
897 : },
898 : /* POST /transfers: */
899 : {
900 : .url_prefix = "/transfers",
901 : .method = MHD_HTTP_METHOD_POST,
902 : .allow_deleted_instance = true,
903 : .handler = &TMH_private_post_transfers,
904 : /* the body should be pretty small, allow 1 MB of upload
905 : to set a conservative bound for sane wallets */
906 : .max_upload = 1024 * 1024
907 : },
908 : /* DELETE /transfers/$ID: */
909 : {
910 : .url_prefix = "/transfers/",
911 : .method = MHD_HTTP_METHOD_DELETE,
912 : .allow_deleted_instance = true,
913 : .handler = &TMH_private_delete_transfers_ID,
914 : .have_id_segment = true,
915 : /* the body should be pretty small, allow 1 MB of upload
916 : to set a conservative bound for sane wallets */
917 : .max_upload = 1024 * 1024
918 : },
919 : /* GET /transfers: */
920 : {
921 : .url_prefix = "/transfers",
922 : .method = MHD_HTTP_METHOD_GET,
923 : .allow_deleted_instance = true,
924 : .handler = &TMH_private_get_transfers
925 : },
926 : {
927 : NULL
928 : }
929 : };
930 : static struct TMH_RequestHandler public_handlers[] = {
931 : {
932 : .url_prefix = "/",
933 : .method = MHD_HTTP_METHOD_GET,
934 : .mime_type = "text/html",
935 : .skip_instance = true,
936 : .handler = &TMH_return_spa,
937 : .response_code = MHD_HTTP_OK
938 : },
939 : {
940 : .url_prefix = "/agpl",
941 : .method = MHD_HTTP_METHOD_GET,
942 : .skip_instance = true,
943 : .handler = &TMH_MHD_handler_agpl_redirect
944 : },
945 : {
946 : .url_prefix = "/config",
947 : .method = MHD_HTTP_METHOD_GET,
948 : .skip_instance = true,
949 : .default_only = true,
950 : .handler = &MH_handler_config
951 : },
952 : /* Also serve the same /config per instance */
953 : {
954 : .url_prefix = "/config",
955 : .method = MHD_HTTP_METHOD_GET,
956 : .skip_instance = false,
957 : .allow_deleted_instance = true,
958 : .handler = &MH_handler_config
959 : },
960 : /* POST /orders/$ID/abort: */
961 : {
962 : .url_prefix = "/orders/",
963 : .have_id_segment = true,
964 : .url_suffix = "abort",
965 : .method = MHD_HTTP_METHOD_POST,
966 : .handler = &TMH_post_orders_ID_abort,
967 : /* wallet may give us many coins to sign, allow 1 MB of upload
968 : to set a conservative bound for sane wallets */
969 : .max_upload = 1024 * 1024
970 : },
971 : /* POST /orders/$ID/claim: */
972 : {
973 : .url_prefix = "/orders/",
974 : .have_id_segment = true,
975 : .url_suffix = "claim",
976 : .method = MHD_HTTP_METHOD_POST,
977 : .handler = &TMH_post_orders_ID_claim,
978 : /* the body should be pretty small, allow 1 MB of upload
979 : to set a conservative bound for sane wallets */
980 : .max_upload = 1024 * 1024
981 : },
982 : /* POST /orders/$ID/pay: */
983 : {
984 : .url_prefix = "/orders/",
985 : .have_id_segment = true,
986 : .url_suffix = "pay",
987 : .method = MHD_HTTP_METHOD_POST,
988 : .handler = &TMH_post_orders_ID_pay,
989 : /* wallet may give us many coins to sign, allow 1 MB of upload
990 : to set a conservative bound for sane wallets */
991 : .max_upload = 1024 * 1024
992 : },
993 : /* POST /orders/$ID/paid: */
994 : {
995 : .url_prefix = "/orders/",
996 : .have_id_segment = true,
997 : .allow_deleted_instance = true,
998 : .url_suffix = "paid",
999 : .method = MHD_HTTP_METHOD_POST,
1000 : .handler = &TMH_post_orders_ID_paid,
1001 : /* the body should be pretty small, allow 1 MB of upload
1002 : to set a conservative bound for sane wallets */
1003 : .max_upload = 1024 * 1024
1004 : },
1005 : /* POST /orders/$ID/refund: */
1006 : {
1007 : .url_prefix = "/orders/",
1008 : .have_id_segment = true,
1009 : .allow_deleted_instance = true,
1010 : .url_suffix = "refund",
1011 : .method = MHD_HTTP_METHOD_POST,
1012 : .handler = &TMH_post_orders_ID_refund,
1013 : /* the body should be pretty small, allow 1 MB of upload
1014 : to set a conservative bound for sane wallets */
1015 : .max_upload = 1024 * 1024
1016 : },
1017 : /* GET /orders/$ID: */
1018 : {
1019 : .url_prefix = "/orders/",
1020 : .method = MHD_HTTP_METHOD_GET,
1021 : .allow_deleted_instance = true,
1022 : .have_id_segment = true,
1023 : .handler = &TMH_get_orders_ID
1024 : },
1025 : /* GET /tips/$ID: */
1026 : {
1027 : .url_prefix = "/tips/",
1028 : .method = MHD_HTTP_METHOD_GET,
1029 : .allow_deleted_instance = true,
1030 : .have_id_segment = true,
1031 : .handler = &TMH_get_tips_ID
1032 : },
1033 : /* POST /tips/$ID/pickup: */
1034 : {
1035 : .url_prefix = "/tips/",
1036 : .method = MHD_HTTP_METHOD_POST,
1037 : .have_id_segment = true,
1038 : .allow_deleted_instance = true,
1039 : .url_suffix = "pickup",
1040 : .handler = &TMH_post_tips_ID_pickup,
1041 : /* wallet may give us many coins to sign, allow 1 MB of upload
1042 : to set a conservative bound for sane wallets */
1043 : .max_upload = 1024 * 1024
1044 : },
1045 : /* GET /static/ *: */
1046 : {
1047 : .url_prefix = "/static/",
1048 : .method = MHD_HTTP_METHOD_GET,
1049 : .have_id_segment = true,
1050 : .handler = &TMH_return_static
1051 : },
1052 : {
1053 : .url_prefix = "*",
1054 : .method = MHD_HTTP_METHOD_OPTIONS,
1055 : .handler = &handle_server_options
1056 : },
1057 : {
1058 : NULL
1059 : }
1060 : };
1061 71 : struct TMH_HandlerContext *hc = *con_cls;
1062 : struct TMH_RequestHandler *handlers;
1063 71 : bool use_default = false;
1064 :
1065 : (void) cls;
1066 : (void) version;
1067 71 : if (NULL != hc)
1068 : {
1069 : /* MHD calls us again for a request, for first call
1070 : see 'else' case below */
1071 33 : GNUNET_assert (NULL != hc->rh);
1072 33 : GNUNET_SCHEDULER_begin_async_scope (&hc->async_scope_id);
1073 33 : if ( (hc->has_body) &&
1074 16 : (NULL == hc->request_body) )
1075 : {
1076 16 : size_t mul = hc->rh->max_upload;
1077 : enum GNUNET_GenericReturnValue res;
1078 :
1079 16 : if (0 == mul)
1080 0 : mul = DEFAULT_MAX_UPLOAD_SIZE;
1081 16 : if ( (hc->total_upload + *upload_data_size < hc->total_upload) ||
1082 16 : (hc->total_upload + *upload_data_size > mul) )
1083 : {
1084 : /* Client exceeds upload limit. Should _usually_ be checked earlier
1085 : when we look at the MHD_HTTP_HEADER_CONTENT_LENGTH, alas with
1086 : chunked encoding an uploader MAY have omitted this, and thus
1087 : not permitted us to check on time. In this case, we just close
1088 : the connection once it exceeds our limit (instead of waiting
1089 : for the upload to complete and then fail). This could theoretically
1090 : cause some clients to retry, alas broken or malicious clients
1091 : are likely to retry anyway, so little we can do about it, and
1092 : failing earlier seems the best option here. */
1093 0 : GNUNET_break_op (0);
1094 0 : return MHD_NO;
1095 : }
1096 16 : hc->total_upload += *upload_data_size;
1097 16 : res = TALER_MHD_parse_post_json (connection,
1098 : &hc->json_parse_context,
1099 : upload_data,
1100 : upload_data_size,
1101 : &hc->request_body);
1102 16 : if (GNUNET_SYSERR == res)
1103 0 : return MHD_NO;
1104 : /* A error response was already generated */
1105 16 : if ( (GNUNET_NO == res) ||
1106 : /* or, need more data to accomplish parsing */
1107 16 : (NULL == hc->request_body) )
1108 8 : return MHD_YES; /* let MHD call us *again* */
1109 : }
1110 : /* Upload complete (if any), call handler to generate reply */
1111 25 : return hc->rh->handler (hc->rh,
1112 : connection,
1113 : hc);
1114 : }
1115 38 : hc = GNUNET_new (struct TMH_HandlerContext);
1116 38 : *con_cls = hc;
1117 38 : GNUNET_async_scope_fresh (&hc->async_scope_id);
1118 38 : GNUNET_SCHEDULER_begin_async_scope (&hc->async_scope_id);
1119 38 : hc->url = url;
1120 : {
1121 : const char *correlation_id;
1122 :
1123 38 : correlation_id = MHD_lookup_connection_value (connection,
1124 : MHD_HEADER_KIND,
1125 : "Taler-Correlation-Id");
1126 38 : if ( (NULL != correlation_id) &&
1127 : (GNUNET_YES !=
1128 0 : GNUNET_CURL_is_valid_scope_id (correlation_id)) )
1129 : {
1130 0 : GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
1131 : "Illegal incoming correlation ID\n");
1132 0 : correlation_id = NULL;
1133 : }
1134 38 : if (NULL != correlation_id)
1135 0 : GNUNET_log (GNUNET_ERROR_TYPE_INFO,
1136 : "Handling request for (%s) URL '%s', correlation_id=%s\n",
1137 : method,
1138 : url,
1139 : correlation_id);
1140 : else
1141 38 : GNUNET_log (GNUNET_ERROR_TYPE_INFO,
1142 : "Handling request (%s) for URL '%s'\n",
1143 : method,
1144 : url);
1145 : }
1146 :
1147 38 : if (0 == strcasecmp (method,
1148 : MHD_HTTP_METHOD_HEAD))
1149 0 : method = MHD_HTTP_METHOD_GET; /* MHD will deal with the rest */
1150 :
1151 : /* Find out the merchant backend instance for the request.
1152 : * If there is an instance, remove the instance specification
1153 : * from the beginning of the request URL. */
1154 : {
1155 38 : const char *instance_prefix = "/instances/";
1156 :
1157 38 : if (0 == strncmp (url,
1158 : instance_prefix,
1159 : strlen (instance_prefix)))
1160 : {
1161 : /* url starts with "/instances/" */
1162 10 : const char *istart = url + strlen (instance_prefix);
1163 10 : const char *slash = strchr (istart, '/');
1164 : char *instance_id;
1165 :
1166 10 : if (NULL == slash)
1167 0 : instance_id = GNUNET_strdup (istart);
1168 : else
1169 10 : instance_id = GNUNET_strndup (istart,
1170 : slash - istart);
1171 10 : hc->instance = TMH_lookup_instance (instance_id);
1172 10 : GNUNET_free (instance_id);
1173 10 : if (NULL == slash)
1174 0 : url = "";
1175 : else
1176 10 : url = slash;
1177 : }
1178 : else
1179 : {
1180 : /* use 'default' */
1181 28 : use_default = true;
1182 28 : hc->instance = TMH_lookup_instance (NULL);
1183 28 : if ( (NULL != TMH_default_auth) &&
1184 0 : (NULL != hc->instance) )
1185 : {
1186 : /* Override default instance access control */
1187 0 : GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
1188 : "Overriding access control\n");
1189 0 : TMH_compute_auth (TMH_default_auth,
1190 0 : &hc->instance->auth.auth_salt,
1191 0 : &hc->instance->auth.auth_hash);
1192 0 : hc->instance->auth_override = true;
1193 0 : GNUNET_free (TMH_default_auth);
1194 : }
1195 : }
1196 38 : if (NULL != hc->instance)
1197 : {
1198 26 : GNUNET_assert (hc->instance->rc < UINT_MAX);
1199 26 : hc->instance->rc++;
1200 : }
1201 : }
1202 :
1203 : {
1204 38 : const char *management_prefix = "/management/";
1205 38 : const char *private_prefix = "/private/";
1206 :
1207 38 : if ( (0 == strncmp (url,
1208 : management_prefix,
1209 : strlen (management_prefix))) )
1210 : {
1211 17 : handlers = management_handlers;
1212 17 : url += strlen (management_prefix) - 1;
1213 : }
1214 21 : else if ( (0 == strncmp (url,
1215 : private_prefix,
1216 10 : strlen (private_prefix))) ||
1217 10 : (0 == strcmp (url,
1218 : "/private")) )
1219 : {
1220 11 : handlers = private_handlers;
1221 11 : url += strlen (private_prefix) - 1;
1222 : }
1223 : else
1224 : {
1225 10 : handlers = public_handlers;
1226 : }
1227 : }
1228 :
1229 38 : if (0 == strcmp (url,
1230 : ""))
1231 0 : url = "/"; /* code below does not like empty string */
1232 :
1233 : {
1234 : /* Matching URL found, but maybe method doesn't match */
1235 : size_t prefix_strlen; /* i.e. 8 for "/orders/", or 7 for "/config" */
1236 38 : const char *infix_url = NULL; /* i.e. "$ORDER_ID", no '/'-es */
1237 38 : size_t infix_strlen = 0; /* number of characters in infix_url */
1238 38 : const char *suffix_url = NULL; /* i.e. "refund", excludes '/' at the beginning */
1239 38 : size_t suffix_strlen = 0; /* number of characters in suffix_url */
1240 :
1241 : /* parse the URL into the three different components */
1242 : {
1243 : const char *slash;
1244 :
1245 38 : slash = strchr (&url[1], '/');
1246 38 : if (NULL == slash)
1247 : {
1248 : /* the prefix was everything */
1249 30 : prefix_strlen = strlen (url);
1250 : }
1251 : else
1252 : {
1253 8 : prefix_strlen = slash - url + 1; /* includes both '/'-es if present! */
1254 8 : infix_url = slash + 1;
1255 8 : slash = strchr (&infix_url[1], '/');
1256 8 : if (NULL == slash)
1257 : {
1258 : /* the infix was the rest */
1259 5 : infix_strlen = strlen (infix_url);
1260 : }
1261 : else
1262 : {
1263 3 : infix_strlen = slash - infix_url; /* excludes both '/'-es */
1264 3 : suffix_url = slash + 1; /* skip the '/' */
1265 3 : suffix_strlen = strlen (suffix_url);
1266 : }
1267 8 : hc->infix = GNUNET_strndup (infix_url,
1268 : infix_strlen);
1269 : }
1270 : }
1271 :
1272 : /* find matching handler */
1273 : {
1274 38 : bool url_found = false;
1275 :
1276 302 : for (unsigned int i = 0; NULL != handlers[i].url_prefix; i++)
1277 : {
1278 295 : struct TMH_RequestHandler *rh = &handlers[i];
1279 :
1280 295 : if (rh->default_only && (! use_default))
1281 4 : continue;
1282 291 : if (! prefix_match (rh,
1283 : url,
1284 : prefix_strlen,
1285 : infix_url,
1286 : infix_strlen,
1287 : suffix_url,
1288 : suffix_strlen))
1289 249 : continue;
1290 42 : url_found = true;
1291 42 : if (0 == strcasecmp (method,
1292 : MHD_HTTP_METHOD_OPTIONS))
1293 : {
1294 0 : return TALER_MHD_reply_cors_preflight (connection);
1295 : }
1296 42 : if ( (rh->method != NULL) &&
1297 42 : (0 != strcasecmp (method,
1298 : rh->method)) )
1299 11 : continue;
1300 31 : hc->rh = rh;
1301 31 : break;
1302 : }
1303 : /* Handle HTTP 405: METHOD NOT ALLOWED case */
1304 38 : if ( (NULL == hc->rh) &&
1305 : (url_found) )
1306 : {
1307 : struct MHD_Response *reply;
1308 : MHD_RESULT ret;
1309 0 : char *allowed = NULL;
1310 :
1311 0 : GNUNET_break_op (0);
1312 : /* compute 'Allowed:' header (required by HTTP spec for 405 replies) */
1313 0 : for (unsigned int i = 0; NULL != handlers[i].url_prefix; i++)
1314 : {
1315 0 : struct TMH_RequestHandler *rh = &handlers[i];
1316 :
1317 0 : if (rh->default_only && (! use_default))
1318 0 : continue;
1319 0 : if (! prefix_match (rh,
1320 : url,
1321 : prefix_strlen,
1322 : infix_url,
1323 : infix_strlen,
1324 : suffix_url,
1325 : suffix_strlen))
1326 0 : continue;
1327 0 : if (NULL == allowed)
1328 : {
1329 0 : allowed = GNUNET_strdup (rh->method);
1330 : }
1331 : else
1332 : {
1333 : char *tmp;
1334 :
1335 0 : GNUNET_asprintf (&tmp,
1336 : "%s, %s",
1337 : allowed,
1338 : rh->method);
1339 0 : GNUNET_free (allowed);
1340 0 : allowed = tmp;
1341 : }
1342 0 : if (0 == strcasecmp (rh->method,
1343 : MHD_HTTP_METHOD_GET))
1344 : {
1345 : char *tmp;
1346 :
1347 0 : GNUNET_asprintf (&tmp,
1348 : "%s, %s",
1349 : allowed,
1350 : MHD_HTTP_METHOD_HEAD);
1351 0 : GNUNET_free (allowed);
1352 0 : allowed = tmp;
1353 : }
1354 : }
1355 0 : reply = TALER_MHD_make_error (TALER_EC_GENERIC_METHOD_INVALID,
1356 : method);
1357 0 : GNUNET_break (MHD_YES ==
1358 : MHD_add_response_header (reply,
1359 : MHD_HTTP_HEADER_ALLOW,
1360 : allowed));
1361 0 : GNUNET_free (allowed);
1362 0 : ret = MHD_queue_response (connection,
1363 : MHD_HTTP_METHOD_NOT_ALLOWED,
1364 : reply);
1365 0 : MHD_destroy_response (reply);
1366 0 : return ret;
1367 : }
1368 38 : if (NULL == hc->rh)
1369 7 : return TALER_MHD_reply_with_error (connection,
1370 : MHD_HTTP_NOT_FOUND,
1371 : TALER_EC_GENERIC_ENDPOINT_UNKNOWN,
1372 : hc->url);
1373 : }
1374 : }
1375 : /* At this point, we must have found a handler */
1376 31 : GNUNET_assert (NULL != hc->rh);
1377 :
1378 : /* If an instance should be there, check one exists */
1379 31 : if ( (NULL == hc->instance) &&
1380 11 : (! hc->rh->skip_instance) )
1381 : {
1382 1 : return TALER_MHD_reply_with_error (connection,
1383 : MHD_HTTP_NOT_FOUND,
1384 : TALER_EC_MERCHANT_GENERIC_INSTANCE_UNKNOWN,
1385 1 : hc->infix);
1386 : }
1387 :
1388 : /* Access control for non-public handlers */
1389 30 : if (public_handlers != handlers)
1390 : {
1391 : const char *auth;
1392 : bool auth_ok;
1393 22 : bool auth_malformed = false;
1394 :
1395 : /* PATCHing an instance can alternatively be checked against
1396 : the default instance */
1397 22 : auth = MHD_lookup_connection_value (connection,
1398 : MHD_HEADER_KIND,
1399 : MHD_HTTP_HEADER_AUTHORIZATION);
1400 22 : if (NULL != auth)
1401 : {
1402 : /* We _only_ complain about malformed auth headers if
1403 : authorization was truly required (#6737). This helps
1404 : in case authorization was disabled in the backend
1405 : because some reverse proxy is already doing it, and
1406 : then that reverse proxy may forward malformed auth
1407 : headers to the backend. */
1408 10 : extract_token (&auth);
1409 10 : if (NULL == auth)
1410 1 : auth_malformed = true;
1411 10 : hc->auth_token = auth;
1412 : }
1413 :
1414 : /* If we have zero configured instances (not even ones that have been
1415 : purged) AND no override credentials, THEN we accept anything (no access
1416 : control), as we then also have no data to protect. */
1417 22 : auth_ok = ( (0 ==
1418 27 : GNUNET_CONTAINER_multihashmap_size (TMH_by_id_map)) &&
1419 5 : (NULL == TMH_default_auth) );
1420 : /* Check against selected instance, if we have one */
1421 22 : if (NULL != hc->instance)
1422 17 : auth_ok |= (GNUNET_OK ==
1423 17 : TMH_check_auth (auth,
1424 17 : &hc->instance->auth.auth_salt,
1425 17 : &hc->instance->auth.auth_hash));
1426 : else /* Are the credentials provided OK for CLI override? */
1427 5 : auth_ok |= ( (use_default) &&
1428 5 : (NULL != TMH_default_auth) &&
1429 0 : (NULL != auth) &&
1430 10 : (! auth_malformed) &&
1431 0 : (0 == strcmp (auth,
1432 : TMH_default_auth)) );
1433 22 : if (! auth_ok)
1434 : {
1435 5 : if (auth_malformed)
1436 5 : return TALER_MHD_reply_with_error (connection,
1437 : MHD_HTTP_UNAUTHORIZED,
1438 : TALER_EC_GENERIC_PARAMETER_MALFORMED,
1439 : "'" RFC_8959_PREFIX
1440 : "' prefix or 'Bearer' missing in 'Authorization' header");
1441 5 : return TALER_MHD_reply_with_error (connection,
1442 : MHD_HTTP_UNAUTHORIZED,
1443 : TALER_EC_MERCHANT_GENERIC_UNAUTHORIZED,
1444 : "Check 'Authorization' header");
1445 : }
1446 : } /* if (use_private) */
1447 :
1448 :
1449 25 : if ( (NULL == hc->instance) &&
1450 10 : (! hc->rh->skip_instance) )
1451 0 : return TALER_MHD_reply_with_error (connection,
1452 : MHD_HTTP_NOT_FOUND,
1453 : TALER_EC_MERCHANT_GENERIC_INSTANCE_UNKNOWN,
1454 : url);
1455 25 : if ( (NULL != hc->instance) && /* make static analysis happy */
1456 15 : (! hc->rh->skip_instance) &&
1457 5 : (hc->instance->deleted) &&
1458 0 : (! hc->rh->allow_deleted_instance) )
1459 0 : return TALER_MHD_reply_with_error (connection,
1460 : MHD_HTTP_NOT_FOUND,
1461 : TALER_EC_MERCHANT_GENERIC_INSTANCE_DELETED,
1462 0 : hc->instance->settings.id);
1463 : /* parse request body */
1464 50 : hc->has_body = ( (0 == strcasecmp (method,
1465 42 : MHD_HTTP_METHOD_POST)) ||
1466 : /* PUT is not yet used */
1467 17 : (0 == strcasecmp (method,
1468 : MHD_HTTP_METHOD_PATCH)) );
1469 25 : if (hc->has_body)
1470 : {
1471 : const char *cl;
1472 :
1473 : /* Maybe check for maximum upload size
1474 : and refuse requests if they are just too big. */
1475 8 : cl = MHD_lookup_connection_value (connection,
1476 : MHD_HEADER_KIND,
1477 : MHD_HTTP_HEADER_CONTENT_LENGTH);
1478 8 : if (NULL != cl)
1479 : {
1480 : unsigned long long cv;
1481 8 : size_t mul = hc->rh->max_upload;
1482 : char dummy;
1483 :
1484 8 : if (0 == mul)
1485 0 : mul = DEFAULT_MAX_UPLOAD_SIZE;
1486 8 : if (1 != sscanf (cl,
1487 : "%llu%c",
1488 : &cv,
1489 : &dummy))
1490 : {
1491 : /* Not valid HTTP request, just close connection. */
1492 0 : GNUNET_break_op (0);
1493 0 : return MHD_NO;
1494 : }
1495 8 : if (cv > mul)
1496 : {
1497 0 : GNUNET_break_op (0);
1498 0 : return TALER_MHD_reply_with_error (connection,
1499 : MHD_HTTP_PAYLOAD_TOO_LARGE,
1500 : TALER_EC_GENERIC_UPLOAD_EXCEEDS_LIMIT,
1501 : cl);
1502 : }
1503 : }
1504 8 : GNUNET_break (NULL == hc->request_body); /* can't have it already */
1505 : }
1506 25 : return MHD_YES; /* wait for MHD to call us again */
1507 : }
1508 :
1509 :
1510 : /**
1511 : * Function called during startup to add all known instances to our
1512 : * hash map in memory for faster lookups when we receive requests.
1513 : *
1514 : * @param cls closure, NULL, unused
1515 : * @param merchant_pub public key of the instance
1516 : * @param merchant_priv private key of the instance, NULL if not available
1517 : * @param is detailed configuration settings for the instance
1518 : * @param ias authentication settings for the instance
1519 : * @param accounts_length length of the @a accounts array
1520 : * @param accounts list of accounts of the merchant
1521 : */
1522 : static void
1523 8 : add_instance_cb (void *cls,
1524 : const struct TALER_MerchantPublicKeyP *merchant_pub,
1525 : const struct TALER_MerchantPrivateKeyP *merchant_priv,
1526 : const struct TALER_MERCHANTDB_InstanceSettings *is,
1527 : const struct TALER_MERCHANTDB_InstanceAuthSettings *ias,
1528 : unsigned int accounts_length,
1529 : const struct TALER_MERCHANTDB_AccountDetails accounts[])
1530 : {
1531 : struct TMH_MerchantInstance *mi;
1532 :
1533 : (void) cls;
1534 8 : mi = TMH_lookup_instance (is->id);
1535 8 : if (NULL != mi)
1536 : {
1537 : /* (outdated) entry exists, remove old entry */
1538 0 : (void) TMH_instance_free_cb (NULL,
1539 0 : &mi->h_instance,
1540 : mi);
1541 : }
1542 :
1543 8 : mi = GNUNET_new (struct TMH_MerchantInstance);
1544 8 : mi->settings = *is;
1545 8 : mi->auth = *ias;
1546 8 : mi->settings.id = GNUNET_strdup (mi->settings.id);
1547 8 : mi->settings.name = GNUNET_strdup (mi->settings.name);
1548 8 : if (NULL != mi->settings.email)
1549 0 : mi->settings.email = GNUNET_strdup (mi->settings.email);
1550 8 : if (NULL != mi->settings.website)
1551 0 : mi->settings.website = GNUNET_strdup (mi->settings.website);
1552 8 : if (NULL != mi->settings.logo)
1553 0 : mi->settings.logo = GNUNET_strdup (mi->settings.logo);
1554 8 : mi->settings.address = json_incref (mi->settings.address);
1555 8 : mi->settings.jurisdiction = json_incref (mi->settings.jurisdiction);
1556 8 : if (NULL != merchant_priv)
1557 8 : mi->merchant_priv = *merchant_priv;
1558 : else
1559 0 : mi->deleted = true;
1560 8 : mi->merchant_pub = *merchant_pub;
1561 16 : for (unsigned int i = 0; i<accounts_length; i++)
1562 : {
1563 8 : const struct TALER_MERCHANTDB_AccountDetails *acc = &accounts[i];
1564 : struct TMH_WireMethod *wm;
1565 :
1566 8 : wm = GNUNET_new (struct TMH_WireMethod);
1567 8 : wm->h_wire = acc->h_wire;
1568 8 : wm->payto_uri = GNUNET_strdup (acc->payto_uri);
1569 8 : wm->wire_salt = acc->salt;
1570 8 : wm->wire_method = TALER_payto_get_method (acc->payto_uri);
1571 8 : wm->active = acc->active;
1572 8 : GNUNET_CONTAINER_DLL_insert (mi->wm_head,
1573 : mi->wm_tail,
1574 : wm);
1575 : }
1576 8 : GNUNET_assert (GNUNET_OK ==
1577 : TMH_add_instance (mi));
1578 8 : }
1579 :
1580 :
1581 : /**
1582 : * Trigger (re)loading of instance settings from DB.
1583 : *
1584 : * @param cls NULL
1585 : * @param extra ID of the instance that changed, NULL
1586 : * to load all instances (will not handle purges!)
1587 : * @param extra_len number of bytes in @a extra
1588 : */
1589 : static void
1590 11 : load_instances (void *cls,
1591 : const void *extra,
1592 : size_t extra_len)
1593 : {
1594 : enum GNUNET_DB_QueryStatus qs;
1595 11 : const char *id = extra;
1596 :
1597 : (void) cls;
1598 : (void) extra;
1599 : (void) extra_len;
1600 11 : if ( (NULL != extra) &&
1601 8 : ( (0 == extra_len) ||
1602 8 : ('\0' != id[extra_len - 1]) ) )
1603 : {
1604 0 : GNUNET_break (0 == extra_len);
1605 0 : extra = NULL;
1606 : }
1607 11 : GNUNET_log (GNUNET_ERROR_TYPE_INFO,
1608 : "Received instance settings notification: reload `%s'\n",
1609 : id);
1610 11 : if (NULL == extra)
1611 : {
1612 3 : qs = TMH_db->lookup_instances (TMH_db->cls,
1613 : false,
1614 : &add_instance_cb,
1615 : NULL);
1616 : }
1617 : else
1618 : {
1619 : struct TMH_MerchantInstance *mi;
1620 :
1621 : /* This must be done here to handle instance
1622 : purging, as for purged instances, the DB
1623 : lookup below will otherwise do nothing */
1624 8 : mi = TMH_lookup_instance (id);
1625 8 : if (NULL != mi)
1626 : {
1627 8 : (void) TMH_instance_free_cb (NULL,
1628 8 : &mi->h_instance,
1629 : mi);
1630 : }
1631 8 : qs = TMH_db->lookup_instance (TMH_db->cls,
1632 : id,
1633 : false,
1634 : &add_instance_cb,
1635 : NULL);
1636 : }
1637 11 : if (0 > qs)
1638 : {
1639 0 : GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
1640 : "Failed initialization. Check database setup.\n");
1641 0 : result = EXIT_FAILURE;
1642 0 : GNUNET_SCHEDULER_shutdown ();
1643 0 : return;
1644 : }
1645 : }
1646 :
1647 :
1648 : /**
1649 : * A transaction modified an instance setting
1650 : * (or created/deleted/purged one). Notify all
1651 : * backends about the change.
1652 : *
1653 : * @param id ID of the instance that changed
1654 : */
1655 : void
1656 8 : TMH_reload_instances (const char *id)
1657 : {
1658 8 : struct GNUNET_DB_EventHeaderP es = {
1659 8 : .size = ntohs (sizeof (es)),
1660 8 : .type = ntohs (TALER_DBEVENT_MERCHANT_INSTANCE_SETTINGS)
1661 : };
1662 :
1663 8 : GNUNET_log (GNUNET_ERROR_TYPE_INFO,
1664 : "Generating instance settings notification: reload `%s'\n",
1665 : id);
1666 16 : TMH_db->event_notify (TMH_db->cls,
1667 : &es,
1668 : id,
1669 : (NULL == id)
1670 : ? 0
1671 8 : : strlen (id) + 1);
1672 8 : }
1673 :
1674 :
1675 : /**
1676 : * Main function that will be run by the scheduler.
1677 : *
1678 : * @param cls closure
1679 : * @param args remaining command-line arguments
1680 : * @param cfgfile name of the configuration file used (for saving, can be
1681 : * NULL!)
1682 : * @param config configuration
1683 : */
1684 : static void
1685 3 : run (void *cls,
1686 : char *const *args,
1687 : const char *cfgfile,
1688 : const struct GNUNET_CONFIGURATION_Handle *config)
1689 : {
1690 : int fh;
1691 : enum TALER_MHD_GlobalOptions go;
1692 : int elen;
1693 : int alen;
1694 : const char *tok;
1695 :
1696 : (void) cls;
1697 : (void) args;
1698 : (void) cfgfile;
1699 3 : tok = getenv ("TALER_MERCHANT_TOKEN");
1700 3 : if ( (NULL != tok) &&
1701 0 : (NULL == TMH_default_auth) )
1702 0 : TMH_default_auth = GNUNET_strdup (tok);
1703 3 : if ( (NULL != TMH_default_auth) &&
1704 0 : (0 != strncmp (TMH_default_auth,
1705 : RFC_8959_PREFIX,
1706 : strlen (RFC_8959_PREFIX))) )
1707 : {
1708 : char *tmp;
1709 :
1710 0 : GNUNET_asprintf (&tmp,
1711 : "%s%s",
1712 : RFC_8959_PREFIX,
1713 : TMH_default_auth);
1714 0 : GNUNET_free (TMH_default_auth);
1715 0 : TMH_default_auth = tmp;
1716 : }
1717 3 : cfg = config;
1718 3 : GNUNET_log (GNUNET_ERROR_TYPE_INFO,
1719 : "Starting taler-merchant-httpd\n");
1720 3 : go = TALER_MHD_GO_NONE;
1721 3 : if (merchant_connection_close)
1722 0 : go |= TALER_MHD_GO_FORCE_CONNECTION_CLOSE;
1723 3 : TALER_MHD_setup (go);
1724 :
1725 3 : result = GNUNET_SYSERR;
1726 3 : GNUNET_SCHEDULER_add_shutdown (&do_shutdown,
1727 : NULL);
1728 3 : if (GNUNET_OK !=
1729 3 : TALER_config_get_currency (cfg,
1730 : &TMH_currency))
1731 : {
1732 0 : GNUNET_SCHEDULER_shutdown ();
1733 0 : return;
1734 : }
1735 3 : if (GNUNET_OK !=
1736 3 : GNUNET_CONFIGURATION_get_value_time (cfg,
1737 : "merchant",
1738 : "LEGAL_PRESERVATION",
1739 : &TMH_legal_expiration))
1740 : {
1741 0 : GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR,
1742 : "merchant",
1743 : "LEGAL_PRESERVATION");
1744 0 : GNUNET_SCHEDULER_shutdown ();
1745 0 : return;
1746 : }
1747 3 : if (GNUNET_YES ==
1748 3 : GNUNET_CONFIGURATION_get_value_yesno (cfg,
1749 : "merchant",
1750 : "FORCE_AUDIT"))
1751 0 : TMH_force_audit = GNUNET_YES;
1752 3 : TMH_templating_init ();
1753 3 : if (GNUNET_OK !=
1754 3 : TMH_spa_init ())
1755 : {
1756 0 : GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
1757 : "Failed to load single page app\n");
1758 0 : GNUNET_SCHEDULER_shutdown ();
1759 0 : return;
1760 : }
1761 3 : TMH_statics_init ();
1762 3 : elen = TMH_EXCHANGES_init (config);
1763 3 : if (GNUNET_SYSERR == elen)
1764 : {
1765 0 : GNUNET_SCHEDULER_shutdown ();
1766 0 : return;
1767 : }
1768 3 : alen = TMH_AUDITORS_init (config);
1769 3 : if (GNUNET_SYSERR == alen)
1770 : {
1771 0 : GNUNET_SCHEDULER_shutdown ();
1772 0 : return;
1773 : }
1774 3 : if (0 == elen + alen)
1775 : {
1776 0 : GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
1777 : "Fatal: no trusted exchanges and no trusted auditors configured. Exiting.\n");
1778 0 : GNUNET_SCHEDULER_shutdown ();
1779 0 : return;
1780 : }
1781 3 : if (NULL ==
1782 3 : (TMH_by_id_map = GNUNET_CONTAINER_multihashmap_create (4,
1783 : GNUNET_YES)))
1784 : {
1785 0 : GNUNET_SCHEDULER_shutdown ();
1786 0 : return;
1787 : }
1788 3 : if (NULL ==
1789 3 : (TMH_db = TALER_MERCHANTDB_plugin_load (cfg)))
1790 : {
1791 0 : GNUNET_SCHEDULER_shutdown ();
1792 0 : return;
1793 : }
1794 3 : if (GNUNET_OK !=
1795 3 : TMH_db->connect (TMH_db->cls))
1796 : {
1797 0 : GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
1798 : "Failed to initialize database connection\n");
1799 0 : GNUNET_SCHEDULER_shutdown ();
1800 0 : return;
1801 : }
1802 : {
1803 3 : struct GNUNET_DB_EventHeaderP es = {
1804 3 : .size = ntohs (sizeof (es)),
1805 3 : .type = ntohs (TALER_DBEVENT_MERCHANT_INSTANCE_SETTINGS)
1806 : };
1807 :
1808 6 : instance_eh = TMH_db->event_listen (TMH_db->cls,
1809 : &es,
1810 3 : GNUNET_TIME_UNIT_FOREVER_REL,
1811 : &load_instances,
1812 : NULL);
1813 : }
1814 3 : load_instances (NULL,
1815 : NULL,
1816 : 0);
1817 : /* start watching reserves */
1818 3 : TMH_RESERVES_init ();
1819 3 : fh = TALER_MHD_bind (cfg,
1820 : "merchant",
1821 : &port);
1822 3 : if ( (0 == port) &&
1823 : (-1 == fh) )
1824 : {
1825 0 : GNUNET_SCHEDULER_shutdown ();
1826 0 : return;
1827 : }
1828 :
1829 : {
1830 : struct MHD_Daemon *mhd;
1831 :
1832 3 : mhd = MHD_start_daemon (MHD_USE_SUSPEND_RESUME | MHD_USE_DUAL_STACK
1833 : | MHD_USE_AUTO,
1834 : port,
1835 : NULL, NULL,
1836 : &url_handler, NULL,
1837 : MHD_OPTION_LISTEN_SOCKET, fh,
1838 : MHD_OPTION_NOTIFY_COMPLETED,
1839 : &handle_mhd_completion_callback, NULL,
1840 : MHD_OPTION_CONNECTION_TIMEOUT,
1841 : (unsigned int) 10 /* 10s */,
1842 : MHD_OPTION_END);
1843 3 : if (NULL == mhd)
1844 : {
1845 0 : GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
1846 : "Failed to launch HTTP service. Is the port in use?\n");
1847 0 : GNUNET_SCHEDULER_shutdown ();
1848 0 : return;
1849 : }
1850 3 : result = GNUNET_OK;
1851 3 : TALER_MHD_daemon_start (mhd);
1852 : }
1853 : }
1854 :
1855 :
1856 : /**
1857 : * The main function of the serve tool
1858 : *
1859 : * @param argc number of arguments from the command line
1860 : * @param argv command line arguments
1861 : * @return 0 ok, non-zero on error
1862 : */
1863 : int
1864 15 : main (int argc,
1865 : char *const *argv)
1866 : {
1867 : enum GNUNET_GenericReturnValue res;
1868 15 : struct GNUNET_GETOPT_CommandLineOption options[] = {
1869 15 : GNUNET_GETOPT_option_flag ('C',
1870 : "connection-close",
1871 : "force HTTP connections to be closed after each request",
1872 : &merchant_connection_close),
1873 15 : GNUNET_GETOPT_option_timetravel ('T',
1874 : "timetravel"),
1875 15 : GNUNET_GETOPT_option_string ('a',
1876 : "auth",
1877 : "TOKEN",
1878 : "use TOKEN to initially authenticate access to the default instance (you can also set the TALER_MERCHANT_TOKEN environment variable instead)",
1879 : &TMH_default_auth),
1880 15 : GNUNET_GETOPT_option_version (PACKAGE_VERSION "-" VCS_VERSION),
1881 : GNUNET_GETOPT_OPTION_END
1882 : };
1883 :
1884 15 : TALER_OS_init ();
1885 15 : res = GNUNET_PROGRAM_run (argc, argv,
1886 : "taler-merchant-httpd",
1887 : "Taler merchant's HTTP backend interface",
1888 : options,
1889 : &run, NULL);
1890 15 : if (GNUNET_SYSERR == res)
1891 0 : return EXIT_INVALIDARGUMENT;
1892 15 : if (GNUNET_NO == res)
1893 12 : return EXIT_SUCCESS;
1894 3 : return (GNUNET_OK == result) ? EXIT_SUCCESS : 1;
1895 : }
|