if ((flags & FLAG_STDERR) && (flags & FLAG_VERBOSE)) \
{ fprintf(stderr, "%s\n", s); fflush(stderr); } }
+/* logevent, only printf-formatted. */
+void logeventf(char *fmt, ...)
+{
+ va_list ap;
+ char stuff[200];
+
+ va_start(ap, fmt);
+ vsprintf(stuff, fmt, ap);
+ va_end(ap);
+ logevent(stuff);
+}
+
#define bombout(msg) ( ssh_state = SSH_STATE_CLOSED, \
(s ? sk_close(s), s = NULL : 0), \
- connection_fatal msg )
+ logeventf msg, connection_fatal msg )
#define SSH1_MSG_DISCONNECT 1 /* 0x1 */
#define SSH1_SMSG_PUBLIC_KEY 2 /* 0x2 */
#define BUG_CHOKES_ON_SSH1_IGNORE 1
#define BUG_SSH2_HMAC 2
#define BUG_NEEDS_SSH1_PLAIN_PASSWORD 4
+#define BUG_CHOKES_ON_RSA 8
static int ssh_pkt_ctx = 0;
extern void pfd_unthrottle(Socket s);
extern void pfd_override_throttle(Socket s, int enable);
+static void ssh2_pkt_init(int pkt_type);
+static void ssh2_pkt_addbool(unsigned char value);
+static void ssh2_pkt_adduint32(unsigned long value);
+static void ssh2_pkt_addstring_start(void);
+static void ssh2_pkt_addstring_str(char *data);
+static void ssh2_pkt_addstring_data(char *data, int len);
+static void ssh2_pkt_addstring(char *data);
+static char *ssh2_mpint_fmt(Bignum b, int *len);
+static void ssh2_pkt_addmp(Bignum b);
+static int ssh2_pkt_construct(void);
+static void ssh2_pkt_send(void);
+
/*
* Buffer management constants. There are several of these for
* various different purposes:
static tree234 *ssh_channels; /* indexed by local id */
static struct ssh_channel *mainchan; /* primary session channel */
+static int ssh_exitcode = -1;
static tree234 *ssh_rportfwds;
st->to_read -= st->chunk;
}
+ if (cipher && detect_attack(pktin.data, st->biglen, NULL)) {
+ bombout(("Network attack (CRC compensation) detected!"));
+ crReturn(0);
+ }
+
if (cipher)
cipher->decrypt(pktin.data, st->biglen);
pktin.type == SSH1_MSG_DEBUG ||
pktin.type == SSH1_SMSG_AUTH_TIS_CHALLENGE ||
pktin.type == SSH1_SMSG_AUTH_CCARD_CHALLENGE) {
- long strlen = GET_32BIT(pktin.body);
- if (strlen + 4 != pktin.length) {
+ long stringlen = GET_32BIT(pktin.body);
+ if (stringlen + 4 != pktin.length) {
bombout(("Received data packet with bogus string length"));
crReturn(0);
}
if (pktin.type == SSH1_MSG_DEBUG) {
/* log debug message */
char buf[80];
- int strlen = GET_32BIT(pktin.body);
+ int stringlen = GET_32BIT(pktin.body);
strcpy(buf, "Remote: ");
- if (strlen > 70)
- strlen = 70;
- memcpy(buf + 8, pktin.body + 4, strlen);
- buf[8 + strlen] = '\0';
+ if (stringlen > 70)
+ stringlen = 70;
+ memcpy(buf + 8, pktin.body + 4, stringlen);
+ buf[8 + stringlen] = '\0';
logevent(buf);
goto next_packet;
} else if (pktin.type == SSH1_MSG_IGNORE) {
msglen = sizeof(buf) - nowlen - 1;
memcpy(buf + nowlen, pktin.body + 4, msglen);
buf[nowlen + msglen] = '\0';
- logevent(buf);
+ /* logevent(buf); (this is now done within the bombout macro) */
bombout(("Server sent disconnect message:\n\"%s\"", buf+nowlen));
crReturn(0);
}
log_packet(PKT_INCOMING, pktin.type, ssh2_pkt_type(pktin.type),
pktin.data+6, pktin.length-6);
- if (pktin.type == SSH2_MSG_IGNORE || pktin.type == SSH2_MSG_DEBUG)
- goto next_packet; /* FIXME: print DEBUG message */
-
- if (pktin.type == SSH2_MSG_DISCONNECT) {
- /* log reason code in disconnect message */
- char buf[256];
- int reason = GET_32BIT(pktin.data + 6);
- unsigned msglen = GET_32BIT(pktin.data + 10);
- unsigned nowlen;
- if (reason > 0 && reason < lenof(ssh2_disconnect_reasons)) {
- sprintf(buf, "Received disconnect message (%s)",
- ssh2_disconnect_reasons[reason]);
- } else {
- sprintf(buf, "Received disconnect message (unknown type %d)",
- reason);
+ switch (pktin.type) {
+ /*
+ * These packets we must handle instantly.
+ */
+ case SSH2_MSG_DISCONNECT:
+ {
+ /* log reason code in disconnect message */
+ char buf[256];
+ int reason = GET_32BIT(pktin.data + 6);
+ unsigned msglen = GET_32BIT(pktin.data + 10);
+ unsigned nowlen;
+ if (reason > 0 && reason < lenof(ssh2_disconnect_reasons)) {
+ sprintf(buf, "Received disconnect message (%s)",
+ ssh2_disconnect_reasons[reason]);
+ } else {
+ sprintf(buf, "Received disconnect message (unknown type %d)",
+ reason);
+ }
+ logevent(buf);
+ strcpy(buf, "Disconnection message text: ");
+ nowlen = strlen(buf);
+ if (msglen > sizeof(buf) - nowlen - 1)
+ msglen = sizeof(buf) - nowlen - 1;
+ memcpy(buf + nowlen, pktin.data + 14, msglen);
+ buf[nowlen + msglen] = '\0';
+ logevent(buf);
+ bombout(("Server sent disconnect message\ntype %d (%s):\n\"%s\"",
+ reason,
+ (reason > 0 && reason < lenof(ssh2_disconnect_reasons)) ?
+ ssh2_disconnect_reasons[reason] : "unknown",
+ buf+nowlen));
+ crReturn(0);
+ }
+ break;
+ case SSH2_MSG_IGNORE:
+ goto next_packet;
+ case SSH2_MSG_DEBUG:
+ {
+ /* log the debug message */
+ char buf[512];
+ /* int display = pktin.body[6]; */
+ int stringlen = GET_32BIT(pktin.data+7);
+ int prefix;
+ strcpy(buf, "Remote debug message: ");
+ prefix = strlen(buf);
+ if (stringlen > sizeof(buf)-prefix-1)
+ stringlen = sizeof(buf)-prefix-1;
+ memcpy(buf + prefix, pktin.data + 11, stringlen);
+ buf[prefix + stringlen] = '\0';
+ logevent(buf);
}
- logevent(buf);
- strcpy(buf, "Disconnection message text: ");
- nowlen = strlen(buf);
- if (msglen > sizeof(buf) - nowlen - 1)
- msglen = sizeof(buf) - nowlen - 1;
- memcpy(buf + nowlen, pktin.data + 14, msglen);
- buf[nowlen + msglen] = '\0';
- logevent(buf);
- bombout(("Server sent disconnect message\ntype %d (%s):\n\"%s\"",
- reason,
- (reason > 0 && reason < lenof(ssh2_disconnect_reasons)) ?
- ssh2_disconnect_reasons[reason] : "unknown",
- buf+nowlen));
- crReturn(0);
+ goto next_packet; /* FIXME: print the debug message */
+
+ /*
+ * These packets we need do nothing about here.
+ */
+ case SSH2_MSG_UNIMPLEMENTED:
+ case SSH2_MSG_SERVICE_REQUEST:
+ case SSH2_MSG_SERVICE_ACCEPT:
+ case SSH2_MSG_KEXINIT:
+ case SSH2_MSG_NEWKEYS:
+ case SSH2_MSG_KEXDH_INIT:
+ case SSH2_MSG_KEXDH_REPLY:
+ /* case SSH2_MSG_KEX_DH_GEX_REQUEST: duplicate case value */
+ /* case SSH2_MSG_KEX_DH_GEX_GROUP: duplicate case value */
+ case SSH2_MSG_KEX_DH_GEX_INIT:
+ case SSH2_MSG_KEX_DH_GEX_REPLY:
+ case SSH2_MSG_USERAUTH_REQUEST:
+ case SSH2_MSG_USERAUTH_FAILURE:
+ case SSH2_MSG_USERAUTH_SUCCESS:
+ case SSH2_MSG_USERAUTH_BANNER:
+ case SSH2_MSG_USERAUTH_PK_OK:
+ /* case SSH2_MSG_USERAUTH_PASSWD_CHANGEREQ: duplicate case value */
+ /* case SSH2_MSG_USERAUTH_INFO_REQUEST: duplicate case value */
+ case SSH2_MSG_USERAUTH_INFO_RESPONSE:
+ case SSH2_MSG_GLOBAL_REQUEST:
+ case SSH2_MSG_REQUEST_SUCCESS:
+ case SSH2_MSG_REQUEST_FAILURE:
+ case SSH2_MSG_CHANNEL_OPEN:
+ case SSH2_MSG_CHANNEL_OPEN_CONFIRMATION:
+ case SSH2_MSG_CHANNEL_OPEN_FAILURE:
+ case SSH2_MSG_CHANNEL_WINDOW_ADJUST:
+ case SSH2_MSG_CHANNEL_DATA:
+ case SSH2_MSG_CHANNEL_EXTENDED_DATA:
+ case SSH2_MSG_CHANNEL_EOF:
+ case SSH2_MSG_CHANNEL_CLOSE:
+ case SSH2_MSG_CHANNEL_REQUEST:
+ case SSH2_MSG_CHANNEL_SUCCESS:
+ case SSH2_MSG_CHANNEL_FAILURE:
+ break;
+
+ /*
+ * For anything else we send SSH2_MSG_UNIMPLEMENTED.
+ */
+ default:
+ ssh2_pkt_init(SSH2_MSG_UNIMPLEMENTED);
+ ssh2_pkt_adduint32(st->incoming_sequence - 1);
+ ssh2_pkt_send();
+ break;
}
crFinish(0);
logevent("We believe remote version needs a plain SSH1 password");
}
+ if (!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.
+ */
+ ssh_remote_bugs |= BUG_CHOKES_ON_RSA;
+ logevent("We believe remote version can't handle RSA authentication");
+ }
+
if (!strncmp(imp, "2.1.0", 5) || !strncmp(imp, "2.0.", 4) ||
!strncmp(imp, "2.2.0", 5) || !strncmp(imp, "2.3.0", 5) ||
!strncmp(imp, "2.1 ", 4)) {
}
if (error_msg) {
/* A socket error has occurred. */
+ logevent(error_msg);
connection_fatal(error_msg);
} else {
/* Otherwise, the remote side closed the connection normally. */
sprintf(buf, "Connecting to %.100s port %d", addrbuf, port);
logevent(buf);
}
- s = sk_new(addr, port, 0, 1, nodelay, &fn_table_ptr);
- if ((err = sk_socket_error(s)))
+ s = new_connection(addr, *realhost, port, 0, 1, nodelay, &fn_table_ptr);
+ if ((err = sk_socket_error(s))) {
+ s = NULL;
return err;
+ }
#ifdef FWHACK
sk_write(s, "connect ", 8);
struct RSAKey servkey, hostkey;
struct MD5Context md5c;
static unsigned long supported_ciphers_mask, supported_auths_mask;
- static int tried_publickey;
+ static int tried_publickey, tried_agent;
static int tis_auth_refused, ccard_auth_refused;
static unsigned char session_id[16];
static int cipher_type;
static char username[100];
+ static void *publickey_blob;
+ int publickey_bloblen;
crBegin;
break;
case 3:
case 4:
- random_save_seed();
- exit(0);
+ cleanup_exit(0);
break;
default:
if (((c >= ' ' && c <= '~') ||
crWaitUntil(ispkt);
- tried_publickey = 0;
+ if ((ssh_remote_bugs & BUG_CHOKES_ON_RSA)) {
+ /* We must not attempt PK auth. Pretend we've already tried it. */
+ tried_publickey = tried_agent = 1;
+ } else {
+ tried_publickey = tried_agent = 0;
+ }
tis_auth_refused = ccard_auth_refused = 0;
+ /* Load the public half of cfg.keyfile so we notice if it's in Pageant */
+ if (*cfg.keyfile) {
+ if (!rsakey_pubblob(cfg.keyfile, &publickey_blob, &publickey_bloblen))
+ publickey_blob = NULL;
+ } else
+ publickey_blob = NULL;
while (pktin.type == SSH1_SMSG_FAILURE) {
static char password[100];
static int pwpkt_type;
pwpkt_type = SSH1_CMSG_AUTH_PASSWORD;
- if (agent_exists()) {
+ if (agent_exists() && !tried_agent) {
/*
* Attempt RSA authentication using Pageant.
*/
static int authed = FALSE;
void *r;
+ tried_agent = 1;
logevent("Pageant is running. Requesting keys.");
/* Request the keys held by the agent. */
sprintf(buf, "Trying Pageant key #%d", i);
logevent(buf);
}
+ if (publickey_blob &&
+ !memcmp(p, publickey_blob, publickey_bloblen)) {
+ logevent("This key matches configured key file");
+ tried_publickey = 1;
+ }
p += 4;
p += ssh1_read_bignum(p, &key.exponent);
p += ssh1_read_bignum(p, &key.modulus);
send_packet(SSH1_MSG_DISCONNECT,
PKT_STR, "No more passwords available to try",
PKT_END);
+ logevent("Unable to authenticate");
connection_fatal("Unable to authenticate");
ssh_state = SSH_STATE_CLOSED;
crReturn(1);
break;
case 3:
case 4:
- random_save_seed();
- exit(0);
+ cleanup_exit(0);
break;
default:
if (pos < sizeof(password)-1)
}
{
- char type, *e;
+ char type;
+ static char *e;
int n;
- int sport,dport;
+ int sport,dport,sserv,dserv;
char sports[256], dports[256], host[256];
char buf[1024];
+ struct servent *se;
ssh_rportfwds = newtree234(ssh_rportcmp_ssh1);
/* Add port forwardings. */
dports[n] = 0;
e++;
dport = atoi(dports);
+ dserv = 0;
+ if (dport == 0) {
+ dserv = 1;
+ se = getservbyname(dports, NULL);
+ if (se != NULL) {
+ dport = ntohs(se->s_port);
+ } else {
+ sprintf(buf,
+ "Service lookup failed for destination port \"%s\"",
+ dports);
+ logevent(buf);
+ }
+ }
sport = atoi(sports);
+ sserv = 0;
+ if (sport == 0) {
+ sserv = 1;
+ se = getservbyname(sports, NULL);
+ if (se != NULL) {
+ sport = ntohs(se->s_port);
+ } else {
+ sprintf(buf,
+ "Service lookup failed for source port \"%s\"",
+ sports);
+ logevent(buf);
+ }
+ }
if (sport && dport) {
if (type == 'L') {
pfd_addforward(host, dport, sport);
- sprintf(buf, "Local port %d forwarding to %s:%d",
- sport, host, dport);
+ sprintf(buf, "Local port %.*s%.*s%d%.*s forwarding to"
+ " %s:%.*s%.*s%d%.*s",
+ sserv ? strlen(sports) : 0, sports,
+ sserv, "(", sport, sserv, ")",
+ host,
+ dserv ? strlen(dports) : 0, dports,
+ dserv, "(", dport, dserv, ")");
logevent(buf);
} else {
struct ssh_rportfwd *pf;
logevent(buf);
sfree(pf);
} else {
- sprintf(buf, "Requesting remote port %d forward to %s:%d",
- sport, host, dport);
+ sprintf(buf, "Requesting remote port %.*s%.*s%d%.*s"
+ " forward to %s:%.*s%.*s%d%.*s",
+ sserv ? strlen(sports) : 0, sports,
+ sserv, "(", sport, sserv, ")",
+ host,
+ dserv ? strlen(dports) : 0, dports,
+ dserv, "(", dport, dserv, ")");
logevent(buf);
send_packet(SSH1_CMSG_PORT_FORWARD_REQUEST,
PKT_INT, sport,
PKT_STR, host,
PKT_INT, dport,
PKT_END);
+ do {
+ crReturnV;
+ } while (!ispkt);
+ if (pktin.type != SSH1_SMSG_SUCCESS
+ && pktin.type != SSH1_SMSG_FAILURE) {
+ bombout(("Protocol confusion"));
+ crReturnV;
+ } else if (pktin.type == SSH1_SMSG_FAILURE) {
+ c_write_str("Server refused port forwarding\r\n");
+ ssh_editing = ssh_echoing = 1;
+ }
+ logevent("Remote port forwarding enabled");
}
}
}
/* may be from EXEC_SHELL on some servers
* if no pty is available or in other odd cases. Ignore */
} else if (pktin.type == SSH1_SMSG_EXIT_STATUS) {
+ char buf[100];
+ ssh_exitcode = GET_32BIT(pktin.body);
+ sprintf(buf, "Server sent command exit status %d",
+ ssh_exitcode);
+ logevent(buf);
send_packet(SSH1_CMSG_EXIT_CONFIRMATION, PKT_END);
/*
* In case `helpful' firewalls or proxies tack
*/
static void ssh2_set_window(struct ssh_channel *c, unsigned newwin)
{
+ /*
+ * Never send WINDOW_ADJUST for a channel that the remote side
+ * already thinks it's closed; there's no point, since it won't
+ * be sending any more data anyway.
+ */
+ if (c->closes != 0)
+ return;
+
if (newwin > c->v.v2.locwindow) {
ssh2_pkt_init(SSH2_MSG_CHANNEL_WINDOW_ADJUST);
ssh2_pkt_adduint32(c->remoteid);
static int we_are_in;
static int num_prompts, echo;
static char username[100];
+ static int got_username;
static char pwprompt[200];
static char password[100];
+ static void *publickey_blob;
+ static int publickey_bloblen;
crBegin;
* retype it!
*/
username[0] = '\0';
+ got_username = FALSE;
do {
static int pos;
static char c;
* Get a username.
*/
pos = 0;
- if (*username && !cfg.change_username) {
+ if (got_username && !cfg.change_username) {
/*
* We got a username last time round this loop, and
* with change_username turned off we don't try to get
break;
case 3:
case 4:
- random_save_seed();
- exit(0);
+ cleanup_exit(0);
break;
default:
if (((c >= ' ' && c <= '~') ||
c_write_str(stuff);
}
}
+ got_username = TRUE;
/*
* Send an authentication request using method "none": (a)
tried_agent = FALSE;
tried_keyb_inter = FALSE;
kbd_inter_running = FALSE;
+ /* Load the pub half of cfg.keyfile so we notice if it's in Pageant */
+ if (*cfg.keyfile) {
+ publickey_blob = ssh2_userkey_loadpub(cfg.keyfile, NULL,
+ &publickey_bloblen);
+ } else
+ publickey_blob = NULL;
while (1) {
/*
}
pklen = GET_32BIT(p);
p += 4;
+ if (publickey_blob &&
+ pklen == publickey_bloblen &&
+ !memcmp(p, publickey_blob, publickey_bloblen)) {
+ logevent("This key matches configured key file");
+ tried_pubkey_config = 1;
+ }
pkblob = p;
p += pklen;
alglen = GET_32BIT(pkblob);
("No more passwords available to try");
ssh2_pkt_addstring("en"); /* language tag */
ssh2_pkt_send();
+ logevent("Unable to authenticate");
connection_fatal("Unable to authenticate");
ssh_state = SSH_STATE_CLOSED;
crReturnV;
break;
case 3:
case 4:
- random_save_seed();
- exit(0);
+ cleanup_exit(0);
break;
default:
if (pos < sizeof(password)-1)
static char *e; /* preserve across crReturn */
char type;
int n;
- int sport,dport;
+ int sport,dport,sserv,dserv;
char sports[256], dports[256], host[256];
char buf[1024];
+ struct servent *se;
ssh_rportfwds = newtree234(ssh_rportcmp_ssh2);
/* Add port forwardings. */
dports[n] = 0;
e++;
dport = atoi(dports);
+ dserv = 0;
+ if (dport == 0) {
+ dserv = 1;
+ se = getservbyname(dports, NULL);
+ if (se != NULL) {
+ dport = ntohs(se->s_port);
+ } else {
+ sprintf(buf,
+ "Service lookup failed for destination port \"%s\"",
+ dports);
+ logevent(buf);
+ }
+ }
sport = atoi(sports);
+ sserv = 0;
+ if (sport == 0) {
+ sserv = 1;
+ se = getservbyname(sports, NULL);
+ if (se != NULL) {
+ sport = ntohs(se->s_port);
+ } else {
+ sprintf(buf,
+ "Service lookup failed for source port \"%s\"",
+ sports);
+ logevent(buf);
+ }
+ }
if (sport && dport) {
if (type == 'L') {
pfd_addforward(host, dport, sport);
- sprintf(buf, "Local port %d forwarding to %s:%d",
- sport, host, dport);
+ sprintf(buf, "Local port %.*s%.*s%d%.*s forwarding to"
+ " %s:%.*s%.*s%d%.*s",
+ sserv ? strlen(sports) : 0, sports,
+ sserv, "(", sport, sserv, ")",
+ host,
+ dserv ? strlen(dports) : 0, dports,
+ dserv, "(", dport, dserv, ")");
logevent(buf);
} else {
struct ssh_rportfwd *pf;
logevent(buf);
sfree(pf);
} else {
- sprintf(buf, "Requesting remote port %d (forwarded to %s:%d)",
- sport, host, dport);
+ sprintf(buf, "Requesting remote port %.*s%.*s%d%.*s"
+ " forward to %s:%.*s%.*s%d%.*s",
+ sserv ? strlen(sports) : 0, sports,
+ sserv, "(", sport, sserv, ")",
+ host,
+ dserv ? strlen(dports) : 0, dports,
+ dserv, "(", dport, dserv, ")");
logevent(buf);
ssh2_pkt_init(SSH2_MSG_GLOBAL_REQUEST);
ssh2_pkt_addstring("tcpip-forward");
ssh2_pkt_addbool(1);/* want reply */
- ssh2_pkt_addstring("127.0.0.1");
+ if (cfg.rport_acceptall)
+ ssh2_pkt_addstring("0.0.0.0");
+ else
+ ssh2_pkt_addstring("127.0.0.1");
ssh2_pkt_adduint32(sport);
ssh2_pkt_send();
ssh2_pkt_addstring(buf);
ssh2_pkt_addstring("en"); /* language tag */
ssh2_pkt_send();
- connection_fatal(buf);
+ connection_fatal("%s", buf);
ssh_state = SSH_STATE_CLOSED;
crReturnV;
}
/*
- * We don't recognise any form of channel request,
- * so we now either ignore the request or respond
- * with CHANNEL_FAILURE, depending on want_reply.
+ * Having got the channel number, we now look at
+ * the request type string to see if it's something
+ * we recognise.
*/
- if (want_reply) {
- ssh2_pkt_init(SSH2_MSG_CHANNEL_FAILURE);
- ssh2_pkt_adduint32(c->remoteid);
- ssh2_pkt_send();
+ if (typelen == 11 && !memcmp(type, "exit-status", 11) &&
+ c == mainchan) {
+ /* We recognise "exit-status" on the primary channel. */
+ char buf[100];
+ ssh_exitcode = ssh2_pkt_getuint32();
+ sprintf(buf, "Server sent command exit status %d",
+ ssh_exitcode);
+ logevent(buf);
+ if (want_reply) {
+ ssh2_pkt_init(SSH2_MSG_CHANNEL_SUCCESS);
+ ssh2_pkt_adduint32(c->remoteid);
+ ssh2_pkt_send();
+ }
+ } else {
+ /*
+ * This is a channel request we don't know
+ * about, so we now either ignore the request
+ * or respond with CHANNEL_FAILURE, depending
+ * on want_reply.
+ */
+ if (want_reply) {
+ ssh2_pkt_init(SSH2_MSG_CHANNEL_FAILURE);
+ ssh2_pkt_adduint32(c->remoteid);
+ ssh2_pkt_send();
+ }
+ }
+ } else if (pktin.type == SSH2_MSG_GLOBAL_REQUEST) {
+ char *type;
+ int typelen, want_reply;
+
+ ssh2_pkt_getstring(&type, &typelen);
+ want_reply = ssh2_pkt_getbool();
+
+ /*
+ * We currently don't support any global requests
+ * at all, so we either ignore the request or
+ * respond with REQUEST_FAILURE, depending on
+ * want_reply.
+ */
+ if (want_reply) {
+ ssh2_pkt_init(SSH2_MSG_REQUEST_FAILURE);
+ ssh2_pkt_send();
}
} else if (pktin.type == SSH2_MSG_CHANNEL_OPEN) {
char *type;
return FALSE;
}
+static int ssh_return_exitcode(void)
+{
+ return ssh_exitcode;
+}
+
Backend ssh_backend = {
ssh_init,
ssh_send,
ssh_size,
ssh_special,
ssh_socket,
+ ssh_return_exitcode,
ssh_sendok,
ssh_ldisc,
ssh_unthrottle,