154359Sroberto/* 254359Sroberto * authreadkeys.c - routines to support the reading of the key file 354359Sroberto */ 4285612Sdelphij#include <config.h> 554359Sroberto#include <stdio.h> 654359Sroberto#include <ctype.h> 754359Sroberto 8294904Sdelphij#include "ntpd.h" /* Only for DPRINTF */ 954359Sroberto#include "ntp_fp.h" 1054359Sroberto#include "ntp.h" 1182498Sroberto#include "ntp_syslog.h" 1254359Sroberto#include "ntp_stdlib.h" 13294904Sdelphij#include "ntp_keyacc.h" 1454359Sroberto 15285612Sdelphij#ifdef OPENSSL 16285612Sdelphij#include "openssl/objects.h" 17285612Sdelphij#include "openssl/evp.h" 18285612Sdelphij#endif /* OPENSSL */ 1954359Sroberto 2054359Sroberto/* Forwards */ 21285612Sdelphijstatic char *nexttok (char **); 2254359Sroberto 2354359Sroberto/* 2454359Sroberto * nexttok - basic internal tokenizing routine 2554359Sroberto */ 2654359Srobertostatic char * 2754359Srobertonexttok( 28285612Sdelphij char **str 2954359Sroberto ) 3054359Sroberto{ 3154359Sroberto register char *cp; 3254359Sroberto char *starttok; 3354359Sroberto 3454359Sroberto cp = *str; 3554359Sroberto 3654359Sroberto /* 3754359Sroberto * Space past white space 3854359Sroberto */ 3954359Sroberto while (*cp == ' ' || *cp == '\t') 40285612Sdelphij cp++; 4154359Sroberto 4254359Sroberto /* 4354359Sroberto * Save this and space to end of token 4454359Sroberto */ 4554359Sroberto starttok = cp; 4654359Sroberto while (*cp != '\0' && *cp != '\n' && *cp != ' ' 4754359Sroberto && *cp != '\t' && *cp != '#') 48285612Sdelphij cp++; 4954359Sroberto 5054359Sroberto /* 5154359Sroberto * If token length is zero return an error, else set end of 5254359Sroberto * token to zero and return start. 5354359Sroberto */ 5454359Sroberto if (starttok == cp) 55285612Sdelphij return NULL; 5654359Sroberto 5754359Sroberto if (*cp == ' ' || *cp == '\t') 58285612Sdelphij *cp++ = '\0'; 5954359Sroberto else 60285612Sdelphij *cp = '\0'; 6154359Sroberto 6254359Sroberto *str = cp; 6354359Sroberto return starttok; 6454359Sroberto} 6554359Sroberto 6654359Sroberto 67289999Sglebius/* TALOS-CAN-0055: possibly DoS attack by setting the key file to the 68289999Sglebius * log file. This is hard to prevent (it would need to check two files 69289999Sglebius * to be the same on the inode level, which will not work so easily with 70289999Sglebius * Windows or VMS) but we can avoid the self-amplification loop: We only 71289999Sglebius * log the first 5 errors, silently ignore the next 10 errors, and give 72289999Sglebius * up when when we have found more than 15 errors. 73289999Sglebius * 74289999Sglebius * This avoids the endless file iteration we will end up with otherwise, 75289999Sglebius * and also avoids overflowing the log file. 76289999Sglebius * 77289999Sglebius * Nevertheless, once this happens, the keys are gone since this would 78289999Sglebius * require a save/swap strategy that is not easy to apply due to the 79289999Sglebius * data on global/static level. 80289999Sglebius */ 81289999Sglebius 82293893Sglebiusstatic const u_int nerr_loglimit = 5u; 83293893Sglebiusstatic const u_int nerr_maxlimit = 15; 84289999Sglebius 85293893Sglebiusstatic void log_maybe(u_int*, const char*, ...) NTP_PRINTF(2, 3); 86289999Sglebius 87293893Sglebiustypedef struct keydata KeyDataT; 88293893Sglebiusstruct keydata { 89293893Sglebius KeyDataT *next; /* queue/stack link */ 90294904Sdelphij KeyAccT *keyacclist; /* key access list */ 91293893Sglebius keyid_t keyid; /* stored key ID */ 92293893Sglebius u_short keytype; /* stored key type */ 93293893Sglebius u_short seclen; /* length of secret */ 94293893Sglebius u_char secbuf[1]; /* begin of secret (formal only)*/ 95293893Sglebius}; 96293893Sglebius 97289999Sglebiusstatic void 98289999Sglebiuslog_maybe( 99293893Sglebius u_int *pnerr, 100289999Sglebius const char *fmt , 101289999Sglebius ...) 102289999Sglebius{ 103289999Sglebius va_list ap; 104298770Sdelphij if ((NULL == pnerr) || (++(*pnerr) <= nerr_loglimit)) { 105289999Sglebius va_start(ap, fmt); 106289999Sglebius mvsyslog(LOG_ERR, fmt, ap); 107289999Sglebius va_end(ap); 108289999Sglebius } 109289999Sglebius} 110289999Sglebius 111298770Sdelphijstatic void 112298770Sdelphijfree_keydata( 113298770Sdelphij KeyDataT *node 114298770Sdelphij ) 115298770Sdelphij{ 116298770Sdelphij KeyAccT *kap; 117298770Sdelphij 118298770Sdelphij if (node) { 119298770Sdelphij while (node->keyacclist) { 120298770Sdelphij kap = node->keyacclist; 121298770Sdelphij node->keyacclist = kap->next; 122298770Sdelphij free(kap); 123298770Sdelphij } 124298770Sdelphij 125298770Sdelphij /* purge secrets from memory before free()ing it */ 126298770Sdelphij memset(node, 0, sizeof(*node) + node->seclen); 127298770Sdelphij free(node); 128298770Sdelphij } 129298770Sdelphij} 130298770Sdelphij 13154359Sroberto/* 13254359Sroberto * authreadkeys - (re)read keys from a file. 13354359Sroberto */ 13454359Srobertoint 13554359Srobertoauthreadkeys( 13654359Sroberto const char *file 13754359Sroberto ) 13854359Sroberto{ 139285612Sdelphij FILE *fp; 140285612Sdelphij char *line; 141285612Sdelphij char *token; 142285612Sdelphij keyid_t keyno; 143285612Sdelphij int keytype; 144285612Sdelphij char buf[512]; /* lots of room for line */ 145285612Sdelphij u_char keystr[32]; /* Bug 2537 */ 146285612Sdelphij size_t len; 147285612Sdelphij size_t j; 148293893Sglebius u_int nerr; 149293893Sglebius KeyDataT *list = NULL; 150293893Sglebius KeyDataT *next = NULL; 15154359Sroberto /* 15254359Sroberto * Open file. Complain and return if it can't be opened. 15354359Sroberto */ 15454359Sroberto fp = fopen(file, "r"); 15554359Sroberto if (fp == NULL) { 156293893Sglebius msyslog(LOG_ERR, "authreadkeys: file '%s': %m", 157285612Sdelphij file); 158293893Sglebius goto onerror; 15954359Sroberto } 160285612Sdelphij INIT_SSL(); 16154359Sroberto 16254359Sroberto /* 163293893Sglebius * Now read lines from the file, looking for key entries. Put 164293893Sglebius * the data into temporary store for later propagation to avoid 165293893Sglebius * two-pass processing. 16654359Sroberto */ 167289999Sglebius nerr = 0; 16854359Sroberto while ((line = fgets(buf, sizeof buf, fp)) != NULL) { 169289999Sglebius if (nerr > nerr_maxlimit) 170289999Sglebius break; 17154359Sroberto token = nexttok(&line); 172285612Sdelphij if (token == NULL) 173285612Sdelphij continue; 17454359Sroberto 17554359Sroberto /* 17654359Sroberto * First is key number. See if it is okay. 17754359Sroberto */ 17854359Sroberto keyno = atoi(token); 179298770Sdelphij if (keyno < 1) { 180289999Sglebius log_maybe(&nerr, 181289999Sglebius "authreadkeys: cannot change key %s", 182289999Sglebius token); 18354359Sroberto continue; 18454359Sroberto } 18554359Sroberto 18654359Sroberto if (keyno > NTP_MAXKEY) { 187289999Sglebius log_maybe(&nerr, 188289999Sglebius "authreadkeys: key %s > %d reserved for Autokey", 189289999Sglebius token, NTP_MAXKEY); 19054359Sroberto continue; 19154359Sroberto } 19254359Sroberto 19354359Sroberto /* 194285612Sdelphij * Next is keytype. See if that is all right. 19554359Sroberto */ 19654359Sroberto token = nexttok(&line); 197285612Sdelphij if (token == NULL) { 198289999Sglebius log_maybe(&nerr, 199289999Sglebius "authreadkeys: no key type for key %d", 200289999Sglebius keyno); 20154359Sroberto continue; 20254359Sroberto } 203298770Sdelphij 204298770Sdelphij /* We want to silently ignore keys where we do not 205298770Sdelphij * support the requested digest type. OTOH, we want to 206298770Sdelphij * make sure the file is well-formed. That means we 207298770Sdelphij * have to process the line completely and have to 208298770Sdelphij * finally throw away the result... This is a bit more 209298770Sdelphij * work, but it also results in better error detection. 210298770Sdelphij */ 211285612Sdelphij#ifdef OPENSSL 212285612Sdelphij /* 213285612Sdelphij * The key type is the NID used by the message digest 214285612Sdelphij * algorithm. There are a number of inconsistencies in 215285612Sdelphij * the OpenSSL database. We attempt to discover them 216285612Sdelphij * here and prevent use of inconsistent data later. 217285612Sdelphij */ 218285612Sdelphij keytype = keytype_from_text(token, NULL); 219285612Sdelphij if (keytype == 0) { 220298770Sdelphij log_maybe(NULL, 221289999Sglebius "authreadkeys: invalid type for key %d", 222289999Sglebius keyno); 223298770Sdelphij } else if (EVP_get_digestbynid(keytype) == NULL) { 224298770Sdelphij log_maybe(NULL, 225289999Sglebius "authreadkeys: no algorithm for key %d", 226289999Sglebius keyno); 227298770Sdelphij keytype = 0; 228285612Sdelphij } 229285612Sdelphij#else /* !OPENSSL follows */ 23054359Sroberto /* 231285612Sdelphij * The key type is unused, but is required to be 'M' or 232285612Sdelphij * 'm' for compatibility. 23354359Sroberto */ 234285612Sdelphij if (!(*token == 'M' || *token == 'm')) { 235298770Sdelphij log_maybe(NULL, 236289999Sglebius "authreadkeys: invalid type for key %d", 237289999Sglebius keyno); 238298770Sdelphij keytype = 0; 239298770Sdelphij } else { 240298770Sdelphij keytype = KEY_TYPE_MD5; 241285612Sdelphij } 242285612Sdelphij#endif /* !OPENSSL */ 243285612Sdelphij 244285612Sdelphij /* 245285612Sdelphij * Finally, get key and insert it. If it is longer than 20 246285612Sdelphij * characters, it is a binary string encoded in hex; 247285612Sdelphij * otherwise, it is a text string of printable ASCII 248285612Sdelphij * characters. 249285612Sdelphij */ 25054359Sroberto token = nexttok(&line); 251285612Sdelphij if (token == NULL) { 252289999Sglebius log_maybe(&nerr, 253289999Sglebius "authreadkeys: no key for key %d", keyno); 254285612Sdelphij continue; 255285612Sdelphij } 256293893Sglebius next = NULL; 257285612Sdelphij len = strlen(token); 258285612Sdelphij if (len <= 20) { /* Bug 2537 */ 259293893Sglebius next = emalloc(sizeof(KeyDataT) + len); 260294904Sdelphij next->keyacclist = NULL; 261293893Sglebius next->keyid = keyno; 262293893Sglebius next->keytype = keytype; 263293893Sglebius next->seclen = len; 264293893Sglebius memcpy(next->secbuf, token, len); 26554359Sroberto } else { 266293893Sglebius static const char hex[] = "0123456789abcdef"; 267285612Sdelphij u_char temp; 268285612Sdelphij char *ptr; 269285612Sdelphij size_t jlim; 270285612Sdelphij 271285612Sdelphij jlim = min(len, 2 * sizeof(keystr)); 272285612Sdelphij for (j = 0; j < jlim; j++) { 273285612Sdelphij ptr = strchr(hex, tolower((unsigned char)token[j])); 274285612Sdelphij if (ptr == NULL) 275285612Sdelphij break; /* abort decoding */ 276285612Sdelphij temp = (u_char)(ptr - hex); 277285612Sdelphij if (j & 1) 278285612Sdelphij keystr[j / 2] |= temp; 279285612Sdelphij else 280285612Sdelphij keystr[j / 2] = temp << 4; 28154359Sroberto } 282285612Sdelphij if (j < jlim) { 283289999Sglebius log_maybe(&nerr, 284289999Sglebius "authreadkeys: invalid hex digit for key %d", 285289999Sglebius keyno); 286285612Sdelphij continue; 287285612Sdelphij } 288293893Sglebius len = jlim/2; /* hmmmm.... what about odd length?!? */ 289293893Sglebius next = emalloc(sizeof(KeyDataT) + len); 290294904Sdelphij next->keyacclist = NULL; 291293893Sglebius next->keyid = keyno; 292293893Sglebius next->keytype = keytype; 293293893Sglebius next->seclen = len; 294293893Sglebius memcpy(next->secbuf, keystr, len); 29554359Sroberto } 296294904Sdelphij 297294904Sdelphij token = nexttok(&line); 298298770Sdelphij DPRINTF(0, ("authreadkeys: full access list <%s>\n", (token) ? token : "NULL")); 299294904Sdelphij if (token != NULL) { /* A comma-separated IP access list */ 300294904Sdelphij char *tp = token; 301294904Sdelphij 302294904Sdelphij while (tp) { 303294904Sdelphij char *i; 304298770Sdelphij sockaddr_u addr; 305294904Sdelphij 306294904Sdelphij i = strchr(tp, (int)','); 307294904Sdelphij if (i) 308294904Sdelphij *i = '\0'; 309298770Sdelphij DPRINTF(0, ("authreadkeys: access list: <%s>\n", tp)); 310294904Sdelphij 311298770Sdelphij if (is_ip_address(tp, AF_UNSPEC, &addr)) { 312298770Sdelphij next->keyacclist = keyacc_new_push( 313298770Sdelphij next->keyacclist, &addr); 314294904Sdelphij } else { 315294904Sdelphij log_maybe(&nerr, 316294904Sdelphij "authreadkeys: invalid IP address <%s> for key %d", 317294904Sdelphij tp, keyno); 318294904Sdelphij } 319294904Sdelphij 320294904Sdelphij if (i) { 321294904Sdelphij tp = i + 1; 322294904Sdelphij } else { 323294904Sdelphij tp = 0; 324294904Sdelphij } 325294904Sdelphij } 326294904Sdelphij } 327294904Sdelphij 328298770Sdelphij /* check if this has to be weeded out... */ 329298770Sdelphij if (0 == keytype) { 330298770Sdelphij free_keydata(next); 331298770Sdelphij next = NULL; 332298770Sdelphij continue; 333298770Sdelphij } 334298770Sdelphij 335293893Sglebius INSIST(NULL != next); 336293893Sglebius next->next = list; 337293893Sglebius list = next; 33854359Sroberto } 339285612Sdelphij fclose(fp); 340293893Sglebius if (nerr > 0) { 341298770Sdelphij const char * why = ""; 342298770Sdelphij if (nerr > nerr_maxlimit) 343298770Sdelphij why = " (emergency break)"; 344289999Sglebius msyslog(LOG_ERR, 345298770Sdelphij "authreadkeys: rejecting file '%s' after %u error(s)%s", 346298770Sdelphij file, nerr, why); 347293893Sglebius goto onerror; 348289999Sglebius } 349293893Sglebius 350293893Sglebius /* first remove old file-based keys */ 351293893Sglebius auth_delkeys(); 352293893Sglebius /* insert the new key material */ 353293893Sglebius while (NULL != (next = list)) { 354293893Sglebius list = next->next; 355293893Sglebius MD5auth_setkey(next->keyid, next->keytype, 356294904Sdelphij next->secbuf, next->seclen, next->keyacclist); 357298770Sdelphij next->keyacclist = NULL; /* consumed by MD5auth_setkey */ 358298770Sdelphij free_keydata(next); 359293893Sglebius } 360285612Sdelphij return (1); 361293893Sglebius 362293893Sglebius onerror: 363293893Sglebius /* Mop up temporary storage before bailing out. */ 364293893Sglebius while (NULL != (next = list)) { 365293893Sglebius list = next->next; 366298770Sdelphij free_keydata(next); 367293893Sglebius } 368293893Sglebius return (0); 36954359Sroberto} 370