authreadkeys.c revision 289999
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 size_t nerr_loglimit = 5u;
81static const size_t nerr_maxlimit = 15;
82
83static void log_maybe(size_t*, const char*, ...) NTP_PRINTF(2, 3);
84
85static void
86log_maybe(
87	size_t     *pnerr,
88	const char *fmt  ,
89	...)
90{
91	va_list ap;
92	if (++(*pnerr) <= nerr_loglimit) {
93		va_start(ap, fmt);
94		mvsyslog(LOG_ERR, fmt, ap);
95		va_end(ap);
96	}
97}
98
99/*
100 * authreadkeys - (re)read keys from a file.
101 */
102int
103authreadkeys(
104	const char *file
105	)
106{
107	FILE	*fp;
108	char	*line;
109	char	*token;
110	keyid_t	keyno;
111	int	keytype;
112	char	buf[512];		/* lots of room for line */
113	u_char	keystr[32];		/* Bug 2537 */
114	size_t	len;
115	size_t	j;
116	size_t  nerr;
117	/*
118	 * Open file.  Complain and return if it can't be opened.
119	 */
120	fp = fopen(file, "r");
121	if (fp == NULL) {
122		msyslog(LOG_ERR, "authreadkeys: file %s: %m",
123		    file);
124		return (0);
125	}
126	INIT_SSL();
127
128	/*
129	 * Remove all existing keys
130	 */
131	auth_delkeys();
132
133	/*
134	 * Now read lines from the file, looking for key entries
135	 */
136	nerr = 0;
137	while ((line = fgets(buf, sizeof buf, fp)) != NULL) {
138		if (nerr > nerr_maxlimit)
139			break;
140		token = nexttok(&line);
141		if (token == NULL)
142			continue;
143
144		/*
145		 * First is key number.  See if it is okay.
146		 */
147		keyno = atoi(token);
148		if (keyno == 0) {
149			log_maybe(&nerr,
150				  "authreadkeys: cannot change key %s",
151				  token);
152			continue;
153		}
154
155		if (keyno > NTP_MAXKEY) {
156			log_maybe(&nerr,
157				  "authreadkeys: key %s > %d reserved for Autokey",
158				  token, NTP_MAXKEY);
159			continue;
160		}
161
162		/*
163		 * Next is keytype. See if that is all right.
164		 */
165		token = nexttok(&line);
166		if (token == NULL) {
167			log_maybe(&nerr,
168				  "authreadkeys: no key type for key %d",
169				  keyno);
170			continue;
171		}
172#ifdef OPENSSL
173		/*
174		 * The key type is the NID used by the message digest
175		 * algorithm. There are a number of inconsistencies in
176		 * the OpenSSL database. We attempt to discover them
177		 * here and prevent use of inconsistent data later.
178		 */
179		keytype = keytype_from_text(token, NULL);
180		if (keytype == 0) {
181			log_maybe(&nerr,
182				  "authreadkeys: invalid type for key %d",
183				  keyno);
184			continue;
185		}
186		if (EVP_get_digestbynid(keytype) == NULL) {
187			log_maybe(&nerr,
188				  "authreadkeys: no algorithm for key %d",
189				  keyno);
190			continue;
191		}
192#else	/* !OPENSSL follows */
193
194		/*
195		 * The key type is unused, but is required to be 'M' or
196		 * 'm' for compatibility.
197		 */
198		if (!(*token == 'M' || *token == 'm')) {
199			log_maybe(&nerr,
200				  "authreadkeys: invalid type for key %d",
201				  keyno);
202			continue;
203		}
204		keytype = KEY_TYPE_MD5;
205#endif	/* !OPENSSL */
206
207		/*
208		 * Finally, get key and insert it. If it is longer than 20
209		 * characters, it is a binary string encoded in hex;
210		 * otherwise, it is a text string of printable ASCII
211		 * characters.
212		 */
213		token = nexttok(&line);
214		if (token == NULL) {
215			log_maybe(&nerr,
216				  "authreadkeys: no key for key %d", keyno);
217			continue;
218		}
219		len = strlen(token);
220		if (len <= 20) {	/* Bug 2537 */
221			MD5auth_setkey(keyno, keytype, (u_char *)token, len);
222		} else {
223			char	hex[] = "0123456789abcdef";
224			u_char	temp;
225			char	*ptr;
226			size_t	jlim;
227
228			jlim = min(len, 2 * sizeof(keystr));
229			for (j = 0; j < jlim; j++) {
230				ptr = strchr(hex, tolower((unsigned char)token[j]));
231				if (ptr == NULL)
232					break;	/* abort decoding */
233				temp = (u_char)(ptr - hex);
234				if (j & 1)
235					keystr[j / 2] |= temp;
236				else
237					keystr[j / 2] = temp << 4;
238			}
239			if (j < jlim) {
240				log_maybe(&nerr,
241					  "authreadkeys: invalid hex digit for key %d",
242					  keyno);
243				continue;
244			}
245			MD5auth_setkey(keyno, keytype, keystr, jlim / 2);
246		}
247	}
248	fclose(fp);
249	if (nerr > nerr_maxlimit) {
250		msyslog(LOG_ERR,
251			"authreadkeys: emergency break after %u errors",
252			nerr);
253		return (0);
254	} else if (nerr > nerr_loglimit) {
255		msyslog(LOG_ERR,
256			"authreadkeys: found %u more error(s)",
257			nerr - nerr_loglimit);
258	}
259	return (1);
260}
261