gpg: Update list of card vendors from master
[gnupg.git] / dirmngr / domaininfo.c
1 /* domaininfo.c - Gather statistics about accessed domains
2  * Copyright (C) 2017 Werner Koch
3  *
4  * This file is part of GnuPG.
5  *
6  * GnuPG is free software; you can redistribute it and/or modify
7  * it under the terms of the GNU General Public License as published by
8  * the Free Software Foundation; either version 3 of the License, or
9  * (at your option) any later version.
10  *
11  * GnuPG is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14  * GNU General Public License for more details.
15  *
16  * You should have received a copy of the GNU General Public License
17  * along with this program; if not, see <https://www.gnu.org/licenses/>.
18  *
19  * SPDX-License-Identifier: GPL-3.0+
20  */
21
22 #include <config.h>
23 #include <stdlib.h>
24 #include <string.h>
25
26 #include "dirmngr.h"
27
28
29 /* Number of bucket for the hash array and limit for the length of a
30  * bucket chain.  For debugging values of 13 and 10 are more suitable
31  * and a command like
32  *   for j   in a b c d e f g h i j k l m n o p q r s t u v w z y z; do \
33  *     for i in a b c d e f g h i j k l m n o p q r s t u v w z y z; do \
34  *       gpg-connect-agent --dirmngr "wkd_get foo@$i.$j.gnupg.net" /bye \
35  *       >/dev/null ; done; done
36  * will quickly add a couple of domains.
37  */
38 #define NO_OF_DOMAINBUCKETS  103
39 #define MAX_DOMAINBUCKET_LEN  20
40
41
42 /* Object to keep track of a domain name.  */
43 struct domaininfo_s
44 {
45   struct domaininfo_s *next;
46   unsigned int no_name:1;            /* Domain name not found.            */
47   unsigned int wkd_not_found:1;      /* A WKD query failed.               */
48   unsigned int wkd_supported:1;      /* One WKD entry was found.          */
49   unsigned int wkd_not_supported:1;  /* Definitely does not support WKD.  */
50   char name[1];
51 };
52 typedef struct domaininfo_s *domaininfo_t;
53
54 /* And the hashed array.  */
55 static domaininfo_t domainbuckets[NO_OF_DOMAINBUCKETS];
56
57
58 /* The hash function we use.  Must not call a system function.  */
59 static inline u32
60 hash_domain (const char *domain)
61 {
62   const unsigned char *s = (const unsigned char*)domain;
63   u32 hashval = 0;
64   u32 carry;
65
66   for (; *s; s++)
67     {
68       if (*s == '.')
69         continue;
70       hashval = (hashval << 4) + *s;
71       if ((carry = (hashval & 0xf0000000)))
72         {
73           hashval ^= (carry >> 24);
74           hashval ^= carry;
75         }
76     }
77
78   return hashval % NO_OF_DOMAINBUCKETS;
79 }
80
81
82 void
83 domaininfo_print_stats (void)
84 {
85   int bidx;
86   domaininfo_t di;
87   int count, no_name, wkd_not_found, wkd_supported, wkd_not_supported;
88   int len, minlen, maxlen;
89
90   count = no_name = wkd_not_found = wkd_supported = wkd_not_supported = 0;
91   maxlen = 0;
92   minlen = -1;
93   for (bidx = 0; bidx < NO_OF_DOMAINBUCKETS; bidx++)
94     {
95       len = 0;
96       for (di = domainbuckets[bidx]; di; di = di->next)
97         {
98           count++;
99           len++;
100           if (di->no_name)
101             no_name++;
102           if (di->wkd_not_found)
103             wkd_not_found++;
104           if (di->wkd_supported)
105             wkd_supported++;
106           if (di->wkd_not_supported)
107             wkd_not_supported++;
108         }
109       if (len > maxlen)
110         maxlen = len;
111       if (minlen == -1 || len < minlen)
112         minlen = len;
113     }
114   log_info ("domaininfo: items=%d chainlen=%d..%d nn=%d nf=%d ns=%d s=%d\n",
115             count,
116             minlen > 0? minlen : 0,
117             maxlen,
118             no_name, wkd_not_found, wkd_not_supported, wkd_supported);
119 }
120
121
122 /* Return true if DOMAIN definitely does not support WKD.  Noet that
123  * DOMAIN is expected to be lowercase.  */
124 int
125 domaininfo_is_wkd_not_supported (const char *domain)
126 {
127   domaininfo_t di;
128
129   for (di = domainbuckets[hash_domain (domain)]; di; di = di->next)
130     if (!strcmp (di->name, domain))
131       return !!di->wkd_not_supported;
132
133   return 0;  /* We don't know.  */
134 }
135
136
137 /* Core update function.  DOMAIN is expected to be lowercase.
138  * CALLBACK is called to update the existing or the newly inserted
139  * item.  */
140 static void
141 insert_or_update (const char *domain,
142                   void (*callback)(domaininfo_t di, int insert_mode))
143 {
144   domaininfo_t di;
145   domaininfo_t di_new;
146   domaininfo_t di_cut;
147   u32 hash;
148   int count;
149
150   hash = hash_domain (domain);
151   for (di = domainbuckets[hash]; di; di = di->next)
152     if (!strcmp (di->name, domain))
153       {
154         callback (di, 0);  /* Update */
155         return;
156       }
157
158   di_new = xtrycalloc (1, sizeof *di + strlen (domain));
159   if (!di_new)
160     return;  /* Out of core - we ignore this.  */
161   strcpy (di_new->name, domain);
162
163   /* Need to do another lookup because the malloc is a system call and
164    * thus the hash array may have been changed by another thread.  */
165   di_cut = NULL;
166   for (count=0, di = domainbuckets[hash]; di; di = di->next, count++)
167     if (!strcmp (di->name, domain))
168       {
169         callback (di, 0);  /* Update */
170         xfree (di_new);
171         return;
172       }
173
174   /* Before we insert we need to check whether the chain gets too long.  */
175   di_cut = NULL;
176   if (count >= MAX_DOMAINBUCKET_LEN)
177     {
178       for (count=0, di = domainbuckets[hash]; di; di = di->next, count++)
179         if (count >= MAX_DOMAINBUCKET_LEN/2)
180           {
181             di_cut = di->next;
182             di->next = NULL;
183             break;
184           }
185     }
186
187   /* Insert */
188   callback (di_new, 1);
189   di = di_new;
190   di->next = domainbuckets[hash];
191   domainbuckets[hash] = di;
192
193   /* Remove the rest of the cutted chain.  */
194   while (di_cut)
195     {
196       di = di_cut->next;
197       xfree (di_cut);
198       di_cut = di;
199     }
200 }
201
202
203 /* Helper for domaininfo_set_no_name.  */
204 static void
205 set_no_name_cb (domaininfo_t di, int insert_mode)
206 {
207   (void)insert_mode;
208
209   di->no_name = 1;
210   /* Obviously the domain is in this case also not supported.  */
211   di->wkd_not_supported = 1;
212
213   /* The next should already be 0 but we clear it anyway in the case
214    * of a temporary DNS failure.  */
215   di->wkd_supported = 0;
216 }
217
218
219 /* Mark DOMAIN as not existent.  */
220 void
221 domaininfo_set_no_name (const char *domain)
222 {
223   insert_or_update (domain, set_no_name_cb);
224 }
225
226
227 /* Helper for domaininfo_set_wkd_supported.  */
228 static void
229 set_wkd_supported_cb (domaininfo_t di, int insert_mode)
230 {
231   (void)insert_mode;
232
233   di->wkd_supported = 1;
234   /* The next will already be set unless the domain enabled WKD in the
235    * meantime.  Thus we need to clear it.  */
236   di->wkd_not_supported = 0;
237 }
238
239
240 /* Mark DOMAIN as supporting WKD.  */
241 void
242 domaininfo_set_wkd_supported (const char *domain)
243 {
244   insert_or_update (domain, set_wkd_supported_cb);
245 }
246
247
248 /* Helper for domaininfo_set_wkd_not_supported.  */
249 static void
250 set_wkd_not_supported_cb (domaininfo_t di, int insert_mode)
251 {
252   (void)insert_mode;
253
254   di->wkd_not_supported = 1;
255   di->wkd_supported = 0;
256 }
257
258
259 /* Mark DOMAIN as not supporting WKD queries (e.g. no policy file).  */
260 void
261 domaininfo_set_wkd_not_supported (const char *domain)
262 {
263   insert_or_update (domain, set_wkd_not_supported_cb);
264 }
265
266
267
268 /* Helper for domaininfo_set_wkd_not_found.  */
269 static void
270 set_wkd_not_found_cb (domaininfo_t di, int insert_mode)
271 {
272   /* Set the not found flag but there is no need to do this if we
273    * already know that the domain either does not support WKD or we
274    * know that it supports WKD.  */
275   if (insert_mode)
276     di->wkd_not_found = 1;
277   else if (!di->wkd_not_supported && !di->wkd_supported)
278     di->wkd_not_found = 1;
279
280   /* Better clear this flag in case we had a DNS failure in the
281    * past.  */
282   di->no_name = 0;
283 }
284
285
286 /* Update a counter for DOMAIN to keep track of failed WKD queries.  */
287 void
288 domaininfo_set_wkd_not_found (const char *domain)
289 {
290   insert_or_update (domain, set_wkd_not_found_cb);
291 }