Line data Source code
1 : /*
2 : This file is part of TALER
3 : Copyright (C) 2014-2021 Taler Systems SA
4 :
5 : TALER 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 : TALER 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 : TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
15 : */
16 :
17 : /**
18 : * @file taler-auditor-httpd.c
19 : * @brief Serve the HTTP interface of the auditor
20 : * @author Florian Dold
21 : * @author Benedikt Mueller
22 : * @author Christian Grothoff
23 : */
24 : #include "platform.h"
25 : #include <gnunet/gnunet_util_lib.h>
26 : #include <jansson.h>
27 : #include <microhttpd.h>
28 : #include <pthread.h>
29 : #include <sys/resource.h>
30 : #include "taler_mhd_lib.h"
31 : #include "taler_auditordb_lib.h"
32 : #include "taler_exchangedb_lib.h"
33 : #include "taler-auditor-httpd_deposit-confirmation.h"
34 : #include "taler-auditor-httpd_exchanges.h"
35 : #include "taler-auditor-httpd_mhd.h"
36 : #include "taler-auditor-httpd.h"
37 :
38 : /**
39 : * Auditor protocol version string.
40 : *
41 : * Taler protocol version in the format CURRENT:REVISION:AGE
42 : * as used by GNU libtool. See
43 : * https://www.gnu.org/software/libtool/manual/html_node/Libtool-versioning.html
44 : *
45 : * Please be very careful when updating and follow
46 : * https://www.gnu.org/software/libtool/manual/html_node/Updating-version-info.html#Updating-version-info
47 : * precisely. Note that this version has NOTHING to do with the
48 : * release version, and the format is NOT the same that semantic
49 : * versioning uses either.
50 : */
51 : #define AUDITOR_PROTOCOL_VERSION "0:0:0"
52 :
53 : /**
54 : * Backlog for listen operation on unix domain sockets.
55 : */
56 : #define UNIX_BACKLOG 500
57 :
58 : /**
59 : * Should we return "Connection: close" in each response?
60 : */
61 : static int auditor_connection_close;
62 :
63 : /**
64 : * The auditor's configuration.
65 : */
66 : static const struct GNUNET_CONFIGURATION_Handle *cfg;
67 :
68 : /**
69 : * Our DB plugin.
70 : */
71 : struct TALER_AUDITORDB_Plugin *TAH_plugin;
72 :
73 : /**
74 : * Our DB plugin to talk to the *exchange* database.
75 : */
76 : struct TALER_EXCHANGEDB_Plugin *TAH_eplugin;
77 :
78 : /**
79 : * Public key of this auditor.
80 : */
81 : static struct TALER_AuditorPublicKeyP auditor_pub;
82 :
83 : /**
84 : * Default timeout in seconds for HTTP requests.
85 : */
86 : static unsigned int connection_timeout = 30;
87 :
88 : /**
89 : * Return value from main()
90 : */
91 : static int global_ret;
92 :
93 : /**
94 : * Port to run the daemon on.
95 : */
96 : static uint16_t serve_port;
97 :
98 : /**
99 : * Our currency.
100 : */
101 : char *TAH_currency;
102 :
103 :
104 : /**
105 : * Function called whenever MHD is done with a request. If the
106 : * request was a POST, we may have stored a `struct Buffer *` in the
107 : * @a con_cls that might still need to be cleaned up. Call the
108 : * respective function to free the memory.
109 : *
110 : * @param cls client-defined closure
111 : * @param connection connection handle
112 : * @param con_cls value as set by the last call to
113 : * the #MHD_AccessHandlerCallback
114 : * @param toe reason for request termination
115 : * @see #MHD_OPTION_NOTIFY_COMPLETED
116 : * @ingroup request
117 : */
118 : static void
119 0 : handle_mhd_completion_callback (void *cls,
120 : struct MHD_Connection *connection,
121 : void **con_cls,
122 : enum MHD_RequestTerminationCode toe)
123 : {
124 : (void) cls;
125 : (void) connection;
126 : (void) toe;
127 0 : if (NULL == *con_cls)
128 0 : return;
129 0 : TALER_MHD_parse_post_cleanup_callback (*con_cls);
130 0 : *con_cls = NULL;
131 : }
132 :
133 :
134 : /**
135 : * Handle a "/version" request.
136 : *
137 : * @param rh context of the handler
138 : * @param connection the MHD connection to handle
139 : * @param[in,out] connection_cls the connection's closure (can be updated)
140 : * @param upload_data upload data
141 : * @param[in,out] upload_data_size number of bytes (left) in @a upload_data
142 : * @return MHD result code
143 : */
144 : static MHD_RESULT
145 0 : handle_version (struct TAH_RequestHandler *rh,
146 : struct MHD_Connection *connection,
147 : void **connection_cls,
148 : const char *upload_data,
149 : size_t *upload_data_size)
150 : {
151 : static json_t *ver; /* we build the response only once, keep around for next query! */
152 :
153 : (void) rh;
154 : (void) upload_data;
155 : (void) upload_data_size;
156 : (void) connection_cls;
157 0 : if (NULL == ver)
158 : {
159 0 : ver = GNUNET_JSON_PACK (
160 : GNUNET_JSON_pack_string ("version",
161 : AUDITOR_PROTOCOL_VERSION),
162 : GNUNET_JSON_pack_string ("currency",
163 : TAH_currency),
164 : GNUNET_JSON_pack_data_auto ("auditor_public_key",
165 : &auditor_pub));
166 : }
167 0 : if (NULL == ver)
168 : {
169 0 : GNUNET_break (0);
170 0 : return MHD_NO;
171 : }
172 0 : return TALER_MHD_reply_json (connection,
173 : ver,
174 : MHD_HTTP_OK);
175 : }
176 :
177 :
178 : /**
179 : * Handle incoming HTTP request.
180 : *
181 : * @param cls closure for MHD daemon (unused)
182 : * @param connection the connection
183 : * @param url the requested url
184 : * @param method the method (POST, GET, ...)
185 : * @param version HTTP version (ignored)
186 : * @param upload_data request data
187 : * @param upload_data_size size of @a upload_data in bytes
188 : * @param con_cls closure for request (a `struct Buffer *`)
189 : * @return MHD result code
190 : */
191 : static MHD_RESULT
192 0 : handle_mhd_request (void *cls,
193 : struct MHD_Connection *connection,
194 : const char *url,
195 : const char *method,
196 : const char *version,
197 : const char *upload_data,
198 : size_t *upload_data_size,
199 : void **con_cls)
200 : {
201 : static struct TAH_RequestHandler handlers[] = {
202 : /* Our most popular handler (thus first!), used by merchants to
203 : probabilistically report us their deposit confirmations. */
204 : { "/deposit-confirmation", MHD_HTTP_METHOD_PUT, "application/json",
205 : NULL, 0,
206 : &TAH_DEPOSIT_CONFIRMATION_handler, MHD_HTTP_OK },
207 : { "/exchanges", MHD_HTTP_METHOD_GET, "application/json",
208 : NULL, 0,
209 : &TAH_EXCHANGES_handler, MHD_HTTP_OK },
210 : { "/version", MHD_HTTP_METHOD_GET, "application/json",
211 : NULL, 0,
212 : &handle_version, MHD_HTTP_OK },
213 : /* Landing page, for now tells humans to go away
214 : * (NOTE: ideally, the reverse proxy will respond with a nicer page) */
215 : { "/", MHD_HTTP_METHOD_GET, "text/plain",
216 : "Hello, I'm the Taler auditor. This HTTP server is not for humans.\n", 0,
217 : &TAH_MHD_handler_static_response, MHD_HTTP_OK },
218 : /* /robots.txt: disallow everything */
219 : { "/robots.txt", MHD_HTTP_METHOD_GET, "text/plain",
220 : "User-agent: *\nDisallow: /\n", 0,
221 : &TAH_MHD_handler_static_response, MHD_HTTP_OK },
222 : /* AGPL licensing page, redirect to source. As per the AGPL-license,
223 : every deployment is required to offer the user a download of the
224 : source. We make this easy by including a redirect t the source
225 : here. */
226 : { "/agpl", MHD_HTTP_METHOD_GET, "text/plain",
227 : NULL, 0,
228 : &TAH_MHD_handler_agpl_redirect, MHD_HTTP_FOUND },
229 : { NULL, NULL, NULL, NULL, 0, NULL, 0 }
230 : };
231 :
232 : (void) cls;
233 : (void) version;
234 0 : GNUNET_log (GNUNET_ERROR_TYPE_INFO,
235 : "Handling request for URL '%s'\n",
236 : url);
237 0 : if (0 == strcasecmp (method,
238 : MHD_HTTP_METHOD_HEAD))
239 0 : method = MHD_HTTP_METHOD_GET; /* treat HEAD as GET here, MHD will do the rest */
240 0 : for (unsigned int i = 0; NULL != handlers[i].url; i++)
241 : {
242 0 : struct TAH_RequestHandler *rh = &handlers[i];
243 :
244 0 : if ( (0 == strcasecmp (url,
245 0 : rh->url)) &&
246 0 : ( (NULL == rh->method) ||
247 0 : (0 == strcasecmp (method,
248 : rh->method)) ) )
249 0 : return rh->handler (rh,
250 : connection,
251 : con_cls,
252 : upload_data,
253 : upload_data_size);
254 : }
255 : #define NOT_FOUND "<html><title>404: not found</title></html>"
256 0 : return TALER_MHD_reply_static (connection,
257 : MHD_HTTP_NOT_FOUND,
258 : "text/html",
259 : NOT_FOUND,
260 : strlen (NOT_FOUND));
261 : #undef NOT_FOUND
262 : }
263 :
264 :
265 : /**
266 : * Load configuration parameters for the auditor
267 : * server into the corresponding global variables.
268 : *
269 : * @return #GNUNET_OK on success
270 : */
271 : static enum GNUNET_GenericReturnValue
272 0 : auditor_serve_process_config (void)
273 : {
274 0 : if (NULL ==
275 0 : (TAH_plugin = TALER_AUDITORDB_plugin_load (cfg)))
276 : {
277 0 : GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
278 : "Failed to initialize DB subsystem to interact with auditor database\n");
279 0 : return GNUNET_SYSERR;
280 : }
281 0 : if (NULL ==
282 0 : (TAH_eplugin = TALER_EXCHANGEDB_plugin_load (cfg)))
283 : {
284 0 : GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
285 : "Failed to initialize DB subsystem to query exchange database\n");
286 0 : return GNUNET_SYSERR;
287 : }
288 0 : if (GNUNET_SYSERR ==
289 0 : TAH_eplugin->preflight (TAH_eplugin->cls))
290 : {
291 0 : GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
292 : "Failed to initialize DB subsystem to query exchange database\n");
293 0 : return GNUNET_SYSERR;
294 : }
295 0 : if (GNUNET_OK !=
296 0 : TALER_config_get_currency (cfg,
297 : &TAH_currency))
298 : {
299 0 : return GNUNET_SYSERR;
300 : }
301 : {
302 : char *pub;
303 :
304 0 : if (GNUNET_OK ==
305 0 : GNUNET_CONFIGURATION_get_value_string (cfg,
306 : "AUDITOR",
307 : "PUBLIC_KEY",
308 : &pub))
309 : {
310 0 : if (GNUNET_OK !=
311 0 : GNUNET_CRYPTO_eddsa_public_key_from_string (pub,
312 : strlen (pub),
313 : &auditor_pub.eddsa_pub))
314 : {
315 0 : GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
316 : "Invalid public key given in auditor configuration.");
317 0 : GNUNET_free (pub);
318 0 : return GNUNET_SYSERR;
319 : }
320 0 : GNUNET_free (pub);
321 0 : return GNUNET_OK;
322 : }
323 : }
324 :
325 : {
326 : /* Fall back to trying to read private key */
327 : char *auditor_key_file;
328 : struct GNUNET_CRYPTO_EddsaPrivateKey eddsa_priv;
329 :
330 0 : if (GNUNET_OK !=
331 0 : GNUNET_CONFIGURATION_get_value_filename (cfg,
332 : "auditor",
333 : "AUDITOR_PRIV_FILE",
334 : &auditor_key_file))
335 : {
336 0 : GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR,
337 : "AUDITOR",
338 : "PUBLIC_KEY");
339 0 : GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR,
340 : "AUDITOR",
341 : "AUDITOR_PRIV_FILE");
342 0 : return GNUNET_SYSERR;
343 : }
344 0 : if (GNUNET_OK !=
345 0 : GNUNET_CRYPTO_eddsa_key_from_file (auditor_key_file,
346 : GNUNET_NO,
347 : &eddsa_priv))
348 : {
349 : /* Both failed, complain! */
350 0 : GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR,
351 : "AUDITOR",
352 : "PUBLIC_KEY");
353 0 : GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
354 : "Failed to initialize auditor key from file `%s'\n",
355 : auditor_key_file);
356 0 : GNUNET_free (auditor_key_file);
357 0 : return 1;
358 : }
359 0 : GNUNET_free (auditor_key_file);
360 0 : GNUNET_CRYPTO_eddsa_key_get_public (&eddsa_priv,
361 : &auditor_pub.eddsa_pub);
362 : }
363 0 : return GNUNET_OK;
364 : }
365 :
366 :
367 : /**
368 : * Function run on shutdown.
369 : *
370 : * @param cls NULL
371 : */
372 : static void
373 0 : do_shutdown (void *cls)
374 : {
375 : struct MHD_Daemon *mhd;
376 : (void) cls;
377 :
378 0 : mhd = TALER_MHD_daemon_stop ();
379 0 : TEAH_DEPOSIT_CONFIRMATION_done ();
380 0 : if (NULL != mhd)
381 0 : MHD_stop_daemon (mhd);
382 0 : if (NULL != TAH_plugin)
383 : {
384 0 : TALER_AUDITORDB_plugin_unload (TAH_plugin);
385 0 : TAH_plugin = NULL;
386 : }
387 0 : if (NULL != TAH_eplugin)
388 : {
389 0 : TALER_EXCHANGEDB_plugin_unload (TAH_eplugin);
390 0 : TAH_eplugin = NULL;
391 : }
392 0 : }
393 :
394 :
395 : /**
396 : * Main function that will be run by the scheduler.
397 : *
398 : * @param cls closure
399 : * @param args remaining command-line arguments
400 : * @param cfgfile name of the configuration file used (for saving, can be
401 : * NULL!)
402 : * @param config configuration
403 : */
404 : static void
405 0 : run (void *cls,
406 : char *const *args,
407 : const char *cfgfile,
408 : const struct GNUNET_CONFIGURATION_Handle *config)
409 : {
410 : enum TALER_MHD_GlobalOptions go;
411 : int fh;
412 :
413 : (void) cls;
414 : (void) args;
415 : (void) cfgfile;
416 0 : go = TALER_MHD_GO_NONE;
417 0 : if (auditor_connection_close)
418 0 : go |= TALER_MHD_GO_FORCE_CONNECTION_CLOSE;
419 0 : TALER_MHD_setup (go);
420 0 : cfg = config;
421 :
422 0 : GNUNET_SCHEDULER_add_shutdown (&do_shutdown,
423 : NULL);
424 0 : if (GNUNET_OK !=
425 0 : auditor_serve_process_config ())
426 : {
427 0 : global_ret = EXIT_NOTCONFIGURED;
428 0 : GNUNET_SCHEDULER_shutdown ();
429 0 : return;
430 : }
431 0 : TEAH_DEPOSIT_CONFIRMATION_init ();
432 0 : fh = TALER_MHD_bind (cfg,
433 : "auditor",
434 : &serve_port);
435 0 : if ( (0 == serve_port) &&
436 : (-1 == fh) )
437 : {
438 0 : GNUNET_SCHEDULER_shutdown ();
439 0 : return;
440 : }
441 : {
442 : struct MHD_Daemon *mhd;
443 :
444 0 : mhd = MHD_start_daemon (MHD_USE_SUSPEND_RESUME
445 : | MHD_USE_PIPE_FOR_SHUTDOWN
446 : | MHD_USE_DEBUG | MHD_USE_DUAL_STACK
447 : | MHD_USE_TCP_FASTOPEN,
448 : (-1 == fh) ? serve_port : 0,
449 : NULL, NULL,
450 : &handle_mhd_request, NULL,
451 : MHD_OPTION_LISTEN_BACKLOG_SIZE,
452 : (unsigned int) 1024,
453 : MHD_OPTION_LISTEN_SOCKET,
454 : fh,
455 : MHD_OPTION_EXTERNAL_LOGGER,
456 : &TALER_MHD_handle_logs,
457 : NULL,
458 : MHD_OPTION_NOTIFY_COMPLETED,
459 : &handle_mhd_completion_callback,
460 : NULL,
461 : MHD_OPTION_CONNECTION_TIMEOUT,
462 : connection_timeout,
463 : MHD_OPTION_END);
464 0 : if (NULL == mhd)
465 : {
466 0 : GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
467 : "Failed to launch HTTP service. Is the port in use?\n");
468 0 : GNUNET_SCHEDULER_shutdown ();
469 0 : return;
470 : }
471 0 : global_ret = EXIT_SUCCESS;
472 0 : TALER_MHD_daemon_start (mhd);
473 : }
474 : }
475 :
476 :
477 : /**
478 : * The main function of the taler-auditor-httpd server ("the auditor").
479 : *
480 : * @param argc number of arguments from the command line
481 : * @param argv command line arguments
482 : * @return 0 ok, 1 on error
483 : */
484 : int
485 2 : main (int argc,
486 : char *const *argv)
487 : {
488 2 : const struct GNUNET_GETOPT_CommandLineOption options[] = {
489 2 : GNUNET_GETOPT_option_flag ('C',
490 : "connection-close",
491 : "force HTTP connections to be closed after each request",
492 : &auditor_connection_close),
493 2 : GNUNET_GETOPT_option_uint ('t',
494 : "timeout",
495 : "SECONDS",
496 : "after how long do connections timeout by default (in seconds)",
497 : &connection_timeout),
498 2 : GNUNET_GETOPT_option_help (
499 : "HTTP server providing a RESTful API to access a Taler auditor"),
500 2 : GNUNET_GETOPT_option_version (VERSION "-" VCS_VERSION),
501 : GNUNET_GETOPT_OPTION_END
502 : };
503 : int ret;
504 :
505 2 : TALER_OS_init ();
506 2 : ret = GNUNET_PROGRAM_run (argc, argv,
507 : "taler-auditor-httpd",
508 : "Taler auditor HTTP service",
509 : options,
510 : &run, NULL);
511 2 : if (GNUNET_SYSERR == ret)
512 2 : return EXIT_INVALIDARGUMENT;
513 0 : if (GNUNET_NO == ret)
514 0 : return EXIT_SUCCESS;
515 0 : return global_ret;
516 : }
517 :
518 :
519 : /* end of taler-auditor-httpd.c */
|