openssh-9.5p1
-----BEGIN SSH SIGNATURE----- U1NIU0lHAAAAAQAAAH8AAAAic2stZWNkc2Etc2hhMi1uaXN0cDI1NkBvcGVuc3NoLmNvbQ AAAAhuaXN0cDI1NgAAAEEEucmjdlUMQ1hkZebm472VTtvSIMWrmAelO7Uxoc9ZMR892/D4 CMVBD+rliLO4wmRcawx1iZuUkQllgemb0hLtmQAAAARzc2g6AAAAA2dpdAAAAAAAAAAGc2 hhNTEyAAAAeAAAACJzay1lY2RzYS1zaGEyLW5pc3RwMjU2QG9wZW5zc2guY29tAAAASQAA ACEA7WcEKKcqxpjfRRhVOznHOSsf6SlAWbpkBYA01cN3nl0AAAAgIlhw5EaLbGdhj9DaVi Mtgw72SsEKJdOA52IQKECVmAQAAAAEDw== -----END SSH SIGNATURE----- resolve merge conflicts; scp and sftp fail to compile
This commit is contained in:
commit
e6fa11e07e
|
@ -6,6 +6,10 @@ master :
|
|||
[](https://bugs.chromium.org/p/oss-fuzz/issues/list?sort=-opened&can=1&q=proj:openssh)
|
||||
[](https://scan.coverity.com/projects/openssh-portable)
|
||||
|
||||
9.4 :
|
||||
[](https://github.com/openssh/openssh-portable/actions/workflows/c-cpp.yml?query=branch:V_9_4)
|
||||
[](https://github.com/openssh/openssh-portable-selfhosted/actions/workflows/selfhosted.yml?query=branch:V_9_4)
|
||||
|
||||
9.3 :
|
||||
[](https://github.com/openssh/openssh-portable/actions/workflows/c-cpp.yml?query=branch:V_9_3)
|
||||
[](https://github.com/openssh/openssh-portable-selfhosted/actions/workflows/selfhosted.yml?query=branch:V_9_3)
|
||||
|
|
|
@ -30,6 +30,13 @@ case "$config" in
|
|||
default|sol64)
|
||||
;;
|
||||
c89)
|
||||
# If we don't have LLONG_MAX, configure will figure out that it can
|
||||
# get it by setting -std=gnu99, at which point we won't be testing
|
||||
# C89 any more. To avoid this, feed it in via CFLAGS.
|
||||
llong_max=`gcc -E -dM - </dev/null | \
|
||||
awk '$2=="__LONG_LONG_MAX__"{print $3}'`
|
||||
CPPFLAGS="-DLLONG_MAX=${llong_max}"
|
||||
|
||||
CC="gcc"
|
||||
CFLAGS="-Wall -std=c89 -pedantic -Werror=vla"
|
||||
CONFIGFLAGS="--without-zlib"
|
||||
|
@ -205,6 +212,10 @@ case "$config" in
|
|||
;;
|
||||
esac
|
||||
;;
|
||||
zlib-develop)
|
||||
INSTALL_ZLIB=develop
|
||||
CONFIGFLAGS="--with-zlib=/opt/zlib --with-rpath=-Wl,-rpath,"
|
||||
;;
|
||||
*)
|
||||
echo "Unknown configuration $config"
|
||||
exit 1
|
||||
|
|
|
@ -133,6 +133,8 @@ for TARGET in $TARGETS; do
|
|||
valgrind*)
|
||||
PACKAGES="$PACKAGES valgrind"
|
||||
;;
|
||||
zlib-*)
|
||||
;;
|
||||
*) echo "Invalid option '${TARGET}'"
|
||||
exit 1
|
||||
;;
|
||||
|
@ -214,3 +216,9 @@ if [ ! -z "${INSTALL_BORINGSSL}" ]; then
|
|||
cp ${HOME}/boringssl/build/crypto/libcrypto.a /opt/boringssl/lib &&
|
||||
cp -r ${HOME}/boringssl/include /opt/boringssl)
|
||||
fi
|
||||
|
||||
if [ ! -z "${INSTALL_ZLIB}" ]; then
|
||||
(cd ${HOME} && git clone https://github.com/madler/zlib.git &&
|
||||
cd ${HOME}/zlib && ./configure && make &&
|
||||
sudo make install prefix=/opt/zlib)
|
||||
fi
|
||||
|
|
|
@ -70,6 +70,7 @@ jobs:
|
|||
- { target: ubuntu-latest, config: openssl-3.1.0 }
|
||||
- { target: ubuntu-latest, config: openssl-1.1.1_stable }
|
||||
- { target: ubuntu-latest, config: openssl-3.0 } # stable branch
|
||||
- { target: ubuntu-latest, config: zlib-develop }
|
||||
- { target: ubuntu-22.04, config: pam }
|
||||
- { target: ubuntu-22.04, config: krb5 }
|
||||
- { target: ubuntu-22.04, config: heimdal }
|
||||
|
|
|
@ -77,6 +77,7 @@ jobs:
|
|||
- { target: ARM64, config: default, host: ARM64 }
|
||||
- { target: ARM64, config: pam, host: ARM64 }
|
||||
- { target: debian-riscv64, config: default, host: debian-riscv64 }
|
||||
- { target: obsd-arm64, config: default, host: obsd-arm64 }
|
||||
- { target: openwrt-mips, config: default, host: openwrt-mips }
|
||||
- { target: openwrt-mipsel, config: default, host: openwrt-mipsel }
|
||||
steps:
|
||||
|
|
35
PROTOCOL
35
PROTOCOL
|
@ -104,6 +104,39 @@ http://git.libssh.org/users/aris/libssh.git/plain/doc/curve25519-sha256@libssh.o
|
|||
|
||||
This is identical to curve25519-sha256 as later published in RFC8731.
|
||||
|
||||
1.9 transport: ping facility
|
||||
|
||||
OpenSSH implements a transport level ping message SSH2_MSG_PING
|
||||
and a corresponding SSH2_MSG_PONG reply.
|
||||
|
||||
#define SSH2_MSG_PING 192
|
||||
#define SSH2_MSG_PONG 193
|
||||
|
||||
The ping message is simply:
|
||||
|
||||
byte SSH_MSG_PING
|
||||
string data
|
||||
|
||||
The reply copies the data (which may be the empty string) from the
|
||||
ping:
|
||||
|
||||
byte SSH_MSG_PONG
|
||||
string data
|
||||
|
||||
Replies are sent in order. They are sent immediately except when rekeying
|
||||
is in progress, in which case they are queued until rekeying completes.
|
||||
|
||||
The server advertises support for these messages using the
|
||||
SSH2_MSG_EXT_INFO mechanism (RFC8308), with the following message:
|
||||
|
||||
string "ping@openssh.com"
|
||||
string "0" (version)
|
||||
|
||||
The ping/reply message is implemented at the transport layer rather
|
||||
than as a named global or channel request to allow pings with very
|
||||
short packet lengths, which would not be possible with other
|
||||
approaches.
|
||||
|
||||
2. Connection protocol changes
|
||||
|
||||
2.1. connection: Channel write close extension "eow@openssh.com"
|
||||
|
@ -712,4 +745,4 @@ master instance and later clients.
|
|||
OpenSSH extends the usual agent protocol. These changes are documented
|
||||
in the PROTOCOL.agent file.
|
||||
|
||||
$OpenBSD: PROTOCOL,v 1.48 2022/11/07 01:53:01 dtucker Exp $
|
||||
$OpenBSD: PROTOCOL,v 1.49 2023/08/28 03:28:43 djm Exp $
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
The SSH agent protocol is described in
|
||||
https://tools.ietf.org/html/draft-miller-ssh-agent-04
|
||||
https://tools.ietf.org/html/draft-miller-ssh-agent
|
||||
|
||||
This file documents OpenSSH's extensions to the agent protocol.
|
||||
|
||||
|
@ -81,4 +81,4 @@ the constraint is:
|
|||
|
||||
This option is only valid for XMSS keys.
|
||||
|
||||
$OpenBSD: PROTOCOL.agent,v 1.19 2023/04/12 08:53:54 jsg Exp $
|
||||
$OpenBSD: PROTOCOL.agent,v 1.20 2023/10/03 23:56:10 djm Exp $
|
||||
|
|
2
README
2
README
|
@ -1,4 +1,4 @@
|
|||
See https://www.openssh.com/releasenotes.html#9.4p1 for the release
|
||||
See https://www.openssh.com/releasenotes.html#9.5p1 for the release
|
||||
notes.
|
||||
|
||||
Please read https://www.openssh.com/report.html for bug reporting
|
||||
|
|
11
auth2.c
11
auth2.c
|
@ -1,4 +1,4 @@
|
|||
/* $OpenBSD: auth2.c,v 1.166 2023/03/08 04:43:12 guenther Exp $ */
|
||||
/* $OpenBSD: auth2.c,v 1.167 2023/08/28 09:48:11 djm Exp $ */
|
||||
/*
|
||||
* Copyright (c) 2000 Markus Friedl. All rights reserved.
|
||||
*
|
||||
|
@ -221,6 +221,7 @@ input_service_request(int type, u_int32_t seq, struct ssh *ssh)
|
|||
}
|
||||
|
||||
#define MIN_FAIL_DELAY_SECONDS 0.005
|
||||
#define MAX_FAIL_DELAY_SECONDS 5.0
|
||||
static double
|
||||
user_specific_delay(const char *user)
|
||||
{
|
||||
|
@ -246,6 +247,12 @@ ensure_minimum_time_since(double start, double seconds)
|
|||
struct timespec ts;
|
||||
double elapsed = monotime_double() - start, req = seconds, remain;
|
||||
|
||||
if (elapsed > MAX_FAIL_DELAY_SECONDS) {
|
||||
debug3_f("elapsed %0.3lfms exceeded the max delay "
|
||||
"requested %0.3lfms)", elapsed*1000, req*1000);
|
||||
return;
|
||||
}
|
||||
|
||||
/* if we've already passed the requested time, scale up */
|
||||
while ((remain = seconds - elapsed) < 0.0)
|
||||
seconds *= 2;
|
||||
|
@ -337,7 +344,7 @@ input_userauth_request(int type, u_int32_t seq, struct ssh *ssh)
|
|||
debug2("input_userauth_request: try method %s", method);
|
||||
authenticated = m->userauth(ssh, method);
|
||||
}
|
||||
if (!authctxt->authenticated)
|
||||
if (!authctxt->authenticated && strcmp(method, "none") != 0)
|
||||
ensure_minimum_time_since(tstart,
|
||||
user_specific_delay(authctxt->user));
|
||||
userauth_finish(ssh, authenticated, method, NULL);
|
||||
|
|
35
channels.c
35
channels.c
|
@ -1,4 +1,4 @@
|
|||
/* $OpenBSD: channels.c,v 1.432 2023/07/04 03:59:21 dlg Exp $ */
|
||||
/* $OpenBSD: channels.c,v 1.433 2023/09/04 00:01:46 djm Exp $ */
|
||||
/*
|
||||
* Author: Tatu Ylonen <ylo@cs.hut.fi>
|
||||
* Copyright (c) 1995 Tatu Ylonen <ylo@cs.hut.fi>, Espoo, Finland
|
||||
|
@ -2899,8 +2899,9 @@ channel_after_poll(struct ssh *ssh, struct pollfd *pfd, u_int npfd)
|
|||
|
||||
/*
|
||||
* Enqueue data for channels with open or draining c->input.
|
||||
* Returns non-zero if a packet was enqueued.
|
||||
*/
|
||||
static void
|
||||
static int
|
||||
channel_output_poll_input_open(struct ssh *ssh, Channel *c)
|
||||
{
|
||||
size_t len, plen;
|
||||
|
@ -2923,7 +2924,7 @@ channel_output_poll_input_open(struct ssh *ssh, Channel *c)
|
|||
else
|
||||
chan_ibuf_empty(ssh, c);
|
||||
}
|
||||
return;
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (!c->have_remote_id)
|
||||
|
@ -2940,7 +2941,7 @@ channel_output_poll_input_open(struct ssh *ssh, Channel *c)
|
|||
*/
|
||||
if (plen > c->remote_window || plen > c->remote_maxpacket) {
|
||||
debug("channel %d: datagram too big", c->self);
|
||||
return;
|
||||
return 0;
|
||||
}
|
||||
/* Enqueue it */
|
||||
if ((r = sshpkt_start(ssh, SSH2_MSG_CHANNEL_DATA)) != 0 ||
|
||||
|
@ -2949,7 +2950,7 @@ channel_output_poll_input_open(struct ssh *ssh, Channel *c)
|
|||
(r = sshpkt_send(ssh)) != 0)
|
||||
fatal_fr(r, "channel %i: send datagram", c->self);
|
||||
c->remote_window -= plen;
|
||||
return;
|
||||
return 1;
|
||||
}
|
||||
|
||||
/* Enqueue packet for buffered data. */
|
||||
|
@ -2958,7 +2959,7 @@ channel_output_poll_input_open(struct ssh *ssh, Channel *c)
|
|||
if (len > c->remote_maxpacket)
|
||||
len = c->remote_maxpacket;
|
||||
if (len == 0)
|
||||
return;
|
||||
return 0;
|
||||
if ((r = sshpkt_start(ssh, SSH2_MSG_CHANNEL_DATA)) != 0 ||
|
||||
(r = sshpkt_put_u32(ssh, c->remote_id)) != 0 ||
|
||||
(r = sshpkt_put_string(ssh, sshbuf_ptr(c->input), len)) != 0 ||
|
||||
|
@ -2967,19 +2968,21 @@ channel_output_poll_input_open(struct ssh *ssh, Channel *c)
|
|||
if ((r = sshbuf_consume(c->input, len)) != 0)
|
||||
fatal_fr(r, "channel %i: consume", c->self);
|
||||
c->remote_window -= len;
|
||||
return 1;
|
||||
}
|
||||
|
||||
/*
|
||||
* Enqueue data for channels with open c->extended in read mode.
|
||||
* Returns non-zero if a packet was enqueued.
|
||||
*/
|
||||
static void
|
||||
static int
|
||||
channel_output_poll_extended_read(struct ssh *ssh, Channel *c)
|
||||
{
|
||||
size_t len;
|
||||
int r;
|
||||
|
||||
if ((len = sshbuf_len(c->extended)) == 0)
|
||||
return;
|
||||
return 0;
|
||||
|
||||
debug2("channel %d: rwin %u elen %zu euse %d", c->self,
|
||||
c->remote_window, sshbuf_len(c->extended), c->extended_usage);
|
||||
|
@ -2988,7 +2991,7 @@ channel_output_poll_extended_read(struct ssh *ssh, Channel *c)
|
|||
if (len > c->remote_maxpacket)
|
||||
len = c->remote_maxpacket;
|
||||
if (len == 0)
|
||||
return;
|
||||
return 0;
|
||||
if (!c->have_remote_id)
|
||||
fatal_f("channel %d: no remote id", c->self);
|
||||
if ((r = sshpkt_start(ssh, SSH2_MSG_CHANNEL_EXTENDED_DATA)) != 0 ||
|
||||
|
@ -3001,15 +3004,20 @@ channel_output_poll_extended_read(struct ssh *ssh, Channel *c)
|
|||
fatal_fr(r, "channel %i: consume", c->self);
|
||||
c->remote_window -= len;
|
||||
debug2("channel %d: sent ext data %zu", c->self, len);
|
||||
return 1;
|
||||
}
|
||||
|
||||
/* If there is data to send to the connection, enqueue some of it now. */
|
||||
void
|
||||
/*
|
||||
* If there is data to send to the connection, enqueue some of it now.
|
||||
* Returns non-zero if data was enqueued.
|
||||
*/
|
||||
int
|
||||
channel_output_poll(struct ssh *ssh)
|
||||
{
|
||||
struct ssh_channels *sc = ssh->chanctxt;
|
||||
Channel *c;
|
||||
u_int i;
|
||||
int ret = 0;
|
||||
|
||||
for (i = 0; i < sc->channels_alloc; i++) {
|
||||
c = sc->channels[i];
|
||||
|
@ -3032,12 +3040,13 @@ channel_output_poll(struct ssh *ssh)
|
|||
/* Get the amount of buffered data for this channel. */
|
||||
if (c->istate == CHAN_INPUT_OPEN ||
|
||||
c->istate == CHAN_INPUT_WAIT_DRAIN)
|
||||
channel_output_poll_input_open(ssh, c);
|
||||
ret |= channel_output_poll_input_open(ssh, c);
|
||||
/* Send extended data, i.e. stderr */
|
||||
if (!(c->flags & CHAN_EOF_SENT) &&
|
||||
c->extended_usage == CHAN_EXTENDED_READ)
|
||||
channel_output_poll_extended_read(ssh, c);
|
||||
ret |= channel_output_poll_extended_read(ssh, c);
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
/* -- mux proxy support */
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
/* $OpenBSD: channels.h,v 1.151 2023/07/04 03:59:21 dlg Exp $ */
|
||||
/* $OpenBSD: channels.h,v 1.152 2023/09/04 00:01:46 djm Exp $ */
|
||||
|
||||
/*
|
||||
* Author: Tatu Ylonen <ylo@cs.hut.fi>
|
||||
|
@ -335,7 +335,7 @@ struct timespec;
|
|||
void channel_prepare_poll(struct ssh *, struct pollfd **,
|
||||
u_int *, u_int *, u_int, struct timespec *);
|
||||
void channel_after_poll(struct ssh *, struct pollfd *, u_int);
|
||||
void channel_output_poll(struct ssh *);
|
||||
int channel_output_poll(struct ssh *);
|
||||
|
||||
int channel_not_very_much_buffered_data(struct ssh *);
|
||||
void channel_close_all(struct ssh *);
|
||||
|
|
189
clientloop.c
189
clientloop.c
|
@ -1,4 +1,4 @@
|
|||
/* $OpenBSD: clientloop.c,v 1.392 2023/04/03 08:10:54 dtucker Exp $ */
|
||||
/* $OpenBSD: clientloop.c,v 1.398 2023/09/10 03:51:55 djm Exp $ */
|
||||
/*
|
||||
* Author: Tatu Ylonen <ylo@cs.hut.fi>
|
||||
* Copyright (c) 1995 Tatu Ylonen <ylo@cs.hut.fi>, Espoo, Finland
|
||||
|
@ -118,6 +118,9 @@
|
|||
/* Permitted RSA signature algorithms for UpdateHostkeys proofs */
|
||||
#define HOSTKEY_PROOF_RSA_ALGS "rsa-sha2-512,rsa-sha2-256"
|
||||
|
||||
/* Uncertainty (in percent) of keystroke timing intervals */
|
||||
#define SSH_KEYSTROKE_TIMING_FUZZ 10
|
||||
|
||||
/* import options */
|
||||
extern Options options;
|
||||
|
||||
|
@ -515,17 +518,181 @@ server_alive_check(struct ssh *ssh)
|
|||
schedule_server_alive_check();
|
||||
}
|
||||
|
||||
/* Try to send a dummy keystroke */
|
||||
static int
|
||||
send_chaff(struct ssh *ssh)
|
||||
{
|
||||
int r;
|
||||
|
||||
if ((ssh->kex->flags & KEX_HAS_PING) == 0)
|
||||
return 0;
|
||||
/* XXX probabilistically send chaff? */
|
||||
/*
|
||||
* a SSH2_MSG_CHANNEL_DATA payload is 9 bytes:
|
||||
* 4 bytes channel ID + 4 bytes string length + 1 byte string data
|
||||
* simulate that here.
|
||||
*/
|
||||
if ((r = sshpkt_start(ssh, SSH2_MSG_PING)) != 0 ||
|
||||
(r = sshpkt_put_cstring(ssh, "PING!")) != 0 ||
|
||||
(r = sshpkt_send(ssh)) != 0)
|
||||
fatal_fr(r, "send packet");
|
||||
return 1;
|
||||
}
|
||||
|
||||
/* Sets the next interval to send a keystroke or chaff packet */
|
||||
static void
|
||||
set_next_interval(const struct timespec *now, struct timespec *next_interval,
|
||||
u_int interval_ms, int starting)
|
||||
{
|
||||
struct timespec tmp;
|
||||
long long interval_ns, fuzz_ns;
|
||||
static long long rate_fuzz;
|
||||
|
||||
interval_ns = interval_ms * (1000LL * 1000);
|
||||
fuzz_ns = (interval_ns * SSH_KEYSTROKE_TIMING_FUZZ) / 100;
|
||||
/* Center fuzz around requested interval */
|
||||
if (fuzz_ns > INT_MAX)
|
||||
fuzz_ns = INT_MAX;
|
||||
if (fuzz_ns > interval_ns) {
|
||||
/* Shouldn't happen */
|
||||
fatal_f("internal error: fuzz %u%% %lldns > interval %lldns",
|
||||
SSH_KEYSTROKE_TIMING_FUZZ, fuzz_ns, interval_ns);
|
||||
}
|
||||
/*
|
||||
* Randomise the keystroke/chaff intervals in two ways:
|
||||
* 1. Each interval has some random jitter applied to make the
|
||||
* interval-to-interval time unpredictable.
|
||||
* 2. The overall interval rate is also randomly perturbed for each
|
||||
* chaffing session to make the average rate unpredictable.
|
||||
*/
|
||||
if (starting)
|
||||
rate_fuzz = arc4random_uniform(fuzz_ns);
|
||||
interval_ns -= fuzz_ns;
|
||||
interval_ns += arc4random_uniform(fuzz_ns) + rate_fuzz;
|
||||
|
||||
tmp.tv_sec = interval_ns / (1000 * 1000 * 1000);
|
||||
tmp.tv_nsec = interval_ns % (1000 * 1000 * 1000);
|
||||
|
||||
timespecadd(now, &tmp, next_interval);
|
||||
}
|
||||
|
||||
/*
|
||||
* Performs keystroke timing obfuscation. Returns non-zero if the
|
||||
* output fd should be polled.
|
||||
*/
|
||||
static int
|
||||
obfuscate_keystroke_timing(struct ssh *ssh, struct timespec *timeout,
|
||||
int channel_did_enqueue)
|
||||
{
|
||||
static int active;
|
||||
static struct timespec next_interval, chaff_until;
|
||||
struct timespec now, tmp;
|
||||
int just_started = 0, had_keystroke = 0;
|
||||
static unsigned long long nchaff;
|
||||
char *stop_reason = NULL;
|
||||
long long n;
|
||||
|
||||
monotime_ts(&now);
|
||||
|
||||
if (options.obscure_keystroke_timing_interval <= 0)
|
||||
return 1; /* disabled in config */
|
||||
|
||||
if (!channel_still_open(ssh) || quit_pending) {
|
||||
/* Stop if no channels left of we're waiting for one to close */
|
||||
stop_reason = "no active channels";
|
||||
} else if (ssh_packet_is_rekeying(ssh)) {
|
||||
/* Stop if we're rekeying */
|
||||
stop_reason = "rekeying started";
|
||||
} else if (!ssh_packet_interactive_data_to_write(ssh) &&
|
||||
ssh_packet_have_data_to_write(ssh)) {
|
||||
/* Stop if the output buffer has more than a few keystrokes */
|
||||
stop_reason = "output buffer filling";
|
||||
} else if (active && channel_did_enqueue &&
|
||||
ssh_packet_have_data_to_write(ssh)) {
|
||||
/* Still in active mode and have a keystroke queued. */
|
||||
had_keystroke = 1;
|
||||
} else if (active) {
|
||||
if (timespeccmp(&now, &chaff_until, >=)) {
|
||||
/* Stop if there have been no keystrokes for a while */
|
||||
stop_reason = "chaff time expired";
|
||||
} else if (timespeccmp(&now, &next_interval, >=)) {
|
||||
/* Otherwise if we were due to send, then send chaff */
|
||||
if (send_chaff(ssh))
|
||||
nchaff++;
|
||||
}
|
||||
}
|
||||
|
||||
if (stop_reason != NULL) {
|
||||
if (active) {
|
||||
debug3_f("stopping: %s (%llu chaff packets sent)",
|
||||
stop_reason, nchaff);
|
||||
active = 0;
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
/*
|
||||
* If we're in interactive mode, and only have a small amount
|
||||
* of outbound data, then we assume that the user is typing
|
||||
* interactively. In this case, start quantising outbound packets to
|
||||
* fixed time intervals to hide inter-keystroke timing.
|
||||
*/
|
||||
if (!active && ssh_packet_interactive_data_to_write(ssh) &&
|
||||
channel_did_enqueue && ssh_packet_have_data_to_write(ssh)) {
|
||||
debug3_f("starting: interval ~%dms",
|
||||
options.obscure_keystroke_timing_interval);
|
||||
just_started = had_keystroke = active = 1;
|
||||
nchaff = 0;
|
||||
set_next_interval(&now, &next_interval,
|
||||
options.obscure_keystroke_timing_interval, 1);
|
||||
}
|
||||
|
||||
/* Don't hold off if obfuscation inactive */
|
||||
if (!active)
|
||||
return 1;
|
||||
|
||||
if (had_keystroke) {
|
||||
/*
|
||||
* Arrange to send chaff packets for a random interval after
|
||||
* the last keystroke was sent.
|
||||
*/
|
||||
ms_to_timespec(&tmp, SSH_KEYSTROKE_CHAFF_MIN_MS +
|
||||
arc4random_uniform(SSH_KEYSTROKE_CHAFF_RNG_MS));
|
||||
timespecadd(&now, &tmp, &chaff_until);
|
||||
}
|
||||
|
||||
ptimeout_deadline_monotime_tsp(timeout, &next_interval);
|
||||
|
||||
if (just_started)
|
||||
return 1;
|
||||
|
||||
/* Don't arm output fd for poll until the timing interval has elapsed */
|
||||
if (timespeccmp(&now, &next_interval, <))
|
||||
return 0;
|
||||
|
||||
/* Calculate number of intervals missed since the last check */
|
||||
n = (now.tv_sec - next_interval.tv_sec) * 1000LL * 1000 * 1000;
|
||||
n += now.tv_nsec - next_interval.tv_nsec;
|
||||
n /= options.obscure_keystroke_timing_interval * 1000LL * 1000;
|
||||
n = (n < 0) ? 1 : n + 1;
|
||||
|
||||
/* Advance to the next interval */
|
||||
set_next_interval(&now, &next_interval,
|
||||
options.obscure_keystroke_timing_interval * n, 0);
|
||||
return 1;
|
||||
}
|
||||
|
||||
/*
|
||||
* Waits until the client can do something (some data becomes available on
|
||||
* one of the file descriptors).
|
||||
*/
|
||||
static void
|
||||
client_wait_until_can_do_something(struct ssh *ssh, struct pollfd **pfdp,
|
||||
u_int *npfd_allocp, u_int *npfd_activep, int rekeying,
|
||||
u_int *npfd_allocp, u_int *npfd_activep, int channel_did_enqueue,
|
||||
int *conn_in_readyp, int *conn_out_readyp)
|
||||
{
|
||||
struct timespec timeout;
|
||||
int ret;
|
||||
int ret, oready;
|
||||
u_int p;
|
||||
|
||||
*conn_in_readyp = *conn_out_readyp = 0;
|
||||
|
@ -545,11 +712,14 @@ client_wait_until_can_do_something(struct ssh *ssh, struct pollfd **pfdp,
|
|||
return;
|
||||
}
|
||||
|
||||
oready = obfuscate_keystroke_timing(ssh, &timeout, channel_did_enqueue);
|
||||
|
||||
/* Monitor server connection on reserved pollfd entries */
|
||||
(*pfdp)[0].fd = connection_in;
|
||||
(*pfdp)[0].events = POLLIN;
|
||||
(*pfdp)[1].fd = connection_out;
|
||||
(*pfdp)[1].events = ssh_packet_have_data_to_write(ssh) ? POLLOUT : 0;
|
||||
(*pfdp)[1].events = (oready && ssh_packet_have_data_to_write(ssh)) ?
|
||||
POLLOUT : 0;
|
||||
|
||||
/*
|
||||
* Wait for something to happen. This will suspend the process until
|
||||
|
@ -561,12 +731,12 @@ client_wait_until_can_do_something(struct ssh *ssh, struct pollfd **pfdp,
|
|||
ptimeout_deadline_monotime(&timeout, control_persist_exit_time);
|
||||
if (options.server_alive_interval > 0)
|
||||
ptimeout_deadline_monotime(&timeout, server_alive_time);
|
||||
if (options.rekey_interval > 0 && !rekeying) {
|
||||
if (options.rekey_interval > 0 && !ssh_packet_is_rekeying(ssh)) {
|
||||
ptimeout_deadline_sec(&timeout,
|
||||
ssh_packet_get_rekey_timeout(ssh));
|
||||
}
|
||||
|
||||
ret = poll(*pfdp, *npfd_activep, ptimeout_get_ms(&timeout));
|
||||
ret = ppoll(*pfdp, *npfd_activep, ptimeout_get_tsp(&timeout), NULL);
|
||||
|
||||
if (ret == -1) {
|
||||
/*
|
||||
|
@ -1283,7 +1453,7 @@ client_loop(struct ssh *ssh, int have_pty, int escape_char_arg,
|
|||
struct pollfd *pfd = NULL;
|
||||
u_int npfd_alloc = 0, npfd_active = 0;
|
||||
double start_time, total_time;
|
||||
int r, len;
|
||||
int channel_did_enqueue = 0, r, len;
|
||||
u_int64_t ibytes, obytes;
|
||||
int conn_in_ready, conn_out_ready;
|
||||
|
||||
|
@ -1373,6 +1543,7 @@ client_loop(struct ssh *ssh, int have_pty, int escape_char_arg,
|
|||
|
||||
/* Main loop of the client for the interactive session mode. */
|
||||
while (!quit_pending) {
|
||||
channel_did_enqueue = 0;
|
||||
|
||||
/* Process buffered packets sent by the server. */
|
||||
client_process_buffered_input_packets(ssh);
|
||||
|
@ -1394,7 +1565,7 @@ client_loop(struct ssh *ssh, int have_pty, int escape_char_arg,
|
|||
* enqueue them for sending to the server.
|
||||
*/
|
||||
if (ssh_packet_not_very_much_data_to_write(ssh))
|
||||
channel_output_poll(ssh);
|
||||
channel_did_enqueue = channel_output_poll(ssh);
|
||||
|
||||
/*
|
||||
* Check if the window size has changed, and buffer a
|
||||
|
@ -1410,7 +1581,7 @@ client_loop(struct ssh *ssh, int have_pty, int escape_char_arg,
|
|||
* available on one of the descriptors).
|
||||
*/
|
||||
client_wait_until_can_do_something(ssh, &pfd, &npfd_alloc,
|
||||
&npfd_active, ssh_packet_is_rekeying(ssh),
|
||||
&npfd_active, channel_did_enqueue,
|
||||
&conn_in_ready, &conn_out_ready);
|
||||
|
||||
if (quit_pending)
|
||||
|
|
10
configure.ac
10
configure.ac
|
@ -187,7 +187,13 @@ if test "$GCC" = "yes" || test "$GCC" = "egcs"; then
|
|||
AC_MSG_RESULT([$GCC_VER])
|
||||
|
||||
AC_MSG_CHECKING([clang version])
|
||||
CLANG_VER=`$CC -v 2>&1 | $AWK '/clang version /{print $3}'`
|
||||
ver="`$CC -v 2>&1`"
|
||||
if echo "$ver" | grep "Apple" >/dev/null; then
|
||||
CLANG_VER="apple-`echo "$ver" | \
|
||||
awk '/Apple LLVM/ {print $4"-"$5}'`"
|
||||
else
|
||||
CLANG_VER=`echo "$ver" | $AWK '/clang version /{print $3}'`
|
||||
fi
|
||||
AC_MSG_RESULT([$CLANG_VER])
|
||||
|
||||
OSSH_CHECK_CFLAG_COMPILE([-pipe])
|
||||
|
@ -225,7 +231,7 @@ if test "$GCC" = "yes" || test "$GCC" = "egcs"; then
|
|||
# https://bugzilla.mindrot.org/show_bug.cgi?id=3475 and
|
||||
# https://github.com/llvm/llvm-project/issues/59242
|
||||
case "$CLANG_VER" in
|
||||
15.*) OSSH_CHECK_CFLAG_COMPILE([-fzero-call-used-regs=used]) ;;
|
||||
15.*|apple*) OSSH_CHECK_CFLAG_COMPILE([-fzero-call-used-regs=used]) ;;
|
||||
*) OSSH_CHECK_CFLAG_COMPILE([-fzero-call-used-regs=all]) ;;
|
||||
esac
|
||||
OSSH_CHECK_CFLAG_COMPILE([-ftrivial-auto-var-init=zero])
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
%global ver 9.4p1
|
||||
%global ver 9.5p1
|
||||
%global rel 1%{?dist}
|
||||
|
||||
# OpenSSH privilege separation requires a user & group ID
|
||||
|
|
|
@ -13,7 +13,7 @@
|
|||
|
||||
Summary: OpenSSH, a free Secure Shell (SSH) protocol implementation
|
||||
Name: openssh
|
||||
Version: 9.4p1
|
||||
Version: 9.5p1
|
||||
URL: https://www.openssh.com/
|
||||
Release: 1
|
||||
Source0: openssh-%{version}.tar.gz
|
||||
|
|
55
kex.c
55
kex.c
|
@ -1,4 +1,4 @@
|
|||
/* $OpenBSD: kex.c,v 1.179 2023/08/18 01:37:41 djm Exp $ */
|
||||
/* $OpenBSD: kex.c,v 1.181 2023/08/28 03:28:43 djm Exp $ */
|
||||
/*
|
||||
* Copyright (c) 2000, 2001 Markus Friedl. All rights reserved.
|
||||
*
|
||||
|
@ -496,12 +496,14 @@ kex_send_ext_info(struct ssh *ssh)
|
|||
return SSH_ERR_ALLOC_FAIL;
|
||||
/* XXX filter algs list by allowed pubkey/hostbased types */
|
||||
if ((r = sshpkt_start(ssh, SSH2_MSG_EXT_INFO)) != 0 ||
|
||||
(r = sshpkt_put_u32(ssh, 2)) != 0 ||
|
||||
(r = sshpkt_put_u32(ssh, 3)) != 0 ||
|
||||
(r = sshpkt_put_cstring(ssh, "server-sig-algs")) != 0 ||
|
||||
(r = sshpkt_put_cstring(ssh, algs)) != 0 ||
|
||||
(r = sshpkt_put_cstring(ssh,
|
||||
"publickey-hostbound@openssh.com")) != 0 ||
|
||||
(r = sshpkt_put_cstring(ssh, "0")) != 0 ||
|
||||
(r = sshpkt_put_cstring(ssh, "ping@openssh.com")) != 0 ||
|
||||
(r = sshpkt_put_cstring(ssh, "0")) != 0 ||
|
||||
(r = sshpkt_send(ssh)) != 0) {
|
||||
error_fr(r, "compose");
|
||||
goto out;
|
||||
|
@ -531,6 +533,23 @@ kex_send_newkeys(struct ssh *ssh)
|
|||
return 0;
|
||||
}
|
||||
|
||||
/* Check whether an ext_info value contains the expected version string */
|
||||
static int
|
||||
kex_ext_info_check_ver(struct kex *kex, const char *name,
|
||||
const u_char *val, size_t len, const char *want_ver, u_int flag)
|
||||
{
|
||||
if (memchr(val, '\0', len) != NULL) {
|
||||
error("SSH2_MSG_EXT_INFO: %s value contains nul byte", name);
|
||||
return SSH_ERR_INVALID_FORMAT;
|
||||
}
|
||||
debug_f("%s=<%s>", name, val);
|
||||
if (strcmp(val, want_ver) == 0)
|
||||
kex->flags |= flag;
|
||||
else
|
||||
debug_f("unsupported version of %s extension", name);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int
|
||||
kex_input_ext_info(int type, u_int32_t seq, struct ssh *ssh)
|
||||
{
|
||||
|
@ -561,6 +580,8 @@ kex_input_ext_info(int type, u_int32_t seq, struct ssh *ssh)
|
|||
/* Ensure no \0 lurking in value */
|
||||
if (memchr(val, '\0', vlen) != NULL) {
|
||||
error_f("nul byte in %s", name);
|
||||
free(name);
|
||||
free(val);
|
||||
return SSH_ERR_INVALID_FORMAT;
|
||||
}
|
||||
debug_f("%s=<%s>", name, val);
|
||||
|
@ -568,18 +589,18 @@ kex_input_ext_info(int type, u_int32_t seq, struct ssh *ssh)
|
|||
val = NULL;
|
||||
} else if (strcmp(name,
|
||||
"publickey-hostbound@openssh.com") == 0) {
|
||||
/* XXX refactor */
|
||||
/* Ensure no \0 lurking in value */
|
||||
if (memchr(val, '\0', vlen) != NULL) {
|
||||
error_f("nul byte in %s", name);
|
||||
return SSH_ERR_INVALID_FORMAT;
|
||||
if ((r = kex_ext_info_check_ver(kex, name, val, vlen,
|
||||
"0", KEX_HAS_PUBKEY_HOSTBOUND)) != 0) {
|
||||
free(name);
|
||||
free(val);
|
||||
return r;
|
||||
}
|
||||
debug_f("%s=<%s>", name, val);
|
||||
if (strcmp(val, "0") == 0)
|
||||
kex->flags |= KEX_HAS_PUBKEY_HOSTBOUND;
|
||||
else {
|
||||
debug_f("unsupported version of %s extension",
|
||||
name);
|
||||
} else if (strcmp(name, "ping@openssh.com") == 0) {
|
||||
if ((r = kex_ext_info_check_ver(kex, name, val, vlen,
|
||||
"0", KEX_HAS_PING)) != 0) {
|
||||
free(name);
|
||||
free(val);
|
||||
return r;
|
||||
}
|
||||
} else
|
||||
debug_f("%s (unrecognised)", name);
|
||||
|
@ -1369,7 +1390,7 @@ kex_exchange_identification(struct ssh *ssh, int timeout_ms,
|
|||
len = atomicio(read, ssh_packet_get_connection_in(ssh),
|
||||
&c, 1);
|
||||
if (len != 1 && errno == EPIPE) {
|
||||
error_f("Connection closed by remote host");
|
||||
verbose_f("Connection closed by remote host");
|
||||
r = SSH_ERR_CONN_CLOSED;
|
||||
goto out;
|
||||
} else if (len != 1) {
|
||||
|
@ -1385,7 +1406,7 @@ kex_exchange_identification(struct ssh *ssh, int timeout_ms,
|
|||
if (c == '\n')
|
||||
break;
|
||||
if (c == '\0' || expect_nl) {
|
||||
error_f("banner line contains invalid "
|
||||
verbose_f("banner line contains invalid "
|
||||
"characters");
|
||||
goto invalid;
|
||||
}
|
||||
|
@ -1395,7 +1416,7 @@ kex_exchange_identification(struct ssh *ssh, int timeout_ms,
|
|||
goto out;
|
||||
}
|
||||
if (sshbuf_len(peer_version) > SSH_MAX_BANNER_LEN) {
|
||||
error_f("banner line too long");
|
||||
verbose_f("banner line too long");
|
||||
goto invalid;
|
||||
}
|
||||
}
|
||||
|
@ -1411,7 +1432,7 @@ kex_exchange_identification(struct ssh *ssh, int timeout_ms,
|
|||
}
|
||||
/* Do not accept lines before the SSH ident from a client */
|
||||
if (ssh->kex->server) {
|
||||
error_f("client sent invalid protocol identifier "
|
||||
verbose_f("client sent invalid protocol identifier "
|
||||
"\"%.256s\"", cp);
|
||||
free(cp);
|
||||
goto invalid;
|
||||
|
|
3
kex.h
3
kex.h
|
@ -1,4 +1,4 @@
|
|||
/* $OpenBSD: kex.h,v 1.118 2023/03/06 12:14:48 dtucker Exp $ */
|
||||
/* $OpenBSD: kex.h,v 1.119 2023/08/28 03:28:43 djm Exp $ */
|
||||
|
||||
/*
|
||||
* Copyright (c) 2000, 2001 Markus Friedl. All rights reserved.
|
||||
|
@ -111,6 +111,7 @@ enum kex_exchange {
|
|||
#define KEX_HAS_PUBKEY_HOSTBOUND 0x0004
|
||||
#define KEX_RSA_SHA2_256_SUPPORTED 0x0008 /* only set in server for now */
|
||||
#define KEX_RSA_SHA2_512_SUPPORTED 0x0010 /* only set in server for now */
|
||||
#define KEX_HAS_PING 0x0020
|
||||
|
||||
struct sshenc {
|
||||
char *name;
|
||||
|
|
31
misc.c
31
misc.c
|
@ -1,4 +1,4 @@
|
|||
/* $OpenBSD: misc.c,v 1.186 2023/08/18 01:37:41 djm Exp $ */
|
||||
/* $OpenBSD: misc.c,v 1.187 2023/08/28 03:31:16 djm Exp $ */
|
||||
/*
|
||||
* Copyright (c) 2000 Markus Friedl. All rights reserved.
|
||||
* Copyright (c) 2005-2020 Damien Miller. All rights reserved.
|
||||
|
@ -3025,22 +3025,33 @@ ptimeout_deadline_ms(struct timespec *pt, long ms)
|
|||
ptimeout_deadline_tsp(pt, &p);
|
||||
}
|
||||
|
||||
/* Specify a poll/ppoll deadline at wall clock monotime 'when' (timespec) */
|
||||
void
|
||||
ptimeout_deadline_monotime_tsp(struct timespec *pt, struct timespec *when)
|
||||
{
|
||||
struct timespec now, t;
|
||||
|
||||
monotime_ts(&now);
|
||||
|
||||
if (timespeccmp(&now, when, >=)) {
|
||||
/* 'when' is now or in the past. Timeout ASAP */
|
||||
pt->tv_sec = 0;
|
||||
pt->tv_nsec = 0;
|
||||
} else {
|
||||
timespecsub(when, &now, &t);
|
||||
ptimeout_deadline_tsp(pt, &t);
|
||||
}
|
||||
}
|
||||
|
||||
/* Specify a poll/ppoll deadline at wall clock monotime 'when' */
|
||||
void
|
||||
ptimeout_deadline_monotime(struct timespec *pt, time_t when)
|
||||
{
|
||||
struct timespec now, t;
|
||||
struct timespec t;
|
||||
|
||||
t.tv_sec = when;
|
||||
t.tv_nsec = 0;
|
||||
monotime_ts(&now);
|
||||
|
||||
if (timespeccmp(&now, &t, >=))
|
||||
ptimeout_deadline_sec(pt, 0);
|
||||
else {
|
||||
timespecsub(&t, &now, &t);
|
||||
ptimeout_deadline_tsp(pt, &t);
|
||||
}
|
||||
ptimeout_deadline_monotime_tsp(pt, &t);
|
||||
}
|
||||
|
||||
/* Get a poll(2) timeout value in milliseconds */
|
||||
|
|
3
misc.h
3
misc.h
|
@ -1,4 +1,4 @@
|
|||
/* $OpenBSD: misc.h,v 1.104 2023/08/18 01:37:41 djm Exp $ */
|
||||
/* $OpenBSD: misc.h,v 1.105 2023/08/28 03:31:16 djm Exp $ */
|
||||
|
||||
/*
|
||||
* Author: Tatu Ylonen <ylo@cs.hut.fi>
|
||||
|
@ -217,6 +217,7 @@ struct timespec;
|
|||
void ptimeout_init(struct timespec *pt);
|
||||
void ptimeout_deadline_sec(struct timespec *pt, long sec);
|
||||
void ptimeout_deadline_ms(struct timespec *pt, long ms);
|
||||
void ptimeout_deadline_monotime_tsp(struct timespec *pt, struct timespec *when);
|
||||
void ptimeout_deadline_monotime(struct timespec *pt, time_t when);
|
||||
int ptimeout_get_ms(struct timespec *pt);
|
||||
struct timespec *ptimeout_get_tsp(struct timespec *pt);
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
/* $OpenBSD: monitor.c,v 1.236 2023/05/10 10:04:20 dtucker Exp $ */
|
||||
/* $OpenBSD: monitor.c,v 1.237 2023/08/16 16:14:11 djm Exp $ */
|
||||
/*
|
||||
* Copyright 2002 Niels Provos <provos@citi.umich.edu>
|
||||
* Copyright 2002 Markus Friedl <markus@openbsd.org>
|
||||
|
@ -347,6 +347,11 @@ monitor_child_preauth(struct ssh *ssh, struct monitor *pmonitor)
|
|||
auth_method, auth_submethod);
|
||||
}
|
||||
}
|
||||
if (authctxt->failures > options.max_authtries) {
|
||||
/* Shouldn't happen */
|
||||
fatal_f("privsep child made too many authentication "
|
||||
"attempts");
|
||||
}
|
||||
}
|
||||
|
||||
if (!authctxt->valid)
|
||||
|
|
|
@ -28,7 +28,6 @@
|
|||
#include <stdlib.h>
|
||||
#include <stddef.h>
|
||||
#include <string.h>
|
||||
#include <unistd.h>
|
||||
#ifdef HAVE_DIRENT_H
|
||||
# include <dirent.h>
|
||||
# define NAMLEN(dirent) strlen((dirent)->d_name)
|
||||
|
|
35
packet.c
35
packet.c
|
@ -1,4 +1,4 @@
|
|||
/* $OpenBSD: packet.c,v 1.310 2023/04/06 03:21:31 djm Exp $ */
|
||||
/* $OpenBSD: packet.c,v 1.312 2023/08/28 03:31:16 djm Exp $ */
|
||||
/*
|
||||
* Author: Tatu Ylonen <ylo@cs.hut.fi>
|
||||
* Copyright (c) 1995 Tatu Ylonen <ylo@cs.hut.fi>, Espoo, Finland
|
||||
|
@ -1054,6 +1054,8 @@ int
|
|||
ssh_packet_log_type(u_char type)
|
||||
{
|
||||
switch (type) {
|
||||
case SSH2_MSG_PING:
|
||||
case SSH2_MSG_PONG:
|
||||
case SSH2_MSG_CHANNEL_DATA:
|
||||
case SSH2_MSG_CHANNEL_EXTENDED_DATA:
|
||||
case SSH2_MSG_CHANNEL_WINDOW_ADJUST:
|
||||
|
@ -1678,7 +1680,7 @@ ssh_packet_read_poll2(struct ssh *ssh, u_char *typep, u_int32_t *seqnr_p)
|
|||
goto out;
|
||||
if (ssh_packet_log_type(*typep))
|
||||
debug3("receive packet: type %u", *typep);
|
||||
if (*typep < SSH2_MSG_MIN || *typep >= SSH2_MSG_LOCAL_MIN) {
|
||||
if (*typep < SSH2_MSG_MIN) {
|
||||
if ((r = sshpkt_disconnect(ssh,
|
||||
"Invalid ssh2 packet type: %d", *typep)) != 0 ||
|
||||
(r = ssh_packet_write_wait(ssh)) != 0)
|
||||
|
@ -1713,6 +1715,8 @@ ssh_packet_read_poll_seqnr(struct ssh *ssh, u_char *typep, u_int32_t *seqnr_p)
|
|||
u_int reason, seqnr;
|
||||
int r;
|
||||
u_char *msg;
|
||||
const u_char *d;
|
||||
size_t len;
|
||||
|
||||
for (;;) {
|
||||
msg = NULL;
|
||||
|
@ -1756,6 +1760,21 @@ ssh_packet_read_poll_seqnr(struct ssh *ssh, u_char *typep, u_int32_t *seqnr_p)
|
|||
debug("Received SSH2_MSG_UNIMPLEMENTED for %u",
|
||||
seqnr);
|
||||
break;
|
||||
case SSH2_MSG_PING:
|
||||
if ((r = sshpkt_get_string_direct(ssh, &d, &len)) != 0)
|
||||
return r;
|
||||
DBG(debug("Received SSH2_MSG_PING len %zu", len));
|
||||
if ((r = sshpkt_start(ssh, SSH2_MSG_PONG)) != 0 ||
|
||||
(r = sshpkt_put_string(ssh, d, len)) != 0 ||
|
||||
(r = sshpkt_send(ssh)) != 0)
|
||||
return r;
|
||||
break;
|
||||
case SSH2_MSG_PONG:
|
||||
if ((r = sshpkt_get_string_direct(ssh,
|
||||
NULL, &len)) != 0)
|
||||
return r;
|
||||
DBG(debug("Received SSH2_MSG_PONG len %zu", len));
|
||||
break;
|
||||
default:
|
||||
return 0;
|
||||
}
|
||||
|
@ -2067,6 +2086,18 @@ ssh_packet_not_very_much_data_to_write(struct ssh *ssh)
|
|||
return sshbuf_len(ssh->state->output) < 128 * 1024;
|
||||
}
|
||||
|
||||
/*
|
||||
* returns true when there are at most a few keystrokes of data to write
|
||||
* and the connection is in interactive mode.
|
||||
*/
|
||||
|
||||
int
|
||||
ssh_packet_interactive_data_to_write(struct ssh *ssh)
|
||||
{
|
||||
return ssh->state->interactive_mode &&
|
||||
sshbuf_len(ssh->state->output) < 256;
|
||||
}
|
||||
|
||||
void
|
||||
ssh_packet_set_tos(struct ssh *ssh, int tos)
|
||||
{
|
||||
|
|
3
packet.h
3
packet.h
|
@ -1,4 +1,4 @@
|
|||
/* $OpenBSD: packet.h,v 1.94 2022/01/22 00:49:34 djm Exp $ */
|
||||
/* $OpenBSD: packet.h,v 1.95 2023/08/28 03:31:16 djm Exp $ */
|
||||
|
||||
/*
|
||||
* Author: Tatu Ylonen <ylo@cs.hut.fi>
|
||||
|
@ -145,6 +145,7 @@ int ssh_packet_write_poll(struct ssh *);
|
|||
int ssh_packet_write_wait(struct ssh *);
|
||||
int ssh_packet_have_data_to_write(struct ssh *);
|
||||
int ssh_packet_not_very_much_data_to_write(struct ssh *);
|
||||
int ssh_packet_interactive_data_to_write(struct ssh *);
|
||||
|
||||
int ssh_packet_connection_is_on_socket(struct ssh *);
|
||||
int ssh_packet_remaining(struct ssh *);
|
||||
|
|
64
readconf.c
64
readconf.c
|
@ -1,4 +1,4 @@
|
|||
/* $OpenBSD: readconf.c,v 1.380 2023/07/17 06:16:33 djm Exp $ */
|
||||
/* $OpenBSD: readconf.c,v 1.381 2023/08/28 03:31:16 djm Exp $ */
|
||||
/*
|
||||
* Author: Tatu Ylonen <ylo@cs.hut.fi>
|
||||
* Copyright (c) 1995 Tatu Ylonen <ylo@cs.hut.fi>, Espoo, Finland
|
||||
|
@ -179,7 +179,7 @@ typedef enum {
|
|||
oFingerprintHash, oUpdateHostkeys, oHostbasedAcceptedAlgorithms,
|
||||
oPubkeyAcceptedAlgorithms, oCASignatureAlgorithms, oProxyJump,
|
||||
oSecurityKeyProvider, oKnownHostsCommand, oRequiredRSASize,
|
||||
oEnableEscapeCommandline,
|
||||
oEnableEscapeCommandline, oObscureKeystrokeTiming,
|
||||
oIgnore, oIgnoredUnknownOption, oDeprecated, oUnsupported
|
||||
} OpCodes;
|
||||
|
||||
|
@ -328,6 +328,7 @@ static struct {
|
|||
{ "knownhostscommand", oKnownHostsCommand },
|
||||
{ "requiredrsasize", oRequiredRSASize },
|
||||
{ "enableescapecommandline", oEnableEscapeCommandline },
|
||||
{ "obscurekeystroketiming", oObscureKeystrokeTiming },
|
||||
|
||||
{ NULL, oBadOption }
|
||||
};
|
||||
|
@ -2291,6 +2292,48 @@ parse_pubkey_algos:
|
|||
intptr = &options->required_rsa_size;
|
||||
goto parse_int;
|
||||
|
||||
case oObscureKeystrokeTiming:
|
||||
value = -1;
|
||||
while ((arg = argv_next(&ac, &av)) != NULL) {
|
||||
if (value != -1) {
|
||||
error("%s line %d: invalid arguments",
|
||||
filename, linenum);
|
||||
goto out;
|
||||
}
|
||||
if (strcmp(arg, "yes") == 0 ||
|
||||
strcmp(arg, "true") == 0)
|
||||
value = SSH_KEYSTROKE_DEFAULT_INTERVAL_MS;
|
||||
else if (strcmp(arg, "no") == 0 ||
|
||||
strcmp(arg, "false") == 0)
|
||||
value = 0;
|
||||
else if (strncmp(arg, "interval:", 9) == 0) {
|
||||
if ((errstr = atoi_err(arg + 9,
|
||||
&value)) != NULL) {
|
||||
error("%s line %d: integer value %s.",
|
||||
filename, linenum, errstr);
|
||||
goto out;
|
||||
}
|
||||
if (value <= 0 || value > 1000) {
|
||||
error("%s line %d: value out of range.",
|
||||
filename, linenum);
|
||||
goto out;
|
||||
}
|
||||
} else {
|
||||
error("%s line %d: unsupported argument \"%s\"",
|
||||
filename, linenum, arg);
|
||||
goto out;
|
||||
}
|
||||
}
|
||||
if (value == -1) {
|
||||
error("%s line %d: missing argument",
|
||||
filename, linenum);
|
||||
goto out;
|
||||
}
|
||||
intptr = &options->obscure_keystroke_timing_interval;
|
||||
if (*activep && *intptr == -1)
|
||||
*intptr = value;
|
||||
break;
|
||||
|
||||
case oDeprecated:
|
||||
debug("%s line %d: Deprecated option \"%s\"",
|
||||
filename, linenum, keyword);
|
||||
|
@ -2552,6 +2595,7 @@ initialize_options(Options * options)
|
|||
options->known_hosts_command = NULL;
|
||||
options->required_rsa_size = -1;
|
||||
options->enable_escape_commandline = -1;
|
||||
options->obscure_keystroke_timing_interval = -1;
|
||||
options->tag = NULL;
|
||||
}
|
||||
|
||||
|
@ -2753,6 +2797,10 @@ fill_default_options(Options * options)
|
|||
options->required_rsa_size = SSH_RSA_MINIMUM_MODULUS_SIZE;
|
||||
if (options->enable_escape_commandline == -1)
|
||||
options->enable_escape_commandline = 0;
|
||||
if (options->obscure_keystroke_timing_interval == -1) {
|
||||
options->obscure_keystroke_timing_interval =
|
||||
SSH_KEYSTROKE_DEFAULT_INTERVAL_MS;
|
||||
}
|
||||
|
||||
/* Expand KEX name lists */
|
||||
all_cipher = cipher_alg_list(',', 0);
|
||||
|
@ -3299,6 +3347,16 @@ lookup_opcode_name(OpCodes code)
|
|||
static void
|
||||
dump_cfg_int(OpCodes code, int val)
|
||||
{
|
||||
if (code == oObscureKeystrokeTiming) {
|
||||
if (val == 0) {
|
||||
printf("%s no\n", lookup_opcode_name(code));
|
||||
return;
|
||||
} else if (val == SSH_KEYSTROKE_DEFAULT_INTERVAL_MS) {
|
||||
printf("%s yes\n", lookup_opcode_name(code));
|
||||
return;
|
||||
}
|
||||
/* FALLTHROUGH */
|
||||
}
|
||||
printf("%s %d\n", lookup_opcode_name(code), val);
|
||||
}
|
||||
|
||||
|
@ -3449,6 +3507,8 @@ dump_client_config(Options *o, const char *host)
|
|||
dump_cfg_int(oServerAliveCountMax, o->server_alive_count_max);
|
||||
dump_cfg_int(oServerAliveInterval, o->server_alive_interval);
|
||||
dump_cfg_int(oRequiredRSASize, o->required_rsa_size);
|
||||
dump_cfg_int(oObscureKeystrokeTiming,
|
||||
o->obscure_keystroke_timing_interval);
|
||||
|
||||
/* String options */
|
||||
dump_cfg_string(oBindAddress, o->bind_address);
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
/* $OpenBSD: readconf.h,v 1.151 2023/07/17 04:08:31 djm Exp $ */
|
||||
/* $OpenBSD: readconf.h,v 1.152 2023/08/28 03:31:16 djm Exp $ */
|
||||
|
||||
/*
|
||||
* Author: Tatu Ylonen <ylo@cs.hut.fi>
|
||||
|
@ -180,6 +180,7 @@ typedef struct {
|
|||
|
||||
int required_rsa_size; /* minimum size of RSA keys */
|
||||
int enable_escape_commandline; /* ~C commandline */
|
||||
int obscure_keystroke_timing_interval;
|
||||
|
||||
char *ignored_unknown; /* Pattern list of unknown tokens to ignore */
|
||||
} Options;
|
||||
|
@ -222,6 +223,11 @@ typedef struct {
|
|||
#define SSH_STRICT_HOSTKEY_YES 2
|
||||
#define SSH_STRICT_HOSTKEY_ASK 3
|
||||
|
||||
/* ObscureKeystrokes parameters */
|
||||
#define SSH_KEYSTROKE_DEFAULT_INTERVAL_MS 20
|
||||
#define SSH_KEYSTROKE_CHAFF_MIN_MS 1024
|
||||
#define SSH_KEYSTROKE_CHAFF_RNG_MS 2048
|
||||
|
||||
const char *kex_default_pk_alg(void);
|
||||
char *ssh_connection_hash(const char *thishost, const char *host,
|
||||
const char *portstr, const char *user);
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
# $OpenBSD: Makefile,v 1.125 2023/05/17 05:52:01 djm Exp $
|
||||
# $OpenBSD: Makefile,v 1.126 2023/09/06 23:36:09 djm Exp $
|
||||
|
||||
tests: prep file-tests t-exec unit
|
||||
|
||||
|
@ -103,7 +103,8 @@ LTESTS= connect \
|
|||
agent-restrict \
|
||||
hostbased \
|
||||
channel-timeout \
|
||||
connection-timeout
|
||||
connection-timeout \
|
||||
match-subsystem
|
||||
|
||||
INTEROP_TESTS= putty-transfer putty-ciphers putty-kex conch-ciphers
|
||||
#INTEROP_TESTS+=ssh-com ssh-com-client ssh-com-keygen ssh-com-sftp
|
||||
|
|
|
@ -0,0 +1,90 @@
|
|||
# $OpenBSD: match-subsystem.sh,v 1.1 2023/09/06 23:36:09 djm Exp $
|
||||
# Placed in the Public Domain.
|
||||
|
||||
tid="sshd_config match subsystem"
|
||||
|
||||
cp $OBJ/sshd_proxy $OBJ/sshd_proxy_bak
|
||||
|
||||
try_subsystem() {
|
||||
_id=$1
|
||||
_subsystem=$2
|
||||
_expect=$3
|
||||
${SSHD} -tf $OBJ/sshd_proxy || fatal "$_id: bad config"
|
||||
${SSH} -sF $OBJ/ssh_proxy somehost $_subsystem
|
||||
_exit=$?
|
||||
trace "$_id subsystem $_subsystem"
|
||||
if [ $_exit -ne $_expect ] ; then
|
||||
fail "$_id: subsystem $_subsystem exit $_exit expected $_expect"
|
||||
fi
|
||||
return $?
|
||||
}
|
||||
|
||||
# Simple case: subsystem in main config.
|
||||
cp $OBJ/sshd_proxy_bak $OBJ/sshd_proxy
|
||||
cat >> $OBJ/sshd_proxy << _EOF
|
||||
Subsystem xxx /bin/sh -c "exit 23"
|
||||
_EOF
|
||||
try_subsystem "main config" xxx 23
|
||||
|
||||
# No clobber in main config.
|
||||
cp $OBJ/sshd_proxy_bak $OBJ/sshd_proxy
|
||||
cat >> $OBJ/sshd_proxy << _EOF
|
||||
Subsystem xxx /bin/sh -c "exit 23"
|
||||
Subsystem xxx /bin/sh -c "exit 24"
|
||||
_EOF
|
||||
try_subsystem "main config no clobber" xxx 23
|
||||
|
||||
# Subsystem in match all block
|
||||
cp $OBJ/sshd_proxy_bak $OBJ/sshd_proxy
|
||||
cat >> $OBJ/sshd_proxy << _EOF
|
||||
Match all
|
||||
Subsystem xxx /bin/sh -c "exit 21"
|
||||
_EOF
|
||||
try_subsystem "match all" xxx 21
|
||||
|
||||
# No clobber in match all block
|
||||
cp $OBJ/sshd_proxy_bak $OBJ/sshd_proxy
|
||||
cat >> $OBJ/sshd_proxy << _EOF
|
||||
Match all
|
||||
Subsystem xxx /bin/sh -c "exit 21"
|
||||
Subsystem xxx /bin/sh -c "exit 24"
|
||||
_EOF
|
||||
try_subsystem "match all no clobber" xxx 21
|
||||
|
||||
# Subsystem in match user block
|
||||
cp $OBJ/sshd_proxy_bak $OBJ/sshd_proxy
|
||||
cat >> $OBJ/sshd_proxy << _EOF
|
||||
Match user *
|
||||
Subsystem xxx /bin/sh -c "exit 20"
|
||||
_EOF
|
||||
try_subsystem "match user" xxx 20
|
||||
|
||||
# No clobber in match user block
|
||||
cp $OBJ/sshd_proxy_bak $OBJ/sshd_proxy
|
||||
cat >> $OBJ/sshd_proxy << _EOF
|
||||
Match user *
|
||||
Subsystem xxx /bin/sh -c "exit 20"
|
||||
Subsystem xxx /bin/sh -c "exit 24"
|
||||
Match all
|
||||
Subsystem xxx /bin/sh -c "exit 24"
|
||||
_EOF
|
||||
try_subsystem "match user no clobber" xxx 20
|
||||
|
||||
# Override main with match all
|
||||
cp $OBJ/sshd_proxy_bak $OBJ/sshd_proxy
|
||||
cat >> $OBJ/sshd_proxy << _EOF
|
||||
Subsystem xxx /bin/sh -c "exit 23"
|
||||
Match all
|
||||
Subsystem xxx /bin/sh -c "exit 19"
|
||||
_EOF
|
||||
try_subsystem "match all override" xxx 19
|
||||
|
||||
# Override main with match user
|
||||
cp $OBJ/sshd_proxy_bak $OBJ/sshd_proxy
|
||||
cat >> $OBJ/sshd_proxy << _EOF
|
||||
Subsystem xxx /bin/sh -c "exit 23"
|
||||
Match user *
|
||||
Subsystem xxx /bin/sh -c "exit 18"
|
||||
_EOF
|
||||
try_subsystem "match user override" xxx 18
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
# $OpenBSD: scp.sh,v 1.18 2023/01/13 04:47:34 dtucker Exp $
|
||||
# $OpenBSD: scp.sh,v 1.19 2023/09/08 05:50:57 djm Exp $
|
||||
# Placed in the Public Domain.
|
||||
|
||||
tid="scp"
|
||||
|
@ -30,22 +30,26 @@ scpclean() {
|
|||
chmod 755 ${DIR} ${DIR2} ${DIR3}
|
||||
}
|
||||
|
||||
# Create directory structure for recursive copy tests.
|
||||
forest() {
|
||||
scpclean
|
||||
rm -rf ${DIR2}
|
||||
cp ${DATA} ${DIR}/copy
|
||||
ln -s ${DIR}/copy ${DIR}/copy-sym
|
||||
mkdir ${DIR}/subdir
|
||||
cp ${DATA} ${DIR}/subdir/copy
|
||||
ln -s ${DIR}/subdir ${DIR}/subdir-sym
|
||||
}
|
||||
|
||||
for mode in scp sftp ; do
|
||||
tag="$tid: $mode mode"
|
||||
# scpopts should be an array to preverse the double quotes
|
||||
if [ "$os" == "windows" ]; then
|
||||
if test $mode = scp ; then
|
||||
scpopts=(-O -q -S "$TEST_SHELL_PATH ${OBJ}/scp-ssh-wrapper.scp")
|
||||
else
|
||||
scpopts=(-s -D ${SFTPSERVER})
|
||||
fi
|
||||
scpopts=(-qs -D ${SFTPSERVER})
|
||||
else
|
||||
if test $mode = scp ; then
|
||||
scpopts="-O -q -S ${OBJ}/scp-ssh-wrapper.scp"
|
||||
else
|
||||
scpopts="-s -D ${SFTPSERVER}"
|
||||
fi
|
||||
scpopts="-qs -D ${SFTPSERVER}"
|
||||
fi
|
||||
|
||||
verbose "$tag: simple copy local file to local file"
|
||||
scpclean
|
||||
$SCP "${scpopts[@]}" ${DATA} ${COPY} 2>&1 1>/dev/null || fail "copy failed"
|
||||
|
@ -107,21 +111,19 @@ for mode in scp sftp ; do
|
|||
cmp ${COPY} ${DIR}/copy || fail "corrupted copy"
|
||||
|
||||
verbose "$tag: recursive local dir to remote dir"
|
||||
scpclean
|
||||
rm -rf ${DIR2}
|
||||
cp ${DATA} ${DIR}/copy
|
||||
$SCP "${scpopts[@]}" -r ${DIR} somehost:${DIR2} || fail "copy failed"
|
||||
forest
|
||||
$SCP $scpopts -r ${DIR} somehost:${DIR2} || fail "copy failed"
|
||||
diff ${DIFFOPT} ${DIR} ${DIR2} || fail "corrupted copy"
|
||||
|
||||
verbose "$tag: recursive local dir to local dir"
|
||||
scpclean
|
||||
forest
|
||||
rm -rf ${DIR2}
|
||||
cp ${DATA} ${DIR}/copy
|
||||
$SCP "${scpopts[@]}" -r ${DIR} ${DIR2} 2>&1 1>/dev/null || fail "copy failed"
|
||||
diff ${DIFFOPT} ${DIR} ${DIR2} || fail "corrupted copy"
|
||||
|
||||
verbose "$tag: recursive remote dir to local dir"
|
||||
scpclean
|
||||
forest
|
||||
rm -rf ${DIR2}
|
||||
cp ${DATA} ${DIR}/copy
|
||||
$SCP "${scpopts[@]}" -r somehost:${DIR} ${DIR2} || fail "copy failed"
|
||||
|
|
|
@ -1,10 +1,8 @@
|
|||
# $OpenBSD: scp3.sh,v 1.4 2023/01/13 04:47:34 dtucker Exp $
|
||||
# $OpenBSD: scp3.sh,v 1.5 2023/09/08 06:10:57 djm Exp $
|
||||
# Placed in the Public Domain.
|
||||
|
||||
tid="scp3"
|
||||
|
||||
set -x
|
||||
|
||||
COPY2=${OBJ}/copy2
|
||||
DIR=${COPY}.dd
|
||||
DIR2=${COPY}.dd2
|
||||
|
@ -22,6 +20,17 @@ scpclean() {
|
|||
chmod 755 ${DIR} ${DIR2}
|
||||
}
|
||||
|
||||
# Create directory structure for recursive copy tests.
|
||||
forest() {
|
||||
scpclean
|
||||
rm -rf ${DIR2}
|
||||
cp ${DATA} ${DIR}/copy
|
||||
ln -s ${DIR}/copy ${DIR}/copy-sym
|
||||
mkdir ${DIR}/subdir
|
||||
cp ${DATA} ${DIR}/subdir/copy
|
||||
ln -s ${DIR}/subdir ${DIR}/subdir-sym
|
||||
}
|
||||
|
||||
for mode in scp sftp ; do
|
||||
tag="$tid: $mode mode"
|
||||
|
||||
|
@ -53,10 +62,8 @@ for mode in scp sftp ; do
|
|||
cmp ${COPY} ${DIR}/copy || fail "corrupted copy"
|
||||
|
||||
verbose "$tag: recursive remote dir to remote dir"
|
||||
scpclean
|
||||
rm -rf ${DIR2}
|
||||
cp ${DATA} ${DIR}/copy
|
||||
$SCP "${scpopts[@]}" -3r hostA:${DIR} hostB:${DIR2} || fail "copy failed"
|
||||
forest
|
||||
$SCP $scpopts -3r hostA:${DIR} hostB:${DIR2} || fail "copy failed"
|
||||
diff -r ${DIR} ${DIR2} || fail "corrupted copy"
|
||||
diff -r ${DIR2} ${DIR} || fail "corrupted copy"
|
||||
|
||||
|
|
52
scp.c
52
scp.c
|
@ -1,4 +1,4 @@
|
|||
/* $OpenBSD: scp.c,v 1.257 2023/07/14 05:31:44 djm Exp $ */
|
||||
/* $OpenBSD: scp.c,v 1.259 2023/09/10 23:12:32 djm Exp $ */
|
||||
/*
|
||||
* scp - secure remote copy. This is basically patched BSD rcp which
|
||||
* uses ssh to do the data transfer (instead of using rcmd).
|
||||
|
@ -198,7 +198,7 @@ size_t sftp_nrequests;
|
|||
/* Needed for sftp */
|
||||
volatile sig_atomic_t interrupted = 0;
|
||||
|
||||
int remote_glob(struct sftp_conn *, const char *, int,
|
||||
int sftp_glob(struct sftp_conn *, const char *, int,
|
||||
int (*)(const char *, int), glob_t *); /* proto for sftp-glob.c */
|
||||
|
||||
static void
|
||||
|
@ -1219,8 +1219,8 @@ do_sftp_connect(char *host, char *user, int port, char *sftp_direct,
|
|||
reminp, remoutp, pidp) < 0)
|
||||
return NULL;
|
||||
}
|
||||
return do_init(*reminp, *remoutp,
|
||||
sftp_copy_buflen, sftp_nrequests, limit_kbps);
|
||||
return sftp_init(*reminp, *remoutp,
|
||||
sftp_copy_buflen, sftp_nrequests, limit_kbps);
|
||||
}
|
||||
|
||||
void
|
||||
|
@ -1558,8 +1558,8 @@ prepare_remote_path(struct sftp_conn *conn, const char *path)
|
|||
return xstrdup(".");
|
||||
return xstrdup(path + 2 + nslash);
|
||||
}
|
||||
if (can_expand_path(conn))
|
||||
return do_expand_path(conn, path);
|
||||
if (sftp_can_expand_path(conn))
|
||||
return sftp_expand_path(conn, path);
|
||||
/* No protocol extension */
|
||||
error("server expand-path extension is required "
|
||||
"for ~user paths in SFTP mode");
|
||||
|
@ -1587,17 +1587,17 @@ source_sftp(int argc, char *src, char *targ, struct sftp_conn *conn)
|
|||
*/
|
||||
if ((target = prepare_remote_path(conn, targ)) == NULL)
|
||||
cleanup_exit(255);
|
||||
target_is_dir = remote_is_dir(conn, target);
|
||||
target_is_dir = sftp_remote_is_dir(conn, target);
|
||||
if (targetshouldbedirectory && !target_is_dir) {
|
||||
debug("target directory \"%s\" does not exist", target);
|
||||
a.flags = SSH2_FILEXFER_ATTR_PERMISSIONS;
|
||||
a.perm = st.st_mode | 0700; /* ensure writable */
|
||||
if (do_mkdir(conn, target, &a, 1) != 0)
|
||||
if (sftp_mkdir(conn, target, &a, 1) != 0)
|
||||
cleanup_exit(255); /* error already logged */
|
||||
target_is_dir = 1;
|
||||
}
|
||||
if (target_is_dir)
|
||||
abs_dst = path_append(target, filename);
|
||||
abs_dst = sftp_path_append(target, filename);
|
||||
else {
|
||||
abs_dst = target;
|
||||
target = NULL;
|
||||
|
@ -1605,12 +1605,12 @@ source_sftp(int argc, char *src, char *targ, struct sftp_conn *conn)
|
|||
debug3_f("copying local %s to remote %s", src, abs_dst);
|
||||
|
||||
if (src_is_dir && iamrecursive) {
|
||||
if (upload_dir(conn, src, abs_dst, pflag,
|
||||
if (sftp_upload_dir(conn, src, abs_dst, pflag,
|
||||
SFTP_PROGRESS_ONLY, 0, 0, 1, 1) != 0) {
|
||||
error("failed to upload directory %s to %s", src, targ);
|
||||
errs = 1;
|
||||
}
|
||||
} else if (do_upload(conn, src, abs_dst, pflag, 0, 0, 1) != 0) {
|
||||
} else if (sftp_upload(conn, src, abs_dst, pflag, 0, 0, 1) != 0) {
|
||||
error("failed to upload file %s to %s", src, targ);
|
||||
errs = 1;
|
||||
}
|
||||
|
@ -1881,7 +1881,7 @@ sink_sftp(int argc, char *dst, const char *src, struct sftp_conn *conn)
|
|||
}
|
||||
|
||||
debug3_f("copying remote %s to local %s", abs_src, dst);
|
||||
if ((r = remote_glob(conn, abs_src, GLOB_NOCHECK|GLOB_MARK,
|
||||
if ((r = sftp_glob(conn, abs_src, GLOB_NOCHECK|GLOB_MARK,
|
||||
NULL, &g)) != 0) {
|
||||
if (r == GLOB_NOSPACE)
|
||||
error("%s: too many glob matches", src);
|
||||
|
@ -1898,7 +1898,7 @@ sink_sftp(int argc, char *dst, const char *src, struct sftp_conn *conn)
|
|||
* a GLOB_NOCHECK result. Check whether the unglobbed path
|
||||
* exists so we can give a nice error message early.
|
||||
*/
|
||||
if (do_stat(conn, g.gl_pathv[0], 1) == NULL) {
|
||||
if (sftp_stat(conn, g.gl_pathv[0], 1, NULL) != 0) {
|
||||
error("%s: %s", src, strerror(ENOENT));
|
||||
err = -1;
|
||||
goto out;
|
||||
|
@ -1934,17 +1934,17 @@ sink_sftp(int argc, char *dst, const char *src, struct sftp_conn *conn)
|
|||
}
|
||||
|
||||
if (dst_is_dir)
|
||||
abs_dst = path_append(dst, filename);
|
||||
abs_dst = sftp_path_append(dst, filename);
|
||||
else
|
||||
abs_dst = xstrdup(dst);
|
||||
|
||||
debug("Fetching %s to %s\n", g.gl_pathv[i], abs_dst);
|
||||
if (globpath_is_dir(g.gl_pathv[i]) && iamrecursive) {
|
||||
if (download_dir(conn, g.gl_pathv[i], abs_dst, NULL,
|
||||
pflag, SFTP_PROGRESS_ONLY, 0, 0, 1, 1) == -1)
|
||||
if (sftp_globpath_is_dir(g.gl_pathv[i]) && iamrecursive) {
|
||||
if (sftp_download_dir(conn, g.gl_pathv[i], abs_dst,
|
||||
NULL, pflag, SFTP_PROGRESS_ONLY, 0, 0, 1, 1) == -1)
|
||||
err = -1;
|
||||
} else {
|
||||
if (do_download(conn, g.gl_pathv[i], abs_dst, NULL,
|
||||
if (sftp_download(conn, g.gl_pathv[i], abs_dst, NULL,
|
||||
pflag, 0, 0, 1) == -1)
|
||||
err = -1;
|
||||
}
|
||||
|
@ -2329,7 +2329,7 @@ throughlocal_sftp(struct sftp_conn *from, struct sftp_conn *to,
|
|||
cleanup_exit(255);
|
||||
memset(&g, 0, sizeof(g));
|
||||
|
||||
targetisdir = remote_is_dir(to, target);
|
||||
targetisdir = sftp_remote_is_dir(to, target);
|
||||
if (!targetisdir && targetshouldbedirectory) {
|
||||
error("%s: destination is not a directory", targ);
|
||||
err = -1;
|
||||
|
@ -2337,7 +2337,7 @@ throughlocal_sftp(struct sftp_conn *from, struct sftp_conn *to,
|
|||
}
|
||||
|
||||
debug3_f("copying remote %s to remote %s", abs_src, target);
|
||||
if ((r = remote_glob(from, abs_src, GLOB_NOCHECK|GLOB_MARK,
|
||||
if ((r = sftp_glob(from, abs_src, GLOB_NOCHECK|GLOB_MARK,
|
||||
NULL, &g)) != 0) {
|
||||
if (r == GLOB_NOSPACE)
|
||||
error("%s: too many glob matches", src);
|
||||
|
@ -2354,7 +2354,7 @@ throughlocal_sftp(struct sftp_conn *from, struct sftp_conn *to,
|
|||
* a GLOB_NOCHECK result. Check whether the unglobbed path
|
||||
* exists so we can give a nice error message early.
|
||||
*/
|
||||
if (do_stat(from, g.gl_pathv[0], 1) == NULL) {
|
||||
if (sftp_stat(from, g.gl_pathv[0], 1, NULL) != 0) {
|
||||
error("%s: %s", src, strerror(ENOENT));
|
||||
err = -1;
|
||||
goto out;
|
||||
|
@ -2370,18 +2370,18 @@ throughlocal_sftp(struct sftp_conn *from, struct sftp_conn *to,
|
|||
}
|
||||
|
||||
if (targetisdir)
|
||||
abs_dst = path_append(target, filename);
|
||||
abs_dst = sftp_path_append(target, filename);
|
||||
else
|
||||
abs_dst = xstrdup(target);
|
||||
|
||||
debug("Fetching %s to %s\n", g.gl_pathv[i], abs_dst);
|
||||
if (globpath_is_dir(g.gl_pathv[i]) && iamrecursive) {
|
||||
if (crossload_dir(from, to, g.gl_pathv[i], abs_dst,
|
||||
if (sftp_globpath_is_dir(g.gl_pathv[i]) && iamrecursive) {
|
||||
if (sftp_crossload_dir(from, to, g.gl_pathv[i], abs_dst,
|
||||
NULL, pflag, SFTP_PROGRESS_ONLY, 1) == -1)
|
||||
err = -1;
|
||||
} else {
|
||||
if (do_crossload(from, to, g.gl_pathv[i], abs_dst, NULL,
|
||||
pflag) == -1)
|
||||
if (sftp_crossload(from, to, g.gl_pathv[i], abs_dst,
|
||||
NULL, pflag) == -1)
|
||||
err = -1;
|
||||
}
|
||||
free(abs_dst);
|
||||
|
|
107
servconf.c
107
servconf.c
|
@ -1,4 +1,4 @@
|
|||
/* $OpenBSD: servconf.c,v 1.396 2023/07/17 05:26:38 djm Exp $ */
|
||||
/* $OpenBSD: servconf.c,v 1.402 2023/09/08 06:34:24 djm Exp $ */
|
||||
/*
|
||||
* Copyright (c) 1995 Tatu Ylonen <ylo@cs.hut.fi>, Espoo, Finland
|
||||
* All rights reserved
|
||||
|
@ -645,7 +645,7 @@ static struct {
|
|||
{ "macs", sMacs, SSHCFG_GLOBAL },
|
||||
{ "protocol", sIgnore, SSHCFG_GLOBAL },
|
||||
{ "gatewayports", sGatewayPorts, SSHCFG_ALL },
|
||||
{ "subsystem", sSubsystem, SSHCFG_GLOBAL },
|
||||
{ "subsystem", sSubsystem, SSHCFG_ALL },
|
||||
{ "maxstartups", sMaxStartups, SSHCFG_GLOBAL },
|
||||
{ "persourcemaxstartups", sPerSourceMaxStartups, SSHCFG_GLOBAL },
|
||||
{ "persourcenetblocksize", sPerSourceNetBlockSize, SSHCFG_GLOBAL },
|
||||
|
@ -1937,39 +1937,54 @@ process_server_config_line_depth(ServerOptions *options, char *line,
|
|||
break;
|
||||
|
||||
case sSubsystem:
|
||||
if (options->num_subsystems >= MAX_SUBSYSTEMS) {
|
||||
fatal("%s line %d: too many subsystems defined.",
|
||||
filename, linenum);
|
||||
}
|
||||
arg = argv_next(&ac, &av);
|
||||
if (!arg || *arg == '\0')
|
||||
fatal("%s line %d: %s missing argument.",
|
||||
filename, linenum, keyword);
|
||||
if (!*activep) {
|
||||
arg = argv_next(&ac, &av);
|
||||
argv_consume(&ac);
|
||||
break;
|
||||
}
|
||||
for (i = 0; i < options->num_subsystems; i++)
|
||||
if (strcmp(arg, options->subsystem_name[i]) == 0)
|
||||
fatal("%s line %d: Subsystem '%s' "
|
||||
"already defined.", filename, linenum, arg);
|
||||
found = 0;
|
||||
for (i = 0; i < options->num_subsystems; i++) {
|
||||
if (strcmp(arg, options->subsystem_name[i]) == 0) {
|
||||
found = 1;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (found) {
|
||||
debug("%s line %d: Subsystem '%s' already defined.",
|
||||
filename, linenum, arg);
|
||||
argv_consume(&ac);
|
||||
break;
|
||||
}
|
||||
options->subsystem_name = xrecallocarray(
|
||||
options->subsystem_name, options->num_subsystems,
|
||||
options->num_subsystems + 1,
|
||||
sizeof(*options->subsystem_name));
|
||||
options->subsystem_command = xrecallocarray(
|
||||
options->subsystem_command, options->num_subsystems,
|
||||
options->num_subsystems + 1,
|
||||
sizeof(*options->subsystem_command));
|
||||
options->subsystem_args = xrecallocarray(
|
||||
options->subsystem_args, options->num_subsystems,
|
||||
options->num_subsystems + 1,
|
||||
sizeof(*options->subsystem_args));
|
||||
options->subsystem_name[options->num_subsystems] = xstrdup(arg);
|
||||
arg = argv_next(&ac, &av);
|
||||
if (!arg || *arg == '\0')
|
||||
if (!arg || *arg == '\0') {
|
||||
fatal("%s line %d: Missing subsystem command.",
|
||||
filename, linenum);
|
||||
options->subsystem_command[options->num_subsystems] = xstrdup(arg);
|
||||
|
||||
/* Collect arguments (separate to executable) */
|
||||
p = xstrdup(arg);
|
||||
len = strlen(p) + 1;
|
||||
while ((arg = argv_next(&ac, &av)) != NULL) {
|
||||
len += 1 + strlen(arg);
|
||||
p = xreallocarray(p, 1, len);
|
||||
strlcat(p, " ", len);
|
||||
strlcat(p, arg, len);
|
||||
}
|
||||
options->subsystem_args[options->num_subsystems] = p;
|
||||
options->subsystem_command[options->num_subsystems] =
|
||||
xstrdup(arg);
|
||||
/* Collect arguments (separate to executable) */
|
||||
arg = argv_assemble(1, &arg); /* quote command correctly */
|
||||
arg2 = argv_assemble(ac, av); /* rest of command */
|
||||
xasprintf(&options->subsystem_args[options->num_subsystems],
|
||||
"%s %s", arg, arg2);
|
||||
free(arg2);
|
||||
argv_consume(&ac);
|
||||
options->num_subsystems++;
|
||||
break;
|
||||
|
||||
|
@ -2034,7 +2049,7 @@ process_server_config_line_depth(ServerOptions *options, char *line,
|
|||
fatal("%s line %d: %s integer value %s.",
|
||||
filename, linenum, keyword, errstr);
|
||||
}
|
||||
if (*activep)
|
||||
if (*activep && options->per_source_max_startups == -1)
|
||||
options->per_source_max_startups = value;
|
||||
break;
|
||||
|
||||
|
@ -2690,6 +2705,47 @@ int parse_server_match_testspec(struct connection_info *ci, char *spec)
|
|||
return 0;
|
||||
}
|
||||
|
||||
void
|
||||
servconf_merge_subsystems(ServerOptions *dst, ServerOptions *src)
|
||||
{
|
||||
u_int i, j, found;
|
||||
|
||||
for (i = 0; i < src->num_subsystems; i++) {
|
||||
found = 0;
|
||||
for (j = 0; j < dst->num_subsystems; j++) {
|
||||
if (strcmp(src->subsystem_name[i],
|
||||
dst->subsystem_name[j]) == 0) {
|
||||
found = 1;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (found) {
|
||||
debug_f("override \"%s\"", dst->subsystem_name[j]);
|
||||
free(dst->subsystem_command[j]);
|
||||
free(dst->subsystem_args[j]);
|
||||
dst->subsystem_command[j] =
|
||||
xstrdup(src->subsystem_command[i]);
|
||||
dst->subsystem_args[j] =
|
||||
xstrdup(src->subsystem_args[i]);
|
||||
continue;
|
||||
}
|
||||
debug_f("add \"%s\"", src->subsystem_name[i]);
|
||||
dst->subsystem_name = xrecallocarray(
|
||||
dst->subsystem_name, dst->num_subsystems,
|
||||
dst->num_subsystems + 1, sizeof(*dst->subsystem_name));
|
||||
dst->subsystem_command = xrecallocarray(
|
||||
dst->subsystem_command, dst->num_subsystems,
|
||||
dst->num_subsystems + 1, sizeof(*dst->subsystem_command));
|
||||
dst->subsystem_args = xrecallocarray(
|
||||
dst->subsystem_args, dst->num_subsystems,
|
||||
dst->num_subsystems + 1, sizeof(*dst->subsystem_args));
|
||||
j = dst->num_subsystems++;
|
||||
dst->subsystem_name[j] = xstrdup(src->subsystem_name[i]);
|
||||
dst->subsystem_command[j] = xstrdup(src->subsystem_command[i]);
|
||||
dst->subsystem_args[j] = xstrdup(src->subsystem_args[i]);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Copy any supported values that are set.
|
||||
*
|
||||
|
@ -2796,6 +2852,9 @@ copy_set_server_options(ServerOptions *dst, ServerOptions *src, int preauth)
|
|||
free(dst->chroot_directory);
|
||||
dst->chroot_directory = NULL;
|
||||
}
|
||||
|
||||
/* Subsystems require merging. */
|
||||
servconf_merge_subsystems(dst, src);
|
||||
}
|
||||
|
||||
#undef M_CP_INTOPT
|
||||
|
|
14
servconf.h
14
servconf.h
|
@ -1,4 +1,4 @@
|
|||
/* $OpenBSD: servconf.h,v 1.159 2023/01/17 09:44:48 djm Exp $ */
|
||||
/* $OpenBSD: servconf.h,v 1.160 2023/09/06 23:35:35 djm Exp $ */
|
||||
|
||||
/*
|
||||
* Author: Tatu Ylonen <ylo@cs.hut.fi>
|
||||
|
@ -20,8 +20,6 @@
|
|||
|
||||
#define MAX_PORTS 256 /* Max # ports. */
|
||||
|
||||
#define MAX_SUBSYSTEMS 256 /* Max # subsystems. */
|
||||
|
||||
/* permit_root_login */
|
||||
#define PERMIT_NOT_SET -1
|
||||
#define PERMIT_NO 0
|
||||
|
@ -165,9 +163,9 @@ typedef struct {
|
|||
char **deny_groups;
|
||||
|
||||
u_int num_subsystems;
|
||||
char *subsystem_name[MAX_SUBSYSTEMS];
|
||||
char *subsystem_command[MAX_SUBSYSTEMS];
|
||||
char *subsystem_args[MAX_SUBSYSTEMS];
|
||||
char **subsystem_name;
|
||||
char **subsystem_command;
|
||||
char **subsystem_args;
|
||||
|
||||
u_int num_accept_env;
|
||||
char **accept_env;
|
||||
|
@ -294,6 +292,9 @@ TAILQ_HEAD(include_list, include_item);
|
|||
M_CP_STRARRAYOPT(permitted_listens, num_permitted_listens); \
|
||||
M_CP_STRARRAYOPT(channel_timeouts, num_channel_timeouts); \
|
||||
M_CP_STRARRAYOPT(log_verbose, num_log_verbose); \
|
||||
M_CP_STRARRAYOPT(subsystem_name, num_subsystems); \
|
||||
M_CP_STRARRAYOPT(subsystem_command, num_subsystems); \
|
||||
M_CP_STRARRAYOPT(subsystem_args, num_subsystems); \
|
||||
} while (0)
|
||||
|
||||
struct connection_info *get_connection_info(struct ssh *, int, int);
|
||||
|
@ -310,6 +311,7 @@ void parse_server_match_config(ServerOptions *,
|
|||
struct include_list *includes, struct connection_info *);
|
||||
int parse_server_match_testspec(struct connection_info *, char *);
|
||||
int server_match_spec_complete(struct connection_info *);
|
||||
void servconf_merge_subsystems(ServerOptions *, ServerOptions *);
|
||||
void copy_set_server_options(ServerOptions *, ServerOptions *, int);
|
||||
void dump_config(ServerOptions *);
|
||||
char *derelativise_path(const char *);
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
/* $OpenBSD: serverloop.c,v 1.236 2023/03/08 04:43:12 guenther Exp $ */
|
||||
/* $OpenBSD: serverloop.c,v 1.237 2023/08/21 04:59:54 djm Exp $ */
|
||||
/*
|
||||
* Author: Tatu Ylonen <ylo@cs.hut.fi>
|
||||
* Copyright (c) 1995 Tatu Ylonen <ylo@cs.hut.fi>, Espoo, Finland
|
||||
|
@ -253,7 +253,7 @@ wait_until_can_do_something(struct ssh *ssh,
|
|||
/* ClientAliveInterval probing */
|
||||
if (client_alive_scheduled) {
|
||||
if (ret == 0 &&
|
||||
now > last_client_time + options.client_alive_interval) {
|
||||
now >= last_client_time + options.client_alive_interval) {
|
||||
/* ppoll timed out and we're due to probe */
|
||||
client_alive_check(ssh);
|
||||
last_client_time = now;
|
||||
|
|
15
session.c
15
session.c
|
@ -1,4 +1,4 @@
|
|||
/* $OpenBSD: session.c,v 1.335 2023/03/07 06:09:14 dtucker Exp $ */
|
||||
/* $OpenBSD: session.c,v 1.336 2023/08/10 23:05:48 djm Exp $ */
|
||||
/*
|
||||
* Copyright (c) 1995 Tatu Ylonen <ylo@cs.hut.fi>, Espoo, Finland
|
||||
* All rights reserved
|
||||
|
@ -2404,17 +2404,17 @@ session_exit_message(struct ssh *ssh, Session *s, int status)
|
|||
{
|
||||
Channel *c;
|
||||
int r;
|
||||
char *note = NULL;
|
||||
|
||||
if ((c = channel_lookup(ssh, s->chanid)) == NULL)
|
||||
fatal_f("session %d: no channel %d", s->self, s->chanid);
|
||||
debug_f("session %d channel %d pid %ld",
|
||||
s->self, s->chanid, (long)s->pid);
|
||||
|
||||
if (WIFEXITED(status)) {
|
||||
channel_request_start(ssh, s->chanid, "exit-status", 0);
|
||||
if ((r = sshpkt_put_u32(ssh, WEXITSTATUS(status))) != 0 ||
|
||||
(r = sshpkt_send(ssh)) != 0)
|
||||
sshpkt_fatal(ssh, r, "%s: exit reply", __func__);
|
||||
xasprintf(¬e, "exit %d", WEXITSTATUS(status));
|
||||
} else if (WIFSIGNALED(status)) {
|
||||
channel_request_start(ssh, s->chanid, "exit-signal", 0);
|
||||
#ifndef WCOREDUMP
|
||||
|
@ -2426,11 +2426,18 @@ session_exit_message(struct ssh *ssh, Session *s, int status)
|
|||
(r = sshpkt_put_cstring(ssh, "")) != 0 ||
|
||||
(r = sshpkt_send(ssh)) != 0)
|
||||
sshpkt_fatal(ssh, r, "%s: exit reply", __func__);
|
||||
xasprintf(¬e, "signal %d%s", WTERMSIG(status),
|
||||
WCOREDUMP(status) ? " core dumped" : "");
|
||||
} else {
|
||||
/* Some weird exit cause. Just exit. */
|
||||
ssh_packet_disconnect(ssh, "wait returned status %04x.", status);
|
||||
ssh_packet_disconnect(ssh, "wait returned status %04x.",
|
||||
status);
|
||||
}
|
||||
|
||||
debug_f("session %d channel %d pid %ld %s", s->self, s->chanid,
|
||||
(long)s->pid, note == NULL ? "UNKNOWN" : note);
|
||||
free(note);
|
||||
|
||||
/* disconnect channel */
|
||||
debug_f("release channel %d", s->chanid);
|
||||
|
||||
|
|
409
sftp-client.c
409
sftp-client.c
File diff suppressed because it is too large
Load Diff
|
@ -1,4 +1,4 @@
|
|||
/* $OpenBSD: sftp-client.h,v 1.38 2022/09/19 10:43:12 djm Exp $ */
|
||||
/* $OpenBSD: sftp-client.h,v 1.39 2023/09/08 05:56:13 djm Exp $ */
|
||||
|
||||
/*
|
||||
* Copyright (c) 2001-2004 Damien Miller <djm@openbsd.org>
|
||||
|
@ -70,107 +70,106 @@ struct sftp_limits {
|
|||
* Initialise a SSH filexfer connection. Returns NULL on error or
|
||||
* a pointer to a initialized sftp_conn struct on success.
|
||||
*/
|
||||
struct sftp_conn *do_init(int, int, u_int, u_int, u_int64_t);
|
||||
struct sftp_conn *sftp_init(int, int, u_int, u_int, u_int64_t);
|
||||
|
||||
u_int sftp_proto_version(struct sftp_conn *);
|
||||
|
||||
/* Query server limits */
|
||||
int do_limits(struct sftp_conn *, struct sftp_limits *);
|
||||
int sftp_get_limits(struct sftp_conn *, struct sftp_limits *);
|
||||
|
||||
/* Close file referred to by 'handle' */
|
||||
int do_close(struct sftp_conn *, const u_char *, u_int);
|
||||
int sftp_close(struct sftp_conn *, const u_char *, u_int);
|
||||
|
||||
/* Read contents of 'path' to NULL-terminated array 'dir' */
|
||||
int do_readdir(struct sftp_conn *, const char *, SFTP_DIRENT ***);
|
||||
int sftp_readdir(struct sftp_conn *, const char *, SFTP_DIRENT ***);
|
||||
|
||||
/* Frees a NULL-terminated array of SFTP_DIRENTs (eg. from do_readdir) */
|
||||
void free_sftp_dirents(SFTP_DIRENT **);
|
||||
/* Frees a NULL-terminated array of SFTP_DIRENTs (eg. from sftp_readdir) */
|
||||
void sftp_free_dirents(SFTP_DIRENT **);
|
||||
|
||||
/* Delete file 'path' */
|
||||
int do_rm(struct sftp_conn *, const char *);
|
||||
int sftp_rm(struct sftp_conn *, const char *);
|
||||
|
||||
/* Create directory 'path' */
|
||||
int do_mkdir(struct sftp_conn *, const char *, Attrib *, int);
|
||||
int sftp_mkdir(struct sftp_conn *, const char *, Attrib *, int);
|
||||
|
||||
/* Remove directory 'path' */
|
||||
int do_rmdir(struct sftp_conn *, const char *);
|
||||
int sftp_rmdir(struct sftp_conn *, const char *);
|
||||
|
||||
/* Get file attributes of 'path' (follows symlinks) */
|
||||
Attrib *do_stat(struct sftp_conn *, const char *, int);
|
||||
int sftp_stat(struct sftp_conn *, const char *, int, Attrib *);
|
||||
|
||||
/* Get file attributes of 'path' (does not follow symlinks) */
|
||||
Attrib *do_lstat(struct sftp_conn *, const char *, int);
|
||||
int sftp_lstat(struct sftp_conn *, const char *, int, Attrib *);
|
||||
|
||||
/* Set file attributes of 'path' */
|
||||
int do_setstat(struct sftp_conn *, const char *, Attrib *);
|
||||
int sftp_setstat(struct sftp_conn *, const char *, Attrib *);
|
||||
|
||||
/* Set file attributes of open file 'handle' */
|
||||
int do_fsetstat(struct sftp_conn *, const u_char *, u_int, Attrib *);
|
||||
int sftp_fsetstat(struct sftp_conn *, const u_char *, u_int, Attrib *);
|
||||
|
||||
/* Set file attributes of 'path', not following symlinks */
|
||||
int do_lsetstat(struct sftp_conn *conn, const char *path, Attrib *a);
|
||||
int sftp_lsetstat(struct sftp_conn *conn, const char *path, Attrib *a);
|
||||
|
||||
/* Canonicalise 'path' - caller must free result */
|
||||
char *do_realpath(struct sftp_conn *, const char *);
|
||||
char *sftp_realpath(struct sftp_conn *, const char *);
|
||||
|
||||
/* Canonicalisation with tilde expansion (requires server extension) */
|
||||
char *do_expand_path(struct sftp_conn *, const char *);
|
||||
char *sftp_expand_path(struct sftp_conn *, const char *);
|
||||
|
||||
/* Returns non-zero if server can tilde-expand paths */
|
||||
int can_expand_path(struct sftp_conn *);
|
||||
int sftp_can_expand_path(struct sftp_conn *);
|
||||
|
||||
/* Get statistics for filesystem hosting file at "path" */
|
||||
int do_statvfs(struct sftp_conn *, const char *, struct sftp_statvfs *, int);
|
||||
int sftp_statvfs(struct sftp_conn *, const char *, struct sftp_statvfs *, int);
|
||||
|
||||
/* Rename 'oldpath' to 'newpath' */
|
||||
int do_rename(struct sftp_conn *, const char *, const char *, int);
|
||||
int sftp_rename(struct sftp_conn *, const char *, const char *, int);
|
||||
|
||||
/* Copy 'oldpath' to 'newpath' */
|
||||
int do_copy(struct sftp_conn *, const char *, const char *);
|
||||
int sftp_copy(struct sftp_conn *, const char *, const char *);
|
||||
|
||||
/* Link 'oldpath' to 'newpath' */
|
||||
int do_hardlink(struct sftp_conn *, const char *, const char *);
|
||||
int sftp_hardlink(struct sftp_conn *, const char *, const char *);
|
||||
|
||||
/* Rename 'oldpath' to 'newpath' */
|
||||
int do_symlink(struct sftp_conn *, const char *, const char *);
|
||||
int sftp_symlink(struct sftp_conn *, const char *, const char *);
|
||||
|
||||
/* Call fsync() on open file 'handle' */
|
||||
int do_fsync(struct sftp_conn *conn, u_char *, u_int);
|
||||
int sftp_fsync(struct sftp_conn *conn, u_char *, u_int);
|
||||
|
||||
/*
|
||||
* Download 'remote_path' to 'local_path'. Preserve permissions and times
|
||||
* if 'pflag' is set
|
||||
*/
|
||||
int do_download(struct sftp_conn *, const char *, const char *, Attrib *,
|
||||
int sftp_download(struct sftp_conn *, const char *, const char *, Attrib *,
|
||||
int, int, int, int);
|
||||
|
||||
/*
|
||||
* Recursively download 'remote_directory' to 'local_directory'. Preserve
|
||||
* times if 'pflag' is set
|
||||
*/
|
||||
int download_dir(struct sftp_conn *, const char *, const char *, Attrib *,
|
||||
int sftp_download_dir(struct sftp_conn *, const char *, const char *, Attrib *,
|
||||
int, int, int, int, int, int);
|
||||
|
||||
/*
|
||||
* Upload 'local_path' to 'remote_path'. Preserve permissions and times
|
||||
* if 'pflag' is set
|
||||
*/
|
||||
int do_upload(struct sftp_conn *, const char *, const char *,
|
||||
int sftp_upload(struct sftp_conn *, const char *, const char *,
|
||||
int, int, int, int);
|
||||
|
||||
/*
|
||||
* Recursively upload 'local_directory' to 'remote_directory'. Preserve
|
||||
* times if 'pflag' is set
|
||||
*/
|
||||
int upload_dir(struct sftp_conn *, const char *, const char *,
|
||||
int sftp_upload_dir(struct sftp_conn *, const char *, const char *,
|
||||
int, int, int, int, int, int);
|
||||
|
||||
/*
|
||||
* Download a 'from_path' from the 'from' connection and upload it to
|
||||
* to 'to' connection at 'to_path'.
|
||||
*/
|
||||
int
|
||||
do_crossload(struct sftp_conn *from, struct sftp_conn *to,
|
||||
int sftp_crossload(struct sftp_conn *from, struct sftp_conn *to,
|
||||
const char *from_path, const char *to_path,
|
||||
Attrib *a, int preserve_flag);
|
||||
|
||||
|
@ -178,7 +177,7 @@ do_crossload(struct sftp_conn *from, struct sftp_conn *to,
|
|||
* Recursively download a directory from 'from_path' from the 'from'
|
||||
* connection and upload it to 'to' connection at 'to_path'.
|
||||
*/
|
||||
int crossload_dir(struct sftp_conn *from, struct sftp_conn *to,
|
||||
int sftp_crossload_dir(struct sftp_conn *from, struct sftp_conn *to,
|
||||
const char *from_path, const char *to_path,
|
||||
Attrib *dirattrib, int preserve_flag, int print_flag,
|
||||
int follow_link_flag);
|
||||
|
@ -186,26 +185,23 @@ int crossload_dir(struct sftp_conn *from, struct sftp_conn *to,
|
|||
/*
|
||||
* User/group ID to name translation.
|
||||
*/
|
||||
int can_get_users_groups_by_id(struct sftp_conn *conn);
|
||||
int do_get_users_groups_by_id(struct sftp_conn *conn,
|
||||
int sftp_can_get_users_groups_by_id(struct sftp_conn *conn);
|
||||
int sftp_get_users_groups_by_id(struct sftp_conn *conn,
|
||||
const u_int *uids, u_int nuids,
|
||||
const u_int *gids, u_int ngids,
|
||||
char ***usernamesp, char ***groupnamesp);
|
||||
|
||||
/* Concatenate paths, taking care of slashes. Caller must free result. */
|
||||
char *path_append(const char *, const char *);
|
||||
char *sftp_path_append(const char *, const char *);
|
||||
|
||||
/* Make absolute path if relative path and CWD is given. Does not modify
|
||||
* original if the path is already absolute. */
|
||||
char *make_absolute(char *, const char *);
|
||||
char *sftp_make_absolute(char *, const char *);
|
||||
|
||||
/* Check if remote path is directory */
|
||||
int remote_is_dir(struct sftp_conn *conn, const char *path);
|
||||
|
||||
/* Check if local path is directory */
|
||||
int local_is_dir(const char *path);
|
||||
int sftp_remote_is_dir(struct sftp_conn *conn, const char *path);
|
||||
|
||||
/* Check whether path returned from glob(..., GLOB_MARK, ...) is a directory */
|
||||
int globpath_is_dir(const char *pathname);
|
||||
int sftp_globpath_is_dir(const char *pathname);
|
||||
|
||||
#endif
|
||||
|
|
28
sftp-glob.c
28
sftp-glob.c
|
@ -1,4 +1,4 @@
|
|||
/* $OpenBSD: sftp-glob.c,v 1.31 2022/10/24 21:51:55 djm Exp $ */
|
||||
/* $OpenBSD: sftp-glob.c,v 1.33 2023/09/10 23:12:32 djm Exp $ */
|
||||
/*
|
||||
* Copyright (c) 2001-2004 Damien Miller <djm@openbsd.org>
|
||||
*
|
||||
|
@ -32,7 +32,7 @@
|
|||
#include "sftp-common.h"
|
||||
#include "sftp-client.h"
|
||||
|
||||
int remote_glob(struct sftp_conn *, const char *, int,
|
||||
int sftp_glob(struct sftp_conn *, const char *, int,
|
||||
int (*)(const char *, int), glob_t *);
|
||||
|
||||
struct SFTP_OPENDIR {
|
||||
|
@ -51,7 +51,7 @@ fudge_opendir(const char *path)
|
|||
|
||||
r = xcalloc(1, sizeof(*r));
|
||||
|
||||
if (do_readdir(cur.conn, path, &r->dir)) {
|
||||
if (sftp_readdir(cur.conn, path, &r->dir)) {
|
||||
free(r);
|
||||
return(NULL);
|
||||
}
|
||||
|
@ -103,38 +103,38 @@ fudge_readdir(struct SFTP_OPENDIR *od)
|
|||
static void
|
||||
fudge_closedir(struct SFTP_OPENDIR *od)
|
||||
{
|
||||
free_sftp_dirents(od->dir);
|
||||
sftp_free_dirents(od->dir);
|
||||
free(od);
|
||||
}
|
||||
|
||||
static int
|
||||
fudge_lstat(const char *path, struct stat *st)
|
||||
{
|
||||
Attrib *a;
|
||||
Attrib a;
|
||||
|
||||
if (!(a = do_lstat(cur.conn, path, 1)))
|
||||
return(-1);
|
||||
if (sftp_lstat(cur.conn, path, 1, &a) != 0)
|
||||
return -1;
|
||||
|
||||
attrib_to_stat(a, st);
|
||||
attrib_to_stat(&a, st);
|
||||
|
||||
return(0);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int
|
||||
fudge_stat(const char *path, struct stat *st)
|
||||
{
|
||||
Attrib *a;
|
||||
Attrib a;
|
||||
|
||||
if (!(a = do_stat(cur.conn, path, 1)))
|
||||
return(-1);
|
||||
if (sftp_stat(cur.conn, path, 1, &a) != 0)
|
||||
return -1;
|
||||
|
||||
attrib_to_stat(a, st);
|
||||
attrib_to_stat(&a, st);
|
||||
|
||||
return(0);
|
||||
}
|
||||
|
||||
int
|
||||
remote_glob(struct sftp_conn *conn, const char *pattern, int flags,
|
||||
sftp_glob(struct sftp_conn *conn, const char *pattern, int flags,
|
||||
int (*errfunc)(const char *, int), glob_t *pglob)
|
||||
{
|
||||
int r;
|
||||
|
|
|
@ -106,9 +106,9 @@ lookup_and_record(struct sftp_conn *conn,
|
|||
u_int i;
|
||||
char **usernames = NULL, **groupnames = NULL;
|
||||
|
||||
if ((r = do_get_users_groups_by_id(conn, uids, nuids, gids, ngids,
|
||||
if ((r = sftp_get_users_groups_by_id(conn, uids, nuids, gids, ngids,
|
||||
&usernames, &groupnames)) != 0) {
|
||||
debug_fr(r, "do_get_users_groups_by_id");
|
||||
debug_fr(r, "sftp_get_users_groups_by_id");
|
||||
return;
|
||||
}
|
||||
for (i = 0; i < nuids; i++) {
|
||||
|
@ -176,7 +176,7 @@ get_remote_user_groups_from_glob(struct sftp_conn *conn, glob_t *g)
|
|||
{
|
||||
u_int *uids = NULL, nuids = 0, *gids = NULL, ngids = 0;
|
||||
|
||||
if (!can_get_users_groups_by_id(conn))
|
||||
if (!sftp_can_get_users_groups_by_id(conn))
|
||||
return;
|
||||
|
||||
collect_ids_from_glob(g, 1, &uids, &nuids);
|
||||
|
@ -215,7 +215,7 @@ get_remote_user_groups_from_dirents(struct sftp_conn *conn, SFTP_DIRENT **d)
|
|||
{
|
||||
u_int *uids = NULL, nuids = 0, *gids = NULL, ngids = 0;
|
||||
|
||||
if (!can_get_users_groups_by_id(conn))
|
||||
if (!sftp_can_get_users_groups_by_id(conn))
|
||||
return;
|
||||
|
||||
collect_ids_from_dirents(d, 1, &uids, &nuids);
|
||||
|
|
138
sftp.c
138
sftp.c
|
@ -1,4 +1,4 @@
|
|||
/* $OpenBSD: sftp.c,v 1.234 2023/04/12 08:53:54 jsg Exp $ */
|
||||
/* $OpenBSD: sftp.c,v 1.236 2023/09/10 23:12:32 djm Exp $ */
|
||||
/*
|
||||
* Copyright (c) 2001-2004 Damien Miller <djm@openbsd.org>
|
||||
*
|
||||
|
@ -114,7 +114,7 @@ struct complete_ctx {
|
|||
char **remote_pathp;
|
||||
};
|
||||
|
||||
int remote_glob(struct sftp_conn *, const char *, int,
|
||||
int sftp_glob(struct sftp_conn *, const char *, int,
|
||||
int (*)(const char *, int), glob_t *); /* proto for sftp-glob.c */
|
||||
|
||||
extern char *__progname;
|
||||
|
@ -667,11 +667,21 @@ make_absolute_pwd_glob(char *p, const char *pwd)
|
|||
escpwd = escape_glob(pwd);
|
||||
if (p == NULL)
|
||||
return escpwd;
|
||||
ret = make_absolute(p, escpwd);
|
||||
ret = sftp_make_absolute(p, escpwd);
|
||||
free(escpwd);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int
|
||||
local_is_dir(const char *path)
|
||||
{
|
||||
struct stat sb;
|
||||
|
||||
if (stat(path, &sb) == -1)
|
||||
return 0;
|
||||
return S_ISDIR(sb.st_mode);
|
||||
}
|
||||
|
||||
static int
|
||||
process_get(struct sftp_conn *conn, const char *src, const char *dst,
|
||||
const char *pwd, int pflag, int rflag, int resume, int fflag)
|
||||
|
@ -684,7 +694,7 @@ process_get(struct sftp_conn *conn, const char *src, const char *dst,
|
|||
memset(&g, 0, sizeof(g));
|
||||
|
||||
debug3("Looking up %s", abs_src);
|
||||
if ((r = remote_glob(conn, abs_src, GLOB_MARK, NULL, &g)) != 0) {
|
||||
if ((r = sftp_glob(conn, abs_src, GLOB_MARK, NULL, &g)) != 0) {
|
||||
if (r == GLOB_NOSPACE) {
|
||||
error("Too many matches for \"%s\".", abs_src);
|
||||
} else {
|
||||
|
@ -716,12 +726,12 @@ process_get(struct sftp_conn *conn, const char *src, const char *dst,
|
|||
|
||||
if (g.gl_matchc == 1 && dst) {
|
||||
if (local_is_dir(dst)) {
|
||||
abs_dst = path_append(dst, filename);
|
||||
abs_dst = sftp_path_append(dst, filename);
|
||||
} else {
|
||||
abs_dst = xstrdup(dst);
|
||||
}
|
||||
} else if (dst) {
|
||||
abs_dst = path_append(dst, filename);
|
||||
abs_dst = sftp_path_append(dst, filename);
|
||||
} else {
|
||||
abs_dst = xstrdup(filename);
|
||||
}
|
||||
|
@ -735,13 +745,14 @@ process_get(struct sftp_conn *conn, const char *src, const char *dst,
|
|||
mprintf("Fetching %s to %s\n",
|
||||
g.gl_pathv[i], abs_dst);
|
||||
/* XXX follow link flag */
|
||||
if (globpath_is_dir(g.gl_pathv[i]) && (rflag || global_rflag)) {
|
||||
if (download_dir(conn, g.gl_pathv[i], abs_dst, NULL,
|
||||
pflag || global_pflag, 1, resume,
|
||||
if (sftp_globpath_is_dir(g.gl_pathv[i]) &&
|
||||
(rflag || global_rflag)) {
|
||||
if (sftp_download_dir(conn, g.gl_pathv[i], abs_dst,
|
||||
NULL, pflag || global_pflag, 1, resume,
|
||||
fflag || global_fflag, 0, 0) == -1)
|
||||
err = -1;
|
||||
} else {
|
||||
if (do_download(conn, g.gl_pathv[i], abs_dst, NULL,
|
||||
if (sftp_download(conn, g.gl_pathv[i], abs_dst, NULL,
|
||||
pflag || global_pflag, resume,
|
||||
fflag || global_fflag, 0) == -1)
|
||||
err = -1;
|
||||
|
@ -770,7 +781,7 @@ process_put(struct sftp_conn *conn, const char *src, const char *dst,
|
|||
|
||||
if (dst) {
|
||||
tmp_dst = xstrdup(dst);
|
||||
tmp_dst = make_absolute(tmp_dst, pwd);
|
||||
tmp_dst = sftp_make_absolute(tmp_dst, pwd);
|
||||
}
|
||||
|
||||
memset(&g, 0, sizeof(g));
|
||||
|
@ -783,7 +794,7 @@ process_put(struct sftp_conn *conn, const char *src, const char *dst,
|
|||
|
||||
/* If we aren't fetching to pwd then stash this status for later */
|
||||
if (tmp_dst != NULL)
|
||||
dst_is_dir = remote_is_dir(conn, tmp_dst);
|
||||
dst_is_dir = sftp_remote_is_dir(conn, tmp_dst);
|
||||
|
||||
/* If multiple matches, dst may be directory or unspecified */
|
||||
if (g.gl_matchc > 1 && tmp_dst && !dst_is_dir) {
|
||||
|
@ -813,13 +824,13 @@ process_put(struct sftp_conn *conn, const char *src, const char *dst,
|
|||
if (g.gl_matchc == 1 && tmp_dst) {
|
||||
/* If directory specified, append filename */
|
||||
if (dst_is_dir)
|
||||
abs_dst = path_append(tmp_dst, filename);
|
||||
abs_dst = sftp_path_append(tmp_dst, filename);
|
||||
else
|
||||
abs_dst = xstrdup(tmp_dst);
|
||||
} else if (tmp_dst) {
|
||||
abs_dst = path_append(tmp_dst, filename);
|
||||
abs_dst = sftp_path_append(tmp_dst, filename);
|
||||
} else {
|
||||
abs_dst = make_absolute(xstrdup(filename), pwd);
|
||||
abs_dst = sftp_make_absolute(xstrdup(filename), pwd);
|
||||
}
|
||||
free(tmp);
|
||||
|
||||
|
@ -831,13 +842,14 @@ process_put(struct sftp_conn *conn, const char *src, const char *dst,
|
|||
mprintf("Uploading %s to %s\n",
|
||||
g.gl_pathv[i], abs_dst);
|
||||
/* XXX follow_link_flag */
|
||||
if (globpath_is_dir(g.gl_pathv[i]) && (rflag || global_rflag)) {
|
||||
if (upload_dir(conn, g.gl_pathv[i], abs_dst,
|
||||
if (sftp_globpath_is_dir(g.gl_pathv[i]) &&
|
||||
(rflag || global_rflag)) {
|
||||
if (sftp_upload_dir(conn, g.gl_pathv[i], abs_dst,
|
||||
pflag || global_pflag, 1, resume,
|
||||
fflag || global_fflag, 0, 0) == -1)
|
||||
err = -1;
|
||||
} else {
|
||||
if (do_upload(conn, g.gl_pathv[i], abs_dst,
|
||||
if (sftp_upload(conn, g.gl_pathv[i], abs_dst,
|
||||
pflag || global_pflag, resume,
|
||||
fflag || global_fflag, 0) == -1)
|
||||
err = -1;
|
||||
|
@ -879,7 +891,7 @@ do_ls_dir(struct sftp_conn *conn, const char *path,
|
|||
u_int c = 1, colspace = 0, columns = 1;
|
||||
SFTP_DIRENT **d;
|
||||
|
||||
if ((n = do_readdir(conn, path, &d)) != 0)
|
||||
if ((n = sftp_readdir(conn, path, &d)) != 0)
|
||||
return (n);
|
||||
|
||||
if (!(lflag & LS_SHORT_VIEW)) {
|
||||
|
@ -921,13 +933,13 @@ do_ls_dir(struct sftp_conn *conn, const char *path,
|
|||
if (d[n]->filename[0] == '.' && !(lflag & LS_SHOW_ALL))
|
||||
continue;
|
||||
|
||||
tmp = path_append(path, d[n]->filename);
|
||||
tmp = sftp_path_append(path, d[n]->filename);
|
||||
fname = path_strip(tmp, strip_path);
|
||||
free(tmp);
|
||||
|
||||
if (lflag & LS_LONG_VIEW) {
|
||||
if ((lflag & (LS_NUMERIC_VIEW|LS_SI_UNITS)) != 0 ||
|
||||
can_get_users_groups_by_id(conn)) {
|
||||
sftp_can_get_users_groups_by_id(conn)) {
|
||||
char *lname;
|
||||
struct stat sb;
|
||||
|
||||
|
@ -956,7 +968,7 @@ do_ls_dir(struct sftp_conn *conn, const char *path,
|
|||
if (!(lflag & LS_LONG_VIEW) && (c != 1))
|
||||
printf("\n");
|
||||
|
||||
free_sftp_dirents(d);
|
||||
sftp_free_dirents(d);
|
||||
return (0);
|
||||
}
|
||||
|
||||
|
@ -1005,7 +1017,7 @@ do_globbed_ls(struct sftp_conn *conn, const char *path,
|
|||
|
||||
memset(&g, 0, sizeof(g));
|
||||
|
||||
if ((r = remote_glob(conn, path,
|
||||
if ((r = sftp_glob(conn, path,
|
||||
GLOB_MARK|GLOB_NOCHECK|GLOB_BRACE|GLOB_KEEPSTAT|GLOB_NOSORT,
|
||||
NULL, &g)) != 0 ||
|
||||
(g.gl_pathc && !g.gl_matchc)) {
|
||||
|
@ -1110,7 +1122,7 @@ do_df(struct sftp_conn *conn, const char *path, int hflag, int iflag)
|
|||
char s_root[FMT_SCALED_STRSIZE], s_total[FMT_SCALED_STRSIZE];
|
||||
char s_icapacity[16], s_dcapacity[16];
|
||||
|
||||
if (do_statvfs(conn, path, &st, 1) == -1)
|
||||
if (sftp_statvfs(conn, path, &st, 1) == -1)
|
||||
return -1;
|
||||
if (st.f_files == 0)
|
||||
strlcpy(s_icapacity, "ERR", sizeof(s_icapacity));
|
||||
|
@ -1584,7 +1596,7 @@ parse_dispatch_command(struct sftp_conn *conn, const char *cmd, char **pwd,
|
|||
int lflag = 0, pflag = 0, rflag = 0, sflag = 0;
|
||||
int cmdnum, i;
|
||||
unsigned long n_arg = 0;
|
||||
Attrib a, *aa;
|
||||
Attrib a, aa;
|
||||
char path_buf[PATH_MAX];
|
||||
int err = 0;
|
||||
glob_t g;
|
||||
|
@ -1632,66 +1644,67 @@ parse_dispatch_command(struct sftp_conn *conn, const char *cmd, char **pwd,
|
|||
rflag, aflag, fflag);
|
||||
break;
|
||||
case I_COPY:
|
||||
path1 = make_absolute(path1, *pwd);
|
||||
path2 = make_absolute(path2, *pwd);
|
||||
err = do_copy(conn, path1, path2);
|
||||
path1 = sftp_make_absolute(path1, *pwd);
|
||||
path2 = sftp_make_absolute(path2, *pwd);
|
||||
err = sftp_copy(conn, path1, path2);
|
||||
break;
|
||||
case I_RENAME:
|
||||
path1 = make_absolute(path1, *pwd);
|
||||
path2 = make_absolute(path2, *pwd);
|
||||
err = do_rename(conn, path1, path2, lflag);
|
||||
path1 = sftp_make_absolute(path1, *pwd);
|
||||
path2 = sftp_make_absolute(path2, *pwd);
|
||||
err = sftp_rename(conn, path1, path2, lflag);
|
||||
break;
|
||||
case I_SYMLINK:
|
||||
sflag = 1;
|
||||
/* FALLTHROUGH */
|
||||
case I_LINK:
|
||||
if (!sflag)
|
||||
path1 = make_absolute(path1, *pwd);
|
||||
path2 = make_absolute(path2, *pwd);
|
||||
err = (sflag ? do_symlink : do_hardlink)(conn, path1, path2);
|
||||
path1 = sftp_make_absolute(path1, *pwd);
|
||||
path2 = sftp_make_absolute(path2, *pwd);
|
||||
err = (sflag ? sftp_symlink : sftp_hardlink)(conn,
|
||||
path1, path2);
|
||||
break;
|
||||
case I_RM:
|
||||
path1 = make_absolute_pwd_glob(path1, *pwd);
|
||||
remote_glob(conn, path1, GLOB_NOCHECK, NULL, &g);
|
||||
sftp_glob(conn, path1, GLOB_NOCHECK, NULL, &g);
|
||||
for (i = 0; g.gl_pathv[i] && !interrupted; i++) {
|
||||
if (!quiet)
|
||||
mprintf("Removing %s\n", g.gl_pathv[i]);
|
||||
err = do_rm(conn, g.gl_pathv[i]);
|
||||
err = sftp_rm(conn, g.gl_pathv[i]);
|
||||
if (err != 0 && err_abort)
|
||||
break;
|
||||
}
|
||||
break;
|
||||
case I_MKDIR:
|
||||
path1 = make_absolute(path1, *pwd);
|
||||
path1 = sftp_make_absolute(path1, *pwd);
|
||||
attrib_clear(&a);
|
||||
a.flags |= SSH2_FILEXFER_ATTR_PERMISSIONS;
|
||||
a.perm = 0777;
|
||||
err = do_mkdir(conn, path1, &a, 1);
|
||||
err = sftp_mkdir(conn, path1, &a, 1);
|
||||
break;
|
||||
case I_RMDIR:
|
||||
path1 = make_absolute(path1, *pwd);
|
||||
err = do_rmdir(conn, path1);
|
||||
path1 = sftp_make_absolute(path1, *pwd);
|
||||
err = sftp_rmdir(conn, path1);
|
||||
break;
|
||||
case I_CHDIR:
|
||||
if (path1 == NULL || *path1 == '\0')
|
||||
path1 = xstrdup(startdir);
|
||||
path1 = make_absolute(path1, *pwd);
|
||||
if ((tmp = do_realpath(conn, path1)) == NULL) {
|
||||
path1 = sftp_make_absolute(path1, *pwd);
|
||||
if ((tmp = sftp_realpath(conn, path1)) == NULL) {
|
||||
err = 1;
|
||||
break;
|
||||
}
|
||||
if ((aa = do_stat(conn, tmp, 0)) == NULL) {
|
||||
if (sftp_stat(conn, tmp, 0, &aa) != 0) {
|
||||
free(tmp);
|
||||
err = 1;
|
||||
break;
|
||||
}
|
||||
if (!(aa->flags & SSH2_FILEXFER_ATTR_PERMISSIONS)) {
|
||||
if (!(aa.flags & SSH2_FILEXFER_ATTR_PERMISSIONS)) {
|
||||
error("Can't change directory: Can't check target");
|
||||
free(tmp);
|
||||
err = 1;
|
||||
break;
|
||||
}
|
||||
if (!S_ISDIR(aa->perm)) {
|
||||
if (!S_ISDIR(aa.perm)) {
|
||||
error("Can't change directory: \"%s\" is not "
|
||||
"a directory", tmp);
|
||||
free(tmp);
|
||||
|
@ -1719,7 +1732,7 @@ parse_dispatch_command(struct sftp_conn *conn, const char *cmd, char **pwd,
|
|||
/* Default to current directory if no path specified */
|
||||
if (path1 == NULL)
|
||||
path1 = xstrdup(*pwd);
|
||||
path1 = make_absolute(path1, *pwd);
|
||||
path1 = sftp_make_absolute(path1, *pwd);
|
||||
err = do_df(conn, path1, hflag, iflag);
|
||||
break;
|
||||
case I_LCHDIR:
|
||||
|
@ -1756,12 +1769,12 @@ parse_dispatch_command(struct sftp_conn *conn, const char *cmd, char **pwd,
|
|||
attrib_clear(&a);
|
||||
a.flags |= SSH2_FILEXFER_ATTR_PERMISSIONS;
|
||||
a.perm = n_arg;
|
||||
remote_glob(conn, path1, GLOB_NOCHECK, NULL, &g);
|
||||
sftp_glob(conn, path1, GLOB_NOCHECK, NULL, &g);
|
||||
for (i = 0; g.gl_pathv[i] && !interrupted; i++) {
|
||||
if (!quiet)
|
||||
mprintf("Changing mode on %s\n",
|
||||
g.gl_pathv[i]);
|
||||
err = (hflag ? do_lsetstat : do_setstat)(conn,
|
||||
err = (hflag ? sftp_lsetstat : sftp_setstat)(conn,
|
||||
g.gl_pathv[i], &a);
|
||||
if (err != 0 && err_abort)
|
||||
break;
|
||||
|
@ -1770,17 +1783,17 @@ parse_dispatch_command(struct sftp_conn *conn, const char *cmd, char **pwd,
|
|||
case I_CHOWN:
|
||||
case I_CHGRP:
|
||||
path1 = make_absolute_pwd_glob(path1, *pwd);
|
||||
remote_glob(conn, path1, GLOB_NOCHECK, NULL, &g);
|
||||
sftp_glob(conn, path1, GLOB_NOCHECK, NULL, &g);
|
||||
for (i = 0; g.gl_pathv[i] && !interrupted; i++) {
|
||||
if (!(aa = (hflag ? do_lstat : do_stat)(conn,
|
||||
g.gl_pathv[i], 0))) {
|
||||
if ((hflag ? sftp_lstat : sftp_stat)(conn,
|
||||
g.gl_pathv[i], 0, &aa) != 0) {
|
||||
if (err_abort) {
|
||||
err = -1;
|
||||
break;
|
||||
} else
|
||||
continue;
|
||||
}
|
||||
if (!(aa->flags & SSH2_FILEXFER_ATTR_UIDGID)) {
|
||||
if (!(aa.flags & SSH2_FILEXFER_ATTR_UIDGID)) {
|
||||
error("Can't get current ownership of "
|
||||
"remote file \"%s\"", g.gl_pathv[i]);
|
||||
if (err_abort) {
|
||||
|
@ -1789,20 +1802,20 @@ parse_dispatch_command(struct sftp_conn *conn, const char *cmd, char **pwd,
|
|||
} else
|
||||
continue;
|
||||
}
|
||||
aa->flags &= SSH2_FILEXFER_ATTR_UIDGID;
|
||||
aa.flags &= SSH2_FILEXFER_ATTR_UIDGID;
|
||||
if (cmdnum == I_CHOWN) {
|
||||
if (!quiet)
|
||||
mprintf("Changing owner on %s\n",
|
||||
g.gl_pathv[i]);
|
||||
aa->uid = n_arg;
|
||||
aa.uid = n_arg;
|
||||
} else {
|
||||
if (!quiet)
|
||||
mprintf("Changing group on %s\n",
|
||||
g.gl_pathv[i]);
|
||||
aa->gid = n_arg;
|
||||
aa.gid = n_arg;
|
||||
}
|
||||
err = (hflag ? do_lsetstat : do_setstat)(conn,
|
||||
g.gl_pathv[i], aa);
|
||||
err = (hflag ? sftp_lsetstat : sftp_setstat)(conn,
|
||||
g.gl_pathv[i], &aa);
|
||||
if (err != 0 && err_abort)
|
||||
break;
|
||||
}
|
||||
|
@ -2051,7 +2064,7 @@ complete_match(EditLine *el, struct sftp_conn *conn, char *remote_path,
|
|||
memset(&g, 0, sizeof(g));
|
||||
if (remote != LOCAL) {
|
||||
tmp = make_absolute_pwd_glob(tmp, remote_path);
|
||||
remote_glob(conn, tmp, GLOB_DOOFFS|GLOB_MARK, NULL, &g);
|
||||
sftp_glob(conn, tmp, GLOB_DOOFFS|GLOB_MARK, NULL, &g);
|
||||
} else
|
||||
(void)glob(tmp, GLOB_DOOFFS|GLOB_MARK, NULL, &g);
|
||||
|
||||
|
@ -2281,16 +2294,15 @@ interactive_loop(struct sftp_conn *conn, char *file1, char *file2)
|
|||
}
|
||||
#endif /* USE_LIBEDIT */
|
||||
|
||||
remote_path = do_realpath(conn, ".");
|
||||
if (remote_path == NULL)
|
||||
if ((remote_path = sftp_realpath(conn, ".")) == NULL)
|
||||
fatal("Need cwd");
|
||||
startdir = xstrdup(remote_path);
|
||||
|
||||
if (file1 != NULL) {
|
||||
dir = xstrdup(file1);
|
||||
dir = make_absolute(dir, remote_path);
|
||||
dir = sftp_make_absolute(dir, remote_path);
|
||||
|
||||
if (remote_is_dir(conn, dir) && file2 == NULL) {
|
||||
if (sftp_remote_is_dir(conn, dir) && file2 == NULL) {
|
||||
if (!quiet)
|
||||
mprintf("Changing to: %s\n", dir);
|
||||
snprintf(cmd, sizeof cmd, "cd \"%s\"", dir);
|
||||
|
@ -2732,7 +2744,7 @@ main(int argc, char **argv)
|
|||
}
|
||||
freeargs(&args);
|
||||
|
||||
conn = do_init(in, out, copy_buffer_len, num_requests, limit_kbps);
|
||||
conn = sftp_init(in, out, copy_buffer_len, num_requests, limit_kbps);
|
||||
if (conn == NULL)
|
||||
fatal("Couldn't initialise connection to server");
|
||||
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
.\" $OpenBSD: ssh-agent.1,v 1.78 2023/07/23 20:04:45 naddy Exp $
|
||||
.\" $OpenBSD: ssh-agent.1,v 1.79 2023/08/10 14:37:32 naddy Exp $
|
||||
.\"
|
||||
.\" Author: Tatu Ylonen <ylo@cs.hut.fi>
|
||||
.\" Copyright (c) 1995 Tatu Ylonen <ylo@cs.hut.fi>, Espoo, Finland
|
||||
|
@ -34,7 +34,7 @@
|
|||
.\" (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
|
||||
.\" THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
.\"
|
||||
.Dd $Mdocdate: July 23 2023 $
|
||||
.Dd $Mdocdate: August 10 2023 $
|
||||
.Dt SSH-AGENT 1
|
||||
.Os
|
||||
.Sh NAME
|
||||
|
@ -122,7 +122,7 @@ Note that signalling that an
|
|||
.Nm
|
||||
client is remote is performed by
|
||||
.Xr ssh 1 ,
|
||||
and use of other tools to forward access to the agent socket, may circumvent
|
||||
and use of other tools to forward access to the agent socket may circumvent
|
||||
this restriction.
|
||||
.Pp
|
||||
The
|
||||
|
@ -156,7 +156,7 @@ See PATTERNS in
|
|||
.Xr ssh_config 5
|
||||
for a description of pattern-list syntax.
|
||||
The default list is
|
||||
.Dq /usr/lib/*,/usr/local/lib/* .
|
||||
.Dq usr/lib*/*,/usr/local/lib*/* .
|
||||
.It Fl s
|
||||
Generate Bourne shell commands on
|
||||
.Dv stdout .
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
.\" $OpenBSD: ssh-keygen.1,v 1.229 2023/07/23 20:04:45 naddy Exp $
|
||||
.\" $OpenBSD: ssh-keygen.1,v 1.230 2023/09/04 10:29:58 job Exp $
|
||||
.\"
|
||||
.\" Author: Tatu Ylonen <ylo@cs.hut.fi>
|
||||
.\" Copyright (c) 1995 Tatu Ylonen <ylo@cs.hut.fi>, Espoo, Finland
|
||||
|
@ -35,7 +35,7 @@
|
|||
.\" (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
|
||||
.\" THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
.\"
|
||||
.Dd $Mdocdate: July 23 2023 $
|
||||
.Dd $Mdocdate: September 4 2023 $
|
||||
.Dt SSH-KEYGEN 1
|
||||
.Os
|
||||
.Sh NAME
|
||||
|
@ -185,7 +185,7 @@ The type of key to be generated is specified with the
|
|||
option.
|
||||
If invoked without any arguments,
|
||||
.Nm
|
||||
will generate an RSA key.
|
||||
will generate an Ed25519 key.
|
||||
.Pp
|
||||
.Nm
|
||||
is also used to generate groups for use in Diffie-Hellman group
|
||||
|
|
10
ssh-keygen.c
10
ssh-keygen.c
|
@ -1,4 +1,4 @@
|
|||
/* $OpenBSD: ssh-keygen.c,v 1.470 2023/07/17 04:01:10 djm Exp $ */
|
||||
/* $OpenBSD: ssh-keygen.c,v 1.471 2023/09/04 10:29:58 job Exp $ */
|
||||
/*
|
||||
* Author: Tatu Ylonen <ylo@cs.hut.fi>
|
||||
* Copyright (c) 1994 Tatu Ylonen <ylo@cs.hut.fi>, Espoo, Finland
|
||||
|
@ -67,11 +67,7 @@
|
|||
#include "sk-api.h" /* XXX for SSH_SK_USER_PRESENCE_REQD; remove */
|
||||
#include "cipher.h"
|
||||
|
||||
#ifdef WITH_OPENSSL
|
||||
# define DEFAULT_KEY_TYPE_NAME "rsa"
|
||||
#else
|
||||
# define DEFAULT_KEY_TYPE_NAME "ed25519"
|
||||
#endif
|
||||
#define DEFAULT_KEY_TYPE_NAME "ed25519"
|
||||
|
||||
/*
|
||||
* Default number of bits in the RSA, DSA and ECDSA keys. These value can be
|
||||
|
@ -263,7 +259,7 @@ ask_filename(struct passwd *pw, const char *prompt)
|
|||
char *name = NULL;
|
||||
|
||||
if (key_type_name == NULL)
|
||||
name = _PATH_SSH_CLIENT_ID_RSA;
|
||||
name = _PATH_SSH_CLIENT_ID_ED25519;
|
||||
else {
|
||||
switch (sshkey_type_from_name(key_type_name)) {
|
||||
case KEY_DSA_CERT:
|
||||
|
|
11
ssh.c
11
ssh.c
|
@ -1,4 +1,4 @@
|
|||
/* $OpenBSD: ssh.c,v 1.593 2023/07/26 23:06:00 djm Exp $ */
|
||||
/* $OpenBSD: ssh.c,v 1.594 2023/09/03 23:59:32 djm Exp $ */
|
||||
/*
|
||||
* Author: Tatu Ylonen <ylo@cs.hut.fi>
|
||||
* Copyright (c) 1995 Tatu Ylonen <ylo@cs.hut.fi>, Espoo, Finland
|
||||
|
@ -2157,7 +2157,7 @@ ssh_session2_open(struct ssh *ssh)
|
|||
static int
|
||||
ssh_session2(struct ssh *ssh, const struct ssh_conn_info *cinfo)
|
||||
{
|
||||
int r, id = -1;
|
||||
int r, interactive, id = -1;
|
||||
char *cp, *tun_fwd_ifname = NULL;
|
||||
|
||||
/* XXX should be pre-session */
|
||||
|
@ -2214,8 +2214,11 @@ ssh_session2(struct ssh *ssh, const struct ssh_conn_info *cinfo)
|
|||
if (options.session_type != SESSION_TYPE_NONE)
|
||||
id = ssh_session2_open(ssh);
|
||||
else {
|
||||
ssh_packet_set_interactive(ssh,
|
||||
options.control_master == SSHCTL_MASTER_NO,
|
||||
interactive = options.control_master == SSHCTL_MASTER_NO;
|
||||
/* ControlPersist may have clobbered ControlMaster, so check */
|
||||
if (need_controlpersist_detach)
|
||||
interactive = otty_flag != 0;
|
||||
ssh_packet_set_interactive(ssh, interactive,
|
||||
options.ip_qos_interactive, options.ip_qos_bulk);
|
||||
}
|
||||
|
||||
|
|
7
ssh2.h
7
ssh2.h
|
@ -1,4 +1,4 @@
|
|||
/* $OpenBSD: ssh2.h,v 1.19 2020/11/19 23:05:05 dtucker Exp $ */
|
||||
/* $OpenBSD: ssh2.h,v 1.21 2023/08/28 03:28:43 djm Exp $ */
|
||||
|
||||
/*
|
||||
* Copyright (c) 2000 Markus Friedl. All rights reserved.
|
||||
|
@ -85,6 +85,7 @@
|
|||
#define SSH2_MSG_SERVICE_REQUEST 5
|
||||
#define SSH2_MSG_SERVICE_ACCEPT 6
|
||||
#define SSH2_MSG_EXT_INFO 7
|
||||
#define SSH2_MSG_NEWCOMPRESS 8
|
||||
|
||||
/* transport layer: alg negotiation */
|
||||
|
||||
|
@ -107,6 +108,10 @@
|
|||
#define SSH2_MSG_KEX_ECDH_INIT 30
|
||||
#define SSH2_MSG_KEX_ECDH_REPLY 31
|
||||
|
||||
/* transport layer: OpenSSH extensions */
|
||||
#define SSH2_MSG_PING 192
|
||||
#define SSH2_MSG_PONG 193
|
||||
|
||||
/* user authentication: generic */
|
||||
|
||||
#define SSH2_MSG_USERAUTH_REQUEST 50
|
||||
|
|
33
ssh_config.5
33
ssh_config.5
|
@ -33,8 +33,8 @@
|
|||
.\" (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
|
||||
.\" THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
.\"
|
||||
.\" $OpenBSD: ssh_config.5,v 1.383 2023/07/17 05:36:14 jsg Exp $
|
||||
.Dd $Mdocdate: July 17 2023 $
|
||||
.\" $OpenBSD: ssh_config.5,v 1.387 2023/10/04 04:03:50 djm Exp $
|
||||
.Dd $Mdocdate: October 4 2023 $
|
||||
.Dt SSH_CONFIG 5
|
||||
.Os
|
||||
.Sh NAME
|
||||
|
@ -1358,6 +1358,25 @@ or
|
|||
Specifies the number of password prompts before giving up.
|
||||
The argument to this keyword must be an integer.
|
||||
The default is 3.
|
||||
.It Cm ObscureKeystrokeTiming
|
||||
Specifies whether
|
||||
.Xr ssh 1
|
||||
should try to obscure inter-keystroke timings from passive observers of
|
||||
network traffic.
|
||||
If enabled, then for interactive sessions,
|
||||
.Xr ssh 1
|
||||
will send keystrokes at fixed intervals of a few tens of milliseconds
|
||||
and will send fake keystroke packets for some time after typing ceases.
|
||||
The argument to this keyword must be
|
||||
.Cm yes ,
|
||||
.Cm no
|
||||
or an interval specifier of the form
|
||||
.Cm interval:milliseconds
|
||||
(e.g.\&
|
||||
.Cm interval:80
|
||||
for 80 milliseconds).
|
||||
The default is to obscure keystrokes using a 20ms packet interval.
|
||||
Note that smaller intervals will result in higher fake keystroke packet rates.
|
||||
.It Cm PasswordAuthentication
|
||||
Specifies whether to use password authentication.
|
||||
The argument to this keyword must be
|
||||
|
@ -2187,6 +2206,16 @@ accepts all tokens.
|
|||
and
|
||||
.Cm ProxyJump
|
||||
accept the tokens %%, %h, %n, %p, and %r.
|
||||
.Pp
|
||||
Note that some of these directives build commands for execution via the shell.
|
||||
Because
|
||||
.Xr ssh 1
|
||||
performs no filtering or escaping of characters that have special meaning in
|
||||
shell commands (e.g. quotes), it is the user's reposibility to ensure that
|
||||
the arguments passed to
|
||||
.Xr ssh 1
|
||||
do not contain such characters and that tokens are appropriately quoted
|
||||
when used.
|
||||
.Sh ENVIRONMENT VARIABLES
|
||||
Arguments to some keywords can be expanded at runtime from environment
|
||||
variables on the client by enclosing them in
|
||||
|
|
6
sshd.8
6
sshd.8
|
@ -33,8 +33,8 @@
|
|||
.\" (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
|
||||
.\" THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
.\"
|
||||
.\" $OpenBSD: sshd.8,v 1.324 2023/02/10 06:39:27 jmc Exp $
|
||||
.Dd $Mdocdate: February 10 2023 $
|
||||
.\" $OpenBSD: sshd.8,v 1.325 2023/09/19 20:37:07 deraadt Exp $
|
||||
.Dd $Mdocdate: September 19 2023 $
|
||||
.Dt SSHD 8
|
||||
.Os
|
||||
.Sh NAME
|
||||
|
@ -320,7 +320,7 @@ forwarding TCP connections, or forwarding the authentication agent
|
|||
connection over the secure channel.
|
||||
.Pp
|
||||
After this, the client either requests an interactive shell or execution
|
||||
or a non-interactive command, which
|
||||
of a non-interactive command, which
|
||||
.Nm
|
||||
will execute via the user's shell using its
|
||||
.Fl c
|
||||
|
|
2
sshd.c
2
sshd.c
|
@ -2142,7 +2142,7 @@ main(int ac, char **av)
|
|||
break;
|
||||
case 'V':
|
||||
fprintf(stderr, "%s, %s\n",
|
||||
SSH_VERSION, SSH_OPENSSL_VERSION);
|
||||
SSH_RELEASE, SSH_OPENSSL_VERSION);
|
||||
exit(0);
|
||||
#ifdef WINDOWS
|
||||
case 'y':
|
||||
|
|
3
sshkey.c
3
sshkey.c
|
@ -1,4 +1,4 @@
|
|||
/* $OpenBSD: sshkey.c,v 1.137 2023/07/27 22:23:05 djm Exp $ */
|
||||
/* $OpenBSD: sshkey.c,v 1.138 2023/08/21 04:36:46 djm Exp $ */
|
||||
/*
|
||||
* Copyright (c) 2000, 2001 Markus Friedl. All rights reserved.
|
||||
* Copyright (c) 2008 Alexander von Gernler. All rights reserved.
|
||||
|
@ -41,6 +41,7 @@
|
|||
#include <errno.h>
|
||||
#include <limits.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <resolv.h>
|
||||
#include <time.h>
|
||||
|
|
27
sshsig.c
27
sshsig.c
|
@ -1,4 +1,4 @@
|
|||
/* $OpenBSD: sshsig.c,v 1.32 2023/04/06 03:56:02 djm Exp $ */
|
||||
/* $OpenBSD: sshsig.c,v 1.33 2023/09/06 23:18:15 djm Exp $ */
|
||||
/*
|
||||
* Copyright (c) 2019 Google LLC
|
||||
*
|
||||
|
@ -41,7 +41,7 @@
|
|||
#define SIG_VERSION 0x01
|
||||
#define MAGIC_PREAMBLE "SSHSIG"
|
||||
#define MAGIC_PREAMBLE_LEN (sizeof(MAGIC_PREAMBLE) - 1)
|
||||
#define BEGIN_SIGNATURE "-----BEGIN SSH SIGNATURE-----\n"
|
||||
#define BEGIN_SIGNATURE "-----BEGIN SSH SIGNATURE-----"
|
||||
#define END_SIGNATURE "-----END SSH SIGNATURE-----"
|
||||
#define RSA_SIGN_ALG "rsa-sha2-512" /* XXX maybe make configurable */
|
||||
#define RSA_SIGN_ALLOWED "rsa-sha2-512,rsa-sha2-256"
|
||||
|
@ -62,8 +62,7 @@ sshsig_armor(const struct sshbuf *blob, struct sshbuf **out)
|
|||
goto out;
|
||||
}
|
||||
|
||||
if ((r = sshbuf_put(buf, BEGIN_SIGNATURE,
|
||||
sizeof(BEGIN_SIGNATURE)-1)) != 0) {
|
||||
if ((r = sshbuf_putf(buf, "%s\n", BEGIN_SIGNATURE)) != 0) {
|
||||
error_fr(r, "sshbuf_putf");
|
||||
goto out;
|
||||
}
|
||||
|
@ -102,23 +101,35 @@ sshsig_dearmor(struct sshbuf *sig, struct sshbuf **out)
|
|||
return SSH_ERR_ALLOC_FAIL;
|
||||
}
|
||||
|
||||
/* Expect and consume preamble + lf/crlf */
|
||||
if ((r = sshbuf_cmp(sbuf, 0,
|
||||
BEGIN_SIGNATURE, sizeof(BEGIN_SIGNATURE)-1)) != 0) {
|
||||
error("Couldn't parse signature: missing header");
|
||||
goto done;
|
||||
}
|
||||
|
||||
if ((r = sshbuf_consume(sbuf, sizeof(BEGIN_SIGNATURE)-1)) != 0) {
|
||||
error_fr(r, "consume");
|
||||
goto done;
|
||||
}
|
||||
|
||||
if ((r = sshbuf_cmp(sbuf, 0, "\r\n", 2)) == 0)
|
||||
eoffset = 2;
|
||||
else if ((r = sshbuf_cmp(sbuf, 0, "\n", 1)) == 0)
|
||||
eoffset = 1;
|
||||
else {
|
||||
r = SSH_ERR_INVALID_FORMAT;
|
||||
error_f("no header eol");
|
||||
goto done;
|
||||
}
|
||||
if ((r = sshbuf_consume(sbuf, eoffset)) != 0) {
|
||||
error_fr(r, "consume eol");
|
||||
goto done;
|
||||
}
|
||||
/* Find and consume lf + suffix (any prior cr would be ignored) */
|
||||
if ((r = sshbuf_find(sbuf, 0, "\n" END_SIGNATURE,
|
||||
sizeof("\n" END_SIGNATURE)-1, &eoffset)) != 0) {
|
||||
sizeof(END_SIGNATURE), &eoffset)) != 0) {
|
||||
error("Couldn't parse signature: missing footer");
|
||||
goto done;
|
||||
}
|
||||
|
||||
if ((r = sshbuf_consume_end(sbuf, sshbuf_len(sbuf)-eoffset)) != 0) {
|
||||
error_fr(r, "consume");
|
||||
goto done;
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
/* $OpenBSD: version.h,v 1.98 2023/08/10 01:01:07 djm Exp $ */
|
||||
/* $OpenBSD: version.h,v 1.99 2023/10/04 04:04:09 djm Exp $ */
|
||||
|
||||
#define SSH_VERSION "OpenSSH_for_Windows_9.4"
|
||||
#define SSH_VERSION "OpenSSH_for_Windows_9.5"
|
||||
|
||||
#define SSH_PORTABLE "p1"
|
||||
#define SSH_RELEASE SSH_VERSION SSH_PORTABLE
|
||||
|
|
Loading…
Reference in New Issue