154359Sroberto/* 254359Sroberto * authreadkeys.c - routines to support the reading of the key file 354359Sroberto */ 4290001Sglebius#include <config.h> 554359Sroberto#include <stdio.h> 654359Sroberto#include <ctype.h> 754359Sroberto 8294905Sdelphij#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" 13294905Sdelphij#include "ntp_keyacc.h" 1454359Sroberto 15290001Sglebius#ifdef OPENSSL 16290001Sglebius#include "openssl/objects.h" 17290001Sglebius#include "openssl/evp.h" 18290001Sglebius#endif /* OPENSSL */ 1954359Sroberto 2054359Sroberto/* Forwards */ 21290001Sglebiusstatic char *nexttok (char **); 2254359Sroberto 2354359Sroberto/* 2454359Sroberto * nexttok - basic internal tokenizing routine 2554359Sroberto */ 2654359Srobertostatic char * 2754359Srobertonexttok( 28290001Sglebius 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') 40290001Sglebius 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 != '#') 48290001Sglebius 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) 55290001Sglebius return NULL; 5654359Sroberto 5754359Sroberto if (*cp == ' ' || *cp == '\t') 58290001Sglebius *cp++ = '\0'; 5954359Sroberto else 60290001Sglebius *cp = '\0'; 6154359Sroberto 6254359Sroberto *str = cp; 6354359Sroberto return starttok; 6454359Sroberto} 6554359Sroberto 6654359Sroberto 67290001Sglebius/* TALOS-CAN-0055: possibly DoS attack by setting the key file to the 68290001Sglebius * log file. This is hard to prevent (it would need to check two files 69290001Sglebius * to be the same on the inode level, which will not work so easily with 70290001Sglebius * Windows or VMS) but we can avoid the self-amplification loop: We only 71290001Sglebius * log the first 5 errors, silently ignore the next 10 errors, and give 72290001Sglebius * up when when we have found more than 15 errors. 73290001Sglebius * 74290001Sglebius * This avoids the endless file iteration we will end up with otherwise, 75290001Sglebius * and also avoids overflowing the log file. 76290001Sglebius * 77290001Sglebius * Nevertheless, once this happens, the keys are gone since this would 78290001Sglebius * require a save/swap strategy that is not easy to apply due to the 79290001Sglebius * data on global/static level. 80290001Sglebius */ 81290001Sglebius 82293896Sglebiusstatic const u_int nerr_loglimit = 5u; 83293896Sglebiusstatic const u_int nerr_maxlimit = 15; 84290001Sglebius 85293896Sglebiusstatic void log_maybe(u_int*, const char*, ...) NTP_PRINTF(2, 3); 86290001Sglebius 87293896Sglebiustypedef struct keydata KeyDataT; 88293896Sglebiusstruct keydata { 89293896Sglebius KeyDataT *next; /* queue/stack link */ 90294905Sdelphij KeyAccT *keyacclist; /* key access list */ 91293896Sglebius keyid_t keyid; /* stored key ID */ 92293896Sglebius u_short keytype; /* stored key type */ 93293896Sglebius u_short seclen; /* length of secret */ 94293896Sglebius u_char secbuf[1]; /* begin of secret (formal only)*/ 95293896Sglebius}; 96293896Sglebius 97290001Sglebiusstatic void 98290001Sglebiuslog_maybe( 99293896Sglebius u_int *pnerr, 100290001Sglebius const char *fmt , 101290001Sglebius ...) 102290001Sglebius{ 103290001Sglebius va_list ap; 104298770Sdelphij if ((NULL == pnerr) || (++(*pnerr) <= nerr_loglimit)) { 105290001Sglebius va_start(ap, fmt); 106290001Sglebius mvsyslog(LOG_ERR, fmt, ap); 107290001Sglebius va_end(ap); 108290001Sglebius } 109290001Sglebius} 110290001Sglebius 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{ 139290001Sglebius FILE *fp; 140290001Sglebius char *line; 141290001Sglebius char *token; 142290001Sglebius keyid_t keyno; 143290001Sglebius int keytype; 144290001Sglebius char buf[512]; /* lots of room for line */ 145290001Sglebius u_char keystr[32]; /* Bug 2537 */ 146290001Sglebius size_t len; 147290001Sglebius size_t j; 148293896Sglebius u_int nerr; 149293896Sglebius KeyDataT *list = NULL; 150293896Sglebius 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) { 156293896Sglebius msyslog(LOG_ERR, "authreadkeys: file '%s': %m", 157290001Sglebius file); 158293896Sglebius goto onerror; 15954359Sroberto } 160290001Sglebius INIT_SSL(); 16154359Sroberto 16254359Sroberto /* 163293896Sglebius * Now read lines from the file, looking for key entries. Put 164293896Sglebius * the data into temporary store for later propagation to avoid 165293896Sglebius * two-pass processing. 16654359Sroberto */ 167290001Sglebius nerr = 0; 16854359Sroberto while ((line = fgets(buf, sizeof buf, fp)) != NULL) { 169290001Sglebius if (nerr > nerr_maxlimit) 170290001Sglebius break; 17154359Sroberto token = nexttok(&line); 172290001Sglebius if (token == NULL) 173290001Sglebius continue; 17454359Sroberto 17554359Sroberto /* 17654359Sroberto * First is key number. See if it is okay. 17754359Sroberto */ 17854359Sroberto keyno = atoi(token); 179298770Sdelphij if (keyno < 1) { 180290001Sglebius log_maybe(&nerr, 181290001Sglebius "authreadkeys: cannot change key %s", 182290001Sglebius token); 18354359Sroberto continue; 18454359Sroberto } 18554359Sroberto 18654359Sroberto if (keyno > NTP_MAXKEY) { 187290001Sglebius log_maybe(&nerr, 188290001Sglebius "authreadkeys: key %s > %d reserved for Autokey", 189290001Sglebius token, NTP_MAXKEY); 19054359Sroberto continue; 19154359Sroberto } 19254359Sroberto 19354359Sroberto /* 194290001Sglebius * Next is keytype. See if that is all right. 19554359Sroberto */ 19654359Sroberto token = nexttok(&line); 197290001Sglebius if (token == NULL) { 198290001Sglebius log_maybe(&nerr, 199290001Sglebius "authreadkeys: no key type for key %d", 200290001Sglebius 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 */ 211290001Sglebius#ifdef OPENSSL 212290001Sglebius /* 213290001Sglebius * The key type is the NID used by the message digest 214290001Sglebius * algorithm. There are a number of inconsistencies in 215290001Sglebius * the OpenSSL database. We attempt to discover them 216290001Sglebius * here and prevent use of inconsistent data later. 217290001Sglebius */ 218290001Sglebius keytype = keytype_from_text(token, NULL); 219290001Sglebius if (keytype == 0) { 220298770Sdelphij log_maybe(NULL, 221290001Sglebius "authreadkeys: invalid type for key %d", 222290001Sglebius keyno); 223298770Sdelphij } else if (EVP_get_digestbynid(keytype) == NULL) { 224298770Sdelphij log_maybe(NULL, 225290001Sglebius "authreadkeys: no algorithm for key %d", 226290001Sglebius keyno); 227298770Sdelphij keytype = 0; 228290001Sglebius } 229290001Sglebius#else /* !OPENSSL follows */ 23054359Sroberto /* 231290001Sglebius * The key type is unused, but is required to be 'M' or 232290001Sglebius * 'm' for compatibility. 23354359Sroberto */ 234290001Sglebius if (!(*token == 'M' || *token == 'm')) { 235298770Sdelphij log_maybe(NULL, 236290001Sglebius "authreadkeys: invalid type for key %d", 237290001Sglebius keyno); 238298770Sdelphij keytype = 0; 239298770Sdelphij } else { 240298770Sdelphij keytype = KEY_TYPE_MD5; 241290001Sglebius } 242290001Sglebius#endif /* !OPENSSL */ 243290001Sglebius 244290001Sglebius /* 245290001Sglebius * Finally, get key and insert it. If it is longer than 20 246290001Sglebius * characters, it is a binary string encoded in hex; 247290001Sglebius * otherwise, it is a text string of printable ASCII 248290001Sglebius * characters. 249290001Sglebius */ 25054359Sroberto token = nexttok(&line); 251290001Sglebius if (token == NULL) { 252290001Sglebius log_maybe(&nerr, 253290001Sglebius "authreadkeys: no key for key %d", keyno); 254290001Sglebius continue; 255290001Sglebius } 256293896Sglebius next = NULL; 257290001Sglebius len = strlen(token); 258290001Sglebius if (len <= 20) { /* Bug 2537 */ 259293896Sglebius next = emalloc(sizeof(KeyDataT) + len); 260294905Sdelphij next->keyacclist = NULL; 261293896Sglebius next->keyid = keyno; 262293896Sglebius next->keytype = keytype; 263293896Sglebius next->seclen = len; 264293896Sglebius memcpy(next->secbuf, token, len); 26554359Sroberto } else { 266293896Sglebius static const char hex[] = "0123456789abcdef"; 267290001Sglebius u_char temp; 268290001Sglebius char *ptr; 269290001Sglebius size_t jlim; 270290001Sglebius 271290001Sglebius jlim = min(len, 2 * sizeof(keystr)); 272290001Sglebius for (j = 0; j < jlim; j++) { 273290001Sglebius ptr = strchr(hex, tolower((unsigned char)token[j])); 274290001Sglebius if (ptr == NULL) 275290001Sglebius break; /* abort decoding */ 276290001Sglebius temp = (u_char)(ptr - hex); 277290001Sglebius if (j & 1) 278290001Sglebius keystr[j / 2] |= temp; 279290001Sglebius else 280290001Sglebius keystr[j / 2] = temp << 4; 28154359Sroberto } 282290001Sglebius if (j < jlim) { 283290001Sglebius log_maybe(&nerr, 284290001Sglebius "authreadkeys: invalid hex digit for key %d", 285290001Sglebius keyno); 286290001Sglebius continue; 287290001Sglebius } 288293896Sglebius len = jlim/2; /* hmmmm.... what about odd length?!? */ 289293896Sglebius next = emalloc(sizeof(KeyDataT) + len); 290294905Sdelphij next->keyacclist = NULL; 291293896Sglebius next->keyid = keyno; 292293896Sglebius next->keytype = keytype; 293293896Sglebius next->seclen = len; 294293896Sglebius memcpy(next->secbuf, keystr, len); 29554359Sroberto } 296294905Sdelphij 297294905Sdelphij token = nexttok(&line); 298298770Sdelphij DPRINTF(0, ("authreadkeys: full access list <%s>\n", (token) ? token : "NULL")); 299294905Sdelphij if (token != NULL) { /* A comma-separated IP access list */ 300294905Sdelphij char *tp = token; 301294905Sdelphij 302294905Sdelphij while (tp) { 303294905Sdelphij char *i; 304298770Sdelphij sockaddr_u addr; 305294905Sdelphij 306294905Sdelphij i = strchr(tp, (int)','); 307294905Sdelphij if (i) 308294905Sdelphij *i = '\0'; 309298770Sdelphij DPRINTF(0, ("authreadkeys: access list: <%s>\n", tp)); 310294905Sdelphij 311298770Sdelphij if (is_ip_address(tp, AF_UNSPEC, &addr)) { 312298770Sdelphij next->keyacclist = keyacc_new_push( 313298770Sdelphij next->keyacclist, &addr); 314294905Sdelphij } else { 315294905Sdelphij log_maybe(&nerr, 316294905Sdelphij "authreadkeys: invalid IP address <%s> for key %d", 317294905Sdelphij tp, keyno); 318294905Sdelphij } 319294905Sdelphij 320294905Sdelphij if (i) { 321294905Sdelphij tp = i + 1; 322294905Sdelphij } else { 323294905Sdelphij tp = 0; 324294905Sdelphij } 325294905Sdelphij } 326294905Sdelphij } 327294905Sdelphij 328298770Sdelphij /* check if this has to be weeded out... */ 329298770Sdelphij if (0 == keytype) { 330298770Sdelphij free_keydata(next); 331298770Sdelphij next = NULL; 332298770Sdelphij continue; 333298770Sdelphij } 334298770Sdelphij 335293896Sglebius INSIST(NULL != next); 336293896Sglebius next->next = list; 337293896Sglebius list = next; 33854359Sroberto } 339290001Sglebius fclose(fp); 340293896Sglebius if (nerr > 0) { 341298770Sdelphij const char * why = ""; 342298770Sdelphij if (nerr > nerr_maxlimit) 343298770Sdelphij why = " (emergency break)"; 344290001Sglebius msyslog(LOG_ERR, 345298770Sdelphij "authreadkeys: rejecting file '%s' after %u error(s)%s", 346298770Sdelphij file, nerr, why); 347293896Sglebius goto onerror; 348290001Sglebius } 349293896Sglebius 350293896Sglebius /* first remove old file-based keys */ 351293896Sglebius auth_delkeys(); 352293896Sglebius /* insert the new key material */ 353293896Sglebius while (NULL != (next = list)) { 354293896Sglebius list = next->next; 355293896Sglebius MD5auth_setkey(next->keyid, next->keytype, 356294905Sdelphij next->secbuf, next->seclen, next->keyacclist); 357298770Sdelphij next->keyacclist = NULL; /* consumed by MD5auth_setkey */ 358298770Sdelphij free_keydata(next); 359293896Sglebius } 360290001Sglebius return (1); 361293896Sglebius 362293896Sglebius onerror: 363293896Sglebius /* Mop up temporary storage before bailing out. */ 364293896Sglebius while (NULL != (next = list)) { 365293896Sglebius list = next->next; 366298770Sdelphij free_keydata(next); 367293896Sglebius } 368293896Sglebius return (0); 36954359Sroberto} 370