#include "array.h"
#include "ntp_core.h"
#include "ntp_io.h"
+#include "ntp_signd.h"
#include "memory.h"
#include "sched.h"
#include "reference.h"
/* 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);
- 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) {
+ 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) {
+ 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) {
/* 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);
--- /dev/null
+/*
+ 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;
+}