]>
Commit | Line | Data |
---|---|---|
577aed48 ML |
1 | /* |
2 | chronyd/chronyc - Programs for keeping computer clocks accurate. | |
3 | ||
4 | ********************************************************************** | |
5 | * Copyright (C) Miroslav Lichvar 2016 | |
6 | * | |
7 | * This program is free software; you can redistribute it and/or modify | |
8 | * it under the terms of version 2 of the GNU General Public License as | |
9 | * published by the Free Software Foundation. | |
10 | * | |
11 | * This program is distributed in the hope that it will be useful, but | |
12 | * WITHOUT ANY WARRANTY; without even the implied warranty of | |
13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU | |
14 | * General Public License for more details. | |
15 | * | |
16 | * You should have received a copy of the GNU General Public License along | |
17 | * with this program; if not, write to the Free Software Foundation, Inc., | |
18 | * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. | |
19 | * | |
20 | ********************************************************************** | |
21 | ||
22 | ======================================================================= | |
23 | ||
24 | Support for MS-SNTP authentication in Samba (ntp_signd) | |
25 | */ | |
26 | ||
27 | #include "config.h" | |
28 | ||
29 | #include "sysincl.h" | |
30 | ||
31 | #include "array.h" | |
32 | #include "conf.h" | |
33 | #include "logging.h" | |
34 | #include "ntp_io.h" | |
35 | #include "ntp_signd.h" | |
36 | #include "sched.h" | |
2de24cfd | 37 | #include "socket.h" |
577aed48 ML |
38 | #include "util.h" |
39 | ||
40 | /* Declarations per samba/source4/librpc/idl/ntp_signd.idl */ | |
41 | ||
42 | #define SIGND_VERSION 0 | |
43 | ||
44 | typedef enum { | |
45 | SIGN_TO_CLIENT = 0, | |
46 | ASK_SERVER_TO_SIGN = 1, | |
47 | CHECK_SERVER_SIGNATURE = 2, | |
48 | SIGNING_SUCCESS = 3, | |
49 | SIGNING_FAILURE = 4, | |
50 | } SigndOp; | |
51 | ||
52 | typedef struct { | |
53 | uint32_t length; | |
54 | uint32_t version; | |
55 | uint32_t op; | |
56 | uint16_t packet_id; | |
57 | uint16_t _pad; | |
58 | uint32_t key_id; | |
59 | NTP_Packet packet_to_sign; | |
60 | } SigndRequest; | |
61 | ||
62 | typedef struct { | |
63 | uint32_t length; | |
64 | uint32_t version; | |
65 | uint32_t op; | |
66 | uint32_t packet_id; | |
67 | NTP_Packet signed_packet; | |
68 | } SigndResponse; | |
69 | ||
70 | typedef struct { | |
71 | NTP_Remote_Address remote_addr; | |
72 | NTP_Local_Address local_addr; | |
73 | ||
74 | int sent; | |
75 | int received; | |
76 | int request_length; | |
d0dfa1de | 77 | struct timespec request_ts; |
577aed48 ML |
78 | SigndRequest request; |
79 | SigndResponse response; | |
80 | } SignInstance; | |
81 | ||
82 | /* As the communication with ntp_signd is asynchronous, incoming packets are | |
83 | saved in a queue in order to avoid loss when they come in bursts */ | |
84 | ||
85 | #define MAX_QUEUE_LENGTH 16U | |
86 | #define NEXT_QUEUE_INDEX(index) (((index) + 1) % MAX_QUEUE_LENGTH) | |
87 | #define IS_QUEUE_EMPTY() (queue_head == queue_tail) | |
88 | ||
89 | /* Fixed-size array of SignInstance */ | |
90 | static ARR_Instance queue; | |
91 | static unsigned int queue_head; | |
92 | static unsigned int queue_tail; | |
93 | ||
2de24cfd | 94 | #define INVALID_SOCK_FD (-6) |
577aed48 ML |
95 | |
96 | /* Unix domain socket connected to ntp_signd */ | |
97 | static int sock_fd; | |
98 | ||
577aed48 ML |
99 | /* Flag indicating if the MS-SNTP authentication is enabled */ |
100 | static int enabled; | |
101 | ||
102 | /* ================================================== */ | |
103 | ||
104 | static void read_write_socket(int sock_fd, int event, void *anything); | |
105 | ||
106 | /* ================================================== */ | |
107 | ||
108 | static void | |
109 | close_socket(void) | |
110 | { | |
111 | SCH_RemoveFileHandler(sock_fd); | |
2de24cfd | 112 | SCK_CloseSocket(sock_fd); |
577aed48 ML |
113 | sock_fd = INVALID_SOCK_FD; |
114 | ||
115 | /* Empty the queue */ | |
116 | queue_head = queue_tail = 0; | |
117 | } | |
118 | ||
119 | /* ================================================== */ | |
120 | ||
121 | static int | |
122 | open_socket(void) | |
123 | { | |
2fc8edac | 124 | char path[PATH_MAX]; |
577aed48 | 125 | |
2de24cfd | 126 | if (sock_fd != INVALID_SOCK_FD) |
577aed48 ML |
127 | return 1; |
128 | ||
2de24cfd | 129 | if (snprintf(path, sizeof (path), "%s/socket", CNF_GetNtpSigndSocket()) >= sizeof (path)) { |
f282856c | 130 | DEBUG_LOG("signd socket path too long"); |
577aed48 ML |
131 | return 0; |
132 | } | |
133 | ||
2de24cfd ML |
134 | sock_fd = SCK_OpenUnixStreamSocket(path, NULL, 0); |
135 | if (sock_fd < 0) { | |
136 | sock_fd = INVALID_SOCK_FD; | |
577aed48 ML |
137 | return 0; |
138 | } | |
139 | ||
2de24cfd | 140 | SCH_AddFileHandler(sock_fd, SCH_FILE_INPUT, read_write_socket, NULL); |
577aed48 ML |
141 | |
142 | return 1; | |
143 | } | |
144 | ||
145 | /* ================================================== */ | |
146 | ||
147 | static void | |
148 | process_response(SignInstance *inst) | |
149 | { | |
d0dfa1de | 150 | struct timespec ts; |
577aed48 ML |
151 | double delay; |
152 | ||
153 | if (ntohs(inst->request.packet_id) != ntohl(inst->response.packet_id)) { | |
f282856c | 154 | DEBUG_LOG("Invalid response ID"); |
577aed48 ML |
155 | return; |
156 | } | |
157 | ||
158 | if (ntohl(inst->response.op) != SIGNING_SUCCESS) { | |
f282856c | 159 | DEBUG_LOG("Signing failed"); |
577aed48 ML |
160 | return; |
161 | } | |
162 | ||
163 | /* Check if the file descriptor is still valid */ | |
164 | if (!NIO_IsServerSocket(inst->local_addr.sock_fd)) { | |
f282856c | 165 | DEBUG_LOG("Invalid NTP socket"); |
577aed48 ML |
166 | return; |
167 | } | |
168 | ||
d0dfa1de | 169 | SCH_GetLastEventTime(NULL, NULL, &ts); |
cfe706f0 | 170 | delay = UTI_DiffTimespecsToDouble(&ts, &inst->request_ts); |
577aed48 | 171 | |
f282856c | 172 | DEBUG_LOG("Signing succeeded (delay %f)", delay); |
577aed48 ML |
173 | |
174 | /* Send the signed NTP packet */ | |
175 | NIO_SendPacket(&inst->response.signed_packet, &inst->remote_addr, &inst->local_addr, | |
176 | ntohl(inst->response.length) + sizeof (inst->response.length) - | |
8f6a1b53 | 177 | offsetof(SigndResponse, signed_packet), 0); |
577aed48 ML |
178 | } |
179 | ||
180 | /* ================================================== */ | |
181 | ||
182 | static void | |
183 | read_write_socket(int sock_fd, int event, void *anything) | |
184 | { | |
185 | SignInstance *inst; | |
186 | uint32_t response_length; | |
187 | int s; | |
188 | ||
189 | inst = ARR_GetElement(queue, queue_head); | |
190 | ||
191 | if (event == SCH_FILE_OUTPUT) { | |
192 | assert(!IS_QUEUE_EMPTY()); | |
193 | assert(inst->sent < inst->request_length); | |
194 | ||
195 | if (!inst->sent) | |
d0dfa1de | 196 | SCH_GetLastEventTime(NULL, NULL, &inst->request_ts); |
577aed48 | 197 | |
2de24cfd ML |
198 | s = SCK_Send(sock_fd, (char *)&inst->request + inst->sent, |
199 | inst->request_length - inst->sent, 0); | |
577aed48 ML |
200 | |
201 | if (s < 0) { | |
577aed48 ML |
202 | close_socket(); |
203 | return; | |
204 | } | |
205 | ||
577aed48 ML |
206 | inst->sent += s; |
207 | ||
208 | /* Try again later if the request is not complete yet */ | |
209 | if (inst->sent < inst->request_length) | |
210 | return; | |
211 | ||
212 | /* Disable output and wait for a response */ | |
2c4c2351 | 213 | SCH_SetFileHandlerEvent(sock_fd, SCH_FILE_OUTPUT, 0); |
577aed48 ML |
214 | } |
215 | ||
216 | if (event == SCH_FILE_INPUT) { | |
217 | if (IS_QUEUE_EMPTY()) { | |
f282856c | 218 | DEBUG_LOG("Unexpected signd response"); |
577aed48 ML |
219 | close_socket(); |
220 | return; | |
221 | } | |
222 | ||
223 | assert(inst->received < sizeof (inst->response)); | |
2de24cfd ML |
224 | s = SCK_Receive(sock_fd, (char *)&inst->response + inst->received, |
225 | sizeof (inst->response) - inst->received, 0); | |
577aed48 ML |
226 | |
227 | if (s <= 0) { | |
577aed48 ML |
228 | close_socket(); |
229 | return; | |
230 | } | |
231 | ||
577aed48 ML |
232 | inst->received += s; |
233 | ||
234 | if (inst->received < sizeof (inst->response.length)) | |
235 | return; | |
236 | ||
237 | response_length = ntohl(inst->response.length) + sizeof (inst->response.length); | |
238 | ||
239 | if (response_length < offsetof(SigndResponse, signed_packet) || | |
240 | response_length > sizeof (SigndResponse)) { | |
f282856c | 241 | DEBUG_LOG("Invalid response length"); |
577aed48 ML |
242 | close_socket(); |
243 | return; | |
244 | } | |
245 | ||
246 | /* Wait for more data if not complete yet */ | |
247 | if (inst->received < response_length) | |
248 | return; | |
249 | ||
250 | process_response(inst); | |
251 | ||
252 | /* Move the head and enable output for the next packet */ | |
253 | queue_head = NEXT_QUEUE_INDEX(queue_head); | |
254 | if (!IS_QUEUE_EMPTY()) | |
2c4c2351 | 255 | SCH_SetFileHandlerEvent(sock_fd, SCH_FILE_OUTPUT, 1); |
577aed48 ML |
256 | } |
257 | } | |
258 | ||
259 | /* ================================================== */ | |
260 | ||
261 | void | |
262 | NSD_Initialise() | |
263 | { | |
264 | sock_fd = INVALID_SOCK_FD; | |
577aed48 ML |
265 | enabled = CNF_GetNtpSigndSocket() && CNF_GetNtpSigndSocket()[0]; |
266 | ||
267 | if (!enabled) | |
268 | return; | |
269 | ||
270 | queue = ARR_CreateInstance(sizeof (SignInstance)); | |
271 | ARR_SetSize(queue, MAX_QUEUE_LENGTH); | |
272 | queue_head = queue_tail = 0; | |
273 | ||
f282856c | 274 | LOG(LOGS_INFO, "MS-SNTP authentication enabled"); |
577aed48 ML |
275 | } |
276 | ||
277 | /* ================================================== */ | |
278 | ||
279 | void | |
280 | NSD_Finalise() | |
281 | { | |
282 | if (!enabled) | |
283 | return; | |
284 | if (sock_fd != INVALID_SOCK_FD) | |
285 | close_socket(); | |
286 | ARR_DestroyInstance(queue); | |
287 | } | |
288 | ||
289 | /* ================================================== */ | |
290 | ||
577aed48 | 291 | int |
cb8660e7 ML |
292 | NSD_SignAndSendPacket(uint32_t key_id, NTP_Packet *packet, NTP_PacketInfo *info, |
293 | NTP_Remote_Address *remote_addr, NTP_Local_Address *local_addr) | |
577aed48 ML |
294 | { |
295 | SignInstance *inst; | |
296 | ||
297 | if (!enabled) { | |
f282856c | 298 | DEBUG_LOG("signd disabled"); |
577aed48 ML |
299 | return 0; |
300 | } | |
301 | ||
302 | if (queue_head == NEXT_QUEUE_INDEX(queue_tail)) { | |
f282856c | 303 | DEBUG_LOG("signd queue full"); |
577aed48 ML |
304 | return 0; |
305 | } | |
306 | ||
cb8660e7 | 307 | if (info->length != NTP_HEADER_LENGTH) { |
f282856c | 308 | DEBUG_LOG("Invalid packet length"); |
577aed48 ML |
309 | return 0; |
310 | } | |
311 | ||
312 | if (!open_socket()) | |
313 | return 0; | |
314 | ||
315 | inst = ARR_GetElement(queue, queue_tail); | |
316 | inst->remote_addr = *remote_addr; | |
317 | inst->local_addr = *local_addr; | |
318 | inst->sent = 0; | |
319 | inst->received = 0; | |
cb8660e7 | 320 | inst->request_length = offsetof(SigndRequest, packet_to_sign) + info->length; |
577aed48 ML |
321 | |
322 | /* The length field doesn't include itself */ | |
323 | inst->request.length = htonl(inst->request_length - sizeof (inst->request.length)); | |
324 | inst->request.version = htonl(SIGND_VERSION); | |
325 | inst->request.op = htonl(SIGN_TO_CLIENT); | |
326 | inst->request.packet_id = htons(queue_tail); | |
327 | inst->request._pad = 0; | |
328 | inst->request.key_id = htonl(key_id); | |
329 | ||
cb8660e7 | 330 | memcpy(&inst->request.packet_to_sign, packet, info->length); |
577aed48 ML |
331 | |
332 | /* Enable output if there was no pending request */ | |
333 | if (IS_QUEUE_EMPTY()) | |
2c4c2351 | 334 | SCH_SetFileHandlerEvent(sock_fd, SCH_FILE_OUTPUT, 1); |
577aed48 ML |
335 | |
336 | queue_tail = NEXT_QUEUE_INDEX(queue_tail); | |
337 | ||
f282856c | 338 | DEBUG_LOG("Packet added to signd queue (%u:%u)", queue_head, queue_tail); |
577aed48 ML |
339 | |
340 | return 1; | |
341 | } |