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