anastasis_authorization_plugin_email.c (25479B)
1 /* 2 This file is part of Anastasis 3 Copyright (C) 2019-2021 Anastasis SARL 4 5 Anastasis 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 Anastasis 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 Anastasis; see the file COPYING.GPL. If not, see <http://www.gnu.org/licenses/> 15 */ 16 /** 17 * @file anastasis_authorization_plugin_email.c 18 * @brief authorization plugin email based 19 * @author Dominik Meister 20 */ 21 #include "platform.h" 22 #include "anastasis_authorization_plugin.h" 23 #include <taler/taler_mhd_lib.h> 24 #include <taler/taler_json_lib.h> 25 #include <regex.h> 26 #include "anastasis_util_lib.h" 27 #include <gnunet/gnunet_db_lib.h> 28 #include "anastasis_database_lib.h" 29 30 /** 31 * How many retries do we allow per code? 32 */ 33 #define INITIAL_RETRY_COUNTER 3 34 35 /** 36 * Saves the State of a authorization plugin. 37 */ 38 struct Email_Context 39 { 40 41 /** 42 * Command which is executed to run the plugin (some bash script or a 43 * command line argument) 44 */ 45 char *auth_command; 46 47 /** 48 * Regex for email address validation. 49 */ 50 regex_t regex; 51 52 /** 53 * Messages of the plugin, read from a resource file. 54 */ 55 json_t *messages; 56 57 /** 58 * Context we operate in. 59 */ 60 const struct ANASTASIS_AuthorizationContext *ac; 61 62 }; 63 64 65 /** 66 * Saves the state of a authorization process 67 */ 68 struct ANASTASIS_AUTHORIZATION_State 69 { 70 /** 71 * Public key of the challenge which is authorised 72 */ 73 struct ANASTASIS_CRYPTO_TruthUUIDP truth_uuid; 74 75 /** 76 * Code which is sent to the user. 77 */ 78 uint64_t code; 79 80 /** 81 * Our plugin context. 82 */ 83 struct Email_Context *ctx; 84 85 /** 86 * Function to call when we made progress. 87 */ 88 GNUNET_SCHEDULER_TaskCallback trigger; 89 90 /** 91 * Closure for @e trigger. 92 */ 93 void *trigger_cls; 94 95 /** 96 * holds the truth information 97 */ 98 char *email; 99 100 /** 101 * Handle to the helper process. 102 */ 103 struct GNUNET_Process *child; 104 105 /** 106 * Handle to wait for @e child 107 */ 108 struct GNUNET_ChildWaitHandle *cwh; 109 110 /** 111 * Our client connection, set if suspended. 112 */ 113 struct MHD_Connection *connection; 114 115 /** 116 * Message to send. 117 */ 118 char *msg; 119 120 /** 121 * Offset of transmission in msg. 122 */ 123 size_t msg_off; 124 125 /** 126 * Exit code from helper. 127 */ 128 long unsigned int exit_code; 129 130 /** 131 * How did the helper die? 132 */ 133 enum GNUNET_OS_ProcessStatusType pst; 134 135 }; 136 137 138 /** 139 * Returned by #get_message() when the configured messages file has no entry 140 * for the requested ID. This is an installation error, but it must not be 141 * allowed to reach GNUNET_asprintf() as a NULL format string. The text 142 * deliberately contains no printf conversions, so substituting it for any 143 * template is safe whatever argument list the caller passes. 144 */ 145 #define MISSING_MESSAGE \ 146 "The provider is misconfigured: a message template is missing." 147 148 149 /** 150 * Obtain internationalized message @a msg_id from @a ctx using 151 * language preferences of @a conn. 152 * 153 * @param messages JSON object to lookup message from 154 * @param conn connection to lookup message for 155 * @param msg_id unique message ID 156 * @return the requested message, or #MISSING_MESSAGE if it was not 157 * configured; never NULL 158 */ 159 static const char * 160 get_message (const json_t *messages, 161 struct MHD_Connection *conn, 162 const char *msg_id) 163 { 164 const char *accept_lang; 165 166 accept_lang = MHD_lookup_connection_value (conn, 167 MHD_HEADER_KIND, 168 MHD_HTTP_HEADER_ACCEPT_LANGUAGE); 169 if (NULL == accept_lang) 170 accept_lang = "en_US"; 171 { 172 const char *ret; 173 struct GNUNET_JSON_Specification spec[] = { 174 TALER_JSON_spec_i18n_string (msg_id, 175 accept_lang, 176 &ret), 177 GNUNET_JSON_spec_end () 178 }; 179 180 if (GNUNET_OK != 181 GNUNET_JSON_parse (messages, 182 spec, 183 NULL, NULL)) 184 { 185 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, 186 "Message `%s' is missing from the configured messages file\n", 187 msg_id); 188 GNUNET_JSON_parse_free (spec); 189 return MISSING_MESSAGE; 190 } 191 GNUNET_JSON_parse_free (spec); 192 if (NULL == ret) 193 { 194 /* The parser of TALER_JSON_spec_i18n_string returns #GNUNET_OK even 195 when the field is absent or is not a string, in which case it stores 196 NULL; the check above therefore never fires for a missing message and 197 this one is what keeps NULL out of the format argument of the 198 GNUNET_asprintf() calls below. */ 199 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, 200 "Message `%s' is missing or not a string in the configured messages file\n", 201 msg_id); 202 return MISSING_MESSAGE; 203 } 204 return ret; 205 } 206 } 207 208 209 /** 210 * Check that @a value can be passed to the helper as a positional argument. 211 * The address is handed to #GNUNET_process_run_command_va(), i.e. it becomes 212 * argv[1] of the (site-supplied) command; an address starting with '-' would 213 * be read as an option by whatever that command is. The e-mail grammar does 214 * allow a leading '-' in the local part, so this has to be screened out 215 * explicitly. Not every helper honours "--", so we refuse anything 216 * option-shaped rather than rely on it. 217 * 218 * @param value address to check, may be NULL 219 * @return true if @a value is safe to pass as a positional argument 220 */ 221 static bool 222 is_safe_argument (const char *value) 223 { 224 return ( (NULL != value) && 225 ('-' != value[0]) ); 226 } 227 228 229 /** 230 * Validate @a data is a well-formed input into the challenge method, 231 * i.e. @a data is a well-formed phone number for sending an SMS, or 232 * a well-formed e-mail address for sending an e-mail. Not expected to 233 * check that the phone number or e-mail account actually exists. 234 * 235 * To be possibly used before issuing a 402 payment required to the client. 236 * 237 * @param cls closure 238 * @param connection HTTP client request (for queuing response) 239 * @param mime_type mime type of @e data 240 * @param data input to validate (i.e. is it a valid phone number, etc.) 241 * @param data_length number of bytes in @a data 242 * @return #GNUNET_OK if @a data is valid, 243 * #GNUNET_NO if @a data is invalid and a reply was successfully queued on @a connection 244 * #GNUNET_SYSERR if @a data invalid but we failed to queue a reply on @a connection 245 */ 246 static enum GNUNET_GenericReturnValue 247 email_validate (void *cls, 248 struct MHD_Connection *connection, 249 const char *mime_type, 250 const char *data, 251 size_t data_length) 252 { 253 struct Email_Context *ctx = cls; 254 int regex_result; 255 bool safe; 256 char *email; 257 258 email = GNUNET_strndup (data, 259 data_length); 260 regex_result = regexec (&ctx->regex, 261 email, 262 0, 263 NULL, 264 0); 265 safe = is_safe_argument (email); 266 GNUNET_free (email); 267 if (0 != regex_result) 268 { 269 if (MHD_NO == 270 TALER_MHD_reply_with_error (connection, 271 MHD_HTTP_CONFLICT, 272 TALER_EC_ANASTASIS_EMAIL_INVALID, 273 NULL)) 274 return GNUNET_SYSERR; 275 return GNUNET_NO; 276 } 277 if (! safe) 278 { 279 if (MHD_NO == 280 TALER_MHD_reply_with_error (connection, 281 MHD_HTTP_CONFLICT, 282 TALER_EC_ANASTASIS_EMAIL_INVALID, 283 "address must not begin with `-'")) 284 return GNUNET_SYSERR; 285 return GNUNET_NO; 286 } 287 return GNUNET_OK; 288 } 289 290 291 /** 292 * Begin issuing authentication challenge to user based on @a data. 293 * I.e. start to send SMS or e-mail or launch video identification. 294 * 295 * @param cls closure 296 * @param trigger function to call when we made progress 297 * @param trigger_cls closure for @a trigger 298 * @param truth_uuid Identifier of the challenge, to be (if possible) included in the 299 * interaction with the user 300 * @param code secret code that the user has to provide back to satisfy the challenge in 301 * the main anastasis protocol 302 * @param data input to validate (i.e. is it a valid phone number, etc.) 303 * @param data_length number of bytes in @a data 304 * @return state to track progress on the authorization operation, NULL on failure 305 */ 306 static struct ANASTASIS_AUTHORIZATION_State * 307 email_start (void *cls, 308 GNUNET_SCHEDULER_TaskCallback trigger, 309 void *trigger_cls, 310 const struct ANASTASIS_CRYPTO_TruthUUIDP *truth_uuid, 311 uint64_t code, 312 const void *data, 313 size_t data_length) 314 { 315 struct Email_Context *ctx = cls; 316 struct ANASTASIS_AUTHORIZATION_State *as; 317 enum GNUNET_DB_QueryStatus qs; 318 319 /* If the user can show this challenge code, this 320 plugin is already happy (no additional 321 requirements), so mark this challenge as 322 already satisfied from the start. */ 323 qs = ANASTASIS_DB_update_to_challenge_code_satisfied ( 324 truth_uuid, 325 code); 326 if (qs <= 0) 327 { 328 GNUNET_break (0); 329 return NULL; 330 } 331 as = GNUNET_new (struct ANASTASIS_AUTHORIZATION_State); 332 as->trigger = trigger; 333 as->trigger_cls = trigger_cls; 334 as->ctx = ctx; 335 as->truth_uuid = *truth_uuid; 336 as->code = code; 337 as->email = GNUNET_strndup (data, 338 data_length); 339 return as; 340 } 341 342 343 /** 344 * Did a transmission helper report that the challenge was transmitted? 345 * 346 * Exit code 0 means the challenge was confirmed to have reached the address. 347 * The 200-210 band means a service accepted it for delivery without confirming 348 * that it arrived: 201 accepted by the provider, 202 suppressed as a duplicate 349 * of a message already in flight. 350 * 351 * The band deliberately does not start at 1: libgnunetutil reports a helper it 352 * failed to exec() as exit code 1, and that must not be mistaken for a 353 * delivery. 354 * 355 * @param exit_code exit status of the helper, which must have exited normally 356 * @return true if the challenge was transmitted 357 */ 358 static bool 359 helper_reported_success (unsigned long int exit_code) 360 { 361 return (0 == exit_code) || 362 ( (exit_code >= 200) && 363 (exit_code <= 210) ); 364 } 365 366 367 /** 368 * Map the exit status of the Email helper onto an HTTP status and error code. 369 * 370 * The helpers shipped with Challenger use a banded exit-code scheme documented 371 * in challenger-send-email(1): 0 and 200-210 mean the challenge was 372 * transmitted -- for e-mail always 201 (accepted by the mail transfer agent), 373 * since delivery to a mailbox cannot be confirmed -- 10-19 blames the address 374 * the user gave us, 20-29 is a recipient that is temporarily unreachable, 375 * 30-39 is the transmission provider and 40-49 is our own configuration. 376 * 377 * Codes we do not recognise are reported as an upstream failure rather than 378 * blamed on the user, so that a helper predating this scheme -- which used 379 * small ad-hoc exit codes -- never yields a client error. 380 * 381 * @param exit_code exit status of the helper, which must have exited normally 382 * @param[out] http_status set to the HTTP status to return 383 * @return error code to return, #TALER_EC_NONE if the challenge was transmitted 384 */ 385 static enum TALER_ErrorCode 386 classify_helper_status (unsigned long int exit_code, 387 unsigned int *http_status) 388 { 389 if (helper_reported_success (exit_code)) 390 { 391 *http_status = MHD_HTTP_OK; 392 return TALER_EC_NONE; 393 } 394 if ( (exit_code >= 10) && 395 (exit_code < 20) ) 396 { 397 *http_status = MHD_HTTP_CONFLICT; 398 return TALER_EC_ANASTASIS_EMAIL_INVALID; 399 } 400 if ( (exit_code >= 20) && 401 (exit_code < 30) ) 402 { 403 *http_status = MHD_HTTP_SERVICE_UNAVAILABLE; 404 return TALER_EC_ANASTASIS_ADDRESS_UNREACHABLE; 405 } 406 if ( (exit_code >= 40) && 407 (exit_code < 50) ) 408 { 409 *http_status = MHD_HTTP_INTERNAL_SERVER_ERROR; 410 return TALER_EC_ANASTASIS_HELPER_MISCONFIGURED; 411 } 412 *http_status = MHD_HTTP_BAD_GATEWAY; 413 return TALER_EC_ANASTASIS_EMAIL_HELPER_COMMAND_FAILED; 414 } 415 416 417 /** 418 * Function called when our Email helper has terminated. 419 * 420 * @param cls our `struct ANASTASIS_AUHTORIZATION_State` 421 * @param type type of the process 422 * @param exit_code status code of the process 423 */ 424 static void 425 email_done_cb (void *cls, 426 enum GNUNET_OS_ProcessStatusType type, 427 long unsigned int exit_code) 428 { 429 struct ANASTASIS_AUTHORIZATION_State *as = cls; 430 431 as->cwh = NULL; 432 if (NULL != as->child) 433 { 434 GNUNET_process_destroy (as->child); 435 as->child = NULL; 436 } 437 as->pst = type; 438 as->exit_code = exit_code; 439 MHD_resume_connection (as->connection); 440 as->trigger (as->trigger_cls); 441 } 442 443 444 /** 445 * Begin issuing authentication challenge to user based on @a data. 446 * I.e. start to send SMS or e-mail or launch video identification. 447 * 448 * @param as authorization state 449 * @param connection HTTP client request (for queuing response, such as redirection to video portal) 450 * @return state of the request 451 */ 452 static enum ANASTASIS_AUTHORIZATION_ChallengeResult 453 email_challenge (struct ANASTASIS_AUTHORIZATION_State *as, 454 struct MHD_Connection *connection) 455 { 456 enum MHD_Result mres; 457 const char *mime; 458 const char *lang; 459 460 /* Re-checked here and not only in #email_validate(), because truths stored 461 before that check existed would otherwise still reach the helper. */ 462 if (! is_safe_argument (as->email)) 463 { 464 GNUNET_break_op (0); 465 mres = TALER_MHD_reply_with_error (connection, 466 MHD_HTTP_INTERNAL_SERVER_ERROR, 467 TALER_EC_ANASTASIS_EMAIL_INVALID, 468 "address must not begin with `-'"); 469 if (MHD_YES != mres) 470 return ANASTASIS_AUTHORIZATION_CRES_FAILED_REPLY_FAILED; 471 return ANASTASIS_AUTHORIZATION_CRES_FAILED; 472 } 473 mime = MHD_lookup_connection_value (connection, 474 MHD_HEADER_KIND, 475 MHD_HTTP_HEADER_ACCEPT); 476 if (NULL == mime) 477 mime = "text/plain"; 478 lang = MHD_lookup_connection_value (connection, 479 MHD_HEADER_KIND, 480 MHD_HTTP_HEADER_ACCEPT_LANGUAGE); 481 if (NULL == lang) 482 lang = "en"; 483 if (NULL == as->msg) 484 { 485 /* First time, start child process and feed pipe */ 486 struct GNUNET_DISK_PipeHandle *p; 487 struct GNUNET_DISK_FileHandle *pipe_stdin; 488 489 p = GNUNET_DISK_pipe (GNUNET_DISK_PF_BLOCKING_RW); 490 if (NULL == p) 491 { 492 mres = TALER_MHD_reply_with_error (connection, 493 MHD_HTTP_INTERNAL_SERVER_ERROR, 494 TALER_EC_ANASTASIS_EMAIL_HELPER_EXEC_FAILED, 495 "pipe"); 496 if (MHD_YES != mres) 497 return ANASTASIS_AUTHORIZATION_CRES_FAILED_REPLY_FAILED; 498 return ANASTASIS_AUTHORIZATION_CRES_FAILED; 499 } 500 as->child = GNUNET_process_create (GNUNET_OS_INHERIT_STD_ERR); 501 GNUNET_assert (GNUNET_OK == 502 GNUNET_process_set_options ( 503 as->child, 504 GNUNET_process_option_inherit_rpipe (p, 505 STDIN_FILENO))); 506 if (GNUNET_OK != 507 GNUNET_process_run_command_va (as->child, 508 as->ctx->auth_command, 509 as->ctx->auth_command, 510 as->email, 511 NULL)) 512 { 513 GNUNET_process_destroy (as->child); 514 as->child = NULL; 515 GNUNET_DISK_pipe_close (p); 516 mres = TALER_MHD_reply_with_error (connection, 517 MHD_HTTP_INTERNAL_SERVER_ERROR, 518 TALER_EC_ANASTASIS_EMAIL_HELPER_EXEC_FAILED, 519 "exec"); 520 if (MHD_YES != mres) 521 return ANASTASIS_AUTHORIZATION_CRES_FAILED_REPLY_FAILED; 522 return ANASTASIS_AUTHORIZATION_CRES_FAILED; 523 } 524 pipe_stdin = GNUNET_DISK_pipe_detach_end (p, 525 GNUNET_DISK_PIPE_END_WRITE); 526 GNUNET_assert (NULL != pipe_stdin); 527 GNUNET_DISK_pipe_close (p); 528 GNUNET_asprintf (&as->msg, 529 get_message (as->ctx->messages, 530 connection, 531 "body"), 532 ANASTASIS_pin2s (as->code), 533 ANASTASIS_CRYPTO_uuid2s (&as->truth_uuid)); 534 535 { 536 const char *off = as->msg; 537 size_t left = strlen (off); 538 539 while (0 != left) 540 { 541 ssize_t ret; 542 543 ret = GNUNET_DISK_file_write (pipe_stdin, 544 off, 545 left); 546 if (ret <= 0) 547 { 548 mres = TALER_MHD_reply_with_error (connection, 549 MHD_HTTP_INTERNAL_SERVER_ERROR, 550 TALER_EC_ANASTASIS_EMAIL_HELPER_EXEC_FAILED, 551 "write"); 552 if (MHD_YES != mres) 553 return ANASTASIS_AUTHORIZATION_CRES_FAILED_REPLY_FAILED; 554 return ANASTASIS_AUTHORIZATION_CRES_FAILED; 555 } 556 as->msg_off += ret; 557 off += ret; 558 left -= ret; 559 } 560 GNUNET_DISK_file_close (pipe_stdin); 561 } 562 as->cwh = GNUNET_wait_child (as->child, 563 &email_done_cb, 564 as); 565 as->connection = connection; 566 MHD_suspend_connection (connection); 567 return ANASTASIS_AUTHORIZATION_CRES_SUSPENDED; 568 } 569 if (NULL != as->cwh) 570 { 571 /* Spurious call, why are we here? */ 572 GNUNET_break (0); 573 MHD_suspend_connection (connection); 574 return ANASTASIS_AUTHORIZATION_CRES_SUSPENDED; 575 } 576 if ( (GNUNET_OS_PROCESS_EXITED != as->pst) || 577 (! helper_reported_success (as->exit_code)) ) 578 { 579 char es[32]; 580 unsigned int http_status; 581 enum TALER_ErrorCode ec; 582 583 if (GNUNET_OS_PROCESS_EXITED != as->pst) 584 { 585 /* Killed by a signal or otherwise abnormal: there is no exit code to 586 classify. */ 587 http_status = MHD_HTTP_BAD_GATEWAY; 588 ec = TALER_EC_ANASTASIS_EMAIL_HELPER_COMMAND_FAILED; 589 } 590 else 591 { 592 ec = classify_helper_status (as->exit_code, 593 &http_status); 594 } 595 GNUNET_snprintf (es, 596 sizeof (es), 597 "%u/%d", 598 (unsigned int) as->exit_code, 599 as->pst); 600 mres = TALER_MHD_reply_with_error (connection, 601 http_status, 602 ec, 603 es); 604 if (MHD_YES != mres) 605 return ANASTASIS_AUTHORIZATION_CRES_FAILED_REPLY_FAILED; 606 return ANASTASIS_AUTHORIZATION_CRES_FAILED; 607 } 608 609 /* Build HTTP response */ 610 { 611 struct MHD_Response *resp; 612 const char *at; 613 size_t len; 614 615 at = strchr (as->email, '@'); 616 if (NULL == at) 617 len = 0; 618 else 619 len = at - as->email; 620 621 if (0.0 < TALER_pattern_matches (mime, 622 "application/json")) 623 { 624 char *user; 625 626 user = GNUNET_strndup (as->email, 627 len); 628 resp = TALER_MHD_MAKE_JSON_PACK ( 629 GNUNET_JSON_pack_string ("challenge_type", 630 "TAN_SENT"), 631 GNUNET_JSON_pack_string ("tan_address_hint", 632 user)); 633 GNUNET_free (user); 634 } 635 else 636 { 637 size_t reply_len; 638 char *reply; 639 640 reply_len = GNUNET_asprintf (&reply, 641 get_message (as->ctx->messages, 642 connection, 643 "instructions"), 644 (unsigned int) len, 645 as->email); 646 resp = MHD_create_response_from_buffer (reply_len, 647 reply, 648 MHD_RESPMEM_MUST_COPY); 649 GNUNET_free (reply); 650 TALER_MHD_add_global_headers (resp, 651 false); 652 GNUNET_break (MHD_YES == 653 MHD_add_response_header (resp, 654 MHD_HTTP_HEADER_CONTENT_TYPE, 655 "text/plain")); 656 } 657 mres = MHD_queue_response (connection, 658 MHD_HTTP_OK, 659 resp); 660 MHD_destroy_response (resp); 661 if (MHD_YES != mres) 662 return ANASTASIS_AUTHORIZATION_CRES_SUCCESS_REPLY_FAILED; 663 return ANASTASIS_AUTHORIZATION_CRES_SUCCESS; 664 } 665 } 666 667 668 /** 669 * Free internal state associated with @a as. 670 * 671 * @param as state to clean up 672 */ 673 static void 674 email_cleanup (struct ANASTASIS_AUTHORIZATION_State *as) 675 { 676 if (NULL != as->cwh) 677 { 678 GNUNET_wait_child_cancel (as->cwh); 679 as->cwh = NULL; 680 } 681 if (NULL != as->child) 682 { 683 GNUNET_break (GNUNET_OK == 684 GNUNET_process_kill (as->child, 685 SIGKILL)); 686 GNUNET_break (GNUNET_OK == 687 GNUNET_process_wait (as->child, 688 true, 689 NULL, 690 NULL)); 691 GNUNET_process_destroy (as->child); 692 as->child = NULL; 693 } 694 GNUNET_free (as->msg); 695 GNUNET_free (as->email); 696 GNUNET_free (as); 697 } 698 699 700 /** 701 * Initialize email based authorization plugin 702 * 703 * @param cls a configuration instance 704 * @return NULL on error, otherwise a `struct ANASTASIS_AuthorizationPlugin` 705 */ 706 void * 707 libanastasis_plugin_authorization_email_init (void *cls); 708 709 /* declaration to fix compiler warning */ 710 void * 711 libanastasis_plugin_authorization_email_init (void *cls) 712 { 713 const struct ANASTASIS_AuthorizationContext *ac = cls; 714 struct ANASTASIS_AuthorizationPlugin *plugin; 715 const struct GNUNET_CONFIGURATION_Handle *cfg = ac->cfg; 716 struct Email_Context *ctx; 717 718 ctx = GNUNET_new (struct Email_Context); 719 ctx->ac = ac; 720 { 721 char *fn; 722 json_error_t err; 723 char *tmp; 724 725 tmp = GNUNET_OS_installation_get_path (ANASTASIS_project_data (), 726 GNUNET_OS_IPK_DATADIR); 727 GNUNET_asprintf (&fn, 728 "%sauthorization-email-messages.json", 729 tmp); 730 GNUNET_free (tmp); 731 ctx->messages = json_load_file (fn, 732 JSON_REJECT_DUPLICATES, 733 &err); 734 if (NULL == ctx->messages) 735 { 736 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, 737 "Failed to load messages from `%s': %s at %d:%d\n", 738 fn, 739 err.text, 740 err.line, 741 err.column); 742 GNUNET_free (fn); 743 GNUNET_free (ctx); 744 return NULL; 745 } 746 GNUNET_free (fn); 747 } 748 { 749 int regex_result; 750 /* anchored so the whole address must match, not merely contain a match */ 751 const char *regexp = "^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\\.[a-zA-Z]{2,}$"; 752 753 regex_result = regcomp (&ctx->regex, 754 regexp, 755 REG_EXTENDED); 756 if (0 < regex_result) 757 { 758 GNUNET_break (0); 759 json_decref (ctx->messages); 760 GNUNET_free (ctx); 761 return NULL; 762 } 763 } 764 765 plugin = GNUNET_new (struct ANASTASIS_AuthorizationPlugin); 766 plugin->retry_counter = INITIAL_RETRY_COUNTER; 767 plugin->code_validity_period = GNUNET_TIME_UNIT_DAYS; 768 plugin->code_rotation_period = GNUNET_TIME_UNIT_HOURS; 769 plugin->code_retransmission_frequency = GNUNET_TIME_UNIT_MINUTES; 770 plugin->cls = ctx; 771 plugin->validate = &email_validate; 772 plugin->start = &email_start; 773 plugin->challenge = &email_challenge; 774 plugin->cleanup = &email_cleanup; 775 776 if (GNUNET_OK != 777 GNUNET_CONFIGURATION_get_value_string (cfg, 778 "authorization-email", 779 "COMMAND", 780 &ctx->auth_command)) 781 { 782 GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR, 783 "authorization-email", 784 "COMMAND"); 785 regfree (&ctx->regex); 786 json_decref (ctx->messages); 787 GNUNET_free (ctx); 788 GNUNET_free (plugin); 789 return NULL; 790 } 791 return plugin; 792 } 793 794 795 /** 796 * Unload authorization plugin 797 * 798 * @param cls a `struct ANASTASIS_AuthorizationPlugin` 799 * @return NULL (always) 800 */ 801 void * 802 libanastasis_plugin_authorization_email_done (void *cls); 803 804 /* declaration to fix compiler warning */ 805 void * 806 libanastasis_plugin_authorization_email_done (void *cls) 807 { 808 struct ANASTASIS_AuthorizationPlugin *plugin = cls; 809 struct Email_Context *ctx = plugin->cls; 810 811 GNUNET_free (ctx->auth_command); 812 regfree (&ctx->regex); 813 json_decref (ctx->messages); 814 GNUNET_free (ctx); 815 GNUNET_free (plugin); 816 return NULL; 817 }