Home | History | Annotate | Line # | Download | only in libntp
authreadkeys.c revision 1.1.1.9
      1 /*	$NetBSD: authreadkeys.c,v 1.1.1.9 2016/01/08 21:21:24 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