authreadkeys.c revision 330106
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 */ 21static char *nexttok (char **); 22 23/* 24 * nexttok - basic internal tokenizing routine 25 */ 26static char * 27nexttok( 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 82static const u_int nerr_loglimit = 5u; 83static const u_int nerr_maxlimit = 15; 84 85static void log_maybe(u_int*, const char*, ...) NTP_PRINTF(2, 3); 86 87typedef struct keydata KeyDataT; 88struct 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 97static void 98log_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 111static void 112free_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 */ 134int 135authreadkeys( 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[32]; /* Bug 2537 */ 146 size_t len; 147 size_t j; 148 u_int nerr; 149 KeyDataT *list = NULL; 150 KeyDataT *next = NULL; 151 152 /* 153 * Open file. Complain and return if it can't be opened. 154 */ 155 fp = fopen(file, "r"); 156 if (fp == NULL) { 157 msyslog(LOG_ERR, "authreadkeys: file '%s': %m", 158 file); 159 goto onerror; 160 } 161 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 nerr = 0; 169 while ((line = fgets(buf, sizeof buf, fp)) != NULL) { 170 if (nerr > nerr_maxlimit) 171 break; 172 token = nexttok(&line); 173 if (token == NULL) 174 continue; 175 176 /* 177 * First is key number. See if it is okay. 178 */ 179 keyno = atoi(token); 180 if (keyno < 1) { 181 log_maybe(&nerr, 182 "authreadkeys: cannot change key %s", 183 token); 184 continue; 185 } 186 187 if (keyno > NTP_MAXKEY) { 188 log_maybe(&nerr, 189 "authreadkeys: key %s > %d reserved for Autokey", 190 token, NTP_MAXKEY); 191 continue; 192 } 193 194 /* 195 * Next is keytype. See if that is all right. 196 */ 197 token = nexttok(&line); 198 if (token == NULL) { 199 log_maybe(&nerr, 200 "authreadkeys: no key type for key %d", 201 keyno); 202 continue; 203 } 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 } else if (NID_cmac != keytype && 225 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 if (token != NULL) { /* A comma-separated IP access list */ 301 char *tp = token; 302 303 while (tp) { 304 char *i; 305 char *snp; /* subnet text pointer */ 306 unsigned int snbits; 307 sockaddr_u addr; 308 309 i = strchr(tp, (int)','); 310 if (i) { 311 *i = '\0'; 312 } 313 snp = strchr(tp, (int)'/'); 314 if (snp) { 315 char *sp; 316 317 *snp++ = '\0'; 318 snbits = 0; 319 sp = snp; 320 321 while (*sp != '\0') { 322 if (!isdigit((unsigned char)*sp)) 323 break; 324 if (snbits > 1000) 325 break; /* overflow */ 326 snbits = 10 * snbits + (*sp++ - '0'); /* ascii dependent */ 327 } 328 if (*sp != '\0') { 329 log_maybe(&nerr, 330 "authreadkeys: Invalid character in subnet specification for <%s/%s> in key %d", 331 sp, snp, keyno); 332 goto nextip; 333 } 334 } else { 335 snbits = UINT_MAX; 336 } 337 338 if (is_ip_address(tp, AF_UNSPEC, &addr)) { 339 /* Make sure that snbits is valid for addr */ 340 if ((snbits < UINT_MAX) && 341 ( (IS_IPV4(&addr) && snbits > 32) || 342 (IS_IPV6(&addr) && snbits > 128))) { 343 log_maybe(NULL, 344 "authreadkeys: excessive subnet mask <%s/%s> for key %d", 345 tp, snp, keyno); 346 } 347 next->keyacclist = keyacc_new_push( 348 next->keyacclist, &addr, snbits); 349 } else { 350 log_maybe(&nerr, 351 "authreadkeys: invalid IP address <%s> for key %d", 352 tp, keyno); 353 } 354 355 nextip: 356 if (i) { 357 tp = i + 1; 358 } else { 359 tp = 0; 360 } 361 } 362 } 363 364 /* check if this has to be weeded out... */ 365 if (0 == keytype) { 366 free_keydata(next); 367 next = NULL; 368 continue; 369 } 370 371 INSIST(NULL != next); 372 next->next = list; 373 list = next; 374 } 375 fclose(fp); 376 if (nerr > 0) { 377 const char * why = ""; 378 if (nerr > nerr_maxlimit) 379 why = " (emergency break)"; 380 msyslog(LOG_ERR, 381 "authreadkeys: rejecting file '%s' after %u error(s)%s", 382 file, nerr, why); 383 goto onerror; 384 } 385 386 /* first remove old file-based keys */ 387 auth_delkeys(); 388 /* insert the new key material */ 389 while (NULL != (next = list)) { 390 list = next->next; 391 MD5auth_setkey(next->keyid, next->keytype, 392 next->secbuf, next->seclen, next->keyacclist); 393 next->keyacclist = NULL; /* consumed by MD5auth_setkey */ 394 free_keydata(next); 395 } 396 return (1); 397 398 onerror: 399 /* Mop up temporary storage before bailing out. */ 400 while (NULL != (next = list)) { 401 list = next->next; 402 free_keydata(next); 403 } 404 return (0); 405} 406