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