aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorChristian Grothoff <christian@grothoff.org>2021-08-02 19:38:23 +0200
committerChristian Grothoff <christian@grothoff.org>2021-08-02 19:38:23 +0200
commitc4ae15c1dbecc65b99e42e6c355952e576cfafe0 (patch)
treeec465fc048591b51bac9a1254746308014ca8795
parent2e6b8e10de123b446ee77f714c0108dce15cd83e (diff)
downloadexchange-c4ae15c1dbecc65b99e42e6c355952e576cfafe0.tar.gz
exchange-c4ae15c1dbecc65b99e42e6c355952e576cfafe0.zip
-strengthen payto validation logic
m---------contrib/gana0
-rw-r--r--src/include/taler_json_lib.h13
-rw-r--r--src/include/taler_util.h13
-rw-r--r--src/json/json_wire.c426
-rw-r--r--src/util/payto.c398
5 files changed, 450 insertions, 400 deletions
diff --git a/contrib/gana b/contrib/gana
Subproject 755e752e3a235df0be8d45374835b109f7843c1 Subproject a92cff199209cbe850a2a0dc39f11a4a342c33b
diff --git a/src/include/taler_json_lib.h b/src/include/taler_json_lib.h
index ea621cea6..bc21957e8 100644
--- a/src/include/taler_json_lib.h
+++ b/src/include/taler_json_lib.h
@@ -523,19 +523,6 @@ TALER_JSON_exchange_wire_signature_check (
523 523
524 524
525/** 525/**
526 * Validate payto:// account URL (only account information,
527 * wire subject and amount are ignored).
528 *
529 * @param payto_uri URL to parse
530 * @return #GNUNET_YES if @a payto_uri is a valid payto://iban URI
531 * #GNUNET_NO if @a payto_uri is a payto URI of an unsupported type (but may be valid)
532 * #GNUNET_SYSERR if the account incorrect or this is not a payto://-URI at all
533 */
534enum GNUNET_GenericReturnValue
535TALER_JSON_validate_payto (const char *payto_uri);
536
537
538/**
539 * Create a signed wire statement for the given account. 526 * Create a signed wire statement for the given account.
540 * 527 *
541 * @param payto_uri account specification 528 * @param payto_uri account specification
diff --git a/src/include/taler_util.h b/src/include/taler_util.h
index ad2f46a45..33c126cbc 100644
--- a/src/include/taler_util.h
+++ b/src/include/taler_util.h
@@ -313,6 +313,7 @@ TALER_payto_get_method (const char *payto_uri);
313char * 313char *
314TALER_xtalerbank_account_from_payto (const char *payto); 314TALER_xtalerbank_account_from_payto (const char *payto);
315 315
316
316/** 317/**
317 * Extract the subject value from the URI parameters. 318 * Extract the subject value from the URI parameters.
318 * 319 *
@@ -323,6 +324,18 @@ TALER_xtalerbank_account_from_payto (const char *payto);
323char * 324char *
324TALER_payto_get_subject (const char *payto_uri); 325TALER_payto_get_subject (const char *payto_uri);
325 326
327
328/**
329 * Check that a payto:// URI is well-formed.
330 *
331 * @param payto_uri the URL to check
332 * @return NULL on success, otherwise an error
333 * message to be freed by the caller!
334 */
335char *
336TALER_payto_validate (const char *payto_uri);
337
338
326/** 339/**
327 * Possible values for a binary filter. 340 * Possible values for a binary filter.
328 */ 341 */
diff --git a/src/json/json_wire.c b/src/json/json_wire.c
index a49e7a54a..3d7e8a81b 100644
--- a/src/json/json_wire.c
+++ b/src/json/json_wire.c
@@ -24,378 +24,6 @@
24#include "taler_json_lib.h" 24#include "taler_json_lib.h"
25 25
26 26
27/* Taken from GNU gettext */
28
29/**
30 * Entry in the country table.
31 */
32struct CountryTableEntry
33{
34 /**
35 * 2-Character international country code.
36 */
37 const char *code;
38
39 /**
40 * Long English name of the country.
41 */
42 const char *english;
43};
44
45
46/* Keep the following table in sync with gettext.
47 WARNING: the entries should stay sorted according to the code */
48/**
49 * List of country codes.
50 */
51static const struct CountryTableEntry country_table[] = {
52 { "AE", "U.A.E." },
53 { "AF", "Afghanistan" },
54 { "AL", "Albania" },
55 { "AM", "Armenia" },
56 { "AN", "Netherlands Antilles" },
57 { "AR", "Argentina" },
58 { "AT", "Austria" },
59 { "AU", "Australia" },
60 { "AZ", "Azerbaijan" },
61 { "BA", "Bosnia and Herzegovina" },
62 { "BD", "Bangladesh" },
63 { "BE", "Belgium" },
64 { "BG", "Bulgaria" },
65 { "BH", "Bahrain" },
66 { "BN", "Brunei Darussalam" },
67 { "BO", "Bolivia" },
68 { "BR", "Brazil" },
69 { "BT", "Bhutan" },
70 { "BY", "Belarus" },
71 { "BZ", "Belize" },
72 { "CA", "Canada" },
73 { "CG", "Congo" },
74 { "CH", "Switzerland" },
75 { "CI", "Cote d'Ivoire" },
76 { "CL", "Chile" },
77 { "CM", "Cameroon" },
78 { "CN", "People's Republic of China" },
79 { "CO", "Colombia" },
80 { "CR", "Costa Rica" },
81 { "CS", "Serbia and Montenegro" },
82 { "CZ", "Czech Republic" },
83 { "DE", "Germany" },
84 { "DK", "Denmark" },
85 { "DO", "Dominican Republic" },
86 { "DZ", "Algeria" },
87 { "EC", "Ecuador" },
88 { "EE", "Estonia" },
89 { "EG", "Egypt" },
90 { "ER", "Eritrea" },
91 { "ES", "Spain" },
92 { "ET", "Ethiopia" },
93 { "FI", "Finland" },
94 { "FO", "Faroe Islands" },
95 { "FR", "France" },
96 { "GB", "United Kingdom" },
97 { "GD", "Caribbean" },
98 { "GE", "Georgia" },
99 { "GL", "Greenland" },
100 { "GR", "Greece" },
101 { "GT", "Guatemala" },
102 { "HK", "Hong Kong" },
103 { "HK", "Hong Kong S.A.R." },
104 { "HN", "Honduras" },
105 { "HR", "Croatia" },
106 { "HT", "Haiti" },
107 { "HU", "Hungary" },
108 { "ID", "Indonesia" },
109 { "IE", "Ireland" },
110 { "IL", "Israel" },
111 { "IN", "India" },
112 { "IQ", "Iraq" },
113 { "IR", "Iran" },
114 { "IS", "Iceland" },
115 { "IT", "Italy" },
116 { "JM", "Jamaica" },
117 { "JO", "Jordan" },
118 { "JP", "Japan" },
119 { "KE", "Kenya" },
120 { "KG", "Kyrgyzstan" },
121 { "KH", "Cambodia" },
122 { "KR", "South Korea" },
123 { "KW", "Kuwait" },
124 { "KZ", "Kazakhstan" },
125 { "LA", "Laos" },
126 { "LB", "Lebanon" },
127 { "LI", "Liechtenstein" },
128 { "LK", "Sri Lanka" },
129 { "LT", "Lithuania" },
130 { "LU", "Luxembourg" },
131 { "LV", "Latvia" },
132 { "LY", "Libya" },
133 { "MA", "Morocco" },
134 { "MC", "Principality of Monaco" },
135 { "MD", "Moldava" },
136 { "MD", "Moldova" },
137 { "ME", "Montenegro" },
138 { "MK", "Former Yugoslav Republic of Macedonia" },
139 { "ML", "Mali" },
140 { "MM", "Myanmar" },
141 { "MN", "Mongolia" },
142 { "MO", "Macau S.A.R." },
143 { "MT", "Malta" },
144 { "MV", "Maldives" },
145 { "MX", "Mexico" },
146 { "MY", "Malaysia" },
147 { "NG", "Nigeria" },
148 { "NI", "Nicaragua" },
149 { "NL", "Netherlands" },
150 { "NO", "Norway" },
151 { "NP", "Nepal" },
152 { "NZ", "New Zealand" },
153 { "OM", "Oman" },
154 { "PA", "Panama" },
155 { "PE", "Peru" },
156 { "PH", "Philippines" },
157 { "PK", "Islamic Republic of Pakistan" },
158 { "PL", "Poland" },
159 { "PR", "Puerto Rico" },
160 { "PT", "Portugal" },
161 { "PY", "Paraguay" },
162 { "QA", "Qatar" },
163 { "RE", "Reunion" },
164 { "RO", "Romania" },
165 { "RS", "Serbia" },
166 { "RU", "Russia" },
167 { "RW", "Rwanda" },
168 { "SA", "Saudi Arabia" },
169 { "SE", "Sweden" },
170 { "SG", "Singapore" },
171 { "SI", "Slovenia" },
172 { "SK", "Slovak" },
173 { "SN", "Senegal" },
174 { "SO", "Somalia" },
175 { "SR", "Suriname" },
176 { "SV", "El Salvador" },
177 { "SY", "Syria" },
178 { "TH", "Thailand" },
179 { "TJ", "Tajikistan" },
180 { "TM", "Turkmenistan" },
181 { "TN", "Tunisia" },
182 { "TR", "Turkey" },
183 { "TT", "Trinidad and Tobago" },
184 { "TW", "Taiwan" },
185 { "TZ", "Tanzania" },
186 { "UA", "Ukraine" },
187 { "US", "United States" },
188 { "UY", "Uruguay" },
189 { "VA", "Vatican" },
190 { "VE", "Venezuela" },
191 { "VN", "Viet Nam" },
192 { "YE", "Yemen" },
193 { "ZA", "South Africa" },
194 { "ZW", "Zimbabwe" }
195};
196
197
198/**
199 * Country code comparator function, for binary search with bsearch().
200 *
201 * @param ptr1 pointer to a `struct table_entry`
202 * @param ptr2 pointer to a `struct table_entry`
203 * @return result of memcmp()'ing the 2-digit country codes of the entries
204 */
205static int
206cmp_country_code (const void *ptr1,
207 const void *ptr2)
208{
209 const struct CountryTableEntry *cc1 = ptr1;
210 const struct CountryTableEntry *cc2 = ptr2;
211
212 return memcmp (cc1->code,
213 cc2->code,
214 2);
215}
216
217
218/**
219 * Validates given IBAN according to the European Banking Standards. See:
220 * http://www.europeanpaymentscouncil.eu/documents/ECBS%20IBAN%20standard%20EBS204_V3.2.pdf
221 *
222 * @param iban the IBAN number to validate
223 * @return #GNUNET_YES if correctly formatted; #GNUNET_NO if not
224 */
225static enum GNUNET_GenericReturnValue
226validate_iban (const char *iban)
227{
228 char cc[2];
229 char ibancpy[35];
230 struct CountryTableEntry cc_entry;
231 unsigned int len;
232 char *nbuf;
233 unsigned long long dividend;
234 unsigned long long remainder;
235 unsigned int i;
236 unsigned int j;
237
238 len = strlen (iban);
239 if (len > 34)
240 {
241 GNUNET_log (GNUNET_ERROR_TYPE_INFO,
242 "IBAN number too long to be valid\n");
243 return GNUNET_NO;
244 }
245 memcpy (cc, iban, 2);
246 memcpy (ibancpy, iban + 4, len - 4);
247 memcpy (ibancpy + len - 4, iban, 4);
248 ibancpy[len] = '\0';
249 cc_entry.code = cc;
250 cc_entry.english = NULL;
251 if (NULL ==
252 bsearch (&cc_entry,
253 country_table,
254 sizeof (country_table) / sizeof (struct CountryTableEntry),
255 sizeof (struct CountryTableEntry),
256 &cmp_country_code))
257 {
258 GNUNET_log (GNUNET_ERROR_TYPE_INFO,
259 "Country code `%c%c' not supported\n",
260 cc[0],
261 cc[1]);
262 return GNUNET_NO;
263 }
264 nbuf = GNUNET_malloc ((len * 2) + 1);
265 for (i = 0, j = 0; i < len; i++)
266 {
267 if (isalpha ((unsigned char) ibancpy[i]))
268 {
269 if (2 != snprintf (&nbuf[j],
270 3,
271 "%2u",
272 (ibancpy[i] - 'A' + 10)))
273 {
274 GNUNET_free (nbuf);
275 return GNUNET_NO;
276 }
277 j += 2;
278 continue;
279 }
280 nbuf[j] = ibancpy[i];
281 j++;
282 }
283 for (j = 0; '\0' != nbuf[j]; j++)
284 {
285 if (! isdigit ( (unsigned char) nbuf[j]))
286 {
287 GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
288 "IBAN `%s' didn't convert to numeric format\n",
289 iban);
290 return GNUNET_NO;
291 }
292 }
293 GNUNET_assert (sizeof(dividend) >= 8);
294 remainder = 0;
295 for (unsigned int i = 0; i<j; i += 16)
296 {
297 int nread;
298
299 if (1 !=
300 sscanf (&nbuf[i],
301 "%16llu %n",
302 &dividend,
303 &nread))
304 {
305 GNUNET_free (nbuf);
306 GNUNET_break_op (0);
307 return GNUNET_NO;
308 }
309 if (0 != remainder)
310 dividend += remainder * (pow (10, nread));
311 remainder = dividend % 97;
312 }
313 GNUNET_free (nbuf);
314 if (1 != remainder)
315 {
316 GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
317 "IBAN `%s' has the wrong checksum\n",
318 iban);
319 return GNUNET_NO;
320 }
321 return GNUNET_YES;
322}
323
324
325/**
326 * Validate payto://iban/ account URL (only account information,
327 * wire subject and amount are ignored).
328 *
329 * @param account_url URL to parse
330 * @return #GNUNET_YES if @a account_url is a valid payto://iban URI,
331 * #GNUNET_NO if @a account_url is a payto URI of a different type,
332 * #GNUNET_SYSERR if the IBAN (checksum) is incorrect or this is not a payto://-URI
333 */
334static enum GNUNET_GenericReturnValue
335validate_payto_iban (const char *account_url)
336{
337 const char *iban;
338 const char *q;
339 char *result;
340
341#define IBAN_PREFIX "payto://iban/"
342 if (0 != strncasecmp (account_url,
343 IBAN_PREFIX,
344 strlen (IBAN_PREFIX)))
345 return GNUNET_NO;
346
347 iban = strrchr (account_url, '/') + 1;
348#undef IBAN_PREFIX
349 q = strchr (iban,
350 '?');
351 if (NULL != q)
352 {
353 result = GNUNET_strndup (iban,
354 q - iban);
355 }
356 else
357 {
358 result = GNUNET_strdup (iban);
359 }
360 if (GNUNET_OK !=
361 validate_iban (result))
362 {
363 GNUNET_free (result);
364 return GNUNET_SYSERR;
365 }
366 GNUNET_free (result);
367 return GNUNET_YES;
368}
369
370
371enum GNUNET_GenericReturnValue
372TALER_JSON_validate_payto (const char *payto_uri)
373{
374 enum GNUNET_GenericReturnValue ret;
375 const char *start;
376 const char *end;
377
378#define PAYTO_PREFIX "payto://"
379 if (0 != strncasecmp (payto_uri,
380 PAYTO_PREFIX,
381 strlen (PAYTO_PREFIX)))
382 return GNUNET_SYSERR; /* not payto */
383 start = &payto_uri[strlen (PAYTO_PREFIX)];
384#undef PAYTO_PREFIX
385 end = strchr (start,
386 (unsigned char) '/');
387 if (NULL == end)
388 return GNUNET_SYSERR;
389 if (GNUNET_NO != (ret = validate_payto_iban (payto_uri)))
390 {
391 GNUNET_break_op (GNUNET_SYSERR != ret);
392 return ret; /* got a definitive answer */
393 }
394 /* Insert other bank account validation methods here later! */
395 return GNUNET_NO;
396}
397
398
399/** 27/**
400 * Compute the hash of the given wire details. The resulting 28 * Compute the hash of the given wire details. The resulting
401 * hash is what is put into the contract. 29 * hash is what is put into the contract.
@@ -429,11 +57,19 @@ TALER_JSON_merchant_wire_signature_hash (const json_t *wire_s,
429 GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, 57 GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
430 "Validating `%s'\n", 58 "Validating `%s'\n",
431 payto_uri); 59 payto_uri);
432 if (GNUNET_SYSERR ==
433 TALER_JSON_validate_payto (payto_uri))
434 { 60 {
435 GNUNET_break_op (0); 61 char *err;
436 return GNUNET_SYSERR; 62
63 err = TALER_payto_validate (payto_uri);
64 if (NULL != err)
65 {
66 GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
67 "URI `%s' ill-formed: %s\n",
68 payto_uri,
69 err);
70 GNUNET_free (err);
71 return GNUNET_SYSERR;
72 }
437 } 73 }
438 TALER_merchant_wire_signature_hash (payto_uri, 74 TALER_merchant_wire_signature_hash (payto_uri,
439 salt, 75 salt,
@@ -472,11 +108,19 @@ TALER_JSON_exchange_wire_signature_check (
472 return GNUNET_SYSERR; 108 return GNUNET_SYSERR;
473 } 109 }
474 110
475 if (GNUNET_SYSERR ==
476 TALER_JSON_validate_payto (payto_uri))
477 { 111 {
478 GNUNET_break_op (0); 112 char *err;
479 return GNUNET_SYSERR; 113
114 err = TALER_payto_validate (payto_uri);
115 if (NULL != err)
116 {
117 GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
118 "URI `%s' ill-formed: %s\n",
119 payto_uri,
120 err);
121 GNUNET_free (err);
122 return GNUNET_SYSERR;
123 }
480 } 124 }
481 125
482 return TALER_exchange_wire_signature_check (payto_uri, 126 return TALER_exchange_wire_signature_check (payto_uri,
@@ -498,11 +142,16 @@ TALER_JSON_exchange_wire_signature_make (
498 const struct TALER_MasterPrivateKeyP *master_priv) 142 const struct TALER_MasterPrivateKeyP *master_priv)
499{ 143{
500 struct TALER_MasterSignatureP master_sig; 144 struct TALER_MasterSignatureP master_sig;
145 char *err;
501 146
502 if (GNUNET_SYSERR == 147 if (NULL !=
503 TALER_JSON_validate_payto (payto_uri)) 148 (err = TALER_payto_validate (payto_uri)))
504 { 149 {
505 GNUNET_break_op (0); 150 GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
151 "Invalid payto URI `%s': %s\n",
152 payto_uri,
153 err);
154 GNUNET_free (err);
506 return NULL; 155 return NULL;
507 } 156 }
508 TALER_exchange_wire_signature_make (payto_uri, 157 TALER_exchange_wire_signature_make (payto_uri,
@@ -528,6 +177,7 @@ TALER_JSON_wire_to_payto (const json_t *wire_s)
528{ 177{
529 json_t *payto_o; 178 json_t *payto_o;
530 const char *payto_str; 179 const char *payto_str;
180 char *err;
531 181
532 payto_o = json_object_get (wire_s, 182 payto_o = json_object_get (wire_s,
533 "payto_uri"); 183 "payto_uri");
@@ -538,12 +188,14 @@ TALER_JSON_wire_to_payto (const json_t *wire_s)
538 "Malformed wire record encountered: lacks payto://-url\n"); 188 "Malformed wire record encountered: lacks payto://-url\n");
539 return NULL; 189 return NULL;
540 } 190 }
541 if (GNUNET_SYSERR == 191 if (NULL !=
542 TALER_JSON_validate_payto (payto_str)) 192 (err = TALER_payto_validate (payto_str)))
543 { 193 {
544 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, 194 GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
545 "Malformed wire record encountered: payto URI `%s' invalid\n", 195 "Malformed wire record encountered: payto URI `%s' invalid: %s\n",
546 payto_str); 196 payto_str,
197 err);
198 GNUNET_free (err);
547 return NULL; 199 return NULL;
548 } 200 }
549 return GNUNET_strdup (payto_str); 201 return GNUNET_strdup (payto_str);
diff --git a/src/util/payto.c b/src/util/payto.c
index 54072d667..7819184e8 100644
--- a/src/util/payto.c
+++ b/src/util/payto.c
@@ -128,3 +128,401 @@ TALER_xtalerbank_account_from_payto (const char *payto)
128 return GNUNET_strndup (beg, 128 return GNUNET_strndup (beg,
129 end - beg); 129 end - beg);
130} 130}
131
132
133/* Country table taken from GNU gettext */
134
135/**
136 * Entry in the country table.
137 */
138struct CountryTableEntry
139{
140 /**
141 * 2-Character international country code.
142 */
143 const char *code;
144
145 /**
146 * Long English name of the country.
147 */
148 const char *english;
149};
150
151
152/* Keep the following table in sync with gettext.
153 WARNING: the entries should stay sorted according to the code */
154/**
155 * List of country codes.
156 */
157static const struct CountryTableEntry country_table[] = {
158 { "AE", "U.A.E." },
159 { "AF", "Afghanistan" },
160 { "AL", "Albania" },
161 { "AM", "Armenia" },
162 { "AN", "Netherlands Antilles" },
163 { "AR", "Argentina" },
164 { "AT", "Austria" },
165 { "AU", "Australia" },
166 { "AZ", "Azerbaijan" },
167 { "BA", "Bosnia and Herzegovina" },
168 { "BD", "Bangladesh" },
169 { "BE", "Belgium" },
170 { "BG", "Bulgaria" },
171 { "BH", "Bahrain" },
172 { "BN", "Brunei Darussalam" },
173 { "BO", "Bolivia" },
174 { "BR", "Brazil" },
175 { "BT", "Bhutan" },
176 { "BY", "Belarus" },
177 { "BZ", "Belize" },
178 { "CA", "Canada" },
179 { "CG", "Congo" },
180 { "CH", "Switzerland" },
181 { "CI", "Cote d'Ivoire" },
182 { "CL", "Chile" },
183 { "CM", "Cameroon" },
184 { "CN", "People's Republic of China" },
185 { "CO", "Colombia" },
186 { "CR", "Costa Rica" },
187 { "CS", "Serbia and Montenegro" },
188 { "CZ", "Czech Republic" },
189 { "DE", "Germany" },
190 { "DK", "Denmark" },
191 { "DO", "Dominican Republic" },
192 { "DZ", "Algeria" },
193 { "EC", "Ecuador" },
194 { "EE", "Estonia" },
195 { "EG", "Egypt" },
196 { "ER", "Eritrea" },
197 { "ES", "Spain" },
198 { "ET", "Ethiopia" },
199 { "FI", "Finland" },
200 { "FO", "Faroe Islands" },
201 { "FR", "France" },
202 { "GB", "United Kingdom" },
203 { "GD", "Caribbean" },
204 { "GE", "Georgia" },
205 { "GL", "Greenland" },
206 { "GR", "Greece" },
207 { "GT", "Guatemala" },
208 { "HK", "Hong Kong" },
209 { "HK", "Hong Kong S.A.R." },
210 { "HN", "Honduras" },
211 { "HR", "Croatia" },
212 { "HT", "Haiti" },
213 { "HU", "Hungary" },
214 { "ID", "Indonesia" },
215 { "IE", "Ireland" },
216 { "IL", "Israel" },
217 { "IN", "India" },
218 { "IQ", "Iraq" },
219 { "IR", "Iran" },
220 { "IS", "Iceland" },
221 { "IT", "Italy" },
222 { "JM", "Jamaica" },
223 { "JO", "Jordan" },
224 { "JP", "Japan" },
225 { "KE", "Kenya" },
226 { "KG", "Kyrgyzstan" },
227 { "KH", "Cambodia" },
228 { "KR", "South Korea" },
229 { "KW", "Kuwait" },
230 { "KZ", "Kazakhstan" },
231 { "LA", "Laos" },
232 { "LB", "Lebanon" },
233 { "LI", "Liechtenstein" },
234 { "LK", "Sri Lanka" },
235 { "LT", "Lithuania" },
236 { "LU", "Luxembourg" },
237 { "LV", "Latvia" },
238 { "LY", "Libya" },
239 { "MA", "Morocco" },
240 { "MC", "Principality of Monaco" },
241 { "MD", "Moldava" },
242 { "MD", "Moldova" },
243 { "ME", "Montenegro" },
244 { "MK", "Former Yugoslav Republic of Macedonia" },
245 { "ML", "Mali" },
246 { "MM", "Myanmar" },
247 { "MN", "Mongolia" },
248 { "MO", "Macau S.A.R." },
249 { "MT", "Malta" },
250 { "MV", "Maldives" },
251 { "MX", "Mexico" },
252 { "MY", "Malaysia" },
253 { "NG", "Nigeria" },
254 { "NI", "Nicaragua" },
255 { "NL", "Netherlands" },
256 { "NO", "Norway" },
257 { "NP", "Nepal" },
258 { "NZ", "New Zealand" },
259 { "OM", "Oman" },
260 { "PA", "Panama" },
261 { "PE", "Peru" },
262 { "PH", "Philippines" },
263 { "PK", "Islamic Republic of Pakistan" },
264 { "PL", "Poland" },
265 { "PR", "Puerto Rico" },
266 { "PT", "Portugal" },
267 { "PY", "Paraguay" },
268 { "QA", "Qatar" },
269 { "RE", "Reunion" },
270 { "RO", "Romania" },
271 { "RS", "Serbia" },
272 { "RU", "Russia" },
273 { "RW", "Rwanda" },
274 { "SA", "Saudi Arabia" },
275 { "SE", "Sweden" },
276 { "SG", "Singapore" },
277 { "SI", "Slovenia" },
278 { "SK", "Slovak" },
279 { "SN", "Senegal" },
280 { "SO", "Somalia" },
281 { "SR", "Suriname" },
282 { "SV", "El Salvador" },
283 { "SY", "Syria" },
284 { "TH", "Thailand" },
285 { "TJ", "Tajikistan" },
286 { "TM", "Turkmenistan" },
287 { "TN", "Tunisia" },
288 { "TR", "Turkey" },
289 { "TT", "Trinidad and Tobago" },
290 { "TW", "Taiwan" },
291 { "TZ", "Tanzania" },
292 { "UA", "Ukraine" },
293 { "US", "United States" },
294 { "UY", "Uruguay" },
295 { "VA", "Vatican" },
296 { "VE", "Venezuela" },
297 { "VN", "Viet Nam" },
298 { "YE", "Yemen" },
299 { "ZA", "South Africa" },
300 { "ZW", "Zimbabwe" }
301};
302
303
304/**
305 * Country code comparator function, for binary search with bsearch().
306 *
307 * @param ptr1 pointer to a `struct table_entry`
308 * @param ptr2 pointer to a `struct table_entry`
309 * @return result of memcmp()'ing the 2-digit country codes of the entries
310 */
311static int
312cmp_country_code (const void *ptr1,
313 const void *ptr2)
314{
315 const struct CountryTableEntry *cc1 = ptr1;
316 const struct CountryTableEntry *cc2 = ptr2;
317
318 return memcmp (cc1->code,
319 cc2->code,
320 2);
321}
322
323
324/**
325 * Validates given IBAN according to the European Banking Standards. See:
326 * http://www.europeanpaymentscouncil.eu/documents/ECBS%20IBAN%20standard%20EBS204_V3.2.pdf
327 *
328 * @param iban the IBAN number to validate
329 * @return NULL if correctly formatted; error message if not
330 */
331static char *
332validate_iban (const char *iban)
333{
334 char cc[2];
335 char ibancpy[35];
336 struct CountryTableEntry cc_entry;
337 unsigned int len;
338 char *nbuf;
339 unsigned long long dividend;
340 unsigned long long remainder;
341 unsigned int i;
342 unsigned int j;
343
344 len = strlen (iban);
345 if (len > 34)
346 return GNUNET_strdup ("IBAN number too long to be valid");
347 memcpy (cc, iban, 2);
348 memcpy (ibancpy, iban + 4, len - 4);
349 memcpy (ibancpy + len - 4, iban, 4);
350 ibancpy[len] = '\0';
351 cc_entry.code = cc;
352 cc_entry.english = NULL;
353 if (NULL ==
354 bsearch (&cc_entry,
355 country_table,
356 sizeof (country_table) / sizeof (struct CountryTableEntry),
357 sizeof (struct CountryTableEntry),
358 &cmp_country_code))
359 {
360 char *msg;
361
362 GNUNET_asprintf (&msg,
363 "Country code `%c%c' not supported\n",
364 cc[0],
365 cc[1]);
366 return msg;
367 }
368 nbuf = GNUNET_malloc ((len * 2) + 1);
369 for (i = 0, j = 0; i < len; i++)
370 {
371 if (isalpha ((unsigned char) ibancpy[i]))
372 {
373 if (2 != snprintf (&nbuf[j],
374 3,
375 "%2u",
376 (ibancpy[i] - 'A' + 10)))
377 {
378 GNUNET_break (0);
379 return GNUNET_strdup ("internal invariant violation");
380 }
381 j += 2;
382 continue;
383 }
384 nbuf[j] = ibancpy[i];
385 j++;
386 }
387 for (j = 0; '\0' != nbuf[j]; j++)
388 {
389 if (! isdigit ( (unsigned char) nbuf[j]))
390 {
391 char *msg;
392
393 GNUNET_asprintf (&msg,
394 "digit expected at `%s'",
395 &nbuf[j]);
396 GNUNET_free (nbuf);
397 return msg;
398 }
399 }
400 GNUNET_assert (sizeof(dividend) >= 8);
401 remainder = 0;
402 for (unsigned int i = 0; i<j; i += 16)
403 {
404 int nread;
405
406 if (1 !=
407 sscanf (&nbuf[i],
408 "%16llu %n",
409 &dividend,
410 &nread))
411 {
412 char *msg;
413
414 GNUNET_asprintf (&msg,
415 "wrong input for checksum calculation at `%s'",
416 &nbuf[i]);
417 GNUNET_free (nbuf);
418 return msg;
419 }
420 if (0 != remainder)
421 dividend += remainder * (pow (10, nread));
422 remainder = dividend % 97;
423 }
424 GNUNET_free (nbuf);
425 if (1 != remainder)
426 return GNUNET_strdup ("IBAN checksum is wrong");
427 return NULL;
428}
429
430
431/**
432 * Validate payto://iban/ account URL (only account information,
433 * wire subject and amount are ignored).
434 *
435 * @param account_url URL to parse
436 * @return NULL on success, otherwise an error message
437 * to be freed by the caller
438 */
439static char *
440validate_payto_iban (const char *account_url)
441{
442 const char *iban;
443 const char *q;
444 char *result;
445 char *err;
446
447#define IBAN_PREFIX "payto://iban/"
448 if (0 != strncasecmp (account_url,
449 IBAN_PREFIX,
450 strlen (IBAN_PREFIX)))
451 return NULL; /* not an IBAN */
452
453 iban = strrchr (account_url, '/') + 1;
454#undef IBAN_PREFIX
455 q = strchr (iban,
456 '?');
457 if (NULL != q)
458 {
459 result = GNUNET_strndup (iban,
460 q - iban);
461 }
462 else
463 {
464 result = GNUNET_strdup (iban);
465 }
466 if (NULL !=
467 (err = validate_iban (result)))
468 {
469 GNUNET_free (result);
470 return err;
471 }
472 GNUNET_free (result);
473 return NULL;
474}
475
476
477/**
478 * Check that a payto:// URI is well-formed.
479 *
480 * @param payto_uri the URL to check
481 * @return NULL on success, otherwise an error
482 * message to be freed by the caller!
483 */
484char *
485TALER_payto_validate (const char *payto_uri)
486{
487 char *ret;
488 const char *start;
489 const char *end;
490
491 if (0 != strncasecmp (payto_uri,
492 PAYTO,
493 strlen (PAYTO)))
494 return GNUNET_strdup ("invalid prefix");
495 for (unsigned int i = 0; '\0' != payto_uri[i]; i++)
496 {
497 /* This is more strict than RFC 8905, alas we do not need to support messages/instructions/etc.,
498 and it is generally better to start with a narrow whitelist; we can be more permissive later ...*/
499#define ALLOWED_CHARACTERS \
500 "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789/:&?-.,="
501 if (NULL == strchr (ALLOWED_CHARACTERS,
502 (int) payto_uri[i]))
503 {
504 char *ret;
505
506 GNUNET_asprintf (&ret,
507 "Encountered invalid character `%c' at offset %u in payto URI `%s'",
508 payto_uri[i],
509 i,
510 payto_uri);
511 return ret;
512 }
513#undef ALLOWED_CHARACTERS
514 }
515
516 start = &payto_uri[strlen (PAYTO)];
517 end = strchr (start,
518 (unsigned char) '/');
519 if (NULL == end)
520 return GNUNET_strdup ("missing '/' in payload");
521
522 if (NULL != (ret = validate_payto_iban (payto_uri)))
523 return ret; /* got a definitive answer */
524
525 /* Insert other bank account validation methods here later! */
526
527 return NULL;
528}