namestore: postgres: fix iterating over zone
[oweals/gnunet.git] / src / namestore / plugin_namestore_postgres.c
1  /*
2   * This file is part of GNUnet
3   * Copyright (C) 2009-2013, 2016, 2017 GNUnet e.V.
4   *
5   * GNUnet is free software; you can redistribute it and/or modify
6   * it under the terms of the GNU General Public License as published
7   * by the Free Software Foundation; either version 3, or (at your
8   * option) any later version.
9   *
10   * GNUnet is distributed in the hope that it will be useful, but
11   * WITHOUT ANY WARRANTY; without even the implied warranty of
12   * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
13   * General Public License for more details.
14   *
15   * You should have received a copy of the GNU General Public License
16   * along with GNUnet; see the file COPYING.  If not, write to the
17   * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
18   * Boston, MA 02110-1301, USA.
19   */
20
21 /**
22  * @file namestore/plugin_namestore_postgres.c
23  * @brief postgres-based namestore backend
24  * @author Christian Grothoff
25  */
26 #include "platform.h"
27 #include "gnunet_namestore_plugin.h"
28 #include "gnunet_namestore_service.h"
29 #include "gnunet_gnsrecord_lib.h"
30 #include "gnunet_pq_lib.h"
31 #include "namestore.h"
32
33
34 #define LOG(kind,...) GNUNET_log_from (kind, "namestore-postgres", __VA_ARGS__)
35
36
37 /**
38  * Context for all functions in this plugin.
39  */
40 struct Plugin
41 {
42
43   /**
44    * Our configuration.
45    */
46   const struct GNUNET_CONFIGURATION_Handle *cfg;
47
48   /**
49    * Native Postgres database handle.
50    */
51   PGconn *dbh;
52
53 };
54
55
56 /**
57  * Initialize the database connections and associated
58  * data structures (create tables and indices
59  * as needed as well).
60  *
61  * @param plugin the plugin context (state for this module)
62  * @return #GNUNET_OK on success
63  */
64 static int
65 database_setup (struct Plugin *plugin)
66 {
67   struct GNUNET_PQ_ExecuteStatement es_temporary =
68     GNUNET_PQ_make_execute ("CREATE TEMPORARY TABLE IF NOT EXISTS ns097records ("
69                             " zone_private_key BYTEA NOT NULL DEFAULT '',"
70                             " pkey BYTEA DEFAULT '',"
71                             " rvalue BYTEA NOT NULL DEFAULT '',"
72                             " record_count INTEGER NOT NULL DEFAULT 0,"
73                             " record_data BYTEA NOT NULL DEFAULT '',"
74                             " label TEXT NOT NULL DEFAULT ''"
75                             ")"
76                             "WITH OIDS");
77   struct GNUNET_PQ_ExecuteStatement es_default =
78     GNUNET_PQ_make_execute ("CREATE TABLE IF NOT EXISTS ns097records ("
79                             " zone_private_key BYTEA NOT NULL DEFAULT '',"
80                             " pkey BYTEA DEFAULT '',"
81                             " rvalue BYTEA NOT NULL DEFAULT '',"
82                             " record_count INTEGER NOT NULL DEFAULT 0,"
83                             " record_data BYTEA NOT NULL DEFAULT '',"
84                             " label TEXT NOT NULL DEFAULT ''"
85                             ")"
86                             "WITH OIDS");
87   const struct GNUNET_PQ_ExecuteStatement *cr;
88
89   plugin->dbh = GNUNET_PQ_connect_with_cfg (plugin->cfg,
90                                             "namestore-postgres");
91   if (NULL == plugin->dbh)
92     return GNUNET_SYSERR;
93   if (GNUNET_YES ==
94       GNUNET_CONFIGURATION_get_value_yesno (plugin->cfg,
95                                             "namestore-postgres",
96                                             "TEMPORARY_TABLE"))
97   {
98     cr = &es_temporary;
99   }
100   else
101   {
102     cr = &es_default;
103   }
104
105   {
106     struct GNUNET_PQ_ExecuteStatement es[] = {
107       *cr,
108       GNUNET_PQ_make_try_execute ("CREATE INDEX IF NOT EXISTS ir_pkey_reverse "
109                                   "ON ns097records (zone_private_key,pkey)"),
110       GNUNET_PQ_make_try_execute ("CREATE INDEX IF NOT EXISTS ir_pkey_iter "
111                                   "ON ns097records (zone_private_key,rvalue)"),
112       GNUNET_PQ_make_try_execute ("CREATE INDEX IF NOT EXISTS it_iter "
113                                   "ON ns097records (rvalue)"),
114       GNUNET_PQ_make_try_execute ("CREATE INDEX IF NOT EXISTS ir_label "
115                                   "ON ns097records (label)"),
116       GNUNET_PQ_EXECUTE_STATEMENT_END
117     };
118
119     if (GNUNET_OK !=
120         GNUNET_PQ_exec_statements (plugin->dbh,
121                                    es))
122     {
123       PQfinish (plugin->dbh);
124       plugin->dbh = NULL;
125       return GNUNET_SYSERR;
126     }
127   }
128
129   {
130     struct GNUNET_PQ_PreparedStatement ps[] = {
131       GNUNET_PQ_make_prepare ("store_records",
132                               "INSERT INTO ns097records (zone_private_key, pkey, rvalue, record_count, record_data, label) VALUES "
133                               "($1, $2, $3, $4, $5, $6)", 6),
134       GNUNET_PQ_make_prepare ("delete_records",
135                               "DELETE FROM ns097records "
136                               "WHERE zone_private_key=$1 AND label=$2", 2),
137       GNUNET_PQ_make_prepare ("zone_to_name",
138                               "SELECT record_count,record_data,label FROM ns097records"
139                               " WHERE zone_private_key=$1 AND pkey=$2", 2),
140       GNUNET_PQ_make_prepare ("iterate_zone",
141                               "SELECT record_count,record_data,label FROM ns097records "
142                               "WHERE zone_private_key=$1 ORDER BY rvalue LIMIT 1 OFFSET $2", 2),
143       GNUNET_PQ_make_prepare ("iterate_all_zones",
144                               "SELECT record_count,record_data,label,zone_private_key"
145                               " FROM ns097records ORDER BY rvalue LIMIT 1 OFFSET $1", 1),
146       GNUNET_PQ_make_prepare ("lookup_label",
147                               "SELECT record_count,record_data,label "
148                               "FROM ns097records WHERE zone_private_key=$1 AND label=$2", 2),
149       GNUNET_PQ_PREPARED_STATEMENT_END
150     };
151
152     if (GNUNET_OK !=
153         GNUNET_PQ_prepare_statements (plugin->dbh,
154                                       ps))
155     {
156       PQfinish (plugin->dbh);
157       plugin->dbh = NULL;
158       return GNUNET_SYSERR;
159     }
160   }
161
162   return GNUNET_OK;
163 }
164
165
166 /**
167  * Store a record in the datastore.  Removes any existing record in the
168  * same zone with the same name.
169  *
170  * @param cls closure (internal context for the plugin)
171  * @param zone_key private key of the zone
172  * @param label name that is being mapped (at most 255 characters long)
173  * @param rd_count number of entries in @a rd array
174  * @param rd array of records with data to store
175  * @return #GNUNET_OK on success, else #GNUNET_SYSERR
176  */
177 static int
178 namestore_postgres_store_records (void *cls,
179                                   const struct GNUNET_CRYPTO_EcdsaPrivateKey *zone_key,
180                                   const char *label,
181                                   unsigned int rd_count,
182                                   const struct GNUNET_GNSRECORD_Data *rd)
183 {
184   struct Plugin *plugin = cls;
185   struct GNUNET_CRYPTO_EcdsaPublicKey pkey;
186   uint64_t rvalue;
187   uint32_t rd_count32 = (uint32_t) rd_count;
188   size_t data_size;
189
190   memset (&pkey, 0, sizeof (pkey));
191   for (unsigned int i=0;i<rd_count;i++)
192     if (GNUNET_GNSRECORD_TYPE_PKEY == rd[i].record_type)
193     {
194       GNUNET_break (sizeof (struct GNUNET_CRYPTO_EcdsaPublicKey) == rd[i].data_size);
195       GNUNET_memcpy (&pkey,
196                      rd[i].data,
197                      rd[i].data_size);
198       break;
199     }
200   rvalue = GNUNET_CRYPTO_random_u64 (GNUNET_CRYPTO_QUALITY_WEAK,
201                                      UINT64_MAX);
202   data_size = GNUNET_GNSRECORD_records_get_size (rd_count, rd);
203   if (data_size > 64 * 65536)
204   {
205     GNUNET_break (0);
206     return GNUNET_SYSERR;
207   }
208   {
209     char data[data_size];
210     struct GNUNET_PQ_QueryParam params[] = {
211       GNUNET_PQ_query_param_auto_from_type (zone_key),
212       GNUNET_PQ_query_param_auto_from_type (&pkey),
213       GNUNET_PQ_query_param_uint64 (&rvalue),
214       GNUNET_PQ_query_param_uint32 (&rd_count32),
215       GNUNET_PQ_query_param_fixed_size (data, data_size),
216       GNUNET_PQ_query_param_string (label),
217       GNUNET_PQ_query_param_end
218     };
219     enum GNUNET_DB_QueryStatus res;
220
221     if (data_size !=
222         GNUNET_GNSRECORD_records_serialize (rd_count, rd,
223                                             data_size, data))
224     {
225       GNUNET_break (0);
226       return GNUNET_SYSERR;
227     }
228
229     res = GNUNET_PQ_eval_prepared_non_select (plugin->dbh,
230                                               "store_records",
231                                               params);
232     if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS != res)
233       return GNUNET_SYSERR;
234   }
235   return GNUNET_OK;
236 }
237
238
239 /**
240  * Closure for #parse_result_call_iterator.
241  */
242 struct ParserContext
243 {
244   /**
245    * Function to call for each result.
246    */
247   GNUNET_NAMESTORE_RecordIterator iter;
248
249   /**
250    * Closure for @e iter.
251    */
252   void *iter_cls;
253
254   /**
255    * Zone key, NULL if part of record.
256    */
257   const struct GNUNET_CRYPTO_EcdsaPrivateKey *zone_key;
258 };
259
260
261 /**
262  * A statement has been run.  We should evaluate the result, and if possible
263  * call the @a iter in @a cls with the result.
264  *
265  * @param cls closure of type `struct ParserContext *`
266  * @param result the postgres result
267  * @param num_result the number of results in @a result
268  */
269 static void
270 parse_result_call_iterator (void *cls,
271                             PGresult *res,
272                             unsigned int num_results)
273 {
274   struct ParserContext *pc = cls;
275
276   for (unsigned int i=0;i<num_results;i++)
277   {
278     void *data;
279     size_t data_size;
280     uint32_t record_count;
281     char *label;
282     struct GNUNET_CRYPTO_EcdsaPrivateKey zk;
283     struct GNUNET_PQ_ResultSpec rs_with_zone[] = {
284       GNUNET_PQ_result_spec_uint32 ("record_count", &record_count),
285       GNUNET_PQ_result_spec_variable_size ("record_data", &data, &data_size),
286       GNUNET_PQ_result_spec_string ("label", &label),
287       GNUNET_PQ_result_spec_auto_from_type ("zone_private_key", &zk),
288       GNUNET_PQ_result_spec_end
289     };
290     struct GNUNET_PQ_ResultSpec rs_without_zone[] = {
291       GNUNET_PQ_result_spec_uint32 ("record_count", &record_count),
292       GNUNET_PQ_result_spec_variable_size ("record_data", &data, &data_size),
293       GNUNET_PQ_result_spec_string ("label", &label),
294       GNUNET_PQ_result_spec_end
295     };
296     struct GNUNET_PQ_ResultSpec *rs;
297
298     rs = (NULL == pc->zone_key) ? rs_with_zone : rs_without_zone;
299     if (GNUNET_YES !=
300         GNUNET_PQ_extract_result (res,
301                                   rs,
302                                   i))
303     {
304       GNUNET_break (0);
305       return;
306     }
307
308     if (record_count > 64 * 1024)
309     {
310       /* sanity check, don't stack allocate far too much just
311          because database might contain a large value here */
312       GNUNET_break (0);
313       GNUNET_PQ_cleanup_result (rs);
314       return;
315     }
316
317     {
318       struct GNUNET_GNSRECORD_Data rd[record_count];
319
320       if (GNUNET_OK !=
321           GNUNET_GNSRECORD_records_deserialize (data_size,
322                                                 data,
323                                                 record_count,
324                                                 rd))
325       {
326         GNUNET_break (0);
327         GNUNET_PQ_cleanup_result (rs);
328         return;
329       }
330       pc->iter (pc->iter_cls,
331                 (NULL == pc->zone_key) ? &zk : pc->zone_key,
332                 label,
333                 record_count,
334                 rd);
335     }
336     GNUNET_PQ_cleanup_result (rs);
337   }
338 }
339
340
341 /**
342  * Lookup records in the datastore for which we are the authority.
343  *
344  * @param cls closure (internal context for the plugin)
345  * @param zone private key of the zone
346  * @param label name of the record in the zone
347  * @param iter function to call with the result
348  * @param iter_cls closure for @a iter
349  * @return #GNUNET_OK on success, else #GNUNET_SYSERR
350  */
351 static int
352 namestore_postgres_lookup_records (void *cls,
353                                    const struct GNUNET_CRYPTO_EcdsaPrivateKey *zone,
354                                    const char *label,
355                                    GNUNET_NAMESTORE_RecordIterator iter,
356                                    void *iter_cls)
357 {
358   struct Plugin *plugin = cls;
359   struct GNUNET_PQ_QueryParam params[] = {
360     GNUNET_PQ_query_param_auto_from_type (zone),
361     GNUNET_PQ_query_param_string (label),
362     GNUNET_PQ_query_param_end
363   };
364   struct ParserContext pc;
365   enum GNUNET_DB_QueryStatus res;
366
367   pc.iter = iter;
368   pc.iter_cls = iter_cls;
369   pc.zone_key = NULL;
370   res = GNUNET_PQ_eval_prepared_multi_select (plugin->dbh,
371                                               "lookup_label",
372                                               params,
373                                               &parse_result_call_iterator,
374                                               &pc);
375   if (res < 0)
376     return GNUNET_SYSERR;
377   return GNUNET_OK;
378 }
379
380
381 /**
382  * Iterate over the results for a particular key and zone in the
383  * datastore.  Will return at most one result to the iterator.
384  *
385  * @param cls closure (internal context for the plugin)
386  * @param zone hash of public key of the zone, NULL to iterate over all zones
387  * @param offset offset in the list of all matching records
388  * @param iter function to call with the result
389  * @param iter_cls closure for @a iter
390  * @return #GNUNET_OK on success, #GNUNET_NO if there were no results, #GNUNET_SYSERR on error
391  */
392 static int
393 namestore_postgres_iterate_records (void *cls,
394                                     const struct GNUNET_CRYPTO_EcdsaPrivateKey *zone,
395                                     uint64_t offset,
396                                     GNUNET_NAMESTORE_RecordIterator iter,
397                                     void *iter_cls)
398 {
399   struct Plugin *plugin = cls;
400   enum GNUNET_DB_QueryStatus res;
401   struct ParserContext pc;
402
403   pc.iter = iter;
404   pc.iter_cls = iter_cls;
405   pc.zone_key = zone;
406   if (NULL == zone)
407   {
408     struct GNUNET_PQ_QueryParam params_without_zone[] = {
409       GNUNET_PQ_query_param_uint64 (&offset),
410       GNUNET_PQ_query_param_end
411     };
412
413     res = GNUNET_PQ_eval_prepared_multi_select (plugin->dbh,
414                                                 "iterate_all_zones",
415                                                 params_without_zone,
416                                                 &parse_result_call_iterator,
417                                                 &pc);
418   }
419   else
420   {
421     struct GNUNET_PQ_QueryParam params_with_zone[] = {
422       GNUNET_PQ_query_param_auto_from_type (zone),
423       GNUNET_PQ_query_param_uint64 (&offset),
424       GNUNET_PQ_query_param_end
425     };
426
427     res = GNUNET_PQ_eval_prepared_multi_select (plugin->dbh,
428                                                 "iterate_zone",
429                                                 params_with_zone,
430                                                 &parse_result_call_iterator,
431                                                 &pc);
432   }
433   if (res < 0)
434     return GNUNET_SYSERR;
435
436   if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == res)
437     return GNUNET_NO;
438
439   return GNUNET_OK;
440 }
441
442
443 /**
444  * Look for an existing PKEY delegation record for a given public key.
445  * Returns at most one result to the iterator.
446  *
447  * @param cls closure (internal context for the plugin)
448  * @param zone private key of the zone to look up in, never NULL
449  * @param value_zone public key of the target zone (value), never NULL
450  * @param iter function to call with the result
451  * @param iter_cls closure for @a iter
452  * @return #GNUNET_OK on success, #GNUNET_NO if there were no results, #GNUNET_SYSERR on error
453  */
454 static int
455 namestore_postgres_zone_to_name (void *cls,
456                                  const struct GNUNET_CRYPTO_EcdsaPrivateKey *zone,
457                                  const struct GNUNET_CRYPTO_EcdsaPublicKey *value_zone,
458                                  GNUNET_NAMESTORE_RecordIterator iter, void *iter_cls)
459 {
460   struct Plugin *plugin = cls;
461   struct GNUNET_PQ_QueryParam params[] = {
462     GNUNET_PQ_query_param_auto_from_type (zone),
463     GNUNET_PQ_query_param_auto_from_type (value_zone),
464     GNUNET_PQ_query_param_end
465   };
466   enum GNUNET_DB_QueryStatus res;
467   struct ParserContext pc;
468
469   pc.iter = iter;
470   pc.iter_cls = iter_cls;
471   pc.zone_key = zone;
472
473   res = GNUNET_PQ_eval_prepared_multi_select (plugin->dbh,
474                                               "zone_to_name",
475                                               params,
476                                               &parse_result_call_iterator,
477                                               &pc);
478   if (res < 0)
479     return GNUNET_SYSERR;
480   return GNUNET_OK;
481 }
482
483
484 /**
485  * Shutdown database connection and associate data
486  * structures.
487  *
488  * @param plugin the plugin context (state for this module)
489  */
490 static void
491 database_shutdown (struct Plugin *plugin)
492 {
493   PQfinish (plugin->dbh);
494   plugin->dbh = NULL;
495 }
496
497
498 /**
499  * Entry point for the plugin.
500  *
501  * @param cls the `struct GNUNET_NAMESTORE_PluginEnvironment*`
502  * @return NULL on error, othrewise the plugin context
503  */
504 void *
505 libgnunet_plugin_namestore_postgres_init (void *cls)
506 {
507   static struct Plugin plugin;
508   const struct GNUNET_CONFIGURATION_Handle *cfg = cls;
509   struct GNUNET_NAMESTORE_PluginFunctions *api;
510
511   if (NULL != plugin.cfg)
512     return NULL;                /* can only initialize once! */
513   memset (&plugin, 0, sizeof (struct Plugin));
514   plugin.cfg = cfg;
515   if (GNUNET_OK != database_setup (&plugin))
516   {
517     database_shutdown (&plugin);
518     return NULL;
519   }
520   api = GNUNET_new (struct GNUNET_NAMESTORE_PluginFunctions);
521   api->cls = &plugin;
522   api->store_records = &namestore_postgres_store_records;
523   api->iterate_records = &namestore_postgres_iterate_records;
524   api->zone_to_name = &namestore_postgres_zone_to_name;
525   api->lookup_records = &namestore_postgres_lookup_records;
526   LOG (GNUNET_ERROR_TYPE_INFO,
527        "Postgres namestore plugin running\n");
528   return api;
529 }
530
531
532 /**
533  * Exit point from the plugin.
534  *
535  * @param cls the plugin context (as returned by "init")
536  * @return always NULL
537  */
538 void *
539 libgnunet_plugin_namestore_postgres_done (void *cls)
540 {
541   struct GNUNET_NAMESTORE_PluginFunctions *api = cls;
542   struct Plugin *plugin = api->cls;
543
544   database_shutdown (plugin);
545   plugin->cfg = NULL;
546   GNUNET_free (api);
547   LOG (GNUNET_ERROR_TYPE_DEBUG,
548        "Postgres namestore plugin is finished\n");
549   return NULL;
550 }
551
552 /* end of plugin_namestore_postgres.c */