154359Sroberto/* 254359Sroberto * authreadkeys.c - routines to support the reading of the key file 354359Sroberto */ 4290000Sglebius#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 15290000Sglebius#ifdef OPENSSL 16290000Sglebius#include "openssl/objects.h" 17290000Sglebius#include "openssl/evp.h" 18290000Sglebius#endif /* OPENSSL */ 1954359Sroberto 2054359Sroberto/* Forwards */ 21290000Sglebiusstatic char *nexttok (char **); 2254359Sroberto 2354359Sroberto/* 2454359Sroberto * nexttok - basic internal tokenizing routine 2554359Sroberto */ 2654359Srobertostatic char * 2754359Srobertonexttok( 28290000Sglebius 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') 40290000Sglebius 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 != '#') 48290000Sglebius 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) 55290000Sglebius return NULL; 5654359Sroberto 5754359Sroberto if (*cp == ' ' || *cp == '\t') 58290000Sglebius *cp++ = '\0'; 5954359Sroberto else 60290000Sglebius *cp = '\0'; 6154359Sroberto 6254359Sroberto *str = cp; 6354359Sroberto return starttok; 6454359Sroberto} 6554359Sroberto 6654359Sroberto 67290000Sglebius/* TALOS-CAN-0055: possibly DoS attack by setting the key file to the 68290000Sglebius * log file. This is hard to prevent (it would need to check two files 69290000Sglebius * to be the same on the inode level, which will not work so easily with 70290000Sglebius * Windows or VMS) but we can avoid the self-amplification loop: We only 71290000Sglebius * log the first 5 errors, silently ignore the next 10 errors, and give 72290000Sglebius * up when when we have found more than 15 errors. 73290000Sglebius * 74290000Sglebius * This avoids the endless file iteration we will end up with otherwise, 75290000Sglebius * and also avoids overflowing the log file. 76290000Sglebius * 77290000Sglebius * Nevertheless, once this happens, the keys are gone since this would 78290000Sglebius * require a save/swap strategy that is not easy to apply due to the 79290000Sglebius * data on global/static level. 80290000Sglebius */ 81290000Sglebius 82293894Sglebiusstatic const u_int nerr_loglimit = 5u; 83293894Sglebiusstatic const u_int nerr_maxlimit = 15; 84290000Sglebius 85293894Sglebiusstatic void log_maybe(u_int*, const char*, ...) NTP_PRINTF(2, 3); 86290000Sglebius 87293894Sglebiustypedef struct keydata KeyDataT; 88293894Sglebiusstruct keydata { 89293894Sglebius KeyDataT *next; /* queue/stack link */ 90294904Sdelphij KeyAccT *keyacclist; /* key access list */ 91293894Sglebius keyid_t keyid; /* stored key ID */ 92293894Sglebius u_short keytype; /* stored key type */ 93293894Sglebius u_short seclen; /* length of secret */ 94293894Sglebius u_char secbuf[1]; /* begin of secret (formal only)*/ 95293894Sglebius}; 96293894Sglebius 97290000Sglebiusstatic void 98290000Sglebiuslog_maybe( 99293894Sglebius u_int *pnerr, 100290000Sglebius const char *fmt , 101290000Sglebius ...) 102290000Sglebius{ 103290000Sglebius va_list ap; 104298770Sdelphij if ((NULL == pnerr) || (++(*pnerr) <= nerr_loglimit)) { 105290000Sglebius va_start(ap, fmt); 106290000Sglebius mvsyslog(LOG_ERR, fmt, ap); 107290000Sglebius va_end(ap); 108290000Sglebius } 109290000Sglebius} 110290000Sglebius 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{ 139290000Sglebius FILE *fp; 140290000Sglebius char *line; 141290000Sglebius char *token; 142290000Sglebius keyid_t keyno; 143290000Sglebius int keytype; 144290000Sglebius char buf[512]; /* lots of room for line */ 145290000Sglebius u_char keystr[32]; /* Bug 2537 */ 146290000Sglebius size_t len; 147290000Sglebius size_t j; 148293894Sglebius u_int nerr; 149293894Sglebius KeyDataT *list = NULL; 150293894Sglebius 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) { 156293894Sglebius msyslog(LOG_ERR, "authreadkeys: file '%s': %m", 157290000Sglebius file); 158293894Sglebius goto onerror; 15954359Sroberto } 160290000Sglebius INIT_SSL(); 16154359Sroberto 16254359Sroberto /* 163293894Sglebius * Now read lines from the file, looking for key entries. Put 164293894Sglebius * the data into temporary store for later propagation to avoid 165293894Sglebius * two-pass processing. 16654359Sroberto */ 167290000Sglebius nerr = 0; 16854359Sroberto while ((line = fgets(buf, sizeof buf, fp)) != NULL) { 169290000Sglebius if (nerr > nerr_maxlimit) 170290000Sglebius break; 17154359Sroberto token = nexttok(&line); 172290000Sglebius if (token == NULL) 173290000Sglebius continue; 17454359Sroberto 17554359Sroberto /* 17654359Sroberto * First is key number. See if it is okay. 17754359Sroberto */ 17854359Sroberto keyno = atoi(token); 179298770Sdelphij if (keyno < 1) { 180290000Sglebius log_maybe(&nerr, 181290000Sglebius "authreadkeys: cannot change key %s", 182290000Sglebius token); 18354359Sroberto continue; 18454359Sroberto } 18554359Sroberto 18654359Sroberto if (keyno > NTP_MAXKEY) { 187290000Sglebius log_maybe(&nerr, 188290000Sglebius "authreadkeys: key %s > %d reserved for Autokey", 189290000Sglebius token, NTP_MAXKEY); 19054359Sroberto continue; 19154359Sroberto } 19254359Sroberto 19354359Sroberto /* 194290000Sglebius * Next is keytype. See if that is all right. 19554359Sroberto */ 19654359Sroberto token = nexttok(&line); 197290000Sglebius if (token == NULL) { 198290000Sglebius log_maybe(&nerr, 199290000Sglebius "authreadkeys: no key type for key %d", 200290000Sglebius 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 */ 211290000Sglebius#ifdef OPENSSL 212290000Sglebius /* 213290000Sglebius * The key type is the NID used by the message digest 214290000Sglebius * algorithm. There are a number of inconsistencies in 215290000Sglebius * the OpenSSL database. We attempt to discover them 216290000Sglebius * here and prevent use of inconsistent data later. 217290000Sglebius */ 218290000Sglebius keytype = keytype_from_text(token, NULL); 219290000Sglebius if (keytype == 0) { 220298770Sdelphij log_maybe(NULL, 221290000Sglebius "authreadkeys: invalid type for key %d", 222290000Sglebius keyno); 223298770Sdelphij } else if (EVP_get_digestbynid(keytype) == NULL) { 224298770Sdelphij log_maybe(NULL, 225290000Sglebius "authreadkeys: no algorithm for key %d", 226290000Sglebius keyno); 227298770Sdelphij keytype = 0; 228290000Sglebius } 229290000Sglebius#else /* !OPENSSL follows */ 23054359Sroberto /* 231290000Sglebius * The key type is unused, but is required to be 'M' or 232290000Sglebius * 'm' for compatibility. 23354359Sroberto */ 234290000Sglebius if (!(*token == 'M' || *token == 'm')) { 235298770Sdelphij log_maybe(NULL, 236290000Sglebius "authreadkeys: invalid type for key %d", 237290000Sglebius keyno); 238298770Sdelphij keytype = 0; 239298770Sdelphij } else { 240298770Sdelphij keytype = KEY_TYPE_MD5; 241290000Sglebius } 242290000Sglebius#endif /* !OPENSSL */ 243290000Sglebius 244290000Sglebius /* 245290000Sglebius * Finally, get key and insert it. If it is longer than 20 246290000Sglebius * characters, it is a binary string encoded in hex; 247290000Sglebius * otherwise, it is a text string of printable ASCII 248290000Sglebius * characters. 249290000Sglebius */ 25054359Sroberto token = nexttok(&line); 251290000Sglebius if (token == NULL) { 252290000Sglebius log_maybe(&nerr, 253290000Sglebius "authreadkeys: no key for key %d", keyno); 254290000Sglebius continue; 255290000Sglebius } 256293894Sglebius next = NULL; 257290000Sglebius len = strlen(token); 258290000Sglebius if (len <= 20) { /* Bug 2537 */ 259293894Sglebius next = emalloc(sizeof(KeyDataT) + len); 260294904Sdelphij next->keyacclist = NULL; 261293894Sglebius next->keyid = keyno; 262293894Sglebius next->keytype = keytype; 263293894Sglebius next->seclen = len; 264293894Sglebius memcpy(next->secbuf, token, len); 26554359Sroberto } else { 266293894Sglebius static const char hex[] = "0123456789abcdef"; 267290000Sglebius u_char temp; 268290000Sglebius char *ptr; 269290000Sglebius size_t jlim; 270290000Sglebius 271290000Sglebius jlim = min(len, 2 * sizeof(keystr)); 272290000Sglebius for (j = 0; j < jlim; j++) { 273290000Sglebius ptr = strchr(hex, tolower((unsigned char)token[j])); 274290000Sglebius if (ptr == NULL) 275290000Sglebius break; /* abort decoding */ 276290000Sglebius temp = (u_char)(ptr - hex); 277290000Sglebius if (j & 1) 278290000Sglebius keystr[j / 2] |= temp; 279290000Sglebius else 280290000Sglebius keystr[j / 2] = temp << 4; 28154359Sroberto } 282290000Sglebius if (j < jlim) { 283290000Sglebius log_maybe(&nerr, 284290000Sglebius "authreadkeys: invalid hex digit for key %d", 285290000Sglebius keyno); 286290000Sglebius continue; 287290000Sglebius } 288293894Sglebius len = jlim/2; /* hmmmm.... what about odd length?!? */ 289293894Sglebius next = emalloc(sizeof(KeyDataT) + len); 290294904Sdelphij next->keyacclist = NULL; 291293894Sglebius next->keyid = keyno; 292293894Sglebius next->keytype = keytype; 293293894Sglebius next->seclen = len; 294293894Sglebius 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 335293894Sglebius INSIST(NULL != next); 336293894Sglebius next->next = list; 337293894Sglebius list = next; 33854359Sroberto } 339290000Sglebius fclose(fp); 340293894Sglebius if (nerr > 0) { 341298770Sdelphij const char * why = ""; 342298770Sdelphij if (nerr > nerr_maxlimit) 343298770Sdelphij why = " (emergency break)"; 344290000Sglebius msyslog(LOG_ERR, 345298770Sdelphij "authreadkeys: rejecting file '%s' after %u error(s)%s", 346298770Sdelphij file, nerr, why); 347293894Sglebius goto onerror; 348290000Sglebius } 349293894Sglebius 350293894Sglebius /* first remove old file-based keys */ 351293894Sglebius auth_delkeys(); 352293894Sglebius /* insert the new key material */ 353293894Sglebius while (NULL != (next = list)) { 354293894Sglebius list = next->next; 355293894Sglebius 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); 359293894Sglebius } 360290000Sglebius return (1); 361293894Sglebius 362293894Sglebius onerror: 363293894Sglebius /* Mop up temporary storage before bailing out. */ 364293894Sglebius while (NULL != (next = list)) { 365293894Sglebius list = next->next; 366298770Sdelphij free_keydata(next); 367293894Sglebius } 368293894Sglebius return (0); 36954359Sroberto} 370