4e7f9a91d9ddd0cf7b354b49c969a2c33411168c
[oweals/gnunet.git] / src / gns / gnunet-service-gns_shorten.c
1 /*
2      This file is part of GNUnet.
3      (C) 2011-2013 Christian Grothoff (and other contributing authors)
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., 59 Temple Place - Suite 330,
18      Boston, MA 02111-1307, USA.
19 */
20
21 /**
22  * @file gns/gnunet-service-gns_shorten.c
23  * @brief GNUnet GNS shortening logic
24  * @author Martin Schanzenbach
25  * @author Christian Grothoff
26  */
27 #include "platform.h"
28 #include "gnunet_util_lib.h"
29 #include "gnunet_dht_service.h"
30 #include "gnunet_gnsrecord_lib.h"
31 #include "gnunet_namestore_service.h"
32 #include "gnunet_resolver_service.h"
33 #include "gnunet_gns_service.h"
34 #include "gns.h"
35 #include "gnunet-service-gns_shorten.h"
36 #include "gnunet_vpn_service.h"
37
38
39 /**
40  * Default DHT timeout for lookups.
41  */
42 #define DHT_LOOKUP_TIMEOUT GNUNET_TIME_relative_multiply (GNUNET_TIME_UNIT_SECONDS, 60)
43
44 /**
45  * DHT replication level
46  */
47 #define DHT_GNS_REPLICATION_LEVEL 5
48
49
50 /**
51  * Handle for a PSEU lookup used to shorten names.
52  */
53 struct GetPseuAuthorityHandle
54 {
55   /**
56    * DLL
57    */
58   struct GetPseuAuthorityHandle *next;
59
60   /**
61    * DLL
62    */
63   struct GetPseuAuthorityHandle *prev;
64
65   /**
66    * Private key of the (shorten) zone to store the resulting
67    * pseudonym in.
68    */
69   struct GNUNET_CRYPTO_EcdsaPrivateKey shorten_zone_key;
70
71   /**
72    * Original label (used if no PSEU record is found).
73    */
74   char label[GNUNET_DNSPARSER_MAX_LABEL_LENGTH + 1];
75
76   /**
77    * Label we are currently trying out (during #perform_pseu_lookup).
78    */
79   char *current_label;
80
81   /**
82    * The zone for which we are trying to find the PSEU record.
83    */
84   struct GNUNET_CRYPTO_EcdsaPublicKey target_zone;
85
86   /**
87    * Handle for DHT lookups. Should be NULL if no lookups are in progress
88    */
89   struct GNUNET_DHT_GetHandle *get_handle;
90
91   /**
92    * Handle to namestore request
93    */
94   struct GNUNET_NAMESTORE_QueueEntry *namestore_task;
95
96   /**
97    * Handle to namecache request
98    */
99   struct GNUNET_NAMECACHE_QueueEntry *namecache_task;
100
101   /**
102    * Task to abort DHT lookup operation.
103    */
104   GNUNET_SCHEDULER_TaskIdentifier timeout_task;
105
106 };
107
108
109 /**
110  * Head of PSEU/shorten operations list.
111  */
112 static struct GetPseuAuthorityHandle *gph_head;
113
114 /**
115  * Tail of PSEU/shorten operations list.
116  */
117 static struct GetPseuAuthorityHandle *gph_tail;
118
119 /**
120  * Our handle to the namestore service
121  */
122 static struct GNUNET_NAMESTORE_Handle *namestore_handle;
123
124 /**
125  * Our handle to the namecache service
126  */
127 static struct GNUNET_NAMECACHE_Handle *namecache_handle;
128
129 /**
130  * Resolver handle to the dht
131  */
132 static struct GNUNET_DHT_Handle *dht_handle;
133
134 /**
135  * Cleanup a 'struct GetPseuAuthorityHandle', terminating all
136  * pending activities.
137  *
138  * @param gph handle to terminate
139  */
140 static void
141 free_get_pseu_authority_handle (struct GetPseuAuthorityHandle *gph)
142 {
143   if (NULL != gph->get_handle)
144   {
145     GNUNET_DHT_get_stop (gph->get_handle);
146     gph->get_handle = NULL;
147   }
148   if (NULL != gph->namestore_task)
149   {
150     GNUNET_NAMESTORE_cancel (gph->namestore_task);
151     gph->namestore_task = NULL;
152   }
153   if (NULL != gph->namecache_task)
154   {
155     GNUNET_NAMECACHE_cancel (gph->namecache_task);
156     gph->namecache_task = NULL;
157   }
158   if (GNUNET_SCHEDULER_NO_TASK != gph->timeout_task)
159   {
160     GNUNET_SCHEDULER_cancel (gph->timeout_task);
161     gph->timeout_task = GNUNET_SCHEDULER_NO_TASK;
162   }
163   GNUNET_CONTAINER_DLL_remove (gph_head, gph_tail, gph);
164   GNUNET_free_non_null (gph->current_label);
165   GNUNET_free (gph);
166 }
167
168
169 /**
170  * Continuation for pkey record creation (shorten)
171  *
172  * @param cls a GetPseuAuthorityHandle
173  * @param success unused
174  * @param emsg unused
175  */
176 static void
177 create_pkey_cont (void* cls,
178                   int32_t success,
179                   const char *emsg)
180 {
181   struct GetPseuAuthorityHandle* gph = cls;
182
183   gph->namestore_task = NULL;
184   free_get_pseu_authority_handle (gph);
185 }
186
187
188 /**
189  * Namestore calls this function if we have record for this name.
190  * (or with rd_count=0 to indicate no matches).
191  *
192  * @param cls the pending query
193  * @param rd_count the number of records with 'name'
194  * @param rd the record data
195  */
196 static void
197 process_pseu_lookup_ns (void *cls,
198                         unsigned int rd_count,
199                         const struct GNUNET_GNSRECORD_Data *rd);
200
201
202 /**
203  * We obtained a result for our query to the shorten zone from
204  * the namestore.  Try to decrypt.
205  *
206  * @param cls the handle to our shorten operation
207  * @param block resulting encrypted block
208  */
209 static void
210 process_pseu_block_ns (void *cls,
211                        const struct GNUNET_GNSRECORD_Block *block)
212 {
213   struct GetPseuAuthorityHandle *gph = cls;
214   struct GNUNET_CRYPTO_EcdsaPublicKey pub;
215
216   gph->namecache_task = NULL;
217   if (NULL == block)
218   {
219     process_pseu_lookup_ns (gph, 0, NULL);
220     return;
221   }
222   GNUNET_CRYPTO_ecdsa_key_get_public (&gph->shorten_zone_key,
223                                     &pub);
224   if (GNUNET_OK !=
225       GNUNET_GNSRECORD_block_decrypt (block,
226                                       &pub,
227                                       gph->current_label,
228                                       &process_pseu_lookup_ns,
229                                       gph))
230   {
231     GNUNET_break (0);
232     free_get_pseu_authority_handle (gph);
233     return;
234   }
235 }
236
237
238 /**
239  * Lookup in the namestore for the shorten zone the given label.
240  *
241  * @param gph the handle to our shorten operation
242  * @param label the label to lookup
243  */
244 static void
245 perform_pseu_lookup (struct GetPseuAuthorityHandle *gph,
246                      const char *label)
247 {
248   struct GNUNET_CRYPTO_EcdsaPublicKey pub;
249   struct GNUNET_HashCode query;
250
251   GNUNET_CRYPTO_ecdsa_key_get_public (&gph->shorten_zone_key,
252                                     &pub);
253   GNUNET_free_non_null (gph->current_label);
254   gph->current_label = GNUNET_strdup (label);
255   GNUNET_GNSRECORD_query_from_public_key (&pub,
256                                           label,
257                                           &query);
258   gph->namecache_task = GNUNET_NAMECACHE_lookup_block (namecache_handle,
259                                                        &query,
260                                                        &process_pseu_block_ns,
261                                                        gph);
262 }
263
264
265 /**
266  * Namestore calls this function if we have record for this name.
267  * (or with rd_count=0 to indicate no matches).
268  *
269  * @param cls the pending query
270  * @param rd_count the number of records with 'name'
271  * @param rd the record data
272  */
273 static void
274 process_pseu_lookup_ns (void *cls,
275                         unsigned int rd_count,
276                         const struct GNUNET_GNSRECORD_Data *rd)
277 {
278   struct GetPseuAuthorityHandle *gph = cls;
279   struct GNUNET_GNSRECORD_Data new_pkey;
280
281   gph->namestore_task = NULL;
282   if (rd_count > 0)
283   {
284     GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
285                 "Name `%s' already taken, cannot shorten.\n",
286                 gph->current_label);
287     /* if this was not yet the original label, try one more
288        time, this time not using PSEU but the original label */
289     if (0 == strcmp (gph->current_label,
290                      gph->label))
291     {
292       free_get_pseu_authority_handle (gph);
293     }
294     else
295     {
296       perform_pseu_lookup (gph, gph->label);
297     }
298     return;
299   }
300   /* name is available */
301   GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
302               "Shortening `%s' to `%s'\n",
303               GNUNET_GNSRECORD_z2s (&gph->target_zone),
304               gph->current_label);
305   new_pkey.expiration_time = UINT64_MAX;
306   new_pkey.data_size = sizeof (struct GNUNET_CRYPTO_EcdsaPublicKey);
307   new_pkey.data = &gph->target_zone;
308   new_pkey.record_type = GNUNET_GNSRECORD_TYPE_PKEY;
309   new_pkey.flags = GNUNET_GNSRECORD_RF_NONE
310                  | GNUNET_GNSRECORD_RF_PRIVATE
311                  | GNUNET_GNSRECORD_RF_PENDING;
312   gph->namestore_task
313     = GNUNET_NAMESTORE_records_store (namestore_handle,
314                                       &gph->shorten_zone_key,
315                                       gph->current_label,
316                                       1, &new_pkey,
317                                       &create_pkey_cont, gph);
318 }
319
320
321 /**
322  * Process result of a DHT lookup for a PSEU record.
323  *
324  * @param gph the handle to our shorten operation
325  * @param pseu the pseu result or NULL
326  */
327 static void
328 process_pseu_result (struct GetPseuAuthorityHandle* gph,
329                      const char *pseu)
330 {
331   if (NULL == pseu)
332   {
333     /* no PSEU found, try original label */
334     GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
335                 "No PSEU found, trying original label `%s' instead.\n",
336                 gph->label);
337     perform_pseu_lookup (gph, gph->label);
338     return;
339   }
340   /* check if 'pseu' is taken */
341   perform_pseu_lookup (gph, pseu);
342 }
343
344
345 /**
346  * Handle timeout for DHT request during shortening.
347  *
348  * @param cls the request handle as closure
349  * @param tc the task context
350  */
351 static void
352 handle_auth_discovery_timeout (void *cls,
353                                const struct GNUNET_SCHEDULER_TaskContext *tc)
354 {
355   struct GetPseuAuthorityHandle *gph = cls;
356
357   gph->timeout_task = GNUNET_SCHEDULER_NO_TASK;
358   GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
359               "DHT lookup for PSEU query in zone `%s' timed out.\n",
360               GNUNET_GNSRECORD_z2s (&gph->target_zone));
361   GNUNET_DHT_get_stop (gph->get_handle);
362   gph->get_handle = NULL;
363   process_pseu_result (gph, NULL);
364 }
365
366
367 /**
368  * Handle decrypted records from DHT result.
369  *
370  * @param cls closure with our 'struct GetPseuAuthorityHandle'
371  * @param rd_count number of entries in 'rd' array
372  * @param rd array of records with data to store
373  */
374 static void
375 process_auth_records (void *cls,
376                       unsigned int rd_count,
377                       const struct GNUNET_GNSRECORD_Data *rd)
378 {
379   struct GetPseuAuthorityHandle *gph = cls;
380   unsigned int i;
381
382   for (i=0; i < rd_count; i++)
383   {
384     if (GNUNET_GNSRECORD_TYPE_PSEU == rd[i].record_type)
385     {
386       char pseu[rd[i].data_size + 1];
387
388       /* found pseu */
389       memcpy (pseu,
390               rd[i].data,
391               rd[i].data_size);
392       pseu[rd[i].data_size] = '\0';
393       process_pseu_result (gph,
394                            pseu);
395       return;
396     }
397   }
398   GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
399               "No PSEU record found in DHT reply with %u records.\n",
400               rd_count);
401   process_pseu_result (gph, NULL);
402 }
403
404
405 /**
406  * Function called when we find a PSEU entry in the DHT
407  *
408  * @param cls the request handle
409  * @param exp lifetime
410  * @param key the key the record was stored under
411  * @param get_path get path
412  * @param get_path_length @a get_path length
413  * @param put_path put path
414  * @param put_path_length @a put_path length
415  * @param type the block type
416  * @param size number of bytes in @a data
417  * @param data the record data
418  */
419 static void
420 process_auth_discovery_dht_result (void* cls,
421                                    struct GNUNET_TIME_Absolute exp,
422                                    const struct GNUNET_HashCode *key,
423                                    const struct GNUNET_PeerIdentity *get_path,
424                                    unsigned int get_path_length,
425                                    const struct GNUNET_PeerIdentity *put_path,
426                                    unsigned int put_path_length,
427                                    enum GNUNET_BLOCK_Type type,
428                                    size_t size,
429                                    const void *data)
430 {
431   struct GetPseuAuthorityHandle *gph = cls;
432   const struct GNUNET_GNSRECORD_Block *block;
433
434   GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
435               "Got DHT result for PSEU request\n");
436   GNUNET_DHT_get_stop (gph->get_handle);
437   gph->get_handle = NULL;
438   GNUNET_SCHEDULER_cancel (gph->timeout_task);
439   gph->timeout_task = GNUNET_SCHEDULER_NO_TASK;
440
441   if (NULL == data)
442   {
443     /* is this allowed!? */
444     GNUNET_break (0);
445     process_pseu_result (gph, NULL);
446     return;
447   }
448   if (size < sizeof (struct GNUNET_GNSRECORD_Block))
449   {
450     /* how did this pass DHT block validation!? */
451     GNUNET_break (0);
452     process_pseu_result (gph, NULL);
453     return;
454   }
455   block = data;
456   if (size !=
457       ntohl (block->purpose.size) +
458       sizeof (struct GNUNET_CRYPTO_EcdsaPublicKey) +
459       sizeof (struct GNUNET_CRYPTO_EcdsaSignature))
460   {
461     /* how did this pass DHT block validation!? */
462     GNUNET_break (0);
463     process_pseu_result (gph, NULL);
464     return;
465   }
466   if (GNUNET_OK !=
467       GNUNET_GNSRECORD_block_decrypt (block,
468                                       &gph->target_zone,
469                                       GNUNET_GNS_TLD_PLUS,
470                                       &process_auth_records,
471                                       gph))
472   {
473     /* other peer encrypted invalid block, complain */
474     GNUNET_break_op (0);
475     process_pseu_result (gph, NULL);
476     return;
477   }
478 }
479
480
481 /**
482  * Callback called by namestore for a zone to name result.  We're
483  * trying to see if a short name for a given zone already exists.
484  *
485  * @param cls the closure
486  * @param zone_key the zone we queried
487  * @param name the name found or NULL
488  * @param rd_len number of records for the name
489  * @param rd the record data (PKEY) for the name
490  */
491 static void
492 process_zone_to_name_discover (void *cls,
493                                const struct GNUNET_CRYPTO_EcdsaPrivateKey *zone_key,
494                                const char *name,
495                                unsigned int rd_len,
496                                const struct GNUNET_GNSRECORD_Data *rd)
497 {
498   struct GetPseuAuthorityHandle* gph = cls;
499   struct GNUNET_HashCode lookup_key;
500
501   gph->namestore_task = NULL;
502   if (0 != rd_len)
503   {
504     /* we found a match in our own zone */
505     GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
506                 "Shortening aborted, name `%s' already reserved for the zone\n",
507                 name);
508     free_get_pseu_authority_handle (gph);
509     return;
510   }
511   /* record does not yet exist, go into DHT to find PSEU record */
512   GNUNET_GNSRECORD_query_from_public_key (&gph->target_zone,
513                                           GNUNET_GNS_TLD_PLUS,
514                                           &lookup_key);
515   GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
516               "Shortening searches in DHT for PSEU record under `%s' in zone `%s'\n",
517               GNUNET_h2s (&lookup_key),
518               GNUNET_GNSRECORD_z2s (&gph->target_zone));
519
520   gph->timeout_task = GNUNET_SCHEDULER_add_delayed (DHT_LOOKUP_TIMEOUT,
521                                                     &handle_auth_discovery_timeout,
522                                                     gph);
523   gph->get_handle = GNUNET_DHT_get_start (dht_handle,
524                                           GNUNET_BLOCK_TYPE_GNS_NAMERECORD,
525                                           &lookup_key,
526                                           DHT_GNS_REPLICATION_LEVEL,
527                                           GNUNET_DHT_RO_DEMULTIPLEX_EVERYWHERE,
528                                           NULL, 0,
529                                           &process_auth_discovery_dht_result,
530                                           gph);
531 }
532
533
534 /**
535  * Start shortening algorithm, try to allocate a nice short
536  * canonical name for @a pub in @a shorten_zone, using
537  * @a original_label as one possible suggestion.
538  *
539  * @param original_label original label for the zone
540  * @param pub public key of the zone to shorten
541  * @param shorten_zone private key of the target zone for the new record
542  */
543 void
544 GNS_shorten_start (const char *original_label,
545                    const struct GNUNET_CRYPTO_EcdsaPublicKey *pub,
546                    const struct GNUNET_CRYPTO_EcdsaPrivateKey *shorten_zone)
547 {
548   struct GetPseuAuthorityHandle *gph;
549
550   if (strlen (original_label) > GNUNET_DNSPARSER_MAX_LABEL_LENGTH)
551   {
552     GNUNET_break (0);
553     return;
554   }
555   GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
556               "Starting shortening process for `%s' with old label `%s'\n",
557               GNUNET_GNSRECORD_z2s (pub),
558               original_label);
559   gph = GNUNET_new (struct GetPseuAuthorityHandle);
560   gph->shorten_zone_key = *shorten_zone;
561   gph->target_zone = *pub;
562   strcpy (gph->label, original_label);
563   GNUNET_CONTAINER_DLL_insert (gph_head, gph_tail, gph);
564   /* first, check if we *already* have a record for this zone */
565   gph->namestore_task = GNUNET_NAMESTORE_zone_to_name (namestore_handle,
566                                                        shorten_zone,
567                                                        pub,
568                                                        &process_zone_to_name_discover,
569                                                        gph);
570 }
571
572
573 /**
574  * Initialize the shortening subsystem
575  *
576  * @param nh the namestore handle
577  * @param nc the namecache handle
578  * @param dht the dht handle
579  */
580 void
581 GNS_shorten_init (struct GNUNET_NAMESTORE_Handle *nh,
582                   struct GNUNET_NAMECACHE_Handle *nc,
583                   struct GNUNET_DHT_Handle *dht)
584 {
585   namestore_handle = nh;
586   namecache_handle = nc;
587   dht_handle = dht;
588 }
589
590
591 /**
592  * Shutdown shortening.
593  */
594 void
595 GNS_shorten_done ()
596 {
597   /* abort active shorten operations */
598   while (NULL != gph_head)
599     free_get_pseu_authority_handle (gph_head);
600   dht_handle = NULL;
601   namestore_handle = NULL;
602   namecache_handle = NULL;
603 }
604
605 /* end of gnunet-service-gns_shorten.c */