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