diff options
author | Christian Grothoff <christian@grothoff.org> | 2019-11-13 15:01:09 +0100 |
---|---|---|
committer | Christian Grothoff <christian@grothoff.org> | 2019-11-13 15:01:09 +0100 |
commit | c8047726772c2f4a18a6b0ebc7ce879bf260860a (patch) | |
tree | a6bab2e9a42c72a720c893b9c2505077067e6bfc /src | |
download | sync-c8047726772c2f4a18a6b0ebc7ce879bf260860a.tar.gz sync-c8047726772c2f4a18a6b0ebc7ce879bf260860a.tar.bz2 sync-c8047726772c2f4a18a6b0ebc7ce879bf260860a.zip |
skeleton
Diffstat (limited to 'src')
26 files changed, 3444 insertions, 0 deletions
diff --git a/src/Makefile.am b/src/Makefile.am new file mode 100644 index 0000000..0746ad4 --- /dev/null +++ b/src/Makefile.am @@ -0,0 +1,3 @@ +# This Makefile is in the public domain +AM_CPPFLAGS = -I$(top_srcdir)/src/include +SUBDIRS = include syncdb sync lib diff --git a/src/include/Makefile.am b/src/include/Makefile.am new file mode 100644 index 0000000..abcbfe9 --- /dev/null +++ b/src/include/Makefile.am @@ -0,0 +1,10 @@ +# This Makefile.am is in the public domain +EXTRA_DIST = \ + platform.h + +talerincludedir = $(includedir)/taler + +talerinclude_HEADERS = \ + sync_database_plugin.h \ + sync_service.h \ + sync_database_lib.h diff --git a/src/include/platform.h b/src/include/platform.h new file mode 100644 index 0000000..b17c64f --- /dev/null +++ b/src/include/platform.h @@ -0,0 +1,60 @@ +/* + This file is part of TALER + Copyright (C) 2014, 2015, 2016 GNUnet e.V. and INRIA + + TALER 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 + Foundation; either version 3, or (at your option) any later version. + + TALER is distributed in the hope that it will be useful, but WITHOUT ANY + WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + A PARTICULAR PURPOSE. See the GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along with + TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/> +*/ + +/** + * @file include/platform.h + * @brief This file contains the includes and definitions which are used by the + * rest of the modules + * @author Sree Harsha Totakura <sreeharsha@totakura.in> + */ + +#ifndef PLATFORM_H_ +#define PLATFORM_H_ + +/* Include our configuration header */ +#ifndef HAVE_USED_CONFIG_H +# define HAVE_USED_CONFIG_H +# ifdef HAVE_CONFIG_H +# include "sync_config.h" +# endif +#endif + + +#if (GNUNET_EXTRA_LOGGING >= 1) +#define VERBOSE(cmd) cmd +#else +#define VERBOSE(cmd) do { break; } while (0) +#endif + +/* Include the features available for GNU source */ +#define _GNU_SOURCE + +/* Include GNUnet's platform file */ +#include <gnunet/platform.h> + +/* Do not use shortcuts for gcrypt mpi */ +#define GCRYPT_NO_MPI_MACROS 1 + +/* Do not use deprecated functions from gcrypt */ +#define GCRYPT_NO_DEPRECATED 1 + +/* Ignore MHD deprecations for now as we want to be compatible + to "ancient" MHD releases. */ +#define MHD_NO_DEPRECATION 1 + +#endif /* PLATFORM_H_ */ + +/* end of platform.h */ diff --git a/src/include/sync_database_lib.h b/src/include/sync_database_lib.h new file mode 100644 index 0000000..2a67ec3 --- /dev/null +++ b/src/include/sync_database_lib.h @@ -0,0 +1,46 @@ +/* + This file is part of TALER + Copyright (C) 2014-2017 Inria & GNUnet e.V. + + TALER is free software; you can redistribute it and/or modify it under the + terms of the GNU General Public License as published by the Free Software + Foundation; either version 3, or (at your option) any later version. + + TALER is distributed in the hope that it will be useful, but WITHOUT ANY + WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + A PARTICULAR PURPOSE. See the GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along with + TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/> +*/ +/** + * + */ +#ifndef SYNC_DB_LIB_H +#define SYNC_DB_LIB_H + +#include <taler/taler_util.h> +#include "sync_database_plugin.h" + +/** + * Initialize the plugin. + * + * @param cfg configuration to use + * @return NULL on failure + */ +struct SYNC_DatabasePlugin * +SYNC_DB_plugin_load (const struct GNUNET_CONFIGURATION_Handle *cfg); + + +/** + * Shutdown the plugin. + * + * @param plugin plugin to unload + */ +void +SYNC_DB_plugin_unload (struct SYNC_DatabasePlugin *plugin); + + +#endif /* SYNC_DB_LIB_H */ + +/* end of sync_database_lib.h */ diff --git a/src/include/sync_database_plugin.h b/src/include/sync_database_plugin.h new file mode 100644 index 0000000..5563cf3 --- /dev/null +++ b/src/include/sync_database_plugin.h @@ -0,0 +1,117 @@ +/* + This file is part of Sync + Copyright (C) 2019 Taler Systems SA + + Sync is free software; you can redistribute it and/or modify it under the + terms of the GNU Lesser General Public License as published by the Free Software + Foundation; either version 3, or (at your option) any later version. + + Sync is distributed in the hope that it will be useful, but WITHOUT ANY + WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + A PARTICULAR PURPOSE. See the GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along with + Sync; see the file COPYING.GPL. If not, see <http://www.gnu.org/licenses/> +*/ +/** + * @file include/sync_database_plugin.h + * @brief database access for Sync + * @author Christian Grothoff + */ +#ifndef TALER_SYNC_DATABASE_PLUGIN_H +#define TALER_SYNC_DATABASE_PLUGIN_H + +#include <gnunet/gnunet_util_lib.h> +#include <sync_error_codes.h> +#include "sync_service.h" +#include <jansson.h> +#include <taler/taler_util.h> + +/** + * Handle to interact with the database. + * + * Functions ending with "_TR" run their OWN transaction scope + * and MUST NOT be called from within a transaction setup by the + * caller. Functions ending with "_NT" require the caller to + * setup a transaction scope. Functions without a suffix are + * simple, single SQL queries that MAY be used either way. + */ +struct SYNC_DatabasePlugin +{ + + /** + * Closure for all callbacks. + */ + void *cls; + + /** + * Name of the library which generated this plugin. Set by the + * plugin loader. + */ + char *library_name; + + /** + * Drop sync tables. Used for testcases. + * + * @param cls closure + * @return #GNUNET_OK upon success; #GNUNET_SYSERR upon failure + */ + int + (*drop_tables) (void *cls); + + /** + * Function called to perform "garbage collection" on the + * database, expiring records we no longer require. Deletes + * all user records that are not paid up (and by cascade deletes + * the associated recovery documents). Also deletes expired + * truth and financial records older than @a fin_expire. + * + * @param cls closure + * @param fin_expire financial records older than the given + * time stamp should be garbage collected (usual + * values might be something like 6-10 years in the past) + * @return transaction status + */ + enum SYNC_DB_QueryStatus + (*gc)(void *cls, + struct GNUNET_TIME_Absolute fin_expire); + + /** + * Do a pre-flight check that we are not in an uncommitted transaction. + * If we are, try to commit the previous transaction and output a warning. + * Does not return anything, as we will continue regardless of the outcome. + * + * @param cls the `struct PostgresClosure` with the plugin-specific state + */ + void + (*preflight) (void *cls); + + /** + * Check that the database connection is still up. + * + * @param pg connection to check + */ + void + (*check_connection) (void *cls); + + /** + * Store backup. + * + * @param cls closure + * @return transaction status + */ + enum GNUNET_DB_QueryStatus + (*store_backup)(void *cls, + ...); + + /** + * Obtain backup. + * + * @param cls closure + */ + enum GNUNET_DB_QueryStatus + (*lookup_backup)(void *cls, + ...); + +}; +#endif diff --git a/src/include/sync_service.h b/src/include/sync_service.h new file mode 100644 index 0000000..efe46fe --- /dev/null +++ b/src/include/sync_service.h @@ -0,0 +1,64 @@ +/* + This file is part of TALER + Copyright (C) 2019 Taler Systems SA + + Anastasis 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 + Foundation; either version 3, or (at your option) any later version. + + Anastasis is distributed in the hope that it will be useful, but WITHOUT ANY + WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License along with + Anastasis; see the file COPYING.LIB. If not, see <http://www.gnu.org/licenses/> +*/ +/** + * @file include/sync_service.h + * @brief C interface of libsync, a C library to use sync's HTTP API + * @author Christian Grothoff + */ +#ifndef _SYNC_SERVICE_H +#define _SYNC_SERVICE_H + +#include <gnunet/gnunet_curl_lib.h> +#include <jansson.h> + + +/** + * An EdDSA public key that is used to identify a user's account. + */ +struct SYNC_AccountPubP +{ + struct GNUNET_CRYPTO_EddsaPublicKey pub; +}; + + +struct SYNC_UploadOperation; + +struct SYNC_UploadOperation * +SYNC_upload (struct GNUNET_CURL_Context *ctx, + const char *base_url, + ...); + + +void +SYNC_upload_cancel (struct SYNC_UploadOperation *uo); + + + +struct SYNC_DownloadOperation; + +struct SYNC_DownloadOperation * +SYNC_download (struct GNUNET_CURL_Context *ctx, + const char *base_url, + ...); + + +void +SYNC_download_cancel (struct SYNC_DownloadOperation *uo); + + + + +#endif /* _SYNC_SERVICE_H */ diff --git a/src/lib/Makefile.am b/src/lib/Makefile.am new file mode 100644 index 0000000..5f05c26 --- /dev/null +++ b/src/lib/Makefile.am @@ -0,0 +1,32 @@ +# This Makefile.am is in the public domain +AM_CPPFLAGS = -I$(top_srcdir)/src/include + +if USE_COVERAGE + AM_CFLAGS = --coverage -O0 + XLIB = -lgcov +endif + +lib_LTLIBRARIES = \ + libsync.la + +libsync_la_LDFLAGS = \ + -version-info 0:0:0 \ + -no-undefined + +libsync_la_SOURCES = \ + sync_api_upload.c + +libsync_la_LIBADD = \ + -lgnunetcurl \ + -lgnunetjson \ + -lgnunetutil \ + -ljansson \ + $(XLIB) + +if HAVE_LIBCURL +libsync_la_LIBADD += -lcurl +else +if HAVE_LIBGNURL +libsync_la_LIBADD += -lgnurl +endif +endif diff --git a/src/lib/sync_api_upload.c b/src/lib/sync_api_upload.c new file mode 100644 index 0000000..353db1a --- /dev/null +++ b/src/lib/sync_api_upload.c @@ -0,0 +1,57 @@ +/* + This file is part of TALER + Copyright (C) 2014-2017 GNUnet e.V. and INRIA + + TALER is free software; you can redistribute it and/or modify + it under the terms of the GNU Lesser General Public License as + published by the Free Software Foundation; either version 2.1, + or (at your option) any later version. + + TALER is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with TALER; see the file COPYING.LGPL. If not, + see <http://www.gnu.org/licenses/> +*/ + +/** + * @file lib/sync_api_upload.c + * @brief Implementation of the upload POST + * @author Christian Grothoff + */ +#include "platform.h" +#include <curl/curl.h> +#include <jansson.h> +#include <microhttpd.h> /* just for HTTP status codes */ +#include <gnunet/gnunet_util_lib.h> +#include <gnunet/gnunet_curl_lib.h> +#include "anastasis_service.h" + + +/** + * @brief + */ +struct SYNC_UploadOperation +{ + + /** + * The url for this request. + */ + char *url; + + /** + * Handle for the request. + */ + struct GNUNET_CURL_Job *job; + + /** + * Reference to the execution context. + */ + struct GNUNET_CURL_Context *ctx; + +}; + +/* end of sync_api_upload.c */ diff --git a/src/sync/Makefile.am b/src/sync/Makefile.am new file mode 100644 index 0000000..c703348 --- /dev/null +++ b/src/sync/Makefile.am @@ -0,0 +1,28 @@ +# This Makefile.am is in the public domain +AM_CPPFLAGS = -I$(top_srcdir)/src/include + +pkgcfgdir = $(prefix)/share/taler/config.d/ + +pkgcfg_DATA = \ + sync.conf + +bin_PROGRAMS = \ + sync-httpd + +sync_httpd_SOURCES = \ + sync-httpd.c sync-httpd.h \ + sync-httpd_parsing.c sync-httpd_parsing.h \ + sync-httpd_responses.c sync-httpd_responses.h \ + sync-httpd_mhd.c sync-httpd_mhd.h \ + sync-httpd_policy.c sync-httpd_policy.h + +sync_httpd_LDADD = \ + $(top_builddir)/src/stasis/libsyncdb.la \ + -lmicrohttpd \ + -ljansson \ + -lgnunetcurl \ + -lgnunetjson \ + -lgnunetutil + +EXTRA_DIST = \ + $(pkgcfg_DATA) diff --git a/src/sync/sync-httpd.c b/src/sync/sync-httpd.c new file mode 100644 index 0000000..d4f3830 --- /dev/null +++ b/src/sync/sync-httpd.c @@ -0,0 +1,744 @@ +/* + This file is part of TALER + (C) 2019 Taler Systems SA + + TALER 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 + Foundation; either version 3, or (at your option) any later version. + + TALER is distributed in the hope that it will be useful, but WITHOUT ANY + WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + A PARTICULAR PURPOSE. See the GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along with + TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/> +*/ +/** + * @file backup/sync-httpd.c + * @brief HTTP serving layer intended to provide basic backup operations + * @author Christian Grothoff + */ +#include "platform.h" +#include <microhttpd.h> +#include <gnunet/gnunet_util_lib.h> +#include "sync-httpd_responses.h" +#include "sync-httpd.h" +#include "sync-httpd_parsing.h" +#include "sync-httpd_mhd.h" +#include "sync_database_lib.h" +#include "sync-httpd_policy.h" + +/** + * Backlog for listen operation on unix-domain sockets. + */ +#define UNIX_BACKLOG 500 + +/** + * The port we are running on + */ +static long long unsigned port; + +/** + * Should a "Connection: close" header be added to each HTTP response? + */ +int TMH_sync_connection_close; + +/** + * Task running the HTTP server. + */ +static struct GNUNET_SCHEDULER_Task *mhd_task; + +/** + * Global return code + */ +static int result; + +/** + * The MHD Daemon + */ +static struct MHD_Daemon *mhd; + +/** + * Path for the unix domain-socket + * to run the daemon on. + */ +static char *serve_unixpath; + +/** + * File mode for unix-domain socket. + */ +static mode_t unixpath_mode; + +/** + * Connection handle to the our database + */ +struct sync_DatabasePlugin *db; + + +/** + * Return GNUNET_YES if given a valid correlation ID and + * GNUNET_NO otherwise. + * + * @returns GNUNET_YES iff given a valid correlation ID + */ +static int +is_valid_correlation_id (const char *correlation_id) +{ + if (strlen (correlation_id) >= 64) + return GNUNET_NO; + for (int i = 0; i < strlen (correlation_id); i++) + if (! (isalnum (correlation_id[i]) ||(correlation_id[i] == '-'))) + return GNUNET_NO; + return GNUNET_YES; +} + + +/** + * A client has requested the given url using the given method + * (#MHD_HTTP_METHOD_GET, #MHD_HTTP_METHOD_PUT, + * #MHD_HTTP_METHOD_DELETE, #MHD_HTTP_METHOD_POST, etc). The callback + * must call MHD callbacks to provide content to give back to the + * client and return an HTTP status code (i.e. #MHD_HTTP_OK, + * #MHD_HTTP_NOT_FOUND, etc.). + * + * @param cls argument given together with the function + * pointer when the handler was registered with MHD + * @param url the requested url + * @param method the HTTP method used (#MHD_HTTP_METHOD_GET, + * #MHD_HTTP_METHOD_PUT, etc.) + * @param version the HTTP version string (i.e. + * #MHD_HTTP_VERSION_1_1) + * @param upload_data the data being uploaded (excluding HEADERS, + * for a POST that fits into memory and that is encoded + * with a supported encoding, the POST data will NOT be + * given in upload_data and is instead available as + * part of #MHD_get_connection_values; very large POST + * data *will* be made available incrementally in + * @a upload_data) + * @param upload_data_size set initially to the size of the + * @a upload_data provided; the method must update this + * value to the number of bytes NOT processed; + * @param con_cls pointer that the callback can set to some + * address and that will be preserved by MHD for future + * calls for this request; since the access handler may + * be called many times (i.e., for a PUT/POST operation + * with plenty of upload data) this allows the application + * to easily associate some request-specific state. + * If necessary, this state can be cleaned up in the + * global #MHD_RequestCompletedCallback (which + * can be set with the #MHD_OPTION_NOTIFY_COMPLETED). + * Initially, `*con_cls` will be NULL. + * @return #MHD_YES if the connection was handled successfully, + * #MHD_NO if the socket must be closed due to a serios + * error while handling the request + */ +static int +url_handler (void *cls, + struct MHD_Connection *connection, + const char *url, + const char *method, + const char *version, + const char *upload_data, + size_t *upload_data_size, + void **con_cls) +{ + static struct TMH_RequestHandler handlers[] = { + /* Landing page, tell humans to go away. */ + { "/", MHD_HTTP_METHOD_GET, "text/plain", + "Hello, I'm sync. This HTTP server is not for humans.\n", 0, + &TMH_MHD_handler_static_response, MHD_HTTP_OK }, + { "/agpl", MHD_HTTP_METHOD_GET, "text/plain", + NULL, 0, + &TMH_MHD_handler_agpl_redirect, MHD_HTTP_FOUND }, + {NULL, NULL, NULL, NULL, 0, 0 } + }; + static struct TMH_RequestHandler h404 = { + "", NULL, "text/html", + "<html><title>404: not found</title></html>", 0, + &TMH_MHD_handler_static_response, MHD_HTTP_NOT_FOUND + }; + + struct TM_HandlerContext *hc; + struct GNUNET_AsyncScopeId aid; + const char *correlation_id = NULL; + + hc = *con_cls; + + if (NULL == hc) + { + GNUNET_async_scope_fresh (&aid); + /* We only read the correlation ID on the first callback for every client */ + correlation_id = MHD_lookup_connection_value (connection, + MHD_HEADER_KIND, + "sync-Correlation-Id"); + if ((NULL != correlation_id) && + (GNUNET_YES != is_valid_correlation_id (correlation_id))) + { + GNUNET_log (GNUNET_ERROR_TYPE_WARNING, + "illegal incoming correlation ID\n"); + correlation_id = NULL; + } + } + else + { + aid = hc->async_scope_id; + } + + GNUNET_SCHEDULER_begin_async_scope (&aid); + + if (NULL != correlation_id) + GNUNET_log (GNUNET_ERROR_TYPE_INFO, + "Handling request for (%s) URL '%s', correlation_id=%s\n", + method, + url, + correlation_id); + else + GNUNET_log (GNUNET_ERROR_TYPE_INFO, + "Handling request (%s) for URL '%s'\n", + method, + url); + if (0 == strncmp (url, + "/backup/", + strlen ("/backup/"))) + { + // return handle_policy (...); + if (0 == strcmp (method, MHD_HTTP_METHOD_GET)) + { + return sync_handler_backup_get (connection, + url, + con_cls); + } + if (0 == strcmp (method, MHD_HTTP_METHOD_POST)) + { + return sync_handler_backup_post (connection, + con_cls, + url, + upload_data, + upload_data_size); + } + } + for (unsigned int i = 0; NULL != handlers[i].url; i++) + { + struct TMH_RequestHandler *rh = &handlers[i]; + + if ( (0 == strcmp (url, + rh->url)) && + ( (NULL == rh->method) || + (0 == strcmp (method, + rh->method)) ) ) + { + int ret; + + ret = rh->handler (rh, + connection, + con_cls, + upload_data, + upload_data_size); + hc = *con_cls; + if (NULL != hc) + { + hc->rh = rh; + /* Store the async context ID, so we can restore it if + * we get another callack for this request. */ + hc->async_scope_id = aid; + } + return ret; + } + } + return TMH_MHD_handler_static_response (&h404, + connection, + con_cls, + upload_data, + upload_data_size); +} + + +/** + * Shutdown task (magically invoked when the application is being + * quit) + * + * @param cls NULL + */ +static void +do_shutdown (void *cls) +{ + if (NULL != mhd_task) + { + GNUNET_SCHEDULER_cancel (mhd_task); + mhd_task = NULL; + } + if (NULL != mhd) + { + MHD_stop_daemon (mhd); + mhd = NULL; + } + if (NULL != db) + { + sync_DB_plugin_unload (db); + db = NULL; + } +} + + +/** + * Function called whenever MHD is done with a request. If the + * request was a POST, we may have stored a `struct Buffer *` in the + * @a con_cls that might still need to be cleaned up. Call the + * respective function to free the memory. + * + * @param cls client-defined closure + * @param connection connection handle + * @param con_cls value as set by the last call to + * the #MHD_AccessHandlerCallback + * @param toe reason for request termination + * @see #MHD_OPTION_NOTIFY_COMPLETED + * @ingroup request + */ +static void +handle_mhd_completion_callback (void *cls, + struct MHD_Connection *connection, + void **con_cls, + enum MHD_RequestTerminationCode toe) +{ + struct TM_HandlerContext *hc = *con_cls; + + if (NULL == hc) + return; + GNUNET_log (GNUNET_ERROR_TYPE_INFO, + "Finished handling request for `%s' with status %d\n", + hc->rh->url, + (int) toe); + hc->cc (hc); + *con_cls = NULL; +} + + +/** + * Function that queries MHD's select sets and + * starts the task waiting for them. + */ +static struct GNUNET_SCHEDULER_Task * +prepare_daemon (void); + + +/** + * Set if we should immediately #MHD_run again. + */ +static int triggered; + + +/** + * Call MHD to process pending requests and then go back + * and schedule the next run. + * + * @param cls the `struct MHD_Daemon` of the HTTP server to run + */ +static void +run_daemon (void *cls) +{ + mhd_task = NULL; + do { + triggered = 0; + GNUNET_assert (MHD_YES == MHD_run (mhd)); + } while (0 != triggered); + mhd_task = prepare_daemon (); +} + + +/** + * Kick MHD to run now, to be called after MHD_resume_connection(). + * Basically, we need to explicitly resume MHD's event loop whenever + * we made progress serving a request. This function re-schedules + * the task processing MHD's activities to run immediately. + */ +void +TMH_trigger_daemon () +{ + if (NULL != mhd_task) + { + GNUNET_SCHEDULER_cancel (mhd_task); + mhd_task = NULL; + run_daemon (NULL); + } + else + { + triggered = 1; + } +} + + +/** + * Function that queries MHD's select sets and + * starts the task waiting for them. + * + * @param daemon_handle HTTP server to prepare to run + */ +static struct GNUNET_SCHEDULER_Task * +prepare_daemon () +{ + struct GNUNET_SCHEDULER_Task *ret; + fd_set rs; + fd_set ws; + fd_set es; + struct GNUNET_NETWORK_FDSet *wrs; + struct GNUNET_NETWORK_FDSet *wws; + int max; + MHD_UNSIGNED_LONG_LONG timeout; + int haveto; + struct GNUNET_TIME_Relative tv; + + FD_ZERO (&rs); + FD_ZERO (&ws); + FD_ZERO (&es); + wrs = GNUNET_NETWORK_fdset_create (); + wws = GNUNET_NETWORK_fdset_create (); + max = -1; + GNUNET_assert (MHD_YES == + MHD_get_fdset (mhd, + &rs, + &ws, + &es, + &max)); + haveto = MHD_get_timeout (mhd, &timeout); + if (haveto == MHD_YES) + tv.rel_value_us = (uint64_t) timeout * 1000LL; + else + tv = GNUNET_TIME_UNIT_FOREVER_REL; + GNUNET_NETWORK_fdset_copy_native (wrs, &rs, max + 1); + GNUNET_NETWORK_fdset_copy_native (wws, &ws, max + 1); + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, + "Adding run_daemon select task\n"); + ret = GNUNET_SCHEDULER_add_select (GNUNET_SCHEDULER_PRIORITY_HIGH, + tv, + wrs, + wws, + &run_daemon, + NULL); + GNUNET_NETWORK_fdset_destroy (wrs); + GNUNET_NETWORK_fdset_destroy (wws); + return ret; +} + + +/** + * Main function that will be run by the scheduler. + * + * @param cls closure + * @param args remaining command-line arguments + * @param cfgfile name of the configuration file used (for saving, can be + * NULL!) + * @param config configuration + */ +static void +run (void *cls, + char *const *args, + const char *cfgfile, + const struct GNUNET_CONFIGURATION_Handle *config) +{ + int fh; + + GNUNET_log (GNUNET_ERROR_TYPE_INFO, + "Starting sync-httpd\n"); + + result = GNUNET_SYSERR; + GNUNET_SCHEDULER_add_shutdown (&do_shutdown, + NULL); + GNUNET_assert (GNUNET_OK == + GNUNET_log_setup ("sync-httpd", + "WARNING", + NULL)); + if (NULL == + (db = sync_DB_plugin_load (config))) + { + GNUNET_SCHEDULER_shutdown (); + return; + } + + { + const char *choices[] = {"tcp", + "unix", + NULL}; + + const char *serve_type; + + if (GNUNET_OK != + GNUNET_CONFIGURATION_get_value_choice (config, + "sync", + "SERVE", + choices, + &serve_type)) + { + GNUNET_log_config_invalid (GNUNET_ERROR_TYPE_ERROR, + "sync", + "SERVE", + "serve type required"); + GNUNET_SCHEDULER_shutdown (); + return; + } + + if (0 == strcmp (serve_type, "unix")) + { + struct sockaddr_un *un; + char *mode; + struct GNUNET_NETWORK_Handle *nh; + + if (GNUNET_OK != + GNUNET_CONFIGURATION_get_value_filename (config, + "sync", + "unixpath", + &serve_unixpath)) + { + GNUNET_log_config_invalid (GNUNET_ERROR_TYPE_ERROR, + "sync", + "unixpath", + "unixpath required"); + GNUNET_SCHEDULER_shutdown (); + return; + } + + if (strlen (serve_unixpath) >= sizeof (un->sun_path)) + { + fprintf (stderr, + "Invalid configuration: unix path too long\n"); + GNUNET_SCHEDULER_shutdown (); + return; + } + + if (GNUNET_OK != + GNUNET_CONFIGURATION_get_value_string (config, + "sync", + "UNIXPATH_MODE", + &mode)) + { + GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR, + "sync", + "UNIXPATH_MODE"); + GNUNET_SCHEDULER_shutdown (); + return; + } + errno = 0; + unixpath_mode = (mode_t) strtoul (mode, NULL, 8); + if (0 != errno) + { + GNUNET_log_config_invalid (GNUNET_ERROR_TYPE_ERROR, + "sync", + "UNIXPATH_MODE", + "must be octal number"); + GNUNET_free (mode); + GNUNET_SCHEDULER_shutdown (); + return; + } + GNUNET_free (mode); + + GNUNET_log (GNUNET_ERROR_TYPE_INFO, + "Creating listen socket '%s' with mode %o\n", + serve_unixpath, unixpath_mode); + + if (GNUNET_OK != GNUNET_DISK_directory_create_for_file (serve_unixpath)) + { + GNUNET_log_strerror_file (GNUNET_ERROR_TYPE_ERROR, + "mkdir", + serve_unixpath); + } + + un = GNUNET_new (struct sockaddr_un); + un->sun_family = AF_UNIX; + strncpy (un->sun_path, + serve_unixpath, + sizeof (un->sun_path) - 1); + + GNUNET_NETWORK_unix_precheck (un); + + if (NULL == (nh = GNUNET_NETWORK_socket_create (AF_UNIX, + SOCK_STREAM, + 0))) + { + GNUNET_log_strerror (GNUNET_ERROR_TYPE_ERROR, + "socket(AF_UNIX)"); + GNUNET_SCHEDULER_shutdown (); + return; + } + if (GNUNET_OK != + GNUNET_NETWORK_socket_bind (nh, + (void *) un, + sizeof (struct sockaddr_un))) + { + GNUNET_log_strerror (GNUNET_ERROR_TYPE_ERROR, + "bind(AF_UNIX)"); + GNUNET_SCHEDULER_shutdown (); + return; + } + if (GNUNET_OK != + GNUNET_NETWORK_socket_listen (nh, + UNIX_BACKLOG)) + { + GNUNET_log_strerror (GNUNET_ERROR_TYPE_ERROR, + "listen(AF_UNIX)"); + GNUNET_SCHEDULER_shutdown (); + return; + } + + fh = GNUNET_NETWORK_get_fd (nh); + GNUNET_NETWORK_socket_free_memory_only_ (nh); + if (0 != chmod (serve_unixpath, + unixpath_mode)) + { + GNUNET_log_strerror (GNUNET_ERROR_TYPE_ERROR, + "chmod"); + GNUNET_SCHEDULER_shutdown (); + return; + } + port = 0; + } + else if (0 == strcmp (serve_type, "tcp")) + { + char *bind_to; + + if (GNUNET_SYSERR == + GNUNET_CONFIGURATION_get_value_number (config, + "sync", + "PORT", + &port)) + { + GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR, + "sync", + "PORT"); + GNUNET_SCHEDULER_shutdown (); + return; + } + if (GNUNET_OK == + GNUNET_CONFIGURATION_get_value_string (config, + "sync", + "BIND_TO", + &bind_to)) + { + char port_str[6]; + struct addrinfo hints; + struct addrinfo *res; + int ec; + struct GNUNET_NETWORK_Handle *nh; + + GNUNET_snprintf (port_str, + sizeof (port_str), + "%u", + (uint16_t) port); + memset (&hints, 0, sizeof (hints)); + hints.ai_family = AF_UNSPEC; + hints.ai_socktype = SOCK_STREAM; + hints.ai_protocol = IPPROTO_TCP; + hints.ai_flags = AI_PASSIVE +#ifdef AI_IDN + | AI_IDN +#endif + ; + if (0 != + (ec = getaddrinfo (bind_to, + port_str, + &hints, + &res))) + { + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "Failed to resolve BIND_TO address `%s': %s\n", + bind_to, + gai_strerror (ec)); + GNUNET_free (bind_to); + GNUNET_SCHEDULER_shutdown (); + return; + } + GNUNET_free (bind_to); + + if (NULL == (nh = GNUNET_NETWORK_socket_create (res->ai_family, + res->ai_socktype, + res->ai_protocol))) + { + GNUNET_log_strerror (GNUNET_ERROR_TYPE_ERROR, + "socket"); + freeaddrinfo (res); + GNUNET_SCHEDULER_shutdown (); + return; + } + if (GNUNET_OK != + GNUNET_NETWORK_socket_bind (nh, + res->ai_addr, + res->ai_addrlen)) + { + GNUNET_log_strerror (GNUNET_ERROR_TYPE_ERROR, + "bind"); + freeaddrinfo (res); + GNUNET_SCHEDULER_shutdown (); + return; + } + freeaddrinfo (res); + if (GNUNET_OK != + GNUNET_NETWORK_socket_listen (nh, + UNIX_BACKLOG)) + { + GNUNET_log_strerror (GNUNET_ERROR_TYPE_ERROR, + "listen"); + GNUNET_SCHEDULER_shutdown (); + return; + } + fh = GNUNET_NETWORK_get_fd (nh); + GNUNET_NETWORK_socket_free_memory_only_ (nh); + } + else + { + fh = -1; + } + } + else + { + // not reached + GNUNET_assert (0); + } + } + mhd = MHD_start_daemon (MHD_USE_SUSPEND_RESUME | MHD_USE_DUAL_STACK, + port, + NULL, NULL, + &url_handler, NULL, + MHD_OPTION_LISTEN_SOCKET, fh, + MHD_OPTION_NOTIFY_COMPLETED, + &handle_mhd_completion_callback, NULL, + MHD_OPTION_CONNECTION_TIMEOUT, (unsigned + int) 10 /* 10s */, + MHD_OPTION_END); + if (NULL == mhd) + { + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "Failed to launch HTTP service, exiting.\n"); + GNUNET_SCHEDULER_shutdown (); + return; + } + result = GNUNET_OK; + mhd_task = prepare_daemon (); +} + + +/** + * The main function of the serve tool + * + * @param argc number of arguments from the command line + * @param argv command line arguments + * @return 0 ok, 1 on error + */ +int +main (int argc, + char *const *argv) +{ + struct GNUNET_GETOPT_CommandLineOption options[] = { + GNUNET_GETOPT_option_flag ('C', + "connection-close", + "force HTTP connections to be closed after each request", + &TMH_sync_connection_close), + + GNUNET_GETOPT_OPTION_END + }; + + if (GNUNET_OK != + GNUNET_PROGRAM_run (argc, argv, + "sync-httpd", + "sync HTTP interface", + options, &run, NULL)) + return 3; + return (GNUNET_OK == result) ? 0 : 1; +} diff --git a/src/sync/sync-httpd.h b/src/sync/sync-httpd.h new file mode 100644 index 0000000..56ad155 --- /dev/null +++ b/src/sync/sync-httpd.h @@ -0,0 +1,148 @@ +/* + This file is part of TALER + Copyright (C) 2019 Taler Systems SA + + TALER is free software; you can redistribute it and/or modify it under the + terms of the GNU General Public License as published by the Free Software + Foundation; either version 3, or (at your option) any later version. + + TALER is distributed in the hope that it will be useful, but WITHOUT ANY + WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + A PARTICULAR PURPOSE. See the GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along with + TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/> +*/ +/** + * @file sync/sync-httpd.h + * @brief HTTP serving layer + * @author Christian Grothoff + */ +#ifndef sync_HTTPD_H +#define sync_HTTPD_H + +#include "platform.h" +#include <microhttpd.h> +#include "sync_database_lib.h" + +/** + * @brief Struct describing an URL and the handler for it. + */ +struct TMH_RequestHandler +{ + + /** + * URL the handler is for. + */ + const char *url; + + /** + * Method the handler is for, NULL for "all". + */ + const char *method; + + /** + * Mime type to use in reply (hint, can be NULL). + */ + const char *mime_type; + + /** + * Raw data for the @e handler + */ + const void *data; + + /** + * Number of bytes in @e data, 0 for 0-terminated. + */ + size_t data_size; + + /** + * Function to call to handle the request. + * + * @param rh this struct + * @param mime_type the @e mime_type for the reply (hint, can be NULL) + * @param connection the MHD connection to handle + * @param[in,out] connection_cls the connection's closure (can be updated) + * @param upload_data upload data + * @param[in,out] upload_data_size number of bytes (left) in @a upload_data + * @return MHD result code + */ + int (*handler)(struct TMH_RequestHandler *rh, + struct MHD_Connection *connection, + void **connection_cls, + const char *upload_data, + size_t *upload_data_size); + + /** + * Default response code. + */ + int response_code; +}; + + +/** + * Each MHD response handler that sets the "connection_cls" to a + * non-NULL value must use a struct that has this struct as its first + * member. This struct contains a single callback, which will be + * invoked to clean up the memory when the contection is completed. + */ +struct TM_HandlerContext; + +/** + * Signature of a function used to clean up the context + * we keep in the "connection_cls" of MHD when handling + * a request. + * + * @param hc header of the context to clean up. + */ +typedef void +(*TM_ContextCleanup)(struct TM_HandlerContext *hc); + + +/** + * Each MHD response handler that sets the "connection_cls" to a + * non-NULL value must use a struct that has this struct as its first + * member. This struct contains a single callback, which will be + * invoked to clean up the memory when the connection is completed. + */ +struct TM_HandlerContext +{ + + /** + * Function to execute the handler-specific cleanup of the + * (typically larger) context. + */ + TM_ContextCleanup cc; + + /** + * Which request handler is handling this request? + */ + const struct TMH_RequestHandler *rh; + + /** + * Asynchronous request context id. + */ + struct GNUNET_AsyncScopeId async_scope_id; +}; + + +/** + * Should a "Connection: close" header be added to each HTTP response? + */ +extern int TMH_sync_connection_close; + +/** + * Handle to the database backend. + */ +extern struct sync_DatabasePlugin *db; + +/** + * Kick MHD to run now, to be called after MHD_resume_connection(). + * Basically, we need to explicitly resume MHD's event loop whenever + * we made progress serving a request. This function re-schedules + * the task processing MHD's activities to run immediately. + */ +void +TMH_trigger_daemon (void); + +#endif diff --git a/src/sync/sync-httpd_backup.c b/src/sync/sync-httpd_backup.c new file mode 100644 index 0000000..86ba955 --- /dev/null +++ b/src/sync/sync-httpd_backup.c @@ -0,0 +1,56 @@ +/* + This file is part of TALER + Copyright (C) 2019 Taler Systems SA + + TALER 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 + Foundation; either version 3, or (at your option) any later version. + + TALER is distributed in the hope that it will be useful, but WITHOUT ANY + WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License along with + TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/> +*/ +/** + * @file sync-httpd_backup.c + * @brief functions to handle incoming requests for backups + * @author Christian Grothoff + */ +#include "platform.h" +#include "sync-httpd.h" +#include <gnunet/gnunet_util_lib.h> + +/** + * @param connection the MHD connection to handle + * @param[in,out] connection_cls the connection's closure (can be updated) + * @param upload_data upload data + * @param[in,out] upload_data_size number of bytes (left) in @a upload_data + * @return MHD result code + */ +int +sync_handler_backup_get (struct MHD_Connection *connection, + const char *url, + void **con_cls) +{ + return MHD_NO; +} + + +/** + * @param connection the MHD connection to handle + * @param[in,out] connection_cls the connection's closure (can be updated) + * @param upload_data upload data + * @param[in,out] upload_data_size number of bytes (left) in @a upload_data + * @return MHD result code + */ +int +sync_handler_backup_post (struct MHD_Connection *connection, + void **con_cls, + const char *url, + const char *upload_data, + size_t *upload_data_size) +{ + return MHD_NO; +} diff --git a/src/sync/sync-httpd_backup.h b/src/sync/sync-httpd_backup.h new file mode 100644 index 0000000..1ba7408 --- /dev/null +++ b/src/sync/sync-httpd_backup.h @@ -0,0 +1,53 @@ +/* + This file is part of TALER + Copyright (C) 2014, 2015, 2016 GNUnet e.V. + + TALER 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 + Foundation; either version 3, or (at your option) any later version. + + TALER is distributed in the hope that it will be useful, but WITHOUT ANY + WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License along with + TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/> +*/ +/** + * @file sync-httpd_policy.h + * @brief functions to handle incoming requests on /backup/ + * @author Christian Grothoff + */ +#ifndef SYNC_HTTPD_BACKUP_H +#define SYNC_HTTPD_BACKUP_H +#include <microhttpd.h> + +/** + * @param connection the MHD connection to handle + * @param[in,out] connection_cls the connection's closure (can be updated) + * @param upload_data upload data + * @param[in,out] upload_data_size number of bytes (left) in @a upload_data + * @return MHD result code + */ +int +sync_handler_backup_get (struct MHD_Connection *connection, + const char *url, + void **con_cls); + + +/** + * @param connection the MHD connection to handle + * @param[in,out] connection_cls the connection's closure (can be updated) + * @param upload_data upload data + * @param[in,out] upload_data_size number of bytes (left) in @a upload_data + * @return MHD result code + */ +int +sync_handler_backup_post (struct MHD_Connection *connection, + void **con_cls, + const char *url, + const char *upload_data, + size_t *upload_data_size); + + +#endif diff --git a/src/sync/sync-httpd_mhd.c b/src/sync/sync-httpd_mhd.c new file mode 100644 index 0000000..269316d --- /dev/null +++ b/src/sync/sync-httpd_mhd.c @@ -0,0 +1,156 @@ +/* + This file is part of TALER + Copyright (C) 2014, 2015, 2016 GNUnet e.V. and INRIA + + TALER 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 + Foundation; either version 3, or (at your option) any later version. + + TALER is distributed in the hope that it will be useful, but WITHOUT ANY + WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License along with + TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/> +*/ +/** + * @file sync-httpd_mhd.c + * @brief helpers for MHD interaction; these are TALER_EXCHANGE_handler_ functions + * that generate simple MHD replies that do not require any real operations + * to be performed (error handling, static pages, etc.) + * @author Florian Dold + * @author Benedikt Mueller + * @author Christian Grothoff + */ +#include "platform.h" +#include <jansson.h> +#include "sync-httpd_mhd.h" +#include "sync-httpd_responses.h" + + +/** + * Function to call to handle the request by sending + * back static data from the @a rh. + * + * @param rh context of the handler + * @param connection the MHD connection to handle + * @param[in,out] connection_cls the connection's closure (can be updated) + * @param upload_data upload data + * @param[in,out] upload_data_size number of bytes (left) in @a upload_data + * @return MHD result code + */ +int +TMH_MHD_handler_static_response (struct TMH_RequestHandler *rh, + struct MHD_Connection *connection, + void **connection_cls, + const char *upload_data, + size_t *upload_data_size) +{ + struct MHD_Response *response; + int ret; + + if (0 == rh->data_size) + rh->data_size = strlen ((const char *) rh->data); + response = MHD_create_response_from_buffer (rh->data_size, + (void *) rh->data, + MHD_RESPMEM_PERSISTENT); + if (NULL == response) + { + GNUNET_break (0); + return MHD_NO; + } + TMH_RESPONSE_add_global_headers (response); + if (NULL != rh->mime_type) + (void) MHD_add_response_header (response, + MHD_HTTP_HEADER_CONTENT_TYPE, + rh->mime_type); + ret = MHD_queue_response (connection, + rh->response_code, + response); + MHD_destroy_response (response); + return ret; +} + + +/** + * Function to call to handle the request by sending + * back a redirect to the AGPL source code. + * + * @param rh context of the handler + * @param connection the MHD connection to handle + * @param[in,out] connection_cls the connection's closure (can be updated) + * @param upload_data upload data + * @param[in,out] upload_data_size number of bytes (left) in @a upload_data + * @return MHD result code + */ +int +TMH_MHD_handler_agpl_redirect (struct TMH_RequestHandler *rh, + struct MHD_Connection *connection, + void **connection_cls, + const char *upload_data, + size_t *upload_data_size) +{ + const char *agpl = + "This server is licensed under the Affero GPL. You will now be redirected to the source code."; + struct MHD_Response *response; + int ret; + + response = MHD_create_response_from_buffer (strlen (agpl), + (void *) agpl, + MHD_RESPMEM_PERSISTENT); + if (NULL == response) + { + GNUNET_break (0); + return MHD_NO; + } + TMH_RESPONSE_add_global_headers (response); + if (NULL != rh->mime_type) + (void) MHD_add_response_header (response, + MHD_HTTP_HEADER_CONTENT_TYPE, + rh->mime_type); + if (MHD_NO == + MHD_add_response_header (response, + MHD_HTTP_HEADER_LOCATION, + "http://www.git.taler.net/sync.git")) + { + GNUNET_break (0); + ret = MHD_NO; + } + else + { + ret = MHD_queue_response (connection, + rh->response_code, + response); + } + MHD_destroy_response (response); + return ret; +} + + +/** + * Function to call to handle the request by building a JSON + * reply with an error message from @a rh. + * + * @param rh context of the handler + * @param connection the MHD connection to handle + * @param[in,out] connection_cls the connection's closure (can be updated) + * @param upload_data upload data + * @param[in,out] upload_data_size number of bytes (left) in @a upload_data + * @return MHD result code + */ +int +TMH_MHD_handler_send_json_pack_error (struct TMH_RequestHandler *rh, + struct MHD_Connection *connection, + void **connection_cls, + const char *upload_data, + size_t *upload_data_size) +{ + return TMH_RESPONSE_reply_json_pack (connection, + rh->response_code, + "{s:s}", + "error", + rh->data); +} + + +/* end of taler-exchange-httpd_mhd.c */ diff --git a/src/sync/sync-httpd_mhd.h b/src/sync/sync-httpd_mhd.h new file mode 100644 index 0000000..b157baa --- /dev/null +++ b/src/sync/sync-httpd_mhd.h @@ -0,0 +1,113 @@ +/* + This file is part of TALER + Copyright (C) 2014, 2015 GNUnet e.V. and INRIA + + TALER 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 + Foundation; either version 3, or (at your option) any later version. + + TALER is distributed in the hope that it will be useful, but WITHOUT ANY + WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License along with + TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/> +*/ + +/** + * @file sync-httpd_mhd.h + * @brief helpers for MHD interaction, used to generate simple responses + * @author Florian Dold + * @author Benedikt Mueller + * @author Christian Grothoff + */ +#ifndef sync_HTTPD_MHD_H +#define sync_HTTPD_MHD_H +#include <gnunet/gnunet_util_lib.h> +#include <microhttpd.h> +#include "sync-httpd.h" + + +/** + * Function to call to handle the request by sending + * back static data from the @a rh. + * + * @param rh context of the handler + * @param connection the MHD connection to handle + * @param[in,out] connection_cls the connection's closure (can be updated) + * @param upload_data upload data + * @param[in,out] upload_data_size number of bytes (left) in @a upload_data + * @param mi merchant backend instance, NULL is allowed in this case! + * @return MHD result code + */ +int +TMH_MHD_handler_static_response (struct TMH_RequestHandler *rh, + struct MHD_Connection *connection, + void **connection_cls, + const char *upload_data, + size_t *upload_data_size); + + +/** + * Function to call to handle the request by sending + * back a redirect to the AGPL source code. + * + * @param rh context of the handler + * @param connection the MHD connection to handle + * @param[in,out] connection_cls the connection's closure (can be updated) + * @param upload_data upload data + * @param[in,out] upload_data_size number of bytes (left) in @a upload_data + * @param mi merchant backend instance, never NULL + * @return MHD result code + */ +int +TMH_MHD_handler_agpl_redirect (struct TMH_RequestHandler *rh, + struct MHD_Connection *connection, + void **connection_cls, + const char *upload_data, + size_t *upload_data_size); + + +/** + * Function to call to handle the request by building a JSON + * reply from varargs. + * + * @param rh context of the handler + * @param connection the MHD connection to handle + * @param[in,out] connection_cls the connection's closure (can be updated) + * @param response_code HTTP response code to use + * @param do_cache can the response be cached? (0: no, 1: yes) + * @param fmt format string for pack + * @param ... varargs + * @return MHD result code + */ +int +TMH_MHD_helper_send_json_pack (struct TMH_RequestHandler *rh, + struct MHD_Connection *connection, + void *connection_cls, + int response_code, + int do_cache, + const char *fmt, + ...); + + +/** + * Function to call to handle the request by building a JSON + * reply with an error message from @a rh. + * + * @param rh context of the handler + * @param connection the MHD connection to handle + * @param[in,out] connection_cls the connection's closure (can be updated) + * @param upload_data upload data + * @param[in,out] upload_data_size number of bytes (left) in @a upload_data + * @return MHD result code + */ +int +TMH_MHD_handler_send_json_pack_error (struct TMH_RequestHandler *rh, + struct MHD_Connection *connection, + void **connection_cls, + const char *upload_data, + size_t *upload_data_size); + + +#endif diff --git a/src/sync/sync-httpd_parsing.c b/src/sync/sync-httpd_parsing.c new file mode 100644 index 0000000..49d9f97 --- /dev/null +++ b/src/sync/sync-httpd_parsing.c @@ -0,0 +1,272 @@ +/* + This file is part of TALER + Copyright (C) 2014, 2015, 2016 GNUnet e.V. + + TALER 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 Foundation; either version 3, + or (at your option) any later version. + + TALER is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public + License along with TALER; see the file COPYING. If not, + see <http://www.gnu.org/licenses/> +*/ + +/** + * @file sync-httpd_parsing.c + * @brief functions to parse incoming requests + * (MHD arguments and JSON snippets) + * @author Florian Dold + * @author Benedikt Mueller + * @author Christian Grothoff + */ +#include "platform.h" +#include <gnunet/gnunet_util_lib.h> +#include <taler/taler_json_lib.h> +#include "sync-httpd_parsing.h" +#include "sync-httpd_responses.h" + +/* FIXME: de-duplicate code with taler-exchange-httpd_parsing.c + and taler-exchange-httpd_response.c */ + +/** + * Initial size for POST request buffer. + */ +#define REQUEST_BUFFER_INITIAL (2 * 1024) + +/** + * Maximum POST request size. + */ +#define REQUEST_BUFFER_MAX (1024 * 1024) + + +/** + * Buffer for POST requests. + */ +struct Buffer +{ + /** + * Allocated memory + */ + char *data; + + /** + * Number of valid bytes in buffer. + */ + size_t fill; + + /** + * Number of allocated bytes in buffer. + */ + size_t alloc; +}; + + + + +/** + * Free the data in a buffer. Does *not* free + * the buffer object itself. + * + * @param buf buffer to de-initialize + */ +static void +buffer_deinit (struct Buffer *buf) +{ + GNUNET_free_non_null (buf->data); + buf->data = NULL; +} + + + +/** + * Function called whenever we are done with a request + * to clean up our state. + * + * @param con_cls value as it was left by + * #TMH_PARSE_post_json(), to be cleaned up + */ +void +TMH_PARSE_post_cleanup_callback (void *con_cls) +{ + struct Buffer *r = con_cls; + + if (NULL != r) + { + buffer_deinit (r); + GNUNET_free (r); + } +} + + +/** + * Process a POST request containing a JSON object. This function + * realizes an MHD POST processor that will (incrementally) process + * JSON data uploaded to the HTTP server. It will store the + * required state in the @a con_cls, which must be cleaned up + * using #TMH_PARSE_post_cleanup_callback(). + * + * @param connection the MHD connection + * @param con_cls the closure (points to a `struct Buffer *`) + * @param upload_data the POST data + * @param upload_data_size number of bytes in @a upload_data + * @param json the JSON object for a completed request + * @return + * #GNUNET_YES if json object was parsed or at least + * may be parsed in the future (call again); + * `*json` will be NULL if we need to be called again, + * and non-NULL if we are done. + * #GNUNET_NO if request is incomplete or invalid + * (error message was generated) + * #GNUNET_SYSERR on internal error + * (we could not even queue an error message, + * close HTTP session with MHD_NO) + */ +int +TMH_PARSE_post_json (struct MHD_Connection *connection, + void **con_cls, + const char *upload_data, + size_t *upload_data_size, + json_t **json) +{ + enum GNUNET_JSON_PostResult pr; + + pr = GNUNET_JSON_post_parser (REQUEST_BUFFER_MAX, + connection, + con_cls, + upload_data, + upload_data_size, + json); + switch (pr) + { + + case GNUNET_JSON_PR_OUT_OF_MEMORY: + return (MHD_NO == TMH_RESPONSE_reply_internal_error + (connection, + TALER_EC_PARSER_OUT_OF_MEMORY, + "out of memory")) ? GNUNET_SYSERR : GNUNET_NO; + + case GNUNET_JSON_PR_CONTINUE: + return GNUNET_YES; + + case GNUNET_JSON_PR_REQUEST_TOO_LARGE: + return (MHD_NO == TMH_RESPONSE_reply_request_too_large + (connection)) ? GNUNET_SYSERR : GNUNET_NO; + + case GNUNET_JSON_PR_JSON_INVALID: + return (MHD_YES == + TMH_RESPONSE_reply_invalid_json (connection)) + ? GNUNET_NO : GNUNET_SYSERR; + case GNUNET_JSON_PR_SUCCESS: + GNUNET_break (NULL != *json); + return GNUNET_YES; + } + /* this should never happen */ + GNUNET_break (0); + return GNUNET_SYSERR; +} + + +/** + * Parse JSON object into components based on the given field + * specification. + * + * @param connection the connection to send an error response to + * @param root the JSON node to start the navigation at. + * @param spec field specification for the parser + * @return + * #GNUNET_YES if navigation was successful (caller is responsible + * for freeing allocated variable-size data using + * #GNUNET_JSON_parse_free() when done) + * #GNUNET_NO if json is malformed, error response was generated + * #GNUNET_SYSERR on internal error + */ +int +TMH_PARSE_json_data (struct MHD_Connection *connection, + const json_t *root, + struct GNUNET_JSON_Specification *spec) +{ + int ret; + const char *error_json_name; + unsigned int error_line; + + ret = GNUNET_JSON_parse (root, + spec, + &error_json_name, + &error_line); + if (GNUNET_SYSERR == ret) + { + if (NULL == error_json_name) + error_json_name = "<no field>"; + GNUNET_log (GNUNET_ERROR_TYPE_WARNING, + "Parsing failed due to field '%s'\n", + error_json_name); + ret = (MHD_YES == + TMH_RESPONSE_reply_json_pack (connection, + MHD_HTTP_BAD_REQUEST, + "{s:s, s:s, s:I}", + "error", "parse error", + "field", error_json_name, + "line", (json_int_t) error_line)) + ? GNUNET_NO : GNUNET_SYSERR; + return ret; + } + return GNUNET_YES; +} + + + +/** + * Extract base32crockford encoded data from request. + * + * Queues an error response to the connection if the parameter is + * missing or invalid. + * + * @param connection the MHD connection + * @param param_name the name of the parameter with the key + * @param[out] out_data pointer to store the result + * @param out_size expected size of data + * @return + * #GNUNET_YES if the the argument is present + * #GNUNET_NO if the argument is absent or malformed + * #GNUNET_SYSERR on internal error (error response could not be sent) + */ +int +TMH_PARSE_mhd_request_arg_data (struct MHD_Connection *connection, + const char *param_name, + void *out_data, + size_t out_size) +{ + const char *str; + + str = MHD_lookup_connection_value (connection, + MHD_GET_ARGUMENT_KIND, + param_name); + if (NULL == str) + { + return (MHD_NO == + TMH_RESPONSE_reply_arg_missing (connection, + TALER_EC_PARAMETER_MISSING, + param_name)) + ? GNUNET_SYSERR : GNUNET_NO; + } + if (GNUNET_OK != + GNUNET_STRINGS_string_to_data (str, + strlen (str), + out_data, + out_size)) + return (MHD_NO == + TMH_RESPONSE_reply_arg_invalid (connection, + TALER_EC_PARAMETER_MALFORMED, + param_name)) + ? GNUNET_SYSERR : GNUNET_NO; + return GNUNET_OK; +} + + +/* end of taler-merchant-httpd_parsing.c */ diff --git a/src/sync/sync-httpd_parsing.h b/src/sync/sync-httpd_parsing.h new file mode 100644 index 0000000..b3c11cd --- /dev/null +++ b/src/sync/sync-httpd_parsing.h @@ -0,0 +1,93 @@ +/* + This file is part of TALER + Copyright (C) 2014, 2015, 2016 GNUnet e.V. + + TALER 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 + Foundation; either version 3, or (at your option) any later version. + + TALER is distributed in the hope that it will be useful, but WITHOUT ANY + WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License along with + TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/> +*/ +/** + * @file sync-httpd_parsing.h + * @brief functions to parse incoming requests + * @author Florian Dold + * @author Benedikt Mueller + * @author Christian Grothoff + */ +#ifndef sync_HTTPD_PARSING_H +#define sync_HTTPD_PARSING_H + +#include <microhttpd.h> +#include <taler/taler_util.h> +#include <taler/taler_json_lib.h> + +/** + * Process a POST request containing a JSON object. This + * function realizes an MHD POST processor that will + * (incrementally) process JSON data uploaded to the HTTP + * server. It will store the required state in the + * "connection_cls", which must be cleaned up using + * #TMH_PARSE_post_cleanup_callback(). + * + * @param connection the MHD connection + * @param con_cls the closure (points to a `struct Buffer *`) + * @param upload_data the POST data + * @param upload_data_size number of bytes in @a upload_data + * @param json the JSON object for a completed request + * @return + * #GNUNET_YES if json object was parsed or at least + * may be parsed in the future (call again); + * `*json` will be NULL if we need to be called again, + * and non-NULL if we are done. + * #GNUNET_NO is request incomplete or invalid + * (error message was generated) + * #GNUNET_SYSERR on internal error + * (we could not even queue an error message, + * close HTTP session with MHD_NO) + */ +int +TMH_PARSE_post_json (struct MHD_Connection *connection, + void **con_cls, + const char *upload_data, + size_t *upload_data_size, + json_t **json); + + +/** + * Function called whenever we are done with a request + * to clean up our state. + * + * @param con_cls value as it was left by + * #TMH_PARSE_post_json(), to be cleaned up + */ +void +TMH_PARSE_post_cleanup_callback (void *con_cls); + + +/** + * Parse JSON object into components based on the given field + * specification. + * + * @param connection the connection to send an error response to + * @param root the JSON node to start the navigation at. + * @param spec field specification for the parser + * @return + * #GNUNET_YES if navigation was successful (caller is responsible + * for freeing allocated variable-size data using + * #GNUNET_JSON_parse_free() when done) + * #GNUNET_NO if json is malformed, error response was generated + * #GNUNET_SYSERR on internal error + */ +int +TMH_PARSE_json_data (struct MHD_Connection *connection, + const json_t *root, + struct GNUNET_JSON_Specification *spec); + + +#endif /* TALER_MERCHANT_HTTPD_PARSING_H */ diff --git a/src/sync/sync-httpd_responses.c b/src/sync/sync-httpd_responses.c new file mode 100644 index 0000000..6a95555 --- /dev/null +++ b/src/sync/sync-httpd_responses.c @@ -0,0 +1,408 @@ +/* + This file is part of TALER + Copyright (C) 2014-2017 GNUnet e.V. + + TALER 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 + Foundation; either version 3, or (at your option) any later version. + + TALER is distributed in the hope that it will be useful, but WITHOUT ANY + WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License along with + TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/> +*/ +/** + * @file sync-httpd_responses.c + * @brief API for generating the various replies of the exchange; these + * functions are called TMH_RESPONSE_reply_ and they generate + * and queue MHD response objects for a given connection. + * @author Florian Dold + * @author Benedikt Mueller + * @author Christian Grothoff + */ +#include "platform.h" +#include "sync-httpd.h" +#include "sync-httpd_responses.h" +#include <taler/taler_util.h> +#include <taler/taler_json_lib.h> +#include <gnunet/gnunet_util_lib.h> + + +/** + * Make JSON response object. + * + * @param json the json object + * @return MHD response object + */ +struct MHD_Response * +TMH_RESPONSE_make_json (const json_t *json) +{ + struct MHD_Response *resp; + char *json_str; + + json_str = json_dumps (json, + JSON_INDENT (2)); + if (NULL == json_str) + { + GNUNET_break (0); + return NULL; + } + resp = MHD_create_response_from_buffer (strlen (json_str), + json_str, + MHD_RESPMEM_MUST_FREE); + if (NULL == resp) + { + free (json_str); + GNUNET_break (0); + return NULL; + } + GNUNET_break (MHD_YES == + MHD_add_response_header (resp, + MHD_HTTP_HEADER_CONTENT_TYPE, + "application/json")); + return resp; +} + + +/** + * Send JSON object as response. + * + * @param connection the MHD connection + * @param json the json object + * @param response_code the http response code + * @return MHD result code + */ +int +TMH_RESPONSE_reply_json (struct MHD_Connection *connection, + const json_t *json, + unsigned int response_code) +{ + struct MHD_Response *resp; + int ret; + + resp = TMH_RESPONSE_make_json (json); + if (NULL == resp) + return MHD_NO; + ret = MHD_queue_response (connection, + response_code, + resp); + MHD_destroy_response (resp); + return ret; +} + + +/** + * Make JSON response object. + * + * @param fmt format string for pack + * @param ... varargs + * @return MHD response object + */ +struct MHD_Response * +TMH_RESPONSE_make_json_pack (const char *fmt, + ...) +{ + json_t *json; + va_list argp; + struct MHD_Response *ret; + json_error_t jerror; + + va_start (argp, fmt); + json = json_vpack_ex (&jerror, + 0, + fmt, + argp); + va_end (argp); + if (NULL == json) + { + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "Failed to pack JSON with format `%s': %s\n", + fmt, + jerror.text); + GNUNET_break (0); + return MHD_NO; + } + ret = TMH_RESPONSE_make_json (json); + json_decref (json); + return ret; +} + + +/** + * Function to call to handle the request by building a JSON + * reply from a format string and varargs. + * + * @param connection the MHD connection to handle + * @param response_code HTTP response code to use + * @param fmt format string for pack + * @param ... varargs + * @return MHD result code + */ +int +TMH_RESPONSE_reply_json_pack (struct MHD_Connection *connection, + unsigned int response_code, + const char *fmt, + ...) +{ + json_t *json; + va_list argp; + int ret; + json_error_t jerror; + + va_start (argp, fmt); + json = json_vpack_ex (&jerror, + 0, + fmt, + argp); + va_end (argp); + if (NULL == json) + { + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "Failed to pack JSON with format `%s': %s\n", + fmt, + jerror.text); + GNUNET_break (0); + return MHD_NO; + } + ret = TMH_RESPONSE_reply_json (connection, + json, + response_code); + json_decref (json); + return ret; +} + + +/** + * Create a response indicating an internal error. + * + * @param ec error code to return + * @param hint hint about the internal error's nature + * @return a MHD response object + */ +struct MHD_Response * +TMH_RESPONSE_make_error (enum TALER_ErrorCode ec, + const char *hint) +{ + return TMH_RESPONSE_make_json_pack ("{s:I, s:s}", + "code", (json_int_t) ec, + "hint", hint); +} + + +/** + * Send a response indicating an internal error. + * + * @param connection the MHD connection to use + * @param ec error code to return + * @param hint hint about the internal error's nature + * @return a MHD result code + */ +int +TMH_RESPONSE_reply_internal_error (struct MHD_Connection *connection, + enum TALER_ErrorCode ec, + const char *hint) +{ + return TMH_RESPONSE_reply_json_pack (connection, + MHD_HTTP_INTERNAL_SERVER_ERROR, + "{s:I, s:s}", + "code", (json_int_t) ec, + "hint", hint); +} + + +/** + * Send a response indicating that the request was too big. + * + * @param connection the MHD connection to use + * @return a MHD result code + */ +int +TMH_RESPONSE_reply_request_too_large (struct MHD_Connection *connection) +{ + struct MHD_Response *resp; + int ret; + + resp = MHD_create_response_from_buffer (0, + NULL, + MHD_RESPMEM_PERSISTENT); + if (NULL == resp) + return MHD_NO; + ret = MHD_queue_response (connection, + MHD_HTTP_REQUEST_ENTITY_TOO_LARGE, + resp); + MHD_destroy_response (resp); + return ret; +} + + +/** + * Send a response indicating that we did not find the @a object + * needed for the reply. + * + * @param connection the MHD connection to use + * @param response_code response code to use + * @param ec error code to return + * @param msg human-readable diagnostic + * @return a MHD result code + */ +int +TMH_RESPONSE_reply_rc (struct MHD_Connection *connection, + unsigned int response_code, + enum TALER_ErrorCode ec, + const char *msg) +{ + return TMH_RESPONSE_reply_json_pack (connection, + response_code, + "{s:I, s:s}", + "code", (json_int_t) ec, + "error", msg); +} + + +/** + * Send a response indicating that the JSON was malformed. + * + * @param connection the MHD connection to use + * @return a MHD result code + */ +int +TMH_RESPONSE_reply_invalid_json (struct MHD_Connection *connection) +{ + return TMH_RESPONSE_reply_json_pack (connection, + MHD_HTTP_BAD_REQUEST, + "{s:I, s:s}", + "code", + (json_int_t) TALER_EC_JSON_INVALID, + "error", "invalid json"); +} + + +/** + * Send a response indicating that we did not find the @a object + * needed for the reply. + * + * @param connection the MHD connection to use + * @param ec error code to return + * @param object name of the object we did not find + * @return a MHD result code + */ +int +TMH_RESPONSE_reply_not_found (struct MHD_Connection *connection, + enum TALER_ErrorCode ec, + const char *object) +{ + return TMH_RESPONSE_reply_json_pack (connection, + MHD_HTTP_NOT_FOUND, + "{s:I, s:s}", + "code", (json_int_t) ec, + "error", object); +} + + +/** + * Send a response indicating that the request was malformed. + * + * @param connection the MHD connection to use + * @param ec error code to return + * @param issue description of what was wrong with the request + * @return a MHD result code + */ +int +TMH_RESPONSE_reply_bad_request (struct MHD_Connection *connection, + enum TALER_ErrorCode ec, + const char *issue) +{ + return TMH_RESPONSE_reply_json_pack (connection, + MHD_HTTP_BAD_REQUEST, + "{s:I, s:s}", + "code", (json_int_t) ec, + "error", issue); +} + + +/** + * Add headers we want to return in every response. + * Useful for testing, like if we want to always close + * connections. + * + * @param response response to modify + */ +void +TMH_RESPONSE_add_global_headers (struct MHD_Response *response) +{ + if (TMH_sync_connection_close) + GNUNET_break (MHD_YES == + MHD_add_response_header (response, + MHD_HTTP_HEADER_CONNECTION, + "close")); +} + + +/** + * Send a response indicating an external error. + * + * @param connection the MHD connection to use + * @param ec error code to return + * @param hint hint about the error's nature + * @return a MHD result code + */ +int +TMH_RESPONSE_reply_external_error (struct MHD_Connection *connection, + enum TALER_ErrorCode ec, + const char *hint) +{ + return TMH_RESPONSE_reply_json_pack (connection, + MHD_HTTP_BAD_REQUEST, + "{s:I, s:s}", + "code", (json_int_t) ec, + "hint", hint); +} + + +/** + * Send a response indicating a missing argument. + * + * @param connection the MHD connection to use + * @param ec error code to return + * @param param_name the parameter that is missing + * @return a MHD result code + */ +int +TMH_RESPONSE_reply_arg_missing (struct MHD_Connection *connection, + enum TALER_ErrorCode ec, + const char *param_name) +{ + return TMH_RESPONSE_reply_json_pack (connection, + MHD_HTTP_BAD_REQUEST, + "{s:s, s:I, s:s}", + "error", "missing parameter", + "code", (json_int_t) ec, + "parameter", param_name); +} + + +/** + * Send a response indicating an invalid argument. + * + * @param connection the MHD connection to use + * @param ec error code to return + * @param param_name the parameter that is invalid + * @return a MHD result code + */ +int +TMH_RESPONSE_reply_arg_invalid (struct MHD_Connection *connection, + enum TALER_ErrorCode ec, + const char *param_name) +{ + return TMH_RESPONSE_reply_json_pack (connection, + MHD_HTTP_BAD_REQUEST, + "{s:s, s:I, s:s}", + "error", "invalid parameter", + "code", (json_int_t) ec, + "parameter", param_name); +} + + +/* end of taler-exchange-httpd_responses.c */ diff --git a/src/sync/sync-httpd_responses.h b/src/sync/sync-httpd_responses.h new file mode 100644 index 0000000..a98ba0d --- /dev/null +++ b/src/sync/sync-httpd_responses.h @@ -0,0 +1,244 @@ +/* + This file is part of TALER + Copyright (C) 2014-2017 GNUnet e.V. + + TALER 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 + Foundation; either version 3, or (at your option) any later version. + + TALER is distributed in the hope that it will be useful, but WITHOUT ANY + WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License along with + TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/> +*/ +/** + * @file sync-httpd_responses.h + * @brief API for generating the various replies of the exchange; these + * functions are called TMH_RESPONSE_reply_ and they generate + * and queue MHD response objects for a given connection. + * @author Florian Dold + * @author Benedikt Mueller + * @author Christian Grothoff + */ +#ifndef sync_HTTPD_RESPONSES_H +#define sync_HTTPD_RESPONSES_H +#include "sync-httpd.h" +#include <taler/taler_util.h> +#include <taler/taler_json_lib.h> +#include <gnunet/gnunet_util_lib.h> + + + +/** + * Make JSON response object. + * + * @param json the json object + * @return MHD response object + */ +struct MHD_Response * +TMH_RESPONSE_make_json (const json_t *json); + + +/** + * Send JSON object as response. + * + * @param connection the MHD connection + * @param json the json object + * @param response_code the http response code + * @return MHD result code + */ +int +TMH_RESPONSE_reply_json (struct MHD_Connection *connection, + const json_t *json, + unsigned int response_code); + + +/** + * Make JSON response object. + * + * @param fmt format string for pack + * @param ... varargs + * @return MHD response object + */ +struct MHD_Response * +TMH_RESPONSE_make_json_pack (const char *fmt, + ...); + + + + +/** + * Function to call to handle the request by building a JSON + * reply from a format string and varargs. + * + * @param connection the MHD connection to handle + * @param response_code HTTP response code to use + * @param fmt format string for pack + * @param ... varargs + * @return MHD result code + */ +int +TMH_RESPONSE_reply_json_pack (struct MHD_Connection *connection, + unsigned int response_code, + const char *fmt, + ...); + + +/** + * Create a response indicating an internal error. + * + * @param ec error code to return + * @param hint hint about the internal error's nature + * @return a MHD response object + */ +struct MHD_Response * +TMH_RESPONSE_make_error (enum TALER_ErrorCode ec, + const char *hint); + + +/** + * Send a response indicating an internal error. + * + * @param connection the MHD connection to use + * @param ec error code to return + * @param hint hint about the internal error's nature + * @return a MHD result code + */ +int +TMH_RESPONSE_reply_internal_error (struct MHD_Connection *connection, + enum TALER_ErrorCode ec, + const char *hint); + + + + +/** + * Send a response indicating that the request was too big. + * + * @param connection the MHD connection to use + * @return a MHD result code + */ +int +TMH_RESPONSE_reply_request_too_large (struct MHD_Connection *connection); + + + +/** + * Send a response indicating that we did not find the @a object + * needed for the reply. + * + * @param connection the MHD connection to use + * @param response_code response code to use + * @param ec error code to return + * @param msg human-readable diagnostic + * @return a MHD result code + */ +int +TMH_RESPONSE_reply_rc (struct MHD_Connection *connection, + unsigned int response_code, + enum TALER_ErrorCode ec, + const char *msg); + + + +/** + * Send a response indicating that the JSON was malformed. + * + * @param connection the MHD connection to use + * @return a MHD result code + */ +int +TMH_RESPONSE_reply_invalid_json (struct MHD_Connection *connection); + + + +/** + * Send a response indicating that we did not find the @a object + * needed for the reply. + * + * @param connection the MHD connection to use + * @param ec error code to return + * @param object name of the object we did not find + * @return a MHD result code + */ +int +TMH_RESPONSE_reply_not_found (struct MHD_Connection *connection, + enum TALER_ErrorCode ec, + const char *object); + + + +/** + * Send a response indicating that the request was malformed. + * + * @param connection the MHD connection to use + * @param ec error code to return + * @param issue description of what was wrong with the request + * @return a MHD result code + */ +int +TMH_RESPONSE_reply_bad_request (struct MHD_Connection *connection, + enum TALER_ErrorCode ec, + const char *issue); + + + + +/** + * Add headers we want to return in every response. + * Useful for testing, like if we want to always close + * connections. + * + * @param response response to modify + */ +void +TMH_RESPONSE_add_global_headers (struct MHD_Response *response); + + + +/** + * Send a response indicating an external error. + * + * @param connection the MHD connection to use + * @param ec error code to return + * @param hint hint about the error's nature + * @return a MHD result code + */ +int +TMH_RESPONSE_reply_external_error (struct MHD_Connection *connection, + enum TALER_ErrorCode ec, + const char *hint); + + + + +/** + * Send a response indicating a missing argument. + * + * @param connection the MHD connection to use + * @param ec error code to return + * @param param_name the parameter that is missing + * @return a MHD result code + */ +int +TMH_RESPONSE_reply_arg_missing (struct MHD_Connection *connection, + enum TALER_ErrorCode ec, + const char *param_name); + + +/** + * Send a response indicating an invalid argument. + * + * @param connection the MHD connection to use + * @param ec error code to return + * @param param_name the parameter that is invalid + * @return a MHD result code + */ +int +TMH_RESPONSE_reply_arg_invalid (struct MHD_Connection *connection, + enum TALER_ErrorCode ec, + const char *param_name); + +#endif diff --git a/src/sync/sync.conf b/src/sync/sync.conf new file mode 100644 index 0000000..9425c22 --- /dev/null +++ b/src/sync/sync.conf @@ -0,0 +1,31 @@ +# This file is in the public domain. + +# These are default/sample settings for a merchant backend. + + +# General settings for the backend. +[sync] + +# Use TCP or UNIX domain sockets? +SERVE = tcp + +# Which HTTP port does the backend listen on? Only used if "SERVE" is 'tcp'. +PORT = 9966 + +# Which IP address should we bind to? i.e. 127.0.0.1 or ::1 for loopback. +# Can also be given as a hostname. We will bind to the wildcard (dual-stack) +# if left empty. Only used if "SERVE" is 'tcp'. +# BIND_TO = + + +# Which unix domain path should we bind to? Only used if "SERVE" is 'unix'. +UNIXPATH = ${sync_RUNTIME_DIR}/backend.http +# What should be the file access permissions (see chmod) for "UNIXPATH"? +UNIXPATH_MODE = 660 + +# Which database backend do we use? +DB = postgres + +# Configuration for postgres database. +[syncdb-postgres] +CONFIG = postgres:///sync diff --git a/src/syncdb/Makefile.am b/src/syncdb/Makefile.am new file mode 100644 index 0000000..56db964 --- /dev/null +++ b/src/syncdb/Makefile.am @@ -0,0 +1,62 @@ +# This Makefile.am is in the public domain +AM_CPPFLAGS = -I$(top_srcdir)/src/include + +plugindir = $(libdir)/taler + +if HAVE_POSTGRESQL +if HAVE_GNUNETPQ +plugin_LTLIBRARIES = \ + libsync_plugin_db_postgres.la +endif +endif + +if USE_COVERAGE + AM_CFLAGS = --coverage -O0 + XLIB = -lgcov +endif + +lib_LTLIBRARIES = \ + libsyncdb.la + +libsyncdb_la_SOURCES = \ + sync_db_plugin.c + +libsyncdb_la_LIBADD = \ + -lgnunetpq \ + -lpq \ + -lgnunetutil +libsyncdb_la_LDFLAGS = \ + $(POSTGRESQL_LDFLAGS) \ + -version-info 2:0:0 \ + -no-undefined + +libsync_plugin_db_postgres_la_SOURCES = \ + plugin_sync_postgres.c +libsync_plugin_db_postgres_la_LIBADD = \ + $(LTLIBINTL) +libsync_plugin_db_postgres_la_LDFLAGS = \ + $(TALER_PLUGIN_LDFLAGS) \ + -lgnunetpq \ + -lpq \ + -ltalerpq \ + -lgnunetutil $(XLIB) + +check_PROGRAMS = \ + $(TESTS) + +test_sync_db_postgres_SOURCES = \ + test_sync_db.c +test_sync_db_postgres_LDFLAGS = \ + $(top_builddir)/src/util/libsyncutil.la \ + libsyncdb.la \ + -lgnunetutil \ + -lgnunetpq \ + -ltalerutil \ + -ltalerpq \ + -luuid + +TESTS = \ + test_sync_db-postgres + +EXTRA_DIST = \ + test_sync_db_postgres.conf diff --git a/src/syncdb/plugin_sync_postgres.c b/src/syncdb/plugin_sync_postgres.c new file mode 100644 index 0000000..4e63198 --- /dev/null +++ b/src/syncdb/plugin_sync_postgres.c @@ -0,0 +1,284 @@ +/* + This file is part of TALER + (C) 2014--2019 Taler Systems SA + + TALER is free software; you can redistribute it and/or modify it under the + terms of the GNU Lesser General Public License as published by the Free Software + Foundation; either version 3, or (at your option) any later version. + + TALER is distributed in the hope that it will be useful, but WITHOUT ANY + WARRANTY; without even the implied warranty of ANASTASISABILITY or FITNESS FOR + A PARTICULAR PURPOSE. See the GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along with + TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/> +*/ +/** + * @file sync/plugin_syncdb_postgres.c + * @brief database helper functions for postgres used by the sync + * @author Sree Harsha Totakura <sreeharsha@totakura.in> + * @author Christian Grothoff + * @author Marcello Stanisci + */ +#include "platform.h" +#include <gnunet/gnunet_util_lib.h> +#include <gnunet/gnunet_pq_lib.h> +#include <taler/taler_pq_lib.h> +#include "sync_database_plugin.h" +#include "sync_database_lib.h" + +/** + * How often do we re-try if we run into a DB serialization error? + */ +#define MAX_RETRIES 3 + + +/** + * Type of the "cls" argument given to each of the functions in + * our API. + */ +struct PostgresClosure +{ + + /** + * Postgres connection handle. + */ + struct GNUNET_PQ_Context *conn; + + /** + * Underlying configuration. + */ + const struct GNUNET_CONFIGURATION_Handle *cfg; + + /** + * Name of the currently active transaction, NULL if none is active. + */ + const char *transaction_name; + +}; + + +/** + * Drop sync tables + * + * @param cls closure our `struct Plugin` + * @return #GNUNET_OK upon success; #GNUNET_SYSERR upon failure + */ +static int +postgres_drop_tables (void *cls) +{ + struct PostgresClosure *pg = cls; + struct GNUNET_PQ_ExecuteStatement es[] = { + GNUNET_PQ_make_try_execute ("DROP TABLE IF EXISTS backups;"), + GNUNET_PQ_EXECUTE_STATEMENT_END + }; + return GNUNET_PQ_exec_statements (pg->conn, + es); +} + + +/** + * Check that the database connection is still up. + * + * @param pg connection to check + */ +static void +check_connection (void *cls) +{ + struct PostgresClosure *pg = cls; + GNUNET_PQ_reconnect_if_down (pg->conn); +} + + +/** + * Do a pre-flight check that we are not in an uncommitted transaction. + * If we are, try to commit the previous transaction and output a warning. + * Does not return anything, as we will continue regardless of the outcome. + * + * @param cls the `struct PostgresClosure` with the plugin-specific state + */ +static void +postgres_preflight (void *cls) +{ + struct PostgresClosure *pg = cls; + struct GNUNET_PQ_ExecuteStatement es[] = { + GNUNET_PQ_make_execute ("COMMIT"), + GNUNET_PQ_EXECUTE_STATEMENT_END + }; + + if (NULL == pg->transaction_name) + return; /* all good */ + if (GNUNET_OK == + GNUNET_PQ_exec_statements (pg->conn, + es)) + { + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "BUG: Preflight check committed transaction `%s'!\n", + pg->transaction_name); + } + else + { + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "BUG: Preflight check failed to commit transaction `%s'!\n", + pg->transaction_name); + } + pg->transaction_name = NULL; +} + +/** + * Start a transaction. + * + * @param cls the `struct PostgresClosure` with the plugin-specific state + * @param name unique name identifying the transaction (for debugging), + * must point to a constant + * @return #GNUNET_OK on success + */ +static int +begin_transaction (void *cls, + const char *name) +{ + struct PostgresClosure *pg = cls; + struct GNUNET_PQ_ExecuteStatement es[] = { + GNUNET_PQ_make_execute ("START TRANSACTION ISOLATION LEVEL SERIALIZABLE"), + GNUNET_PQ_EXECUTE_STATEMENT_END + }; + + check_connection (pg); + postgres_preflight (pg); + pg->transaction_name = name; + if (GNUNET_OK != + GNUNET_PQ_exec_statements (pg->conn, + es)) + { + TALER_LOG_ERROR ("Failed to start transaction\n"); + GNUNET_break (0); + return GNUNET_SYSERR; + } + return GNUNET_OK; +} + +/** +* Roll back the current transaction of a database connection. +* +* @param cls the `struct PostgresClosure` with the plugin-specific state +* @return #GNUNET_OK on success +*/ +static void +rollback (void *cls) +{ + struct PostgresClosure *pg = cls; + struct GNUNET_PQ_ExecuteStatement es[] = { + GNUNET_PQ_make_execute ("ROLLBACK"), + GNUNET_PQ_EXECUTE_STATEMENT_END + }; + + if (GNUNET_OK != + GNUNET_PQ_exec_statements (pg->conn, + es)) + { + TALER_LOG_ERROR ("Failed to rollback transaction\n"); + GNUNET_break (0); + } + pg->transaction_name = NULL; +} + +/** + * Commit the current transaction of a database connection. + * + * @param cls the `struct PostgresClosure` with the plugin-specific state + * @return transaction status code + */ + +static enum SYNC_DB_QueryStatus +commit_transaction (void *cls) +{ + struct PostgresClosure *pg = cls; + enum SYNC_DB_QueryStatus qs; + struct GNUNET_PQ_QueryParam no_params[] = { + GNUNET_PQ_query_param_end + }; + + qs = GNUNET_PQ_eval_prepared_non_select (pg->conn, + "do_commit", + no_params); + pg->transaction_name = NULL; + return qs; +} + + + +/** + * Initialize Postgres database subsystem. + * + * @param cls a configuration instance + * @return NULL on error, otherwise a `struct TALER_SYNCDB_Plugin` + */ +void * +libsync_plugin_db_postgres_init (void *cls) +{ + struct GNUNET_CONFIGURATION_Handle *cfg = cls; + struct PostgresClosure *pg; + struct SYNC_DatabasePlugin *plugin; + struct GNUNET_PQ_ExecuteStatement es[] = { + /* Orders created by the frontend, not signed or given a nonce yet. + The contract terms will change (nonce will be added) when moved to the + contract terms table */ + GNUNET_PQ_make_execute ("CREATE TABLE IF NOT EXISTS backups" + "(" + "data BYTEA NOT NULL," + ");"), + GNUNET_PQ_EXECUTE_STATEMENT_END + }; + struct GNUNET_PQ_PreparedStatement ps[] = { + GNUNET_PQ_make_prepare ("backup_insert", + "INSERT INTO backups " + "(data" + ") VALUES " + "($1);", + 1), + GNUNET_PQ_make_prepare ("do_commit", + "COMMIT", + 0), + GNUNET_PQ_PREPARED_STATEMENT_END + }; + + pg = GNUNET_new (struct PostgresClosure); + pg->cfg = cfg; + pg->conn = GNUNET_PQ_connect_with_cfg (cfg, + "syncdb-postgres", + es, + ps); + if (NULL == pg->conn) + { + GNUNET_free (pg); + return NULL; + } + plugin = GNUNET_new (struct SYNC_DatabasePlugin); + plugin->cls = pg; + plugin->drop_tables = &postgres_drop_tables; + plugin->preflight = &postgres_preflight; + plugin->rollback = &rollback; + plugin->commit = &commit_transaction; + return plugin; +} + + +/** + * Shutdown Postgres database subsystem. + * + * @param cls a `struct SYNC_DB_Plugin` + * @return NULL (always) + */ +void * +libsync_plugin_db_postgres_done (void *cls) +{ + struct SYNC_DatabasePlugin *plugin = cls; + struct PostgresClosure *pg = plugin->cls; + + GNUNET_PQ_disconnect (pg->conn); + GNUNET_free (pg); + GNUNET_free (plugin); + return NULL; +} + +/* end of plugin_syncdb_postgres.c */ diff --git a/src/syncdb/sync_db_plugin.c b/src/syncdb/sync_db_plugin.c new file mode 100644 index 0000000..6b2c6e0 --- /dev/null +++ b/src/syncdb/sync_db_plugin.c @@ -0,0 +1,149 @@ +/* + This file is part of TALER + Copyright (C) 2015, 2016 GNUnet e.V. and INRIA + + TALER is free software; you can redistribute it and/or modify it under the + terms of the GNU Lesser General Public License as published by the Free Software + Foundation; either version 3, or (at your option) any later version. + + TALER is distributed in the hope that it will be useful, but WITHOUT ANY + WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + A PARTICULAR PURPOSE. See the GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along with + TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/> +*/ +/** + * @file merchantdb/merchantdb_plugin.c + * @brief Logic to load database plugin + * @author Christian Grothoff + * @author Sree Harsha Totakura <sreeharsha@totakura.in> + */ +#include "platform.h" +#include "anastasis_database_plugin.h" +#include <ltdl.h> + + +/** + * Initialize the plugin. + * + * @param cfg configuration to use + * @return #GNUNET_OK on success + */ +struct ANASTASIS_DatabasePlugin * +ANASTASIS_DB_plugin_load (const struct GNUNET_CONFIGURATION_Handle *cfg) +{ + char *plugin_name; + char *lib_name; + struct GNUNET_CONFIGURATION_Handle *cfg_dup; + struct ANASTASIS_DatabasePlugin *plugin; + + if (GNUNET_SYSERR == + GNUNET_CONFIGURATION_get_value_string (cfg, + "anastasis", + "db", + &plugin_name)) + { + GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR, + "anastasis", + "db"); + return NULL; + } + (void) GNUNET_asprintf (&lib_name, + "libanastasis_plugin_db_%s", + plugin_name); + GNUNET_free (plugin_name); + cfg_dup = GNUNET_CONFIGURATION_dup (cfg); + plugin = GNUNET_PLUGIN_load (lib_name, cfg_dup); + if (NULL != plugin) + plugin->library_name = lib_name; + else + GNUNET_free (lib_name); + GNUNET_CONFIGURATION_destroy (cfg_dup); + return plugin; +} + + +/** + * Shutdown the plugin. + * + * @param plugin the plugin to unload + */ +void +ANASTASIS_DB_plugin_unload (struct ANASTASIS_DatabasePlugin *plugin) +{ + char *lib_name; + + if (NULL == plugin) + return; + lib_name = plugin->library_name; + GNUNET_assert (NULL == GNUNET_PLUGIN_unload (lib_name, + plugin)); + GNUNET_free (lib_name); +} + + +/** + * Libtool search path before we started. + */ +static char *old_dlsearchpath; + + +/** + * Setup libtool paths. + */ +void __attribute__ ((constructor)) +plugin_init () +{ + int err; + const char *opath; + char *path; + char *cpath; + + err = lt_dlinit (); + if (err > 0) + { + fprintf (stderr, + _ ("Initialization of plugin mechanism failed: %s!\n"), + lt_dlerror ()); + return; + } + opath = lt_dlgetsearchpath (); + if (NULL != opath) + old_dlsearchpath = GNUNET_strdup (opath); + path = GNUNET_OS_installation_get_path (GNUNET_OS_IPK_LIBDIR); + if (NULL != path) + { + if (NULL != opath) + { + GNUNET_asprintf (&cpath, "%s:%s", opath, path); + lt_dlsetsearchpath (cpath); + GNUNET_free (path); + GNUNET_free (cpath); + } + else + { + lt_dlsetsearchpath (path); + GNUNET_free (path); + } + } +} + + +/** + * Shutdown libtool. + */ +void __attribute__ ((destructor)) +plugin_fini () +{ + lt_dlsetsearchpath (old_dlsearchpath); + if (NULL != old_dlsearchpath) + { + GNUNET_free (old_dlsearchpath); + old_dlsearchpath = NULL; + } + lt_dlexit (); +} + + +/* end of anastasis_db_plugin.c */ diff --git a/src/syncdb/sync_db_postgres.conf b/src/syncdb/sync_db_postgres.conf new file mode 100644 index 0000000..0460bd2 --- /dev/null +++ b/src/syncdb/sync_db_postgres.conf @@ -0,0 +1,7 @@ +[anastasis] +#The DB plugin to use +DB = postgres + +[anastasisdb-postgres] +#The connection string the plugin has to use for connecting to the database +CONFIG = postgres:///anastasis diff --git a/src/syncdb/test_sync_db.c b/src/syncdb/test_sync_db.c new file mode 100644 index 0000000..e57a548 --- /dev/null +++ b/src/syncdb/test_sync_db.c @@ -0,0 +1,200 @@ +/* + This file is part of + (C) 2014-2017 INRIA + + TALER is free software; you can redistribute it and/or modify it under the + terms of the GNU Lesser General Public License as published by the Free Software + Foundation; either version 3, or (at your option) any later version. + + TALER is distributed in the hope that it will be useful, but WITHOUT ANY + WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + A PARTICULAR PURPOSE. See the GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along with + TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/> +*/ +/** + * @file sync/test_sync_db.c + * @brief testcase for sync postgres db plugin + * @author Marcello Stanisci + * @author Christian Grothoff + */ + +#include "platform.h" +#include <gnunet/gnunet_util_lib.h> +#include <taler/taler_util.h> +#include "sync_database_plugin.h" +#include "sync_database_lib.h" +#include "sync_error_codes.h" +#include <uuid/uuid.h> + + +#define FAILIF(cond) \ + do { \ + if (! (cond)) { break;} \ + GNUNET_break (0); \ + goto drop; \ + } while (0) + +#define RND_BLK(ptr) \ + GNUNET_CRYPTO_random_block (GNUNET_CRYPTO_QUALITY_WEAK, ptr, sizeof (*ptr)) + +/** + * Global return value for the test. Initially -1, set to 0 upon + * completion. Other values indicate some kind of error. + */ +static int result; + +/** + * Handle to the plugin we are testing. + */ +static struct SYNC_DatabasePlugin *plugin; + +/** + * Payment Secret for the test, set to a random value + */ +static struct SYNC_PaymentSecretP paymentSecretP; + +/** + * User public key, set to a random value + */ +static struct SYNC_AccountPubP accountPubP; + +/** + * Amount which is deposited, set to random value + */ +static struct TALER_Amount amount; + +/** + * How many posts are paid by the payment + */ +static unsigned int post_counter; + +/** + * Recoverydata which is stored into the Database, set to a random value + */ +static void *recovery_data; + +/** + * Recovery_data for the select test + */ +static void *res_recovery_data; + +/** + * Truthdata which is stored into the Database, set to a random value + */ +static void *truth_data; + +/** + * Truth for the select test + */ +static void *truth; + +/** + * Keyshare which is stored into the Database, set to a random value + */ +static void *key_share; + +/** + * Keyshare for the select test + */ +static void *res_key_share; + +/** + * Mime-type of truth + */ +static char *mime_type; + +/** + * Mime-type of truth for the select test + */ +static char *res_mime_type; + +/** + * Version of a Recoverydocument + */ +static uint32_t version; + +/** + * Version of the latest Recoverydocument + */ +static uint32_t res_version; + + +/** + * Main function that will be run by the scheduler. + * + * @param cls closure with config + */ +static void +run (void *cls) +{ + struct GNUNET_CONFIGURATION_Handle *cfg = cls; + + if (NULL == (plugin = SYNC_DB_plugin_load (cfg))) + { + result = 77; + return; + } + if (GNUNET_OK != plugin->drop_tables (plugin->cls)) + { + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "Dropping tables failed\n"); + result = 77; + return; + } + SYNC_DB_plugin_unload (plugin); + if (NULL == (plugin = SYNC_DB_plugin_load (cfg))) + { + result = 77; + return; + } + + GNUNET_break (GNUNET_OK == + plugin->drop_tables (plugin->cls)); + SYNC_DB_plugin_unload (plugin); + plugin = NULL; +} + + +int +main (int argc, + char *const argv[]) +{ + const char *plugin_name; + char *config_filename; + char *testname; + struct GNUNET_CONFIGURATION_Handle *cfg; + + result = -1; + if (NULL == (plugin_name = strrchr (argv[0], (int) '-'))) + { + GNUNET_break (0); + return -1; + } + GNUNET_log_setup (argv[0], "DEBUG", NULL); + plugin_name++; + (void) GNUNET_asprintf (&testname, + "%s", + plugin_name); + (void) GNUNET_asprintf (&config_filename, + "test_sync_db_%s.conf", + testname); + cfg = GNUNET_CONFIGURATION_create (); + if (GNUNET_OK != + GNUNET_CONFIGURATION_parse (cfg, + config_filename)) + { + GNUNET_break (0); + GNUNET_free (config_filename); + GNUNET_free (testname); + return 2; + } + GNUNET_SCHEDULER_run (&run, cfg); + GNUNET_CONFIGURATION_destroy (cfg); + GNUNET_free (config_filename); + GNUNET_free (testname); + return result; +} + +/* end of test_sync_db.c */ diff --git a/src/syncdb/test_sync_db_postgres.conf b/src/syncdb/test_sync_db_postgres.conf new file mode 100644 index 0000000..f91dea1 --- /dev/null +++ b/src/syncdb/test_sync_db_postgres.conf @@ -0,0 +1,7 @@ +[anastasis] +#The DB plugin to use +DB = postgres + +[anastasisdb-postgres] +#The connection string the plugin has to use for connecting to the database +CONFIG = postgres:///anastasischeck |