authreadkeys.c revision 293896
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 "ntp_fp.h"
9#include "ntp.h"
10#include "ntp_syslog.h"
11#include "ntp_stdlib.h"
12
13#ifdef OPENSSL
14#include "openssl/objects.h"
15#include "openssl/evp.h"
16#endif	/* OPENSSL */
17
18/* Forwards */
19static char *nexttok (char **);
20
21/*
22 * nexttok - basic internal tokenizing routine
23 */
24static char *
25nexttok(
26	char	**str
27	)
28{
29	register char *cp;
30	char *starttok;
31
32	cp = *str;
33
34	/*
35	 * Space past white space
36	 */
37	while (*cp == ' ' || *cp == '\t')
38		cp++;
39
40	/*
41	 * Save this and space to end of token
42	 */
43	starttok = cp;
44	while (*cp != '\0' && *cp != '\n' && *cp != ' '
45	       && *cp != '\t' && *cp != '#')
46		cp++;
47
48	/*
49	 * If token length is zero return an error, else set end of
50	 * token to zero and return start.
51	 */
52	if (starttok == cp)
53		return NULL;
54
55	if (*cp == ' ' || *cp == '\t')
56		*cp++ = '\0';
57	else
58		*cp = '\0';
59
60	*str = cp;
61	return starttok;
62}
63
64
65/* TALOS-CAN-0055: possibly DoS attack by setting the key file to the
66 * log file. This is hard to prevent (it would need to check two files
67 * to be the same on the inode level, which will not work so easily with
68 * Windows or VMS) but we can avoid the self-amplification loop: We only
69 * log the first 5 errors, silently ignore the next 10 errors, and give
70 * up when when we have found more than 15 errors.
71 *
72 * This avoids the endless file iteration we will end up with otherwise,
73 * and also avoids overflowing the log file.
74 *
75 * Nevertheless, once this happens, the keys are gone since this would
76 * require a save/swap strategy that is not easy to apply due to the
77 * data on global/static level.
78 */
79
80static const u_int nerr_loglimit = 5u;
81static const u_int nerr_maxlimit = 15;
82
83static void log_maybe(u_int*, const char*, ...) NTP_PRINTF(2, 3);
84
85typedef struct keydata KeyDataT;
86struct keydata {
87	KeyDataT *next;		/* queue/stack link		*/
88	keyid_t   keyid;	/* stored key ID		*/
89	u_short   keytype;	/* stored key type		*/
90	u_short   seclen;	/* length of secret		*/
91	u_char    secbuf[1];	/* begin of secret (formal only)*/
92};
93
94static void
95log_maybe(
96	u_int      *pnerr,
97	const char *fmt  ,
98	...)
99{
100	va_list ap;
101	if (++(*pnerr) <= nerr_loglimit) {
102		va_start(ap, fmt);
103		mvsyslog(LOG_ERR, fmt, ap);
104		va_end(ap);
105	}
106}
107
108/*
109 * authreadkeys - (re)read keys from a file.
110 */
111int
112authreadkeys(
113	const char *file
114	)
115{
116	FILE	*fp;
117	char	*line;
118	char	*token;
119	keyid_t	keyno;
120	int	keytype;
121	char	buf[512];		/* lots of room for line */
122	u_char	keystr[32];		/* Bug 2537 */
123	size_t	len;
124	size_t	j;
125	u_int   nerr;
126	KeyDataT *list = NULL;
127	KeyDataT *next = NULL;
128	/*
129	 * Open file.  Complain and return if it can't be opened.
130	 */
131	fp = fopen(file, "r");
132	if (fp == NULL) {
133		msyslog(LOG_ERR, "authreadkeys: file '%s': %m",
134		    file);
135		goto onerror;
136	}
137	INIT_SSL();
138
139	/*
140	 * Now read lines from the file, looking for key entries. Put
141	 * the data into temporary store for later propagation to avoid
142	 * two-pass processing.
143	 */
144	nerr = 0;
145	while ((line = fgets(buf, sizeof buf, fp)) != NULL) {
146		if (nerr > nerr_maxlimit)
147			break;
148		token = nexttok(&line);
149		if (token == NULL)
150			continue;
151
152		/*
153		 * First is key number.  See if it is okay.
154		 */
155		keyno = atoi(token);
156		if (keyno == 0) {
157			log_maybe(&nerr,
158				  "authreadkeys: cannot change key %s",
159				  token);
160			continue;
161		}
162
163		if (keyno > NTP_MAXKEY) {
164			log_maybe(&nerr,
165				  "authreadkeys: key %s > %d reserved for Autokey",
166				  token, NTP_MAXKEY);
167			continue;
168		}
169
170		/*
171		 * Next is keytype. See if that is all right.
172		 */
173		token = nexttok(&line);
174		if (token == NULL) {
175			log_maybe(&nerr,
176				  "authreadkeys: no key type for key %d",
177				  keyno);
178			continue;
179		}
180#ifdef OPENSSL
181		/*
182		 * The key type is the NID used by the message digest
183		 * algorithm. There are a number of inconsistencies in
184		 * the OpenSSL database. We attempt to discover them
185		 * here and prevent use of inconsistent data later.
186		 */
187		keytype = keytype_from_text(token, NULL);
188		if (keytype == 0) {
189			log_maybe(&nerr,
190				  "authreadkeys: invalid type for key %d",
191				  keyno);
192			continue;
193		}
194		if (EVP_get_digestbynid(keytype) == NULL) {
195			log_maybe(&nerr,
196				  "authreadkeys: no algorithm for key %d",
197				  keyno);
198			continue;
199		}
200#else	/* !OPENSSL follows */
201
202		/*
203		 * The key type is unused, but is required to be 'M' or
204		 * 'm' for compatibility.
205		 */
206		if (!(*token == 'M' || *token == 'm')) {
207			log_maybe(&nerr,
208				  "authreadkeys: invalid type for key %d",
209				  keyno);
210			continue;
211		}
212		keytype = KEY_TYPE_MD5;
213#endif	/* !OPENSSL */
214
215		/*
216		 * Finally, get key and insert it. If it is longer than 20
217		 * characters, it is a binary string encoded in hex;
218		 * otherwise, it is a text string of printable ASCII
219		 * characters.
220		 */
221		token = nexttok(&line);
222		if (token == NULL) {
223			log_maybe(&nerr,
224				  "authreadkeys: no key for key %d", keyno);
225			continue;
226		}
227		next = NULL;
228		len = strlen(token);
229		if (len <= 20) {	/* Bug 2537 */
230			next = emalloc(sizeof(KeyDataT) + len);
231			next->keyid   = keyno;
232			next->keytype = keytype;
233			next->seclen  = len;
234			memcpy(next->secbuf, token, len);
235		} else {
236			static const char hex[] = "0123456789abcdef";
237			u_char	temp;
238			char	*ptr;
239			size_t	jlim;
240
241			jlim = min(len, 2 * sizeof(keystr));
242			for (j = 0; j < jlim; j++) {
243				ptr = strchr(hex, tolower((unsigned char)token[j]));
244				if (ptr == NULL)
245					break;	/* abort decoding */
246				temp = (u_char)(ptr - hex);
247				if (j & 1)
248					keystr[j / 2] |= temp;
249				else
250					keystr[j / 2] = temp << 4;
251			}
252			if (j < jlim) {
253				log_maybe(&nerr,
254					  "authreadkeys: invalid hex digit for key %d",
255					  keyno);
256				continue;
257			}
258			len = jlim/2; /* hmmmm.... what about odd length?!? */
259			next = emalloc(sizeof(KeyDataT) + len);
260			next->keyid   = keyno;
261			next->keytype = keytype;
262			next->seclen  = len;
263			memcpy(next->secbuf, keystr, len);
264		}
265		INSIST(NULL != next);
266		next->next = list;
267		list = next;
268	}
269	fclose(fp);
270	if (nerr > nerr_maxlimit) {
271		msyslog(LOG_ERR,
272			"authreadkeys: rejecting file '%s' after %u errors (emergency break)",
273			file, nerr);
274		goto onerror;
275	}
276	if (nerr > 0) {
277		msyslog(LOG_ERR,
278			"authreadkeys: rejecting file '%s' after %u error(s)",
279			file, nerr);
280		goto onerror;
281	}
282
283	/* first remove old file-based keys */
284	auth_delkeys();
285	/* insert the new key material */
286	while (NULL != (next = list)) {
287		list = next->next;
288		MD5auth_setkey(next->keyid, next->keytype,
289			       next->secbuf, next->seclen);
290		/* purge secrets from memory before free()ing it */
291		memset(next, 0, sizeof(*next) + next->seclen);
292		free(next);
293	}
294	return (1);
295
296  onerror:
297	/* Mop up temporary storage before bailing out. */
298	while (NULL != (next = list)) {
299		list = next->next;
300		/* purge secrets from memory before free()ing it */
301		memset(next, 0, sizeof(*next) + next->seclen);
302		free(next);
303	}
304	return (0);
305}
306