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