NAMESTORE/JSON: fix parsing exp and flags
[oweals/gnunet.git] / src / namestore / plugin_namestore_postgres.c
1  /*
2   * This file is part of GNUnet
3   * Copyright (C) 2009-2013, 2016-2018 GNUnet e.V.
4   *
5   * GNUnet is free software: you can redistribute it and/or modify it
6   * under the terms of the GNU Affero General Public License as published
7   * by the Free Software Foundation, either version 3 of the License,
8   * or (at your 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   * Affero General Public License for more details.
14   *
15   * You should have received a copy of the GNU Affero General Public License
16   * along with this program.  If not, see <http://www.gnu.org/licenses/>.
17
18      SPDX-License-Identifier: AGPL3.0-or-later
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 ns098records ("
69                             " seq BIGSERIAL PRIMARY KEY,"
70                             " zone_private_key BYTEA NOT NULL DEFAULT '',"
71                             " pkey BYTEA DEFAULT '',"
72                             " rvalue BYTEA NOT NULL DEFAULT '',"
73                             " record_count INTEGER NOT NULL DEFAULT 0,"
74                             " record_data BYTEA NOT NULL DEFAULT '',"
75                             " label TEXT NOT NULL DEFAULT '',"
76                             " CONSTRAINT zl UNIQUE (zone_private_key,label)"
77                             ")"
78                             "WITH OIDS");
79   struct GNUNET_PQ_ExecuteStatement es_default =
80     GNUNET_PQ_make_execute ("CREATE TABLE IF NOT EXISTS ns098records ("
81                             " seq BIGSERIAL PRIMARY KEY,"
82                             " zone_private_key BYTEA NOT NULL DEFAULT '',"
83                             " pkey BYTEA DEFAULT '',"
84                             " rvalue BYTEA NOT NULL DEFAULT '',"
85                             " record_count INTEGER NOT NULL DEFAULT 0,"
86                             " record_data BYTEA NOT NULL DEFAULT '',"
87                             " label TEXT NOT NULL DEFAULT '',"
88                             " CONSTRAINT zl UNIQUE (zone_private_key,label)"
89                             ")"
90                             "WITH OIDS");
91   const struct GNUNET_PQ_ExecuteStatement *cr;
92
93   plugin->dbh = GNUNET_PQ_connect_with_cfg (plugin->cfg,
94                                             "namestore-postgres");
95   if (NULL == plugin->dbh)
96     return GNUNET_SYSERR;
97   if (GNUNET_YES ==
98       GNUNET_CONFIGURATION_get_value_yesno (plugin->cfg,
99                                             "namestore-postgres",
100                                             "ASYNC_COMMIT"))
101   {
102     struct GNUNET_PQ_ExecuteStatement es[] = {
103       GNUNET_PQ_make_try_execute ("SET synchronous_commit TO off"),
104       GNUNET_PQ_EXECUTE_STATEMENT_END
105     };
106
107     if (GNUNET_OK !=
108         GNUNET_PQ_exec_statements (plugin->dbh,
109                                    es))
110     {
111       PQfinish (plugin->dbh);
112       plugin->dbh = NULL;
113       return GNUNET_SYSERR;
114     }
115   }
116   if (GNUNET_YES ==
117       GNUNET_CONFIGURATION_get_value_yesno (plugin->cfg,
118                                             "namestore-postgres",
119                                             "TEMPORARY_TABLE"))
120   {
121     cr = &es_temporary;
122   }
123   else
124   {
125     cr = &es_default;
126   }
127
128   {
129     struct GNUNET_PQ_ExecuteStatement es[] = {
130       *cr,
131       GNUNET_PQ_make_try_execute ("CREATE INDEX IF NOT EXISTS ir_pkey_reverse "
132                                   "ON ns098records (zone_private_key,pkey)"),
133       GNUNET_PQ_make_try_execute ("CREATE INDEX IF NOT EXISTS ir_pkey_iter "
134                                   "ON ns098records (zone_private_key,seq)"),
135       GNUNET_PQ_make_try_execute ("CREATE INDEX IF NOT EXISTS ir_label "
136                                   "ON ns098records (label)"),
137       GNUNET_PQ_make_try_execute ("CREATE INDEX IF NOT EXISTS zone_label "
138                                   "ON ns098records (zone_private_key,label)"),
139       GNUNET_PQ_EXECUTE_STATEMENT_END
140     };
141
142     if (GNUNET_OK !=
143         GNUNET_PQ_exec_statements (plugin->dbh,
144                                    es))
145     {
146       PQfinish (plugin->dbh);
147       plugin->dbh = NULL;
148       return GNUNET_SYSERR;
149     }
150   }
151
152   {
153     struct GNUNET_PQ_PreparedStatement ps[] = {
154       GNUNET_PQ_make_prepare ("store_records",
155                               "INSERT INTO ns098records"
156                               " (zone_private_key, pkey, rvalue, record_count, record_data, label)"
157                               " VALUES ($1, $2, $3, $4, $5, $6)"
158                               " ON CONFLICT ON CONSTRAINT zl"
159                               " DO UPDATE"
160                               "    SET pkey=$2,rvalue=$3,record_count=$4,record_data=$5"
161                               "    WHERE ns098records.zone_private_key = $1"
162                               "          AND ns098records.label = $6",
163                               6),
164       GNUNET_PQ_make_prepare ("delete_records",
165                               "DELETE FROM ns098records "
166                               "WHERE zone_private_key=$1 AND label=$2",
167                               2),
168       GNUNET_PQ_make_prepare ("zone_to_name",
169                               "SELECT seq,record_count,record_data,label FROM ns098records"
170                               " WHERE zone_private_key=$1 AND pkey=$2",
171                               2),
172       GNUNET_PQ_make_prepare ("iterate_zone",
173                               "SELECT seq,record_count,record_data,label FROM ns098records "
174                               "WHERE zone_private_key=$1 AND seq > $2 ORDER BY seq ASC LIMIT $3",
175                               3),
176       GNUNET_PQ_make_prepare ("iterate_all_zones",
177                               "SELECT seq,record_count,record_data,label,zone_private_key"
178                               " FROM ns098records WHERE seq > $1 ORDER BY seq ASC LIMIT $2",
179                               2),
180       GNUNET_PQ_make_prepare ("lookup_label",
181                               "SELECT seq,record_count,record_data,label "
182                               "FROM ns098records WHERE zone_private_key=$1 AND label=$2",
183                               2),
184       GNUNET_PQ_PREPARED_STATEMENT_END
185     };
186
187     if (GNUNET_OK !=
188         GNUNET_PQ_prepare_statements (plugin->dbh,
189                                       ps))
190     {
191       PQfinish (plugin->dbh);
192       plugin->dbh = NULL;
193       return GNUNET_SYSERR;
194     }
195   }
196
197   return GNUNET_OK;
198 }
199
200
201 /**
202  * Store a record in the datastore.  Removes any existing record in the
203  * same zone with the same name.
204  *
205  * @param cls closure (internal context for the plugin)
206  * @param zone_key private key of the zone
207  * @param label name that is being mapped (at most 255 characters long)
208  * @param rd_count number of entries in @a rd array
209  * @param rd array of records with data to store
210  * @return #GNUNET_OK on success, else #GNUNET_SYSERR
211  */
212 static int
213 namestore_postgres_store_records (void *cls,
214                                   const struct GNUNET_CRYPTO_EcdsaPrivateKey *zone_key,
215                                   const char *label,
216                                   unsigned int rd_count,
217                                   const struct GNUNET_GNSRECORD_Data *rd)
218 {
219   struct Plugin *plugin = cls;
220   struct GNUNET_CRYPTO_EcdsaPublicKey pkey;
221   uint64_t rvalue;
222   uint32_t rd_count32 = (uint32_t) rd_count;
223   ssize_t data_size;
224
225   memset (&pkey,
226           0,
227           sizeof (pkey));
228   for (unsigned int i=0;i<rd_count;i++)
229     if (GNUNET_GNSRECORD_TYPE_PKEY == rd[i].record_type)
230     {
231       GNUNET_break (sizeof (struct GNUNET_CRYPTO_EcdsaPublicKey) == rd[i].data_size);
232       GNUNET_memcpy (&pkey,
233                      rd[i].data,
234                      rd[i].data_size);
235       break;
236     }
237   rvalue = GNUNET_CRYPTO_random_u64 (GNUNET_CRYPTO_QUALITY_WEAK,
238                                      UINT64_MAX);
239   data_size = GNUNET_GNSRECORD_records_get_size (rd_count,
240                                                  rd);
241   if (data_size < 0)
242   {
243     GNUNET_break (0);
244     return GNUNET_SYSERR;
245   }
246   if (data_size >= UINT16_MAX)
247   {
248     GNUNET_break (0);
249     return GNUNET_SYSERR;
250   }
251   /* if record set is empty, delete existing records */
252   if (0 == rd_count)
253   {
254     struct GNUNET_PQ_QueryParam params[] = {
255       GNUNET_PQ_query_param_auto_from_type (zone_key),
256       GNUNET_PQ_query_param_string (label),
257       GNUNET_PQ_query_param_end
258     };
259     enum GNUNET_DB_QueryStatus res;
260
261     res = GNUNET_PQ_eval_prepared_non_select (plugin->dbh,
262                                               "delete_records",
263                                               params);
264     if ( (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT != res) &&
265          (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS != res) )
266     {
267       GNUNET_break (0);
268       return GNUNET_SYSERR;
269     }
270     GNUNET_log_from (GNUNET_ERROR_TYPE_DEBUG,
271                      "postgres",
272                      "Record deleted\n");
273     return GNUNET_OK;
274   }
275   /* otherwise, UPSERT (i.e. UPDATE if exists, otherwise INSERT) */
276   {
277     char data[data_size];
278     struct GNUNET_PQ_QueryParam params[] = {
279       GNUNET_PQ_query_param_auto_from_type (zone_key),
280       GNUNET_PQ_query_param_auto_from_type (&pkey),
281       GNUNET_PQ_query_param_uint64 (&rvalue),
282       GNUNET_PQ_query_param_uint32 (&rd_count32),
283       GNUNET_PQ_query_param_fixed_size (data, data_size),
284       GNUNET_PQ_query_param_string (label),
285       GNUNET_PQ_query_param_end
286     };
287     enum GNUNET_DB_QueryStatus res;
288     ssize_t ret;
289
290     ret = GNUNET_GNSRECORD_records_serialize (rd_count,
291                                               rd,
292                                               data_size,
293                                               data);
294     if ( (ret < 0) ||
295          (data_size != ret) )
296     {
297       GNUNET_break (0);
298       return GNUNET_SYSERR;
299     }
300
301     res = GNUNET_PQ_eval_prepared_non_select (plugin->dbh,
302                                               "store_records",
303                                               params);
304     if (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT != res)
305       return GNUNET_SYSERR;
306   }
307   return GNUNET_OK;
308 }
309
310
311 /**
312  * Closure for #parse_result_call_iterator.
313  */
314 struct ParserContext
315 {
316   /**
317    * Function to call for each result.
318    */
319   GNUNET_NAMESTORE_RecordIterator iter;
320
321   /**
322    * Closure for @e iter.
323    */
324   void *iter_cls;
325
326   /**
327    * Zone key, NULL if part of record.
328    */
329   const struct GNUNET_CRYPTO_EcdsaPrivateKey *zone_key;
330
331   /**
332    * Number of results still to return (counted down by
333    * number of results given to iterator).
334    */
335   uint64_t limit;
336 };
337
338
339 /**
340  * A statement has been run.  We should evaluate the result, and if possible
341  * call the @a iter in @a cls with the result.
342  *
343  * @param cls closure of type `struct ParserContext *`
344  * @param result the postgres result
345  * @param num_result the number of results in @a result
346  */
347 static void
348 parse_result_call_iterator (void *cls,
349                             PGresult *res,
350                             unsigned int num_results)
351 {
352   struct ParserContext *pc = cls;
353
354   if (NULL == pc->iter)
355     return; /* no need to do more work */
356   for (unsigned int i=0;i<num_results;i++)
357   {
358     uint64_t serial;
359     void *data;
360     size_t data_size;
361     uint32_t record_count;
362     char *label;
363     struct GNUNET_CRYPTO_EcdsaPrivateKey zk;
364     struct GNUNET_PQ_ResultSpec rs_with_zone[] = {
365       GNUNET_PQ_result_spec_uint64 ("seq", &serial),
366       GNUNET_PQ_result_spec_uint32 ("record_count", &record_count),
367       GNUNET_PQ_result_spec_variable_size ("record_data", &data, &data_size),
368       GNUNET_PQ_result_spec_string ("label", &label),
369       GNUNET_PQ_result_spec_auto_from_type ("zone_private_key", &zk),
370       GNUNET_PQ_result_spec_end
371     };
372     struct GNUNET_PQ_ResultSpec rs_without_zone[] = {
373       GNUNET_PQ_result_spec_uint64 ("seq", &serial),
374       GNUNET_PQ_result_spec_uint32 ("record_count", &record_count),
375       GNUNET_PQ_result_spec_variable_size ("record_data", &data, &data_size),
376       GNUNET_PQ_result_spec_string ("label", &label),
377       GNUNET_PQ_result_spec_end
378     };
379     struct GNUNET_PQ_ResultSpec *rs;
380
381     rs = (NULL == pc->zone_key) ? rs_with_zone : rs_without_zone;
382     if (GNUNET_YES !=
383         GNUNET_PQ_extract_result (res,
384                                   rs,
385                                   i))
386     {
387       GNUNET_break (0);
388       return;
389     }
390
391     if (record_count > 64 * 1024)
392     {
393       /* sanity check, don't stack allocate far too much just
394          because database might contain a large value here */
395       GNUNET_break (0);
396       GNUNET_PQ_cleanup_result (rs);
397       return;
398     }
399
400     {
401       struct GNUNET_GNSRECORD_Data rd[GNUNET_NZL(record_count)];
402
403       GNUNET_assert (0 != serial);
404       if (GNUNET_OK !=
405           GNUNET_GNSRECORD_records_deserialize (data_size,
406                                                 data,
407                                                 record_count,
408                                                 rd))
409       {
410         GNUNET_break (0);
411         GNUNET_PQ_cleanup_result (rs);
412         return;
413       }
414       pc->iter (pc->iter_cls,
415                 serial,
416                 (NULL == pc->zone_key) ? &zk : pc->zone_key,
417                 label,
418                 record_count,
419                 rd);
420     }
421     GNUNET_PQ_cleanup_result (rs);
422   }
423   pc->limit -= num_results;
424 }
425
426
427 /**
428  * Lookup records in the datastore for which we are the authority.
429  *
430  * @param cls closure (internal context for the plugin)
431  * @param zone private key of the zone
432  * @param label name of the record in the zone
433  * @param iter function to call with the result
434  * @param iter_cls closure for @a iter
435  * @return #GNUNET_OK on success, #GNUNET_NO for no results, else #GNUNET_SYSERR
436  */
437 static int
438 namestore_postgres_lookup_records (void *cls,
439                                    const struct GNUNET_CRYPTO_EcdsaPrivateKey *zone,
440                                    const char *label,
441                                    GNUNET_NAMESTORE_RecordIterator iter,
442                                    void *iter_cls)
443 {
444   struct Plugin *plugin = cls;
445   struct GNUNET_PQ_QueryParam params[] = {
446     GNUNET_PQ_query_param_auto_from_type (zone),
447     GNUNET_PQ_query_param_string (label),
448     GNUNET_PQ_query_param_end
449   };
450   struct ParserContext pc;
451   enum GNUNET_DB_QueryStatus res;
452
453   if (NULL == zone)
454   {
455     GNUNET_break (0);
456     return GNUNET_SYSERR;
457   }
458   pc.iter = iter;
459   pc.iter_cls = iter_cls;
460   pc.zone_key = zone;
461   res = GNUNET_PQ_eval_prepared_multi_select (plugin->dbh,
462                                               "lookup_label",
463                                               params,
464                                               &parse_result_call_iterator,
465                                               &pc);
466   if (res < 0)
467     return GNUNET_SYSERR;
468   if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == res)
469     return GNUNET_NO;
470   return GNUNET_OK;
471 }
472
473
474 /**
475  * Iterate over the results for a particular key and zone in the
476  * datastore.  Will return at most one result to the iterator.
477  *
478  * @param cls closure (internal context for the plugin)
479  * @param zone hash of public key of the zone, NULL to iterate over all zones
480  * @param serial serial number to exclude in the list of all matching records
481  * @param limit maximum number of results to fetch
482  * @param iter function to call with the result
483  * @param iter_cls closure for @a iter
484  * @return #GNUNET_OK on success, #GNUNET_NO if there were no more results, #GNUNET_SYSERR on error
485  */
486 static int
487 namestore_postgres_iterate_records (void *cls,
488                                     const struct GNUNET_CRYPTO_EcdsaPrivateKey *zone,
489                                     uint64_t serial,
490                                     uint64_t limit,
491                                     GNUNET_NAMESTORE_RecordIterator iter,
492                                     void *iter_cls)
493 {
494   struct Plugin *plugin = cls;
495   enum GNUNET_DB_QueryStatus res;
496   struct ParserContext pc;
497
498   pc.iter = iter;
499   pc.iter_cls = iter_cls;
500   pc.zone_key = zone;
501   pc.limit = limit;
502   if (NULL == zone)
503   {
504     struct GNUNET_PQ_QueryParam params_without_zone[] = {
505       GNUNET_PQ_query_param_uint64 (&serial),
506       GNUNET_PQ_query_param_uint64 (&limit),
507       GNUNET_PQ_query_param_end
508     };
509
510     res = GNUNET_PQ_eval_prepared_multi_select (plugin->dbh,
511                                                 "iterate_all_zones",
512                                                 params_without_zone,
513                                                 &parse_result_call_iterator,
514                                                 &pc);
515   }
516   else
517   {
518     struct GNUNET_PQ_QueryParam params_with_zone[] = {
519       GNUNET_PQ_query_param_auto_from_type (zone),
520       GNUNET_PQ_query_param_uint64 (&serial),
521       GNUNET_PQ_query_param_uint64 (&limit),
522       GNUNET_PQ_query_param_end
523     };
524
525     res = GNUNET_PQ_eval_prepared_multi_select (plugin->dbh,
526                                                 "iterate_zone",
527                                                 params_with_zone,
528                                                 &parse_result_call_iterator,
529                                                 &pc);
530   }
531   if (res < 0)
532     return GNUNET_SYSERR;
533
534   if ( (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == res) ||
535        (pc.limit > 0) )
536     return GNUNET_NO;
537   return GNUNET_OK;
538 }
539
540
541 /**
542  * Look for an existing PKEY delegation record for a given public key.
543  * Returns at most one result to the iterator.
544  *
545  * @param cls closure (internal context for the plugin)
546  * @param zone private key of the zone to look up in, never NULL
547  * @param value_zone public key of the target zone (value), never NULL
548  * @param iter function to call with the result
549  * @param iter_cls closure for @a iter
550  * @return #GNUNET_OK on success, #GNUNET_NO if there were no results, #GNUNET_SYSERR on error
551  */
552 static int
553 namestore_postgres_zone_to_name (void *cls,
554                                  const struct GNUNET_CRYPTO_EcdsaPrivateKey *zone,
555                                  const struct GNUNET_CRYPTO_EcdsaPublicKey *value_zone,
556                                  GNUNET_NAMESTORE_RecordIterator iter, void *iter_cls)
557 {
558   struct Plugin *plugin = cls;
559   struct GNUNET_PQ_QueryParam params[] = {
560     GNUNET_PQ_query_param_auto_from_type (zone),
561     GNUNET_PQ_query_param_auto_from_type (value_zone),
562     GNUNET_PQ_query_param_end
563   };
564   enum GNUNET_DB_QueryStatus res;
565   struct ParserContext pc;
566
567   pc.iter = iter;
568   pc.iter_cls = iter_cls;
569   pc.zone_key = zone;
570   res = GNUNET_PQ_eval_prepared_multi_select (plugin->dbh,
571                                               "zone_to_name",
572                                               params,
573                                               &parse_result_call_iterator,
574                                               &pc);
575   if (res < 0)
576     return GNUNET_SYSERR;
577   return GNUNET_OK;
578 }
579
580
581 /**
582  * Shutdown database connection and associate data
583  * structures.
584  *
585  * @param plugin the plugin context (state for this module)
586  */
587 static void
588 database_shutdown (struct Plugin *plugin)
589 {
590   PQfinish (plugin->dbh);
591   plugin->dbh = NULL;
592 }
593
594
595 /**
596  * Entry point for the plugin.
597  *
598  * @param cls the `struct GNUNET_NAMESTORE_PluginEnvironment*`
599  * @return NULL on error, othrewise the plugin context
600  */
601 void *
602 libgnunet_plugin_namestore_postgres_init (void *cls)
603 {
604   static struct Plugin plugin;
605   const struct GNUNET_CONFIGURATION_Handle *cfg = cls;
606   struct GNUNET_NAMESTORE_PluginFunctions *api;
607
608   if (NULL != plugin.cfg)
609     return NULL;                /* can only initialize once! */
610   memset (&plugin, 0, sizeof (struct Plugin));
611   plugin.cfg = cfg;
612   if (GNUNET_OK != database_setup (&plugin))
613   {
614     database_shutdown (&plugin);
615     return NULL;
616   }
617   api = GNUNET_new (struct GNUNET_NAMESTORE_PluginFunctions);
618   api->cls = &plugin;
619   api->store_records = &namestore_postgres_store_records;
620   api->iterate_records = &namestore_postgres_iterate_records;
621   api->zone_to_name = &namestore_postgres_zone_to_name;
622   api->lookup_records = &namestore_postgres_lookup_records;
623   LOG (GNUNET_ERROR_TYPE_INFO,
624        "Postgres namestore plugin running\n");
625   return api;
626 }
627
628
629 /**
630  * Exit point from the plugin.
631  *
632  * @param cls the plugin context (as returned by "init")
633  * @return always NULL
634  */
635 void *
636 libgnunet_plugin_namestore_postgres_done (void *cls)
637 {
638   struct GNUNET_NAMESTORE_PluginFunctions *api = cls;
639   struct Plugin *plugin = api->cls;
640
641   database_shutdown (plugin);
642   plugin->cfg = NULL;
643   GNUNET_free (api);
644   LOG (GNUNET_ERROR_TYPE_DEBUG,
645        "Postgres namestore plugin is finished\n");
646   return NULL;
647 }
648
649 /* end of plugin_namestore_postgres.c */