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