/* * authreadkeys.c - routines to support the reading of the key file */ #include #include #include //#include "ntpd.h" /* Only for DPRINTF */ //#include "ntp_fp.h" #include "ntp.h" #include "ntp_syslog.h" #include "ntp_stdlib.h" #include "ntp_keyacc.h" #ifdef OPENSSL #include "openssl/objects.h" #include "openssl/evp.h" #endif /* OPENSSL */ /* Forwards */ static char *nexttok (char **); /* * nexttok - basic internal tokenizing routine */ static char * nexttok( char **str ) { register char *cp; char *starttok; cp = *str; /* * Space past white space */ while (*cp == ' ' || *cp == '\t') cp++; /* * Save this and space to end of token */ starttok = cp; while (*cp != '\0' && *cp != '\n' && *cp != ' ' && *cp != '\t' && *cp != '#') cp++; /* * If token length is zero return an error, else set end of * token to zero and return start. */ if (starttok == cp) return NULL; if (*cp == ' ' || *cp == '\t') *cp++ = '\0'; else *cp = '\0'; *str = cp; return starttok; } /* TALOS-CAN-0055: possibly DoS attack by setting the key file to the * log file. This is hard to prevent (it would need to check two files * to be the same on the inode level, which will not work so easily with * Windows or VMS) but we can avoid the self-amplification loop: We only * log the first 5 errors, silently ignore the next 10 errors, and give * up when when we have found more than 15 errors. * * This avoids the endless file iteration we will end up with otherwise, * and also avoids overflowing the log file. * * Nevertheless, once this happens, the keys are gone since this would * require a save/swap strategy that is not easy to apply due to the * data on global/static level. */ static const u_int nerr_loglimit = 5u; static const u_int nerr_maxlimit = 15; static void log_maybe(u_int*, const char*, ...) NTP_PRINTF(2, 3); typedef struct keydata KeyDataT; struct keydata { KeyDataT *next; /* queue/stack link */ KeyAccT *keyacclist; /* key access list */ keyid_t keyid; /* stored key ID */ u_short keytype; /* stored key type */ u_short seclen; /* length of secret */ u_char secbuf[1]; /* begin of secret (formal only)*/ }; static void log_maybe( u_int *pnerr, const char *fmt , ...) { va_list ap; if ((NULL == pnerr) || (++(*pnerr) <= nerr_loglimit)) { va_start(ap, fmt); mvsyslog(LOG_ERR, fmt, ap); va_end(ap); } } static void free_keydata( KeyDataT *node ) { KeyAccT *kap; if (node) { while (node->keyacclist) { kap = node->keyacclist; node->keyacclist = kap->next; free(kap); } /* purge secrets from memory before free()ing it */ memset(node, 0, sizeof(*node) + node->seclen); free(node); } } /* * authreadkeys - (re)read keys from a file. */ int authreadkeys( const char *file ) { FILE *fp; char *line; char *token; keyid_t keyno; int keytype; char buf[512]; /* lots of room for line */ u_char keystr[AUTHPWD_MAXSECLEN]; size_t len; u_int nerr; KeyDataT *list = NULL; KeyDataT *next = NULL; /* * Open file. Complain and return if it can't be opened. */ fp = fopen(file, "r"); if (fp == NULL) { msyslog(LOG_ERR, "authreadkeys: file '%s': %m", file); goto onerror; } INIT_SSL(); /* * Now read lines from the file, looking for key entries. Put * the data into temporary store for later propagation to avoid * two-pass processing. */ nerr = 0; while ((line = fgets(buf, sizeof buf, fp)) != NULL) { if (nerr > nerr_maxlimit) break; token = nexttok(&line); if (token == NULL) continue; /* * First is key number. See if it is okay. */ keyno = atoi(token); if (keyno < 1) { log_maybe(&nerr, "authreadkeys: cannot change key %s", token); continue; } if (keyno > NTP_MAXKEY) { log_maybe(&nerr, "authreadkeys: key %s > %d reserved for Autokey", token, NTP_MAXKEY); continue; } /* * Next is keytype. See if that is all right. */ token = nexttok(&line); if (token == NULL) { log_maybe(&nerr, "authreadkeys: no key type for key %d", keyno); continue; } /* We want to silently ignore keys where we do not * support the requested digest type. OTOH, we want to * make sure the file is well-formed. That means we * have to process the line completely and have to * finally throw away the result... This is a bit more * work, but it also results in better error detection. */ #ifdef OPENSSL /* * The key type is the NID used by the message digest * algorithm. There are a number of inconsistencies in * the OpenSSL database. We attempt to discover them * here and prevent use of inconsistent data later. */ keytype = keytype_from_text(token, NULL); if (keytype == 0) { log_maybe(NULL, "authreadkeys: invalid type for key %d", keyno); # ifdef ENABLE_CMAC } else if (NID_cmac != keytype && EVP_get_digestbynid(keytype) == NULL) { log_maybe(NULL, "authreadkeys: no algorithm for key %d", keyno); keytype = 0; # endif /* ENABLE_CMAC */ } #else /* !OPENSSL follows */ /* * The key type is unused, but is required to be 'M' or * 'm' for compatibility. */ if (! (toupper(*token) == 'M')) { log_maybe(NULL, "authreadkeys: invalid type for key %d", keyno); keytype = 0; } else { keytype = KEY_TYPE_MD5; } #endif /* !OPENSSL */ /* * Finally, get key and insert it. If it is longer than 20 * characters, it is a binary string encoded in hex; * otherwise, it is a text string of printable ASCII * characters. */ token = nexttok(&line); if (token == NULL) { log_maybe(&nerr, "authreadkeys: no key for key %d", keyno); continue; } next = NULL; len = authdecodepw(keystr, sizeof(keystr), token, AUTHPWD_UNSPEC); if (len > sizeof(keystr)) { switch (errno) { case ENOMEM: log_maybe(&nerr, "authreadkeys: passwd too long for key %d", keyno); break; case EINVAL: log_maybe(&nerr, "authreadkeys: passwd has bad char for key %d", keyno); break; default: log_maybe(&nerr, "authreadkeys: unknown errno %d for key %d", errno, keyno); break; } continue; } next = emalloc(sizeof(KeyDataT) + len); next->keyacclist = NULL; next->keyid = keyno; next->keytype = keytype; next->seclen = len; memcpy(next->secbuf, keystr, len); token = nexttok(&line); if (token != NULL) { /* A comma-separated IP access list */ char *tp = token; while (tp) { char *i; char *snp; /* subnet text pointer */ unsigned int snbits; sockaddr_u addr; i = strchr(tp, (int)','); if (i) { *i = '\0'; } snp = strchr(tp, (int)'/'); if (snp) { char *sp; *snp++ = '\0'; snbits = 0; sp = snp; while (*sp != '\0') { if (!isdigit((unsigned char)*sp)) break; if (snbits > 1000) break; /* overflow */ snbits = 10 * snbits + (*sp++ - '0'); /* ascii dependent */ } if (*sp != '\0') { log_maybe(&nerr, "authreadkeys: Invalid character in subnet specification for <%s/%s> in key %d", sp, snp, keyno); goto nextip; } } else { snbits = UINT_MAX; } if (is_ip_address(tp, AF_UNSPEC, &addr)) { /* Make sure that snbits is valid for addr */ if ((snbits < UINT_MAX) && ( (IS_IPV4(&addr) && snbits > 32) || (IS_IPV6(&addr) && snbits > 128))) { log_maybe(NULL, "authreadkeys: excessive subnet mask <%s/%s> for key %d", tp, snp, keyno); } next->keyacclist = keyacc_new_push( next->keyacclist, &addr, snbits); } else { log_maybe(&nerr, "authreadkeys: invalid IP address <%s> for key %d", tp, keyno); } nextip: if (i) { tp = i + 1; } else { tp = 0; } } } /* check if this has to be weeded out... */ if (0 == keytype) { free_keydata(next); next = NULL; continue; } DEBUG_INSIST(NULL != next); #if defined(OPENSSL) && defined(ENABLE_CMAC) if (NID_cmac == keytype && len < 16) { msyslog(LOG_WARNING, CMAC " keys are 128 bits, " "zero-extending key %u by %u bits", (u_int)keyno, 8 * (16 - (u_int)len)); } #endif /* OPENSSL && ENABLE_CMAC */ next->next = list; list = next; } fclose(fp); if (nerr > 0) { const char * why = ""; if (nerr > nerr_maxlimit) why = " (emergency break)"; msyslog(LOG_ERR, "authreadkeys: rejecting file '%s' after %u error(s)%s", file, nerr, why); goto onerror; } /* first remove old file-based keys */ auth_delkeys(); /* insert the new key material */ while (NULL != (next = list)) { list = next->next; MD5auth_setkey(next->keyid, next->keytype, next->secbuf, next->seclen, next->keyacclist); next->keyacclist = NULL; /* consumed by MD5auth_setkey */ free_keydata(next); } return (1); onerror: /* Mop up temporary storage before bailing out. */ while (NULL != (next = list)) { list = next->next; free_keydata(next); } return (0); }