authreadkeys.c revision 1.2.8.3 1 /* $NetBSD: authreadkeys.c,v 1.2.8.3 2016/05/08 22:02:09 snj 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 * Open file. Complain and return if it can't be opened.
155 */
156 fp = fopen(file, "r");
157 if (fp == NULL) {
158 msyslog(LOG_ERR, "authreadkeys: file '%s': %m",
159 file);
160 goto onerror;
161 }
162 INIT_SSL();
163
164 /*
165 * Now read lines from the file, looking for key entries. Put
166 * the data into temporary store for later propagation to avoid
167 * two-pass processing.
168 */
169 nerr = 0;
170 while ((line = fgets(buf, sizeof buf, fp)) != NULL) {
171 if (nerr > nerr_maxlimit)
172 break;
173 token = nexttok(&line);
174 if (token == NULL)
175 continue;
176
177 /*
178 * First is key number. See if it is okay.
179 */
180 keyno = atoi(token);
181 if (keyno < 1) {
182 log_maybe(&nerr,
183 "authreadkeys: cannot change key %s",
184 token);
185 continue;
186 }
187
188 if (keyno > NTP_MAXKEY) {
189 log_maybe(&nerr,
190 "authreadkeys: key %s > %d reserved for Autokey",
191 token, NTP_MAXKEY);
192 continue;
193 }
194
195 /*
196 * Next is keytype. See if that is all right.
197 */
198 token = nexttok(&line);
199 if (token == NULL) {
200 log_maybe(&nerr,
201 "authreadkeys: no key type for key %d",
202 keyno);
203 continue;
204 }
205
206 /* We want to silently ignore keys where we do not
207 * support the requested digest type. OTOH, we want to
208 * make sure the file is well-formed. That means we
209 * have to process the line completely and have to
210 * finally throw away the result... This is a bit more
211 * work, but it also results in better error detection.
212 */
213 #ifdef OPENSSL
214 /*
215 * The key type is the NID used by the message digest
216 * algorithm. There are a number of inconsistencies in
217 * the OpenSSL database. We attempt to discover them
218 * here and prevent use of inconsistent data later.
219 */
220 keytype = keytype_from_text(token, NULL);
221 if (keytype == 0) {
222 log_maybe(NULL,
223 "authreadkeys: invalid type for key %d",
224 keyno);
225 } else if (EVP_get_digestbynid(keytype) == NULL) {
226 log_maybe(NULL,
227 "authreadkeys: no algorithm for key %d",
228 keyno);
229 keytype = 0;
230 }
231 #else /* !OPENSSL follows */
232 /*
233 * The key type is unused, but is required to be 'M' or
234 * 'm' for compatibility.
235 */
236 if (!(*token == 'M' || *token == 'm')) {
237 log_maybe(NULL,
238 "authreadkeys: invalid type for key %d",
239 keyno);
240 keytype = 0;
241 } else {
242 keytype = KEY_TYPE_MD5;
243 }
244 #endif /* !OPENSSL */
245
246 /*
247 * Finally, get key and insert it. If it is longer than 20
248 * characters, it is a binary string encoded in hex;
249 * otherwise, it is a text string of printable ASCII
250 * characters.
251 */
252 token = nexttok(&line);
253 if (token == NULL) {
254 log_maybe(&nerr,
255 "authreadkeys: no key for key %d", keyno);
256 continue;
257 }
258 next = NULL;
259 len = strlen(token);
260 if (len <= 20) { /* Bug 2537 */
261 next = emalloc(sizeof(KeyDataT) + len);
262 next->keyacclist = NULL;
263 next->keyid = keyno;
264 next->keytype = keytype;
265 next->seclen = len;
266 memcpy(next->secbuf, token, len);
267 } else {
268 static const char hex[] = "0123456789abcdef";
269 u_char temp;
270 char *ptr;
271 size_t jlim;
272
273 jlim = min(len, 2 * sizeof(keystr));
274 for (j = 0; j < jlim; j++) {
275 ptr = strchr(hex, tolower((unsigned char)token[j]));
276 if (ptr == NULL)
277 break; /* abort decoding */
278 temp = (u_char)(ptr - hex);
279 if (j & 1)
280 keystr[j / 2] |= temp;
281 else
282 keystr[j / 2] = temp << 4;
283 }
284 if (j < jlim) {
285 log_maybe(&nerr,
286 "authreadkeys: invalid hex digit for key %d",
287 keyno);
288 continue;
289 }
290 len = jlim/2; /* hmmmm.... what about odd length?!? */
291 next = emalloc(sizeof(KeyDataT) + len);
292 next->keyacclist = NULL;
293 next->keyid = keyno;
294 next->keytype = keytype;
295 next->seclen = len;
296 memcpy(next->secbuf, keystr, len);
297 }
298
299 token = nexttok(&line);
300 DPRINTF(0, ("authreadkeys: full access list <%s>\n", (token) ? token : "NULL"));
301 if (token != NULL) { /* A comma-separated IP access list */
302 char *tp = token;
303
304 while (tp) {
305 char *i;
306 sockaddr_u addr;
307
308 i = strchr(tp, (int)',');
309 if (i)
310 *i = '\0';
311 DPRINTF(0, ("authreadkeys: access list: <%s>\n", tp));
312
313 if (is_ip_address(tp, AF_UNSPEC, &addr)) {
314 next->keyacclist = keyacc_new_push(
315 next->keyacclist, &addr);
316 } else {
317 log_maybe(&nerr,
318 "authreadkeys: invalid IP address <%s> for key %d",
319 tp, keyno);
320 }
321
322 if (i) {
323 tp = i + 1;
324 } else {
325 tp = 0;
326 }
327 }
328 }
329
330 /* check if this has to be weeded out... */
331 if (0 == keytype) {
332 free_keydata(next);
333 next = NULL;
334 continue;
335 }
336
337 INSIST(NULL != next);
338 next->next = list;
339 list = next;
340 }
341 fclose(fp);
342 if (nerr > 0) {
343 const char * why = "";
344 if (nerr > nerr_maxlimit)
345 why = " (emergency break)";
346 msyslog(LOG_ERR,
347 "authreadkeys: rejecting file '%s' after %u error(s)%s",
348 file, nerr, why);
349 goto onerror;
350 }
351
352 /* first remove old file-based keys */
353 auth_delkeys();
354 /* insert the new key material */
355 while (NULL != (next = list)) {
356 list = next->next;
357 MD5auth_setkey(next->keyid, next->keytype,
358 next->secbuf, next->seclen, next->keyacclist);
359 next->keyacclist = NULL; /* consumed by MD5auth_setkey */
360 free_keydata(next);
361 }
362 return (1);
363
364 onerror:
365 /* Mop up temporary storage before bailing out. */
366 while (NULL != (next = list)) {
367 list = next->next;
368 free_keydata(next);
369 }
370 return (0);
371 }
372