Line data Source code
1 : /*
2 : This file is part of TALER
3 : Copyright (C) 2026 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 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 Public License for more details.
12 :
13 : You should have received a copy of the GNU Affero Public License along with
14 : TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
15 : */
16 : /**
17 : * @file auditor/taler-helper-auditor-aml.c
18 : * @brief audits the AML decisions and the appointments of the AML staff who
19 : * made them
20 : * @author Christian Grothoff
21 : *
22 : * Three checks, in one transaction and in this order, because each is judged
23 : * against the result of the one before it:
24 : *
25 : * 1) every row in `aml_staff` must carry a valid signature by the exchange's
26 : * *offline* master key. Without this an exchange could appoint an AML
27 : * officer -- or quietly widen a read-only officer to read-write -- with
28 : * the offline key never having been involved.
29 : *
30 : * 2) every AML decision in `aml_history` must carry a valid signature by an
31 : * officer who, at the time of the decision, was appointed and had
32 : * read-write access.
33 : *
34 : * 3) every row in `legitimization_outcomes` must have something that accounts
35 : * for it. That table holds the rules currently in force for an account,
36 : * so it is what decides whether a customer's transaction is allowed; a row
37 : * that nothing produced is precisely how an exchange would quietly exempt
38 : * a customer from KYC. See #check_outcome_cb() for what counts.
39 : *
40 : * None of the findings is a loss: neither an AML decision nor a rule change
41 : * moves money, so a bad signature or an unaccounted-for rule set costs the
42 : * exchange nothing directly. What it costs is the point of having officers
43 : * sign at all, so the findings are reported as row inconsistencies rather
44 : * than as bad-sig losses.
45 : *
46 : * `aml_staff` is append-only -- a status change is a new row -- so the
47 : * exchange's own database does answer "was this officer allowed to decide at
48 : * that time?". The auditor nevertheless keeps its own record of every status
49 : * change it has seen, in `auditor_aml_staff`, and answers the question from
50 : * that: append-only is a promise the exchange's code makes, not one the
51 : * database enforces against the exchange's operator, and an independent
52 : * record is the point of an auditor.
53 : *
54 : * That record is also what makes backdating detectable. The master key is
55 : * the exchange operator's own, so a dishonest operator can sign an
56 : * appointment naming any `last_change` it likes, and a decision naming any
57 : * `decision_time`. What it cannot do is change what the auditor already
58 : * saw: if the auditor recorded an officer as disabled before this round
59 : * began, a decision by that officer surfacing now is backdated, whatever
60 : * `decision_time` claims. This does not stop backdating *within* the set of
61 : * rows processed in one round -- an auditor running promptly keeps that
62 : * window small, which is the whole defence.
63 : */
64 : #include "platform.h"
65 : #include <gnunet/gnunet_util_lib.h>
66 : #include "auditordb_lib.h"
67 : #include "exchangedb_lib.h"
68 : #include "report-lib.h"
69 : #include "taler/taler_dbevents.h"
70 : #include <jansson.h>
71 : #include <inttypes.h>
72 : #include "auditor-database/event_listen.h"
73 : #include "auditor-database/get_auditor_progress.h"
74 : #include "auditor-database/insert_aml_staff.h"
75 : #include "auditor-database/insert_auditor_progress.h"
76 : #include "auditor-database/insert_row_inconsistency.h"
77 : #include "auditor-database/iterate_aml_staff.h"
78 : #include "exchange-database/iterate_aml_history_above_serial_id.h"
79 : #include "exchange-database/iterate_aml_staff_above_serial_id.h"
80 : #include "exchange-database/iterate_legitimization_outcomes_above_serial_id.h"
81 :
82 :
83 : /**
84 : * Return value from main().
85 : */
86 : static int global_ret;
87 :
88 : /**
89 : * Row of `aml_history` up to which we have checked AML decisions.
90 : */
91 : static TALER_ARL_DEF_PP (aml_history_serial_id);
92 :
93 : /**
94 : * Row of `aml_staff` up to which we have checked status changes.
95 : */
96 : static TALER_ARL_DEF_PP (aml_staff_uuid);
97 :
98 : /**
99 : * Row of `legitimization_outcomes` up to which we have checked that the
100 : * rules in force for an account are accounted for.
101 : */
102 : static TALER_ARL_DEF_PP (legitimization_outcome_serial_id);
103 :
104 : /**
105 : * Run in test mode. Exit when idle instead of
106 : * going to sleep and waiting for more work.
107 : */
108 : static int test_mode;
109 :
110 : /**
111 : * Should we run checks that only work for exchange-internal audits?
112 : * Does nothing for this helper (present only for uniformity).
113 : */
114 : static int internal_checks;
115 :
116 : /**
117 : * Handle to the database event we wait on when running resident.
118 : */
119 : static struct GNUNET_DB_EventHandler *eh;
120 :
121 : /**
122 : * The auditors's configuration.
123 : */
124 : static const struct GNUNET_CONFIGURATION_Handle *cfg;
125 :
126 : /**
127 : * Map from the hash of an officer's public key to a `struct StaffMember`.
128 : */
129 : static struct GNUNET_CONTAINER_MultiHashMap *staff_map;
130 :
131 : /**
132 : * Status of the DB operations of our callbacks; they cannot return a
133 : * query status themselves.
134 : */
135 : static enum GNUNET_DB_QueryStatus global_qs;
136 :
137 :
138 : /**
139 : * One observed status of an AML staff member, in force from @e last_change
140 : * until the next status of the same member.
141 : */
142 : struct StaffStatus
143 : {
144 : /**
145 : * When this status took effect, as claimed by the exchange.
146 : */
147 : struct GNUNET_TIME_Timestamp last_change;
148 :
149 : /**
150 : * Was the member allowed to act?
151 : */
152 : bool is_active;
153 :
154 : /**
155 : * Was the member restricted to read-only access?
156 : */
157 : bool read_only;
158 :
159 : /**
160 : * Did the exchange's offline master key actually sign this status?
161 : * A status that fails this check confers no authority.
162 : */
163 : bool master_sig_valid;
164 : };
165 :
166 :
167 : /**
168 : * What we know about one AML staff member.
169 : */
170 : struct StaffMember
171 : {
172 : /**
173 : * Key under which this member is stored in #staff_map. The map is
174 : * created with @a do_not_copy_keys, so it keeps this pointer: the key
175 : * has to live as long as the entry does.
176 : */
177 : struct GNUNET_HashCode key;
178 :
179 : /**
180 : * Observed statuses, oldest first.
181 : */
182 : struct StaffStatus *statuses;
183 :
184 : /**
185 : * Length of @e statuses.
186 : */
187 : unsigned int num_statuses;
188 :
189 : /**
190 : * Did we already know this member when the round began?
191 : */
192 : bool known_at_start;
193 :
194 : /**
195 : * Was the member enabled according to the most recent status we knew
196 : * when the round began? Meaningless unless @e known_at_start.
197 : */
198 : bool active_at_start;
199 :
200 : /**
201 : * Did we observe the member being enabled during this round?
202 : */
203 : bool enabled_this_round;
204 : };
205 :
206 :
207 : /**
208 : * Compute the map key for @a decider_pub.
209 : *
210 : * @param decider_pub public key of the staff member
211 : * @param[out] key set to the key to use
212 : */
213 : static void
214 0 : staff_key (const struct TALER_AmlOfficerPublicKeyP *decider_pub,
215 : struct GNUNET_HashCode *key)
216 : {
217 0 : GNUNET_CRYPTO_hash (decider_pub,
218 : sizeof (*decider_pub),
219 : key);
220 0 : }
221 :
222 :
223 : /**
224 : * Look up @a decider_pub in #staff_map, creating the entry if needed.
225 : *
226 : * @param decider_pub public key of the staff member
227 : * @return the entry, never NULL
228 : */
229 : static struct StaffMember *
230 0 : staff_member_get (const struct TALER_AmlOfficerPublicKeyP *decider_pub)
231 : {
232 : struct GNUNET_HashCode key;
233 : struct StaffMember *sm;
234 :
235 0 : staff_key (decider_pub,
236 : &key);
237 0 : sm = GNUNET_CONTAINER_multihashmap_get (staff_map,
238 : &key);
239 0 : if (NULL != sm)
240 0 : return sm;
241 0 : sm = GNUNET_new (struct StaffMember);
242 0 : sm->key = key;
243 0 : GNUNET_assert (GNUNET_OK ==
244 : GNUNET_CONTAINER_multihashmap_put (
245 : staff_map,
246 : &sm->key,
247 : sm,
248 : GNUNET_CONTAINER_MULTIHASHMAPOPTION_UNIQUE_ONLY));
249 0 : return sm;
250 : }
251 :
252 :
253 : /**
254 : * Append @a status to the statuses of @a sm, keeping them ordered by
255 : * @e last_change.
256 : *
257 : * @param[in,out] sm staff member to extend
258 : * @param status status to add
259 : */
260 : static void
261 0 : staff_member_add (struct StaffMember *sm,
262 : const struct StaffStatus *status)
263 : {
264 : unsigned int i;
265 :
266 0 : GNUNET_array_grow (sm->statuses,
267 : sm->num_statuses,
268 : sm->num_statuses + 1);
269 0 : for (i = sm->num_statuses - 1; i > 0; i--)
270 : {
271 0 : if (GNUNET_TIME_timestamp_cmp (sm->statuses[i - 1].last_change,
272 : <=,
273 : status->last_change))
274 0 : break;
275 0 : sm->statuses[i] = sm->statuses[i - 1];
276 : }
277 0 : sm->statuses[i] = *status;
278 0 : }
279 :
280 :
281 : /**
282 : * Find the status that was in force for @a sm at @a when, considering only
283 : * statuses the offline master key really signed.
284 : *
285 : * @param sm staff member to look at
286 : * @param when point in time of interest
287 : * @return NULL if no signed status covers @a when
288 : */
289 : static const struct StaffStatus *
290 0 : staff_member_status_at (const struct StaffMember *sm,
291 : struct GNUNET_TIME_Timestamp when)
292 : {
293 0 : const struct StaffStatus *ret = NULL;
294 :
295 0 : for (unsigned int i = 0; i < sm->num_statuses; i++)
296 : {
297 0 : const struct StaffStatus *ss = &sm->statuses[i];
298 :
299 0 : if (GNUNET_TIME_timestamp_cmp (ss->last_change,
300 : >,
301 : when))
302 0 : break; /* sorted, so no later entry can apply either */
303 0 : if (ss->master_sig_valid)
304 0 : ret = ss;
305 : }
306 0 : return ret;
307 : }
308 :
309 :
310 : /**
311 : * Report a row inconsistency.
312 : *
313 : * @param table name of the exchange table the bad row is in
314 : * @param rowid row that is bad
315 : * @param diagnostic what is wrong with it
316 : * @return true on success, false if the database failed us
317 : */
318 : static bool
319 0 : report_row (const char *table,
320 : uint64_t rowid,
321 : const char *diagnostic)
322 : {
323 0 : struct TALER_AUDITORDB_RowInconsistency ri = {
324 : .problem_row_id = rowid,
325 : .row_table = (char *) table,
326 : .diagnostic = (char *) diagnostic
327 : };
328 : enum GNUNET_DB_QueryStatus qs;
329 :
330 0 : GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
331 : "Row %llu of `%s' is bad: %s\n",
332 : (unsigned long long) rowid,
333 : table,
334 : diagnostic);
335 0 : qs = TALER_AUDITORDB_insert_row_inconsistency (TALER_ARL_adb,
336 : &ri);
337 0 : if (0 > qs)
338 : {
339 0 : GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs);
340 0 : global_qs = qs;
341 0 : return false;
342 : }
343 0 : return true;
344 : }
345 :
346 :
347 : /**
348 : * Function called with an AML staff status change we recorded earlier.
349 : * Rebuilds our view of who was allowed to decide when.
350 : *
351 : * @param cls NULL
352 : * @param decider_pub public key of the staff member
353 : * @param decider_name legal name of the staff member
354 : * @param is_active true if the member could act from @a last_change on
355 : * @param read_only true if the member had read-only access
356 : * @param master_sig_valid true if the master key really signed this status
357 : * @param last_change when the status took effect
358 : * @return #GNUNET_OK to continue to iterate
359 : */
360 : static enum GNUNET_GenericReturnValue
361 0 : known_staff_cb (void *cls,
362 : const struct TALER_AmlOfficerPublicKeyP *decider_pub,
363 : const char *decider_name,
364 : bool is_active,
365 : bool read_only,
366 : bool master_sig_valid,
367 : struct GNUNET_TIME_Timestamp last_change)
368 : {
369 0 : struct StaffMember *sm = staff_member_get (decider_pub);
370 0 : struct StaffStatus ss = {
371 : .last_change = last_change,
372 : .is_active = is_active,
373 : .read_only = read_only,
374 : .master_sig_valid = master_sig_valid
375 : };
376 :
377 : (void) cls;
378 : (void) decider_name;
379 0 : staff_member_add (sm,
380 : &ss);
381 : /* We are called oldest-first, so the last call for a member leaves the
382 : status that was in force when this round began. */
383 0 : sm->known_at_start = true;
384 0 : sm->active_at_start = is_active && master_sig_valid;
385 0 : return GNUNET_OK;
386 : }
387 :
388 :
389 : /**
390 : * Function called with a status change of an AML staff member the exchange
391 : * recorded. Verifies the offline master key's signature on it and, if this
392 : * is a status we have not seen before, records it.
393 : *
394 : * @param cls NULL
395 : * @param rowid row in `aml_staff`
396 : * @param decider_pub public key of the staff member
397 : * @param master_sig signature by the offline master key, NULL if absent
398 : * @param decider_name legal name of the staff member
399 : * @param is_active true if the member may currently act
400 : * @param read_only true if the member has read-only access
401 : * @param last_change when the status took effect, as claimed
402 : * @return #GNUNET_OK to continue to iterate, #GNUNET_SYSERR to stop
403 : */
404 : static enum GNUNET_GenericReturnValue
405 0 : check_staff_cb (void *cls,
406 : uint64_t rowid,
407 : const struct TALER_AmlOfficerPublicKeyP *decider_pub,
408 : const struct TALER_MasterSignatureP *master_sig,
409 : const char *decider_name,
410 : bool is_active,
411 : bool read_only,
412 : struct GNUNET_TIME_Timestamp last_change)
413 : {
414 0 : struct StaffMember *sm = staff_member_get (decider_pub);
415 0 : struct StaffStatus ss = {
416 : .last_change = last_change,
417 : .is_active = is_active,
418 : .read_only = read_only,
419 : .master_sig_valid = false
420 : };
421 : enum GNUNET_DB_QueryStatus qs;
422 0 : bool regression = false;
423 :
424 : (void) cls;
425 0 : TALER_ARL_USE_PP (aml_staff_uuid) = rowid + 1;
426 0 : for (unsigned int i = 0; i < sm->num_statuses; i++)
427 : {
428 0 : if (GNUNET_TIME_timestamp_cmp (sm->statuses[i].last_change,
429 : ==,
430 : last_change))
431 0 : return GNUNET_OK; /* already seen and judged in an earlier round */
432 0 : if (GNUNET_TIME_timestamp_cmp (sm->statuses[i].last_change,
433 : >,
434 : last_change))
435 0 : regression = true;
436 : }
437 0 : if (NULL == master_sig)
438 : {
439 0 : if (! report_row ("aml_staff",
440 : rowid,
441 : "staff status without a master key signature"))
442 0 : return GNUNET_SYSERR;
443 : }
444 0 : else if (GNUNET_OK !=
445 0 : TALER_exchange_offline_aml_officer_status_verify (
446 : decider_pub,
447 : decider_name,
448 : last_change,
449 : is_active,
450 : read_only,
451 : &TALER_ARL_master_pub,
452 : master_sig))
453 : {
454 0 : if (! report_row ("aml_staff",
455 : rowid,
456 : "invalid master key signature on staff status")
457 : )
458 0 : return GNUNET_SYSERR;
459 : }
460 : else
461 : {
462 0 : ss.master_sig_valid = true;
463 : }
464 0 : if (regression)
465 : {
466 : /* exchange_do_insert_aml_officer() refuses to store a status older than
467 : the one it has, so seeing one means somebody wrote to the table
468 : behind the exchange's back. */
469 0 : if (! report_row ("aml_staff",
470 : rowid,
471 : "staff status is older than one seen before"))
472 0 : return GNUNET_SYSERR;
473 : }
474 0 : if (ss.master_sig_valid && is_active)
475 0 : sm->enabled_this_round = true;
476 0 : staff_member_add (sm,
477 : &ss);
478 0 : qs = TALER_AUDITORDB_insert_aml_staff (TALER_ARL_adb,
479 : decider_pub,
480 : decider_name,
481 : is_active,
482 : read_only,
483 0 : ss.master_sig_valid,
484 : last_change);
485 0 : if (0 > qs)
486 : {
487 0 : GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs);
488 0 : global_qs = qs;
489 0 : return GNUNET_SYSERR;
490 : }
491 0 : return GNUNET_OK;
492 : }
493 :
494 :
495 : /**
496 : * Function called with an AML decision the exchange recorded. Checks the
497 : * officer's signature and that the officer was allowed to make it.
498 : *
499 : * @param cls NULL
500 : * @param rowid row in `aml_history`
501 : * @param h_payto account the decision is about
502 : * @param justification justification given
503 : * @param decider_pub officer who decided, NULL if not recorded
504 : * @param decider_sig the officer's signature, NULL if not recorded
505 : * @param decision_time when the decision was taken, as claimed
506 : * @param jproperties new account properties, NULL for none
507 : * @param jnew_rules new KYC rules, NULL if not recorded
508 : * @param new_measure_name measure to apply, NULL for none
509 : * @param to_investigate whether staff should investigate the account
510 : * @param attributes_expiration when attributes set with the decision expire
511 : * @param h_attributes hash of the attributes set, NULL if none
512 : * @return #GNUNET_OK to continue to iterate, #GNUNET_SYSERR to stop
513 : */
514 : static enum GNUNET_GenericReturnValue
515 0 : check_decision_cb (void *cls,
516 : uint64_t rowid,
517 : const struct TALER_NormalizedPaytoHashP *h_payto,
518 : const char *justification,
519 : const struct TALER_AmlOfficerPublicKeyP *decider_pub,
520 : const struct TALER_AmlOfficerSignatureP *decider_sig,
521 : struct GNUNET_TIME_Timestamp decision_time,
522 : const json_t *jproperties,
523 : const json_t *jnew_rules,
524 : const char *new_measure_name,
525 : bool to_investigate,
526 : struct GNUNET_TIME_Timestamp attributes_expiration,
527 : const struct GNUNET_HashCode *h_attributes)
528 : {
529 : struct StaffMember *sm;
530 : const struct StaffStatus *ss;
531 :
532 : (void) cls;
533 0 : GNUNET_log (GNUNET_ERROR_TYPE_INFO,
534 : "Analyzing AML decision %llu taken at %s\n",
535 : (unsigned long long) rowid,
536 : GNUNET_TIME_timestamp2s (decision_time));
537 0 : TALER_ARL_USE_PP (aml_history_serial_id) = rowid + 1;
538 0 : if (NULL == decider_pub)
539 : {
540 : /* exchange_do_insert_aml_decision() only writes an aml_history row when
541 : it has a decider, so this cannot happen without tampering. */
542 0 : if (! report_row ("aml_history",
543 : rowid,
544 : "decision without an officer public key"))
545 0 : return GNUNET_SYSERR;
546 0 : return GNUNET_OK;
547 : }
548 0 : if (NULL == decider_sig)
549 : {
550 0 : if (! report_row ("aml_history",
551 : rowid,
552 : "decision without an officer signature"))
553 0 : return GNUNET_SYSERR;
554 : }
555 0 : else if (NULL == jnew_rules)
556 : {
557 : /* The signature is over the new rules, so without them there is nothing
558 : we could check it against. */
559 0 : if (! report_row ("aml_history",
560 : rowid,
561 : "decision without the new rules it signed over"))
562 0 : return GNUNET_SYSERR;
563 : }
564 0 : else if (GNUNET_OK !=
565 0 : TALER_officer_aml_decision_verify_hashed (
566 : justification,
567 : decision_time,
568 : h_payto,
569 : jnew_rules,
570 : jproperties,
571 : new_measure_name,
572 : to_investigate,
573 : attributes_expiration,
574 : h_attributes,
575 : decider_pub,
576 : decider_sig))
577 : {
578 0 : if (! report_row ("aml_history",
579 : rowid,
580 : "invalid officer signature on decision"))
581 0 : return GNUNET_SYSERR;
582 : }
583 0 : sm = staff_member_get (decider_pub);
584 0 : ss = staff_member_status_at (sm,
585 : decision_time);
586 0 : if (NULL == ss)
587 : {
588 0 : if (! report_row ("aml_history",
589 : rowid,
590 : "officer was not appointed when the decision was made"))
591 0 : return GNUNET_SYSERR;
592 : }
593 0 : else if (! ss->is_active)
594 : {
595 0 : if (! report_row ("aml_history",
596 : rowid,
597 : "officer was not active when the decision was made"))
598 0 : return GNUNET_SYSERR;
599 : }
600 0 : else if (ss->read_only)
601 : {
602 0 : if (! report_row ("aml_history",
603 : rowid,
604 : "officer had read-only access when deciding"))
605 0 : return GNUNET_SYSERR;
606 : }
607 : /* The eligibility check above trusts decision_time, which the exchange
608 : picks. This one does not: whatever the decision claims, a record for an
609 : officer we already knew to be disabled has no business appearing now. */
610 0 : if (sm->known_at_start &&
611 0 : (! sm->active_at_start) &&
612 0 : (! sm->enabled_this_round))
613 : {
614 0 : if (! report_row ("aml_history",
615 : rowid,
616 : "decision appeared after the officer was disabled"))
617 0 : return GNUNET_SYSERR;
618 : }
619 0 : return GNUNET_OK;
620 : }
621 :
622 :
623 : /**
624 : * Function called with a row of `legitimization_outcomes`, the table that
625 : * says which KYC rules currently apply to an account. Checks that something
626 : * in the exchange's books accounts for the row existing.
627 : *
628 : * The exchange has exactly three ways to create one, and each leaves its own
629 : * trace:
630 : *
631 : * - an AML officer decides, and `exchange_do_insert_aml_decision()` writes an
632 : * `aml_history` row pointing at the outcome. #check_decision_cb() has
633 : * already checked that decision's signature and the officer's appointment,
634 : * so this is the strongest of the three;
635 : * - a customer passes (or fails) a KYC check, and the AML program's verdict
636 : * on it becomes the outcome. What is left of the check is a row in
637 : * `legitimization_processes` for the same account, started no later than
638 : * the decision;
639 : * - the rules in force expire, and `exchange_do_insert_successor_measure()`
640 : * or a re-run of the AML program replaces them. What is left of that is
641 : * the expired predecessor outcome.
642 : *
643 : * A row with none of the three was written by something other than the
644 : * exchange's own code. Note that the third alternative is the weak one: an
645 : * account that has ever had an outcome expire can be given further outcomes
646 : * without any fresh justification, exactly as the exchange legitimately does
647 : * on expiry. The check is a floor, not a proof that the rules are right.
648 : *
649 : * @param cls NULL
650 : * @param rowid row in `legitimization_outcomes`
651 : * @param h_payto account the outcome is about
652 : * @param decision_time when the outcome was decided, as claimed
653 : * @param expiration_time when the outcome expires
654 : * @param has_aml_decision an `aml_history` row points at this outcome
655 : * @param has_legitimization_process the account had a legitimization process
656 : * that had started by @a decision_time
657 : * @param has_expired_predecessor an earlier outcome for the account had
658 : * expired by @a decision_time
659 : * @return #GNUNET_OK to continue to iterate, #GNUNET_SYSERR to stop
660 : */
661 : static enum GNUNET_GenericReturnValue
662 0 : check_outcome_cb (void *cls,
663 : uint64_t rowid,
664 : const struct TALER_NormalizedPaytoHashP *h_payto,
665 : struct GNUNET_TIME_Timestamp decision_time,
666 : struct GNUNET_TIME_Timestamp expiration_time,
667 : bool has_aml_decision,
668 : bool has_legitimization_process,
669 : bool has_expired_predecessor)
670 : {
671 : (void) cls;
672 : (void) h_payto;
673 : (void) expiration_time;
674 0 : GNUNET_log (GNUNET_ERROR_TYPE_INFO,
675 : "Analyzing legitimization outcome %llu decided at %s\n",
676 : (unsigned long long) rowid,
677 : GNUNET_TIME_timestamp2s (decision_time));
678 0 : TALER_ARL_USE_PP (legitimization_outcome_serial_id) = rowid + 1;
679 0 : if (has_aml_decision ||
680 0 : has_legitimization_process ||
681 : has_expired_predecessor)
682 0 : return GNUNET_OK;
683 0 : if (! report_row ("legitimization_outcomes",
684 : rowid,
685 : "KYC rules in force without an AML decision,"
686 : " a legitimization process or an expired predecessor"))
687 0 : return GNUNET_SYSERR;
688 0 : return GNUNET_OK;
689 : }
690 :
691 :
692 : /**
693 : * Free a `struct StaffMember`.
694 : *
695 : * @param cls NULL
696 : * @param key unused
697 : * @param value the `struct StaffMember` to free
698 : * @return #GNUNET_OK
699 : */
700 : static enum GNUNET_GenericReturnValue
701 0 : free_staff_member (void *cls,
702 : const struct GNUNET_HashCode *key,
703 : void *value)
704 : {
705 0 : struct StaffMember *sm = value;
706 :
707 : (void) cls;
708 : (void) key;
709 0 : GNUNET_assert (GNUNET_YES ==
710 : GNUNET_CONTAINER_multihashmap_remove (staff_map,
711 : &sm->key,
712 : sm));
713 0 : GNUNET_array_grow (sm->statuses,
714 : sm->num_statuses,
715 : 0);
716 0 : GNUNET_free (sm);
717 0 : return GNUNET_OK;
718 : }
719 :
720 :
721 : /**
722 : * Drop our in-memory view of the AML staff.
723 : */
724 : static void
725 12 : clear_staff_map (void)
726 : {
727 12 : if (NULL == staff_map)
728 6 : return;
729 6 : GNUNET_CONTAINER_multihashmap_iterate (staff_map,
730 : &free_staff_member,
731 : NULL);
732 6 : GNUNET_CONTAINER_multihashmap_destroy (staff_map);
733 6 : staff_map = NULL;
734 : }
735 :
736 :
737 : /**
738 : * Analyze the AML staff appointments and the decisions they justify.
739 : *
740 : * @param cls NULL
741 : * @return transaction status code
742 : */
743 : static enum GNUNET_DB_QueryStatus
744 6 : analyze_aml (void *cls)
745 : {
746 : enum GNUNET_DB_QueryStatus qs;
747 : bool had_pp;
748 :
749 : (void) cls;
750 6 : global_qs = GNUNET_DB_STATUS_SUCCESS_NO_RESULTS;
751 6 : clear_staff_map ();
752 6 : staff_map = GNUNET_CONTAINER_multihashmap_create (32,
753 : GNUNET_YES);
754 6 : qs = TALER_AUDITORDB_get_auditor_progress (
755 : TALER_ARL_adb,
756 : TALER_ARL_GET_PP (aml_history_serial_id),
757 : TALER_ARL_GET_PP (aml_staff_uuid),
758 : TALER_ARL_GET_PP (legitimization_outcome_serial_id),
759 : NULL);
760 6 : if (0 > qs)
761 : {
762 0 : GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs);
763 0 : return qs;
764 : }
765 : /* Not from @a qs: auditor_do_get_auditor_progress() returns one row per
766 : key whether or not the key is on file, so the query status says nothing
767 : about whether we have run before. */
768 18 : had_pp = (0 != TALER_ARL_USE_PP (aml_staff_uuid)) ||
769 12 : (0 != TALER_ARL_USE_PP (aml_history_serial_id)) ||
770 6 : (0 != TALER_ARL_USE_PP (legitimization_outcome_serial_id));
771 6 : if (had_pp)
772 0 : GNUNET_log (GNUNET_ERROR_TYPE_INFO,
773 : "Resuming AML audit at %llu/%llu/%llu\n",
774 : (unsigned long long) TALER_ARL_USE_PP (aml_staff_uuid),
775 : (unsigned long long) TALER_ARL_USE_PP (aml_history_serial_id),
776 : (unsigned long long) TALER_ARL_USE_PP (
777 : legitimization_outcome_serial_id));
778 : else
779 6 : GNUNET_log (GNUNET_ERROR_TYPE_MESSAGE,
780 : "First analysis using AML auditor, starting audit from scratch\n");
781 :
782 : /* What we knew about the staff when this round began. */
783 6 : qs = TALER_AUDITORDB_iterate_aml_staff (TALER_ARL_adb,
784 : &known_staff_cb,
785 : NULL);
786 6 : if (0 > qs)
787 : {
788 0 : GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs);
789 0 : return qs;
790 : }
791 :
792 : /* Status changes the exchange recorded since we last looked. Must run
793 : before the decisions below, which are judged against the result. */
794 6 : qs = TALER_EXCHANGEDB_iterate_aml_staff_above_serial_id (
795 : TALER_ARL_edb,
796 : TALER_ARL_USE_PP (aml_staff_uuid),
797 : &check_staff_cb,
798 : NULL);
799 6 : if (0 > qs)
800 : {
801 0 : GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs);
802 0 : return qs;
803 : }
804 6 : if (0 > global_qs)
805 0 : return global_qs;
806 :
807 6 : qs = TALER_EXCHANGEDB_iterate_aml_history_above_serial_id (
808 : TALER_ARL_edb,
809 : TALER_ARL_USE_PP (aml_history_serial_id),
810 : &check_decision_cb,
811 : NULL);
812 6 : if (0 > qs)
813 : {
814 0 : GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs);
815 0 : return qs;
816 : }
817 6 : if (0 > global_qs)
818 0 : return global_qs;
819 6 : GNUNET_log (GNUNET_ERROR_TYPE_INFO,
820 : "Analyzed %d AML decisions\n",
821 : (int) qs);
822 :
823 : /* Runs after the decisions: an outcome an AML officer signed is accounted
824 : for by the `aml_history' row we just judged, so leaving that judgement
825 : for a later round would report the outcome against an incomplete
826 : picture. */
827 6 : qs = TALER_EXCHANGEDB_iterate_legitimization_outcomes_above_serial_id (
828 : TALER_ARL_edb,
829 : TALER_ARL_USE_PP (legitimization_outcome_serial_id),
830 : &check_outcome_cb,
831 : NULL);
832 6 : if (0 > qs)
833 : {
834 0 : GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs);
835 0 : return qs;
836 : }
837 6 : if (0 > global_qs)
838 0 : return global_qs;
839 6 : GNUNET_log (GNUNET_ERROR_TYPE_INFO,
840 : "Analyzed %d legitimization outcomes\n",
841 : (int) qs);
842 : /* Insert first (a no-op once the rows exist), then update: the three
843 : progress points are written on the very first round as well as on
844 : every later one. Branching on @e had_pp instead would leave the
845 : rows uncreated for ever, and the helper would re-audit the whole
846 : history every time it woke up. */
847 6 : qs = TALER_AUDITORDB_insert_auditor_progress (
848 : TALER_ARL_adb,
849 : TALER_ARL_SET_PP (aml_history_serial_id),
850 : TALER_ARL_SET_PP (aml_staff_uuid),
851 : TALER_ARL_SET_PP (legitimization_outcome_serial_id),
852 : NULL);
853 6 : if (0 > qs)
854 : {
855 0 : GNUNET_log (GNUNET_ERROR_TYPE_INFO,
856 : "Failed to update auditor DB, not recording progress\n");
857 0 : GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs);
858 0 : return qs;
859 : }
860 6 : return GNUNET_DB_STATUS_SUCCESS_ONE_RESULT;
861 : }
862 :
863 :
864 : /**
865 : * Function called when the exchange recorded an AML staff status change or
866 : * an AML decision. Re-runs the analysis.
867 : *
868 : * @param cls NULL
869 : * @param extra additional event data provided
870 : * @param extra_size number of bytes in @a extra
871 : */
872 : static void
873 0 : db_notify (void *cls,
874 : const void *extra,
875 : size_t extra_size)
876 : {
877 : (void) cls;
878 : (void) extra;
879 : (void) extra_size;
880 0 : GNUNET_log (GNUNET_ERROR_TYPE_INFO,
881 : "Received notification for new AML data\n");
882 0 : if (GNUNET_OK !=
883 0 : TALER_ARL_setup_sessions_and_run (&analyze_aml,
884 : NULL))
885 : {
886 0 : GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
887 : "Audit failed\n");
888 0 : GNUNET_SCHEDULER_shutdown ();
889 0 : global_ret = EXIT_FAILURE;
890 0 : return;
891 : }
892 : }
893 :
894 :
895 : /**
896 : * Function called on shutdown.
897 : *
898 : * @param cls NULL
899 : */
900 : static void
901 6 : do_shutdown (void *cls)
902 : {
903 : (void) cls;
904 6 : if (NULL != eh)
905 : {
906 0 : TALER_AUDITORDB_event_listen_cancel (eh);
907 0 : eh = NULL;
908 : }
909 6 : clear_staff_map ();
910 6 : TALER_ARL_done ();
911 6 : }
912 :
913 :
914 : /**
915 : * Main function that will be run.
916 : *
917 : * @param cls closure
918 : * @param args remaining command-line arguments
919 : * @param cfgfile name of the configuration file used (for saving, can be NULL!)
920 : * @param c configuration
921 : */
922 : static void
923 6 : run (void *cls,
924 : char *const *args,
925 : const char *cfgfile,
926 : const struct GNUNET_CONFIGURATION_Handle *c)
927 : {
928 : (void) cls;
929 : (void) args;
930 : (void) cfgfile;
931 6 : cfg = c;
932 6 : GNUNET_SCHEDULER_add_shutdown (&do_shutdown,
933 : NULL);
934 6 : GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
935 : "Launching AML auditor\n");
936 6 : if (EXIT_SUCCESS !=
937 6 : (global_ret = TALER_ARL_init (c)))
938 : {
939 0 : return;
940 : }
941 6 : if (test_mode != 1)
942 : {
943 0 : struct GNUNET_DB_EventHeaderP es = {
944 0 : .size = htons (sizeof (es)),
945 0 : .type = htons (TALER_DBEVENT_EXCHANGE_AUDITOR_WAKE_HELPER_AML)
946 : };
947 :
948 0 : GNUNET_log (GNUNET_ERROR_TYPE_INFO,
949 : "Running helper indefinitely\n");
950 0 : eh = TALER_AUDITORDB_event_listen (TALER_ARL_adb,
951 : &es,
952 0 : GNUNET_TIME_UNIT_FOREVER_REL,
953 : &db_notify,
954 : NULL);
955 : }
956 6 : GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
957 : "Starting audit\n");
958 6 : if (GNUNET_OK !=
959 6 : TALER_ARL_setup_sessions_and_run (&analyze_aml,
960 : NULL))
961 : {
962 0 : GNUNET_SCHEDULER_shutdown ();
963 0 : global_ret = EXIT_FAILURE;
964 0 : return;
965 : }
966 : }
967 :
968 :
969 : /**
970 : * The main function of the AML auditing helper tool.
971 : *
972 : * @param argc number of arguments from the command line
973 : * @param argv command line arguments
974 : * @return 0 ok, 1 on error
975 : */
976 : int
977 6 : main (int argc,
978 : char *const *argv)
979 : {
980 6 : const struct GNUNET_GETOPT_CommandLineOption options[] = {
981 6 : GNUNET_GETOPT_option_flag ('i',
982 : "internal",
983 : "perform checks only applicable for exchange-internal audits",
984 : &internal_checks),
985 6 : GNUNET_GETOPT_option_flag ('t',
986 : "test",
987 : "run in test mode and exit when idle",
988 : &test_mode),
989 6 : GNUNET_GETOPT_option_timetravel ('T',
990 : "timetravel"),
991 : GNUNET_GETOPT_OPTION_END
992 : };
993 : enum GNUNET_GenericReturnValue ret;
994 :
995 6 : ret = GNUNET_PROGRAM_run (
996 : TALER_AUDITOR_project_data (),
997 : argc,
998 : argv,
999 : "taler-helper-auditor-aml",
1000 : gettext_noop (
1001 : "Audit exchange database for AML decisions made by unauthorised staff"),
1002 : options,
1003 : &run,
1004 : NULL);
1005 6 : if (GNUNET_SYSERR == ret)
1006 0 : return EXIT_NOTCONFIGURED;
1007 6 : if (GNUNET_NO == ret)
1008 0 : return EXIT_SUCCESS;
1009 6 : return global_ret;
1010 : }
1011 :
1012 :
1013 : /* end of taler-helper-auditor-aml.c */
|