summaryrefslogtreecommitdiffstats
path: root/ntp_signd.c
diff options
context:
space:
mode:
Diffstat (limited to 'ntp_signd.c')
-rw-r--r--ntp_signd.c379
1 files changed, 379 insertions, 0 deletions
diff --git a/ntp_signd.c b/ntp_signd.c
new file mode 100644
index 0000000..6328b61
--- /dev/null
+++ b/ntp_signd.c
@@ -0,0 +1,379 @@
+/*
+ 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 timespec request_ts;
+ 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("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("signd socket path too long");
+ close_socket();
+ return 0;
+ }
+
+ if (connect(sock_fd, (struct sockaddr *)&s, sizeof (s)) < 0) {
+ DEBUG_LOG("Could not connect to signd : %s", strerror(errno));
+ close_socket();
+ return 0;
+ }
+
+ DEBUG_LOG("Connected to signd");
+
+ return 1;
+}
+
+/* ================================================== */
+
+static void
+process_response(SignInstance *inst)
+{
+ struct timespec ts;
+ double delay;
+
+ if (ntohs(inst->request.packet_id) != ntohl(inst->response.packet_id)) {
+ DEBUG_LOG("Invalid response ID");
+ return;
+ }
+
+ if (ntohl(inst->response.op) != SIGNING_SUCCESS) {
+ DEBUG_LOG("Signing failed");
+ return;
+ }
+
+ /* Check if the file descriptor is still valid */
+ if (!NIO_IsServerSocket(inst->local_addr.sock_fd)) {
+ DEBUG_LOG("Invalid NTP socket");
+ return;
+ }
+
+ SCH_GetLastEventTime(NULL, NULL, &ts);
+ delay = UTI_DiffTimespecsToDouble(&ts, &inst->request_ts);
+
+ DEBUG_LOG("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), 0);
+
+ /* 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_ts);
+
+ s = send(sock_fd, (char *)&inst->request + inst->sent,
+ inst->request_length - inst->sent, 0);
+
+ if (s < 0) {
+ DEBUG_LOG("signd socket error: %s", strerror(errno));
+ close_socket();
+ return;
+ }
+
+ DEBUG_LOG("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_SetFileHandlerEvent(sock_fd, SCH_FILE_OUTPUT, 0);
+ }
+
+ if (event == SCH_FILE_INPUT) {
+ if (IS_QUEUE_EMPTY()) {
+ DEBUG_LOG("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("signd socket error: %s", strerror(errno));
+ else
+ DEBUG_LOG("signd socket closed");
+
+ close_socket();
+ return;
+ }
+
+ DEBUG_LOG("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("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_SetFileHandlerEvent(sock_fd, SCH_FILE_OUTPUT, 1);
+ }
+}
+
+/* ================================================== */
+
+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, "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 1.0e9 * auth_delay;
+}
+
+/* ================================================== */
+
+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("signd disabled");
+ return 0;
+ }
+
+ if (queue_head == NEXT_QUEUE_INDEX(queue_tail)) {
+ DEBUG_LOG("signd queue full");
+ return 0;
+ }
+
+ if (length != NTP_NORMAL_PACKET_LENGTH) {
+ DEBUG_LOG("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_SetFileHandlerEvent(sock_fd, SCH_FILE_OUTPUT, 1);
+
+ queue_tail = NEXT_QUEUE_INDEX(queue_tail);
+
+ DEBUG_LOG("Packet added to signd queue (%u:%u)", queue_head, queue_tail);
+
+ return 1;
+}