diff --git a/src/include/taler_mint_service.h b/src/include/taler_mint_service.h index 0e79a5f73..6edd1e428 100644 --- a/src/include/taler_mint_service.h +++ b/src/include/taler_mint_service.h @@ -363,12 +363,13 @@ typedef void /** - * Submit a deposit permission to the mint and get the mint's response. - * Note that while we return the response verbatim to the caller for - * further processing, we do already verify that the response is - * well-formed (i.e. that signatures included in the response are all - * valid). If the mint's reply is not well-formed, we return an - * HTTP status code of zero to @a cb. + * Submit a deposit permission to the mint and get the mint's + * response. This API is typically used by a merchant. Note that + * while we return the response verbatim to the caller for further + * processing, we do already verify that the response is well-formed + * (i.e. that signatures included in the response are all valid). If + * the mint's reply is not well-formed, we return an HTTP status code + * of zero to @a cb. * * We also verify that the @a coin_sig is valid for this deposit * request, and that the @a ub_sig is a valid signature for @a @@ -564,11 +565,12 @@ typedef void /** - * Withdraw a coin from the mint using a /withdraw/sign request. Note - * that to ensure that no money is lost in case of hardware failures, - * the caller must have committed (most of) the arguments to disk - * before calling, and be ready to repeat the request with the same - * arguments in case of failures. + * Withdraw a coin from the mint using a /withdraw/sign request. This + * API is typically used by a wallet. Note that to ensure that no + * money is lost in case of hardware failures, the caller must have + * committed (most of) the arguments to disk before calling, and be + * ready to repeat the request with the same arguments in case of + * failures. * * @param mint the mint handle; the mint must be ready to operate * @param pk kind of coin to create @@ -578,7 +580,7 @@ typedef void * caller must have committed this value to disk before the call (with @a pk) * @param res_cb the callback to call when the final result for this request is available * @param res_cb_cls closure for the above callback - * @return #GNUNET_OK on success, #GNUNET_SYSERR + * @return NULL * if the inputs are invalid (i.e. denomination key not with this mint). * In this case, the callback is not called. */ @@ -602,5 +604,67 @@ void TALER_MINT_withdraw_sign_cancel (struct TALER_MINT_WithdrawSignHandle *sign); +/* ********************* /admin/add/incoming *********************** */ + + + +/** + * @brief A /admin/add/incoming Handle + */ +struct TALER_MINT_AdminAddIncomingHandle; + + +/** + * Callbacks of this type are used to serve the result of submitting + * information about an incoming transaction to a mint. + * + * @param cls closure + * @param http_status HTTP response code, #MHD_HTTP_OK (200) for successful status request + * 0 if the mint's reply is bogus (fails to follow the protocol) + * @param full_response full response from the mint (for logging, in case of errors) + */ +typedef void +(*TALER_MINT_AdminAddIncomingResultCallback) (void *cls, + unsigned int http_status, + json_t *full_response); + + +/** + * Notify the mint that we have received an incoming transaction + * which fills a reserve. Note that this API is an administrative + * API and thus not accessible to typical mint clients, but only + * to the operators of the mint. + * + * @param mint the mint handle; the mint must be ready to operate + * @param reserve_pub public key of the reserve + * @param amount amount that was deposited + * @param execution_date when did we receive the amount + * @param wire wire details + * @param res_cb the callback to call when the final result for this request is available + * @param res_cb_cls closure for the above callback + * @return NULL + * if the inputs are invalid (i.e. invalid amount). + * In this case, the callback is not called. + */ +struct TALER_MINT_AdminAddIncomingHandle * +TALER_MINT_admin_add_incoming (struct TALER_MINT_Handle *mint, + const struct TALER_ReservePublicKeyP *reserve_pub, + const struct TALER_Amount *amount, + const struct GNUNET_TIME_Absolute execution_date, + const json_t *wire, + TALER_MINT_AdminAddIncomingResultCallback res_cb, + void *res_cb_cls); + + +/** + * Cancel an add incoming. This function cannot be used on a request + * handle if a response is already served for it. + * + * @param sign the admin add incoming request handle + */ +void +TALER_MINT_admin_add_incoming_cancel (struct TALER_MINT_AdminAddIncomingHandle *aai); + + #endif /* _TALER_MINT_SERVICE_H */ diff --git a/src/mint-lib/Makefile.am b/src/mint-lib/Makefile.am index f365b3f1f..edd26a025 100644 --- a/src/mint-lib/Makefile.am +++ b/src/mint-lib/Makefile.am @@ -17,6 +17,7 @@ libtalermint_la_SOURCES = \ mint_api_context.c mint_api_context.h \ mint_api_json.c mint_api_json.h \ mint_api_handle.c mint_api_handle.h \ + mint_api_admin.c \ mint_api_deposit.c \ mint_api_withdraw.c diff --git a/src/mint-lib/mint_api_admin.c b/src/mint-lib/mint_api_admin.c new file mode 100644 index 000000000..c058cdcfe --- /dev/null +++ b/src/mint-lib/mint_api_admin.c @@ -0,0 +1,334 @@ +/* + This file is part of TALER + Copyright (C) 2014, 2015 Christian Grothoff (and other contributing authors) + + 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, If not, see + +*/ +/** + * @file mint-lib/mint_api_admin.c + * @brief Implementation of the /admin/ requests of the mint's HTTP API + * @author Christian Grothoff + */ +#include "platform.h" +#include +#include +#include /* just for HTTP status codes */ +#include +#include "taler_mint_service.h" +#include "mint_api_json.h" +#include "mint_api_context.h" +#include "mint_api_handle.h" +#include "taler_signatures.h" + + +/** + * Print JSON parsing related error information + */ +#define JSON_WARN(error) \ + GNUNET_log (GNUNET_ERROR_TYPE_WARNING, \ + "JSON parsing failed at %s:%u: %s (%s)", \ + __FILE__, __LINE__, error.text, error.source) + + +/** + * @brief An admin/add/incoming Handle + */ +struct TALER_MINT_AdminAddIncomingHandle +{ + + /** + * The connection to mint this request handle will use + */ + struct TALER_MINT_Handle *mint; + + /** + * The url for this request. + */ + char *url; + + /** + * JSON encoding of the request to POST. + */ + char *json_enc; + + /** + * Handle for the request. + */ + struct MAC_Job *job; + + /** + * HTTP headers for the request. + */ + struct curl_slist *headers; + + /** + * Function to call with the result. + */ + TALER_MINT_AdminAddIncomingResultCallback cb; + + /** + * Closure for @a cb. + */ + void *cb_cls; + + /** + * Download buffer + */ + void *buf; + + /** + * The size of the download buffer + */ + size_t buf_size; + + /** + * Error code (based on libc errno) if we failed to download + * (i.e. response too large). + */ + int eno; + +}; + + +/** + * Function called when we're done processing the + * HTTP /admin/add/incoming request. + * + * @param cls the `struct TALER_MINT_AdminAddIncomingHandle` + */ +static void +handle_admin_add_incoming_finished (void *cls, + CURL *eh) +{ + struct TALER_MINT_AdminAddIncomingHandle *aai = cls; + long response_code; + json_error_t error; + json_t *json; + + json = NULL; + if (0 == aai->eno) + { + json = json_loadb (aai->buf, + aai->buf_size, + JSON_REJECT_DUPLICATES | JSON_DISABLE_EOF_CHECK, + &error); + if (NULL == json) + { + JSON_WARN (error); + response_code = 0; + } + } + if (NULL != json) + { + if (CURLE_OK != + curl_easy_getinfo (eh, + CURLINFO_RESPONSE_CODE, + &response_code)) + { + /* unexpected error... */ + GNUNET_break (0); + response_code = 0; + } + } + switch (response_code) + { + case 0: + break; + case MHD_HTTP_OK: + break; + case MHD_HTTP_BAD_REQUEST: + /* This should never happen, either us or the mint is buggy + (or API version conflict); just pass JSON reply to the application */ + break; + case MHD_HTTP_FORBIDDEN: + /* Access denied */ + break; + case MHD_HTTP_UNAUTHORIZED: + /* Nothing really to verify, mint says one of the signatures is + invalid; as we checked them, this should never happen, we + should pass the JSON reply to the application */ + break; + case MHD_HTTP_NOT_FOUND: + /* Nothing really to verify, this should never + happen, we should pass the JSON reply to the application */ + break; + case MHD_HTTP_INTERNAL_SERVER_ERROR: + /* Server had an internal issue; we should retry, but this API + leaves this to the application */ + break; + default: + /* unexpected response code */ + GNUNET_break (0); + response_code = 0; + break; + } + aai->cb (aai->cb_cls, + response_code, + json); + json_decref (json); + TALER_MINT_admin_add_incoming_cancel (aai); +} + + +/** + * Callback used when downloading the reply to a /admin/add/incoming + * request. Just appends all of the data to the `buf` in the `struct + * TALER_MINT_AdminAddIncomingHandle` for further processing. The size + * of the download is limited to #GNUNET_MAX_MALLOC_CHECKED, if the + * download exceeds this size, we abort with an error. + * + * @param bufptr data downloaded via HTTP + * @param size size of an item in @a bufptr + * @param nitems number of items in @a bufptr + * @param cls the `struct TALER_MINT_DepositHandle` + * @return number of bytes processed from @a bufptr + */ +static int +admin_add_incoming_download_cb (char *bufptr, + size_t size, + size_t nitems, + void *cls) +{ + struct TALER_MINT_AdminAddIncomingHandle *aai = cls; + size_t msize; + void *buf; + + if (0 == size * nitems) + { + /* Nothing (left) to do */ + return 0; + } + msize = size * nitems; + if ( (msize + aai->buf_size) >= GNUNET_MAX_MALLOC_CHECKED) + { + aai->eno = ENOMEM; + return 0; /* signals an error to curl */ + } + aai->buf = GNUNET_realloc (aai->buf, + aai->buf_size + msize); + buf = aai->buf + aai->buf_size; + memcpy (buf, bufptr, msize); + aai->buf_size += msize; + return msize; +} + + +/** + * Notify the mint that we have received an incoming transaction + * which fills a reserve. Note that this API is an administrative + * API and thus not accessible to typical mint clients, but only + * to the operators of the mint. + * + * @param mint the mint handle; the mint must be ready to operate + * @param reserve_pub public key of the reserve + * @param amount amount that was deposited + * @param execution_date when did we receive the amount + * @param wire wire details + * @param res_cb the callback to call when the final result for this request is available + * @param res_cb_cls closure for the above callback + * @return NULL + * if the inputs are invalid (i.e. invalid amount). + * In this case, the callback is not called. + */ +struct TALER_MINT_AdminAddIncomingHandle * +TALER_MINT_admin_add_incoming (struct TALER_MINT_Handle *mint, + const struct TALER_ReservePublicKeyP *reserve_pub, + const struct TALER_Amount *amount, + const struct GNUNET_TIME_Absolute execution_date, + const json_t *wire, + TALER_MINT_AdminAddIncomingResultCallback res_cb, + void *res_cb_cls) +{ + struct TALER_MINT_AdminAddIncomingHandle *aai; + struct TALER_MINT_Context *ctx; + json_t *admin_obj; + CURL *eh; + + if (GNUNET_YES != + MAH_handle_is_ready (mint)) + { + GNUNET_break (0); + return NULL; + } + admin_obj = json_pack ("{s:o, s:o," /* reserve_pub/amount */ + " s:o, s:o}", /* execution_Date/wire */ + "reserve_pub", TALER_json_from_data (reserve_pub, + sizeof (*reserve_pub)), + "amount", TALER_json_from_amount (amount), + "execution_date", TALER_json_from_abs (execution_date), + "wire", wire); + aai = GNUNET_new (struct TALER_MINT_AdminAddIncomingHandle); + aai->mint = mint; + aai->cb = res_cb; + aai->cb_cls = res_cb_cls; + aai->url = MAH_path_to_url (mint, "/admin/add/incoming"); + + eh = curl_easy_init (); + GNUNET_assert (NULL != (aai->json_enc = + json_dumps (admin_obj, + JSON_COMPACT))); + json_decref (admin_obj); + GNUNET_assert (CURLE_OK == + curl_easy_setopt (eh, + CURLOPT_URL, + aai->url)); + GNUNET_assert (CURLE_OK == + curl_easy_setopt (eh, + CURLOPT_POSTFIELDS, + aai->json_enc)); + GNUNET_assert (CURLE_OK == + curl_easy_setopt (eh, + CURLOPT_POSTFIELDSIZE, + strlen (aai->json_enc))); + GNUNET_assert (CURLE_OK == + curl_easy_setopt (eh, + CURLOPT_WRITEFUNCTION, + &admin_add_incoming_download_cb)); + GNUNET_assert (CURLE_OK == + curl_easy_setopt (eh, + CURLOPT_WRITEDATA, + aai)); + GNUNET_assert (NULL != (aai->headers = + curl_slist_append (aai->headers, + "Content-Type: application/json"))); + GNUNET_assert (CURLE_OK == + curl_easy_setopt (eh, + CURLOPT_HTTPHEADER, + aai->headers)); + ctx = MAH_handle_to_context (mint); + aai->job = MAC_job_add (ctx, + eh, + &handle_admin_add_incoming_finished, + aai); + return aai; +} + + +/** + * Cancel an add incoming. This function cannot be used on a request + * handle if a response is already served for it. + * + * @param sign the admin add incoming request handle + */ +void +TALER_MINT_admin_add_incoming_cancel (struct TALER_MINT_AdminAddIncomingHandle *aai) +{ + MAC_job_cancel (aai->job); + curl_slist_free_all (aai->headers); + GNUNET_free (aai->url); + GNUNET_free (aai->json_enc); + GNUNET_free (aai); +} + + +/* end of mint_api_admin.c */ diff --git a/src/mint/taler-mint-httpd_admin.c b/src/mint/taler-mint-httpd_admin.c new file mode 100644 index 000000000..22ead53ee --- /dev/null +++ b/src/mint/taler-mint-httpd_admin.c @@ -0,0 +1,152 @@ +/* + This file is part of TALER + Copyright (C) 2014 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, If not, see +*/ +/** + * @file taler-mint-httpd_admin.c + * @brief Handle /admin/ requests + * @author Christian Grothoff + */ +#include "platform.h" +#include +#include +#include "taler-mint-httpd_admin.h" +#include "taler-mint-httpd_parsing.h" +#include "taler-mint-httpd_responses.h" + + +/** + * Check permissions (we only allow access to /admin/ from loopback). + * + * @param connection connection to perform access check for + * @return #GNUNET_OK if permitted, + * #GNUNET_NO if denied and error was queued, + * #GNUNET_SYSERR if denied and we failed to report + */ +static int +check_permissions (struct MHD_Connection *connection) +{ + const union MHD_ConnectionInfo *ci; + const struct sockaddr *addr; + int res; + + ci = MHD_get_connection_info (connection, + MHD_CONNECTION_INFO_CLIENT_ADDRESS); + if (NULL == ci) + { + GNUNET_break (0); + res = TMH_RESPONSE_reply_internal_error (connection, + "Failed to verify client address"); + return (MHD_YES == res) ? GNUNET_NO : GNUNET_SYSERR; + } + addr = ci->client_addr; + switch (addr->sa_family) + { + case AF_INET: + { + const struct sockaddr_in *sin = (const struct sockaddr_in *) addr; + + if (INADDR_LOOPBACK != sin->sin_addr.s_addr) + { + res = TMH_RESPONSE_reply_permission_denied (connection, + "/admin/ only allowed via loopback"); + return (MHD_YES == res) ? GNUNET_NO : GNUNET_SYSERR; + } + break; + } + case AF_INET6: + { + const struct sockaddr_in6 *sin6 = (const struct sockaddr_in6 *) addr; + + if (! IN6_IS_ADDR_LOOPBACK (&sin6->sin6_addr)) + { + res = TMH_RESPONSE_reply_permission_denied (connection, + "/admin/ only allowed via loopback"); + return (MHD_YES == res) ? GNUNET_NO : GNUNET_SYSERR; + } + break; + } + default: + GNUNET_break (0); + res = TMH_RESPONSE_reply_internal_error (connection, + "Unsupported AF"); + return (MHD_YES == res) ? GNUNET_NO : GNUNET_SYSERR; + } + return GNUNET_OK; +} + + + +/** + * Handle a "/admin/add/incoming" request. Parses the + * given "reserve_pub", "amount", "transaction" and "h_wire" + * details and adds the respective transaction to the database. + * + * @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_ADMIN_handler_admin_add_incoming (struct TMH_RequestHandler *rh, + struct MHD_Connection *connection, + void **connection_cls, + const char *upload_data, + size_t *upload_data_size) +{ + struct TALER_ReservePublicKeyP reserve_pub; + struct TALER_Amount amount; + struct GNUNET_TIME_Absolute at; + json_t *wire; + json_t *root; + struct TMH_PARSE_FieldSpecification spec[] = { + TMH_PARSE_MEMBER_FIXED ("reserve_pub", &reserve_pub), + TMH_PARSE_MEMBER_AMOUNT ("amount", &amount), + TMH_PARSE_MEMBER_TIME_ABS ("execution_date", &at), + TMH_PARSE_MEMBER_OBJECT ("wire", &wire), + TMH_PARSE_MEMBER_END + }; + int res; + + res = check_permissions (connection); + if (GNUNET_OK != res) + return (GNUNET_OK == res) ? MHD_YES : MHD_NO; + + res = TMH_PARSE_post_json (connection, + connection_cls, + upload_data, + upload_data_size, + &root); + if (GNUNET_SYSERR == res) + return MHD_NO; + if ( (GNUNET_NO == res) || (NULL == root) ) + return MHD_YES; + res = TMH_PARSE_json_data (connection, + root, + spec); + json_decref (root); + if (GNUNET_OK != res) + return (GNUNET_SYSERR == res) ? MHD_NO : MHD_YES; + res = TMH_DB_execute_admin_add_incoming (connection, + &reserve_pub, + &amount, + at, + wire); + TMH_PARSE_release_data (spec); + return res; +} + +/* end of taler-mint-httpd_admin.c */ diff --git a/src/mint/taler-mint-httpd_admin.h b/src/mint/taler-mint-httpd_admin.h new file mode 100644 index 000000000..b8ca3ce59 --- /dev/null +++ b/src/mint/taler-mint-httpd_admin.h @@ -0,0 +1,46 @@ +/* + This file is part of TALER + Copyright (C) 2014 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, If not, see +*/ +/** + * @file taler-mint-httpd_admin.h + * @brief Handle /admin/ requests + * @author Christian Grothoff + */ +#ifndef TALER_MINT_HTTPD_ADMIN_H +#define TALER_MINT_HTTPD_ADMIN_H + +#include +#include "taler-mint-httpd.h" + +/** + * Handle a "/admin/add/incoming" request. Parses the + * given "reserve_pub", "amount", "transaction" and "h_wire" + * details and adds the respective transaction to the database. + * + * @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_ADMIN_handler_admin_add_incoming (struct TMH_RequestHandler *rh, + struct MHD_Connection *connection, + void **connection_cls, + const char *upload_data, + size_t *upload_data_size); + +#endif