From c07e8624d2f5e63395d951e19d26c831f935f811 Mon Sep 17 00:00:00 2001 From: TJ Saunders Date: Sun, 26 Sep 2021 14:43:38 -0700 Subject: [PATCH] Issue #1330: Implement OpenSSH "Encrypt-Then-MAC" (ETM) algorithm extensions. --- NEWS | 1 + RELEASE_NOTES | 3 + contrib/mod_sftp/cipher.c | 63 +- contrib/mod_sftp/crypto.c | 30 +- contrib/mod_sftp/mac.c | 117 +- contrib/mod_sftp/mac.h | 22 +- contrib/mod_sftp/packet.c | 315 +++-- doc/contrib/mod_sftp.html | 13 + tests/t/lib/ProFTPD/Tests/Modules/mod_sftp.pm | 1021 +++++++++++++++++ 9 files changed, 1450 insertions(+), 135 deletions(-) diff --git a/NEWS b/NEWS index f087237e8d..05e54a0342 100644 --- a/NEWS +++ b/NEWS @@ -24,6 +24,7 @@ added to mod_sftp. - Issue 1333 - Implement an LDAPConnectTimeout directive, to configure the timeout used when connecting to LDAP servers. +- Issue 1330 - Implement OpenSSH "Encrypt-Then-MAC" (ETM) algorithm extensions. 1.3.8rc2 - Released 29-Aug-2021 -------------------------------- diff --git a/RELEASE_NOTES b/RELEASE_NOTES index 08976a8956..b7087b3ab8 100644 --- a/RELEASE_NOTES +++ b/RELEASE_NOTES @@ -26,6 +26,9 @@ ChangeLog files. SFTPCiphers "aes128-gcm@openssh.com", "aes256-gcm@openssh.com" (Bug #3759) + SFTPDigests + "hmac-md5-etm@openssh.com", ... (Issue #1330) + SFTPOptions NoHostkeyRotation diff --git a/contrib/mod_sftp/cipher.c b/contrib/mod_sftp/cipher.c index fd4b2541a7..496bbd06a2 100644 --- a/contrib/mod_sftp/cipher.c +++ b/contrib/mod_sftp/cipher.c @@ -479,7 +479,7 @@ int sftp_cipher_set_read_key(pool *p, const EVP_MD *hash, const BIGNUM *k, pctx = read_ctxs[read_cipher_idx]; bufsz = buflen = SFTP_CIPHER_BUFSZ; - ptr = buf = sftp_msg_getbuf(p, bufsz); + ptr = buf = palloc(p, bufsz); /* Need to use SSH2-style format of K for the IV and key. */ sftp_msg_write_mpint(&buf, &buflen, k); @@ -626,38 +626,31 @@ int sftp_cipher_read_data(struct ssh2_packet *pkt, unsigned char *data, /* Allocate a buffer that's large enough. */ bufsz = (data_len + read_blocksz - 1); - ptr = buf2 = palloc(pkt->pool, bufsz); + ptr = buf2 = pcalloc(pkt->pool, bufsz); } else { ptr = buf2 = *buf; } - if (pkt->packet_len == 0 && - auth_len > 0) { - unsigned char prev_iv[1]; - + if (pkt->packet_len == 0) { + if (auth_len > 0) { #if defined(EVP_CTRL_GCM_IV_GEN) - /* Increment the IV. */ - if (EVP_CIPHER_CTX_ctrl(pctx, EVP_CTRL_GCM_IV_GEN, 1, prev_iv) != 1) { - (void) pr_log_writefile(sftp_logfd, MOD_SFTP_VERSION, - "error incrementing %s IV data for client: %s", cipher->algo, - sftp_crypto_get_errors()); - errno = EIO; - return -1; - } -#endif + unsigned char prev_iv[1]; - if (pkt->aad_len > 0 && - pkt->aad == NULL) { - if (EVP_Cipher(pctx, NULL, data, pkt->aad_len) < 0) { + /* Increment the IV. */ + if (EVP_CIPHER_CTX_ctrl(pctx, EVP_CTRL_GCM_IV_GEN, 1, prev_iv) != 1) { (void) pr_log_writefile(sftp_logfd, MOD_SFTP_VERSION, - "error setting %s AAD data for client: %s", cipher->algo, + "error incrementing %s IV data for client: %s", cipher->algo, sftp_crypto_get_errors()); errno = EIO; return -1; } +#endif + } - pkt->aad = palloc(pkt->pool, pkt->aad_len); + if (pkt->aad_len > 0 && + pkt->aad == NULL) { + pkt->aad = pcalloc(pkt->pool, pkt->aad_len); memcpy(pkt->aad, data, pkt->aad_len); memcpy(ptr, data, pkt->aad_len); @@ -668,6 +661,16 @@ int sftp_cipher_read_data(struct ssh2_packet *pkt, unsigned char *data, data += pkt->aad_len; data_len -= pkt->aad_len; output_buflen -= pkt->aad_len; + + if (auth_len > 0) { + if (EVP_Cipher(pctx, NULL, pkt->aad, pkt->aad_len) < 0) { + (void) pr_log_writefile(sftp_logfd, MOD_SFTP_VERSION, + "error setting %s AAD data for client: %s", cipher->algo, + sftp_crypto_get_errors()); + errno = EIO; + return -1; + } + } } } @@ -819,7 +822,7 @@ int sftp_cipher_set_write_key(pool *p, const EVP_MD *hash, const BIGNUM *k, pctx = write_ctxs[write_cipher_idx]; bufsz = buflen = SFTP_CIPHER_BUFSZ; - ptr = buf = sftp_msg_getbuf(p, bufsz); + ptr = buf = palloc(p, bufsz); /* Need to use SSH2-style format of K for the IV and key. */ sftp_msg_write_mpint(&buf, &buflen, k); @@ -964,15 +967,18 @@ int sftp_cipher_write_data(struct ssh2_packet *pkt, unsigned char *buf, * Encrypt-Then-MAC modes. */ datasz -= pkt->aad_len; + + /* And, for ETM modes, we may need a little more space. */ + datasz += sftp_cipher_get_write_block_size(); } datalen = datasz; ptr = data = palloc(pkt->pool, datasz); if (auth_len > 0) { +#if defined(EVP_CTRL_GCM_IV_GEN) unsigned char prev_iv[1]; -#if defined(EVP_CTRL_GCM_IV_GEN) /* Increment the IV. */ if (EVP_CIPHER_CTX_ctrl(pctx, EVP_CTRL_GCM_IV_GEN, 1, prev_iv) != 1) { (void) pr_log_writefile(sftp_logfd, MOD_SFTP_VERSION, @@ -982,14 +988,17 @@ int sftp_cipher_write_data(struct ssh2_packet *pkt, unsigned char *buf, return -1; } #endif + } - if (pkt->aad_len > 0) { - uint32_t packet_len; + if (pkt->aad_len > 0 && + pkt->aad == NULL) { + uint32_t packet_len; - packet_len = htonl(pkt->packet_len); - pkt->aad = palloc(pkt->pool, pkt->aad_len); - memcpy(pkt->aad, &packet_len, pkt->aad_len); + packet_len = htonl(pkt->packet_len); + pkt->aad = pcalloc(pkt->pool, pkt->aad_len); + memcpy(pkt->aad, &packet_len, pkt->aad_len); + if (auth_len > 0) { if (EVP_Cipher(pctx, NULL, pkt->aad, pkt->aad_len) < 0) { (void) pr_log_writefile(sftp_logfd, MOD_SFTP_VERSION, "error setting %s AAD (%lu bytes) for client: %s", cipher->algo, diff --git a/contrib/mod_sftp/crypto.c b/contrib/mod_sftp/crypto.c index 930b752e44..1ed1f4d476 100644 --- a/contrib/mod_sftp/crypto.c +++ b/contrib/mod_sftp/crypto.c @@ -169,20 +169,28 @@ static struct sftp_digest digests[] = { */ #ifdef HAVE_SHA256_OPENSSL { "hmac-sha2-256", "sha256", EVP_sha256, 0, TRUE, TRUE }, + { "hmac-sha2-256-etm@openssh.com", "sha256", EVP_sha256, 0, TRUE, TRUE }, #endif /* SHA256 support in OpenSSL */ #ifdef HAVE_SHA512_OPENSSL { "hmac-sha2-512", "sha512", EVP_sha512, 0, TRUE, TRUE }, + { "hmac-sha2-512-etm@openssh.com", "sha512", EVP_sha512, 0, TRUE, TRUE }, #endif /* SHA512 support in OpenSSL */ { "hmac-sha1", "sha1", EVP_sha1, 0, TRUE, TRUE }, + { "hmac-sha1-etm@openssh.com", "sha1",EVP_sha1, 0, TRUE, TRUE }, { "hmac-sha1-96", "sha1", EVP_sha1, 12, TRUE, TRUE }, + { "hmac-sha1-96-etm@openssh.com", "sha1", EVP_sha1, 12, TRUE, TRUE }, { "hmac-md5", "md5", EVP_md5, 0, FALSE, FALSE }, + { "hmac-md5-etm@openssh.com", "md5", EVP_md5, 0, FALSE, TRUE }, { "hmac-md5-96", "md5", EVP_md5, 12, FALSE, FALSE }, + { "hmac-md5-96-etm@openssh.com", "md5", EVP_md5, 12, FALSE, TRUE }, #if !defined(OPENSSL_NO_RIPEMD) { "hmac-ripemd160", "rmd160", EVP_ripemd160, 0, FALSE, FALSE }, #endif /* !OPENSSL_NO_RIPEMD */ #if OPENSSL_VERSION_NUMBER > 0x000907000L { "umac-64@openssh.com", NULL, NULL, 8, TRUE, FALSE }, + { "umac-64-etm@openssh.com", NULL, NULL, 8, TRUE, FALSE }, { "umac-128@openssh.com", NULL, NULL, 16, TRUE, FALSE }, + { "umac-128-etm@openssh.com", NULL, NULL, 16, TRUE, FALSE }, #endif /* OpenSSL-0.9.7 or later */ { "none", "null", EVP_md_null, 0, FALSE, TRUE }, { NULL, NULL, NULL, 0, FALSE, FALSE } @@ -1046,10 +1054,12 @@ const EVP_MD *sftp_crypto_get_digest(const char *name, uint32_t *mac_len) { const EVP_MD *digest = NULL; #if OPENSSL_VERSION_NUMBER > 0x000907000L - if (strncmp(name, "umac-64@openssh.com", 12) == 0) { + if (strcmp(name, "umac-64@openssh.com") == 0 || + strcmp(name, "umac-64-etm@openssh.com") == 0) { digest = get_umac64_digest(); - } else if (strncmp(name, "umac-128@openssh.com", 13) == 0) { + } else if (strcmp(name, "umac-128@openssh.com") == 0 || + strcmp(name, "umac-128-etm@openssh.com") == 0) { digest = get_umac128_digest(); #else if (FALSE) { @@ -1237,7 +1247,7 @@ const char *sftp_crypto_get_kexinit_digest_list(pool *p) { } #endif /* OPENSSL_FIPS */ - if (strncmp(c->argv[i], "none", 5) != 0) { + if (strcmp(c->argv[i], "none") != 0) { if (digests[j].openssl_name != NULL && EVP_get_digestbyname(digests[j].openssl_name) != NULL) { res = pstrcat(p, res, *res ? "," : "", @@ -1245,8 +1255,10 @@ const char *sftp_crypto_get_kexinit_digest_list(pool *p) { } else { /* The umac-64/umac-128 digests are special cases. */ - if (strncmp(digests[j].name, "umac-64@openssh.com", 12) == 0 || - strncmp(digests[j].name, "umac-128@openssh.com", 13) == 0) { + if (strcmp(digests[j].name, "umac-64@openssh.com") == 0 || + strcmp(digests[j].name, "umac-64-etm@openssh.com") == 0 || + strcmp(digests[j].name, "umac-128@openssh.com") == 0 || + strcmp(digests[j].name, "umac-128-etm@openssh.com") == 0) { res = pstrcat(p, res, *res ? "," : "", pstrdup(p, digests[j].name), NULL); @@ -1284,7 +1296,7 @@ const char *sftp_crypto_get_kexinit_digest_list(pool *p) { } #endif /* OPENSSL_FIPS */ - if (strncmp(digests[i].name, "none", 5) != 0) { + if (strcmp(digests[i].name, "none") != 0) { if (digests[i].openssl_name != NULL && EVP_get_digestbyname(digests[i].openssl_name) != NULL) { res = pstrcat(p, res, *res ? "," : "", @@ -1292,8 +1304,10 @@ const char *sftp_crypto_get_kexinit_digest_list(pool *p) { } else { /* The umac-64/umac-128 digests are special cases. */ - if (strncmp(digests[i].name, "umac-64@openssh.com", 12) == 0 || - strncmp(digests[i].name, "umac-128@openssh.com", 13) == 0) { + if (strcmp(digests[i].name, "umac-64@openssh.com") == 0 || + strcmp(digests[i].name, "umac-64-etm@openssh.com") == 0 || + strcmp(digests[i].name, "umac-128@openssh.com") == 0 || + strcmp(digests[i].name, "umac-128-etm@openssh.com") == 0) { res = pstrcat(p, res, *res ? "," : "", pstrdup(p, digests[i].name), NULL); diff --git a/contrib/mod_sftp/mac.c b/contrib/mod_sftp/mac.c index fa3fbc0d84..3a40bcea47 100644 --- a/contrib/mod_sftp/mac.c +++ b/contrib/mod_sftp/mac.c @@ -38,6 +38,7 @@ struct sftp_mac { pool *pool; const char *algo; int algo_type; + int is_etm; const EVP_MD *digest; @@ -66,15 +67,15 @@ struct sftp_mac { */ static struct sftp_mac read_macs[] = { - { NULL, NULL, 0, NULL, NULL, 0, 0, 0 }, - { NULL, NULL, 0, NULL, NULL, 0, 0, 0 } + { NULL, NULL, 0, FALSE, NULL, NULL, 0, 0, 0 }, + { NULL, NULL, 0, FALSE, NULL, NULL, 0, 0, 0 } }; static HMAC_CTX *hmac_read_ctxs[2]; static struct umac_ctx *umac_read_ctxs[2]; static struct sftp_mac write_macs[] = { - { NULL, NULL, 0, NULL, NULL, 0, 0, 0 }, - { NULL, NULL, 0, NULL, NULL, 0, 0, 0 } + { NULL, NULL, 0, FALSE, NULL, NULL, 0, 0, 0 }, + { NULL, NULL, 0, FALSE, NULL, NULL, 0, 0, 0 } }; static HMAC_CTX *hmac_write_ctxs[2]; static struct umac_ctx *umac_write_ctxs[2]; @@ -222,7 +223,7 @@ static int init_mac(pool *p, struct sftp_mac *mac, HMAC_CTX *hmac_ctx, } static int get_mac(struct ssh2_packet *pkt, struct sftp_mac *mac, - HMAC_CTX *hmac_ctx, struct umac_ctx *umac_ctx, int flags) { + HMAC_CTX *hmac_ctx, struct umac_ctx *umac_ctx, int etm_mac, int flags) { unsigned char *mac_data; unsigned char *buf, *ptr; uint32_t buflen, bufsz = 0, mac_len = 0; @@ -231,14 +232,28 @@ static int get_mac(struct ssh2_packet *pkt, struct sftp_mac *mac, bufsz = (sizeof(uint32_t) * 2) + pkt->packet_len; mac_data = pcalloc(pkt->pool, EVP_MAX_MD_SIZE); + if (etm_mac == TRUE) { + bufsz += sftp_mac_get_block_size(); + } + buflen = bufsz; - ptr = buf = sftp_msg_getbuf(pkt->pool, bufsz); + ptr = buf = palloc(pkt->pool, bufsz); sftp_msg_write_int(&buf, &buflen, pkt->seqno); sftp_msg_write_int(&buf, &buflen, pkt->packet_len); - sftp_msg_write_byte(&buf, &buflen, pkt->padding_len); + + if (etm_mac == FALSE) { + /* For Encrypt-Then-Mac modes, padding and its length will be part of + * the encrypted payload. + */ + sftp_msg_write_byte(&buf, &buflen, pkt->padding_len); + } + sftp_msg_write_data(&buf, &buflen, pkt->payload, pkt->payload_len, FALSE); - sftp_msg_write_data(&buf, &buflen, pkt->padding, pkt->padding_len, FALSE); + + if (etm_mac == FALSE) { + sftp_msg_write_data(&buf, &buflen, pkt->padding, pkt->padding_len, FALSE); + } #if OPENSSL_VERSION_NUMBER > 0x000907000L # if OPENSSL_VERSION_NUMBER >= 0x10000001L @@ -284,13 +299,27 @@ static int get_mac(struct ssh2_packet *pkt, struct sftp_mac *mac, bufsz = sizeof(uint32_t) + pkt->packet_len; mac_data = pcalloc(pkt->pool, EVP_MAX_MD_SIZE); + if (etm_mac == TRUE) { + bufsz += sftp_mac_get_block_size(); + } + buflen = bufsz; - ptr = buf = sftp_msg_getbuf(pkt->pool, bufsz); + ptr = buf = palloc(pkt->pool, bufsz); sftp_msg_write_int(&buf, &buflen, pkt->packet_len); - sftp_msg_write_byte(&buf, &buflen, pkt->padding_len); + + if (etm_mac == FALSE) { + /* For Encrypt-Then-Mac modes, padding and its length will be part of + * the encrypted payload. + */ + sftp_msg_write_byte(&buf, &buflen, pkt->padding_len); + } + sftp_msg_write_data(&buf, &buflen, pkt->payload, pkt->payload_len, FALSE); - sftp_msg_write_data(&buf, &buflen, pkt->padding, pkt->padding_len, FALSE); + + if (etm_mac == FALSE) { + sftp_msg_write_data(&buf, &buflen, pkt->padding, pkt->padding_len, FALSE); + } nonce_ptr = nonce; nonce_len = sizeof(nonce); @@ -660,7 +689,17 @@ const char *sftp_mac_get_read_algo(void) { return NULL; } +int sftp_mac_is_read_etm(void) { + if (read_macs[read_mac_idx].key) { + return read_macs[read_mac_idx].is_etm; + } + + return FALSE; +} + int sftp_mac_set_read_algo(const char *algo) { + const char *etm_suffix; + size_t algo_len, etm_len; uint32_t mac_len; unsigned int idx = read_mac_idx; @@ -706,11 +745,13 @@ int sftp_mac_set_read_algo(const char *algo) { pr_pool_tag(read_macs[idx].pool, "SFTP MAC read pool"); read_macs[idx].algo = pstrdup(read_macs[idx].pool, algo); - if (strncmp(read_macs[idx].algo, "umac-64@openssh.com", 12) == 0) { + if (strcmp(read_macs[idx].algo, "umac-64@openssh.com") == 0 || + strcmp(read_macs[idx].algo, "umac-64-etm@openssh.com") == 0) { read_macs[idx].algo_type = SFTP_MAC_ALGO_TYPE_UMAC64; umac_read_ctxs[idx] = umac_alloc(); - } else if (strncmp(read_macs[idx].algo, "umac-128@openssh.com", 13) == 0) { + } else if (strcmp(read_macs[idx].algo, "umac-128@openssh.com") == 0 || + strcmp(read_macs[idx].algo, "umac-128-etm@openssh.com") == 0) { read_macs[idx].algo_type = SFTP_MAC_ALGO_TYPE_UMAC128; umac_read_ctxs[idx] = umac128_alloc(); @@ -719,6 +760,15 @@ int sftp_mac_set_read_algo(const char *algo) { } read_macs[idx].mac_len = mac_len; + + algo_len = strlen(algo); + etm_suffix = "-etm@openssh.com"; + etm_len = strlen(etm_suffix); + + if (pr_strnrstr(algo, algo_len, etm_suffix, etm_len, 0) == TRUE) { + read_macs[idx].is_etm = TRUE; + } + return 0; } @@ -745,7 +795,7 @@ int sftp_mac_set_read_key(pool *p, const EVP_MD *hash, const BIGNUM *k, umac_ctx = umac_read_ctxs[read_mac_idx]; bufsz = buflen = SFTP_MAC_BUFSZ; - ptr = buf = sftp_msg_getbuf(p, bufsz); + ptr = buf = palloc(p, bufsz); /* Need to use SSH2-style format of K for the key. */ sftp_msg_write_mpint(&buf, &buflen, k); @@ -785,13 +835,15 @@ int sftp_mac_read_data(struct ssh2_packet *pkt) { struct sftp_mac *mac; HMAC_CTX *hmac_ctx; struct umac_ctx *umac_ctx; - int res; + int res, etm_mac = FALSE; /* For authenticated encryption ciphers, there is no separate MAC. */ if (sftp_cipher_get_read_auth_size() > 0) { return 0; } + etm_mac = sftp_mac_is_read_etm(); + mac = &(read_macs[read_mac_idx]); hmac_ctx = hmac_read_ctxs[read_mac_idx]; umac_ctx = umac_read_ctxs[read_mac_idx]; @@ -803,7 +855,7 @@ int sftp_mac_read_data(struct ssh2_packet *pkt) { return 0; } - res = get_mac(pkt, mac, hmac_ctx, umac_ctx, SFTP_MAC_FL_READ_MAC); + res = get_mac(pkt, mac, hmac_ctx, umac_ctx, etm_mac, SFTP_MAC_FL_READ_MAC); if (res < 0) { return -1; } @@ -819,7 +871,17 @@ const char *sftp_mac_get_write_algo(void) { return NULL; } +int sftp_mac_is_write_etm(void) { + if (write_macs[write_mac_idx].key) { + return write_macs[write_mac_idx].is_etm; + } + + return FALSE; +} + int sftp_mac_set_write_algo(const char *algo) { + const char *etm_suffix; + size_t algo_len, etm_len; uint32_t mac_len; unsigned int idx = write_mac_idx; @@ -865,11 +927,13 @@ int sftp_mac_set_write_algo(const char *algo) { pr_pool_tag(write_macs[idx].pool, "SFTP MAC write pool"); write_macs[idx].algo = pstrdup(write_macs[idx].pool, algo); - if (strncmp(write_macs[idx].algo, "umac-64@openssh.com", 12) == 0) { + if (strcmp(write_macs[idx].algo, "umac-64@openssh.com") == 0 || + strcmp(write_macs[idx].algo, "umac-64-etm@openssh.com") == 0) { write_macs[idx].algo_type = SFTP_MAC_ALGO_TYPE_UMAC64; umac_write_ctxs[idx] = umac_alloc(); - } else if (strncmp(write_macs[idx].algo, "umac-128@openssh.com", 13) == 0) { + } else if (strcmp(write_macs[idx].algo, "umac-128@openssh.com") == 0 || + strcmp(write_macs[idx].algo, "umac-128-etm@openssh.com") == 0) { write_macs[idx].algo_type = SFTP_MAC_ALGO_TYPE_UMAC128; umac_write_ctxs[idx] = umac128_alloc(); @@ -878,6 +942,15 @@ int sftp_mac_set_write_algo(const char *algo) { } write_macs[idx].mac_len = mac_len; + + algo_len = strlen(algo); + etm_suffix = "-etm@openssh.com"; + etm_len = strlen(etm_suffix); + + if (pr_strnrstr(algo, algo_len, etm_suffix, etm_len, 0) == TRUE) { + write_macs[idx].is_etm = TRUE; + } + return 0; } @@ -903,7 +976,7 @@ int sftp_mac_set_write_key(pool *p, const EVP_MD *hash, const BIGNUM *k, umac_ctx = umac_write_ctxs[write_mac_idx]; bufsz = buflen = SFTP_MAC_BUFSZ; - ptr = buf = sftp_msg_getbuf(p, bufsz); + ptr = buf = palloc(p, bufsz); /* Need to use SSH2-style format of K for the key. */ sftp_msg_write_mpint(&buf, &buflen, k); @@ -935,13 +1008,15 @@ int sftp_mac_write_data(struct ssh2_packet *pkt) { struct sftp_mac *mac; HMAC_CTX *hmac_ctx; struct umac_ctx *umac_ctx; - int res; + int res, etm_mac = FALSE; /* For authenticated encryption ciphers, there is no separate MAC. */ if (sftp_cipher_get_write_auth_size() > 0) { return 0; } + etm_mac = sftp_mac_is_write_etm(); + mac = &(write_macs[write_mac_idx]); hmac_ctx = hmac_write_ctxs[write_mac_idx]; umac_ctx = umac_write_ctxs[write_mac_idx]; @@ -953,7 +1028,7 @@ int sftp_mac_write_data(struct ssh2_packet *pkt) { return 0; } - res = get_mac(pkt, mac, hmac_ctx, umac_ctx, SFTP_MAC_FL_WRITE_MAC); + res = get_mac(pkt, mac, hmac_ctx, umac_ctx, etm_mac, SFTP_MAC_FL_WRITE_MAC); if (res < 0) { return -1; } diff --git a/contrib/mod_sftp/mac.h b/contrib/mod_sftp/mac.h index 40dd92e09a..229d391ef6 100644 --- a/contrib/mod_sftp/mac.h +++ b/contrib/mod_sftp/mac.h @@ -1,6 +1,6 @@ /* * ProFTPD - mod_sftp MAC mgmt - * Copyright (c) 2008-2017 TJ Saunders + * Copyright (c) 2008-2021 TJ Saunders * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -35,18 +35,20 @@ int sftp_mac_free(void); * has been negotiated yet. */ size_t sftp_mac_get_block_size(void); -void sftp_mac_set_block_size(size_t); +void sftp_mac_set_block_size(size_t blocksz); const char *sftp_mac_get_read_algo(void); -int sftp_mac_set_read_algo(const char *); -int sftp_mac_set_read_key(pool *, const EVP_MD *, const BIGNUM *, const char *, - uint32_t, int); -int sftp_mac_read_data(struct ssh2_packet *); +int sftp_mac_is_read_etm(void); +int sftp_mac_set_read_algo(const char *algo); +int sftp_mac_set_read_key(pool *p, const EVP_MD *md, const BIGNUM *k, + const char *h, uint32_t hlen, int role); +int sftp_mac_read_data(struct ssh2_packet *pkt); const char *sftp_mac_get_write_algo(void); -int sftp_mac_set_write_algo(const char *); -int sftp_mac_set_write_key(pool *, const EVP_MD *, const BIGNUM *, const char *, - uint32_t, int); -int sftp_mac_write_data(struct ssh2_packet *); +int sftp_mac_is_write_etm(void); +int sftp_mac_set_write_algo(const char *algo); +int sftp_mac_set_write_key(pool *p, const EVP_MD *md, const BIGNUM *k, + const char *h, uint32_t hlen, int role); +int sftp_mac_write_data(struct ssh2_packet *pkt); #endif /* MOD_SFTP_MAC_H */ diff --git a/contrib/mod_sftp/packet.c b/contrib/mod_sftp/packet.c index e38ca337c4..82dc95e297 100644 --- a/contrib/mod_sftp/packet.c +++ b/contrib/mod_sftp/packet.c @@ -478,7 +478,8 @@ static void read_packet_discard(int sockfd) { } static int read_packet_len(int sockfd, struct ssh2_packet *pkt, - unsigned char *buf, size_t *offset, size_t *buflen, size_t bufsz) { + unsigned char *buf, size_t *offset, size_t *buflen, size_t bufsz, + int etm_mac) { uint32_t packet_len = 0, len = 0; size_t readsz; int res; @@ -492,10 +493,16 @@ static int read_packet_len(int sockfd, struct ssh2_packet *pkt, */ if (pkt->aad_len > 0) { - /* If we are dealing with an authenticated encryption algorithm, read - * enough to include the AAD and the first block. + /* If we are dealing with an authenticated encryption algorithm, or an + * ETM mode, read enough to include the AAD. For ETM modes, leave the + * first block for later. */ - readsz += pkt->aad_len; + if (etm_mac == TRUE) { + readsz = pkt->aad_len; + + } else { + readsz += pkt->aad_len; + } } res = sftp_ssh2_packet_sock_read(sockfd, buf, readsz, 0); @@ -547,15 +554,31 @@ static int read_packet_padding_len(int sockfd, struct ssh2_packet *pkt, } static int read_packet_payload(int sockfd, struct ssh2_packet *pkt, - unsigned char *buf, size_t *offset, size_t *buflen, size_t bufsz) { + unsigned char *buf, size_t *offset, size_t *buflen, size_t bufsz, + int etm_mac) { unsigned char *ptr = NULL; int res; - uint32_t payload_len = pkt->payload_len, padding_len = pkt->padding_len, - auth_len = 0, data_len, len = 0; + uint32_t payload_len = pkt->payload_len, padding_len = 0, auth_len = 0, + data_len, len = 0; + + /* For authenticated encryption or ETM modes, we will NOT have the + * pkt->padding_len field yet. + * + * For authenticated encryption, we need to read in the first block, then + * decrypt it, to find the padding. + * + * For ETM, we only want to find the payload and padding AFTER we've read + * the entire (encrypted) payload, MAC'd it, THEN decrypt it. + */ + + if (pkt->padding_len > 0) { + padding_len = pkt->padding_len; + } auth_len = sftp_cipher_get_read_auth_size(); - if (payload_len + padding_len + auth_len == 0) { + if (payload_len + padding_len + auth_len == 0 && + etm_mac == FALSE) { return 0; } @@ -602,14 +625,20 @@ static int read_packet_payload(int sockfd, struct ssh2_packet *pkt, } } - /* The padding length is required to be greater than zero. */ - pkt->padding = pcalloc(pkt->pool, padding_len); + /* The padding length is required to be greater than zero. However, we may + * not know the padding length yet, as for authenticated encryption or ETM + * modes. + */ + if (padding_len > 0) { + pkt->padding = pcalloc(pkt->pool, padding_len); + } /* If there's data in the buffer we received, it's probably already part * of the padding, unencrypted. That will leave the remaining padding * data, if any, to be read in and decrypted. */ - if (*buflen > 0) { + if (*buflen > 0 && + padding_len > 0) { if (*buflen < padding_len) { memmove(pkt->padding, buf + *offset, *buflen); @@ -627,7 +656,13 @@ static int read_packet_payload(int sockfd, struct ssh2_packet *pkt, } } - data_len = payload_len + padding_len + auth_len; + if (etm_mac == TRUE) { + data_len = pkt->packet_len; + + } else { + data_len = payload_len + padding_len + auth_len; + } + if (data_len == 0) { return 0; } @@ -644,19 +679,30 @@ static int read_packet_payload(int sockfd, struct ssh2_packet *pkt, if (res < 0) { return res; } - + len = res; - if (sftp_cipher_read_data(pkt, buf + *offset, data_len, &ptr, &len) < 0) { - return -1; - } - if (payload_len > 0) { - memmove(pkt->payload + (pkt->payload_len - payload_len), ptr, - payload_len); + /* For ETM modes, we do NOT want to decrypt the data yet; we need to read/ + * compare MACs first. + */ + + if (etm_mac == TRUE) { + *buflen = res; + + } else { + if (sftp_cipher_read_data(pkt, buf + *offset, data_len, &ptr, &len) < 0) { + return -1; + } + + if (payload_len > 0) { + memmove(pkt->payload + (pkt->payload_len - payload_len), ptr, + payload_len); + } + + memmove(pkt->padding + (pkt->padding_len - padding_len), ptr + payload_len, + padding_len); } - memmove(pkt->padding + (pkt->padding_len - padding_len), ptr + payload_len, - padding_len); return 0; } @@ -674,7 +720,7 @@ static int read_packet_mac(int sockfd, struct ssh2_packet *pkt, return res; } - pkt->mac = palloc(pkt->pool, pkt->mac_len); + pkt->mac = pcalloc(pkt->pool, pkt->mac_len); memmove(pkt->mac, buf, res); return 0; @@ -857,6 +903,7 @@ int sftp_ssh2_packet_set_client_alive(unsigned int max, unsigned int interval) { int sftp_ssh2_packet_read(int sockfd, struct ssh2_packet *pkt) { unsigned char buf[SFTP_MAX_PACKET_LEN]; size_t buflen, bufsz = SFTP_MAX_PACKET_LEN, offset = 0, auth_len = 0; + int etm_mac = FALSE; pr_session_set_idle(); @@ -868,6 +915,14 @@ int sftp_ssh2_packet_read(int sockfd, struct ssh2_packet *pkt) { pkt->aad_len = sizeof(uint32_t); } + etm_mac = sftp_mac_is_read_etm(); + if (etm_mac == TRUE) { + /* ETM modes do not encrypt the packet length, and instead use it as + * Additional Authenticated Data (AAD). + */ + pkt->aad_len = sizeof(uint32_t); + } + while (TRUE) { uint32_t encrypted_datasz, req_blocksz; int res; @@ -881,7 +936,8 @@ int sftp_ssh2_packet_read(int sockfd, struct ssh2_packet *pkt) { buflen = 0; memset(buf, 0, sizeof(buf)); - if (read_packet_len(sockfd, pkt, buf, &offset, &buflen, bufsz) < 0) { + if (read_packet_len(sockfd, pkt, buf, &offset, &buflen, bufsz, + etm_mac) < 0) { (void) pr_log_writefile(sftp_logfd, MOD_SFTP_VERSION, "no data to be read from socket %d", sockfd); return -1; @@ -904,18 +960,20 @@ int sftp_ssh2_packet_read(int sockfd, struct ssh2_packet *pkt) { * Thus that particular check is omitted. */ - if (read_packet_padding_len(sockfd, pkt, buf, &offset, &buflen, - bufsz) < 0) { - (void) pr_log_writefile(sftp_logfd, MOD_SFTP_VERSION, - "no data to be read from socket %d", sockfd); - read_packet_discard(sockfd); - return -1; - } + if (etm_mac == FALSE) { + if (read_packet_padding_len(sockfd, pkt, buf, &offset, &buflen, + bufsz) < 0) { + (void) pr_log_writefile(sftp_logfd, MOD_SFTP_VERSION, + "no data to be read from socket %d", sockfd); + read_packet_discard(sockfd); + return -1; + } - pr_trace_msg(trace_channel, 20, "SSH2 packet padding len = %u bytes", - (unsigned int) pkt->padding_len); + pr_trace_msg(trace_channel, 20, "SSH2 packet padding len = %u bytes", + (unsigned int) pkt->padding_len); - pkt->payload_len = (pkt->packet_len - pkt->padding_len - 1); + pkt->payload_len = (pkt->packet_len - pkt->padding_len - 1); + } pr_trace_msg(trace_channel, 20, "SSH2 packet payload len = %lu bytes", (unsigned long) pkt->payload_len); @@ -923,38 +981,108 @@ int sftp_ssh2_packet_read(int sockfd, struct ssh2_packet *pkt) { /* Read both payload and padding, since we may need to have both before * decrypting the data. */ - if (read_packet_payload(sockfd, pkt, buf, &offset, &buflen, bufsz) < 0) { + if (read_packet_payload(sockfd, pkt, buf, &offset, &buflen, bufsz, + etm_mac) < 0) { (void) pr_log_writefile(sftp_logfd, MOD_SFTP_VERSION, "unable to read payload from socket %d", sockfd); read_packet_discard(sockfd); return -1; } - memset(buf, 0, sizeof(buf)); pkt->mac_len = sftp_mac_get_block_size(); - pr_trace_msg(trace_channel, 20, "SSH2 packet MAC len = %lu bytes", (unsigned long) pkt->mac_len); - if (read_packet_mac(sockfd, pkt, buf) < 0) { - (void) pr_log_writefile(sftp_logfd, MOD_SFTP_VERSION, - "unable to read MAC from socket %d", sockfd); - read_packet_discard(sockfd); - return -1; - } + if (etm_mac == TRUE) { + unsigned char *buf2; + size_t buflen2, bufsz2; - pkt->seqno = packet_client_seqno; + bufsz2 = buflen2 = pkt->mac_len; + buf2 = pcalloc(pkt->pool, bufsz2); - if (sftp_mac_read_data(pkt) < 0) { - (void) pr_log_writefile(sftp_logfd, MOD_SFTP_VERSION, - "unable to verify MAC on packet from socket %d", sockfd); + /* The MAC routines assume the presence of the necessary data in + * pkt->payload, so we temporarily put our encrypted packet data there. + */ + pkt->payload = buf; + pkt->payload_len = buflen; + + pkt->seqno = packet_client_seqno; + + if (read_packet_mac(sockfd, pkt, buf2) < 0) { + (void) pr_log_writefile(sftp_logfd, MOD_SFTP_VERSION, + "unable to read MAC from socket %d", sockfd); + read_packet_discard(sockfd); + return -1; + } - /* In order to further mitigate CPNI-957037, we will read in a - * random amount of more data from the network before closing - * the connection. + if (sftp_mac_read_data(pkt) < 0) { + (void) pr_log_writefile(sftp_logfd, MOD_SFTP_VERSION, + "unable to verify MAC on packet from socket %d", sockfd); + + /* In order to further mitigate CPNI-957037, we will read in a + * random amount of more data from the network before closing + * the connection. + */ + read_packet_discard(sockfd); + return -1; + } + + /* Now we can decrypt the payload; `buf/buflen` are the encrypted + * packet from read_packet_payload(). */ - read_packet_discard(sockfd); - return -1; + bufsz2 = buflen2 = SFTP_MAX_PACKET_LEN; + buf2 = pcalloc(pkt->pool, bufsz2); + + if (sftp_cipher_read_data(pkt, buf, buflen, &buf2, + (uint32_t *) &buflen2) < 0) { + return -1; + } + + offset = 0; + + if (read_packet_padding_len(sockfd, pkt, buf2, &offset, &buflen2, + bufsz2) < 0) { + (void) pr_log_writefile(sftp_logfd, MOD_SFTP_VERSION, + "no data to be read from socket %d", sockfd); + read_packet_discard(sockfd); + return -1; + } + + pr_trace_msg(trace_channel, 20, "SSH2 packet padding len = %u bytes", + (unsigned int) pkt->padding_len); + + pkt->payload_len = (pkt->packet_len - pkt->padding_len - 1); + if (pkt->payload_len > 0) { + pkt->payload = pcalloc(pkt->pool, pkt->payload_len); + memmove(pkt->payload, buf2 + offset, pkt->payload_len); + } + + pkt->padding = pcalloc(pkt->pool, pkt->padding_len); + memmove(pkt->padding, buf2 + offset + pkt->payload_len, pkt->padding_len); + + } else { + memset(buf, 0, sizeof(buf)); + + if (read_packet_mac(sockfd, pkt, buf) < 0) { + (void) pr_log_writefile(sftp_logfd, MOD_SFTP_VERSION, + "unable to read MAC from socket %d", sockfd); + read_packet_discard(sockfd); + return -1; + } + + pkt->seqno = packet_client_seqno; + + if (sftp_mac_read_data(pkt) < 0) { + (void) pr_log_writefile(sftp_logfd, MOD_SFTP_VERSION, + "unable to verify MAC on packet from socket %d", sockfd); + + /* In order to further mitigate CPNI-957037, we will read in a + * random amount of more data from the network before closing + * the connection. + */ + read_packet_discard(sockfd); + return -1; + } } /* Now that the MAC check has passed, we can do sanity checks based @@ -1145,7 +1273,7 @@ int sftp_ssh2_packet_send(int sockfd, struct ssh2_packet *pkt) { unsigned char buf[SFTP_MAX_PACKET_LEN * 2], msg_type; size_t buflen = 0, bufsz = SFTP_MAX_PACKET_LEN; uint32_t packet_len = 0, auth_len = 0; - int res, write_len = 0, block_alarms = FALSE; + int res, write_len = 0, block_alarms = FALSE, etm_mac = FALSE; /* No interruptions, please. If, for example, we are interrupted here * by the SFTPRekey timer, that timer will cause this same function to @@ -1167,6 +1295,16 @@ int sftp_ssh2_packet_send(int sockfd, struct ssh2_packet *pkt) { * and instead use it as Additional Authenticated Data (AAD). */ pkt->aad_len = sizeof(uint32_t); + pkt->aad = NULL; + } + + etm_mac = sftp_mac_is_write_etm(); + if (etm_mac == TRUE) { + /* Encrypt-Then-Mac modes do not encrypt the packet length; treat it + * as Additional Authenticated Data (AAD). + */ + pkt->aad_len = sizeof(uint32_t); + pkt->aad = NULL; } /* Clear the iovec array before sending the data, if possible. */ @@ -1182,6 +1320,7 @@ int sftp_ssh2_packet_send(int sockfd, struct ssh2_packet *pkt) { if (block_alarms == TRUE) { pr_alarms_unblock(); } + errno = xerrno; return -1; } @@ -1192,6 +1331,7 @@ int sftp_ssh2_packet_send(int sockfd, struct ssh2_packet *pkt) { if (block_alarms == TRUE) { pr_alarms_unblock(); } + errno = xerrno; return -1; } @@ -1202,27 +1342,60 @@ int sftp_ssh2_packet_send(int sockfd, struct ssh2_packet *pkt) { pkt->seqno = packet_server_seqno; - if (sftp_mac_write_data(pkt) < 0) { - int xerrno = errno; + memset(buf, 0, sizeof(buf)); + buflen = bufsz; - if (block_alarms == TRUE) { - pr_alarms_unblock(); + if (etm_mac == TRUE) { + if (sftp_cipher_write_data(pkt, buf, &buflen) < 0) { + int xerrno = errno; + + if (block_alarms == TRUE) { + pr_alarms_unblock(); + } + + errno = xerrno; + return -1; } - errno = xerrno; - return -1; - } - memset(buf, 0, sizeof(buf)); - buflen = bufsz; + /* Once we have the encrypted data, overwrite the plaintext packet payload + * with it, so that the MAC is calculated from the encrypted data. + */ + pkt->payload = buf; + pkt->payload_len = buflen; - if (sftp_cipher_write_data(pkt, buf, &buflen) < 0) { - int xerrno = errno; + if (sftp_mac_write_data(pkt) < 0) { + int xerrno = errno; - if (block_alarms == TRUE) { - pr_alarms_unblock(); + if (block_alarms == TRUE) { + pr_alarms_unblock(); + } + + errno = xerrno; + return -1; + } + + } else { + if (sftp_mac_write_data(pkt) < 0) { + int xerrno = errno; + + if (block_alarms == TRUE) { + pr_alarms_unblock(); + } + + errno = xerrno; + return -1; + } + + if (sftp_cipher_write_data(pkt, buf, &buflen) < 0) { + int xerrno = errno; + + if (block_alarms == TRUE) { + pr_alarms_unblock(); + } + + errno = xerrno; + return -1; } - errno = xerrno; - return -1; } if (buflen > 0) { @@ -1313,6 +1486,7 @@ int sftp_ssh2_packet_send(int sockfd, struct ssh2_packet *pkt) { if (block_alarms == TRUE) { pr_alarms_unblock(); } + errno = xerrno; return -1; } @@ -1362,6 +1536,7 @@ int sftp_ssh2_packet_send(int sockfd, struct ssh2_packet *pkt) { if (block_alarms == TRUE) { pr_alarms_unblock(); } + errno = xerrno; return -1; } @@ -1784,15 +1959,17 @@ int sftp_ssh2_packet_rekey_reset(void) { if (rekey_client_seqno > 0) { rekey_client_seqno = packet_client_seqno + SFTP_PACKET_CLIENT_REKEY_SEQNO_LIMIT; - if (rekey_client_seqno == 0) + if (rekey_client_seqno == 0) { rekey_client_seqno++; + } } if (rekey_server_seqno > 0) { rekey_server_seqno = packet_client_seqno + SFTP_PACKET_SERVER_REKEY_SEQNO_LIMIT; - if (rekey_server_seqno == 0) + if (rekey_server_seqno == 0) { rekey_server_seqno++; + } } return 0; diff --git a/doc/contrib/mod_sftp.html b/doc/contrib/mod_sftp.html index ca8d5cfe7d..a33ef8ae03 100644 --- a/doc/contrib/mod_sftp.html +++ b/doc/contrib/mod_sftp.html @@ -611,11 +611,17 @@

