Merge remote-tracking branch 'origin/master' into credentials
[oweals/gnunet.git] / src / namecache / plugin_namecache_postgres.c
1  /*
2   * This file is part of GNUnet
3   * Copyright (C) 2009-2013, 2016, 2017 GNUnet e.V.
4   *
5   * GNUnet is free software; you can redistribute it and/or modify
6   * it under the terms of the GNU General Public License as published
7   * by the Free Software Foundation; either version 3, or (at your
8   * 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   * General Public License for more details.
14   *
15   * You should have received a copy of the GNU General Public License
16   * along with GNUnet; see the file COPYING.  If not, write to the
17   * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
18   * Boston, MA 02110-1301, USA.
19   */
20
21 /**
22  * @file namecache/plugin_namecache_postgres.c
23  * @brief postgres-based namecache backend
24  * @author Christian Grothoff
25  */
26 #include "platform.h"
27 #include "gnunet_namecache_plugin.h"
28 #include "gnunet_namecache_service.h"
29 #include "gnunet_gnsrecord_lib.h"
30 #include "gnunet_pq_lib.h"
31 #include "namecache.h"
32
33
34 #define LOG(kind,...) GNUNET_log_from (kind, "namecache-postgres", __VA_ARGS__)
35
36
37 /**
38  * Context for all functions in this plugin.
39  */
40 struct Plugin
41 {
42
43   const struct GNUNET_CONFIGURATION_Handle *cfg;
44
45   /**
46    * Native Postgres database handle.
47    */
48   PGconn *dbh;
49
50 };
51
52
53 /**
54  * Initialize the database connections and associated
55  * data structures (create tables and indices
56  * as needed as well).
57  *
58  * @param plugin the plugin context (state for this module)
59  * @return #GNUNET_OK on success
60  */
61 static int
62 database_setup (struct Plugin *plugin)
63 {
64   struct GNUNET_PQ_ExecuteStatement es_temporary =
65     GNUNET_PQ_make_execute ("CREATE TEMPORARY TABLE IF NOT EXISTS ns096blocks ("
66                             " query BYTEA NOT NULL DEFAULT '',"
67                             " block BYTEA NOT NULL DEFAULT '',"
68                             " expiration_time BIGINT NOT NULL DEFAULT 0"
69                             ")"
70                             "WITH OIDS");
71   struct GNUNET_PQ_ExecuteStatement es_default =
72     GNUNET_PQ_make_execute ("CREATE TABLE IF NOT EXISTS ns096blocks ("
73                             " query BYTEA NOT NULL DEFAULT '',"
74                             " block BYTEA NOT NULL DEFAULT '',"
75                             " expiration_time BIGINT NOT NULL DEFAULT 0"
76                             ")"
77                             "WITH OIDS");
78   const struct GNUNET_PQ_ExecuteStatement *cr;
79
80   plugin->dbh = GNUNET_PQ_connect_with_cfg (plugin->cfg,
81                                             "namecache-postgres");
82   if (NULL == plugin->dbh)
83     return GNUNET_SYSERR;
84   if (GNUNET_YES ==
85       GNUNET_CONFIGURATION_get_value_yesno (plugin->cfg,
86                                             "namecache-postgres",
87                                             "TEMPORARY_TABLE"))
88   {
89     cr = &es_temporary;
90   }
91   else
92   {
93     cr = &es_default;
94   }
95
96   {
97     struct GNUNET_PQ_ExecuteStatement es[] = {
98       *cr,
99       GNUNET_PQ_make_try_execute ("CREATE INDEX ir_query_hash ON ns096blocks (query,expiration_time)"),
100       GNUNET_PQ_make_try_execute ("CREATE INDEX ir_block_expiration ON ns096blocks (expiration_time)"),
101       GNUNET_PQ_EXECUTE_STATEMENT_END
102     };
103
104     if (GNUNET_OK !=
105         GNUNET_PQ_exec_statements (plugin->dbh,
106                                    es))
107     {
108       PQfinish (plugin->dbh);
109       plugin->dbh = NULL;
110       return GNUNET_SYSERR;
111     }
112   }
113
114   {
115     struct GNUNET_PQ_PreparedStatement ps[] = {
116       GNUNET_PQ_make_prepare ("cache_block",
117                               "INSERT INTO ns096blocks (query, block, expiration_time) VALUES "
118                               "($1, $2, $3)", 3),
119       GNUNET_PQ_make_prepare ("expire_blocks",
120                               "DELETE FROM ns096blocks WHERE expiration_time<$1", 1),
121       GNUNET_PQ_make_prepare ("delete_block",
122                               "DELETE FROM ns096blocks WHERE query=$1 AND expiration_time<=$2", 2),
123       GNUNET_PQ_make_prepare ("lookup_block",
124                               "SELECT block FROM ns096blocks WHERE query=$1"
125                               " ORDER BY expiration_time DESC LIMIT 1", 1),
126       GNUNET_PQ_PREPARED_STATEMENT_END
127     };
128
129     if (GNUNET_OK !=
130         GNUNET_PQ_prepare_statements (plugin->dbh,
131                                       ps))
132     {
133       PQfinish (plugin->dbh);
134       plugin->dbh = NULL;
135       return GNUNET_SYSERR;
136     }
137   }
138
139   return GNUNET_OK;
140 }
141
142
143 /**
144  * Removes any expired block.
145  *
146  * @param plugin the plugin
147  */
148 static void
149 namecache_postgres_expire_blocks (struct Plugin *plugin)
150 {
151   struct GNUNET_TIME_Absolute now = GNUNET_TIME_absolute_get ();
152   struct GNUNET_PQ_QueryParam params[] = {
153     GNUNET_PQ_query_param_absolute_time (&now),
154     GNUNET_PQ_query_param_end
155   };
156   enum GNUNET_DB_QueryStatus res;
157
158   res = GNUNET_PQ_eval_prepared_non_select (plugin->dbh,
159                                             "expire_blocks",
160                                             params);
161   GNUNET_break (GNUNET_DB_STATUS_HARD_ERROR != res);
162 }
163
164
165 /**
166  * Delete older block in the datastore.
167  *
168  * @param plugin the plugin
169  * @param query query for the block
170  * @param expiration_time how old does the block have to be for deletion
171  */
172 static void
173 delete_old_block (struct Plugin *plugin,
174                   const struct GNUNET_HashCode *query,
175                   struct GNUNET_TIME_AbsoluteNBO expiration_time)
176 {
177   struct GNUNET_PQ_QueryParam params[] = {
178     GNUNET_PQ_query_param_auto_from_type (query),
179     GNUNET_PQ_query_param_absolute_time_nbo (&expiration_time),
180     GNUNET_PQ_query_param_end
181   };
182   enum GNUNET_DB_QueryStatus res;
183
184   res = GNUNET_PQ_eval_prepared_non_select (plugin->dbh,
185                                             "delete_block",
186                                             params);
187   GNUNET_break (GNUNET_DB_STATUS_HARD_ERROR != res);
188 }
189
190
191 /**
192  * Cache a block in the datastore.
193  *
194  * @param cls closure (internal context for the plugin)
195  * @param block block to cache
196  * @return #GNUNET_OK on success, else #GNUNET_SYSERR
197  */
198 static int
199 namecache_postgres_cache_block (void *cls,
200                                 const struct GNUNET_GNSRECORD_Block *block)
201 {
202   struct Plugin *plugin = cls;
203   struct GNUNET_HashCode query;
204   size_t block_size = ntohl (block->purpose.size) +
205     sizeof (struct GNUNET_CRYPTO_EcdsaPublicKey) +
206     sizeof (struct GNUNET_CRYPTO_EcdsaSignature);
207   struct GNUNET_PQ_QueryParam params[] = {
208     GNUNET_PQ_query_param_auto_from_type (&query),
209     GNUNET_PQ_query_param_fixed_size (block, block_size),
210     GNUNET_PQ_query_param_absolute_time_nbo (&block->expiration_time),
211     GNUNET_PQ_query_param_end
212   };
213   enum GNUNET_DB_QueryStatus res;
214
215   namecache_postgres_expire_blocks (plugin);
216   GNUNET_CRYPTO_hash (&block->derived_key,
217                       sizeof (struct GNUNET_CRYPTO_EcdsaPublicKey),
218                       &query);
219   if (block_size > 64 * 65536)
220   {
221     GNUNET_break (0);
222     return GNUNET_SYSERR;
223   }
224   delete_old_block (plugin,
225                     &query,
226                     block->expiration_time);
227
228   res = GNUNET_PQ_eval_prepared_non_select (plugin->dbh,
229                                             "cache_block",
230                                             params);
231   if (0 > res)
232     return GNUNET_SYSERR;
233   return GNUNET_OK;
234 }
235
236
237 /**
238  * Get the block for a particular zone and label in the
239  * datastore.  Will return at most one result to the iterator.
240  *
241  * @param cls closure (internal context for the plugin)
242  * @param query hash of public key derived from the zone and the label
243  * @param iter function to call with the result
244  * @param iter_cls closure for @a iter
245  * @return #GNUNET_OK on success, #GNUNET_NO if there were no results, #GNUNET_SYSERR on error
246  */
247 static int
248 namecache_postgres_lookup_block (void *cls,
249                                  const struct GNUNET_HashCode *query,
250                                  GNUNET_NAMECACHE_BlockCallback iter,
251                                  void *iter_cls)
252 {
253   struct Plugin *plugin = cls;
254   size_t bsize;
255   struct GNUNET_GNSRECORD_Block *block;
256   struct GNUNET_PQ_QueryParam params[] = {
257     GNUNET_PQ_query_param_auto_from_type (query),
258     GNUNET_PQ_query_param_end
259   };
260   struct GNUNET_PQ_ResultSpec rs[] = {
261     GNUNET_PQ_result_spec_variable_size ("block",
262                                          (void **) &block,
263                                          &bsize),
264     GNUNET_PQ_result_spec_end
265   };
266   enum GNUNET_DB_QueryStatus res;
267
268   res = GNUNET_PQ_eval_prepared_singleton_select (plugin->dbh,
269                                                   "lookup_block",
270                                                   params,
271                                                   rs);
272   if (0 > res)
273   {
274     LOG (GNUNET_ERROR_TYPE_WARNING,
275          "Failing lookup block in namecache (postgres error)\n");
276     return GNUNET_SYSERR;
277   }
278   if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == res)
279   {
280     /* no result */
281     LOG (GNUNET_ERROR_TYPE_DEBUG,
282          "Ending iteration (no more results)\n");
283     return GNUNET_NO;
284   }
285   if ( (bsize < sizeof (*block)) ||
286        (bsize != ntohl (block->purpose.size) +
287         sizeof (struct GNUNET_CRYPTO_EcdsaPublicKey) +
288         sizeof (struct GNUNET_CRYPTO_EcdsaSignature)) )
289   {
290     GNUNET_break (0);
291     LOG (GNUNET_ERROR_TYPE_DEBUG,
292          "Failing lookup (corrupt block)\n");
293     GNUNET_PQ_cleanup_result (rs);
294     return GNUNET_SYSERR;
295   }
296   iter (iter_cls,
297         block);
298   GNUNET_PQ_cleanup_result (rs);
299   return GNUNET_OK;
300 }
301
302
303 /**
304  * Shutdown database connection and associate data
305  * structures.
306  *
307  * @param plugin the plugin context (state for this module)
308  */
309 static void
310 database_shutdown (struct Plugin *plugin)
311 {
312   PQfinish (plugin->dbh);
313   plugin->dbh = NULL;
314 }
315
316
317 /**
318  * Entry point for the plugin.
319  *
320  * @param cls the `struct GNUNET_NAMECACHE_PluginEnvironment *`
321  * @return NULL on error, otherwise the plugin context
322  */
323 void *
324 libgnunet_plugin_namecache_postgres_init (void *cls)
325 {
326   static struct Plugin plugin;
327   const struct GNUNET_CONFIGURATION_Handle *cfg = cls;
328   struct GNUNET_NAMECACHE_PluginFunctions *api;
329
330   if (NULL != plugin.cfg)
331     return NULL;                /* can only initialize once! */
332   memset (&plugin, 0, sizeof (struct Plugin));
333   plugin.cfg = cfg;
334   if (GNUNET_OK != database_setup (&plugin))
335   {
336     database_shutdown (&plugin);
337     return NULL;
338   }
339   api = GNUNET_new (struct GNUNET_NAMECACHE_PluginFunctions);
340   api->cls = &plugin;
341   api->cache_block = &namecache_postgres_cache_block;
342   api->lookup_block = &namecache_postgres_lookup_block;
343   LOG (GNUNET_ERROR_TYPE_INFO,
344        "Postgres namecache plugin running\n");
345   return api;
346 }
347
348
349 /**
350  * Exit point from the plugin.
351  *
352  * @param cls the plugin context (as returned by "init")
353  * @return always NULL
354  */
355 void *
356 libgnunet_plugin_namecache_postgres_done (void *cls)
357 {
358   struct GNUNET_NAMECACHE_PluginFunctions *api = cls;
359   struct Plugin *plugin = api->cls;
360
361   database_shutdown (plugin);
362   plugin->cfg = NULL;
363   GNUNET_free (api);
364   LOG (GNUNET_ERROR_TYPE_DEBUG,
365        "Postgres namecache plugin is finished\n");
366   return NULL;
367 }
368
369 /* end of plugin_namecache_postgres.c */