commit 6e9a98b056705a7bc31321af085f92ae022a2516
parent e49e33a13df92c6a1d6f119775baa31778163531
Author: Christian Grothoff <christian@grothoff.org>
Date: Sun, 30 Mar 2025 14:14:45 +0200
allow sending of custom messages for address validation
Diffstat:
6 files changed, 223 insertions(+), 7 deletions(-)
diff --git a/configure.ac b/configure.ac
@@ -132,6 +132,18 @@ AS_IF([test $libtalermhd != 1],
*** https://taler.net
*** ]])])
+libtalertemplating=0
+AC_CHECK_HEADERS([taler/taler_templating_lib.h],
+ [AC_CHECK_LIB([talertemplating], [TALER_TEMPLATING_fill2], libtalertemplating=1)])
+AM_CONDITIONAL(HAVE_TALERTEMPLATING, test x$libtalertemplating = x1)
+AS_IF([test $libtalertemplating != 1],
+ [AC_MSG_ERROR([[
+***
+*** You need libtalertemplating >= 0.14.6 to build this program.
+*** This library is part of the GNU Taler exchange, available at
+*** https://taler.net
+*** ]])])
+
# check for libmicrohttpd
microhttpd=0
diff --git a/src/challenger/Makefile.am b/src/challenger/Makefile.am
@@ -6,6 +6,11 @@ if USE_COVERAGE
XLIB = -lgcov
endif
+tmpldatadir = $(prefix)/share/challenger/templates/
+
+dist_tmpldata_DATA = \
+ default-challenge-message.txt
+
pkgcfgdir = $(prefix)/share/challenger/config.d/
pkgcfg_DATA = \
diff --git a/src/challenger/challenger-httpd.c b/src/challenger/challenger-httpd.c
@@ -1,6 +1,6 @@
/*
This file is part of Challenger
- (C) 2023, 2024 Taler Systems SA
+ (C) 2023, 2024, 2025 Taler Systems SA
Challenger is free software; you can redistribute it and/or modify it under the
terms of the GNU Affero General Public License as published by the Free Software
@@ -79,6 +79,22 @@ static struct MHD_Daemon *mhd;
struct CHALLENGER_DatabasePlugin *CH_db;
/**
+ * (external) base URL of this service.
+ */
+char *CH_base_url;
+
+/**
+ * Mustach template for the letter to send.
+ * WARNING: not 0-terminated! Allocated via mmap(), free with munmap()!
+ */
+void *CH_message_template;
+
+/**
+ * Number of bytes in #CH_message_template
+ */
+size_t CH_message_template_len;
+
+/**
* How long is an individual validation request valid?
*/
struct GNUNET_TIME_Relative CH_validation_duration;
@@ -361,6 +377,17 @@ do_shutdown (void *cls)
GNUNET_SCHEDULER_cancel (mhd_task);
mhd_task = NULL;
}
+ if ( (NULL != CH_message_template) &&
+ (MAP_FAILED != CH_message_template) )
+ {
+ if (0 !=
+ munmap (CH_message_template,
+ CH_message_template_len))
+ GNUNET_log_strerror (GNUNET_ERROR_TYPE_ERROR,
+ "munmap");
+ CH_message_template = NULL;
+ CH_message_template_len = 0;
+ }
if (NULL != CH_ctx)
{
GNUNET_CURL_fini (CH_ctx);
@@ -588,6 +615,8 @@ run (void *cls,
GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR,
"CHALLENGER",
"VALIDATION_DURATION");
+ global_ret = EXIT_NOTCONFIGURED;
+ GNUNET_SCHEDULER_shutdown ();
return;
}
if (GNUNET_OK !=
@@ -599,6 +628,8 @@ run (void *cls,
GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR,
"CHALLENGER",
"VALIDATION_EXPIRATION");
+ global_ret = EXIT_NOTCONFIGURED;
+ GNUNET_SCHEDULER_shutdown ();
return;
}
if (GNUNET_OK !=
@@ -610,6 +641,8 @@ run (void *cls,
GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR,
"CHALLENGER",
"AUTH_COMMAND");
+ global_ret = EXIT_NOTCONFIGURED;
+ GNUNET_SCHEDULER_shutdown ();
return;
}
if (GNUNET_OK !=
@@ -621,8 +654,101 @@ run (void *cls,
GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR,
"CHALLENGER",
"ADDRESS_TYPE");
+ global_ret = EXIT_NOTCONFIGURED;
+ GNUNET_SCHEDULER_shutdown ();
+ return;
+ }
+ if (GNUNET_OK !=
+ GNUNET_CONFIGURATION_get_value_string (config,
+ "CHALLENGER",
+ "BASE_URL",
+ &CH_base_url))
+ {
+ GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR,
+ "CHALLENGER",
+ "BASE_URL");
+ global_ret = EXIT_NOTCONFIGURED;
+ GNUNET_SCHEDULER_shutdown ();
return;
}
+ if ( (! TALER_url_valid_charset (CH_base_url)) ||
+ (! TALER_is_web_url (CH_base_url) ) ||
+ (0 == strlen (CH_base_url)) ||
+ ('/' != CH_base_url[strlen (CH_base_url) - 1]) )
+ {
+ GNUNET_log_config_invalid (GNUNET_ERROR_TYPE_ERROR,
+ "CHALLENGER",
+ "BASE_URL",
+ "invalid URL");
+ global_ret = EXIT_NOTCONFIGURED;
+ GNUNET_SCHEDULER_shutdown ();
+ return;
+ }
+ {
+ char *tmpl_file;
+ int fd;
+ struct stat s;
+
+ if (GNUNET_OK !=
+ GNUNET_CONFIGURATION_get_value_filename (config,
+ "CHALLENGER",
+ "MESSAGE_TEMPLATE_FILE",
+ &tmpl_file))
+ {
+ GNUNET_log_config_missing (GNUNET_ERROR_TYPE_WARNING,
+ "CHALLENGER",
+ "MESSAGE_TEMPLATE_FILE");
+ }
+ else
+ {
+ fd = open (tmpl_file,
+ O_RDONLY);
+ GNUNET_free (tmpl_file);
+ if (-1 == fd)
+ {
+ GNUNET_log_config_invalid (GNUNET_ERROR_TYPE_ERROR,
+ "CHALLENGER",
+ "MESSAGE_TEMPLATE_FILE",
+ strerror (errno));
+ global_ret = EXIT_NOTCONFIGURED;
+ GNUNET_SCHEDULER_shutdown ();
+ return;
+ }
+ if (0 != fstat (fd,
+ &s))
+ {
+ GNUNET_log_config_invalid (GNUNET_ERROR_TYPE_ERROR,
+ "CHALLENGER",
+ "MESSAGE_TEMPLATE_FILE",
+ strerror (errno));
+ GNUNET_free (tmpl_file);
+ GNUNET_break (0 == close (fd));
+ global_ret = EXIT_NOTCONFIGURED;
+ GNUNET_SCHEDULER_shutdown ();
+ return;
+ }
+ CH_message_template_len = (size_t) s.st_size;
+ CH_message_template = mmap (NULL,
+ CH_message_template_len,
+ PROT_READ,
+ MAP_SHARED,
+ fd,
+ 0);
+ GNUNET_break (0 == close (fd));
+ if (MAP_FAILED == CH_message_template)
+ {
+ GNUNET_log_config_invalid (GNUNET_ERROR_TYPE_ERROR,
+ "CHALLENGER",
+ "MESSAGE_TEMPLATE_FILE",
+ strerror (errno));
+ GNUNET_free (tmpl_file);
+ global_ret = EXIT_NOTCONFIGURED;
+ GNUNET_SCHEDULER_shutdown ();
+ return;
+ }
+ }
+ }
+
{
char *restrictions;
@@ -644,6 +770,8 @@ run (void *cls,
"CHALLENGER",
"ADDRESS_RESTRICTIONS",
err.text);
+ global_ret = EXIT_NOTCONFIGURED;
+ GNUNET_SCHEDULER_shutdown ();
return;
}
}
diff --git a/src/challenger/challenger-httpd.h b/src/challenger/challenger-httpd.h
@@ -147,6 +147,22 @@ extern char *CH_auth_command;
extern char *CH_address_type;
/**
+ * (external) base URL of this service.
+ */
+extern char *CH_base_url;
+
+/**
+ * Mustach template for the letter to send.
+ * WARNING: not 0-terminated! Allocated via mmap(), free with munmap()!
+ */
+extern void *CH_message_template;
+
+/**
+ * Number of bytes in #CH_message_template
+ */
+extern size_t CH_message_template_len;
+
+/**
* How long is an individual validation request valid?
*/
extern struct GNUNET_TIME_Relative CH_validation_duration;
diff --git a/src/challenger/challenger-httpd_challenge.c b/src/challenger/challenger-httpd_challenge.c
@@ -293,7 +293,8 @@ send_tan (struct ChallengeContext *bc)
{
struct GNUNET_DISK_PipeHandle *p;
struct GNUNET_DISK_FileHandle *pipe_stdin;
- char *msg;
+ void *msg;
+ size_t msg_len;
p = GNUNET_DISK_pipe (GNUNET_DISK_PF_BLOCKING_RW);
if (NULL == p)
@@ -352,13 +353,60 @@ send_tan (struct ChallengeContext *bc)
GNUNET_assert (NULL != pipe_stdin);
GNUNET_break (GNUNET_OK ==
GNUNET_DISK_pipe_close (p));
+ if (0 != CH_message_template_len)
+ {
+ json_t *root;
+ int mret;
+ char *my_url;
+
+ GNUNET_asprintf (&my_url,
+ "%schallenge/%s",
+ CH_base_url,
+ bc->hc->path);
+ root = GNUNET_JSON_PACK (
+ GNUNET_JSON_pack_string ("challenger_url",
+ my_url),
+ GNUNET_JSON_pack_object_steal ("address",
+ bc->address),
+ GNUNET_JSON_pack_int64 ("pin",
+ bc->tan));
+ GNUNET_free (my_url);
+ mret = TALER_TEMPLATING_fill2 (CH_message_template,
+ CH_message_template_len,
+ root,
+ &msg,
+ &msg_len);
+ json_decref (root);
+ if (0 != mret)
+ {
+ MHD_RESULT mres;
- GNUNET_asprintf (&msg,
- "PIN: %u",
- (unsigned int) bc->tan);
+ GNUNET_break (0);
+ mres = reply_error (bc,
+ "internal-error",
+ MHD_HTTP_INTERNAL_SERVER_ERROR,
+ TALER_EC_GENERIC_FAILED_TO_EXPAND_TEMPLATE,
+ NULL);
+ GNUNET_DISK_file_close (pipe_stdin);
+ bc->status = (MHD_YES == mres)
+ ? GNUNET_NO
+ : GNUNET_SYSERR;
+ return;
+ }
+ }
+ else
+ {
+ char *cmsg;
+
+ GNUNET_asprintf (&cmsg,
+ "PIN: %u",
+ (unsigned int) bc->tan);
+ msg_len = strlen (msg);
+ msg = cmsg;
+ }
{
const char *off = msg;
- size_t left = strlen (off);
+ size_t left = msg_len;
while (0 != left)
{
@@ -377,6 +425,7 @@ send_tan (struct ChallengeContext *bc)
MHD_HTTP_INTERNAL_SERVER_ERROR,
TALER_EC_CHALLENGER_HELPER_EXEC_FAILED,
"write");
+ GNUNET_DISK_file_close (pipe_stdin);
GNUNET_free (msg);
bc->status = (MHD_YES == mres)
? GNUNET_NO
diff --git a/src/challenger/challenger.conf b/src/challenger/challenger.conf
@@ -30,6 +30,12 @@ VALIDATION_DURATION = 1d
# How long is an validation valid?
VALIDATION_EXPIRATION = 365d
+# Base URL of our service. Must end with '/'.
+#BASE_URL = https://challenger.DOMAIN/
+
+# Name of a file with the message to send with the challenge.
+MESSAGE_TEMPLATE_FILE = ${DATADIR}templates/default-challenge-message.txt
+
# Which external command should be used to transmit challenges?
# Example commands are challenger-send-{sms,email,post}.sh
# AUTH_COMMAND =
@@ -39,5 +45,5 @@ VALIDATION_EXPIRATION = 365d
# A template of the form 'enter-$ADDRESS_TYPE-form' must
# exist and the field names must be supported by the
# AUTH_COMMAND.
-#
+#
# ADDRESS_TYPE =