154359Sroberto/*
254359Sroberto * authreadkeys.c - routines to support the reading of the key file
354359Sroberto */
4285612Sdelphij#include <config.h>
554359Sroberto#include <stdio.h>
654359Sroberto#include <ctype.h>
754359Sroberto
8294904Sdelphij#include "ntpd.h"	/* Only for DPRINTF */
954359Sroberto#include "ntp_fp.h"
1054359Sroberto#include "ntp.h"
1182498Sroberto#include "ntp_syslog.h"
1254359Sroberto#include "ntp_stdlib.h"
13294904Sdelphij#include "ntp_keyacc.h"
1454359Sroberto
15285612Sdelphij#ifdef OPENSSL
16285612Sdelphij#include "openssl/objects.h"
17285612Sdelphij#include "openssl/evp.h"
18285612Sdelphij#endif	/* OPENSSL */
1954359Sroberto
2054359Sroberto/* Forwards */
21285612Sdelphijstatic char *nexttok (char **);
2254359Sroberto
2354359Sroberto/*
2454359Sroberto * nexttok - basic internal tokenizing routine
2554359Sroberto */
2654359Srobertostatic char *
2754359Srobertonexttok(
28285612Sdelphij	char	**str
2954359Sroberto	)
3054359Sroberto{
3154359Sroberto	register char *cp;
3254359Sroberto	char *starttok;
3354359Sroberto
3454359Sroberto	cp = *str;
3554359Sroberto
3654359Sroberto	/*
3754359Sroberto	 * Space past white space
3854359Sroberto	 */
3954359Sroberto	while (*cp == ' ' || *cp == '\t')
40285612Sdelphij		cp++;
4154359Sroberto
4254359Sroberto	/*
4354359Sroberto	 * Save this and space to end of token
4454359Sroberto	 */
4554359Sroberto	starttok = cp;
4654359Sroberto	while (*cp != '\0' && *cp != '\n' && *cp != ' '
4754359Sroberto	       && *cp != '\t' && *cp != '#')
48285612Sdelphij		cp++;
4954359Sroberto
5054359Sroberto	/*
5154359Sroberto	 * If token length is zero return an error, else set end of
5254359Sroberto	 * token to zero and return start.
5354359Sroberto	 */
5454359Sroberto	if (starttok == cp)
55285612Sdelphij		return NULL;
5654359Sroberto
5754359Sroberto	if (*cp == ' ' || *cp == '\t')
58285612Sdelphij		*cp++ = '\0';
5954359Sroberto	else
60285612Sdelphij		*cp = '\0';
6154359Sroberto
6254359Sroberto	*str = cp;
6354359Sroberto	return starttok;
6454359Sroberto}
6554359Sroberto
6654359Sroberto
67289999Sglebius/* TALOS-CAN-0055: possibly DoS attack by setting the key file to the
68289999Sglebius * log file. This is hard to prevent (it would need to check two files
69289999Sglebius * to be the same on the inode level, which will not work so easily with
70289999Sglebius * Windows or VMS) but we can avoid the self-amplification loop: We only
71289999Sglebius * log the first 5 errors, silently ignore the next 10 errors, and give
72289999Sglebius * up when when we have found more than 15 errors.
73289999Sglebius *
74289999Sglebius * This avoids the endless file iteration we will end up with otherwise,
75289999Sglebius * and also avoids overflowing the log file.
76289999Sglebius *
77289999Sglebius * Nevertheless, once this happens, the keys are gone since this would
78289999Sglebius * require a save/swap strategy that is not easy to apply due to the
79289999Sglebius * data on global/static level.
80289999Sglebius */
81289999Sglebius
82293893Sglebiusstatic const u_int nerr_loglimit = 5u;
83293893Sglebiusstatic const u_int nerr_maxlimit = 15;
84289999Sglebius
85293893Sglebiusstatic void log_maybe(u_int*, const char*, ...) NTP_PRINTF(2, 3);
86289999Sglebius
87293893Sglebiustypedef struct keydata KeyDataT;
88293893Sglebiusstruct keydata {
89293893Sglebius	KeyDataT *next;		/* queue/stack link		*/
90294904Sdelphij	KeyAccT  *keyacclist;	/* key access list		*/
91293893Sglebius	keyid_t   keyid;	/* stored key ID		*/
92293893Sglebius	u_short   keytype;	/* stored key type		*/
93293893Sglebius	u_short   seclen;	/* length of secret		*/
94293893Sglebius	u_char    secbuf[1];	/* begin of secret (formal only)*/
95293893Sglebius};
96293893Sglebius
97289999Sglebiusstatic void
98289999Sglebiuslog_maybe(
99293893Sglebius	u_int      *pnerr,
100289999Sglebius	const char *fmt  ,
101289999Sglebius	...)
102289999Sglebius{
103289999Sglebius	va_list ap;
104298770Sdelphij	if ((NULL == pnerr) || (++(*pnerr) <= nerr_loglimit)) {
105289999Sglebius		va_start(ap, fmt);
106289999Sglebius		mvsyslog(LOG_ERR, fmt, ap);
107289999Sglebius		va_end(ap);
108289999Sglebius	}
109289999Sglebius}
110289999Sglebius
111298770Sdelphijstatic void
112298770Sdelphijfree_keydata(
113298770Sdelphij	KeyDataT *node
114298770Sdelphij	)
115298770Sdelphij{
116298770Sdelphij	KeyAccT *kap;
117298770Sdelphij
118298770Sdelphij	if (node) {
119298770Sdelphij		while (node->keyacclist) {
120298770Sdelphij			kap = node->keyacclist;
121298770Sdelphij			node->keyacclist = kap->next;
122298770Sdelphij			free(kap);
123298770Sdelphij		}
124298770Sdelphij
125298770Sdelphij		/* purge secrets from memory before free()ing it */
126298770Sdelphij		memset(node, 0, sizeof(*node) + node->seclen);
127298770Sdelphij		free(node);
128298770Sdelphij	}
129298770Sdelphij}
130298770Sdelphij
13154359Sroberto/*
13254359Sroberto * authreadkeys - (re)read keys from a file.
13354359Sroberto */
13454359Srobertoint
13554359Srobertoauthreadkeys(
13654359Sroberto	const char *file
13754359Sroberto	)
13854359Sroberto{
139285612Sdelphij	FILE	*fp;
140285612Sdelphij	char	*line;
141285612Sdelphij	char	*token;
142285612Sdelphij	keyid_t	keyno;
143285612Sdelphij	int	keytype;
144285612Sdelphij	char	buf[512];		/* lots of room for line */
145285612Sdelphij	u_char	keystr[32];		/* Bug 2537 */
146285612Sdelphij	size_t	len;
147285612Sdelphij	size_t	j;
148293893Sglebius	u_int   nerr;
149293893Sglebius	KeyDataT *list = NULL;
150293893Sglebius	KeyDataT *next = NULL;
15154359Sroberto	/*
15254359Sroberto	 * Open file.  Complain and return if it can't be opened.
15354359Sroberto	 */
15454359Sroberto	fp = fopen(file, "r");
15554359Sroberto	if (fp == NULL) {
156293893Sglebius		msyslog(LOG_ERR, "authreadkeys: file '%s': %m",
157285612Sdelphij		    file);
158293893Sglebius		goto onerror;
15954359Sroberto	}
160285612Sdelphij	INIT_SSL();
16154359Sroberto
16254359Sroberto	/*
163293893Sglebius	 * Now read lines from the file, looking for key entries. Put
164293893Sglebius	 * the data into temporary store for later propagation to avoid
165293893Sglebius	 * two-pass processing.
16654359Sroberto	 */
167289999Sglebius	nerr = 0;
16854359Sroberto	while ((line = fgets(buf, sizeof buf, fp)) != NULL) {
169289999Sglebius		if (nerr > nerr_maxlimit)
170289999Sglebius			break;
17154359Sroberto		token = nexttok(&line);
172285612Sdelphij		if (token == NULL)
173285612Sdelphij			continue;
17454359Sroberto
17554359Sroberto		/*
17654359Sroberto		 * First is key number.  See if it is okay.
17754359Sroberto		 */
17854359Sroberto		keyno = atoi(token);
179298770Sdelphij		if (keyno < 1) {
180289999Sglebius			log_maybe(&nerr,
181289999Sglebius				  "authreadkeys: cannot change key %s",
182289999Sglebius				  token);
18354359Sroberto			continue;
18454359Sroberto		}
18554359Sroberto
18654359Sroberto		if (keyno > NTP_MAXKEY) {
187289999Sglebius			log_maybe(&nerr,
188289999Sglebius				  "authreadkeys: key %s > %d reserved for Autokey",
189289999Sglebius				  token, NTP_MAXKEY);
19054359Sroberto			continue;
19154359Sroberto		}
19254359Sroberto
19354359Sroberto		/*
194285612Sdelphij		 * Next is keytype. See if that is all right.
19554359Sroberto		 */
19654359Sroberto		token = nexttok(&line);
197285612Sdelphij		if (token == NULL) {
198289999Sglebius			log_maybe(&nerr,
199289999Sglebius				  "authreadkeys: no key type for key %d",
200289999Sglebius				  keyno);
20154359Sroberto			continue;
20254359Sroberto		}
203298770Sdelphij
204298770Sdelphij		/* We want to silently ignore keys where we do not
205298770Sdelphij		 * support the requested digest type. OTOH, we want to
206298770Sdelphij		 * make sure the file is well-formed.  That means we
207298770Sdelphij		 * have to process the line completely and have to
208298770Sdelphij		 * finally throw away the result... This is a bit more
209298770Sdelphij		 * work, but it also results in better error detection.
210298770Sdelphij		 */
211285612Sdelphij#ifdef OPENSSL
212285612Sdelphij		/*
213285612Sdelphij		 * The key type is the NID used by the message digest
214285612Sdelphij		 * algorithm. There are a number of inconsistencies in
215285612Sdelphij		 * the OpenSSL database. We attempt to discover them
216285612Sdelphij		 * here and prevent use of inconsistent data later.
217285612Sdelphij		 */
218285612Sdelphij		keytype = keytype_from_text(token, NULL);
219285612Sdelphij		if (keytype == 0) {
220298770Sdelphij			log_maybe(NULL,
221289999Sglebius				  "authreadkeys: invalid type for key %d",
222289999Sglebius				  keyno);
223298770Sdelphij		} else if (EVP_get_digestbynid(keytype) == NULL) {
224298770Sdelphij			log_maybe(NULL,
225289999Sglebius				  "authreadkeys: no algorithm for key %d",
226289999Sglebius				  keyno);
227298770Sdelphij			keytype = 0;
228285612Sdelphij		}
229285612Sdelphij#else	/* !OPENSSL follows */
23054359Sroberto		/*
231285612Sdelphij		 * The key type is unused, but is required to be 'M' or
232285612Sdelphij		 * 'm' for compatibility.
23354359Sroberto		 */
234285612Sdelphij		if (!(*token == 'M' || *token == 'm')) {
235298770Sdelphij			log_maybe(NULL,
236289999Sglebius				  "authreadkeys: invalid type for key %d",
237289999Sglebius				  keyno);
238298770Sdelphij			keytype = 0;
239298770Sdelphij		} else {
240298770Sdelphij			keytype = KEY_TYPE_MD5;
241285612Sdelphij		}
242285612Sdelphij#endif	/* !OPENSSL */
243285612Sdelphij
244285612Sdelphij		/*
245285612Sdelphij		 * Finally, get key and insert it. If it is longer than 20
246285612Sdelphij		 * characters, it is a binary string encoded in hex;
247285612Sdelphij		 * otherwise, it is a text string of printable ASCII
248285612Sdelphij		 * characters.
249285612Sdelphij		 */
25054359Sroberto		token = nexttok(&line);
251285612Sdelphij		if (token == NULL) {
252289999Sglebius			log_maybe(&nerr,
253289999Sglebius				  "authreadkeys: no key for key %d", keyno);
254285612Sdelphij			continue;
255285612Sdelphij		}
256293893Sglebius		next = NULL;
257285612Sdelphij		len = strlen(token);
258285612Sdelphij		if (len <= 20) {	/* Bug 2537 */
259293893Sglebius			next = emalloc(sizeof(KeyDataT) + len);
260294904Sdelphij			next->keyacclist = NULL;
261293893Sglebius			next->keyid   = keyno;
262293893Sglebius			next->keytype = keytype;
263293893Sglebius			next->seclen  = len;
264293893Sglebius			memcpy(next->secbuf, token, len);
26554359Sroberto		} else {
266293893Sglebius			static const char hex[] = "0123456789abcdef";
267285612Sdelphij			u_char	temp;
268285612Sdelphij			char	*ptr;
269285612Sdelphij			size_t	jlim;
270285612Sdelphij
271285612Sdelphij			jlim = min(len, 2 * sizeof(keystr));
272285612Sdelphij			for (j = 0; j < jlim; j++) {
273285612Sdelphij				ptr = strchr(hex, tolower((unsigned char)token[j]));
274285612Sdelphij				if (ptr == NULL)
275285612Sdelphij					break;	/* abort decoding */
276285612Sdelphij				temp = (u_char)(ptr - hex);
277285612Sdelphij				if (j & 1)
278285612Sdelphij					keystr[j / 2] |= temp;
279285612Sdelphij				else
280285612Sdelphij					keystr[j / 2] = temp << 4;
28154359Sroberto			}
282285612Sdelphij			if (j < jlim) {
283289999Sglebius				log_maybe(&nerr,
284289999Sglebius					  "authreadkeys: invalid hex digit for key %d",
285289999Sglebius					  keyno);
286285612Sdelphij				continue;
287285612Sdelphij			}
288293893Sglebius			len = jlim/2; /* hmmmm.... what about odd length?!? */
289293893Sglebius			next = emalloc(sizeof(KeyDataT) + len);
290294904Sdelphij			next->keyacclist = NULL;
291293893Sglebius			next->keyid   = keyno;
292293893Sglebius			next->keytype = keytype;
293293893Sglebius			next->seclen  = len;
294293893Sglebius			memcpy(next->secbuf, keystr, len);
29554359Sroberto		}
296294904Sdelphij
297294904Sdelphij		token = nexttok(&line);
298298770Sdelphij		DPRINTF(0, ("authreadkeys: full access list <%s>\n", (token) ? token : "NULL"));
299294904Sdelphij		if (token != NULL) {	/* A comma-separated IP access list */
300294904Sdelphij			char *tp = token;
301294904Sdelphij
302294904Sdelphij			while (tp) {
303294904Sdelphij				char *i;
304298770Sdelphij				sockaddr_u addr;
305294904Sdelphij
306294904Sdelphij				i = strchr(tp, (int)',');
307294904Sdelphij				if (i)
308294904Sdelphij					*i = '\0';
309298770Sdelphij				DPRINTF(0, ("authreadkeys: access list:  <%s>\n", tp));
310294904Sdelphij
311298770Sdelphij				if (is_ip_address(tp, AF_UNSPEC, &addr)) {
312298770Sdelphij					next->keyacclist = keyacc_new_push(
313298770Sdelphij						next->keyacclist, &addr);
314294904Sdelphij				} else {
315294904Sdelphij					log_maybe(&nerr,
316294904Sdelphij						  "authreadkeys: invalid IP address <%s> for key %d",
317294904Sdelphij						  tp, keyno);
318294904Sdelphij				}
319294904Sdelphij
320294904Sdelphij				if (i) {
321294904Sdelphij					tp = i + 1;
322294904Sdelphij				} else {
323294904Sdelphij					tp = 0;
324294904Sdelphij				}
325294904Sdelphij			}
326294904Sdelphij		}
327294904Sdelphij
328298770Sdelphij		/* check if this has to be weeded out... */
329298770Sdelphij		if (0 == keytype) {
330298770Sdelphij			free_keydata(next);
331298770Sdelphij			next = NULL;
332298770Sdelphij			continue;
333298770Sdelphij		}
334298770Sdelphij
335293893Sglebius		INSIST(NULL != next);
336293893Sglebius		next->next = list;
337293893Sglebius		list = next;
33854359Sroberto	}
339285612Sdelphij	fclose(fp);
340293893Sglebius	if (nerr > 0) {
341298770Sdelphij		const char * why = "";
342298770Sdelphij		if (nerr > nerr_maxlimit)
343298770Sdelphij			why = " (emergency break)";
344289999Sglebius		msyslog(LOG_ERR,
345298770Sdelphij			"authreadkeys: rejecting file '%s' after %u error(s)%s",
346298770Sdelphij			file, nerr, why);
347293893Sglebius		goto onerror;
348289999Sglebius	}
349293893Sglebius
350293893Sglebius	/* first remove old file-based keys */
351293893Sglebius	auth_delkeys();
352293893Sglebius	/* insert the new key material */
353293893Sglebius	while (NULL != (next = list)) {
354293893Sglebius		list = next->next;
355293893Sglebius		MD5auth_setkey(next->keyid, next->keytype,
356294904Sdelphij			       next->secbuf, next->seclen, next->keyacclist);
357298770Sdelphij		next->keyacclist = NULL; /* consumed by MD5auth_setkey */
358298770Sdelphij		free_keydata(next);
359293893Sglebius	}
360285612Sdelphij	return (1);
361293893Sglebius
362293893Sglebius  onerror:
363293893Sglebius	/* Mop up temporary storage before bailing out. */
364293893Sglebius	while (NULL != (next = list)) {
365293893Sglebius		list = next->next;
366298770Sdelphij		free_keydata(next);
367293893Sglebius	}
368293893Sglebius	return (0);
36954359Sroberto}
370