xref: /freebsd/contrib/ntp/libntp/authreadkeys.c (revision 39ee7a7a6bdd1557b1c3532abf60d139798ac88b)
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 */
19 static char *nexttok (char **);
20 
21 /*
22  * nexttok - basic internal tokenizing routine
23  */
24 static char *
25 nexttok(
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 
80 static const size_t nerr_loglimit = 5u;
81 static const size_t nerr_maxlimit = 15;
82 
83 static void log_maybe(size_t*, const char*, ...) NTP_PRINTF(2, 3);
84 
85 static void
86 log_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  */
102 int
103 authreadkeys(
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