diff options
author | djm@openbsd.org <djm@openbsd.org> | 2018-12-27 03:25:24 +0000 |
---|---|---|
committer | Damien Miller <djm@mindrot.org> | 2018-12-27 14:38:22 +1100 |
commit | 0a843d9a0e805f14653a555f5c7a8ba99d62c12d (patch) | |
tree | 481f36e9fd1918be5449e369a97c086a1a8d2432 /ssh_api.c | |
parent | 434b587afe41c19391821e7392005068fda76248 (diff) |
upstream: move client/server SSH-* banners to buffers under
ssh->kex and factor out the banner exchange. This eliminates some common code
from the client and server.
Also be more strict about handling \r characters - these should only
be accepted immediately before \n (pointed out by Jann Horn).
Inspired by a patch from Markus Schmidt.
(lots of) feedback and ok markus@
OpenBSD-Commit-ID: 1cc7885487a6754f63641d7d3279b0941890275b
Diffstat (limited to 'ssh_api.c')
-rw-r--r-- | ssh_api.c | 125 |
1 files changed, 70 insertions, 55 deletions
@@ -1,4 +1,4 @@ | |||
1 | /* $OpenBSD: ssh_api.c,v 1.8 2017/04/30 23:13:25 djm Exp $ */ | 1 | /* $OpenBSD: ssh_api.c,v 1.9 2018/12/27 03:25:25 djm Exp $ */ |
2 | /* | 2 | /* |
3 | * Copyright (c) 2012 Markus Friedl. All rights reserved. | 3 | * Copyright (c) 2012 Markus Friedl. All rights reserved. |
4 | * | 4 | * |
@@ -34,8 +34,8 @@ | |||
34 | #include <string.h> | 34 | #include <string.h> |
35 | 35 | ||
36 | int _ssh_exchange_banner(struct ssh *); | 36 | int _ssh_exchange_banner(struct ssh *); |
37 | int _ssh_send_banner(struct ssh *, char **); | 37 | int _ssh_send_banner(struct ssh *, struct sshbuf *); |
38 | int _ssh_read_banner(struct ssh *, char **); | 38 | int _ssh_read_banner(struct ssh *, struct sshbuf *); |
39 | int _ssh_order_hostkeyalgs(struct ssh *); | 39 | int _ssh_order_hostkeyalgs(struct ssh *); |
40 | int _ssh_verify_host_key(struct sshkey *, struct ssh *); | 40 | int _ssh_verify_host_key(struct sshkey *, struct ssh *); |
41 | struct sshkey *_ssh_host_public_key(int, int, struct ssh *); | 41 | struct sshkey *_ssh_host_public_key(int, int, struct ssh *); |
@@ -92,7 +92,7 @@ ssh_init(struct ssh **sshp, int is_server, struct kex_params *kex_params) | |||
92 | 92 | ||
93 | /* Initialize key exchange */ | 93 | /* Initialize key exchange */ |
94 | proposal = kex_params ? kex_params->proposal : myproposal; | 94 | proposal = kex_params ? kex_params->proposal : myproposal; |
95 | if ((r = kex_new(ssh, proposal, &ssh->kex)) != 0) { | 95 | if ((r = kex_ready(ssh, proposal)) != 0) { |
96 | ssh_free(ssh); | 96 | ssh_free(ssh); |
97 | return r; | 97 | return r; |
98 | } | 98 | } |
@@ -236,8 +236,8 @@ ssh_packet_next(struct ssh *ssh, u_char *typep) | |||
236 | * enough data. | 236 | * enough data. |
237 | */ | 237 | */ |
238 | *typep = SSH_MSG_NONE; | 238 | *typep = SSH_MSG_NONE; |
239 | if (ssh->kex->client_version_string == NULL || | 239 | if (sshbuf_len(ssh->kex->client_version) == 0 || |
240 | ssh->kex->server_version_string == NULL) | 240 | sshbuf_len(ssh->kex->server_version) == 0) |
241 | return _ssh_exchange_banner(ssh); | 241 | return _ssh_exchange_banner(ssh); |
242 | /* | 242 | /* |
243 | * If we enough data and a dispatch function then | 243 | * If we enough data and a dispatch function then |
@@ -312,39 +312,46 @@ ssh_input_space(struct ssh *ssh, size_t len) | |||
312 | 312 | ||
313 | /* Read other side's version identification. */ | 313 | /* Read other side's version identification. */ |
314 | int | 314 | int |
315 | _ssh_read_banner(struct ssh *ssh, char **bannerp) | 315 | _ssh_read_banner(struct ssh *ssh, struct sshbuf *banner) |
316 | { | 316 | { |
317 | struct sshbuf *input; | 317 | struct sshbuf *input = ssh_packet_get_input(ssh); |
318 | const char *s; | ||
319 | char buf[256], remote_version[256]; /* must be same size! */ | ||
320 | const char *mismatch = "Protocol mismatch.\r\n"; | 318 | const char *mismatch = "Protocol mismatch.\r\n"; |
321 | int r, remote_major, remote_minor; | 319 | const u_char *s = sshbuf_ptr(input); |
322 | size_t i, n, j, len; | 320 | u_char c; |
321 | char *cp, *remote_version; | ||
322 | int r, remote_major, remote_minor, expect_nl; | ||
323 | size_t n, j; | ||
323 | 324 | ||
324 | *bannerp = NULL; | ||
325 | input = ssh_packet_get_input(ssh); | ||
326 | len = sshbuf_len(input); | ||
327 | s = (const char *)sshbuf_ptr(input); | ||
328 | for (j = n = 0;;) { | 325 | for (j = n = 0;;) { |
329 | for (i = 0; i < sizeof(buf) - 1; i++) { | 326 | sshbuf_reset(banner); |
330 | if (j >= len) | 327 | expect_nl = 0; |
331 | return (0); | 328 | for (;;) { |
332 | buf[i] = s[j++]; | 329 | if (j >= sshbuf_len(input)) |
333 | if (buf[i] == '\r') { | 330 | return 0; /* insufficient data in input buf */ |
334 | buf[i] = '\n'; | 331 | c = s[j++]; |
335 | buf[i + 1] = 0; | 332 | if (c == '\r') { |
336 | continue; /**XXX wait for \n */ | 333 | expect_nl = 1; |
334 | continue; | ||
337 | } | 335 | } |
338 | if (buf[i] == '\n') { | 336 | if (c == '\n') |
339 | buf[i + 1] = 0; | ||
340 | break; | 337 | break; |
341 | } | 338 | if (expect_nl) |
339 | goto bad; | ||
340 | if ((r = sshbuf_put_u8(banner, c)) != 0) | ||
341 | return r; | ||
342 | if (sshbuf_len(banner) > SSH_MAX_BANNER_LEN) | ||
343 | goto bad; | ||
342 | } | 344 | } |
343 | buf[sizeof(buf) - 1] = 0; | 345 | if (sshbuf_len(banner) >= 4 && |
344 | if (strncmp(buf, "SSH-", 4) == 0) | 346 | memcmp(sshbuf_ptr(banner), "SSH-", 4) == 0) |
345 | break; | 347 | break; |
346 | debug("ssh_exchange_identification: %s", buf); | 348 | if ((cp = sshbuf_dup_string(banner)) == NULL) |
347 | if (ssh->kex->server || ++n > 65536) { | 349 | return SSH_ERR_ALLOC_FAIL; |
350 | debug("%s: %s", __func__, cp); | ||
351 | free(cp); | ||
352 | /* Accept lines before banner only on client */ | ||
353 | if (ssh->kex->server || ++n > SSH_MAX_PRE_BANNER_LINES) { | ||
354 | bad: | ||
348 | if ((r = sshbuf_put(ssh_packet_get_output(ssh), | 355 | if ((r = sshbuf_put(ssh_packet_get_output(ssh), |
349 | mismatch, strlen(mismatch))) != 0) | 356 | mismatch, strlen(mismatch))) != 0) |
350 | return r; | 357 | return r; |
@@ -354,11 +361,17 @@ _ssh_read_banner(struct ssh *ssh, char **bannerp) | |||
354 | if ((r = sshbuf_consume(input, j)) != 0) | 361 | if ((r = sshbuf_consume(input, j)) != 0) |
355 | return r; | 362 | return r; |
356 | 363 | ||
364 | if ((cp = sshbuf_dup_string(banner)) == NULL) | ||
365 | return SSH_ERR_ALLOC_FAIL; | ||
366 | /* XXX remote version must be the same size as banner for sscanf */ | ||
367 | if ((remote_version = calloc(1, sshbuf_len(banner))) == NULL) | ||
368 | return SSH_ERR_ALLOC_FAIL; | ||
369 | |||
357 | /* | 370 | /* |
358 | * Check that the versions match. In future this might accept | 371 | * Check that the versions match. In future this might accept |
359 | * several versions and set appropriate flags to handle them. | 372 | * several versions and set appropriate flags to handle them. |
360 | */ | 373 | */ |
361 | if (sscanf(buf, "SSH-%d.%d-%[^\n]\n", | 374 | if (sscanf(cp, "SSH-%d.%d-%[^\n]\n", |
362 | &remote_major, &remote_minor, remote_version) != 3) | 375 | &remote_major, &remote_minor, remote_version) != 3) |
363 | return SSH_ERR_INVALID_FORMAT; | 376 | return SSH_ERR_INVALID_FORMAT; |
364 | debug("Remote protocol version %d.%d, remote software version %.100s", | 377 | debug("Remote protocol version %d.%d, remote software version %.100s", |
@@ -371,27 +384,29 @@ _ssh_read_banner(struct ssh *ssh, char **bannerp) | |||
371 | } | 384 | } |
372 | if (remote_major != 2) | 385 | if (remote_major != 2) |
373 | return SSH_ERR_PROTOCOL_MISMATCH; | 386 | return SSH_ERR_PROTOCOL_MISMATCH; |
374 | chop(buf); | 387 | debug("Remote version string %.100s", cp); |
375 | debug("Remote version string %.100s", buf); | 388 | free(cp); |
376 | if ((*bannerp = strdup(buf)) == NULL) | ||
377 | return SSH_ERR_ALLOC_FAIL; | ||
378 | return 0; | 389 | return 0; |
379 | } | 390 | } |
380 | 391 | ||
381 | /* Send our own protocol version identification. */ | 392 | /* Send our own protocol version identification. */ |
382 | int | 393 | int |
383 | _ssh_send_banner(struct ssh *ssh, char **bannerp) | 394 | _ssh_send_banner(struct ssh *ssh, struct sshbuf *banner) |
384 | { | 395 | { |
385 | char buf[256]; | 396 | char *cp; |
386 | int r; | 397 | int r; |
387 | 398 | ||
388 | snprintf(buf, sizeof buf, "SSH-2.0-%.100s\r\n", SSH_VERSION); | 399 | if ((r = sshbuf_putf(banner, "SSH-2.0-%.100s\r\n", SSH_VERSION)) != 0) |
389 | if ((r = sshbuf_put(ssh_packet_get_output(ssh), buf, strlen(buf))) != 0) | 400 | return r; |
401 | if ((r = sshbuf_putb(ssh_packet_get_output(ssh), banner)) != 0) | ||
402 | return r; | ||
403 | /* Remove trailing \r\n */ | ||
404 | if ((r = sshbuf_consume_end(banner, 2)) != 0) | ||
390 | return r; | 405 | return r; |
391 | chop(buf); | 406 | if ((cp = sshbuf_dup_string(banner)) == NULL) |
392 | debug("Local version string %.100s", buf); | ||
393 | if ((*bannerp = strdup(buf)) == NULL) | ||
394 | return SSH_ERR_ALLOC_FAIL; | 407 | return SSH_ERR_ALLOC_FAIL; |
408 | debug("Local version string %.100s", cp); | ||
409 | free(cp); | ||
395 | return 0; | 410 | return 0; |
396 | } | 411 | } |
397 | 412 | ||
@@ -408,25 +423,25 @@ _ssh_exchange_banner(struct ssh *ssh) | |||
408 | 423 | ||
409 | r = 0; | 424 | r = 0; |
410 | if (kex->server) { | 425 | if (kex->server) { |
411 | if (kex->server_version_string == NULL) | 426 | if (sshbuf_len(ssh->kex->server_version) == 0) |
412 | r = _ssh_send_banner(ssh, &kex->server_version_string); | 427 | r = _ssh_send_banner(ssh, ssh->kex->server_version); |
413 | if (r == 0 && | 428 | if (r == 0 && |
414 | kex->server_version_string != NULL && | 429 | sshbuf_len(ssh->kex->server_version) != 0 && |
415 | kex->client_version_string == NULL) | 430 | sshbuf_len(ssh->kex->client_version) == 0) |
416 | r = _ssh_read_banner(ssh, &kex->client_version_string); | 431 | r = _ssh_read_banner(ssh, ssh->kex->client_version); |
417 | } else { | 432 | } else { |
418 | if (kex->server_version_string == NULL) | 433 | if (sshbuf_len(ssh->kex->server_version) == 0) |
419 | r = _ssh_read_banner(ssh, &kex->server_version_string); | 434 | r = _ssh_read_banner(ssh, ssh->kex->server_version); |
420 | if (r == 0 && | 435 | if (r == 0 && |
421 | kex->server_version_string != NULL && | 436 | sshbuf_len(ssh->kex->server_version) != 0 && |
422 | kex->client_version_string == NULL) | 437 | sshbuf_len(ssh->kex->client_version) == 0) |
423 | r = _ssh_send_banner(ssh, &kex->client_version_string); | 438 | r = _ssh_send_banner(ssh, ssh->kex->client_version); |
424 | } | 439 | } |
425 | if (r != 0) | 440 | if (r != 0) |
426 | return r; | 441 | return r; |
427 | /* start initial kex as soon as we have exchanged the banners */ | 442 | /* start initial kex as soon as we have exchanged the banners */ |
428 | if (kex->server_version_string != NULL && | 443 | if (sshbuf_len(ssh->kex->server_version) != 0 && |
429 | kex->client_version_string != NULL) { | 444 | sshbuf_len(ssh->kex->client_version) != 0) { |
430 | if ((r = _ssh_order_hostkeyalgs(ssh)) != 0 || | 445 | if ((r = _ssh_order_hostkeyalgs(ssh)) != 0 || |
431 | (r = kex_send_kexinit(ssh)) != 0) | 446 | (r = kex_send_kexinit(ssh)) != 0) |
432 | return r; | 447 | return r; |