authreadkeys.c revision 1.1.1.11 1 /* $NetBSD: authreadkeys.c,v 1.1.1.11 2018/04/07 00:15:47 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 "ntpd.h" /* Only for DPRINTF */
11 //#include "ntp_fp.h"
12 #include "ntp.h"
13 #include "ntp_syslog.h"
14 #include "ntp_stdlib.h"
15 #include "ntp_keyacc.h"
16
17 #ifdef OPENSSL
18 #include "openssl/objects.h"
19 #include "openssl/evp.h"
20 #endif /* OPENSSL */
21
22 /* Forwards */
23 static char *nexttok (char **);
24
25 /*
26 * nexttok - basic internal tokenizing routine
27 */
28 static char *
29 nexttok(
30 char **str
31 )
32 {
33 register char *cp;
34 char *starttok;
35
36 cp = *str;
37
38 /*
39 * Space past white space
40 */
41 while (*cp == ' ' || *cp == '\t')
42 cp++;
43
44 /*
45 * Save this and space to end of token
46 */
47 starttok = cp;
48 while (*cp != '\0' && *cp != '\n' && *cp != ' '
49 && *cp != '\t' && *cp != '#')
50 cp++;
51
52 /*
53 * If token length is zero return an error, else set end of
54 * token to zero and return start.
55 */
56 if (starttok == cp)
57 return NULL;
58
59 if (*cp == ' ' || *cp == '\t')
60 *cp++ = '\0';
61 else
62 *cp = '\0';
63
64 *str = cp;
65 return starttok;
66 }
67
68
69 /* TALOS-CAN-0055: possibly DoS attack by setting the key file to the
70 * log file. This is hard to prevent (it would need to check two files
71 * to be the same on the inode level, which will not work so easily with
72 * Windows or VMS) but we can avoid the self-amplification loop: We only
73 * log the first 5 errors, silently ignore the next 10 errors, and give
74 * up when when we have found more than 15 errors.
75 *
76 * This avoids the endless file iteration we will end up with otherwise,
77 * and also avoids overflowing the log file.
78 *
79 * Nevertheless, once this happens, the keys are gone since this would
80 * require a save/swap strategy that is not easy to apply due to the
81 * data on global/static level.
82 */
83
84 static const u_int nerr_loglimit = 5u;
85 static const u_int nerr_maxlimit = 15;
86
87 static void log_maybe(u_int*, const char*, ...) NTP_PRINTF(2, 3);
88
89 typedef struct keydata KeyDataT;
90 struct keydata {
91 KeyDataT *next; /* queue/stack link */
92 KeyAccT *keyacclist; /* key access list */
93 keyid_t keyid; /* stored key ID */
94 u_short keytype; /* stored key type */
95 u_short seclen; /* length of secret */
96 u_char secbuf[1]; /* begin of secret (formal only)*/
97 };
98
99 static void
100 log_maybe(
101 u_int *pnerr,
102 const char *fmt ,
103 ...)
104 {
105 va_list ap;
106 if ((NULL == pnerr) || (++(*pnerr) <= nerr_loglimit)) {
107 va_start(ap, fmt);
108 mvsyslog(LOG_ERR, fmt, ap);
109 va_end(ap);
110 }
111 }
112
113 static void
114 free_keydata(
115 KeyDataT *node
116 )
117 {
118 KeyAccT *kap;
119
120 if (node) {
121 while (node->keyacclist) {
122 kap = node->keyacclist;
123 node->keyacclist = kap->next;
124 free(kap);
125 }
126
127 /* purge secrets from memory before free()ing it */
128 memset(node, 0, sizeof(*node) + node->seclen);
129 free(node);
130 }
131 }
132
133 /*
134 * authreadkeys - (re)read keys from a file.
135 */
136 int
137 authreadkeys(
138 const char *file
139 )
140 {
141 FILE *fp;
142 char *line;
143 char *token;
144 keyid_t keyno;
145 int keytype;
146 char buf[512]; /* lots of room for line */
147 u_char keystr[32]; /* Bug 2537 */
148 size_t len;
149 size_t j;
150 u_int nerr;
151 KeyDataT *list = NULL;
152 KeyDataT *next = NULL;
153
154 /*
155 * Open file. Complain and return if it can't be opened.
156 */
157 fp = fopen(file, "r");
158 if (fp == NULL) {
159 msyslog(LOG_ERR, "authreadkeys: file '%s': %m",
160 file);
161 goto onerror;
162 }
163 INIT_SSL();
164
165 /*
166 * Now read lines from the file, looking for key entries. Put
167 * the data into temporary store for later propagation to avoid
168 * two-pass processing.
169 */
170 nerr = 0;
171 while ((line = fgets(buf, sizeof buf, fp)) != NULL) {
172 if (nerr > nerr_maxlimit)
173 break;
174 token = nexttok(&line);
175 if (token == NULL)
176 continue;
177
178 /*
179 * First is key number. See if it is okay.
180 */
181 keyno = atoi(token);
182 if (keyno < 1) {
183 log_maybe(&nerr,
184 "authreadkeys: cannot change key %s",
185 token);
186 continue;
187 }
188
189 if (keyno > NTP_MAXKEY) {
190 log_maybe(&nerr,
191 "authreadkeys: key %s > %d reserved for Autokey",
192 token, NTP_MAXKEY);
193 continue;
194 }
195
196 /*
197 * Next is keytype. See if that is all right.
198 */
199 token = nexttok(&line);
200 if (token == NULL) {
201 log_maybe(&nerr,
202 "authreadkeys: no key type for key %d",
203 keyno);
204 continue;
205 }
206
207 /* We want to silently ignore keys where we do not
208 * support the requested digest type. OTOH, we want to
209 * make sure the file is well-formed. That means we
210 * have to process the line completely and have to
211 * finally throw away the result... This is a bit more
212 * work, but it also results in better error detection.
213 */
214 #ifdef OPENSSL
215 /*
216 * The key type is the NID used by the message digest
217 * algorithm. There are a number of inconsistencies in
218 * the OpenSSL database. We attempt to discover them
219 * here and prevent use of inconsistent data later.
220 */
221 keytype = keytype_from_text(token, NULL);
222 if (keytype == 0) {
223 log_maybe(NULL,
224 "authreadkeys: invalid type for key %d",
225 keyno);
226 } else if (NID_cmac != keytype &&
227 EVP_get_digestbynid(keytype) == NULL) {
228 log_maybe(NULL,
229 "authreadkeys: no algorithm for key %d",
230 keyno);
231 keytype = 0;
232 }
233 #else /* !OPENSSL follows */
234 /*
235 * The key type is unused, but is required to be 'M' or
236 * 'm' for compatibility.
237 */
238 if (!(*token == 'M' || *token == 'm')) {
239 log_maybe(NULL,
240 "authreadkeys: invalid type for key %d",
241 keyno);
242 keytype = 0;
243 } else {
244 keytype = KEY_TYPE_MD5;
245 }
246 #endif /* !OPENSSL */
247
248 /*
249 * Finally, get key and insert it. If it is longer than 20
250 * characters, it is a binary string encoded in hex;
251 * otherwise, it is a text string of printable ASCII
252 * characters.
253 */
254 token = nexttok(&line);
255 if (token == NULL) {
256 log_maybe(&nerr,
257 "authreadkeys: no key for key %d", keyno);
258 continue;
259 }
260 next = NULL;
261 len = strlen(token);
262 if (len <= 20) { /* Bug 2537 */
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, token, len);
269 } else {
270 static const char hex[] = "0123456789abcdef";
271 u_char temp;
272 char *ptr;
273 size_t jlim;
274
275 jlim = min(len, 2 * sizeof(keystr));
276 for (j = 0; j < jlim; j++) {
277 ptr = strchr(hex, tolower((unsigned char)token[j]));
278 if (ptr == NULL)
279 break; /* abort decoding */
280 temp = (u_char)(ptr - hex);
281 if (j & 1)
282 keystr[j / 2] |= temp;
283 else
284 keystr[j / 2] = temp << 4;
285 }
286 if (j < jlim) {
287 log_maybe(&nerr,
288 "authreadkeys: invalid hex digit for key %d",
289 keyno);
290 continue;
291 }
292 len = jlim/2; /* hmmmm.... what about odd length?!? */
293 next = emalloc(sizeof(KeyDataT) + len);
294 next->keyacclist = NULL;
295 next->keyid = keyno;
296 next->keytype = keytype;
297 next->seclen = len;
298 memcpy(next->secbuf, keystr, len);
299 }
300
301 token = nexttok(&line);
302 if (token != NULL) { /* A comma-separated IP access list */
303 char *tp = token;
304
305 while (tp) {
306 char *i;
307 char *snp; /* subnet text pointer */
308 unsigned int snbits;
309 sockaddr_u addr;
310
311 i = strchr(tp, (int)',');
312 if (i) {
313 *i = '\0';
314 }
315 snp = strchr(tp, (int)'/');
316 if (snp) {
317 char *sp;
318
319 *snp++ = '\0';
320 snbits = 0;
321 sp = snp;
322
323 while (*sp != '\0') {
324 if (!isdigit((unsigned char)*sp))
325 break;
326 if (snbits > 1000)
327 break; /* overflow */
328 snbits = 10 * snbits + (*sp++ - '0'); /* ascii dependent */
329 }
330 if (*sp != '\0') {
331 log_maybe(&nerr,
332 "authreadkeys: Invalid character in subnet specification for <%s/%s> in key %d",
333 sp, snp, keyno);
334 goto nextip;
335 }
336 } else {
337 snbits = UINT_MAX;
338 }
339
340 if (is_ip_address(tp, AF_UNSPEC, &addr)) {
341 /* Make sure that snbits is valid for addr */
342 if ((snbits < UINT_MAX) &&
343 ( (IS_IPV4(&addr) && snbits > 32) ||
344 (IS_IPV6(&addr) && snbits > 128))) {
345 log_maybe(NULL,
346 "authreadkeys: excessive subnet mask <%s/%s> for key %d",
347 tp, snp, keyno);
348 }
349 next->keyacclist = keyacc_new_push(
350 next->keyacclist, &addr, snbits);
351 } else {
352 log_maybe(&nerr,
353 "authreadkeys: invalid IP address <%s> for key %d",
354 tp, keyno);
355 }
356
357 nextip:
358 if (i) {
359 tp = i + 1;
360 } else {
361 tp = 0;
362 }
363 }
364 }
365
366 /* check if this has to be weeded out... */
367 if (0 == keytype) {
368 free_keydata(next);
369 next = NULL;
370 continue;
371 }
372
373 INSIST(NULL != next);
374 next->next = list;
375 list = next;
376 }
377 fclose(fp);
378 if (nerr > 0) {
379 const char * why = "";
380 if (nerr > nerr_maxlimit)
381 why = " (emergency break)";
382 msyslog(LOG_ERR,
383 "authreadkeys: rejecting file '%s' after %u error(s)%s",
384 file, nerr, why);
385 goto onerror;
386 }
387
388 /* first remove old file-based keys */
389 auth_delkeys();
390 /* insert the new key material */
391 while (NULL != (next = list)) {
392 list = next->next;
393 MD5auth_setkey(next->keyid, next->keytype,
394 next->secbuf, next->seclen, next->keyacclist);
395 next->keyacclist = NULL; /* consumed by MD5auth_setkey */
396 free_keydata(next);
397 }
398 return (1);
399
400 onerror:
401 /* Mop up temporary storage before bailing out. */
402 while (NULL != (next = list)) {
403 list = next->next;
404 free_keydata(next);
405 }
406 return (0);
407 }
408