SFTPDigests

of supported MAC algorithms is: By default, all of the above MAC algorithms are presented to the client, in the above order, during the key exchange. Note that some algorithms @@ -628,6 +634,7 @@

SFTPDigests

configured via SFTPDigests to be available for use by clients: @@ -646,14 +653,20 @@

SFTPDigests

by default are: Thus if you wanted to configure mod_sftp to present the same MAC algorithms as OpenSSH, you would use: diff --git a/tests/t/lib/ProFTPD/Tests/Modules/mod_sftp.pm b/tests/t/lib/ProFTPD/Tests/Modules/mod_sftp.pm index 452f824d7c..5e1da7e0f2 100644 --- a/tests/t/lib/ProFTPD/Tests/Modules/mod_sftp.pm +++ b/tests/t/lib/ProFTPD/Tests/Modules/mod_sftp.pm @@ -332,6 +332,36 @@ my $TESTS = { test_class => [qw(bug forking ssh2)], }, + ssh2_ext_mac_hmac_md5_etm_openssh => { + order => ++$order, + test_class => [qw(forking ssh2)], + }, + + ssh2_ext_mac_hmac_sha1_etm_openssh => { + order => ++$order, + test_class => [qw(forking ssh2)], + }, + + ssh2_ext_mac_hmac_sha256_etm_openssh => { + order => ++$order, + test_class => [qw(forking ssh2)], + }, + + ssh2_ext_mac_hmac_sha512_etm_openssh => { + order => ++$order, + test_class => [qw(forking ssh2)], + }, + + ssh2_ext_mac_umac_64_etm_openssh => { + order => ++$order, + test_class => [qw(forking ssh2)], + }, + + ssh2_ext_mac_umac_128_etm_openssh => { + order => ++$order, + test_class => [qw(forking ssh2)], + }, + ssh2_compress_c2s_none => { order => ++$order, test_class => [qw(forking ssh2)], @@ -11665,6 +11695,997 @@ EOC unlink($log_file); } +sub ssh2_ext_mac_hmac_md5_etm_openssh { + my $self = shift; + my $tmpdir = $self->{tmpdir}; + my $setup = test_setup($tmpdir, 'sftp'); + + my $rsa_host_key = File::Spec->rel2abs('t/etc/modules/mod_sftp/ssh_host_rsa_key'); + my $dsa_host_key = File::Spec->rel2abs('t/etc/modules/mod_sftp/ssh_host_dsa_key'); + + my $rsa_priv_key = File::Spec->rel2abs('t/etc/modules/mod_sftp/test_rsa_key'); + my $rsa_pub_key = File::Spec->rel2abs('t/etc/modules/mod_sftp/test_rsa_key.pub'); + my $rsa_rfc4716_key = File::Spec->rel2abs('t/etc/modules/mod_sftp/authorized_rsa_keys'); + + my $authorized_keys = File::Spec->rel2abs("$tmpdir/.authorized_keys"); + unless (copy($rsa_rfc4716_key, $authorized_keys)) { + die("Can't copy $rsa_rfc4716_key to $authorized_keys: $!"); + } + + my $batch_file = File::Spec->rel2abs("$tmpdir/sftp-batch.conf"); + if (open(my $fh, "> $batch_file")) { + print $fh "ls -al\n"; + + unless (close($fh)) { + die("Can't write $batch_file: $!"); + } + + } else { + die("Can't open $batch_file: $!"); + } + + my $config = { + PidFile => $setup->{pid_file}, + ScoreboardFile => $setup->{scoreboard_file}, + SystemLog => $setup->{log_file}, + TraceLog => $setup->{log_file}, + Trace => 'DEFAULT:10 lock:0 scoreboard:0 ssh2:30 sftp:30', + + AuthUserFile => $setup->{auth_user_file}, + AuthGroupFile => $setup->{auth_group_file}, + + IfModules => { + 'mod_delay.c' => { + DelayEngine => 'off', + }, + + 'mod_sftp.c' => [ + "SFTPEngine on", + "SFTPLog $setup->{log_file}", + "SFTPHostKey $rsa_host_key", + "SFTPHostKey $dsa_host_key", + "SFTPAuthorizedUserKeys file:~/.authorized_keys", + 'SFTPDigests hmac-md5-etm@openssh.com', + ], + }, + }; + + my ($port, $config_user, $config_group) = config_write($setup->{config_file}, + $config); + + # Open pipes, for use between the parent and child processes. Specifically, + # the child will indicate when it's done with its test by writing a message + # to the parent. + my ($rfh, $wfh); + unless (pipe($rfh, $wfh)) { + die("Can't open pipe: $!"); + } + + my $ex; + + # Fork child + $self->handle_sigchld(); + defined(my $pid = fork()) or die("Can't fork: $!"); + if ($pid) { + eval { + # Allow server startup + sleep(1); + + my $sftp = '/Users/tj/local/openssh-7.9p1/bin/sftp'; + + my @cmd = ( + $sftp, + '-oBatchMode=yes', + '-oCheckHostIP=no', + '-oCompression=yes', + '-oMACs=hmac-md5-etm@openssh.com', + "-oPort=$port", + "-oIdentityFile=$rsa_priv_key", + '-oPubkeyAuthentication=yes', + '-oStrictHostKeyChecking=no', + '-vvv', + '-b', + $batch_file, + "$setup->{user}\@127.0.0.1", + ); + + my $sftp_rh = IO::Handle->new(); + my $sftp_wh = IO::Handle->new(); + my $sftp_eh = IO::Handle->new(); + + $sftp_wh->autoflush(1); + + local $SIG{CHLD} = 'DEFAULT'; + + # Make sure that the perms on the priv key are what OpenSSH wants + unless (chmod(0400, $rsa_priv_key)) { + die("Can't set perms on $rsa_priv_key to 0400: $!"); + } + + if ($ENV{TEST_VERBOSE}) { + print STDERR "Executing: ", join(' ', @cmd), "\n"; + } + + my $sftp_pid = open3($sftp_wh, $sftp_rh, $sftp_eh, @cmd); + waitpid($sftp_pid, 0); + my $exit_status = $?; + + # Restore the perms on the priv key + unless (chmod(0644, $rsa_priv_key)) { + die("Can't set perms on $rsa_priv_key to 0644: $!"); + } + + my ($res, $errstr); + if ($exit_status >> 8 == 0) { + $errstr = join('', <$sftp_eh>); + if ($ENV{TEST_VERBOSE}) { + print STDERR "Stderr: $errstr\n"; + } + + $res = 0; + + } else { + $errstr = join('', <$sftp_eh>); + if ($ENV{TEST_VERBOSE}) { + print STDERR "Stderr: $errstr\n"; + } + + $res = 1; + } + + unless ($res == 0) { + die("Can't list files on server: $errstr"); + } + }; + if ($@) { + $ex = $@; + } + + $wfh->print("done\n"); + $wfh->flush(); + + } else { + eval { server_wait($setup->{config_file}, $rfh) }; + if ($@) { + warn($@); + exit 1; + } + + exit 0; + } + + # Stop server + server_stop($setup->{pid_file}); + $self->assert_child_ok($pid); + + test_cleanup($setup->{log_file}, $ex); +} + +sub ssh2_ext_mac_hmac_sha1_etm_openssh { + my $self = shift; + my $tmpdir = $self->{tmpdir}; + my $setup = test_setup($tmpdir, 'sftp'); + + my $rsa_host_key = File::Spec->rel2abs('t/etc/modules/mod_sftp/ssh_host_rsa_key'); + my $dsa_host_key = File::Spec->rel2abs('t/etc/modules/mod_sftp/ssh_host_dsa_key'); + + my $rsa_priv_key = File::Spec->rel2abs('t/etc/modules/mod_sftp/test_rsa_key'); + my $rsa_pub_key = File::Spec->rel2abs('t/etc/modules/mod_sftp/test_rsa_key.pub'); + my $rsa_rfc4716_key = File::Spec->rel2abs('t/etc/modules/mod_sftp/authorized_rsa_keys'); + + my $authorized_keys = File::Spec->rel2abs("$tmpdir/.authorized_keys"); + unless (copy($rsa_rfc4716_key, $authorized_keys)) { + die("Can't copy $rsa_rfc4716_key to $authorized_keys: $!"); + } + + my $batch_file = File::Spec->rel2abs("$tmpdir/sftp-batch.conf"); + if (open(my $fh, "> $batch_file")) { + print $fh "ls -al\n"; + + unless (close($fh)) { + die("Can't write $batch_file: $!"); + } + + } else { + die("Can't open $batch_file: $!"); + } + + my $config = { + PidFile => $setup->{pid_file}, + ScoreboardFile => $setup->{scoreboard_file}, + SystemLog => $setup->{log_file}, + TraceLog => $setup->{log_file}, + Trace => 'DEFAULT:10 lock:0 scoreboard:0 ssh2:30 sftp:30', + + AuthUserFile => $setup->{auth_user_file}, + AuthGroupFile => $setup->{auth_group_file}, + + IfModules => { + 'mod_delay.c' => { + DelayEngine => 'off', + }, + + 'mod_sftp.c' => [ + "SFTPEngine on", + "SFTPLog $setup->{log_file}", + "SFTPHostKey $rsa_host_key", + "SFTPHostKey $dsa_host_key", + "SFTPAuthorizedUserKeys file:~/.authorized_keys", + 'SFTPDigests hmac-sha1-etm@openssh.com', + ], + }, + }; + + my ($port, $config_user, $config_group) = config_write($setup->{config_file}, + $config); + + # Open pipes, for use between the parent and child processes. Specifically, + # the child will indicate when it's done with its test by writing a message + # to the parent. + my ($rfh, $wfh); + unless (pipe($rfh, $wfh)) { + die("Can't open pipe: $!"); + } + + my $ex; + + # Fork child + $self->handle_sigchld(); + defined(my $pid = fork()) or die("Can't fork: $!"); + if ($pid) { + eval { + # Allow server startup + sleep(1); + + my $sftp = '/Users/tj/local/openssh-7.9p1/bin/sftp'; + + my @cmd = ( + $sftp, + '-oBatchMode=yes', + '-oCheckHostIP=no', + '-oCompression=yes', + "-oPort=$port", + "-oIdentityFile=$rsa_priv_key", + '-oPubkeyAuthentication=yes', + '-oStrictHostKeyChecking=no', + '-vvv', + '-b', + $batch_file, + "$setup->{user}\@127.0.0.1", + ); + + my $sftp_rh = IO::Handle->new(); + my $sftp_wh = IO::Handle->new(); + my $sftp_eh = IO::Handle->new(); + + $sftp_wh->autoflush(1); + + local $SIG{CHLD} = 'DEFAULT'; + + # Make sure that the perms on the priv key are what OpenSSH wants + unless (chmod(0400, $rsa_priv_key)) { + die("Can't set perms on $rsa_priv_key to 0400: $!"); + } + + if ($ENV{TEST_VERBOSE}) { + print STDERR "Executing: ", join(' ', @cmd), "\n"; + } + + my $sftp_pid = open3($sftp_wh, $sftp_rh, $sftp_eh, @cmd); + waitpid($sftp_pid, 0); + my $exit_status = $?; + + # Restore the perms on the priv key + unless (chmod(0644, $rsa_priv_key)) { + die("Can't set perms on $rsa_priv_key to 0644: $!"); + } + + my ($res, $errstr); + if ($exit_status >> 8 == 0) { + $errstr = join('', <$sftp_eh>); + if ($ENV{TEST_VERBOSE}) { + print STDERR "Stderr: $errstr\n"; + } + + $res = 0; + + } else { + $errstr = join('', <$sftp_eh>); + if ($ENV{TEST_VERBOSE}) { + print STDERR "Stderr: $errstr\n"; + } + + $res = 1; + } + + unless ($res == 0) { + die("Can't list files on server: $errstr"); + } + }; + if ($@) { + $ex = $@; + } + + $wfh->print("done\n"); + $wfh->flush(); + + } else { + eval { server_wait($setup->{config_file}, $rfh) }; + if ($@) { + warn($@); + exit 1; + } + + exit 0; + } + + # Stop server + server_stop($setup->{pid_file}); + $self->assert_child_ok($pid); + + test_cleanup($setup->{log_file}, $ex); +} + +sub ssh2_ext_mac_hmac_sha256_etm_openssh { + my $self = shift; + my $tmpdir = $self->{tmpdir}; + my $setup = test_setup($tmpdir, 'sftp'); + + my $rsa_host_key = File::Spec->rel2abs('t/etc/modules/mod_sftp/ssh_host_rsa_key'); + my $dsa_host_key = File::Spec->rel2abs('t/etc/modules/mod_sftp/ssh_host_dsa_key'); + + my $rsa_priv_key = File::Spec->rel2abs('t/etc/modules/mod_sftp/test_rsa_key'); + my $rsa_pub_key = File::Spec->rel2abs('t/etc/modules/mod_sftp/test_rsa_key.pub'); + my $rsa_rfc4716_key = File::Spec->rel2abs('t/etc/modules/mod_sftp/authorized_rsa_keys'); + + my $authorized_keys = File::Spec->rel2abs("$tmpdir/.authorized_keys"); + unless (copy($rsa_rfc4716_key, $authorized_keys)) { + die("Can't copy $rsa_rfc4716_key to $authorized_keys: $!"); + } + + my $batch_file = File::Spec->rel2abs("$tmpdir/sftp-batch.conf"); + if (open(my $fh, "> $batch_file")) { + print $fh "ls -al\n"; + + unless (close($fh)) { + die("Can't write $batch_file: $!"); + } + + } else { + die("Can't open $batch_file: $!"); + } + + my $config = { + PidFile => $setup->{pid_file}, + ScoreboardFile => $setup->{scoreboard_file}, + SystemLog => $setup->{log_file}, + TraceLog => $setup->{log_file}, + Trace => 'DEFAULT:10 lock:0 scoreboard:0 ssh2:30 sftp:30', + + AuthUserFile => $setup->{auth_user_file}, + AuthGroupFile => $setup->{auth_group_file}, + + IfModules => { + 'mod_delay.c' => { + DelayEngine => 'off', + }, + + 'mod_sftp.c' => [ + "SFTPEngine on", + "SFTPLog $setup->{log_file}", + "SFTPHostKey $rsa_host_key", + "SFTPHostKey $dsa_host_key", + "SFTPAuthorizedUserKeys file:~/.authorized_keys", + 'SFTPDigests hmac-sha2-256-etm@openssh.com', + ], + }, + }; + + my ($port, $config_user, $config_group) = config_write($setup->{config_file}, + $config); + + # Open pipes, for use between the parent and child processes. Specifically, + # the child will indicate when it's done with its test by writing a message + # to the parent. + my ($rfh, $wfh); + unless (pipe($rfh, $wfh)) { + die("Can't open pipe: $!"); + } + + my $ex; + + # Fork child + $self->handle_sigchld(); + defined(my $pid = fork()) or die("Can't fork: $!"); + if ($pid) { + eval { + # Allow server startup + sleep(1); + + my $sftp = '/Users/tj/local/openssh-7.9p1/bin/sftp'; + + my @cmd = ( + $sftp, + '-oBatchMode=yes', + '-oCheckHostIP=no', + '-oCompression=yes', + "-oPort=$port", + "-oIdentityFile=$rsa_priv_key", + '-oPubkeyAuthentication=yes', + '-oStrictHostKeyChecking=no', + '-vvv', + '-b', + $batch_file, + "$setup->{user}\@127.0.0.1", + ); + + my $sftp_rh = IO::Handle->new(); + my $sftp_wh = IO::Handle->new(); + my $sftp_eh = IO::Handle->new(); + + $sftp_wh->autoflush(1); + + local $SIG{CHLD} = 'DEFAULT'; + + # Make sure that the perms on the priv key are what OpenSSH wants + unless (chmod(0400, $rsa_priv_key)) { + die("Can't set perms on $rsa_priv_key to 0400: $!"); + } + + if ($ENV{TEST_VERBOSE}) { + print STDERR "Executing: ", join(' ', @cmd), "\n"; + } + + my $sftp_pid = open3($sftp_wh, $sftp_rh, $sftp_eh, @cmd); + waitpid($sftp_pid, 0); + my $exit_status = $?; + + # Restore the perms on the priv key + unless (chmod(0644, $rsa_priv_key)) { + die("Can't set perms on $rsa_priv_key to 0644: $!"); + } + + my ($res, $errstr); + if ($exit_status >> 8 == 0) { + $errstr = join('', <$sftp_eh>); + if ($ENV{TEST_VERBOSE}) { + print STDERR "Stderr: $errstr\n"; + } + + $res = 0; + + } else { + $errstr = join('', <$sftp_eh>); + if ($ENV{TEST_VERBOSE}) { + print STDERR "Stderr: $errstr\n"; + } + + $res = 1; + } + + unless ($res == 0) { + die("Can't list files on server: $errstr"); + } + }; + if ($@) { + $ex = $@; + } + + $wfh->print("done\n"); + $wfh->flush(); + + } else { + eval { server_wait($setup->{config_file}, $rfh) }; + if ($@) { + warn($@); + exit 1; + } + + exit 0; + } + + # Stop server + server_stop($setup->{pid_file}); + $self->assert_child_ok($pid); + + test_cleanup($setup->{log_file}, $ex); +} + +sub ssh2_ext_mac_hmac_sha512_etm_openssh { + my $self = shift; + my $tmpdir = $self->{tmpdir}; + my $setup = test_setup($tmpdir, 'sftp'); + + my $rsa_host_key = File::Spec->rel2abs('t/etc/modules/mod_sftp/ssh_host_rsa_key'); + my $dsa_host_key = File::Spec->rel2abs('t/etc/modules/mod_sftp/ssh_host_dsa_key'); + + my $rsa_priv_key = File::Spec->rel2abs('t/etc/modules/mod_sftp/test_rsa_key'); + my $rsa_pub_key = File::Spec->rel2abs('t/etc/modules/mod_sftp/test_rsa_key.pub'); + my $rsa_rfc4716_key = File::Spec->rel2abs('t/etc/modules/mod_sftp/authorized_rsa_keys'); + + my $authorized_keys = File::Spec->rel2abs("$tmpdir/.authorized_keys"); + unless (copy($rsa_rfc4716_key, $authorized_keys)) { + die("Can't copy $rsa_rfc4716_key to $authorized_keys: $!"); + } + + my $batch_file = File::Spec->rel2abs("$tmpdir/sftp-batch.conf"); + if (open(my $fh, "> $batch_file")) { + print $fh "ls -al\n"; + + unless (close($fh)) { + die("Can't write $batch_file: $!"); + } + + } else { + die("Can't open $batch_file: $!"); + } + + my $config = { + PidFile => $setup->{pid_file}, + ScoreboardFile => $setup->{scoreboard_file}, + SystemLog => $setup->{log_file}, + TraceLog => $setup->{log_file}, + Trace => 'DEFAULT:10 lock:0 scoreboard:0 ssh2:30 sftp:30', + + AuthUserFile => $setup->{auth_user_file}, + AuthGroupFile => $setup->{auth_group_file}, + + IfModules => { + 'mod_delay.c' => { + DelayEngine => 'off', + }, + + 'mod_sftp.c' => [ + "SFTPEngine on", + "SFTPLog $setup->{log_file}", + "SFTPHostKey $rsa_host_key", + "SFTPHostKey $dsa_host_key", + "SFTPAuthorizedUserKeys file:~/.authorized_keys", + 'SFTPDigests hmac-sha2-512-etm@openssh.com', + ], + }, + }; + + my ($port, $config_user, $config_group) = config_write($setup->{config_file}, + $config); + + # Open pipes, for use between the parent and child processes. Specifically, + # the child will indicate when it's done with its test by writing a message + # to the parent. + my ($rfh, $wfh); + unless (pipe($rfh, $wfh)) { + die("Can't open pipe: $!"); + } + + my $ex; + + # Fork child + $self->handle_sigchld(); + defined(my $pid = fork()) or die("Can't fork: $!"); + if ($pid) { + eval { + # Allow server startup + sleep(1); + + my $sftp = '/Users/tj/local/openssh-7.9p1/bin/sftp'; + + my @cmd = ( + $sftp, + '-oBatchMode=yes', + '-oCheckHostIP=no', + '-oCompression=yes', + "-oPort=$port", + "-oIdentityFile=$rsa_priv_key", + '-oPubkeyAuthentication=yes', + '-oStrictHostKeyChecking=no', + '-vvv', + '-b', + $batch_file, + "$setup->{user}\@127.0.0.1", + ); + + my $sftp_rh = IO::Handle->new(); + my $sftp_wh = IO::Handle->new(); + my $sftp_eh = IO::Handle->new(); + + $sftp_wh->autoflush(1); + + local $SIG{CHLD} = 'DEFAULT'; + + # Make sure that the perms on the priv key are what OpenSSH wants + unless (chmod(0400, $rsa_priv_key)) { + die("Can't set perms on $rsa_priv_key to 0400: $!"); + } + + if ($ENV{TEST_VERBOSE}) { + print STDERR "Executing: ", join(' ', @cmd), "\n"; + } + + my $sftp_pid = open3($sftp_wh, $sftp_rh, $sftp_eh, @cmd); + waitpid($sftp_pid, 0); + my $exit_status = $?; + + # Restore the perms on the priv key + unless (chmod(0644, $rsa_priv_key)) { + die("Can't set perms on $rsa_priv_key to 0644: $!"); + } + + my ($res, $errstr); + if ($exit_status >> 8 == 0) { + $errstr = join('', <$sftp_eh>); + if ($ENV{TEST_VERBOSE}) { + print STDERR "Stderr: $errstr\n"; + } + + $res = 0; + + } else { + $errstr = join('', <$sftp_eh>); + if ($ENV{TEST_VERBOSE}) { + print STDERR "Stderr: $errstr\n"; + } + + $res = 1; + } + + unless ($res == 0) { + die("Can't list files on server: $errstr"); + } + }; + if ($@) { + $ex = $@; + } + + $wfh->print("done\n"); + $wfh->flush(); + + } else { + eval { server_wait($setup->{config_file}, $rfh) }; + if ($@) { + warn($@); + exit 1; + } + + exit 0; + } + + # Stop server + server_stop($setup->{pid_file}); + $self->assert_child_ok($pid); + + test_cleanup($setup->{log_file}, $ex); +} + +sub ssh2_ext_mac_umac_64_etm_openssh { + my $self = shift; + my $tmpdir = $self->{tmpdir}; + my $setup = test_setup($tmpdir, 'sftp'); + + my $rsa_host_key = File::Spec->rel2abs('t/etc/modules/mod_sftp/ssh_host_rsa_key'); + my $dsa_host_key = File::Spec->rel2abs('t/etc/modules/mod_sftp/ssh_host_dsa_key'); + + my $rsa_priv_key = File::Spec->rel2abs('t/etc/modules/mod_sftp/test_rsa_key'); + my $rsa_pub_key = File::Spec->rel2abs('t/etc/modules/mod_sftp/test_rsa_key.pub'); + my $rsa_rfc4716_key = File::Spec->rel2abs('t/etc/modules/mod_sftp/authorized_rsa_keys'); + + my $authorized_keys = File::Spec->rel2abs("$tmpdir/.authorized_keys"); + unless (copy($rsa_rfc4716_key, $authorized_keys)) { + die("Can't copy $rsa_rfc4716_key to $authorized_keys: $!"); + } + + my $batch_file = File::Spec->rel2abs("$tmpdir/sftp-batch.conf"); + if (open(my $fh, "> $batch_file")) { + print $fh "ls -al\n"; + + unless (close($fh)) { + die("Can't write $batch_file: $!"); + } + + } else { + die("Can't open $batch_file: $!"); + } + + my $config = { + PidFile => $setup->{pid_file}, + ScoreboardFile => $setup->{scoreboard_file}, + SystemLog => $setup->{log_file}, + TraceLog => $setup->{log_file}, + Trace => 'DEFAULT:10 lock:0 scoreboard:0 ssh2:30 sftp:30', + + AuthUserFile => $setup->{auth_user_file}, + AuthGroupFile => $setup->{auth_group_file}, + + IfModules => { + 'mod_delay.c' => { + DelayEngine => 'off', + }, + + 'mod_sftp.c' => [ + "SFTPEngine on", + "SFTPLog $setup->{log_file}", + "SFTPHostKey $rsa_host_key", + "SFTPHostKey $dsa_host_key", + "SFTPAuthorizedUserKeys file:~/.authorized_keys", + 'SFTPDigests umac-64-etm@openssh.com', + ], + }, + }; + + my ($port, $config_user, $config_group) = config_write($setup->{config_file}, + $config); + + # Open pipes, for use between the parent and child processes. Specifically, + # the child will indicate when it's done with its test by writing a message + # to the parent. + my ($rfh, $wfh); + unless (pipe($rfh, $wfh)) { + die("Can't open pipe: $!"); + } + + my $ex; + + # Fork child + $self->handle_sigchld(); + defined(my $pid = fork()) or die("Can't fork: $!"); + if ($pid) { + eval { + # Allow server startup + sleep(1); + + my $sftp = '/Users/tj/local/openssh-7.9p1/bin/sftp'; + + my @cmd = ( + $sftp, + '-oBatchMode=yes', + '-oCheckHostIP=no', + '-oCompression=yes', + "-oPort=$port", + "-oIdentityFile=$rsa_priv_key", + '-oPubkeyAuthentication=yes', + '-oStrictHostKeyChecking=no', + '-vvv', + '-b', + $batch_file, + "$setup->{user}\@127.0.0.1", + ); + + my $sftp_rh = IO::Handle->new(); + my $sftp_wh = IO::Handle->new(); + my $sftp_eh = IO::Handle->new(); + + $sftp_wh->autoflush(1); + + local $SIG{CHLD} = 'DEFAULT'; + + # Make sure that the perms on the priv key are what OpenSSH wants + unless (chmod(0400, $rsa_priv_key)) { + die("Can't set perms on $rsa_priv_key to 0400: $!"); + } + + if ($ENV{TEST_VERBOSE}) { + print STDERR "Executing: ", join(' ', @cmd), "\n"; + } + + my $sftp_pid = open3($sftp_wh, $sftp_rh, $sftp_eh, @cmd); + waitpid($sftp_pid, 0); + my $exit_status = $?; + + # Restore the perms on the priv key + unless (chmod(0644, $rsa_priv_key)) { + die("Can't set perms on $rsa_priv_key to 0644: $!"); + } + + my ($res, $errstr); + if ($exit_status >> 8 == 0) { + $errstr = join('', <$sftp_eh>); + if ($ENV{TEST_VERBOSE}) { + print STDERR "Stderr: $errstr\n"; + } + + $res = 0; + + } else { + $errstr = join('', <$sftp_eh>); + if ($ENV{TEST_VERBOSE}) { + print STDERR "Stderr: $errstr\n"; + } + + $res = 1; + } + + unless ($res == 0) { + die("Can't list files on server: $errstr"); + } + }; + if ($@) { + $ex = $@; + } + + $wfh->print("done\n"); + $wfh->flush(); + + } else { + eval { server_wait($setup->{config_file}, $rfh) }; + if ($@) { + warn($@); + exit 1; + } + + exit 0; + } + + # Stop server + server_stop($setup->{pid_file}); + $self->assert_child_ok($pid); + + test_cleanup($setup->{log_file}, $ex); +} + +sub ssh2_ext_mac_umac_128_etm_openssh { + my $self = shift; + my $tmpdir = $self->{tmpdir}; + my $setup = test_setup($tmpdir, 'sftp'); + + my $rsa_host_key = File::Spec->rel2abs('t/etc/modules/mod_sftp/ssh_host_rsa_key'); + my $dsa_host_key = File::Spec->rel2abs('t/etc/modules/mod_sftp/ssh_host_dsa_key'); + + my $rsa_priv_key = File::Spec->rel2abs('t/etc/modules/mod_sftp/test_rsa_key'); + my $rsa_pub_key = File::Spec->rel2abs('t/etc/modules/mod_sftp/test_rsa_key.pub'); + my $rsa_rfc4716_key = File::Spec->rel2abs('t/etc/modules/mod_sftp/authorized_rsa_keys'); + + my $authorized_keys = File::Spec->rel2abs("$tmpdir/.authorized_keys"); + unless (copy($rsa_rfc4716_key, $authorized_keys)) { + die("Can't copy $rsa_rfc4716_key to $authorized_keys: $!"); + } + + my $batch_file = File::Spec->rel2abs("$tmpdir/sftp-batch.conf"); + if (open(my $fh, "> $batch_file")) { + print $fh "ls -al\n"; + + unless (close($fh)) { + die("Can't write $batch_file: $!"); + } + + } else { + die("Can't open $batch_file: $!"); + } + + my $config = { + PidFile => $setup->{pid_file}, + ScoreboardFile => $setup->{scoreboard_file}, + SystemLog => $setup->{log_file}, + TraceLog => $setup->{log_file}, + Trace => 'DEFAULT:10 lock:0 scoreboard:0 ssh2:30 sftp:30', + + AuthUserFile => $setup->{auth_user_file}, + AuthGroupFile => $setup->{auth_group_file}, + + IfModules => { + 'mod_delay.c' => { + DelayEngine => 'off', + }, + + 'mod_sftp.c' => [ + "SFTPEngine on", + "SFTPLog $setup->{log_file}", + "SFTPHostKey $rsa_host_key", + "SFTPHostKey $dsa_host_key", + "SFTPAuthorizedUserKeys file:~/.authorized_keys", + 'SFTPDigests umac-128-etm@openssh.com', + ], + }, + }; + + my ($port, $config_user, $config_group) = config_write($setup->{config_file}, + $config); + + # Open pipes, for use between the parent and child processes. Specifically, + # the child will indicate when it's done with its test by writing a message + # to the parent. + my ($rfh, $wfh); + unless (pipe($rfh, $wfh)) { + die("Can't open pipe: $!"); + } + + my $ex; + + # Fork child + $self->handle_sigchld(); + defined(my $pid = fork()) or die("Can't fork: $!"); + if ($pid) { + eval { + # Allow server startup + sleep(1); + + my $sftp = '/Users/tj/local/openssh-7.9p1/bin/sftp'; + + my @cmd = ( + $sftp, + '-oBatchMode=yes', + '-oCheckHostIP=no', + '-oCompression=yes', + "-oPort=$port", + "-oIdentityFile=$rsa_priv_key", + '-oPubkeyAuthentication=yes', + '-oStrictHostKeyChecking=no', + '-vvv', + '-b', + $batch_file, + "$setup->{user}\@127.0.0.1", + ); + + my $sftp_rh = IO::Handle->new(); + my $sftp_wh = IO::Handle->new(); + my $sftp_eh = IO::Handle->new(); + + $sftp_wh->autoflush(1); + + local $SIG{CHLD} = 'DEFAULT'; + + # Make sure that the perms on the priv key are what OpenSSH wants + unless (chmod(0400, $rsa_priv_key)) { + die("Can't set perms on $rsa_priv_key to 0400: $!"); + } + + if ($ENV{TEST_VERBOSE}) { + print STDERR "Executing: ", join(' ', @cmd), "\n"; + } + + my $sftp_pid = open3($sftp_wh, $sftp_rh, $sftp_eh, @cmd); + waitpid($sftp_pid, 0); + my $exit_status = $?; + + # Restore the perms on the priv key + unless (chmod(0644, $rsa_priv_key)) { + die("Can't set perms on $rsa_priv_key to 0644: $!"); + } + + my ($res, $errstr); + if ($exit_status >> 8 == 0) { + $errstr = join('', <$sftp_eh>); + if ($ENV{TEST_VERBOSE}) { + print STDERR "Stderr: $errstr\n"; + } + + $res = 0; + + } else { + $errstr = join('', <$sftp_eh>); + if ($ENV{TEST_VERBOSE}) { + print STDERR "Stderr: $errstr\n"; + } + + $res = 1; + } + + unless ($res == 0) { + die("Can't list files on server: $errstr"); + } + }; + if ($@) { + $ex = $@; + } + + $wfh->print("done\n"); + $wfh->flush(); + + } else { + eval { server_wait($setup->{config_file}, $rfh) }; + if ($@) { + warn($@); + exit 1; + } + + exit 0; + } + + # Stop server + server_stop($setup->{pid_file}); + $self->assert_child_ok($pid); + + test_cleanup($setup->{log_file}, $ex); +} + sub ssh2_compress_c2s_none { my $self = shift; my $tmpdir = $self->{tmpdir};