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 */
21static char *nexttok (char **);
22
23/*
24 * nexttok - basic internal tokenizing routine
25 */
26static char *
27nexttok(
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
82static const u_int nerr_loglimit = 5u;
83static const u_int nerr_maxlimit = 15;
84
85static void log_maybe(u_int*, const char*, ...) NTP_PRINTF(2, 3);
86
87typedef struct keydata KeyDataT;
88struct 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
97static void
98log_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
111static void
112free_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 */
134int
135authreadkeys(
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#  ifdef ENABLE_CMAC
225		} else if (NID_cmac != keytype &&
226				EVP_get_digestbynid(keytype) == NULL) {
227			log_maybe(NULL,
228				  "authreadkeys: no algorithm for key %d",
229				  keyno);
230			keytype = 0;
231#  endif /* ENABLE_CMAC */
232		}
233#else	/* !OPENSSL follows */
234		/*
235		 * The key type is unused, but is required to be 'M' or
236		 * 'm' for compatibility.
237		 */
238		if (!(*token == 'M' || *token == 'm')) {
239			log_maybe(NULL,
240				  "authreadkeys: invalid type for key %d",
241				  keyno);
242			keytype = 0;
243		} else {
244			keytype = KEY_TYPE_MD5;
245		}
246#endif	/* !OPENSSL */
247
248		/*
249		 * Finally, get key and insert it. If it is longer than 20
250		 * characters, it is a binary string encoded in hex;
251		 * otherwise, it is a text string of printable ASCII
252		 * characters.
253		 */
254		token = nexttok(&line);
255		if (token == NULL) {
256			log_maybe(&nerr,
257				  "authreadkeys: no key for key %d", keyno);
258			continue;
259		}
260		next = NULL;
261		len = strlen(token);
262		if (len <= 20) {	/* Bug 2537 */
263			next = emalloc(sizeof(KeyDataT) + len);
264			next->keyacclist = NULL;
265			next->keyid   = keyno;
266			next->keytype = keytype;
267			next->seclen  = len;
268			memcpy(next->secbuf, token, len);
269		} else {
270			static const char hex[] = "0123456789abcdef";
271			u_char	temp;
272			char	*ptr;
273			size_t	jlim;
274
275			jlim = min(len, 2 * sizeof(keystr));
276			for (j = 0; j < jlim; j++) {
277				ptr = strchr(hex, tolower((unsigned char)token[j]));
278				if (ptr == NULL)
279					break;	/* abort decoding */
280				temp = (u_char)(ptr - hex);
281				if (j & 1)
282					keystr[j / 2] |= temp;
283				else
284					keystr[j / 2] = temp << 4;
285			}
286			if (j < jlim) {
287				log_maybe(&nerr,
288					  "authreadkeys: invalid hex digit for key %d",
289					  keyno);
290				continue;
291			}
292			len = jlim/2; /* hmmmm.... what about odd length?!? */
293			next = emalloc(sizeof(KeyDataT) + len);
294			next->keyacclist = NULL;
295			next->keyid   = keyno;
296			next->keytype = keytype;
297			next->seclen  = len;
298			memcpy(next->secbuf, keystr, len);
299		}
300
301		token = nexttok(&line);
302		if (token != NULL) {	/* A comma-separated IP access list */
303			char *tp = token;
304
305			while (tp) {
306				char *i;
307				char *snp;	/* subnet text pointer */
308				unsigned int snbits;
309				sockaddr_u addr;
310
311				i = strchr(tp, (int)',');
312				if (i) {
313					*i = '\0';
314				}
315				snp = strchr(tp, (int)'/');
316				if (snp) {
317					char *sp;
318
319					*snp++ = '\0';
320					snbits = 0;
321					sp = snp;
322
323					while (*sp != '\0') {
324						if (!isdigit((unsigned char)*sp))
325						    break;
326						if (snbits > 1000)
327						    break;	/* overflow */
328						snbits = 10 * snbits + (*sp++ - '0');       /* ascii dependent */
329					}
330					if (*sp != '\0') {
331						log_maybe(&nerr,
332							  "authreadkeys: Invalid character in subnet specification for <%s/%s> in key %d",
333							  sp, snp, keyno);
334						goto nextip;
335					}
336				} else {
337					snbits = UINT_MAX;
338				}
339
340				if (is_ip_address(tp, AF_UNSPEC, &addr)) {
341					/* Make sure that snbits is valid for addr */
342				    if ((snbits < UINT_MAX) &&
343					( (IS_IPV4(&addr) && snbits > 32) ||
344					  (IS_IPV6(&addr) && snbits > 128))) {
345						log_maybe(NULL,
346							  "authreadkeys: excessive subnet mask <%s/%s> for key %d",
347							  tp, snp, keyno);
348				    }
349				    next->keyacclist = keyacc_new_push(
350					next->keyacclist, &addr, snbits);
351				} else {
352					log_maybe(&nerr,
353						  "authreadkeys: invalid IP address <%s> for key %d",
354						  tp, keyno);
355				}
356
357			nextip:
358				if (i) {
359					tp = i + 1;
360				} else {
361					tp = 0;
362				}
363			}
364		}
365
366		/* check if this has to be weeded out... */
367		if (0 == keytype) {
368			free_keydata(next);
369			next = NULL;
370			continue;
371		}
372
373		INSIST(NULL != next);
374		next->next = list;
375		list = next;
376	}
377	fclose(fp);
378	if (nerr > 0) {
379		const char * why = "";
380		if (nerr > nerr_maxlimit)
381			why = " (emergency break)";
382		msyslog(LOG_ERR,
383			"authreadkeys: rejecting file '%s' after %u error(s)%s",
384			file, nerr, why);
385		goto onerror;
386	}
387
388	/* first remove old file-based keys */
389	auth_delkeys();
390	/* insert the new key material */
391	while (NULL != (next = list)) {
392		list = next->next;
393		MD5auth_setkey(next->keyid, next->keytype,
394			       next->secbuf, next->seclen, next->keyacclist);
395		next->keyacclist = NULL; /* consumed by MD5auth_setkey */
396		free_keydata(next);
397	}
398	return (1);
399
400  onerror:
401	/* Mop up temporary storage before bailing out. */
402	while (NULL != (next = list)) {
403		list = next->next;
404		free_keydata(next);
405	}
406	return (0);
407}
408