1 /* $NetBSD: authreadkeys.c,v 1.9 2016/05/01 23:32:00 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[32]; /* Bug 2537 */ 148 size_t len; 149 size_t j; 150 u_int nerr; 151 KeyDataT *list = NULL; 152 KeyDataT *next = NULL; 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: invalid type for key %d", 224 keyno); 225 } else if (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 DPRINTF(0, ("authreadkeys: full access list <%s>\n", (token) ? token : "NULL")); 301 if (token != NULL) { /* A comma-separated IP access list */ 302 char *tp = token; 303 304 while (tp) { 305 char *i; 306 sockaddr_u addr; 307 308 i = strchr(tp, (int)','); 309 if (i) 310 *i = '\0'; 311 DPRINTF(0, ("authreadkeys: access list: <%s>\n", tp)); 312 313 if (is_ip_address(tp, AF_UNSPEC, &addr)) { 314 next->keyacclist = keyacc_new_push( 315 next->keyacclist, &addr); 316 } else { 317 log_maybe(&nerr, 318 "authreadkeys: invalid IP address <%s> for key %d", 319 tp, keyno); 320 } 321 322 if (i) { 323 tp = i + 1; 324 } else { 325 tp = 0; 326 } 327 } 328 } 329 330 /* check if this has to be weeded out... */ 331 if (0 == keytype) { 332 free_keydata(next); 333 next = NULL; 334 continue; 335 } 336 337 INSIST(NULL != next); 338 next->next = list; 339 list = next; 340 } 341 fclose(fp); 342 if (nerr > 0) { 343 const char * why = ""; 344 if (nerr > nerr_maxlimit) 345 why = " (emergency break)"; 346 msyslog(LOG_ERR, 347 "authreadkeys: rejecting file '%s' after %u error(s)%s", 348 file, nerr, why); 349 goto onerror; 350 } 351 352 /* first remove old file-based keys */ 353 auth_delkeys(); 354 /* insert the new key material */ 355 while (NULL != (next = list)) { 356 list = next->next; 357 MD5auth_setkey(next->keyid, next->keytype, 358 next->secbuf, next->seclen, next->keyacclist); 359 next->keyacclist = NULL; /* consumed by MD5auth_setkey */ 360 free_keydata(next); 361 } 362 return (1); 363 364 onerror: 365 /* Mop up temporary storage before bailing out. */ 366 while (NULL != (next = list)) { 367 list = next->next; 368 free_keydata(next); 369 } 370 return (0); 371 } 372