4652900433e738303caef74829950a80627f7c13
[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_postgres_lib.h"
31 #include "gnunet_pq_lib.h"
32 #include "namecache.h"
33
34
35 #define LOG(kind,...) GNUNET_log_from (kind, "namecache-postgres", __VA_ARGS__)
36
37
38 /**
39  * Context for all functions in this plugin.
40  */
41 struct Plugin
42 {
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 ns096blocks ("
67                             " query BYTEA NOT NULL DEFAULT '',"
68                             " block BYTEA NOT NULL DEFAULT '',"
69                             " expiration_time BIGINT NOT NULL DEFAULT 0"
70                             ")"
71                             "WITH OIDS");
72   struct GNUNET_PQ_ExecuteStatement es_default =
73     GNUNET_PQ_make_execute ("CREATE TABLE IF NOT EXISTS ns096blocks ("
74                             " query BYTEA NOT NULL DEFAULT '',"
75                             " block BYTEA NOT NULL DEFAULT '',"
76                             " expiration_time BIGINT NOT NULL DEFAULT 0"
77                             ")"
78                             "WITH OIDS");
79   const struct GNUNET_PQ_ExecuteStatement *cr;
80
81   plugin->dbh = GNUNET_PQ_connect_with_cfg (plugin->cfg,
82                                             "namecache-postgres");
83   if (NULL == plugin->dbh)
84     return GNUNET_SYSERR;
85   if (GNUNET_YES ==
86       GNUNET_CONFIGURATION_get_value_yesno (plugin->cfg,
87                                             "namecache-postgres",
88                                             "TEMPORARY_TABLE"))
89   {
90     cr = &es_temporary;
91   }
92   else
93   {
94     cr = &es_default;
95   }
96
97   {
98     struct GNUNET_PQ_ExecuteStatement es[] = {
99       *cr,
100       GNUNET_PQ_make_try_execute ("CREATE INDEX ir_query_hash ON ns096blocks (query,expiration_time)"),
101       GNUNET_PQ_make_try_execute ("CREATE INDEX ir_block_expiration ON ns096blocks (expiration_time)"),
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
115   {
116     struct GNUNET_PQ_PreparedStatement ps[] = {
117       GNUNET_PQ_make_prepare ("cache_block",
118                               "INSERT INTO ns096blocks (query, block, expiration_time) VALUES "
119                               "($1, $2, $3)", 3),
120       GNUNET_PQ_make_prepare ("expire_blocks",
121                               "DELETE FROM ns096blocks WHERE expiration_time<$1", 1),
122       GNUNET_PQ_make_prepare ("delete_block",
123                               "DELETE FROM ns096blocks WHERE query=$1 AND expiration_time<=$2", 2),
124       GNUNET_PQ_make_prepare ("lookup_block",
125                               "SELECT block FROM ns096blocks WHERE query=$1"
126                               " ORDER BY expiration_time DESC LIMIT 1", 1),
127       GNUNET_PQ_PREPARED_STATEMENT_END
128     };
129
130     if (GNUNET_OK !=
131         GNUNET_PQ_prepare_statements (plugin->dbh,
132                                       ps))
133     {
134       PQfinish (plugin->dbh);
135       plugin->dbh = NULL;
136       return GNUNET_SYSERR;
137     }
138   }
139
140   return GNUNET_OK;
141 }
142
143
144 /**
145  * Removes any expired block.
146  *
147  * @param plugin the plugin
148  */
149 static void
150 namecache_postgres_expire_blocks (struct Plugin *plugin)
151 {
152   struct GNUNET_TIME_Absolute now = GNUNET_TIME_absolute_get ();
153   struct GNUNET_PQ_QueryParam params[] = {
154     GNUNET_PQ_query_param_absolute_time (&now),
155     GNUNET_PQ_query_param_end
156   };
157   enum GNUNET_PQ_QueryStatus res;
158
159   res = GNUNET_PQ_eval_prepared_non_select (plugin->dbh,
160                                             "expire_blocks",
161                                             params);
162   GNUNET_break (GNUNET_PQ_STATUS_HARD_ERROR != res);
163 }
164
165
166 /**
167  * Delete older block in the datastore.
168  *
169  * @param plugin the plugin
170  * @param query query for the block
171  * @param expiration_time how old does the block have to be for deletion
172  */
173 static void
174 delete_old_block (struct Plugin *plugin,
175                   const struct GNUNET_HashCode *query,
176                   struct GNUNET_TIME_AbsoluteNBO expiration_time)
177 {
178   struct GNUNET_PQ_QueryParam params[] = {
179     GNUNET_PQ_query_param_auto_from_type (query),
180     GNUNET_PQ_query_param_absolute_time_nbo (&expiration_time),
181     GNUNET_PQ_query_param_end
182   };
183   enum GNUNET_PQ_QueryStatus res;
184
185   res = GNUNET_PQ_eval_prepared_non_select (plugin->dbh,
186                                             "delete_block",
187                                             params);
188   GNUNET_break (GNUNET_PQ_STATUS_HARD_ERROR != res);
189 }
190
191
192 /**
193  * Cache a block in the datastore.
194  *
195  * @param cls closure (internal context for the plugin)
196  * @param block block to cache
197  * @return #GNUNET_OK on success, else #GNUNET_SYSERR
198  */
199 static int
200 namecache_postgres_cache_block (void *cls,
201                                 const struct GNUNET_GNSRECORD_Block *block)
202 {
203   struct Plugin *plugin = cls;
204   struct GNUNET_HashCode query;
205   size_t block_size = ntohl (block->purpose.size) +
206     sizeof (struct GNUNET_CRYPTO_EcdsaPublicKey) +
207     sizeof (struct GNUNET_CRYPTO_EcdsaSignature);
208   struct GNUNET_PQ_QueryParam params[] = {
209     GNUNET_PQ_query_param_auto_from_type (&query),
210     GNUNET_PQ_query_param_fixed_size (block, block_size),
211     GNUNET_PQ_query_param_absolute_time_nbo (&block->expiration_time),
212     GNUNET_PQ_query_param_end
213   };
214   enum GNUNET_PQ_QueryStatus res;
215
216   namecache_postgres_expire_blocks (plugin);
217   GNUNET_CRYPTO_hash (&block->derived_key,
218                       sizeof (struct GNUNET_CRYPTO_EcdsaPublicKey),
219                       &query);
220   if (block_size > 64 * 65536)
221   {
222     GNUNET_break (0);
223     return GNUNET_SYSERR;
224   }
225   delete_old_block (plugin,
226                     &query,
227                     block->expiration_time);
228
229   res = GNUNET_PQ_eval_prepared_non_select (plugin->dbh,
230                                             "cache_block",
231                                             params);
232   if (0 > res)
233     return GNUNET_SYSERR;
234   return GNUNET_OK;
235 }
236
237
238 /**
239  * Get the block for a particular zone and label in the
240  * datastore.  Will return at most one result to the iterator.
241  *
242  * @param cls closure (internal context for the plugin)
243  * @param query hash of public key derived from the zone and the label
244  * @param iter function to call with the result
245  * @param iter_cls closure for @a iter
246  * @return #GNUNET_OK on success, #GNUNET_NO if there were no results, #GNUNET_SYSERR on error
247  */
248 static int
249 namecache_postgres_lookup_block (void *cls,
250                                  const struct GNUNET_HashCode *query,
251                                  GNUNET_NAMECACHE_BlockCallback iter,
252                                  void *iter_cls)
253 {
254   struct Plugin *plugin = cls;
255   size_t bsize;
256   struct GNUNET_GNSRECORD_Block *block;
257   struct GNUNET_PQ_QueryParam params[] = {
258     GNUNET_PQ_query_param_auto_from_type (query),
259     GNUNET_PQ_query_param_end
260   };
261   struct GNUNET_PQ_ResultSpec rs[] = {
262     GNUNET_PQ_result_spec_variable_size ("block",
263                                          (void **) &block,
264                                          &bsize),
265     GNUNET_PQ_result_spec_end
266   };
267   enum GNUNET_PQ_QueryStatus res;
268
269   res = GNUNET_PQ_eval_prepared_singleton_select (plugin->dbh,
270                                                   "lookup_block",
271                                                   params,
272                                                   rs);
273   if (0 > res)
274   {
275     LOG (GNUNET_ERROR_TYPE_WARNING,
276          "Failing lookup block in namecache (postgres error)\n");
277     return GNUNET_SYSERR;
278   }
279   if (GNUNET_PQ_STATUS_SUCCESS_NO_RESULTS == res)
280   {
281     /* no result */
282     LOG (GNUNET_ERROR_TYPE_DEBUG,
283          "Ending iteration (no more results)\n");
284     return GNUNET_NO;
285   }
286   if ( (bsize < sizeof (*block)) ||
287        (bsize != ntohl (block->purpose.size) +
288         sizeof (struct GNUNET_CRYPTO_EcdsaPublicKey) +
289         sizeof (struct GNUNET_CRYPTO_EcdsaSignature)) )
290   {
291     GNUNET_break (0);
292     LOG (GNUNET_ERROR_TYPE_DEBUG,
293          "Failing lookup (corrupt block)\n");
294     GNUNET_PQ_cleanup_result (rs);
295     return GNUNET_SYSERR;
296   }
297   iter (iter_cls,
298         block);
299   GNUNET_PQ_cleanup_result (rs);
300   return GNUNET_OK;
301 }
302
303
304 /**
305  * Shutdown database connection and associate data
306  * structures.
307  *
308  * @param plugin the plugin context (state for this module)
309  */
310 static void
311 database_shutdown (struct Plugin *plugin)
312 {
313   PQfinish (plugin->dbh);
314   plugin->dbh = NULL;
315 }
316
317
318 /**
319  * Entry point for the plugin.
320  *
321  * @param cls the `struct GNUNET_NAMECACHE_PluginEnvironment *`
322  * @return NULL on error, otherwise the plugin context
323  */
324 void *
325 libgnunet_plugin_namecache_postgres_init (void *cls)
326 {
327   static struct Plugin plugin;
328   const struct GNUNET_CONFIGURATION_Handle *cfg = cls;
329   struct GNUNET_NAMECACHE_PluginFunctions *api;
330
331   if (NULL != plugin.cfg)
332     return NULL;                /* can only initialize once! */
333   memset (&plugin, 0, sizeof (struct Plugin));
334   plugin.cfg = cfg;
335   if (GNUNET_OK != database_setup (&plugin))
336   {
337     database_shutdown (&plugin);
338     return NULL;
339   }
340   api = GNUNET_new (struct GNUNET_NAMECACHE_PluginFunctions);
341   api->cls = &plugin;
342   api->cache_block = &namecache_postgres_cache_block;
343   api->lookup_block = &namecache_postgres_lookup_block;
344   LOG (GNUNET_ERROR_TYPE_INFO,
345        "Postgres namecache plugin running\n");
346   return api;
347 }
348
349
350 /**
351  * Exit point from the plugin.
352  *
353  * @param cls the plugin context (as returned by "init")
354  * @return always NULL
355  */
356 void *
357 libgnunet_plugin_namecache_postgres_done (void *cls)
358 {
359   struct GNUNET_NAMECACHE_PluginFunctions *api = cls;
360   struct Plugin *plugin = api->cls;
361
362   database_shutdown (plugin);
363   plugin->cfg = NULL;
364   GNUNET_free (api);
365   LOG (GNUNET_ERROR_TYPE_DEBUG,
366        "Postgres namecache plugin is finished\n");
367   return NULL;
368 }
369
370 /* end of plugin_namecache_postgres.c */