Line data Source code
1 : /*
2 : This file is part of TALER
3 : Copyright (C) 2023, 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 Affero General Public License for more details.
12 :
13 : You should have received a copy of the GNU Affero General Public License along with
14 : TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
15 : */
16 : /**
17 : * @file src/backend/taler-merchant-wirewatch.c
18 : * @brief Process that imports information about incoming bank transfers into the merchant backend
19 : * @author Christian Grothoff
20 : */
21 : #include "platform.h"
22 : #include "microhttpd.h"
23 : #include <gnunet/gnunet_util_lib.h>
24 : #include <jansson.h>
25 : #include <pthread.h>
26 : #include <taler/taler_dbevents.h>
27 : #include "taler/taler_merchant_util.h"
28 : #include "taler/taler_merchant_bank_lib.h"
29 : #include "merchantdb_lib.h"
30 : #include "merchantdb_lib.h"
31 : #include "merchant-database/insert_transfer.h"
32 : #include "merchant-database/set_instance.h"
33 : #include "merchant-database/iterate_wirewatch_accounts.h"
34 : #include "merchant-database/update_account_last_bank_serial.h"
35 : #include "merchant-database/event_listen.h"
36 :
37 :
38 : /**
39 : * Timeout for the bank interaction. Rather long as we should do long-polling
40 : * and do not want to wake up too often.
41 : */
42 : #define BANK_TIMEOUT GNUNET_TIME_relative_multiply (GNUNET_TIME_UNIT_MINUTES, \
43 : 5)
44 :
45 :
46 : /**
47 : * Information about a watch job.
48 : */
49 : struct Watch
50 : {
51 : /**
52 : * Kept in a DLL.
53 : */
54 : struct Watch *next;
55 :
56 : /**
57 : * Kept in a DLL.
58 : */
59 : struct Watch *prev;
60 :
61 : /**
62 : * Next task to run, if any.
63 : */
64 : struct GNUNET_SCHEDULER_Task *task;
65 :
66 : /**
67 : * Dynamically adjusted long polling time-out.
68 : */
69 : struct GNUNET_TIME_Relative bank_timeout;
70 :
71 : /**
72 : * For which instance are we importing bank transfers?
73 : */
74 : char *instance_id;
75 :
76 : /**
77 : * For which account are we importing bank transfers?
78 : */
79 : struct TALER_FullPayto payto_uri;
80 :
81 : /**
82 : * Bank history request.
83 : */
84 : struct TALER_MERCHANT_BANK_CreditHistoryHandle *hh;
85 :
86 : /**
87 : * Start row for the bank interaction. Exclusive.
88 : */
89 : uint64_t start_row;
90 :
91 : /**
92 : * Artificial delay to use between API calls. Used to
93 : * throttle on failures.
94 : */
95 : struct GNUNET_TIME_Relative delay;
96 :
97 : /**
98 : * When did we start our last HTTP request?
99 : */
100 : struct GNUNET_TIME_Absolute start_time;
101 :
102 : /**
103 : * How long should long-polling take at least?
104 : */
105 : struct GNUNET_TIME_Absolute long_poll_timeout;
106 :
107 : /**
108 : * Login data for the bank.
109 : */
110 : struct TALER_MERCHANT_BANK_AuthenticationData ad;
111 :
112 : /**
113 : * Set to true if we found a transaction in the last iteration.
114 : */
115 : bool found;
116 :
117 : };
118 :
119 :
120 : /**
121 : * Head of active watches.
122 : */
123 : static struct Watch *w_head;
124 :
125 : /**
126 : * Tail of active watches.
127 : */
128 : static struct Watch *w_tail;
129 :
130 : /**
131 : * The merchant's configuration.
132 : */
133 : static const struct GNUNET_CONFIGURATION_Handle *cfg;
134 :
135 : /**
136 : * Our database connection.
137 : */
138 : static struct TALER_MERCHANTDB_PostgresContext *pg;
139 :
140 : /**
141 : * Handle to the context for interacting with the bank.
142 : */
143 : static struct GNUNET_CURL_Context *ctx;
144 :
145 : /**
146 : * Scheduler context for running the @e ctx.
147 : */
148 : static struct GNUNET_CURL_RescheduleContext *rc;
149 :
150 : /**
151 : * Event handler to learn that the configuration changed
152 : * and we should shutdown (to be restarted).
153 : */
154 : static struct GNUNET_DB_EventHandler *eh;
155 :
156 : /**
157 : * Value to return from main(). 0 on success, non-zero on errors.
158 : */
159 : static int global_ret;
160 :
161 : /**
162 : * How many transactions should we fetch at most per batch?
163 : */
164 : static unsigned int batch_size = 32;
165 :
166 : /**
167 : * #GNUNET_YES if we are in test mode and should exit when idle.
168 : */
169 : static int test_mode;
170 :
171 : /**
172 : * #GNUNET_YES if we are in persistent mode and do
173 : * not exit on #config_changed.
174 : */
175 : static int persist_mode;
176 :
177 : /**
178 : * Set to true if we are shutting down due to a
179 : * configuration change.
180 : */
181 : static bool config_changed_flag;
182 :
183 : /**
184 : * Save progress in DB.
185 : */
186 : static void
187 8 : save (struct Watch *w)
188 : {
189 : enum GNUNET_DB_QueryStatus qs;
190 :
191 8 : qs = TALER_MERCHANTDB_set_instance (pg,
192 8 : w->instance_id);
193 8 : if (qs < 0)
194 : {
195 0 : GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
196 : "Failed to set instance to %s (%d)\n",
197 : w->instance_id,
198 : qs);
199 0 : GNUNET_SCHEDULER_shutdown ();
200 0 : global_ret = EXIT_FAILURE;
201 0 : return;
202 : }
203 :
204 8 : qs = TALER_MERCHANTDB_update_account_last_bank_serial (pg,
205 8 : w->instance_id,
206 : w->payto_uri,
207 : w->start_row);
208 8 : GNUNET_break (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT ==
209 : TALER_MERCHANTDB_set_instance (pg,
210 : NULL));
211 8 : if (qs < 0)
212 : {
213 0 : GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
214 : "Failed to persist wirewatch progress for %s/%s (%d)\n",
215 : w->instance_id,
216 : w->payto_uri.full_payto,
217 : qs);
218 0 : GNUNET_SCHEDULER_shutdown ();
219 0 : global_ret = EXIT_FAILURE;
220 : }
221 : }
222 :
223 :
224 : /**
225 : * Free resources of @a w.
226 : *
227 : * @param w watch job to terminate
228 : */
229 : static void
230 4 : end_watch (struct Watch *w)
231 : {
232 4 : if (NULL != w->task)
233 : {
234 0 : GNUNET_SCHEDULER_cancel (w->task);
235 0 : w->task = NULL;
236 : }
237 4 : if (NULL != w->hh)
238 : {
239 0 : TALER_MERCHANT_BANK_credit_history_cancel (w->hh);
240 0 : w->hh = NULL;
241 : }
242 4 : GNUNET_free (w->instance_id);
243 4 : GNUNET_free (w->payto_uri.full_payto);
244 4 : TALER_MERCHANT_BANK_auth_free (&w->ad);
245 4 : GNUNET_CONTAINER_DLL_remove (w_head,
246 : w_tail,
247 : w);
248 4 : GNUNET_free (w);
249 4 : }
250 :
251 :
252 : /**
253 : * We're being aborted with CTRL-C (or SIGTERM). Shut down.
254 : *
255 : * @param cls closure
256 : */
257 : static void
258 4 : shutdown_task (void *cls)
259 : {
260 : (void) cls;
261 4 : GNUNET_log (GNUNET_ERROR_TYPE_INFO,
262 : "Running shutdown\n");
263 4 : while (NULL != w_head)
264 : {
265 0 : struct Watch *w = w_head;
266 :
267 0 : save (w);
268 0 : end_watch (w);
269 : }
270 4 : if (NULL != eh)
271 : {
272 4 : TALER_MERCHANTDB_event_listen_cancel (eh);
273 4 : eh = NULL;
274 : }
275 4 : if (NULL != pg)
276 : {
277 4 : TALER_MERCHANTDB_disconnect (pg);
278 4 : pg = NULL;
279 : }
280 4 : cfg = NULL;
281 4 : if (NULL != ctx)
282 : {
283 4 : GNUNET_CURL_fini (ctx);
284 4 : ctx = NULL;
285 : }
286 4 : if (NULL != rc)
287 : {
288 4 : GNUNET_CURL_gnunet_rc_destroy (rc);
289 4 : rc = NULL;
290 : }
291 4 : }
292 :
293 :
294 : /**
295 : * Parse @a subject from wire transfer into @a wtid and @a exchange_url.
296 : *
297 : * @param subject wire transfer subject to parse;
298 : * format is "$WTID $URL"
299 : * @param[out] wtid wire transfer ID to extract
300 : * @param[out] exchange_url set to exchange URL
301 : * @return #GNUNET_OK on success
302 : */
303 : static enum GNUNET_GenericReturnValue
304 4 : parse_subject (const char *subject,
305 : struct TALER_WireTransferIdentifierRawP *wtid,
306 : char **exchange_url)
307 : {
308 : const char *space;
309 :
310 4 : space = strchr (subject, ' ');
311 4 : if (NULL == space)
312 0 : return GNUNET_NO;
313 4 : if (GNUNET_OK !=
314 4 : GNUNET_STRINGS_string_to_data (subject,
315 4 : space - subject,
316 : wtid,
317 : sizeof (*wtid)))
318 0 : return GNUNET_NO;
319 4 : space++;
320 4 : if (! TALER_url_valid_charset (space))
321 0 : return GNUNET_NO;
322 4 : if ( (0 != strncasecmp ("http://",
323 : space,
324 0 : strlen ("http://"))) &&
325 0 : (0 != strncasecmp ("https://",
326 : space,
327 : strlen ("https://"))) )
328 0 : return GNUNET_NO;
329 4 : *exchange_url = GNUNET_strdup (space);
330 4 : return GNUNET_OK;
331 : }
332 :
333 :
334 : /**
335 : * Run next iteration.
336 : *
337 : * @param cls a `struct Watch *`
338 : */
339 : static void
340 : do_work (void *cls);
341 :
342 :
343 : /**
344 : * Callbacks of this type are used to serve the result of asking
345 : * the bank for the credit transaction history.
346 : *
347 : * @param cls a `struct Watch *`
348 : * @param http_status HTTP response code, #MHD_HTTP_OK (200) for successful status request
349 : * 0 if the bank's reply is bogus (fails to follow the protocol),
350 : * #MHD_HTTP_NO_CONTENT if there are no more results; on success the
351 : * last callback is always of this status (even if `abs(num_results)` were
352 : * already returned).
353 : * @param ec detailed error code
354 : * @param serial_id monotonically increasing counter corresponding to the transaction
355 : * @param details details about the wire transfer
356 : * @return #GNUNET_OK to continue, #GNUNET_SYSERR to abort iteration
357 : */
358 : static enum GNUNET_GenericReturnValue
359 12 : credit_cb (
360 : void *cls,
361 : unsigned int http_status,
362 : enum TALER_ErrorCode ec,
363 : uint64_t serial_id,
364 : const struct TALER_MERCHANT_BANK_CreditDetails *details)
365 : {
366 12 : struct Watch *w = cls;
367 :
368 12 : switch (http_status)
369 : {
370 0 : case 0:
371 0 : GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
372 : "Invalid HTTP response (HTTP status: 0, %d) from bank\n",
373 : ec);
374 0 : w->delay = GNUNET_TIME_STD_BACKOFF (w->delay);
375 0 : break;
376 4 : case MHD_HTTP_OK:
377 : {
378 : enum GNUNET_DB_QueryStatus qs;
379 : char *exchange_url;
380 : struct TALER_WireTransferIdentifierRawP wtid;
381 : bool no_account;
382 : bool conflict;
383 :
384 4 : GNUNET_log (GNUNET_ERROR_TYPE_INFO,
385 : "Received wire transfer `%s' over %s\n",
386 : details->wire_subject,
387 : TALER_amount2s (&details->amount));
388 4 : w->found = true;
389 4 : if (GNUNET_OK !=
390 4 : parse_subject (details->wire_subject,
391 : &wtid,
392 : &exchange_url))
393 : {
394 0 : GNUNET_log (GNUNET_ERROR_TYPE_INFO,
395 : "Skipping transfer %llu (%s): not from exchange\n",
396 : (unsigned long long) serial_id,
397 : details->wire_subject);
398 0 : w->start_row = serial_id;
399 0 : return GNUNET_OK;
400 : }
401 : /* FIXME-Performance-Optimization: consider grouping multiple inserts
402 : into one bigger transaction with just one notify. */
403 4 : qs = TALER_MERCHANTDB_set_instance (pg,
404 4 : w->instance_id);
405 4 : if (qs < 0)
406 : {
407 0 : GNUNET_break (0);
408 0 : GNUNET_SCHEDULER_shutdown ();
409 0 : w->hh = NULL;
410 0 : GNUNET_free (exchange_url);
411 0 : return GNUNET_SYSERR;
412 : }
413 4 : qs = TALER_MERCHANTDB_insert_transfer (pg,
414 4 : w->instance_id,
415 : exchange_url,
416 : &wtid,
417 : &details->amount,
418 : details->credit_account_uri,
419 : serial_id,
420 : &no_account,
421 : &conflict);
422 :
423 4 : GNUNET_break (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT ==
424 : TALER_MERCHANTDB_set_instance (pg,
425 : NULL));
426 4 : GNUNET_free (exchange_url);
427 4 : if (qs < 0)
428 : {
429 0 : GNUNET_break (0);
430 0 : GNUNET_SCHEDULER_shutdown ();
431 0 : w->hh = NULL;
432 0 : return GNUNET_SYSERR;
433 : }
434 4 : if (no_account)
435 : {
436 0 : GNUNET_break (0);
437 0 : GNUNET_SCHEDULER_shutdown ();
438 0 : w->hh = NULL;
439 0 : return GNUNET_SYSERR;
440 : }
441 4 : if (conflict)
442 : {
443 0 : GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
444 : "Fatal: new wire transfer #%llu has same WTID but different amount %s compared to a previous transfer\n",
445 : (unsigned long long) serial_id,
446 : TALER_amount2s (&details->amount));
447 0 : GNUNET_SCHEDULER_shutdown ();
448 0 : w->hh = NULL;
449 0 : return GNUNET_SYSERR;
450 : }
451 : /* Success => reset back-off timer! */
452 4 : w->delay = GNUNET_TIME_UNIT_ZERO;
453 : }
454 4 : w->start_row = serial_id;
455 4 : return GNUNET_OK;
456 8 : case MHD_HTTP_NO_CONTENT:
457 8 : save (w);
458 : /* Delay artificially if server returned before long-poll timeout */
459 8 : if (! w->found)
460 4 : w->delay = GNUNET_TIME_absolute_get_remaining (w->long_poll_timeout);
461 8 : break;
462 0 : case MHD_HTTP_NOT_FOUND:
463 : /* configuration likely wrong, wait at least 1 minute, backoff up to 15 minutes! */
464 0 : w->delay = GNUNET_TIME_relative_max (GNUNET_TIME_UNIT_MINUTES,
465 : GNUNET_TIME_STD_BACKOFF (w->delay));
466 0 : GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
467 : "Bank claims account is unknown, waiting for %s before trying again\n",
468 : GNUNET_TIME_relative2s (w->delay,
469 : true));
470 0 : break;
471 0 : case MHD_HTTP_GATEWAY_TIMEOUT:
472 0 : GNUNET_log (GNUNET_ERROR_TYPE_INFO,
473 : "Gateway timeout, adjusting long polling threshold\n");
474 : /* Limit new timeout at request delay */
475 : w->bank_timeout
476 0 : = GNUNET_TIME_relative_min (GNUNET_TIME_absolute_get_duration (
477 : w->start_time),
478 : w->bank_timeout);
479 : /* set the timeout a bit earlier */
480 : w->bank_timeout
481 0 : = GNUNET_TIME_relative_subtract (w->bank_timeout,
482 : GNUNET_TIME_UNIT_SECONDS);
483 : /* do not allow it to go to zero */
484 : w->bank_timeout
485 0 : = GNUNET_TIME_relative_max (w->bank_timeout,
486 : GNUNET_TIME_UNIT_SECONDS);
487 0 : w->delay = GNUNET_TIME_STD_BACKOFF (w->delay);
488 0 : break;
489 0 : default:
490 : /* Something went wrong, try again, but with back-off */
491 0 : w->delay = GNUNET_TIME_STD_BACKOFF (w->delay);
492 0 : GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
493 : "Unexpected HTTP status code %u(%d) from wire gateway\n",
494 : http_status,
495 : ec);
496 0 : break;
497 : }
498 8 : w->hh = NULL;
499 8 : if (test_mode && (! w->found))
500 : {
501 4 : GNUNET_log (GNUNET_ERROR_TYPE_INFO,
502 : "No transactions found and in test mode. Ending watch!\n");
503 4 : end_watch (w);
504 4 : if (NULL == w_head)
505 4 : GNUNET_SCHEDULER_shutdown ();
506 4 : return GNUNET_OK;
507 : }
508 4 : w->task = GNUNET_SCHEDULER_add_delayed (w->delay,
509 : &do_work,
510 : w);
511 4 : return GNUNET_OK;
512 : }
513 :
514 :
515 : static void
516 8 : do_work (void *cls)
517 : {
518 8 : struct Watch *w = cls;
519 :
520 8 : w->task = NULL;
521 8 : w->found = false;
522 : w->long_poll_timeout
523 8 : = GNUNET_TIME_relative_to_absolute (w->bank_timeout);
524 : w->start_time
525 8 : = GNUNET_TIME_absolute_get ();
526 8 : w->hh = TALER_MERCHANT_BANK_credit_history (ctx,
527 8 : &w->ad,
528 : w->start_row,
529 : batch_size,
530 : test_mode
531 8 : ? GNUNET_TIME_UNIT_ZERO
532 : : w->bank_timeout,
533 : &credit_cb,
534 : w);
535 8 : if (NULL == w->hh)
536 : {
537 0 : GNUNET_break (0);
538 0 : GNUNET_SCHEDULER_shutdown ();
539 0 : return;
540 : }
541 : }
542 :
543 :
544 : /**
545 : * Function called with information about a accounts
546 : * the wirewatcher should monitor.
547 : *
548 : * @param cls closure (NULL)
549 : * @param instance instance that owns the account
550 : * @param payto_uri account URI
551 : * @param credit_facade_url URL for the credit facade
552 : * @param credit_facade_credentials account access credentials
553 : * @param last_serial last transaction serial (inclusive) we have seen from this account
554 : */
555 : static void
556 4 : start_watch (
557 : void *cls,
558 : const char *instance,
559 : struct TALER_FullPayto payto_uri,
560 : const char *credit_facade_url,
561 : const json_t *credit_facade_credentials,
562 : uint64_t last_serial)
563 : {
564 4 : struct Watch *w = GNUNET_new (struct Watch);
565 :
566 : (void) cls;
567 4 : w->bank_timeout = BANK_TIMEOUT;
568 4 : if (GNUNET_OK !=
569 4 : TALER_MERCHANT_BANK_auth_parse_json (credit_facade_credentials,
570 : credit_facade_url,
571 : &w->ad))
572 : {
573 0 : GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
574 : "Failed to parse authentication data of `%s/%s'\n",
575 : instance,
576 : payto_uri.full_payto);
577 0 : GNUNET_free (w);
578 0 : GNUNET_SCHEDULER_shutdown ();
579 0 : global_ret = EXIT_NOTCONFIGURED;
580 0 : return;
581 : }
582 :
583 4 : GNUNET_CONTAINER_DLL_insert (w_head,
584 : w_tail,
585 : w);
586 4 : w->instance_id = GNUNET_strdup (instance);
587 4 : w->payto_uri.full_payto = GNUNET_strdup (payto_uri.full_payto);
588 4 : w->start_row = last_serial;
589 4 : w->task = GNUNET_SCHEDULER_add_now (&do_work,
590 : w);
591 : }
592 :
593 :
594 : /**
595 : * Function called on configuration change events received from Postgres. We
596 : * shutdown (and systemd should restart us).
597 : *
598 : * @param cls closure (NULL)
599 : * @param extra additional event data provided
600 : * @param extra_size number of bytes in @a extra
601 : */
602 : static void
603 0 : config_changed (void *cls,
604 : const void *extra,
605 : size_t extra_size)
606 : {
607 : (void) cls;
608 : (void) extra;
609 : (void) extra_size;
610 0 : GNUNET_log (GNUNET_ERROR_TYPE_INFO,
611 : "Configuration changed, %s\n",
612 : 0 == persist_mode
613 : ? "restarting"
614 : : "reinitializing");
615 0 : config_changed_flag = true;
616 0 : GNUNET_SCHEDULER_shutdown ();
617 0 : }
618 :
619 :
620 : /**
621 : * First task.
622 : *
623 : * @param cls closure, NULL
624 : * @param args remaining command-line arguments
625 : * @param cfgfile name of the configuration file used (for saving, can be NULL!)
626 : * @param c configuration
627 : */
628 : static void
629 4 : run (void *cls,
630 : char *const *args,
631 : const char *cfgfile,
632 : const struct GNUNET_CONFIGURATION_Handle *c)
633 : {
634 : (void) args;
635 : (void) cfgfile;
636 :
637 4 : cfg = c;
638 4 : GNUNET_SCHEDULER_add_shutdown (&shutdown_task,
639 : NULL);
640 4 : ctx = GNUNET_CURL_init (&GNUNET_CURL_gnunet_scheduler_reschedule,
641 : &rc);
642 4 : rc = GNUNET_CURL_gnunet_rc_create (ctx);
643 4 : if (NULL == ctx)
644 : {
645 0 : GNUNET_break (0);
646 0 : GNUNET_SCHEDULER_shutdown ();
647 0 : global_ret = EXIT_FAILURE;
648 0 : return;
649 : }
650 4 : if (NULL ==
651 4 : (pg = TALER_MERCHANTDB_connect (cfg)))
652 : {
653 0 : GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
654 : "Failed to initialize DB subsystem. Consider running taler-merchant-dbconfig\n");
655 0 : GNUNET_SCHEDULER_shutdown ();
656 0 : global_ret = EXIT_FAILURE;
657 0 : return;
658 : }
659 : {
660 4 : struct GNUNET_DB_EventHeaderP es = {
661 4 : .size = htons (sizeof (es)),
662 4 : .type = htons (TALER_DBEVENT_MERCHANT_ACCOUNTS_CHANGED)
663 : };
664 :
665 8 : eh = TALER_MERCHANTDB_event_listen (pg,
666 : &es,
667 4 : GNUNET_TIME_UNIT_FOREVER_REL,
668 : &config_changed,
669 : NULL);
670 : }
671 : {
672 : enum GNUNET_DB_QueryStatus qs;
673 :
674 4 : qs = TALER_MERCHANTDB_iterate_wirewatch_accounts (pg,
675 : &start_watch,
676 : NULL);
677 4 : if (qs < 0)
678 : {
679 0 : GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
680 : "Failed to obtain wirewatch accounts from database\n");
681 0 : GNUNET_SCHEDULER_shutdown ();
682 0 : global_ret = EXIT_FAILURE;
683 0 : return;
684 : }
685 4 : if ( (NULL == w_head) &&
686 0 : (GNUNET_YES == test_mode) )
687 : {
688 0 : GNUNET_log (GNUNET_ERROR_TYPE_INFO,
689 : "No active wirewatch accounts in database and in test mode. Exiting.\n");
690 0 : GNUNET_SCHEDULER_shutdown ();
691 0 : global_ret = EXIT_SUCCESS;
692 0 : return;
693 : }
694 : }
695 : }
696 :
697 :
698 : /**
699 : * The main function of taler-merchant-wirewatch
700 : *
701 : * @param argc number of arguments from the command line
702 : * @param argv command line arguments
703 : * @return 0 ok, 1 on error
704 : */
705 : int
706 4 : main (int argc,
707 : char *const *argv)
708 : {
709 4 : struct GNUNET_GETOPT_CommandLineOption options[] = {
710 4 : GNUNET_GETOPT_option_flag ('p',
711 : "persist",
712 : "run in persist mode and do not exit on configuration changes",
713 : &persist_mode),
714 4 : GNUNET_GETOPT_option_timetravel ('T',
715 : "timetravel"),
716 4 : GNUNET_GETOPT_option_flag ('t',
717 : "test",
718 : "run in test mode and exit when idle",
719 : &test_mode),
720 4 : GNUNET_GETOPT_option_version (VERSION),
721 : GNUNET_GETOPT_OPTION_END
722 : };
723 : enum GNUNET_GenericReturnValue ret;
724 :
725 : do {
726 4 : config_changed_flag = false;
727 4 : ret = GNUNET_PROGRAM_run (
728 : TALER_MERCHANT_project_data (),
729 : argc, argv,
730 : "taler-merchant-wirewatch",
731 : gettext_noop (
732 : "background process that watches for incoming wire transfers to the merchant bank account"),
733 : options,
734 : &run, NULL);
735 4 : } while ( (1 == persist_mode) &&
736 : config_changed_flag);
737 4 : if (GNUNET_SYSERR == ret)
738 0 : return EXIT_NOTCONFIGURED;
739 4 : if (GNUNET_NO == ret)
740 0 : return EXIT_SUCCESS;
741 4 : return global_ret;
742 : }
743 :
744 :
745 : /* end of taler-exchange-wirewatch.c */
|