xref: /netbsd-src/external/bsd/ntp/dist/libntp/authreadkeys.c (revision 6cd39ddb8550f6fa1bff3fed32053d7f19fd0453)
1 /*	$NetBSD: authreadkeys.c,v 1.8 2016/01/08 21:35:38 christos Exp $	*/
2 
3 /*
4  * authreadkeys.c - routines to support the reading of the key file
5  */
6 #include <config.h>
7 #include <stdio.h>
8 #include <ctype.h>
9 
10 #include "ntp_fp.h"
11 #include "ntp.h"
12 #include "ntp_syslog.h"
13 #include "ntp_stdlib.h"
14 
15 #ifdef OPENSSL
16 #include "openssl/objects.h"
17 #include "openssl/evp.h"
18 #endif	/* OPENSSL */
19 
20 /* Forwards */
21 static char *nexttok (char **);
22 
23 /*
24  * nexttok - basic internal tokenizing routine
25  */
26 static char *
27 nexttok(
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 
82 static const u_int nerr_loglimit = 5u;
83 static const u_int nerr_maxlimit = 15;
84 
85 static void log_maybe(u_int*, const char*, ...) NTP_PRINTF(2, 3);
86 
87 typedef struct keydata KeyDataT;
88 struct keydata {
89 	KeyDataT *next;		/* queue/stack link		*/
90 	keyid_t   keyid;	/* stored key ID		*/
91 	u_short   keytype;	/* stored key type		*/
92 	u_short   seclen;	/* length of secret		*/
93 	u_char    secbuf[1];	/* begin of secret (formal only)*/
94 };
95 
96 static void
97 log_maybe(
98 	u_int      *pnerr,
99 	const char *fmt  ,
100 	...)
101 {
102 	va_list ap;
103 	if (++(*pnerr) <= nerr_loglimit) {
104 		va_start(ap, fmt);
105 		mvsyslog(LOG_ERR, fmt, ap);
106 		va_end(ap);
107 	}
108 }
109 
110 /*
111  * authreadkeys - (re)read keys from a file.
112  */
113 int
114 authreadkeys(
115 	const char *file
116 	)
117 {
118 	FILE	*fp;
119 	char	*line;
120 	char	*token;
121 	keyid_t	keyno;
122 	int	keytype;
123 	char	buf[512];		/* lots of room for line */
124 	u_char	keystr[32];		/* Bug 2537 */
125 	size_t	len;
126 	size_t	j;
127 	u_int   nerr;
128 	KeyDataT *list = NULL;
129 	KeyDataT *next = NULL;
130 	/*
131 	 * Open file.  Complain and return if it can't be opened.
132 	 */
133 	fp = fopen(file, "r");
134 	if (fp == NULL) {
135 		msyslog(LOG_ERR, "authreadkeys: file '%s': %m",
136 		    file);
137 		goto onerror;
138 	}
139 	INIT_SSL();
140 
141 	/*
142 	 * Now read lines from the file, looking for key entries. Put
143 	 * the data into temporary store for later propagation to avoid
144 	 * two-pass processing.
145 	 */
146 	nerr = 0;
147 	while ((line = fgets(buf, sizeof buf, fp)) != NULL) {
148 		if (nerr > nerr_maxlimit)
149 			break;
150 		token = nexttok(&line);
151 		if (token == NULL)
152 			continue;
153 
154 		/*
155 		 * First is key number.  See if it is okay.
156 		 */
157 		keyno = atoi(token);
158 		if (keyno == 0) {
159 			log_maybe(&nerr,
160 				  "authreadkeys: cannot change key %s",
161 				  token);
162 			continue;
163 		}
164 
165 		if (keyno > NTP_MAXKEY) {
166 			log_maybe(&nerr,
167 				  "authreadkeys: key %s > %d reserved for Autokey",
168 				  token, NTP_MAXKEY);
169 			continue;
170 		}
171 
172 		/*
173 		 * Next is keytype. See if that is all right.
174 		 */
175 		token = nexttok(&line);
176 		if (token == NULL) {
177 			log_maybe(&nerr,
178 				  "authreadkeys: no key type for key %d",
179 				  keyno);
180 			continue;
181 		}
182 #ifdef OPENSSL
183 		/*
184 		 * The key type is the NID used by the message digest
185 		 * algorithm. There are a number of inconsistencies in
186 		 * the OpenSSL database. We attempt to discover them
187 		 * here and prevent use of inconsistent data later.
188 		 */
189 		keytype = keytype_from_text(token, NULL);
190 		if (keytype == 0) {
191 			log_maybe(&nerr,
192 				  "authreadkeys: invalid type for key %d",
193 				  keyno);
194 			continue;
195 		}
196 		if (EVP_get_digestbynid(keytype) == NULL) {
197 			log_maybe(&nerr,
198 				  "authreadkeys: no algorithm for key %d",
199 				  keyno);
200 			continue;
201 		}
202 #else	/* !OPENSSL follows */
203 
204 		/*
205 		 * The key type is unused, but is required to be 'M' or
206 		 * 'm' for compatibility.
207 		 */
208 		if (!(*token == 'M' || *token == 'm')) {
209 			log_maybe(&nerr,
210 				  "authreadkeys: invalid type for key %d",
211 				  keyno);
212 			continue;
213 		}
214 		keytype = KEY_TYPE_MD5;
215 #endif	/* !OPENSSL */
216 
217 		/*
218 		 * Finally, get key and insert it. If it is longer than 20
219 		 * characters, it is a binary string encoded in hex;
220 		 * otherwise, it is a text string of printable ASCII
221 		 * characters.
222 		 */
223 		token = nexttok(&line);
224 		if (token == NULL) {
225 			log_maybe(&nerr,
226 				  "authreadkeys: no key for key %d", keyno);
227 			continue;
228 		}
229 		next = NULL;
230 		len = strlen(token);
231 		if (len <= 20) {	/* Bug 2537 */
232 			next = emalloc(sizeof(KeyDataT) + len);
233 			next->keyid   = keyno;
234 			next->keytype = keytype;
235 			next->seclen  = len;
236 			memcpy(next->secbuf, token, len);
237 		} else {
238 			static const char hex[] = "0123456789abcdef";
239 			u_char	temp;
240 			char	*ptr;
241 			size_t	jlim;
242 
243 			jlim = min(len, 2 * sizeof(keystr));
244 			for (j = 0; j < jlim; j++) {
245 				ptr = strchr(hex, tolower((unsigned char)token[j]));
246 				if (ptr == NULL)
247 					break;	/* abort decoding */
248 				temp = (u_char)(ptr - hex);
249 				if (j & 1)
250 					keystr[j / 2] |= temp;
251 				else
252 					keystr[j / 2] = temp << 4;
253 			}
254 			if (j < jlim) {
255 				log_maybe(&nerr,
256 					  "authreadkeys: invalid hex digit for key %d",
257 					  keyno);
258 				continue;
259 			}
260 			len = jlim/2; /* hmmmm.... what about odd length?!? */
261 			next = emalloc(sizeof(KeyDataT) + len);
262 			next->keyid   = keyno;
263 			next->keytype = keytype;
264 			next->seclen  = len;
265 			memcpy(next->secbuf, keystr, len);
266 		}
267 		INSIST(NULL != next);
268 		next->next = list;
269 		list = next;
270 	}
271 	fclose(fp);
272 	if (nerr > nerr_maxlimit) {
273 		msyslog(LOG_ERR,
274 			"authreadkeys: rejecting file '%s' after %u errors (emergency break)",
275 			file, nerr);
276 		goto onerror;
277 	}
278 	if (nerr > 0) {
279 		msyslog(LOG_ERR,
280 			"authreadkeys: rejecting file '%s' after %u error(s)",
281 			file, nerr);
282 		goto onerror;
283 	}
284 
285 	/* first remove old file-based keys */
286 	auth_delkeys();
287 	/* insert the new key material */
288 	while (NULL != (next = list)) {
289 		list = next->next;
290 		MD5auth_setkey(next->keyid, next->keytype,
291 			       next->secbuf, next->seclen);
292 		/* purge secrets from memory before free()ing it */
293 		memset(next, 0, sizeof(*next) + next->seclen);
294 		free(next);
295 	}
296 	return (1);
297 
298   onerror:
299 	/* Mop up temporary storage before bailing out. */
300 	while (NULL != (next = list)) {
301 		list = next->next;
302 		/* purge secrets from memory before free()ing it */
303 		memset(next, 0, sizeof(*next) + next->seclen);
304 		free(next);
305 	}
306 	return (0);
307 }
308