authreadkeys.c revision 294905
11834Swollman/* 21834Swollman * authreadkeys.c - routines to support the reading of the key file 31834Swollman */ 41834Swollman#include <config.h> 51834Swollman#include <stdio.h> 61834Swollman#include <ctype.h> 71834Swollman 81834Swollman#include "ntpd.h" /* Only for DPRINTF */ 91834Swollman#include "ntp_fp.h" 101834Swollman#include "ntp.h" 111834Swollman#include "ntp_syslog.h" 121834Swollman#include "ntp_stdlib.h" 131834Swollman#include "ntp_keyacc.h" 141834Swollman 151834Swollman#ifdef OPENSSL 161834Swollman#include "openssl/objects.h" 171834Swollman#include "openssl/evp.h" 181834Swollman#endif /* OPENSSL */ 191834Swollman 201834Swollman/* Forwards */ 211834Swollmanstatic char *nexttok (char **); 221834Swollman 231834Swollman/* 241834Swollman * nexttok - basic internal tokenizing routine 251834Swollman */ 261834Swollmanstatic char * 271834Swollmannexttok( 281834Swollman char **str 291834Swollman ) 301834Swollman{ 311834Swollman register char *cp; 321834Swollman char *starttok; 331834Swollman 3450477Speter cp = *str; 351834Swollman 361834Swollman /* 371834Swollman * Space past white space 381834Swollman */ 391834Swollman while (*cp == ' ' || *cp == '\t') 401834Swollman cp++; 411862Swollman 421862Swollman /* 431834Swollman * Save this and space to end of token 441834Swollman */ 451834Swollman starttok = cp; 461834Swollman while (*cp != '\0' && *cp != '\n' && *cp != ' ' 471834Swollman && *cp != '\t' && *cp != '#') 481834Swollman cp++; 491834Swollman 501834Swollman /* 511834Swollman * If token length is zero return an error, else set end of 521834Swollman * token to zero and return start. 531834Swollman */ 541834Swollman if (starttok == cp) 5513765Smpp return NULL; 561834Swollman 571834Swollman if (*cp == ' ' || *cp == '\t') 5813765Smpp *cp++ = '\0'; 591834Swollman else 601834Swollman *cp = '\0'; 6113765Smpp 621834Swollman *str = cp; 631834Swollman return starttok; 641834Swollman} 651834Swollman 661834Swollman 671834Swollman/* TALOS-CAN-0055: possibly DoS attack by setting the key file to the 681834Swollman * log file. This is hard to prevent (it would need to check two files 691834Swollman * to be the same on the inode level, which will not work so easily with 701834Swollman * Windows or VMS) but we can avoid the self-amplification loop: We only 711834Swollman * log the first 5 errors, silently ignore the next 10 errors, and give 721834Swollman * up when when we have found more than 15 errors. 731834Swollman * 741834Swollman * This avoids the endless file iteration we will end up with otherwise, 7549081Scracauer * and also avoids overflowing the log file. 761834Swollman * 771834Swollman * Nevertheless, once this happens, the keys are gone since this would 781834Swollman * require a save/swap strategy that is not easy to apply due to the 791834Swollman * data on global/static level. 801834Swollman */ 811834Swollman 821834Swollmanstatic const u_int nerr_loglimit = 5u; 831834Swollmanstatic const u_int nerr_maxlimit = 15; 841834Swollman 851834Swollmanstatic void log_maybe(u_int*, const char*, ...) NTP_PRINTF(2, 3); 861834Swollman 871834Swollmantypedef struct keydata KeyDataT; 881834Swollmanstruct keydata { 891834Swollman KeyDataT *next; /* queue/stack link */ 901834Swollman KeyAccT *keyacclist; /* key access list */ 911834Swollman keyid_t keyid; /* stored key ID */ 921834Swollman u_short keytype; /* stored key type */ 931834Swollman u_short seclen; /* length of secret */ 941834Swollman u_char secbuf[1]; /* begin of secret (formal only)*/ 951834Swollman}; 961834Swollman 971834Swollmanstatic void 981834Swollmanlog_maybe( 991834Swollman u_int *pnerr, 1001834Swollman const char *fmt , 101109520Smarcel ...) 102109520Smarcel{ 103109520Smarcel va_list ap; 104109520Smarcel if (++(*pnerr) <= nerr_loglimit) { 105109520Smarcel va_start(ap, fmt); 106109520Smarcel mvsyslog(LOG_ERR, fmt, ap); 107109520Smarcel va_end(ap); 108109520Smarcel } 109109520Smarcel} 110109520Smarcel 111109520Smarcel/* 112109520Smarcel * authreadkeys - (re)read keys from a file. 113109520Smarcel */ 114109520Smarcelint 115109520Smarcelauthreadkeys( 116109520Smarcel const char *file 117109520Smarcel ) 118109520Smarcel{ 119109520Smarcel FILE *fp; 120109520Smarcel char *line; 121109520Smarcel char *token; 122109520Smarcel keyid_t keyno; 123109520Smarcel int keytype; 124109520Smarcel char buf[512]; /* lots of room for line */ 125109520Smarcel u_char keystr[32]; /* Bug 2537 */ 126109520Smarcel size_t len; 127109520Smarcel size_t j; 128109520Smarcel u_int nerr; 129109520Smarcel KeyDataT *list = NULL; 130109520Smarcel KeyDataT *next = NULL; 131109520Smarcel /* 132109520Smarcel * Open file. Complain and return if it can't be opened. 133109520Smarcel */ 134109520Smarcel fp = fopen(file, "r"); 135109520Smarcel if (fp == NULL) { 136109520Smarcel msyslog(LOG_ERR, "authreadkeys: file '%s': %m", 137109520Smarcel file); 138109520Smarcel goto onerror; 139109520Smarcel } 140109520Smarcel INIT_SSL(); 141109520Smarcel 142109520Smarcel /* 143109520Smarcel * Now read lines from the file, looking for key entries. Put 144109520Smarcel * the data into temporary store for later propagation to avoid 145109520Smarcel * two-pass processing. 146109520Smarcel */ 147109520Smarcel nerr = 0; 148109520Smarcel while ((line = fgets(buf, sizeof buf, fp)) != NULL) { 149109520Smarcel if (nerr > nerr_maxlimit) 150109520Smarcel break; 151109520Smarcel token = nexttok(&line); 152109520Smarcel if (token == NULL) 153109520Smarcel continue; 154109520Smarcel 155109520Smarcel /* 156109520Smarcel * First is key number. See if it is okay. 157109520Smarcel */ 158109520Smarcel keyno = atoi(token); 159109520Smarcel if (keyno == 0) { 160109520Smarcel log_maybe(&nerr, 161109520Smarcel "authreadkeys: cannot change key %s", 162109520Smarcel token); 163109520Smarcel continue; 164109520Smarcel } 165109520Smarcel 166109520Smarcel if (keyno > NTP_MAXKEY) { 167109520Smarcel log_maybe(&nerr, 168109520Smarcel "authreadkeys: key %s > %d reserved for Autokey", 169109520Smarcel token, NTP_MAXKEY); 170109520Smarcel continue; 171109520Smarcel } 172109520Smarcel 173109520Smarcel /* 174109520Smarcel * Next is keytype. See if that is all right. 175109520Smarcel */ 176109520Smarcel token = nexttok(&line); 177109520Smarcel if (token == NULL) { 178109520Smarcel log_maybe(&nerr, 179109520Smarcel "authreadkeys: no key type for key %d", 180109520Smarcel keyno); 1811862Swollman continue; 182 } 183#ifdef OPENSSL 184 /* 185 * The key type is the NID used by the message digest 186 * algorithm. There are a number of inconsistencies in 187 * the OpenSSL database. We attempt to discover them 188 * here and prevent use of inconsistent data later. 189 */ 190 keytype = keytype_from_text(token, NULL); 191 if (keytype == 0) { 192 log_maybe(&nerr, 193 "authreadkeys: invalid type for key %d", 194 keyno); 195 continue; 196 } 197 if (EVP_get_digestbynid(keytype) == NULL) { 198 log_maybe(&nerr, 199 "authreadkeys: no algorithm for key %d", 200 keyno); 201 continue; 202 } 203#else /* !OPENSSL follows */ 204 205 /* 206 * The key type is unused, but is required to be 'M' or 207 * 'm' for compatibility. 208 */ 209 if (!(*token == 'M' || *token == 'm')) { 210 log_maybe(&nerr, 211 "authreadkeys: invalid type for key %d", 212 keyno); 213 continue; 214 } 215 keytype = KEY_TYPE_MD5; 216#endif /* !OPENSSL */ 217 218 /* 219 * Finally, get key and insert it. If it is longer than 20 220 * characters, it is a binary string encoded in hex; 221 * otherwise, it is a text string of printable ASCII 222 * characters. 223 */ 224 token = nexttok(&line); 225 if (token == NULL) { 226 log_maybe(&nerr, 227 "authreadkeys: no key for key %d", keyno); 228 continue; 229 } 230 next = NULL; 231 len = strlen(token); 232 if (len <= 20) { /* Bug 2537 */ 233 next = emalloc(sizeof(KeyDataT) + len); 234 next->keyacclist = NULL; 235 next->keyid = keyno; 236 next->keytype = keytype; 237 next->seclen = len; 238 memcpy(next->secbuf, token, len); 239 } else { 240 static const char hex[] = "0123456789abcdef"; 241 u_char temp; 242 char *ptr; 243 size_t jlim; 244 245 jlim = min(len, 2 * sizeof(keystr)); 246 for (j = 0; j < jlim; j++) { 247 ptr = strchr(hex, tolower((unsigned char)token[j])); 248 if (ptr == NULL) 249 break; /* abort decoding */ 250 temp = (u_char)(ptr - hex); 251 if (j & 1) 252 keystr[j / 2] |= temp; 253 else 254 keystr[j / 2] = temp << 4; 255 } 256 if (j < jlim) { 257 log_maybe(&nerr, 258 "authreadkeys: invalid hex digit for key %d", 259 keyno); 260 continue; 261 } 262 len = jlim/2; /* hmmmm.... what about odd length?!? */ 263 next = emalloc(sizeof(KeyDataT) + len); 264 next->keyacclist = NULL; 265 next->keyid = keyno; 266 next->keytype = keytype; 267 next->seclen = len; 268 memcpy(next->secbuf, keystr, len); 269 } 270 271 token = nexttok(&line); 272DPRINTF(0, ("authreadkeys: full access list <%s>\n", (token) ? token : "NULL")); 273 if (token != NULL) { /* A comma-separated IP access list */ 274 char *tp = token; 275 276 while (tp) { 277 char *i; 278 KeyAccT ka; 279 280 i = strchr(tp, (int)','); 281 if (i) 282 *i = '\0'; 283DPRINTF(0, ("authreadkeys: access list: <%s>\n", tp)); 284 285 if (is_ip_address(tp, AF_UNSPEC, &ka.addr)) { 286 KeyAccT *kap; 287 288 kap = emalloc(sizeof(KeyAccT)); 289 memcpy(kap, &ka, sizeof ka); 290 kap->next = next->keyacclist; 291 next->keyacclist = kap; 292 } else { 293 log_maybe(&nerr, 294 "authreadkeys: invalid IP address <%s> for key %d", 295 tp, keyno); 296 } 297 298 if (i) { 299 tp = i + 1; 300 } else { 301 tp = 0; 302 } 303 } 304 } 305 306 INSIST(NULL != next); 307 next->next = list; 308 list = next; 309 } 310 fclose(fp); 311 if (nerr > nerr_maxlimit) { 312 msyslog(LOG_ERR, 313 "authreadkeys: rejecting file '%s' after %u errors (emergency break)", 314 file, nerr); 315 goto onerror; 316 } 317 if (nerr > 0) { 318 msyslog(LOG_ERR, 319 "authreadkeys: rejecting file '%s' after %u error(s)", 320 file, nerr); 321 goto onerror; 322 } 323 324 /* first remove old file-based keys */ 325 auth_delkeys(); 326 /* insert the new key material */ 327 while (NULL != (next = list)) { 328 list = next->next; 329 MD5auth_setkey(next->keyid, next->keytype, 330 next->secbuf, next->seclen, next->keyacclist); 331 /* purge secrets from memory before free()ing it */ 332 memset(next, 0, sizeof(*next) + next->seclen); 333 free(next); 334 } 335 return (1); 336 337 onerror: 338 /* Mop up temporary storage before bailing out. */ 339 while (NULL != (next = list)) { 340 list = next->next; 341 342 while (next->keyacclist) { 343 KeyAccT *kap = next->keyacclist; 344 345 next->keyacclist = kap->next; 346 free(kap); 347 } 348 349 /* purge secrets from memory before free()ing it */ 350 memset(next, 0, sizeof(*next) + next->seclen); 351 free(next); 352 } 353 return (0); 354} 355