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 *
nexttok(char ** str)27 nexttok(
28 char **str
29 )
30 {
31 register char *cp;
32 char *starttok;
33
34 cp = *str;
35
36 /*
37 * Space past white space
38 */
39 while (*cp == ' ' || *cp == '\t')
40 cp++;
41
42 /*
43 * Save this and space to end of token
44 */
45 starttok = cp;
46 while (*cp != '\0' && *cp != '\n' && *cp != ' '
47 && *cp != '\t' && *cp != '#')
48 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 if (starttok == cp)
55 return NULL;
56
57 if (*cp == ' ' || *cp == '\t')
58 *cp++ = '\0';
59 else
60 *cp = '\0';
61
62 *str = cp;
63 return starttok;
64 }
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
log_maybe(u_int * pnerr,const char * fmt,...)98 log_maybe(
99 u_int *pnerr,
100 const char *fmt ,
101 ...)
102 {
103 va_list ap;
104 if ((NULL == pnerr) || (++(*pnerr) <= nerr_loglimit)) {
105 va_start(ap, fmt);
106 mvsyslog(LOG_ERR, fmt, ap);
107 va_end(ap);
108 }
109 }
110
111 static void
free_keydata(KeyDataT * node)112 free_keydata(
113 KeyDataT *node
114 )
115 {
116 KeyAccT *kap;
117
118 if (node) {
119 while (node->keyacclist) {
120 kap = node->keyacclist;
121 node->keyacclist = kap->next;
122 free(kap);
123 }
124
125 /* purge secrets from memory before free()ing it */
126 memset(node, 0, sizeof(*node) + node->seclen);
127 free(node);
128 }
129 }
130
131 /*
132 * authreadkeys - (re)read keys from a file.
133 */
134 int
authreadkeys(const char * file)135 authreadkeys(
136 const char *file
137 )
138 {
139 FILE *fp;
140 char *line;
141 char *token;
142 keyid_t keyno;
143 int keytype;
144 char buf[512]; /* lots of room for line */
145 u_char keystr[AUTHPWD_MAXSECLEN];
146 size_t len;
147 u_int nerr;
148 KeyDataT *list = NULL;
149 KeyDataT *next = NULL;
150
151 /*
152 * Open file. Complain and return if it can't be opened.
153 */
154 fp = fopen(file, "r");
155 if (fp == NULL) {
156 msyslog(LOG_ERR, "authreadkeys: file '%s': %m",
157 file);
158 goto onerror;
159 }
160 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 nerr = 0;
168 while ((line = fgets(buf, sizeof buf, fp)) != NULL) {
169 if (nerr > nerr_maxlimit)
170 break;
171 token = nexttok(&line);
172 if (token == NULL)
173 continue;
174
175 /*
176 * First is key number. See if it is okay.
177 */
178 keyno = atoi(token);
179 if (keyno < 1) {
180 log_maybe(&nerr,
181 "authreadkeys: cannot change key %s",
182 token);
183 continue;
184 }
185
186 if (keyno > NTP_MAXKEY) {
187 log_maybe(&nerr,
188 "authreadkeys: key %s > %d reserved for Autokey",
189 token, NTP_MAXKEY);
190 continue;
191 }
192
193 /*
194 * Next is keytype. See if that is all right.
195 */
196 token = nexttok(&line);
197 if (token == NULL) {
198 log_maybe(&nerr,
199 "authreadkeys: no key type for key %d",
200 keyno);
201 continue;
202 }
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 #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 keytype = keytype_from_text(token, NULL);
219 if (keytype == 0) {
220 log_maybe(NULL,
221 "authreadkeys: unsupported type %s for key %d",
222 token, keyno);
223 # ifdef ENABLE_CMAC
224 } else if (NID_cmac != keytype &&
225 EVP_get_digestbynid(keytype) == NULL) {
226 log_maybe(NULL,
227 "authreadkeys: no algorithm for %s key %d",
228 token, keyno);
229 keytype = 0;
230 # endif /* ENABLE_CMAC */
231 }
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 token = nexttok(&line);
254 if (token == NULL) {
255 log_maybe(&nerr,
256 "authreadkeys: no key for key %d", keyno);
257 continue;
258 }
259 next = NULL;
260 len = authdecodepw(keystr, sizeof(keystr), token, AUTHPWD_UNSPEC);
261 if (len > sizeof(keystr)) {
262 switch (errno) {
263 case ENOMEM:
264 log_maybe(&nerr,
265 "authreadkeys: passwd too long for key %d",
266 keyno);
267 break;
268 case EINVAL:
269 log_maybe(&nerr,
270 "authreadkeys: passwd has bad char for key %d",
271 keyno);
272 break;
273 #ifdef DEBUG
274 default:
275 log_maybe(&nerr,
276 "authreadkeys: unexpected errno %d for key %d: %m",
277 errno, keyno);
278 break;
279 #endif
280 }
281 continue;
282 }
283 next = emalloc(sizeof(KeyDataT) + len);
284 next->keyacclist = NULL;
285 next->keyid = keyno;
286 next->keytype = keytype;
287 next->seclen = len;
288 memcpy(next->secbuf, keystr, len);
289
290 token = nexttok(&line);
291 if (token != NULL) { /* A comma-separated IP access list */
292 char *tp = token;
293
294 while (tp) {
295 char *i;
296 char *snp; /* subnet text pointer */
297 unsigned int snbits;
298 sockaddr_u addr;
299
300 i = strchr(tp, (int)',');
301 if (i) {
302 *i = '\0';
303 }
304 snp = strchr(tp, (int)'/');
305 if (snp) {
306 char *sp;
307
308 *snp++ = '\0';
309 snbits = 0;
310 sp = snp;
311
312 while (*sp != '\0') {
313 if (!isdigit((unsigned char)*sp))
314 break;
315 if (snbits > 1000)
316 break; /* overflow */
317 snbits = 10 * snbits + (*sp++ - '0'); /* ascii dependent */
318 }
319 if (*sp != '\0') {
320 log_maybe(&nerr,
321 "authreadkeys: Invalid character in subnet specification for <%s/%s> in key %d",
322 sp, snp, keyno);
323 goto nextip;
324 }
325 } else {
326 snbits = UINT_MAX;
327 }
328
329 if (is_ip_address(tp, AF_UNSPEC, &addr)) {
330 /* Make sure that snbits is valid for addr */
331 if ((snbits < UINT_MAX) &&
332 ( (IS_IPV4(&addr) && snbits > 32) ||
333 (IS_IPV6(&addr) && snbits > 128))) {
334 log_maybe(NULL,
335 "authreadkeys: excessive subnet mask <%s/%s> for key %d",
336 tp, snp, keyno);
337 }
338 next->keyacclist = keyacc_new_push(
339 next->keyacclist, &addr, snbits);
340 } else {
341 log_maybe(&nerr,
342 "authreadkeys: invalid IP address <%s> for key %d",
343 tp, keyno);
344 }
345
346 nextip:
347 if (i) {
348 tp = i + 1;
349 } else {
350 tp = 0;
351 }
352 }
353 }
354
355 /* check if this has to be weeded out... */
356 if (0 == keytype) {
357 free_keydata(next);
358 next = NULL;
359 continue;
360 }
361
362 DEBUG_INSIST(NULL != next);
363 #if defined(OPENSSL) && defined(ENABLE_CMAC)
364 if (NID_cmac == keytype && len < 16) {
365 msyslog(LOG_WARNING, CMAC " keys are 128 bits, "
366 "zero-extending key %u by %u bits",
367 (u_int)keyno, 8 * (16 - (u_int)len));
368 }
369 #endif /* OPENSSL && ENABLE_CMAC */
370 next->next = list;
371 list = next;
372 }
373 fclose(fp);
374 if (nerr > 0) {
375 const char * why = "";
376
377 if (nerr > nerr_maxlimit)
378 why = " (emergency break)";
379 msyslog(LOG_ERR,
380 "authreadkeys: rejecting file '%s' after %u error(s)%s",
381 file, nerr, why);
382 goto onerror;
383 }
384
385 /* first remove old file-based keys */
386 auth_delkeys();
387 /* insert the new key material */
388 while (NULL != (next = list)) {
389 list = next->next;
390 MD5auth_setkey(next->keyid, next->keytype,
391 next->secbuf, next->seclen, next->keyacclist);
392 next->keyacclist = NULL; /* consumed by MD5auth_setkey */
393 free_keydata(next);
394 }
395 return (1);
396
397 onerror:
398 /* Mop up temporary storage before bailing out. */
399 while (NULL != (next = list)) {
400 list = next->next;
401 free_keydata(next);
402 }
403 return (0);
404 }
405