1 /* $NetBSD: authreadkeys.c,v 1.8 2016/01/08 21:35:38 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 "ntp_fp.h" 11 #include "ntp.h" 12 #include "ntp_syslog.h" 13 #include "ntp_stdlib.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 * 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 keyid_t keyid; /* stored key ID */ 91 u_short keytype; /* stored key type */ 92 u_short seclen; /* length of secret */ 93 u_char secbuf[1]; /* begin of secret (formal only)*/ 94 }; 95 96 static void 97 log_maybe( 98 u_int *pnerr, 99 const char *fmt , 100 ...) 101 { 102 va_list ap; 103 if (++(*pnerr) <= nerr_loglimit) { 104 va_start(ap, fmt); 105 mvsyslog(LOG_ERR, fmt, ap); 106 va_end(ap); 107 } 108 } 109 110 /* 111 * authreadkeys - (re)read keys from a file. 112 */ 113 int 114 authreadkeys( 115 const char *file 116 ) 117 { 118 FILE *fp; 119 char *line; 120 char *token; 121 keyid_t keyno; 122 int keytype; 123 char buf[512]; /* lots of room for line */ 124 u_char keystr[32]; /* Bug 2537 */ 125 size_t len; 126 size_t j; 127 u_int nerr; 128 KeyDataT *list = NULL; 129 KeyDataT *next = NULL; 130 /* 131 * Open file. Complain and return if it can't be opened. 132 */ 133 fp = fopen(file, "r"); 134 if (fp == NULL) { 135 msyslog(LOG_ERR, "authreadkeys: file '%s': %m", 136 file); 137 goto onerror; 138 } 139 INIT_SSL(); 140 141 /* 142 * Now read lines from the file, looking for key entries. Put 143 * the data into temporary store for later propagation to avoid 144 * two-pass processing. 145 */ 146 nerr = 0; 147 while ((line = fgets(buf, sizeof buf, fp)) != NULL) { 148 if (nerr > nerr_maxlimit) 149 break; 150 token = nexttok(&line); 151 if (token == NULL) 152 continue; 153 154 /* 155 * First is key number. See if it is okay. 156 */ 157 keyno = atoi(token); 158 if (keyno == 0) { 159 log_maybe(&nerr, 160 "authreadkeys: cannot change key %s", 161 token); 162 continue; 163 } 164 165 if (keyno > NTP_MAXKEY) { 166 log_maybe(&nerr, 167 "authreadkeys: key %s > %d reserved for Autokey", 168 token, NTP_MAXKEY); 169 continue; 170 } 171 172 /* 173 * Next is keytype. See if that is all right. 174 */ 175 token = nexttok(&line); 176 if (token == NULL) { 177 log_maybe(&nerr, 178 "authreadkeys: no key type for key %d", 179 keyno); 180 continue; 181 } 182 #ifdef OPENSSL 183 /* 184 * The key type is the NID used by the message digest 185 * algorithm. There are a number of inconsistencies in 186 * the OpenSSL database. We attempt to discover them 187 * here and prevent use of inconsistent data later. 188 */ 189 keytype = keytype_from_text(token, NULL); 190 if (keytype == 0) { 191 log_maybe(&nerr, 192 "authreadkeys: invalid type for key %d", 193 keyno); 194 continue; 195 } 196 if (EVP_get_digestbynid(keytype) == NULL) { 197 log_maybe(&nerr, 198 "authreadkeys: no algorithm for key %d", 199 keyno); 200 continue; 201 } 202 #else /* !OPENSSL follows */ 203 204 /* 205 * The key type is unused, but is required to be 'M' or 206 * 'm' for compatibility. 207 */ 208 if (!(*token == 'M' || *token == 'm')) { 209 log_maybe(&nerr, 210 "authreadkeys: invalid type for key %d", 211 keyno); 212 continue; 213 } 214 keytype = KEY_TYPE_MD5; 215 #endif /* !OPENSSL */ 216 217 /* 218 * Finally, get key and insert it. If it is longer than 20 219 * characters, it is a binary string encoded in hex; 220 * otherwise, it is a text string of printable ASCII 221 * characters. 222 */ 223 token = nexttok(&line); 224 if (token == NULL) { 225 log_maybe(&nerr, 226 "authreadkeys: no key for key %d", keyno); 227 continue; 228 } 229 next = NULL; 230 len = strlen(token); 231 if (len <= 20) { /* Bug 2537 */ 232 next = emalloc(sizeof(KeyDataT) + len); 233 next->keyid = keyno; 234 next->keytype = keytype; 235 next->seclen = len; 236 memcpy(next->secbuf, token, len); 237 } else { 238 static const char hex[] = "0123456789abcdef"; 239 u_char temp; 240 char *ptr; 241 size_t jlim; 242 243 jlim = min(len, 2 * sizeof(keystr)); 244 for (j = 0; j < jlim; j++) { 245 ptr = strchr(hex, tolower((unsigned char)token[j])); 246 if (ptr == NULL) 247 break; /* abort decoding */ 248 temp = (u_char)(ptr - hex); 249 if (j & 1) 250 keystr[j / 2] |= temp; 251 else 252 keystr[j / 2] = temp << 4; 253 } 254 if (j < jlim) { 255 log_maybe(&nerr, 256 "authreadkeys: invalid hex digit for key %d", 257 keyno); 258 continue; 259 } 260 len = jlim/2; /* hmmmm.... what about odd length?!? */ 261 next = emalloc(sizeof(KeyDataT) + len); 262 next->keyid = keyno; 263 next->keytype = keytype; 264 next->seclen = len; 265 memcpy(next->secbuf, keystr, len); 266 } 267 INSIST(NULL != next); 268 next->next = list; 269 list = next; 270 } 271 fclose(fp); 272 if (nerr > nerr_maxlimit) { 273 msyslog(LOG_ERR, 274 "authreadkeys: rejecting file '%s' after %u errors (emergency break)", 275 file, nerr); 276 goto onerror; 277 } 278 if (nerr > 0) { 279 msyslog(LOG_ERR, 280 "authreadkeys: rejecting file '%s' after %u error(s)", 281 file, nerr); 282 goto onerror; 283 } 284 285 /* first remove old file-based keys */ 286 auth_delkeys(); 287 /* insert the new key material */ 288 while (NULL != (next = list)) { 289 list = next->next; 290 MD5auth_setkey(next->keyid, next->keytype, 291 next->secbuf, next->seclen); 292 /* purge secrets from memory before free()ing it */ 293 memset(next, 0, sizeof(*next) + next->seclen); 294 free(next); 295 } 296 return (1); 297 298 onerror: 299 /* Mop up temporary storage before bailing out. */ 300 while (NULL != (next = list)) { 301 list = next->next; 302 /* purge secrets from memory before free()ing it */ 303 memset(next, 0, sizeof(*next) + next->seclen); 304 free(next); 305 } 306 return (0); 307 } 308