ntp: add support for MS-SNTP authentication in Samba

Add support for authenticating MS-SNTP responses in Samba (ntp_signd).
Supported is currently only the old MS-SNTP authenticator field. It's
disabled by default. It can be enabled with the --enable-ntp-signd
configure option and the ntpsigndsocket directive, which specifies the
location of the Samba ntp_signd socket.
This commit is contained in:
Miroslav Lichvar 2016-07-27 14:09:32 +02:00
parent 2a8ce63fc7
commit 577aed4842
11 changed files with 514 additions and 12 deletions

14
conf.c
View file

@ -181,6 +181,9 @@ static IPAddr bind_cmd_address4, bind_cmd_address6;
/* Path to the Unix domain command socket. */
static char *bind_cmd_path;
/* Path to Samba (ntp_signd) socket. */
static char *ntp_signd_socket = NULL;
/* Filename to use for storing pid of running chronyd, to prevent multiple
* chronyds being started. */
static char *pidfile;
@ -361,6 +364,7 @@ CNF_Finalise(void)
Free(leapsec_tz);
Free(logdir);
Free(bind_cmd_path);
Free(ntp_signd_socket);
Free(pidfile);
Free(rtc_device);
Free(rtc_file);
@ -506,6 +510,8 @@ CNF_ParseLine(const char *filename, int number, char *line)
parse_int(p, &min_sources);
} else if (!strcasecmp(command, "noclientlog")) {
no_client_log = parse_null(p);
} else if (!strcasecmp(command, "ntpsigndsocket")) {
parse_string(p, &ntp_signd_socket);
} else if (!strcasecmp(command, "peer")) {
parse_source(p, NTP_PEER, 0);
} else if (!strcasecmp(command, "pidfile")) {
@ -1736,6 +1742,14 @@ CNF_GetBindCommandAddress(int family, IPAddr *addr)
/* ================================================== */
char *
CNF_GetNtpSigndSocket(void)
{
return ntp_signd_socket;
}
/* ================================================== */
char *
CNF_GetPidFile(void)
{

1
conf.h
View file

@ -76,6 +76,7 @@ extern void CNF_GetBindAddress(int family, IPAddr *addr);
extern void CNF_GetBindAcquisitionAddress(int family, IPAddr *addr);
extern void CNF_GetBindCommandAddress(int family, IPAddr *addr);
extern char *CNF_GetBindCommandPath(void);
extern char *CNF_GetNtpSigndSocket(void);
extern char *CNF_GetPidFile(void);
extern REF_LeapMode CNF_GetLeapSecMode(void);
extern char *CNF_GetLeapSecTimezone(void);

11
configure vendored
View file

@ -99,6 +99,7 @@ For better control, use the options below.
--without-seccomp Don't use seccomp even if it is available
--disable-asyncdns Disable asynchronous name resolving
--disable-forcednsretry Don't retry on permanent DNS error
--enable-ntp-signd Enable support for MS-SNTP authentication in Samba
--with-ntp-era=SECONDS Specify earliest assumed NTP time in seconds
since 1970-01-01 [50*365 days ago]
--with-user=USER Specify default chronyd user [root]
@ -213,6 +214,7 @@ try_setsched=0
try_lockmem=0
feat_asyncdns=1
feat_forcednsretry=1
feat_ntp_signd=0
ntp_era_split=""
default_user="root"
default_hwclockfile=""
@ -317,6 +319,9 @@ do
--disable-forcednsretry)
feat_forcednsretry=0
;;
--enable-ntp-signd)
feat_ntp_signd=1
;;
--with-ntp-era=* )
ntp_era_split=`echo $option | sed -e 's/^.*=//;'`
;;
@ -432,6 +437,10 @@ fi
if [ $feat_ntp = "1" ]; then
add_def FEAT_NTP
EXTRA_OBJECTS="$EXTRA_OBJECTS ntp_core.o ntp_io.o ntp_sources.o"
if [ $feat_ntp_signd = "1" ]; then
add_def FEAT_SIGND
EXTRA_OBJECTS="$EXTRA_OBJECTS ntp_signd.o"
fi
else
feat_asyncdns=0
fi
@ -826,7 +835,7 @@ add_def MAIL_PROGRAM "\"$mail_program\""
common_features="`get_features IPV6 DEBUG`"
chronyc_features="`get_features READLINE`"
chronyd_features="`get_features CMDMON NTP REFCLOCK RTC PRIVDROP SCFILTER SECHASH ASYNCDNS`"
chronyd_features="`get_features CMDMON NTP REFCLOCK RTC PRIVDROP SCFILTER SECHASH SIGND ASYNCDNS`"
add_def CHRONYC_FEATURES "\"$chronyc_features $common_features\""
add_def CHRONYD_FEATURES "\"$chronyd_features $common_features\""
echo "Features : $chronyd_features $chronyc_features $common_features"

