1 /* $NetBSD: authreadkeys.c,v 1.12 2024/08/18 20:47:13 christos Exp $ */
2
3 /*
4 * authreadkeys.c - routines to support the reading of the key file
5 */
6 #include <config.h>
7 #include <stdio.h>
8 #include <ctype.h>
9
10 //#include "ntpd.h" /* Only for DPRINTF */
11 //#include "ntp_fp.h"
12 #include "ntp.h"
13 #include "ntp_syslog.h"
14 #include "ntp_stdlib.h"
15 #include "ntp_keyacc.h"
16
17 #ifdef OPENSSL
18 #include "openssl/objects.h"
19 #include "openssl/evp.h"
20 #endif /* OPENSSL */
21
22 /* Forwards */
23 static char *nexttok (char **);
24
25 /*
26 * nexttok - basic internal tokenizing routine
27 */
28 static char *
nexttok(char ** str)29 nexttok(
30 char **str
31 )
32 {
33 register char *cp;
34 char *starttok;
35
36 cp = *str;
37
38 /*
39 * Space past white space
40 */
41 while (*cp == ' ' || *cp == '\t')
42 cp++;
43
44 /*
45 * Save this and space to end of token
46 */
47 starttok = cp;
48 while (*cp != '\0' && *cp != '\n' && *cp != ' '
49 && *cp != '\t' && *cp != '#')
50 cp++;
51
52 /*
53 * If token length is zero return an error, else set end of
54 * token to zero and return start.
55 */
56 if (starttok == cp)
57 return NULL;
58
59 if (*cp == ' ' || *cp == '\t')
60 *cp++ = '\0';
61 else
62 *cp = '\0';
63
64 *str = cp;
65 return starttok;
66 }
67
68
69 /* TALOS-CAN-0055: possibly DoS attack by setting the key file to the
70 * log file. This is hard to prevent (it would need to check two files
71 * to be the same on the inode level, which will not work so easily with
72 * Windows or VMS) but we can avoid the self-amplification loop: We only
73 * log the first 5 errors, silently ignore the next 10 errors, and give
74 * up when when we have found more than 15 errors.
75 *
76 * This avoids the endless file iteration we will end up with otherwise,
77 * and also avoids overflowing the log file.
78 *
79 * Nevertheless, once this happens, the keys are gone since this would
80 * require a save/swap strategy that is not easy to apply due to the
81 * data on global/static level.
82 */
83
84 static const u_int nerr_loglimit = 5u;
85 static const u_int nerr_maxlimit = 15;
86
87 static void log_maybe(u_int*, const char*, ...) NTP_PRINTF(2, 3);
88
89 typedef struct keydata KeyDataT;
90 struct keydata {
91 KeyDataT *next; /* queue/stack link */
92 KeyAccT *keyacclist; /* key access list */
93 keyid_t keyid; /* stored key ID */
94 u_short keytype; /* stored key type */
95 u_short seclen; /* length of secret */
96 u_char secbuf[1]; /* begin of secret (formal only)*/
97 };
98
99 static void
log_maybe(u_int * pnerr,const char * fmt,...)100 log_maybe(
101 u_int *pnerr,
102 const char *fmt ,
103 ...)
104 {
105 va_list ap;
106 if ((NULL == pnerr) || (++(*pnerr) <= nerr_loglimit)) {
107 va_start(ap, fmt);
108 mvsyslog(LOG_ERR, fmt, ap);
109 va_end(ap);
110 }
111 }
112
113 static void
free_keydata(KeyDataT * node)114 free_keydata(
115 KeyDataT *node
116 )
117 {
118 KeyAccT *kap;
119
120 if (node) {
121 while (node->keyacclist) {
122 kap = node->keyacclist;
123 node->keyacclist = kap->next;
124 free(kap);
125 }
126
127 /* purge secrets from memory before free()ing it */
128 memset(node, 0, sizeof(*node) + node->seclen);
129 free(node);
130 }
131 }
132
133 /*
134 * authreadkeys - (re)read keys from a file.
135 */
136 int
authreadkeys(const char * file)137 authreadkeys(
138 const char *file
139 )
140 {
141 FILE *fp;
142 char *line;
143 char *token;
144 keyid_t keyno;
145 int keytype;
146 char buf[512]; /* lots of room for line */
147 u_char keystr[AUTHPWD_MAXSECLEN];
148 size_t len;
149 u_int nerr;
150 KeyDataT *list = NULL;
151 KeyDataT *next = NULL;
152
153 /*
154 * Open file. Complain and return if it can't be opened.
155 */
156 fp = fopen(file, "r");
157 if (fp == NULL) {
158 msyslog(LOG_ERR, "authreadkeys: file '%s': %m",
159 file);
160 goto onerror;
161 }
162 INIT_SSL();
163
164 /*
165 * Now read lines from the file, looking for key entries. Put
166 * the data into temporary store for later propagation to avoid
167 * two-pass processing.
168 */
169 nerr = 0;
170 while ((line = fgets(buf, sizeof buf, fp)) != NULL) {
171 if (nerr > nerr_maxlimit)
172 break;
173 token = nexttok(&line);
174 if (token == NULL)
175 continue;
176
177 /*
178 * First is key number. See if it is okay.
179 */
180 keyno = atoi(token);
181 if (keyno < 1) {
182 log_maybe(&nerr,
183 "authreadkeys: cannot change key %s",
184 token);
185 continue;
186 }
187
188 if (keyno > NTP_MAXKEY) {
189 log_maybe(&nerr,
190 "authreadkeys: key %s > %d reserved for Autokey",
191 token, NTP_MAXKEY);
192 continue;
193 }
194
195 /*
196 * Next is keytype. See if that is all right.
197 */
198 token = nexttok(&line);
199 if (token == NULL) {
200 log_maybe(&nerr,
201 "authreadkeys: no key type for key %d",
202 keyno);
203 continue;
204 }
205
206 /* We want to silently ignore keys where we do not
207 * support the requested digest type. OTOH, we want to
208 * make sure the file is well-formed. That means we
209 * have to process the line completely and have to
210 * finally throw away the result... This is a bit more
211 * work, but it also results in better error detection.
212 */
213 #ifdef OPENSSL
214 /*
215 * The key type is the NID used by the message digest
216 * algorithm. There are a number of inconsistencies in
217 * the OpenSSL database. We attempt to discover them
218 * here and prevent use of inconsistent data later.
219 */
220 keytype = keytype_from_text(token, NULL);
221 if (keytype == 0) {
222 log_maybe(NULL,
223 "authreadkeys: unsupported type %s for key %d",
224 token, keyno);
225 # ifdef ENABLE_CMAC
226 } else if (NID_cmac != keytype &&
227 EVP_get_digestbynid(keytype) == NULL) {
228 log_maybe(NULL,
229 "authreadkeys: no algorithm for %s key %d",
230 token, keyno);
231 keytype = 0;
232 # endif /* ENABLE_CMAC */
233 }
234 #else /* !OPENSSL follows */
235 /*
236 * The key type is unused, but is required to be 'M' or
237 * 'm' for compatibility.
238 */
239 if (! (toupper(*token) == 'M')) {
240 log_maybe(NULL,
241 "authreadkeys: invalid type for key %d",
242 keyno);
243 keytype = 0;
244 } else {
245 keytype = KEY_TYPE_MD5;
246 }
247 #endif /* !OPENSSL */
248
249 /*
250 * Finally, get key and insert it. If it is longer than 20
251 * characters, it is a binary string encoded in hex;
252 * otherwise, it is a text string of printable ASCII
253 * characters.
254 */
255 token = nexttok(&line);
256 if (token == NULL) {
257 log_maybe(&nerr,
258 "authreadkeys: no key for key %d", keyno);
259 continue;
260 }
261 next = NULL;
262 len = authdecodepw(keystr, sizeof(keystr), token, AUTHPWD_UNSPEC);
263 if (len > sizeof(keystr)) {
264 switch (errno) {
265 case ENOMEM:
266 log_maybe(&nerr,
267 "authreadkeys: passwd too long for key %d",
268 keyno);
269 break;
270 case EINVAL:
271 log_maybe(&nerr,
272 "authreadkeys: passwd has bad char for key %d",
273 keyno);
274 break;
275 #ifdef DEBUG
276 default:
277 log_maybe(&nerr,
278 "authreadkeys: unexpected errno %d for key %d",
279 errno, keyno);
280 break;
281 #endif
282 }
283 continue;
284 }
285 next = emalloc(sizeof(KeyDataT) + len);
286 next->keyacclist = NULL;
287 next->keyid = keyno;
288 next->keytype = keytype;
289 next->seclen = len;
290 memcpy(next->secbuf, keystr, len);
291
292 token = nexttok(&line);
293 if (token != NULL) { /* A comma-separated IP access list */
294 char *tp = token;
295
296 while (tp) {
297 char *i;
298 char *snp; /* subnet text pointer */
299 unsigned int snbits;
300 sockaddr_u addr;
301
302 i = strchr(tp, (int)',');
303 if (i) {
304 *i = '\0';
305 }
306 snp = strchr(tp, (int)'/');
307 if (snp) {
308 char *sp;
309
310 *snp++ = '\0';
311 snbits = 0;
312 sp = snp;
313
314 while (*sp != '\0') {
315 if (!isdigit((unsigned char)*sp))
316 break;
317 if (snbits > 1000)
318 break; /* overflow */
319 snbits = 10 * snbits + (*sp++ - '0'); /* ascii dependent */
320 }
321 if (*sp != '\0') {
322 log_maybe(&nerr,
323 "authreadkeys: Invalid character in subnet specification for <%s/%s> in key %d",
324 sp, snp, keyno);
325 goto nextip;
326 }
327 } else {
328 snbits = UINT_MAX;
329 }
330
331 if (is_ip_address(tp, AF_UNSPEC, &addr)) {
332 /* Make sure that snbits is valid for addr */
333 if ((snbits < UINT_MAX) &&
334 ( (IS_IPV4(&addr) && snbits > 32) ||
335 (IS_IPV6(&addr) && snbits > 128))) {
336 log_maybe(NULL,
337 "authreadkeys: excessive subnet mask <%s/%s> for key %d",
338 tp, snp, keyno);
339 }
340 next->keyacclist = keyacc_new_push(
341 next->keyacclist, &addr, snbits);
342 } else {
343 log_maybe(&nerr,
344 "authreadkeys: invalid IP address <%s> for key %d",
345 tp, keyno);
346 }
347
348 nextip:
349 if (i) {
350 tp = i + 1;
351 } else {
352 tp = 0;
353 }
354 }
355 }
356
357 /* check if this has to be weeded out... */
358 if (0 == keytype) {
359 free_keydata(next);
360 next = NULL;
361 continue;
362 }
363
364 DEBUG_INSIST(NULL != next);
365 #if defined(OPENSSL) && defined(ENABLE_CMAC)
366 if (NID_cmac == keytype && len < 16) {
367 msyslog(LOG_WARNING, CMAC " keys are 128 bits, "
368 "zero-extending key %u by %u bits",
369 (u_int)keyno, 8 * (16 - (u_int)len));
370 }
371 #endif /* OPENSSL && ENABLE_CMAC */
372 next->next = list;
373 list = next;
374 }
375 fclose(fp);
376 if (nerr > 0) {
377 const char * why = "";
378
379 if (nerr > nerr_maxlimit)
380 why = " (emergency break)";
381 msyslog(LOG_ERR,
382 "authreadkeys: rejecting file '%s' after %u error(s)%s",
383 file, nerr, why);
384 goto onerror;
385 }
386
387 /* first remove old file-based keys */
388 auth_delkeys();
389 /* insert the new key material */
390 while (NULL != (next = list)) {
391 list = next->next;
392 MD5auth_setkey(next->keyid, next->keytype,
393 next->secbuf, next->seclen, next->keyacclist);
394 next->keyacclist = NULL; /* consumed by MD5auth_setkey */
395 free_keydata(next);
396 }
397 return (1);
398
399 onerror:
400 /* Mop up temporary storage before bailing out. */
401 while (NULL != (next = list)) {
402 list = next->next;
403 free_keydata(next);
404 }
405 return (0);
406 }
407