authreadkeys.c revision 294904
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 (++(*pnerr) <= nerr_loglimit) {
105		va_start(ap, fmt);
106		mvsyslog(LOG_ERR, fmt, ap);
107		va_end(ap);
108	}
109}
110
111/*
112 * authreadkeys - (re)read keys from a file.
113 */
114int
115authreadkeys(
116	const char *file
117	)
118{
119	FILE	*fp;
120	char	*line;
121	char	*token;
122	keyid_t	keyno;
123	int	keytype;
124	char	buf[512];		/* lots of room for line */
125	u_char	keystr[32];		/* Bug 2537 */
126	size_t	len;
127	size_t	j;
128	u_int   nerr;
129	KeyDataT *list = NULL;
130	KeyDataT *next = NULL;
131	/*
132	 * Open file.  Complain and return if it can't be opened.
133	 */
134	fp = fopen(file, "r");
135	if (fp == NULL) {
136		msyslog(LOG_ERR, "authreadkeys: file '%s': %m",
137		    file);
138		goto onerror;
139	}
140	INIT_SSL();
141
142	/*
143	 * Now read lines from the file, looking for key entries. Put
144	 * the data into temporary store for later propagation to avoid
145	 * two-pass processing.
146	 */
147	nerr = 0;
148	while ((line = fgets(buf, sizeof buf, fp)) != NULL) {
149		if (nerr > nerr_maxlimit)
150			break;
151		token = nexttok(&line);
152		if (token == NULL)
153			continue;
154
155		/*
156		 * First is key number.  See if it is okay.
157		 */
158		keyno = atoi(token);
159		if (keyno == 0) {
160			log_maybe(&nerr,
161				  "authreadkeys: cannot change key %s",
162				  token);
163			continue;
164		}
165
166		if (keyno > NTP_MAXKEY) {
167			log_maybe(&nerr,
168				  "authreadkeys: key %s > %d reserved for Autokey",
169				  token, NTP_MAXKEY);
170			continue;
171		}
172
173		/*
174		 * Next is keytype. See if that is all right.
175		 */
176		token = nexttok(&line);
177		if (token == NULL) {
178			log_maybe(&nerr,
179				  "authreadkeys: no key type for key %d",
180				  keyno);
181			continue;
182		}
183#ifdef OPENSSL
184		/*
185		 * The key type is the NID used by the message digest
186		 * algorithm. There are a number of inconsistencies in
187		 * the OpenSSL database. We attempt to discover them
188		 * here and prevent use of inconsistent data later.
189		 */
190		keytype = keytype_from_text(token, NULL);
191		if (keytype == 0) {
192			log_maybe(&nerr,
193				  "authreadkeys: invalid type for key %d",
194				  keyno);
195			continue;
196		}
197		if (EVP_get_digestbynid(keytype) == NULL) {
198			log_maybe(&nerr,
199				  "authreadkeys: no algorithm for key %d",
200				  keyno);
201			continue;
202		}
203#else	/* !OPENSSL follows */
204
205		/*
206		 * The key type is unused, but is required to be 'M' or
207		 * 'm' for compatibility.
208		 */
209		if (!(*token == 'M' || *token == 'm')) {
210			log_maybe(&nerr,
211				  "authreadkeys: invalid type for key %d",
212				  keyno);
213			continue;
214		}
215		keytype = KEY_TYPE_MD5;
216#endif	/* !OPENSSL */
217
218		/*
219		 * Finally, get key and insert it. If it is longer than 20
220		 * characters, it is a binary string encoded in hex;
221		 * otherwise, it is a text string of printable ASCII
222		 * characters.
223		 */
224		token = nexttok(&line);
225		if (token == NULL) {
226			log_maybe(&nerr,
227				  "authreadkeys: no key for key %d", keyno);
228			continue;
229		}
230		next = NULL;
231		len = strlen(token);
232		if (len <= 20) {	/* Bug 2537 */
233			next = emalloc(sizeof(KeyDataT) + len);
234			next->keyacclist = NULL;
235			next->keyid   = keyno;
236			next->keytype = keytype;
237			next->seclen  = len;
238			memcpy(next->secbuf, token, len);
239		} else {
240			static const char hex[] = "0123456789abcdef";
241			u_char	temp;
242			char	*ptr;
243			size_t	jlim;
244
245			jlim = min(len, 2 * sizeof(keystr));
246			for (j = 0; j < jlim; j++) {
247				ptr = strchr(hex, tolower((unsigned char)token[j]));
248				if (ptr == NULL)
249					break;	/* abort decoding */
250				temp = (u_char)(ptr - hex);
251				if (j & 1)
252					keystr[j / 2] |= temp;
253				else
254					keystr[j / 2] = temp << 4;
255			}
256			if (j < jlim) {
257				log_maybe(&nerr,
258					  "authreadkeys: invalid hex digit for key %d",
259					  keyno);
260				continue;
261			}
262			len = jlim/2; /* hmmmm.... what about odd length?!? */
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, keystr, len);
269		}
270
271		token = nexttok(&line);
272DPRINTF(0, ("authreadkeys: full access list <%s>\n", (token) ? token : "NULL"));
273		if (token != NULL) {	/* A comma-separated IP access list */
274			char *tp = token;
275
276			while (tp) {
277				char *i;
278				KeyAccT ka;
279
280				i = strchr(tp, (int)',');
281				if (i)
282					*i = '\0';
283DPRINTF(0, ("authreadkeys: access list:  <%s>\n", tp));
284
285				if (is_ip_address(tp, AF_UNSPEC, &ka.addr)) {
286					KeyAccT *kap;
287
288					kap = emalloc(sizeof(KeyAccT));
289					memcpy(kap, &ka, sizeof ka);
290					kap->next = next->keyacclist;
291					next->keyacclist = kap;
292				} else {
293					log_maybe(&nerr,
294						  "authreadkeys: invalid IP address <%s> for key %d",
295						  tp, keyno);
296				}
297
298				if (i) {
299					tp = i + 1;
300				} else {
301					tp = 0;
302				}
303			}
304		}
305
306		INSIST(NULL != next);
307		next->next = list;
308		list = next;
309	}
310	fclose(fp);
311	if (nerr > nerr_maxlimit) {
312		msyslog(LOG_ERR,
313			"authreadkeys: rejecting file '%s' after %u errors (emergency break)",
314			file, nerr);
315		goto onerror;
316	}
317	if (nerr > 0) {
318		msyslog(LOG_ERR,
319			"authreadkeys: rejecting file '%s' after %u error(s)",
320			file, nerr);
321		goto onerror;
322	}
323
324	/* first remove old file-based keys */
325	auth_delkeys();
326	/* insert the new key material */
327	while (NULL != (next = list)) {
328		list = next->next;
329		MD5auth_setkey(next->keyid, next->keytype,
330			       next->secbuf, next->seclen, next->keyacclist);
331		/* purge secrets from memory before free()ing it */
332		memset(next, 0, sizeof(*next) + next->seclen);
333		free(next);
334	}
335	return (1);
336
337  onerror:
338	/* Mop up temporary storage before bailing out. */
339	while (NULL != (next = list)) {
340		list = next->next;
341
342		while (next->keyacclist) {
343			KeyAccT *kap = next->keyacclist;
344
345			next->keyacclist = kap->next;
346			free(kap);
347		}
348
349		/* purge secrets from memory before free()ing it */
350		memset(next, 0, sizeof(*next) + next->seclen);
351		free(next);
352	}
353	return (0);
354}
355