dirmngr: Keep track of domains used for WKD queries
[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 #define NO_OF_DOMAINBUCKETS 103
30
31 /* Object to keep track of a domain name.  */
32 struct domaininfo_s
33 {
34   struct domaininfo_s *next;
35   unsigned int no_name:1;            /* Domain name not found.            */
36   unsigned int wkd_not_found:1;      /* A WKD query failed.               */
37   unsigned int wkd_supported:1;      /* One WKD entry was found.          */
38   unsigned int wkd_not_supported:1;  /* Definitely does not support WKD.  */
39   char name[1];
40 };
41 typedef struct domaininfo_s *domaininfo_t;
42
43 /* And the hashed array.  */
44 static domaininfo_t domainbuckets[NO_OF_DOMAINBUCKETS];
45
46
47 /* The hash function we use.  Must not call a system function.  */
48 static inline u32
49 hash_domain (const char *domain)
50 {
51   const unsigned char *s = (const unsigned char*)domain;
52   u32 hashval = 0;
53   u32 carry;
54
55   for (; *s; s++)
56     {
57       if (*s == '.')
58         continue;
59       hashval = (hashval << 4) + *s;
60       if ((carry = (hashval & 0xf0000000)))
61         {
62           hashval ^= (carry >> 24);
63           hashval ^= carry;
64         }
65     }
66
67   return hashval % NO_OF_DOMAINBUCKETS;
68 }
69
70
71 void
72 domaininfo_print_stats (void)
73 {
74   int bidx;
75   domaininfo_t di;
76   int count, no_name, wkd_not_found, wkd_supported, wkd_not_supported;
77
78   for (bidx = 0; bidx < NO_OF_DOMAINBUCKETS; bidx++)
79     {
80       count = no_name = wkd_not_found = wkd_supported = wkd_not_supported = 0;
81       for (di = domainbuckets[bidx]; di; di = di->next)
82         {
83           count++;
84           if (di->no_name)
85             no_name++;
86           if (di->wkd_not_found)
87             wkd_not_found++;
88           if (di->wkd_supported)
89             wkd_supported++;
90           if (di->wkd_not_supported)
91             wkd_not_supported++;
92         }
93       if (count)
94         log_info ("domaininfo: chain %3d length=%d nn=%d nf=%d s=%d ns=%d\n",
95                   bidx, count, no_name,
96                   wkd_not_found, wkd_supported, wkd_not_supported);
97     }
98 }
99
100
101 /* Return true if DOMAIN definitely does not support WKD.  Noet that
102  * DOMAIN is expected to be lowercase.  */
103 int
104 domaininfo_is_wkd_not_supported (const char *domain)
105 {
106   domaininfo_t di;
107
108   for (di = domainbuckets[hash_domain (domain)]; di; di = di->next)
109     if (!strcmp (di->name, domain))
110       return !!di->wkd_not_supported;
111
112   return 0;  /* We don't know.  */
113 }
114
115
116 /* Core update function.  DOMAIN is expected to be lowercase.
117  * CALLBACK is called to update the existing or the newly inserted
118  * item.  */
119 static void
120 insert_or_update (const char *domain,
121                   void (*callback)(domaininfo_t di, int insert_mode))
122 {
123   domaininfo_t di;
124   domaininfo_t di_new;
125   u32 hash;
126
127   hash = hash_domain (domain);
128   for (di = domainbuckets[hash]; di; di = di->next)
129     if (!strcmp (di->name, domain))
130       {
131         callback (di, 0);  /* Update */
132         return;
133       }
134
135   di_new = xtrycalloc (1, sizeof *di + strlen (domain));
136   if (!di_new)
137     return;  /* Out of core - we ignore this.  */
138
139   /* Need to do another lookup because the malloc is a system call and
140    * thus the hash array may have been changed by another thread.  */
141   for (di = domainbuckets[hash]; di; di = di->next)
142     if (!strcmp (di->name, domain))
143       {
144         callback (di, 0);  /* Update */
145         xfree (di_new);
146         return;
147       }
148
149   callback (di_new, 1);  /* Insert */
150   di = di_new;
151   di->next = domainbuckets[hash];
152   domainbuckets[hash] = di;
153 }
154
155
156 /* Helper for domaininfo_set_no_name.  */
157 static void
158 set_no_name_cb (domaininfo_t di, int insert_mode)
159 {
160   (void)insert_mode;
161
162   di->no_name = 1;
163   /* Obviously the domain is in this case also not supported.  */
164   di->wkd_not_supported = 1;
165
166   /* The next should already be 0 but we clear it anyway in the case
167    * of a temporary DNS failure.  */
168   di->wkd_supported = 0;
169 }
170
171
172 /* Mark DOMAIN as not existent.  */
173 void
174 domaininfo_set_no_name (const char *domain)
175 {
176   insert_or_update (domain, set_no_name_cb);
177 }
178
179
180 /* Helper for domaininfo_set_wkd_supported.  */
181 static void
182 set_wkd_supported_cb (domaininfo_t di, int insert_mode)
183 {
184   (void)insert_mode;
185
186   di->wkd_supported = 1;
187   /* The next will already be set unless the domain enabled WKD in the
188    * meantime.  Thus we need to clear it.  */
189   di->wkd_not_supported = 0;
190 }
191
192
193 /* Mark DOMAIN as supporting WKD.  */
194 void
195 domaininfo_set_wkd_supported (const char *domain)
196 {
197   insert_or_update (domain, set_wkd_supported_cb);
198 }
199
200
201 /* Helper for domaininfo_set_wkd_not_supported.  */
202 static void
203 set_wkd_not_supported_cb (domaininfo_t di, int insert_mode)
204 {
205   (void)insert_mode;
206
207   di->wkd_not_supported = 1;
208   di->wkd_supported = 0;
209 }
210
211
212 /* Mark DOMAIN as not supporting WKD queries (e.g. no policy file).  */
213 void
214 domaininfo_set_wkd_not_supported (const char *domain)
215 {
216   insert_or_update (domain, set_wkd_not_supported_cb);
217 }
218
219
220
221 /* Helper for domaininfo_set_wkd_not_found.  */
222 static void
223 set_wkd_not_found_cb (domaininfo_t di, int insert_mode)
224 {
225   /* Set the not found flag but there is no need to do this if we
226    * already know that the domain either does not support WKD or we
227    * know that it supports WKD.  */
228   if (insert_mode)
229     di->wkd_not_found = 1;
230   else if (!di->wkd_not_supported && !di->wkd_supported)
231     di->wkd_not_found = 1;
232
233   /* Better clear this flag in case we had a DNS failure in the
234    * past.  */
235   di->no_name = 0;
236 }
237
238
239 /* Update a counter for DOMAIN to keep track of failed WKD queries.  */
240 void
241 domaininfo_set_wkd_not_found (const char *domain)
242 {
243   insert_or_update (domain, set_wkd_not_found_cb);
244 }