authreadkeys.c revision 294905
11834Swollman/*
21834Swollman * authreadkeys.c - routines to support the reading of the key file
31834Swollman */
41834Swollman#include <config.h>
51834Swollman#include <stdio.h>
61834Swollman#include <ctype.h>
71834Swollman
81834Swollman#include "ntpd.h"	/* Only for DPRINTF */
91834Swollman#include "ntp_fp.h"
101834Swollman#include "ntp.h"
111834Swollman#include "ntp_syslog.h"
121834Swollman#include "ntp_stdlib.h"
131834Swollman#include "ntp_keyacc.h"
141834Swollman
151834Swollman#ifdef OPENSSL
161834Swollman#include "openssl/objects.h"
171834Swollman#include "openssl/evp.h"
181834Swollman#endif	/* OPENSSL */
191834Swollman
201834Swollman/* Forwards */
211834Swollmanstatic char *nexttok (char **);
221834Swollman
231834Swollman/*
241834Swollman * nexttok - basic internal tokenizing routine
251834Swollman */
261834Swollmanstatic char *
271834Swollmannexttok(
281834Swollman	char	**str
291834Swollman	)
301834Swollman{
311834Swollman	register char *cp;
321834Swollman	char *starttok;
331834Swollman
3450477Speter	cp = *str;
351834Swollman
361834Swollman	/*
371834Swollman	 * Space past white space
381834Swollman	 */
391834Swollman	while (*cp == ' ' || *cp == '\t')
401834Swollman		cp++;
411862Swollman
421862Swollman	/*
431834Swollman	 * Save this and space to end of token
441834Swollman	 */
451834Swollman	starttok = cp;
461834Swollman	while (*cp != '\0' && *cp != '\n' && *cp != ' '
471834Swollman	       && *cp != '\t' && *cp != '#')
481834Swollman		cp++;
491834Swollman
501834Swollman	/*
511834Swollman	 * If token length is zero return an error, else set end of
521834Swollman	 * token to zero and return start.
531834Swollman	 */
541834Swollman	if (starttok == cp)
5513765Smpp		return NULL;
561834Swollman
571834Swollman	if (*cp == ' ' || *cp == '\t')
5813765Smpp		*cp++ = '\0';
591834Swollman	else
601834Swollman		*cp = '\0';
6113765Smpp
621834Swollman	*str = cp;
631834Swollman	return starttok;
641834Swollman}
651834Swollman
661834Swollman
671834Swollman/* TALOS-CAN-0055: possibly DoS attack by setting the key file to the
681834Swollman * log file. This is hard to prevent (it would need to check two files
691834Swollman * to be the same on the inode level, which will not work so easily with
701834Swollman * Windows or VMS) but we can avoid the self-amplification loop: We only
711834Swollman * log the first 5 errors, silently ignore the next 10 errors, and give
721834Swollman * up when when we have found more than 15 errors.
731834Swollman *
741834Swollman * This avoids the endless file iteration we will end up with otherwise,
7549081Scracauer * and also avoids overflowing the log file.
761834Swollman *
771834Swollman * Nevertheless, once this happens, the keys are gone since this would
781834Swollman * require a save/swap strategy that is not easy to apply due to the
791834Swollman * data on global/static level.
801834Swollman */
811834Swollman
821834Swollmanstatic const u_int nerr_loglimit = 5u;
831834Swollmanstatic const u_int nerr_maxlimit = 15;
841834Swollman
851834Swollmanstatic void log_maybe(u_int*, const char*, ...) NTP_PRINTF(2, 3);
861834Swollman
871834Swollmantypedef struct keydata KeyDataT;
881834Swollmanstruct keydata {
891834Swollman	KeyDataT *next;		/* queue/stack link		*/
901834Swollman	KeyAccT  *keyacclist;	/* key access list		*/
911834Swollman	keyid_t   keyid;	/* stored key ID		*/
921834Swollman	u_short   keytype;	/* stored key type		*/
931834Swollman	u_short   seclen;	/* length of secret		*/
941834Swollman	u_char    secbuf[1];	/* begin of secret (formal only)*/
951834Swollman};
961834Swollman
971834Swollmanstatic void
981834Swollmanlog_maybe(
991834Swollman	u_int      *pnerr,
1001834Swollman	const char *fmt  ,
101109520Smarcel	...)
102109520Smarcel{
103109520Smarcel	va_list ap;
104109520Smarcel	if (++(*pnerr) <= nerr_loglimit) {
105109520Smarcel		va_start(ap, fmt);
106109520Smarcel		mvsyslog(LOG_ERR, fmt, ap);
107109520Smarcel		va_end(ap);
108109520Smarcel	}
109109520Smarcel}
110109520Smarcel
111109520Smarcel/*
112109520Smarcel * authreadkeys - (re)read keys from a file.
113109520Smarcel */
114109520Smarcelint
115109520Smarcelauthreadkeys(
116109520Smarcel	const char *file
117109520Smarcel	)
118109520Smarcel{
119109520Smarcel	FILE	*fp;
120109520Smarcel	char	*line;
121109520Smarcel	char	*token;
122109520Smarcel	keyid_t	keyno;
123109520Smarcel	int	keytype;
124109520Smarcel	char	buf[512];		/* lots of room for line */
125109520Smarcel	u_char	keystr[32];		/* Bug 2537 */
126109520Smarcel	size_t	len;
127109520Smarcel	size_t	j;
128109520Smarcel	u_int   nerr;
129109520Smarcel	KeyDataT *list = NULL;
130109520Smarcel	KeyDataT *next = NULL;
131109520Smarcel	/*
132109520Smarcel	 * Open file.  Complain and return if it can't be opened.
133109520Smarcel	 */
134109520Smarcel	fp = fopen(file, "r");
135109520Smarcel	if (fp == NULL) {
136109520Smarcel		msyslog(LOG_ERR, "authreadkeys: file '%s': %m",
137109520Smarcel		    file);
138109520Smarcel		goto onerror;
139109520Smarcel	}
140109520Smarcel	INIT_SSL();
141109520Smarcel
142109520Smarcel	/*
143109520Smarcel	 * Now read lines from the file, looking for key entries. Put
144109520Smarcel	 * the data into temporary store for later propagation to avoid
145109520Smarcel	 * two-pass processing.
146109520Smarcel	 */
147109520Smarcel	nerr = 0;
148109520Smarcel	while ((line = fgets(buf, sizeof buf, fp)) != NULL) {
149109520Smarcel		if (nerr > nerr_maxlimit)
150109520Smarcel			break;
151109520Smarcel		token = nexttok(&line);
152109520Smarcel		if (token == NULL)
153109520Smarcel			continue;
154109520Smarcel
155109520Smarcel		/*
156109520Smarcel		 * First is key number.  See if it is okay.
157109520Smarcel		 */
158109520Smarcel		keyno = atoi(token);
159109520Smarcel		if (keyno == 0) {
160109520Smarcel			log_maybe(&nerr,
161109520Smarcel				  "authreadkeys: cannot change key %s",
162109520Smarcel				  token);
163109520Smarcel			continue;
164109520Smarcel		}
165109520Smarcel
166109520Smarcel		if (keyno > NTP_MAXKEY) {
167109520Smarcel			log_maybe(&nerr,
168109520Smarcel				  "authreadkeys: key %s > %d reserved for Autokey",
169109520Smarcel				  token, NTP_MAXKEY);
170109520Smarcel			continue;
171109520Smarcel		}
172109520Smarcel
173109520Smarcel		/*
174109520Smarcel		 * Next is keytype. See if that is all right.
175109520Smarcel		 */
176109520Smarcel		token = nexttok(&line);
177109520Smarcel		if (token == NULL) {
178109520Smarcel			log_maybe(&nerr,
179109520Smarcel				  "authreadkeys: no key type for key %d",
180109520Smarcel				  keyno);
1811862Swollman			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