diff options
Diffstat (limited to 'src/util/payto.c')
-rw-r--r-- | src/util/payto.c | 398 |
1 files changed, 398 insertions, 0 deletions
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 | */ | ||
138 | struct 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 | */ | ||
157 | static 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 | */ | ||
311 | static int | ||
312 | cmp_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 | */ | ||
331 | static char * | ||
332 | validate_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 | ÷nd, | ||
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 | */ | ||
439 | static char * | ||
440 | validate_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 | */ | ||
484 | char * | ||
485 | TALER_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 | } | ||