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