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