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