/src/libssh/src/packet_cb.c
Line | Count | Source |
1 | | /* |
2 | | * packet_cb.c - packet building functions |
3 | | * |
4 | | * This file is part of the SSH Library |
5 | | * |
6 | | * Copyright (c) 2011 Aris Adamantiadis |
7 | | * |
8 | | * The SSH Library is free software; you can redistribute it and/or modify |
9 | | * it under the terms of the GNU Lesser General Public License as published by |
10 | | * the Free Software Foundation; either version 2.1 of the License, or (at your |
11 | | * option) any later version. |
12 | | * |
13 | | * The SSH Library is distributed in the hope that it will be useful, but |
14 | | * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY |
15 | | * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public |
16 | | * License for more details. |
17 | | * |
18 | | * You should have received a copy of the GNU Lesser General Public License |
19 | | * along with the SSH Library; see the file COPYING. If not, write to |
20 | | * the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, |
21 | | * MA 02111-1307, USA. |
22 | | */ |
23 | | |
24 | | #include "config.h" |
25 | | |
26 | | #include <stdlib.h> |
27 | | #ifdef HAVE_ARPA_INET_H |
28 | | #include <arpa/inet.h> |
29 | | #endif |
30 | | #ifdef WITH_GSSAPI |
31 | | #include "libssh/gssapi.h" |
32 | | #include <gssapi/gssapi.h> |
33 | | #endif |
34 | | |
35 | | #include "libssh/priv.h" |
36 | | #include "libssh/buffer.h" |
37 | | #include "libssh/crypto.h" |
38 | | #include "libssh/dh.h" |
39 | | #include "libssh/misc.h" |
40 | | #include "libssh/packet.h" |
41 | | #include "libssh/pki.h" |
42 | | #include "libssh/session.h" |
43 | | #include "libssh/socket.h" |
44 | | #include "libssh/ssh2.h" |
45 | | #include "libssh/curve25519.h" |
46 | | |
47 | | /** |
48 | | * @internal |
49 | | * |
50 | | * @brief Handle a SSH_DISCONNECT packet. |
51 | | */ |
52 | | SSH_PACKET_CALLBACK(ssh_packet_disconnect_callback) |
53 | 23 | { |
54 | 23 | int rc; |
55 | 23 | uint32_t code = 0; |
56 | 23 | char *error = NULL; |
57 | 23 | ssh_string error_s = NULL; |
58 | | |
59 | 23 | (void)user; |
60 | 23 | (void)type; |
61 | | |
62 | 23 | rc = ssh_buffer_get_u32(packet, &code); |
63 | 23 | if (rc != 0) { |
64 | 19 | code = ntohl(code); |
65 | 19 | } |
66 | | |
67 | 23 | error_s = ssh_buffer_get_ssh_string(packet); |
68 | 23 | if (error_s != NULL) { |
69 | 6 | error = ssh_string_to_char(error_s); |
70 | 6 | SSH_STRING_FREE(error_s); |
71 | 6 | } |
72 | | |
73 | 23 | if (error != NULL) { |
74 | 6 | session->peer_discon_msg = strdup(error); |
75 | 6 | } |
76 | | |
77 | 23 | SSH_LOG(SSH_LOG_PACKET, |
78 | 23 | "Received SSH_MSG_DISCONNECT %" PRIu32 ":%s", |
79 | 23 | code, |
80 | 23 | error != NULL ? error : "no error"); |
81 | 23 | ssh_set_error(session, |
82 | 23 | SSH_FATAL, |
83 | 23 | "Received SSH_MSG_DISCONNECT: %" PRIu32 ":%s", |
84 | 23 | code, |
85 | 23 | error != NULL ? error : "no error"); |
86 | 23 | SAFE_FREE(error); |
87 | | |
88 | 23 | ssh_session_socket_close(session); |
89 | | /* correctly handle disconnect during authorization */ |
90 | 23 | session->auth.state = SSH_AUTH_STATE_FAILED; |
91 | | |
92 | | /* TODO: handle a graceful disconnect */ |
93 | 23 | return SSH_PACKET_USED; |
94 | 23 | } |
95 | | |
96 | | /** |
97 | | * @internal |
98 | | * |
99 | | * @brief Handle a SSH_IGNORE packet. |
100 | | */ |
101 | | SSH_PACKET_CALLBACK(ssh_packet_ignore_callback) |
102 | 315 | { |
103 | 315 | (void)session; /* unused */ |
104 | 315 | (void)user; |
105 | 315 | (void)type; |
106 | 315 | (void)packet; |
107 | | |
108 | 315 | SSH_LOG(SSH_LOG_DEBUG, "Received SSH_MSG_IGNORE packet"); |
109 | | |
110 | 315 | return SSH_PACKET_USED; |
111 | 315 | } |
112 | | |
113 | | /** |
114 | | * @internal |
115 | | * |
116 | | * @brief Handle a SSH_DEBUG packet. |
117 | | */ |
118 | | SSH_PACKET_CALLBACK(ssh_packet_debug_callback) |
119 | 92.9k | { |
120 | 92.9k | uint8_t always_display = -1; |
121 | 92.9k | char *message = NULL; |
122 | 92.9k | int rc; |
123 | | |
124 | 92.9k | (void)session; /* unused */ |
125 | 92.9k | (void)type; |
126 | 92.9k | (void)user; |
127 | | |
128 | 92.9k | rc = ssh_buffer_unpack(packet, "bs", &always_display, &message); |
129 | 92.9k | if (rc != SSH_OK) { |
130 | 92.7k | SSH_LOG(SSH_LOG_PACKET, "Error reading debug message"); |
131 | 92.7k | return SSH_PACKET_USED; |
132 | 92.7k | } |
133 | 183 | SSH_LOG(SSH_LOG_DEBUG, |
134 | 183 | "Received SSH_MSG_DEBUG packet with message %s%s", |
135 | 183 | message, |
136 | 183 | always_display != 0 ? " (always display)" : ""); |
137 | 183 | SAFE_FREE(message); |
138 | | |
139 | 183 | return SSH_PACKET_USED; |
140 | 92.9k | } |
141 | | |
142 | | SSH_PACKET_CALLBACK(ssh_packet_newkeys) |
143 | 6.09k | { |
144 | 6.09k | ssh_string sig_blob = NULL; |
145 | 6.09k | ssh_signature sig = NULL; |
146 | 6.09k | int rc; |
147 | | |
148 | 6.09k | (void)packet; |
149 | 6.09k | (void)user; |
150 | 6.09k | (void)type; |
151 | | |
152 | 6.09k | SSH_LOG(SSH_LOG_DEBUG, "Received SSH_MSG_NEWKEYS"); |
153 | | |
154 | 6.09k | if (session->session_state != SSH_SESSION_STATE_DH || |
155 | 6.09k | session->dh_handshake_state != DH_STATE_NEWKEYS_SENT) { |
156 | 0 | ssh_set_error(session, |
157 | 0 | SSH_FATAL, |
158 | 0 | "ssh_packet_newkeys called in wrong state : %d:%d", |
159 | 0 | session->session_state, |
160 | 0 | session->dh_handshake_state); |
161 | 0 | goto error; |
162 | 0 | } |
163 | | |
164 | 6.09k | if (session->flags & SSH_SESSION_FLAG_KEX_STRICT) { |
165 | | /* reset packet sequence number when running in strict kex mode */ |
166 | 183 | session->recv_seq = 0; |
167 | | /* Check that we aren't tainted */ |
168 | 183 | if (session->flags & SSH_SESSION_FLAG_KEX_TAINTED) { |
169 | 0 | ssh_set_error(session, |
170 | 0 | SSH_FATAL, |
171 | 0 | "Received unexpected packets in strict KEX mode."); |
172 | 0 | goto error; |
173 | 0 | } |
174 | 183 | } |
175 | | |
176 | 6.09k | if (session->server) { |
177 | | /* server things are done in server.c */ |
178 | 6.09k | session->dh_handshake_state=DH_STATE_FINISHED; |
179 | 6.09k | } else { |
180 | | #ifdef WITH_GSSAPI |
181 | | if (ssh_kex_is_gss(session->next_crypto)) { |
182 | | OM_uint32 maj_stat, min_stat; |
183 | | gss_buffer_desc mic = GSS_C_EMPTY_BUFFER, msg = GSS_C_EMPTY_BUFFER; |
184 | | |
185 | | if (session->gssapi == NULL || session->gssapi->ctx == NULL) { |
186 | | ssh_set_error(session, SSH_FATAL, "GSSAPI context not initialized"); |
187 | | goto error; |
188 | | } |
189 | | |
190 | | if (session->gssapi_key_exchange_mic == NULL) { |
191 | | ssh_set_error(session, |
192 | | SSH_FATAL, |
193 | | "GSSAPI mic not set"); |
194 | | goto error; |
195 | | } |
196 | | |
197 | | mic.length = ssh_string_len(session->gssapi_key_exchange_mic); |
198 | | mic.value = ssh_string_data(session->gssapi_key_exchange_mic); |
199 | | |
200 | | msg.length = session->next_crypto->digest_len; |
201 | | msg.value = session->next_crypto->secret_hash; |
202 | | |
203 | | maj_stat = gss_verify_mic(&min_stat, |
204 | | session->gssapi->ctx, |
205 | | &msg, |
206 | | &mic, |
207 | | NULL); |
208 | | if (maj_stat != GSS_S_COMPLETE) { |
209 | | ssh_set_error(session, |
210 | | SSH_FATAL, |
211 | | "Failed to verify mic after GSSAPI Key Exchange"); |
212 | | goto error; |
213 | | } |
214 | | SSH_STRING_FREE(session->gssapi_key_exchange_mic); |
215 | | } else |
216 | | #endif |
217 | 0 | { |
218 | 0 | ssh_key server_key = NULL; |
219 | | |
220 | | /* client */ |
221 | | |
222 | | /* Verify the host's signature. FIXME do it sooner */ |
223 | 0 | sig_blob = session->next_crypto->dh_server_signature; |
224 | 0 | session->next_crypto->dh_server_signature = NULL; |
225 | | |
226 | | /* get the server public key */ |
227 | 0 | server_key = ssh_dh_get_next_server_publickey(session); |
228 | 0 | if (server_key == NULL) { |
229 | 0 | goto error; |
230 | 0 | } |
231 | | |
232 | 0 | rc = ssh_pki_import_signature_blob(sig_blob, server_key, &sig); |
233 | 0 | ssh_string_burn(sig_blob); |
234 | 0 | SSH_STRING_FREE(sig_blob); |
235 | 0 | if (rc != SSH_OK) { |
236 | 0 | goto error; |
237 | 0 | } |
238 | | |
239 | | /* Check if signature from server matches user preferences */ |
240 | 0 | if (session->opts.wanted_methods[SSH_HOSTKEYS]) { |
241 | 0 | rc = match_group(session->opts.wanted_methods[SSH_HOSTKEYS], |
242 | 0 | sig->type_c); |
243 | 0 | if (rc == 0) { |
244 | 0 | ssh_set_error( |
245 | 0 | session, |
246 | 0 | SSH_FATAL, |
247 | 0 | "Public key from server (%s) doesn't match user " |
248 | 0 | "preference (%s)", |
249 | 0 | sig->type_c, |
250 | 0 | session->opts.wanted_methods[SSH_HOSTKEYS]); |
251 | 0 | goto error; |
252 | 0 | } |
253 | 0 | } |
254 | | |
255 | 0 | rc = ssh_pki_signature_verify(session, |
256 | 0 | sig, |
257 | 0 | server_key, |
258 | 0 | session->next_crypto->secret_hash, |
259 | 0 | session->next_crypto->digest_len); |
260 | 0 | SSH_SIGNATURE_FREE(sig); |
261 | 0 | if (rc == SSH_ERROR) { |
262 | 0 | ssh_set_error(session, |
263 | 0 | SSH_FATAL, |
264 | 0 | "Failed to verify server hostkey signature"); |
265 | 0 | goto error; |
266 | 0 | } |
267 | 0 | } |
268 | 0 | SSH_LOG(SSH_LOG_DEBUG, "Signature verified and valid"); |
269 | | |
270 | | /* When receiving this packet, we switch on the incoming crypto. */ |
271 | 0 | rc = ssh_packet_set_newkeys(session, SSH_DIRECTION_IN); |
272 | 0 | if (rc != SSH_OK) { |
273 | 0 | goto error; |
274 | 0 | } |
275 | 0 | } |
276 | 6.09k | session->dh_handshake_state = DH_STATE_FINISHED; |
277 | 6.09k | session->ssh_connection_callback(session); |
278 | 6.09k | return SSH_PACKET_USED; |
279 | | |
280 | 0 | error: |
281 | | #ifdef WITH_GSSAPI |
282 | | SSH_STRING_FREE(session->gssapi_key_exchange_mic); |
283 | | #endif |
284 | 0 | SSH_SIGNATURE_FREE(sig); |
285 | 0 | ssh_string_burn(sig_blob); |
286 | 0 | SSH_STRING_FREE(sig_blob); |
287 | 0 | session->session_state = SSH_SESSION_STATE_ERROR; |
288 | 0 | return SSH_PACKET_USED; |
289 | 6.09k | } |
290 | | |
291 | | /** |
292 | | * @internal |
293 | | * @brief handles a SSH_SERVICE_ACCEPT packet |
294 | | * |
295 | | */ |
296 | | SSH_PACKET_CALLBACK(ssh_packet_service_accept) |
297 | 0 | { |
298 | 0 | (void)packet; |
299 | 0 | (void)type; |
300 | 0 | (void)user; |
301 | |
|
302 | 0 | session->auth.service_state = SSH_AUTH_SERVICE_ACCEPTED; |
303 | 0 | SSH_LOG(SSH_LOG_PACKET, "Received SSH_MSG_SERVICE_ACCEPT"); |
304 | |
|
305 | 0 | return SSH_PACKET_USED; |
306 | 0 | } |
307 | | |
308 | | /** |
309 | | * @internal |
310 | | * @brief handles a SSH2_MSG_EXT_INFO packet defined in RFC 8308 |
311 | | * |
312 | | */ |
313 | | SSH_PACKET_CALLBACK(ssh_packet_ext_info) |
314 | 3.27k | { |
315 | 3.27k | int rc; |
316 | 3.27k | uint32_t nr_extensions = 0; |
317 | 3.27k | uint32_t i; |
318 | | |
319 | 3.27k | (void)type; |
320 | 3.27k | (void)user; |
321 | | |
322 | 3.27k | SSH_LOG(SSH_LOG_PACKET, "Received SSH_MSG_EXT_INFO"); |
323 | | |
324 | 3.27k | rc = ssh_buffer_get_u32(packet, &nr_extensions); |
325 | 3.27k | if (rc == 0) { |
326 | 233 | SSH_LOG(SSH_LOG_PACKET, "Failed to read number of extensions"); |
327 | 233 | return SSH_PACKET_USED; |
328 | 233 | } |
329 | | |
330 | 3.03k | nr_extensions = ntohl(nr_extensions); |
331 | 3.03k | if (nr_extensions > 128) { |
332 | 537 | SSH_LOG(SSH_LOG_PACKET, "Invalid number of extensions"); |
333 | 537 | return SSH_PACKET_USED; |
334 | 537 | } |
335 | | |
336 | 2.50k | SSH_LOG(SSH_LOG_PACKET, "Follows %" PRIu32 " extensions", nr_extensions); |
337 | | |
338 | 4.81k | for (i = 0; i < nr_extensions; i++) { |
339 | 3.79k | char *name = NULL; |
340 | 3.79k | char *value = NULL; |
341 | | |
342 | 3.79k | rc = ssh_buffer_unpack(packet, "ss", &name, &value); |
343 | 3.79k | if (rc != SSH_OK) { |
344 | 1.48k | SSH_LOG(SSH_LOG_PACKET, "Error reading extension name-value pair"); |
345 | 1.48k | return SSH_PACKET_USED; |
346 | 1.48k | } |
347 | | |
348 | 2.31k | if (strcmp(name, "server-sig-algs") == 0) { |
349 | | /* TODO check for NULL bytes */ |
350 | 839 | SSH_LOG(SSH_LOG_PACKET, "Extension: %s=<%s>", name, value); |
351 | | |
352 | 839 | rc = match_group(value, "rsa-sha2-512"); |
353 | 839 | if (rc == 1) { |
354 | 618 | session->extensions |= SSH_EXT_SIG_RSA_SHA512; |
355 | 618 | } |
356 | | |
357 | 839 | rc = match_group(value, "rsa-sha2-256"); |
358 | 839 | if (rc == 1) { |
359 | 617 | session->extensions |= SSH_EXT_SIG_RSA_SHA256; |
360 | 617 | } |
361 | 1.47k | } else if (strcmp(name, "publickey-hostbound@openssh.com") == 0) { |
362 | 19 | SSH_LOG(SSH_LOG_PACKET, "Extension: %s=<%s>", name, value); |
363 | 19 | session->extensions |= SSH_EXT_PUBLICKEY_HOSTBOUND; |
364 | 1.45k | } else if (strcmp(name, "ping@openssh.com") == 0) { |
365 | 6 | if (strcmp(value, "0") == 0) { |
366 | 3 | SSH_LOG(SSH_LOG_PACKET, |
367 | 3 | "Extension: %s=<%s> (supported)", |
368 | 3 | name, |
369 | 3 | value); |
370 | 3 | session->extensions |= SSH_EXT_PING; |
371 | 3 | } else { |
372 | 3 | SSH_LOG(SSH_LOG_PACKET, |
373 | 3 | "Extension: %s=<%s> (unsupported version, expected 0)", |
374 | 3 | name, |
375 | 3 | value); |
376 | 3 | } |
377 | 1.44k | } else { |
378 | 1.44k | SSH_LOG(SSH_LOG_PACKET, "Unknown extension: %s", name); |
379 | 1.44k | } |
380 | 2.31k | free(name); |
381 | 2.31k | free(value); |
382 | 2.31k | } |
383 | | |
384 | 1.01k | return SSH_PACKET_USED; |
385 | 2.50k | } |
386 | | |
387 | | SSH_PACKET_CALLBACK(ssh_packet_ping) |
388 | 248 | { |
389 | 248 | int rc; |
390 | 248 | ssh_string payload = NULL; |
391 | | |
392 | 248 | (void)type; |
393 | 248 | (void)user; |
394 | | |
395 | 248 | SSH_LOG(SSH_LOG_PACKET, "Received SSH2_MSG_PING"); |
396 | | |
397 | | /* Drop PING before the initial key exchange completes. During rekeying, |
398 | | * ssh_packet_send() queues the PONG response automatically via out_queue. |
399 | | */ |
400 | 248 | if (!(session->flags & SSH_SESSION_FLAG_AUTHENTICATED) && |
401 | 192 | session->dh_handshake_state != DH_STATE_FINISHED) { |
402 | 177 | SSH_LOG(SSH_LOG_DEBUG, |
403 | 177 | "Ignoring PING before initial key exchange is complete"); |
404 | 177 | return SSH_PACKET_USED; |
405 | 177 | } |
406 | | |
407 | 71 | payload = ssh_buffer_get_ssh_string(packet); |
408 | 71 | if (payload == NULL) { |
409 | 1 | SSH_LOG(SSH_LOG_PACKET, "SSH2_MSG_PING: missing string payload"); |
410 | 1 | ssh_set_error(session, |
411 | 1 | SSH_FATAL, |
412 | 1 | "SSH2_MSG_PING: missing string payload"); |
413 | 1 | session->session_state = SSH_SESSION_STATE_ERROR; |
414 | 1 | return SSH_PACKET_USED; |
415 | 1 | } |
416 | | |
417 | 70 | if (ssh_buffer_get_len(packet) != 0) { |
418 | 5 | SSH_LOG(SSH_LOG_PACKET, |
419 | 5 | "SSH2_MSG_PING: unexpected trailing data after payload"); |
420 | 5 | ssh_set_error(session, |
421 | 5 | SSH_FATAL, |
422 | 5 | "SSH2_MSG_PING: unexpected trailing data after payload"); |
423 | 5 | session->session_state = SSH_SESSION_STATE_ERROR; |
424 | 5 | SSH_STRING_FREE(payload); |
425 | 5 | return SSH_PACKET_USED; |
426 | 5 | } |
427 | | |
428 | 65 | rc = ssh_buffer_pack(session->out_buffer, "bS", SSH2_MSG_PONG, payload); |
429 | 65 | SSH_STRING_FREE(payload); |
430 | 65 | if (rc != SSH_OK) { |
431 | 0 | ssh_set_error_oom(session); |
432 | 0 | ssh_buffer_reinit(session->out_buffer); |
433 | 0 | return SSH_PACKET_USED; |
434 | 0 | } |
435 | | |
436 | 65 | rc = ssh_packet_send(session); |
437 | 65 | if (rc != SSH_OK) { |
438 | 0 | SSH_LOG(SSH_LOG_PACKET, "Failed to send SSH2_MSG_PONG"); |
439 | 0 | return SSH_PACKET_USED; |
440 | 0 | } |
441 | | |
442 | 65 | return SSH_PACKET_USED; |
443 | 65 | } |
444 | | |
445 | | /** |
446 | | * @internal |
447 | | * |
448 | | * @brief Handle a SSH2_MSG_PONG packet. |
449 | | * |
450 | | * This handler consumes PONG messages and invokes the user callback if |
451 | | * provided. |
452 | | */ |
453 | | SSH_PACKET_CALLBACK(ssh_packet_pong) |
454 | 0 | { |
455 | 0 | const void *payload_data = NULL; |
456 | 0 | ssh_string payload = NULL; |
457 | 0 | size_t payload_len = 0; |
458 | |
|
459 | 0 | (void)type; |
460 | 0 | (void)user; |
461 | |
|
462 | 0 | SSH_LOG(SSH_LOG_DEBUG, "Received SSH2_MSG_PONG"); |
463 | |
|
464 | 0 | if (session->pending_pings == 0) { |
465 | 0 | SSH_LOG(SSH_LOG_PACKET, "Received unsolicited SSH2_MSG_PONG"); |
466 | 0 | ssh_set_error(session, SSH_FATAL, "Received unsolicited SSH2_MSG_PONG"); |
467 | 0 | session->session_state = SSH_SESSION_STATE_ERROR; |
468 | 0 | return SSH_PACKET_USED; |
469 | 0 | } |
470 | | |
471 | 0 | payload = ssh_buffer_get_ssh_string(packet); |
472 | 0 | if (payload == NULL) { |
473 | 0 | SSH_LOG(SSH_LOG_PACKET, "SSH2_MSG_PONG: missing string payload"); |
474 | 0 | ssh_set_error(session, |
475 | 0 | SSH_FATAL, |
476 | 0 | "SSH2_MSG_PONG: missing string payload"); |
477 | 0 | session->session_state = SSH_SESSION_STATE_ERROR; |
478 | 0 | return SSH_PACKET_USED; |
479 | 0 | } |
480 | | |
481 | 0 | if (ssh_buffer_get_len(packet) != 0) { |
482 | 0 | SSH_LOG(SSH_LOG_PACKET, |
483 | 0 | "SSH2_MSG_PONG: unexpected trailing data after payload"); |
484 | 0 | ssh_set_error(session, |
485 | 0 | SSH_FATAL, |
486 | 0 | "SSH2_MSG_PONG: unexpected trailing data after payload"); |
487 | 0 | session->session_state = SSH_SESSION_STATE_ERROR; |
488 | 0 | SSH_STRING_FREE(payload); |
489 | 0 | return SSH_PACKET_USED; |
490 | 0 | } |
491 | | |
492 | 0 | payload_data = ssh_string_data(payload); |
493 | 0 | payload_len = ssh_string_len(payload); |
494 | | |
495 | | /* If user provided a high-level PONG callback, decode and invoke it. */ |
496 | 0 | if (ssh_callbacks_exists(session->common.callbacks, pong_function)) { |
497 | 0 | session->common.callbacks->pong_function( |
498 | 0 | session, |
499 | 0 | payload_data, |
500 | 0 | payload_len, |
501 | 0 | session->common.callbacks->userdata); |
502 | 0 | } |
503 | |
|
504 | 0 | SSH_STRING_FREE(payload); |
505 | 0 | session->pending_pings--; |
506 | |
|
507 | 0 | return SSH_PACKET_USED; |
508 | 0 | } |