1 /* $NetBSD: authreadkeys.c,v 1.7 2015/10/23 18:06:19 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 size_t nerr_loglimit = 5u; 83 static const size_t nerr_maxlimit = 15; 84 85 static void log_maybe(size_t*, const char*, ...) NTP_PRINTF(2, 3); 86 87 static void 88 log_maybe( 89 size_t *pnerr, 90 const char *fmt , 91 ...) 92 { 93 va_list ap; 94 if (++(*pnerr) <= nerr_loglimit) { 95 va_start(ap, fmt); 96 mvsyslog(LOG_ERR, fmt, ap); 97 va_end(ap); 98 } 99 } 100 101 /* 102 * authreadkeys - (re)read keys from a file. 103 */ 104 int 105 authreadkeys( 106 const char *file 107 ) 108 { 109 FILE *fp; 110 char *line; 111 char *token; 112 keyid_t keyno; 113 int keytype; 114 char buf[512]; /* lots of room for line */ 115 u_char keystr[32]; /* Bug 2537 */ 116 size_t len; 117 size_t j; 118 size_t nerr; 119 /* 120 * Open file. Complain and return if it can't be opened. 121 */ 122 fp = fopen(file, "r"); 123 if (fp == NULL) { 124 msyslog(LOG_ERR, "authreadkeys: file %s: %m", 125 file); 126 return (0); 127 } 128 INIT_SSL(); 129 130 /* 131 * Remove all existing keys 132 */ 133 auth_delkeys(); 134 135 /* 136 * Now read lines from the file, looking for key entries 137 */ 138 nerr = 0; 139 while ((line = fgets(buf, sizeof buf, fp)) != NULL) { 140 if (nerr > nerr_maxlimit) 141 break; 142 token = nexttok(&line); 143 if (token == NULL) 144 continue; 145 146 /* 147 * First is key number. See if it is okay. 148 */ 149 keyno = atoi(token); 150 if (keyno == 0) { 151 log_maybe(&nerr, 152 "authreadkeys: cannot change key %s", 153 token); 154 continue; 155 } 156 157 if (keyno > NTP_MAXKEY) { 158 log_maybe(&nerr, 159 "authreadkeys: key %s > %d reserved for Autokey", 160 token, NTP_MAXKEY); 161 continue; 162 } 163 164 /* 165 * Next is keytype. See if that is all right. 166 */ 167 token = nexttok(&line); 168 if (token == NULL) { 169 log_maybe(&nerr, 170 "authreadkeys: no key type for key %d", 171 keyno); 172 continue; 173 } 174 #ifdef OPENSSL 175 /* 176 * The key type is the NID used by the message digest 177 * algorithm. There are a number of inconsistencies in 178 * the OpenSSL database. We attempt to discover them 179 * here and prevent use of inconsistent data later. 180 */ 181 keytype = keytype_from_text(token, NULL); 182 if (keytype == 0) { 183 log_maybe(&nerr, 184 "authreadkeys: invalid type for key %d", 185 keyno); 186 continue; 187 } 188 if (EVP_get_digestbynid(keytype) == NULL) { 189 log_maybe(&nerr, 190 "authreadkeys: no algorithm for key %d", 191 keyno); 192 continue; 193 } 194 #else /* !OPENSSL follows */ 195 196 /* 197 * The key type is unused, but is required to be 'M' or 198 * 'm' for compatibility. 199 */ 200 if (!(*token == 'M' || *token == 'm')) { 201 log_maybe(&nerr, 202 "authreadkeys: invalid type for key %d", 203 keyno); 204 continue; 205 } 206 keytype = KEY_TYPE_MD5; 207 #endif /* !OPENSSL */ 208 209 /* 210 * Finally, get key and insert it. If it is longer than 20 211 * characters, it is a binary string encoded in hex; 212 * otherwise, it is a text string of printable ASCII 213 * characters. 214 */ 215 token = nexttok(&line); 216 if (token == NULL) { 217 log_maybe(&nerr, 218 "authreadkeys: no key for key %d", keyno); 219 continue; 220 } 221 len = strlen(token); 222 if (len <= 20) { /* Bug 2537 */ 223 MD5auth_setkey(keyno, keytype, (u_char *)token, len); 224 } else { 225 char hex[] = "0123456789abcdef"; 226 u_char temp; 227 char *ptr; 228 size_t jlim; 229 230 jlim = min(len, 2 * sizeof(keystr)); 231 for (j = 0; j < jlim; j++) { 232 ptr = strchr(hex, tolower((unsigned char)token[j])); 233 if (ptr == NULL) 234 break; /* abort decoding */ 235 temp = (u_char)(ptr - hex); 236 if (j & 1) 237 keystr[j / 2] |= temp; 238 else 239 keystr[j / 2] = temp << 4; 240 } 241 if (j < jlim) { 242 log_maybe(&nerr, 243 "authreadkeys: invalid hex digit for key %d", 244 keyno); 245 continue; 246 } 247 MD5auth_setkey(keyno, keytype, keystr, jlim / 2); 248 } 249 } 250 fclose(fp); 251 if (nerr > nerr_maxlimit) { 252 msyslog(LOG_ERR, 253 "authreadkeys: emergency break after %zu errors", 254 nerr); 255 return (0); 256 } else if (nerr > nerr_loglimit) { 257 msyslog(LOG_ERR, 258 "authreadkeys: found %zu more error(s)", 259 nerr - nerr_loglimit); 260 } 261 return (1); 262 } 263