+* [Bug 1531] Require nonce with mrulist requests.
+* [Bug 1532] Remove ntpd support for ntpdc's monlist in favor of ntpq's
+ mrulist.
* Include (4.2.6p2-RC1) - [Bug 1503] [Bug 1504] [Bug 1518] [Bug 1522],
all of which were fixed in 4.2.7 previously.
(4.2.7p24) 2010/04/13 Released by Harlan Stenn <stenn@ntp.org>
#define CTL_OP_SAVECONFIG 9 /* save config to file */
#define CTL_OP_READ_MRU 10 /* retrieve MRU (mrulist) */
#define CTL_OP_READ_IFSTATS 11 /* interface stats (ifstats) */
+#define CTL_OP_REQ_NONCE 12 /* request a client nonce */
#define CTL_OP_UNSETTRAP 31 /* unset trap */
/*
/*
* ntp_md5.h: deal with md5.h headers
+ *
+ * Use the system MD5 if available, otherwise libisc's.
*/
-
#if defined HAVE_MD5_H && defined HAVE_MD5INIT
# include <md5.h>
#else
# include "isc/md5.h"
-# define MD5_CTX isc_md5_t
-# define MD5Init isc_md5_init
-# define MD5Update isc_md5_update
-# define MD5Final(d,c) isc_md5_final(c,d)
+ typedef isc_md5_t MD5_CTX;
+# define MD5Init(c) isc_md5_init(c)
+# define MD5Update(c, p, s) isc_md5_update(c, p, s)
+# define MD5Final(d, c) isc_md5_final((c), (d)) /* swapped */
+#endif
+
+/*
+ * Provide OpenSSL-alike MD5 API if we're not using OpenSSL
+ */
+#ifndef OPENSSL
+ typedef MD5_CTX EVP_MD_CTX;
+# define EVP_get_digestbynid(t) NULL
+# define EVP_DigestInit(c, dt) MD5Init(c)
+# define EVP_DigestUpdate(c, p, s) MD5Update(c, p, s)
+# define EVP_DigestFinal(c, d, pdl) \
+ do { \
+ MD5Final((d), (c)); \
+ *(pdl) = 16; \
+ } while (0)
#endif
extern int ntp_minpkt;
extern u_char ntp_minpoll;
+/* ntp_scanner.c */
+extern u_int32 conf_file_sum; /* Simple sum of characters */
+
/* ntp_signd.c */
#ifdef HAVE_NTP_SIGND
extern void send_via_ntp_signd(struct recvbuf, int, keyid_t, int,
#include "ntp_stdlib.h"
#include "ntp.h"
#ifdef OPENSSL
-#include "openssl/evp.h"
+# include "openssl/evp.h"
#else
-#include "ntp_md5.h"
-#endif /* OPENSSSL */
+# include "ntp_md5.h" /* provides clone of OpenSSL MD5 API */
+#endif
/*
* MD5authencrypt - generate message digest
{
u_char digest[EVP_MAX_MD_SIZE];
u_int len;
-#ifdef OPENSSL
EVP_MD_CTX ctx;
-#else
- MD5_CTX md5;
-#endif /* OPENSSL */
/*
* Compute digest of key concatenated with packet. Note: the
* key type and digest type have been verified when the key
* was creaded.
*/
-#ifdef OPENSSL
INIT_SSL();
EVP_DigestInit(&ctx, EVP_get_digestbynid(type));
EVP_DigestUpdate(&ctx, key, (u_int)cache_keylen);
EVP_DigestUpdate(&ctx, (u_char *)pkt, (u_int)length);
EVP_DigestFinal(&ctx, digest, &len);
-#else /* OPENSSL */
- MD5Init(&md5);
- MD5Update(&md5, key, (u_int)cache_keylen);
- MD5Update(&md5, (u_char *)pkt, (u_int)length);
- MD5Final(digest, &md5);
- len = 16;
-#endif /* OPENSSL */
memmove((u_char *)pkt + length + 4, digest, len);
return (len + 4);
}
{
u_char digest[EVP_MAX_MD_SIZE];
u_int len;
-#ifdef OPENSSL
EVP_MD_CTX ctx;
-#else
- MD5_CTX md5;
-#endif /* OPENSSL */
/*
* Compute digest of key concatenated with packet. Note: the
* key type and digest type have been verified when the key
* was created.
*/
-#ifdef OPENSSL
INIT_SSL();
EVP_DigestInit(&ctx, EVP_get_digestbynid(type));
EVP_DigestUpdate(&ctx, key, (u_int)cache_keylen);
EVP_DigestUpdate(&ctx, (u_char *)pkt, (u_int)length);
EVP_DigestFinal(&ctx, digest, &len);
-#else /* OPENSSL */
- MD5Init(&md5);
- MD5Update(&md5, key, (u_int)cache_keylen);
- MD5Update(&md5, (u_char *)pkt, (u_int)length);
- MD5Final(digest, &md5);
- len = 16;
-#endif /* OPENSSL */
if ((u_int)size != len + 4) {
msyslog(LOG_ERR,
"MAC decrypt: MAC length error");
{
u_char digest[20];
u_int32 addr_refid;
-#ifdef OPENSSL
EVP_MD_CTX ctx;
u_int len;
-#else
- MD5_CTX md5;
-#endif /* OPENSSL */
if (IS_IPV4(addr))
return (NSRCADR(addr));
-#ifdef OPENSSL
INIT_SSL();
EVP_DigestInit(&ctx, EVP_get_digestbynid(NID_md5));
EVP_DigestUpdate(&ctx, (u_char *)PSOCK_ADDR6(addr),
sizeof(struct in6_addr));
EVP_DigestFinal(&ctx, digest, &len);
-#else
- MD5Init(&md5);
- MD5Update(&md5, (u_char *)PSOCK_ADDR6(addr),
- sizeof(struct in6_addr));
- MD5Final(digest, &md5);
-#endif /* OPENSSL */
memcpy(&addr_refid, digest, 4);
return (addr_refid);
}
#endif
#include <arpa/inet.h>
+#ifdef OPENSSL
+# include "openssl/evp.h"
+#else
+# include "ntp_md5.h" /* provides clone of OpenSSL MD5 API */
+#endif
+
+
/*
* Structure to hold request procedure information
*/
static void send_mru_entry (mon_entry *, int);
static void send_random_tag_value(int);
static void read_mru_list (struct recvbuf *, int);
-static void send_ifstats_entry(struct interface *, u_int);
+static void send_ifstats_entry(struct interface *, u_int);
static void read_ifstats (struct recvbuf *, int);
+static u_int32 derive_nonce (sockaddr_u *, u_int32, u_int32);
+static void generate_nonce (struct recvbuf *, char *, size_t);
+static int validate_nonce (const char *, struct recvbuf *);
+static void req_nonce (struct recvbuf *, int);
static void unset_trap (struct recvbuf *, int);
static struct ctl_trap *ctlfindtrap(sockaddr_u *,
struct interface *);
{ CTL_OP_CONFIGURE, AUTH, configure },
{ CTL_OP_READ_MRU, NOAUTH, read_mru_list },
{ CTL_OP_READ_IFSTATS, AUTH, read_ifstats },
+ { CTL_OP_REQ_NONCE, NOAUTH, req_nonce },
{ CTL_OP_UNSETTRAP, NOAUTH, unset_trap },
{ NO_REQUEST, 0 }
};
}
+/*
+ * derive_nonce - generate client-address-specific nonce value
+ * associated with a given timestamp.
+ */
+static u_int32 derive_nonce(
+ sockaddr_u * addr,
+ u_int32 ts_i,
+ u_int32 ts_f
+ )
+{
+ static u_int32 salt[2];
+ union d_tag {
+ u_char digest[EVP_MAX_MD_SIZE];
+ u_int32 extract;
+ } d;
+ EVP_MD_CTX ctx;
+ u_int len;
+
+ while (!salt[0])
+ salt[0] = ntp_random();
+ salt[1] = conf_file_sum;
+
+ EVP_DigestInit(&ctx, EVP_get_digestbynid(NID_md5));
+ EVP_DigestUpdate(&ctx, salt, sizeof(salt));
+ EVP_DigestUpdate(&ctx, &ts_i, sizeof(ts_i));
+ EVP_DigestUpdate(&ctx, &ts_f, sizeof(ts_f));
+ if (IS_IPV4(addr))
+ EVP_DigestUpdate(&ctx, &SOCK_ADDR4(addr),
+ sizeof(SOCK_ADDR4(addr)));
+ else
+ EVP_DigestUpdate(&ctx, &SOCK_ADDR6(addr),
+ sizeof(SOCK_ADDR6(addr)));
+ EVP_DigestUpdate(&ctx, &NSRCPORT(addr), sizeof(NSRCPORT(addr)));
+ EVP_DigestUpdate(&ctx, salt, sizeof(salt));
+ EVP_DigestFinal(&ctx, d.digest, &len);
+
+ return d.extract;
+}
+
+
+/*
+ * generate_nonce - generate client-address-specific nonce string.
+ */
+static void generate_nonce(
+ struct recvbuf * rbufp,
+ char * nonce,
+ size_t nonce_octets
+ )
+{
+ u_int32 derived;
+
+ derived = derive_nonce(&rbufp->recv_srcadr,
+ rbufp->recv_time.l_ui,
+ rbufp->recv_time.l_uf);
+ snprintf(nonce, nonce_octets, "%08x%08x%08x",
+ rbufp->recv_time.l_ui, rbufp->recv_time.l_uf, derived);
+}
+
+
+/*
+ * validate_nonce - validate client-address-specific nonce string.
+ *
+ * Returns TRUE if the local calculation of the nonce matches the
+ * client-provided value and the timestamp is recent enough.
+ */
+static int validate_nonce(
+ const char * pnonce,
+ struct recvbuf * rbufp
+ )
+{
+ u_int ts_i;
+ u_int ts_f;
+ l_fp ts;
+ l_fp now_delta;
+ u_int supposed;
+ u_int derived;
+
+ if (3 != sscanf(pnonce, "%08x%08x%08x", &ts_i, &ts_f, &supposed))
+ return FALSE;
+
+ ts.l_ui = (u_int32)ts_i;
+ ts.l_uf = (u_int32)ts_f;
+ derived = derive_nonce(&rbufp->recv_srcadr, ts.l_ui, ts.l_uf);
+ get_systime(&now_delta);
+ L_SUB(&now_delta, &ts);
+
+ return (supposed == derived && now_delta.l_ui < 16);
+}
+
+
/*
* Send a MRU list entry in response to a "ntpq -c mrulist" operation.
*
* random integer value.
*
* To try to force clients to ignore unrecognized tags in mrulist
- * and iflist responses, the first and last rows are spiced with
+ * and ifstats responses, the first and last rows are spiced with
* randomly-generated tag names with correct .# index.
* Make it three characters knowing that none of the currently-used
* tags have that length, avoiding the need to test for tag collision.
* backing up all the way to the starting point.
*
* input parameters:
+ * nonce= Regurgitated nonce retrieved by the client
+ * previously using CTL_OP_REQ_NONCE, demonstrating
+ * ability to receive traffic sent to its address.
* limit= Limit on MRU entries returned. This is the sole
* required input parameter. [1...256]
* limit=1 is a special case: Instead of fetching
* ntpq provides as many last/addr pairs as will fit in a single request
* packet, except for the first request in a MRU fetch operation.
*
- * The response begins with the next newer entry than referred to by
- * last.0 and addr.0, if the "0" entry has not been bumped to the front.
- * Otherwise, it will begin with the next entry newer than referred to
- * by last.1 and addr.1, and so on. If none of the referenced entries
- * remain unchanged, the request fails and ntpq backs up to the next
- * earlier set of entries to resync.
+ * The response begins with a new nonce value to be used for any
+ * followup request. Following the nonce is the next newer entry than
+ * referred to by last.0 and addr.0, if the "0" entry has not been
+ * bumped to the front. If it has, the first entry returned will be the
+ * next entry newer than referred to by last.1 and addr.1, and so on.
+ * If none of the referenced entries remain unchanged, the request fails
+ * and ntpq backs up to the next earlier set of entries to resync.
*
* Except for the first response, the response begins with confirmation
* of the entry that precedes the first additional entry provided:
int restrict_mask
)
{
+ const char nonce_text[] = "nonce";
const char limit_text[] = "limit";
const char mincount_text[] = "mincount";
const char resall_text[] = "resall";
struct ctl_var * v;
char * val;
char * pch;
+ char * pnonce;
+ int nonce_valid;
int i;
int priors;
u_short hash;
* fill in_parms var list with all possible input parameters.
*/
in_parms = NULL;
+ set_var(&in_parms, nonce_text, sizeof(nonce_text), 0);
set_var(&in_parms, limit_text, sizeof(limit_text), 0);
set_var(&in_parms, mincount_text, sizeof(mincount_text), 0);
set_var(&in_parms, resall_text, sizeof(resall_text), 0);
}
/* decode input parms */
+ pnonce = NULL;
limit = 0;
mincount = 0;
resall = 0;
while (NULL != (v = ctl_getitem(in_parms, &val)) &&
!(EOV & v->flags)) {
- if (!strcmp(limit_text, v->text))
+ if (!strcmp(nonce_text, v->text))
+ pnonce = estrdup(val);
+ else if (!strcmp(limit_text, v->text))
sscanf(val, "%u", &limit);
else if (!strcmp(mincount_text, v->text)) {
if (1 != sscanf(val, "%d", &mincount) ||
}
free_varlist(in_parms);
in_parms = NULL;
+
+ /* return no responses until the nonce is validated */
+ if (NULL == pnonce)
+ return;
+
+ nonce_valid = validate_nonce(pnonce, rbufp);
+ free(pnonce);
+ if (!nonce_valid)
+ return;
+
if (!(0 < limit && limit <= MRU_ROW_LIMIT)) {
ctl_error(CERR_BADVALUE);
return;
* send up to limit= entries
*/
get_systime(&now);
+ generate_nonce(rbufp, buf, sizeof(buf));
+ ctl_putunqstr("nonce", buf, strlen(buf));
prior_mon = NULL;
for (count = 0;
count < limit && mon != NULL;
}
+/*
+ * req_nonce - CTL_OP_REQ_NONCE for ntpq -c mrulist prerequisite.
+ */
+static void req_nonce(
+ struct recvbuf * rbufp,
+ int restrict_mask
+ )
+{
+ char buf[64];
+
+ generate_nonce(rbufp, buf, sizeof(buf));
+ ctl_putunqstr("nonce", buf, strlen(buf));
+ ctl_flushpkt(0);
+}
+
+
/*
* read_clockstatus - return clock radio status
*/
static void do_ressubflags (sockaddr_u *, struct interface *, struct req_pkt *);
static void do_unrestrict (sockaddr_u *, struct interface *, struct req_pkt *);
static void do_restrict (sockaddr_u *, struct interface *, struct req_pkt *, int);
-static void mon_getlist_0 (sockaddr_u *, struct interface *, struct req_pkt *);
-static void mon_getlist_1 (sockaddr_u *, struct interface *, struct req_pkt *);
+static void mon_getlist (sockaddr_u *, struct interface *, struct req_pkt *);
static void reset_stats (sockaddr_u *, struct interface *, struct req_pkt *);
static void reset_peer (sockaddr_u *, struct interface *, struct req_pkt *);
static void do_key_reread (sockaddr_u *, struct interface *, struct req_pkt *);
sizeof(struct conf_restrict), do_ressubflags },
{ REQ_UNRESTRICT, AUTH, v4sizeof(struct conf_restrict),
sizeof(struct conf_restrict), do_unrestrict },
- { REQ_MON_GETLIST, NOAUTH, 0, 0, mon_getlist_0 },
- { REQ_MON_GETLIST_1, NOAUTH, 0, 0, mon_getlist_1 },
+ { REQ_MON_GETLIST, NOAUTH, 0, 0, mon_getlist },
+ { REQ_MON_GETLIST_1, NOAUTH, 0, 0, mon_getlist },
{ REQ_RESET_STATS, AUTH, sizeof(struct reset_flags), 0, reset_stats },
{ REQ_RESET_PEER, AUTH, v4sizeof(struct conf_unpeer),
sizeof(struct conf_unpeer), reset_peer },
* mon_getlist - return monitor data
*/
static void
-mon_getlist_0(
+mon_getlist(
sockaddr_u *srcadr,
struct interface *inter,
struct req_pkt *inpkt
)
{
- register struct info_monitor *im;
- register mon_entry *md;
- l_fp now;
- l_fp diff;
- size_t count;
-
- DPRINTF(3, ("wants monitor 0 list\n"));
-
- if (!mon_enabled) {
- req_ack(srcadr, inter, inpkt, INFO_ERR_NODATA);
- return;
- }
- get_systime(&now);
- im = (struct info_monitor *)prepare_pkt(srcadr, inter, inpkt,
- v6sizeof(struct info_monitor));
- count = 0;
-
- ITER_DLIST_BEGIN(mon_mru_list, md, mru, mon_entry)
- diff = now;
- L_SUB(&diff, &md->first);
- im->avg_int = htonl((u_int32)(diff.l_ui / md->count));
- diff = now;
- L_SUB(&diff, &md->last);
- im->last_int = htonl(diff.l_ui);
- im->restr = htonl(md->flags);
- im->count = htonl((u_int32)(md->count));
- if (IS_IPV6(&md->rmtadr)) {
- if (!client_v6_capable)
- continue;
- im->addr6 = SOCK_ADDR6(&md->rmtadr);
- im->v6_flag = 1;
- } else {
- im->addr = NSRCADR(&md->rmtadr);
- if (client_v6_capable)
- im->v6_flag = 0;
- }
- im->port = NSRCPORT(&md->rmtadr);
- im->mode = PKT_MODE(md->vn_mode);
- im->version = PKT_VERSION(md->vn_mode);
- im = (struct info_monitor *)more_pkt();
- count++;
- if (NULL == im || count >= 600)
- break;
- ITER_DLIST_END()
-
- flush_pkt();
+ req_ack(srcadr, inter, inpkt, INFO_ERR_NODATA);
}
-/*
- * mon_getlist - return monitor data
- */
-static void
-mon_getlist_1(
- sockaddr_u *srcadr,
- struct interface *inter,
- struct req_pkt *inpkt
- )
-{
- register struct info_monitor_1 *im;
- register mon_entry *md;
- l_fp now;
- l_fp diff;
- size_t count;
-
- if (!mon_enabled) {
- req_ack(srcadr, inter, inpkt, INFO_ERR_NODATA);
- return;
- }
- get_systime(&now);
- im = (struct info_monitor_1 *)prepare_pkt(srcadr, inter, inpkt,
- v6sizeof(struct info_monitor_1));
- count = 0;
-
- ITER_DLIST_BEGIN(mon_mru_list, md, mru, mon_entry)
- diff = now;
- L_SUB(&diff, &md->first);
- im->avg_int = htonl((u_int32)(diff.l_ui / md->count));
- diff = now;
- L_SUB(&diff, &md->last);
- im->last_int = htonl(diff.l_ui);
- im->restr = htonl((u_int32)md->flags);
- im->count = htonl((u_int32)md->count);
- if (IS_IPV6(&md->rmtadr)) {
- if (!client_v6_capable)
- continue;
- im->addr6 = SOCK_ADDR6(&md->rmtadr);
- im->v6_flag = 1;
- im->daddr6 = SOCK_ADDR6(&md->lcladr->sin);
- } else {
- im->addr = NSRCADR(&md->rmtadr);
- if (client_v6_capable)
- im->v6_flag = 0;
- if (MDF_BCAST == md->cast_flags)
- im->daddr = NSRCADR(&md->lcladr->bcast);
- else if (md->cast_flags) {
- im->daddr = NSRCADR(&md->lcladr->sin);
- if (!im->daddr)
- im->daddr = NSRCADR(&md->lcladr->bcast);
- } else
- im->daddr = 4;
- }
- im->flags = htonl(md->cast_flags);
- im->port = NSRCPORT(&md->rmtadr);
- im->mode = PKT_MODE(md->vn_mode);
- im->version = PKT_VERSION(md->vn_mode);
- im = (struct info_monitor_1 *)more_pkt();
- count++;
- if (NULL == im || count >= 600)
- break;
- ITER_DLIST_END()
-
- flush_pkt();
-}
/*
* Module entry points and the flags they correspond with
#define MAX_LEXEME (1024 + 1) /* The maximum size of a lexeme */
char yytext[MAX_LEXEME]; /* Buffer for storing the input text/lexeme */
+u_int32 conf_file_sum; /* Simple sum of characters read */
extern int input_from_file;
while (EOF != ch && (CHAR_MIN > ch || ch > CHAR_MAX));
if (EOF != ch) {
+ if (input_from_file)
+ conf_file_sum += (u_char)ch;
++stream->col_no;
if (ch == '\n') {
stream->prev_line_col_no = stream->col_no;
struct FILE_INFO *stream
)
{
+ if (input_from_file)
+ conf_file_sum -= (u_char)ch;
if (ch == '\n') {
stream->col_no = stream->prev_line_col_no;
stream->prev_line_col_no = -1;
int c_mru_l_rc; /* this function's return code */
u_char got; /* MRU_GOT_* bits */
const char ts_fmt[] = "0x%08x.%08x";
+ const char nonce_eq[] = "nonce=";
size_t cb;
mru *mon;
mru *head;
mru *recent;
int list_complete;
+ char nonce[128];
char buf[128];
char req_buf[CTL_MAX_DATA_LEN];
char *req;
u_short hash;
mru *unlinked;
+ /*
+ * Retrieve a nonce specific to this client to demonstrate to
+ * ntpd that we're capable of receiving responses to our source
+ * IP address, and thereby unlikely to be forging the source.
+ */
+ qres = doquery(CTL_OP_REQ_NONCE, 0, 0, 0, NULL, &rstatus,
+ &rsize, &rdata);
+ if (qres) {
+ fprintf(stderr, "nonce request failed\n");
+ return FALSE;
+ }
+
+ if (strncmp(rdata, nonce_eq, sizeof(nonce_eq) - 1)) {
+ fprintf(stderr, "unexpected nonce response format: %.*s\n",
+ rsize, rdata);
+ return FALSE;
+ }
+ strncpy(nonce, rdata + sizeof(nonce_eq) - 1, sizeof(nonce));
+ chars = strlen(nonce);
+ while (chars > 0 &&
+ ('\r' == nonce[chars - 1] || '\n' == nonce[chars - 1])) {
+ chars--;
+ nonce[chars] = '\0';
+ }
+
mru_count = 0;
INIT_DLIST(mru_list, mlink);
cb = NTP_HASH_SIZE * sizeof(*hash_table);
memset(&last_older, 0, sizeof(last_older));
limit = min(3 * MAXFRAGS, ntpd_row_limit);
- snprintf(req_buf, sizeof(req_buf), "limit=%d%s", limit, parms);
+ snprintf(req_buf, sizeof(req_buf), "nonce=%s, limit=%d%s",
+ nonce, limit, parms);
while (TRUE) {
if (debug)
- fprintf(stderr, "READ_MRU: %s\n", req_buf);
+ fprintf(stderr, "READ_MRU parms: %s\n", req_buf);
qres = doqueryex(CTL_OP_READ_MRU, 0, 0, strlen(req_buf),
req_buf, &rstatus, &rsize, &rdata, TRUE);
NTP_INSIST(unlinked == recent);
free(recent);
}
- } else if (CERR_BADVALUE == qres /* temp -> */ || CERR_BADFMT == qres /* <- temp */) {
+ } else if (CERR_BADVALUE == qres) {
/* ntpd has lower cap on row limit */
ntpd_row_limit--;
limit = min(limit, ntpd_row_limit);
break;
case 'n':
- if (strcmp(tag, "now") ||
+ if (!strcmp(tag, "nonce")) {
+ strncpy(nonce, val,
+ sizeof(nonce));
+ break; /* case */
+ } else if (strcmp(tag, "now") ||
2 != sscanf(val, ts_fmt,
&pnow->l_ui,
&pnow->l_uf))
req = req_buf;
req_end = req_buf + sizeof(req_buf);
#define REQ_ROOM (req_end - req)
- snprintf(req, REQ_ROOM, "limit=%d%s", limit, parms);
+ snprintf(req, REQ_ROOM, "nonce=%s, limit=%d%s", nonce,
+ limit, parms);
req += strlen(req);
for (ri = 0, recent = HEAD_DLIST(mru_list, mlink);