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