aboutsummaryrefslogtreecommitdiff
path: root/src/authorization/anastasis_authorization_plugin_post.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/authorization/anastasis_authorization_plugin_post.c')
-rw-r--r--src/authorization/anastasis_authorization_plugin_post.c655
1 files changed, 655 insertions, 0 deletions
diff --git a/src/authorization/anastasis_authorization_plugin_post.c b/src/authorization/anastasis_authorization_plugin_post.c
new file mode 100644
index 0000000..1f20ff3
--- /dev/null
+++ b/src/authorization/anastasis_authorization_plugin_post.c
@@ -0,0 +1,655 @@
1/*
2 This file is part of Anastasis
3 Copyright (C) 2021 Taler Systems SA
4
5 Anastasis is free software; you can redistribute it and/or modify it under the
6 terms of the GNU Lesser 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 General Public License for more details.
12
13 You should have received a copy of the GNU General Public License along with
14 Anastasis; see the file COPYING.GPL. If not, see <http://www.gnu.org/licenses/>
15*/
16/**
17 * @file include/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
28
29/**
30 * Saves the State of a authorization plugin.
31 */
32struct PostContext
33{
34
35 /**
36 * Command which is executed to run the plugin (some bash script or a
37 * command line argument)
38 */
39 char *auth_command;
40
41 /**
42 * Messages of the plugin, read from a resource file.
43 */
44 json_t *messages;
45};
46
47
48/**
49 * Saves the state of a authorization process
50 */
51struct ANASTASIS_AUTHORIZATION_State
52{
53 /**
54 * Public key of the challenge which is authorised
55 */
56 struct ANASTASIS_CRYPTO_TruthUUIDP truth_uuid;
57
58 /**
59 * Code which is sent to the user.
60 */
61 uint64_t code;
62
63 /**
64 * Our plugin context.
65 */
66 struct PostContext *ctx;
67
68 /**
69 * Function to call when we made progress.
70 */
71 GNUNET_SCHEDULER_TaskCallback trigger;
72
73 /**
74 * Closure for @e trigger.
75 */
76 void *trigger_cls;
77
78 /**
79 * holds the truth information
80 */
81 json_t *post;
82
83 /**
84 * Handle to the helper process.
85 */
86 struct GNUNET_OS_Process *child;
87
88 /**
89 * Handle to wait for @e child
90 */
91 struct GNUNET_ChildWaitHandle *cwh;
92
93 /**
94 * Our client connection, set if suspended.
95 */
96 struct MHD_Connection *connection;
97
98 /**
99 * Message to send.
100 */
101 char *msg;
102
103 /**
104 * Offset of transmission in msg.
105 */
106 size_t msg_off;
107
108 /**
109 * Exit code from helper.
110 */
111 long unsigned int exit_code;
112
113 /**
114 * How did the helper die?
115 */
116 enum GNUNET_OS_ProcessStatusType pst;
117
118
119};
120
121
122/**
123 * Obtain internationalized message @a msg_id from @a ctx using
124 * language preferences of @a conn.
125 *
126 * @param messages JSON object to lookup message from
127 * @param conn connection to lookup message for
128 * @param msg_id unique message ID
129 * @return NULL if message was not found
130 */
131static const char *
132get_message (const json_t *messages,
133 struct MHD_Connection *conn,
134 const char *msg_id)
135{
136 const char *accept_lang;
137
138 accept_lang = MHD_lookup_connection_value (conn,
139 MHD_HEADER_KIND,
140 MHD_HTTP_HEADER_ACCEPT_LANGUAGE);
141 if (NULL == accept_lang)
142 accept_lang = "en_US";
143 {
144 const char *ret;
145 struct GNUNET_JSON_Specification spec[] = {
146 TALER_JSON_spec_i18n_string (msg_id,
147 accept_lang,
148 &ret),
149 GNUNET_JSON_spec_end ()
150 };
151
152 if (GNUNET_OK !=
153 GNUNET_JSON_parse (messages,
154 spec,
155 NULL, NULL))
156 {
157 GNUNET_break (0);
158 return NULL;
159 }
160 return ret;
161 }
162}
163
164
165/**
166 * Validate @a data is a well-formed input into the challenge method,
167 * i.e. @a data is a well-formed phone number for sending an SMS, or
168 * a well-formed e-mail address for sending an e-mail. Not expected to
169 * check that the phone number or e-mail account actually exists.
170 *
171 * To be possibly used before issuing a 402 payment required to the client.
172 *
173 * @param cls closure
174 * @param connection HTTP client request (for queuing response)
175 * @param truth_mime mime type of @e data
176 * @param data input to validate (i.e. is it a valid phone number, etc.)
177 * @param data_length number of bytes in @a data
178 * @return #GNUNET_OK if @a data is valid,
179 * #GNUNET_NO if @a data is invalid and a reply was successfully queued on @a connection
180 * #GNUNET_SYSERR if @a data invalid but we failed to queue a reply on @a connection
181 */
182static enum GNUNET_GenericReturnValue
183post_validate (void *cls,
184 struct MHD_Connection *connection,
185 const char *mime_type,
186 const char *data,
187 size_t data_length)
188{
189 struct PostContext *ctx = cls;
190 json_t *j;
191 json_error_t error;
192 const char *name;
193 const char *street;
194 const char *city;
195 const char *zip;
196 const char *country;
197 struct GNUNET_JSON_Specification spec[] = {
198 GNUNET_JSON_spec_string ("full_name",
199 &name),
200 GNUNET_JSON_spec_string ("street",
201 &street),
202 GNUNET_JSON_spec_string ("city",
203 &city),
204 GNUNET_JSON_spec_string ("postcode",
205 &zip),
206 GNUNET_JSON_spec_string ("country",
207 &country),
208 GNUNET_JSON_spec_end ()
209 };
210
211 (void) ctx;
212 j = json_loadb (data,
213 data_length,
214 JSON_REJECT_DUPLICATES,
215 &error);
216 if (NULL == j)
217 {
218 if (MHD_NO ==
219 TALER_MHD_reply_with_error (connection,
220 MHD_HTTP_EXPECTATION_FAILED,
221 TALER_EC_ANASTASIS_POST_INVALID,
222 "JSON malformed"))
223 return GNUNET_SYSERR;
224 return GNUNET_NO;
225 }
226
227 if (GNUNET_OK !=
228 GNUNET_JSON_parse (j,
229 spec,
230 NULL, NULL))
231 {
232 GNUNET_break (0);
233 json_decref (j);
234 if (MHD_NO ==
235 TALER_MHD_reply_with_error (connection,
236 MHD_HTTP_EXPECTATION_FAILED,
237 TALER_EC_ANASTASIS_POST_INVALID,
238 "JSON lacked required address information"))
239 return GNUNET_SYSERR;
240 return GNUNET_NO;
241 }
242 json_decref (j);
243 return GNUNET_OK;
244}
245
246
247/**
248 * Begin issuing authentication challenge to user based on @a data.
249 * I.e. start to send mail.
250 *
251 * @param cls closure
252 * @param trigger function to call when we made progress
253 * @param trigger_cls closure for @a trigger
254 * @param truth_uuid Identifier of the challenge, to be (if possible) included in the
255 * interaction with the user
256 * @param code secret code that the user has to provide back to satisfy the challenge in
257 * the main anastasis protocol
258 * @param data input to validate (i.e. is it a valid phone number, etc.)
259 * @return state to track progress on the authorization operation, NULL on failure
260 */
261static struct ANASTASIS_AUTHORIZATION_State *
262post_start (void *cls,
263 GNUNET_SCHEDULER_TaskCallback trigger,
264 void *trigger_cls,
265 const struct ANASTASIS_CRYPTO_TruthUUIDP *truth_uuid,
266 uint64_t code,
267 const void *data,
268 size_t data_length)
269{
270 struct PostContext *ctx = cls;
271 struct ANASTASIS_AUTHORIZATION_State *as;
272 json_error_t error;
273
274 as = GNUNET_new (struct ANASTASIS_AUTHORIZATION_State);
275 as->trigger = trigger;
276 as->trigger_cls = trigger_cls;
277 as->ctx = ctx;
278 as->truth_uuid = *truth_uuid;
279 as->code = code;
280 as->post = json_loadb (data,
281 data_length,
282 JSON_REJECT_DUPLICATES,
283 &error);
284 if (NULL == as->post)
285 {
286 GNUNET_break (0);
287 GNUNET_free (as);
288 return NULL;
289 }
290 return as;
291}
292
293
294/**
295 * Function called when our Post helper has terminated.
296 *
297 * @param cls our `struct ANASTASIS_AUHTORIZATION_State`
298 * @param type type of the process
299 * @param exit_code status code of the process
300 */
301static void
302post_done_cb (void *cls,
303 enum GNUNET_OS_ProcessStatusType type,
304 long unsigned int exit_code)
305{
306 struct ANASTASIS_AUTHORIZATION_State *as = cls;
307
308 as->child = NULL;
309 as->cwh = NULL;
310 as->pst = type;
311 as->exit_code = exit_code;
312 MHD_resume_connection (as->connection);
313 as->trigger (as->trigger_cls);
314}
315
316
317/**
318 * Begin issuing authentication challenge to user based on @a data.
319 * I.e. start to send SMS or e-mail or launch video identification.
320 *
321 * @param as authorization state
322 * @param connection HTTP client request (for queuing response, such as redirection to video portal)
323 * @return state of the request
324 */
325static enum ANASTASIS_AUTHORIZATION_Result
326post_process (struct ANASTASIS_AUTHORIZATION_State *as,
327 struct MHD_Connection *connection)
328{
329 const char *mime;
330 const char *lang;
331 MHD_RESULT mres;
332 const char *name;
333 const char *street;
334 const char *city;
335 const char *zip;
336 const char *country;
337 struct GNUNET_JSON_Specification spec[] = {
338 GNUNET_JSON_spec_string ("full_name",
339 &name),
340 GNUNET_JSON_spec_string ("street",
341 &street),
342 GNUNET_JSON_spec_string ("city",
343 &city),
344 GNUNET_JSON_spec_string ("postcode",
345 &zip),
346 GNUNET_JSON_spec_string ("country",
347 &country),
348 GNUNET_JSON_spec_end ()
349 };
350
351 mime = MHD_lookup_connection_value (connection,
352 MHD_HEADER_KIND,
353 MHD_HTTP_HEADER_ACCEPT);
354 if (NULL == mime)
355 mime = "text/plain";
356 lang = MHD_lookup_connection_value (connection,
357 MHD_HEADER_KIND,
358 MHD_HTTP_HEADER_ACCEPT_LANGUAGE);
359 if (NULL == lang)
360 lang = "en";
361 if (GNUNET_OK !=
362 GNUNET_JSON_parse (as->post,
363 spec,
364 NULL, NULL))
365 {
366 GNUNET_break (0);
367 mres = TALER_MHD_reply_with_error (connection,
368 MHD_HTTP_INTERNAL_SERVER_ERROR,
369 TALER_EC_ANASTASIS_POST_INVALID,
370 "address information incomplete");
371 if (MHD_YES != mres)
372 return ANASTASIS_AUTHORIZATION_RES_FAILED_REPLY_FAILED;
373 return ANASTASIS_AUTHORIZATION_RES_FAILED;
374 }
375 if (NULL == as->msg)
376 {
377 /* First time, start child process and feed pipe */
378 struct GNUNET_DISK_PipeHandle *p;
379 struct GNUNET_DISK_FileHandle *pipe_stdin;
380
381 p = GNUNET_DISK_pipe (GNUNET_DISK_PF_BLOCKING_RW);
382 if (NULL == p)
383 {
384 mres = TALER_MHD_reply_with_error (connection,
385 MHD_HTTP_INTERNAL_SERVER_ERROR,
386 TALER_EC_ANASTASIS_POST_HELPER_EXEC_FAILED,
387 "pipe");
388 if (MHD_YES != mres)
389 return ANASTASIS_AUTHORIZATION_RES_FAILED_REPLY_FAILED;
390 return ANASTASIS_AUTHORIZATION_RES_FAILED;
391 }
392 as->child = GNUNET_OS_start_process (GNUNET_OS_INHERIT_STD_ERR,
393 p,
394 NULL,
395 NULL,
396 as->ctx->auth_command,
397 as->ctx->auth_command,
398 name,
399 street,
400 city,
401 zip,
402 country,
403 NULL);
404 if (NULL == as->child)
405 {
406 GNUNET_DISK_pipe_close (p);
407 mres = TALER_MHD_reply_with_error (connection,
408 MHD_HTTP_INTERNAL_SERVER_ERROR,
409 TALER_EC_ANASTASIS_POST_HELPER_EXEC_FAILED,
410 "exec");
411 if (MHD_YES != mres)
412 return ANASTASIS_AUTHORIZATION_RES_FAILED_REPLY_FAILED;
413 return ANASTASIS_AUTHORIZATION_RES_FAILED;
414 }
415 pipe_stdin = GNUNET_DISK_pipe_detach_end (p,
416 GNUNET_DISK_PIPE_END_WRITE);
417 GNUNET_assert (NULL != pipe_stdin);
418 GNUNET_DISK_pipe_close (p);
419 {
420 char *tpk;
421
422 tpk = GNUNET_STRINGS_data_to_string_alloc (
423 &as->truth_uuid,
424 sizeof (as->truth_uuid));
425 GNUNET_asprintf (&as->msg,
426 get_message (as->ctx->messages,
427 connection,
428 "body"),
429 (unsigned long long) as->code,
430 tpk);
431 GNUNET_free (tpk);
432 }
433
434 {
435 const char *off = as->msg;
436 size_t left = strlen (off);
437
438 while (0 != left)
439 {
440 ssize_t ret;
441
442 if (0 == left)
443 break;
444 ret = GNUNET_DISK_file_write (pipe_stdin,
445 off,
446 left);
447 if (ret <= 0)
448 {
449 mres = TALER_MHD_reply_with_error (connection,
450 MHD_HTTP_INTERNAL_SERVER_ERROR,
451 TALER_EC_ANASTASIS_POST_HELPER_EXEC_FAILED,
452 "write");
453 if (MHD_YES != mres)
454 return ANASTASIS_AUTHORIZATION_RES_FAILED_REPLY_FAILED;
455 return ANASTASIS_AUTHORIZATION_RES_FAILED;
456 }
457 as->msg_off += ret;
458 off += ret;
459 left -= ret;
460 }
461 GNUNET_DISK_file_close (pipe_stdin);
462 }
463 as->cwh = GNUNET_wait_child (as->child,
464 &post_done_cb,
465 as);
466 as->connection = connection;
467 MHD_suspend_connection (connection);
468 return ANASTASIS_AUTHORIZATION_RES_SUSPENDED;
469 }
470 if (NULL != as->cwh)
471 {
472 /* Spurious call, why are we here? */
473 GNUNET_break (0);
474 MHD_suspend_connection (connection);
475 return ANASTASIS_AUTHORIZATION_RES_SUSPENDED;
476 }
477 if ( (GNUNET_OS_PROCESS_EXITED != as->pst) ||
478 (0 != as->exit_code) )
479 {
480 char es[32];
481
482 GNUNET_snprintf (es,
483 sizeof (es),
484 "%u/%d",
485 (unsigned int) as->exit_code,
486 as->pst);
487 mres = TALER_MHD_reply_with_error (connection,
488 MHD_HTTP_INTERNAL_SERVER_ERROR,
489 TALER_EC_ANASTASIS_POST_HELPER_COMMAND_FAILED,
490 es);
491 if (MHD_YES != mres)
492 return ANASTASIS_AUTHORIZATION_RES_FAILED_REPLY_FAILED;
493 return ANASTASIS_AUTHORIZATION_RES_FAILED;
494 }
495
496 /* Build HTTP response */
497 {
498 struct MHD_Response *resp;
499
500 if (TALER_MHD_xmime_matches (mime,
501 "application/json"))
502 {
503 json_t *body;
504
505 body = json_pack ("{s:I, s:s, s:s}",
506 "code",
507 TALER_EC_ANASTASIS_TRUTH_CHALLENGE_RESPONSE_REQUIRED,
508 "hint",
509 TALER_ErrorCode_get_hint (
510 TALER_EC_ANASTASIS_TRUTH_CHALLENGE_RESPONSE_REQUIRED),
511 "detail",
512 zip);
513 GNUNET_break (NULL != body);
514 resp = TALER_MHD_make_json (body);
515 }
516 else
517 {
518 size_t reply_len;
519 char *reply;
520
521 reply_len = GNUNET_asprintf (&reply,
522 get_message (as->ctx->messages,
523 connection,
524 "instructions"),
525 zip);
526 resp = MHD_create_response_from_buffer (reply_len,
527 reply,
528 MHD_RESPMEM_MUST_COPY);
529 GNUNET_free (reply);
530 TALER_MHD_add_global_headers (resp);
531 }
532 mres = MHD_queue_response (connection,
533 MHD_HTTP_FORBIDDEN,
534 resp);
535 MHD_destroy_response (resp);
536 if (MHD_YES != mres)
537 return ANASTASIS_AUTHORIZATION_RES_SUCCESS_REPLY_FAILED;
538 return ANASTASIS_AUTHORIZATION_RES_SUCCESS;
539 }
540}
541
542
543/**
544 * Free internal state associated with @a as.
545 *
546 * @param as state to clean up
547 */
548static void
549post_cleanup (struct ANASTASIS_AUTHORIZATION_State *as)
550{
551 if (NULL != as->cwh)
552 {
553 GNUNET_wait_child_cancel (as->cwh);
554 as->cwh = NULL;
555 }
556 if (NULL != as->child)
557 {
558 (void) GNUNET_OS_process_kill (as->child,
559 SIGKILL);
560 GNUNET_break (GNUNET_OK ==
561 GNUNET_OS_process_wait (as->child));
562 as->child = NULL;
563 }
564 GNUNET_free (as->msg);
565 json_decref (as->post);
566 GNUNET_free (as);
567}
568
569
570/**
571 * Initialize post based authorization plugin
572 *
573 * @param cls a configuration instance
574 * @return NULL on error, otherwise a `struct ANASTASIS_AuthorizationPlugin`
575 */
576void *
577libanastasis_plugin_authorization_post_init (void *cls)
578{
579 struct ANASTASIS_AuthorizationPlugin *plugin;
580 struct GNUNET_CONFIGURATION_Handle *cfg = cls;
581 struct PostContext *ctx;
582
583 ctx = GNUNET_new (struct PostContext);
584 {
585 char *fn;
586 json_error_t err;
587
588 GNUNET_asprintf (&fn,
589 "%sauthorization-post-messages.json",
590 GNUNET_OS_installation_get_path (GNUNET_OS_IPK_DATADIR));
591 ctx->messages = json_load_file (fn,
592 JSON_REJECT_DUPLICATES,
593 &err);
594 if (NULL == ctx->messages)
595 {
596 GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
597 "Failed to load messages from `%s': %s at %d:%d\n",
598 fn,
599 err.text,
600 err.line,
601 err.column);
602 GNUNET_free (fn);
603 GNUNET_free (ctx);
604 return NULL;
605 }
606 GNUNET_free (fn);
607 }
608 plugin = GNUNET_new (struct ANASTASIS_AuthorizationPlugin);
609 plugin->code_validity_period = GNUNET_TIME_UNIT_MONTHS;
610 plugin->code_rotation_period = GNUNET_TIME_UNIT_WEEKS;
611 plugin->code_retransmission_frequency
612 = GNUNET_TIME_relative_multiply (GNUNET_TIME_UNIT_DAYS,
613 2);
614 plugin->cls = ctx;
615 plugin->validate = &post_validate;
616 plugin->start = &post_start;
617 plugin->process = &post_process;
618 plugin->cleanup = &post_cleanup;
619
620 if (GNUNET_OK !=
621 GNUNET_CONFIGURATION_get_value_string (cfg,
622 "authorization-post",
623 "COMMAND",
624 &ctx->auth_command))
625 {
626 GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR,
627 "authorization-post",
628 "COMMAND");
629 json_decref (ctx->messages);
630 GNUNET_free (ctx);
631 GNUNET_free (plugin);
632 return NULL;
633 }
634 return plugin;
635}
636
637
638/**
639 * Unload authorization plugin
640 *
641 * @param cls a `struct ANASTASIS_AuthorizationPlugin`
642 * @return NULL (always)
643 */
644void *
645libanastasis_plugin_authorization_post_done (void *cls)
646{
647 struct ANASTASIS_AuthorizationPlugin *plugin = cls;
648 struct PostContext *ctx = plugin->cls;
649
650 GNUNET_free (ctx->auth_command);
651 json_decref (ctx->messages);
652 GNUNET_free (ctx);
653 GNUNET_free (plugin);
654 return NULL;
655}