1
0
mirror of https://git.tartarus.org/simon/putty.git synced 2025-06-30 19:12:48 -05:00

Move the SSH implementation into its own subdirectory.

This clears up another large pile of clutter at the top level, and in
the process, allows me to rename source files to things that don't all
have that annoying 'ssh' prefix at the top.
This commit is contained in:
Simon Tatham
2021-04-22 17:58:40 +01:00
parent 66e62915d2
commit 83fa43497f
76 changed files with 186 additions and 161 deletions

51
ssh/CMakeLists.txt Normal file
View File

@ -0,0 +1,51 @@
add_library(sshcommon OBJECT
bpp1.c
bpp2.c
bpp-bare.c
censor1.c
censor2.c
common.c
connection1.c
connection2.c
crc-attack-detector.c
gssc.c
login1.c
pgssapi.c
portfwd.c
../sshpubk.c
../sshrand.c
transient-hostkey-cache.c
transport2.c
verstring.c
x11fwd.c
zlib.c)
add_library(sftpcommon OBJECT sftpcommon.c)
add_library(sshclient STATIC
agentf.c
connection1-client.c
connection2-client.c
kex2-client.c
mainchan.c
sharing.c
ssh.c
userauth2-client.c
$<TARGET_OBJECTS:sshcommon>
$<TARGET_OBJECTS:all-backends>
$<TARGET_OBJECTS:logging>)
add_library(sshserver STATIC
connection1-server.c
connection2-server.c
kex2-server.c
login1-server.c
server.c
sesschan.c
sftpserver.c
userauth2-server.c
$<TARGET_OBJECTS:sftpcommon>
$<TARGET_OBJECTS:sshcommon>)
add_sources_from_current_dir(sftpclient sftp.c)
target_sources(sftpclient PRIVATE $<TARGET_OBJECTS:sftpcommon>)

249
ssh/agentf.c Normal file
View File

@ -0,0 +1,249 @@
/*
* SSH agent forwarding.
*/
#include <assert.h>
#include <stdio.h>
#include <stdlib.h>
#include "putty.h"
#include "ssh.h"
#include "pageant.h"
#include "channel.h"
typedef struct agentf {
SshChannel *c;
bufchain inbuffer;
agent_pending_query *pending;
bool input_wanted;
bool rcvd_eof;
Channel chan;
} agentf;
static void agentf_got_response(agentf *af, void *reply, int replylen)
{
af->pending = NULL;
if (!reply) {
/* The real agent didn't send any kind of reply at all for
* some reason, so fake an SSH_AGENT_FAILURE. */
reply = "\0\0\0\1\5";
replylen = 5;
}
sshfwd_write(af->c, reply, replylen);
}
static void agentf_callback(void *vctx, void *reply, int replylen);
static void agentf_try_forward(agentf *af)
{
size_t datalen, length;
strbuf *message;
unsigned char msglen[4];
void *reply;
int replylen;
/*
* Don't try to parallelise agent requests. Wait for each one to
* return before attempting the next.
*/
if (af->pending)
return;
/*
* If the outgoing side of the channel connection is currently
* throttled, don't submit any new forwarded requests to the real
* agent. This causes the input side of the agent forwarding not
* to be emptied, exerting the required back-pressure on the
* remote client, and encouraging it to read our responses before
* sending too many more requests.
*/
if (!af->input_wanted)
return;
while (1) {
/*
* Try to extract a complete message from the input buffer.
*/
datalen = bufchain_size(&af->inbuffer);
if (datalen < 4)
break; /* not even a length field available yet */
bufchain_fetch(&af->inbuffer, msglen, 4);
length = GET_32BIT_MSB_FIRST(msglen);
if (length > AGENT_MAX_MSGLEN-4) {
/*
* If the remote has sent a message that's just _too_
* long, we should reject it in advance of seeing the rest
* of the incoming message, and also close the connection
* for good measure (which avoids us having to faff about
* with carefully ignoring just the right number of bytes
* from the overlong message).
*/
agentf_got_response(af, NULL, 0);
sshfwd_write_eof(af->c);
return;
}
if (length > datalen - 4)
break; /* a whole message is not yet available */
bufchain_consume(&af->inbuffer, 4);
message = strbuf_new_for_agent_query();
bufchain_fetch_consume(
&af->inbuffer, strbuf_append(message, length), length);
af->pending = agent_query(
message, &reply, &replylen, agentf_callback, af);
strbuf_free(message);
if (af->pending)
return; /* agent_query promised to reply in due course */
/*
* If the agent gave us an answer immediately, pass it
* straight on and go round this loop again.
*/
agentf_got_response(af, reply, replylen);
sfree(reply);
}
/*
* If we get here (i.e. we left the above while loop via 'break'
* rather than 'return'), that means we've determined that the
* input buffer for the agent forwarding connection doesn't
* contain a complete request.
*
* So if there's potentially more data to come, we can return now,
* and wait for the remote client to send it. But if the remote
* has sent EOF, it would be a mistake to do that, because we'd be
* waiting a long time. So this is the moment to check for EOF,
* and respond appropriately.
*/
if (af->rcvd_eof)
sshfwd_write_eof(af->c);
}
static void agentf_callback(void *vctx, void *reply, int replylen)
{
agentf *af = (agentf *)vctx;
agentf_got_response(af, reply, replylen);
sfree(reply);
/*
* Now try to extract and send further messages from the channel's
* input-side buffer.
*/
agentf_try_forward(af);
}
static void agentf_free(Channel *chan);
static size_t agentf_send(Channel *chan, bool is_stderr, const void *, size_t);
static void agentf_send_eof(Channel *chan);
static char *agentf_log_close_msg(Channel *chan);
static void agentf_set_input_wanted(Channel *chan, bool wanted);
static const ChannelVtable agentf_channelvt = {
.free = agentf_free,
.open_confirmation = chan_remotely_opened_confirmation,
.open_failed = chan_remotely_opened_failure,
.send = agentf_send,
.send_eof = agentf_send_eof,
.set_input_wanted = agentf_set_input_wanted,
.log_close_msg = agentf_log_close_msg,
.want_close = chan_default_want_close,
.rcvd_exit_status = chan_no_exit_status,
.rcvd_exit_signal = chan_no_exit_signal,
.rcvd_exit_signal_numeric = chan_no_exit_signal_numeric,
.run_shell = chan_no_run_shell,
.run_command = chan_no_run_command,
.run_subsystem = chan_no_run_subsystem,
.enable_x11_forwarding = chan_no_enable_x11_forwarding,
.enable_agent_forwarding = chan_no_enable_agent_forwarding,
.allocate_pty = chan_no_allocate_pty,
.set_env = chan_no_set_env,
.send_break = chan_no_send_break,
.send_signal = chan_no_send_signal,
.change_window_size = chan_no_change_window_size,
.request_response = chan_no_request_response,
};
Channel *agentf_new(SshChannel *c)
{
agentf *af = snew(agentf);
af->c = c;
af->chan.vt = &agentf_channelvt;
af->chan.initial_fixed_window_size = 0;
af->rcvd_eof = false;
bufchain_init(&af->inbuffer);
af->pending = NULL;
af->input_wanted = true;
return &af->chan;
}
static void agentf_free(Channel *chan)
{
assert(chan->vt == &agentf_channelvt);
agentf *af = container_of(chan, agentf, chan);
if (af->pending)
agent_cancel_query(af->pending);
bufchain_clear(&af->inbuffer);
sfree(af);
}
static size_t agentf_send(Channel *chan, bool is_stderr,
const void *data, size_t length)
{
assert(chan->vt == &agentf_channelvt);
agentf *af = container_of(chan, agentf, chan);
bufchain_add(&af->inbuffer, data, length);
agentf_try_forward(af);
/*
* We exert back-pressure on an agent forwarding client if and
* only if we're waiting for the response to an asynchronous agent
* request. This prevents the client running out of window while
* receiving the _first_ message, but means that if any message
* takes time to process, the client will be discouraged from
* sending an endless stream of further ones after it.
*/
return (af->pending ? bufchain_size(&af->inbuffer) : 0);
}
static void agentf_send_eof(Channel *chan)
{
assert(chan->vt == &agentf_channelvt);
agentf *af = container_of(chan, agentf, chan);
af->rcvd_eof = true;
/* Call try_forward, which will respond to the EOF now if
* appropriate, or wait until the queue of outstanding requests is
* dealt with if not. */
agentf_try_forward(af);
}
static char *agentf_log_close_msg(Channel *chan)
{
return dupstr("Agent-forwarding connection closed");
}
static void agentf_set_input_wanted(Channel *chan, bool wanted)
{
assert(chan->vt == &agentf_channelvt);
agentf *af = container_of(chan, agentf, chan);
af->input_wanted = wanted;
/* Agent forwarding channels are buffer-managed by not asking the
* agent questions if the SSH channel isn't accepting input. So if
* it's started again, we should ask a question if we have one
* pending.. */
if (wanted)
agentf_try_forward(af);
}

204
ssh/bpp-bare.c Normal file
View File

@ -0,0 +1,204 @@
/*
* Trivial binary packet protocol for the 'bare' ssh-connection
* protocol used in PuTTY's SSH-2 connection sharing system.
*/
#include <assert.h>
#include "putty.h"
#include "ssh.h"
#include "bpp.h"
#include "sshcr.h"
struct ssh2_bare_bpp_state {
int crState;
long packetlen, maxlen;
unsigned char *data;
unsigned long incoming_sequence, outgoing_sequence;
PktIn *pktin;
BinaryPacketProtocol bpp;
};
static void ssh2_bare_bpp_free(BinaryPacketProtocol *bpp);
static void ssh2_bare_bpp_handle_input(BinaryPacketProtocol *bpp);
static void ssh2_bare_bpp_handle_output(BinaryPacketProtocol *bpp);
static PktOut *ssh2_bare_bpp_new_pktout(int type);
static const BinaryPacketProtocolVtable ssh2_bare_bpp_vtable = {
.free = ssh2_bare_bpp_free,
.handle_input = ssh2_bare_bpp_handle_input,
.handle_output = ssh2_bare_bpp_handle_output,
.new_pktout = ssh2_bare_bpp_new_pktout,
.queue_disconnect = ssh2_bpp_queue_disconnect, /* in common.c */
/* packet size limit, per protocol spec in sharing.c comment */
.packet_size_limit = 0x4000,
};
BinaryPacketProtocol *ssh2_bare_bpp_new(LogContext *logctx)
{
struct ssh2_bare_bpp_state *s = snew(struct ssh2_bare_bpp_state);
memset(s, 0, sizeof(*s));
s->bpp.vt = &ssh2_bare_bpp_vtable;
s->bpp.logctx = logctx;
ssh_bpp_common_setup(&s->bpp);
return &s->bpp;
}
static void ssh2_bare_bpp_free(BinaryPacketProtocol *bpp)
{
struct ssh2_bare_bpp_state *s =
container_of(bpp, struct ssh2_bare_bpp_state, bpp);
sfree(s->pktin);
sfree(s);
}
#define BPP_READ(ptr, len) do \
{ \
bool success; \
crMaybeWaitUntilV((success = bufchain_try_fetch_consume( \
s->bpp.in_raw, ptr, len)) || \
s->bpp.input_eof); \
if (!success) \
goto eof; \
ssh_check_frozen(s->bpp.ssh); \
} while (0)
static void ssh2_bare_bpp_handle_input(BinaryPacketProtocol *bpp)
{
struct ssh2_bare_bpp_state *s =
container_of(bpp, struct ssh2_bare_bpp_state, bpp);
crBegin(s->crState);
while (1) {
/* Read the length field. */
{
unsigned char lenbuf[4];
BPP_READ(lenbuf, 4);
s->packetlen = toint(GET_32BIT_MSB_FIRST(lenbuf));
}
if (s->packetlen <= 0 || s->packetlen >= (long)OUR_V2_PACKETLIMIT) {
ssh_sw_abort(s->bpp.ssh, "Invalid packet length received");
crStopV;
}
/*
* Allocate the packet to return, now we know its length.
*/
s->pktin = snew_plus(PktIn, s->packetlen);
s->pktin->qnode.prev = s->pktin->qnode.next = NULL;
s->pktin->qnode.on_free_queue = false;
s->maxlen = 0;
s->data = snew_plus_get_aux(s->pktin);
s->pktin->sequence = s->incoming_sequence++;
/*
* Read the remainder of the packet.
*/
BPP_READ(s->data, s->packetlen);
/*
* The data we just read is precisely the initial type byte
* followed by the packet payload.
*/
s->pktin->type = s->data[0];
s->data++;
s->packetlen--;
BinarySource_INIT(s->pktin, s->data, s->packetlen);
if (s->pktin->type == SSH2_MSG_EXT_INFO) {
/*
* Mild layer violation: EXT_INFO is not permitted in the
* bare ssh-connection protocol. Faulting it here means
* that ssh2_common_filter_queue doesn't receive it in the
* first place unless it's legal to have sent it.
*/
ssh_proto_error(s->bpp.ssh, "Remote side sent SSH2_MSG_EXT_INFO "
"in bare connection protocol");
return;
}
/*
* Log incoming packet, possibly omitting sensitive fields.
*/
if (s->bpp.logctx) {
logblank_t blanks[MAX_BLANKS];
int nblanks = ssh2_censor_packet(
s->bpp.pls, s->pktin->type, false,
make_ptrlen(s->data, s->packetlen), blanks);
log_packet(s->bpp.logctx, PKT_INCOMING, s->pktin->type,
ssh2_pkt_type(s->bpp.pls->kctx, s->bpp.pls->actx,
s->pktin->type),
get_ptr(s->pktin), get_avail(s->pktin), nblanks, blanks,
&s->pktin->sequence, 0, NULL);
}
if (ssh2_bpp_check_unimplemented(&s->bpp, s->pktin)) {
sfree(s->pktin);
s->pktin = NULL;
continue;
}
s->pktin->qnode.formal_size = get_avail(s->pktin);
pq_push(&s->bpp.in_pq, s->pktin);
s->pktin = NULL;
}
eof:
if (!s->bpp.expect_close) {
ssh_remote_error(s->bpp.ssh,
"Remote side unexpectedly closed network connection");
} else {
ssh_remote_eof(s->bpp.ssh, "Remote side closed network connection");
}
return; /* avoid touching s now it's been freed */
crFinishV;
}
static PktOut *ssh2_bare_bpp_new_pktout(int pkt_type)
{
PktOut *pkt = ssh_new_packet();
pkt->length = 4; /* space for packet length */
pkt->type = pkt_type;
put_byte(pkt, pkt_type);
return pkt;
}
static void ssh2_bare_bpp_format_packet(struct ssh2_bare_bpp_state *s,
PktOut *pkt)
{
if (s->bpp.logctx) {
ptrlen pktdata = make_ptrlen(pkt->data + 5, pkt->length - 5);
logblank_t blanks[MAX_BLANKS];
int nblanks = ssh2_censor_packet(
s->bpp.pls, pkt->type, true, pktdata, blanks);
log_packet(s->bpp.logctx, PKT_OUTGOING, pkt->type,
ssh2_pkt_type(s->bpp.pls->kctx, s->bpp.pls->actx,
pkt->type),
pktdata.ptr, pktdata.len, nblanks, blanks,
&s->outgoing_sequence,
pkt->downstream_id, pkt->additional_log_text);
}
s->outgoing_sequence++; /* only for diagnostics, really */
PUT_32BIT_MSB_FIRST(pkt->data, pkt->length - 4);
bufchain_add(s->bpp.out_raw, pkt->data, pkt->length);
}
static void ssh2_bare_bpp_handle_output(BinaryPacketProtocol *bpp)
{
struct ssh2_bare_bpp_state *s =
container_of(bpp, struct ssh2_bare_bpp_state, bpp);
PktOut *pkt;
while ((pkt = pq_pop(&s->bpp.out_pq)) != NULL) {
ssh2_bare_bpp_format_packet(s, pkt);
ssh_free_pktout(pkt);
}
}

179
ssh/bpp.h Normal file
View File

@ -0,0 +1,179 @@
/*
* Abstraction of the binary packet protocols used in SSH.
*/
#ifndef PUTTY_SSHBPP_H
#define PUTTY_SSHBPP_H
typedef struct BinaryPacketProtocolVtable BinaryPacketProtocolVtable;
struct BinaryPacketProtocolVtable {
void (*free)(BinaryPacketProtocol *);
void (*handle_input)(BinaryPacketProtocol *);
void (*handle_output)(BinaryPacketProtocol *);
PktOut *(*new_pktout)(int type);
void (*queue_disconnect)(BinaryPacketProtocol *,
const char *msg, int category);
uint32_t packet_size_limit;
};
struct BinaryPacketProtocol {
const struct BinaryPacketProtocolVtable *vt;
bufchain *in_raw, *out_raw;
bool input_eof; /* set this if in_raw will never be added to again */
PktInQueue in_pq;
PktOutQueue out_pq;
PacketLogSettings *pls;
LogContext *logctx;
Ssh *ssh;
/* ic_in_raw is filled in by the BPP (probably by calling
* ssh_bpp_common_setup). The BPP's owner triggers it when data is
* added to in_raw, and also when the BPP is newly created. */
IdempotentCallback ic_in_raw;
/* ic_out_pq is entirely internal to the BPP itself; it's used as
* the callback on out_pq. */
IdempotentCallback ic_out_pq;
/* Information that all packet layers sharing this BPP will
* potentially be interested in. */
int remote_bugs;
bool ext_info_rsa_sha256_ok, ext_info_rsa_sha512_ok;
/* Set this if remote connection closure should not generate an
* error message (either because it's not to be treated as an
* error at all, or because some other error message has already
* been emitted). */
bool expect_close;
};
static inline void ssh_bpp_handle_input(BinaryPacketProtocol *bpp)
{ bpp->vt->handle_input(bpp); }
static inline void ssh_bpp_handle_output(BinaryPacketProtocol *bpp)
{ bpp->vt->handle_output(bpp); }
static inline PktOut *ssh_bpp_new_pktout(BinaryPacketProtocol *bpp, int type)
{ return bpp->vt->new_pktout(type); }
static inline void ssh_bpp_queue_disconnect(BinaryPacketProtocol *bpp,
const char *msg, int category)
{ bpp->vt->queue_disconnect(bpp, msg, category); }
/* ssh_bpp_free is more than just a macro wrapper on the vtable; it
* does centralised parts of the freeing too. */
void ssh_bpp_free(BinaryPacketProtocol *bpp);
BinaryPacketProtocol *ssh1_bpp_new(LogContext *logctx);
void ssh1_bpp_new_cipher(BinaryPacketProtocol *bpp,
const ssh_cipheralg *cipher,
const void *session_key);
/* This is only called from outside the BPP in server mode; in client
* mode the BPP detects compression start time automatically by
* snooping message types */
void ssh1_bpp_start_compression(BinaryPacketProtocol *bpp);
/* Helper routine which does common BPP initialisation, e.g. setting
* up in_pq and out_pq, and initialising input_consumer. */
void ssh_bpp_common_setup(BinaryPacketProtocol *);
/* Common helper functions between the SSH-2 full and bare BPPs */
void ssh2_bpp_queue_disconnect(BinaryPacketProtocol *bpp,
const char *msg, int category);
bool ssh2_bpp_check_unimplemented(BinaryPacketProtocol *bpp, PktIn *pktin);
/* Convenience macro for BPPs to send formatted strings to the Event
* Log. Assumes a function parameter called 'bpp' is in scope. */
#define bpp_logevent(...) ( \
logevent_and_free((bpp)->logctx, dupprintf(__VA_ARGS__)))
/*
* Structure that tracks how much data is sent and received, for
* purposes of triggering an SSH-2 rekey when either one gets over a
* configured limit. In each direction, the flag 'running' indicates
* that we haven't hit the limit yet, and 'remaining' tracks how much
* longer until we do. The function dts_consume() subtracts a given
* amount from the counter in a particular direction, and sets
* 'expired' if the limit has been hit.
*
* The limit is sticky: once 'running' has flipped to false,
* 'remaining' is no longer decremented, so it shouldn't dangerously
* wrap round.
*/
struct DataTransferStatsDirection {
bool running, expired;
unsigned long remaining;
};
struct DataTransferStats {
struct DataTransferStatsDirection in, out;
};
static inline void dts_consume(struct DataTransferStatsDirection *s,
unsigned long size_consumed)
{
if (s->running) {
if (s->remaining <= size_consumed) {
s->running = false;
s->expired = true;
} else {
s->remaining -= size_consumed;
}
}
}
static inline void dts_reset(struct DataTransferStatsDirection *s,
unsigned long starting_size)
{
s->expired = false;
s->remaining = starting_size;
/*
* The semantics of setting CONF_ssh_rekey_data to zero are to
* disable data-volume based rekeying completely. So if the
* starting size is actually zero, we don't set 'running' to true
* in the first place, which means we won't ever set the expired
* flag.
*/
s->running = (starting_size != 0);
}
BinaryPacketProtocol *ssh2_bpp_new(
LogContext *logctx, struct DataTransferStats *stats, bool is_server);
void ssh2_bpp_new_outgoing_crypto(
BinaryPacketProtocol *bpp,
const ssh_cipheralg *cipher, const void *ckey, const void *iv,
const ssh2_macalg *mac, bool etm_mode, const void *mac_key,
const ssh_compression_alg *compression, bool delayed_compression);
void ssh2_bpp_new_incoming_crypto(
BinaryPacketProtocol *bpp,
const ssh_cipheralg *cipher, const void *ckey, const void *iv,
const ssh2_macalg *mac, bool etm_mode, const void *mac_key,
const ssh_compression_alg *compression, bool delayed_compression);
/*
* A query method specific to the interface between ssh2transport and
* ssh2bpp. If true, it indicates that we're potentially in the
* race-condition-prone part of delayed compression setup and so
* asynchronous outgoing transport-layer packets are currently not
* being sent, which means in particular that it would be a bad idea
* to start a rekey because then we'd stop responding to anything
* _other_ than transport-layer packets and deadlock the protocol.
*/
bool ssh2_bpp_rekey_inadvisable(BinaryPacketProtocol *bpp);
BinaryPacketProtocol *ssh2_bare_bpp_new(LogContext *logctx);
/*
* The initial code to handle the SSH version exchange is also
* structured as an implementation of BinaryPacketProtocol, because
* that makes it easy to switch from that to the next BPP once it
* tells us which one we're using.
*/
struct ssh_version_receiver {
void (*got_ssh_version)(struct ssh_version_receiver *rcv,
int major_version);
};
BinaryPacketProtocol *ssh_verstring_new(
Conf *conf, LogContext *logctx, bool bare_connection_mode,
const char *protoversion, struct ssh_version_receiver *rcv,
bool server_mode, const char *impl_name);
const char *ssh_verstring_get_remote(BinaryPacketProtocol *);
const char *ssh_verstring_get_local(BinaryPacketProtocol *);
int ssh_verstring_get_bugs(BinaryPacketProtocol *);
#endif /* PUTTY_SSHBPP_H */

387
ssh/bpp1.c Normal file
View File

@ -0,0 +1,387 @@
/*
* Binary packet protocol for SSH-1.
*/
#include <assert.h>
#include "putty.h"
#include "ssh.h"
#include "bpp.h"
#include "sshcr.h"
struct ssh1_bpp_state {
int crState;
long len, pad, biglen, length, maxlen;
unsigned char *data;
uint32_t realcrc, gotcrc;
int chunk;
PktIn *pktin;
ssh_cipher *cipher_in, *cipher_out;
struct crcda_ctx *crcda_ctx;
uint8_t iv[8]; /* for crcda */
bool pending_compression_request;
ssh_compressor *compctx;
ssh_decompressor *decompctx;
BinaryPacketProtocol bpp;
};
static void ssh1_bpp_free(BinaryPacketProtocol *bpp);
static void ssh1_bpp_handle_input(BinaryPacketProtocol *bpp);
static void ssh1_bpp_handle_output(BinaryPacketProtocol *bpp);
static void ssh1_bpp_queue_disconnect(BinaryPacketProtocol *bpp,
const char *msg, int category);
static PktOut *ssh1_bpp_new_pktout(int type);
static const BinaryPacketProtocolVtable ssh1_bpp_vtable = {
.free = ssh1_bpp_free,
.handle_input = ssh1_bpp_handle_input,
.handle_output = ssh1_bpp_handle_output,
.new_pktout = ssh1_bpp_new_pktout,
.queue_disconnect = ssh1_bpp_queue_disconnect,
.packet_size_limit = 0xFFFFFFFF, /* no special limit for this bpp */
};
BinaryPacketProtocol *ssh1_bpp_new(LogContext *logctx)
{
struct ssh1_bpp_state *s = snew(struct ssh1_bpp_state);
memset(s, 0, sizeof(*s));
s->bpp.vt = &ssh1_bpp_vtable;
s->bpp.logctx = logctx;
ssh_bpp_common_setup(&s->bpp);
return &s->bpp;
}
static void ssh1_bpp_free(BinaryPacketProtocol *bpp)
{
struct ssh1_bpp_state *s = container_of(bpp, struct ssh1_bpp_state, bpp);
if (s->cipher_in)
ssh_cipher_free(s->cipher_in);
if (s->cipher_out)
ssh_cipher_free(s->cipher_out);
if (s->compctx)
ssh_compressor_free(s->compctx);
if (s->decompctx)
ssh_decompressor_free(s->decompctx);
if (s->crcda_ctx)
crcda_free_context(s->crcda_ctx);
sfree(s->pktin);
sfree(s);
}
void ssh1_bpp_new_cipher(BinaryPacketProtocol *bpp,
const ssh_cipheralg *cipher,
const void *session_key)
{
struct ssh1_bpp_state *s;
assert(bpp->vt == &ssh1_bpp_vtable);
s = container_of(bpp, struct ssh1_bpp_state, bpp);
assert(!s->cipher_in);
assert(!s->cipher_out);
if (cipher) {
s->cipher_in = ssh_cipher_new(cipher);
s->cipher_out = ssh_cipher_new(cipher);
ssh_cipher_setkey(s->cipher_in, session_key);
ssh_cipher_setkey(s->cipher_out, session_key);
assert(!s->crcda_ctx);
s->crcda_ctx = crcda_make_context();
bpp_logevent("Initialised %s encryption", cipher->text_name);
memset(s->iv, 0, sizeof(s->iv));
assert(cipher->blksize <= sizeof(s->iv));
ssh_cipher_setiv(s->cipher_in, s->iv);
ssh_cipher_setiv(s->cipher_out, s->iv);
}
}
void ssh1_bpp_start_compression(BinaryPacketProtocol *bpp)
{
struct ssh1_bpp_state *s;
assert(bpp->vt == &ssh1_bpp_vtable);
s = container_of(bpp, struct ssh1_bpp_state, bpp);
assert(!s->compctx);
assert(!s->decompctx);
s->compctx = ssh_compressor_new(&ssh_zlib);
s->decompctx = ssh_decompressor_new(&ssh_zlib);
bpp_logevent("Started zlib (RFC1950) compression");
}
#define BPP_READ(ptr, len) do \
{ \
bool success; \
crMaybeWaitUntilV((success = bufchain_try_fetch_consume( \
s->bpp.in_raw, ptr, len)) || \
s->bpp.input_eof); \
if (!success) \
goto eof; \
ssh_check_frozen(s->bpp.ssh); \
} while (0)
static void ssh1_bpp_handle_input(BinaryPacketProtocol *bpp)
{
struct ssh1_bpp_state *s = container_of(bpp, struct ssh1_bpp_state, bpp);
crBegin(s->crState);
while (1) {
s->maxlen = 0;
s->length = 0;
{
unsigned char lenbuf[4];
BPP_READ(lenbuf, 4);
s->len = toint(GET_32BIT_MSB_FIRST(lenbuf));
}
if (s->len < 5 || s->len > 262144) { /* SSH1.5-mandated max size */
ssh_sw_abort(s->bpp.ssh,
"Out-of-range packet length from remote suggests"
" data stream corruption");
crStopV;
}
s->pad = 8 - (s->len % 8);
s->biglen = s->len + s->pad;
s->length = s->len - 5;
/*
* Allocate the packet to return, now we know its length.
*/
s->pktin = snew_plus(PktIn, s->biglen);
s->pktin->qnode.prev = s->pktin->qnode.next = NULL;
s->pktin->qnode.on_free_queue = false;
s->pktin->type = 0;
s->maxlen = s->biglen;
s->data = snew_plus_get_aux(s->pktin);
BPP_READ(s->data, s->biglen);
if (s->cipher_in && detect_attack(s->crcda_ctx,
s->data, s->biglen, s->iv)) {
ssh_sw_abort(s->bpp.ssh,
"Network attack (CRC compensation) detected!");
crStopV;
}
/* Save the last cipher block, to be passed to the next call
* to detect_attack */
assert(s->biglen >= 8);
memcpy(s->iv, s->data + s->biglen - 8, sizeof(s->iv));
if (s->cipher_in)
ssh_cipher_decrypt(s->cipher_in, s->data, s->biglen);
s->realcrc = crc32_ssh1(make_ptrlen(s->data, s->biglen - 4));
s->gotcrc = GET_32BIT_MSB_FIRST(s->data + s->biglen - 4);
if (s->gotcrc != s->realcrc) {
ssh_sw_abort(s->bpp.ssh, "Incorrect CRC received on packet");
crStopV;
}
if (s->decompctx) {
unsigned char *decompblk;
int decomplen;
if (!ssh_decompressor_decompress(
s->decompctx, s->data + s->pad, s->length + 1,
&decompblk, &decomplen)) {
ssh_sw_abort(s->bpp.ssh,
"Zlib decompression encountered invalid data");
crStopV;
}
if (s->maxlen < s->pad + decomplen) {
PktIn *old_pktin = s->pktin;
s->maxlen = s->pad + decomplen;
s->pktin = snew_plus(PktIn, s->maxlen);
*s->pktin = *old_pktin; /* structure copy */
s->data = snew_plus_get_aux(s->pktin);
smemclr(old_pktin, s->biglen);
sfree(old_pktin);
}
memcpy(s->data + s->pad, decompblk, decomplen);
sfree(decompblk);
s->length = decomplen - 1;
}
/*
* Now we can find the bounds of the semantic content of the
* packet, and the initial type byte.
*/
s->data += s->pad;
s->pktin->type = *s->data++;
BinarySource_INIT(s->pktin, s->data, s->length);
if (s->bpp.logctx) {
logblank_t blanks[MAX_BLANKS];
int nblanks = ssh1_censor_packet(
s->bpp.pls, s->pktin->type, false,
make_ptrlen(s->data, s->length), blanks);
log_packet(s->bpp.logctx, PKT_INCOMING, s->pktin->type,
ssh1_pkt_type(s->pktin->type),
get_ptr(s->pktin), get_avail(s->pktin), nblanks, blanks,
NULL, 0, NULL);
}
s->pktin->qnode.formal_size = get_avail(s->pktin);
pq_push(&s->bpp.in_pq, s->pktin);
{
int type = s->pktin->type;
s->pktin = NULL;
switch (type) {
case SSH1_SMSG_SUCCESS:
case SSH1_SMSG_FAILURE:
if (s->pending_compression_request) {
/*
* This is the response to
* SSH1_CMSG_REQUEST_COMPRESSION.
*/
if (type == SSH1_SMSG_SUCCESS) {
/*
* If the response was positive, start
* compression.
*/
ssh1_bpp_start_compression(&s->bpp);
}
/*
* Either way, cancel the pending flag, and
* schedule a run of our output side in case we
* had any packets queued up in the meantime.
*/
s->pending_compression_request = false;
queue_idempotent_callback(&s->bpp.ic_out_pq);
}
break;
}
}
}
eof:
if (!s->bpp.expect_close) {
ssh_remote_error(s->bpp.ssh,
"Remote side unexpectedly closed network connection");
} else {
ssh_remote_eof(s->bpp.ssh, "Remote side closed network connection");
}
return; /* avoid touching s now it's been freed */
crFinishV;
}
static PktOut *ssh1_bpp_new_pktout(int pkt_type)
{
PktOut *pkt = ssh_new_packet();
pkt->length = 4 + 8; /* space for length + max padding */
put_byte(pkt, pkt_type);
pkt->prefix = pkt->length;
pkt->type = pkt_type;
pkt->downstream_id = 0;
pkt->additional_log_text = NULL;
return pkt;
}
static void ssh1_bpp_format_packet(struct ssh1_bpp_state *s, PktOut *pkt)
{
int pad, biglen, pktoffs;
uint32_t crc;
int len;
if (s->bpp.logctx) {
ptrlen pktdata = make_ptrlen(pkt->data + pkt->prefix,
pkt->length - pkt->prefix);
logblank_t blanks[MAX_BLANKS];
int nblanks = ssh1_censor_packet(
s->bpp.pls, pkt->type, true, pktdata, blanks);
log_packet(s->bpp.logctx, PKT_OUTGOING, pkt->type,
ssh1_pkt_type(pkt->type),
pktdata.ptr, pktdata.len, nblanks, blanks,
NULL, 0, NULL);
}
if (s->compctx) {
unsigned char *compblk;
int complen;
ssh_compressor_compress(s->compctx, pkt->data + 12, pkt->length - 12,
&compblk, &complen, 0);
/* Replace the uncompressed packet data with the compressed
* version. */
pkt->length = 12;
put_data(pkt, compblk, complen);
sfree(compblk);
}
put_uint32(pkt, 0); /* space for CRC */
len = pkt->length - 4 - 8; /* len(type+data+CRC) */
pad = 8 - (len % 8);
pktoffs = 8 - pad;
biglen = len + pad; /* len(padding+type+data+CRC) */
random_read(pkt->data + pktoffs, 4+8 - pktoffs);
crc = crc32_ssh1(
make_ptrlen(pkt->data + pktoffs + 4, biglen - 4)); /* all ex len */
PUT_32BIT_MSB_FIRST(pkt->data + pktoffs + 4 + biglen - 4, crc);
PUT_32BIT_MSB_FIRST(pkt->data + pktoffs, len);
if (s->cipher_out)
ssh_cipher_encrypt(s->cipher_out, pkt->data + pktoffs + 4, biglen);
bufchain_add(s->bpp.out_raw, pkt->data + pktoffs,
biglen + 4); /* len(length+padding+type+data+CRC) */
}
static void ssh1_bpp_handle_output(BinaryPacketProtocol *bpp)
{
struct ssh1_bpp_state *s = container_of(bpp, struct ssh1_bpp_state, bpp);
PktOut *pkt;
if (s->pending_compression_request) {
/*
* Don't send any output packets while we're awaiting a
* response to SSH1_CMSG_REQUEST_COMPRESSION, because if they
* cross over in transit with the responding SSH1_CMSG_SUCCESS
* then the other end could decode them with the wrong
* compression settings.
*/
return;
}
while ((pkt = pq_pop(&s->bpp.out_pq)) != NULL) {
int type = pkt->type;
ssh1_bpp_format_packet(s, pkt);
ssh_free_pktout(pkt);
if (type == SSH1_CMSG_REQUEST_COMPRESSION) {
/*
* When we see the actual compression request go past, set
* the pending flag, and stop processing packets this
* time.
*/
s->pending_compression_request = true;
break;
}
}
}
static void ssh1_bpp_queue_disconnect(BinaryPacketProtocol *bpp,
const char *msg, int category)
{
PktOut *pkt = ssh_bpp_new_pktout(bpp, SSH1_MSG_DISCONNECT);
put_stringz(pkt, msg);
pq_push(&bpp->out_pq, pkt);
}

982
ssh/bpp2.c Normal file
View File

@ -0,0 +1,982 @@
/*
* Binary packet protocol for SSH-2.
*/
#include <assert.h>
#include "putty.h"
#include "ssh.h"
#include "bpp.h"
#include "sshcr.h"
struct ssh2_bpp_direction {
unsigned long sequence;
ssh_cipher *cipher;
ssh2_mac *mac;
bool etm_mode;
const ssh_compression_alg *pending_compression;
};
struct ssh2_bpp_state {
int crState;
long len, pad, payload, packetlen, maclen, length, maxlen;
unsigned char *buf;
size_t bufsize;
unsigned char *data;
unsigned cipherblk;
PktIn *pktin;
struct DataTransferStats *stats;
bool cbc_ignore_workaround;
struct ssh2_bpp_direction in, out;
/* comp and decomp logically belong in the per-direction
* substructure, except that they have different types */
ssh_decompressor *in_decomp;
ssh_compressor *out_comp;
bool is_server;
bool pending_newkeys;
bool pending_compression, seen_userauth_success;
bool enforce_next_packet_is_userauth_success;
unsigned nnewkeys;
int prev_type;
BinaryPacketProtocol bpp;
};
static void ssh2_bpp_free(BinaryPacketProtocol *bpp);
static void ssh2_bpp_handle_input(BinaryPacketProtocol *bpp);
static void ssh2_bpp_handle_output(BinaryPacketProtocol *bpp);
static PktOut *ssh2_bpp_new_pktout(int type);
static const BinaryPacketProtocolVtable ssh2_bpp_vtable = {
.free = ssh2_bpp_free,
.handle_input = ssh2_bpp_handle_input,
.handle_output = ssh2_bpp_handle_output,
.new_pktout = ssh2_bpp_new_pktout,
.queue_disconnect = ssh2_bpp_queue_disconnect, /* in common.c */
.packet_size_limit = 0xFFFFFFFF, /* no special limit for this bpp */
};
BinaryPacketProtocol *ssh2_bpp_new(
LogContext *logctx, struct DataTransferStats *stats, bool is_server)
{
struct ssh2_bpp_state *s = snew(struct ssh2_bpp_state);
memset(s, 0, sizeof(*s));
s->bpp.vt = &ssh2_bpp_vtable;
s->bpp.logctx = logctx;
s->stats = stats;
s->is_server = is_server;
ssh_bpp_common_setup(&s->bpp);
return &s->bpp;
}
static void ssh2_bpp_free_outgoing_crypto(struct ssh2_bpp_state *s)
{
/*
* We must free the MAC before the cipher, because sometimes the
* MAC is not actually separately allocated but just a different
* facet of the same object as the cipher, in which case
* ssh2_mac_free does nothing and ssh_cipher_free does the actual
* freeing. So if we freed the cipher first and then tried to
* dereference the MAC's vtable pointer to find out how to free
* that too, we'd be accessing freed memory.
*/
if (s->out.mac)
ssh2_mac_free(s->out.mac);
if (s->out.cipher)
ssh_cipher_free(s->out.cipher);
if (s->out_comp)
ssh_compressor_free(s->out_comp);
}
static void ssh2_bpp_free_incoming_crypto(struct ssh2_bpp_state *s)
{
/* As above, take care to free in.mac before in.cipher */
if (s->in.mac)
ssh2_mac_free(s->in.mac);
if (s->in.cipher)
ssh_cipher_free(s->in.cipher);
if (s->in_decomp)
ssh_decompressor_free(s->in_decomp);
}
static void ssh2_bpp_free(BinaryPacketProtocol *bpp)
{
struct ssh2_bpp_state *s = container_of(bpp, struct ssh2_bpp_state, bpp);
sfree(s->buf);
ssh2_bpp_free_outgoing_crypto(s);
ssh2_bpp_free_incoming_crypto(s);
sfree(s->pktin);
sfree(s);
}
void ssh2_bpp_new_outgoing_crypto(
BinaryPacketProtocol *bpp,
const ssh_cipheralg *cipher, const void *ckey, const void *iv,
const ssh2_macalg *mac, bool etm_mode, const void *mac_key,
const ssh_compression_alg *compression, bool delayed_compression)
{
struct ssh2_bpp_state *s;
assert(bpp->vt == &ssh2_bpp_vtable);
s = container_of(bpp, struct ssh2_bpp_state, bpp);
ssh2_bpp_free_outgoing_crypto(s);
if (cipher) {
s->out.cipher = ssh_cipher_new(cipher);
ssh_cipher_setkey(s->out.cipher, ckey);
ssh_cipher_setiv(s->out.cipher, iv);
s->cbc_ignore_workaround = (
(ssh_cipher_alg(s->out.cipher)->flags & SSH_CIPHER_IS_CBC) &&
!(s->bpp.remote_bugs & BUG_CHOKES_ON_SSH2_IGNORE));
bpp_logevent("Initialised %s outbound encryption",
ssh_cipher_alg(s->out.cipher)->text_name);
} else {
s->out.cipher = NULL;
s->cbc_ignore_workaround = false;
}
s->out.etm_mode = etm_mode;
if (mac) {
s->out.mac = ssh2_mac_new(mac, s->out.cipher);
ssh2_mac_setkey(s->out.mac, make_ptrlen(mac_key, mac->keylen));
bpp_logevent("Initialised %s outbound MAC algorithm%s%s",
ssh2_mac_text_name(s->out.mac),
etm_mode ? " (in ETM mode)" : "",
(s->out.cipher &&
ssh_cipher_alg(s->out.cipher)->required_mac ?
" (required by cipher)" : ""));
} else {
s->out.mac = NULL;
}
if (delayed_compression && !s->seen_userauth_success) {
s->out.pending_compression = compression;
s->out_comp = NULL;
bpp_logevent("Will enable %s compression after user authentication",
s->out.pending_compression->text_name);
} else {
s->out.pending_compression = NULL;
/* 'compression' is always non-NULL, because no compression is
* indicated by ssh_comp_none. But this setup call may return a
* null out_comp. */
s->out_comp = ssh_compressor_new(compression);
if (s->out_comp)
bpp_logevent("Initialised %s compression",
ssh_compressor_alg(s->out_comp)->text_name);
}
}
void ssh2_bpp_new_incoming_crypto(
BinaryPacketProtocol *bpp,
const ssh_cipheralg *cipher, const void *ckey, const void *iv,
const ssh2_macalg *mac, bool etm_mode, const void *mac_key,
const ssh_compression_alg *compression, bool delayed_compression)
{
struct ssh2_bpp_state *s;
assert(bpp->vt == &ssh2_bpp_vtable);
s = container_of(bpp, struct ssh2_bpp_state, bpp);
ssh2_bpp_free_incoming_crypto(s);
if (cipher) {
s->in.cipher = ssh_cipher_new(cipher);
ssh_cipher_setkey(s->in.cipher, ckey);
ssh_cipher_setiv(s->in.cipher, iv);
bpp_logevent("Initialised %s inbound encryption",
ssh_cipher_alg(s->in.cipher)->text_name);
} else {
s->in.cipher = NULL;
}
s->in.etm_mode = etm_mode;
if (mac) {
s->in.mac = ssh2_mac_new(mac, s->in.cipher);
ssh2_mac_setkey(s->in.mac, make_ptrlen(mac_key, mac->keylen));
bpp_logevent("Initialised %s inbound MAC algorithm%s%s",
ssh2_mac_text_name(s->in.mac),
etm_mode ? " (in ETM mode)" : "",
(s->in.cipher &&
ssh_cipher_alg(s->in.cipher)->required_mac ?
" (required by cipher)" : ""));
} else {
s->in.mac = NULL;
}
if (delayed_compression && !s->seen_userauth_success) {
s->in.pending_compression = compression;
s->in_decomp = NULL;
bpp_logevent("Will enable %s decompression after user authentication",
s->in.pending_compression->text_name);
} else {
s->in.pending_compression = NULL;
/* 'compression' is always non-NULL, because no compression is
* indicated by ssh_comp_none. But this setup call may return a
* null in_decomp. */
s->in_decomp = ssh_decompressor_new(compression);
if (s->in_decomp)
bpp_logevent("Initialised %s decompression",
ssh_decompressor_alg(s->in_decomp)->text_name);
}
/* Clear the pending_newkeys flag, so that handle_input below will
* start consuming the input data again. */
s->pending_newkeys = false;
/* And schedule a run of handle_input, in case there's already
* input data in the queue. */
queue_idempotent_callback(&s->bpp.ic_in_raw);
}
bool ssh2_bpp_rekey_inadvisable(BinaryPacketProtocol *bpp)
{
struct ssh2_bpp_state *s;
assert(bpp->vt == &ssh2_bpp_vtable);
s = container_of(bpp, struct ssh2_bpp_state, bpp);
return s->pending_compression;
}
static void ssh2_bpp_enable_pending_compression(struct ssh2_bpp_state *s)
{
BinaryPacketProtocol *bpp = &s->bpp; /* for bpp_logevent */
if (s->in.pending_compression) {
s->in_decomp = ssh_decompressor_new(s->in.pending_compression);
bpp_logevent("Initialised delayed %s decompression",
ssh_decompressor_alg(s->in_decomp)->text_name);
s->in.pending_compression = NULL;
}
if (s->out.pending_compression) {
s->out_comp = ssh_compressor_new(s->out.pending_compression);
bpp_logevent("Initialised delayed %s compression",
ssh_compressor_alg(s->out_comp)->text_name);
s->out.pending_compression = NULL;
}
}
#define BPP_READ(ptr, len) do \
{ \
bool success; \
crMaybeWaitUntilV((success = bufchain_try_fetch_consume( \
s->bpp.in_raw, ptr, len)) || \
s->bpp.input_eof); \
if (!success) \
goto eof; \
ssh_check_frozen(s->bpp.ssh); \
} while (0)
#define userauth_range(pkttype) ((unsigned)((pkttype) - 50) < 20)
static void ssh2_bpp_handle_input(BinaryPacketProtocol *bpp)
{
struct ssh2_bpp_state *s = container_of(bpp, struct ssh2_bpp_state, bpp);
crBegin(s->crState);
while (1) {
s->maxlen = 0;
s->length = 0;
if (s->in.cipher)
s->cipherblk = ssh_cipher_alg(s->in.cipher)->blksize;
else
s->cipherblk = 8;
if (s->cipherblk < 8)
s->cipherblk = 8;
s->maclen = s->in.mac ? ssh2_mac_alg(s->in.mac)->len : 0;
if (s->in.cipher &&
(ssh_cipher_alg(s->in.cipher)->flags & SSH_CIPHER_IS_CBC) &&
s->in.mac && !s->in.etm_mode) {
/*
* When dealing with a CBC-mode cipher, we want to avoid the
* possibility of an attacker's tweaking the ciphertext stream
* so as to cause us to feed the same block to the block
* cipher more than once and thus leak information
* (VU#958563). The way we do this is not to take any
* decisions on the basis of anything we've decrypted until
* we've verified it with a MAC. That includes the packet
* length, so we just read data and check the MAC repeatedly,
* and when the MAC passes, see if the length we've got is
* plausible.
*
* This defence is unnecessary in OpenSSH ETM mode, because
* the whole point of ETM mode is that the attacker can't
* tweak the ciphertext stream at all without the MAC
* detecting it before we decrypt anything.
*/
/*
* Make sure we have buffer space for a maximum-size packet.
*/
unsigned buflimit = OUR_V2_PACKETLIMIT + s->maclen;
if (s->bufsize < buflimit) {
s->bufsize = buflimit;
s->buf = sresize(s->buf, s->bufsize, unsigned char);
}
/* Read an amount corresponding to the MAC. */
BPP_READ(s->buf, s->maclen);
s->packetlen = 0;
ssh2_mac_start(s->in.mac);
put_uint32(s->in.mac, s->in.sequence);
for (;;) { /* Once around this loop per cipher block. */
/* Read another cipher-block's worth, and tack it on to
* the end. */
BPP_READ(s->buf + (s->packetlen + s->maclen), s->cipherblk);
/* Decrypt one more block (a little further back in
* the stream). */
ssh_cipher_decrypt(s->in.cipher,
s->buf + s->packetlen, s->cipherblk);
/* Feed that block to the MAC. */
put_data(s->in.mac,
s->buf + s->packetlen, s->cipherblk);
s->packetlen += s->cipherblk;
/* See if that gives us a valid packet. */
if (ssh2_mac_verresult(s->in.mac, s->buf + s->packetlen) &&
((s->len = toint(GET_32BIT_MSB_FIRST(s->buf))) ==
s->packetlen-4))
break;
if (s->packetlen >= (long)OUR_V2_PACKETLIMIT) {
ssh_sw_abort(s->bpp.ssh,
"No valid incoming packet found");
crStopV;
}
}
s->maxlen = s->packetlen + s->maclen;
/*
* Now transfer the data into an output packet.
*/
s->pktin = snew_plus(PktIn, s->maxlen);
s->pktin->qnode.prev = s->pktin->qnode.next = NULL;
s->pktin->type = 0;
s->pktin->qnode.on_free_queue = false;
s->data = snew_plus_get_aux(s->pktin);
memcpy(s->data, s->buf, s->maxlen);
} else if (s->in.mac && s->in.etm_mode) {
if (s->bufsize < 4) {
s->bufsize = 4;
s->buf = sresize(s->buf, s->bufsize, unsigned char);
}
/*
* OpenSSH encrypt-then-MAC mode: the packet length is
* unencrypted, unless the cipher supports length encryption.
*/
BPP_READ(s->buf, 4);
/* Cipher supports length decryption, so do it */
if (s->in.cipher && (ssh_cipher_alg(s->in.cipher)->flags &
SSH_CIPHER_SEPARATE_LENGTH)) {
/* Keep the packet the same though, so the MAC passes */
unsigned char len[4];
memcpy(len, s->buf, 4);
ssh_cipher_decrypt_length(
s->in.cipher, len, 4, s->in.sequence);
s->len = toint(GET_32BIT_MSB_FIRST(len));
} else {
s->len = toint(GET_32BIT_MSB_FIRST(s->buf));
}
/*
* _Completely_ silly lengths should be stomped on before they
* do us any more damage.
*/
if (s->len < 0 || s->len > (long)OUR_V2_PACKETLIMIT ||
s->len % s->cipherblk != 0) {
ssh_sw_abort(s->bpp.ssh,
"Incoming packet length field was garbled");
crStopV;
}
/*
* So now we can work out the total packet length.
*/
s->packetlen = s->len + 4;
/*
* Allocate the packet to return, now we know its length.
*/
s->pktin = snew_plus(PktIn, OUR_V2_PACKETLIMIT + s->maclen);
s->pktin->qnode.prev = s->pktin->qnode.next = NULL;
s->pktin->type = 0;
s->pktin->qnode.on_free_queue = false;
s->data = snew_plus_get_aux(s->pktin);
memcpy(s->data, s->buf, 4);
/*
* Read the remainder of the packet.
*/
BPP_READ(s->data + 4, s->packetlen + s->maclen - 4);
/*
* Check the MAC.
*/
if (s->in.mac && !ssh2_mac_verify(
s->in.mac, s->data, s->len + 4, s->in.sequence)) {
ssh_sw_abort(s->bpp.ssh, "Incorrect MAC received on packet");
crStopV;
}
/* Decrypt everything between the length field and the MAC. */
if (s->in.cipher)
ssh_cipher_decrypt(
s->in.cipher, s->data + 4, s->packetlen - 4);
} else {
if (s->bufsize < s->cipherblk) {
s->bufsize = s->cipherblk;
s->buf = sresize(s->buf, s->bufsize, unsigned char);
}
/*
* Acquire and decrypt the first block of the packet. This will
* contain the length and padding details.
*/
BPP_READ(s->buf, s->cipherblk);
if (s->in.cipher)
ssh_cipher_decrypt(s->in.cipher, s->buf, s->cipherblk);
/*
* Now get the length figure.
*/
s->len = toint(GET_32BIT_MSB_FIRST(s->buf));
/*
* _Completely_ silly lengths should be stomped on before they
* do us any more damage.
*/
if (s->len < 0 || s->len > (long)OUR_V2_PACKETLIMIT ||
(s->len + 4) % s->cipherblk != 0) {
ssh_sw_abort(s->bpp.ssh,
"Incoming packet was garbled on decryption");
crStopV;
}
/*
* So now we can work out the total packet length.
*/
s->packetlen = s->len + 4;
/*
* Allocate the packet to return, now we know its length.
*/
s->maxlen = s->packetlen + s->maclen;
s->pktin = snew_plus(PktIn, s->maxlen);
s->pktin->qnode.prev = s->pktin->qnode.next = NULL;
s->pktin->type = 0;
s->pktin->qnode.on_free_queue = false;
s->data = snew_plus_get_aux(s->pktin);
memcpy(s->data, s->buf, s->cipherblk);
/*
* Read and decrypt the remainder of the packet.
*/
BPP_READ(s->data + s->cipherblk,
s->packetlen + s->maclen - s->cipherblk);
/* Decrypt everything _except_ the MAC. */
if (s->in.cipher)
ssh_cipher_decrypt(
s->in.cipher,
s->data + s->cipherblk, s->packetlen - s->cipherblk);
/*
* Check the MAC.
*/
if (s->in.mac && !ssh2_mac_verify(
s->in.mac, s->data, s->len + 4, s->in.sequence)) {
ssh_sw_abort(s->bpp.ssh, "Incorrect MAC received on packet");
crStopV;
}
}
/* Get and sanity-check the amount of random padding. */
s->pad = s->data[4];
if (s->pad < 4 || s->len - s->pad < 1) {
ssh_sw_abort(s->bpp.ssh,
"Invalid padding length on received packet");
crStopV;
}
/*
* This enables us to deduce the payload length.
*/
s->payload = s->len - s->pad - 1;
s->length = s->payload + 5;
dts_consume(&s->stats->in, s->packetlen);
s->pktin->sequence = s->in.sequence++;
s->length = s->packetlen - s->pad;
assert(s->length >= 0);
/*
* Decompress packet payload.
*/
{
unsigned char *newpayload;
int newlen;
if (s->in_decomp && ssh_decompressor_decompress(
s->in_decomp, s->data + 5, s->length - 5,
&newpayload, &newlen)) {
if (s->maxlen < newlen + 5) {
PktIn *old_pktin = s->pktin;
s->maxlen = newlen + 5;
s->pktin = snew_plus(PktIn, s->maxlen);
*s->pktin = *old_pktin; /* structure copy */
s->data = snew_plus_get_aux(s->pktin);
smemclr(old_pktin, s->packetlen + s->maclen);
sfree(old_pktin);
}
s->length = 5 + newlen;
memcpy(s->data + 5, newpayload, newlen);
sfree(newpayload);
}
}
/*
* Now we can identify the semantic content of the packet,
* and also the initial type byte.
*/
if (s->length <= 5) { /* == 5 we hope, but robustness */
/*
* RFC 4253 doesn't explicitly say that completely empty
* packets with no type byte are forbidden. We handle them
* here by giving them a type code larger than 0xFF, which
* will be picked up at the next layer and trigger
* SSH_MSG_UNIMPLEMENTED.
*/
s->pktin->type = SSH_MSG_NO_TYPE_CODE;
s->data += 5;
s->length = 0;
} else {
s->pktin->type = s->data[5];
s->data += 6;
s->length -= 6;
}
BinarySource_INIT(s->pktin, s->data, s->length);
if (s->bpp.logctx) {
logblank_t blanks[MAX_BLANKS];
int nblanks = ssh2_censor_packet(
s->bpp.pls, s->pktin->type, false,
make_ptrlen(s->data, s->length), blanks);
log_packet(s->bpp.logctx, PKT_INCOMING, s->pktin->type,
ssh2_pkt_type(s->bpp.pls->kctx, s->bpp.pls->actx,
s->pktin->type),
s->data, s->length, nblanks, blanks,
&s->pktin->sequence, 0, NULL);
}
if (ssh2_bpp_check_unimplemented(&s->bpp, s->pktin)) {
sfree(s->pktin);
s->pktin = NULL;
continue;
}
s->pktin->qnode.formal_size = get_avail(s->pktin);
pq_push(&s->bpp.in_pq, s->pktin);
{
int type = s->pktin->type;
int prev_type = s->prev_type;
s->prev_type = type;
s->pktin = NULL;
if (s->enforce_next_packet_is_userauth_success) {
/* See EXT_INFO handler below */
if (type != SSH2_MSG_USERAUTH_SUCCESS) {
ssh_proto_error(s->bpp.ssh,
"Remote side sent SSH2_MSG_EXT_INFO "
"not either preceded by NEWKEYS or "
"followed by USERAUTH_SUCCESS");
return;
}
s->enforce_next_packet_is_userauth_success = false;
}
if (type == SSH2_MSG_NEWKEYS) {
if (s->nnewkeys < 2)
s->nnewkeys++;
/*
* Mild layer violation: in this situation we must
* suspend processing of the input byte stream until
* the transport layer has initialised the new keys by
* calling ssh2_bpp_new_incoming_crypto above.
*/
s->pending_newkeys = true;
crWaitUntilV(!s->pending_newkeys);
continue;
}
if (type == SSH2_MSG_USERAUTH_SUCCESS && !s->is_server) {
/*
* Another one: if we were configured with OpenSSH's
* deferred compression which is triggered on receipt
* of USERAUTH_SUCCESS, then this is the moment to
* turn on compression.
*/
ssh2_bpp_enable_pending_compression(s);
/*
* Whether or not we were doing delayed compression in
* _this_ set of crypto parameters, we should set a
* flag indicating that we're now authenticated, so
* that a delayed compression method enabled in any
* future rekey will be treated as un-delayed.
*/
s->seen_userauth_success = true;
}
if (type == SSH2_MSG_EXT_INFO) {
/*
* And another: enforce that an incoming EXT_INFO is
* either the message immediately after the initial
* NEWKEYS, or (if we're the client) the one
* immediately before USERAUTH_SUCCESS.
*/
if (prev_type == SSH2_MSG_NEWKEYS && s->nnewkeys == 1) {
/* OK - this is right after the first NEWKEYS. */
} else if (s->is_server) {
/* We're the server, so they're the client.
* Clients may not send EXT_INFO at _any_ other
* time. */
ssh_proto_error(s->bpp.ssh,
"Remote side sent SSH2_MSG_EXT_INFO "
"that was not immediately after the "
"initial NEWKEYS");
return;
} else if (s->nnewkeys > 0 && s->seen_userauth_success) {
/* We're the client, so they're the server. In
* that case they may also send EXT_INFO
* immediately before USERAUTH_SUCCESS. Error out
* immediately if this can't _possibly_ be that
* moment (because we haven't even seen NEWKEYS
* yet, or because we've already seen
* USERAUTH_SUCCESS). */
ssh_proto_error(s->bpp.ssh,
"Remote side sent SSH2_MSG_EXT_INFO "
"after USERAUTH_SUCCESS");
return;
} else {
/* This _could_ be OK, provided the next packet is
* USERAUTH_SUCCESS. Set a flag to remember to
* fault it if not. */
s->enforce_next_packet_is_userauth_success = true;
}
}
if (s->pending_compression && userauth_range(type)) {
/*
* Receiving any userauth message at all indicates
* that we're not about to turn on delayed compression
* - either because we just _have_ done, or because
* this message is a USERAUTH_FAILURE or some kind of
* intermediate 'please send more data' continuation
* message. Either way, we turn off the outgoing
* packet blockage for now, and release any queued
* output packets, so that we can make another attempt
* to authenticate. The next userauth packet we send
* will re-block the output direction.
*/
s->pending_compression = false;
queue_idempotent_callback(&s->bpp.ic_out_pq);
}
}
}
eof:
/*
* We've seen EOF. But we might have pushed stuff on the outgoing
* packet queue first, and that stuff _might_ include a DISCONNECT
* message, in which case we'd like to use that as the diagnostic.
* So first wait for the queue to have been processed.
*/
crMaybeWaitUntilV(!pq_peek(&s->bpp.in_pq));
if (!s->bpp.expect_close) {
ssh_remote_error(s->bpp.ssh,
"Remote side unexpectedly closed network connection");
} else {
ssh_remote_eof(s->bpp.ssh, "Remote side closed network connection");
}
return; /* avoid touching s now it's been freed */
crFinishV;
}
static PktOut *ssh2_bpp_new_pktout(int pkt_type)
{
PktOut *pkt = ssh_new_packet();
pkt->length = 5; /* space for packet length + padding length */
pkt->minlen = 0;
pkt->type = pkt_type;
put_byte(pkt, pkt_type);
pkt->prefix = pkt->length;
return pkt;
}
static void ssh2_bpp_format_packet_inner(struct ssh2_bpp_state *s, PktOut *pkt)
{
int origlen, cipherblk, maclen, padding, unencrypted_prefix, i;
if (s->bpp.logctx) {
ptrlen pktdata = make_ptrlen(pkt->data + pkt->prefix,
pkt->length - pkt->prefix);
logblank_t blanks[MAX_BLANKS];
int nblanks = ssh2_censor_packet(
s->bpp.pls, pkt->type, true, pktdata, blanks);
log_packet(s->bpp.logctx, PKT_OUTGOING, pkt->type,
ssh2_pkt_type(s->bpp.pls->kctx, s->bpp.pls->actx,
pkt->type),
pktdata.ptr, pktdata.len, nblanks, blanks, &s->out.sequence,
pkt->downstream_id, pkt->additional_log_text);
}
cipherblk = s->out.cipher ? ssh_cipher_alg(s->out.cipher)->blksize : 8;
cipherblk = cipherblk < 8 ? 8 : cipherblk; /* or 8 if blksize < 8 */
if (s->out_comp) {
unsigned char *newpayload;
int minlen, newlen;
/*
* Compress packet payload.
*/
minlen = pkt->minlen;
if (minlen) {
/*
* Work out how much compressed data we need (at least) to
* make the overall packet length come to pkt->minlen.
*/
if (s->out.mac)
minlen -= ssh2_mac_alg(s->out.mac)->len;
minlen -= 8; /* length field + min padding */
}
ssh_compressor_compress(s->out_comp, pkt->data + 5, pkt->length - 5,
&newpayload, &newlen, minlen);
pkt->length = 5;
put_data(pkt, newpayload, newlen);
sfree(newpayload);
}
/*
* Add padding. At least four bytes, and must also bring total
* length (minus MAC) up to a multiple of the block size.
* If pkt->forcepad is set, make sure the packet is at least that size
* after padding.
*/
padding = 4;
unencrypted_prefix = (s->out.mac && s->out.etm_mode) ? 4 : 0;
padding +=
(cipherblk - (pkt->length - unencrypted_prefix + padding) % cipherblk)
% cipherblk;
assert(padding <= 255);
maclen = s->out.mac ? ssh2_mac_alg(s->out.mac)->len : 0;
origlen = pkt->length;
for (i = 0; i < padding; i++)
put_byte(pkt, 0); /* make space for random padding */
random_read(pkt->data + origlen, padding);
pkt->data[4] = padding;
PUT_32BIT_MSB_FIRST(pkt->data, origlen + padding - 4);
/* Encrypt length if the scheme requires it */
if (s->out.cipher &&
(ssh_cipher_alg(s->out.cipher)->flags & SSH_CIPHER_SEPARATE_LENGTH)) {
ssh_cipher_encrypt_length(s->out.cipher, pkt->data, 4,
s->out.sequence);
}
put_padding(pkt, maclen, 0);
if (s->out.mac && s->out.etm_mode) {
/*
* OpenSSH-defined encrypt-then-MAC protocol.
*/
if (s->out.cipher)
ssh_cipher_encrypt(s->out.cipher,
pkt->data + 4, origlen + padding - 4);
ssh2_mac_generate(s->out.mac, pkt->data, origlen + padding,
s->out.sequence);
} else {
/*
* SSH-2 standard protocol.
*/
if (s->out.mac)
ssh2_mac_generate(s->out.mac, pkt->data, origlen + padding,
s->out.sequence);
if (s->out.cipher)
ssh_cipher_encrypt(s->out.cipher, pkt->data, origlen + padding);
}
s->out.sequence++; /* whether or not we MACed */
dts_consume(&s->stats->out, origlen + padding);
}
static void ssh2_bpp_format_packet(struct ssh2_bpp_state *s, PktOut *pkt)
{
if (pkt->minlen > 0 && !s->out_comp) {
/*
* If we've been told to pad the packet out to a given minimum
* length, but we're not compressing (and hence can't get the
* compression to do the padding by pointlessly opening and
* closing zlib blocks), then our other strategy is to precede
* this message with an SSH_MSG_IGNORE that makes it up to the
* right length.
*
* A third option in principle, and the most obviously
* sensible, would be to set the explicit padding field in the
* packet to more than its minimum value. Sadly, that turns
* out to break some servers (our institutional memory thinks
* Cisco in particular) and so we abandoned that idea shortly
* after trying it.
*/
/*
* Calculate the length we expect the real packet to have.
*/
int block, length;
PktOut *ignore_pkt;
block = s->out.cipher ? ssh_cipher_alg(s->out.cipher)->blksize : 0;
if (block < 8)
block = 8;
length = pkt->length;
length += 4; /* minimum 4 byte padding */
length += block-1;
length -= (length % block);
if (s->out.mac)
length += ssh2_mac_alg(s->out.mac)->len;
if (length < pkt->minlen) {
/*
* We need an ignore message. Calculate its length.
*/
length = pkt->minlen - length;
/*
* And work backwards from that to the length of the
* contained string.
*/
if (s->out.mac)
length -= ssh2_mac_alg(s->out.mac)->len;
length -= 8; /* length field + min padding */
length -= 5; /* type code + string length prefix */
if (length < 0)
length = 0;
ignore_pkt = ssh2_bpp_new_pktout(SSH2_MSG_IGNORE);
put_uint32(ignore_pkt, length);
size_t origlen = ignore_pkt->length;
for (size_t i = 0; i < length; i++)
put_byte(ignore_pkt, 0); /* make space for random padding */
random_read(ignore_pkt->data + origlen, length);
ssh2_bpp_format_packet_inner(s, ignore_pkt);
bufchain_add(s->bpp.out_raw, ignore_pkt->data, ignore_pkt->length);
ssh_free_pktout(ignore_pkt);
}
}
ssh2_bpp_format_packet_inner(s, pkt);
bufchain_add(s->bpp.out_raw, pkt->data, pkt->length);
}
static void ssh2_bpp_handle_output(BinaryPacketProtocol *bpp)
{
struct ssh2_bpp_state *s = container_of(bpp, struct ssh2_bpp_state, bpp);
PktOut *pkt;
int n_userauth;
/*
* Count the userauth packets in the queue.
*/
n_userauth = 0;
for (pkt = pq_first(&s->bpp.out_pq); pkt != NULL;
pkt = pq_next(&s->bpp.out_pq, pkt))
if (userauth_range(pkt->type))
n_userauth++;
if (s->pending_compression && !n_userauth) {
/*
* We're currently blocked from sending any outgoing packets
* until the other end tells us whether we're going to have to
* enable compression or not.
*
* If our end has pushed a userauth packet on the queue, that
* must mean it knows that a USERAUTH_SUCCESS is not
* immediately forthcoming, so we unblock ourselves and send
* up to and including that packet. But in this if statement,
* there aren't any, so we're still blocked.
*/
return;
}
if (s->cbc_ignore_workaround) {
/*
* When using a CBC-mode cipher in SSH-2, it's necessary to
* ensure that an attacker can't provide data to be encrypted
* using an IV that they know. We ensure this by inserting an
* SSH_MSG_IGNORE if the last cipher block of the previous
* packet has already been sent to the network (which we
* approximate conservatively by checking if it's vanished
* from out_raw).
*/
if (bufchain_size(s->bpp.out_raw) <
(ssh_cipher_alg(s->out.cipher)->blksize +
ssh2_mac_alg(s->out.mac)->len)) {
/*
* There's less data in out_raw than the MAC size plus the
* cipher block size, which means at least one byte of
* that cipher block must already have left. Add an
* IGNORE.
*/
pkt = ssh_bpp_new_pktout(&s->bpp, SSH2_MSG_IGNORE);
put_stringz(pkt, "");
ssh2_bpp_format_packet(s, pkt);
}
}
while ((pkt = pq_pop(&s->bpp.out_pq)) != NULL) {
int type = pkt->type;
if (userauth_range(type))
n_userauth--;
ssh2_bpp_format_packet(s, pkt);
ssh_free_pktout(pkt);
if (n_userauth == 0 && s->out.pending_compression && !s->is_server) {
/*
* This is the last userauth packet in the queue, so
* unless our side decides to send another one in future,
* we have to assume will potentially provoke
* USERAUTH_SUCCESS. Block (non-userauth) outgoing packets
* until we see the reply.
*/
s->pending_compression = true;
return;
} else if (type == SSH2_MSG_USERAUTH_SUCCESS && s->is_server) {
ssh2_bpp_enable_pending_compression(s);
}
}
}

76
ssh/censor1.c Normal file
View File

@ -0,0 +1,76 @@
/*
* Packet-censoring code for SSH-1, used to identify sensitive fields
* like passwords so that the logging system can avoid writing them
* into log files.
*/
#include <assert.h>
#include "putty.h"
#include "ssh.h"
int ssh1_censor_packet(
const PacketLogSettings *pls, int type, bool sender_is_client,
ptrlen pkt, logblank_t *blanks)
{
int nblanks = 0;
ptrlen str;
BinarySource src[1];
BinarySource_BARE_INIT_PL(src, pkt);
if (pls->omit_data &&
(type == SSH1_SMSG_STDOUT_DATA ||
type == SSH1_SMSG_STDERR_DATA ||
type == SSH1_CMSG_STDIN_DATA ||
type == SSH1_MSG_CHANNEL_DATA)) {
/* "Session data" packets - omit the data string. */
if (type == SSH1_MSG_CHANNEL_DATA)
get_uint32(src); /* skip channel id */
str = get_string(src);
if (!get_err(src)) {
assert(nblanks < MAX_BLANKS);
blanks[nblanks].offset = src->pos - str.len;
blanks[nblanks].type = PKTLOG_OMIT;
blanks[nblanks].len = str.len;
nblanks++;
}
}
if (sender_is_client && pls->omit_passwords) {
if (type == SSH1_CMSG_AUTH_PASSWORD ||
type == SSH1_CMSG_AUTH_TIS_RESPONSE ||
type == SSH1_CMSG_AUTH_CCARD_RESPONSE) {
/* If this is a password or similar packet, blank the
* password(s). */
assert(nblanks < MAX_BLANKS);
blanks[nblanks].offset = 0;
blanks[nblanks].len = pkt.len;
blanks[nblanks].type = PKTLOG_BLANK;
nblanks++;
} else if (type == SSH1_CMSG_X11_REQUEST_FORWARDING) {
/*
* If this is an X forwarding request packet, blank the
* fake auth data.
*
* Note that while we blank the X authentication data
* here, we don't take any special action to blank the
* start of an X11 channel, so using MIT-MAGIC-COOKIE-1
* and actually opening an X connection without having
* session blanking enabled is likely to leak your cookie
* into the log.
*/
get_string(src); /* skip protocol name */
str = get_string(src);
if (!get_err(src)) {
assert(nblanks < MAX_BLANKS);
blanks[nblanks].offset = src->pos - str.len;
blanks[nblanks].type = PKTLOG_BLANK;
blanks[nblanks].len = str.len;
nblanks++;
}
}
}
return nblanks;
}

107
ssh/censor2.c Normal file
View File

@ -0,0 +1,107 @@
/*
* Packet-censoring code for SSH-2, used to identify sensitive fields
* like passwords so that the logging system can avoid writing them
* into log files.
*/
#include <assert.h>
#include "putty.h"
#include "ssh.h"
int ssh2_censor_packet(
const PacketLogSettings *pls, int type, bool sender_is_client,
ptrlen pkt, logblank_t *blanks)
{
int nblanks = 0;
ptrlen str;
BinarySource src[1];
BinarySource_BARE_INIT_PL(src, pkt);
if (pls->omit_data &&
(type == SSH2_MSG_CHANNEL_DATA ||
type == SSH2_MSG_CHANNEL_EXTENDED_DATA)) {
/* "Session data" packets - omit the data string. */
get_uint32(src); /* skip channel id */
if (type == SSH2_MSG_CHANNEL_EXTENDED_DATA)
get_uint32(src); /* skip extended data type */
str = get_string(src);
if (!get_err(src)) {
assert(nblanks < MAX_BLANKS);
blanks[nblanks].offset = src->pos - str.len;
blanks[nblanks].type = PKTLOG_OMIT;
blanks[nblanks].len = str.len;
nblanks++;
}
}
if (sender_is_client && pls->omit_passwords) {
if (type == SSH2_MSG_USERAUTH_REQUEST) {
/* If this is a password packet, blank the password(s). */
get_string(src); /* username */
get_string(src); /* service name */
str = get_string(src); /* auth method */
if (ptrlen_eq_string(str, "password")) {
get_bool(src);
/* Blank the password field. */
str = get_string(src);
if (!get_err(src)) {
assert(nblanks < MAX_BLANKS);
blanks[nblanks].offset = src->pos - str.len;
blanks[nblanks].type = PKTLOG_BLANK;
blanks[nblanks].len = str.len;
nblanks++;
/* If there's another password field beyond it
* (change of password), blank that too. */
str = get_string(src);
if (!get_err(src))
blanks[nblanks-1].len =
src->pos - blanks[nblanks].offset;
}
}
} else if (pls->actx == SSH2_PKTCTX_KBDINTER &&
type == SSH2_MSG_USERAUTH_INFO_RESPONSE) {
/* If this is a keyboard-interactive response packet,
* blank the responses. */
get_uint32(src);
assert(nblanks < MAX_BLANKS);
blanks[nblanks].offset = src->pos;
blanks[nblanks].type = PKTLOG_BLANK;
do {
str = get_string(src);
} while (!get_err(src));
blanks[nblanks].len = src->pos - blanks[nblanks].offset;
nblanks++;
} else if (type == SSH2_MSG_CHANNEL_REQUEST) {
/*
* If this is an X forwarding request packet, blank the
* fake auth data.
*
* Note that while we blank the X authentication data
* here, we don't take any special action to blank the
* start of an X11 channel, so using MIT-MAGIC-COOKIE-1
* and actually opening an X connection without having
* session blanking enabled is likely to leak your cookie
* into the log.
*/
get_uint32(src);
str = get_string(src);
if (ptrlen_eq_string(str, "x11-req")) {
get_bool(src);
get_bool(src);
get_string(src);
str = get_string(src);
if (!get_err(src)) {
assert(nblanks < MAX_BLANKS);
blanks[nblanks].offset = src->pos - str.len;
blanks[nblanks].type = PKTLOG_BLANK;
blanks[nblanks].len = str.len;
nblanks++;
}
}
}
}
return nblanks;
}

316
ssh/channel.h Normal file
View File

@ -0,0 +1,316 @@
/*
* Abstraction of the various ways to handle the local end of an SSH
* connection-layer channel.
*/
#ifndef PUTTY_SSHCHAN_H
#define PUTTY_SSHCHAN_H
typedef struct ChannelVtable ChannelVtable;
struct ChannelVtable {
void (*free)(Channel *);
/* Called for channel types that were created at the same time as
* we sent an outgoing CHANNEL_OPEN, when the confirmation comes
* back from the server indicating that the channel has been
* opened, or the failure message indicating that it hasn't,
* respectively. In the latter case, this must _not_ free the
* Channel structure - the client will call the free method
* separately. But it might do logging or other local cleanup. */
void (*open_confirmation)(Channel *);
void (*open_failed)(Channel *, const char *error_text);
size_t (*send)(Channel *, bool is_stderr, const void *buf, size_t len);
void (*send_eof)(Channel *);
void (*set_input_wanted)(Channel *, bool wanted);
char *(*log_close_msg)(Channel *);
bool (*want_close)(Channel *, bool sent_local_eof, bool rcvd_remote_eof);
/* A method for every channel request we know of. All of these
* return true for success or false for failure. */
bool (*rcvd_exit_status)(Channel *, int status);
bool (*rcvd_exit_signal)(
Channel *chan, ptrlen signame, bool core_dumped, ptrlen msg);
bool (*rcvd_exit_signal_numeric)(
Channel *chan, int signum, bool core_dumped, ptrlen msg);
bool (*run_shell)(Channel *chan);
bool (*run_command)(Channel *chan, ptrlen command);
bool (*run_subsystem)(Channel *chan, ptrlen subsys);
bool (*enable_x11_forwarding)(
Channel *chan, bool oneshot, ptrlen authproto, ptrlen authdata,
unsigned screen_number);
bool (*enable_agent_forwarding)(Channel *chan);
bool (*allocate_pty)(
Channel *chan, ptrlen termtype, unsigned width, unsigned height,
unsigned pixwidth, unsigned pixheight, struct ssh_ttymodes modes);
bool (*set_env)(Channel *chan, ptrlen var, ptrlen value);
bool (*send_break)(Channel *chan, unsigned length);
bool (*send_signal)(Channel *chan, ptrlen signame);
bool (*change_window_size)(
Channel *chan, unsigned width, unsigned height,
unsigned pixwidth, unsigned pixheight);
/* A method for signalling success/failure responses to channel
* requests initiated from the SshChannel vtable with want_reply
* true. */
void (*request_response)(Channel *, bool success);
};
struct Channel {
const struct ChannelVtable *vt;
unsigned initial_fixed_window_size;
};
static inline void chan_free(Channel *ch)
{ ch->vt->free(ch); }
static inline void chan_open_confirmation(Channel *ch)
{ ch->vt->open_confirmation(ch); }
static inline void chan_open_failed(Channel *ch, const char *err)
{ ch->vt->open_failed(ch, err); }
static inline size_t chan_send(
Channel *ch, bool err, const void *buf, size_t len)
{ return ch->vt->send(ch, err, buf, len); }
static inline void chan_send_eof(Channel *ch)
{ ch->vt->send_eof(ch); }
static inline void chan_set_input_wanted(Channel *ch, bool wanted)
{ ch->vt->set_input_wanted(ch, wanted); }
static inline char *chan_log_close_msg(Channel *ch)
{ return ch->vt->log_close_msg(ch); }
static inline bool chan_want_close(Channel *ch, bool leof, bool reof)
{ return ch->vt->want_close(ch, leof, reof); }
static inline bool chan_rcvd_exit_status(Channel *ch, int status)
{ return ch->vt->rcvd_exit_status(ch, status); }
static inline bool chan_rcvd_exit_signal(
Channel *ch, ptrlen sig, bool core, ptrlen msg)
{ return ch->vt->rcvd_exit_signal(ch, sig, core, msg); }
static inline bool chan_rcvd_exit_signal_numeric(
Channel *ch, int sig, bool core, ptrlen msg)
{ return ch->vt->rcvd_exit_signal_numeric(ch, sig, core, msg); }
static inline bool chan_run_shell(Channel *ch)
{ return ch->vt->run_shell(ch); }
static inline bool chan_run_command(Channel *ch, ptrlen cmd)
{ return ch->vt->run_command(ch, cmd); }
static inline bool chan_run_subsystem(Channel *ch, ptrlen subsys)
{ return ch->vt->run_subsystem(ch, subsys); }
static inline bool chan_enable_x11_forwarding(
Channel *ch, bool oneshot, ptrlen ap, ptrlen ad, unsigned scr)
{ return ch->vt->enable_x11_forwarding(ch, oneshot, ap, ad, scr); }
static inline bool chan_enable_agent_forwarding(Channel *ch)
{ return ch->vt->enable_agent_forwarding(ch); }
static inline bool chan_allocate_pty(
Channel *ch, ptrlen termtype, unsigned w, unsigned h,
unsigned pw, unsigned ph, struct ssh_ttymodes modes)
{ return ch->vt->allocate_pty(ch, termtype, w, h, pw, ph, modes); }
static inline bool chan_set_env(Channel *ch, ptrlen var, ptrlen value)
{ return ch->vt->set_env(ch, var, value); }
static inline bool chan_send_break(Channel *ch, unsigned length)
{ return ch->vt->send_break(ch, length); }
static inline bool chan_send_signal(Channel *ch, ptrlen signame)
{ return ch->vt->send_signal(ch, signame); }
static inline bool chan_change_window_size(
Channel *ch, unsigned w, unsigned h, unsigned pw, unsigned ph)
{ return ch->vt->change_window_size(ch, w, h, pw, ph); }
static inline void chan_request_response(Channel *ch, bool success)
{ ch->vt->request_response(ch, success); }
/*
* Reusable methods you can put in vtables to give default handling of
* some of those functions.
*/
/* open_confirmation / open_failed for any channel it doesn't apply to */
void chan_remotely_opened_confirmation(Channel *chan);
void chan_remotely_opened_failure(Channel *chan, const char *errtext);
/* want_close for any channel that wants the default behaviour of not
* closing until both directions have had an EOF */
bool chan_default_want_close(Channel *, bool, bool);
/* default implementations that refuse all the channel requests */
bool chan_no_exit_status(Channel *, int);
bool chan_no_exit_signal(Channel *, ptrlen, bool, ptrlen);
bool chan_no_exit_signal_numeric(Channel *, int, bool, ptrlen);
bool chan_no_run_shell(Channel *chan);
bool chan_no_run_command(Channel *chan, ptrlen command);
bool chan_no_run_subsystem(Channel *chan, ptrlen subsys);
bool chan_no_enable_x11_forwarding(
Channel *chan, bool oneshot, ptrlen authproto, ptrlen authdata,
unsigned screen_number);
bool chan_no_enable_agent_forwarding(Channel *chan);
bool chan_no_allocate_pty(
Channel *chan, ptrlen termtype, unsigned width, unsigned height,
unsigned pixwidth, unsigned pixheight, struct ssh_ttymodes modes);
bool chan_no_set_env(Channel *chan, ptrlen var, ptrlen value);
bool chan_no_send_break(Channel *chan, unsigned length);
bool chan_no_send_signal(Channel *chan, ptrlen signame);
bool chan_no_change_window_size(
Channel *chan, unsigned width, unsigned height,
unsigned pixwidth, unsigned pixheight);
/* default implementation that never expects to receive a response */
void chan_no_request_response(Channel *, bool);
/*
* Constructor for a trivial do-nothing implementation of
* ChannelVtable. Used for 'zombie' channels, i.e. channels whose
* proper local source of data has been shut down or otherwise stopped
* existing, but the SSH side is still there and needs some kind of a
* Channel implementation to talk to. In particular, the want_close
* method for this channel always returns 'yes, please close this
* channel asap', regardless of whether local and/or remote EOF have
* been sent - indeed, even if _neither_ has.
*/
Channel *zombiechan_new(void);
/* ----------------------------------------------------------------------
* This structure is owned by an SSH connection layer, and identifies
* the connection layer's end of the channel, for the Channel
* implementation to talk back to.
*/
typedef struct SshChannelVtable SshChannelVtable;
struct SshChannelVtable {
size_t (*write)(SshChannel *c, bool is_stderr, const void *, size_t);
void (*write_eof)(SshChannel *c);
void (*initiate_close)(SshChannel *c, const char *err);
void (*unthrottle)(SshChannel *c, size_t bufsize);
Conf *(*get_conf)(SshChannel *c);
void (*window_override_removed)(SshChannel *c);
void (*x11_sharing_handover)(SshChannel *c,
ssh_sharing_connstate *share_cs,
share_channel *share_chan,
const char *peer_addr, int peer_port,
int endian, int protomajor, int protominor,
const void *initial_data, int initial_len);
/*
* All the outgoing channel requests we support. Each one has a
* want_reply flag, which will cause a callback to
* chan_request_response when the result is available.
*
* The ones that return 'bool' use it to indicate that the SSH
* protocol in use doesn't support this request at all.
*
* (It's also intentional that not all of them have a want_reply
* flag: the ones that don't are because SSH-1 has no method for
* signalling success or failure of that request, or because we
* wouldn't do anything usefully different with the reply in any
* case.)
*/
void (*send_exit_status)(SshChannel *c, int status);
void (*send_exit_signal)(
SshChannel *c, ptrlen signame, bool core_dumped, ptrlen msg);
void (*send_exit_signal_numeric)(
SshChannel *c, int signum, bool core_dumped, ptrlen msg);
void (*request_x11_forwarding)(
SshChannel *c, bool want_reply, const char *authproto,
const char *authdata, int screen_number, bool oneshot);
void (*request_agent_forwarding)(
SshChannel *c, bool want_reply);
void (*request_pty)(
SshChannel *c, bool want_reply, Conf *conf, int w, int h);
bool (*send_env_var)(
SshChannel *c, bool want_reply, const char *var, const char *value);
void (*start_shell)(
SshChannel *c, bool want_reply);
void (*start_command)(
SshChannel *c, bool want_reply, const char *command);
bool (*start_subsystem)(
SshChannel *c, bool want_reply, const char *subsystem);
bool (*send_serial_break)(
SshChannel *c, bool want_reply, int length); /* length=0 for default */
bool (*send_signal)(
SshChannel *c, bool want_reply, const char *signame);
void (*send_terminal_size_change)(
SshChannel *c, int w, int h);
void (*hint_channel_is_simple)(SshChannel *c);
};
struct SshChannel {
const struct SshChannelVtable *vt;
ConnectionLayer *cl;
};
static inline size_t sshfwd_write_ext(
SshChannel *c, bool is_stderr, const void *data, size_t len)
{ return c->vt->write(c, is_stderr, data, len); }
static inline size_t sshfwd_write(SshChannel *c, const void *data, size_t len)
{ return sshfwd_write_ext(c, false, data, len); }
static inline void sshfwd_write_eof(SshChannel *c)
{ c->vt->write_eof(c); }
static inline void sshfwd_initiate_close(SshChannel *c, const char *err)
{ c->vt->initiate_close(c, err); }
static inline void sshfwd_unthrottle(SshChannel *c, size_t bufsize)
{ c->vt->unthrottle(c, bufsize); }
static inline Conf *sshfwd_get_conf(SshChannel *c)
{ return c->vt->get_conf(c); }
static inline void sshfwd_window_override_removed(SshChannel *c)
{ c->vt->window_override_removed(c); }
static inline void sshfwd_x11_sharing_handover(
SshChannel *c, ssh_sharing_connstate *cs, share_channel *sch,
const char *addr, int port, int endian, int maj, int min,
const void *idata, int ilen)
{ c->vt->x11_sharing_handover(c, cs, sch, addr, port, endian,
maj, min, idata, ilen); }
static inline void sshfwd_send_exit_status(SshChannel *c, int status)
{ c->vt->send_exit_status(c, status); }
static inline void sshfwd_send_exit_signal(
SshChannel *c, ptrlen signame, bool core_dumped, ptrlen msg)
{ c->vt->send_exit_signal(c, signame, core_dumped, msg); }
static inline void sshfwd_send_exit_signal_numeric(
SshChannel *c, int signum, bool core_dumped, ptrlen msg)
{ c->vt->send_exit_signal_numeric(c, signum, core_dumped, msg); }
static inline void sshfwd_request_x11_forwarding(
SshChannel *c, bool want_reply, const char *proto,
const char *data, int scr, bool once)
{ c->vt->request_x11_forwarding(c, want_reply, proto, data, scr, once); }
static inline void sshfwd_request_agent_forwarding(
SshChannel *c, bool want_reply)
{ c->vt->request_agent_forwarding(c, want_reply); }
static inline void sshfwd_request_pty(
SshChannel *c, bool want_reply, Conf *conf, int w, int h)
{ c->vt->request_pty(c, want_reply, conf, w, h); }
static inline bool sshfwd_send_env_var(
SshChannel *c, bool want_reply, const char *var, const char *value)
{ return c->vt->send_env_var(c, want_reply, var, value); }
static inline void sshfwd_start_shell(
SshChannel *c, bool want_reply)
{ c->vt->start_shell(c, want_reply); }
static inline void sshfwd_start_command(
SshChannel *c, bool want_reply, const char *command)
{ c->vt->start_command(c, want_reply, command); }
static inline bool sshfwd_start_subsystem(
SshChannel *c, bool want_reply, const char *subsystem)
{ return c->vt->start_subsystem(c, want_reply, subsystem); }
static inline bool sshfwd_send_serial_break(
SshChannel *c, bool want_reply, int length)
{ return c->vt->send_serial_break(c, want_reply, length); }
static inline bool sshfwd_send_signal(
SshChannel *c, bool want_reply, const char *signame)
{ return c->vt->send_signal(c, want_reply, signame); }
static inline void sshfwd_send_terminal_size_change(
SshChannel *c, int w, int h)
{ c->vt->send_terminal_size_change(c, w, h); }
static inline void sshfwd_hint_channel_is_simple(SshChannel *c)
{ c->vt->hint_channel_is_simple(c); }
/* ----------------------------------------------------------------------
* The 'main' or primary channel of the SSH connection is special,
* because it's the one that's connected directly to parts of the
* frontend such as the terminal and the specials menu. So it exposes
* a richer API.
*/
mainchan *mainchan_new(
PacketProtocolLayer *ppl, ConnectionLayer *cl, Conf *conf,
int term_width, int term_height, bool is_simple, SshChannel **sc_out);
void mainchan_get_specials(
mainchan *mc, add_special_fn_t add_special, void *ctx);
void mainchan_special_cmd(mainchan *mc, SessionSpecialCode code, int arg);
void mainchan_terminal_size(mainchan *mc, int width, int height);
#endif /* PUTTY_SSHCHAN_H */

946
ssh/common.c Normal file
View File

@ -0,0 +1,946 @@
/*
* Supporting routines used in common by all the various components of
* the SSH system.
*/
#include <assert.h>
#include <stdlib.h>
#include "putty.h"
#include "mpint.h"
#include "ssh.h"
#include "bpp.h"
#include "ppl.h"
#include "channel.h"
/* ----------------------------------------------------------------------
* Implementation of PacketQueue.
*/
static void pq_ensure_unlinked(PacketQueueNode *node)
{
if (node->on_free_queue) {
node->next->prev = node->prev;
node->prev->next = node->next;
} else {
assert(!node->next);
assert(!node->prev);
}
}
void pq_base_push(PacketQueueBase *pqb, PacketQueueNode *node)
{
pq_ensure_unlinked(node);
node->next = &pqb->end;
node->prev = pqb->end.prev;
node->next->prev = node;
node->prev->next = node;
pqb->total_size += node->formal_size;
if (pqb->ic)
queue_idempotent_callback(pqb->ic);
}
void pq_base_push_front(PacketQueueBase *pqb, PacketQueueNode *node)
{
pq_ensure_unlinked(node);
node->prev = &pqb->end;
node->next = pqb->end.next;
node->next->prev = node;
node->prev->next = node;
pqb->total_size += node->formal_size;
if (pqb->ic)
queue_idempotent_callback(pqb->ic);
}
static PacketQueueNode pktin_freeq_head = {
&pktin_freeq_head, &pktin_freeq_head, true
};
static void pktin_free_queue_callback(void *vctx)
{
while (pktin_freeq_head.next != &pktin_freeq_head) {
PacketQueueNode *node = pktin_freeq_head.next;
PktIn *pktin = container_of(node, PktIn, qnode);
pktin_freeq_head.next = node->next;
sfree(pktin);
}
pktin_freeq_head.prev = &pktin_freeq_head;
}
static IdempotentCallback ic_pktin_free = {
pktin_free_queue_callback, NULL, false
};
static inline void pq_unlink_common(PacketQueueBase *pqb,
PacketQueueNode *node)
{
node->next->prev = node->prev;
node->prev->next = node->next;
/* Check total_size doesn't drift out of sync downwards, by
* ensuring it doesn't underflow when we do this subtraction */
assert(pqb->total_size >= node->formal_size);
pqb->total_size -= node->formal_size;
/* Check total_size doesn't drift out of sync upwards, by checking
* that it's returned to exactly zero whenever a queue is
* emptied */
assert(pqb->end.next != &pqb->end || pqb->total_size == 0);
}
static PktIn *pq_in_after(PacketQueueBase *pqb,
PacketQueueNode *prev, bool pop)
{
PacketQueueNode *node = prev->next;
if (node == &pqb->end)
return NULL;
if (pop) {
pq_unlink_common(pqb, node);
node->prev = pktin_freeq_head.prev;
node->next = &pktin_freeq_head;
node->next->prev = node;
node->prev->next = node;
node->on_free_queue = true;
queue_idempotent_callback(&ic_pktin_free);
}
return container_of(node, PktIn, qnode);
}
static PktOut *pq_out_after(PacketQueueBase *pqb,
PacketQueueNode *prev, bool pop)
{
PacketQueueNode *node = prev->next;
if (node == &pqb->end)
return NULL;
if (pop) {
pq_unlink_common(pqb, node);
node->prev = node->next = NULL;
}
return container_of(node, PktOut, qnode);
}
void pq_in_init(PktInQueue *pq)
{
pq->pqb.ic = NULL;
pq->pqb.end.next = pq->pqb.end.prev = &pq->pqb.end;
pq->after = pq_in_after;
pq->pqb.total_size = 0;
}
void pq_out_init(PktOutQueue *pq)
{
pq->pqb.ic = NULL;
pq->pqb.end.next = pq->pqb.end.prev = &pq->pqb.end;
pq->after = pq_out_after;
pq->pqb.total_size = 0;
}
void pq_in_clear(PktInQueue *pq)
{
PktIn *pkt;
pq->pqb.ic = NULL;
while ((pkt = pq_pop(pq)) != NULL) {
/* No need to actually free these packets: pq_pop on a
* PktInQueue will automatically move them to the free
* queue. */
}
}
void pq_out_clear(PktOutQueue *pq)
{
PktOut *pkt;
pq->pqb.ic = NULL;
while ((pkt = pq_pop(pq)) != NULL)
ssh_free_pktout(pkt);
}
/*
* Concatenate the contents of the two queues q1 and q2, and leave the
* result in qdest. qdest must be either empty, or one of the input
* queues.
*/
void pq_base_concatenate(PacketQueueBase *qdest,
PacketQueueBase *q1, PacketQueueBase *q2)
{
struct PacketQueueNode *head1, *tail1, *head2, *tail2;
size_t total_size = q1->total_size + q2->total_size;
/*
* Extract the contents from both input queues, and empty them.
*/
head1 = (q1->end.next == &q1->end ? NULL : q1->end.next);
tail1 = (q1->end.prev == &q1->end ? NULL : q1->end.prev);
head2 = (q2->end.next == &q2->end ? NULL : q2->end.next);
tail2 = (q2->end.prev == &q2->end ? NULL : q2->end.prev);
q1->end.next = q1->end.prev = &q1->end;
q2->end.next = q2->end.prev = &q2->end;
q1->total_size = q2->total_size = 0;
/*
* Link the two lists together, handling the case where one or
* both is empty.
*/
if (tail1)
tail1->next = head2;
else
head1 = head2;
if (head2)
head2->prev = tail1;
else
tail2 = tail1;
/*
* Check the destination queue is currently empty. (If it was one
* of the input queues, then it will be, because we emptied both
* of those just a moment ago.)
*/
assert(qdest->end.next == &qdest->end);
assert(qdest->end.prev == &qdest->end);
/*
* If our concatenated list has anything in it, then put it in
* dest.
*/
if (!head1) {
assert(!tail2);
} else {
assert(tail2);
qdest->end.next = head1;
qdest->end.prev = tail2;
head1->prev = &qdest->end;
tail2->next = &qdest->end;
if (qdest->ic)
queue_idempotent_callback(qdest->ic);
}
qdest->total_size = total_size;
}
/* ----------------------------------------------------------------------
* Low-level functions for the packet structures themselves.
*/
static void ssh_pkt_BinarySink_write(BinarySink *bs,
const void *data, size_t len);
PktOut *ssh_new_packet(void)
{
PktOut *pkt = snew(PktOut);
BinarySink_INIT(pkt, ssh_pkt_BinarySink_write);
pkt->data = NULL;
pkt->length = 0;
pkt->maxlen = 0;
pkt->downstream_id = 0;
pkt->additional_log_text = NULL;
pkt->qnode.next = pkt->qnode.prev = NULL;
pkt->qnode.on_free_queue = false;
return pkt;
}
static void ssh_pkt_adddata(PktOut *pkt, const void *data, int len)
{
sgrowarrayn_nm(pkt->data, pkt->maxlen, pkt->length, len);
memcpy(pkt->data + pkt->length, data, len);
pkt->length += len;
pkt->qnode.formal_size = pkt->length;
}
static void ssh_pkt_BinarySink_write(BinarySink *bs,
const void *data, size_t len)
{
PktOut *pkt = BinarySink_DOWNCAST(bs, PktOut);
ssh_pkt_adddata(pkt, data, len);
}
void ssh_free_pktout(PktOut *pkt)
{
sfree(pkt->data);
sfree(pkt);
}
/* ----------------------------------------------------------------------
* Implement zombiechan_new() and its trivial vtable.
*/
static void zombiechan_free(Channel *chan);
static size_t zombiechan_send(
Channel *chan, bool is_stderr, const void *, size_t);
static void zombiechan_set_input_wanted(Channel *chan, bool wanted);
static void zombiechan_do_nothing(Channel *chan);
static void zombiechan_open_failure(Channel *chan, const char *);
static bool zombiechan_want_close(Channel *chan, bool sent_eof, bool rcvd_eof);
static char *zombiechan_log_close_msg(Channel *chan) { return NULL; }
static const ChannelVtable zombiechan_channelvt = {
.free = zombiechan_free,
.open_confirmation = zombiechan_do_nothing,
.open_failed = zombiechan_open_failure,
.send = zombiechan_send,
.send_eof = zombiechan_do_nothing,
.set_input_wanted = zombiechan_set_input_wanted,
.log_close_msg = zombiechan_log_close_msg,
.want_close = zombiechan_want_close,
.rcvd_exit_status = chan_no_exit_status,
.rcvd_exit_signal = chan_no_exit_signal,
.rcvd_exit_signal_numeric = chan_no_exit_signal_numeric,
.run_shell = chan_no_run_shell,
.run_command = chan_no_run_command,
.run_subsystem = chan_no_run_subsystem,
.enable_x11_forwarding = chan_no_enable_x11_forwarding,
.enable_agent_forwarding = chan_no_enable_agent_forwarding,
.allocate_pty = chan_no_allocate_pty,
.set_env = chan_no_set_env,
.send_break = chan_no_send_break,
.send_signal = chan_no_send_signal,
.change_window_size = chan_no_change_window_size,
.request_response = chan_no_request_response,
};
Channel *zombiechan_new(void)
{
Channel *chan = snew(Channel);
chan->vt = &zombiechan_channelvt;
chan->initial_fixed_window_size = 0;
return chan;
}
static void zombiechan_free(Channel *chan)
{
assert(chan->vt == &zombiechan_channelvt);
sfree(chan);
}
static void zombiechan_do_nothing(Channel *chan)
{
assert(chan->vt == &zombiechan_channelvt);
}
static void zombiechan_open_failure(Channel *chan, const char *errtext)
{
assert(chan->vt == &zombiechan_channelvt);
}
static size_t zombiechan_send(Channel *chan, bool is_stderr,
const void *data, size_t length)
{
assert(chan->vt == &zombiechan_channelvt);
return 0;
}
static void zombiechan_set_input_wanted(Channel *chan, bool enable)
{
assert(chan->vt == &zombiechan_channelvt);
}
static bool zombiechan_want_close(Channel *chan, bool sent_eof, bool rcvd_eof)
{
return true;
}
/* ----------------------------------------------------------------------
* Common routines for handling SSH tty modes.
*/
static unsigned real_ttymode_opcode(unsigned our_opcode, int ssh_version)
{
switch (our_opcode) {
case TTYMODE_ISPEED:
return ssh_version == 1 ? TTYMODE_ISPEED_SSH1 : TTYMODE_ISPEED_SSH2;
case TTYMODE_OSPEED:
return ssh_version == 1 ? TTYMODE_OSPEED_SSH1 : TTYMODE_OSPEED_SSH2;
default:
return our_opcode;
}
}
static unsigned our_ttymode_opcode(unsigned real_opcode, int ssh_version)
{
if (ssh_version == 1) {
switch (real_opcode) {
case TTYMODE_ISPEED_SSH1:
return TTYMODE_ISPEED;
case TTYMODE_OSPEED_SSH1:
return TTYMODE_OSPEED;
default:
return real_opcode;
}
} else {
switch (real_opcode) {
case TTYMODE_ISPEED_SSH2:
return TTYMODE_ISPEED;
case TTYMODE_OSPEED_SSH2:
return TTYMODE_OSPEED;
default:
return real_opcode;
}
}
}
struct ssh_ttymodes get_ttymodes_from_conf(Seat *seat, Conf *conf)
{
struct ssh_ttymodes modes;
size_t i;
static const struct mode_name_type {
const char *mode;
int opcode;
enum { TYPE_CHAR, TYPE_BOOL } type;
} modes_names_types[] = {
#define TTYMODE_CHAR(name, val, index) { #name, val, TYPE_CHAR },
#define TTYMODE_FLAG(name, val, field, mask) { #name, val, TYPE_BOOL },
#include "ttymode-list.h"
#undef TTYMODE_CHAR
#undef TTYMODE_FLAG
};
memset(&modes, 0, sizeof(modes));
for (i = 0; i < lenof(modes_names_types); i++) {
const struct mode_name_type *mode = &modes_names_types[i];
const char *sval = conf_get_str_str(conf, CONF_ttymodes, mode->mode);
char *to_free = NULL;
if (!sval)
sval = "N"; /* just in case */
/*
* sval[0] can be
* - 'V', indicating that an explicit value follows it;
* - 'A', indicating that we should pass the value through from
* the local environment via get_ttymode; or
* - 'N', indicating that we should explicitly not send this
* mode.
*/
if (sval[0] == 'A') {
sval = to_free = seat_get_ttymode(seat, mode->mode);
} else if (sval[0] == 'V') {
sval++; /* skip the 'V' */
} else {
/* else 'N', or something from the future we don't understand */
continue;
}
if (sval) {
/*
* Parse the string representation of the tty mode
* into the integer value it will take on the wire.
*/
unsigned ival = 0;
switch (mode->type) {
case TYPE_CHAR:
if (*sval) {
char *next = NULL;
/* We know ctrlparse won't write to the string, so
* casting away const is ugly but allowable. */
ival = ctrlparse((char *)sval, &next);
if (!next)
ival = sval[0];
} else {
ival = 255; /* special value meaning "don't set" */
}
break;
case TYPE_BOOL:
if (stricmp(sval, "yes") == 0 ||
stricmp(sval, "on") == 0 ||
stricmp(sval, "true") == 0 ||
stricmp(sval, "+") == 0)
ival = 1; /* true */
else if (stricmp(sval, "no") == 0 ||
stricmp(sval, "off") == 0 ||
stricmp(sval, "false") == 0 ||
stricmp(sval, "-") == 0)
ival = 0; /* false */
else
ival = (atoi(sval) != 0);
break;
default:
unreachable("Bad mode->type");
}
modes.have_mode[mode->opcode] = true;
modes.mode_val[mode->opcode] = ival;
}
sfree(to_free);
}
{
unsigned ospeed, ispeed;
/* Unpick the terminal-speed config string. */
ospeed = ispeed = 38400; /* last-resort defaults */
sscanf(conf_get_str(conf, CONF_termspeed), "%u,%u", &ospeed, &ispeed);
/* Currently we unconditionally set these */
modes.have_mode[TTYMODE_ISPEED] = true;
modes.mode_val[TTYMODE_ISPEED] = ispeed;
modes.have_mode[TTYMODE_OSPEED] = true;
modes.mode_val[TTYMODE_OSPEED] = ospeed;
}
return modes;
}
struct ssh_ttymodes read_ttymodes_from_packet(
BinarySource *bs, int ssh_version)
{
struct ssh_ttymodes modes;
memset(&modes, 0, sizeof(modes));
while (1) {
unsigned real_opcode, our_opcode;
real_opcode = get_byte(bs);
if (real_opcode == TTYMODE_END_OF_LIST)
break;
if (real_opcode >= 160) {
/*
* RFC 4254 (and the SSH 1.5 spec): "Opcodes 160 to 255
* are not yet defined, and cause parsing to stop (they
* should only be used after any other data)."
*
* My interpretation of this is that if one of these
* opcodes appears, it's not a parse _error_, but it is
* something that we don't know how to parse even well
* enough to step over it to find the next opcode, so we
* stop parsing now and assume that the rest of the string
* is composed entirely of things we don't understand and
* (as usual for unsupported terminal modes) silently
* ignore.
*/
return modes;
}
our_opcode = our_ttymode_opcode(real_opcode, ssh_version);
assert(our_opcode < TTYMODE_LIMIT);
modes.have_mode[our_opcode] = true;
if (ssh_version == 1 && real_opcode >= 1 && real_opcode <= 127)
modes.mode_val[our_opcode] = get_byte(bs);
else
modes.mode_val[our_opcode] = get_uint32(bs);
}
return modes;
}
void write_ttymodes_to_packet(BinarySink *bs, int ssh_version,
struct ssh_ttymodes modes)
{
unsigned i;
for (i = 0; i < TTYMODE_LIMIT; i++) {
if (modes.have_mode[i]) {
unsigned val = modes.mode_val[i];
unsigned opcode = real_ttymode_opcode(i, ssh_version);
put_byte(bs, opcode);
if (ssh_version == 1 && opcode >= 1 && opcode <= 127)
put_byte(bs, val);
else
put_uint32(bs, val);
}
}
put_byte(bs, TTYMODE_END_OF_LIST);
}
/* ----------------------------------------------------------------------
* Routine for allocating a new channel ID, given a means of finding
* the index field in a given channel structure.
*/
unsigned alloc_channel_id_general(tree234 *channels, size_t localid_offset)
{
const unsigned CHANNEL_NUMBER_OFFSET = 256;
search234_state ss;
/*
* First-fit allocation of channel numbers: we always pick the
* lowest unused one.
*
* Every channel before that, and no channel after it, has an ID
* exactly equal to its tree index plus CHANNEL_NUMBER_OFFSET. So
* we can use the search234 system to identify the length of that
* initial sequence, in a single log-time pass down the channels
* tree.
*/
search234_start(&ss, channels);
while (ss.element) {
unsigned localid = *(unsigned *)((char *)ss.element + localid_offset);
if (localid == ss.index + CHANNEL_NUMBER_OFFSET)
search234_step(&ss, +1);
else
search234_step(&ss, -1);
}
/*
* Now ss.index gives exactly the number of channels in that
* initial sequence. So adding CHANNEL_NUMBER_OFFSET to it must
* give precisely the lowest unused channel number.
*/
return ss.index + CHANNEL_NUMBER_OFFSET;
}
/* ----------------------------------------------------------------------
* Functions for handling the comma-separated strings used to store
* lists of protocol identifiers in SSH-2.
*/
void add_to_commasep(strbuf *buf, const char *data)
{
if (buf->len > 0)
put_byte(buf, ',');
put_data(buf, data, strlen(data));
}
bool get_commasep_word(ptrlen *list, ptrlen *word)
{
const char *comma;
/*
* Discard empty list elements, should there be any, because we
* never want to return one as if it was a real string. (This
* introduces a mild tolerance of badly formatted data in lists we
* receive, but I think that's acceptable.)
*/
while (list->len > 0 && *(const char *)list->ptr == ',') {
list->ptr = (const char *)list->ptr + 1;
list->len--;
}
if (!list->len)
return false;
comma = memchr(list->ptr, ',', list->len);
if (!comma) {
*word = *list;
list->len = 0;
} else {
size_t wordlen = comma - (const char *)list->ptr;
word->ptr = list->ptr;
word->len = wordlen;
list->ptr = (const char *)list->ptr + wordlen + 1;
list->len -= wordlen + 1;
}
return true;
}
/* ----------------------------------------------------------------------
* Functions for translating SSH packet type codes into their symbolic
* string names.
*/
#define TRANSLATE_UNIVERSAL(y, name, value) \
if (type == value) return #name;
#define TRANSLATE_KEX(y, name, value, ctx) \
if (type == value && pkt_kctx == ctx) return #name;
#define TRANSLATE_AUTH(y, name, value, ctx) \
if (type == value && pkt_actx == ctx) return #name;
const char *ssh1_pkt_type(int type)
{
SSH1_MESSAGE_TYPES(TRANSLATE_UNIVERSAL, y);
return "unknown";
}
const char *ssh2_pkt_type(Pkt_KCtx pkt_kctx, Pkt_ACtx pkt_actx, int type)
{
SSH2_MESSAGE_TYPES(TRANSLATE_UNIVERSAL, TRANSLATE_KEX, TRANSLATE_AUTH, y);
return "unknown";
}
#undef TRANSLATE_UNIVERSAL
#undef TRANSLATE_KEX
#undef TRANSLATE_AUTH
/* ----------------------------------------------------------------------
* Common helper function for clients and implementations of
* PacketProtocolLayer.
*/
void ssh_ppl_replace(PacketProtocolLayer *old, PacketProtocolLayer *new)
{
new->bpp = old->bpp;
ssh_ppl_setup_queues(new, old->in_pq, old->out_pq);
new->selfptr = old->selfptr;
new->user_input = old->user_input;
new->seat = old->seat;
new->ssh = old->ssh;
*new->selfptr = new;
ssh_ppl_free(old);
/* The new layer might need to be the first one that sends a
* packet, so trigger a call to its main coroutine immediately. If
* it doesn't need to go first, the worst that will do is return
* straight away. */
queue_idempotent_callback(&new->ic_process_queue);
}
void ssh_ppl_free(PacketProtocolLayer *ppl)
{
delete_callbacks_for_context(ppl);
ppl->vt->free(ppl);
}
static void ssh_ppl_ic_process_queue_callback(void *context)
{
PacketProtocolLayer *ppl = (PacketProtocolLayer *)context;
ssh_ppl_process_queue(ppl);
}
void ssh_ppl_setup_queues(PacketProtocolLayer *ppl,
PktInQueue *inq, PktOutQueue *outq)
{
ppl->in_pq = inq;
ppl->out_pq = outq;
ppl->in_pq->pqb.ic = &ppl->ic_process_queue;
ppl->ic_process_queue.fn = ssh_ppl_ic_process_queue_callback;
ppl->ic_process_queue.ctx = ppl;
/* If there's already something on the input queue, it will want
* handling immediately. */
if (pq_peek(ppl->in_pq))
queue_idempotent_callback(&ppl->ic_process_queue);
}
void ssh_ppl_user_output_string_and_free(PacketProtocolLayer *ppl, char *text)
{
/* Messages sent via this function are from the SSH layer, not
* from the server-side process, so they always have the stderr
* flag set. */
seat_stderr_pl(ppl->seat, ptrlen_from_asciz(text));
sfree(text);
}
size_t ssh_ppl_default_queued_data_size(PacketProtocolLayer *ppl)
{
return ppl->out_pq->pqb.total_size;
}
/* ----------------------------------------------------------------------
* Common helper functions for clients and implementations of
* BinaryPacketProtocol.
*/
static void ssh_bpp_input_raw_data_callback(void *context)
{
BinaryPacketProtocol *bpp = (BinaryPacketProtocol *)context;
Ssh *ssh = bpp->ssh; /* in case bpp is about to get freed */
ssh_bpp_handle_input(bpp);
/* If we've now cleared enough backlog on the input connection, we
* may need to unfreeze it. */
ssh_conn_processed_data(ssh);
}
static void ssh_bpp_output_packet_callback(void *context)
{
BinaryPacketProtocol *bpp = (BinaryPacketProtocol *)context;
ssh_bpp_handle_output(bpp);
}
void ssh_bpp_common_setup(BinaryPacketProtocol *bpp)
{
pq_in_init(&bpp->in_pq);
pq_out_init(&bpp->out_pq);
bpp->input_eof = false;
bpp->ic_in_raw.fn = ssh_bpp_input_raw_data_callback;
bpp->ic_in_raw.ctx = bpp;
bpp->ic_out_pq.fn = ssh_bpp_output_packet_callback;
bpp->ic_out_pq.ctx = bpp;
bpp->out_pq.pqb.ic = &bpp->ic_out_pq;
}
void ssh_bpp_free(BinaryPacketProtocol *bpp)
{
delete_callbacks_for_context(bpp);
bpp->vt->free(bpp);
}
void ssh2_bpp_queue_disconnect(BinaryPacketProtocol *bpp,
const char *msg, int category)
{
PktOut *pkt = ssh_bpp_new_pktout(bpp, SSH2_MSG_DISCONNECT);
put_uint32(pkt, category);
put_stringz(pkt, msg);
put_stringz(pkt, "en"); /* language tag */
pq_push(&bpp->out_pq, pkt);
}
#define BITMAP_UNIVERSAL(y, name, value) \
| (value >= y && value < y+32 ? 1UL << (value-y) : 0)
#define BITMAP_CONDITIONAL(y, name, value, ctx) \
BITMAP_UNIVERSAL(y, name, value)
#define SSH2_BITMAP_WORD(y) \
(0 SSH2_MESSAGE_TYPES(BITMAP_UNIVERSAL, BITMAP_CONDITIONAL, \
BITMAP_CONDITIONAL, (32*y)))
bool ssh2_bpp_check_unimplemented(BinaryPacketProtocol *bpp, PktIn *pktin)
{
static const unsigned valid_bitmap[] = {
SSH2_BITMAP_WORD(0),
SSH2_BITMAP_WORD(1),
SSH2_BITMAP_WORD(2),
SSH2_BITMAP_WORD(3),
SSH2_BITMAP_WORD(4),
SSH2_BITMAP_WORD(5),
SSH2_BITMAP_WORD(6),
SSH2_BITMAP_WORD(7),
};
if (pktin->type < 0x100 &&
!((valid_bitmap[pktin->type >> 5] >> (pktin->type & 0x1F)) & 1)) {
PktOut *pkt = ssh_bpp_new_pktout(bpp, SSH2_MSG_UNIMPLEMENTED);
put_uint32(pkt, pktin->sequence);
pq_push(&bpp->out_pq, pkt);
return true;
}
return false;
}
#undef BITMAP_UNIVERSAL
#undef BITMAP_CONDITIONAL
#undef SSH1_BITMAP_WORD
/* ----------------------------------------------------------------------
* Function to check a host key against any manually configured in Conf.
*/
int verify_ssh_manual_host_key(Conf *conf, char **fingerprints, ssh_key *key)
{
if (!conf_get_str_nthstrkey(conf, CONF_ssh_manual_hostkeys, 0))
return -1; /* no manual keys configured */
if (fingerprints) {
for (size_t i = 0; i < SSH_N_FPTYPES; i++) {
/*
* Each fingerprint string we've been given will have
* things like 'ssh-rsa 2048' at the front of it. Strip
* those off and narrow down to just the hash at the end
* of the string.
*/
const char *fingerprint = fingerprints[i];
if (!fingerprint)
continue;
const char *p = strrchr(fingerprint, ' ');
fingerprint = p ? p+1 : fingerprint;
if (conf_get_str_str_opt(conf, CONF_ssh_manual_hostkeys,
fingerprint))
return 1; /* success */
}
}
if (key) {
/*
* Construct the base64-encoded public key blob and see if
* that's listed.
*/
strbuf *binblob;
char *base64blob;
int atoms, i;
binblob = strbuf_new();
ssh_key_public_blob(key, BinarySink_UPCAST(binblob));
atoms = (binblob->len + 2) / 3;
base64blob = snewn(atoms * 4 + 1, char);
for (i = 0; i < atoms; i++)
base64_encode_atom(binblob->u + 3*i,
binblob->len - 3*i, base64blob + 4*i);
base64blob[atoms * 4] = '\0';
strbuf_free(binblob);
if (conf_get_str_str_opt(conf, CONF_ssh_manual_hostkeys, base64blob)) {
sfree(base64blob);
return 1; /* success */
}
sfree(base64blob);
}
return 0;
}
/* ----------------------------------------------------------------------
* Common functions shared between SSH-1 layers.
*/
bool ssh1_common_get_specials(
PacketProtocolLayer *ppl, add_special_fn_t add_special, void *ctx)
{
/*
* Don't bother offering IGNORE if we've decided the remote
* won't cope with it, since we wouldn't bother sending it if
* asked anyway.
*/
if (!(ppl->remote_bugs & BUG_CHOKES_ON_SSH1_IGNORE)) {
add_special(ctx, "IGNORE message", SS_NOP, 0);
return true;
}
return false;
}
bool ssh1_common_filter_queue(PacketProtocolLayer *ppl)
{
PktIn *pktin;
ptrlen msg;
while ((pktin = pq_peek(ppl->in_pq)) != NULL) {
switch (pktin->type) {
case SSH1_MSG_DISCONNECT:
msg = get_string(pktin);
ssh_remote_error(ppl->ssh,
"Remote side sent disconnect message:\n\"%.*s\"",
PTRLEN_PRINTF(msg));
/* don't try to pop the queue, because we've been freed! */
return true; /* indicate that we've been freed */
case SSH1_MSG_DEBUG:
msg = get_string(pktin);
ppl_logevent("Remote debug message: %.*s", PTRLEN_PRINTF(msg));
pq_pop(ppl->in_pq);
break;
case SSH1_MSG_IGNORE:
/* Do nothing, because we're ignoring it! Duhh. */
pq_pop(ppl->in_pq);
break;
default:
return false;
}
}
return false;
}
void ssh1_compute_session_id(
unsigned char *session_id, const unsigned char *cookie,
RSAKey *hostkey, RSAKey *servkey)
{
ssh_hash *hash = ssh_hash_new(&ssh_md5);
for (size_t i = (mp_get_nbits(hostkey->modulus) + 7) / 8; i-- ;)
put_byte(hash, mp_get_byte(hostkey->modulus, i));
for (size_t i = (mp_get_nbits(servkey->modulus) + 7) / 8; i-- ;)
put_byte(hash, mp_get_byte(servkey->modulus, i));
put_data(hash, cookie, 8);
ssh_hash_final(hash, session_id);
}

547
ssh/connection1-client.c Normal file
View File

@ -0,0 +1,547 @@
/*
* Client-specific parts of the SSH-1 connection layer.
*/
#include <assert.h>
#include "putty.h"
#include "ssh.h"
#include "bpp.h"
#include "ppl.h"
#include "channel.h"
#include "sshcr.h"
#include "connection1.h"
void ssh1_connection_direction_specific_setup(
struct ssh1_connection_state *s)
{
if (!s->mainchan) {
/*
* Start up the main session, by telling mainchan.c to do it
* all just as it would in SSH-2, and translating those
* concepts to SSH-1's non-channel-shaped idea of the main
* session.
*/
s->mainchan = mainchan_new(
&s->ppl, &s->cl, s->conf, s->term_width, s->term_height,
false /* is_simple */, NULL);
}
}
typedef void (*sf_handler_fn_t)(struct ssh1_connection_state *s,
bool success, void *ctx);
struct outstanding_succfail {
sf_handler_fn_t handler;
void *ctx;
struct outstanding_succfail *next;
/*
* The 'trivial' flag is set if this handler is in response to a
* request for which the SSH-1 protocol doesn't actually specify a
* response packet. The client of this system (mainchan.c) will
* expect to get an acknowledgment regardless, so we arrange to
* send that ack immediately after the rest of the queue empties.
*/
bool trivial;
};
static void ssh1_connection_process_trivial_succfails(void *vs);
static void ssh1_queue_succfail_handler(
struct ssh1_connection_state *s, sf_handler_fn_t handler, void *ctx,
bool trivial)
{
struct outstanding_succfail *osf = snew(struct outstanding_succfail);
osf->handler = handler;
osf->ctx = ctx;
osf->trivial = trivial;
osf->next = NULL;
if (s->succfail_tail)
s->succfail_tail->next = osf;
else
s->succfail_head = osf;
s->succfail_tail = osf;
/* In case this one was trivial and the queue was already empty,
* we should make sure we run the handler promptly, and the
* easiest way is to queue it anyway and then run a trivials pass
* by callback. */
queue_toplevel_callback(ssh1_connection_process_trivial_succfails, s);
}
static void ssh1_connection_process_succfail(
struct ssh1_connection_state *s, bool success)
{
struct outstanding_succfail *prevhead = s->succfail_head;
s->succfail_head = s->succfail_head->next;
if (!s->succfail_head)
s->succfail_tail = NULL;
prevhead->handler(s, success, prevhead->ctx);
sfree(prevhead);
}
static void ssh1_connection_process_trivial_succfails(void *vs)
{
struct ssh1_connection_state *s = (struct ssh1_connection_state *)vs;
while (s->succfail_head && s->succfail_head->trivial)
ssh1_connection_process_succfail(s, true);
}
bool ssh1_handle_direction_specific_packet(
struct ssh1_connection_state *s, PktIn *pktin)
{
PacketProtocolLayer *ppl = &s->ppl; /* for ppl_logevent */
PktOut *pktout;
struct ssh1_channel *c;
unsigned remid;
struct ssh_rportfwd pf, *pfp;
ptrlen host, data;
int port;
switch (pktin->type) {
case SSH1_SMSG_SUCCESS:
case SSH1_SMSG_FAILURE:
if (!s->succfail_head) {
ssh_remote_error(s->ppl.ssh,
"Received %s with no outstanding request",
ssh1_pkt_type(pktin->type));
return true;
}
ssh1_connection_process_succfail(
s, pktin->type == SSH1_SMSG_SUCCESS);
queue_toplevel_callback(
ssh1_connection_process_trivial_succfails, s);
return true;
case SSH1_SMSG_X11_OPEN:
remid = get_uint32(pktin);
/* Refuse if X11 forwarding is disabled. */
if (!s->X11_fwd_enabled) {
pktout = ssh_bpp_new_pktout(
s->ppl.bpp, SSH1_MSG_CHANNEL_OPEN_FAILURE);
put_uint32(pktout, remid);
pq_push(s->ppl.out_pq, pktout);
ppl_logevent("Rejected X11 connect request");
} else {
c = snew(struct ssh1_channel);
c->connlayer = s;
ssh1_channel_init(c);
c->remoteid = remid;
c->chan = x11_new_channel(s->x11authtree, &c->sc,
NULL, -1, false);
c->remoteid = remid;
c->halfopen = false;
pktout = ssh_bpp_new_pktout(
s->ppl.bpp, SSH1_MSG_CHANNEL_OPEN_CONFIRMATION);
put_uint32(pktout, c->remoteid);
put_uint32(pktout, c->localid);
pq_push(s->ppl.out_pq, pktout);
ppl_logevent("Opened X11 forward channel");
}
return true;
case SSH1_SMSG_AGENT_OPEN:
remid = get_uint32(pktin);
/* Refuse if agent forwarding is disabled. */
if (!ssh_agent_forwarding_permitted(&s->cl)) {
pktout = ssh_bpp_new_pktout(
s->ppl.bpp, SSH1_MSG_CHANNEL_OPEN_FAILURE);
put_uint32(pktout, remid);
pq_push(s->ppl.out_pq, pktout);
} else {
c = snew(struct ssh1_channel);
c->connlayer = s;
ssh1_channel_init(c);
c->remoteid = remid;
c->halfopen = false;
/*
* If possible, make a stream-oriented connection to the
* agent and set up an ordinary port-forwarding type
* channel over it.
*/
Plug *plug;
Channel *ch = portfwd_raw_new(&s->cl, &plug, true);
Socket *skt = agent_connect(plug);
if (!sk_socket_error(skt)) {
portfwd_raw_setup(ch, skt, &c->sc);
c->chan = ch;
} else {
portfwd_raw_free(ch);
/*
* Otherwise, fall back to the old-fashioned system of
* parsing the forwarded data stream ourselves for
* message boundaries, and passing each individual
* message to the one-off agent_query().
*/
c->chan = agentf_new(&c->sc);
}
pktout = ssh_bpp_new_pktout(
s->ppl.bpp, SSH1_MSG_CHANNEL_OPEN_CONFIRMATION);
put_uint32(pktout, c->remoteid);
put_uint32(pktout, c->localid);
pq_push(s->ppl.out_pq, pktout);
}
return true;
case SSH1_MSG_PORT_OPEN:
remid = get_uint32(pktin);
host = get_string(pktin);
port = toint(get_uint32(pktin));
pf.dhost = mkstr(host);
pf.dport = port;
pfp = find234(s->rportfwds, &pf, NULL);
if (!pfp) {
ppl_logevent("Rejected remote port open request for %s:%d",
pf.dhost, port);
pktout = ssh_bpp_new_pktout(
s->ppl.bpp, SSH1_MSG_CHANNEL_OPEN_FAILURE);
put_uint32(pktout, remid);
pq_push(s->ppl.out_pq, pktout);
} else {
char *err;
c = snew(struct ssh1_channel);
c->connlayer = s;
ppl_logevent("Received remote port open request for %s:%d",
pf.dhost, port);
err = portfwdmgr_connect(
s->portfwdmgr, &c->chan, pf.dhost, port,
&c->sc, pfp->addressfamily);
if (err) {
ppl_logevent("Port open failed: %s", err);
sfree(err);
ssh1_channel_free(c);
pktout = ssh_bpp_new_pktout(
s->ppl.bpp, SSH1_MSG_CHANNEL_OPEN_FAILURE);
put_uint32(pktout, remid);
pq_push(s->ppl.out_pq, pktout);
} else {
ssh1_channel_init(c);
c->remoteid = remid;
c->halfopen = false;
pktout = ssh_bpp_new_pktout(
s->ppl.bpp, SSH1_MSG_CHANNEL_OPEN_CONFIRMATION);
put_uint32(pktout, c->remoteid);
put_uint32(pktout, c->localid);
pq_push(s->ppl.out_pq, pktout);
ppl_logevent("Forwarded port opened successfully");
}
}
sfree(pf.dhost);
return true;
case SSH1_SMSG_STDOUT_DATA:
case SSH1_SMSG_STDERR_DATA:
data = get_string(pktin);
if (!get_err(pktin)) {
int bufsize = seat_output(
s->ppl.seat, pktin->type == SSH1_SMSG_STDERR_DATA,
data.ptr, data.len);
if (!s->stdout_throttling && bufsize > SSH1_BUFFER_LIMIT) {
s->stdout_throttling = true;
ssh_throttle_conn(s->ppl.ssh, +1);
}
}
return true;
case SSH1_SMSG_EXIT_STATUS: {
int exitcode = get_uint32(pktin);
ppl_logevent("Server sent command exit status %d", exitcode);
ssh_got_exitcode(s->ppl.ssh, exitcode);
s->session_terminated = true;
return true;
}
default:
return false;
}
}
static void ssh1mainchan_succfail_wantreply(struct ssh1_connection_state *s,
bool success, void *ctx)
{
chan_request_response(s->mainchan_chan, success);
}
static void ssh1mainchan_succfail_nowantreply(struct ssh1_connection_state *s,
bool success, void *ctx)
{
}
static void ssh1mainchan_queue_response(struct ssh1_connection_state *s,
bool want_reply, bool trivial)
{
sf_handler_fn_t handler = (want_reply ? ssh1mainchan_succfail_wantreply :
ssh1mainchan_succfail_nowantreply);
ssh1_queue_succfail_handler(s, handler, NULL, trivial);
}
static void ssh1mainchan_request_x11_forwarding(
SshChannel *sc, bool want_reply, const char *authproto,
const char *authdata, int screen_number, bool oneshot)
{
struct ssh1_connection_state *s =
container_of(sc, struct ssh1_connection_state, mainchan_sc);
PktOut *pktout;
pktout = ssh_bpp_new_pktout(s->ppl.bpp, SSH1_CMSG_X11_REQUEST_FORWARDING);
put_stringz(pktout, authproto);
put_stringz(pktout, authdata);
if (s->local_protoflags & SSH1_PROTOFLAG_SCREEN_NUMBER)
put_uint32(pktout, screen_number);
pq_push(s->ppl.out_pq, pktout);
ssh1mainchan_queue_response(s, want_reply, false);
}
static void ssh1mainchan_request_agent_forwarding(
SshChannel *sc, bool want_reply)
{
struct ssh1_connection_state *s =
container_of(sc, struct ssh1_connection_state, mainchan_sc);
PktOut *pktout;
pktout = ssh_bpp_new_pktout(
s->ppl.bpp, SSH1_CMSG_AGENT_REQUEST_FORWARDING);
pq_push(s->ppl.out_pq, pktout);
ssh1mainchan_queue_response(s, want_reply, false);
}
static void ssh1mainchan_request_pty(
SshChannel *sc, bool want_reply, Conf *conf, int w, int h)
{
struct ssh1_connection_state *s =
container_of(sc, struct ssh1_connection_state, mainchan_sc);
PktOut *pktout;
pktout = ssh_bpp_new_pktout(s->ppl.bpp, SSH1_CMSG_REQUEST_PTY);
put_stringz(pktout, conf_get_str(s->conf, CONF_termtype));
put_uint32(pktout, h);
put_uint32(pktout, w);
put_uint32(pktout, 0); /* width in pixels */
put_uint32(pktout, 0); /* height in pixels */
write_ttymodes_to_packet(
BinarySink_UPCAST(pktout), 1,
get_ttymodes_from_conf(s->ppl.seat, conf));
pq_push(s->ppl.out_pq, pktout);
ssh1mainchan_queue_response(s, want_reply, false);
}
static bool ssh1mainchan_send_env_var(
SshChannel *sc, bool want_reply, const char *var, const char *value)
{
return false; /* SSH-1 doesn't support this at all */
}
static void ssh1mainchan_start_shell(SshChannel *sc, bool want_reply)
{
struct ssh1_connection_state *s =
container_of(sc, struct ssh1_connection_state, mainchan_sc);
PktOut *pktout;
pktout = ssh_bpp_new_pktout(s->ppl.bpp, SSH1_CMSG_EXEC_SHELL);
pq_push(s->ppl.out_pq, pktout);
ssh1mainchan_queue_response(s, want_reply, true);
}
static void ssh1mainchan_start_command(
SshChannel *sc, bool want_reply, const char *command)
{
struct ssh1_connection_state *s =
container_of(sc, struct ssh1_connection_state, mainchan_sc);
PktOut *pktout;
pktout = ssh_bpp_new_pktout(s->ppl.bpp, SSH1_CMSG_EXEC_CMD);
put_stringz(pktout, command);
pq_push(s->ppl.out_pq, pktout);
ssh1mainchan_queue_response(s, want_reply, true);
}
static bool ssh1mainchan_start_subsystem(
SshChannel *sc, bool want_reply, const char *subsystem)
{
return false; /* SSH-1 doesn't support this at all */
}
static bool ssh1mainchan_send_serial_break(
SshChannel *sc, bool want_reply, int length)
{
return false; /* SSH-1 doesn't support this at all */
}
static bool ssh1mainchan_send_signal(
SshChannel *sc, bool want_reply, const char *signame)
{
return false; /* SSH-1 doesn't support this at all */
}
static void ssh1mainchan_send_terminal_size_change(
SshChannel *sc, int w, int h)
{
struct ssh1_connection_state *s =
container_of(sc, struct ssh1_connection_state, mainchan_sc);
PktOut *pktout;
pktout = ssh_bpp_new_pktout(s->ppl.bpp, SSH1_CMSG_WINDOW_SIZE);
put_uint32(pktout, h);
put_uint32(pktout, w);
put_uint32(pktout, 0); /* width in pixels */
put_uint32(pktout, 0); /* height in pixels */
pq_push(s->ppl.out_pq, pktout);
}
static void ssh1mainchan_hint_channel_is_simple(SshChannel *sc)
{
}
static size_t ssh1mainchan_write(
SshChannel *sc, bool is_stderr, const void *data, size_t len)
{
struct ssh1_connection_state *s =
container_of(sc, struct ssh1_connection_state, mainchan_sc);
PktOut *pktout;
pktout = ssh_bpp_new_pktout(s->ppl.bpp, SSH1_CMSG_STDIN_DATA);
put_string(pktout, data, len);
pq_push(s->ppl.out_pq, pktout);
return 0;
}
static void ssh1mainchan_write_eof(SshChannel *sc)
{
struct ssh1_connection_state *s =
container_of(sc, struct ssh1_connection_state, mainchan_sc);
PktOut *pktout;
pktout = ssh_bpp_new_pktout(s->ppl.bpp, SSH1_CMSG_EOF);
pq_push(s->ppl.out_pq, pktout);
}
static const SshChannelVtable ssh1mainchan_vtable = {
.write = ssh1mainchan_write,
.write_eof = ssh1mainchan_write_eof,
.request_x11_forwarding = ssh1mainchan_request_x11_forwarding,
.request_agent_forwarding = ssh1mainchan_request_agent_forwarding,
.request_pty = ssh1mainchan_request_pty,
.send_env_var = ssh1mainchan_send_env_var,
.start_shell = ssh1mainchan_start_shell,
.start_command = ssh1mainchan_start_command,
.start_subsystem = ssh1mainchan_start_subsystem,
.send_serial_break = ssh1mainchan_send_serial_break,
.send_signal = ssh1mainchan_send_signal,
.send_terminal_size_change = ssh1mainchan_send_terminal_size_change,
.hint_channel_is_simple = ssh1mainchan_hint_channel_is_simple,
/* other methods are NULL */
};
static void ssh1_session_confirm_callback(void *vctx)
{
struct ssh1_connection_state *s = (struct ssh1_connection_state *)vctx;
chan_open_confirmation(s->mainchan_chan);
}
SshChannel *ssh1_session_open(ConnectionLayer *cl, Channel *chan)
{
struct ssh1_connection_state *s =
container_of(cl, struct ssh1_connection_state, cl);
s->mainchan_sc.vt = &ssh1mainchan_vtable;
s->mainchan_sc.cl = &s->cl;
s->mainchan_chan = chan;
queue_toplevel_callback(ssh1_session_confirm_callback, s);
return &s->mainchan_sc;
}
static void ssh1_rportfwd_response(struct ssh1_connection_state *s,
bool success, void *ctx)
{
PacketProtocolLayer *ppl = &s->ppl; /* for ppl_logevent */
struct ssh_rportfwd *rpf = (struct ssh_rportfwd *)ctx;
if (success) {
ppl_logevent("Remote port forwarding from %s enabled",
rpf->log_description);
} else {
ppl_logevent("Remote port forwarding from %s refused",
rpf->log_description);
struct ssh_rportfwd *realpf = del234(s->rportfwds, rpf);
assert(realpf == rpf);
portfwdmgr_close(s->portfwdmgr, rpf->pfr);
free_rportfwd(rpf);
}
}
struct ssh_rportfwd *ssh1_rportfwd_alloc(
ConnectionLayer *cl,
const char *shost, int sport, const char *dhost, int dport,
int addressfamily, const char *log_description, PortFwdRecord *pfr,
ssh_sharing_connstate *share_ctx)
{
struct ssh1_connection_state *s =
container_of(cl, struct ssh1_connection_state, cl);
struct ssh_rportfwd *rpf = snew(struct ssh_rportfwd);
rpf->shost = dupstr(shost);
rpf->sport = sport;
rpf->dhost = dupstr(dhost);
rpf->dport = dport;
rpf->addressfamily = addressfamily;
rpf->log_description = dupstr(log_description);
rpf->pfr = pfr;
if (add234(s->rportfwds, rpf) != rpf) {
free_rportfwd(rpf);
return NULL;
}
PktOut *pktout = ssh_bpp_new_pktout(
s->ppl.bpp, SSH1_CMSG_PORT_FORWARD_REQUEST);
put_uint32(pktout, rpf->sport);
put_stringz(pktout, rpf->dhost);
put_uint32(pktout, rpf->dport);
pq_push(s->ppl.out_pq, pktout);
ssh1_queue_succfail_handler(s, ssh1_rportfwd_response, rpf, false);
return rpf;
}
SshChannel *ssh1_serverside_x11_open(
ConnectionLayer *cl, Channel *chan, const SocketPeerInfo *pi)
{
unreachable("Should never be called in the client");
}
SshChannel *ssh1_serverside_agent_open(ConnectionLayer *cl, Channel *chan)
{
unreachable("Should never be called in the client");
}
bool ssh1_connection_need_antispoof_prompt(struct ssh1_connection_state *s)
{
return !seat_set_trust_status(s->ppl.seat, false);
}

365
ssh/connection1-server.c Normal file
View File

@ -0,0 +1,365 @@
/*
* Server-specific parts of the SSH-1 connection layer.
*/
#include <assert.h>
#include "putty.h"
#include "ssh.h"
#include "bpp.h"
#include "ppl.h"
#include "channel.h"
#include "sshcr.h"
#include "connection1.h"
#include "server.h"
static size_t ssh1sesschan_write(SshChannel *c, bool is_stderr,
const void *, size_t);
static void ssh1sesschan_write_eof(SshChannel *c);
static void ssh1sesschan_initiate_close(SshChannel *c, const char *err);
static void ssh1sesschan_send_exit_status(SshChannel *c, int status);
static void ssh1sesschan_send_exit_signal(
SshChannel *c, ptrlen signame, bool core_dumped, ptrlen msg);
static const SshChannelVtable ssh1sesschan_vtable = {
.write = ssh1sesschan_write,
.write_eof = ssh1sesschan_write_eof,
.initiate_close = ssh1sesschan_initiate_close,
.send_exit_status = ssh1sesschan_send_exit_status,
.send_exit_signal = ssh1sesschan_send_exit_signal,
/* everything else is NULL */
};
void ssh1connection_server_configure(
PacketProtocolLayer *ppl, const SshServerConfig *ssc)
{
struct ssh1_connection_state *s =
container_of(ppl, struct ssh1_connection_state, ppl);
s->ssc = ssc;
}
void ssh1_connection_direction_specific_setup(
struct ssh1_connection_state *s)
{
if (!s->mainchan_chan) {
s->mainchan_sc.vt = &ssh1sesschan_vtable;
s->mainchan_sc.cl = &s->cl;
s->mainchan_chan = sesschan_new(
&s->mainchan_sc, s->ppl.logctx, NULL, s->ssc);
}
}
bool ssh1_handle_direction_specific_packet(
struct ssh1_connection_state *s, PktIn *pktin)
{
PacketProtocolLayer *ppl = &s->ppl; /* for ppl_logevent */
PktOut *pktout;
struct ssh1_channel *c;
unsigned remid;
ptrlen host, cmd, data;
char *host_str, *err;
int port, listenport;
bool success;
switch (pktin->type) {
case SSH1_CMSG_EXEC_SHELL:
if (s->finished_setup)
goto unexpected_setup_packet;
ppl_logevent("Client requested a shell");
chan_run_shell(s->mainchan_chan);
s->finished_setup = true;
return true;
case SSH1_CMSG_EXEC_CMD:
if (s->finished_setup)
goto unexpected_setup_packet;
cmd = get_string(pktin);
ppl_logevent("Client sent command '%.*s'", PTRLEN_PRINTF(cmd));
chan_run_command(s->mainchan_chan, cmd);
s->finished_setup = true;
return true;
case SSH1_CMSG_REQUEST_COMPRESSION:
if (s->compressing || !s->ssc->ssh1_allow_compression) {
pktout = ssh_bpp_new_pktout(s->ppl.bpp, SSH1_SMSG_FAILURE);
pq_push(s->ppl.out_pq, pktout);
} else {
pktout = ssh_bpp_new_pktout(s->ppl.bpp, SSH1_SMSG_SUCCESS);
pq_push(s->ppl.out_pq, pktout);
/* Synchronous run of output formatting, to ensure that
* success packet is converted into wire format before we
* start compressing */
ssh_bpp_handle_output(s->ppl.bpp);
/* And now ensure that the _next_ packet will be the first
* compressed one. */
ssh1_bpp_start_compression(s->ppl.bpp);
s->compressing = true;
}
return true;
case SSH1_CMSG_REQUEST_PTY: {
if (s->finished_setup)
goto unexpected_setup_packet;
ptrlen termtype = get_string(pktin);
unsigned height = get_uint32(pktin);
unsigned width = get_uint32(pktin);
unsigned pixwidth = get_uint32(pktin);
unsigned pixheight = get_uint32(pktin);
struct ssh_ttymodes modes = read_ttymodes_from_packet(
BinarySource_UPCAST(pktin), 1);
if (get_err(pktin)) {
ppl_logevent("Unable to decode pty request packet");
success = false;
} else if (!chan_allocate_pty(
s->mainchan_chan, termtype, width, height,
pixwidth, pixheight, modes)) {
ppl_logevent("Unable to allocate a pty");
success = false;
} else {
success = true;
}
pktout = ssh_bpp_new_pktout(
s->ppl.bpp, (success ? SSH1_SMSG_SUCCESS : SSH1_SMSG_FAILURE));
pq_push(s->ppl.out_pq, pktout);
return true;
}
case SSH1_CMSG_PORT_FORWARD_REQUEST:
if (s->finished_setup)
goto unexpected_setup_packet;
listenport = toint(get_uint32(pktin));
host = get_string(pktin);
port = toint(get_uint32(pktin));
ppl_logevent("Client requested port %d forward to %.*s:%d",
listenport, PTRLEN_PRINTF(host), port);
host_str = mkstr(host);
success = portfwdmgr_listen(
s->portfwdmgr, NULL, listenport, host_str, port, s->conf);
sfree(host_str);
pktout = ssh_bpp_new_pktout(
s->ppl.bpp, (success ? SSH1_SMSG_SUCCESS : SSH1_SMSG_FAILURE));
pq_push(s->ppl.out_pq, pktout);
return true;
case SSH1_CMSG_X11_REQUEST_FORWARDING: {
if (s->finished_setup)
goto unexpected_setup_packet;
ptrlen authproto = get_string(pktin);
ptrlen authdata = get_string(pktin);
unsigned screen_number = 0;
if (s->remote_protoflags & SSH1_PROTOFLAG_SCREEN_NUMBER)
screen_number = get_uint32(pktin);
success = chan_enable_x11_forwarding(
s->mainchan_chan, false, authproto, authdata, screen_number);
pktout = ssh_bpp_new_pktout(
s->ppl.bpp, (success ? SSH1_SMSG_SUCCESS : SSH1_SMSG_FAILURE));
pq_push(s->ppl.out_pq, pktout);
return true;
}
case SSH1_CMSG_AGENT_REQUEST_FORWARDING:
if (s->finished_setup)
goto unexpected_setup_packet;
success = chan_enable_agent_forwarding(s->mainchan_chan);
pktout = ssh_bpp_new_pktout(
s->ppl.bpp, (success ? SSH1_SMSG_SUCCESS : SSH1_SMSG_FAILURE));
pq_push(s->ppl.out_pq, pktout);
return true;
case SSH1_CMSG_STDIN_DATA:
data = get_string(pktin);
chan_send(s->mainchan_chan, false, data.ptr, data.len);
return true;
case SSH1_CMSG_EOF:
chan_send_eof(s->mainchan_chan);
return true;
case SSH1_CMSG_WINDOW_SIZE:
return true;
case SSH1_MSG_PORT_OPEN:
remid = get_uint32(pktin);
host = get_string(pktin);
port = toint(get_uint32(pktin));
host_str = mkstr(host);
ppl_logevent("Received request to connect to port %s:%d",
host_str, port);
c = snew(struct ssh1_channel);
c->connlayer = s;
err = portfwdmgr_connect(
s->portfwdmgr, &c->chan, host_str, port,
&c->sc, ADDRTYPE_UNSPEC);
sfree(host_str);
if (err) {
ppl_logevent("Port open failed: %s", err);
sfree(err);
ssh1_channel_free(c);
pktout = ssh_bpp_new_pktout(
s->ppl.bpp, SSH1_MSG_CHANNEL_OPEN_FAILURE);
put_uint32(pktout, remid);
pq_push(s->ppl.out_pq, pktout);
} else {
ssh1_channel_init(c);
c->remoteid = remid;
c->halfopen = false;
pktout = ssh_bpp_new_pktout(
s->ppl.bpp, SSH1_MSG_CHANNEL_OPEN_CONFIRMATION);
put_uint32(pktout, c->remoteid);
put_uint32(pktout, c->localid);
pq_push(s->ppl.out_pq, pktout);
ppl_logevent("Forwarded port opened successfully");
}
return true;
case SSH1_CMSG_EXIT_CONFIRMATION:
if (!s->sent_exit_status) {
ssh_proto_error(s->ppl.ssh, "Received SSH1_CMSG_EXIT_CONFIRMATION"
" without having sent SSH1_SMSG_EXIT_STATUS");
return true;
}
ppl_logevent("Client sent exit confirmation");
return true;
default:
return false;
}
unexpected_setup_packet:
ssh_proto_error(s->ppl.ssh, "Received unexpected setup packet after the "
"setup phase, type %d (%s)", pktin->type,
ssh1_pkt_type(pktin->type));
/* FIXME: ensure caller copes with us just having freed the whole layer */
return true;
}
SshChannel *ssh1_session_open(ConnectionLayer *cl, Channel *chan)
{
unreachable("Should never be called in the server");
}
struct ssh_rportfwd *ssh1_rportfwd_alloc(
ConnectionLayer *cl,
const char *shost, int sport, const char *dhost, int dport,
int addressfamily, const char *log_description, PortFwdRecord *pfr,
ssh_sharing_connstate *share_ctx)
{
unreachable("Should never be called in the server");
}
static size_t ssh1sesschan_write(SshChannel *sc, bool is_stderr,
const void *data, size_t len)
{
struct ssh1_connection_state *s =
container_of(sc, struct ssh1_connection_state, mainchan_sc);
PktOut *pktout;
pktout = ssh_bpp_new_pktout(
s->ppl.bpp,
(is_stderr ? SSH1_SMSG_STDERR_DATA : SSH1_SMSG_STDOUT_DATA));
put_string(pktout, data, len);
pq_push(s->ppl.out_pq, pktout);
return 0;
}
static void ssh1sesschan_write_eof(SshChannel *sc)
{
/* SSH-1 can't represent server-side EOF */
/* FIXME: some kind of check-termination system, whereby once this has been called _and_ we've had an exit status _and_ we've got no other channels open, we send the actual EXIT_STATUS message */
}
static void ssh1sesschan_initiate_close(SshChannel *sc, const char *err)
{
/* SSH-1 relies on the client to close the connection in the end */
}
static void ssh1sesschan_send_exit_status(SshChannel *sc, int status)
{
struct ssh1_connection_state *s =
container_of(sc, struct ssh1_connection_state, mainchan_sc);
PktOut *pktout;
pktout = ssh_bpp_new_pktout(s->ppl.bpp, SSH1_SMSG_EXIT_STATUS);
put_uint32(pktout, status);
pq_push(s->ppl.out_pq, pktout);
s->sent_exit_status = true;
}
static void ssh1sesschan_send_exit_signal(
SshChannel *sc, ptrlen signame, bool core_dumped, ptrlen msg)
{
/* SSH-1 has no separate representation for signals */
ssh1sesschan_send_exit_status(sc, 128);
}
SshChannel *ssh1_serverside_x11_open(
ConnectionLayer *cl, Channel *chan, const SocketPeerInfo *pi)
{
struct ssh1_connection_state *s =
container_of(cl, struct ssh1_connection_state, cl);
PacketProtocolLayer *ppl = &s->ppl; /* for ppl_logevent */
struct ssh1_channel *c = snew(struct ssh1_channel);
PktOut *pktout;
c->connlayer = s;
ssh1_channel_init(c);
c->halfopen = true;
c->chan = chan;
ppl_logevent("Forwarding X11 connection to client");
pktout = ssh_bpp_new_pktout(s->ppl.bpp, SSH1_SMSG_X11_OPEN);
put_uint32(pktout, c->localid);
pq_push(s->ppl.out_pq, pktout);
return &c->sc;
}
SshChannel *ssh1_serverside_agent_open(ConnectionLayer *cl, Channel *chan)
{
struct ssh1_connection_state *s =
container_of(cl, struct ssh1_connection_state, cl);
PacketProtocolLayer *ppl = &s->ppl; /* for ppl_logevent */
struct ssh1_channel *c = snew(struct ssh1_channel);
PktOut *pktout;
c->connlayer = s;
ssh1_channel_init(c);
c->halfopen = true;
c->chan = chan;
ppl_logevent("Forwarding agent connection to client");
pktout = ssh_bpp_new_pktout(s->ppl.bpp, SSH1_SMSG_AGENT_OPEN);
put_uint32(pktout, c->localid);
pq_push(s->ppl.out_pq, pktout);
return &c->sc;
}
bool ssh1_connection_need_antispoof_prompt(struct ssh1_connection_state *s)
{
return false;
}

815
ssh/connection1.c Normal file
View File

@ -0,0 +1,815 @@
/*
* Packet protocol layer for the SSH-1 'connection protocol', i.e.
* everything after authentication finishes.
*/
#include <assert.h>
#include "putty.h"
#include "ssh.h"
#include "bpp.h"
#include "ppl.h"
#include "channel.h"
#include "sshcr.h"
#include "connection1.h"
static int ssh1_rportfwd_cmp(void *av, void *bv)
{
struct ssh_rportfwd *a = (struct ssh_rportfwd *) av;
struct ssh_rportfwd *b = (struct ssh_rportfwd *) bv;
int i;
if ( (i = strcmp(a->dhost, b->dhost)) != 0)
return i < 0 ? -1 : +1;
if (a->dport > b->dport)
return +1;
if (a->dport < b->dport)
return -1;
return 0;
}
static void ssh1_connection_free(PacketProtocolLayer *);
static void ssh1_connection_process_queue(PacketProtocolLayer *);
static void ssh1_connection_special_cmd(PacketProtocolLayer *ppl,
SessionSpecialCode code, int arg);
static bool ssh1_connection_want_user_input(PacketProtocolLayer *ppl);
static void ssh1_connection_got_user_input(PacketProtocolLayer *ppl);
static void ssh1_connection_reconfigure(PacketProtocolLayer *ppl, Conf *conf);
static const PacketProtocolLayerVtable ssh1_connection_vtable = {
.free = ssh1_connection_free,
.process_queue = ssh1_connection_process_queue,
.get_specials = ssh1_common_get_specials,
.special_cmd = ssh1_connection_special_cmd,
.want_user_input = ssh1_connection_want_user_input,
.got_user_input = ssh1_connection_got_user_input,
.reconfigure = ssh1_connection_reconfigure,
.queued_data_size = ssh_ppl_default_queued_data_size,
.name = NULL, /* no layer names in SSH-1 */
};
static void ssh1_rportfwd_remove(
ConnectionLayer *cl, struct ssh_rportfwd *rpf);
static SshChannel *ssh1_lportfwd_open(
ConnectionLayer *cl, const char *hostname, int port,
const char *description, const SocketPeerInfo *pi, Channel *chan);
static struct X11FakeAuth *ssh1_add_x11_display(
ConnectionLayer *cl, int authtype, struct X11Display *disp);
static bool ssh1_agent_forwarding_permitted(ConnectionLayer *cl);
static void ssh1_terminal_size(ConnectionLayer *cl, int width, int height);
static void ssh1_stdout_unthrottle(ConnectionLayer *cl, size_t bufsize);
static size_t ssh1_stdin_backlog(ConnectionLayer *cl);
static void ssh1_throttle_all_channels(ConnectionLayer *cl, bool throttled);
static bool ssh1_ldisc_option(ConnectionLayer *cl, int option);
static void ssh1_set_ldisc_option(ConnectionLayer *cl, int option, bool value);
static void ssh1_enable_x_fwd(ConnectionLayer *cl);
static void ssh1_set_wants_user_input(ConnectionLayer *cl, bool wanted);
static const ConnectionLayerVtable ssh1_connlayer_vtable = {
.rportfwd_alloc = ssh1_rportfwd_alloc,
.rportfwd_remove = ssh1_rportfwd_remove,
.lportfwd_open = ssh1_lportfwd_open,
.session_open = ssh1_session_open,
.serverside_x11_open = ssh1_serverside_x11_open,
.serverside_agent_open = ssh1_serverside_agent_open,
.add_x11_display = ssh1_add_x11_display,
.agent_forwarding_permitted = ssh1_agent_forwarding_permitted,
.terminal_size = ssh1_terminal_size,
.stdout_unthrottle = ssh1_stdout_unthrottle,
.stdin_backlog = ssh1_stdin_backlog,
.throttle_all_channels = ssh1_throttle_all_channels,
.ldisc_option = ssh1_ldisc_option,
.set_ldisc_option = ssh1_set_ldisc_option,
.enable_x_fwd = ssh1_enable_x_fwd,
.set_wants_user_input = ssh1_set_wants_user_input,
/* other methods are NULL */
};
static size_t ssh1channel_write(
SshChannel *c, bool is_stderr, const void *buf, size_t len);
static void ssh1channel_write_eof(SshChannel *c);
static void ssh1channel_initiate_close(SshChannel *c, const char *err);
static void ssh1channel_unthrottle(SshChannel *c, size_t bufsize);
static Conf *ssh1channel_get_conf(SshChannel *c);
static void ssh1channel_window_override_removed(SshChannel *c) { /* ignore */ }
static const SshChannelVtable ssh1channel_vtable = {
.write = ssh1channel_write,
.write_eof = ssh1channel_write_eof,
.initiate_close = ssh1channel_initiate_close,
.unthrottle = ssh1channel_unthrottle,
.get_conf = ssh1channel_get_conf,
.window_override_removed = ssh1channel_window_override_removed,
/* everything else is NULL */
};
static void ssh1_channel_try_eof(struct ssh1_channel *c);
static void ssh1_channel_close_local(struct ssh1_channel *c,
const char *reason);
static void ssh1_channel_destroy(struct ssh1_channel *c);
static void ssh1_channel_check_close(struct ssh1_channel *c);
static int ssh1_channelcmp(void *av, void *bv)
{
const struct ssh1_channel *a = (const struct ssh1_channel *) av;
const struct ssh1_channel *b = (const struct ssh1_channel *) bv;
if (a->localid < b->localid)
return -1;
if (a->localid > b->localid)
return +1;
return 0;
}
static int ssh1_channelfind(void *av, void *bv)
{
const unsigned *a = (const unsigned *) av;
const struct ssh1_channel *b = (const struct ssh1_channel *) bv;
if (*a < b->localid)
return -1;
if (*a > b->localid)
return +1;
return 0;
}
void ssh1_channel_free(struct ssh1_channel *c)
{
if (c->chan)
chan_free(c->chan);
sfree(c);
}
PacketProtocolLayer *ssh1_connection_new(
Ssh *ssh, Conf *conf, ConnectionLayer **cl_out)
{
struct ssh1_connection_state *s = snew(struct ssh1_connection_state);
memset(s, 0, sizeof(*s));
s->ppl.vt = &ssh1_connection_vtable;
s->conf = conf_copy(conf);
s->channels = newtree234(ssh1_channelcmp);
s->x11authtree = newtree234(x11_authcmp);
/* Need to get the log context for s->cl now, because we won't be
* helpfully notified when a copy is written into s->ppl by our
* owner. */
s->cl.vt = &ssh1_connlayer_vtable;
s->cl.logctx = ssh_get_logctx(ssh);
s->portfwdmgr = portfwdmgr_new(&s->cl);
s->rportfwds = newtree234(ssh1_rportfwd_cmp);
*cl_out = &s->cl;
return &s->ppl;
}
static void ssh1_connection_free(PacketProtocolLayer *ppl)
{
struct ssh1_connection_state *s =
container_of(ppl, struct ssh1_connection_state, ppl);
struct X11FakeAuth *auth;
struct ssh1_channel *c;
struct ssh_rportfwd *rpf;
conf_free(s->conf);
while ((c = delpos234(s->channels, 0)) != NULL)
ssh1_channel_free(c);
freetree234(s->channels);
if (s->mainchan_chan)
chan_free(s->mainchan_chan);
if (s->x11disp)
x11_free_display(s->x11disp);
while ((auth = delpos234(s->x11authtree, 0)) != NULL)
x11_free_fake_auth(auth);
freetree234(s->x11authtree);
while ((rpf = delpos234(s->rportfwds, 0)) != NULL)
free_rportfwd(rpf);
freetree234(s->rportfwds);
portfwdmgr_free(s->portfwdmgr);
if (s->antispoof_prompt)
free_prompts(s->antispoof_prompt);
delete_callbacks_for_context(s);
sfree(s);
}
void ssh1_connection_set_protoflags(PacketProtocolLayer *ppl,
int local, int remote)
{
assert(ppl->vt == &ssh1_connection_vtable);
struct ssh1_connection_state *s =
container_of(ppl, struct ssh1_connection_state, ppl);
s->local_protoflags = local;
s->remote_protoflags = remote;
}
static bool ssh1_connection_filter_queue(struct ssh1_connection_state *s)
{
PktIn *pktin;
ptrlen data;
struct ssh1_channel *c;
unsigned localid;
bool expect_halfopen;
while (1) {
if (ssh1_common_filter_queue(&s->ppl))
return true;
if ((pktin = pq_peek(s->ppl.in_pq)) == NULL)
return false;
switch (pktin->type) {
case SSH1_MSG_CHANNEL_DATA:
case SSH1_MSG_CHANNEL_OPEN_CONFIRMATION:
case SSH1_MSG_CHANNEL_OPEN_FAILURE:
case SSH1_MSG_CHANNEL_CLOSE:
case SSH1_MSG_CHANNEL_CLOSE_CONFIRMATION:
/*
* Common preliminary code for all the messages from the
* server that cite one of our channel ids: look up that
* channel id, check it exists, and if it's for a sharing
* downstream, pass it on.
*/
localid = get_uint32(pktin);
c = find234(s->channels, &localid, ssh1_channelfind);
expect_halfopen = (
pktin->type == SSH1_MSG_CHANNEL_OPEN_CONFIRMATION ||
pktin->type == SSH1_MSG_CHANNEL_OPEN_FAILURE);
if (!c || c->halfopen != expect_halfopen) {
ssh_remote_error(
s->ppl.ssh, "Received %s for %s channel %u",
ssh1_pkt_type(pktin->type),
!c ? "nonexistent" : c->halfopen ? "half-open" : "open",
localid);
return true;
}
switch (pktin->type) {
case SSH1_MSG_CHANNEL_OPEN_CONFIRMATION:
assert(c->halfopen);
c->remoteid = get_uint32(pktin);
c->halfopen = false;
c->throttling_conn = false;
chan_open_confirmation(c->chan);
/*
* Now that the channel is fully open, it's possible
* in principle to immediately close it. Check whether
* it wants us to!
*
* This can occur if a local socket error occurred
* between us sending out CHANNEL_OPEN and receiving
* OPEN_CONFIRMATION. If that happens, all we can do
* is immediately initiate close proceedings now that
* we know the server's id to put in the close
* message. We'll have handled that in this code by
* having already turned c->chan into a zombie, so its
* want_close method (which ssh1_channel_check_close
* will consult) will already be returning true.
*/
ssh1_channel_check_close(c);
if (c->pending_eof)
ssh1_channel_try_eof(c); /* in case we had a pending EOF */
break;
case SSH1_MSG_CHANNEL_OPEN_FAILURE:
assert(c->halfopen);
chan_open_failed(c->chan, NULL);
chan_free(c->chan);
del234(s->channels, c);
ssh1_channel_free(c);
break;
case SSH1_MSG_CHANNEL_DATA:
data = get_string(pktin);
if (!get_err(pktin)) {
int bufsize = chan_send(
c->chan, false, data.ptr, data.len);
if (!c->throttling_conn && bufsize > SSH1_BUFFER_LIMIT) {
c->throttling_conn = true;
ssh_throttle_conn(s->ppl.ssh, +1);
}
}
break;
case SSH1_MSG_CHANNEL_CLOSE:
if (!(c->closes & CLOSES_RCVD_CLOSE)) {
c->closes |= CLOSES_RCVD_CLOSE;
chan_send_eof(c->chan);
ssh1_channel_check_close(c);
}
break;
case SSH1_MSG_CHANNEL_CLOSE_CONFIRMATION:
if (!(c->closes & CLOSES_RCVD_CLOSECONF)) {
if (!(c->closes & CLOSES_SENT_CLOSE)) {
ssh_remote_error(
s->ppl.ssh,
"Received CHANNEL_CLOSE_CONFIRMATION for channel"
" %u for which we never sent CHANNEL_CLOSE\n",
c->localid);
return true;
}
c->closes |= CLOSES_RCVD_CLOSECONF;
ssh1_channel_check_close(c);
}
break;
}
pq_pop(s->ppl.in_pq);
break;
default:
if (ssh1_handle_direction_specific_packet(s, pktin)) {
pq_pop(s->ppl.in_pq);
if (ssh1_check_termination(s))
return true;
} else {
return false;
}
}
}
}
static PktIn *ssh1_connection_pop(struct ssh1_connection_state *s)
{
ssh1_connection_filter_queue(s);
return pq_pop(s->ppl.in_pq);
}
static void ssh1_connection_process_queue(PacketProtocolLayer *ppl)
{
struct ssh1_connection_state *s =
container_of(ppl, struct ssh1_connection_state, ppl);
PktIn *pktin;
if (ssh1_connection_filter_queue(s)) /* no matter why we were called */
return;
crBegin(s->crState);
/*
* Signal the seat that authentication is done, so that it can
* deploy spoofing defences. If it doesn't have any, deploy our
* own fallback one.
*
* We do this here rather than at the end of userauth, because we
* might not have gone through userauth at all (if we're a
* connection-sharing downstream).
*/
if (ssh1_connection_need_antispoof_prompt(s)) {
s->antispoof_prompt = new_prompts();
s->antispoof_prompt->to_server = true;
s->antispoof_prompt->from_server = false;
s->antispoof_prompt->name = dupstr("Authentication successful");
add_prompt(
s->antispoof_prompt,
dupstr("Access granted. Press Return to begin session. "), false);
s->antispoof_ret = seat_get_userpass_input(
s->ppl.seat, s->antispoof_prompt, NULL);
while (1) {
while (s->antispoof_ret < 0 &&
bufchain_size(s->ppl.user_input) > 0)
s->antispoof_ret = seat_get_userpass_input(
s->ppl.seat, s->antispoof_prompt, s->ppl.user_input);
if (s->antispoof_ret >= 0)
break;
s->want_user_input = true;
crReturnV;
s->want_user_input = false;
}
free_prompts(s->antispoof_prompt);
s->antispoof_prompt = NULL;
}
portfwdmgr_config(s->portfwdmgr, s->conf);
s->portfwdmgr_configured = true;
while (!s->finished_setup) {
ssh1_connection_direction_specific_setup(s);
crReturnV;
}
while (1) {
/*
* By this point, most incoming packets are already being
* handled by filter_queue, and we need only pay attention to
* the unusual ones.
*/
if ((pktin = ssh1_connection_pop(s)) != NULL) {
ssh_proto_error(s->ppl.ssh, "Unexpected packet received, "
"type %d (%s)", pktin->type,
ssh1_pkt_type(pktin->type));
return;
}
crReturnV;
}
crFinishV;
}
static void ssh1_channel_check_close(struct ssh1_channel *c)
{
struct ssh1_connection_state *s = c->connlayer;
PktOut *pktout;
if (c->halfopen) {
/*
* If we've sent out our own CHANNEL_OPEN but not yet seen
* either OPEN_CONFIRMATION or OPEN_FAILURE in response, then
* it's too early to be sending close messages of any kind.
*/
return;
}
if ((!((CLOSES_SENT_CLOSE | CLOSES_RCVD_CLOSE) & ~c->closes) ||
chan_want_close(c->chan, (c->closes & CLOSES_SENT_CLOSE),
(c->closes & CLOSES_RCVD_CLOSE))) &&
!(c->closes & CLOSES_SENT_CLOSECONF)) {
/*
* We have both sent and received CLOSE (or the channel type
* doesn't need us to), which means the channel is in final
* wind-up. Send CLOSE and/or CLOSE_CONFIRMATION, whichever we
* haven't sent yet.
*/
if (!(c->closes & CLOSES_SENT_CLOSE)) {
pktout = ssh_bpp_new_pktout(s->ppl.bpp, SSH1_MSG_CHANNEL_CLOSE);
put_uint32(pktout, c->remoteid);
pq_push(s->ppl.out_pq, pktout);
c->closes |= CLOSES_SENT_CLOSE;
}
if (c->closes & CLOSES_RCVD_CLOSE) {
pktout = ssh_bpp_new_pktout(
s->ppl.bpp, SSH1_MSG_CHANNEL_CLOSE_CONFIRMATION);
put_uint32(pktout, c->remoteid);
pq_push(s->ppl.out_pq, pktout);
c->closes |= CLOSES_SENT_CLOSECONF;
}
}
if (!((CLOSES_SENT_CLOSECONF | CLOSES_RCVD_CLOSECONF) & ~c->closes)) {
/*
* We have both sent and received CLOSE_CONFIRMATION, which
* means we're completely done with the channel.
*/
ssh1_channel_destroy(c);
}
}
static void ssh1_channel_try_eof(struct ssh1_channel *c)
{
struct ssh1_connection_state *s = c->connlayer;
PktOut *pktout;
assert(c->pending_eof); /* precondition for calling us */
if (c->halfopen)
return; /* can't close: not even opened yet */
c->pending_eof = false; /* we're about to send it */
pktout = ssh_bpp_new_pktout(s->ppl.bpp, SSH1_MSG_CHANNEL_CLOSE);
put_uint32(pktout, c->remoteid);
pq_push(s->ppl.out_pq, pktout);
c->closes |= CLOSES_SENT_CLOSE;
ssh1_channel_check_close(c);
}
/*
* Close any local socket and free any local resources associated with
* a channel. This converts the channel into a zombie.
*/
static void ssh1_channel_close_local(struct ssh1_channel *c,
const char *reason)
{
struct ssh1_connection_state *s = c->connlayer;
PacketProtocolLayer *ppl = &s->ppl; /* for ppl_logevent */
char *msg = chan_log_close_msg(c->chan);
if (msg != NULL) {
ppl_logevent("%s%s%s", msg, reason ? " " : "", reason ? reason : "");
sfree(msg);
}
chan_free(c->chan);
c->chan = zombiechan_new();
}
static void ssh1_check_termination_callback(void *vctx)
{
struct ssh1_connection_state *s = (struct ssh1_connection_state *)vctx;
ssh1_check_termination(s);
}
static void ssh1_channel_destroy(struct ssh1_channel *c)
{
struct ssh1_connection_state *s = c->connlayer;
ssh1_channel_close_local(c, NULL);
del234(s->channels, c);
ssh1_channel_free(c);
/*
* If that was the last channel left open, we might need to
* terminate. But we'll be a bit cautious, by doing that in a
* toplevel callback, just in case anything on the current call
* stack objects to this entire PPL being freed.
*/
queue_toplevel_callback(ssh1_check_termination_callback, s);
}
bool ssh1_check_termination(struct ssh1_connection_state *s)
{
/*
* Decide whether we should terminate the SSH connection now.
* Called after a channel goes away, or when the main session
* returns SSH1_SMSG_EXIT_STATUS; we terminate when none of either
* is left.
*/
if (s->session_terminated && count234(s->channels) == 0) {
PktOut *pktout = ssh_bpp_new_pktout(
s->ppl.bpp, SSH1_CMSG_EXIT_CONFIRMATION);
pq_push(s->ppl.out_pq, pktout);
ssh_user_close(s->ppl.ssh, "Session finished");
return true;
}
return false;
}
/*
* Set up most of a new ssh1_channel. Leaves chan untouched (since it
* will sometimes have been filled in before calling this).
*/
void ssh1_channel_init(struct ssh1_channel *c)
{
struct ssh1_connection_state *s = c->connlayer;
c->closes = 0;
c->pending_eof = false;
c->throttling_conn = false;
c->sc.vt = &ssh1channel_vtable;
c->sc.cl = &s->cl;
c->localid = alloc_channel_id(s->channels, struct ssh1_channel);
add234(s->channels, c);
}
static Conf *ssh1channel_get_conf(SshChannel *sc)
{
struct ssh1_channel *c = container_of(sc, struct ssh1_channel, sc);
struct ssh1_connection_state *s = c->connlayer;
return s->conf;
}
static void ssh1channel_write_eof(SshChannel *sc)
{
struct ssh1_channel *c = container_of(sc, struct ssh1_channel, sc);
if (c->closes & CLOSES_SENT_CLOSE)
return;
c->pending_eof = true;
ssh1_channel_try_eof(c);
}
static void ssh1channel_initiate_close(SshChannel *sc, const char *err)
{
struct ssh1_channel *c = container_of(sc, struct ssh1_channel, sc);
char *reason;
reason = err ? dupprintf("due to local error: %s", err) : NULL;
ssh1_channel_close_local(c, reason);
sfree(reason);
c->pending_eof = false; /* this will confuse a zombie channel */
ssh1_channel_check_close(c);
}
static void ssh1channel_unthrottle(SshChannel *sc, size_t bufsize)
{
struct ssh1_channel *c = container_of(sc, struct ssh1_channel, sc);
struct ssh1_connection_state *s = c->connlayer;
if (c->throttling_conn && bufsize <= SSH1_BUFFER_LIMIT) {
c->throttling_conn = false;
ssh_throttle_conn(s->ppl.ssh, -1);
}
}
static size_t ssh1channel_write(
SshChannel *sc, bool is_stderr, const void *buf, size_t len)
{
struct ssh1_channel *c = container_of(sc, struct ssh1_channel, sc);
struct ssh1_connection_state *s = c->connlayer;
assert(!(c->closes & CLOSES_SENT_CLOSE));
PktOut *pktout = ssh_bpp_new_pktout(s->ppl.bpp, SSH1_MSG_CHANNEL_DATA);
put_uint32(pktout, c->remoteid);
put_string(pktout, buf, len);
pq_push(s->ppl.out_pq, pktout);
/*
* In SSH-1 we can return 0 here - implying that channels are
* never individually throttled - because the only circumstance
* that can cause throttling will be the whole SSH connection
* backing up, in which case _everything_ will be throttled as a
* whole.
*/
return 0;
}
static struct X11FakeAuth *ssh1_add_x11_display(
ConnectionLayer *cl, int authtype, struct X11Display *disp)
{
struct ssh1_connection_state *s =
container_of(cl, struct ssh1_connection_state, cl);
struct X11FakeAuth *auth = x11_invent_fake_auth(s->x11authtree, authtype);
auth->disp = disp;
return auth;
}
static SshChannel *ssh1_lportfwd_open(
ConnectionLayer *cl, const char *hostname, int port,
const char *description, const SocketPeerInfo *pi, Channel *chan)
{
struct ssh1_connection_state *s =
container_of(cl, struct ssh1_connection_state, cl);
PacketProtocolLayer *ppl = &s->ppl; /* for ppl_logevent */
struct ssh1_channel *c = snew(struct ssh1_channel);
PktOut *pktout;
c->connlayer = s;
ssh1_channel_init(c);
c->halfopen = true;
c->chan = chan;
ppl_logevent("Opening connection to %s:%d for %s",
hostname, port, description);
pktout = ssh_bpp_new_pktout(s->ppl.bpp, SSH1_MSG_PORT_OPEN);
put_uint32(pktout, c->localid);
put_stringz(pktout, hostname);
put_uint32(pktout, port);
/* originator string would go here, but we didn't specify
* SSH_PROTOFLAG_HOST_IN_FWD_OPEN */
pq_push(s->ppl.out_pq, pktout);
return &c->sc;
}
static void ssh1_rportfwd_remove(ConnectionLayer *cl, struct ssh_rportfwd *rpf)
{
/*
* We cannot cancel listening ports on the server side in SSH-1!
* There's no message to support it.
*/
}
static bool ssh1_agent_forwarding_permitted(ConnectionLayer *cl)
{
struct ssh1_connection_state *s =
container_of(cl, struct ssh1_connection_state, cl);
return conf_get_bool(s->conf, CONF_agentfwd) && agent_exists();
}
static void ssh1_connection_special_cmd(PacketProtocolLayer *ppl,
SessionSpecialCode code, int arg)
{
struct ssh1_connection_state *s =
container_of(ppl, struct ssh1_connection_state, ppl);
PktOut *pktout;
if (code == SS_PING || code == SS_NOP) {
if (!(s->ppl.remote_bugs & BUG_CHOKES_ON_SSH1_IGNORE)) {
pktout = ssh_bpp_new_pktout(s->ppl.bpp, SSH1_MSG_IGNORE);
put_stringz(pktout, "");
pq_push(s->ppl.out_pq, pktout);
}
} else if (s->mainchan) {
mainchan_special_cmd(s->mainchan, code, arg);
}
}
static void ssh1_terminal_size(ConnectionLayer *cl, int width, int height)
{
struct ssh1_connection_state *s =
container_of(cl, struct ssh1_connection_state, cl);
s->term_width = width;
s->term_height = height;
if (s->mainchan)
mainchan_terminal_size(s->mainchan, width, height);
}
static void ssh1_stdout_unthrottle(ConnectionLayer *cl, size_t bufsize)
{
struct ssh1_connection_state *s =
container_of(cl, struct ssh1_connection_state, cl);
if (s->stdout_throttling && bufsize < SSH1_BUFFER_LIMIT) {
s->stdout_throttling = false;
ssh_throttle_conn(s->ppl.ssh, -1);
}
}
static size_t ssh1_stdin_backlog(ConnectionLayer *cl)
{
return 0;
}
static void ssh1_throttle_all_channels(ConnectionLayer *cl, bool throttled)
{
struct ssh1_connection_state *s =
container_of(cl, struct ssh1_connection_state, cl);
struct ssh1_channel *c;
int i;
for (i = 0; NULL != (c = index234(s->channels, i)); i++)
chan_set_input_wanted(c->chan, !throttled);
}
static bool ssh1_ldisc_option(ConnectionLayer *cl, int option)
{
struct ssh1_connection_state *s =
container_of(cl, struct ssh1_connection_state, cl);
return s->ldisc_opts[option];
}
static void ssh1_set_ldisc_option(ConnectionLayer *cl, int option, bool value)
{
struct ssh1_connection_state *s =
container_of(cl, struct ssh1_connection_state, cl);
s->ldisc_opts[option] = value;
}
static void ssh1_enable_x_fwd(ConnectionLayer *cl)
{
struct ssh1_connection_state *s =
container_of(cl, struct ssh1_connection_state, cl);
s->X11_fwd_enabled = true;
}
static void ssh1_set_wants_user_input(ConnectionLayer *cl, bool wanted)
{
struct ssh1_connection_state *s =
container_of(cl, struct ssh1_connection_state, cl);
s->want_user_input = wanted;
s->finished_setup = true;
}
static bool ssh1_connection_want_user_input(PacketProtocolLayer *ppl)
{
struct ssh1_connection_state *s =
container_of(ppl, struct ssh1_connection_state, ppl);
return s->want_user_input;
}
static void ssh1_connection_got_user_input(PacketProtocolLayer *ppl)
{
struct ssh1_connection_state *s =
container_of(ppl, struct ssh1_connection_state, ppl);
while (s->mainchan && bufchain_size(s->ppl.user_input) > 0) {
/*
* Add user input to the main channel's buffer.
*/
ptrlen data = bufchain_prefix(s->ppl.user_input);
if (data.len > 512)
data.len = 512;
sshfwd_write(&s->mainchan_sc, data.ptr, data.len);
bufchain_consume(s->ppl.user_input, data.len);
}
}
static void ssh1_connection_reconfigure(PacketProtocolLayer *ppl, Conf *conf)
{
struct ssh1_connection_state *s =
container_of(ppl, struct ssh1_connection_state, ppl);
conf_free(s->conf);
s->conf = conf_copy(conf);
if (s->portfwdmgr_configured)
portfwdmgr_config(s->portfwdmgr, s->conf);
}

123
ssh/connection1.h Normal file
View File

@ -0,0 +1,123 @@
struct ssh1_channel;
struct outstanding_succfail;
struct ssh1_connection_state {
int crState;
Conf *conf;
int local_protoflags, remote_protoflags;
tree234 *channels; /* indexed by local id */
/* In SSH-1, the main session doesn't take the form of a 'channel'
* according to the wire protocol. But we want to use the same API
* for it, so we define an SshChannel here - but one that uses a
* separate vtable from the usual one, so it doesn't map to a
* struct ssh1_channel as all the others do. */
SshChannel mainchan_sc;
Channel *mainchan_chan; /* the other end of mainchan_sc */
mainchan *mainchan; /* and its subtype */
bool got_pty;
bool ldisc_opts[LD_N_OPTIONS];
bool stdout_throttling;
bool want_user_input;
bool session_terminated;
int term_width, term_height, term_width_orig, term_height_orig;
bool X11_fwd_enabled;
struct X11Display *x11disp;
struct X11FakeAuth *x11auth;
tree234 *x11authtree;
tree234 *rportfwds;
PortFwdManager *portfwdmgr;
bool portfwdmgr_configured;
bool finished_setup;
/*
* These store the list of requests that we're waiting for
* SSH_SMSG_{SUCCESS,FAILURE} replies to. (Those messages don't
* come with any indication of what they're in response to, so we
* have to keep track of the queue ourselves.)
*/
struct outstanding_succfail *succfail_head, *succfail_tail;
bool compressing; /* used in server mode only */
bool sent_exit_status; /* also for server mode */
prompts_t *antispoof_prompt;
int antispoof_ret;
const SshServerConfig *ssc;
ConnectionLayer cl;
PacketProtocolLayer ppl;
};
struct ssh1_channel {
struct ssh1_connection_state *connlayer;
unsigned remoteid, localid;
int type;
/* True if we opened this channel but server hasn't confirmed. */
bool halfopen;
/* Bitmap of whether we've sent/received CHANNEL_CLOSE and
* CHANNEL_CLOSE_CONFIRMATION. */
#define CLOSES_SENT_CLOSE 1
#define CLOSES_SENT_CLOSECONF 2
#define CLOSES_RCVD_CLOSE 4
#define CLOSES_RCVD_CLOSECONF 8
int closes;
/*
* This flag indicates that an EOF is pending on the outgoing side
* of the channel: that is, wherever we're getting the data for
* this channel has sent us some data followed by EOF. We can't
* actually send the EOF until we've finished sending the data, so
* we set this flag instead to remind us to do so once our buffer
* is clear.
*/
bool pending_eof;
/*
* True if this channel is causing the underlying connection to be
* throttled.
*/
bool throttling_conn;
/*
* True if we currently have backed-up data on the direction of
* this channel pointing out of the SSH connection, and therefore
* would prefer the 'Channel' implementation not to read further
* local input if possible.
*/
bool throttled_by_backlog;
Channel *chan; /* handle the client side of this channel, if not */
SshChannel sc; /* entry point for chan to talk back to */
};
SshChannel *ssh1_session_open(ConnectionLayer *cl, Channel *chan);
void ssh1_channel_init(struct ssh1_channel *c);
void ssh1_channel_free(struct ssh1_channel *c);
struct ssh_rportfwd *ssh1_rportfwd_alloc(
ConnectionLayer *cl,
const char *shost, int sport, const char *dhost, int dport,
int addressfamily, const char *log_description, PortFwdRecord *pfr,
ssh_sharing_connstate *share_ctx);
SshChannel *ssh1_serverside_x11_open(
ConnectionLayer *cl, Channel *chan, const SocketPeerInfo *pi);
SshChannel *ssh1_serverside_agent_open(ConnectionLayer *cl, Channel *chan);
void ssh1_connection_direction_specific_setup(
struct ssh1_connection_state *s);
bool ssh1_handle_direction_specific_packet(
struct ssh1_connection_state *s, PktIn *pktin);
bool ssh1_check_termination(struct ssh1_connection_state *s);
bool ssh1_connection_need_antispoof_prompt(struct ssh1_connection_state *s);

505
ssh/connection2-client.c Normal file
View File

@ -0,0 +1,505 @@
/*
* Client-specific parts of the SSH-2 connection layer.
*/
#include <assert.h>
#include "putty.h"
#include "ssh.h"
#include "bpp.h"
#include "ppl.h"
#include "channel.h"
#include "sshcr.h"
#include "connection2.h"
static ChanopenResult chan_open_x11(
struct ssh2_connection_state *s, SshChannel *sc,
ptrlen peeraddr, int peerport)
{
PacketProtocolLayer *ppl = &s->ppl; /* for ppl_logevent */
char *peeraddr_str;
Channel *ch;
ppl_logevent("Received X11 connect request from %.*s:%d",
PTRLEN_PRINTF(peeraddr), peerport);
if (!s->X11_fwd_enabled && !s->connshare) {
CHANOPEN_RETURN_FAILURE(
SSH2_OPEN_ADMINISTRATIVELY_PROHIBITED,
("X11 forwarding is not enabled"));
}
peeraddr_str = peeraddr.ptr ? mkstr(peeraddr) : NULL;
ch = x11_new_channel(
s->x11authtree, sc, peeraddr_str, peerport, s->connshare != NULL);
sfree(peeraddr_str);
ppl_logevent("Opened X11 forward channel");
CHANOPEN_RETURN_SUCCESS(ch);
}
static ChanopenResult chan_open_forwarded_tcpip(
struct ssh2_connection_state *s, SshChannel *sc,
ptrlen fwdaddr, int fwdport, ptrlen peeraddr, int peerport)
{
PacketProtocolLayer *ppl = &s->ppl; /* for ppl_logevent */
struct ssh_rportfwd pf, *realpf;
Channel *ch;
char *err;
ppl_logevent("Received remote port %.*s:%d open request from %.*s:%d",
PTRLEN_PRINTF(fwdaddr), fwdport,
PTRLEN_PRINTF(peeraddr), peerport);
pf.shost = mkstr(fwdaddr);
pf.sport = fwdport;
realpf = find234(s->rportfwds, &pf, NULL);
sfree(pf.shost);
if (realpf == NULL) {
CHANOPEN_RETURN_FAILURE(
SSH2_OPEN_ADMINISTRATIVELY_PROHIBITED,
("Remote port is not recognised"));
}
if (realpf->share_ctx) {
/*
* This port forwarding is on behalf of a connection-sharing
* downstream.
*/
CHANOPEN_RETURN_DOWNSTREAM(realpf->share_ctx);
}
err = portfwdmgr_connect(
s->portfwdmgr, &ch, realpf->dhost, realpf->dport,
sc, realpf->addressfamily);
ppl_logevent("Attempting to forward remote port to %s:%d",
realpf->dhost, realpf->dport);
if (err != NULL) {
ppl_logevent("Port open failed: %s", err);
sfree(err);
CHANOPEN_RETURN_FAILURE(
SSH2_OPEN_CONNECT_FAILED,
("Port open failed"));
}
ppl_logevent("Forwarded port opened successfully");
CHANOPEN_RETURN_SUCCESS(ch);
}
static ChanopenResult chan_open_auth_agent(
struct ssh2_connection_state *s, SshChannel *sc)
{
if (!ssh_agent_forwarding_permitted(&s->cl)) {
CHANOPEN_RETURN_FAILURE(
SSH2_OPEN_ADMINISTRATIVELY_PROHIBITED,
("Agent forwarding is not enabled"));
}
/*
* If possible, make a stream-oriented connection to the agent and
* set up an ordinary port-forwarding type channel over it.
*/
Plug *plug;
Channel *ch = portfwd_raw_new(&s->cl, &plug, true);
Socket *skt = agent_connect(plug);
if (!sk_socket_error(skt)) {
portfwd_raw_setup(ch, skt, sc);
CHANOPEN_RETURN_SUCCESS(ch);
} else {
portfwd_raw_free(ch);
/*
* Otherwise, fall back to the old-fashioned system of parsing the
* forwarded data stream ourselves for message boundaries, and
* passing each individual message to the one-off agent_query().
*/
CHANOPEN_RETURN_SUCCESS(agentf_new(sc));
}
}
ChanopenResult ssh2_connection_parse_channel_open(
struct ssh2_connection_state *s, ptrlen type,
PktIn *pktin, SshChannel *sc)
{
if (ptrlen_eq_string(type, "x11")) {
ptrlen peeraddr = get_string(pktin);
int peerport = get_uint32(pktin);
return chan_open_x11(s, sc, peeraddr, peerport);
} else if (ptrlen_eq_string(type, "forwarded-tcpip")) {
ptrlen fwdaddr = get_string(pktin);
int fwdport = toint(get_uint32(pktin));
ptrlen peeraddr = get_string(pktin);
int peerport = toint(get_uint32(pktin));
return chan_open_forwarded_tcpip(
s, sc, fwdaddr, fwdport, peeraddr, peerport);
} else if (ptrlen_eq_string(type, "auth-agent@openssh.com")) {
return chan_open_auth_agent(s, sc);
} else {
CHANOPEN_RETURN_FAILURE(
SSH2_OPEN_UNKNOWN_CHANNEL_TYPE,
("Unsupported channel type requested"));
}
}
bool ssh2_connection_parse_global_request(
struct ssh2_connection_state *s, ptrlen type, PktIn *pktin)
{
/*
* We don't know of any global requests that an SSH client needs
* to honour.
*/
return false;
}
PktOut *ssh2_portfwd_chanopen(
struct ssh2_connection_state *s, struct ssh2_channel *c,
const char *hostname, int port,
const char *description, const SocketPeerInfo *peerinfo)
{
PacketProtocolLayer *ppl = &s->ppl; /* for ppl_logevent */
PktOut *pktout;
/*
* In client mode, this function is called by portfwdmgr in
* response to PortListeners that were set up in
* portfwdmgr_config, which means that the hostname and port
* parameters will indicate the host we want to tell the server to
* connect _to_.
*/
ppl_logevent("Opening connection to %s:%d for %s",
hostname, port, description);
pktout = ssh2_chanopen_init(c, "direct-tcpip");
{
char *trimmed_host = host_strduptrim(hostname);
put_stringz(pktout, trimmed_host);
sfree(trimmed_host);
}
put_uint32(pktout, port);
/*
* We make up values for the originator data; partly it's too much
* hassle to keep track, and partly I'm not convinced the server
* should be told details like that about my local network
* configuration. The "originator IP address" is syntactically a
* numeric IP address, and some servers (e.g., Tectia) get upset
* if it doesn't match this syntax.
*/
put_stringz(pktout, "0.0.0.0");
put_uint32(pktout, 0);
return pktout;
}
static int ssh2_rportfwd_cmp(void *av, void *bv)
{
struct ssh_rportfwd *a = (struct ssh_rportfwd *) av;
struct ssh_rportfwd *b = (struct ssh_rportfwd *) bv;
int i;
if ( (i = strcmp(a->shost, b->shost)) != 0)
return i < 0 ? -1 : +1;
if (a->sport > b->sport)
return +1;
if (a->sport < b->sport)
return -1;
return 0;
}
static void ssh2_rportfwd_globreq_response(struct ssh2_connection_state *s,
PktIn *pktin, void *ctx)
{
PacketProtocolLayer *ppl = &s->ppl; /* for ppl_logevent */
struct ssh_rportfwd *rpf = (struct ssh_rportfwd *)ctx;
if (pktin->type == SSH2_MSG_REQUEST_SUCCESS) {
ppl_logevent("Remote port forwarding from %s enabled",
rpf->log_description);
} else {
ppl_logevent("Remote port forwarding from %s refused",
rpf->log_description);
struct ssh_rportfwd *realpf = del234(s->rportfwds, rpf);
assert(realpf == rpf);
portfwdmgr_close(s->portfwdmgr, rpf->pfr);
free_rportfwd(rpf);
}
}
struct ssh_rportfwd *ssh2_rportfwd_alloc(
ConnectionLayer *cl,
const char *shost, int sport, const char *dhost, int dport,
int addressfamily, const char *log_description, PortFwdRecord *pfr,
ssh_sharing_connstate *share_ctx)
{
struct ssh2_connection_state *s =
container_of(cl, struct ssh2_connection_state, cl);
struct ssh_rportfwd *rpf = snew(struct ssh_rportfwd);
if (!s->rportfwds)
s->rportfwds = newtree234(ssh2_rportfwd_cmp);
rpf->shost = dupstr(shost);
rpf->sport = sport;
rpf->dhost = dupstr(dhost);
rpf->dport = dport;
rpf->addressfamily = addressfamily;
rpf->log_description = dupstr(log_description);
rpf->pfr = pfr;
rpf->share_ctx = share_ctx;
if (add234(s->rportfwds, rpf) != rpf) {
free_rportfwd(rpf);
return NULL;
}
if (!rpf->share_ctx) {
PktOut *pktout = ssh_bpp_new_pktout(
s->ppl.bpp, SSH2_MSG_GLOBAL_REQUEST);
put_stringz(pktout, "tcpip-forward");
put_bool(pktout, true); /* want reply */
put_stringz(pktout, rpf->shost);
put_uint32(pktout, rpf->sport);
pq_push(s->ppl.out_pq, pktout);
ssh2_queue_global_request_handler(
s, ssh2_rportfwd_globreq_response, rpf);
}
return rpf;
}
void ssh2_rportfwd_remove(ConnectionLayer *cl, struct ssh_rportfwd *rpf)
{
struct ssh2_connection_state *s =
container_of(cl, struct ssh2_connection_state, cl);
if (rpf->share_ctx) {
/*
* We don't manufacture a cancel-tcpip-forward message for
* remote port forwardings being removed on behalf of a
* downstream; we just pass through the one the downstream
* sent to us.
*/
} else {
PktOut *pktout = ssh_bpp_new_pktout(
s->ppl.bpp, SSH2_MSG_GLOBAL_REQUEST);
put_stringz(pktout, "cancel-tcpip-forward");
put_bool(pktout, false); /* _don't_ want reply */
put_stringz(pktout, rpf->shost);
put_uint32(pktout, rpf->sport);
pq_push(s->ppl.out_pq, pktout);
}
assert(s->rportfwds);
struct ssh_rportfwd *realpf = del234(s->rportfwds, rpf);
assert(realpf == rpf);
free_rportfwd(rpf);
}
SshChannel *ssh2_session_open(ConnectionLayer *cl, Channel *chan)
{
struct ssh2_connection_state *s =
container_of(cl, struct ssh2_connection_state, cl);
PacketProtocolLayer *ppl = &s->ppl; /* for ppl_logevent */
struct ssh2_channel *c = snew(struct ssh2_channel);
PktOut *pktout;
c->connlayer = s;
ssh2_channel_init(c);
c->halfopen = true;
c->chan = chan;
ppl_logevent("Opening main session channel");
pktout = ssh2_chanopen_init(c, "session");
pq_push(s->ppl.out_pq, pktout);
return &c->sc;
}
SshChannel *ssh2_serverside_x11_open(
ConnectionLayer *cl, Channel *chan, const SocketPeerInfo *pi)
{
unreachable("Should never be called in the client");
}
SshChannel *ssh2_serverside_agent_open(ConnectionLayer *cl, Channel *chan)
{
unreachable("Should never be called in the client");
}
static void ssh2_channel_response(
struct ssh2_channel *c, PktIn *pkt, void *ctx)
{
/* If pkt==NULL (because this handler has been called in response
* to CHANNEL_CLOSE arriving while the request was still
* outstanding), we treat that the same as CHANNEL_FAILURE. */
chan_request_response(c->chan,
pkt && pkt->type == SSH2_MSG_CHANNEL_SUCCESS);
}
void ssh2channel_start_shell(SshChannel *sc, bool want_reply)
{
struct ssh2_channel *c = container_of(sc, struct ssh2_channel, sc);
struct ssh2_connection_state *s = c->connlayer;
PktOut *pktout = ssh2_chanreq_init(
c, "shell", want_reply ? ssh2_channel_response : NULL, NULL);
pq_push(s->ppl.out_pq, pktout);
}
void ssh2channel_start_command(
SshChannel *sc, bool want_reply, const char *command)
{
struct ssh2_channel *c = container_of(sc, struct ssh2_channel, sc);
struct ssh2_connection_state *s = c->connlayer;
PktOut *pktout = ssh2_chanreq_init(
c, "exec", want_reply ? ssh2_channel_response : NULL, NULL);
put_stringz(pktout, command);
pq_push(s->ppl.out_pq, pktout);
}
bool ssh2channel_start_subsystem(
SshChannel *sc, bool want_reply, const char *subsystem)
{
struct ssh2_channel *c = container_of(sc, struct ssh2_channel, sc);
struct ssh2_connection_state *s = c->connlayer;
PktOut *pktout = ssh2_chanreq_init(
c, "subsystem", want_reply ? ssh2_channel_response : NULL, NULL);
put_stringz(pktout, subsystem);
pq_push(s->ppl.out_pq, pktout);
return true;
}
void ssh2channel_send_exit_status(SshChannel *sc, int status)
{
unreachable("Should never be called in the client");
}
void ssh2channel_send_exit_signal(
SshChannel *sc, ptrlen signame, bool core_dumped, ptrlen msg)
{
unreachable("Should never be called in the client");
}
void ssh2channel_send_exit_signal_numeric(
SshChannel *sc, int signum, bool core_dumped, ptrlen msg)
{
unreachable("Should never be called in the client");
}
void ssh2channel_request_x11_forwarding(
SshChannel *sc, bool want_reply, const char *authproto,
const char *authdata, int screen_number, bool oneshot)
{
struct ssh2_channel *c = container_of(sc, struct ssh2_channel, sc);
struct ssh2_connection_state *s = c->connlayer;
PktOut *pktout = ssh2_chanreq_init(
c, "x11-req", want_reply ? ssh2_channel_response : NULL, NULL);
put_bool(pktout, oneshot);
put_stringz(pktout, authproto);
put_stringz(pktout, authdata);
put_uint32(pktout, screen_number);
pq_push(s->ppl.out_pq, pktout);
}
void ssh2channel_request_agent_forwarding(SshChannel *sc, bool want_reply)
{
struct ssh2_channel *c = container_of(sc, struct ssh2_channel, sc);
struct ssh2_connection_state *s = c->connlayer;
PktOut *pktout = ssh2_chanreq_init(
c, "auth-agent-req@openssh.com",
want_reply ? ssh2_channel_response : NULL, NULL);
pq_push(s->ppl.out_pq, pktout);
}
void ssh2channel_request_pty(
SshChannel *sc, bool want_reply, Conf *conf, int w, int h)
{
struct ssh2_channel *c = container_of(sc, struct ssh2_channel, sc);
struct ssh2_connection_state *s = c->connlayer;
strbuf *modebuf;
PktOut *pktout = ssh2_chanreq_init(
c, "pty-req", want_reply ? ssh2_channel_response : NULL, NULL);
put_stringz(pktout, conf_get_str(conf, CONF_termtype));
put_uint32(pktout, w);
put_uint32(pktout, h);
put_uint32(pktout, 0); /* pixel width */
put_uint32(pktout, 0); /* pixel height */
modebuf = strbuf_new();
write_ttymodes_to_packet(
BinarySink_UPCAST(modebuf), 2,
get_ttymodes_from_conf(s->ppl.seat, conf));
put_stringsb(pktout, modebuf);
pq_push(s->ppl.out_pq, pktout);
}
bool ssh2channel_send_env_var(
SshChannel *sc, bool want_reply, const char *var, const char *value)
{
struct ssh2_channel *c = container_of(sc, struct ssh2_channel, sc);
struct ssh2_connection_state *s = c->connlayer;
PktOut *pktout = ssh2_chanreq_init(
c, "env", want_reply ? ssh2_channel_response : NULL, NULL);
put_stringz(pktout, var);
put_stringz(pktout, value);
pq_push(s->ppl.out_pq, pktout);
return true;
}
bool ssh2channel_send_serial_break(SshChannel *sc, bool want_reply, int length)
{
struct ssh2_channel *c = container_of(sc, struct ssh2_channel, sc);
struct ssh2_connection_state *s = c->connlayer;
PktOut *pktout = ssh2_chanreq_init(
c, "break", want_reply ? ssh2_channel_response : NULL, NULL);
put_uint32(pktout, length);
pq_push(s->ppl.out_pq, pktout);
return true;
}
bool ssh2channel_send_signal(
SshChannel *sc, bool want_reply, const char *signame)
{
struct ssh2_channel *c = container_of(sc, struct ssh2_channel, sc);
struct ssh2_connection_state *s = c->connlayer;
PktOut *pktout = ssh2_chanreq_init(
c, "signal", want_reply ? ssh2_channel_response : NULL, NULL);
put_stringz(pktout, signame);
pq_push(s->ppl.out_pq, pktout);
return true;
}
void ssh2channel_send_terminal_size_change(SshChannel *sc, int w, int h)
{
struct ssh2_channel *c = container_of(sc, struct ssh2_channel, sc);
struct ssh2_connection_state *s = c->connlayer;
PktOut *pktout = ssh2_chanreq_init(c, "window-change", NULL, NULL);
put_uint32(pktout, w);
put_uint32(pktout, h);
put_uint32(pktout, 0); /* pixel width */
put_uint32(pktout, 0); /* pixel height */
pq_push(s->ppl.out_pq, pktout);
}
bool ssh2_connection_need_antispoof_prompt(struct ssh2_connection_state *s)
{
bool success = seat_set_trust_status(s->ppl.seat, false);
return (!success && !ssh_is_bare(s->ppl.ssh));
}

306
ssh/connection2-server.c Normal file
View File

@ -0,0 +1,306 @@
/*
* Server-specific parts of the SSH-2 connection layer.
*/
#include <assert.h>
#include "putty.h"
#include "ssh.h"
#include "bpp.h"
#include "ppl.h"
#include "channel.h"
#include "sshcr.h"
#include "connection2.h"
#include "server.h"
void ssh2connection_server_configure(
PacketProtocolLayer *ppl, const SftpServerVtable *sftpserver_vt,
const SshServerConfig *ssc)
{
struct ssh2_connection_state *s =
container_of(ppl, struct ssh2_connection_state, ppl);
s->sftpserver_vt = sftpserver_vt;
s->ssc = ssc;
}
static ChanopenResult chan_open_session(
struct ssh2_connection_state *s, SshChannel *sc)
{
PacketProtocolLayer *ppl = &s->ppl; /* for ppl_logevent */
ppl_logevent("Opened session channel");
CHANOPEN_RETURN_SUCCESS(sesschan_new(sc, s->ppl.logctx,
s->sftpserver_vt, s->ssc));
}
static ChanopenResult chan_open_direct_tcpip(
struct ssh2_connection_state *s, SshChannel *sc,
ptrlen dstaddr, int dstport, ptrlen peeraddr, int peerport)
{
PacketProtocolLayer *ppl = &s->ppl; /* for ppl_logevent */
Channel *ch;
char *dstaddr_str, *err;
dstaddr_str = mkstr(dstaddr);
ppl_logevent("Received request to connect to port %s:%d (from %.*s:%d)",
dstaddr_str, dstport, PTRLEN_PRINTF(peeraddr), peerport);
err = portfwdmgr_connect(
s->portfwdmgr, &ch, dstaddr_str, dstport, sc, ADDRTYPE_UNSPEC);
sfree(dstaddr_str);
if (err != NULL) {
ppl_logevent("Port open failed: %s", err);
sfree(err);
CHANOPEN_RETURN_FAILURE(
SSH2_OPEN_CONNECT_FAILED, ("Connection failed"));
}
ppl_logevent("Port opened successfully");
CHANOPEN_RETURN_SUCCESS(ch);
}
ChanopenResult ssh2_connection_parse_channel_open(
struct ssh2_connection_state *s, ptrlen type,
PktIn *pktin, SshChannel *sc)
{
if (ptrlen_eq_string(type, "session")) {
return chan_open_session(s, sc);
} else if (ptrlen_eq_string(type, "direct-tcpip")) {
ptrlen dstaddr = get_string(pktin);
int dstport = toint(get_uint32(pktin));
ptrlen peeraddr = get_string(pktin);
int peerport = toint(get_uint32(pktin));
return chan_open_direct_tcpip(
s, sc, dstaddr, dstport, peeraddr, peerport);
} else {
CHANOPEN_RETURN_FAILURE(
SSH2_OPEN_UNKNOWN_CHANNEL_TYPE,
("Unsupported channel type requested"));
}
}
bool ssh2_connection_parse_global_request(
struct ssh2_connection_state *s, ptrlen type, PktIn *pktin)
{
if (ptrlen_eq_string(type, "tcpip-forward")) {
char *host = mkstr(get_string(pktin));
unsigned port = get_uint32(pktin);
/* In SSH-2, the host/port we listen on are the same host/port
* we want reported back to us when a connection comes in,
* because that's what we tell the client */
bool toret = portfwdmgr_listen(
s->portfwdmgr, host, port, host, port, s->conf);
sfree(host);
return toret;
} else if (ptrlen_eq_string(type, "cancel-tcpip-forward")) {
char *host = mkstr(get_string(pktin));
unsigned port = get_uint32(pktin);
bool toret = portfwdmgr_unlisten(s->portfwdmgr, host, port);
sfree(host);
return toret;
} else {
/* Unrecognised request. */
return false;
}
}
PktOut *ssh2_portfwd_chanopen(
struct ssh2_connection_state *s, struct ssh2_channel *c,
const char *hostname, int port,
const char *description, const SocketPeerInfo *pi)
{
PacketProtocolLayer *ppl = &s->ppl; /* for ppl_logevent */
PktOut *pktout;
/*
* In server mode, this function is called by portfwdmgr in
* response to PortListeners that were set up by calling
* portfwdmgr_listen, which means that the hostname and port
* parameters will identify the listening socket on which a
* connection just came in.
*/
if (pi && pi->log_text)
ppl_logevent("Forwarding connection to listening port %s:%d from %s",
hostname, port, pi->log_text);
else
ppl_logevent("Forwarding connection to listening port %s:%d",
hostname, port);
pktout = ssh2_chanopen_init(c, "forwarded-tcpip");
put_stringz(pktout, hostname);
put_uint32(pktout, port);
put_stringz(pktout, (pi && pi->addr_text ? pi->addr_text : "0.0.0.0"));
put_uint32(pktout, (pi && pi->port >= 0 ? pi->port : 0));
return pktout;
}
struct ssh_rportfwd *ssh2_rportfwd_alloc(
ConnectionLayer *cl,
const char *shost, int sport, const char *dhost, int dport,
int addressfamily, const char *log_description, PortFwdRecord *pfr,
ssh_sharing_connstate *share_ctx)
{
unreachable("Should never be called in the server");
}
void ssh2_rportfwd_remove(ConnectionLayer *cl, struct ssh_rportfwd *rpf)
{
unreachable("Should never be called in the server");
}
SshChannel *ssh2_session_open(ConnectionLayer *cl, Channel *chan)
{
unreachable("Should never be called in the server");
}
SshChannel *ssh2_serverside_x11_open(
ConnectionLayer *cl, Channel *chan, const SocketPeerInfo *pi)
{
struct ssh2_connection_state *s =
container_of(cl, struct ssh2_connection_state, cl);
PacketProtocolLayer *ppl = &s->ppl; /* for ppl_logevent */
struct ssh2_channel *c = snew(struct ssh2_channel);
PktOut *pktout;
c->connlayer = s;
ssh2_channel_init(c);
c->halfopen = true;
c->chan = chan;
ppl_logevent("Forwarding X11 channel to client");
pktout = ssh2_chanopen_init(c, "x11");
put_stringz(pktout, (pi && pi->addr_text ? pi->addr_text : "0.0.0.0"));
put_uint32(pktout, (pi && pi->port >= 0 ? pi->port : 0));
pq_push(s->ppl.out_pq, pktout);
return &c->sc;
}
SshChannel *ssh2_serverside_agent_open(ConnectionLayer *cl, Channel *chan)
{
struct ssh2_connection_state *s =
container_of(cl, struct ssh2_connection_state, cl);
PacketProtocolLayer *ppl = &s->ppl; /* for ppl_logevent */
struct ssh2_channel *c = snew(struct ssh2_channel);
PktOut *pktout;
c->connlayer = s;
ssh2_channel_init(c);
c->halfopen = true;
c->chan = chan;
ppl_logevent("Forwarding SSH agent to client");
pktout = ssh2_chanopen_init(c, "auth-agent@openssh.com");
pq_push(s->ppl.out_pq, pktout);
return &c->sc;
}
void ssh2channel_start_shell(SshChannel *sc, bool want_reply)
{
unreachable("Should never be called in the server");
}
void ssh2channel_start_command(
SshChannel *sc, bool want_reply, const char *command)
{
unreachable("Should never be called in the server");
}
bool ssh2channel_start_subsystem(
SshChannel *sc, bool want_reply, const char *subsystem)
{
unreachable("Should never be called in the server");
}
void ssh2channel_send_exit_status(SshChannel *sc, int status)
{
struct ssh2_channel *c = container_of(sc, struct ssh2_channel, sc);
struct ssh2_connection_state *s = c->connlayer;
PktOut *pktout = ssh2_chanreq_init(c, "exit-status", NULL, NULL);
put_uint32(pktout, status);
pq_push(s->ppl.out_pq, pktout);
}
void ssh2channel_send_exit_signal(
SshChannel *sc, ptrlen signame, bool core_dumped, ptrlen msg)
{
struct ssh2_channel *c = container_of(sc, struct ssh2_channel, sc);
struct ssh2_connection_state *s = c->connlayer;
PktOut *pktout = ssh2_chanreq_init(c, "exit-signal", NULL, NULL);
put_stringpl(pktout, signame);
put_bool(pktout, core_dumped);
put_stringpl(pktout, msg);
put_stringz(pktout, ""); /* language tag */
pq_push(s->ppl.out_pq, pktout);
}
void ssh2channel_send_exit_signal_numeric(
SshChannel *sc, int signum, bool core_dumped, ptrlen msg)
{
struct ssh2_channel *c = container_of(sc, struct ssh2_channel, sc);
struct ssh2_connection_state *s = c->connlayer;
PktOut *pktout = ssh2_chanreq_init(c, "exit-signal", NULL, NULL);
put_uint32(pktout, signum);
put_bool(pktout, core_dumped);
put_stringpl(pktout, msg);
put_stringz(pktout, ""); /* language tag */
pq_push(s->ppl.out_pq, pktout);
}
void ssh2channel_request_x11_forwarding(
SshChannel *sc, bool want_reply, const char *authproto,
const char *authdata, int screen_number, bool oneshot)
{
unreachable("Should never be called in the server");
}
void ssh2channel_request_agent_forwarding(SshChannel *sc, bool want_reply)
{
unreachable("Should never be called in the server");
}
void ssh2channel_request_pty(
SshChannel *sc, bool want_reply, Conf *conf, int w, int h)
{
unreachable("Should never be called in the server");
}
bool ssh2channel_send_env_var(
SshChannel *sc, bool want_reply, const char *var, const char *value)
{
unreachable("Should never be called in the server");
}
bool ssh2channel_send_serial_break(SshChannel *sc, bool want_reply, int length)
{
unreachable("Should never be called in the server");
}
bool ssh2channel_send_signal(
SshChannel *sc, bool want_reply, const char *signame)
{
unreachable("Should never be called in the server");
}
void ssh2channel_send_terminal_size_change(SshChannel *sc, int w, int h)
{
unreachable("Should never be called in the server");
}
bool ssh2_connection_need_antispoof_prompt(struct ssh2_connection_state *s)
{
return false;
}

1745
ssh/connection2.c Normal file

File diff suppressed because it is too large Load Diff

235
ssh/connection2.h Normal file
View File

@ -0,0 +1,235 @@
#ifndef PUTTY_SSH2CONNECTION_H
#define PUTTY_SSH2CONNECTION_H
struct outstanding_channel_request;
struct outstanding_global_request;
struct ssh2_connection_state {
int crState;
ssh_sharing_state *connshare;
char *peer_verstring;
mainchan *mainchan;
SshChannel *mainchan_sc;
bool ldisc_opts[LD_N_OPTIONS];
int session_attempt, session_status;
int term_width, term_height;
bool want_user_input;
bool ssh_is_simple;
bool persistent;
bool started;
Conf *conf;
tree234 *channels; /* indexed by local id */
bool all_channels_throttled;
bool X11_fwd_enabled;
tree234 *x11authtree;
bool got_pty;
tree234 *rportfwds;
PortFwdManager *portfwdmgr;
bool portfwdmgr_configured;
prompts_t *antispoof_prompt;
int antispoof_ret;
const SftpServerVtable *sftpserver_vt;
const SshServerConfig *ssc;
/*
* These store the list of global requests that we're waiting for
* replies to. (REQUEST_FAILURE doesn't come with any indication
* of what message caused it, so we have to keep track of the
* queue ourselves.)
*/
struct outstanding_global_request *globreq_head, *globreq_tail;
ConnectionLayer cl;
PacketProtocolLayer ppl;
};
typedef void (*gr_handler_fn_t)(struct ssh2_connection_state *s,
PktIn *pktin, void *ctx);
void ssh2_queue_global_request_handler(
struct ssh2_connection_state *s, gr_handler_fn_t handler, void *ctx);
struct ssh2_channel {
struct ssh2_connection_state *connlayer;
unsigned remoteid, localid;
int type;
/* True if we opened this channel but server hasn't confirmed. */
bool halfopen;
/* Bitmap of whether we've sent/received CHANNEL_EOF and
* CHANNEL_CLOSE. */
#define CLOSES_SENT_EOF 1
#define CLOSES_SENT_CLOSE 2
#define CLOSES_RCVD_EOF 4
#define CLOSES_RCVD_CLOSE 8
int closes;
/*
* This flag indicates that an EOF is pending on the outgoing side
* of the channel: that is, wherever we're getting the data for
* this channel has sent us some data followed by EOF. We can't
* actually send the EOF until we've finished sending the data, so
* we set this flag instead to remind us to do so once our buffer
* is clear.
*/
bool pending_eof;
/*
* True if this channel is causing the underlying connection to be
* throttled.
*/
bool throttling_conn;
/*
* True if we currently have backed-up data on the direction of
* this channel pointing out of the SSH connection, and therefore
* would prefer the 'Channel' implementation not to read further
* local input if possible.
*/
bool throttled_by_backlog;
bufchain outbuffer, errbuffer;
unsigned remwindow, remmaxpkt;
/* locwindow is signed so we can cope with excess data. */
int locwindow, locmaxwin;
/*
* remlocwin is the amount of local window that we think
* the remote end had available to it after it sent the
* last data packet or window adjust ack.
*/
int remlocwin;
/*
* These store the list of channel requests that we're waiting for
* replies to. (CHANNEL_FAILURE doesn't come with any indication
* of what message caused it, so we have to keep track of the
* queue ourselves.)
*/
struct outstanding_channel_request *chanreq_head, *chanreq_tail;
enum { THROTTLED, UNTHROTTLING, UNTHROTTLED } throttle_state;
ssh_sharing_connstate *sharectx; /* sharing context, if this is a
* downstream channel */
Channel *chan; /* handle the client side of this channel, if not */
SshChannel sc; /* entry point for chan to talk back to */
};
typedef void (*cr_handler_fn_t)(struct ssh2_channel *, PktIn *, void *);
void ssh2_channel_init(struct ssh2_channel *c);
PktOut *ssh2_chanreq_init(struct ssh2_channel *c, const char *type,
cr_handler_fn_t handler, void *ctx);
typedef enum ChanopenOutcome {
CHANOPEN_RESULT_FAILURE,
CHANOPEN_RESULT_SUCCESS,
CHANOPEN_RESULT_DOWNSTREAM,
} ChanopenOutcome;
typedef struct ChanopenResult {
ChanopenOutcome outcome;
union {
struct {
char *wire_message; /* must be freed by recipient */
unsigned reason_code;
} failure;
struct {
Channel *channel;
} success;
struct {
ssh_sharing_connstate *share_ctx;
} downstream;
} u;
} ChanopenResult;
PktOut *ssh2_chanopen_init(struct ssh2_channel *c, const char *type);
PktOut *ssh2_portfwd_chanopen(
struct ssh2_connection_state *s, struct ssh2_channel *c,
const char *hostname, int port,
const char *description, const SocketPeerInfo *peerinfo);
struct ssh_rportfwd *ssh2_rportfwd_alloc(
ConnectionLayer *cl,
const char *shost, int sport, const char *dhost, int dport,
int addressfamily, const char *log_description, PortFwdRecord *pfr,
ssh_sharing_connstate *share_ctx);
void ssh2_rportfwd_remove(
ConnectionLayer *cl, struct ssh_rportfwd *rpf);
SshChannel *ssh2_session_open(ConnectionLayer *cl, Channel *chan);
SshChannel *ssh2_serverside_x11_open(
ConnectionLayer *cl, Channel *chan, const SocketPeerInfo *pi);
SshChannel *ssh2_serverside_agent_open(ConnectionLayer *cl, Channel *chan);
void ssh2channel_send_exit_status(SshChannel *c, int status);
void ssh2channel_send_exit_signal(
SshChannel *c, ptrlen signame, bool core_dumped, ptrlen msg);
void ssh2channel_send_exit_signal_numeric(
SshChannel *c, int signum, bool core_dumped, ptrlen msg);
void ssh2channel_request_x11_forwarding(
SshChannel *c, bool want_reply, const char *authproto,
const char *authdata, int screen_number, bool oneshot);
void ssh2channel_request_agent_forwarding(SshChannel *c, bool want_reply);
void ssh2channel_request_pty(
SshChannel *c, bool want_reply, Conf *conf, int w, int h);
bool ssh2channel_send_env_var(
SshChannel *c, bool want_reply, const char *var, const char *value);
void ssh2channel_start_shell(SshChannel *c, bool want_reply);
void ssh2channel_start_command(
SshChannel *c, bool want_reply, const char *command);
bool ssh2channel_start_subsystem(
SshChannel *c, bool want_reply, const char *subsystem);
bool ssh2channel_send_env_var(
SshChannel *c, bool want_reply, const char *var, const char *value);
bool ssh2channel_send_serial_break(
SshChannel *c, bool want_reply, int length);
bool ssh2channel_send_signal(
SshChannel *c, bool want_reply, const char *signame);
void ssh2channel_send_terminal_size_change(SshChannel *c, int w, int h);
#define CHANOPEN_RETURN_FAILURE(code, msgparams) do \
{ \
ChanopenResult toret; \
toret.outcome = CHANOPEN_RESULT_FAILURE; \
toret.u.failure.reason_code = code; \
toret.u.failure.wire_message = dupprintf msgparams; \
return toret; \
} while (0)
#define CHANOPEN_RETURN_SUCCESS(chan) do \
{ \
ChanopenResult toret; \
toret.outcome = CHANOPEN_RESULT_SUCCESS; \
toret.u.success.channel = chan; \
return toret; \
} while (0)
#define CHANOPEN_RETURN_DOWNSTREAM(shctx) do \
{ \
ChanopenResult toret; \
toret.outcome = CHANOPEN_RESULT_DOWNSTREAM; \
toret.u.downstream.share_ctx = shctx; \
return toret; \
} while (0)
ChanopenResult ssh2_connection_parse_channel_open(
struct ssh2_connection_state *s, ptrlen type,
PktIn *pktin, SshChannel *sc);
bool ssh2_connection_parse_global_request(
struct ssh2_connection_state *s, ptrlen type, PktIn *pktin);
bool ssh2_connection_need_antispoof_prompt(struct ssh2_connection_state *s);
#endif /* PUTTY_SSH2CONNECTION_H */

171
ssh/crc-attack-detector.c Normal file
View File

@ -0,0 +1,171 @@
/* $OpenBSD: deattack.c,v 1.14 2001/06/23 15:12:18 itojun Exp $ */
/*
* Cryptographic attack detector for ssh - source code
*
* Copyright (c) 1998 CORE SDI S.A., Buenos Aires, Argentina.
*
* All rights reserved. Redistribution and use in source and binary
* forms, with or without modification, are permitted provided that
* this copyright notice is retained.
*
* THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED
* WARRANTIES ARE DISCLAIMED. IN NO EVENT SHALL CORE SDI S.A. BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY OR
* CONSEQUENTIAL DAMAGES RESULTING FROM THE USE OR MISUSE OF THIS
* SOFTWARE.
*
* Ariel Futoransky <futo@core-sdi.com>
* <http://www.core-sdi.com>
*
* Modified for use in PuTTY by Simon Tatham
*/
#include <assert.h>
#include "misc.h"
#include "ssh.h"
/* SSH Constants */
#define SSH_MAXBLOCKS (32 * 1024)
#define SSH_BLOCKSIZE (8)
/* Hashing constants */
#define HASH_MINSIZE (8 * 1024)
#define HASH_ENTRYSIZE (sizeof(uint16_t))
#define HASH_FACTOR(x) ((x)*3/2)
#define HASH_UNUSEDCHAR (0xff)
#define HASH_UNUSED (0xffff)
#define HASH_IV (0xfffe)
#define HASH_MINBLOCKS (7*SSH_BLOCKSIZE)
/* Hash function (Input keys are cipher results) */
#define HASH(x) GET_32BIT_MSB_FIRST(x)
#define CMP(a, b) (memcmp(a, b, SSH_BLOCKSIZE))
static const uint8_t ONE[4] = { 1, 0, 0, 0 };
static const uint8_t ZERO[4] = { 0, 0, 0, 0 };
struct crcda_ctx {
uint16_t *h;
uint32_t n;
};
struct crcda_ctx *crcda_make_context(void)
{
struct crcda_ctx *ret = snew(struct crcda_ctx);
ret->h = NULL;
ret->n = HASH_MINSIZE / HASH_ENTRYSIZE;
return ret;
}
void crcda_free_context(struct crcda_ctx *ctx)
{
if (ctx) {
sfree(ctx->h);
ctx->h = NULL;
sfree(ctx);
}
}
static void crc_update(uint32_t *a, const void *b)
{
*a = crc32_update(*a, make_ptrlen(b, 4));
}
/* detect if a block is used in a particular pattern */
static bool check_crc(const uint8_t *S, const uint8_t *buf,
uint32_t len, const uint8_t *IV)
{
uint32_t crc;
const uint8_t *c;
crc = 0;
if (IV && !CMP(S, IV)) {
crc_update(&crc, ONE);
crc_update(&crc, ZERO);
}
for (c = buf; c < buf + len; c += SSH_BLOCKSIZE) {
if (!CMP(S, c)) {
crc_update(&crc, ONE);
crc_update(&crc, ZERO);
} else {
crc_update(&crc, ZERO);
crc_update(&crc, ZERO);
}
}
return (crc == 0);
}
/* Detect a crc32 compensation attack on a packet */
bool detect_attack(struct crcda_ctx *ctx,
const unsigned char *buf, uint32_t len,
const unsigned char *IV)
{
register uint32_t i, j;
uint32_t l;
register const uint8_t *c;
const uint8_t *d;
assert(!(len > (SSH_MAXBLOCKS * SSH_BLOCKSIZE) ||
len % SSH_BLOCKSIZE != 0));
for (l = ctx->n; l < HASH_FACTOR(len / SSH_BLOCKSIZE); l = l << 2)
;
if (ctx->h == NULL) {
ctx->n = l;
ctx->h = snewn(ctx->n, uint16_t);
} else {
if (l > ctx->n) {
ctx->n = l;
ctx->h = sresize(ctx->h, ctx->n, uint16_t);
}
}
if (len <= HASH_MINBLOCKS) {
for (c = buf; c < buf + len; c += SSH_BLOCKSIZE) {
if (IV && (!CMP(c, IV))) {
if ((check_crc(c, buf, len, IV)))
return true; /* attack detected */
else
break;
}
for (d = buf; d < c; d += SSH_BLOCKSIZE) {
if (!CMP(c, d)) {
if ((check_crc(c, buf, len, IV)))
return true; /* attack detected */
else
break;
}
}
}
return false; /* ok */
}
memset(ctx->h, HASH_UNUSEDCHAR, ctx->n * HASH_ENTRYSIZE);
if (IV)
ctx->h[HASH(IV) & (ctx->n - 1)] = HASH_IV;
for (c = buf, j = 0; c < (buf + len); c += SSH_BLOCKSIZE, j++) {
for (i = HASH(c) & (ctx->n - 1); ctx->h[i] != HASH_UNUSED;
i = (i + 1) & (ctx->n - 1)) {
if (ctx->h[i] == HASH_IV) {
assert(IV); /* or we wouldn't have stored HASH_IV above */
if (!CMP(c, IV)) {
if (check_crc(c, buf, len, IV))
return true; /* attack detected */
else
break;
}
} else if (!CMP(c, buf + ctx->h[i] * SSH_BLOCKSIZE)) {
if (check_crc(c, buf, len, IV))
return true; /* attack detected */
else
break;
}
}
ctx->h[i] = j;
}
return false; /* ok */
}

217
ssh/gss.h Normal file
View File

@ -0,0 +1,217 @@
#ifndef PUTTY_SSHGSS_H
#define PUTTY_SSHGSS_H
#include "putty.h"
#include "pgssapi.h"
#ifndef NO_GSSAPI
#define SSH2_GSS_OIDTYPE 0x06
typedef void *Ssh_gss_ctx;
typedef enum Ssh_gss_stat {
SSH_GSS_OK = 0,
SSH_GSS_S_CONTINUE_NEEDED,
SSH_GSS_NO_MEM,
SSH_GSS_BAD_HOST_NAME,
SSH_GSS_BAD_MIC,
SSH_GSS_NO_CREDS,
SSH_GSS_FAILURE
} Ssh_gss_stat;
#define SSH_GSS_S_COMPLETE SSH_GSS_OK
#define SSH_GSS_CLEAR_BUF(buf) do { \
(*buf).length = 0; \
(*buf).value = NULL; \
} while (0)
typedef gss_buffer_desc Ssh_gss_buf;
typedef gss_name_t Ssh_gss_name;
#define GSS_NO_EXPIRATION ((time_t)-1)
#define GSS_DEF_REKEY_MINS 2 /* Default minutes between GSS cache checks */
/* Functions, provided by either wingss.c or gssc.c */
struct ssh_gss_library;
/*
* Prepare a collection of GSSAPI libraries for use in a single SSH
* connection. Returns a structure containing a list of libraries,
* with their ids (see struct ssh_gss_library below) filled in so
* that the client can go through them in the SSH user's preferred
* order.
*
* Must always return non-NULL. (Even if no libraries are available,
* it must return an empty structure.)
*
* The free function cleans up the structure, and its associated
* libraries (if any).
*/
struct ssh_gss_liblist {
struct ssh_gss_library *libraries;
int nlibraries;
};
struct ssh_gss_liblist *ssh_gss_setup(Conf *conf);
void ssh_gss_cleanup(struct ssh_gss_liblist *list);
/*
* Fills in buf with a string describing the GSSAPI mechanism in
* use. buf->data is not dynamically allocated.
*/
typedef Ssh_gss_stat (*t_ssh_gss_indicate_mech)(struct ssh_gss_library *lib,
Ssh_gss_buf *buf);
/*
* Converts a name such as a hostname into a GSSAPI internal form,
* which is placed in "out". The result should be freed by
* ssh_gss_release_name().
*/
typedef Ssh_gss_stat (*t_ssh_gss_import_name)(struct ssh_gss_library *lib,
char *in, Ssh_gss_name *out);
/*
* Frees the contents of an Ssh_gss_name structure filled in by
* ssh_gss_import_name().
*/
typedef Ssh_gss_stat (*t_ssh_gss_release_name)(struct ssh_gss_library *lib,
Ssh_gss_name *name);
/*
* The main GSSAPI security context setup function. The "out"
* parameter will need to be freed by ssh_gss_free_tok.
*/
typedef Ssh_gss_stat (*t_ssh_gss_init_sec_context)
(struct ssh_gss_library *lib,
Ssh_gss_ctx *ctx, Ssh_gss_name name, int delegate,
Ssh_gss_buf *in, Ssh_gss_buf *out, time_t *expiry,
unsigned long *lifetime);
/*
* Frees the contents of an Ssh_gss_buf filled in by
* ssh_gss_init_sec_context(). Do not accidentally call this on
* something filled in by ssh_gss_get_mic() (which requires a
* different free function) or something filled in by any other
* way.
*/
typedef Ssh_gss_stat (*t_ssh_gss_free_tok)(struct ssh_gss_library *lib,
Ssh_gss_buf *);
/*
* Acquires the credentials to perform authentication in the first
* place. Needs to be freed by ssh_gss_release_cred().
*/
typedef Ssh_gss_stat (*t_ssh_gss_acquire_cred)(struct ssh_gss_library *lib,
Ssh_gss_ctx *,
time_t *expiry);
/*
* Frees the contents of an Ssh_gss_ctx filled in by
* ssh_gss_acquire_cred().
*/
typedef Ssh_gss_stat (*t_ssh_gss_release_cred)(struct ssh_gss_library *lib,
Ssh_gss_ctx *);
/*
* Gets a MIC for some input data. "out" needs to be freed by
* ssh_gss_free_mic().
*/
typedef Ssh_gss_stat (*t_ssh_gss_get_mic)(struct ssh_gss_library *lib,
Ssh_gss_ctx ctx, Ssh_gss_buf *in,
Ssh_gss_buf *out);
/*
* Validates an input MIC for some input data.
*/
typedef Ssh_gss_stat (*t_ssh_gss_verify_mic)(struct ssh_gss_library *lib,
Ssh_gss_ctx ctx,
Ssh_gss_buf *in_data,
Ssh_gss_buf *in_mic);
/*
* Frees the contents of an Ssh_gss_buf filled in by
* ssh_gss_get_mic(). Do not accidentally call this on something
* filled in by ssh_gss_init_sec_context() (which requires a
* different free function) or something filled in by any other
* way.
*/
typedef Ssh_gss_stat (*t_ssh_gss_free_mic)(struct ssh_gss_library *lib,
Ssh_gss_buf *);
/*
* Return an error message after authentication failed. The
* message string is returned in "buf", with buf->len giving the
* number of characters of printable message text and buf->data
* containing one more character which is a trailing NUL.
* buf->data should be manually freed by the caller.
*/
typedef Ssh_gss_stat (*t_ssh_gss_display_status)(struct ssh_gss_library *lib,
Ssh_gss_ctx, Ssh_gss_buf *buf);
struct ssh_gss_library {
/*
* Identifying number in the enumeration used by the
* configuration code to specify a preference order.
*/
int id;
/*
* Filled in at initialisation time, if there's anything
* interesting to say about how GSSAPI was initialised (e.g.
* which of a number of alternative libraries was used).
*/
const char *gsslogmsg;
/*
* Function pointers implementing the SSH wrapper layer on top
* of GSSAPI. (Defined in sshgssc, typically, though Windows
* provides an alternative layer to sit on top of the annoyingly
* different SSPI.)
*/
t_ssh_gss_indicate_mech indicate_mech;
t_ssh_gss_import_name import_name;
t_ssh_gss_release_name release_name;
t_ssh_gss_init_sec_context init_sec_context;
t_ssh_gss_free_tok free_tok;
t_ssh_gss_acquire_cred acquire_cred;
t_ssh_gss_release_cred release_cred;
t_ssh_gss_get_mic get_mic;
t_ssh_gss_verify_mic verify_mic;
t_ssh_gss_free_mic free_mic;
t_ssh_gss_display_status display_status;
/*
* Additional data for the wrapper layers.
*/
union {
struct gssapi_functions gssapi;
/*
* The SSPI wrappers don't need to store their Windows API
* function pointers in this structure, because there can't
* be more than one set of them available.
*/
} u;
/*
* Wrapper layers will often also need to store a library handle
* of some sort for cleanup time.
*/
void *handle;
};
/*
* State that has to be shared between all GSSAPI-using parts of the
* same SSH connection, in particular between GSS key exchange and the
* subsequent trivial userauth method that reuses its output.
*/
struct ssh_connection_shared_gss_state {
struct ssh_gss_liblist *libs;
struct ssh_gss_library *lib;
Ssh_gss_name srv_name;
Ssh_gss_ctx ctx;
};
#endif /* NO_GSSAPI */
#endif /*PUTTY_SSHGSS_H*/

288
ssh/gssc.c Normal file
View File

@ -0,0 +1,288 @@
#include "putty.h"
#include <string.h>
#include <limits.h>
#include "gssc.h"
#include "misc.h"
#ifndef NO_GSSAPI
static Ssh_gss_stat ssh_gssapi_indicate_mech(struct ssh_gss_library *lib,
Ssh_gss_buf *mech)
{
/* Copy constant into mech */
mech->length = GSS_MECH_KRB5->length;
mech->value = GSS_MECH_KRB5->elements;
return SSH_GSS_OK;
}
static Ssh_gss_stat ssh_gssapi_import_name(struct ssh_gss_library *lib,
char *host,
Ssh_gss_name *srv_name)
{
struct gssapi_functions *gss = &lib->u.gssapi;
OM_uint32 min_stat,maj_stat;
gss_buffer_desc host_buf;
char *pStr;
pStr = dupcat("host@", host);
host_buf.value = pStr;
host_buf.length = strlen(pStr);
maj_stat = gss->import_name(&min_stat, &host_buf,
GSS_C_NT_HOSTBASED_SERVICE, srv_name);
/* Release buffer */
sfree(pStr);
if (maj_stat == GSS_S_COMPLETE) return SSH_GSS_OK;
return SSH_GSS_FAILURE;
}
static Ssh_gss_stat ssh_gssapi_acquire_cred(struct ssh_gss_library *lib,
Ssh_gss_ctx *ctx,
time_t *expiry)
{
struct gssapi_functions *gss = &lib->u.gssapi;
gss_OID_set_desc k5only = { 1, GSS_MECH_KRB5 };
gss_cred_id_t cred;
OM_uint32 dummy;
OM_uint32 time_rec;
gssapi_ssh_gss_ctx *gssctx = snew(gssapi_ssh_gss_ctx);
gssctx->ctx = GSS_C_NO_CONTEXT;
gssctx->expiry = 0;
gssctx->maj_stat =
gss->acquire_cred(&gssctx->min_stat, GSS_C_NO_NAME, GSS_C_INDEFINITE,
&k5only, GSS_C_INITIATE, &cred,
(gss_OID_set *)0, &time_rec);
if (gssctx->maj_stat != GSS_S_COMPLETE) {
sfree(gssctx);
return SSH_GSS_FAILURE;
}
/*
* When the credential lifetime is not yet available due to deferred
* processing, gss_acquire_cred should return a 0 lifetime which is
* distinct from GSS_C_INDEFINITE which signals a crential that never
* expires. However, not all implementations get this right, and with
* Kerberos, initiator credentials always expire at some point. So when
* lifetime is 0 or GSS_C_INDEFINITE we call gss_inquire_cred_by_mech() to
* complete deferred processing.
*/
if (time_rec == GSS_C_INDEFINITE || time_rec == 0) {
gssctx->maj_stat =
gss->inquire_cred_by_mech(&gssctx->min_stat, cred,
(gss_OID) GSS_MECH_KRB5,
GSS_C_NO_NAME,
&time_rec,
NULL,
NULL);
}
(void) gss->release_cred(&dummy, &cred);
if (gssctx->maj_stat != GSS_S_COMPLETE) {
sfree(gssctx);
return SSH_GSS_FAILURE;
}
if (time_rec != GSS_C_INDEFINITE)
gssctx->expiry = time(NULL) + time_rec;
else
gssctx->expiry = GSS_NO_EXPIRATION;
if (expiry) {
*expiry = gssctx->expiry;
}
*ctx = (Ssh_gss_ctx) gssctx;
return SSH_GSS_OK;
}
static Ssh_gss_stat ssh_gssapi_init_sec_context(struct ssh_gss_library *lib,
Ssh_gss_ctx *ctx,
Ssh_gss_name srv_name,
int to_deleg,
Ssh_gss_buf *recv_tok,
Ssh_gss_buf *send_tok,
time_t *expiry,
unsigned long *lifetime)
{
struct gssapi_functions *gss = &lib->u.gssapi;
gssapi_ssh_gss_ctx *gssctx = (gssapi_ssh_gss_ctx*) *ctx;
OM_uint32 ret_flags;
OM_uint32 lifetime_rec;
if (to_deleg) to_deleg = GSS_C_DELEG_FLAG;
gssctx->maj_stat = gss->init_sec_context(&gssctx->min_stat,
GSS_C_NO_CREDENTIAL,
&gssctx->ctx,
srv_name,
(gss_OID) GSS_MECH_KRB5,
GSS_C_MUTUAL_FLAG |
GSS_C_INTEG_FLAG | to_deleg,
0,
GSS_C_NO_CHANNEL_BINDINGS,
recv_tok,
NULL, /* ignore mech type */
send_tok,
&ret_flags,
&lifetime_rec);
if (lifetime) {
if (lifetime_rec == GSS_C_INDEFINITE)
*lifetime = ULONG_MAX;
else
*lifetime = lifetime_rec;
}
if (expiry) {
if (lifetime_rec == GSS_C_INDEFINITE)
*expiry = GSS_NO_EXPIRATION;
else
*expiry = time(NULL) + lifetime_rec;
}
if (gssctx->maj_stat == GSS_S_COMPLETE) return SSH_GSS_S_COMPLETE;
if (gssctx->maj_stat == GSS_S_CONTINUE_NEEDED) return SSH_GSS_S_CONTINUE_NEEDED;
return SSH_GSS_FAILURE;
}
static Ssh_gss_stat ssh_gssapi_display_status(struct ssh_gss_library *lib,
Ssh_gss_ctx ctx,
Ssh_gss_buf *buf)
{
struct gssapi_functions *gss = &lib->u.gssapi;
gssapi_ssh_gss_ctx *gssctx = (gssapi_ssh_gss_ctx *) ctx;
OM_uint32 lmin,lmax;
OM_uint32 ccc;
gss_buffer_desc msg_maj=GSS_C_EMPTY_BUFFER;
gss_buffer_desc msg_min=GSS_C_EMPTY_BUFFER;
/* Return empty buffer in case of failure */
SSH_GSS_CLEAR_BUF(buf);
/* get first mesg from GSS */
ccc=0;
lmax=gss->display_status(&lmin,gssctx->maj_stat,GSS_C_GSS_CODE,(gss_OID) GSS_MECH_KRB5,&ccc,&msg_maj);
if (lmax != GSS_S_COMPLETE) return SSH_GSS_FAILURE;
/* get first mesg from Kerberos */
ccc=0;
lmax=gss->display_status(&lmin,gssctx->min_stat,GSS_C_MECH_CODE,(gss_OID) GSS_MECH_KRB5,&ccc,&msg_min);
if (lmax != GSS_S_COMPLETE) {
gss->release_buffer(&lmin, &msg_maj);
return SSH_GSS_FAILURE;
}
/* copy data into buffer */
buf->length = msg_maj.length + msg_min.length + 1;
buf->value = snewn(buf->length + 1, char);
/* copy mem */
memcpy((char *)buf->value, msg_maj.value, msg_maj.length);
((char *)buf->value)[msg_maj.length] = ' ';
memcpy((char *)buf->value + msg_maj.length + 1, msg_min.value, msg_min.length);
((char *)buf->value)[buf->length] = 0;
/* free mem & exit */
gss->release_buffer(&lmin, &msg_maj);
gss->release_buffer(&lmin, &msg_min);
return SSH_GSS_OK;
}
static Ssh_gss_stat ssh_gssapi_free_tok(struct ssh_gss_library *lib,
Ssh_gss_buf *send_tok)
{
struct gssapi_functions *gss = &lib->u.gssapi;
OM_uint32 min_stat,maj_stat;
maj_stat = gss->release_buffer(&min_stat, send_tok);
if (maj_stat == GSS_S_COMPLETE) return SSH_GSS_OK;
return SSH_GSS_FAILURE;
}
static Ssh_gss_stat ssh_gssapi_release_cred(struct ssh_gss_library *lib,
Ssh_gss_ctx *ctx)
{
struct gssapi_functions *gss = &lib->u.gssapi;
gssapi_ssh_gss_ctx *gssctx = (gssapi_ssh_gss_ctx *) *ctx;
OM_uint32 min_stat;
OM_uint32 maj_stat=GSS_S_COMPLETE;
if (gssctx == NULL) return SSH_GSS_FAILURE;
if (gssctx->ctx != GSS_C_NO_CONTEXT)
maj_stat = gss->delete_sec_context(&min_stat,&gssctx->ctx,GSS_C_NO_BUFFER);
sfree(gssctx);
*ctx = NULL;
if (maj_stat == GSS_S_COMPLETE) return SSH_GSS_OK;
return SSH_GSS_FAILURE;
}
static Ssh_gss_stat ssh_gssapi_release_name(struct ssh_gss_library *lib,
Ssh_gss_name *srv_name)
{
struct gssapi_functions *gss = &lib->u.gssapi;
OM_uint32 min_stat,maj_stat;
maj_stat = gss->release_name(&min_stat, srv_name);
if (maj_stat == GSS_S_COMPLETE) return SSH_GSS_OK;
return SSH_GSS_FAILURE;
}
static Ssh_gss_stat ssh_gssapi_get_mic(struct ssh_gss_library *lib,
Ssh_gss_ctx ctx, Ssh_gss_buf *buf,
Ssh_gss_buf *hash)
{
struct gssapi_functions *gss = &lib->u.gssapi;
gssapi_ssh_gss_ctx *gssctx = (gssapi_ssh_gss_ctx *) ctx;
if (gssctx == NULL) return SSH_GSS_FAILURE;
return gss->get_mic(&(gssctx->min_stat), gssctx->ctx, 0, buf, hash);
}
static Ssh_gss_stat ssh_gssapi_verify_mic(struct ssh_gss_library *lib,
Ssh_gss_ctx ctx, Ssh_gss_buf *buf,
Ssh_gss_buf *hash)
{
struct gssapi_functions *gss = &lib->u.gssapi;
gssapi_ssh_gss_ctx *gssctx = (gssapi_ssh_gss_ctx *) ctx;
if (gssctx == NULL) return SSH_GSS_FAILURE;
return gss->verify_mic(&(gssctx->min_stat), gssctx->ctx, buf, hash, NULL);
}
static Ssh_gss_stat ssh_gssapi_free_mic(struct ssh_gss_library *lib,
Ssh_gss_buf *hash)
{
/* On Unix this is the same freeing process as ssh_gssapi_free_tok. */
return ssh_gssapi_free_tok(lib, hash);
}
void ssh_gssapi_bind_fns(struct ssh_gss_library *lib)
{
lib->indicate_mech = ssh_gssapi_indicate_mech;
lib->import_name = ssh_gssapi_import_name;
lib->release_name = ssh_gssapi_release_name;
lib->init_sec_context = ssh_gssapi_init_sec_context;
lib->free_tok = ssh_gssapi_free_tok;
lib->acquire_cred = ssh_gssapi_acquire_cred;
lib->release_cred = ssh_gssapi_release_cred;
lib->get_mic = ssh_gssapi_get_mic;
lib->verify_mic = ssh_gssapi_verify_mic;
lib->free_mic = ssh_gssapi_free_mic;
lib->display_status = ssh_gssapi_display_status;
}
#else
/* Dummy function so this source file defines something if NO_GSSAPI
is defined. */
int ssh_gssapi_init(void)
{
return 0;
}
#endif

24
ssh/gssc.h Normal file
View File

@ -0,0 +1,24 @@
#ifndef PUTTY_SSHGSSC_H
#define PUTTY_SSHGSSC_H
#include "putty.h"
#ifndef NO_GSSAPI
#include "pgssapi.h"
#include "gss.h"
typedef struct gssapi_ssh_gss_ctx {
OM_uint32 maj_stat;
OM_uint32 min_stat;
gss_ctx_id_t ctx;
time_t expiry;
} gssapi_ssh_gss_ctx;
void ssh_gssapi_bind_fns(struct ssh_gss_library *lib);
#else
int ssh_gssapi_init(void);
#endif /*NO_GSSAPI*/
#endif /*PUTTY_SSHGSSC_H*/

930
ssh/kex2-client.c Normal file
View File

@ -0,0 +1,930 @@
/*
* Client side of key exchange for the SSH-2 transport protocol (RFC 4253).
*/
#include <assert.h>
#include "putty.h"
#include "ssh.h"
#include "bpp.h"
#include "ppl.h"
#include "sshcr.h"
#include "storage.h"
#include "transport2.h"
#include "mpint.h"
/*
* Another copy of the symbol defined in mpunsafe.c. See the comment
* there.
*/
const int deliberate_symbol_clash = 12345;
void ssh2kex_coroutine(struct ssh2_transport_state *s, bool *aborted)
{
PacketProtocolLayer *ppl = &s->ppl; /* for ppl_logevent */
PktIn *pktin;
PktOut *pktout;
crBegin(s->crStateKex);
if (s->kex_alg->main_type == KEXTYPE_DH) {
/*
* Work out the number of bits of key we will need from the
* key exchange. We start with the maximum key length of
* either cipher...
*/
{
int csbits, scbits;
csbits = s->out.cipher ? s->out.cipher->real_keybits : 0;
scbits = s->in.cipher ? s->in.cipher->real_keybits : 0;
s->nbits = (csbits > scbits ? csbits : scbits);
}
/* The keys only have hlen-bit entropy, since they're based on
* a hash. So cap the key size at hlen bits. */
if (s->nbits > s->kex_alg->hash->hlen * 8)
s->nbits = s->kex_alg->hash->hlen * 8;
/*
* If we're doing Diffie-Hellman group exchange, start by
* requesting a group.
*/
if (dh_is_gex(s->kex_alg)) {
ppl_logevent("Doing Diffie-Hellman group exchange");
s->ppl.bpp->pls->kctx = SSH2_PKTCTX_DHGEX;
/*
* Work out how big a DH group we will need to allow that
* much data.
*/
s->pbits = 512 << ((s->nbits - 1) / 64);
if (s->pbits < DH_MIN_SIZE)
s->pbits = DH_MIN_SIZE;
if (s->pbits > DH_MAX_SIZE)
s->pbits = DH_MAX_SIZE;
if ((s->ppl.remote_bugs & BUG_SSH2_OLDGEX)) {
pktout = ssh_bpp_new_pktout(
s->ppl.bpp, SSH2_MSG_KEX_DH_GEX_REQUEST_OLD);
put_uint32(pktout, s->pbits);
} else {
pktout = ssh_bpp_new_pktout(
s->ppl.bpp, SSH2_MSG_KEX_DH_GEX_REQUEST);
put_uint32(pktout, DH_MIN_SIZE);
put_uint32(pktout, s->pbits);
put_uint32(pktout, DH_MAX_SIZE);
}
pq_push(s->ppl.out_pq, pktout);
crMaybeWaitUntilV((pktin = ssh2_transport_pop(s)) != NULL);
if (pktin->type != SSH2_MSG_KEX_DH_GEX_GROUP) {
ssh_proto_error(s->ppl.ssh, "Received unexpected packet when "
"expecting Diffie-Hellman group, type %d (%s)",
pktin->type,
ssh2_pkt_type(s->ppl.bpp->pls->kctx,
s->ppl.bpp->pls->actx,
pktin->type));
*aborted = true;
return;
}
s->p = get_mp_ssh2(pktin);
s->g = get_mp_ssh2(pktin);
if (get_err(pktin)) {
ssh_proto_error(s->ppl.ssh,
"Unable to parse Diffie-Hellman group packet");
*aborted = true;
return;
}
s->dh_ctx = dh_setup_gex(s->p, s->g);
s->kex_init_value = SSH2_MSG_KEX_DH_GEX_INIT;
s->kex_reply_value = SSH2_MSG_KEX_DH_GEX_REPLY;
ppl_logevent("Doing Diffie-Hellman key exchange using %d-bit "
"modulus and hash %s with a server-supplied group",
dh_modulus_bit_size(s->dh_ctx),
ssh_hash_alg(s->exhash)->text_name);
} else {
s->ppl.bpp->pls->kctx = SSH2_PKTCTX_DHGROUP;
s->dh_ctx = dh_setup_group(s->kex_alg);
s->kex_init_value = SSH2_MSG_KEXDH_INIT;
s->kex_reply_value = SSH2_MSG_KEXDH_REPLY;
ppl_logevent("Doing Diffie-Hellman key exchange using %d-bit "
"modulus and hash %s with standard group \"%s\"",
dh_modulus_bit_size(s->dh_ctx),
ssh_hash_alg(s->exhash)->text_name,
s->kex_alg->groupname);
}
/*
* Now generate and send e for Diffie-Hellman.
*/
seat_set_busy_status(s->ppl.seat, BUSY_CPU);
s->e = dh_create_e(s->dh_ctx, s->nbits * 2);
pktout = ssh_bpp_new_pktout(s->ppl.bpp, s->kex_init_value);
put_mp_ssh2(pktout, s->e);
pq_push(s->ppl.out_pq, pktout);
seat_set_busy_status(s->ppl.seat, BUSY_WAITING);
crMaybeWaitUntilV((pktin = ssh2_transport_pop(s)) != NULL);
if (pktin->type != s->kex_reply_value) {
ssh_proto_error(s->ppl.ssh, "Received unexpected packet when "
"expecting Diffie-Hellman reply, type %d (%s)",
pktin->type,
ssh2_pkt_type(s->ppl.bpp->pls->kctx,
s->ppl.bpp->pls->actx,
pktin->type));
*aborted = true;
return;
}
seat_set_busy_status(s->ppl.seat, BUSY_CPU);
s->hostkeydata = get_string(pktin);
s->hkey = ssh_key_new_pub(s->hostkey_alg, s->hostkeydata);
s->f = get_mp_ssh2(pktin);
s->sigdata = get_string(pktin);
if (get_err(pktin)) {
ssh_proto_error(s->ppl.ssh,
"Unable to parse Diffie-Hellman reply packet");
*aborted = true;
return;
}
{
const char *err = dh_validate_f(s->dh_ctx, s->f);
if (err) {
ssh_proto_error(s->ppl.ssh, "Diffie-Hellman reply failed "
"validation: %s", err);
*aborted = true;
return;
}
}
s->K = dh_find_K(s->dh_ctx, s->f);
/* We assume everything from now on will be quick, and it might
* involve user interaction. */
seat_set_busy_status(s->ppl.seat, BUSY_NOT);
put_stringpl(s->exhash, s->hostkeydata);
if (dh_is_gex(s->kex_alg)) {
if (!(s->ppl.remote_bugs & BUG_SSH2_OLDGEX))
put_uint32(s->exhash, DH_MIN_SIZE);
put_uint32(s->exhash, s->pbits);
if (!(s->ppl.remote_bugs & BUG_SSH2_OLDGEX))
put_uint32(s->exhash, DH_MAX_SIZE);
put_mp_ssh2(s->exhash, s->p);
put_mp_ssh2(s->exhash, s->g);
}
put_mp_ssh2(s->exhash, s->e);
put_mp_ssh2(s->exhash, s->f);
dh_cleanup(s->dh_ctx);
s->dh_ctx = NULL;
mp_free(s->f); s->f = NULL;
if (dh_is_gex(s->kex_alg)) {
mp_free(s->g); s->g = NULL;
mp_free(s->p); s->p = NULL;
}
} else if (s->kex_alg->main_type == KEXTYPE_ECDH) {
ppl_logevent("Doing ECDH key exchange with curve %s and hash %s",
ssh_ecdhkex_curve_textname(s->kex_alg),
ssh_hash_alg(s->exhash)->text_name);
s->ppl.bpp->pls->kctx = SSH2_PKTCTX_ECDHKEX;
s->ecdh_key = ssh_ecdhkex_newkey(s->kex_alg);
if (!s->ecdh_key) {
ssh_sw_abort(s->ppl.ssh, "Unable to generate key for ECDH");
*aborted = true;
return;
}
pktout = ssh_bpp_new_pktout(s->ppl.bpp, SSH2_MSG_KEX_ECDH_INIT);
{
strbuf *pubpoint = strbuf_new();
ssh_ecdhkex_getpublic(s->ecdh_key, BinarySink_UPCAST(pubpoint));
put_stringsb(pktout, pubpoint);
}
pq_push(s->ppl.out_pq, pktout);
crMaybeWaitUntilV((pktin = ssh2_transport_pop(s)) != NULL);
if (pktin->type != SSH2_MSG_KEX_ECDH_REPLY) {
ssh_proto_error(s->ppl.ssh, "Received unexpected packet when "
"expecting ECDH reply, type %d (%s)", pktin->type,
ssh2_pkt_type(s->ppl.bpp->pls->kctx,
s->ppl.bpp->pls->actx,
pktin->type));
*aborted = true;
return;
}
s->hostkeydata = get_string(pktin);
put_stringpl(s->exhash, s->hostkeydata);
s->hkey = ssh_key_new_pub(s->hostkey_alg, s->hostkeydata);
{
strbuf *pubpoint = strbuf_new();
ssh_ecdhkex_getpublic(s->ecdh_key, BinarySink_UPCAST(pubpoint));
put_string(s->exhash, pubpoint->u, pubpoint->len);
strbuf_free(pubpoint);
}
{
ptrlen keydata = get_string(pktin);
put_stringpl(s->exhash, keydata);
s->K = ssh_ecdhkex_getkey(s->ecdh_key, keydata);
if (!get_err(pktin) && !s->K) {
ssh_proto_error(s->ppl.ssh, "Received invalid elliptic curve "
"point in ECDH reply");
*aborted = true;
return;
}
}
s->sigdata = get_string(pktin);
if (get_err(pktin)) {
ssh_proto_error(s->ppl.ssh, "Unable to parse ECDH reply packet");
*aborted = true;
return;
}
ssh_ecdhkex_freekey(s->ecdh_key);
s->ecdh_key = NULL;
#ifndef NO_GSSAPI
} else if (s->kex_alg->main_type == KEXTYPE_GSS) {
ptrlen data;
s->ppl.bpp->pls->kctx = SSH2_PKTCTX_GSSKEX;
s->init_token_sent = false;
s->complete_rcvd = false;
s->hkey = NULL;
s->keystr = NULL;
/*
* Work out the number of bits of key we will need from the
* key exchange. We start with the maximum key length of
* either cipher...
*
* This is rote from the KEXTYPE_DH section above.
*/
{
int csbits, scbits;
csbits = s->out.cipher->real_keybits;
scbits = s->in.cipher->real_keybits;
s->nbits = (csbits > scbits ? csbits : scbits);
}
/* The keys only have hlen-bit entropy, since they're based on
* a hash. So cap the key size at hlen bits. */
if (s->nbits > s->kex_alg->hash->hlen * 8)
s->nbits = s->kex_alg->hash->hlen * 8;
if (dh_is_gex(s->kex_alg)) {
/*
* Work out how big a DH group we will need to allow that
* much data.
*/
s->pbits = 512 << ((s->nbits - 1) / 64);
ppl_logevent("Doing GSSAPI (with Kerberos V5) Diffie-Hellman "
"group exchange, with minimum %d bits", s->pbits);
pktout = ssh_bpp_new_pktout(s->ppl.bpp, SSH2_MSG_KEXGSS_GROUPREQ);
put_uint32(pktout, s->pbits); /* min */
put_uint32(pktout, s->pbits); /* preferred */
put_uint32(pktout, s->pbits * 2); /* max */
pq_push(s->ppl.out_pq, pktout);
crMaybeWaitUntilV(
(pktin = ssh2_transport_pop(s)) != NULL);
if (pktin->type != SSH2_MSG_KEXGSS_GROUP) {
ssh_proto_error(s->ppl.ssh, "Received unexpected packet when "
"expecting Diffie-Hellman group, type %d (%s)",
pktin->type,
ssh2_pkt_type(s->ppl.bpp->pls->kctx,
s->ppl.bpp->pls->actx,
pktin->type));
*aborted = true;
return;
}
s->p = get_mp_ssh2(pktin);
s->g = get_mp_ssh2(pktin);
if (get_err(pktin)) {
ssh_proto_error(s->ppl.ssh,
"Unable to parse Diffie-Hellman group packet");
*aborted = true;
return;
}
s->dh_ctx = dh_setup_gex(s->p, s->g);
} else {
s->dh_ctx = dh_setup_group(s->kex_alg);
ppl_logevent("Using GSSAPI (with Kerberos V5) Diffie-Hellman with"
" standard group \"%s\"", s->kex_alg->groupname);
}
ppl_logevent("Doing GSSAPI (with Kerberos V5) Diffie-Hellman key "
"exchange with hash %s", ssh_hash_alg(s->exhash)->text_name);
/* Now generate e for Diffie-Hellman. */
seat_set_busy_status(s->ppl.seat, BUSY_CPU);
s->e = dh_create_e(s->dh_ctx, s->nbits * 2);
if (s->shgss->lib->gsslogmsg)
ppl_logevent("%s", s->shgss->lib->gsslogmsg);
/* initial tokens are empty */
SSH_GSS_CLEAR_BUF(&s->gss_rcvtok);
SSH_GSS_CLEAR_BUF(&s->gss_sndtok);
SSH_GSS_CLEAR_BUF(&s->mic);
s->gss_stat = s->shgss->lib->acquire_cred(
s->shgss->lib, &s->shgss->ctx, &s->gss_cred_expiry);
if (s->gss_stat != SSH_GSS_OK) {
ssh_sw_abort(s->ppl.ssh,
"GSSAPI key exchange failed to initialise");
*aborted = true;
return;
}
/* now enter the loop */
assert(s->shgss->srv_name);
do {
/*
* When acquire_cred yields no useful expiration, go with the
* service ticket expiration.
*/
s->gss_stat = s->shgss->lib->init_sec_context(
s->shgss->lib, &s->shgss->ctx, s->shgss->srv_name,
s->gss_delegate, &s->gss_rcvtok, &s->gss_sndtok,
(s->gss_cred_expiry == GSS_NO_EXPIRATION ?
&s->gss_cred_expiry : NULL), NULL);
SSH_GSS_CLEAR_BUF(&s->gss_rcvtok);
if (s->gss_stat == SSH_GSS_S_COMPLETE && s->complete_rcvd)
break; /* MIC is verified after the loop */
if (s->gss_stat != SSH_GSS_S_COMPLETE &&
s->gss_stat != SSH_GSS_S_CONTINUE_NEEDED) {
if (s->shgss->lib->display_status(
s->shgss->lib, s->shgss->ctx,
&s->gss_buf) == SSH_GSS_OK) {
char *err = s->gss_buf.value;
ssh_sw_abort(s->ppl.ssh,
"GSSAPI key exchange failed to initialise "
"context: %s", err);
sfree(err);
*aborted = true;
return;
}
}
assert(s->gss_stat == SSH_GSS_S_COMPLETE ||
s->gss_stat == SSH_GSS_S_CONTINUE_NEEDED);
if (!s->init_token_sent) {
s->init_token_sent = true;
pktout = ssh_bpp_new_pktout(s->ppl.bpp,
SSH2_MSG_KEXGSS_INIT);
if (s->gss_sndtok.length == 0) {
ssh_sw_abort(s->ppl.ssh, "GSSAPI key exchange failed: "
"no initial context token");
*aborted = true;
return;
}
put_string(pktout,
s->gss_sndtok.value, s->gss_sndtok.length);
put_mp_ssh2(pktout, s->e);
pq_push(s->ppl.out_pq, pktout);
s->shgss->lib->free_tok(s->shgss->lib, &s->gss_sndtok);
ppl_logevent("GSSAPI key exchange initialised");
} else if (s->gss_sndtok.length != 0) {
pktout = ssh_bpp_new_pktout(
s->ppl.bpp, SSH2_MSG_KEXGSS_CONTINUE);
put_string(pktout,
s->gss_sndtok.value, s->gss_sndtok.length);
pq_push(s->ppl.out_pq, pktout);
s->shgss->lib->free_tok(s->shgss->lib, &s->gss_sndtok);
}
if (s->gss_stat == SSH_GSS_S_COMPLETE && s->complete_rcvd)
break;
wait_for_gss_token:
crMaybeWaitUntilV(
(pktin = ssh2_transport_pop(s)) != NULL);
switch (pktin->type) {
case SSH2_MSG_KEXGSS_CONTINUE:
data = get_string(pktin);
s->gss_rcvtok.value = (char *)data.ptr;
s->gss_rcvtok.length = data.len;
continue;
case SSH2_MSG_KEXGSS_COMPLETE:
s->complete_rcvd = true;
s->f = get_mp_ssh2(pktin);
data = get_string(pktin);
s->mic.value = (char *)data.ptr;
s->mic.length = data.len;
/* If there's a final token we loop to consume it */
if (get_bool(pktin)) {
data = get_string(pktin);
s->gss_rcvtok.value = (char *)data.ptr;
s->gss_rcvtok.length = data.len;
continue;
}
break;
case SSH2_MSG_KEXGSS_HOSTKEY:
s->hostkeydata = get_string(pktin);
if (s->hostkey_alg) {
s->hkey = ssh_key_new_pub(s->hostkey_alg,
s->hostkeydata);
put_stringpl(s->exhash, s->hostkeydata);
}
/*
* Can't loop as we have no token to pass to
* init_sec_context.
*/
goto wait_for_gss_token;
case SSH2_MSG_KEXGSS_ERROR:
/*
* We have no use for the server's major and minor
* status. The minor status is really only
* meaningful to the server, and with luck the major
* status means something to us (but not really all
* that much). The string is more meaningful, and
* hopefully the server sends any error tokens, as
* that will produce the most useful information for
* us.
*/
get_uint32(pktin); /* server's major status */
get_uint32(pktin); /* server's minor status */
data = get_string(pktin);
ppl_logevent("GSSAPI key exchange failed; "
"server's message: %.*s", PTRLEN_PRINTF(data));
/* Language tag, but we have no use for it */
get_string(pktin);
/*
* Wait for an error token, if there is one, or the
* server's disconnect. The error token, if there
* is one, must follow the SSH2_MSG_KEXGSS_ERROR
* message, per the RFC.
*/
goto wait_for_gss_token;
default:
ssh_proto_error(s->ppl.ssh, "Received unexpected packet "
"during GSSAPI key exchange, type %d (%s)",
pktin->type,
ssh2_pkt_type(s->ppl.bpp->pls->kctx,
s->ppl.bpp->pls->actx,
pktin->type));
*aborted = true;
return;
}
} while (s->gss_rcvtok.length ||
s->gss_stat == SSH_GSS_S_CONTINUE_NEEDED ||
!s->complete_rcvd);
{
const char *err = dh_validate_f(s->dh_ctx, s->f);
if (err) {
ssh_proto_error(s->ppl.ssh, "GSSAPI reply failed "
"validation: %s", err);
*aborted = true;
return;
}
}
s->K = dh_find_K(s->dh_ctx, s->f);
/* We assume everything from now on will be quick, and it might
* involve user interaction. */
seat_set_busy_status(s->ppl.seat, BUSY_NOT);
if (!s->hkey)
put_stringz(s->exhash, "");
if (dh_is_gex(s->kex_alg)) {
/* min, preferred, max */
put_uint32(s->exhash, s->pbits);
put_uint32(s->exhash, s->pbits);
put_uint32(s->exhash, s->pbits * 2);
put_mp_ssh2(s->exhash, s->p);
put_mp_ssh2(s->exhash, s->g);
}
put_mp_ssh2(s->exhash, s->e);
put_mp_ssh2(s->exhash, s->f);
/*
* MIC verification is done below, after we compute the hash
* used as the MIC input.
*/
dh_cleanup(s->dh_ctx);
s->dh_ctx = NULL;
mp_free(s->f); s->f = NULL;
if (dh_is_gex(s->kex_alg)) {
mp_free(s->g); s->g = NULL;
mp_free(s->p); s->p = NULL;
}
#endif
} else {
ptrlen rsakeydata;
assert(s->kex_alg->main_type == KEXTYPE_RSA);
ppl_logevent("Doing RSA key exchange with hash %s",
ssh_hash_alg(s->exhash)->text_name);
s->ppl.bpp->pls->kctx = SSH2_PKTCTX_RSAKEX;
/*
* RSA key exchange. First expect a KEXRSA_PUBKEY packet
* from the server.
*/
crMaybeWaitUntilV((pktin = ssh2_transport_pop(s)) != NULL);
if (pktin->type != SSH2_MSG_KEXRSA_PUBKEY) {
ssh_proto_error(s->ppl.ssh, "Received unexpected packet when "
"expecting RSA public key, type %d (%s)",
pktin->type,
ssh2_pkt_type(s->ppl.bpp->pls->kctx,
s->ppl.bpp->pls->actx,
pktin->type));
*aborted = true;
return;
}
s->hostkeydata = get_string(pktin);
put_stringpl(s->exhash, s->hostkeydata);
s->hkey = ssh_key_new_pub(s->hostkey_alg, s->hostkeydata);
rsakeydata = get_string(pktin);
s->rsa_kex_key = ssh_rsakex_newkey(rsakeydata);
if (!s->rsa_kex_key) {
ssh_proto_error(s->ppl.ssh,
"Unable to parse RSA public key packet");
*aborted = true;
return;
}
s->rsa_kex_key_needs_freeing = true;
put_stringpl(s->exhash, rsakeydata);
/*
* Next, set up a shared secret K, of precisely KLEN -
* 2*HLEN - 49 bits, where KLEN is the bit length of the
* RSA key modulus and HLEN is the bit length of the hash
* we're using.
*/
{
int klen = ssh_rsakex_klen(s->rsa_kex_key);
const struct ssh_rsa_kex_extra *extra =
(const struct ssh_rsa_kex_extra *)s->kex_alg->extra;
if (klen < extra->minklen) {
ssh_proto_error(s->ppl.ssh, "Server sent %d-bit RSA key, "
"less than the minimum size %d for %s "
"key exchange", klen, extra->minklen,
s->kex_alg->name);
*aborted = true;
return;
}
int nbits = klen - (2*s->kex_alg->hash->hlen*8 + 49);
assert(nbits > 0);
strbuf *buf, *outstr;
mp_int *tmp = mp_random_bits(nbits - 1);
s->K = mp_power_2(nbits - 1);
mp_add_into(s->K, s->K, tmp);
mp_free(tmp);
/*
* Encode this as an mpint.
*/
buf = strbuf_new_nm();
put_mp_ssh2(buf, s->K);
/*
* Encrypt it with the given RSA key.
*/
outstr = ssh_rsakex_encrypt(s->rsa_kex_key, s->kex_alg->hash,
ptrlen_from_strbuf(buf));
/*
* And send it off in a return packet.
*/
pktout = ssh_bpp_new_pktout(s->ppl.bpp, SSH2_MSG_KEXRSA_SECRET);
put_stringpl(pktout, ptrlen_from_strbuf(outstr));
pq_push(s->ppl.out_pq, pktout);
put_stringsb(s->exhash, outstr); /* frees outstr */
strbuf_free(buf);
}
ssh_rsakex_freekey(s->rsa_kex_key);
s->rsa_kex_key = NULL;
s->rsa_kex_key_needs_freeing = false;
crMaybeWaitUntilV((pktin = ssh2_transport_pop(s)) != NULL);
if (pktin->type != SSH2_MSG_KEXRSA_DONE) {
ssh_proto_error(s->ppl.ssh, "Received unexpected packet when "
"expecting RSA kex signature, type %d (%s)",
pktin->type,
ssh2_pkt_type(s->ppl.bpp->pls->kctx,
s->ppl.bpp->pls->actx,
pktin->type));
*aborted = true;
return;
}
s->sigdata = get_string(pktin);
if (get_err(pktin)) {
ssh_proto_error(s->ppl.ssh, "Unable to parse RSA kex signature");
*aborted = true;
return;
}
}
ssh2transport_finalise_exhash(s);
#ifndef NO_GSSAPI
if (s->kex_alg->main_type == KEXTYPE_GSS) {
Ssh_gss_buf gss_buf;
SSH_GSS_CLEAR_BUF(&s->gss_buf);
gss_buf.value = s->exchange_hash;
gss_buf.length = s->kex_alg->hash->hlen;
s->gss_stat = s->shgss->lib->verify_mic(
s->shgss->lib, s->shgss->ctx, &gss_buf, &s->mic);
if (s->gss_stat != SSH_GSS_OK) {
if (s->shgss->lib->display_status(
s->shgss->lib, s->shgss->ctx, &s->gss_buf) == SSH_GSS_OK) {
char *err = s->gss_buf.value;
ssh_sw_abort(s->ppl.ssh, "GSSAPI key exchange MIC was "
"not valid: %s", err);
sfree(err);
} else {
ssh_sw_abort(s->ppl.ssh, "GSSAPI key exchange MIC was "
"not valid");
}
*aborted = true;
return;
}
s->gss_kex_used = true;
/*-
* If this the first KEX, save the GSS context for "gssapi-keyex"
* authentication.
*
* http://tools.ietf.org/html/rfc4462#section-4
*
* This method may be used only if the initial key exchange was
* performed using a GSS-API-based key exchange method defined in
* accordance with Section 2. The GSS-API context used with this
* method is always that established during an initial GSS-API-based
* key exchange. Any context established during key exchange for the
* purpose of rekeying MUST NOT be used with this method.
*/
if (s->got_session_id) {
s->shgss->lib->release_cred(s->shgss->lib, &s->shgss->ctx);
}
ppl_logevent("GSSAPI Key Exchange complete!");
}
#endif
s->dh_ctx = NULL;
/* In GSS keyex there's no hostkey signature to verify */
if (s->kex_alg->main_type != KEXTYPE_GSS) {
if (!s->hkey) {
ssh_proto_error(s->ppl.ssh, "Server's host key is invalid");
*aborted = true;
return;
}
if (!ssh_key_verify(
s->hkey, s->sigdata,
make_ptrlen(s->exchange_hash, s->kex_alg->hash->hlen))) {
#ifndef FUZZING
ssh_proto_error(s->ppl.ssh, "Signature from server's host key "
"is invalid");
*aborted = true;
return;
#endif
}
}
s->keystr = (s->hkey ? ssh_key_cache_str(s->hkey) : NULL);
#ifndef NO_GSSAPI
if (s->gss_kex_used) {
/*
* In a GSS-based session, check the host key (if any) against
* the transient host key cache.
*/
if (s->kex_alg->main_type == KEXTYPE_GSS) {
/*
* We've just done a GSS key exchange. If it gave us a
* host key, store it.
*/
if (s->hkey) {
char *fingerprint = ssh2_fingerprint(
s->hkey, SSH_FPTYPE_DEFAULT);
ppl_logevent("GSS kex provided fallback host key:");
ppl_logevent("%s", fingerprint);
sfree(fingerprint);
ssh_transient_hostkey_cache_add(s->thc, s->hkey);
} else if (!ssh_transient_hostkey_cache_non_empty(s->thc)) {
/*
* But if it didn't, then we currently have no
* fallback host key to use in subsequent non-GSS
* rekeys. So we should immediately trigger a non-GSS
* rekey of our own, to set one up, before the session
* keys have been used for anything else.
*
* This is similar to the cross-certification done at
* user request in the permanent host key cache, but
* here we do it automatically, once, at session
* startup, and only add the key to the transient
* cache.
*/
if (s->hostkey_alg) {
s->need_gss_transient_hostkey = true;
} else {
/*
* If we negotiated the "null" host key algorithm
* in the key exchange, that's an indication that
* no host key at all is available from the server
* (both because we listed "null" last, and
* because RFC 4462 section 5 says that a server
* MUST NOT offer "null" as a host key algorithm
* unless that is the only algorithm it provides
* at all).
*
* In that case we actually _can't_ perform a
* non-GSSAPI key exchange, so it's pointless to
* attempt one proactively. This is also likely to
* cause trouble later if a rekey is required at a
* moment whne GSS credentials are not available,
* but someone setting up a server in this
* configuration presumably accepts that as a
* consequence.
*/
if (!s->warned_about_no_gss_transient_hostkey) {
ppl_logevent("No fallback host key available");
s->warned_about_no_gss_transient_hostkey = true;
}
}
}
} else {
/*
* We've just done a fallback key exchange, so make
* sure the host key it used is in the cache of keys
* we previously received in GSS kexes.
*
* An exception is if this was the non-GSS key exchange we
* triggered on purpose to populate the transient cache.
*/
assert(s->hkey); /* only KEXTYPE_GSS lets this be null */
char *fingerprint = ssh2_fingerprint(s->hkey, SSH_FPTYPE_DEFAULT);
if (s->need_gss_transient_hostkey) {
ppl_logevent("Post-GSS rekey provided fallback host key:");
ppl_logevent("%s", fingerprint);
ssh_transient_hostkey_cache_add(s->thc, s->hkey);
s->need_gss_transient_hostkey = false;
} else if (!ssh_transient_hostkey_cache_verify(s->thc, s->hkey)) {
ppl_logevent("Non-GSS rekey after initial GSS kex "
"used host key:");
ppl_logevent("%s", fingerprint);
sfree(fingerprint);
ssh_sw_abort(s->ppl.ssh, "Server's host key did not match any "
"used in previous GSS kex");
*aborted = true;
return;
}
sfree(fingerprint);
}
} else
#endif /* NO_GSSAPI */
if (!s->got_session_id) {
/*
* Make a note of any other host key formats that are available.
*/
{
int i, j, nkeys = 0;
char *list = NULL;
for (i = 0; i < lenof(ssh2_hostkey_algs); i++) {
if (ssh2_hostkey_algs[i].alg == s->hostkey_alg)
continue;
for (j = 0; j < s->n_uncert_hostkeys; j++)
if (s->uncert_hostkeys[j] == i)
break;
if (j < s->n_uncert_hostkeys) {
char *newlist;
if (list)
newlist = dupprintf(
"%s/%s", list,
ssh2_hostkey_algs[i].alg->ssh_id);
else
newlist = dupprintf(
"%s", ssh2_hostkey_algs[i].alg->ssh_id);
sfree(list);
list = newlist;
nkeys++;
}
}
if (list) {
ppl_logevent("Server also has %s host key%s, but we "
"don't know %s", list,
nkeys > 1 ? "s" : "",
nkeys > 1 ? "any of them" : "it");
sfree(list);
}
}
/*
* Authenticate remote host: verify host key. (We've already
* checked the signature of the exchange hash.)
*/
char **fingerprints = ssh2_all_fingerprints(s->hkey);
FingerprintType fptype_default =
ssh2_pick_default_fingerprint(fingerprints);
ppl_logevent("Host key fingerprint is:");
ppl_logevent("%s", fingerprints[fptype_default]);
/* First check against manually configured host keys. */
s->dlgret = verify_ssh_manual_host_key(
s->conf, fingerprints, s->hkey);
if (s->dlgret == 0) { /* did not match */
ssh2_free_all_fingerprints(fingerprints);
ssh_sw_abort(s->ppl.ssh, "Host key did not appear in manually "
"configured list");
*aborted = true;
return;
} else if (s->dlgret < 0) { /* none configured; use standard handling */
ssh2_userkey uk = { .key = s->hkey, .comment = NULL };
char *keydisp = ssh2_pubkey_openssh_str(&uk);
s->dlgret = seat_verify_ssh_host_key(
s->ppl.seat, s->savedhost, s->savedport,
ssh_key_cache_id(s->hkey), s->keystr, keydisp,
fingerprints, ssh2_transport_dialog_callback, s);
sfree(keydisp);
ssh2_free_all_fingerprints(fingerprints);
#ifdef FUZZING
s->dlgret = 1;
#endif
crMaybeWaitUntilV(s->dlgret >= 0);
if (s->dlgret == 0) {
ssh_user_close(s->ppl.ssh,
"User aborted at host key verification");
*aborted = true;
return;
}
}
/*
* Save this host key, to check against the one presented in
* subsequent rekeys.
*/
s->hostkey_str = s->keystr;
s->keystr = NULL;
} else if (s->cross_certifying) {
assert(s->hkey);
assert(ssh_key_alg(s->hkey) == s->cross_certifying);
char *fingerprint = ssh2_fingerprint(s->hkey, SSH_FPTYPE_DEFAULT);
ppl_logevent("Storing additional host key for this host:");
ppl_logevent("%s", fingerprint);
sfree(fingerprint);
store_host_key(s->savedhost, s->savedport,
ssh_key_cache_id(s->hkey), s->keystr);
/*
* Don't forget to store the new key as the one we'll be
* re-checking in future normal rekeys.
*/
s->hostkey_str = s->keystr;
s->keystr = NULL;
} else {
/*
* In a rekey, we never present an interactive host key
* verification request to the user. Instead, we simply
* enforce that the key we're seeing this time is identical to
* the one we saw before.
*/
assert(s->keystr); /* filled in by prior key exchange */
if (strcmp(s->hostkey_str, s->keystr)) {
#ifndef FUZZING
ssh_sw_abort(s->ppl.ssh,
"Host key was different in repeat key exchange");
*aborted = true;
return;
#endif
}
}
sfree(s->keystr);
s->keystr = NULL;
if (s->hkey) {
ssh_key_free(s->hkey);
s->hkey = NULL;
}
crFinishV;
}

330
ssh/kex2-server.c Normal file
View File

@ -0,0 +1,330 @@
/*
* Server side of key exchange for the SSH-2 transport protocol (RFC 4253).
*/
#include <assert.h>
#include "putty.h"
#include "ssh.h"
#include "bpp.h"
#include "ppl.h"
#include "sshcr.h"
#include "server.h"
#include "sshkeygen.h"
#include "storage.h"
#include "transport2.h"
#include "mpint.h"
void ssh2_transport_provide_hostkeys(PacketProtocolLayer *ppl,
ssh_key *const *hostkeys, int nhostkeys)
{
struct ssh2_transport_state *s =
container_of(ppl, struct ssh2_transport_state, ppl);
s->hostkeys = hostkeys;
s->nhostkeys = nhostkeys;
}
static strbuf *finalise_and_sign_exhash(struct ssh2_transport_state *s)
{
strbuf *sb;
ssh2transport_finalise_exhash(s);
sb = strbuf_new();
ssh_key_sign(
s->hkey, make_ptrlen(s->exchange_hash, s->kex_alg->hash->hlen),
s->hkflags, BinarySink_UPCAST(sb));
return sb;
}
void ssh2kex_coroutine(struct ssh2_transport_state *s, bool *aborted)
{
PacketProtocolLayer *ppl = &s->ppl; /* for ppl_logevent */
PktIn *pktin;
PktOut *pktout;
crBegin(s->crStateKex);
{
int i;
for (i = 0; i < s->nhostkeys; i++)
if (ssh_key_alg(s->hostkeys[i]) == s->hostkey_alg) {
s->hkey = s->hostkeys[i];
break;
}
assert(s->hkey);
}
strbuf_clear(s->hostkeyblob);
ssh_key_public_blob(s->hkey, BinarySink_UPCAST(s->hostkeyblob));
s->hostkeydata = ptrlen_from_strbuf(s->hostkeyblob);
put_stringpl(s->exhash, s->hostkeydata);
if (s->kex_alg->main_type == KEXTYPE_DH) {
/*
* If we're doing Diffie-Hellman group exchange, start by
* waiting for the group request.
*/
if (dh_is_gex(s->kex_alg)) {
ppl_logevent("Doing Diffie-Hellman group exchange");
s->ppl.bpp->pls->kctx = SSH2_PKTCTX_DHGEX;
crMaybeWaitUntilV((pktin = ssh2_transport_pop(s)) != NULL);
if (pktin->type != SSH2_MSG_KEX_DH_GEX_REQUEST &&
pktin->type != SSH2_MSG_KEX_DH_GEX_REQUEST_OLD) {
ssh_proto_error(s->ppl.ssh, "Received unexpected packet when "
"expecting Diffie-Hellman group exchange "
"request, type %d (%s)", pktin->type,
ssh2_pkt_type(s->ppl.bpp->pls->kctx,
s->ppl.bpp->pls->actx,
pktin->type));
*aborted = true;
return;
}
if (pktin->type != SSH2_MSG_KEX_DH_GEX_REQUEST_OLD) {
s->dh_got_size_bounds = true;
s->dh_min_size = get_uint32(pktin);
s->pbits = get_uint32(pktin);
s->dh_max_size = get_uint32(pktin);
} else {
s->dh_got_size_bounds = false;
s->pbits = get_uint32(pktin);
}
/*
* This is a hopeless strategy for making a secure DH
* group! It's good enough for testing a client against,
* but not for serious use.
*/
PrimeGenerationContext *pgc = primegen_new_context(
&primegen_probabilistic);
ProgressReceiver null_progress;
null_progress.vt = &null_progress_vt;
s->p = primegen_generate(pgc, pcs_new(s->pbits), &null_progress);
primegen_free_context(pgc);
s->g = mp_from_integer(2);
s->dh_ctx = dh_setup_gex(s->p, s->g);
s->kex_init_value = SSH2_MSG_KEX_DH_GEX_INIT;
s->kex_reply_value = SSH2_MSG_KEX_DH_GEX_REPLY;
pktout = ssh_bpp_new_pktout(s->ppl.bpp, SSH2_MSG_KEX_DH_GEX_GROUP);
put_mp_ssh2(pktout, s->p);
put_mp_ssh2(pktout, s->g);
pq_push(s->ppl.out_pq, pktout);
} else {
s->ppl.bpp->pls->kctx = SSH2_PKTCTX_DHGROUP;
s->dh_ctx = dh_setup_group(s->kex_alg);
s->kex_init_value = SSH2_MSG_KEXDH_INIT;
s->kex_reply_value = SSH2_MSG_KEXDH_REPLY;
ppl_logevent("Using Diffie-Hellman with standard group \"%s\"",
s->kex_alg->groupname);
}
ppl_logevent("Doing Diffie-Hellman key exchange with hash %s",
ssh_hash_alg(s->exhash)->text_name);
/*
* Generate e for Diffie-Hellman.
*/
s->e = dh_create_e(s->dh_ctx, s->nbits * 2);
/*
* Wait to receive f.
*/
crMaybeWaitUntilV((pktin = ssh2_transport_pop(s)) != NULL);
if (pktin->type != s->kex_init_value) {
ssh_proto_error(s->ppl.ssh, "Received unexpected packet when "
"expecting Diffie-Hellman initial packet, "
"type %d (%s)", pktin->type,
ssh2_pkt_type(s->ppl.bpp->pls->kctx,
s->ppl.bpp->pls->actx,
pktin->type));
*aborted = true;
return;
}
s->f = get_mp_ssh2(pktin);
if (get_err(pktin)) {
ssh_proto_error(s->ppl.ssh,
"Unable to parse Diffie-Hellman initial packet");
*aborted = true;
return;
}
{
const char *err = dh_validate_f(s->dh_ctx, s->f);
if (err) {
ssh_proto_error(s->ppl.ssh, "Diffie-Hellman initial packet "
"failed validation: %s", err);
*aborted = true;
return;
}
}
s->K = dh_find_K(s->dh_ctx, s->f);
if (dh_is_gex(s->kex_alg)) {
if (s->dh_got_size_bounds)
put_uint32(s->exhash, s->dh_min_size);
put_uint32(s->exhash, s->pbits);
if (s->dh_got_size_bounds)
put_uint32(s->exhash, s->dh_max_size);
put_mp_ssh2(s->exhash, s->p);
put_mp_ssh2(s->exhash, s->g);
}
put_mp_ssh2(s->exhash, s->f);
put_mp_ssh2(s->exhash, s->e);
pktout = ssh_bpp_new_pktout(s->ppl.bpp, s->kex_reply_value);
put_stringpl(pktout, s->hostkeydata);
put_mp_ssh2(pktout, s->e);
put_stringsb(pktout, finalise_and_sign_exhash(s));
pq_push(s->ppl.out_pq, pktout);
dh_cleanup(s->dh_ctx);
s->dh_ctx = NULL;
mp_free(s->f); s->f = NULL;
if (dh_is_gex(s->kex_alg)) {
mp_free(s->g); s->g = NULL;
mp_free(s->p); s->p = NULL;
}
} else if (s->kex_alg->main_type == KEXTYPE_ECDH) {
ppl_logevent("Doing ECDH key exchange with curve %s and hash %s",
ssh_ecdhkex_curve_textname(s->kex_alg),
ssh_hash_alg(s->exhash)->text_name);
s->ppl.bpp->pls->kctx = SSH2_PKTCTX_ECDHKEX;
s->ecdh_key = ssh_ecdhkex_newkey(s->kex_alg);
if (!s->ecdh_key) {
ssh_sw_abort(s->ppl.ssh, "Unable to generate key for ECDH");
*aborted = true;
return;
}
crMaybeWaitUntilV((pktin = ssh2_transport_pop(s)) != NULL);
if (pktin->type != SSH2_MSG_KEX_ECDH_INIT) {
ssh_proto_error(s->ppl.ssh, "Received unexpected packet when "
"expecting ECDH initial packet, type %d (%s)",
pktin->type,
ssh2_pkt_type(s->ppl.bpp->pls->kctx,
s->ppl.bpp->pls->actx,
pktin->type));
*aborted = true;
return;
}
{
ptrlen keydata = get_string(pktin);
put_stringpl(s->exhash, keydata);
s->K = ssh_ecdhkex_getkey(s->ecdh_key, keydata);
if (!get_err(pktin) && !s->K) {
ssh_proto_error(s->ppl.ssh, "Received invalid elliptic curve "
"point in ECDH initial packet");
*aborted = true;
return;
}
}
pktout = ssh_bpp_new_pktout(s->ppl.bpp, SSH2_MSG_KEX_ECDH_REPLY);
put_stringpl(pktout, s->hostkeydata);
{
strbuf *pubpoint = strbuf_new();
ssh_ecdhkex_getpublic(s->ecdh_key, BinarySink_UPCAST(pubpoint));
put_string(s->exhash, pubpoint->u, pubpoint->len);
put_stringsb(pktout, pubpoint);
}
put_stringsb(pktout, finalise_and_sign_exhash(s));
pq_push(s->ppl.out_pq, pktout);
ssh_ecdhkex_freekey(s->ecdh_key);
s->ecdh_key = NULL;
} else if (s->kex_alg->main_type == KEXTYPE_GSS) {
ssh_sw_abort(s->ppl.ssh, "GSS key exchange not supported in server");
} else {
assert(s->kex_alg->main_type == KEXTYPE_RSA);
ppl_logevent("Doing RSA key exchange with hash %s",
ssh_hash_alg(s->exhash)->text_name);
s->ppl.bpp->pls->kctx = SSH2_PKTCTX_RSAKEX;
const struct ssh_rsa_kex_extra *extra =
(const struct ssh_rsa_kex_extra *)s->kex_alg->extra;
if (s->ssc && s->ssc->rsa_kex_key) {
int klen = ssh_rsakex_klen(s->ssc->rsa_kex_key);
if (klen >= extra->minklen) {
ppl_logevent("Using configured %d-bit RSA key", klen);
s->rsa_kex_key = s->ssc->rsa_kex_key;
} else {
ppl_logevent("Configured %d-bit RSA key is too short (min %d)",
klen, extra->minklen);
}
}
if (!s->rsa_kex_key) {
ppl_logevent("Generating a %d-bit RSA key", extra->minklen);
s->rsa_kex_key = snew(RSAKey);
PrimeGenerationContext *pgc = primegen_new_context(
&primegen_probabilistic);
ProgressReceiver null_progress;
null_progress.vt = &null_progress_vt;
rsa_generate(s->rsa_kex_key, extra->minklen, false,
pgc, &null_progress);
primegen_free_context(pgc);
s->rsa_kex_key->comment = NULL;
s->rsa_kex_key_needs_freeing = true;
}
pktout = ssh_bpp_new_pktout(s->ppl.bpp, SSH2_MSG_KEXRSA_PUBKEY);
put_stringpl(pktout, s->hostkeydata);
{
strbuf *pubblob = strbuf_new();
ssh_key_public_blob(&s->rsa_kex_key->sshk,
BinarySink_UPCAST(pubblob));
put_string(s->exhash, pubblob->u, pubblob->len);
put_stringsb(pktout, pubblob);
}
pq_push(s->ppl.out_pq, pktout);
crMaybeWaitUntilV((pktin = ssh2_transport_pop(s)) != NULL);
if (pktin->type != SSH2_MSG_KEXRSA_SECRET) {
ssh_proto_error(s->ppl.ssh, "Received unexpected packet when "
"expecting RSA kex secret, type %d (%s)",
pktin->type,
ssh2_pkt_type(s->ppl.bpp->pls->kctx,
s->ppl.bpp->pls->actx,
pktin->type));
*aborted = true;
return;
}
{
ptrlen encrypted_secret = get_string(pktin);
put_stringpl(s->exhash, encrypted_secret);
s->K = ssh_rsakex_decrypt(
s->rsa_kex_key, s->kex_alg->hash, encrypted_secret);
}
if (!s->K) {
ssh_proto_error(s->ppl.ssh, "Unable to decrypt RSA kex secret");
*aborted = true;
return;
}
if (s->rsa_kex_key_needs_freeing) {
ssh_rsakex_freekey(s->rsa_kex_key);
sfree(s->rsa_kex_key);
}
s->rsa_kex_key = NULL;
s->rsa_kex_key_needs_freeing = false;
pktout = ssh_bpp_new_pktout(s->ppl.bpp, SSH2_MSG_KEXRSA_DONE);
put_stringsb(pktout, finalise_and_sign_exhash(s));
pq_push(s->ppl.out_pq, pktout);
}
crFinishV;
}

447
ssh/login1-server.c Normal file
View File

@ -0,0 +1,447 @@
/*
* Packet protocol layer for the SSH-1 login phase, from the server side.
*/
#include <assert.h>
#include "putty.h"
#include "mpint.h"
#include "ssh.h"
#include "bpp.h"
#include "ppl.h"
#include "sshcr.h"
#include "server.h"
#include "sshkeygen.h"
struct ssh1_login_server_state {
int crState;
PacketProtocolLayer *successor_layer;
const SshServerConfig *ssc;
int remote_protoflags;
int local_protoflags;
unsigned long supported_ciphers_mask, supported_auths_mask;
unsigned cipher_type;
unsigned char cookie[8];
unsigned char session_key[32];
unsigned char session_id[16];
char *username_str;
ptrlen username;
RSAKey *servkey, *hostkey;
bool servkey_generated_here;
mp_int *sesskey;
AuthPolicy *authpolicy;
unsigned ap_methods, current_method;
unsigned char auth_rsa_expected_response[16];
RSAKey *authkey;
bool auth_successful;
PacketProtocolLayer ppl;
};
static void ssh1_login_server_free(PacketProtocolLayer *);
static void ssh1_login_server_process_queue(PacketProtocolLayer *);
static bool ssh1_login_server_get_specials(
PacketProtocolLayer *ppl, add_special_fn_t add_special,
void *ctx) { return false; }
static void ssh1_login_server_special_cmd(PacketProtocolLayer *ppl,
SessionSpecialCode code, int arg) {}
static bool ssh1_login_server_want_user_input(
PacketProtocolLayer *ppl) { return false; }
static void ssh1_login_server_got_user_input(PacketProtocolLayer *ppl) {}
static void ssh1_login_server_reconfigure(
PacketProtocolLayer *ppl, Conf *conf) {}
static const PacketProtocolLayerVtable ssh1_login_server_vtable = {
.free = ssh1_login_server_free,
.process_queue = ssh1_login_server_process_queue,
.get_specials = ssh1_login_server_get_specials,
.special_cmd = ssh1_login_server_special_cmd,
.want_user_input = ssh1_login_server_want_user_input,
.got_user_input = ssh1_login_server_got_user_input,
.reconfigure = ssh1_login_server_reconfigure,
.queued_data_size = ssh_ppl_default_queued_data_size,
.name = NULL, /* no layer names in SSH-1 */
};
PacketProtocolLayer *ssh1_login_server_new(
PacketProtocolLayer *successor_layer, RSAKey *hostkey,
AuthPolicy *authpolicy, const SshServerConfig *ssc)
{
struct ssh1_login_server_state *s = snew(struct ssh1_login_server_state);
memset(s, 0, sizeof(*s));
s->ppl.vt = &ssh1_login_server_vtable;
s->ssc = ssc;
s->hostkey = hostkey;
s->authpolicy = authpolicy;
s->successor_layer = successor_layer;
return &s->ppl;
}
static void ssh1_login_server_free(PacketProtocolLayer *ppl)
{
struct ssh1_login_server_state *s =
container_of(ppl, struct ssh1_login_server_state, ppl);
if (s->successor_layer)
ssh_ppl_free(s->successor_layer);
if (s->servkey_generated_here && s->servkey) {
freersakey(s->servkey);
sfree(s->servkey);
}
smemclr(s->session_key, sizeof(s->session_key));
sfree(s->username_str);
sfree(s);
}
static bool ssh1_login_server_filter_queue(struct ssh1_login_server_state *s)
{
return ssh1_common_filter_queue(&s->ppl);
}
static PktIn *ssh1_login_server_pop(struct ssh1_login_server_state *s)
{
if (ssh1_login_server_filter_queue(s))
return NULL;
return pq_pop(s->ppl.in_pq);
}
static void ssh1_login_server_process_queue(PacketProtocolLayer *ppl)
{
struct ssh1_login_server_state *s =
container_of(ppl, struct ssh1_login_server_state, ppl);
PktIn *pktin;
PktOut *pktout;
int i;
/* Filter centrally handled messages off the front of the queue on
* every entry to this coroutine, no matter where we're resuming
* from, even if we're _not_ looping on pq_pop. That way we can
* still proactively handle those messages even if we're waiting
* for a user response. */
if (ssh1_login_server_filter_queue(s))
return;
crBegin(s->crState);
if (!s->servkey) {
int server_key_bits = s->hostkey->bytes - 256;
if (server_key_bits < 512)
server_key_bits = s->hostkey->bytes + 256;
s->servkey = snew(RSAKey);
PrimeGenerationContext *pgc = primegen_new_context(
&primegen_probabilistic);
ProgressReceiver null_progress;
null_progress.vt = &null_progress_vt;
rsa_generate(s->servkey, server_key_bits, false, pgc, &null_progress);
primegen_free_context(pgc);
s->servkey->comment = NULL;
s->servkey_generated_here = true;
}
s->local_protoflags = SSH1_PROTOFLAGS_SUPPORTED;
s->supported_ciphers_mask = s->ssc->ssh1_cipher_mask;
s->supported_auths_mask = 0;
s->ap_methods = auth_methods(s->authpolicy);
if (s->ap_methods & AUTHMETHOD_PASSWORD)
s->supported_auths_mask |= (1U << SSH1_AUTH_PASSWORD);
if (s->ap_methods & AUTHMETHOD_PUBLICKEY)
s->supported_auths_mask |= (1U << SSH1_AUTH_RSA);
if (s->ap_methods & AUTHMETHOD_TIS)
s->supported_auths_mask |= (1U << SSH1_AUTH_TIS);
if (s->ap_methods & AUTHMETHOD_CRYPTOCARD)
s->supported_auths_mask |= (1U << SSH1_AUTH_CCARD);
random_read(s->cookie, 8);
pktout = ssh_bpp_new_pktout(s->ppl.bpp, SSH1_SMSG_PUBLIC_KEY);
put_data(pktout, s->cookie, 8);
rsa_ssh1_public_blob(BinarySink_UPCAST(pktout),
s->servkey, RSA_SSH1_EXPONENT_FIRST);
rsa_ssh1_public_blob(BinarySink_UPCAST(pktout),
s->hostkey, RSA_SSH1_EXPONENT_FIRST);
put_uint32(pktout, s->local_protoflags);
put_uint32(pktout, s->supported_ciphers_mask);
put_uint32(pktout, s->supported_auths_mask);
pq_push(s->ppl.out_pq, pktout);
crMaybeWaitUntilV((pktin = ssh1_login_server_pop(s)) != NULL);
if (pktin->type != SSH1_CMSG_SESSION_KEY) {
ssh_proto_error(s->ppl.ssh, "Received unexpected packet in response"
" to initial public key packet, type %d (%s)",
pktin->type, ssh1_pkt_type(pktin->type));
return;
}
{
ptrlen client_cookie;
s->cipher_type = get_byte(pktin);
client_cookie = get_data(pktin, 8);
s->sesskey = get_mp_ssh1(pktin);
s->remote_protoflags = get_uint32(pktin);
if (get_err(pktin)) {
ssh_proto_error(s->ppl.ssh, "Unable to parse session key packet");
return;
}
if (!ptrlen_eq_ptrlen(client_cookie, make_ptrlen(s->cookie, 8))) {
ssh_proto_error(s->ppl.ssh,
"Client sent incorrect anti-spoofing cookie");
return;
}
}
if (s->cipher_type >= 32 ||
!((s->supported_ciphers_mask >> s->cipher_type) & 1)) {
ssh_proto_error(s->ppl.ssh, "Client selected an unsupported cipher");
return;
}
{
RSAKey *smaller, *larger;
strbuf *data = strbuf_new_nm();
if (mp_get_nbits(s->hostkey->modulus) >
mp_get_nbits(s->servkey->modulus)) {
larger = s->hostkey;
smaller = s->servkey;
} else {
smaller = s->hostkey;
larger = s->servkey;
}
if (rsa_ssh1_decrypt_pkcs1(s->sesskey, larger, data)) {
mp_free(s->sesskey);
s->sesskey = mp_from_bytes_be(ptrlen_from_strbuf(data));
strbuf_clear(data);
if (rsa_ssh1_decrypt_pkcs1(s->sesskey, smaller, data) &&
data->len == sizeof(s->session_key)) {
memcpy(s->session_key, data->u, sizeof(s->session_key));
mp_free(s->sesskey);
s->sesskey = NULL; /* indicates success */
}
}
strbuf_free(data);
}
if (s->sesskey) {
ssh_proto_error(s->ppl.ssh, "Failed to decrypt session key");
return;
}
ssh1_compute_session_id(s->session_id, s->cookie, s->hostkey, s->servkey);
for (i = 0; i < 16; i++)
s->session_key[i] ^= s->session_id[i];
{
const ssh_cipheralg *cipher =
(s->cipher_type == SSH1_CIPHER_BLOWFISH ? &ssh_blowfish_ssh1 :
s->cipher_type == SSH1_CIPHER_DES ? &ssh_des : &ssh_3des_ssh1);
ssh1_bpp_new_cipher(s->ppl.bpp, cipher, s->session_key);
}
pktout = ssh_bpp_new_pktout(s->ppl.bpp, SSH1_SMSG_SUCCESS);
pq_push(s->ppl.out_pq, pktout);
crMaybeWaitUntilV((pktin = ssh1_login_server_pop(s)) != NULL);
if (pktin->type != SSH1_CMSG_USER) {
ssh_proto_error(s->ppl.ssh, "Received unexpected packet while "
"expecting username, type %d (%s)",
pktin->type, ssh1_pkt_type(pktin->type));
return;
}
s->username = get_string(pktin);
s->username.ptr = s->username_str = mkstr(s->username);
ppl_logevent("Received username '%.*s'", PTRLEN_PRINTF(s->username));
s->auth_successful = auth_none(s->authpolicy, s->username);
while (1) {
/* Signal failed authentication */
pktout = ssh_bpp_new_pktout(s->ppl.bpp, SSH1_SMSG_FAILURE);
pq_push(s->ppl.out_pq, pktout);
crMaybeWaitUntilV((pktin = ssh1_login_server_pop(s)) != NULL);
if (pktin->type == SSH1_CMSG_AUTH_PASSWORD) {
s->current_method = AUTHMETHOD_PASSWORD;
if (!(s->ap_methods & s->current_method))
continue;
ptrlen password = get_string(pktin);
/* Tolerate historic traffic-analysis defence of NUL +
* garbage on the end of the binary password string */
char *nul = memchr(password.ptr, '\0', password.len);
if (nul)
password.len = (const char *)nul - (const char *)password.ptr;
if (auth_password(s->authpolicy, s->username, password, NULL))
goto auth_success;
} else if (pktin->type == SSH1_CMSG_AUTH_RSA) {
s->current_method = AUTHMETHOD_PUBLICKEY;
if (!(s->ap_methods & s->current_method))
continue;
{
mp_int *modulus = get_mp_ssh1(pktin);
s->authkey = auth_publickey_ssh1(
s->authpolicy, s->username, modulus);
if (!s->authkey &&
s->ssc->stunt_pretend_to_accept_any_pubkey) {
mp_int *zero = mp_from_integer(0);
mp_int *fake_challenge = mp_random_in_range(zero, modulus);
pktout = ssh_bpp_new_pktout(
s->ppl.bpp, SSH1_SMSG_AUTH_RSA_CHALLENGE);
put_mp_ssh1(pktout, fake_challenge);
pq_push(s->ppl.out_pq, pktout);
mp_free(zero);
mp_free(fake_challenge);
}
mp_free(modulus);
}
if (!s->authkey &&
!s->ssc->stunt_pretend_to_accept_any_pubkey)
continue;
if (s->authkey && s->authkey->bytes < 32) {
ppl_logevent("Auth key far too small");
continue;
}
if (s->authkey) {
unsigned char *rsabuf =
snewn(s->authkey->bytes, unsigned char);
random_read(rsabuf, 32);
{
ssh_hash *h = ssh_hash_new(&ssh_md5);
put_data(h, rsabuf, 32);
put_data(h, s->session_id, 16);
ssh_hash_final(h, s->auth_rsa_expected_response);
}
if (!rsa_ssh1_encrypt(rsabuf, 32, s->authkey)) {
sfree(rsabuf);
ppl_logevent("Failed to encrypt auth challenge");
continue;
}
mp_int *bn = mp_from_bytes_be(
make_ptrlen(rsabuf, s->authkey->bytes));
smemclr(rsabuf, s->authkey->bytes);
sfree(rsabuf);
pktout = ssh_bpp_new_pktout(
s->ppl.bpp, SSH1_SMSG_AUTH_RSA_CHALLENGE);
put_mp_ssh1(pktout, bn);
pq_push(s->ppl.out_pq, pktout);
mp_free(bn);
}
crMaybeWaitUntilV((pktin = ssh1_login_server_pop(s)) != NULL);
if (pktin->type != SSH1_CMSG_AUTH_RSA_RESPONSE) {
ssh_proto_error(s->ppl.ssh, "Received unexpected packet in "
"response to RSA auth challenge, type %d (%s)",
pktin->type, ssh1_pkt_type(pktin->type));
return;
}
if (!s->authkey)
continue;
{
ptrlen response = get_data(pktin, 16);
ptrlen expected = make_ptrlen(
s->auth_rsa_expected_response, 16);
if (!ptrlen_eq_ptrlen(response, expected)) {
ppl_logevent("Wrong response to auth challenge");
continue;
}
}
goto auth_success;
} else if (pktin->type == SSH1_CMSG_AUTH_TIS ||
pktin->type == SSH1_CMSG_AUTH_CCARD) {
char *challenge;
unsigned response_type;
ptrlen response;
s->current_method = (pktin->type == SSH1_CMSG_AUTH_TIS ?
AUTHMETHOD_TIS : AUTHMETHOD_CRYPTOCARD);
if (!(s->ap_methods & s->current_method))
continue;
challenge = auth_ssh1int_challenge(
s->authpolicy, s->current_method, s->username);
if (!challenge)
continue;
pktout = ssh_bpp_new_pktout(
s->ppl.bpp,
(s->current_method == AUTHMETHOD_TIS ?
SSH1_SMSG_AUTH_TIS_CHALLENGE :
SSH1_SMSG_AUTH_CCARD_CHALLENGE));
put_stringz(pktout, challenge);
pq_push(s->ppl.out_pq, pktout);
sfree(challenge);
crMaybeWaitUntilV((pktin = ssh1_login_server_pop(s)) != NULL);
response_type = (s->current_method == AUTHMETHOD_TIS ?
SSH1_CMSG_AUTH_TIS_RESPONSE :
SSH1_CMSG_AUTH_CCARD_RESPONSE);
if (pktin->type != response_type) {
ssh_proto_error(s->ppl.ssh, "Received unexpected packet in "
"response to %s challenge, type %d (%s)",
(s->current_method == AUTHMETHOD_TIS ?
"TIS" : "CryptoCard"),
pktin->type, ssh1_pkt_type(pktin->type));
return;
}
response = get_string(pktin);
if (auth_ssh1int_response(s->authpolicy, response))
goto auth_success;
}
}
auth_success:
if (!auth_successful(s->authpolicy, s->username, s->current_method)) {
ssh_sw_abort(s->ppl.ssh, "Multiple authentications required but SSH-1"
" cannot perform them");
return;
}
/* Signal successful authentication */
pktout = ssh_bpp_new_pktout(s->ppl.bpp, SSH1_SMSG_SUCCESS);
pq_push(s->ppl.out_pq, pktout);
ssh1_connection_set_protoflags(
s->successor_layer, s->local_protoflags, s->remote_protoflags);
{
PacketProtocolLayer *successor = s->successor_layer;
s->successor_layer = NULL; /* avoid freeing it ourself */
ssh_ppl_replace(&s->ppl, successor);
return; /* we've just freed s, so avoid even touching s->crState */
}
crFinishV;
}

1242
ssh/login1.c Normal file

File diff suppressed because it is too large Load Diff

538
ssh/mainchan.c Normal file
View File

@ -0,0 +1,538 @@
/*
* SSH main session channel handling.
*/
#include <assert.h>
#include <stdio.h>
#include <stdlib.h>
#include "putty.h"
#include "ssh.h"
#include "ppl.h"
#include "channel.h"
static void mainchan_free(Channel *chan);
static void mainchan_open_confirmation(Channel *chan);
static void mainchan_open_failure(Channel *chan, const char *errtext);
static size_t mainchan_send(
Channel *chan, bool is_stderr, const void *, size_t);
static void mainchan_send_eof(Channel *chan);
static void mainchan_set_input_wanted(Channel *chan, bool wanted);
static char *mainchan_log_close_msg(Channel *chan);
static bool mainchan_rcvd_exit_status(Channel *chan, int status);
static bool mainchan_rcvd_exit_signal(
Channel *chan, ptrlen signame, bool core_dumped, ptrlen msg);
static bool mainchan_rcvd_exit_signal_numeric(
Channel *chan, int signum, bool core_dumped, ptrlen msg);
static void mainchan_request_response(Channel *chan, bool success);
static const ChannelVtable mainchan_channelvt = {
.free = mainchan_free,
.open_confirmation = mainchan_open_confirmation,
.open_failed = mainchan_open_failure,
.send = mainchan_send,
.send_eof = mainchan_send_eof,
.set_input_wanted = mainchan_set_input_wanted,
.log_close_msg = mainchan_log_close_msg,
.want_close = chan_default_want_close,
.rcvd_exit_status = mainchan_rcvd_exit_status,
.rcvd_exit_signal = mainchan_rcvd_exit_signal,
.rcvd_exit_signal_numeric = mainchan_rcvd_exit_signal_numeric,
.run_shell = chan_no_run_shell,
.run_command = chan_no_run_command,
.run_subsystem = chan_no_run_subsystem,
.enable_x11_forwarding = chan_no_enable_x11_forwarding,
.enable_agent_forwarding = chan_no_enable_agent_forwarding,
.allocate_pty = chan_no_allocate_pty,
.set_env = chan_no_set_env,
.send_break = chan_no_send_break,
.send_signal = chan_no_send_signal,
.change_window_size = chan_no_change_window_size,
.request_response = mainchan_request_response,
};
typedef enum MainChanType {
MAINCHAN_SESSION, MAINCHAN_DIRECT_TCPIP
} MainChanType;
struct mainchan {
SshChannel *sc;
Conf *conf;
PacketProtocolLayer *ppl;
ConnectionLayer *cl;
MainChanType type;
bool is_simple;
bool req_x11, req_agent, req_pty, req_cmd_primary, req_cmd_fallback;
int n_req_env, n_env_replies, n_env_fails;
bool eof_pending, eof_sent, got_pty, ready;
int term_width, term_height;
Channel chan;
};
mainchan *mainchan_new(
PacketProtocolLayer *ppl, ConnectionLayer *cl, Conf *conf,
int term_width, int term_height, bool is_simple, SshChannel **sc_out)
{
mainchan *mc;
if (conf_get_bool(conf, CONF_ssh_no_shell))
return NULL; /* no main channel at all */
mc = snew(mainchan);
memset(mc, 0, sizeof(mainchan));
mc->ppl = ppl;
mc->cl = cl;
mc->conf = conf_copy(conf);
mc->term_width = term_width;
mc->term_height = term_height;
mc->is_simple = is_simple;
mc->sc = NULL;
mc->chan.vt = &mainchan_channelvt;
mc->chan.initial_fixed_window_size = 0;
if (*conf_get_str(mc->conf, CONF_ssh_nc_host)) {
const char *host = conf_get_str(mc->conf, CONF_ssh_nc_host);
int port = conf_get_int(mc->conf, CONF_ssh_nc_port);
mc->sc = ssh_lportfwd_open(cl, host, port, "main channel",
NULL, &mc->chan);
mc->type = MAINCHAN_DIRECT_TCPIP;
} else {
mc->sc = ssh_session_open(cl, &mc->chan);
mc->type = MAINCHAN_SESSION;
}
if (sc_out) *sc_out = mc->sc;
return mc;
}
static void mainchan_free(Channel *chan)
{
assert(chan->vt == &mainchan_channelvt);
mainchan *mc = container_of(chan, mainchan, chan);
conf_free(mc->conf);
sfree(mc);
}
static void mainchan_try_fallback_command(mainchan *mc);
static void mainchan_ready(mainchan *mc);
static void mainchan_open_confirmation(Channel *chan)
{
mainchan *mc = container_of(chan, mainchan, chan);
PacketProtocolLayer *ppl = mc->ppl; /* for ppl_logevent */
seat_update_specials_menu(mc->ppl->seat);
ppl_logevent("Opened main channel");
if (mc->is_simple)
sshfwd_hint_channel_is_simple(mc->sc);
if (mc->type == MAINCHAN_SESSION) {
/*
* Send the CHANNEL_REQUESTS for the main session channel.
*/
char *key, *val, *cmd;
struct X11Display *x11disp;
struct X11FakeAuth *x11auth;
bool retry_cmd_now = false;
if (conf_get_bool(mc->conf, CONF_x11_forward)) {
char *x11_setup_err;
if ((x11disp = x11_setup_display(
conf_get_str(mc->conf, CONF_x11_display),
mc->conf, &x11_setup_err)) == NULL) {
ppl_logevent("X11 forwarding not enabled: unable to"
" initialise X display: %s", x11_setup_err);
sfree(x11_setup_err);
} else {
x11auth = ssh_add_x11_display(
mc->cl, conf_get_int(mc->conf, CONF_x11_auth), x11disp);
sshfwd_request_x11_forwarding(
mc->sc, true, x11auth->protoname, x11auth->datastring,
x11disp->screennum, false);
mc->req_x11 = true;
}
}
if (ssh_agent_forwarding_permitted(mc->cl)) {
sshfwd_request_agent_forwarding(mc->sc, true);
mc->req_agent = true;
}
if (!conf_get_bool(mc->conf, CONF_nopty)) {
sshfwd_request_pty(
mc->sc, true, mc->conf, mc->term_width, mc->term_height);
mc->req_pty = true;
}
for (val = conf_get_str_strs(mc->conf, CONF_environmt, NULL, &key);
val != NULL;
val = conf_get_str_strs(mc->conf, CONF_environmt, key, &key)) {
sshfwd_send_env_var(mc->sc, true, key, val);
mc->n_req_env++;
}
if (mc->n_req_env)
ppl_logevent("Sent %d environment variables", mc->n_req_env);
cmd = conf_get_str(mc->conf, CONF_remote_cmd);
if (conf_get_bool(mc->conf, CONF_ssh_subsys)) {
retry_cmd_now = !sshfwd_start_subsystem(mc->sc, true, cmd);
} else if (*cmd) {
sshfwd_start_command(mc->sc, true, cmd);
} else {
sshfwd_start_shell(mc->sc, true);
}
if (retry_cmd_now)
mainchan_try_fallback_command(mc);
else
mc->req_cmd_primary = true;
} else {
ssh_set_ldisc_option(mc->cl, LD_ECHO, true);
ssh_set_ldisc_option(mc->cl, LD_EDIT, true);
mainchan_ready(mc);
}
}
static void mainchan_try_fallback_command(mainchan *mc)
{
const char *cmd = conf_get_str(mc->conf, CONF_remote_cmd2);
if (conf_get_bool(mc->conf, CONF_ssh_subsys2)) {
sshfwd_start_subsystem(mc->sc, true, cmd);
} else {
sshfwd_start_command(mc->sc, true, cmd);
}
mc->req_cmd_fallback = true;
}
static void mainchan_request_response(Channel *chan, bool success)
{
assert(chan->vt == &mainchan_channelvt);
mainchan *mc = container_of(chan, mainchan, chan);
PacketProtocolLayer *ppl = mc->ppl; /* for ppl_logevent */
if (mc->req_x11) {
mc->req_x11 = false;
if (success) {
ppl_logevent("X11 forwarding enabled");
ssh_enable_x_fwd(mc->cl);
} else {
ppl_logevent("X11 forwarding refused");
}
return;
}
if (mc->req_agent) {
mc->req_agent = false;
if (success) {
ppl_logevent("Agent forwarding enabled");
} else {
ppl_logevent("Agent forwarding refused");
}
return;
}
if (mc->req_pty) {
mc->req_pty = false;
if (success) {
ppl_logevent("Allocated pty");
mc->got_pty = true;
} else {
ppl_logevent("Server refused to allocate pty");
ppl_printf("Server refused to allocate pty\r\n");
ssh_set_ldisc_option(mc->cl, LD_ECHO, true);
ssh_set_ldisc_option(mc->cl, LD_EDIT, true);
}
return;
}
if (mc->n_env_replies < mc->n_req_env) {
int j = mc->n_env_replies++;
if (!success) {
ppl_logevent("Server refused to set environment variable %s",
conf_get_str_nthstrkey(mc->conf,
CONF_environmt, j));
mc->n_env_fails++;
}
if (mc->n_env_replies == mc->n_req_env) {
if (mc->n_env_fails == 0) {
ppl_logevent("All environment variables successfully set");
} else if (mc->n_env_fails == mc->n_req_env) {
ppl_logevent("All environment variables refused");
ppl_printf("Server refused to set environment "
"variables\r\n");
} else {
ppl_printf("Server refused to set all environment "
"variables\r\n");
}
}
return;
}
if (mc->req_cmd_primary) {
mc->req_cmd_primary = false;
if (success) {
ppl_logevent("Started a shell/command");
mainchan_ready(mc);
} else if (*conf_get_str(mc->conf, CONF_remote_cmd2)) {
ppl_logevent("Primary command failed; attempting fallback");
mainchan_try_fallback_command(mc);
} else {
/*
* If there's no remote_cmd2 configured, then we have no
* fallback command, so we've run out of options.
*/
ssh_sw_abort(mc->ppl->ssh,
"Server refused to start a shell/command");
}
return;
}
if (mc->req_cmd_fallback) {
mc->req_cmd_fallback = false;
if (success) {
ppl_logevent("Started a shell/command");
ssh_got_fallback_cmd(mc->ppl->ssh);
mainchan_ready(mc);
} else {
ssh_sw_abort(mc->ppl->ssh,
"Server refused to start a shell/command");
}
return;
}
}
static void mainchan_ready(mainchan *mc)
{
mc->ready = true;
ssh_set_wants_user_input(mc->cl, true);
ssh_ppl_got_user_input(mc->ppl); /* in case any is already queued */
/* If an EOF arrived before we were ready, handle it now. */
if (mc->eof_pending) {
mc->eof_pending = false;
mainchan_special_cmd(mc, SS_EOF, 0);
}
ssh_ldisc_update(mc->ppl->ssh);
queue_idempotent_callback(&mc->ppl->ic_process_queue);
}
static void mainchan_open_failure(Channel *chan, const char *errtext)
{
assert(chan->vt == &mainchan_channelvt);
mainchan *mc = container_of(chan, mainchan, chan);
ssh_sw_abort_deferred(mc->ppl->ssh,
"Server refused to open main channel: %s", errtext);
}
static size_t mainchan_send(Channel *chan, bool is_stderr,
const void *data, size_t length)
{
assert(chan->vt == &mainchan_channelvt);
mainchan *mc = container_of(chan, mainchan, chan);
return seat_output(mc->ppl->seat, is_stderr, data, length);
}
static void mainchan_send_eof(Channel *chan)
{
assert(chan->vt == &mainchan_channelvt);
mainchan *mc = container_of(chan, mainchan, chan);
PacketProtocolLayer *ppl = mc->ppl; /* for ppl_logevent */
if (!mc->eof_sent && (seat_eof(mc->ppl->seat) || mc->got_pty)) {
/*
* Either seat_eof told us that the front end wants us to
* close the outgoing side of the connection as soon as we see
* EOF from the far end, or else we've unilaterally decided to
* do that because we've allocated a remote pty and hence EOF
* isn't a particularly meaningful concept.
*/
sshfwd_write_eof(mc->sc);
ppl_logevent("Sent EOF message");
mc->eof_sent = true;
ssh_set_wants_user_input(mc->cl, false); /* stop reading from stdin */
}
}
static void mainchan_set_input_wanted(Channel *chan, bool wanted)
{
assert(chan->vt == &mainchan_channelvt);
mainchan *mc = container_of(chan, mainchan, chan);
/*
* This is the main channel of the SSH session, i.e. the one tied
* to the standard input (or GUI) of the primary SSH client user
* interface. So ssh->send_ok is how we control whether we're
* reading from that input.
*/
ssh_set_wants_user_input(mc->cl, wanted);
}
static char *mainchan_log_close_msg(Channel *chan)
{
return dupstr("Main session channel closed");
}
static bool mainchan_rcvd_exit_status(Channel *chan, int status)
{
assert(chan->vt == &mainchan_channelvt);
mainchan *mc = container_of(chan, mainchan, chan);
PacketProtocolLayer *ppl = mc->ppl; /* for ppl_logevent */
ssh_got_exitcode(mc->ppl->ssh, status);
ppl_logevent("Session sent command exit status %d", status);
return true;
}
static void mainchan_log_exit_signal_common(
mainchan *mc, const char *sigdesc,
bool core_dumped, ptrlen msg)
{
PacketProtocolLayer *ppl = mc->ppl; /* for ppl_logevent */
const char *core_msg = core_dumped ? " (core dumped)" : "";
const char *msg_pre = (msg.len ? " (" : "");
const char *msg_post = (msg.len ? ")" : "");
ppl_logevent("Session exited on %s%s%s%.*s%s",
sigdesc, core_msg, msg_pre, PTRLEN_PRINTF(msg), msg_post);
}
static bool mainchan_rcvd_exit_signal(
Channel *chan, ptrlen signame, bool core_dumped, ptrlen msg)
{
assert(chan->vt == &mainchan_channelvt);
mainchan *mc = container_of(chan, mainchan, chan);
int exitcode;
char *signame_str;
/*
* Translate the signal description back into a locally meaningful
* number, or 128 if the string didn't match any we recognise.
*/
exitcode = 128;
#define SIGNAL_SUB(s) \
if (ptrlen_eq_string(signame, #s)) \
exitcode = 128 + SIG ## s;
#define SIGNAL_MAIN(s, text) SIGNAL_SUB(s)
#define SIGNALS_LOCAL_ONLY
#include "signal-list.h"
#undef SIGNAL_SUB
#undef SIGNAL_MAIN
#undef SIGNALS_LOCAL_ONLY
ssh_got_exitcode(mc->ppl->ssh, exitcode);
if (exitcode == 128)
signame_str = dupprintf("unrecognised signal \"%.*s\"",
PTRLEN_PRINTF(signame));
else
signame_str = dupprintf("signal SIG%.*s", PTRLEN_PRINTF(signame));
mainchan_log_exit_signal_common(mc, signame_str, core_dumped, msg);
sfree(signame_str);
return true;
}
static bool mainchan_rcvd_exit_signal_numeric(
Channel *chan, int signum, bool core_dumped, ptrlen msg)
{
assert(chan->vt == &mainchan_channelvt);
mainchan *mc = container_of(chan, mainchan, chan);
char *signum_str;
ssh_got_exitcode(mc->ppl->ssh, 128 + signum);
signum_str = dupprintf("signal %d", signum);
mainchan_log_exit_signal_common(mc, signum_str, core_dumped, msg);
sfree(signum_str);
return true;
}
void mainchan_get_specials(
mainchan *mc, add_special_fn_t add_special, void *ctx)
{
/* FIXME: this _does_ depend on whether these services are supported */
add_special(ctx, "Break", SS_BRK, 0);
#define SIGNAL_MAIN(name, desc) \
add_special(ctx, "SIG" #name " (" desc ")", SS_SIG ## name, 0);
#define SIGNAL_SUB(name)
#include "signal-list.h"
#undef SIGNAL_MAIN
#undef SIGNAL_SUB
add_special(ctx, "More signals", SS_SUBMENU, 0);
#define SIGNAL_MAIN(name, desc)
#define SIGNAL_SUB(name) \
add_special(ctx, "SIG" #name, SS_SIG ## name, 0);
#include "signal-list.h"
#undef SIGNAL_MAIN
#undef SIGNAL_SUB
add_special(ctx, NULL, SS_EXITMENU, 0);
}
static const char *ssh_signal_lookup(SessionSpecialCode code)
{
#define SIGNAL_SUB(name) \
if (code == SS_SIG ## name) return #name;
#define SIGNAL_MAIN(name, desc) SIGNAL_SUB(name)
#include "signal-list.h"
#undef SIGNAL_MAIN
#undef SIGNAL_SUB
/* If none of those clauses matched, fail lookup. */
return NULL;
}
void mainchan_special_cmd(mainchan *mc, SessionSpecialCode code, int arg)
{
PacketProtocolLayer *ppl = mc->ppl; /* for ppl_logevent */
const char *signame;
if (code == SS_EOF) {
if (!mc->ready) {
/*
* Buffer the EOF to send as soon as the main channel is
* fully set up.
*/
mc->eof_pending = true;
} else if (!mc->eof_sent) {
sshfwd_write_eof(mc->sc);
mc->eof_sent = true;
}
} else if (code == SS_BRK) {
sshfwd_send_serial_break(
mc->sc, false, 0 /* default break length */);
} else if ((signame = ssh_signal_lookup(code)) != NULL) {
/* It's a signal. */
sshfwd_send_signal(mc->sc, false, signame);
ppl_logevent("Sent signal SIG%s", signame);
}
}
void mainchan_terminal_size(mainchan *mc, int width, int height)
{
mc->term_width = width;
mc->term_height = height;
if (mc->req_pty || mc->got_pty)
sshfwd_send_terminal_size_change(mc->sc, width, height);
}

19
ssh/nogss.c Normal file
View File

@ -0,0 +1,19 @@
#include "putty.h"
#ifndef NO_GSSAPI
/* For platforms not supporting GSSAPI */
struct ssh_gss_liblist *ssh_gss_setup(Conf *conf)
{
struct ssh_gss_liblist *list = snew(struct ssh_gss_liblist *);
list->libraries = NULL;
list->nlibraries = 0;
return list;
}
void ssh_gss_cleanup(struct ssh_gss_liblist *list)
{
sfree(list);
}
#endif /* NO_GSSAPI */

25
ssh/nosharing.c Normal file
View File

@ -0,0 +1,25 @@
/*
* Stub implementation of SSH connection-sharing IPC, for any
* platform which can't support it at all.
*/
#include <stdio.h>
#include <assert.h>
#include <errno.h>
#include "tree234.h"
#include "putty.h"
#include "ssh.h"
#include "network.h"
int platform_ssh_share(const char *name, Conf *conf,
Plug *downplug, Plug *upplug, Socket **sock,
char **logtext, char **ds_err, char **us_err,
bool can_upstream, bool can_downstream)
{
return SHARE_NONE;
}
void platform_ssh_share_cleanup(const char *name)
{
}

105
ssh/pgssapi.c Normal file
View File

@ -0,0 +1,105 @@
/* This file actually defines the GSSAPI function pointers for
* functions we plan to import from a GSSAPI library.
*/
#include "putty.h"
#ifndef NO_GSSAPI
#include "pgssapi.h"
#ifndef NO_LIBDL
/* Reserved static storage for GSS_oids. Comments are quotes from RFC 2744. */
static const gss_OID_desc oids[] = {
/* The implementation must reserve static storage for a
* gss_OID_desc object containing the value */
{10, (void *)"\x2a\x86\x48\x86\xf7\x12\x01\x02\x01\x01"},
/* corresponding to an object-identifier value of
* {iso(1) member-body(2) United States(840) mit(113554)
* infosys(1) gssapi(2) generic(1) user_name(1)}. The constant
* GSS_C_NT_USER_NAME should be initialized to point
* to that gss_OID_desc.
* The implementation must reserve static storage for a
* gss_OID_desc object containing the value */
{10, (void *)"\x2a\x86\x48\x86\xf7\x12\x01\x02\x01\x02"},
/* corresponding to an object-identifier value of
* {iso(1) member-body(2) United States(840) mit(113554)
* infosys(1) gssapi(2) generic(1) machine_uid_name(2)}.
* The constant GSS_C_NT_MACHINE_UID_NAME should be
* initialized to point to that gss_OID_desc.
* The implementation must reserve static storage for a
* gss_OID_desc object containing the value */
{10, (void *)"\x2a\x86\x48\x86\xf7\x12\x01\x02\x01\x03"},
/* corresponding to an object-identifier value of
* {iso(1) member-body(2) United States(840) mit(113554)
* infosys(1) gssapi(2) generic(1) string_uid_name(3)}.
* The constant GSS_C_NT_STRING_UID_NAME should be
* initialized to point to that gss_OID_desc.
*
* The implementation must reserve static storage for a
* gss_OID_desc object containing the value */
{6, (void *)"\x2b\x06\x01\x05\x06\x02"},
/* corresponding to an object-identifier value of
* {iso(1) org(3) dod(6) internet(1) security(5)
* nametypes(6) gss-host-based-services(2))}. The constant
* GSS_C_NT_HOSTBASED_SERVICE_X should be initialized to point
* to that gss_OID_desc. This is a deprecated OID value, and
* implementations wishing to support hostbased-service names
* should instead use the GSS_C_NT_HOSTBASED_SERVICE OID,
* defined below, to identify such names;
* GSS_C_NT_HOSTBASED_SERVICE_X should be accepted a synonym
* for GSS_C_NT_HOSTBASED_SERVICE when presented as an input
* parameter, but should not be emitted by GSS-API
* implementations
*
* The implementation must reserve static storage for a
* gss_OID_desc object containing the value */
{10, (void *)"\x2a\x86\x48\x86\xf7\x12\x01\x02\x01\x04"},
/* corresponding to an object-identifier value of {iso(1)
* member-body(2) Unites States(840) mit(113554) infosys(1)
* gssapi(2) generic(1) service_name(4)}. The constant
* GSS_C_NT_HOSTBASED_SERVICE should be initialized
* to point to that gss_OID_desc.
*
* The implementation must reserve static storage for a
* gss_OID_desc object containing the value */
{6, (void *)"\x2b\x06\01\x05\x06\x03"},
/* corresponding to an object identifier value of
* {1(iso), 3(org), 6(dod), 1(internet), 5(security),
* 6(nametypes), 3(gss-anonymous-name)}. The constant
* and GSS_C_NT_ANONYMOUS should be initialized to point
* to that gss_OID_desc.
*
* The implementation must reserve static storage for a
* gss_OID_desc object containing the value */
{6, (void *)"\x2b\x06\x01\x05\x06\x04"},
/* corresponding to an object-identifier value of
* {1(iso), 3(org), 6(dod), 1(internet), 5(security),
* 6(nametypes), 4(gss-api-exported-name)}. The constant
* GSS_C_NT_EXPORT_NAME should be initialized to point
* to that gss_OID_desc.
*/
};
/* Here are the constants which point to the static structure above.
*
* Constants of the form GSS_C_NT_* are specified by rfc 2744.
*/
const_gss_OID GSS_C_NT_USER_NAME = oids+0;
const_gss_OID GSS_C_NT_MACHINE_UID_NAME = oids+1;
const_gss_OID GSS_C_NT_STRING_UID_NAME = oids+2;
const_gss_OID GSS_C_NT_HOSTBASED_SERVICE_X = oids+3;
const_gss_OID GSS_C_NT_HOSTBASED_SERVICE = oids+4;
const_gss_OID GSS_C_NT_ANONYMOUS = oids+5;
const_gss_OID GSS_C_NT_EXPORT_NAME = oids+6;
#endif /* NO_LIBDL */
static gss_OID_desc gss_mech_krb5_desc =
{ 9, (void *)"\x2a\x86\x48\x86\xf7\x12\x01\x02\x02" };
/* iso(1) member-body(2) United States(840) mit(113554) infosys(1) gssapi(2) krb5(2)*/
const gss_OID GSS_MECH_KRB5 = &gss_mech_krb5_desc;
#endif /* NO_GSSAPI */

333
ssh/pgssapi.h Normal file
View File

@ -0,0 +1,333 @@
#ifndef PUTTY_PGSSAPI_H
#define PUTTY_PGSSAPI_H
#include "putty.h"
#ifndef NO_GSSAPI
/*
* On Unix, if we're statically linking against GSSAPI, we leave the
* declaration of all this lot to the official header. If we're
* dynamically linking, we declare it ourselves, because that avoids
* us needing the official header at compile time.
*
* However, we still need the function pointer types, because even
* with statically linked GSSAPI we use the ssh_gss_library wrapper.
*/
#ifdef STATIC_GSSAPI
#include <gssapi/gssapi.h>
typedef gss_OID const_gss_OID; /* for our prototypes below */
#else /* STATIC_GSSAPI */
/*******************************************************************************
* GSSAPI Definitions, taken from RFC 2744
******************************************************************************/
/* GSSAPI Type Definitions */
typedef uint32_t OM_uint32;
typedef struct gss_OID_desc_struct {
OM_uint32 length;
void *elements;
} gss_OID_desc;
typedef const gss_OID_desc *const_gss_OID;
typedef gss_OID_desc *gss_OID;
typedef struct gss_OID_set_desc_struct {
size_t count;
gss_OID elements;
} gss_OID_set_desc;
typedef const gss_OID_set_desc *const_gss_OID_set;
typedef gss_OID_set_desc *gss_OID_set;
typedef struct gss_buffer_desc_struct {
size_t length;
void *value;
} gss_buffer_desc, *gss_buffer_t;
typedef struct gss_channel_bindings_struct {
OM_uint32 initiator_addrtype;
gss_buffer_desc initiator_address;
OM_uint32 acceptor_addrtype;
gss_buffer_desc acceptor_address;
gss_buffer_desc application_data;
} *gss_channel_bindings_t;
typedef void * gss_ctx_id_t;
typedef void * gss_name_t;
typedef void * gss_cred_id_t;
typedef OM_uint32 gss_qop_t;
typedef int gss_cred_usage_t;
/* Flag bits for context-level services. */
#define GSS_C_DELEG_FLAG 1
#define GSS_C_MUTUAL_FLAG 2
#define GSS_C_REPLAY_FLAG 4
#define GSS_C_SEQUENCE_FLAG 8
#define GSS_C_CONF_FLAG 16
#define GSS_C_INTEG_FLAG 32
#define GSS_C_ANON_FLAG 64
#define GSS_C_PROT_READY_FLAG 128
#define GSS_C_TRANS_FLAG 256
/* Credential usage options */
#define GSS_C_BOTH 0
#define GSS_C_INITIATE 1
#define GSS_C_ACCEPT 2
/*-
* RFC 2744 Page 86
* Expiration time of 2^32-1 seconds means infinite lifetime for a
* credential or security context
*/
#define GSS_C_INDEFINITE 0xfffffffful
/* Status code types for gss_display_status */
#define GSS_C_GSS_CODE 1
#define GSS_C_MECH_CODE 2
/* The constant definitions for channel-bindings address families */
#define GSS_C_AF_UNSPEC 0
#define GSS_C_AF_LOCAL 1
#define GSS_C_AF_INET 2
#define GSS_C_AF_IMPLINK 3
#define GSS_C_AF_PUP 4
#define GSS_C_AF_CHAOS 5
#define GSS_C_AF_NS 6
#define GSS_C_AF_NBS 7
#define GSS_C_AF_ECMA 8
#define GSS_C_AF_DATAKIT 9
#define GSS_C_AF_CCITT 10
#define GSS_C_AF_SNA 11
#define GSS_C_AF_DECnet 12
#define GSS_C_AF_DLI 13
#define GSS_C_AF_LAT 14
#define GSS_C_AF_HYLINK 15
#define GSS_C_AF_APPLETALK 16
#define GSS_C_AF_BSC 17
#define GSS_C_AF_DSS 18
#define GSS_C_AF_OSI 19
#define GSS_C_AF_X25 21
#define GSS_C_AF_NULLADDR 255
/* Various Null values */
#define GSS_C_NO_NAME ((gss_name_t) 0)
#define GSS_C_NO_BUFFER ((gss_buffer_t) 0)
#define GSS_C_NO_OID ((gss_OID) 0)
#define GSS_C_NO_OID_SET ((gss_OID_set) 0)
#define GSS_C_NO_CONTEXT ((gss_ctx_id_t) 0)
#define GSS_C_NO_CREDENTIAL ((gss_cred_id_t) 0)
#define GSS_C_NO_CHANNEL_BINDINGS ((gss_channel_bindings_t) 0)
#define GSS_C_EMPTY_BUFFER {0, NULL}
/* Major status codes */
#define GSS_S_COMPLETE 0
/* Some "helper" definitions to make the status code macros obvious. */
#define GSS_C_CALLING_ERROR_OFFSET 24
#define GSS_C_ROUTINE_ERROR_OFFSET 16
#define GSS_C_SUPPLEMENTARY_OFFSET 0
#define GSS_C_CALLING_ERROR_MASK 0377ul
#define GSS_C_ROUTINE_ERROR_MASK 0377ul
#define GSS_C_SUPPLEMENTARY_MASK 0177777ul
/*
* The macros that test status codes for error conditions.
* Note that the GSS_ERROR() macro has changed slightly from
* the V1 GSS-API so that it now evaluates its argument
* only once.
*/
#define GSS_CALLING_ERROR(x) \
(x & (GSS_C_CALLING_ERROR_MASK << GSS_C_CALLING_ERROR_OFFSET))
#define GSS_ROUTINE_ERROR(x) \
(x & (GSS_C_ROUTINE_ERROR_MASK << GSS_C_ROUTINE_ERROR_OFFSET))
#define GSS_SUPPLEMENTARY_INFO(x) \
(x & (GSS_C_SUPPLEMENTARY_MASK << GSS_C_SUPPLEMENTARY_OFFSET))
#define GSS_ERROR(x) \
(x & ((GSS_C_CALLING_ERROR_MASK << GSS_C_CALLING_ERROR_OFFSET) | \
(GSS_C_ROUTINE_ERROR_MASK << GSS_C_ROUTINE_ERROR_OFFSET)))
/* Now the actual status code definitions */
/* Calling errors: */
#define GSS_S_CALL_INACCESSIBLE_READ \
(1ul << GSS_C_CALLING_ERROR_OFFSET)
#define GSS_S_CALL_INACCESSIBLE_WRITE \
(2ul << GSS_C_CALLING_ERROR_OFFSET)
#define GSS_S_CALL_BAD_STRUCTURE \
(3ul << GSS_C_CALLING_ERROR_OFFSET)
/* Routine errors: */
#define GSS_S_BAD_MECH (1ul << \
GSS_C_ROUTINE_ERROR_OFFSET)
#define GSS_S_BAD_NAME (2ul << \
GSS_C_ROUTINE_ERROR_OFFSET)
#define GSS_S_BAD_NAMETYPE (3ul << \
GSS_C_ROUTINE_ERROR_OFFSET)
#define GSS_S_BAD_BINDINGS (4ul << \
GSS_C_ROUTINE_ERROR_OFFSET)
#define GSS_S_BAD_STATUS (5ul << \
GSS_C_ROUTINE_ERROR_OFFSET)
#define GSS_S_BAD_SIG (6ul << \
GSS_C_ROUTINE_ERROR_OFFSET)
#define GSS_S_BAD_MIC GSS_S_BAD_SIG
#define GSS_S_NO_CRED (7ul << \
GSS_C_ROUTINE_ERROR_OFFSET)
#define GSS_S_NO_CONTEXT (8ul << \
GSS_C_ROUTINE_ERROR_OFFSET)
#define GSS_S_DEFECTIVE_TOKEN (9ul << \
GSS_C_ROUTINE_ERROR_OFFSET)
#define GSS_S_DEFECTIVE_CREDENTIAL (10ul << \
GSS_C_ROUTINE_ERROR_OFFSET)
#define GSS_S_CREDENTIALS_EXPIRED (11ul << \
GSS_C_ROUTINE_ERROR_OFFSET)
#define GSS_S_CONTEXT_EXPIRED (12ul << \
GSS_C_ROUTINE_ERROR_OFFSET)
#define GSS_S_FAILURE (13ul << \
GSS_C_ROUTINE_ERROR_OFFSET)
#define GSS_S_BAD_QOP (14ul << \
GSS_C_ROUTINE_ERROR_OFFSET)
#define GSS_S_UNAUTHORIZED (15ul << \
GSS_C_ROUTINE_ERROR_OFFSET)
#define GSS_S_UNAVAILABLE (16ul << \
GSS_C_ROUTINE_ERROR_OFFSET)
#define GSS_S_DUPLICATE_ELEMENT (17ul << \
GSS_C_ROUTINE_ERROR_OFFSET)
#define GSS_S_NAME_NOT_MN (18ul << \
GSS_C_ROUTINE_ERROR_OFFSET)
/* Supplementary info bits: */
#define GSS_S_CONTINUE_NEEDED \
(1ul << (GSS_C_SUPPLEMENTARY_OFFSET + 0))
#define GSS_S_DUPLICATE_TOKEN \
(1ul << (GSS_C_SUPPLEMENTARY_OFFSET + 1))
#define GSS_S_OLD_TOKEN \
(1ul << (GSS_C_SUPPLEMENTARY_OFFSET + 2))
#define GSS_S_UNSEQ_TOKEN \
(1ul << (GSS_C_SUPPLEMENTARY_OFFSET + 3))
#define GSS_S_GAP_TOKEN \
(1ul << (GSS_C_SUPPLEMENTARY_OFFSET + 4))
extern const_gss_OID GSS_C_NT_USER_NAME;
extern const_gss_OID GSS_C_NT_MACHINE_UID_NAME;
extern const_gss_OID GSS_C_NT_STRING_UID_NAME;
extern const_gss_OID GSS_C_NT_HOSTBASED_SERVICE_X;
extern const_gss_OID GSS_C_NT_HOSTBASED_SERVICE;
extern const_gss_OID GSS_C_NT_ANONYMOUS;
extern const_gss_OID GSS_C_NT_EXPORT_NAME;
#endif /* STATIC_GSSAPI */
extern const gss_OID GSS_MECH_KRB5;
/* GSSAPI functions we use.
* TODO: Replace with all GSSAPI functions from RFC?
*/
/* Calling convention, just in case we need one. */
#ifndef GSS_CC
#define GSS_CC
#endif /*GSS_CC*/
typedef OM_uint32 (GSS_CC *t_gss_release_cred)
(OM_uint32 * /*minor_status*/,
gss_cred_id_t * /*cred_handle*/);
typedef OM_uint32 (GSS_CC *t_gss_init_sec_context)
(OM_uint32 * /*minor_status*/,
const gss_cred_id_t /*initiator_cred_handle*/,
gss_ctx_id_t * /*context_handle*/,
const gss_name_t /*target_name*/,
const gss_OID /*mech_type*/,
OM_uint32 /*req_flags*/,
OM_uint32 /*time_req*/,
const gss_channel_bindings_t /*input_chan_bindings*/,
const gss_buffer_t /*input_token*/,
gss_OID * /*actual_mech_type*/,
gss_buffer_t /*output_token*/,
OM_uint32 * /*ret_flags*/,
OM_uint32 * /*time_rec*/);
typedef OM_uint32 (GSS_CC *t_gss_delete_sec_context)
(OM_uint32 * /*minor_status*/,
gss_ctx_id_t * /*context_handle*/,
gss_buffer_t /*output_token*/);
typedef OM_uint32 (GSS_CC *t_gss_get_mic)
(OM_uint32 * /*minor_status*/,
const gss_ctx_id_t /*context_handle*/,
gss_qop_t /*qop_req*/,
const gss_buffer_t /*message_buffer*/,
gss_buffer_t /*msg_token*/);
typedef OM_uint32 (GSS_CC *t_gss_verify_mic)
(OM_uint32 * /*minor_status*/,
const gss_ctx_id_t /*context_handle*/,
const gss_buffer_t /*message_buffer*/,
const gss_buffer_t /*msg_token*/,
gss_qop_t * /*qop_state*/);
typedef OM_uint32 (GSS_CC *t_gss_display_status)
(OM_uint32 * /*minor_status*/,
OM_uint32 /*status_value*/,
int /*status_type*/,
const gss_OID /*mech_type*/,
OM_uint32 * /*message_context*/,
gss_buffer_t /*status_string*/);
typedef OM_uint32 (GSS_CC *t_gss_import_name)
(OM_uint32 * /*minor_status*/,
const gss_buffer_t /*input_name_buffer*/,
const_gss_OID /*input_name_type*/,
gss_name_t * /*output_name*/);
typedef OM_uint32 (GSS_CC *t_gss_release_name)
(OM_uint32 * /*minor_status*/,
gss_name_t * /*name*/);
typedef OM_uint32 (GSS_CC *t_gss_release_buffer)
(OM_uint32 * /*minor_status*/,
gss_buffer_t /*buffer*/);
typedef OM_uint32 (GSS_CC *t_gss_acquire_cred)
(OM_uint32 * /*minor_status*/,
const gss_name_t /*desired_name*/,
OM_uint32 /*time_req*/,
const gss_OID_set /*desired_mechs*/,
gss_cred_usage_t /*cred_usage*/,
gss_cred_id_t * /*output_cred_handle*/,
gss_OID_set * /*actual_mechs*/,
OM_uint32 * /*time_rec*/);
typedef OM_uint32 (GSS_CC *t_gss_inquire_cred_by_mech)
(OM_uint32 * /*minor_status*/,
const gss_cred_id_t /*cred_handle*/,
const gss_OID /*mech_type*/,
gss_name_t * /*name*/,
OM_uint32 * /*initiator_lifetime*/,
OM_uint32 * /*acceptor_lifetime*/,
gss_cred_usage_t * /*cred_usage*/);
struct gssapi_functions {
t_gss_delete_sec_context delete_sec_context;
t_gss_display_status display_status;
t_gss_get_mic get_mic;
t_gss_verify_mic verify_mic;
t_gss_import_name import_name;
t_gss_init_sec_context init_sec_context;
t_gss_release_buffer release_buffer;
t_gss_release_cred release_cred;
t_gss_release_name release_name;
t_gss_acquire_cred acquire_cred;
t_gss_inquire_cred_by_mech inquire_cred_by_mech;
};
#endif /* NO_GSSAPI */
#endif /* PUTTY_PGSSAPI_H */

1174
ssh/portfwd.c Normal file

File diff suppressed because it is too large Load Diff

174
ssh/ppl.h Normal file
View File

@ -0,0 +1,174 @@
/*
* Abstraction of the various layers of SSH packet-level protocol,
* general enough to take in all three of the main SSH-2 layers and
* both of the SSH-1 phases.
*/
#ifndef PUTTY_SSHPPL_H
#define PUTTY_SSHPPL_H
typedef void (*packet_handler_fn_t)(PacketProtocolLayer *ppl, PktIn *pktin);
typedef struct PacketProtocolLayerVtable PacketProtocolLayerVtable;
struct PacketProtocolLayerVtable {
void (*free)(PacketProtocolLayer *);
void (*process_queue)(PacketProtocolLayer *ppl);
bool (*get_specials)(
PacketProtocolLayer *ppl, add_special_fn_t add_special, void *ctx);
void (*special_cmd)(
PacketProtocolLayer *ppl, SessionSpecialCode code, int arg);
bool (*want_user_input)(PacketProtocolLayer *ppl);
void (*got_user_input)(PacketProtocolLayer *ppl);
void (*reconfigure)(PacketProtocolLayer *ppl, Conf *conf);
size_t (*queued_data_size)(PacketProtocolLayer *ppl);
/* Protocol-level name of this layer. */
const char *name;
};
struct PacketProtocolLayer {
const struct PacketProtocolLayerVtable *vt;
/* Link to the underlying SSH BPP. */
BinaryPacketProtocol *bpp;
/* Queue from which the layer receives its input packets, and one
* to put its output packets on. */
PktInQueue *in_pq;
PktOutQueue *out_pq;
/* Idempotent callback that in_pq will be linked to, causing a
* call to the process_queue method. in_pq points to this, so it
* will be automatically triggered by pushing things on the
* layer's input queue, but it can also be triggered on purpose. */
IdempotentCallback ic_process_queue;
/* Owner's pointer to this layer. Permits a layer to unilaterally
* abdicate in favour of a replacement, by overwriting this
* pointer and then freeing itself. */
PacketProtocolLayer **selfptr;
/* Bufchain of keyboard input from the user, for login prompts and
* similar. */
bufchain *user_input;
/* Logging and error-reporting facilities. */
LogContext *logctx;
Seat *seat; /* for dialog boxes, session output etc */
Ssh *ssh; /* for session termination + assorted connection-layer ops */
/* Known bugs in the remote implementation. */
unsigned remote_bugs;
};
static inline void ssh_ppl_process_queue(PacketProtocolLayer *ppl)
{ ppl->vt->process_queue(ppl); }
static inline bool ssh_ppl_get_specials(
PacketProtocolLayer *ppl, add_special_fn_t add_special, void *ctx)
{ return ppl->vt->get_specials(ppl, add_special, ctx); }
static inline void ssh_ppl_special_cmd(
PacketProtocolLayer *ppl, SessionSpecialCode code, int arg)
{ ppl->vt->special_cmd(ppl, code, arg); }
static inline bool ssh_ppl_want_user_input(PacketProtocolLayer *ppl)
{ return ppl->vt->want_user_input(ppl); }
static inline void ssh_ppl_got_user_input(PacketProtocolLayer *ppl)
{ ppl->vt->got_user_input(ppl); }
static inline void ssh_ppl_reconfigure(PacketProtocolLayer *ppl, Conf *conf)
{ ppl->vt->reconfigure(ppl, conf); }
static inline size_t ssh_ppl_queued_data_size(PacketProtocolLayer *ppl)
{ return ppl->vt->queued_data_size(ppl); }
/* ssh_ppl_free is more than just a macro wrapper on the vtable; it
* does centralised parts of the freeing too. */
void ssh_ppl_free(PacketProtocolLayer *ppl);
/* Helper routine to point a PPL at its input and output queues. Also
* sets up the IdempotentCallback on the input queue to trigger a call
* to process_queue whenever packets are added to it. */
void ssh_ppl_setup_queues(PacketProtocolLayer *ppl,
PktInQueue *inq, PktOutQueue *outq);
/* Routine a PPL can call to abdicate in favour of a replacement, by
* overwriting ppl->selfptr. Has the side effect of freeing 'old', so
* if 'old' actually called this (which is likely) then it should
* avoid dereferencing itself on return from this function! */
void ssh_ppl_replace(PacketProtocolLayer *old, PacketProtocolLayer *new);
/* Default implementation of queued_data_size, which just adds up the
* sizes of all the packets in pq_out. A layer can override this if it
* has other things to take into account as well. */
size_t ssh_ppl_default_queued_data_size(PacketProtocolLayer *ppl);
PacketProtocolLayer *ssh1_login_new(
Conf *conf, const char *host, int port,
PacketProtocolLayer *successor_layer);
PacketProtocolLayer *ssh1_connection_new(
Ssh *ssh, Conf *conf, ConnectionLayer **cl_out);
struct DataTransferStats;
struct ssh_connection_shared_gss_state;
PacketProtocolLayer *ssh2_transport_new(
Conf *conf, const char *host, int port, const char *fullhostname,
const char *client_greeting, const char *server_greeting,
struct ssh_connection_shared_gss_state *shgss,
struct DataTransferStats *stats, PacketProtocolLayer *higher_layer,
const SshServerConfig *ssc);
PacketProtocolLayer *ssh2_userauth_new(
PacketProtocolLayer *successor_layer,
const char *hostname, const char *fullhostname,
Filename *keyfile, bool show_banner, bool tryagent,
const char *default_username, bool change_username,
bool try_ki_auth,
bool try_gssapi_auth, bool try_gssapi_kex_auth,
bool gssapi_fwd, struct ssh_connection_shared_gss_state *shgss);
PacketProtocolLayer *ssh2_connection_new(
Ssh *ssh, ssh_sharing_state *connshare, bool is_simple,
Conf *conf, const char *peer_verstring, ConnectionLayer **cl_out);
/* Can't put this in the userauth constructor without having a
* dependency loop at setup time (transport and userauth can't _both_
* be constructed second and given a pointer to the other). */
void ssh2_userauth_set_transport_layer(PacketProtocolLayer *userauth,
PacketProtocolLayer *transport);
/* Convenience macro for protocol layers to send formatted strings to
* the Event Log. Assumes a function parameter called 'ppl' is in
* scope. */
#define ppl_logevent(...) ( \
logevent_and_free((ppl)->logctx, dupprintf(__VA_ARGS__)))
/* Convenience macro for protocol layers to send formatted strings to
* the terminal. Also expects 'ppl' to be in scope. */
#define ppl_printf(...) \
ssh_ppl_user_output_string_and_free(ppl, dupprintf(__VA_ARGS__))
void ssh_ppl_user_output_string_and_free(PacketProtocolLayer *ppl, char *text);
/* Methods for userauth to communicate back to the transport layer */
ptrlen ssh2_transport_get_session_id(PacketProtocolLayer *ssh2_transport_ptr);
void ssh2_transport_notify_auth_done(PacketProtocolLayer *ssh2_transport_ptr);
/* Shared method between ssh2 layers (defined in transport2.c) to
* handle the common packets between login and connection: DISCONNECT,
* DEBUG and IGNORE. Those messages are handled by the ssh2transport
* layer if we have one, but in bare ssh2-connection mode they have to
* be handled by ssh2connection. */
bool ssh2_common_filter_queue(PacketProtocolLayer *ppl);
/* Methods for ssh1login to pass protocol flags to ssh1connection */
void ssh1_connection_set_protoflags(
PacketProtocolLayer *ppl, int local, int remote);
/* Shared get_specials method between the two ssh1 layers */
bool ssh1_common_get_specials(PacketProtocolLayer *, add_special_fn_t, void *);
/* Other shared functions between ssh1 layers */
bool ssh1_common_filter_queue(PacketProtocolLayer *ppl);
void ssh1_compute_session_id(
unsigned char *session_id, const unsigned char *cookie,
RSAKey *hostkey, RSAKey *servkey);
/* Method used by the SSH server */
void ssh2_transport_provide_hostkeys(PacketProtocolLayer *ssh2_transport_ptr,
ssh_key *const *hostkeys, int nhostkeys);
#endif /* PUTTY_SSHPPL_H */

1399
ssh/scpserver.c Normal file

File diff suppressed because it is too large Load Diff

591
ssh/server.c Normal file
View File

@ -0,0 +1,591 @@
/*
* Top-level code for SSH server implementation.
*/
#include <assert.h>
#include <stddef.h>
#include "putty.h"
#include "ssh.h"
#include "bpp.h"
#include "ppl.h"
#include "channel.h"
#include "server.h"
#ifndef NO_GSSAPI
#include "gssc.h"
#include "gss.h"
#endif
struct Ssh { int dummy; };
typedef struct server server;
struct server {
bufchain in_raw, out_raw;
IdempotentCallback ic_out_raw;
bool pending_close;
bufchain dummy_user_input; /* we never put anything on this */
PacketLogSettings pls;
LogContext *logctx;
struct DataTransferStats stats;
int remote_bugs;
Socket *socket;
Plug plug;
int conn_throttle_count;
bool frozen;
Conf *conf;
const SshServerConfig *ssc;
ssh_key *const *hostkeys;
int nhostkeys;
RSAKey *hostkey1;
AuthPolicy *authpolicy;
LogPolicy *logpolicy;
const SftpServerVtable *sftpserver_vt;
agentfwd *stunt_agentfwd;
Seat seat;
Ssh ssh;
struct ssh_version_receiver version_receiver;
BinaryPacketProtocol *bpp;
PacketProtocolLayer *base_layer;
ConnectionLayer *cl;
#ifndef NO_GSSAPI
struct ssh_connection_shared_gss_state gss_state;
#endif
};
static void ssh_server_free_callback(void *vsrv);
static void server_got_ssh_version(struct ssh_version_receiver *rcv,
int major_version);
static void server_connect_bpp(server *srv);
static void server_bpp_output_raw_data_callback(void *vctx);
void share_activate(ssh_sharing_state *sharestate,
const char *server_verstring) {}
void ssh_connshare_provide_connlayer(ssh_sharing_state *sharestate,
ConnectionLayer *cl) {}
int share_ndownstreams(ssh_sharing_state *sharestate) { return 0; }
void share_got_pkt_from_server(ssh_sharing_connstate *cs, int type,
const void *vpkt, int pktlen) {}
void share_setup_x11_channel(ssh_sharing_connstate *cs, share_channel *chan,
unsigned upstream_id, unsigned server_id,
unsigned server_currwin, unsigned server_maxpkt,
unsigned client_adjusted_window,
const char *peer_addr, int peer_port, int endian,
int protomajor, int protominor,
const void *initial_data, int initial_len) {}
Channel *agentf_new(SshChannel *c) { return NULL; }
bool agent_exists(void) { return false; }
void ssh_got_exitcode(Ssh *ssh, int exitcode) {}
void ssh_check_frozen(Ssh *ssh) {}
mainchan *mainchan_new(
PacketProtocolLayer *ppl, ConnectionLayer *cl, Conf *conf,
int term_width, int term_height, bool is_simple, SshChannel **sc_out)
{ return NULL; }
void mainchan_get_specials(
mainchan *mc, add_special_fn_t add_special, void *ctx) {}
void mainchan_special_cmd(mainchan *mc, SessionSpecialCode code, int arg) {}
void mainchan_terminal_size(mainchan *mc, int width, int height) {}
/* Seat functions to ensure we don't get choosy about crypto - as the
* server, it's not up to us to give user warnings */
static int server_confirm_weak_crypto_primitive(
Seat *seat, const char *algtype, const char *algname,
void (*callback)(void *ctx, int result), void *ctx) { return 1; }
static int server_confirm_weak_cached_hostkey(
Seat *seat, const char *algname, const char *betteralgs,
void (*callback)(void *ctx, int result), void *ctx) { return 1; }
static const SeatVtable server_seat_vt = {
.output = nullseat_output,
.eof = nullseat_eof,
.get_userpass_input = nullseat_get_userpass_input,
.notify_remote_exit = nullseat_notify_remote_exit,
.connection_fatal = nullseat_connection_fatal,
.update_specials_menu = nullseat_update_specials_menu,
.get_ttymode = nullseat_get_ttymode,
.set_busy_status = nullseat_set_busy_status,
.verify_ssh_host_key = nullseat_verify_ssh_host_key,
.confirm_weak_crypto_primitive = server_confirm_weak_crypto_primitive,
.confirm_weak_cached_hostkey = server_confirm_weak_cached_hostkey,
.is_utf8 = nullseat_is_never_utf8,
.echoedit_update = nullseat_echoedit_update,
.get_x_display = nullseat_get_x_display,
.get_windowid = nullseat_get_windowid,
.get_window_pixel_size = nullseat_get_window_pixel_size,
.stripctrl_new = nullseat_stripctrl_new,
.set_trust_status = nullseat_set_trust_status,
.verbose = nullseat_verbose_no,
.interactive = nullseat_interactive_no,
.get_cursor_position = nullseat_get_cursor_position,
};
static void server_socket_log(Plug *plug, PlugLogType type, SockAddr *addr,
int port, const char *error_msg, int error_code)
{
/* server *srv = container_of(plug, server, plug); */
/* FIXME */
}
static void server_closing(Plug *plug, const char *error_msg, int error_code,
bool calling_back)
{
server *srv = container_of(plug, server, plug);
if (error_msg) {
ssh_remote_error(&srv->ssh, "%s", error_msg);
} else if (srv->bpp) {
srv->bpp->input_eof = true;
queue_idempotent_callback(&srv->bpp->ic_in_raw);
}
}
static void server_receive(
Plug *plug, int urgent, const char *data, size_t len)
{
server *srv = container_of(plug, server, plug);
/* Log raw data, if we're in that mode. */
if (srv->logctx)
log_packet(srv->logctx, PKT_INCOMING, -1, NULL, data, len,
0, NULL, NULL, 0, NULL);
bufchain_add(&srv->in_raw, data, len);
if (!srv->frozen && srv->bpp)
queue_idempotent_callback(&srv->bpp->ic_in_raw);
}
static void server_sent(Plug *plug, size_t bufsize)
{
#ifdef FIXME
server *srv = container_of(plug, server, plug);
/*
* If the send backlog on the SSH socket itself clears, we should
* unthrottle the whole world if it was throttled. Also trigger an
* extra call to the consumer of the BPP's output, to try to send
* some more data off its bufchain.
*/
if (bufsize < SSH_MAX_BACKLOG) {
srv_throttle_all(srv, 0, bufsize);
queue_idempotent_callback(&srv->ic_out_raw);
}
#endif
}
LogContext *ssh_get_logctx(Ssh *ssh)
{
server *srv = container_of(ssh, server, ssh);
return srv->logctx;
}
void ssh_throttle_conn(Ssh *ssh, int adjust)
{
server *srv = container_of(ssh, server, ssh);
int old_count = srv->conn_throttle_count;
bool frozen;
srv->conn_throttle_count += adjust;
assert(srv->conn_throttle_count >= 0);
if (srv->conn_throttle_count && !old_count) {
frozen = true;
} else if (!srv->conn_throttle_count && old_count) {
frozen = false;
} else {
return; /* don't change current frozen state */
}
srv->frozen = frozen;
if (srv->socket) {
sk_set_frozen(srv->socket, frozen);
/*
* Now process any SSH connection data that was stashed in our
* queue while we were frozen.
*/
queue_idempotent_callback(&srv->bpp->ic_in_raw);
}
}
void ssh_conn_processed_data(Ssh *ssh)
{
/* FIXME: we could add the same check_frozen_state system as we
* have in ssh.c, but because that was originally added to work
* around a peculiarity of the GUI event loop, I haven't yet. */
}
Conf *make_ssh_server_conf(void)
{
Conf *conf = conf_new();
load_open_settings(NULL, conf);
/* In Uppity, we support even the legacy des-cbc cipher by
* default, so that it will be available if the user forces it by
* overriding the KEXINIT strings. If the user wants it _not_
* supported, of course, they can override KEXINIT in the other
* direction. */
conf_set_bool(conf, CONF_ssh2_des_cbc, true);
return conf;
}
static const PlugVtable ssh_server_plugvt = {
.log = server_socket_log,
.closing = server_closing,
.receive = server_receive,
.sent = server_sent,
};
Plug *ssh_server_plug(
Conf *conf, const SshServerConfig *ssc,
ssh_key *const *hostkeys, int nhostkeys,
RSAKey *hostkey1, AuthPolicy *authpolicy, LogPolicy *logpolicy,
const SftpServerVtable *sftpserver_vt)
{
server *srv = snew(server);
memset(srv, 0, sizeof(server));
srv->plug.vt = &ssh_server_plugvt;
srv->conf = conf_copy(conf);
srv->ssc = ssc;
srv->logctx = log_init(logpolicy, conf);
conf_set_bool(srv->conf, CONF_ssh_no_shell, true);
srv->nhostkeys = nhostkeys;
srv->hostkeys = hostkeys;
srv->hostkey1 = hostkey1;
srv->authpolicy = authpolicy;
srv->logpolicy = logpolicy;
srv->sftpserver_vt = sftpserver_vt;
srv->seat.vt = &server_seat_vt;
bufchain_init(&srv->in_raw);
bufchain_init(&srv->out_raw);
bufchain_init(&srv->dummy_user_input);
#ifndef NO_GSSAPI
/* FIXME: replace with sensible */
srv->gss_state.libs = snew(struct ssh_gss_liblist);
srv->gss_state.libs->nlibraries = 0;
#endif
return &srv->plug;
}
void ssh_server_start(Plug *plug, Socket *socket)
{
server *srv = container_of(plug, server, plug);
const char *our_protoversion;
if (srv->ssc->bare_connection) {
our_protoversion = "2.0"; /* SSH-2 only */
} else if (srv->hostkey1 && srv->nhostkeys) {
our_protoversion = "1.99"; /* offer both SSH-1 and SSH-2 */
} else if (srv->hostkey1) {
our_protoversion = "1.5"; /* SSH-1 only */
} else {
assert(srv->nhostkeys);
our_protoversion = "2.0"; /* SSH-2 only */
}
srv->socket = socket;
srv->ic_out_raw.fn = server_bpp_output_raw_data_callback;
srv->ic_out_raw.ctx = srv;
srv->version_receiver.got_ssh_version = server_got_ssh_version;
srv->bpp = ssh_verstring_new(
srv->conf, srv->logctx, srv->ssc->bare_connection,
our_protoversion, &srv->version_receiver,
true, srv->ssc->application_name);
server_connect_bpp(srv);
queue_idempotent_callback(&srv->bpp->ic_in_raw);
}
static void ssh_server_free_callback(void *vsrv)
{
server *srv = (server *)vsrv;
logeventf(srv->logctx, "freeing server instance");
bufchain_clear(&srv->in_raw);
bufchain_clear(&srv->out_raw);
bufchain_clear(&srv->dummy_user_input);
if (srv->socket)
sk_close(srv->socket);
if (srv->stunt_agentfwd)
agentfwd_free(srv->stunt_agentfwd);
if (srv->base_layer)
ssh_ppl_free(srv->base_layer);
if (srv->bpp)
ssh_bpp_free(srv->bpp);
delete_callbacks_for_context(srv);
conf_free(srv->conf);
log_free(srv->logctx);
#ifndef NO_GSSAPI
sfree(srv->gss_state.libs); /* FIXME: replace with sensible */
#endif
LogPolicy *lp = srv->logpolicy;
sfree(srv);
server_instance_terminated(lp);
}
static void server_connect_bpp(server *srv)
{
srv->bpp->ssh = &srv->ssh;
srv->bpp->in_raw = &srv->in_raw;
srv->bpp->out_raw = &srv->out_raw;
bufchain_set_callback(srv->bpp->out_raw, &srv->ic_out_raw);
srv->bpp->pls = &srv->pls;
srv->bpp->logctx = srv->logctx;
srv->bpp->remote_bugs = srv->remote_bugs;
/* Servers don't really have a notion of 'unexpected' connection
* closure. The client is free to close if it likes. */
srv->bpp->expect_close = true;
}
static void server_connect_ppl(server *srv, PacketProtocolLayer *ppl)
{
ppl->bpp = srv->bpp;
ppl->user_input = &srv->dummy_user_input;
ppl->logctx = srv->logctx;
ppl->ssh = &srv->ssh;
ppl->seat = &srv->seat;
ppl->remote_bugs = srv->remote_bugs;
}
static void server_bpp_output_raw_data_callback(void *vctx)
{
server *srv = (server *)vctx;
if (!srv->socket)
return;
while (bufchain_size(&srv->out_raw) > 0) {
size_t backlog;
ptrlen data = bufchain_prefix(&srv->out_raw);
if (srv->logctx)
log_packet(srv->logctx, PKT_OUTGOING, -1, NULL, data.ptr, data.len,
0, NULL, NULL, 0, NULL);
backlog = sk_write(srv->socket, data.ptr, data.len);
bufchain_consume(&srv->out_raw, data.len);
if (backlog > SSH_MAX_BACKLOG) {
#ifdef FIXME
ssh_throttle_all(ssh, 1, backlog);
#endif
return;
}
}
if (srv->pending_close) {
sk_close(srv->socket);
srv->socket = NULL;
queue_toplevel_callback(ssh_server_free_callback, srv);
}
}
static void server_shutdown_internal(server *srv)
{
/*
* We only need to free the base PPL, which will free the others
* (if any) transitively.
*/
if (srv->base_layer) {
ssh_ppl_free(srv->base_layer);
srv->base_layer = NULL;
}
srv->cl = NULL;
}
static void server_initiate_connection_close(server *srv)
{
/* Wind up everything above the BPP. */
server_shutdown_internal(srv);
/* Force any remaining queued SSH packets through the BPP, and
* schedule closing the network socket after they go out. */
ssh_bpp_handle_output(srv->bpp);
srv->pending_close = true;
queue_idempotent_callback(&srv->ic_out_raw);
/* Now we expect the other end to close the connection too in
* response, so arrange that we'll receive notification of that
* via ssh_remote_eof. */
srv->bpp->expect_close = true;
}
#define GET_FORMATTED_MSG(fmt) \
char *msg; \
va_list ap; \
va_start(ap, fmt); \
msg = dupvprintf(fmt, ap); \
va_end(ap);
#define LOG_FORMATTED_MSG(logctx, fmt) do \
{ \
va_list ap; \
va_start(ap, fmt); \
logeventvf(logctx, fmt, ap); \
va_end(ap); \
} while (0)
void ssh_remote_error(Ssh *ssh, const char *fmt, ...)
{
server *srv = container_of(ssh, server, ssh);
LOG_FORMATTED_MSG(srv->logctx, fmt);
queue_toplevel_callback(ssh_server_free_callback, srv);
}
void ssh_remote_eof(Ssh *ssh, const char *fmt, ...)
{
server *srv = container_of(ssh, server, ssh);
LOG_FORMATTED_MSG(srv->logctx, fmt);
queue_toplevel_callback(ssh_server_free_callback, srv);
}
void ssh_proto_error(Ssh *ssh, const char *fmt, ...)
{
server *srv = container_of(ssh, server, ssh);
if (srv->base_layer) {
GET_FORMATTED_MSG(fmt);
ssh_bpp_queue_disconnect(srv->bpp, msg,
SSH2_DISCONNECT_PROTOCOL_ERROR);
server_initiate_connection_close(srv);
logeventf(srv->logctx, "Protocol error: %s", msg);
sfree(msg);
}
}
void ssh_sw_abort(Ssh *ssh, const char *fmt, ...)
{
server *srv = container_of(ssh, server, ssh);
LOG_FORMATTED_MSG(srv->logctx, fmt);
queue_toplevel_callback(ssh_server_free_callback, srv);
}
void ssh_user_close(Ssh *ssh, const char *fmt, ...)
{
server *srv = container_of(ssh, server, ssh);
LOG_FORMATTED_MSG(srv->logctx, fmt);
queue_toplevel_callback(ssh_server_free_callback, srv);
}
static void server_got_ssh_version(struct ssh_version_receiver *rcv,
int major_version)
{
server *srv = container_of(rcv, server, version_receiver);
BinaryPacketProtocol *old_bpp;
PacketProtocolLayer *connection_layer;
old_bpp = srv->bpp;
srv->remote_bugs = ssh_verstring_get_bugs(old_bpp);
if (srv->ssc->bare_connection) {
srv->bpp = ssh2_bare_bpp_new(srv->logctx);
server_connect_bpp(srv);
connection_layer = ssh2_connection_new(
&srv->ssh, NULL, false, srv->conf,
ssh_verstring_get_local(old_bpp), &srv->cl);
ssh2connection_server_configure(connection_layer,
srv->sftpserver_vt, srv->ssc);
server_connect_ppl(srv, connection_layer);
srv->base_layer = connection_layer;
} else if (major_version == 2) {
PacketProtocolLayer *userauth_layer, *transport_child_layer;
srv->bpp = ssh2_bpp_new(srv->logctx, &srv->stats, true);
server_connect_bpp(srv);
connection_layer = ssh2_connection_new(
&srv->ssh, NULL, false, srv->conf,
ssh_verstring_get_local(old_bpp), &srv->cl);
ssh2connection_server_configure(connection_layer,
srv->sftpserver_vt, srv->ssc);
server_connect_ppl(srv, connection_layer);
if (conf_get_bool(srv->conf, CONF_ssh_no_userauth)) {
userauth_layer = NULL;
transport_child_layer = connection_layer;
} else {
userauth_layer = ssh2_userauth_server_new(
connection_layer, srv->authpolicy, srv->ssc);
server_connect_ppl(srv, userauth_layer);
transport_child_layer = userauth_layer;
}
srv->base_layer = ssh2_transport_new(
srv->conf, NULL, 0, NULL,
ssh_verstring_get_remote(old_bpp),
ssh_verstring_get_local(old_bpp),
#ifndef NO_GSSAPI
&srv->gss_state,
#else
NULL,
#endif
&srv->stats, transport_child_layer, srv->ssc);
ssh2_transport_provide_hostkeys(
srv->base_layer, srv->hostkeys, srv->nhostkeys);
if (userauth_layer)
ssh2_userauth_server_set_transport_layer(
userauth_layer, srv->base_layer);
server_connect_ppl(srv, srv->base_layer);
} else {
srv->bpp = ssh1_bpp_new(srv->logctx);
server_connect_bpp(srv);
connection_layer = ssh1_connection_new(&srv->ssh, srv->conf, &srv->cl);
ssh1connection_server_configure(connection_layer, srv->ssc);
server_connect_ppl(srv, connection_layer);
srv->base_layer = ssh1_login_server_new(
connection_layer, srv->hostkey1, srv->authpolicy, srv->ssc);
server_connect_ppl(srv, srv->base_layer);
}
/* Connect the base layer - whichever it is - to the BPP, and set
* up its selfptr. */
srv->base_layer->selfptr = &srv->base_layer;
ssh_ppl_setup_queues(srv->base_layer, &srv->bpp->in_pq, &srv->bpp->out_pq);
#ifdef FIXME // we probably will want one of these, in the end
srv->pinger = pinger_new(srv->conf, &srv->backend);
#endif
if (srv->ssc->stunt_open_unconditional_agent_socket) {
char *socketname;
srv->stunt_agentfwd = agentfwd_new(srv->cl, &socketname);
if (srv->stunt_agentfwd) {
logeventf(srv->logctx, "opened unconditional agent socket at %s\n",
socketname);
sfree(socketname);
}
}
queue_idempotent_callback(&srv->bpp->ic_in_raw);
ssh_ppl_process_queue(srv->base_layer);
ssh_bpp_free(old_bpp);
}

143
ssh/server.h Normal file
View File

@ -0,0 +1,143 @@
typedef struct AuthPolicy AuthPolicy;
struct SshServerConfig {
const char *application_name;
const char *session_starting_dir;
RSAKey *rsa_kex_key;
/*
* In all of these ptrlens, setting the 'ptr' member to NULL means
* that we're not overriding the default configuration.
*/
ptrlen banner; /* default here is 'no banner' */
ptrlen kex_override[NKEXLIST];
bool exit_signal_numeric; /* mimic an old server bug */
unsigned long ssh1_cipher_mask;
bool ssh1_allow_compression;
bool bare_connection;
bool stunt_pretend_to_accept_any_pubkey;
bool stunt_open_unconditional_agent_socket;
};
Plug *ssh_server_plug(
Conf *conf, const SshServerConfig *ssc,
ssh_key *const *hostkeys, int nhostkeys,
RSAKey *hostkey1, AuthPolicy *authpolicy, LogPolicy *logpolicy,
const SftpServerVtable *sftpserver_vt);
void ssh_server_start(Plug *plug, Socket *socket);
void server_instance_terminated(LogPolicy *logpolicy);
void platform_logevent(const char *msg);
#define AUTHMETHODS(X) \
X(NONE) \
X(PASSWORD) \
X(PUBLICKEY) \
X(KBDINT) \
X(TIS) \
X(CRYPTOCARD) \
/* end of list */
#define AUTHMETHOD_BIT_INDEX(name) AUTHMETHOD_BIT_INDEX_##name,
enum { AUTHMETHODS(AUTHMETHOD_BIT_INDEX) AUTHMETHOD_BIT_INDEX_dummy };
#define AUTHMETHOD_BIT_VALUE(name) \
AUTHMETHOD_##name = 1 << AUTHMETHOD_BIT_INDEX_##name,
enum { AUTHMETHODS(AUTHMETHOD_BIT_VALUE) AUTHMETHOD_BIT_VALUE_dummy };
typedef struct AuthKbdInt AuthKbdInt;
typedef struct AuthKbdIntPrompt AuthKbdIntPrompt;
struct AuthKbdInt {
char *title, *instruction; /* both need freeing */
int nprompts;
AuthKbdIntPrompt *prompts; /* the array itself needs freeing */
};
struct AuthKbdIntPrompt {
char *prompt; /* needs freeing */
bool echo;
};
unsigned auth_methods(AuthPolicy *);
bool auth_none(AuthPolicy *, ptrlen username);
int auth_password(AuthPolicy *, ptrlen username, ptrlen password,
ptrlen *opt_new_password);
/* auth_password returns 1 for 'accepted', 0 for 'rejected', and 2 for
* 'ok but now you need to change your password' */
bool auth_publickey(AuthPolicy *, ptrlen username, ptrlen public_blob);
/* auth_publickey_ssh1 must return the whole public key given the modulus,
* because the SSH-1 client never transmits the exponent over the wire.
* The key remains owned by the AuthPolicy. */
AuthKbdInt *auth_kbdint_prompts(AuthPolicy *, ptrlen username);
/* auth_kbdint_prompts returns NULL to trigger auth failure */
int auth_kbdint_responses(AuthPolicy *, const ptrlen *responses);
/* auth_kbdint_responses returns >0 for success, <0 for failure, and 0
* to indicate that we haven't decided yet and further prompts are
* coming */
/* The very similar SSH-1 TIS and CryptoCard methods are combined into
* a single API for AuthPolicy, which takes a method argument */
char *auth_ssh1int_challenge(AuthPolicy *, unsigned method, ptrlen username);
bool auth_ssh1int_response(AuthPolicy *, ptrlen response);
RSAKey *auth_publickey_ssh1(
AuthPolicy *ap, ptrlen username, mp_int *rsa_modulus);
/* auth_successful returns false if further authentication is needed */
bool auth_successful(AuthPolicy *, ptrlen username, unsigned method);
PacketProtocolLayer *ssh2_userauth_server_new(
PacketProtocolLayer *successor_layer, AuthPolicy *authpolicy,
const SshServerConfig *ssc);
void ssh2_userauth_server_set_transport_layer(
PacketProtocolLayer *userauth, PacketProtocolLayer *transport);
void ssh2connection_server_configure(
PacketProtocolLayer *ppl, const SftpServerVtable *sftpserver_vt,
const SshServerConfig *ssc);
void ssh1connection_server_configure(
PacketProtocolLayer *ppl, const SshServerConfig *ssc);
PacketProtocolLayer *ssh1_login_server_new(
PacketProtocolLayer *successor_layer, RSAKey *hostkey,
AuthPolicy *authpolicy, const SshServerConfig *ssc);
Channel *sesschan_new(SshChannel *c, LogContext *logctx,
const SftpServerVtable *sftpserver_vt,
const SshServerConfig *ssc);
Backend *pty_backend_create(
Seat *seat, LogContext *logctx, Conf *conf, char **argv, const char *cmd,
struct ssh_ttymodes ttymodes, bool pipes_instead_of_pty, const char *dir,
const char *const *env_vars_to_unset);
int pty_backend_exit_signum(Backend *be);
ptrlen pty_backend_exit_signame(Backend *be, char **aux_msg);
/*
* Establish a listening X server. Return value is the _number_ of
* Sockets that it established pointing at the given Plug. (0
* indicates complete failure.) The socket pointers themselves are
* written into sockets[], up to a possible total of MAX_X11_SOCKETS.
*
* The supplied Conf has necessary environment variables written into
* it. (And is also used to open the port listeners, though that
* shouldn't affect anything.)
*/
#define MAX_X11_SOCKETS 2
int platform_make_x11_server(Plug *plug, const char *progname, int mindisp,
const char *screen_number_suffix,
ptrlen authproto, ptrlen authdata,
Socket **sockets, Conf *conf);
Conf *make_ssh_server_conf(void);
/* Provided by Unix front end programs to uxsftpserver.c */
void make_unix_sftp_filehandle_key(void *data, size_t size);
typedef struct agentfwd agentfwd;
agentfwd *agentfwd_new(ConnectionLayer *cl, char **socketname_out);
void agentfwd_free(agentfwd *agent);

787
ssh/sesschan.c Normal file
View File

@ -0,0 +1,787 @@
/*
* Implement the "session" channel type for the SSH server.
*/
#include <assert.h>
#include <stdio.h>
#include <stdlib.h>
#include <ctype.h>
#include "putty.h"
#include "ssh.h"
#include "channel.h"
#include "server.h"
#include "sftp.h"
struct agentfwd {
ConnectionLayer *cl;
Socket *socket;
Plug plug;
};
typedef struct sesschan {
SshChannel *c;
LogContext *parent_logctx, *child_logctx;
Conf *conf;
const SftpServerVtable *sftpserver_vt;
LogPolicy logpolicy;
Seat seat;
bool want_pty;
struct ssh_ttymodes ttymodes;
int wc, hc, wp, hp;
strbuf *termtype;
bool ignoring_input;
bool seen_eof, seen_exit;
Plug xfwd_plug;
int n_x11_sockets;
Socket *x11_sockets[MAX_X11_SOCKETS];
agentfwd *agent;
Backend *backend;
bufchain subsys_input;
SftpServer *sftpsrv;
ScpServer *scpsrv;
const SshServerConfig *ssc;
Channel chan;
} sesschan;
static void sesschan_free(Channel *chan);
static size_t sesschan_send(
Channel *chan, bool is_stderr, const void *, size_t);
static void sesschan_send_eof(Channel *chan);
static char *sesschan_log_close_msg(Channel *chan);
static bool sesschan_want_close(Channel *, bool, bool);
static void sesschan_set_input_wanted(Channel *chan, bool wanted);
static bool sesschan_run_shell(Channel *chan);
static bool sesschan_run_command(Channel *chan, ptrlen command);
static bool sesschan_run_subsystem(Channel *chan, ptrlen subsys);
static bool sesschan_enable_x11_forwarding(
Channel *chan, bool oneshot, ptrlen authproto, ptrlen authdata,
unsigned screen_number);
static bool sesschan_enable_agent_forwarding(Channel *chan);
static bool sesschan_allocate_pty(
Channel *chan, ptrlen termtype, unsigned width, unsigned height,
unsigned pixwidth, unsigned pixheight, struct ssh_ttymodes modes);
static bool sesschan_set_env(Channel *chan, ptrlen var, ptrlen value);
static bool sesschan_send_break(Channel *chan, unsigned length);
static bool sesschan_send_signal(Channel *chan, ptrlen signame);
static bool sesschan_change_window_size(
Channel *chan, unsigned width, unsigned height,
unsigned pixwidth, unsigned pixheight);
static const ChannelVtable sesschan_channelvt = {
.free = sesschan_free,
.open_confirmation = chan_remotely_opened_confirmation,
.open_failed = chan_remotely_opened_failure,
.send = sesschan_send,
.send_eof = sesschan_send_eof,
.set_input_wanted = sesschan_set_input_wanted,
.log_close_msg = sesschan_log_close_msg,
.want_close = sesschan_want_close,
.rcvd_exit_status = chan_no_exit_status,
.rcvd_exit_signal = chan_no_exit_signal,
.rcvd_exit_signal_numeric = chan_no_exit_signal_numeric,
.run_shell = sesschan_run_shell,
.run_command = sesschan_run_command,
.run_subsystem = sesschan_run_subsystem,
.enable_x11_forwarding = sesschan_enable_x11_forwarding,
.enable_agent_forwarding = sesschan_enable_agent_forwarding,
.allocate_pty = sesschan_allocate_pty,
.set_env = sesschan_set_env,
.send_break = sesschan_send_break,
.send_signal = sesschan_send_signal,
.change_window_size = sesschan_change_window_size,
.request_response = chan_no_request_response,
};
static size_t sftp_chan_send(
Channel *chan, bool is_stderr, const void *, size_t);
static void sftp_chan_send_eof(Channel *chan);
static char *sftp_log_close_msg(Channel *chan);
static const ChannelVtable sftp_channelvt = {
.free = sesschan_free,
.open_confirmation = chan_remotely_opened_confirmation,
.open_failed = chan_remotely_opened_failure,
.send = sftp_chan_send,
.send_eof = sftp_chan_send_eof,
.set_input_wanted = sesschan_set_input_wanted,
.log_close_msg = sftp_log_close_msg,
.want_close = chan_default_want_close,
.rcvd_exit_status = chan_no_exit_status,
.rcvd_exit_signal = chan_no_exit_signal,
.rcvd_exit_signal_numeric = chan_no_exit_signal_numeric,
.run_shell = chan_no_run_shell,
.run_command = chan_no_run_command,
.run_subsystem = chan_no_run_subsystem,
.enable_x11_forwarding = chan_no_enable_x11_forwarding,
.enable_agent_forwarding = chan_no_enable_agent_forwarding,
.allocate_pty = chan_no_allocate_pty,
.set_env = chan_no_set_env,
.send_break = chan_no_send_break,
.send_signal = chan_no_send_signal,
.change_window_size = chan_no_change_window_size,
.request_response = chan_no_request_response,
};
static size_t scp_chan_send(
Channel *chan, bool is_stderr, const void *, size_t);
static void scp_chan_send_eof(Channel *chan);
static void scp_set_input_wanted(Channel *chan, bool wanted);
static char *scp_log_close_msg(Channel *chan);
static const ChannelVtable scp_channelvt = {
.free = sesschan_free,
.open_confirmation = chan_remotely_opened_confirmation,
.open_failed = chan_remotely_opened_failure,
.send = scp_chan_send,
.send_eof = scp_chan_send_eof,
.set_input_wanted = scp_set_input_wanted,
.log_close_msg = scp_log_close_msg,
.want_close = chan_default_want_close,
.rcvd_exit_status = chan_no_exit_status,
.rcvd_exit_signal = chan_no_exit_signal,
.rcvd_exit_signal_numeric = chan_no_exit_signal_numeric,
.run_shell = chan_no_run_shell,
.run_command = chan_no_run_command,
.run_subsystem = chan_no_run_subsystem,
.enable_x11_forwarding = chan_no_enable_x11_forwarding,
.enable_agent_forwarding = chan_no_enable_agent_forwarding,
.allocate_pty = chan_no_allocate_pty,
.set_env = chan_no_set_env,
.send_break = chan_no_send_break,
.send_signal = chan_no_send_signal,
.change_window_size = chan_no_change_window_size,
.request_response = chan_no_request_response,
};
static void sesschan_eventlog(LogPolicy *lp, const char *event) {}
static void sesschan_logging_error(LogPolicy *lp, const char *event) {}
static int sesschan_askappend(
LogPolicy *lp, Filename *filename,
void (*callback)(void *ctx, int result), void *ctx) { return 2; }
static const LogPolicyVtable sesschan_logpolicy_vt = {
.eventlog = sesschan_eventlog,
.askappend = sesschan_askappend,
.logging_error = sesschan_logging_error,
.verbose = null_lp_verbose_no,
};
static size_t sesschan_seat_output(
Seat *, bool is_stderr, const void *, size_t);
static bool sesschan_seat_eof(Seat *);
static void sesschan_notify_remote_exit(Seat *seat);
static void sesschan_connection_fatal(Seat *seat, const char *message);
static bool sesschan_get_window_pixel_size(Seat *seat, int *w, int *h);
static const SeatVtable sesschan_seat_vt = {
.output = sesschan_seat_output,
.eof = sesschan_seat_eof,
.get_userpass_input = nullseat_get_userpass_input,
.notify_remote_exit = sesschan_notify_remote_exit,
.connection_fatal = sesschan_connection_fatal,
.update_specials_menu = nullseat_update_specials_menu,
.get_ttymode = nullseat_get_ttymode,
.set_busy_status = nullseat_set_busy_status,
.verify_ssh_host_key = nullseat_verify_ssh_host_key,
.confirm_weak_crypto_primitive = nullseat_confirm_weak_crypto_primitive,
.confirm_weak_cached_hostkey = nullseat_confirm_weak_cached_hostkey,
.is_utf8 = nullseat_is_never_utf8,
.echoedit_update = nullseat_echoedit_update,
.get_x_display = nullseat_get_x_display,
.get_windowid = nullseat_get_windowid,
.get_window_pixel_size = sesschan_get_window_pixel_size,
.stripctrl_new = nullseat_stripctrl_new,
.set_trust_status = nullseat_set_trust_status,
.verbose = nullseat_verbose_no,
.interactive = nullseat_interactive_no,
.get_cursor_position = nullseat_get_cursor_position,
};
Channel *sesschan_new(SshChannel *c, LogContext *logctx,
const SftpServerVtable *sftpserver_vt,
const SshServerConfig *ssc)
{
sesschan *sess = snew(sesschan);
memset(sess, 0, sizeof(sesschan));
sess->c = c;
sess->chan.vt = &sesschan_channelvt;
sess->chan.initial_fixed_window_size = 0;
sess->parent_logctx = logctx;
sess->ssc = ssc;
/* Start with a completely default Conf */
sess->conf = conf_new();
load_open_settings(NULL, sess->conf);
/* Set close-on-exit = true to suppress uxpty.c's "[pterm: process
* terminated with status x]" message */
conf_set_int(sess->conf, CONF_close_on_exit, FORCE_ON);
sess->seat.vt = &sesschan_seat_vt;
sess->logpolicy.vt = &sesschan_logpolicy_vt;
sess->child_logctx = log_init(&sess->logpolicy, sess->conf);
sess->sftpserver_vt = sftpserver_vt;
bufchain_init(&sess->subsys_input);
return &sess->chan;
}
static void sesschan_free(Channel *chan)
{
sesschan *sess = container_of(chan, sesschan, chan);
int i;
delete_callbacks_for_context(sess);
conf_free(sess->conf);
if (sess->backend)
backend_free(sess->backend);
bufchain_clear(&sess->subsys_input);
if (sess->sftpsrv)
sftpsrv_free(sess->sftpsrv);
for (i = 0; i < sess->n_x11_sockets; i++)
sk_close(sess->x11_sockets[i]);
if (sess->agent)
agentfwd_free(sess->agent);
sfree(sess);
}
static size_t sesschan_send(Channel *chan, bool is_stderr,
const void *data, size_t length)
{
sesschan *sess = container_of(chan, sesschan, chan);
if (!sess->backend || sess->ignoring_input)
return 0;
return backend_send(sess->backend, data, length);
}
static void sesschan_send_eof(Channel *chan)
{
sesschan *sess = container_of(chan, sesschan, chan);
if (sess->backend)
backend_special(sess->backend, SS_EOF, 0);
}
static char *sesschan_log_close_msg(Channel *chan)
{
return dupstr("Session channel closed");
}
static void sesschan_set_input_wanted(Channel *chan, bool wanted)
{
/* I don't think we need to do anything here */
}
static void sesschan_start_backend(sesschan *sess, const char *cmd)
{
/*
* List of environment variables that we should not pass through
* from the login session Uppity was run in (which, it being a
* test server, there will usually be one of). These variables
* will be set as part of X or agent forwarding, and shouldn't be
* confusingly set in the absence of that.
*
* (DISPLAY must also be cleared, but uxpty.c will do that anyway
* when our get_x_display method returns NULL.)
*/
static const char *const env_to_unset[] = {
"XAUTHORITY", "SSH_AUTH_SOCK", "SSH_AGENT_PID",
NULL /* terminator */
};
sess->backend = pty_backend_create(
&sess->seat, sess->child_logctx, sess->conf, NULL, cmd,
sess->ttymodes, !sess->want_pty, sess->ssc->session_starting_dir,
env_to_unset);
backend_size(sess->backend, sess->wc, sess->hc);
}
bool sesschan_run_shell(Channel *chan)
{
sesschan *sess = container_of(chan, sesschan, chan);
if (sess->backend)
return false;
sesschan_start_backend(sess, NULL);
return true;
}
bool sesschan_run_command(Channel *chan, ptrlen command)
{
sesschan *sess = container_of(chan, sesschan, chan);
if (sess->backend)
return false;
/* FIXME: make this possible to configure off */
if ((sess->scpsrv = scp_recognise_exec(sess->c, sess->sftpserver_vt,
command)) != NULL) {
sess->chan.vt = &scp_channelvt;
logevent(sess->parent_logctx, "Starting built-in SCP server");
return true;
}
char *command_str = mkstr(command);
sesschan_start_backend(sess, command_str);
sfree(command_str);
return true;
}
bool sesschan_run_subsystem(Channel *chan, ptrlen subsys)
{
sesschan *sess = container_of(chan, sesschan, chan);
if (ptrlen_eq_string(subsys, "sftp") && sess->sftpserver_vt) {
sess->sftpsrv = sftpsrv_new(sess->sftpserver_vt);
sess->chan.vt = &sftp_channelvt;
logevent(sess->parent_logctx, "Starting built-in SFTP subsystem");
return true;
}
return false;
}
static void fwd_log(Plug *plug, PlugLogType type, SockAddr *addr, int port,
const char *error_msg, int error_code)
{ /* don't expect any weirdnesses from a listening socket */ }
static void fwd_closing(Plug *plug, const char *error_msg, int error_code,
bool calling_back)
{ /* not here, either */ }
static int xfwd_accepting(Plug *p, accept_fn_t constructor, accept_ctx_t ctx)
{
sesschan *sess = container_of(p, sesschan, xfwd_plug);
Plug *plug;
Channel *chan;
Socket *s;
SocketPeerInfo *pi;
const char *err;
chan = portfwd_raw_new(sess->c->cl, &plug, false);
s = constructor(ctx, plug);
if ((err = sk_socket_error(s)) != NULL) {
portfwd_raw_free(chan);
return 1;
}
pi = sk_peer_info(s);
portfwd_raw_setup(chan, s, ssh_serverside_x11_open(sess->c->cl, chan, pi));
sk_free_peer_info(pi);
return 0;
}
static const PlugVtable xfwd_plugvt = {
.log = fwd_log,
.closing = fwd_closing,
.accepting = xfwd_accepting,
};
bool sesschan_enable_x11_forwarding(
Channel *chan, bool oneshot, ptrlen authproto, ptrlen authdata_hex,
unsigned screen_number)
{
sesschan *sess = container_of(chan, sesschan, chan);
strbuf *authdata_bin;
size_t i;
if (oneshot)
return false; /* not supported */
/*
* Decode the authorisation data from ASCII hex into binary.
*/
if (authdata_hex.len % 2)
return false; /* expected an even number of digits */
authdata_bin = strbuf_new_nm();
for (i = 0; i < authdata_hex.len; i += 2) {
const unsigned char *hex = authdata_hex.ptr;
char hexbuf[3];
if (!isxdigit(hex[i]) || !isxdigit(hex[i+1])) {
strbuf_free(authdata_bin);
return false; /* not hex */
}
hexbuf[0] = hex[i];
hexbuf[1] = hex[i+1];
hexbuf[2] = '\0';
put_byte(authdata_bin, strtoul(hexbuf, NULL, 16));
}
sess->xfwd_plug.vt = &xfwd_plugvt;
char *screensuffix = dupprintf(".%u", screen_number);
sess->n_x11_sockets = platform_make_x11_server(
&sess->xfwd_plug, appname, 10, screensuffix,
authproto, ptrlen_from_strbuf(authdata_bin),
sess->x11_sockets, sess->conf);
sfree(screensuffix);
strbuf_free(authdata_bin);
return sess->n_x11_sockets != 0;
}
static int agentfwd_accepting(
Plug *p, accept_fn_t constructor, accept_ctx_t ctx)
{
agentfwd *agent = container_of(p, agentfwd, plug);
Plug *plug;
Channel *chan;
Socket *s;
const char *err;
chan = portfwd_raw_new(agent->cl, &plug, false);
s = constructor(ctx, plug);
if ((err = sk_socket_error(s)) != NULL) {
portfwd_raw_free(chan);
return 1;
}
portfwd_raw_setup(chan, s, ssh_serverside_agent_open(agent->cl, chan));
return 0;
}
static const PlugVtable agentfwd_plugvt = {
.log = fwd_log,
.closing = fwd_closing,
.accepting = agentfwd_accepting,
};
agentfwd *agentfwd_new(ConnectionLayer *cl, char **socketname_out)
{
agentfwd *agent = snew(agentfwd);
agent->cl = cl;
agent->plug.vt = &agentfwd_plugvt;
char *dir_prefix = dupprintf("/tmp/%s-agentfwd", appname);
char *error = NULL, *socketname = NULL;
agent->socket = platform_make_agent_socket(
&agent->plug, dir_prefix, &error, &socketname);
sfree(dir_prefix);
sfree(error);
if (!agent->socket) {
sfree(agent);
sfree(socketname);
return NULL;
}
*socketname_out = socketname;
return agent;
}
void agentfwd_free(agentfwd *agent)
{
if (agent->socket)
sk_close(agent->socket);
sfree(agent);
}
bool sesschan_enable_agent_forwarding(Channel *chan)
{
sesschan *sess = container_of(chan, sesschan, chan);
char *socketname;
assert(!sess->agent);
sess->agent = agentfwd_new(sess->c->cl, &socketname);
if (!sess->agent)
return false;
conf_set_str_str(sess->conf, CONF_environmt, "SSH_AUTH_SOCK", socketname);
sfree(socketname);
return true;
}
bool sesschan_allocate_pty(
Channel *chan, ptrlen termtype, unsigned width, unsigned height,
unsigned pixwidth, unsigned pixheight, struct ssh_ttymodes modes)
{
sesschan *sess = container_of(chan, sesschan, chan);
char *s;
if (sess->want_pty)
return false;
s = mkstr(termtype);
conf_set_str(sess->conf, CONF_termtype, s);
sfree(s);
sess->want_pty = true;
sess->ttymodes = modes;
sess->wc = width;
sess->hc = height;
sess->wp = pixwidth;
sess->hp = pixheight;
return true;
}
bool sesschan_set_env(Channel *chan, ptrlen var, ptrlen value)
{
sesschan *sess = container_of(chan, sesschan, chan);
char *svar = mkstr(var), *svalue = mkstr(value);
conf_set_str_str(sess->conf, CONF_environmt, svar, svalue);
sfree(svar);
sfree(svalue);
return true;
}
bool sesschan_send_break(Channel *chan, unsigned length)
{
sesschan *sess = container_of(chan, sesschan, chan);
if (sess->backend) {
/* We ignore the break length. We could pass it through as the
* 'arg' parameter, and have uxpty.c collect it and pass it on
* to tcsendbreak, but since tcsendbreak in turn assigns
* implementation-defined semantics to _its_ duration
* parameter, this all just sounds too difficult. */
backend_special(sess->backend, SS_BRK, 0);
return true;
}
return false;
}
bool sesschan_send_signal(Channel *chan, ptrlen signame)
{
sesschan *sess = container_of(chan, sesschan, chan);
/* Start with a code that definitely isn't a signal (or indeed a
* special command at all), to indicate 'nothing matched'. */
SessionSpecialCode code = SS_EXITMENU;
#define SIGNAL_SUB(name) \
if (ptrlen_eq_string(signame, #name)) code = SS_SIG ## name;
#define SIGNAL_MAIN(name, text) SIGNAL_SUB(name)
#include "signal-list.h"
#undef SIGNAL_MAIN
#undef SIGNAL_SUB
if (code == SS_EXITMENU)
return false;
backend_special(sess->backend, code, 0);
return true;
}
bool sesschan_change_window_size(
Channel *chan, unsigned width, unsigned height,
unsigned pixwidth, unsigned pixheight)
{
sesschan *sess = container_of(chan, sesschan, chan);
if (!sess->want_pty)
return false;
sess->wc = width;
sess->hc = height;
sess->wp = pixwidth;
sess->hp = pixheight;
if (sess->backend)
backend_size(sess->backend, sess->wc, sess->hc);
return true;
}
static size_t sesschan_seat_output(
Seat *seat, bool is_stderr, const void *data, size_t len)
{
sesschan *sess = container_of(seat, sesschan, seat);
return sshfwd_write_ext(sess->c, is_stderr, data, len);
}
static void sesschan_check_close_callback(void *vctx)
{
sesschan *sess = (sesschan *)vctx;
/*
* Once we've seen incoming EOF from the backend (aka EIO from the
* pty master) and also passed on the process's exit status, we
* should proactively initiate closure of the session channel.
*/
if (sess->seen_eof && sess->seen_exit)
sshfwd_initiate_close(sess->c, NULL);
}
static bool sesschan_want_close(Channel *chan, bool seen_eof, bool rcvd_eof)
{
sesschan *sess = container_of(chan, sesschan, chan);
/*
* Similarly to above, we don't want to initiate channel closure
* until we've sent the process's exit status, _even_ if EOF of
* the actual data stream has happened in both directions.
*/
return (sess->seen_eof && sess->seen_exit);
}
static bool sesschan_seat_eof(Seat *seat)
{
sesschan *sess = container_of(seat, sesschan, seat);
sshfwd_write_eof(sess->c);
sess->seen_eof = true;
queue_toplevel_callback(sesschan_check_close_callback, sess);
return true;
}
static void sesschan_notify_remote_exit(Seat *seat)
{
sesschan *sess = container_of(seat, sesschan, seat);
if (!sess->backend)
return;
bool got_signal = false;
if (!sess->ssc->exit_signal_numeric) {
char *sigmsg;
ptrlen signame = pty_backend_exit_signame(sess->backend, &sigmsg);
if (signame.len) {
if (!sigmsg)
sigmsg = dupstr("");
sshfwd_send_exit_signal(
sess->c, signame, false, ptrlen_from_asciz(sigmsg));
got_signal = true;
}
sfree(sigmsg);
} else {
int signum = pty_backend_exit_signum(sess->backend);
if (signum >= 0) {
sshfwd_send_exit_signal_numeric(sess->c, signum, false,
PTRLEN_LITERAL(""));
got_signal = true;
}
}
if (!got_signal)
sshfwd_send_exit_status(sess->c, backend_exitcode(sess->backend));
sess->seen_exit = true;
queue_toplevel_callback(sesschan_check_close_callback, sess);
}
static void sesschan_connection_fatal(Seat *seat, const char *message)
{
sesschan *sess = container_of(seat, sesschan, seat);
/* Closest translation I can think of */
sshfwd_send_exit_signal(
sess->c, PTRLEN_LITERAL("HUP"), false, ptrlen_from_asciz(message));
sess->ignoring_input = true;
}
static bool sesschan_get_window_pixel_size(Seat *seat, int *width, int *height)
{
sesschan *sess = container_of(seat, sesschan, seat);
*width = sess->wp;
*height = sess->hp;
return true;
}
/* ----------------------------------------------------------------------
* Built-in SFTP subsystem.
*/
static size_t sftp_chan_send(Channel *chan, bool is_stderr,
const void *data, size_t length)
{
sesschan *sess = container_of(chan, sesschan, chan);
bufchain_add(&sess->subsys_input, data, length);
while (bufchain_size(&sess->subsys_input) >= 4) {
char lenbuf[4];
unsigned pktlen;
struct sftp_packet *pkt, *reply;
bufchain_fetch(&sess->subsys_input, lenbuf, 4);
pktlen = GET_32BIT_MSB_FIRST(lenbuf);
if (bufchain_size(&sess->subsys_input) - 4 < pktlen)
break; /* wait for more data */
bufchain_consume(&sess->subsys_input, 4);
pkt = sftp_recv_prepare(pktlen);
bufchain_fetch_consume(&sess->subsys_input, pkt->data, pktlen);
sftp_recv_finish(pkt);
reply = sftp_handle_request(sess->sftpsrv, pkt);
sftp_pkt_free(pkt);
sftp_send_prepare(reply);
sshfwd_write(sess->c, reply->data, reply->length);
sftp_pkt_free(reply);
}
return 0;
}
static void sftp_chan_send_eof(Channel *chan)
{
sesschan *sess = container_of(chan, sesschan, chan);
sshfwd_write_eof(sess->c);
}
static char *sftp_log_close_msg(Channel *chan)
{
return dupstr("Session channel (SFTP) closed");
}
/* ----------------------------------------------------------------------
* Built-in SCP subsystem.
*/
static size_t scp_chan_send(Channel *chan, bool is_stderr,
const void *data, size_t length)
{
sesschan *sess = container_of(chan, sesschan, chan);
return scp_send(sess->scpsrv, data, length);
}
static void scp_chan_send_eof(Channel *chan)
{
sesschan *sess = container_of(chan, sesschan, chan);
scp_eof(sess->scpsrv);
}
static char *scp_log_close_msg(Channel *chan)
{
return dupstr("Session channel (SCP) closed");
}
static void scp_set_input_wanted(Channel *chan, bool wanted)
{
sesschan *sess = container_of(chan, sesschan, chan);
scp_throttle(sess->scpsrv, !wanted);
}

1205
ssh/sftp.c Normal file

File diff suppressed because it is too large Load Diff

544
ssh/sftp.h Normal file
View File

@ -0,0 +1,544 @@
/*
* sftp.h: definitions for SFTP and the sftp.c routines.
*/
#include "defs.h"
#define SSH_FXP_INIT 1 /* 0x1 */
#define SSH_FXP_VERSION 2 /* 0x2 */
#define SSH_FXP_OPEN 3 /* 0x3 */
#define SSH_FXP_CLOSE 4 /* 0x4 */
#define SSH_FXP_READ 5 /* 0x5 */
#define SSH_FXP_WRITE 6 /* 0x6 */
#define SSH_FXP_LSTAT 7 /* 0x7 */
#define SSH_FXP_FSTAT 8 /* 0x8 */
#define SSH_FXP_SETSTAT 9 /* 0x9 */
#define SSH_FXP_FSETSTAT 10 /* 0xa */
#define SSH_FXP_OPENDIR 11 /* 0xb */
#define SSH_FXP_READDIR 12 /* 0xc */
#define SSH_FXP_REMOVE 13 /* 0xd */
#define SSH_FXP_MKDIR 14 /* 0xe */
#define SSH_FXP_RMDIR 15 /* 0xf */
#define SSH_FXP_REALPATH 16 /* 0x10 */
#define SSH_FXP_STAT 17 /* 0x11 */
#define SSH_FXP_RENAME 18 /* 0x12 */
#define SSH_FXP_STATUS 101 /* 0x65 */
#define SSH_FXP_HANDLE 102 /* 0x66 */
#define SSH_FXP_DATA 103 /* 0x67 */
#define SSH_FXP_NAME 104 /* 0x68 */
#define SSH_FXP_ATTRS 105 /* 0x69 */
#define SSH_FXP_EXTENDED 200 /* 0xc8 */
#define SSH_FXP_EXTENDED_REPLY 201 /* 0xc9 */
#define SSH_FX_OK 0
#define SSH_FX_EOF 1
#define SSH_FX_NO_SUCH_FILE 2
#define SSH_FX_PERMISSION_DENIED 3
#define SSH_FX_FAILURE 4
#define SSH_FX_BAD_MESSAGE 5
#define SSH_FX_NO_CONNECTION 6
#define SSH_FX_CONNECTION_LOST 7
#define SSH_FX_OP_UNSUPPORTED 8
#define SSH_FILEXFER_ATTR_SIZE 0x00000001
#define SSH_FILEXFER_ATTR_UIDGID 0x00000002
#define SSH_FILEXFER_ATTR_PERMISSIONS 0x00000004
#define SSH_FILEXFER_ATTR_ACMODTIME 0x00000008
#define SSH_FILEXFER_ATTR_EXTENDED 0x80000000
#define SSH_FXF_READ 0x00000001
#define SSH_FXF_WRITE 0x00000002
#define SSH_FXF_APPEND 0x00000004
#define SSH_FXF_CREAT 0x00000008
#define SSH_FXF_TRUNC 0x00000010
#define SSH_FXF_EXCL 0x00000020
#define SFTP_PROTO_VERSION 3
#define PERMS_DIRECTORY 040000
/*
* External references. The sftp client module sftp.c expects to be
* able to get at these functions.
*
* sftp_recvdata must never return less than len. It either blocks
* until len is available and then returns true, or it returns false
* for failure.
*
* sftp_senddata returns true on success, false on failure.
*
* sftp_sendbuffer returns the size of the backlog of data in the
* transmit queue.
*/
bool sftp_senddata(const char *data, size_t len);
size_t sftp_sendbuffer(void);
bool sftp_recvdata(char *data, size_t len);
/*
* Free sftp_requests
*/
void sftp_cleanup_request(void);
struct fxp_attrs {
unsigned long flags;
uint64_t size;
unsigned long uid;
unsigned long gid;
unsigned long permissions;
unsigned long atime;
unsigned long mtime;
};
extern const struct fxp_attrs no_attrs;
/*
* Copy between the possibly-unused permissions field in an fxp_attrs
* and a possibly-negative integer containing the same permissions.
*/
#define PUT_PERMISSIONS(attrs, perms) \
((perms) >= 0 ? \
((attrs).flags |= SSH_FILEXFER_ATTR_PERMISSIONS, \
(attrs).permissions = (perms)) : \
((attrs).flags &= ~SSH_FILEXFER_ATTR_PERMISSIONS))
#define GET_PERMISSIONS(attrs, defaultperms) \
((attrs).flags & SSH_FILEXFER_ATTR_PERMISSIONS ? \
(attrs).permissions : defaultperms)
struct fxp_handle {
char *hstring;
int hlen;
};
struct fxp_name {
char *filename, *longname;
struct fxp_attrs attrs;
};
struct fxp_names {
int nnames;
struct fxp_name *names;
};
struct sftp_request;
/*
* Packet-manipulation functions.
*/
struct sftp_packet {
char *data;
size_t length, maxlen, savedpos;
int type;
BinarySink_IMPLEMENTATION;
BinarySource_IMPLEMENTATION;
};
/* When sending a packet, create it with sftp_pkt_init, then add
* things to it by treating it as a BinarySink. When it's done, call
* sftp_send_prepare, and then pkt->data and pkt->length describe its
* wire format. */
struct sftp_packet *sftp_pkt_init(int pkt_type);
void sftp_send_prepare(struct sftp_packet *pkt);
/* When receiving a packet, create it with sftp_recv_prepare once you
* decode its length from the first 4 bytes of wire data. Then write
* that many bytes into pkt->data, and call sftp_recv_finish to set up
* the type code and BinarySource. */
struct sftp_packet *sftp_recv_prepare(unsigned length);
bool sftp_recv_finish(struct sftp_packet *pkt);
/* Either kind of packet can be freed afterwards with sftp_pkt_free. */
void sftp_pkt_free(struct sftp_packet *pkt);
void BinarySink_put_fxp_attrs(BinarySink *bs, struct fxp_attrs attrs);
bool BinarySource_get_fxp_attrs(BinarySource *src, struct fxp_attrs *attrs);
#define put_fxp_attrs(bs, attrs) \
BinarySink_put_fxp_attrs(BinarySink_UPCAST(bs), attrs)
#define get_fxp_attrs(bs, attrs) \
BinarySource_get_fxp_attrs(BinarySource_UPCAST(bs), attrs)
/*
* Error handling.
*/
const char *fxp_error(void);
int fxp_error_type(void);
/*
* Perform exchange of init/version packets. Return false on failure.
*/
bool fxp_init(void);
/*
* Canonify a pathname. Concatenate the two given path elements
* with a separating slash, unless the second is NULL.
*/
struct sftp_request *fxp_realpath_send(const char *path);
char *fxp_realpath_recv(struct sftp_packet *pktin, struct sftp_request *req);
/*
* Open a file. 'attrs' contains attributes to be applied to the file
* if it's being created.
*/
struct sftp_request *fxp_open_send(const char *path, int type,
const struct fxp_attrs *attrs);
struct fxp_handle *fxp_open_recv(struct sftp_packet *pktin,
struct sftp_request *req);
/*
* Open a directory.
*/
struct sftp_request *fxp_opendir_send(const char *path);
struct fxp_handle *fxp_opendir_recv(struct sftp_packet *pktin,
struct sftp_request *req);
/*
* Close a file/dir. Returns true on success, false on error.
*/
struct sftp_request *fxp_close_send(struct fxp_handle *handle);
bool fxp_close_recv(struct sftp_packet *pktin, struct sftp_request *req);
/*
* Make a directory.
*/
struct sftp_request *fxp_mkdir_send(const char *path,
const struct fxp_attrs *attrs);
bool fxp_mkdir_recv(struct sftp_packet *pktin, struct sftp_request *req);
/*
* Remove a directory.
*/
struct sftp_request *fxp_rmdir_send(const char *path);
bool fxp_rmdir_recv(struct sftp_packet *pktin, struct sftp_request *req);
/*
* Remove a file.
*/
struct sftp_request *fxp_remove_send(const char *fname);
bool fxp_remove_recv(struct sftp_packet *pktin, struct sftp_request *req);
/*
* Rename a file.
*/
struct sftp_request *fxp_rename_send(const char *srcfname,
const char *dstfname);
bool fxp_rename_recv(struct sftp_packet *pktin, struct sftp_request *req);
/*
* Return file attributes.
*/
struct sftp_request *fxp_stat_send(const char *fname);
bool fxp_stat_recv(struct sftp_packet *pktin, struct sftp_request *req,
struct fxp_attrs *attrs);
struct sftp_request *fxp_fstat_send(struct fxp_handle *handle);
bool fxp_fstat_recv(struct sftp_packet *pktin, struct sftp_request *req,
struct fxp_attrs *attrs);
/*
* Set file attributes.
*/
struct sftp_request *fxp_setstat_send(const char *fname,
struct fxp_attrs attrs);
bool fxp_setstat_recv(struct sftp_packet *pktin, struct sftp_request *req);
struct sftp_request *fxp_fsetstat_send(struct fxp_handle *handle,
struct fxp_attrs attrs);
bool fxp_fsetstat_recv(struct sftp_packet *pktin, struct sftp_request *req);
/*
* Read from a file.
*/
struct sftp_request *fxp_read_send(struct fxp_handle *handle,
uint64_t offset, int len);
int fxp_read_recv(struct sftp_packet *pktin, struct sftp_request *req,
char *buffer, int len);
/*
* Write to a file.
*/
struct sftp_request *fxp_write_send(struct fxp_handle *handle,
void *buffer, uint64_t offset, int len);
bool fxp_write_recv(struct sftp_packet *pktin, struct sftp_request *req);
/*
* Read from a directory.
*/
struct sftp_request *fxp_readdir_send(struct fxp_handle *handle);
struct fxp_names *fxp_readdir_recv(struct sftp_packet *pktin,
struct sftp_request *req);
/*
* Free up an fxp_names structure.
*/
void fxp_free_names(struct fxp_names *names);
/*
* Duplicate and free fxp_name structures.
*/
struct fxp_name *fxp_dup_name(struct fxp_name *name);
void fxp_free_name(struct fxp_name *name);
/*
* Store user data in an sftp_request structure.
*/
void *fxp_get_userdata(struct sftp_request *req);
void fxp_set_userdata(struct sftp_request *req, void *data);
/*
* These functions might well be temporary placeholders to be
* replaced with more useful similar functions later. They form the
* main dispatch loop for processing incoming SFTP responses.
*/
void sftp_register(struct sftp_request *req);
struct sftp_request *sftp_find_request(struct sftp_packet *pktin);
struct sftp_packet *sftp_recv(void);
/*
* A wrapper to go round fxp_read_* and fxp_write_*, which manages
* the queueing of multiple read/write requests.
*/
struct fxp_xfer;
struct fxp_xfer *xfer_download_init(struct fxp_handle *fh, uint64_t offset);
void xfer_download_queue(struct fxp_xfer *xfer);
int xfer_download_gotpkt(struct fxp_xfer *xfer, struct sftp_packet *pktin);
bool xfer_download_data(struct fxp_xfer *xfer, void **buf, int *len);
struct fxp_xfer *xfer_upload_init(struct fxp_handle *fh, uint64_t offset);
bool xfer_upload_ready(struct fxp_xfer *xfer);
void xfer_upload_data(struct fxp_xfer *xfer, char *buffer, int len);
int xfer_upload_gotpkt(struct fxp_xfer *xfer, struct sftp_packet *pktin);
bool xfer_done(struct fxp_xfer *xfer);
void xfer_set_error(struct fxp_xfer *xfer);
void xfer_cleanup(struct fxp_xfer *xfer);
/*
* Vtable for the platform-specific filesystem implementation that
* answers requests in an SFTP server.
*/
typedef struct SftpReplyBuilder SftpReplyBuilder;
struct SftpServer {
const SftpServerVtable *vt;
};
struct SftpServerVtable {
SftpServer *(*new)(const SftpServerVtable *vt);
void (*free)(SftpServer *srv);
/*
* Handle actual filesystem requests.
*
* Each of these functions replies by calling an appropiate
* sftp_reply_foo() function on the given reply packet.
*/
/* Should call fxp_reply_error or fxp_reply_simple_name */
void (*realpath)(SftpServer *srv, SftpReplyBuilder *reply,
ptrlen path);
/* Should call fxp_reply_error or fxp_reply_handle */
void (*open)(SftpServer *srv, SftpReplyBuilder *reply,
ptrlen path, unsigned flags, struct fxp_attrs attrs);
/* Should call fxp_reply_error or fxp_reply_handle */
void (*opendir)(SftpServer *srv, SftpReplyBuilder *reply,
ptrlen path);
/* Should call fxp_reply_error or fxp_reply_ok */
void (*close)(SftpServer *srv, SftpReplyBuilder *reply, ptrlen handle);
/* Should call fxp_reply_error or fxp_reply_ok */
void (*mkdir)(SftpServer *srv, SftpReplyBuilder *reply,
ptrlen path, struct fxp_attrs attrs);
/* Should call fxp_reply_error or fxp_reply_ok */
void (*rmdir)(SftpServer *srv, SftpReplyBuilder *reply, ptrlen path);
/* Should call fxp_reply_error or fxp_reply_ok */
void (*remove)(SftpServer *srv, SftpReplyBuilder *reply, ptrlen path);
/* Should call fxp_reply_error or fxp_reply_ok */
void (*rename)(SftpServer *srv, SftpReplyBuilder *reply,
ptrlen srcpath, ptrlen dstpath);
/* Should call fxp_reply_error or fxp_reply_attrs */
void (*stat)(SftpServer *srv, SftpReplyBuilder *reply, ptrlen path,
bool follow_symlinks);
/* Should call fxp_reply_error or fxp_reply_attrs */
void (*fstat)(SftpServer *srv, SftpReplyBuilder *reply, ptrlen handle);
/* Should call fxp_reply_error or fxp_reply_ok */
void (*setstat)(SftpServer *srv, SftpReplyBuilder *reply,
ptrlen path, struct fxp_attrs attrs);
/* Should call fxp_reply_error or fxp_reply_ok */
void (*fsetstat)(SftpServer *srv, SftpReplyBuilder *reply,
ptrlen handle, struct fxp_attrs attrs);
/* Should call fxp_reply_error or fxp_reply_data */
void (*read)(SftpServer *srv, SftpReplyBuilder *reply,
ptrlen handle, uint64_t offset, unsigned length);
/* Should call fxp_reply_error or fxp_reply_ok */
void (*write)(SftpServer *srv, SftpReplyBuilder *reply,
ptrlen handle, uint64_t offset, ptrlen data);
/* Should call fxp_reply_error, or fxp_reply_name_count once and
* then fxp_reply_full_name that many times */
void (*readdir)(SftpServer *srv, SftpReplyBuilder *reply, ptrlen handle,
int max_entries, bool omit_longname);
};
static inline SftpServer *sftpsrv_new(const SftpServerVtable *vt)
{ return vt->new(vt); }
static inline void sftpsrv_free(SftpServer *srv)
{ srv->vt->free(srv); }
static inline void sftpsrv_realpath(SftpServer *srv, SftpReplyBuilder *reply,
ptrlen path)
{ srv->vt->realpath(srv, reply, path); }
static inline void sftpsrv_open(
SftpServer *srv, SftpReplyBuilder *reply,
ptrlen path, unsigned flags, struct fxp_attrs attrs)
{ srv->vt->open(srv, reply, path, flags, attrs); }
static inline void sftpsrv_opendir(
SftpServer *srv, SftpReplyBuilder *reply, ptrlen path)
{ srv->vt->opendir(srv, reply, path); }
static inline void sftpsrv_close(
SftpServer *srv, SftpReplyBuilder *reply, ptrlen handle)
{ srv->vt->close(srv, reply, handle); }
static inline void sftpsrv_mkdir(SftpServer *srv, SftpReplyBuilder *reply,
ptrlen path, struct fxp_attrs attrs)
{ srv->vt->mkdir(srv, reply, path, attrs); }
static inline void sftpsrv_rmdir(
SftpServer *srv, SftpReplyBuilder *reply, ptrlen path)
{ srv->vt->rmdir(srv, reply, path); }
static inline void sftpsrv_remove(
SftpServer *srv, SftpReplyBuilder *reply, ptrlen path)
{ srv->vt->remove(srv, reply, path); }
static inline void sftpsrv_rename(SftpServer *srv, SftpReplyBuilder *reply,
ptrlen srcpath, ptrlen dstpath)
{ srv->vt->rename(srv, reply, srcpath, dstpath); }
static inline void sftpsrv_stat(
SftpServer *srv, SftpReplyBuilder *reply, ptrlen path, bool follow)
{ srv->vt->stat(srv, reply, path, follow); }
static inline void sftpsrv_fstat(
SftpServer *srv, SftpReplyBuilder *reply, ptrlen handle)
{ srv->vt->fstat(srv, reply, handle); }
static inline void sftpsrv_setstat(SftpServer *srv, SftpReplyBuilder *reply,
ptrlen path, struct fxp_attrs attrs)
{ srv->vt->setstat(srv, reply, path, attrs); }
static inline void sftpsrv_fsetstat(SftpServer *srv, SftpReplyBuilder *reply,
ptrlen handle, struct fxp_attrs attrs)
{ srv->vt->fsetstat(srv, reply, handle, attrs); }
static inline void sftpsrv_read(
SftpServer *srv, SftpReplyBuilder *reply,
ptrlen handle, uint64_t offset, unsigned length)
{ srv->vt->read(srv, reply, handle, offset, length); }
static inline void sftpsrv_write(SftpServer *srv, SftpReplyBuilder *reply,
ptrlen handle, uint64_t offset, ptrlen data)
{ srv->vt->write(srv, reply, handle, offset, data); }
static inline void sftpsrv_readdir(
SftpServer *srv, SftpReplyBuilder *reply, ptrlen handle,
int max_entries, bool omit_longname)
{ srv->vt->readdir(srv, reply, handle, max_entries, omit_longname); }
typedef struct SftpReplyBuilderVtable SftpReplyBuilderVtable;
struct SftpReplyBuilder {
const SftpReplyBuilderVtable *vt;
};
struct SftpReplyBuilderVtable {
void (*reply_ok)(SftpReplyBuilder *reply);
void (*reply_error)(SftpReplyBuilder *reply, unsigned code,
const char *msg);
void (*reply_simple_name)(SftpReplyBuilder *reply, ptrlen name);
void (*reply_name_count)(SftpReplyBuilder *reply, unsigned count);
void (*reply_full_name)(SftpReplyBuilder *reply, ptrlen name,
ptrlen longname, struct fxp_attrs attrs);
void (*reply_handle)(SftpReplyBuilder *reply, ptrlen handle);
void (*reply_data)(SftpReplyBuilder *reply, ptrlen data);
void (*reply_attrs)(SftpReplyBuilder *reply, struct fxp_attrs attrs);
};
static inline void fxp_reply_ok(SftpReplyBuilder *reply)
{ reply->vt->reply_ok(reply); }
static inline void fxp_reply_error(SftpReplyBuilder *reply, unsigned code,
const char *msg)
{ reply->vt->reply_error(reply, code, msg); }
static inline void fxp_reply_simple_name(SftpReplyBuilder *reply, ptrlen name)
{ reply->vt->reply_simple_name(reply, name); }
static inline void fxp_reply_name_count(
SftpReplyBuilder *reply, unsigned count)
{ reply->vt->reply_name_count(reply, count); }
static inline void fxp_reply_full_name(SftpReplyBuilder *reply, ptrlen name,
ptrlen longname, struct fxp_attrs attrs)
{ reply->vt->reply_full_name(reply, name, longname, attrs); }
static inline void fxp_reply_handle(SftpReplyBuilder *reply, ptrlen handle)
{ reply->vt->reply_handle(reply, handle); }
static inline void fxp_reply_data(SftpReplyBuilder *reply, ptrlen data)
{ reply->vt->reply_data(reply, data); }
static inline void fxp_reply_attrs(
SftpReplyBuilder *reply, struct fxp_attrs attrs)
{ reply->vt->reply_attrs(reply, attrs); }
/*
* The usual implementation of an SftpReplyBuilder, containing a
* 'struct sftp_packet' which is assumed to be already initialised
* before one of the above request methods is called.
*/
extern const struct SftpReplyBuilderVtable DefaultSftpReplyBuilder_vt;
typedef struct DefaultSftpReplyBuilder DefaultSftpReplyBuilder;
struct DefaultSftpReplyBuilder {
SftpReplyBuilder rb;
struct sftp_packet *pkt;
};
/*
* The top-level function that handles an SFTP request, given an
* implementation of the above SftpServer abstraction to do the actual
* filesystem work. It handles all the marshalling and unmarshalling
* of packets, and the copying of request ids into the responses.
*/
struct sftp_packet *sftp_handle_request(
SftpServer *srv, struct sftp_packet *request);
/* ----------------------------------------------------------------------
* Not exactly SFTP-related, but here's a system that implements an
* old-fashioned SCP server module, given an SftpServer vtable to use
* as its underlying filesystem access.
*/
typedef struct ScpServer ScpServer;
typedef struct ScpServerVtable ScpServerVtable;
struct ScpServer {
const struct ScpServerVtable *vt;
};
struct ScpServerVtable {
void (*free)(ScpServer *s);
size_t (*send)(ScpServer *s, const void *data, size_t length);
void (*throttle)(ScpServer *s, bool throttled);
void (*eof)(ScpServer *s);
};
static inline void scp_free(ScpServer *s)
{ s->vt->free(s); }
static inline size_t scp_send(ScpServer *s, const void *data, size_t length)
{ return s->vt->send(s, data, length); }
static inline void scp_throttle(ScpServer *s, bool throttled)
{ s->vt->throttle(s, throttled); }
static inline void scp_eof(ScpServer *s)
{ s->vt->eof(s); }
/*
* Create an ScpServer by calling this function, giving it the command
* you received from the SSH client to execute. If that command is
* recognised as an scp command, it will construct an ScpServer object
* and return it; otherwise, it will return NULL, and you should
* execute the command in whatever way you normally would.
*
* The ScpServer will generate output for the client by writing it to
* the provided SshChannel using sshfwd_write; you pass it input using
* the send method in its own vtable.
*/
ScpServer *scp_recognise_exec(
SshChannel *sc, const SftpServerVtable *sftpserver_vt, ptrlen command);

139
ssh/sftpcommon.c Normal file
View File

@ -0,0 +1,139 @@
/*
* sftpcommon.c: SFTP code shared between client and server.
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <assert.h>
#include <limits.h>
#include "misc.h"
#include "sftp.h"
static void sftp_pkt_BinarySink_write(
BinarySink *bs, const void *data, size_t length)
{
struct sftp_packet *pkt = BinarySink_DOWNCAST(bs, struct sftp_packet);
assert(length <= 0xFFFFFFFFU - pkt->length);
sgrowarrayn_nm(pkt->data, pkt->maxlen, pkt->length, length);
memcpy(pkt->data + pkt->length, data, length);
pkt->length += length;
}
struct sftp_packet *sftp_pkt_init(int type)
{
struct sftp_packet *pkt;
pkt = snew(struct sftp_packet);
pkt->data = NULL;
pkt->savedpos = -1;
pkt->length = 0;
pkt->maxlen = 0;
pkt->type = type;
BinarySink_INIT(pkt, sftp_pkt_BinarySink_write);
put_uint32(pkt, 0); /* length field will be filled in later */
put_byte(pkt, 0); /* so will the type field */
return pkt;
}
void BinarySink_put_fxp_attrs(BinarySink *bs, struct fxp_attrs attrs)
{
put_uint32(bs, attrs.flags);
if (attrs.flags & SSH_FILEXFER_ATTR_SIZE)
put_uint64(bs, attrs.size);
if (attrs.flags & SSH_FILEXFER_ATTR_UIDGID) {
put_uint32(bs, attrs.uid);
put_uint32(bs, attrs.gid);
}
if (attrs.flags & SSH_FILEXFER_ATTR_PERMISSIONS) {
put_uint32(bs, attrs.permissions);
}
if (attrs.flags & SSH_FILEXFER_ATTR_ACMODTIME) {
put_uint32(bs, attrs.atime);
put_uint32(bs, attrs.mtime);
}
if (attrs.flags & SSH_FILEXFER_ATTR_EXTENDED) {
/*
* We currently don't support sending any extended
* attributes.
*/
}
}
const struct fxp_attrs no_attrs = { 0 };
#define put_fxp_attrs(bs, attrs) \
BinarySink_put_fxp_attrs(BinarySink_UPCAST(bs), attrs)
bool BinarySource_get_fxp_attrs(BinarySource *src, struct fxp_attrs *attrs)
{
attrs->flags = get_uint32(src);
if (attrs->flags & SSH_FILEXFER_ATTR_SIZE)
attrs->size = get_uint64(src);
if (attrs->flags & SSH_FILEXFER_ATTR_UIDGID) {
attrs->uid = get_uint32(src);
attrs->gid = get_uint32(src);
}
if (attrs->flags & SSH_FILEXFER_ATTR_PERMISSIONS)
attrs->permissions = get_uint32(src);
if (attrs->flags & SSH_FILEXFER_ATTR_ACMODTIME) {
attrs->atime = get_uint32(src);
attrs->mtime = get_uint32(src);
}
if (attrs->flags & SSH_FILEXFER_ATTR_EXTENDED) {
unsigned long count = get_uint32(src);
while (count--) {
if (get_err(src)) {
/* Truncated packet. Don't waste time looking for
* attributes that aren't there. Caller should spot
* the truncation. */
break;
}
/*
* We should try to analyse these, if we ever find one
* we recognise.
*/
get_string(src);
get_string(src);
}
}
return true;
}
void sftp_pkt_free(struct sftp_packet *pkt)
{
if (pkt->data)
sfree(pkt->data);
sfree(pkt);
}
void sftp_send_prepare(struct sftp_packet *pkt)
{
PUT_32BIT_MSB_FIRST(pkt->data, pkt->length - 4);
if (pkt->length >= 5) {
/* Rewrite the type code, in case the caller changed its mind
* about pkt->type since calling sftp_pkt_init */
pkt->data[4] = pkt->type;
}
}
struct sftp_packet *sftp_recv_prepare(unsigned length)
{
struct sftp_packet *pkt;
pkt = snew(struct sftp_packet);
pkt->savedpos = 0;
pkt->length = pkt->maxlen = length;
pkt->data = snewn(pkt->length, char);
return pkt;
}
bool sftp_recv_finish(struct sftp_packet *pkt)
{
BinarySource_INIT(pkt, pkt->data, pkt->length);
pkt->type = get_byte(pkt);
return !get_err(pkt);
}

279
ssh/sftpserver.c Normal file
View File

@ -0,0 +1,279 @@
/*
* Implement the centralised parts of the server side of SFTP.
*/
#include <assert.h>
#include <stdio.h>
#include <stdlib.h>
#include "putty.h"
#include "ssh.h"
#include "sftp.h"
struct sftp_packet *sftp_handle_request(
SftpServer *srv, struct sftp_packet *req)
{
struct sftp_packet *reply;
unsigned id;
uint32_t flags;
ptrlen path, dstpath, handle, data;
uint64_t offset;
unsigned length;
struct fxp_attrs attrs;
DefaultSftpReplyBuilder dsrb;
SftpReplyBuilder *rb;
if (req->type == SSH_FXP_INIT) {
/*
* Special case which doesn't have a request id at the start.
*/
reply = sftp_pkt_init(SSH_FXP_VERSION);
/*
* Since we support only the lowest protocol version, we don't
* need to take the min of this and the client's version, or
* even to bother reading the client version number out of the
* input packet.
*/
put_uint32(reply, SFTP_PROTO_VERSION);
return reply;
}
/*
* Centralise the request id handling. We'll overwrite the type
* code of the output packet later.
*/
id = get_uint32(req);
reply = sftp_pkt_init(0);
put_uint32(reply, id);
dsrb.rb.vt = &DefaultSftpReplyBuilder_vt;
dsrb.pkt = reply;
rb = &dsrb.rb;
switch (req->type) {
case SSH_FXP_REALPATH:
path = get_string(req);
if (get_err(req))
goto decode_error;
sftpsrv_realpath(srv, rb, path);
break;
case SSH_FXP_OPEN:
path = get_string(req);
flags = get_uint32(req);
get_fxp_attrs(req, &attrs);
if (get_err(req))
goto decode_error;
if ((flags & (SSH_FXF_READ|SSH_FXF_WRITE)) == 0) {
fxp_reply_error(rb, SSH_FX_BAD_MESSAGE,
"open without READ or WRITE flag");
} else if ((flags & (SSH_FXF_CREAT|SSH_FXF_TRUNC)) == SSH_FXF_TRUNC) {
fxp_reply_error(rb, SSH_FX_BAD_MESSAGE,
"open with TRUNC but not CREAT");
} else if ((flags & (SSH_FXF_CREAT|SSH_FXF_EXCL)) == SSH_FXF_EXCL) {
fxp_reply_error(rb, SSH_FX_BAD_MESSAGE,
"open with EXCL but not CREAT");
} else {
sftpsrv_open(srv, rb, path, flags, attrs);
}
break;
case SSH_FXP_OPENDIR:
path = get_string(req);
if (get_err(req))
goto decode_error;
sftpsrv_opendir(srv, rb, path);
break;
case SSH_FXP_CLOSE:
handle = get_string(req);
if (get_err(req))
goto decode_error;
sftpsrv_close(srv, rb, handle);
break;
case SSH_FXP_MKDIR:
path = get_string(req);
get_fxp_attrs(req, &attrs);
if (get_err(req))
goto decode_error;
sftpsrv_mkdir(srv, rb, path, attrs);
break;
case SSH_FXP_RMDIR:
path = get_string(req);
if (get_err(req))
goto decode_error;
sftpsrv_rmdir(srv, rb, path);
break;
case SSH_FXP_REMOVE:
path = get_string(req);
if (get_err(req))
goto decode_error;
sftpsrv_remove(srv, rb, path);
break;
case SSH_FXP_RENAME:
path = get_string(req);
dstpath = get_string(req);
if (get_err(req))
goto decode_error;
sftpsrv_rename(srv, rb, path, dstpath);
break;
case SSH_FXP_STAT:
path = get_string(req);
if (get_err(req))
goto decode_error;
sftpsrv_stat(srv, rb, path, true);
break;
case SSH_FXP_LSTAT:
path = get_string(req);
if (get_err(req))
goto decode_error;
sftpsrv_stat(srv, rb, path, false);
break;
case SSH_FXP_FSTAT:
handle = get_string(req);
if (get_err(req))
goto decode_error;
sftpsrv_fstat(srv, rb, handle);
break;
case SSH_FXP_SETSTAT:
path = get_string(req);
get_fxp_attrs(req, &attrs);
if (get_err(req))
goto decode_error;
sftpsrv_setstat(srv, rb, path, attrs);
break;
case SSH_FXP_FSETSTAT:
handle = get_string(req);
get_fxp_attrs(req, &attrs);
if (get_err(req))
goto decode_error;
sftpsrv_fsetstat(srv, rb, handle, attrs);
break;
case SSH_FXP_READ:
handle = get_string(req);
offset = get_uint64(req);
length = get_uint32(req);
if (get_err(req))
goto decode_error;
sftpsrv_read(srv, rb, handle, offset, length);
break;
case SSH_FXP_READDIR:
handle = get_string(req);
if (get_err(req))
goto decode_error;
sftpsrv_readdir(srv, rb, handle, INT_MAX, false);
break;
case SSH_FXP_WRITE:
handle = get_string(req);
offset = get_uint64(req);
data = get_string(req);
if (get_err(req))
goto decode_error;
sftpsrv_write(srv, rb, handle, offset, data);
break;
default:
if (get_err(req))
goto decode_error;
fxp_reply_error(rb, SSH_FX_OP_UNSUPPORTED,
"Unrecognised request type");
break;
decode_error:
fxp_reply_error(rb, SSH_FX_BAD_MESSAGE, "Unable to decode request");
}
return reply;
}
static void default_reply_ok(SftpReplyBuilder *reply)
{
DefaultSftpReplyBuilder *d =
container_of(reply, DefaultSftpReplyBuilder, rb);
d->pkt->type = SSH_FXP_STATUS;
put_uint32(d->pkt, SSH_FX_OK);
put_stringz(d->pkt, "");
}
static void default_reply_error(
SftpReplyBuilder *reply, unsigned code, const char *msg)
{
DefaultSftpReplyBuilder *d =
container_of(reply, DefaultSftpReplyBuilder, rb);
d->pkt->type = SSH_FXP_STATUS;
put_uint32(d->pkt, code);
put_stringz(d->pkt, msg);
}
static void default_reply_name_count(SftpReplyBuilder *reply, unsigned count)
{
DefaultSftpReplyBuilder *d =
container_of(reply, DefaultSftpReplyBuilder, rb);
d->pkt->type = SSH_FXP_NAME;
put_uint32(d->pkt, count);
}
static void default_reply_full_name(SftpReplyBuilder *reply, ptrlen name,
ptrlen longname, struct fxp_attrs attrs)
{
DefaultSftpReplyBuilder *d =
container_of(reply, DefaultSftpReplyBuilder, rb);
d->pkt->type = SSH_FXP_NAME;
put_stringpl(d->pkt, name);
put_stringpl(d->pkt, longname);
put_fxp_attrs(d->pkt, attrs);
}
static void default_reply_simple_name(SftpReplyBuilder *reply, ptrlen name)
{
fxp_reply_name_count(reply, 1);
fxp_reply_full_name(reply, name, PTRLEN_LITERAL(""), no_attrs);
}
static void default_reply_handle(SftpReplyBuilder *reply, ptrlen handle)
{
DefaultSftpReplyBuilder *d =
container_of(reply, DefaultSftpReplyBuilder, rb);
d->pkt->type = SSH_FXP_HANDLE;
put_stringpl(d->pkt, handle);
}
static void default_reply_data(SftpReplyBuilder *reply, ptrlen data)
{
DefaultSftpReplyBuilder *d =
container_of(reply, DefaultSftpReplyBuilder, rb);
d->pkt->type = SSH_FXP_DATA;
put_stringpl(d->pkt, data);
}
static void default_reply_attrs(
SftpReplyBuilder *reply, struct fxp_attrs attrs)
{
DefaultSftpReplyBuilder *d =
container_of(reply, DefaultSftpReplyBuilder, rb);
d->pkt->type = SSH_FXP_ATTRS;
put_fxp_attrs(d->pkt, attrs);
}
const SftpReplyBuilderVtable DefaultSftpReplyBuilder_vt = {
.reply_ok = default_reply_ok,
.reply_error = default_reply_error,
.reply_simple_name = default_reply_simple_name,
.reply_name_count = default_reply_name_count,
.reply_full_name = default_reply_full_name,
.reply_handle = default_reply_handle,
.reply_data = default_reply_data,
.reply_attrs = default_reply_attrs,
};

2180
ssh/sharing.c Normal file

File diff suppressed because it is too large Load Diff

53
ssh/signal-list.h Normal file
View File

@ -0,0 +1,53 @@
/*
* List of signal names known to SSH, indicating whether PuTTY's UI
* for special session commands likes to put them in the main specials
* menu or in a submenu (and if the former, what title they have).
*
* This is a separate header file rather than my usual style of a
* parametric list macro, because in this case I need to be able to
* #ifdef out each mode in case it's not defined on a particular
* target system.
*
* If you want only the locally defined signals, #define
* SIGNALS_LOCAL_ONLY before including this header.
*/
#if !defined SIGNALS_LOCAL_ONLY || defined SIGINT
SIGNAL_MAIN(INT, "Interrupt")
#endif
#if !defined SIGNALS_LOCAL_ONLY || defined SIGTERM
SIGNAL_MAIN(TERM, "Terminate")
#endif
#if !defined SIGNALS_LOCAL_ONLY || defined SIGKILL
SIGNAL_MAIN(KILL, "Kill")
#endif
#if !defined SIGNALS_LOCAL_ONLY || defined SIGQUIT
SIGNAL_MAIN(QUIT, "Quit")
#endif
#if !defined SIGNALS_LOCAL_ONLY || defined SIGHUP
SIGNAL_MAIN(HUP, "Hangup")
#endif
#if !defined SIGNALS_LOCAL_ONLY || defined SIGABRT
SIGNAL_SUB(ABRT)
#endif
#if !defined SIGNALS_LOCAL_ONLY || defined SIGALRM
SIGNAL_SUB(ALRM)
#endif
#if !defined SIGNALS_LOCAL_ONLY || defined SIGFPE
SIGNAL_SUB(FPE)
#endif
#if !defined SIGNALS_LOCAL_ONLY || defined SIGILL
SIGNAL_SUB(ILL)
#endif
#if !defined SIGNALS_LOCAL_ONLY || defined SIGPIPE
SIGNAL_SUB(PIPE)
#endif
#if !defined SIGNALS_LOCAL_ONLY || defined SIGSEGV
SIGNAL_SUB(SEGV)
#endif
#if !defined SIGNALS_LOCAL_ONLY || defined SIGUSR1
SIGNAL_SUB(USR1)
#endif
#if !defined SIGNALS_LOCAL_ONLY || defined SIGUSR2
SIGNAL_SUB(USR2)
#endif

1248
ssh/ssh.c Normal file

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,126 @@
/*
* Data structure managing host keys in sessions based on GSSAPI KEX.
*
* In a session we started with a GSSAPI key exchange, the concept of
* 'host key' has completely different lifetime and security semantics
* from the usual ones. Per RFC 4462 section 2.1, we assume that any
* host key delivered to us in the course of a GSSAPI key exchange is
* _solely_ there to use as a transient fallback within the same
* session, if at the time of a subsequent rekey the GSS credentials
* are temporarily invalid and so a non-GSS KEX method has to be used.
*
* In particular, in a GSS-based SSH deployment, host keys may not
* even _be_ persistent identities for the server; it would be
* legitimate for a server to generate a fresh one routinely if it
* wanted to, like SSH-1 server keys.
*
* So, in this mode, we never touch the persistent host key cache at
* all, either to check keys against it _or_ to store keys in it.
* Instead, we maintain an in-memory cache of host keys that have been
* mentioned in GSS key exchanges within this particular session, and
* we permit precisely those host keys in non-GSS rekeys.
*/
#include <assert.h>
#include "putty.h"
#include "ssh.h"
struct ssh_transient_hostkey_cache {
tree234 *cache;
};
struct ssh_transient_hostkey_cache_entry {
const ssh_keyalg *alg;
strbuf *pub_blob;
};
static int ssh_transient_hostkey_cache_cmp(void *av, void *bv)
{
const struct ssh_transient_hostkey_cache_entry
*a = (const struct ssh_transient_hostkey_cache_entry *)av,
*b = (const struct ssh_transient_hostkey_cache_entry *)bv;
return strcmp(a->alg->ssh_id, b->alg->ssh_id);
}
static int ssh_transient_hostkey_cache_find(void *av, void *bv)
{
const ssh_keyalg *aalg = (const ssh_keyalg *)av;
const struct ssh_transient_hostkey_cache_entry
*b = (const struct ssh_transient_hostkey_cache_entry *)bv;
return strcmp(aalg->ssh_id, b->alg->ssh_id);
}
ssh_transient_hostkey_cache *ssh_transient_hostkey_cache_new(void)
{
ssh_transient_hostkey_cache *thc = snew(ssh_transient_hostkey_cache);
thc->cache = newtree234(ssh_transient_hostkey_cache_cmp);
return thc;
}
void ssh_transient_hostkey_cache_free(ssh_transient_hostkey_cache *thc)
{
struct ssh_transient_hostkey_cache_entry *ent;
while ((ent = delpos234(thc->cache, 0)) != NULL) {
strbuf_free(ent->pub_blob);
sfree(ent);
}
freetree234(thc->cache);
sfree(thc);
}
void ssh_transient_hostkey_cache_add(
ssh_transient_hostkey_cache *thc, ssh_key *key)
{
struct ssh_transient_hostkey_cache_entry *ent, *retd;
if ((ent = find234(thc->cache, (void *)ssh_key_alg(key),
ssh_transient_hostkey_cache_find)) != NULL) {
del234(thc->cache, ent);
strbuf_free(ent->pub_blob);
sfree(ent);
}
ent = snew(struct ssh_transient_hostkey_cache_entry);
ent->alg = ssh_key_alg(key);
ent->pub_blob = strbuf_new();
ssh_key_public_blob(key, BinarySink_UPCAST(ent->pub_blob));
retd = add234(thc->cache, ent);
assert(retd == ent);
}
bool ssh_transient_hostkey_cache_verify(
ssh_transient_hostkey_cache *thc, ssh_key *key)
{
struct ssh_transient_hostkey_cache_entry *ent;
bool toret = false;
if ((ent = find234(thc->cache, (void *)ssh_key_alg(key),
ssh_transient_hostkey_cache_find)) != NULL) {
strbuf *this_blob = strbuf_new();
ssh_key_public_blob(key, BinarySink_UPCAST(this_blob));
if (this_blob->len == ent->pub_blob->len &&
!memcmp(this_blob->s, ent->pub_blob->s,
this_blob->len))
toret = true;
strbuf_free(this_blob);
}
return toret;
}
bool ssh_transient_hostkey_cache_has(
ssh_transient_hostkey_cache *thc, const ssh_keyalg *alg)
{
struct ssh_transient_hostkey_cache_entry *ent =
find234(thc->cache, (void *)alg,
ssh_transient_hostkey_cache_find);
return ent != NULL;
}
bool ssh_transient_hostkey_cache_non_empty(ssh_transient_hostkey_cache *thc)
{
return count234(thc->cache) > 0;
}

2181
ssh/transport2.c Normal file

File diff suppressed because it is too large Load Diff

245
ssh/transport2.h Normal file
View File

@ -0,0 +1,245 @@
/*
* Header connecting the pieces of the SSH-2 transport layer.
*/
#ifndef PUTTY_SSH2TRANSPORT_H
#define PUTTY_SSH2TRANSPORT_H
#ifndef NO_GSSAPI
#include "gssc.h"
#include "gss.h"
#define MIN_CTXT_LIFETIME 5 /* Avoid rekey with short lifetime (seconds) */
#define GSS_KEX_CAPABLE (1<<0) /* Can do GSS KEX */
#define GSS_CRED_UPDATED (1<<1) /* Cred updated since previous delegation */
#define GSS_CTXT_EXPIRES (1<<2) /* Context expires before next timer */
#define GSS_CTXT_MAYFAIL (1<<3) /* Context may expire during handshake */
#endif
#define DH_MIN_SIZE 1024
#define DH_MAX_SIZE 8192
#define MAXKEXLIST 16
struct kexinit_algorithm {
const char *name;
union {
struct {
const ssh_kex *kex;
bool warn;
} kex;
struct {
const ssh_keyalg *hostkey;
unsigned hkflags;
bool warn;
} hk;
struct {
const ssh_cipheralg *cipher;
bool warn;
} cipher;
struct {
const ssh2_macalg *mac;
bool etm;
} mac;
struct {
const ssh_compression_alg *comp;
bool delayed;
} comp;
} u;
};
#define HOSTKEY_ALGORITHMS(X) \
X(HK_ED25519, ssh_ecdsa_ed25519) \
X(HK_ED448, ssh_ecdsa_ed448) \
X(HK_ECDSA, ssh_ecdsa_nistp256) \
X(HK_ECDSA, ssh_ecdsa_nistp384) \
X(HK_ECDSA, ssh_ecdsa_nistp521) \
X(HK_DSA, ssh_dss) \
X(HK_RSA, ssh_rsa_sha512) \
X(HK_RSA, ssh_rsa_sha256) \
X(HK_RSA, ssh_rsa) \
/* end of list */
#define COUNT_HOSTKEY_ALGORITHM(type, alg) +1
#define N_HOSTKEY_ALGORITHMS (0 HOSTKEY_ALGORITHMS(COUNT_HOSTKEY_ALGORITHM))
struct ssh_signkey_with_user_pref_id {
const ssh_keyalg *alg;
int id;
};
extern const struct ssh_signkey_with_user_pref_id
ssh2_hostkey_algs[N_HOSTKEY_ALGORITHMS];
/*
* Enumeration of high-level classes of reason why we might need to do
* a repeat key exchange. A full detailed reason in human-readable
* string form for the Event Log is also provided, but this enum type
* is used to discriminate between classes of reason that the code
* needs to treat differently.
*
* RK_NONE == 0 is the value indicating that no rekey is currently
* needed at all. RK_INITIAL indicates that we haven't even done the
* _first_ key exchange yet. RK_SERVER indicates that we're rekeying
* because the server asked for it, not because we decided it
* ourselves. RK_NORMAL is the usual case. RK_GSS_UPDATE indicates
* that we're rekeying because we've just got new GSSAPI credentials
* (hence there's no point in doing a preliminary check for new GSS
* creds, because we already know the answer); RK_POST_USERAUTH
* indicates that _if_ we're going to need a post-userauth immediate
* rekey for any reason, this is the moment to do it.
*
* So RK_POST_USERAUTH only tells the transport layer to _consider_
* rekeying, not to definitely do it. Also, that one enum value is
* special in that the user-readable reason text is passed in to the
* transport layer as NULL, whereas fills in the reason text after it
* decides whether it needs a rekey at all. In the other cases,
* rekey_reason is passed in to the at the same time as rekey_class.
*/
typedef enum RekeyClass {
RK_NONE = 0,
RK_INITIAL,
RK_SERVER,
RK_NORMAL,
RK_POST_USERAUTH,
RK_GSS_UPDATE
} RekeyClass;
typedef struct transport_direction {
const ssh_cipheralg *cipher;
const ssh2_macalg *mac;
bool etm_mode;
const ssh_compression_alg *comp;
bool comp_delayed;
int mkkey_adjust;
} transport_direction;
struct ssh2_transport_state {
int crState, crStateKex;
PacketProtocolLayer *higher_layer;
PktInQueue pq_in_higher;
PktOutQueue pq_out_higher;
IdempotentCallback ic_pq_out_higher;
Conf *conf;
char *savedhost;
int savedport;
const char *rekey_reason;
enum RekeyClass rekey_class;
unsigned long max_data_size;
const ssh_kex *kex_alg;
const ssh_keyalg *hostkey_alg;
char *hostkey_str; /* string representation, for easy checking in rekeys */
unsigned char session_id[MAX_HASH_LEN];
int session_id_len;
int dh_min_size, dh_max_size;
bool dh_got_size_bounds;
dh_ctx *dh_ctx;
ssh_hash *exhash;
struct DataTransferStats *stats;
const SshServerConfig *ssc;
char *client_greeting, *server_greeting;
bool kex_in_progress;
unsigned long next_rekey, last_rekey;
const char *deferred_rekey_reason;
bool higher_layer_ok;
/*
* Fully qualified host name, which we need if doing GSSAPI.
*/
char *fullhostname;
/* shgss is outside the ifdef on purpose to keep APIs simple. If
* NO_GSSAPI is not defined, then it's just an opaque structure
* tag and the pointer will be NULL. */
struct ssh_connection_shared_gss_state *shgss;
#ifndef NO_GSSAPI
int gss_status;
time_t gss_cred_expiry; /* Re-delegate if newer */
unsigned long gss_ctxt_lifetime; /* Re-delegate when short */
#endif
ssh_transient_hostkey_cache *thc;
bool gss_kex_used;
int nbits, pbits;
bool warn_kex, warn_hk, warn_cscipher, warn_sccipher;
mp_int *p, *g, *e, *f, *K;
strbuf *outgoing_kexinit, *incoming_kexinit;
strbuf *client_kexinit, *server_kexinit; /* aliases to the above */
int kex_init_value, kex_reply_value;
transport_direction in, out, *cstrans, *sctrans;
ptrlen hostkeydata, sigdata;
strbuf *hostkeyblob;
char *keystr;
ssh_key *hkey; /* actual host key */
unsigned hkflags; /* signing flags, used in server */
RSAKey *rsa_kex_key; /* for RSA kex */
bool rsa_kex_key_needs_freeing;
ecdh_key *ecdh_key; /* for ECDH kex */
unsigned char exchange_hash[MAX_HASH_LEN];
bool can_gssapi_keyex;
bool need_gss_transient_hostkey;
bool warned_about_no_gss_transient_hostkey;
bool got_session_id;
bool can_send_ext_info, post_newkeys_ext_info;
int dlgret;
bool guessok;
bool ignorepkt;
struct kexinit_algorithm kexlists[NKEXLIST][MAXKEXLIST];
#ifndef NO_GSSAPI
Ssh_gss_buf gss_buf;
Ssh_gss_buf gss_rcvtok, gss_sndtok;
Ssh_gss_stat gss_stat;
Ssh_gss_buf mic;
bool init_token_sent;
bool complete_rcvd;
bool gss_delegate;
#endif
/* List of crypto primitives below the warning threshold that the
* user has already clicked OK to, so that we don't keep asking
* about them again during rekeys. This directly stores pointers
* to the algorithm vtables, compared by pointer value (which is
* not a determinism hazard, because we're only using it as a
* set). */
tree234 *weak_algorithms_consented_to;
/*
* List of host key algorithms for which we _don't_ have a stored
* host key. These are indices into the main hostkey_algs[] array
*/
int uncert_hostkeys[N_HOSTKEY_ALGORITHMS];
int n_uncert_hostkeys;
/*
* Indicate that the current rekey is intended to finish with a
* newly cross-certified host key. To double-check that we
* certified the right one, we set this to point to the host key
* algorithm we expect it to be.
*/
const ssh_keyalg *cross_certifying;
ssh_key *const *hostkeys;
int nhostkeys;
PacketProtocolLayer ppl;
};
/* Helpers shared between transport and kex */
PktIn *ssh2_transport_pop(struct ssh2_transport_state *s);
void ssh2_transport_dialog_callback(void *, int);
/* Provided by transport for use in kex */
void ssh2transport_finalise_exhash(struct ssh2_transport_state *s);
/* Provided by kex for use in transport. Must set the 'aborted' flag
* if it throws a connection-terminating error, so that the caller
* won't have to check that by looking inside its state parameter
* which might already have been freed. */
void ssh2kex_coroutine(struct ssh2_transport_state *s, bool *aborted);
#endif /* PUTTY_SSH2TRANSPORT_H */

179
ssh/ttymode-list.h Normal file
View File

@ -0,0 +1,179 @@
/*
* List of SSH terminal modes, indicating whether SSH types them as
* char or boolean, and if they're boolean, which POSIX flags field of
* a termios structure they appear in, and what bit mask removes them
* (e.g. CS7 and CS8 aren't single bits).
*
* Sources: RFC 4254, SSH-1 RFC-1.2.31, POSIX 2017, and the Linux
* termios manpage for flags not specified by POSIX.
*
* This is a separate header file rather than my usual style of a
* parametric list macro, because in this case I need to be able to
* #ifdef out each mode in case it's not defined on a particular
* target system.
*
* If you want only the locally defined modes, #define
* TTYMODES_LOCAL_ONLY before including this header.
*/
#if !defined TTYMODES_LOCAL_ONLY || defined VINTR
TTYMODE_CHAR(INTR, 1, VINTR)
#endif
#if !defined TTYMODES_LOCAL_ONLY || defined VQUIT
TTYMODE_CHAR(QUIT, 2, VQUIT)
#endif
#if !defined TTYMODES_LOCAL_ONLY || defined VERASE
TTYMODE_CHAR(ERASE, 3, VERASE)
#endif
#if !defined TTYMODES_LOCAL_ONLY || defined VKILL
TTYMODE_CHAR(KILL, 4, VKILL)
#endif
#if !defined TTYMODES_LOCAL_ONLY || defined VEOF
TTYMODE_CHAR(EOF, 5, VEOF)
#endif
#if !defined TTYMODES_LOCAL_ONLY || defined VEOL
TTYMODE_CHAR(EOL, 6, VEOL)
#endif
#if !defined TTYMODES_LOCAL_ONLY || defined VEOL2
TTYMODE_CHAR(EOL2, 7, VEOL2)
#endif
#if !defined TTYMODES_LOCAL_ONLY || defined VSTART
TTYMODE_CHAR(START, 8, VSTART)
#endif
#if !defined TTYMODES_LOCAL_ONLY || defined VSTOP
TTYMODE_CHAR(STOP, 9, VSTOP)
#endif
#if !defined TTYMODES_LOCAL_ONLY || defined VSUSP
TTYMODE_CHAR(SUSP, 10, VSUSP)
#endif
#if !defined TTYMODES_LOCAL_ONLY || defined VDSUSP
TTYMODE_CHAR(DSUSP, 11, VDSUSP)
#endif
#if !defined TTYMODES_LOCAL_ONLY || defined VREPRINT
TTYMODE_CHAR(REPRINT, 12, VREPRINT)
#endif
#if !defined TTYMODES_LOCAL_ONLY || defined VWERASE
TTYMODE_CHAR(WERASE, 13, VWERASE)
#endif
#if !defined TTYMODES_LOCAL_ONLY || defined VLNEXT
TTYMODE_CHAR(LNEXT, 14, VLNEXT)
#endif
#if !defined TTYMODES_LOCAL_ONLY || defined VFLUSH
TTYMODE_CHAR(FLUSH, 15, VFLUSH)
#endif
#if !defined TTYMODES_LOCAL_ONLY || defined VSWTCH
TTYMODE_CHAR(SWTCH, 16, VSWTCH)
#endif
#if !defined TTYMODES_LOCAL_ONLY || defined VSTATUS
TTYMODE_CHAR(STATUS, 17, VSTATUS)
#endif
#if !defined TTYMODES_LOCAL_ONLY || defined VDISCARD
TTYMODE_CHAR(DISCARD, 18, VDISCARD)
#endif
#if !defined TTYMODES_LOCAL_ONLY || defined IGNPAR
TTYMODE_FLAG(IGNPAR, 30, i, IGNPAR)
#endif
#if !defined TTYMODES_LOCAL_ONLY || defined PARMRK
TTYMODE_FLAG(PARMRK, 31, i, PARMRK)
#endif
#if !defined TTYMODES_LOCAL_ONLY || defined INPCK
TTYMODE_FLAG(INPCK, 32, i, INPCK)
#endif
#if !defined TTYMODES_LOCAL_ONLY || defined ISTRIP
TTYMODE_FLAG(ISTRIP, 33, i, ISTRIP)
#endif
#if !defined TTYMODES_LOCAL_ONLY || defined INLCR
TTYMODE_FLAG(INLCR, 34, i, INLCR)
#endif
#if !defined TTYMODES_LOCAL_ONLY || defined IGNCR
TTYMODE_FLAG(IGNCR, 35, i, IGNCR)
#endif
#if !defined TTYMODES_LOCAL_ONLY || defined ICRNL
TTYMODE_FLAG(ICRNL, 36, i, ICRNL)
#endif
#if !defined TTYMODES_LOCAL_ONLY || defined IUCLC
TTYMODE_FLAG(IUCLC, 37, i, IUCLC)
#endif
#if !defined TTYMODES_LOCAL_ONLY || defined IXON
TTYMODE_FLAG(IXON, 38, i, IXON)
#endif
#if !defined TTYMODES_LOCAL_ONLY || defined IXANY
TTYMODE_FLAG(IXANY, 39, i, IXANY)
#endif
#if !defined TTYMODES_LOCAL_ONLY || defined IXOFF
TTYMODE_FLAG(IXOFF, 40, i, IXOFF)
#endif
#if !defined TTYMODES_LOCAL_ONLY || defined IMAXBEL
TTYMODE_FLAG(IMAXBEL, 41, i, IMAXBEL)
#endif
#if !defined TTYMODES_LOCAL_ONLY || defined IUTF8
TTYMODE_FLAG(IUTF8, 42, i, IUTF8)
#endif
#if !defined TTYMODES_LOCAL_ONLY || defined ISIG
TTYMODE_FLAG(ISIG, 50, l, ISIG)
#endif
#if !defined TTYMODES_LOCAL_ONLY || defined ICANON
TTYMODE_FLAG(ICANON, 51, l, ICANON)
#endif
#if !defined TTYMODES_LOCAL_ONLY || defined XCASE
TTYMODE_FLAG(XCASE, 52, l, XCASE)
#endif
#if !defined TTYMODES_LOCAL_ONLY || defined ECHO
TTYMODE_FLAG(ECHO, 53, l, ECHO)
#endif
#if !defined TTYMODES_LOCAL_ONLY || defined ECHOE
TTYMODE_FLAG(ECHOE, 54, l, ECHOE)
#endif
#if !defined TTYMODES_LOCAL_ONLY || defined ECHOK
TTYMODE_FLAG(ECHOK, 55, l, ECHOK)
#endif
#if !defined TTYMODES_LOCAL_ONLY || defined ECHONL
TTYMODE_FLAG(ECHONL, 56, l, ECHONL)
#endif
#if !defined TTYMODES_LOCAL_ONLY || defined NOFLSH
TTYMODE_FLAG(NOFLSH, 57, l, NOFLSH)
#endif
#if !defined TTYMODES_LOCAL_ONLY || defined TOSTOP
TTYMODE_FLAG(TOSTOP, 58, l, TOSTOP)
#endif
#if !defined TTYMODES_LOCAL_ONLY || defined IEXTEN
TTYMODE_FLAG(IEXTEN, 59, l, IEXTEN)
#endif
#if !defined TTYMODES_LOCAL_ONLY || defined ECHOCTL
TTYMODE_FLAG(ECHOCTL, 60, l, ECHOCTL)
#endif
#if !defined TTYMODES_LOCAL_ONLY || defined ECHOKE
TTYMODE_FLAG(ECHOKE, 61, l, ECHOKE)
#endif
#if !defined TTYMODES_LOCAL_ONLY || defined PENDIN
TTYMODE_FLAG(PENDIN, 62, l, PENDIN)
#endif
#if !defined TTYMODES_LOCAL_ONLY || defined OPOST
TTYMODE_FLAG(OPOST, 70, o, OPOST)
#endif
#if !defined TTYMODES_LOCAL_ONLY || defined OLCUC
TTYMODE_FLAG(OLCUC, 71, o, OLCUC)
#endif
#if !defined TTYMODES_LOCAL_ONLY || defined ONLCR
TTYMODE_FLAG(ONLCR, 72, o, ONLCR)
#endif
#if !defined TTYMODES_LOCAL_ONLY || defined OCRNL
TTYMODE_FLAG(OCRNL, 73, o, OCRNL)
#endif
#if !defined TTYMODES_LOCAL_ONLY || defined ONOCR
TTYMODE_FLAG(ONOCR, 74, o, ONOCR)
#endif
#if !defined TTYMODES_LOCAL_ONLY || defined ONLRET
TTYMODE_FLAG(ONLRET, 75, o, ONLRET)
#endif
#if !defined TTYMODES_LOCAL_ONLY || defined CS7
TTYMODE_FLAG(CS7, 90, c, CSIZE)
#endif
#if !defined TTYMODES_LOCAL_ONLY || defined CS8
TTYMODE_FLAG(CS8, 91, c, CSIZE)
#endif
#if !defined TTYMODES_LOCAL_ONLY || defined PARENB
TTYMODE_FLAG(PARENB, 92, c, PARENB)
#endif
#if !defined TTYMODES_LOCAL_ONLY || defined PARODD
TTYMODE_FLAG(PARODD, 93, c, PARODD)
#endif

1961
ssh/userauth2-client.c Normal file

File diff suppressed because it is too large Load Diff

377
ssh/userauth2-server.c Normal file
View File

@ -0,0 +1,377 @@
/*
* Packet protocol layer for the server side of the SSH-2 userauth
* protocol (RFC 4252).
*/
#include <assert.h>
#include "putty.h"
#include "ssh.h"
#include "bpp.h"
#include "ppl.h"
#include "sshcr.h"
#include "server.h"
#ifndef NO_GSSAPI
#include "gssc.h"
#include "gss.h"
#endif
struct ssh2_userauth_server_state {
int crState;
PacketProtocolLayer *transport_layer, *successor_layer;
ptrlen session_id;
AuthPolicy *authpolicy;
const SshServerConfig *ssc;
ptrlen username, service, method;
unsigned methods, this_method;
bool partial_success;
AuthKbdInt *aki;
PacketProtocolLayer ppl;
};
static void ssh2_userauth_server_free(PacketProtocolLayer *);
static void ssh2_userauth_server_process_queue(PacketProtocolLayer *);
static const PacketProtocolLayerVtable ssh2_userauth_server_vtable = {
.free = ssh2_userauth_server_free,
.process_queue = ssh2_userauth_server_process_queue,
.queued_data_size = ssh_ppl_default_queued_data_size,
.name = "ssh-userauth",
/* other methods are NULL */
};
static void free_auth_kbdint(AuthKbdInt *aki)
{
int i;
if (!aki)
return;
sfree(aki->title);
sfree(aki->instruction);
for (i = 0; i < aki->nprompts; i++)
sfree(aki->prompts[i].prompt);
sfree(aki->prompts);
sfree(aki);
}
PacketProtocolLayer *ssh2_userauth_server_new(
PacketProtocolLayer *successor_layer, AuthPolicy *authpolicy,
const SshServerConfig *ssc)
{
struct ssh2_userauth_server_state *s =
snew(struct ssh2_userauth_server_state);
memset(s, 0, sizeof(*s));
s->ppl.vt = &ssh2_userauth_server_vtable;
s->successor_layer = successor_layer;
s->authpolicy = authpolicy;
s->ssc = ssc;
return &s->ppl;
}
void ssh2_userauth_server_set_transport_layer(PacketProtocolLayer *userauth,
PacketProtocolLayer *transport)
{
struct ssh2_userauth_server_state *s =
container_of(userauth, struct ssh2_userauth_server_state, ppl);
s->transport_layer = transport;
}
static void ssh2_userauth_server_free(PacketProtocolLayer *ppl)
{
struct ssh2_userauth_server_state *s =
container_of(ppl, struct ssh2_userauth_server_state, ppl);
if (s->successor_layer)
ssh_ppl_free(s->successor_layer);
free_auth_kbdint(s->aki);
sfree(s);
}
static PktIn *ssh2_userauth_server_pop(struct ssh2_userauth_server_state *s)
{
return pq_pop(s->ppl.in_pq);
}
static void ssh2_userauth_server_add_session_id(
struct ssh2_userauth_server_state *s, strbuf *sigdata)
{
if (s->ppl.remote_bugs & BUG_SSH2_PK_SESSIONID) {
put_datapl(sigdata, s->session_id);
} else {
put_stringpl(sigdata, s->session_id);
}
}
static void ssh2_userauth_server_process_queue(PacketProtocolLayer *ppl)
{
struct ssh2_userauth_server_state *s =
container_of(ppl, struct ssh2_userauth_server_state, ppl);
PktIn *pktin;
PktOut *pktout;
crBegin(s->crState);
s->session_id = ssh2_transport_get_session_id(s->transport_layer);
if (s->ssc->banner.ptr) {
pktout = ssh_bpp_new_pktout(s->ppl.bpp, SSH2_MSG_USERAUTH_BANNER);
put_stringpl(pktout, s->ssc->banner);
put_stringz(pktout, ""); /* language tag */
pq_push(s->ppl.out_pq, pktout);
}
while (1) {
crMaybeWaitUntilV((pktin = ssh2_userauth_server_pop(s)) != NULL);
if (pktin->type != SSH2_MSG_USERAUTH_REQUEST) {
ssh_proto_error(s->ppl.ssh, "Received unexpected packet when "
"expecting USERAUTH_REQUEST, type %d (%s)",
pktin->type,
ssh2_pkt_type(s->ppl.bpp->pls->kctx,
s->ppl.bpp->pls->actx, pktin->type));
return;
}
s->username = get_string(pktin);
s->service = get_string(pktin);
s->method = get_string(pktin);
if (!ptrlen_eq_string(s->service, s->successor_layer->vt->name)) {
/*
* Unconditionally reject authentication for any service
* other than the one we're going to hand over to.
*/
pktout = ssh_bpp_new_pktout(s->ppl.bpp, SSH2_MSG_USERAUTH_FAILURE);
put_stringz(pktout, "");
put_bool(pktout, false);
pq_push(s->ppl.out_pq, pktout);
continue;
}
s->methods = auth_methods(s->authpolicy);
s->partial_success = false;
if (ptrlen_eq_string(s->method, "none")) {
s->this_method = AUTHMETHOD_NONE;
if (!(s->methods & s->this_method))
goto failure;
if (!auth_none(s->authpolicy, s->username))
goto failure;
} else if (ptrlen_eq_string(s->method, "password")) {
bool changing;
ptrlen password, new_password, *new_password_ptr;
s->this_method = AUTHMETHOD_PASSWORD;
if (!(s->methods & s->this_method))
goto failure;
changing = get_bool(pktin);
password = get_string(pktin);
if (changing) {
new_password = get_string(pktin);
new_password_ptr = &new_password;
} else {
new_password_ptr = NULL;
}
int result = auth_password(s->authpolicy, s->username,
password, new_password_ptr);
if (result == 2) {
pktout = ssh_bpp_new_pktout(
s->ppl.bpp, SSH2_MSG_USERAUTH_PASSWD_CHANGEREQ);
put_stringz(pktout, "Please change your password");
put_stringz(pktout, ""); /* language tag */
pq_push(s->ppl.out_pq, pktout);
continue; /* skip USERAUTH_{SUCCESS,FAILURE} epilogue */
} else if (result != 1) {
goto failure;
}
} else if (ptrlen_eq_string(s->method, "publickey")) {
bool has_signature, success, send_pk_ok, key_really_ok;
ptrlen algorithm, blob, signature;
const ssh_keyalg *keyalg;
ssh_key *key;
strbuf *sigdata;
s->this_method = AUTHMETHOD_PUBLICKEY;
if (!(s->methods & s->this_method))
goto failure;
has_signature = get_bool(pktin);
algorithm = get_string(pktin);
blob = get_string(pktin);
key_really_ok = auth_publickey(s->authpolicy, s->username, blob);
send_pk_ok = key_really_ok ||
s->ssc->stunt_pretend_to_accept_any_pubkey;
if (!has_signature) {
if (!send_pk_ok)
goto failure;
pktout = ssh_bpp_new_pktout(
s->ppl.bpp, SSH2_MSG_USERAUTH_PK_OK);
put_stringpl(pktout, algorithm);
put_stringpl(pktout, blob);
pq_push(s->ppl.out_pq, pktout);
continue; /* skip USERAUTH_{SUCCESS,FAILURE} epilogue */
}
if (!key_really_ok)
goto failure;
keyalg = find_pubkey_alg_len(algorithm);
if (!keyalg)
goto failure;
key = ssh_key_new_pub(keyalg, blob);
if (!key)
goto failure;
sigdata = strbuf_new();
ssh2_userauth_server_add_session_id(s, sigdata);
put_byte(sigdata, SSH2_MSG_USERAUTH_REQUEST);
put_stringpl(sigdata, s->username);
put_stringpl(sigdata, s->service);
put_stringpl(sigdata, s->method);
put_bool(sigdata, has_signature);
put_stringpl(sigdata, algorithm);
put_stringpl(sigdata, blob);
signature = get_string(pktin);
success = ssh_key_verify(key, signature,
ptrlen_from_strbuf(sigdata));
ssh_key_free(key);
strbuf_free(sigdata);
if (!success)
goto failure;
} else if (ptrlen_eq_string(s->method, "keyboard-interactive")) {
int i, ok;
unsigned n;
s->this_method = AUTHMETHOD_KBDINT;
if (!(s->methods & s->this_method))
goto failure;
do {
s->aki = auth_kbdint_prompts(s->authpolicy, s->username);
if (!s->aki)
goto failure;
pktout = ssh_bpp_new_pktout(
s->ppl.bpp, SSH2_MSG_USERAUTH_INFO_REQUEST);
put_stringz(pktout, s->aki->title);
put_stringz(pktout, s->aki->instruction);
put_stringz(pktout, ""); /* language tag */
put_uint32(pktout, s->aki->nprompts);
for (i = 0; i < s->aki->nprompts; i++) {
put_stringz(pktout, s->aki->prompts[i].prompt);
put_bool(pktout, s->aki->prompts[i].echo);
}
pq_push(s->ppl.out_pq, pktout);
crMaybeWaitUntilV(
(pktin = ssh2_userauth_server_pop(s)) != NULL);
if (pktin->type != SSH2_MSG_USERAUTH_INFO_RESPONSE) {
ssh_proto_error(
s->ppl.ssh, "Received unexpected packet when "
"expecting USERAUTH_INFO_RESPONSE, type %d (%s)",
pktin->type,
ssh2_pkt_type(s->ppl.bpp->pls->kctx,
s->ppl.bpp->pls->actx, pktin->type));
return;
}
n = get_uint32(pktin);
if (n != s->aki->nprompts) {
ssh_proto_error(
s->ppl.ssh, "Received %u keyboard-interactive "
"responses after sending %u prompts",
n, s->aki->nprompts);
return;
}
{
ptrlen *responses = snewn(s->aki->nprompts, ptrlen);
for (i = 0; i < s->aki->nprompts; i++)
responses[i] = get_string(pktin);
ok = auth_kbdint_responses(s->authpolicy, responses);
sfree(responses);
}
free_auth_kbdint(s->aki);
s->aki = NULL;
} while (ok == 0);
if (ok <= 0)
goto failure;
} else {
goto failure;
}
/*
* If we get here, we've successfully completed this
* authentication step.
*/
if (auth_successful(s->authpolicy, s->username, s->this_method)) {
/*
* ... and it was the last one, so we're completely done.
*/
pktout = ssh_bpp_new_pktout(s->ppl.bpp, SSH2_MSG_USERAUTH_SUCCESS);
pq_push(s->ppl.out_pq, pktout);
break;
} else {
/*
* ... but another is required, so fall through to
* generation of USERAUTH_FAILURE, having first refreshed
* the bit mask of available methods.
*/
s->methods = auth_methods(s->authpolicy);
}
s->partial_success = true;
failure:
pktout = ssh_bpp_new_pktout(s->ppl.bpp, SSH2_MSG_USERAUTH_FAILURE);
{
strbuf *list = strbuf_new();
if (s->methods & AUTHMETHOD_NONE)
add_to_commasep(list, "none");
if (s->methods & AUTHMETHOD_PASSWORD)
add_to_commasep(list, "password");
if (s->methods & AUTHMETHOD_PUBLICKEY)
add_to_commasep(list, "publickey");
if (s->methods & AUTHMETHOD_KBDINT)
add_to_commasep(list, "keyboard-interactive");
put_stringsb(pktout, list);
}
put_bool(pktout, s->partial_success);
pq_push(s->ppl.out_pq, pktout);
}
/*
* Finally, hand over to our successor layer, and return
* immediately without reaching the crFinishV: ssh_ppl_replace
* will have freed us, so crFinishV's zeroing-out of crState would
* be a use-after-free bug.
*/
{
PacketProtocolLayer *successor = s->successor_layer;
s->successor_layer = NULL; /* avoid freeing it ourself */
ssh_ppl_replace(&s->ppl, successor);
return; /* we've just freed s, so avoid even touching s->crState */
}
crFinishV;
}

628
ssh/verstring.c Normal file
View File

@ -0,0 +1,628 @@
/*
* Code to handle the initial SSH version string exchange.
*/
#include <assert.h>
#include <string.h>
#include <stdlib.h>
#include "putty.h"
#include "ssh.h"
#include "bpp.h"
#include "sshcr.h"
#define PREFIX_MAXLEN 64
struct ssh_verstring_state {
int crState;
Conf *conf;
ptrlen prefix_wanted;
char *our_protoversion;
struct ssh_version_receiver *receiver;
bool send_early;
bool found_prefix;
int major_protoversion;
int remote_bugs;
char prefix[PREFIX_MAXLEN];
char *impl_name;
strbuf *vstring;
char *protoversion;
const char *softwareversion;
char *our_vstring;
int i;
BinaryPacketProtocol bpp;
};
static void ssh_verstring_free(BinaryPacketProtocol *bpp);
static void ssh_verstring_handle_input(BinaryPacketProtocol *bpp);
static void ssh_verstring_handle_output(BinaryPacketProtocol *bpp);
static PktOut *ssh_verstring_new_pktout(int type);
static void ssh_verstring_queue_disconnect(BinaryPacketProtocol *bpp,
const char *msg, int category);
static const BinaryPacketProtocolVtable ssh_verstring_vtable = {
.free = ssh_verstring_free,
.handle_input = ssh_verstring_handle_input,
.handle_output = ssh_verstring_handle_output,
.new_pktout = ssh_verstring_new_pktout,
.queue_disconnect = ssh_verstring_queue_disconnect,
.packet_size_limit = 0xFFFFFFFF, /* no special limit for this bpp */
};
static void ssh_detect_bugs(struct ssh_verstring_state *s);
static bool ssh_version_includes_v1(const char *ver);
static bool ssh_version_includes_v2(const char *ver);
BinaryPacketProtocol *ssh_verstring_new(
Conf *conf, LogContext *logctx, bool bare_connection_mode,
const char *protoversion, struct ssh_version_receiver *rcv,
bool server_mode, const char *impl_name)
{
struct ssh_verstring_state *s = snew(struct ssh_verstring_state);
memset(s, 0, sizeof(struct ssh_verstring_state));
if (!bare_connection_mode) {
s->prefix_wanted = PTRLEN_LITERAL("SSH-");
} else {
/*
* Ordinary SSH begins with the banner "SSH-x.y-...". Here,
* we're going to be speaking just the ssh-connection
* subprotocol, extracted and given a trivial binary packet
* protocol, so we need a new banner.
*
* The new banner is like the ordinary SSH banner, but
* replaces the prefix 'SSH-' at the start with a new name. In
* proper SSH style (though of course this part of the proper
* SSH protocol _isn't_ subject to this kind of
* DNS-domain-based extension), we define the new name in our
* extension space.
*/
s->prefix_wanted = PTRLEN_LITERAL(
"SSHCONNECTION@putty.projects.tartarus.org-");
}
assert(s->prefix_wanted.len <= PREFIX_MAXLEN);
s->conf = conf_copy(conf);
s->bpp.logctx = logctx;
s->our_protoversion = dupstr(protoversion);
s->receiver = rcv;
s->impl_name = dupstr(impl_name);
s->vstring = strbuf_new();
/*
* We send our version string early if we can. But if it includes
* SSH-1, we can't, because we have to take the other end into
* account too (see below).
*
* In server mode, we do send early.
*/
s->send_early = server_mode || !ssh_version_includes_v1(protoversion);
s->bpp.vt = &ssh_verstring_vtable;
ssh_bpp_common_setup(&s->bpp);
return &s->bpp;
}
void ssh_verstring_free(BinaryPacketProtocol *bpp)
{
struct ssh_verstring_state *s =
container_of(bpp, struct ssh_verstring_state, bpp);
conf_free(s->conf);
sfree(s->impl_name);
strbuf_free(s->vstring);
sfree(s->protoversion);
sfree(s->our_vstring);
sfree(s->our_protoversion);
sfree(s);
}
static int ssh_versioncmp(const char *a, const char *b)
{
char *ae, *be;
unsigned long av, bv;
av = strtoul(a, &ae, 10);
bv = strtoul(b, &be, 10);
if (av != bv)
return (av < bv ? -1 : +1);
if (*ae == '.')
ae++;
if (*be == '.')
be++;
av = strtoul(ae, &ae, 10);
bv = strtoul(be, &be, 10);
if (av != bv)
return (av < bv ? -1 : +1);
return 0;
}
static bool ssh_version_includes_v1(const char *ver)
{
return ssh_versioncmp(ver, "2.0") < 0;
}
static bool ssh_version_includes_v2(const char *ver)
{
return ssh_versioncmp(ver, "1.99") >= 0;
}
static void ssh_verstring_send(struct ssh_verstring_state *s)
{
BinaryPacketProtocol *bpp = &s->bpp; /* for bpp_logevent */
char *p;
int sv_pos;
/*
* Construct our outgoing version string.
*/
s->our_vstring = dupprintf(
"%.*s%s-%s%s",
(int)s->prefix_wanted.len, (const char *)s->prefix_wanted.ptr,
s->our_protoversion, s->impl_name, sshver);
sv_pos = s->prefix_wanted.len + strlen(s->our_protoversion) + 1;
/* Convert minus signs and spaces in the software version string
* into underscores. */
for (p = s->our_vstring + sv_pos; *p; p++) {
if (*p == '-' || *p == ' ')
*p = '_';
}
#ifdef FUZZING
/*
* Replace the first character of the string with an "I" if we're
* compiling this code for fuzzing - i.e. the protocol prefix
* becomes "ISH-" instead of "SSH-".
*
* This is irrelevant to any real client software (the only thing
* reading the output of PuTTY built for fuzzing is the fuzzer,
* which can adapt to whatever it sees anyway). But it's a safety
* precaution making it difficult to accidentally run such a
* version of PuTTY (which would be hugely insecure) against a
* live peer implementation.
*
* (So the replacement prefix "ISH" notionally stands for
* 'Insecure Shell', of course.)
*/
s->our_vstring[0] = 'I';
#endif
/*
* Now send that version string, plus trailing \r\n or just \n
* (the latter in SSH-1 mode).
*/
bufchain_add(s->bpp.out_raw, s->our_vstring, strlen(s->our_vstring));
if (ssh_version_includes_v2(s->our_protoversion))
bufchain_add(s->bpp.out_raw, "\015", 1);
bufchain_add(s->bpp.out_raw, "\012", 1);
bpp_logevent("We claim version: %s", s->our_vstring);
}
#define BPP_WAITFOR(minlen) do \
{ \
bool success; \
crMaybeWaitUntilV( \
(success = (bufchain_size(s->bpp.in_raw) >= (minlen))) || \
s->bpp.input_eof); \
if (!success) \
goto eof; \
} while (0)
void ssh_verstring_handle_input(BinaryPacketProtocol *bpp)
{
struct ssh_verstring_state *s =
container_of(bpp, struct ssh_verstring_state, bpp);
crBegin(s->crState);
/*
* If we're sending our version string up front before seeing the
* other side's, then do it now.
*/
if (s->send_early)
ssh_verstring_send(s);
/*
* Search for a line beginning with the protocol name prefix in
* the input.
*/
s->i = 0;
while (1) {
/*
* Every time round this loop, we're at the start of a new
* line, so look for the prefix.
*/
BPP_WAITFOR(s->prefix_wanted.len);
bufchain_fetch(s->bpp.in_raw, s->prefix, s->prefix_wanted.len);
if (!memcmp(s->prefix, s->prefix_wanted.ptr, s->prefix_wanted.len)) {
bufchain_consume(s->bpp.in_raw, s->prefix_wanted.len);
ssh_check_frozen(s->bpp.ssh);
break;
}
/*
* If we didn't find it, consume data until we see a newline.
*/
while (1) {
ptrlen data;
char *nl;
/* Wait to receive at least 1 byte, but then consume more
* than that if it's there. */
BPP_WAITFOR(1);
data = bufchain_prefix(s->bpp.in_raw);
if ((nl = memchr(data.ptr, '\012', data.len)) != NULL) {
bufchain_consume(s->bpp.in_raw, nl - (char *)data.ptr + 1);
ssh_check_frozen(s->bpp.ssh);
break;
} else {
bufchain_consume(s->bpp.in_raw, data.len);
ssh_check_frozen(s->bpp.ssh);
}
}
}
s->found_prefix = true;
/*
* Copy the greeting line so far into vstring.
*/
put_data(s->vstring, s->prefix_wanted.ptr, s->prefix_wanted.len);
/*
* Now read the rest of the greeting line.
*/
s->i = 0;
do {
ptrlen data;
char *nl;
BPP_WAITFOR(1);
data = bufchain_prefix(s->bpp.in_raw);
if ((nl = memchr(data.ptr, '\012', data.len)) != NULL) {
data.len = nl - (char *)data.ptr + 1;
}
put_datapl(s->vstring, data);
bufchain_consume(s->bpp.in_raw, data.len);
ssh_check_frozen(s->bpp.ssh);
} while (s->vstring->s[s->vstring->len-1] != '\012');
/*
* Trim \r and \n from the version string, and replace them with
* a NUL terminator.
*/
while (s->vstring->len > 0 &&
(s->vstring->s[s->vstring->len-1] == '\r' ||
s->vstring->s[s->vstring->len-1] == '\n'))
strbuf_shrink_by(s->vstring, 1);
bpp_logevent("Remote version: %s", s->vstring->s);
/*
* Pick out the protocol version and software version. The former
* goes in a separately allocated string, so that s->vstring
* remains intact for later use in key exchange; the latter is the
* tail of s->vstring, so it doesn't need to be allocated.
*/
{
const char *pv_start = s->vstring->s + s->prefix_wanted.len;
int pv_len = strcspn(pv_start, "-");
s->protoversion = dupprintf("%.*s", pv_len, pv_start);
s->softwareversion = pv_start + pv_len;
if (*s->softwareversion) {
assert(*s->softwareversion == '-');
s->softwareversion++;
}
}
ssh_detect_bugs(s);
/*
* Figure out what actual SSH protocol version we're speaking.
*/
if (ssh_version_includes_v2(s->our_protoversion) &&
ssh_version_includes_v2(s->protoversion)) {
/*
* We're doing SSH-2.
*/
s->major_protoversion = 2;
} else if (ssh_version_includes_v1(s->our_protoversion) &&
ssh_version_includes_v1(s->protoversion)) {
/*
* We're doing SSH-1.
*/
s->major_protoversion = 1;
/*
* There are multiple minor versions of SSH-1, and the
* protocol does not specify that the minimum of client
* and server versions is used. So we must adjust our
* outgoing protocol version to be no higher than that of
* the other side.
*/
if (!s->send_early &&
ssh_versioncmp(s->our_protoversion, s->protoversion) > 0) {
sfree(s->our_protoversion);
s->our_protoversion = dupstr(s->protoversion);
}
} else {
/*
* Unable to agree on a major protocol version at all.
*/
if (!ssh_version_includes_v2(s->our_protoversion)) {
ssh_sw_abort(s->bpp.ssh,
"SSH protocol version 1 required by our "
"configuration but not provided by remote");
} else {
ssh_sw_abort(s->bpp.ssh,
"SSH protocol version 2 required by our "
"configuration but remote only provides "
"(old, insecure) SSH-1");
}
crStopV;
}
bpp_logevent("Using SSH protocol version %d", s->major_protoversion);
if (!s->send_early) {
/*
* If we didn't send our version string early, construct and
* send it now, because now we know what it is.
*/
ssh_verstring_send(s);
}
/*
* And we're done. Notify our receiver that we now know our
* protocol version. This will cause it to disconnect us from the
* input stream and ultimately free us, because our job is now
* done.
*/
s->receiver->got_ssh_version(s->receiver, s->major_protoversion);
return;
eof:
ssh_remote_error(s->bpp.ssh,
"Remote side unexpectedly closed network connection");
return; /* avoid touching s now it's been freed */
crFinishV;
}
static PktOut *ssh_verstring_new_pktout(int type)
{
unreachable("Should never try to send packets during SSH version "
"string exchange");
}
static void ssh_verstring_handle_output(BinaryPacketProtocol *bpp)
{
if (pq_peek(&bpp->out_pq)) {
unreachable("Should never try to send packets during SSH version "
"string exchange");
}
}
/*
* Examine the remote side's version string, and compare it against a
* list of known buggy implementations.
*/
static void ssh_detect_bugs(struct ssh_verstring_state *s)
{
BinaryPacketProtocol *bpp = &s->bpp; /* for bpp_logevent */
const char *imp = s->softwareversion;
s->remote_bugs = 0;
/*
* General notes on server version strings:
* - Not all servers reporting "Cisco-1.25" have all the bugs listed
* here -- in particular, we've heard of one that's perfectly happy
* with SSH1_MSG_IGNOREs -- but this string never seems to change,
* so we can't distinguish them.
*/
if (conf_get_int(s->conf, CONF_sshbug_ignore1) == FORCE_ON ||
(conf_get_int(s->conf, CONF_sshbug_ignore1) == AUTO &&
(!strcmp(imp, "1.2.18") || !strcmp(imp, "1.2.19") ||
!strcmp(imp, "1.2.20") || !strcmp(imp, "1.2.21") ||
!strcmp(imp, "1.2.22") || !strcmp(imp, "Cisco-1.25") ||
!strcmp(imp, "OSU_1.4alpha3") || !strcmp(imp, "OSU_1.5alpha4")))) {
/*
* These versions don't support SSH1_MSG_IGNORE, so we have
* to use a different defence against password length
* sniffing.
*/
s->remote_bugs |= BUG_CHOKES_ON_SSH1_IGNORE;
bpp_logevent("We believe remote version has SSH-1 ignore bug");
}
if (conf_get_int(s->conf, CONF_sshbug_plainpw1) == FORCE_ON ||
(conf_get_int(s->conf, CONF_sshbug_plainpw1) == AUTO &&
(!strcmp(imp, "Cisco-1.25") || !strcmp(imp, "OSU_1.4alpha3")))) {
/*
* These versions need a plain password sent; they can't
* handle having a null and a random length of data after
* the password.
*/
s->remote_bugs |= BUG_NEEDS_SSH1_PLAIN_PASSWORD;
bpp_logevent("We believe remote version needs a "
"plain SSH-1 password");
}
if (conf_get_int(s->conf, CONF_sshbug_rsa1) == FORCE_ON ||
(conf_get_int(s->conf, CONF_sshbug_rsa1) == AUTO &&
(!strcmp(imp, "Cisco-1.25")))) {
/*
* These versions apparently have no clue whatever about
* RSA authentication and will panic and die if they see
* an AUTH_RSA message.
*/
s->remote_bugs |= BUG_CHOKES_ON_RSA;
bpp_logevent("We believe remote version can't handle SSH-1 "
"RSA authentication");
}
if (conf_get_int(s->conf, CONF_sshbug_hmac2) == FORCE_ON ||
(conf_get_int(s->conf, CONF_sshbug_hmac2) == AUTO &&
!wc_match("* VShell", imp) &&
(wc_match("2.1.0*", imp) || wc_match("2.0.*", imp) ||
wc_match("2.2.0*", imp) || wc_match("2.3.0*", imp) ||
wc_match("2.1 *", imp)))) {
/*
* These versions have the HMAC bug.
*/
s->remote_bugs |= BUG_SSH2_HMAC;
bpp_logevent("We believe remote version has SSH-2 HMAC bug");
}
if (conf_get_int(s->conf, CONF_sshbug_derivekey2) == FORCE_ON ||
(conf_get_int(s->conf, CONF_sshbug_derivekey2) == AUTO &&
!wc_match("* VShell", imp) &&
(wc_match("2.0.0*", imp) || wc_match("2.0.10*", imp) ))) {
/*
* These versions have the key-derivation bug (failing to
* include the literal shared secret in the hashes that
* generate the keys).
*/
s->remote_bugs |= BUG_SSH2_DERIVEKEY;
bpp_logevent("We believe remote version has SSH-2 "
"key-derivation bug");
}
if (conf_get_int(s->conf, CONF_sshbug_rsapad2) == FORCE_ON ||
(conf_get_int(s->conf, CONF_sshbug_rsapad2) == AUTO &&
(wc_match("OpenSSH_2.[5-9]*", imp) ||
wc_match("OpenSSH_3.[0-2]*", imp) ||
wc_match("mod_sftp/0.[0-8]*", imp) ||
wc_match("mod_sftp/0.9.[0-8]", imp)))) {
/*
* These versions have the SSH-2 RSA padding bug.
*/
s->remote_bugs |= BUG_SSH2_RSA_PADDING;
bpp_logevent("We believe remote version has SSH-2 RSA padding bug");
}
if (conf_get_int(s->conf, CONF_sshbug_pksessid2) == FORCE_ON ||
(conf_get_int(s->conf, CONF_sshbug_pksessid2) == AUTO &&
wc_match("OpenSSH_2.[0-2]*", imp))) {
/*
* These versions have the SSH-2 session-ID bug in
* public-key authentication.
*/
s->remote_bugs |= BUG_SSH2_PK_SESSIONID;
bpp_logevent("We believe remote version has SSH-2 "
"public-key-session-ID bug");
}
if (conf_get_int(s->conf, CONF_sshbug_rekey2) == FORCE_ON ||
(conf_get_int(s->conf, CONF_sshbug_rekey2) == AUTO &&
(wc_match("DigiSSH_2.0", imp) ||
wc_match("OpenSSH_2.[0-4]*", imp) ||
wc_match("OpenSSH_2.5.[0-3]*", imp) ||
wc_match("Sun_SSH_1.0", imp) ||
wc_match("Sun_SSH_1.0.1", imp) ||
/* All versions <= 1.2.6 (they changed their format in 1.2.7) */
wc_match("WeOnlyDo-*", imp)))) {
/*
* These versions have the SSH-2 rekey bug.
*/
s->remote_bugs |= BUG_SSH2_REKEY;
bpp_logevent("We believe remote version has SSH-2 rekey bug");
}
if (conf_get_int(s->conf, CONF_sshbug_maxpkt2) == FORCE_ON ||
(conf_get_int(s->conf, CONF_sshbug_maxpkt2) == AUTO &&
(wc_match("1.36_sshlib GlobalSCAPE", imp) ||
wc_match("1.36 sshlib: GlobalScape", imp)))) {
/*
* This version ignores our makpkt and needs to be throttled.
*/
s->remote_bugs |= BUG_SSH2_MAXPKT;
bpp_logevent("We believe remote version ignores SSH-2 "
"maximum packet size");
}
if (conf_get_int(s->conf, CONF_sshbug_ignore2) == FORCE_ON) {
/*
* Servers that don't support SSH2_MSG_IGNORE. Currently,
* none detected automatically.
*/
s->remote_bugs |= BUG_CHOKES_ON_SSH2_IGNORE;
bpp_logevent("We believe remote version has SSH-2 ignore bug");
}
if (conf_get_int(s->conf, CONF_sshbug_oldgex2) == FORCE_ON ||
(conf_get_int(s->conf, CONF_sshbug_oldgex2) == AUTO &&
(wc_match("OpenSSH_2.[235]*", imp)))) {
/*
* These versions only support the original (pre-RFC4419)
* SSH-2 GEX request, and disconnect with a protocol error if
* we use the newer version.
*/
s->remote_bugs |= BUG_SSH2_OLDGEX;
bpp_logevent("We believe remote version has outdated SSH-2 GEX");
}
if (conf_get_int(s->conf, CONF_sshbug_winadj) == FORCE_ON) {
/*
* Servers that don't support our winadj request for one
* reason or another. Currently, none detected automatically.
*/
s->remote_bugs |= BUG_CHOKES_ON_WINADJ;
bpp_logevent("We believe remote version has winadj bug");
}
if (conf_get_int(s->conf, CONF_sshbug_chanreq) == FORCE_ON ||
(conf_get_int(s->conf, CONF_sshbug_chanreq) == AUTO &&
(wc_match("OpenSSH_[2-5].*", imp) ||
wc_match("OpenSSH_6.[0-6]*", imp) ||
wc_match("dropbear_0.[2-4][0-9]*", imp) ||
wc_match("dropbear_0.5[01]*", imp)))) {
/*
* These versions have the SSH-2 channel request bug.
* OpenSSH 6.7 and above do not:
* https://bugzilla.mindrot.org/show_bug.cgi?id=1818
* dropbear_0.52 and above do not:
* https://secure.ucc.asn.au/hg/dropbear/rev/cd02449b709c
*/
s->remote_bugs |= BUG_SENDS_LATE_REQUEST_REPLY;
bpp_logevent("We believe remote version has SSH-2 "
"channel request bug");
}
}
const char *ssh_verstring_get_remote(BinaryPacketProtocol *bpp)
{
struct ssh_verstring_state *s =
container_of(bpp, struct ssh_verstring_state, bpp);
return s->vstring->s;
}
const char *ssh_verstring_get_local(BinaryPacketProtocol *bpp)
{
struct ssh_verstring_state *s =
container_of(bpp, struct ssh_verstring_state, bpp);
return s->our_vstring;
}
int ssh_verstring_get_bugs(BinaryPacketProtocol *bpp)
{
struct ssh_verstring_state *s =
container_of(bpp, struct ssh_verstring_state, bpp);
return s->remote_bugs;
}
static void ssh_verstring_queue_disconnect(BinaryPacketProtocol *bpp,
const char *msg, int category)
{
/* No way to send disconnect messages at this stage of the protocol! */
}

639
ssh/x11fwd.c Normal file
View File

@ -0,0 +1,639 @@
/*
* Platform-independent bits of X11 forwarding.
*/
#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
#include <time.h>
#include "putty.h"
#include "ssh.h"
#include "channel.h"
#include "tree234.h"
struct XDMSeen {
unsigned int time;
unsigned char clientid[6];
};
typedef struct X11Connection {
unsigned char firstpkt[12]; /* first X data packet */
tree234 *authtree;
struct X11Display *disp;
char *auth_protocol;
unsigned char *auth_data;
int data_read, auth_plen, auth_psize, auth_dlen, auth_dsize;
bool verified;
bool input_wanted;
bool no_data_sent_to_x_client;
char *peer_addr;
int peer_port;
SshChannel *c; /* channel structure held by SSH backend */
Socket *s;
Plug plug;
Channel chan;
} X11Connection;
static int xdmseen_cmp(void *a, void *b)
{
struct XDMSeen *sa = a, *sb = b;
return sa->time > sb->time ? 1 :
sa->time < sb->time ? -1 :
memcmp(sa->clientid, sb->clientid, sizeof(sa->clientid));
}
struct X11FakeAuth *x11_invent_fake_auth(tree234 *authtree, int authtype)
{
struct X11FakeAuth *auth = snew(struct X11FakeAuth);
int i;
/*
* This function has the job of inventing a set of X11 fake auth
* data, and adding it to 'authtree'. We must preserve the
* property that for any given actual authorisation attempt, _at
* most one_ thing in the tree can possibly match it.
*
* For MIT-MAGIC-COOKIE-1, that's not too difficult: the match
* criterion is simply that the entire cookie is correct, so we
* just have to make sure we don't make up two cookies the same.
* (Vanishingly unlikely, but we check anyway to be sure, and go
* round again inventing a new cookie if add234 tells us the one
* we thought of is already in use.)
*
* For XDM-AUTHORIZATION-1, it's a little more fiddly. The setup
* with XA1 is that half the cookie is used as a DES key with
* which to CBC-encrypt an assortment of stuff. Happily, the stuff
* encrypted _begins_ with the other half of the cookie, and the
* IV is always zero, which means that any valid XA1 authorisation
* attempt for a given cookie must begin with the same cipher
* block, consisting of the DES ECB encryption of the first half
* of the cookie using the second half as a key. So we compute
* that cipher block here and now, and use it as the sorting key
* for distinguishing XA1 entries in the tree.
*/
if (authtype == X11_MIT) {
auth->proto = X11_MIT;
/* MIT-MAGIC-COOKIE-1. Cookie size is 128 bits (16 bytes). */
auth->datalen = 16;
auth->data = snewn(auth->datalen, unsigned char);
auth->xa1_firstblock = NULL;
while (1) {
random_read(auth->data, auth->datalen);
if (add234(authtree, auth) == auth)
break;
}
auth->xdmseen = NULL;
} else {
assert(authtype == X11_XDM);
auth->proto = X11_XDM;
/* XDM-AUTHORIZATION-1. Cookie size is 16 bytes; byte 8 is zero. */
auth->datalen = 16;
auth->data = snewn(auth->datalen, unsigned char);
auth->xa1_firstblock = snewn(8, unsigned char);
memset(auth->xa1_firstblock, 0, 8);
while (1) {
random_read(auth->data, 15);
auth->data[15] = auth->data[8];
auth->data[8] = 0;
memcpy(auth->xa1_firstblock, auth->data, 8);
des_encrypt_xdmauth(auth->data + 9, auth->xa1_firstblock, 8);
if (add234(authtree, auth) == auth)
break;
}
auth->xdmseen = newtree234(xdmseen_cmp);
}
auth->protoname = dupstr(x11_authnames[auth->proto]);
auth->datastring = snewn(auth->datalen * 2 + 1, char);
for (i = 0; i < auth->datalen; i++)
sprintf(auth->datastring + i*2, "%02x",
auth->data[i]);
auth->disp = NULL;
auth->share_cs = NULL;
auth->share_chan = NULL;
return auth;
}
void x11_free_fake_auth(struct X11FakeAuth *auth)
{
if (auth->data)
smemclr(auth->data, auth->datalen);
sfree(auth->data);
sfree(auth->protoname);
sfree(auth->datastring);
sfree(auth->xa1_firstblock);
if (auth->xdmseen != NULL) {
struct XDMSeen *seen;
while ((seen = delpos234(auth->xdmseen, 0)) != NULL)
sfree(seen);
freetree234(auth->xdmseen);
}
sfree(auth);
}
int x11_authcmp(void *av, void *bv)
{
struct X11FakeAuth *a = (struct X11FakeAuth *)av;
struct X11FakeAuth *b = (struct X11FakeAuth *)bv;
if (a->proto < b->proto)
return -1;
else if (a->proto > b->proto)
return +1;
if (a->proto == X11_MIT) {
if (a->datalen < b->datalen)
return -1;
else if (a->datalen > b->datalen)
return +1;
return memcmp(a->data, b->data, a->datalen);
} else {
assert(a->proto == X11_XDM);
return memcmp(a->xa1_firstblock, b->xa1_firstblock, 8);
}
}
#define XDM_MAXSKEW 20*60 /* 20 minute clock skew should be OK */
static const char *x11_verify(unsigned long peer_ip, int peer_port,
tree234 *authtree, char *proto,
unsigned char *data, int dlen,
struct X11FakeAuth **auth_ret)
{
struct X11FakeAuth match_dummy; /* for passing to find234 */
struct X11FakeAuth *auth;
/*
* First, do a lookup in our tree to find the only authorisation
* record that _might_ match.
*/
if (!strcmp(proto, x11_authnames[X11_MIT])) {
/*
* Just look up the whole cookie that was presented to us,
* which x11_authcmp will compare against the cookies we
* currently believe in.
*/
match_dummy.proto = X11_MIT;
match_dummy.datalen = dlen;
match_dummy.data = data;
} else if (!strcmp(proto, x11_authnames[X11_XDM])) {
/*
* Look up the first cipher block, against the stored first
* cipher blocks for the XDM-AUTHORIZATION-1 cookies we
* currently know. (See comment in x11_invent_fake_auth.)
*/
match_dummy.proto = X11_XDM;
match_dummy.xa1_firstblock = data;
} else {
return "Unsupported authorisation protocol";
}
if ((auth = find234(authtree, &match_dummy, 0)) == NULL)
return "Authorisation not recognised";
/*
* If we're using MIT-MAGIC-COOKIE-1, that was all we needed. If
* we're doing XDM-AUTHORIZATION-1, though, we have to check the
* rest of the auth data.
*/
if (auth->proto == X11_XDM) {
unsigned long t;
time_t tim;
int i;
struct XDMSeen *seen, *ret;
if (dlen != 24)
return "XDM-AUTHORIZATION-1 data was wrong length";
if (peer_port == -1)
return "cannot do XDM-AUTHORIZATION-1 without remote address data";
des_decrypt_xdmauth(auth->data+9, data, 24);
if (memcmp(auth->data, data, 8) != 0)
return "XDM-AUTHORIZATION-1 data failed check"; /* cookie wrong */
if (GET_32BIT_MSB_FIRST(data+8) != peer_ip)
return "XDM-AUTHORIZATION-1 data failed check"; /* IP wrong */
if ((int)GET_16BIT_MSB_FIRST(data+12) != peer_port)
return "XDM-AUTHORIZATION-1 data failed check"; /* port wrong */
t = GET_32BIT_MSB_FIRST(data+14);
for (i = 18; i < 24; i++)
if (data[i] != 0) /* zero padding wrong */
return "XDM-AUTHORIZATION-1 data failed check";
tim = time(NULL);
if (((unsigned long)t - (unsigned long)tim
+ XDM_MAXSKEW) > 2*XDM_MAXSKEW)
return "XDM-AUTHORIZATION-1 time stamp was too far out";
seen = snew(struct XDMSeen);
seen->time = t;
memcpy(seen->clientid, data+8, 6);
assert(auth->xdmseen != NULL);
ret = add234(auth->xdmseen, seen);
if (ret != seen) {
sfree(seen);
return "XDM-AUTHORIZATION-1 data replayed";
}
/* While we're here, purge entries too old to be replayed. */
for (;;) {
seen = index234(auth->xdmseen, 0);
assert(seen != NULL);
if (t - seen->time <= XDM_MAXSKEW)
break;
sfree(delpos234(auth->xdmseen, 0));
}
}
/* implement other protocols here if ever required */
*auth_ret = auth;
return NULL;
}
static void x11_log(Plug *p, PlugLogType type, SockAddr *addr, int port,
const char *error_msg, int error_code)
{
/* We have no interface to the logging module here, so we drop these. */
}
static void x11_send_init_error(struct X11Connection *conn,
const char *err_message);
static void x11_closing(Plug *plug, const char *error_msg, int error_code,
bool calling_back)
{
struct X11Connection *xconn = container_of(
plug, struct X11Connection, plug);
if (error_msg) {
/*
* Socket error. If we're still at the connection setup stage,
* construct an X11 error packet passing on the problem.
*/
if (xconn->no_data_sent_to_x_client) {
char *err_message = dupprintf("unable to connect to forwarded "
"X server: %s", error_msg);
x11_send_init_error(xconn, err_message);
sfree(err_message);
}
/*
* Whether we did that or not, now we slam the connection
* shut.
*/
sshfwd_initiate_close(xconn->c, error_msg);
} else {
/*
* Ordinary EOF received on socket. Send an EOF on the SSH
* channel.
*/
if (xconn->c)
sshfwd_write_eof(xconn->c);
}
}
static void x11_receive(Plug *plug, int urgent, const char *data, size_t len)
{
struct X11Connection *xconn = container_of(
plug, struct X11Connection, plug);
xconn->no_data_sent_to_x_client = false;
sshfwd_write(xconn->c, data, len);
}
static void x11_sent(Plug *plug, size_t bufsize)
{
struct X11Connection *xconn = container_of(
plug, struct X11Connection, plug);
sshfwd_unthrottle(xconn->c, bufsize);
}
static const PlugVtable X11Connection_plugvt = {
.log = x11_log,
.closing = x11_closing,
.receive = x11_receive,
.sent = x11_sent,
};
static void x11_chan_free(Channel *chan);
static size_t x11_send(
Channel *chan, bool is_stderr, const void *vdata, size_t len);
static void x11_send_eof(Channel *chan);
static void x11_set_input_wanted(Channel *chan, bool wanted);
static char *x11_log_close_msg(Channel *chan);
static const ChannelVtable X11Connection_channelvt = {
.free = x11_chan_free,
.open_confirmation = chan_remotely_opened_confirmation,
.open_failed = chan_remotely_opened_failure,
.send = x11_send,
.send_eof = x11_send_eof,
.set_input_wanted = x11_set_input_wanted,
.log_close_msg = x11_log_close_msg,
.want_close = chan_default_want_close,
.rcvd_exit_status = chan_no_exit_status,
.rcvd_exit_signal = chan_no_exit_signal,
.rcvd_exit_signal_numeric = chan_no_exit_signal_numeric,
.run_shell = chan_no_run_shell,
.run_command = chan_no_run_command,
.run_subsystem = chan_no_run_subsystem,
.enable_x11_forwarding = chan_no_enable_x11_forwarding,
.enable_agent_forwarding = chan_no_enable_agent_forwarding,
.allocate_pty = chan_no_allocate_pty,
.set_env = chan_no_set_env,
.send_break = chan_no_send_break,
.send_signal = chan_no_send_signal,
.change_window_size = chan_no_change_window_size,
.request_response = chan_no_request_response,
};
/*
* Called to set up the X11Connection structure, though this does not
* yet connect to an actual server.
*/
Channel *x11_new_channel(tree234 *authtree, SshChannel *c,
const char *peeraddr, int peerport,
bool connection_sharing_possible)
{
struct X11Connection *xconn;
/*
* Open socket.
*/
xconn = snew(struct X11Connection);
xconn->plug.vt = &X11Connection_plugvt;
xconn->chan.vt = &X11Connection_channelvt;
xconn->chan.initial_fixed_window_size =
(connection_sharing_possible ? 128 : 0);
xconn->auth_protocol = NULL;
xconn->authtree = authtree;
xconn->verified = false;
xconn->data_read = 0;
xconn->input_wanted = true;
xconn->no_data_sent_to_x_client = true;
xconn->c = c;
/*
* We don't actually open a local socket to the X server just yet,
* because we don't know which one it is. Instead, we'll wait
* until we see the incoming authentication data, which may tell
* us what display to connect to, or whether we have to divert
* this X forwarding channel to a connection-sharing downstream
* rather than handling it ourself.
*/
xconn->disp = NULL;
xconn->s = NULL;
/*
* Stash the peer address we were given in its original text form.
*/
xconn->peer_addr = peeraddr ? dupstr(peeraddr) : NULL;
xconn->peer_port = peerport;
return &xconn->chan;
}
static void x11_chan_free(Channel *chan)
{
assert(chan->vt == &X11Connection_channelvt);
X11Connection *xconn = container_of(chan, X11Connection, chan);
if (xconn->auth_protocol) {
sfree(xconn->auth_protocol);
sfree(xconn->auth_data);
}
if (xconn->s)
sk_close(xconn->s);
sfree(xconn->peer_addr);
sfree(xconn);
}
static void x11_set_input_wanted(Channel *chan, bool wanted)
{
assert(chan->vt == &X11Connection_channelvt);
X11Connection *xconn = container_of(chan, X11Connection, chan);
xconn->input_wanted = wanted;
if (xconn->s)
sk_set_frozen(xconn->s, !xconn->input_wanted);
}
static void x11_send_init_error(struct X11Connection *xconn,
const char *err_message)
{
char *full_message;
int msglen, msgsize;
unsigned char *reply;
full_message = dupprintf("%s X11 proxy: %s\n", appname, err_message);
msglen = strlen(full_message);
reply = snewn(8 + msglen+1 + 4, unsigned char); /* include zero */
msgsize = (msglen + 3) & ~3;
reply[0] = 0; /* failure */
reply[1] = msglen; /* length of reason string */
memcpy(reply + 2, xconn->firstpkt + 2, 4); /* major/minor proto vsn */
PUT_16BIT_X11(xconn->firstpkt[0], reply + 6, msgsize >> 2);/* data len */
memset(reply + 8, 0, msgsize);
memcpy(reply + 8, full_message, msglen);
sshfwd_write(xconn->c, reply, 8 + msgsize);
sshfwd_write_eof(xconn->c);
xconn->no_data_sent_to_x_client = false;
sfree(reply);
sfree(full_message);
}
/*
* Called to send data down the raw connection.
*/
static size_t x11_send(
Channel *chan, bool is_stderr, const void *vdata, size_t len)
{
assert(chan->vt == &X11Connection_channelvt);
X11Connection *xconn = container_of(chan, X11Connection, chan);
const char *data = (const char *)vdata;
/*
* Read the first packet.
*/
while (len > 0 && xconn->data_read < 12)
xconn->firstpkt[xconn->data_read++] = (unsigned char) (len--, *data++);
if (xconn->data_read < 12)
return 0;
/*
* If we have not allocated the auth_protocol and auth_data
* strings, do so now.
*/
if (!xconn->auth_protocol) {
char endian = xconn->firstpkt[0];
xconn->auth_plen = GET_16BIT_X11(endian, xconn->firstpkt + 6);
xconn->auth_dlen = GET_16BIT_X11(endian, xconn->firstpkt + 8);
xconn->auth_psize = (xconn->auth_plen + 3) & ~3;
xconn->auth_dsize = (xconn->auth_dlen + 3) & ~3;
/* Leave room for a terminating zero, to make our lives easier. */
xconn->auth_protocol = snewn(xconn->auth_psize + 1, char);
xconn->auth_data = snewn(xconn->auth_dsize, unsigned char);
}
/*
* Read the auth_protocol and auth_data strings.
*/
while (len > 0 &&
xconn->data_read < 12 + xconn->auth_psize)
xconn->auth_protocol[xconn->data_read++ - 12] = (len--, *data++);
while (len > 0 &&
xconn->data_read < 12 + xconn->auth_psize + xconn->auth_dsize)
xconn->auth_data[xconn->data_read++ - 12 -
xconn->auth_psize] = (unsigned char) (len--, *data++);
if (xconn->data_read < 12 + xconn->auth_psize + xconn->auth_dsize)
return 0;
/*
* If we haven't verified the authorisation, do so now.
*/
if (!xconn->verified) {
const char *err;
struct X11FakeAuth *auth_matched = NULL;
unsigned long peer_ip;
int peer_port;
int protomajor, protominor;
void *greeting;
int greeting_len;
unsigned char *socketdata;
int socketdatalen;
char new_peer_addr[32];
int new_peer_port;
char endian = xconn->firstpkt[0];
protomajor = GET_16BIT_X11(endian, xconn->firstpkt + 2);
protominor = GET_16BIT_X11(endian, xconn->firstpkt + 4);
assert(!xconn->s);
xconn->auth_protocol[xconn->auth_plen] = '\0'; /* ASCIZ */
peer_ip = 0; /* placate optimiser */
if (x11_parse_ip(xconn->peer_addr, &peer_ip))
peer_port = xconn->peer_port;
else
peer_port = -1; /* signal no peer address data available */
err = x11_verify(peer_ip, peer_port,
xconn->authtree, xconn->auth_protocol,
xconn->auth_data, xconn->auth_dlen, &auth_matched);
if (err) {
x11_send_init_error(xconn, err);
return 0;
}
assert(auth_matched);
/*
* If this auth points to a connection-sharing downstream
* rather than an X display we know how to connect to
* directly, pass it off to the sharing module now. (This will
* have the side effect of freeing xconn.)
*/
if (auth_matched->share_cs) {
sshfwd_x11_sharing_handover(xconn->c, auth_matched->share_cs,
auth_matched->share_chan,
xconn->peer_addr, xconn->peer_port,
xconn->firstpkt[0],
protomajor, protominor, data, len);
return 0;
}
/*
* Now we know we're going to accept the connection, and what
* X display to connect to. Actually connect to it.
*/
xconn->chan.initial_fixed_window_size = 0;
sshfwd_window_override_removed(xconn->c);
xconn->disp = auth_matched->disp;
xconn->s = new_connection(sk_addr_dup(xconn->disp->addr),
xconn->disp->realhost, xconn->disp->port,
false, true, false, false, &xconn->plug,
sshfwd_get_conf(xconn->c));
if ((err = sk_socket_error(xconn->s)) != NULL) {
char *err_message = dupprintf("unable to connect to"
" forwarded X server: %s", err);
x11_send_init_error(xconn, err_message);
sfree(err_message);
return 0;
}
/*
* Write a new connection header containing our replacement
* auth data.
*/
socketdatalen = 0; /* placate compiler warning */
socketdata = sk_getxdmdata(xconn->s, &socketdatalen);
if (socketdata && socketdatalen==6) {
sprintf(new_peer_addr, "%d.%d.%d.%d", socketdata[0],
socketdata[1], socketdata[2], socketdata[3]);
new_peer_port = GET_16BIT_MSB_FIRST(socketdata + 4);
} else {
strcpy(new_peer_addr, "0.0.0.0");
new_peer_port = 0;
}
greeting = x11_make_greeting(xconn->firstpkt[0],
protomajor, protominor,
xconn->disp->localauthproto,
xconn->disp->localauthdata,
xconn->disp->localauthdatalen,
new_peer_addr, new_peer_port,
&greeting_len);
sk_write(xconn->s, greeting, greeting_len);
smemclr(greeting, greeting_len);
sfree(greeting);
/*
* Now we're done.
*/
xconn->verified = true;
}
/*
* After initialisation, just copy data simply.
*/
return sk_write(xconn->s, data, len);
}
static void x11_send_eof(Channel *chan)
{
assert(chan->vt == &X11Connection_channelvt);
X11Connection *xconn = container_of(chan, X11Connection, chan);
if (xconn->s) {
sk_write_eof(xconn->s);
} else {
/*
* If EOF is received from the X client before we've got to
* the point of actually connecting to an X server, then we
* should send an EOF back to the client so that the
* forwarded channel will be terminated.
*/
if (xconn->c)
sshfwd_write_eof(xconn->c);
}
}
static char *x11_log_close_msg(Channel *chan)
{
return dupstr("Forwarded X11 connection terminated");
}

1253
ssh/zlib.c Normal file

File diff suppressed because it is too large Load Diff