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