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