View file

@ -1760,6 +1760,20 @@ should result in lower and more consistent latency. It should not have
significant impact on performance as *chronyd's* memory usage is modest. The
*mlockall(2)* man page has more details.
[[ntpsigndsocket]]*ntpsigndsocket* _directory_::
This directive specifies the location of the Samba *ntp_signd* socket when it
is running as a Domain Controller (DC). If *chronyd* is compiled with this
feature, responses to MS-SNTP clients will be signed by the *smbd* daemon. Note
that MS-SNTP requests are not authenticated, so any NTP client can get
responses authenticated with passwords of users in the domain. Access to the
server should be carefully controlled.
+
An example of the directive is:
+
----
ntpsigndsocket /var/lib/samba/ntp_signd
----
[[pidfile]]*pidfile* _file_::
*chronyd* always writes its process ID (PID) to a file, and checks this file on
startup to see if another *chronyd* may already be running on the system. By

View file

@ -83,6 +83,7 @@ typedef enum {
LOGF_Reference,
LOGF_NtpIO,
LOGF_NtpCore,
LOGF_NtpSignd,
LOGF_NtpSources,
LOGF_Scheduler,
LOGF_SourceStats,

3
main.c
View file

@ -35,6 +35,7 @@
#include "local.h"
#include "sys.h"
#include "ntp_io.h"
#include "ntp_signd.h"
#include "ntp_sources.h"
#include "ntp_core.h"
#include "sources.h"
@ -107,6 +108,7 @@ MAI_CleanupAndExit(void)
TMC_Finalise();
MNL_Finalise();
CLG_Finalise();
NSD_Finalise();
NSR_Finalise();
SST_Finalise();
NCR_Finalise();
@ -523,6 +525,7 @@ int main
REF_Initialise();
SST_Initialise();
NSR_Initialise();
NSD_Initialise();
CLG_Initialise();
MNL_Initialise();
TMC_Initialise();

View file

@ -32,6 +32,7 @@
#include "array.h"
#include "ntp_core.h"
#include "ntp_io.h"
#include "ntp_signd.h"
#include "memory.h"
#include "sched.h"
#include "reference.h"
@ -919,24 +920,28 @@ transmit_packet(NTP_Mode my_mode, /* The mode this machine wants to be */
/* Authenticate the packet if needed */
if (auth_mode == AUTH_SYMMETRIC) {
if (auth_mode == AUTH_SYMMETRIC || auth_mode == AUTH_MSSNTP) {
/* Pre-compensate the transmit time by approx. how long it will
take to generate the authentication data. */
local_transmit.tv_usec += KEY_GetAuthDelay(key_id);
local_transmit.tv_usec += auth_mode == AUTH_SYMMETRIC ?
KEY_GetAuthDelay(key_id) : NSD_GetAuthDelay(key_id);
UTI_NormaliseTimeval(&local_transmit);
UTI_TimevalToInt64(&local_transmit, &message.transmit_ts, &ts_fuzz);
if (auth_mode == AUTH_SYMMETRIC) {
auth_len = KEY_GenerateAuth(key_id, (unsigned char *) &message,
offsetof(NTP_Packet, auth_keyid),
(unsigned char *)&message.auth_data, sizeof (message.auth_data));
if (auth_len > 0) {
(unsigned char *)&message.auth_data,
sizeof (message.auth_data));
if (!auth_len) {
DEBUG_LOG(LOGF_NtpCore, "Could not generate auth data with key %"PRIu32, key_id);
return 0;
}
message.auth_keyid = htonl(key_id);
length += sizeof (message.auth_keyid) + auth_len;
} else {
DEBUG_LOG(LOGF_NtpCore,
"Could not generate auth data with key %"PRIu32" to send packet",
key_id);
return 0;
} else if (auth_mode == AUTH_MSSNTP) {
/* MS-SNTP packets are signed (asynchronously) by ntp_signd */
return NSD_SignAndSendPacket(key_id, &message, where_to, from, length);
}
} else {
if (auth_mode == AUTH_CRYPTO_NAK) {
@ -1755,6 +1760,9 @@ NCR_ProcessUnknown
/* Reply with crypto-NAK */
auth_mode = AUTH_CRYPTO_NAK;
break;
case AUTH_MSSNTP:
/* Ignore the failure (MS-SNTP servers don't check client MAC) */
break;
default:
/* Discard packets in other modes */
DEBUG_LOG(LOGF_NtpCore, "NTP packet discarded auth_mode=%d", auth_mode);

380
ntp_signd.c Normal file
View file

@ -0,0 +1,380 @@
/*
chronyd/chronyc - Programs for keeping computer clocks accurate.
**********************************************************************
* Copyright (C) Miroslav Lichvar 2016
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of version 2 of the GNU General Public License as
* published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*
**********************************************************************
=======================================================================
Support for MS-SNTP authentication in Samba (ntp_signd)
*/
#include "config.h"
#include "sysincl.h"
#include "array.h"
#include "conf.h"
#include "logging.h"
#include "ntp_io.h"
#include "ntp_signd.h"
#include "sched.h"
#include "util.h"
/* Declarations per samba/source4/librpc/idl/ntp_signd.idl */
#define SIGND_VERSION 0
typedef enum {
SIGN_TO_CLIENT = 0,
ASK_SERVER_TO_SIGN = 1,
CHECK_SERVER_SIGNATURE = 2,
SIGNING_SUCCESS = 3,
SIGNING_FAILURE = 4,
} SigndOp;
typedef struct {
uint32_t length;
uint32_t version;
uint32_t op;
uint16_t packet_id;
uint16_t _pad;
uint32_t key_id;
NTP_Packet packet_to_sign;
} SigndRequest;
typedef struct {
uint32_t length;
uint32_t version;
uint32_t op;
uint32_t packet_id;
NTP_Packet signed_packet;
} SigndResponse;
typedef struct {
NTP_Remote_Address remote_addr;
NTP_Local_Address local_addr;
int sent;
int received;
int request_length;
struct timeval request_tv;
SigndRequest request;
SigndResponse response;
} SignInstance;
/* As the communication with ntp_signd is asynchronous, incoming packets are
saved in a queue in order to avoid loss when they come in bursts */
#define MAX_QUEUE_LENGTH 16U
#define NEXT_QUEUE_INDEX(index) (((index) + 1) % MAX_QUEUE_LENGTH)
#define IS_QUEUE_EMPTY() (queue_head == queue_tail)
/* Fixed-size array of SignInstance */
static ARR_Instance queue;
static unsigned int queue_head;
static unsigned int queue_tail;
#define INVALID_SOCK_FD -1
/* Unix domain socket connected to ntp_signd */
static int sock_fd;
#define MIN_AUTH_DELAY 1.0e-5
#define MAX_AUTH_DELAY 1.0e-2
/* Average time needed for signing one packet. This is used to adjust the
transmit timestamp in NTP packets. The timestamp won't be very accurate as
the delay is variable, but it should be good enough for MS-SNTP clients. */
static double auth_delay;
/* Flag indicating if the MS-SNTP authentication is enabled */
static int enabled;
/* ================================================== */
static void read_write_socket(int sock_fd, int event, void *anything);
/* ================================================== */
static void
close_socket(void)
{
SCH_RemoveFileHandler(sock_fd);
close(sock_fd);
sock_fd = INVALID_SOCK_FD;
/* Empty the queue */
queue_head = queue_tail = 0;
}
/* ================================================== */
static int
open_socket(void)
{
struct sockaddr_un s;
if (sock_fd >= 0)
return 1;
sock_fd = socket(AF_UNIX, SOCK_STREAM, 0);
if (sock_fd < 0) {
DEBUG_LOG(LOGF_NtpSignd, "Could not open signd socket : %s", strerror(errno));
return 0;
}
UTI_FdSetCloexec(sock_fd);
SCH_AddFileHandler(sock_fd, SCH_FILE_INPUT, read_write_socket, NULL);
s.sun_family = AF_UNIX;
if (snprintf(s.sun_path, sizeof (s.sun_path), "%s/socket",
CNF_GetNtpSigndSocket()) >= sizeof (s.sun_path)) {
DEBUG_LOG(LOGF_NtpSignd, "signd socket path too long");
close_socket();
return 0;
}
if (connect(sock_fd, (struct sockaddr *)&s, sizeof (s)) < 0) {
DEBUG_LOG(LOGF_NtpSignd, "Could not connect to signd : %s", strerror(errno));
close_socket();
return 0;
}
DEBUG_LOG(LOGF_NtpSignd, "Connected to signd");
return 1;
}
/* ================================================== */
static void
process_response(SignInstance *inst)
{
struct timeval tv;
double delay;
if (ntohs(inst->request.packet_id) != ntohl(inst->response.packet_id)) {
DEBUG_LOG(LOGF_NtpSignd, "Invalid response ID");
return;
}
if (ntohl(inst->response.op) != SIGNING_SUCCESS) {
DEBUG_LOG(LOGF_NtpSignd, "Signing failed");
return;
}
/* Check if the file descriptor is still valid */
if (!NIO_IsServerSocket(inst->local_addr.sock_fd)) {
DEBUG_LOG(LOGF_NtpSignd, "Invalid NTP socket");
return;
}
SCH_GetLastEventTime(NULL, NULL, &tv);
UTI_DiffTimevalsToDouble(&delay, &tv, &inst->request_tv);
DEBUG_LOG(LOGF_NtpSignd, "Signing succeeded (delay %f)", delay);
/* Send the signed NTP packet */
NIO_SendPacket(&inst->response.signed_packet, &inst->remote_addr, &inst->local_addr,
ntohl(inst->response.length) + sizeof (inst->response.length) -
offsetof(SigndResponse, signed_packet));
/* Update exponential moving average of the authentication delay */
delay = CLAMP(MIN_AUTH_DELAY, delay, MAX_AUTH_DELAY);
auth_delay += 0.1 * (delay - auth_delay);
}
/* ================================================== */
static void
read_write_socket(int sock_fd, int event, void *anything)
{
SignInstance *inst;
uint32_t response_length;
int s;
inst = ARR_GetElement(queue, queue_head);
if (event == SCH_FILE_OUTPUT) {
assert(!IS_QUEUE_EMPTY());
assert(inst->sent < inst->request_length);
if (!inst->sent)
SCH_GetLastEventTime(NULL, NULL, &inst->request_tv);
s = send(sock_fd, (char *)&inst->request + inst->sent,
inst->request_length - inst->sent, 0);
if (s < 0) {
DEBUG_LOG(LOGF_NtpSignd, "signd socket error: %s", strerror(errno));
close_socket();
return;
}
DEBUG_LOG(LOGF_NtpSignd, "Sent %d bytes to signd", s);
inst->sent += s;
/* Try again later if the request is not complete yet */
if (inst->sent < inst->request_length)
return;
/* Disable output and wait for a response */
SCH_SetFileHandlerEvents(sock_fd, SCH_FILE_INPUT);
}
if (event == SCH_FILE_INPUT) {
if (IS_QUEUE_EMPTY()) {
DEBUG_LOG(LOGF_NtpSignd, "Unexpected signd response");
close_socket();
return;
}
assert(inst->received < sizeof (inst->response));
s = recv(sock_fd, (char *)&inst->response + inst->received,
sizeof (inst->response) - inst->received, 0);
if (s <= 0) {
if (s < 0)
DEBUG_LOG(LOGF_NtpSignd, "signd socket error: %s", strerror(errno));
else
DEBUG_LOG(LOGF_NtpSignd, "signd socket closed");
close_socket();
return;
}
DEBUG_LOG(LOGF_NtpSignd, "Received %d bytes from signd", s);
inst->received += s;
if (inst->received < sizeof (inst->response.length))
return;
response_length = ntohl(inst->response.length) + sizeof (inst->response.length);
if (response_length < offsetof(SigndResponse, signed_packet) ||
response_length > sizeof (SigndResponse)) {
DEBUG_LOG(LOGF_NtpSignd, "Invalid response length");
close_socket();
return;
}
/* Wait for more data if not complete yet */
if (inst->received < response_length)
return;
process_response(inst);
/* Move the head and enable output for the next packet */
queue_head = NEXT_QUEUE_INDEX(queue_head);
if (!IS_QUEUE_EMPTY())
SCH_SetFileHandlerEvents(sock_fd, SCH_FILE_INPUT | SCH_FILE_OUTPUT);
}
}
/* ================================================== */
void
NSD_Initialise()
{
sock_fd = INVALID_SOCK_FD;
auth_delay = MIN_AUTH_DELAY;
enabled = CNF_GetNtpSigndSocket() && CNF_GetNtpSigndSocket()[0];
if (!enabled)
return;
queue = ARR_CreateInstance(sizeof (SignInstance));
ARR_SetSize(queue, MAX_QUEUE_LENGTH);
queue_head = queue_tail = 0;
LOG(LOGS_INFO, LOGF_NtpSignd, "MS-SNTP authentication enabled");
}
/* ================================================== */
void
NSD_Finalise()
{
if (!enabled)
return;
if (sock_fd != INVALID_SOCK_FD)
close_socket();
ARR_DestroyInstance(queue);
}
/* ================================================== */
extern int NSD_GetAuthDelay(uint32_t key_id)
{
return auth_delay * 1.0e6;
}
/* ================================================== */
int
NSD_SignAndSendPacket(uint32_t key_id, NTP_Packet *packet, NTP_Remote_Address *remote_addr, NTP_Local_Address *local_addr, int length)
{
SignInstance *inst;
if (!enabled) {
DEBUG_LOG(LOGF_NtpSignd, "signd disabled");
return 0;
}
if (queue_head == NEXT_QUEUE_INDEX(queue_tail)) {
DEBUG_LOG(LOGF_NtpSignd, "signd queue full");
return 0;
}
if (length != NTP_NORMAL_PACKET_LENGTH) {
DEBUG_LOG(LOGF_NtpSignd, "Invalid packet length");
return 0;
}
if (!open_socket())
return 0;
inst = ARR_GetElement(queue, queue_tail);
inst->remote_addr = *remote_addr;
inst->local_addr = *local_addr;
inst->sent = 0;
inst->received = 0;
inst->request_length = offsetof(SigndRequest, packet_to_sign) + length;
/* The length field doesn't include itself */
inst->request.length = htonl(inst->request_length - sizeof (inst->request.length));
inst->request.version = htonl(SIGND_VERSION);
inst->request.op = htonl(SIGN_TO_CLIENT);
inst->request.packet_id = htons(queue_tail);
inst->request._pad = 0;
inst->request.key_id = htonl(key_id);
memcpy(&inst->request.packet_to_sign, packet, length);
/* Enable output if there was no pending request */
if (IS_QUEUE_EMPTY())
SCH_SetFileHandlerEvents(sock_fd, SCH_FILE_INPUT | SCH_FILE_OUTPUT);
queue_tail = NEXT_QUEUE_INDEX(queue_tail);
DEBUG_LOG(LOGF_NtpSignd, "Packet added to signd queue (%u:%u)",
queue_head, queue_tail);
return 1;
}

44
ntp_signd.h Normal file
View file

@ -0,0 +1,44 @@
/*
chronyd/chronyc - Programs for keeping computer clocks accurate.
**********************************************************************
* Copyright (C) Miroslav Lichvar 2016
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of version 2 of the GNU General Public License as
* published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*
**********************************************************************
=======================================================================
Header for MS-SNTP authentication via Samba (ntp_signd) */
#ifndef GOT_NTP_SIGND_H
#define GOT_NTP_SIGND_H
#include "addressing.h"
#include "ntp.h"
/* Initialisation function */
extern void NSD_Initialise(void);
/* Finalisation function */
extern void NSD_Finalise(void);
/* Function to get an estimate of delay due to signing */
extern int NSD_GetAuthDelay(uint32_t key_id);
/* Function to sign an NTP packet and send it */
extern int NSD_SignAndSendPacket(uint32_t key_id, NTP_Packet *packet, NTP_Remote_Address *remote_addr, NTP_Local_Address *local_addr, int length);
#endif

27
stubs.c
View file

@ -38,6 +38,7 @@
#include "ntp_core.h"
#include "ntp_io.h"
#include "ntp_sources.h"
#include "ntp_signd.h"
#include "privops.h"
#include "refclock.h"
#include "sched.h"
@ -367,3 +368,29 @@ RCL_ReportSource(RPT_SourceReport *report, struct timeval *now)
}
#endif /* !FEAT_REFCLOCK */
#ifndef FEAT_SIGND
void
NSD_Initialise(void)
{
}
void
NSD_Finalise(void)
{
}
int
NSD_GetAuthDelay(uint32_t key_id)
{
return 0;
}
int
NSD_SignAndSendPacket(uint32_t key_id, NTP_Packet *packet, NTP_Remote_Address *remote_addr, NTP_Local_Address *local_addr, int length)
{
return 0;
}
#endif /* !FEAT_SIGND */

View file

@ -6,6 +6,7 @@ cd ../..
for opts in \
"--enable-debug" \
"--enable-ntp-signd" \
"--enable-scfilter" \
"--disable-asyncdns" \
"--disable-ipv6" \