fix gnunet-gns performance issue for many egos
[oweals/gnunet.git] / src / gns / gns_tld_api.c
1 /*
2      This file is part of GNUnet.
3      Copyright (C) 2009-2013, 2016, 2018 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  * @file gns/gns_tld_api.c
22  * @brief library to access the GNS service, including TLD lookup
23  * @author Martin Schanzenbach
24  * @author Christian Grothoff
25  */
26 #include "platform.h"
27 #include "gnunet_util_lib.h"
28 #include "gnunet_constants.h"
29 #include "gnunet_arm_service.h"
30 #include "gnunet_identity_service.h"
31 #include "gnunet_hello_lib.h"
32 #include "gnunet_protocols.h"
33 #include "gnunet_dht_service.h"
34 #include "gns.h"
35 #include "gns_api.h"
36
37
38 #define LOG(kind, ...) GNUNET_log_from (kind, "gns-tld-api", __VA_ARGS__)
39
40
41 /**
42  * Handle to a lookup request
43  */
44 struct GNUNET_GNS_LookupWithTldRequest
45 {
46
47   /**
48    * handle to gns
49    */
50   struct GNUNET_GNS_Handle *gns_handle;
51
52   /**
53    * processor to call on lookup result
54    */
55   GNUNET_GNS_LookupResultProcessor2 lookup_proc;
56
57   /**
58    * Domain name we are resolving.
59    */
60   char *name;
61
62   /**
63    * @e lookup_proc closure
64    */
65   void *lookup_proc_cls;
66
67   /**
68    * Underlying GNS lookup.
69    */
70   struct GNUNET_GNS_LookupRequest *lr;
71
72   /**
73    * Lookup an ego with the identity service.
74    */
75   struct GNUNET_IDENTITY_EgoSuffixLookup *id_co;
76
77   /**
78    * Name of the longest matching ego found so far.
79    * Must be freed on termination.
80    */
81   char *longest_match;
82
83   /**
84    * Ego corresponding to @e longest_match.
85    */
86   struct GNUNET_IDENTITY_Ego *longest_match_ego;
87
88   /**
89    * Desired result record type.
90    */
91   uint32_t type;
92
93   /**
94    * Lookup options.
95    */
96   enum GNUNET_GNS_LocalOptions options;
97 };
98
99
100 /**
101  * Obtain the TLD of the given @a name.
102  *
103  * @param name a name
104  * @return the part of @a name after the last ".",
105  *         or @a name if @a name does not contain a "."
106  */
107 static const char *
108 get_tld (const char *name)
109 {
110   const char *tld;
111
112   tld = strrchr (name, (unsigned char) '.');
113   if (NULL == tld)
114     tld = name;
115   else
116     tld++; /* skip the '.' */
117   return tld;
118 }
119
120
121 /**
122  * Eat the "TLD" (last bit) of the given @a name.
123  *
124  * @param[in,out] name a name
125  * @param tld what to eat (can be more than just the tld)
126  */
127 static void
128 eat_tld (char *name, const char *tld)
129 {
130   GNUNET_assert (0 < strlen (name));
131   if ((NULL == tld) || (strlen (name) == strlen (tld)))
132   {
133     strcpy (name, GNUNET_GNS_EMPTY_LABEL_AT);
134   }
135   else
136   {
137     GNUNET_assert (strlen (tld) < strlen (name));
138     name[strlen (name) - strlen (tld) - 1] = '\0';
139   }
140 }
141
142
143 /**
144  * Function called with the result of a GNS lookup.
145  *
146  * @param cls a `struct GNUNET_GNS_LookupWithTldRequest *`
147  * @param rd_count number of records returned
148  * @param rd array of @a rd_count records with the results
149  */
150 static void
151 process_lookup_result (void *cls,
152                        uint32_t rd_count,
153                        const struct GNUNET_GNSRECORD_Data *rd)
154 {
155   struct GNUNET_GNS_LookupWithTldRequest *ltr = cls;
156
157   ltr->lr = NULL;
158   ltr->lookup_proc (ltr->lookup_proc_cls, GNUNET_YES, rd_count, rd);
159   GNUNET_GNS_lookup_with_tld_cancel (ltr);
160 }
161
162
163 /**
164  * Perform the actual resolution, starting with the zone
165  * identified by the given public key.
166  *
167  * @param pkey public key to use for the zone, can be NULL
168  */
169 static void
170 lookup_with_public_key (struct GNUNET_GNS_LookupWithTldRequest *ltr,
171                         const struct GNUNET_CRYPTO_EcdsaPublicKey *pkey)
172 {
173   ltr->lr = GNUNET_GNS_lookup (ltr->gns_handle,
174                                ltr->name,
175                                pkey,
176                                ltr->type,
177                                ltr->options,
178                                &process_lookup_result,
179                                ltr);
180 }
181
182
183 /**
184  * Method called to with the ego we are to use for the lookup,
185  * when the ego is determined by a name.
186  *
187  * @param cls a `struct GNUNET_GNS_LookupWithTldRequest *`
188  * @param ego ego handle, NULL at the end of the iteration
189  * @param ctx context we could store data to associate with @e ego
190  * @param name name of the ego
191  */
192 static void
193 identity_zone_cb (void *cls,
194                   const struct GNUNET_CRYPTO_EcdsaPrivateKey *priv,
195                   const char *ego_name)
196 {
197   struct GNUNET_GNS_LookupWithTldRequest *ltr = cls;
198   struct GNUNET_CRYPTO_EcdsaPublicKey pkey;
199
200   ltr->id_co = NULL;
201   if (NULL == priv)
202   {
203     /* no matching ego found */
204     ltr->lookup_proc (ltr->lookup_proc_cls, GNUNET_NO, 0, NULL);
205     return;
206   }
207   /* Final case: TLD matches one of our egos */
208   if (0 == strcmp (ltr->name, ego_name))
209   {
210     /* name matches ego name perfectly, only "@" remains */
211     strcpy (ltr->name, GNUNET_GNS_EMPTY_LABEL_AT);
212   }
213   else
214   {
215     GNUNET_assert (strlen (ego_name) < strlen (ltr->name));
216     ltr->name[strlen (ltr->name) - strlen (ego_name) - 1] = '\0';
217   }
218   /* if the name is of the form 'label' (and not 'label.SUBDOMAIN'), never go to the DHT */
219   if (NULL == strchr (ltr->name, (unsigned char) '.'))
220     ltr->options = GNUNET_GNS_LO_NO_DHT;
221   else
222     ltr->options = GNUNET_GNS_LO_LOCAL_MASTER;
223   GNUNET_CRYPTO_ecdsa_key_get_public (priv, &pkey);
224   lookup_with_public_key (ltr, &pkey);
225 }
226
227
228 /**
229  * Perform an asynchronous lookup operation on the GNS,
230  * determining the zone using the TLD of the given name
231  * and the current configuration to resolve TLDs to zones.
232  *
233  * @param handle handle to the GNS service
234  * @param name the name to look up, including TLD
235  * @param type the record type to look up
236  * @param options local options for the lookup
237  * @param proc processor to call on result
238  * @param proc_cls closure for @a proc
239  * @return handle to the get request, NULL on error (i.e. bad configuration)
240  */
241 struct GNUNET_GNS_LookupWithTldRequest *
242 GNUNET_GNS_lookup_with_tld (struct GNUNET_GNS_Handle *handle,
243                             const char *name,
244                             uint32_t type,
245                             enum GNUNET_GNS_LocalOptions options,
246                             GNUNET_GNS_LookupResultProcessor2 proc,
247                             void *proc_cls)
248 {
249   struct GNUNET_GNS_LookupWithTldRequest *ltr;
250   const char *tld;
251   char *dot_tld;
252   char *zonestr;
253   struct GNUNET_CRYPTO_EcdsaPublicKey pkey;
254
255   ltr = GNUNET_new (struct GNUNET_GNS_LookupWithTldRequest);
256   ltr->gns_handle = handle;
257   ltr->name = GNUNET_strdup (name);
258   ltr->type = type;
259   ltr->options = options;
260   ltr->lookup_proc = proc;
261   ltr->lookup_proc_cls = proc_cls;
262   /* start with trivial case: TLD is zkey */
263   tld = get_tld (ltr->name);
264   if (GNUNET_OK ==
265       GNUNET_CRYPTO_ecdsa_public_key_from_string (tld, strlen (tld), &pkey))
266   {
267     eat_tld (ltr->name, tld);
268     lookup_with_public_key (ltr, &pkey);
269     return ltr;
270   }
271
272   /* second case: domain is mapped in our configuration file */
273   for (const char *domain = name; NULL != domain;
274        domain = strchr (domain, (unsigned char) '.'))
275   {
276     if ('.' == domain[0])
277       domain++;
278     GNUNET_asprintf (&dot_tld, ".%s", domain);
279     if (GNUNET_OK == GNUNET_CONFIGURATION_get_value_string (handle->cfg,
280                                                             "gns",
281                                                             dot_tld,
282                                                             &zonestr))
283     {
284       if (GNUNET_OK !=
285           GNUNET_CRYPTO_ecdsa_public_key_from_string (zonestr,
286                                                       strlen (zonestr),
287                                                       &pkey))
288       {
289         GNUNET_log_config_invalid (
290           GNUNET_ERROR_TYPE_ERROR,
291           "gns",
292           dot_tld,
293           _ ("Expected a base32-encoded public zone key\n"));
294         GNUNET_free (zonestr);
295         GNUNET_free (dot_tld);
296         GNUNET_free (ltr->name);
297         GNUNET_free (ltr);
298         return NULL;
299       }
300       eat_tld (ltr->name, &dot_tld[1]);
301       GNUNET_free (zonestr);
302       GNUNET_free (dot_tld);
303       lookup_with_public_key (ltr, &pkey);
304       return ltr;
305     }
306     GNUNET_free (dot_tld);
307   }
308   ltr->id_co =
309     GNUNET_IDENTITY_ego_lookup_by_suffix (ltr->gns_handle->cfg,
310                                           ltr->name,
311                                           &identity_zone_cb,
312                                           ltr);
313   if (NULL == ltr->id_co)
314   {
315     GNUNET_free (ltr->name);
316     GNUNET_free (ltr);
317     return NULL;
318   }
319   return ltr;
320 }
321
322
323 /**
324  * Cancel pending lookup request
325  *
326  * @param ltr the lookup request to cancel
327  * @return closure from the lookup result processor
328  */
329 void *
330 GNUNET_GNS_lookup_with_tld_cancel (struct GNUNET_GNS_LookupWithTldRequest *ltr)
331 {
332   void *ret = ltr->lookup_proc_cls;
333
334   if (NULL != ltr->id_co)
335   {
336     GNUNET_IDENTITY_ego_lookup_by_suffix_cancel (ltr->id_co);
337     ltr->id_co = NULL;
338   }
339   if (NULL != ltr->lr)
340   {
341     GNUNET_GNS_lookup_cancel (ltr->lr);
342     ltr->lr = NULL;
343   }
344   GNUNET_free_non_null (ltr->longest_match);
345   GNUNET_free (ltr->name);
346   GNUNET_free (ltr);
347   return ret;
348 }
349
350 /* end of gns_tld_api.c */