From: Schanzenbach, Martin Date: Thu, 5 Oct 2017 17:59:39 +0000 (+0200) Subject: -add ticket DB for IdP X-Git-Tag: gnunet-0.11.0rc0~24^2~49 X-Git-Url: https://git.librecmc.org/?a=commitdiff_plain;h=76817ee408cff4aee534d6016423c8a4ecb5555f;p=oweals%2Fgnunet.git -add ticket DB for IdP --- diff --git a/src/identity-provider/Makefile.am b/src/identity-provider/Makefile.am index 106c8a92b..1b35c6c04 100644 --- a/src/identity-provider/Makefile.am +++ b/src/identity-provider/Makefile.am @@ -12,6 +12,10 @@ if USE_COVERAGE XLIB = -lgcov endif +if HAVE_SQLITE +SQLITE_PLUGIN = libgnunet_plugin_identity_provider_sqlite.la +endif + pkgcfgdir= $(pkgdatadir)/config.d/ libexecdir= $(pkglibdir)/libexec/ @@ -23,7 +27,8 @@ lib_LTLIBRARIES = \ libgnunetidentityprovider.la plugin_LTLIBRARIES = \ libgnunet_plugin_rest_identity_provider.la \ - libgnunet_plugin_gnsrecord_identity_provider.la + libgnunet_plugin_gnsrecord_identity_provider.la \ + $(SQLITE_PLUGIN) bin_PROGRAMS = \ gnunet-identity-token \ @@ -40,6 +45,18 @@ libgnunet_plugin_gnsrecord_identity_provider_la_LIBADD = \ libgnunet_plugin_gnsrecord_identity_provider_la_LDFLAGS = \ $(GN_PLUGIN_LDFLAGS) +libgnunet_plugin_identity_provider_sqlite_la_SOURCES = \ + plugin_identity_provider_sqlite.c +libgnunet_plugin_identity_provider_sqlite_la_LIBADD = \ + $(top_builddir)/src/identity-provider/libgnunetidentityprovider.la \ + $(top_builddir)/src/sq/libgnunetsq.la \ + $(top_builddir)/src/statistics/libgnunetstatistics.la \ + $(top_builddir)/src/util/libgnunetutil.la $(XLIBS) -lsqlite3 \ + $(LTLIBINTL) +libgnunet_plugin_identity_provider_sqlite_la_LDFLAGS = \ + $(GN_PLUGIN_LDFLAGS) + + gnunet_service_identity_provider_SOURCES = \ gnunet-service-identity-provider.c \ diff --git a/src/identity-provider/gnunet-service-identity-provider.c b/src/identity-provider/gnunet-service-identity-provider.c index 9a919102f..f77eebd6d 100644 --- a/src/identity-provider/gnunet-service-identity-provider.c +++ b/src/identity-provider/gnunet-service-identity-provider.c @@ -33,6 +33,7 @@ #include "gnunet_credential_service.h" #include "gnunet_statistics_service.h" #include "gnunet_gns_service.h" +#include "gnunet_identity_provider_plugin.h" #include "gnunet_signatures.h" #include "identity_provider.h" #include "identity_token.h" @@ -64,6 +65,16 @@ */ static struct GNUNET_IDENTITY_Handle *identity_handle; +/** + * Database handle + */ +static struct GNUNET_IDENTITY_PROVIDER_PluginFunctions *TKT_database; + +/** + * Name of DB plugin + */ +static char *db_lib_name; + /** * Token expiration interval */ @@ -135,6 +146,54 @@ static const struct GNUNET_CONFIGURATION_Handle *cfg; */ struct IdpClient; +/** + * A ticket iteration operation. + */ +struct TicketIteration +{ + /** + * DLL + */ + struct TicketIteration *next; + + /** + * DLL + */ + struct TicketIteration *prev; + + /** + * Client which intiated this zone iteration + */ + struct IdpClient *client; + + /** + * Key of the identity we are iterating over. + */ + struct GNUNET_CRYPTO_EcdsaPublicKey identity; + + /** + * Identity is audience + */ + uint32_t is_audience; + + /** + * The operation id fot the iteration in the response for the client + */ + uint32_t r_id; + + /** + * Offset of the iteration used to address next result of the + * iteration in the store + * + * Initialy set to 0 in handle_iteration_start + * Incremented with by every call to handle_iteration_next + */ + uint32_t offset; + +}; + + + /** * Callback after an ABE bootstrap * @@ -247,6 +306,16 @@ struct IdpClient * in progress initiated by this client */ struct AttributeIterator *op_tail; + + /** + * Head of DLL of ticket iteration ops + */ + struct TicketIteration *ticket_iter_head; + + /** + * Tail of DLL of ticket iteration ops + */ + struct TicketIteration *ticket_iter_tail; }; @@ -605,7 +674,10 @@ cleanup() GNUNET_STATISTICS_destroy (stats, GNUNET_NO); stats = NULL; } - + GNUNET_break (NULL == GNUNET_PLUGIN_unload (db_lib_name, + TKT_database)); + GNUNET_free (db_lib_name); + db_lib_name = NULL; if (NULL != timeout_task) GNUNET_SCHEDULER_cancel (timeout_task); if (NULL != update_task) @@ -1666,31 +1738,48 @@ handle_issue_message (void *cls, static void cleanup_ticket_issue_handle (struct TicketIssueHandle *handle) { - struct GNUNET_IDENTITY_PROVIDER_AttributeListEntry *le; - struct GNUNET_IDENTITY_PROVIDER_AttributeListEntry *tmp_le; - - for (le = handle->attrs->list_head; NULL != le;) - { - GNUNET_free (le->attribute); - tmp_le = le; - le = le->next; - GNUNET_free (tmp_le); - } - GNUNET_free (handle->attrs); + if (NULL != handle->attrs) + attribute_list_destroy (handle->attrs); if (NULL != handle->ns_qe) GNUNET_NAMESTORE_cancel (handle->ns_qe); GNUNET_free (handle); } + +static void +send_ticket_result (struct IdpClient *client, + uint32_t r_id, + const struct GNUNET_IDENTITY_PROVIDER_Ticket2 *ticket, + const struct GNUNET_IDENTITY_PROVIDER_AttributeList *attrs) +{ + struct TicketResultMessage *irm; + struct GNUNET_MQ_Envelope *env; + size_t attrs_size; + struct GNUNET_IDENTITY_PROVIDER_Ticket2 *ticket_buf; + char *attrs_buf; + + attrs_size = attribute_list_serialize_get_size (attrs); + + env = GNUNET_MQ_msg_extra (irm, + sizeof (struct GNUNET_IDENTITY_PROVIDER_Ticket2) + attrs_size, + GNUNET_MESSAGE_TYPE_IDENTITY_PROVIDER_TICKET_RESULT); + ticket_buf = (struct GNUNET_IDENTITY_PROVIDER_Ticket2 *)&irm[1]; + *ticket_buf = *ticket; + attrs_buf = (char*)&ticket_buf[1]; + attribute_list_serialize (attrs, + attrs_buf); + irm->id = htonl (r_id); + + GNUNET_MQ_send (client->mq, + env); +} + static void store_ticket_issue_cont (void *cls, int32_t success, const char *emsg) { - struct GNUNET_IDENTITY_PROVIDER_Ticket2 *ticket; struct TicketIssueHandle *handle = cls; - struct TicketResultMessage *irm; - struct GNUNET_MQ_Envelope *env; handle->ns_qe = NULL; if (GNUNET_SYSERR == success) @@ -1701,15 +1790,10 @@ store_ticket_issue_cont (void *cls, GNUNET_SCHEDULER_add_now (&do_shutdown, NULL); return; } - env = GNUNET_MQ_msg_extra (irm, - sizeof (struct GNUNET_IDENTITY_PROVIDER_Ticket2), - GNUNET_MESSAGE_TYPE_IDENTITY_PROVIDER_TICKET_RESULT); - ticket = (struct GNUNET_IDENTITY_PROVIDER_Ticket2 *)&irm[1]; - *ticket = handle->ticket; - irm->id = handle->r_id; - - GNUNET_MQ_send (handle->client->mq, - env); + send_ticket_result (handle->client, + handle->r_id, + &handle->ticket, + handle->attrs); cleanup_ticket_issue_handle (handle); } @@ -1717,9 +1801,9 @@ store_ticket_issue_cont (void *cls, int serialize_abe_keyinfo2 (const struct TicketIssueHandle *handle, - const struct GNUNET_CRYPTO_AbeKey *rp_key, - struct GNUNET_CRYPTO_EcdhePrivateKey **ecdh_privkey, - char **result) + const struct GNUNET_CRYPTO_AbeKey *rp_key, + struct GNUNET_CRYPTO_EcdhePrivateKey **ecdh_privkey, + char **result) { struct GNUNET_CRYPTO_EcdhePublicKey ecdh_pubkey; struct GNUNET_IDENTITY_PROVIDER_AttributeListEntry *le; @@ -1729,12 +1813,12 @@ serialize_abe_keyinfo2 (const struct TicketIssueHandle *handle, char *write_ptr; char attrs_str_len; ssize_t size; - + struct GNUNET_CRYPTO_SymmetricSessionKey skey; struct GNUNET_CRYPTO_SymmetricInitializationVector iv; struct GNUNET_HashCode new_key_hash; ssize_t enc_size; - + size = GNUNET_CRYPTO_cpabe_serialize_key (rp_key, (void**)&serialized_key); attrs_str_len = 0; @@ -1889,7 +1973,7 @@ handle_ticket_issue_message (void *cls, ih = GNUNET_new (struct TicketIssueHandle); attrs_len = ntohs (im->attr_len); ih->attrs = attribute_list_deserialize ((char*)&im[1], attrs_len); - ih->r_id = im->id; + ih->r_id = ntohl (im->id); ih->client = idp; ih->identity = im->identity; GNUNET_CRYPTO_ecdsa_key_get_public (&ih->identity, @@ -2488,6 +2572,234 @@ handle_iteration_next (void *cls, GNUNET_SERVICE_client_continue (idp->client); } +/** + * Ticket iteration processor result + */ +enum ZoneIterationResult +{ + /** + * Iteration start. + */ + IT_START = 0, + + /** + * Found tickets, + * Continue to iterate with next iteration_next call + */ + IT_SUCCESS_MORE_AVAILABLE = 1, + + /** + * Iteration complete + */ + IT_SUCCESS_NOT_MORE_RESULTS_AVAILABLE = 2 +}; + + +/** + * Context for ticket iteration + */ +struct TicketIterationProcResult +{ + /** + * The ticket iteration handle + */ + struct TicketIteration *ti; + + /** + * Iteration result: iteration done? + * #IT_SUCCESS_MORE_AVAILABLE: if there may be more results overall but + * we got one for now and have sent it to the client + * #IT_SUCCESS_NOT_MORE_RESULTS_AVAILABLE: if there are no further results, + * #IT_START: if we are still trying to find a result. + */ + int res_iteration_finished; + +}; + + + +/** + * Process ticket from database + * + * @param cls struct TicketIterationProcResult + * @param ticket the ticket + * @param attrs the attributes + */ +static void +ticket_iterate_proc (void *cls, + const struct GNUNET_IDENTITY_PROVIDER_Ticket2 *ticket, + const struct GNUNET_IDENTITY_PROVIDER_AttributeList *attrs) +{ + struct TicketIterationProcResult *proc = cls; + + if (NULL == ticket) + { + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, + "Iteration done\n"); + proc->res_iteration_finished = IT_SUCCESS_NOT_MORE_RESULTS_AVAILABLE; + return; + } + if ((NULL == ticket) || (NULL == attrs)) + { + /* error */ + proc->res_iteration_finished = IT_START; + GNUNET_break (0); + return; + } + proc->res_iteration_finished = IT_SUCCESS_MORE_AVAILABLE; + send_ticket_result (proc->ti->client, + proc->ti->r_id, + ticket, + attrs); + +} + +/** + * Perform ticket iteration step + * + * @param ti ticket iterator to process + */ +static void +run_ticket_iteration_round (struct TicketIteration *ti) +{ + struct TicketIterationProcResult proc; + struct GNUNET_MQ_Envelope *env; + struct TicketResultMessage *trm; + int ret; + + memset (&proc, 0, sizeof (proc)); + proc.ti = ti; + proc.res_iteration_finished = IT_START; + while (IT_START == proc.res_iteration_finished) + { + if (GNUNET_SYSERR == + (ret = TKT_database->iterate_tickets (TKT_database->cls, + &ti->identity, + ti->is_audience, + ti->offset, + &ticket_iterate_proc, + &proc))) + { + GNUNET_break (0); + break; + } + if (GNUNET_NO == ret) + proc.res_iteration_finished = IT_SUCCESS_NOT_MORE_RESULTS_AVAILABLE; + ti->offset++; + } + if (IT_SUCCESS_MORE_AVAILABLE == proc.res_iteration_finished) + { + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, + "More results available\n"); + return; /* more later */ + } + /* send empty response to indicate end of list */ + env = GNUNET_MQ_msg (trm, + GNUNET_MESSAGE_TYPE_IDENTITY_PROVIDER_TICKET_RESULT); + trm->id = htonl (ti->r_id); + GNUNET_MQ_send (ti->client->mq, + env); + GNUNET_CONTAINER_DLL_remove (ti->client->ticket_iter_head, + ti->client->ticket_iter_tail, + ti); + GNUNET_free (ti); +} + +/** + * Handles a #GNUNET_MESSAGE_TYPE_IDENTITY_PROVIDER_TICKET_ITERATION_START message + * + * @param cls the client sending the message + * @param tis_msg message from the client + */ +static void +handle_ticket_iteration_start (void *cls, + const struct TicketIterationStartMessage *tis_msg) +{ + struct IdpClient *client = cls; + struct TicketIteration *ti; + + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, + "Received TICKET_ITERATION_START message\n"); + ti = GNUNET_new (struct TicketIteration); + ti->r_id = ntohl (tis_msg->id); + ti->offset = 0; + ti->client = client; + ti->identity = tis_msg->identity; + ti->is_audience = ntohl (tis_msg->is_audience); + + GNUNET_CONTAINER_DLL_insert (client->ticket_iter_head, + client->ticket_iter_tail, + ti); + run_ticket_iteration_round (ti); + GNUNET_SERVICE_client_continue (client->client); +} + + +/** + * Handles a #GNUNET_MESSAGE_TYPE_IDENTITY_PROVIDER_TICKET_ITERATION_STOP message + * + * @param cls the client sending the message + * @param tis_msg message from the client + */ +static void +handle_ticket_iteration_stop (void *cls, + const struct TicketIterationStopMessage *tis_msg) +{ + struct IdpClient *client = cls; + struct TicketIteration *ti; + uint32_t rid; + + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, + "Received `%s' message\n", + "TICKET_ITERATION_STOP"); + rid = ntohl (tis_msg->id); + for (ti = client->ticket_iter_head; NULL != ti; ti = ti->next) + if (ti->r_id == rid) + break; + if (NULL == ti) + { + GNUNET_break (0); + GNUNET_SERVICE_client_drop (client->client); + return; + } + GNUNET_CONTAINER_DLL_remove (client->ticket_iter_head, + client->ticket_iter_tail, + ti); + GNUNET_free (ti); + GNUNET_SERVICE_client_continue (client->client); +} + + +/** + * Handles a #GNUNET_MESSAGE_TYPE_IDENTITY_PROVIDER_TICKET_ITERATION_NEXT message + * + * @param cls the client sending the message + * @param message message from the client + */ +static void +handle_ticket_iteration_next (void *cls, + const struct TicketIterationNextMessage *tis_msg) +{ + struct IdpClient *client = cls; + struct TicketIteration *ti; + uint32_t rid; + + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, + "Received TICKET_ITERATION_NEXT message\n"); + rid = ntohl (tis_msg->id); + for (ti = client->ticket_iter_head; NULL != ti; ti = ti->next) + if (ti->r_id == rid) + break; + if (NULL == ti) + { + GNUNET_break (0); + GNUNET_SERVICE_client_drop (client->client); + return; + } + run_ticket_iteration_round (ti); + GNUNET_SERVICE_client_continue (client->client); +} + @@ -2504,6 +2816,7 @@ run (void *cls, const struct GNUNET_CONFIGURATION_Handle *c, struct GNUNET_SERVICE_Handle *server) { + char *database; cfg = c; stats = GNUNET_STATISTICS_create ("identity-provider", cfg); @@ -2529,6 +2842,29 @@ run (void *cls, NULL, NULL); + /* Loading DB plugin */ + if (GNUNET_OK != + GNUNET_CONFIGURATION_get_value_string (cfg, + "identity-provider", + "database", + &database)) + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "No database backend configured\n"); + GNUNET_asprintf (&db_lib_name, + "libgnunet_plugin_identity_provider_%s", + database); + TKT_database = GNUNET_PLUGIN_load (db_lib_name, + (void *) cfg); + GNUNET_free (database); + if (NULL == TKT_database) + { + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "Could not load database backend `%s'\n", + db_lib_name); + GNUNET_SCHEDULER_shutdown (); + return; + } + if (GNUNET_OK == GNUNET_CONFIGURATION_get_value_time (cfg, "identity-provider", @@ -2645,5 +2981,18 @@ GNUNET_SERVICE_MAIN GNUNET_MESSAGE_TYPE_IDENTITY_PROVIDER_CONSUME_TICKET, struct ConsumeTicketMessage, NULL), + GNUNET_MQ_hd_fixed_size (ticket_iteration_start, + GNUNET_MESSAGE_TYPE_IDENTITY_PROVIDER_TICKET_ITERATION_START, + struct TicketIterationStartMessage, + NULL), + GNUNET_MQ_hd_fixed_size (ticket_iteration_next, + GNUNET_MESSAGE_TYPE_IDENTITY_PROVIDER_TICKET_ITERATION_NEXT, + struct TicketIterationNextMessage, + NULL), + GNUNET_MQ_hd_fixed_size (ticket_iteration_stop, + GNUNET_MESSAGE_TYPE_IDENTITY_PROVIDER_TICKET_ITERATION_STOP, + struct TicketIterationStopMessage, + NULL), + GNUNET_MQ_handler_end()); -/* end of gnunet-service-identity-provider.c */ + /* end of gnunet-service-identity-provider.c */ diff --git a/src/identity-provider/identity-provider.conf b/src/identity-provider/identity-provider.conf index bac8e69ed..826b2419e 100644 --- a/src/identity-provider/identity-provider.conf +++ b/src/identity-provider/identity-provider.conf @@ -10,3 +10,6 @@ UNIXPATH = $GNUNET_USER_RUNTIME_DIR/gnunet-service-identity-provider.sock UNIX_MATCH_UID = NO UNIX_MATCH_GID = YES TOKEN_EXPIRATION_INTERVAL = 30 m + +[identity-provider-sqlite] +FILENAME = $GNUNET_DATA_HOME/identity-provider/sqlite.db diff --git a/src/identity-provider/identity_attribute.c b/src/identity-provider/identity_attribute.c index 916386754..1c5654946 100644 --- a/src/identity-provider/identity_attribute.c +++ b/src/identity-provider/identity_attribute.c @@ -125,7 +125,22 @@ attribute_list_deserialize (const char* data, return attrs; } +void +attribute_list_destroy (struct GNUNET_IDENTITY_PROVIDER_AttributeList *attrs) +{ + struct GNUNET_IDENTITY_PROVIDER_AttributeListEntry *le; + struct GNUNET_IDENTITY_PROVIDER_AttributeListEntry *tmp_le; + + for (le = attrs->list_head; NULL != le;) + { + GNUNET_free (le->attribute); + tmp_le = le; + le = le->next; + GNUNET_free (tmp_le); + } + GNUNET_free (attrs); +} size_t attribute_serialize_get_size (const struct GNUNET_IDENTITY_PROVIDER_Attribute *attr) diff --git a/src/identity-provider/identity_attribute.h b/src/identity-provider/identity_attribute.h index 00e520a38..d44f4c17f 100644 --- a/src/identity-provider/identity_attribute.h +++ b/src/identity-provider/identity_attribute.h @@ -58,6 +58,8 @@ struct Attribute size_t attribute_list_serialize_get_size (const struct GNUNET_IDENTITY_PROVIDER_AttributeList *attrs); +void +attribute_list_destroy (struct GNUNET_IDENTITY_PROVIDER_AttributeList *attrs); /** diff --git a/src/identity-provider/identity_provider.h b/src/identity-provider/identity_provider.h index dcad35118..434af4d8c 100644 --- a/src/identity-provider/identity_provider.h +++ b/src/identity-provider/identity_provider.h @@ -318,6 +318,70 @@ struct AttributeIterationStopMessage }; +/** + * Start a ticket iteration for the given identity + */ +struct TicketIterationStartMessage +{ + /** + * Message + */ + struct GNUNET_MessageHeader header; + + /** + * Unique identifier for this request (for key collisions). + */ + uint32_t id GNUNET_PACKED; + + /** + * Identity. + */ + struct GNUNET_CRYPTO_EcdsaPublicKey identity; + + /** + * Identity is audience or issuer + */ + uint32_t is_audience GNUNET_PACKED; +}; + + +/** + * Ask for next result of ticket iteration for the given operation + */ +struct TicketIterationNextMessage +{ + /** + * Type will be #GNUNET_MESSAGE_TYPE_IDENTITY_PROVIDER_TICKET_ITERATION_NEXT + */ + struct GNUNET_MessageHeader header; + + /** + * Unique identifier for this request (for key collisions). + */ + uint32_t id GNUNET_PACKED; + +}; + + +/** + * Stop ticket iteration for the given operation + */ +struct TicketIterationStopMessage +{ + /** + * Type will be #GNUNET_MESSAGE_TYPE_IDENTITY_PROVIDER_TICKET_ITERATION_STOP + */ + struct GNUNET_MessageHeader header; + + /** + * Unique identifier for this request (for key collisions). + */ + uint32_t id GNUNET_PACKED; + +}; + + + /** * Ticket issue message */ diff --git a/src/identity-provider/plugin_identity_provider_sqlite.c b/src/identity-provider/plugin_identity_provider_sqlite.c new file mode 100644 index 000000000..d05baa79d --- /dev/null +++ b/src/identity-provider/plugin_identity_provider_sqlite.c @@ -0,0 +1,669 @@ + /* + * This file is part of GNUnet + * Copyright (C) 2009-2017 GNUnet e.V. + * + * GNUnet 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. + * + * GNUnet 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 GNUnet; see the file COPYING. If not, write to the + * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +/** + * @file identity-provider/plugin_identity_provider_sqlite.c + * @brief sqlite-based idp backend + * @author Martin Schanzenbach + */ + +#include "platform.h" +#include "gnunet_identity_provider_service.h" +#include "gnunet_identity_provider_plugin.h" +#include "identity_attribute.h" +#include "gnunet_sq_lib.h" +#include + +/** + * After how many ms "busy" should a DB operation fail for good? A + * low value makes sure that we are more responsive to requests + * (especially PUTs). A high value guarantees a higher success rate + * (SELECTs in iterate can take several seconds despite LIMIT=1). + * + * The default value of 1s should ensure that users do not experience + * huge latencies while at the same time allowing operations to + * succeed with reasonable probability. + */ +#define BUSY_TIMEOUT_MS 1000 + + +/** + * Log an error message at log-level 'level' that indicates + * a failure of the command 'cmd' on file 'filename' + * with the message given by strerror(errno). + */ +#define LOG_SQLITE(db, level, cmd) do { GNUNET_log_from (level, "namestore-identity-provider", _("`%s' failed at %s:%d with error: %s\n"), cmd, __FILE__, __LINE__, sqlite3_errmsg(db->dbh)); } while(0) + +#define LOG(kind,...) GNUNET_log_from (kind, "namestore-sqlite", __VA_ARGS__) + + +/** + * Context for all functions in this plugin. + */ +struct Plugin +{ + + const struct GNUNET_CONFIGURATION_Handle *cfg; + + /** + * Database filename. + */ + char *fn; + + /** + * Native SQLite database handle. + */ + sqlite3 *dbh; + + /** + * Precompiled SQL to store ticket. + */ + sqlite3_stmt *store_ticket; + + /** + * Precompiled SQL to delete existing ticket. + */ + sqlite3_stmt *delete_ticket; + + /** + * Precompiled SQL to iterate tickets. + */ + sqlite3_stmt *iterate_tickets; + + /** + * Precompiled SQL to iterate tickets by audience. + */ + sqlite3_stmt *iterate_tickets_by_audience; +}; + + +/** + * @brief Prepare a SQL statement + * + * @param dbh handle to the database + * @param zSql SQL statement, UTF-8 encoded + * @param ppStmt set to the prepared statement + * @return 0 on success + */ +static int +sq_prepare (sqlite3 *dbh, + const char *zSql, + sqlite3_stmt **ppStmt) +{ + char *dummy; + int result; + + result = + sqlite3_prepare_v2 (dbh, + zSql, + strlen (zSql), + ppStmt, + (const char **) &dummy); + LOG (GNUNET_ERROR_TYPE_DEBUG, + "Prepared `%s' / %p: %d\n", + zSql, + *ppStmt, + result); + return result; +} + +/** + * Create our database indices. + * + * @param dbh handle to the database + */ +static void +create_indices (sqlite3 * dbh) +{ + /* create indices */ + if ( (SQLITE_OK != + sqlite3_exec (dbh, + "CREATE INDEX IF NOT EXISTS identity_reverse ON identity001tickets (identity,audience)", + NULL, NULL, NULL)) || + (SQLITE_OK != + sqlite3_exec (dbh, + "CREATE INDEX IF NOT EXISTS it_iter ON identity001tickets (rnd)", + NULL, NULL, NULL)) ) + LOG (GNUNET_ERROR_TYPE_ERROR, + "Failed to create indices: %s\n", + sqlite3_errmsg (dbh)); +} + + + +#if 0 +#define CHECK(a) GNUNET_break(a) +#define ENULL NULL +#else +#define ENULL &e +#define ENULL_DEFINED 1 +#define CHECK(a) if (! (a)) { GNUNET_log(GNUNET_ERROR_TYPE_ERROR, "%s\n", e); sqlite3_free(e); } +#endif + + +/** + * Initialize the database connections and associated + * data structures (create tables and indices + * as needed as well). + * + * @param plugin the plugin context (state for this module) + * @return #GNUNET_OK on success + */ +static int +database_setup (struct Plugin *plugin) +{ + sqlite3_stmt *stmt; + char *afsdir; +#if ENULL_DEFINED + char *e; +#endif + + if (GNUNET_OK != + GNUNET_CONFIGURATION_get_value_filename (plugin->cfg, + "identity-provider-sqlite", + "FILENAME", + &afsdir)) + { + GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR, + "identity-provider-sqlite", + "FILENAME"); + return GNUNET_SYSERR; + } + if (GNUNET_OK != + GNUNET_DISK_file_test (afsdir)) + { + if (GNUNET_OK != + GNUNET_DISK_directory_create_for_file (afsdir)) + { + GNUNET_break (0); + GNUNET_free (afsdir); + return GNUNET_SYSERR; + } + } + /* afsdir should be UTF-8-encoded. If it isn't, it's a bug */ + plugin->fn = afsdir; + + /* Open database and precompile statements */ + if (sqlite3_open (plugin->fn, &plugin->dbh) != SQLITE_OK) + { + LOG (GNUNET_ERROR_TYPE_ERROR, + _("Unable to initialize SQLite: %s.\n"), + sqlite3_errmsg (plugin->dbh)); + return GNUNET_SYSERR; + } + CHECK (SQLITE_OK == + sqlite3_exec (plugin->dbh, + "PRAGMA temp_store=MEMORY", NULL, NULL, + ENULL)); + CHECK (SQLITE_OK == + sqlite3_exec (plugin->dbh, + "PRAGMA synchronous=NORMAL", NULL, NULL, + ENULL)); + CHECK (SQLITE_OK == + sqlite3_exec (plugin->dbh, + "PRAGMA legacy_file_format=OFF", NULL, NULL, + ENULL)); + CHECK (SQLITE_OK == + sqlite3_exec (plugin->dbh, + "PRAGMA auto_vacuum=INCREMENTAL", NULL, + NULL, ENULL)); + CHECK (SQLITE_OK == + sqlite3_exec (plugin->dbh, + "PRAGMA encoding=\"UTF-8\"", NULL, + NULL, ENULL)); + CHECK (SQLITE_OK == + sqlite3_exec (plugin->dbh, + "PRAGMA locking_mode=EXCLUSIVE", NULL, NULL, + ENULL)); + CHECK (SQLITE_OK == + sqlite3_exec (plugin->dbh, + "PRAGMA page_size=4092", NULL, NULL, + ENULL)); + + CHECK (SQLITE_OK == + sqlite3_busy_timeout (plugin->dbh, + BUSY_TIMEOUT_MS)); + + + /* Create table */ + CHECK (SQLITE_OK == + sq_prepare (plugin->dbh, + "SELECT 1 FROM sqlite_master WHERE tbl_name = 'identity001tickets'", + &stmt)); + if ((sqlite3_step (stmt) == SQLITE_DONE) && + (sqlite3_exec + (plugin->dbh, + "CREATE TABLE identity001tickets (" + " identity BLOB NOT NULL DEFAULT ''," + " audience BLOB NOT NULL DEFAULT ''," + " rnd INT8 NOT NULL DEFAULT ''," + " attributes BLOB NOT NULL DEFAULT ''" + ")", + NULL, NULL, NULL) != SQLITE_OK)) + { + LOG_SQLITE (plugin, GNUNET_ERROR_TYPE_ERROR, + "sqlite3_exec"); + sqlite3_finalize (stmt); + return GNUNET_SYSERR; + } + sqlite3_finalize (stmt); + + create_indices (plugin->dbh); + + if ( (SQLITE_OK != + sq_prepare (plugin->dbh, + "INSERT INTO identity001tickets (identity, audience, rnd, attributes)" + " VALUES (?, ?, ?, ?)", + &plugin->store_ticket)) || + (SQLITE_OK != + sq_prepare (plugin->dbh, + "DELETE FROM identity001tickets WHERE identity=? AND rnd=?", + &plugin->delete_ticket)) || + (SQLITE_OK != + sq_prepare (plugin->dbh, + "SELECT identity,audience,rnd,attributes" + " FROM identity001tickets WHERE identity=?" + " ORDER BY rnd LIMIT 1 OFFSET ?", + &plugin->iterate_tickets)) || + (SQLITE_OK != + sq_prepare (plugin->dbh, + "SELECT identity,audience,rnd,attributes" + " FROM identity001tickets WHERE audience=?" + " ORDER BY rnd LIMIT 1 OFFSET ?", + &plugin->iterate_tickets_by_audience)) ) + { + LOG_SQLITE (plugin, + GNUNET_ERROR_TYPE_ERROR, + "precompiling"); + return GNUNET_SYSERR; + } + return GNUNET_OK; +} + + +/** + * Shutdown database connection and associate data + * structures. + * @param plugin the plugin context (state for this module) + */ +static void +database_shutdown (struct Plugin *plugin) +{ + int result; + sqlite3_stmt *stmt; + + if (NULL != plugin->store_ticket) + sqlite3_finalize (plugin->store_ticket); + if (NULL != plugin->delete_ticket) + sqlite3_finalize (plugin->delete_ticket); + if (NULL != plugin->iterate_tickets) + sqlite3_finalize (plugin->iterate_tickets); + result = sqlite3_close (plugin->dbh); + if (result == SQLITE_BUSY) + { + LOG (GNUNET_ERROR_TYPE_WARNING, + _("Tried to close sqlite without finalizing all prepared statements.\n")); + stmt = sqlite3_next_stmt (plugin->dbh, + NULL); + while (NULL != stmt) + { + GNUNET_log_from (GNUNET_ERROR_TYPE_DEBUG, + "sqlite", + "Closing statement %p\n", + stmt); + result = sqlite3_finalize (stmt); + if (result != SQLITE_OK) + GNUNET_log_from (GNUNET_ERROR_TYPE_WARNING, + "sqlite", + "Failed to close statement %p: %d\n", + stmt, + result); + stmt = sqlite3_next_stmt (plugin->dbh, + NULL); + } + result = sqlite3_close (plugin->dbh); + } + if (SQLITE_OK != result) + LOG_SQLITE (plugin, + GNUNET_ERROR_TYPE_ERROR, + "sqlite3_close"); + + GNUNET_free_non_null (plugin->fn); +} + + +/** + * Store a ticket in the database. + * + * @param cls closure (internal context for the plugin) + * @param ticket the ticket to persist + * @param attrs attributes to persist + * @return #GNUNET_OK on success, else #GNUNET_SYSERR + */ +static int +identity_provider_sqlite_store_ticket (void *cls, + const struct GNUNET_IDENTITY_PROVIDER_Ticket2 *ticket, + const struct GNUNET_IDENTITY_PROVIDER_AttributeList *attrs) +{ + struct Plugin *plugin = cls; + int n; + size_t attrs_size; + char *attrs_serialized; + + attrs_size = attribute_list_serialize_get_size (attrs); + + attrs_serialized = GNUNET_malloc (attrs_size); + + attribute_list_serialize (attrs, + attrs_serialized); + + { + struct GNUNET_SQ_QueryParam sparams[] = { + GNUNET_SQ_query_param_auto_from_type (&ticket->identity), + GNUNET_SQ_query_param_auto_from_type (&ticket->audience), + GNUNET_SQ_query_param_uint64 (&ticket->rnd), + GNUNET_SQ_query_param_fixed_size (attrs_serialized, attrs_size), + GNUNET_SQ_query_param_end + }; + + if (GNUNET_OK != + GNUNET_SQ_bind (plugin->store_ticket, + sparams)) + { + LOG_SQLITE (plugin, + GNUNET_ERROR_TYPE_ERROR | GNUNET_ERROR_TYPE_BULK, + "sqlite3_bind_XXXX"); + GNUNET_SQ_reset (plugin->dbh, + plugin->store_ticket); + return GNUNET_SYSERR; + } + n = sqlite3_step (plugin->store_ticket); + GNUNET_SQ_reset (plugin->dbh, + plugin->store_ticket); + } + switch (n) + { + case SQLITE_DONE: + GNUNET_log_from (GNUNET_ERROR_TYPE_DEBUG, + "sqlite", + "Ticket stored\n"); + return GNUNET_OK; + case SQLITE_BUSY: + LOG_SQLITE (plugin, + GNUNET_ERROR_TYPE_WARNING | GNUNET_ERROR_TYPE_BULK, + "sqlite3_step"); + return GNUNET_NO; + default: + LOG_SQLITE (plugin, + GNUNET_ERROR_TYPE_ERROR | GNUNET_ERROR_TYPE_BULK, + "sqlite3_step"); + return GNUNET_SYSERR; + } +} + + +/** + * Store a ticket in the database. + * + * @param cls closure (internal context for the plugin) + * @param ticket the ticket to delete + * @return #GNUNET_OK on success, else #GNUNET_SYSERR + */ +static int +identity_provider_sqlite_delete_ticket (void *cls, + const struct GNUNET_IDENTITY_PROVIDER_Ticket2 *ticket) +{ + struct Plugin *plugin = cls; + int n; + + { + struct GNUNET_SQ_QueryParam sparams[] = { + GNUNET_SQ_query_param_auto_from_type (&ticket->identity), + GNUNET_SQ_query_param_uint64 (&ticket->rnd), + GNUNET_SQ_query_param_end + }; + + if (GNUNET_OK != + GNUNET_SQ_bind (plugin->delete_ticket, + sparams)) + { + LOG_SQLITE (plugin, + GNUNET_ERROR_TYPE_ERROR | GNUNET_ERROR_TYPE_BULK, + "sqlite3_bind_XXXX"); + GNUNET_SQ_reset (plugin->dbh, + plugin->store_ticket); + return GNUNET_SYSERR; + } + n = sqlite3_step (plugin->delete_ticket); + GNUNET_SQ_reset (plugin->dbh, + plugin->delete_ticket); + } + switch (n) + { + case SQLITE_DONE: + GNUNET_log_from (GNUNET_ERROR_TYPE_DEBUG, + "sqlite", + "Ticket deleted\n"); + return GNUNET_OK; + case SQLITE_BUSY: + LOG_SQLITE (plugin, + GNUNET_ERROR_TYPE_WARNING | GNUNET_ERROR_TYPE_BULK, + "sqlite3_step"); + return GNUNET_NO; + default: + LOG_SQLITE (plugin, + GNUNET_ERROR_TYPE_ERROR | GNUNET_ERROR_TYPE_BULK, + "sqlite3_step"); + return GNUNET_SYSERR; + } +} + + +/** + * The given 'sqlite' statement has been prepared to be run. + * It will return a record which should be given to the iterator. + * Runs the statement and parses the returned record. + * + * @param plugin plugin context + * @param stmt to run (and then clean up) + * @param iter iterator to call with the result + * @param iter_cls closure for @a iter + * @return #GNUNET_OK on success, #GNUNET_NO if there were no results, #GNUNET_SYSERR on error + */ +static int +get_ticket_and_call_iterator (struct Plugin *plugin, + sqlite3_stmt *stmt, + GNUNET_IDENTITY_PROVIDER_TicketIterator iter, + void *iter_cls) +{ + struct GNUNET_IDENTITY_PROVIDER_Ticket2 ticket; + size_t attrs_size; + void *attrs_serialized; + int ret; + int sret; + + ret = GNUNET_NO; + if (SQLITE_ROW == (sret = sqlite3_step (stmt))) + { + struct GNUNET_SQ_ResultSpec rs[] = { + GNUNET_SQ_result_spec_auto_from_type (&ticket.identity), + GNUNET_SQ_result_spec_auto_from_type (&ticket.audience), + GNUNET_SQ_result_spec_uint64 (&ticket.rnd), + GNUNET_SQ_result_spec_variable_size (&attrs_serialized, &attrs_size), + GNUNET_SQ_result_spec_end + + }; + ret = GNUNET_SQ_extract_result (stmt, + rs); + if (GNUNET_OK != ret) + { + GNUNET_break (0); + ret = GNUNET_SYSERR; + } + else + { + struct GNUNET_IDENTITY_PROVIDER_AttributeList *attrs; + + attrs = attribute_list_deserialize (attrs_serialized, attrs_size); + + if (NULL == attrs) + { + GNUNET_break (0); + ret = GNUNET_SYSERR; + } + else + { + if (NULL != iter) + iter (iter_cls, + &ticket, + attrs); + ret = GNUNET_YES; + } + } + GNUNET_SQ_cleanup_result (rs); + } + else + { + if (SQLITE_DONE != sret) + LOG_SQLITE (plugin, + GNUNET_ERROR_TYPE_ERROR, + "sqlite_step"); + } + GNUNET_SQ_reset (plugin->dbh, + stmt); + return ret; +} + +/** + * Iterate over the results for a particular key and zone in the + * datastore. Will return at most one result to the iterator. + * + * @param cls closure (internal context for the plugin) + * @param identity the issuing identity or audience (depending on audience switch) + * @param audience GNUNET_YES if identity is audience + * @param offset offset in the list of all matching records + * @param iter function to call with the result + * @param iter_cls closure for @a iter + * @return #GNUNET_OK on success, #GNUNET_NO if there were no results, #GNUNET_SYSERR on error + */ +static int +identity_provider_sqlite_iterate_tickets (void *cls, + const struct GNUNET_CRYPTO_EcdsaPublicKey *identity, + int audience, + uint64_t offset, + GNUNET_IDENTITY_PROVIDER_TicketIterator iter, + void *iter_cls) +{ + struct Plugin *plugin = cls; + sqlite3_stmt *stmt; + int err; + + if (NULL == identity) + { + GNUNET_break (0); + return GNUNET_SYSERR; + } + struct GNUNET_SQ_QueryParam params[] = { + GNUNET_SQ_query_param_auto_from_type (identity), + GNUNET_SQ_query_param_uint64 (&offset), + GNUNET_SQ_query_param_end + }; + if (GNUNET_YES == audience) + { + stmt = plugin->iterate_tickets_by_audience; + err = GNUNET_SQ_bind (stmt, + params); + } + else + { + stmt = plugin->iterate_tickets; + err = GNUNET_SQ_bind (stmt, + params); + } + if (GNUNET_OK != err) + { + LOG_SQLITE (plugin, + GNUNET_ERROR_TYPE_ERROR | GNUNET_ERROR_TYPE_BULK, + "sqlite3_bind_XXXX"); + GNUNET_SQ_reset (plugin->dbh, + stmt); + return GNUNET_SYSERR; + } + return get_ticket_and_call_iterator (plugin, + stmt, + iter, + iter_cls); +} + + +/** + * Entry point for the plugin. + * + * @param cls the "struct GNUNET_IDENTITY_PROVIDER_PluginEnvironment*" + * @return NULL on error, otherwise the plugin context + */ +void * +libgnunet_plugin_identity_provider_sqlite_init (void *cls) +{ + static struct Plugin plugin; + const struct GNUNET_CONFIGURATION_Handle *cfg = cls; + struct GNUNET_IDENTITY_PROVIDER_PluginFunctions *api; + + if (NULL != plugin.cfg) + return NULL; /* can only initialize once! */ + memset (&plugin, 0, sizeof (struct Plugin)); + plugin.cfg = cfg; + if (GNUNET_OK != database_setup (&plugin)) + { + database_shutdown (&plugin); + return NULL; + } + api = GNUNET_new (struct GNUNET_IDENTITY_PROVIDER_PluginFunctions); + api->cls = &plugin; + api->store_ticket = &identity_provider_sqlite_store_ticket; + api->delete_ticket = &identity_provider_sqlite_delete_ticket; + api->iterate_tickets = &identity_provider_sqlite_iterate_tickets; + LOG (GNUNET_ERROR_TYPE_INFO, + _("Sqlite database running\n")); + return api; +} + + +/** + * Exit point from the plugin. + * + * @param cls the plugin context (as returned by "init") + * @return always NULL + */ +void * +libgnunet_plugin_namestore_sqlite_done (void *cls) +{ + struct GNUNET_IDENTITY_PROVIDER_PluginFunctions *api = cls; + struct Plugin *plugin = api->cls; + + database_shutdown (plugin); + plugin->cfg = NULL; + GNUNET_free (api); + LOG (GNUNET_ERROR_TYPE_DEBUG, + "sqlite plugin is finished\n"); + return NULL; +} + +/* end of plugin_identity_provider_sqlite.c */ diff --git a/src/include/gnunet_identity_provider_plugin.h b/src/include/gnunet_identity_provider_plugin.h new file mode 100644 index 000000000..5867a5b80 --- /dev/null +++ b/src/include/gnunet_identity_provider_plugin.h @@ -0,0 +1,123 @@ +/* + This file is part of GNUnet + Copyright (C) 2012, 2013 GNUnet e.V. + + GNUnet 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. + + GNUnet 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 GNUnet; see the file COPYING. If not, write to the + Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + Boston, MA 02110-1301, USA. +*/ + +/** + * @author Martin Schanzenbach + * + * @file + * Plugin API for the idp database backend + * + * @defgroup identity-provider-plugin IdP service plugin API + * Plugin API for the idp database backend + * @{ + */ +#ifndef GNUNET_IDENTITY_PROVIDER_PLUGIN_H +#define GNUNET_IDENTITY_PROVIDER_PLUGIN_H + +#include "gnunet_util_lib.h" +#include "gnunet_identity_provider_service.h" + +#ifdef __cplusplus +extern "C" +{ +#if 0 /* keep Emacsens' auto-indent happy */ +} +#endif +#endif + + +/** + * Function called by for each matching ticket. + * + * @param cls closure + * @param ticket the ticket + * @prarm attrs the attributes + */ +typedef void (*GNUNET_IDENTITY_PROVIDER_TicketIterator) (void *cls, + const struct GNUNET_IDENTITY_PROVIDER_Ticket2 *ticket, + const struct GNUNET_IDENTITY_PROVIDER_AttributeList *attrs); + + +/** + * @brief struct returned by the initialization function of the plugin + */ +struct GNUNET_IDENTITY_PROVIDER_PluginFunctions +{ + + /** + * Closure to pass to all plugin functions. + */ + void *cls; + + /** + * Store a ticket in the database. + * + * @param cls closure (internal context for the plugin) + * @param ticket the ticket to store + * @param attrs the attributes shared with the ticket + * @return #GNUNET_OK on success, else #GNUNET_SYSERR + */ + int (*store_ticket) (void *cls, + const struct GNUNET_IDENTITY_PROVIDER_Ticket2 *ticket, + const struct GNUNET_IDENTITY_PROVIDER_AttributeList *attrs); + + /** + * Delete a ticket from the database. + * + * @param cls closure (internal context for the plugin) + * @param ticket the ticket to store + * @return #GNUNET_OK on success, else #GNUNET_SYSERR + */ + int (*delete_ticket) (void *cls, + const struct GNUNET_IDENTITY_PROVIDER_Ticket2 *ticket); + + + + /** + * Iterate over all tickets + * + * @param cls closure (internal context for the plugin) + * @param identity the identity + * @param audience GNUNET_YES if the identity is the audience of the ticket + * else it is considered the issuer + * @param iter function to call with the result + * @param iter_cls closure for @a iter + * @return #GNUNET_OK on success, #GNUNET_NO if there were no results, #GNUNET_SYSERR on error + */ + int (*iterate_tickets) (void *cls, + const struct GNUNET_CRYPTO_EcdsaPublicKey *identity, + int audience, + uint64_t offset, + GNUNET_IDENTITY_PROVIDER_TicketIterator iter, void *iter_cls); + + +}; + + +#if 0 /* keep Emacsens' auto-indent happy */ +{ +#endif +#ifdef __cplusplus +} +#endif + +#endif + +/** @} */ /* end of group */ diff --git a/src/include/gnunet_identity_provider_service.h b/src/include/gnunet_identity_provider_service.h index 049f891cc..02cd15959 100644 --- a/src/include/gnunet_identity_provider_service.h +++ b/src/include/gnunet_identity_provider_service.h @@ -336,17 +336,25 @@ GNUNET_IDENTITY_PROVIDER_get_attributes_stop (struct GNUNET_IDENTITY_PROVIDER_At * token * * @param cls closure - * @param grant the label in GNS pointing to the token * @param ticket the ticket - * @param token the issued token - * @param name name assigned by the user for this ego, - * NULL if the user just deleted the ego and it - * must thus no longer be used */ typedef void (*GNUNET_IDENTITY_PROVIDER_TicketCallback)(void *cls, const struct GNUNET_IDENTITY_PROVIDER_Ticket2 *ticket); +/** + * Method called when issued tickets are retrieved. Also returns the attributes + * that were issued at the time. + * + * @param cls closure + * @param ticket the ticket + * @param attrs the attributes as list + */ +typedef void +(*GNUNET_IDENTITY_PROVIDER_TicketResult)(void *cls, + const struct GNUNET_IDENTITY_PROVIDER_Ticket2 *ticket, + const struct GNUNET_IDENTITY_PROVIDER_AttributeList *attrs); + /** * Issues a ticket to another identity. The identity may use @@ -389,7 +397,7 @@ GNUNET_IDENTITY_PROVIDER_idp_ticket_revoke (struct GNUNET_IDENTITY_PROVIDER_Hand -/** TODO +/** * Consumes an issued ticket. The ticket is persisted * and used to retrieve identity information from the issuer * diff --git a/src/include/gnunet_protocols.h b/src/include/gnunet_protocols.h index 63afeba8d..743a28946 100644 --- a/src/include/gnunet_protocols.h +++ b/src/include/gnunet_protocols.h @@ -2646,6 +2646,12 @@ extern "C" #define GNUNET_MESSAGE_TYPE_IDENTITY_PROVIDER_CONSUME_TICKET 973 +#define GNUNET_MESSAGE_TYPE_IDENTITY_PROVIDER_TICKET_ITERATION_START 974 + +#define GNUNET_MESSAGE_TYPE_IDENTITY_PROVIDER_TICKET_ITERATION_STOP 975 + +#define GNUNET_MESSAGE_TYPE_IDENTITY_PROVIDER_TICKET_ITERATION_NEXT 976 + /************************************************** * * CREDENTIAL MESSAGE TYPES