diff options
-rw-r--r-- | networking/ntpd.c | 266 |
1 files changed, 241 insertions, 25 deletions
diff --git a/networking/ntpd.c b/networking/ntpd.c index 1ebdc34c3..354bff897 100644 --- a/networking/ntpd.c +++ b/networking/ntpd.c @@ -62,13 +62,19 @@ //config: help //config: Make ntpd look in /etc/ntp.conf for peers. Only "server address" //config: is supported. +//config:config FEATURE_NTP_AUTH +//config: bool "Support md5/sha1 message authentication codes" +//config: default n +//config: depends on NTPD //applet:IF_NTPD(APPLET(ntpd, BB_DIR_USR_SBIN, BB_SUID_DROP)) //kbuild:lib-$(CONFIG_NTPD) += ntpd.o //usage:#define ntpd_trivial_usage -//usage: "[-dnqNw"IF_FEATURE_NTPD_SERVER("l -I IFACE")"] [-S PROG] [-p PEER]..." +//usage: "[-dnqNw"IF_FEATURE_NTPD_SERVER("l] [-I IFACE")"] [-S PROG]" +//usage: IF_NOT_FEATURE_NTP_AUTH(" [-p PEER]...") +//usage: IF_FEATURE_NTP_AUTH(" [-k KEYFILE] [-p [keyno:N:]PEER]...") //usage:#define ntpd_full_usage "\n\n" //usage: "NTP client/server\n" //usage: "\n -d Verbose (may be repeated)" @@ -76,8 +82,16 @@ //usage: "\n -q Quit after clock is set" //usage: "\n -N Run at high priority" //usage: "\n -w Do not set time (only query peers), implies -n" -//usage: "\n -S PROG Run PROG after stepping time, stratum change, and every 11 mins" +//usage: "\n -S PROG Run PROG after stepping time, stratum change, and every 11 min" +//usage: IF_NOT_FEATURE_NTP_AUTH( //usage: "\n -p PEER Obtain time from PEER (may be repeated)" +//usage: ) +//usage: IF_FEATURE_NTP_AUTH( +//usage: "\n -k FILE Key file (ntp.keys compatible)" +//usage: "\n -p [keyno:NUM:]PEER" +//usage: "\n Obtain time from PEER (may be repeated)" +//usage: "\n Use key NUM for authentication" +//usage: ) //usage: IF_FEATURE_NTPD_CONF( //usage: "\n If -p is not given, 'server HOST' lines" //usage: "\n from /etc/ntp.conf are used" @@ -228,14 +242,18 @@ /* Parameter averaging constant */ #define AVG 4 +#define MAX_KEY_NUMBER 65535 +#define KEYID_SIZE sizeof(uint32_t) enum { NTP_VERSION = 4, NTP_MAXSTRATUM = 15, - NTP_DIGESTSIZE = 16, - NTP_MSGSIZE_NOAUTH = 48, - NTP_MSGSIZE = (NTP_MSGSIZE_NOAUTH + 4 + NTP_DIGESTSIZE), + NTP_MD5_DIGESTSIZE = 16, + NTP_MSGSIZE_NOAUTH = 48, + NTP_MSGSIZE_MD5_AUTH = NTP_MSGSIZE_NOAUTH + KEYID_SIZE + NTP_MD5_DIGESTSIZE, + NTP_SHA1_DIGESTSIZE = 20, + NTP_MSGSIZE_SHA1_AUTH = NTP_MSGSIZE_NOAUTH + KEYID_SIZE + NTP_SHA1_DIGESTSIZE, /* Status Masks */ MODE_MASK = (7 << 0), @@ -288,7 +306,7 @@ typedef struct { l_fixedpt_t m_rectime; l_fixedpt_t m_xmttime; uint32_t m_keyid; - uint8_t m_digest[NTP_DIGESTSIZE]; + uint8_t m_digest[ENABLE_FEATURE_NTP_AUTH ? NTP_SHA1_DIGESTSIZE : NTP_MD5_DIGESTSIZE]; } msg_t; typedef struct { @@ -297,9 +315,26 @@ typedef struct { double d_dispersion; } datapoint_t; +#if ENABLE_FEATURE_NTP_AUTH +enum { + HASH_MD5, + HASH_SHA1, +}; +typedef struct { + unsigned id; //try uint16_t? + smalluint type; + smalluint msg_size; + smalluint key_length; + char key[0]; +} key_entry_t; +#endif + typedef struct { len_and_sockaddr *p_lsa; char *p_dotted; +#if ENABLE_FEATURE_NTP_AUTH + key_entry_t *key_entry; +#endif int p_fd; int datapoint_idx; uint32_t lastpkt_refid; @@ -337,13 +372,14 @@ enum { OPT_q = (1 << 1), OPT_N = (1 << 2), OPT_x = (1 << 3), + OPT_k = (1 << 4) * ENABLE_FEATURE_NTP_AUTH, /* Insert new options above this line. */ /* Non-compat options: */ - OPT_w = (1 << 4), - OPT_p = (1 << 5), - OPT_S = (1 << 6), - OPT_l = (1 << 7) * ENABLE_FEATURE_NTPD_SERVER, - OPT_I = (1 << 8) * ENABLE_FEATURE_NTPD_SERVER, + OPT_w = (1 << (4+ENABLE_FEATURE_NTP_AUTH)), + OPT_p = (1 << (5+ENABLE_FEATURE_NTP_AUTH)), + OPT_S = (1 << (6+ENABLE_FEATURE_NTP_AUTH)), + OPT_l = (1 << (7+ENABLE_FEATURE_NTP_AUTH)) * ENABLE_FEATURE_NTPD_SERVER, + OPT_I = (1 << (8+ENABLE_FEATURE_NTP_AUTH)) * ENABLE_FEATURE_NTPD_SERVER, /* We hijack some bits for other purposes */ OPT_qq = (1 << 31), }; @@ -816,8 +852,12 @@ resolve_peer_hostname(peer_t *p) return lsa; } +#if !ENABLE_FEATURE_NTP_AUTH +#define add_peers(s, key_entry) \ + add_peers(s) +#endif static void -add_peers(const char *s) +add_peers(const char *s, key_entry_t *key_entry) { llist_t *item; peer_t *p; @@ -846,6 +886,7 @@ add_peers(const char *s) } } + IF_FEATURE_NTP_AUTH(p->key_entry = key_entry;) llist_add_to(&G.ntp_peers, p); G.peer_cnt++; } @@ -870,6 +911,48 @@ do_sendto(int fd, return 0; } +#if ENABLE_FEATURE_NTP_AUTH +static void +hash(key_entry_t *key_entry, const msg_t *msg, uint8_t *output) +{ + union { + md5_ctx_t m; + sha1_ctx_t s; + } ctx; + unsigned hash_size = sizeof(*msg) - sizeof(msg->m_keyid) - sizeof(msg->m_digest); + + switch (key_entry->type) { + case HASH_MD5: + md5_begin(&ctx.m); + md5_hash(&ctx.m, key_entry->key, key_entry->key_length); + md5_hash(&ctx.m, msg, hash_size); + md5_end(&ctx.m, output); + break; + default: /* it's HASH_SHA1 */ + sha1_begin(&ctx.s); + sha1_hash(&ctx.s, key_entry->key, key_entry->key_length); + sha1_hash(&ctx.s, msg, hash_size); + sha1_end(&ctx.s, output); + break; + } +} + +static void +hash_peer(peer_t *p) +{ + p->p_xmt_msg.m_keyid = htonl(p->key_entry->id); + hash(p->key_entry, &p->p_xmt_msg, p->p_xmt_msg.m_digest); +} + +static int +hashes_differ(peer_t *p, const msg_t *msg) +{ + uint8_t digest[NTP_SHA1_DIGESTSIZE]; + hash(p->key_entry, msg, digest); + return memcmp(digest, msg->m_digest, p->key_entry->msg_size - NTP_MSGSIZE_NOAUTH - KEYID_SIZE); +} +#endif + static void send_query_to_peer(peer_t *p) { @@ -946,9 +1029,18 @@ send_query_to_peer(peer_t *p) */ p->reachable_bits <<= 1; +#if ENABLE_FEATURE_NTP_AUTH + if (p->key_entry) + hash_peer(p); if (do_sendto(p->p_fd, /*from:*/ NULL, /*to:*/ &p->p_lsa->u.sa, /*addrlen:*/ p->p_lsa->len, - &p->p_xmt_msg, NTP_MSGSIZE_NOAUTH) == -1 - ) { + &p->p_xmt_msg, !p->key_entry ? NTP_MSGSIZE_NOAUTH : p->key_entry->msg_size) == -1 + ) +#else + if (do_sendto(p->p_fd, /*from:*/ NULL, /*to:*/ &p->p_lsa->u.sa, /*addrlen:*/ p->p_lsa->len, + &p->p_xmt_msg, NTP_MSGSIZE_NOAUTH) == -1 + ) +#endif + { close(p->p_fd); p->p_fd = -1; /* @@ -1924,10 +2016,21 @@ recv_and_process_peer_pkt(peer_t *p) bb_perror_msg_and_die("recv(%s) error", p->p_dotted); } - if (size != NTP_MSGSIZE_NOAUTH && size != NTP_MSGSIZE) { +#if ENABLE_FEATURE_NTP_AUTH + if (size != NTP_MSGSIZE_NOAUTH && size != NTP_MSGSIZE_MD5_AUTH && size != NTP_MSGSIZE_SHA1_AUTH) { + bb_error_msg("malformed packet received from %s", p->p_dotted); + return; + } + if (p->key_entry && hashes_differ(p, &msg)) { + bb_error_msg("invalid cryptographic hash received from %s", p->p_dotted); + return; + } +#else + if (size != NTP_MSGSIZE_NOAUTH && size != NTP_MSGSIZE_MD5_AUTH) { bb_error_msg("malformed packet received from %s", p->p_dotted); return; } +#endif if (msg.m_orgtime.int_partl != p->p_xmt_msg.m_xmttime.int_partl || msg.m_orgtime.fractionl != p->p_xmt_msg.m_xmttime.fractionl @@ -2135,7 +2238,12 @@ recv_and_process_client_pkt(void /*int fd*/) from = xzalloc(to->len); size = recv_from_to(G_listen_fd, &msg, sizeof(msg), MSG_DONTWAIT, from, &to->u.sa, to->len); - if (size != NTP_MSGSIZE_NOAUTH && size != NTP_MSGSIZE) { +#if ENABLE_FEATURE_NTP_AUTH + if (size != NTP_MSGSIZE_NOAUTH && size != NTP_MSGSIZE_MD5_AUTH && size != NTP_MSGSIZE_SHA1_AUTH) +#else + if (size != NTP_MSGSIZE_NOAUTH && size != NTP_MSGSIZE_MD5_AUTH) +#endif + { char *addr; if (size < 0) { if (errno == EAGAIN) @@ -2278,6 +2386,19 @@ recv_and_process_client_pkt(void /*int fd*/) * with the -g and -q options. See the tinker command for other options. * Note: The kernel time discipline is disabled with this option. */ +#if ENABLE_FEATURE_NTP_AUTH +static key_entry_t * +find_key_entry(llist_t *key_entries, unsigned id) +{ + while (key_entries) { + key_entry_t *cur = (key_entry_t*) key_entries->data; + if (cur->id == id) + return cur; + key_entries = key_entries->link; + } + bb_error_msg_and_die("key %u is not defined", id); +} +#endif /* By doing init in a separate function we decrease stack usage * in main loop. @@ -2286,6 +2407,10 @@ static NOINLINE void ntp_init(char **argv) { unsigned opts; llist_t *peers; +#if ENABLE_FEATURE_NTP_AUTH + llist_t *key_entries; + char *key_file_path; +#endif srand(getpid()); @@ -2302,8 +2427,10 @@ static NOINLINE void ntp_init(char **argv) /* Parse options */ peers = NULL; + IF_FEATURE_NTP_AUTH(key_entries = NULL;) opts = getopt32(argv, "^" "nqNx" /* compat */ + IF_FEATURE_NTP_AUTH("k:") /* compat */ "wp:*S:"IF_FEATURE_NTPD_SERVER("l") /* NOT compat */ IF_FEATURE_NTPD_SERVER("I:") /* compat */ "d" /* compat */ @@ -2311,11 +2438,11 @@ static NOINLINE void ntp_init(char **argv) "\0" "dd:wn" /* -d: counter; -p: list; -w implies -n */ IF_FEATURE_NTPD_SERVER(":Il") /* -I implies -l */ - , &peers, &G.script_name, -#if ENABLE_FEATURE_NTPD_SERVER - &G.if_name, -#endif - &G.verbose); + IF_FEATURE_NTP_AUTH(, &key_file_path) + , &peers, &G.script_name + IF_FEATURE_NTPD_SERVER(, &G.if_name) + , &G.verbose + ); // if (opts & OPT_x) /* disable stepping, only slew is allowed */ // G.time_was_stepped = 1; @@ -2341,19 +2468,107 @@ static NOINLINE void ntp_init(char **argv) logmode = LOGMODE_NONE; } +#if ENABLE_FEATURE_NTP_AUTH + if (opts & OPT_k) { + char *tokens[4]; + parser_t *parser; + + parser = config_open(key_file_path); + while (config_read(parser, tokens, 4, 3, "# \t", PARSE_NORMAL | PARSE_MIN_DIE) == 3) { + key_entry_t *key_entry; + char buffer[40]; + smalluint hash_type; + smalluint msg_size; + smalluint key_length; + char *key; + + if ((tokens[1][0] | 0x20) == 'm') + /* supports 'M' and 'md5' formats */ + hash_type = HASH_MD5; + else + if (strncasecmp(tokens[1], "sha", 3) == 0) + /* supports 'sha' and 'sha1' formats */ + hash_type = HASH_SHA1; + else + bb_error_msg_and_die("only MD5 and SHA1 keys supported"); +/* man ntp.keys: + * MD5 The key is 1 to 16 printable characters terminated by an EOL, + * whitespace, or a # (which is the "start of comment" character). + * SHA + * SHA1 + * RMD160 The key is a hex-encoded ASCII string of 40 characters, which + * is truncated as necessary. + */ + key_length = strnlen(tokens[2], sizeof(buffer)+1); + if (key_length >= sizeof(buffer)+1) { + err: + bb_error_msg_and_die("malformed key at line %u", parser->lineno); + } + if (hash_type == HASH_MD5) { + key = tokens[2]; + msg_size = NTP_MSGSIZE_MD5_AUTH; + } else /* it's hash_type == HASH_SHA1 */ + if (!(key_length & 1)) { + key_length >>= 1; + if (!hex2bin(buffer, tokens[2], key_length)) + goto err; + key = buffer; + msg_size = NTP_MSGSIZE_SHA1_AUTH; + } else { + goto err; + } + key_entry = xzalloc(sizeof(*key_entry) + key_length); + key_entry->type = hash_type; + key_entry->msg_size = msg_size; + key_entry->key_length = key_length; + memcpy(key_entry->key, key, key_length); + key_entry->id = xatou_range(tokens[0], 1, MAX_KEY_NUMBER); + llist_add_to(&key_entries, key_entry); + } + config_close(parser); + } +#endif if (peers) { +#if ENABLE_FEATURE_NTP_AUTH + while (peers) { + char *peer = llist_pop(&peers); + key_entry_t *key_entry = NULL; + if (strncmp(peer, "keyno:", 6) == 0) { + char *end; + int key_id; + peer += 6; + end = strchr(peer, ':'); + *end = '\0'; + key_id = xatou_range(peer, 1, MAX_KEY_NUMBER); + *end = ':'; + key_entry = find_key_entry(key_entries, key_id); + peer = end + 1; + } + add_peers(peer, key_entry); + } +#else while (peers) - add_peers(llist_pop(&peers)); + add_peers(llist_pop(&peers), NULL); +#endif } #if ENABLE_FEATURE_NTPD_CONF else { parser_t *parser; - char *token[3]; + char *token[3 + 2*ENABLE_FEATURE_NTP_AUTH]; parser = config_open("/etc/ntp.conf"); - while (config_read(parser, token, 3, 1, "# \t", PARSE_NORMAL)) { + while (config_read(parser, token, 3 + 2*ENABLE_FEATURE_NTP_AUTH, 1, "# \t", PARSE_NORMAL)) { if (strcmp(token[0], "server") == 0 && token[1]) { - add_peers(token[1]); +# if ENABLE_FEATURE_NTP_AUTH + key_entry_t *key_entry = NULL; + if (token[2] && token[3] && strcmp(token[2], "key") == 0) { + unsigned key_id = xatou_range(token[3], 1, MAX_KEY_NUMBER); + key_entry = find_key_entry(key_entries, key_id); + } + add_peers(token[1], key_entry); +# else + add_peers(token[1], NULL); +# endif continue; } bb_error_msg("skipping %s:%u: unimplemented command '%s'", @@ -2394,6 +2609,7 @@ static NOINLINE void ntp_init(char **argv) | (1 << SIGCHLD) , SIG_IGN ); +//TODO: free unused elements of key_entries? } int ntpd_main(int argc UNUSED_PARAM, char **argv) MAIN_EXTERNALLY_VISIBLE; |