/src/ntp-dev/libntp/authreadkeys.c
Line | Count | Source |
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[AUTHPWD_MAXSECLEN]; |
146 | 0 | size_t len; |
147 | 0 | u_int nerr; |
148 | 0 | KeyDataT *list = NULL; |
149 | 0 | KeyDataT *next = NULL; |
150 | | |
151 | | /* |
152 | | * Open file. Complain and return if it can't be opened. |
153 | | */ |
154 | 0 | fp = fopen(file, "r"); |
155 | 0 | if (fp == NULL) { |
156 | 0 | msyslog(LOG_ERR, "authreadkeys: file '%s': %m", |
157 | 0 | file); |
158 | 0 | goto onerror; |
159 | 0 | } |
160 | 0 | INIT_SSL(); |
161 | | |
162 | | /* |
163 | | * Now read lines from the file, looking for key entries. Put |
164 | | * the data into temporary store for later propagation to avoid |
165 | | * two-pass processing. |
166 | | */ |
167 | 0 | nerr = 0; |
168 | 0 | while ((line = fgets(buf, sizeof buf, fp)) != NULL) { |
169 | 0 | if (nerr > nerr_maxlimit) |
170 | 0 | break; |
171 | 0 | token = nexttok(&line); |
172 | 0 | if (token == NULL) |
173 | 0 | continue; |
174 | | |
175 | | /* |
176 | | * First is key number. See if it is okay. |
177 | | */ |
178 | 0 | keyno = atoi(token); |
179 | 0 | if (keyno < 1) { |
180 | 0 | log_maybe(&nerr, |
181 | 0 | "authreadkeys: cannot change key %s", |
182 | 0 | token); |
183 | 0 | continue; |
184 | 0 | } |
185 | | |
186 | 0 | if (keyno > NTP_MAXKEY) { |
187 | 0 | log_maybe(&nerr, |
188 | 0 | "authreadkeys: key %s > %d reserved for Autokey", |
189 | 0 | token, NTP_MAXKEY); |
190 | 0 | continue; |
191 | 0 | } |
192 | | |
193 | | /* |
194 | | * Next is keytype. See if that is all right. |
195 | | */ |
196 | 0 | token = nexttok(&line); |
197 | 0 | if (token == NULL) { |
198 | 0 | log_maybe(&nerr, |
199 | 0 | "authreadkeys: no key type for key %d", |
200 | 0 | keyno); |
201 | 0 | continue; |
202 | 0 | } |
203 | | |
204 | | /* We want to silently ignore keys where we do not |
205 | | * support the requested digest type. OTOH, we want to |
206 | | * make sure the file is well-formed. That means we |
207 | | * have to process the line completely and have to |
208 | | * finally throw away the result... This is a bit more |
209 | | * work, but it also results in better error detection. |
210 | | */ |
211 | 0 | #ifdef OPENSSL |
212 | | /* |
213 | | * The key type is the NID used by the message digest |
214 | | * algorithm. There are a number of inconsistencies in |
215 | | * the OpenSSL database. We attempt to discover them |
216 | | * here and prevent use of inconsistent data later. |
217 | | */ |
218 | 0 | keytype = keytype_from_text(token, NULL); |
219 | 0 | if (keytype == 0) { |
220 | 0 | log_maybe(NULL, |
221 | 0 | "authreadkeys: unsupported type %s for key %d", |
222 | 0 | token, keyno); |
223 | 0 | # ifdef ENABLE_CMAC |
224 | 0 | } else if (NID_cmac != keytype && |
225 | 0 | EVP_get_digestbynid(keytype) == NULL) { |
226 | 0 | log_maybe(NULL, |
227 | 0 | "authreadkeys: no algorithm for %s key %d", |
228 | 0 | token, keyno); |
229 | 0 | keytype = 0; |
230 | 0 | # endif /* ENABLE_CMAC */ |
231 | 0 | } |
232 | | #else /* !OPENSSL follows */ |
233 | | /* |
234 | | * The key type is unused, but is required to be 'M' or |
235 | | * 'm' for compatibility. |
236 | | */ |
237 | | if (! (toupper(*token) == 'M')) { |
238 | | log_maybe(NULL, |
239 | | "authreadkeys: invalid type for key %d", |
240 | | keyno); |
241 | | keytype = 0; |
242 | | } else { |
243 | | keytype = KEY_TYPE_MD5; |
244 | | } |
245 | | #endif /* !OPENSSL */ |
246 | | |
247 | | /* |
248 | | * Finally, get key and insert it. If it is longer than 20 |
249 | | * characters, it is a binary string encoded in hex; |
250 | | * otherwise, it is a text string of printable ASCII |
251 | | * characters. |
252 | | */ |
253 | 0 | token = nexttok(&line); |
254 | 0 | if (token == NULL) { |
255 | 0 | log_maybe(&nerr, |
256 | 0 | "authreadkeys: no key for key %d", keyno); |
257 | 0 | continue; |
258 | 0 | } |
259 | 0 | next = NULL; |
260 | 0 | len = authdecodepw(keystr, sizeof(keystr), token, AUTHPWD_UNSPEC); |
261 | 0 | if (len > sizeof(keystr)) { |
262 | 0 | switch (errno) { |
263 | 0 | case ENOMEM: |
264 | 0 | log_maybe(&nerr, |
265 | 0 | "authreadkeys: passwd too long for key %d", |
266 | 0 | keyno); |
267 | 0 | break; |
268 | 0 | case EINVAL: |
269 | 0 | log_maybe(&nerr, |
270 | 0 | "authreadkeys: passwd has bad char for key %d", |
271 | 0 | keyno); |
272 | 0 | break; |
273 | 0 | #ifdef DEBUG |
274 | 0 | default: |
275 | 0 | log_maybe(&nerr, |
276 | 0 | "authreadkeys: unexpected errno %d for key %d: %m", |
277 | 0 | errno, keyno); |
278 | 0 | break; |
279 | 0 | #endif |
280 | 0 | } |
281 | 0 | continue; |
282 | 0 | } |
283 | 0 | next = emalloc(sizeof(KeyDataT) + len); |
284 | 0 | next->keyacclist = NULL; |
285 | 0 | next->keyid = keyno; |
286 | 0 | next->keytype = keytype; |
287 | 0 | next->seclen = len; |
288 | 0 | memcpy(next->secbuf, keystr, len); |
289 | |
|
290 | 0 | token = nexttok(&line); |
291 | 0 | if (token != NULL) { /* A comma-separated IP access list */ |
292 | 0 | char *tp = token; |
293 | |
|
294 | 0 | while (tp) { |
295 | 0 | char *i; |
296 | 0 | char *snp; /* subnet text pointer */ |
297 | 0 | unsigned int snbits; |
298 | 0 | sockaddr_u addr; |
299 | |
|
300 | 0 | i = strchr(tp, (int)','); |
301 | 0 | if (i) { |
302 | 0 | *i = '\0'; |
303 | 0 | } |
304 | 0 | snp = strchr(tp, (int)'/'); |
305 | 0 | if (snp) { |
306 | 0 | char *sp; |
307 | |
|
308 | 0 | *snp++ = '\0'; |
309 | 0 | snbits = 0; |
310 | 0 | sp = snp; |
311 | |
|
312 | 0 | while (*sp != '\0') { |
313 | 0 | if (!isdigit((unsigned char)*sp)) |
314 | 0 | break; |
315 | 0 | if (snbits > 1000) |
316 | 0 | break; /* overflow */ |
317 | 0 | snbits = 10 * snbits + (*sp++ - '0'); /* ascii dependent */ |
318 | 0 | } |
319 | 0 | if (*sp != '\0') { |
320 | 0 | log_maybe(&nerr, |
321 | 0 | "authreadkeys: Invalid character in subnet specification for <%s/%s> in key %d", |
322 | 0 | sp, snp, keyno); |
323 | 0 | goto nextip; |
324 | 0 | } |
325 | 0 | } else { |
326 | 0 | snbits = UINT_MAX; |
327 | 0 | } |
328 | | |
329 | 0 | if (is_ip_address(tp, AF_UNSPEC, &addr)) { |
330 | | /* Make sure that snbits is valid for addr */ |
331 | 0 | if ((snbits < UINT_MAX) && |
332 | 0 | ( (IS_IPV4(&addr) && snbits > 32) || |
333 | 0 | (IS_IPV6(&addr) && snbits > 128))) { |
334 | 0 | log_maybe(NULL, |
335 | 0 | "authreadkeys: excessive subnet mask <%s/%s> for key %d", |
336 | 0 | tp, snp, keyno); |
337 | 0 | } |
338 | 0 | next->keyacclist = keyacc_new_push( |
339 | 0 | next->keyacclist, &addr, snbits); |
340 | 0 | } else { |
341 | 0 | log_maybe(&nerr, |
342 | 0 | "authreadkeys: invalid IP address <%s> for key %d", |
343 | 0 | tp, keyno); |
344 | 0 | } |
345 | |
|
346 | 0 | nextip: |
347 | 0 | if (i) { |
348 | 0 | tp = i + 1; |
349 | 0 | } else { |
350 | 0 | tp = 0; |
351 | 0 | } |
352 | 0 | } |
353 | 0 | } |
354 | | |
355 | | /* check if this has to be weeded out... */ |
356 | 0 | if (0 == keytype) { |
357 | 0 | free_keydata(next); |
358 | 0 | next = NULL; |
359 | 0 | continue; |
360 | 0 | } |
361 | | |
362 | 0 | DEBUG_INSIST(NULL != next); |
363 | 0 | #if defined(OPENSSL) && defined(ENABLE_CMAC) |
364 | 0 | if (NID_cmac == keytype && len < 16) { |
365 | 0 | msyslog(LOG_WARNING, CMAC " keys are 128 bits, " |
366 | 0 | "zero-extending key %u by %u bits", |
367 | 0 | (u_int)keyno, 8 * (16 - (u_int)len)); |
368 | 0 | } |
369 | 0 | #endif /* OPENSSL && ENABLE_CMAC */ |
370 | 0 | next->next = list; |
371 | 0 | list = next; |
372 | 0 | } |
373 | 0 | fclose(fp); |
374 | 0 | if (nerr > 0) { |
375 | 0 | const char * why = ""; |
376 | |
|
377 | 0 | if (nerr > nerr_maxlimit) |
378 | 0 | why = " (emergency break)"; |
379 | 0 | msyslog(LOG_ERR, |
380 | 0 | "authreadkeys: rejecting file '%s' after %u error(s)%s", |
381 | 0 | file, nerr, why); |
382 | 0 | goto onerror; |
383 | 0 | } |
384 | | |
385 | | /* first remove old file-based keys */ |
386 | 0 | auth_delkeys(); |
387 | | /* insert the new key material */ |
388 | 0 | while (NULL != (next = list)) { |
389 | 0 | list = next->next; |
390 | 0 | MD5auth_setkey(next->keyid, next->keytype, |
391 | 0 | next->secbuf, next->seclen, next->keyacclist); |
392 | 0 | next->keyacclist = NULL; /* consumed by MD5auth_setkey */ |
393 | 0 | free_keydata(next); |
394 | 0 | } |
395 | 0 | return (1); |
396 | | |
397 | 0 | onerror: |
398 | | /* Mop up temporary storage before bailing out. */ |
399 | 0 | while (NULL != (next = list)) { |
400 | 0 | list = next->next; |
401 | 0 | free_keydata(next); |
402 | 0 | } |
403 | 0 | return (0); |
404 | 0 | } |