1 /* $NetBSD: authreadkeys.c,v 1.12 2024/08/18 20:47:13 christos Exp $ */ 2 3 /* 4 * authreadkeys.c - routines to support the reading of the key file 5 */ 6 #include <config.h> 7 #include <stdio.h> 8 #include <ctype.h> 9 10 //#include "ntpd.h" /* Only for DPRINTF */ 11 //#include "ntp_fp.h" 12 #include "ntp.h" 13 #include "ntp_syslog.h" 14 #include "ntp_stdlib.h" 15 #include "ntp_keyacc.h" 16 17 #ifdef OPENSSL 18 #include "openssl/objects.h" 19 #include "openssl/evp.h" 20 #endif /* OPENSSL */ 21 22 /* Forwards */ 23 static char *nexttok (char **); 24 25 /* 26 * nexttok - basic internal tokenizing routine 27 */ 28 static char * 29 nexttok( 30 char **str 31 ) 32 { 33 register char *cp; 34 char *starttok; 35 36 cp = *str; 37 38 /* 39 * Space past white space 40 */ 41 while (*cp == ' ' || *cp == '\t') 42 cp++; 43 44 /* 45 * Save this and space to end of token 46 */ 47 starttok = cp; 48 while (*cp != '\0' && *cp != '\n' && *cp != ' ' 49 && *cp != '\t' && *cp != '#') 50 cp++; 51 52 /* 53 * If token length is zero return an error, else set end of 54 * token to zero and return start. 55 */ 56 if (starttok == cp) 57 return NULL; 58 59 if (*cp == ' ' || *cp == '\t') 60 *cp++ = '\0'; 61 else 62 *cp = '\0'; 63 64 *str = cp; 65 return starttok; 66 } 67 68 69 /* TALOS-CAN-0055: possibly DoS attack by setting the key file to the 70 * log file. This is hard to prevent (it would need to check two files 71 * to be the same on the inode level, which will not work so easily with 72 * Windows or VMS) but we can avoid the self-amplification loop: We only 73 * log the first 5 errors, silently ignore the next 10 errors, and give 74 * up when when we have found more than 15 errors. 75 * 76 * This avoids the endless file iteration we will end up with otherwise, 77 * and also avoids overflowing the log file. 78 * 79 * Nevertheless, once this happens, the keys are gone since this would 80 * require a save/swap strategy that is not easy to apply due to the 81 * data on global/static level. 82 */ 83 84 static const u_int nerr_loglimit = 5u; 85 static const u_int nerr_maxlimit = 15; 86 87 static void log_maybe(u_int*, const char*, ...) NTP_PRINTF(2, 3); 88 89 typedef struct keydata KeyDataT; 90 struct keydata { 91 KeyDataT *next; /* queue/stack link */ 92 KeyAccT *keyacclist; /* key access list */ 93 keyid_t keyid; /* stored key ID */ 94 u_short keytype; /* stored key type */ 95 u_short seclen; /* length of secret */ 96 u_char secbuf[1]; /* begin of secret (formal only)*/ 97 }; 98 99 static void 100 log_maybe( 101 u_int *pnerr, 102 const char *fmt , 103 ...) 104 { 105 va_list ap; 106 if ((NULL == pnerr) || (++(*pnerr) <= nerr_loglimit)) { 107 va_start(ap, fmt); 108 mvsyslog(LOG_ERR, fmt, ap); 109 va_end(ap); 110 } 111 } 112 113 static void 114 free_keydata( 115 KeyDataT *node 116 ) 117 { 118 KeyAccT *kap; 119 120 if (node) { 121 while (node->keyacclist) { 122 kap = node->keyacclist; 123 node->keyacclist = kap->next; 124 free(kap); 125 } 126 127 /* purge secrets from memory before free()ing it */ 128 memset(node, 0, sizeof(*node) + node->seclen); 129 free(node); 130 } 131 } 132 133 /* 134 * authreadkeys - (re)read keys from a file. 135 */ 136 int 137 authreadkeys( 138 const char *file 139 ) 140 { 141 FILE *fp; 142 char *line; 143 char *token; 144 keyid_t keyno; 145 int keytype; 146 char buf[512]; /* lots of room for line */ 147 u_char keystr[AUTHPWD_MAXSECLEN]; 148 size_t len; 149 u_int nerr; 150 KeyDataT *list = NULL; 151 KeyDataT *next = NULL; 152 153 /* 154 * Open file. Complain and return if it can't be opened. 155 */ 156 fp = fopen(file, "r"); 157 if (fp == NULL) { 158 msyslog(LOG_ERR, "authreadkeys: file '%s': %m", 159 file); 160 goto onerror; 161 } 162 INIT_SSL(); 163 164 /* 165 * Now read lines from the file, looking for key entries. Put 166 * the data into temporary store for later propagation to avoid 167 * two-pass processing. 168 */ 169 nerr = 0; 170 while ((line = fgets(buf, sizeof buf, fp)) != NULL) { 171 if (nerr > nerr_maxlimit) 172 break; 173 token = nexttok(&line); 174 if (token == NULL) 175 continue; 176 177 /* 178 * First is key number. See if it is okay. 179 */ 180 keyno = atoi(token); 181 if (keyno < 1) { 182 log_maybe(&nerr, 183 "authreadkeys: cannot change key %s", 184 token); 185 continue; 186 } 187 188 if (keyno > NTP_MAXKEY) { 189 log_maybe(&nerr, 190 "authreadkeys: key %s > %d reserved for Autokey", 191 token, NTP_MAXKEY); 192 continue; 193 } 194 195 /* 196 * Next is keytype. See if that is all right. 197 */ 198 token = nexttok(&line); 199 if (token == NULL) { 200 log_maybe(&nerr, 201 "authreadkeys: no key type for key %d", 202 keyno); 203 continue; 204 } 205 206 /* We want to silently ignore keys where we do not 207 * support the requested digest type. OTOH, we want to 208 * make sure the file is well-formed. That means we 209 * have to process the line completely and have to 210 * finally throw away the result... This is a bit more 211 * work, but it also results in better error detection. 212 */ 213 #ifdef OPENSSL 214 /* 215 * The key type is the NID used by the message digest 216 * algorithm. There are a number of inconsistencies in 217 * the OpenSSL database. We attempt to discover them 218 * here and prevent use of inconsistent data later. 219 */ 220 keytype = keytype_from_text(token, NULL); 221 if (keytype == 0) { 222 log_maybe(NULL, 223 "authreadkeys: unsupported type %s for key %d", 224 token, keyno); 225 # ifdef ENABLE_CMAC 226 } else if (NID_cmac != keytype && 227 EVP_get_digestbynid(keytype) == NULL) { 228 log_maybe(NULL, 229 "authreadkeys: no algorithm for %s key %d", 230 token, keyno); 231 keytype = 0; 232 # endif /* ENABLE_CMAC */ 233 } 234 #else /* !OPENSSL follows */ 235 /* 236 * The key type is unused, but is required to be 'M' or 237 * 'm' for compatibility. 238 */ 239 if (! (toupper(*token) == 'M')) { 240 log_maybe(NULL, 241 "authreadkeys: invalid type for key %d", 242 keyno); 243 keytype = 0; 244 } else { 245 keytype = KEY_TYPE_MD5; 246 } 247 #endif /* !OPENSSL */ 248 249 /* 250 * Finally, get key and insert it. If it is longer than 20 251 * characters, it is a binary string encoded in hex; 252 * otherwise, it is a text string of printable ASCII 253 * characters. 254 */ 255 token = nexttok(&line); 256 if (token == NULL) { 257 log_maybe(&nerr, 258 "authreadkeys: no key for key %d", keyno); 259 continue; 260 } 261 next = NULL; 262 len = authdecodepw(keystr, sizeof(keystr), token, AUTHPWD_UNSPEC); 263 if (len > sizeof(keystr)) { 264 switch (errno) { 265 case ENOMEM: 266 log_maybe(&nerr, 267 "authreadkeys: passwd too long for key %d", 268 keyno); 269 break; 270 case EINVAL: 271 log_maybe(&nerr, 272 "authreadkeys: passwd has bad char for key %d", 273 keyno); 274 break; 275 #ifdef DEBUG 276 default: 277 log_maybe(&nerr, 278 "authreadkeys: unexpected errno %d for key %d", 279 errno, keyno); 280 break; 281 #endif 282 } 283 continue; 284 } 285 next = emalloc(sizeof(KeyDataT) + len); 286 next->keyacclist = NULL; 287 next->keyid = keyno; 288 next->keytype = keytype; 289 next->seclen = len; 290 memcpy(next->secbuf, keystr, len); 291 292 token = nexttok(&line); 293 if (token != NULL) { /* A comma-separated IP access list */ 294 char *tp = token; 295 296 while (tp) { 297 char *i; 298 char *snp; /* subnet text pointer */ 299 unsigned int snbits; 300 sockaddr_u addr; 301 302 i = strchr(tp, (int)','); 303 if (i) { 304 *i = '\0'; 305 } 306 snp = strchr(tp, (int)'/'); 307 if (snp) { 308 char *sp; 309 310 *snp++ = '\0'; 311 snbits = 0; 312 sp = snp; 313 314 while (*sp != '\0') { 315 if (!isdigit((unsigned char)*sp)) 316 break; 317 if (snbits > 1000) 318 break; /* overflow */ 319 snbits = 10 * snbits + (*sp++ - '0'); /* ascii dependent */ 320 } 321 if (*sp != '\0') { 322 log_maybe(&nerr, 323 "authreadkeys: Invalid character in subnet specification for <%s/%s> in key %d", 324 sp, snp, keyno); 325 goto nextip; 326 } 327 } else { 328 snbits = UINT_MAX; 329 } 330 331 if (is_ip_address(tp, AF_UNSPEC, &addr)) { 332 /* Make sure that snbits is valid for addr */ 333 if ((snbits < UINT_MAX) && 334 ( (IS_IPV4(&addr) && snbits > 32) || 335 (IS_IPV6(&addr) && snbits > 128))) { 336 log_maybe(NULL, 337 "authreadkeys: excessive subnet mask <%s/%s> for key %d", 338 tp, snp, keyno); 339 } 340 next->keyacclist = keyacc_new_push( 341 next->keyacclist, &addr, snbits); 342 } else { 343 log_maybe(&nerr, 344 "authreadkeys: invalid IP address <%s> for key %d", 345 tp, keyno); 346 } 347 348 nextip: 349 if (i) { 350 tp = i + 1; 351 } else { 352 tp = 0; 353 } 354 } 355 } 356 357 /* check if this has to be weeded out... */ 358 if (0 == keytype) { 359 free_keydata(next); 360 next = NULL; 361 continue; 362 } 363 364 DEBUG_INSIST(NULL != next); 365 #if defined(OPENSSL) && defined(ENABLE_CMAC) 366 if (NID_cmac == keytype && len < 16) { 367 msyslog(LOG_WARNING, CMAC " keys are 128 bits, " 368 "zero-extending key %u by %u bits", 369 (u_int)keyno, 8 * (16 - (u_int)len)); 370 } 371 #endif /* OPENSSL && ENABLE_CMAC */ 372 next->next = list; 373 list = next; 374 } 375 fclose(fp); 376 if (nerr > 0) { 377 const char * why = ""; 378 379 if (nerr > nerr_maxlimit) 380 why = " (emergency break)"; 381 msyslog(LOG_ERR, 382 "authreadkeys: rejecting file '%s' after %u error(s)%s", 383 file, nerr, why); 384 goto onerror; 385 } 386 387 /* first remove old file-based keys */ 388 auth_delkeys(); 389 /* insert the new key material */ 390 while (NULL != (next = list)) { 391 list = next->next; 392 MD5auth_setkey(next->keyid, next->keytype, 393 next->secbuf, next->seclen, next->keyacclist); 394 next->keyacclist = NULL; /* consumed by MD5auth_setkey */ 395 free_keydata(next); 396 } 397 return (1); 398 399 onerror: 400 /* Mop up temporary storage before bailing out. */ 401 while (NULL != (next = list)) { 402 list = next->next; 403 free_keydata(next); 404 } 405 return (0); 406 } 407