Home | History | Annotate | Line # | Download | only in libntp
authreadkeys.c revision 1.1.1.11
      1 /*	$NetBSD: authreadkeys.c,v 1.1.1.11 2018/04/07 00:15:47 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 	/*
    155 	 * Open file.  Complain and return if it can't be opened.
    156 	 */
    157 	fp = fopen(file, "r");
    158 	if (fp == NULL) {
    159 		msyslog(LOG_ERR, "authreadkeys: file '%s': %m",
    160 		    file);
    161 		goto onerror;
    162 	}
    163 	INIT_SSL();
    164 
    165 	/*
    166 	 * Now read lines from the file, looking for key entries. Put
    167 	 * the data into temporary store for later propagation to avoid
    168 	 * two-pass processing.
    169 	 */
    170 	nerr = 0;
    171 	while ((line = fgets(buf, sizeof buf, fp)) != NULL) {
    172 		if (nerr > nerr_maxlimit)
    173 			break;
    174 		token = nexttok(&line);
    175 		if (token == NULL)
    176 			continue;
    177 
    178 		/*
    179 		 * First is key number.  See if it is okay.
    180 		 */
    181 		keyno = atoi(token);
    182 		if (keyno < 1) {
    183 			log_maybe(&nerr,
    184 				  "authreadkeys: cannot change key %s",
    185 				  token);
    186 			continue;
    187 		}
    188 
    189 		if (keyno > NTP_MAXKEY) {
    190 			log_maybe(&nerr,
    191 				  "authreadkeys: key %s > %d reserved for Autokey",
    192 				  token, NTP_MAXKEY);
    193 			continue;
    194 		}
    195 
    196 		/*
    197 		 * Next is keytype. See if that is all right.
    198 		 */
    199 		token = nexttok(&line);
    200 		if (token == NULL) {
    201 			log_maybe(&nerr,
    202 				  "authreadkeys: no key type for key %d",
    203 				  keyno);
    204 			continue;
    205 		}
    206 
    207 		/* We want to silently ignore keys where we do not
    208 		 * support the requested digest type. OTOH, we want to
    209 		 * make sure the file is well-formed.  That means we
    210 		 * have to process the line completely and have to
    211 		 * finally throw away the result... This is a bit more
    212 		 * work, but it also results in better error detection.
    213 		 */
    214 #ifdef OPENSSL
    215 		/*
    216 		 * The key type is the NID used by the message digest
    217 		 * algorithm. There are a number of inconsistencies in
    218 		 * the OpenSSL database. We attempt to discover them
    219 		 * here and prevent use of inconsistent data later.
    220 		 */
    221 		keytype = keytype_from_text(token, NULL);
    222 		if (keytype == 0) {
    223 			log_maybe(NULL,
    224 				  "authreadkeys: invalid type for key %d",
    225 				  keyno);
    226 		} else if (NID_cmac != keytype &&
    227 				EVP_get_digestbynid(keytype) == NULL) {
    228 			log_maybe(NULL,
    229 				  "authreadkeys: no algorithm for key %d",
    230 				  keyno);
    231 			keytype = 0;
    232 		}
    233 #else	/* !OPENSSL follows */
    234 		/*
    235 		 * The key type is unused, but is required to be 'M' or
    236 		 * 'm' for compatibility.
    237 		 */
    238 		if (!(*token == 'M' || *token == 'm')) {
    239 			log_maybe(NULL,
    240 				  "authreadkeys: invalid type for key %d",
    241 				  keyno);
    242 			keytype = 0;
    243 		} else {
    244 			keytype = KEY_TYPE_MD5;
    245 		}
    246 #endif	/* !OPENSSL */
    247 
    248 		/*
    249 		 * Finally, get key and insert it. If it is longer than 20
    250 		 * characters, it is a binary string encoded in hex;
    251 		 * otherwise, it is a text string of printable ASCII
    252 		 * characters.
    253 		 */
    254 		token = nexttok(&line);
    255 		if (token == NULL) {
    256 			log_maybe(&nerr,
    257 				  "authreadkeys: no key for key %d", keyno);
    258 			continue;
    259 		}
    260 		next = NULL;
    261 		len = strlen(token);
    262 		if (len <= 20) {	/* Bug 2537 */
    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, token, len);
    269 		} else {
    270 			static const char hex[] = "0123456789abcdef";
    271 			u_char	temp;
    272 			char	*ptr;
    273 			size_t	jlim;
    274 
    275 			jlim = min(len, 2 * sizeof(keystr));
    276 			for (j = 0; j < jlim; j++) {
    277 				ptr = strchr(hex, tolower((unsigned char)token[j]));
    278 				if (ptr == NULL)
    279 					break;	/* abort decoding */
    280 				temp = (u_char)(ptr - hex);
    281 				if (j & 1)
    282 					keystr[j / 2] |= temp;
    283 				else
    284 					keystr[j / 2] = temp << 4;
    285 			}
    286 			if (j < jlim) {
    287 				log_maybe(&nerr,
    288 					  "authreadkeys: invalid hex digit for key %d",
    289 					  keyno);
    290 				continue;
    291 			}
    292 			len = jlim/2; /* hmmmm.... what about odd length?!? */
    293 			next = emalloc(sizeof(KeyDataT) + len);
    294 			next->keyacclist = NULL;
    295 			next->keyid   = keyno;
    296 			next->keytype = keytype;
    297 			next->seclen  = len;
    298 			memcpy(next->secbuf, keystr, len);
    299 		}
    300 
    301 		token = nexttok(&line);
    302 		if (token != NULL) {	/* A comma-separated IP access list */
    303 			char *tp = token;
    304 
    305 			while (tp) {
    306 				char *i;
    307 				char *snp;	/* subnet text pointer */
    308 				unsigned int snbits;
    309 				sockaddr_u addr;
    310 
    311 				i = strchr(tp, (int)',');
    312 				if (i) {
    313 					*i = '\0';
    314 				}
    315 				snp = strchr(tp, (int)'/');
    316 				if (snp) {
    317 					char *sp;
    318 
    319 					*snp++ = '\0';
    320 					snbits = 0;
    321 					sp = snp;
    322 
    323 					while (*sp != '\0') {
    324 						if (!isdigit((unsigned char)*sp))
    325 						    break;
    326 						if (snbits > 1000)
    327 						    break;	/* overflow */
    328 						snbits = 10 * snbits + (*sp++ - '0');       /* ascii dependent */
    329 					}
    330 					if (*sp != '\0') {
    331 						log_maybe(&nerr,
    332 							  "authreadkeys: Invalid character in subnet specification for <%s/%s> in key %d",
    333 							  sp, snp, keyno);
    334 						goto nextip;
    335 					}
    336 				} else {
    337 					snbits = UINT_MAX;
    338 				}
    339 
    340 				if (is_ip_address(tp, AF_UNSPEC, &addr)) {
    341 					/* Make sure that snbits is valid for addr */
    342 				    if ((snbits < UINT_MAX) &&
    343 					( (IS_IPV4(&addr) && snbits > 32) ||
    344 					  (IS_IPV6(&addr) && snbits > 128))) {
    345 						log_maybe(NULL,
    346 							  "authreadkeys: excessive subnet mask <%s/%s> for key %d",
    347 							  tp, snp, keyno);
    348 				    }
    349 				    next->keyacclist = keyacc_new_push(
    350 					next->keyacclist, &addr, snbits);
    351 				} else {
    352 					log_maybe(&nerr,
    353 						  "authreadkeys: invalid IP address <%s> for key %d",
    354 						  tp, keyno);
    355 				}
    356 
    357 			nextip:
    358 				if (i) {
    359 					tp = i + 1;
    360 				} else {
    361 					tp = 0;
    362 				}
    363 			}
    364 		}
    365 
    366 		/* check if this has to be weeded out... */
    367 		if (0 == keytype) {
    368 			free_keydata(next);
    369 			next = NULL;
    370 			continue;
    371 		}
    372 
    373 		INSIST(NULL != next);
    374 		next->next = list;
    375 		list = next;
    376 	}
    377 	fclose(fp);
    378 	if (nerr > 0) {
    379 		const char * why = "";
    380 		if (nerr > nerr_maxlimit)
    381 			why = " (emergency break)";
    382 		msyslog(LOG_ERR,
    383 			"authreadkeys: rejecting file '%s' after %u error(s)%s",
    384 			file, nerr, why);
    385 		goto onerror;
    386 	}
    387 
    388 	/* first remove old file-based keys */
    389 	auth_delkeys();
    390 	/* insert the new key material */
    391 	while (NULL != (next = list)) {
    392 		list = next->next;
    393 		MD5auth_setkey(next->keyid, next->keytype,
    394 			       next->secbuf, next->seclen, next->keyacclist);
    395 		next->keyacclist = NULL; /* consumed by MD5auth_setkey */
    396 		free_keydata(next);
    397 	}
    398 	return (1);
    399 
    400   onerror:
    401 	/* Mop up temporary storage before bailing out. */
    402 	while (NULL != (next = list)) {
    403 		list = next->next;
    404 		free_keydata(next);
    405 	}
    406 	return (0);
    407 }
    408