+ crFinish(1);
+}
+
+/*
+ * Add data to an SSH2 channel output buffer.
+ */
+static void ssh2_add_channel_data(struct ssh_channel *c, char *buf,
+ int len)
+{
+ bufchain_add(&c->v.v2.outbuffer, buf, len);
+}
+
+/*
+ * Attempt to send data on an SSH2 channel.
+ */
+static int ssh2_try_send(struct ssh_channel *c)
+{
+ Ssh ssh = c->ssh;
+
+ while (c->v.v2.remwindow > 0 && bufchain_size(&c->v.v2.outbuffer) > 0) {
+ int len;
+ void *data;
+ bufchain_prefix(&c->v.v2.outbuffer, &data, &len);
+ if ((unsigned)len > c->v.v2.remwindow)
+ len = c->v.v2.remwindow;
+ if ((unsigned)len > c->v.v2.remmaxpkt)
+ len = c->v.v2.remmaxpkt;
+ ssh2_pkt_init(ssh, SSH2_MSG_CHANNEL_DATA);
+ ssh2_pkt_adduint32(ssh, c->remoteid);
+ ssh2_pkt_addstring_start(ssh);
+ ssh2_pkt_addstring_data(ssh, data, len);
+ ssh2_pkt_send(ssh);
+ bufchain_consume(&c->v.v2.outbuffer, len);
+ c->v.v2.remwindow -= len;
+ }
+
+ /*
+ * After having sent as much data as we can, return the amount
+ * still buffered.
+ */
+ return bufchain_size(&c->v.v2.outbuffer);
+}
+
+/*
+ * Potentially enlarge the window on an SSH2 channel.
+ */
+static void ssh2_set_window(struct ssh_channel *c, unsigned newwin)
+{
+ Ssh ssh = c->ssh;
+
+ /*
+ * 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(ssh, SSH2_MSG_CHANNEL_WINDOW_ADJUST);
+ ssh2_pkt_adduint32(ssh, c->remoteid);
+ ssh2_pkt_adduint32(ssh, newwin - c->v.v2.locwindow);
+ ssh2_pkt_send(ssh);
+ c->v.v2.locwindow = newwin;
+ }
+}
+
+/*
+ * Handle the SSH2 userauth and connection layers.
+ */
+static void do_ssh2_authconn(Ssh ssh, unsigned char *in, int inlen, int ispkt)
+{
+ struct do_ssh2_authconn_state {
+ enum {
+ AUTH_INVALID, AUTH_PUBLICKEY_AGENT, AUTH_PUBLICKEY_FILE,
+ AUTH_PASSWORD,
+ AUTH_KEYBOARD_INTERACTIVE
+ } method;
+ enum {
+ AUTH_TYPE_NONE,
+ AUTH_TYPE_PUBLICKEY,
+ AUTH_TYPE_PUBLICKEY_OFFER_LOUD,
+ AUTH_TYPE_PUBLICKEY_OFFER_QUIET,
+ AUTH_TYPE_PASSWORD,
+ AUTH_TYPE_KEYBOARD_INTERACTIVE,
+ AUTH_TYPE_KEYBOARD_INTERACTIVE_QUIET
+ } type;
+ int gotit, need_pw, can_pubkey, can_passwd, can_keyb_inter;
+ int tried_pubkey_config, tried_agent, tried_keyb_inter;
+ int kbd_inter_running;
+ int we_are_in;
+ int num_prompts, curr_prompt, echo;
+ char username[100];
+ int got_username;
+ char pwprompt[200];
+ char password[100];
+ void *publickey_blob;
+ int publickey_bloblen;
+ unsigned char request[5], *response, *p;
+ int responselen;
+ int keyi, nkeys;
+ int authed;
+ char *pkblob, *alg, *commentp;
+ int pklen, alglen, commentlen;
+ int siglen, retlen, len;
+ char *q, *agentreq, *ret;
+ int try_send;
+ };
+ crState(do_ssh2_authconn_state);
+
+ crBegin(ssh->do_ssh2_authconn_crstate);
+
+ /*
+ * Request userauth protocol, and await a response to it.
+ */
+ ssh2_pkt_init(ssh, SSH2_MSG_SERVICE_REQUEST);
+ ssh2_pkt_addstring(ssh, "ssh-userauth");
+ ssh2_pkt_send(ssh);
+ crWaitUntilV(ispkt);
+ if (ssh->pktin.type != SSH2_MSG_SERVICE_ACCEPT) {
+ bombout((ssh,"Server refused user authentication protocol"));
+ crReturnV;
+ }
+
+ /*
+ * We repeat this whole loop, including the username prompt,
+ * until we manage a successful authentication. If the user
+ * types the wrong _password_, they can be sent back to the
+ * beginning to try another username, if this is configured on.
+ * (If they specify a username in the config, they are never
+ * asked, even if they do give a wrong password.)
+ *
+ * I think this best serves the needs of
+ *
+ * - the people who have no configuration, no keys, and just
+ * want to try repeated (username,password) pairs until they
+ * type both correctly
+ *
+ * - people who have keys and configuration but occasionally
+ * need to fall back to passwords
+ *
+ * - people with a key held in Pageant, who might not have
+ * logged in to a particular machine before; so they want to
+ * type a username, and then _either_ their key will be
+ * accepted, _or_ they will type a password. If they mistype
+ * the username they will want to be able to get back and
+ * retype it!
+ */
+ s->username[0] = '\0';
+ s->got_username = FALSE;
+ do {
+ /*
+ * Get a username.
+ */
+ if (s->got_username && !ssh->cfg.change_username) {
+ /*
+ * We got a username last time round this loop, and
+ * with change_username turned off we don't try to get
+ * it again.
+ */
+ } else if ((flags & FLAG_INTERACTIVE) && !*ssh->cfg.username) {
+ if (ssh_get_line && !ssh_getline_pw_only) {
+ if (!ssh_get_line("login as: ",
+ s->username, sizeof(s->username), FALSE)) {
+ /*
+ * get_line failed to get a username.
+ * Terminate.
+ */
+ logevent("No username provided. Abandoning session.");
+ ssh->state = SSH_STATE_CLOSED;
+ crReturnV;
+ }
+ } else {
+ int ret; /* need not be saved across crReturn */
+ c_write_str(ssh, "login as: ");
+ ssh->send_ok = 1;
+ setup_userpass_input(ssh, s->username, sizeof(s->username), 1);
+ do {
+ crWaitUntilV(!ispkt);
+ ret = process_userpass_input(ssh, in, inlen);
+ } while (ret == 0);
+ if (ret < 0)
+ cleanup_exit(0);
+ c_write_str(ssh, "\r\n");
+ }
+ s->username[strcspn(s->username, "\n\r")] = '\0';
+ } else {
+ char *stuff;
+ strncpy(s->username, ssh->cfg.username, sizeof(s->username));
+ s->username[sizeof(s->username)-1] = '\0';
+ if ((flags & FLAG_VERBOSE) || (flags & FLAG_INTERACTIVE)) {
+ stuff = dupprintf("Using username \"%s\".\r\n", s->username);
+ c_write_str(ssh, stuff);
+ sfree(stuff);
+ }
+ }
+ s->got_username = TRUE;
+
+ /*
+ * Send an authentication request using method "none": (a)
+ * just in case it succeeds, and (b) so that we know what
+ * authentication methods we can usefully try next.
+ */
+ ssh->pkt_ctx &= ~SSH2_PKTCTX_AUTH_MASK;
+
+ ssh2_pkt_init(ssh, SSH2_MSG_USERAUTH_REQUEST);
+ ssh2_pkt_addstring(ssh, s->username);
+ ssh2_pkt_addstring(ssh, "ssh-connection");/* service requested */
+ ssh2_pkt_addstring(ssh, "none"); /* method */
+ ssh2_pkt_send(ssh);
+ s->type = AUTH_TYPE_NONE;
+ s->gotit = FALSE;
+ s->we_are_in = FALSE;
+
+ s->tried_pubkey_config = FALSE;
+ s->tried_agent = FALSE;
+ s->tried_keyb_inter = FALSE;
+ s->kbd_inter_running = FALSE;
+ /* Load the pub half of ssh->cfg.keyfile so we notice if it's in Pageant */
+ if (!filename_is_null(ssh->cfg.keyfile)) {
+ int keytype;
+ logeventf(ssh, "Reading private key file \"%.150s\"",
+ filename_to_str(&ssh->cfg.keyfile));
+ keytype = key_type(&ssh->cfg.keyfile);
+ if (keytype == SSH_KEYTYPE_SSH2) {
+ s->publickey_blob =
+ ssh2_userkey_loadpub(&ssh->cfg.keyfile, NULL,
+ &s->publickey_bloblen);
+ } else {
+ char *msgbuf;
+ logeventf(ssh, "Unable to use this key file (%s)",
+ key_type_to_str(keytype));
+ msgbuf = dupprintf("Unable to use key file \"%.150s\""
+ " (%s)\r\n",
+ filename_to_str(&ssh->cfg.keyfile),
+ key_type_to_str(keytype));
+ c_write_str(ssh, msgbuf);
+ sfree(msgbuf);
+ s->publickey_blob = NULL;
+ }
+ } else
+ s->publickey_blob = NULL;
+
+ while (1) {
+ /*
+ * Wait for the result of the last authentication request.
+ */
+ if (!s->gotit)
+ crWaitUntilV(ispkt);
+ while (ssh->pktin.type == SSH2_MSG_USERAUTH_BANNER) {
+ char *banner;
+ int size;
+ /*
+ * Don't show the banner if we're operating in
+ * non-verbose non-interactive mode. (It's probably
+ * a script, which means nobody will read the
+ * banner _anyway_, and moreover the printing of
+ * the banner will screw up processing on the
+ * output of (say) plink.)
+ */
+ if (flags & (FLAG_VERBOSE | FLAG_INTERACTIVE)) {
+ ssh2_pkt_getstring(ssh, &banner, &size);
+ if (banner)
+ c_write_untrusted(ssh, banner, size);
+ }
+ crWaitUntilV(ispkt);
+ }
+ if (ssh->pktin.type == SSH2_MSG_USERAUTH_SUCCESS) {
+ logevent("Access granted");
+ s->we_are_in = TRUE;
+ break;
+ }
+
+ if (s->kbd_inter_running &&
+ ssh->pktin.type == SSH2_MSG_USERAUTH_INFO_REQUEST) {
+ /*
+ * This is either a further set-of-prompts packet
+ * in keyboard-interactive authentication, or it's
+ * the same one and we came back here with `gotit'
+ * set. In the former case, we must reset the
+ * curr_prompt variable.
+ */
+ if (!s->gotit)
+ s->curr_prompt = 0;
+ } else if (ssh->pktin.type != SSH2_MSG_USERAUTH_FAILURE) {
+ bombout((ssh,"Strange packet received during authentication: type %d",
+ ssh->pktin.type));
+ crReturnV;
+ }
+
+ s->gotit = FALSE;
+
+ /*
+ * OK, we're now sitting on a USERAUTH_FAILURE message, so
+ * we can look at the string in it and know what we can
+ * helpfully try next.
+ */
+ if (ssh->pktin.type == SSH2_MSG_USERAUTH_FAILURE) {
+ char *methods;
+ int methlen;
+ ssh2_pkt_getstring(ssh, &methods, &methlen);
+ s->kbd_inter_running = FALSE;
+ if (!ssh2_pkt_getbool(ssh)) {
+ /*
+ * We have received an unequivocal Access
+ * Denied. This can translate to a variety of
+ * messages:
+ *
+ * - if we'd just tried "none" authentication,
+ * it's not worth printing anything at all
+ *
+ * - if we'd just tried a public key _offer_,
+ * the message should be "Server refused our
+ * key" (or no message at all if the key
+ * came from Pageant)
+ *
+ * - if we'd just tried anything else, the
+ * message really should be "Access denied".
+ *
+ * Additionally, if we'd just tried password
+ * authentication, we should break out of this
+ * whole loop so as to go back to the username
+ * prompt.
+ */
+ if (s->type == AUTH_TYPE_NONE) {
+ /* do nothing */
+ } else if (s->type == AUTH_TYPE_PUBLICKEY_OFFER_LOUD ||
+ s->type == AUTH_TYPE_PUBLICKEY_OFFER_QUIET) {
+ if (s->type == AUTH_TYPE_PUBLICKEY_OFFER_LOUD)
+ c_write_str(ssh, "Server refused our key\r\n");
+ logevent("Server refused public key");
+ } else if (s->type==AUTH_TYPE_KEYBOARD_INTERACTIVE_QUIET) {
+ /* server declined keyboard-interactive; ignore */
+ } else {
+ c_write_str(ssh, "Access denied\r\n");
+ logevent("Access denied");
+ if (s->type == AUTH_TYPE_PASSWORD) {
+ s->we_are_in = FALSE;
+ break;
+ }
+ }
+ } else {
+ c_write_str(ssh, "Further authentication required\r\n");
+ logevent("Further authentication required");
+ }
+
+ s->can_pubkey =
+ in_commasep_string("publickey", methods, methlen);
+ s->can_passwd =
+ in_commasep_string("password", methods, methlen);
+ s->can_keyb_inter = ssh->cfg.try_ki_auth &&
+ in_commasep_string("keyboard-interactive", methods, methlen);
+ }
+
+ s->method = 0;
+ ssh->pkt_ctx &= ~SSH2_PKTCTX_AUTH_MASK;
+
+ /*
+ * Most password/passphrase prompts will be
+ * non-echoing, so we set this to 0 by default.
+ * Exception is that some keyboard-interactive prompts
+ * can be echoing, in which case we'll set this to 1.
+ */
+ s->echo = 0;
+
+ if (!s->method && s->can_pubkey &&
+ agent_exists() && !s->tried_agent) {
+ /*
+ * Attempt public-key authentication using Pageant.
+ */
+ void *r;
+ s->authed = FALSE;
+
+ ssh->pkt_ctx &= ~SSH2_PKTCTX_AUTH_MASK;
+ ssh->pkt_ctx |= SSH2_PKTCTX_PUBLICKEY;
+
+ s->tried_agent = TRUE;
+
+ logevent("Pageant is running. Requesting keys.");
+
+ /* Request the keys held by the agent. */
+ PUT_32BIT(s->request, 1);
+ s->request[4] = SSH2_AGENTC_REQUEST_IDENTITIES;
+ agent_query(s->request, 5, &r, &s->responselen);
+ s->response = (unsigned char *) r;
+ if (s->response && s->responselen >= 5 &&
+ s->response[4] == SSH2_AGENT_IDENTITIES_ANSWER) {
+ s->p = s->response + 5;
+ s->nkeys = GET_32BIT(s->p);
+ s->p += 4;
+ {
+ char buf[64];
+ sprintf(buf, "Pageant has %d SSH2 keys", s->nkeys);
+ logevent(buf);
+ }
+ for (s->keyi = 0; s->keyi < s->nkeys; s->keyi++) {
+ void *vret;
+
+ {
+ char buf[64];
+ sprintf(buf, "Trying Pageant key #%d", s->keyi);
+ logevent(buf);
+ }
+ s->pklen = GET_32BIT(s->p);
+ s->p += 4;
+ if (s->publickey_blob &&
+ s->pklen == s->publickey_bloblen &&
+ !memcmp(s->p, s->publickey_blob,
+ s->publickey_bloblen)) {
+ logevent("This key matches configured key file");
+ s->tried_pubkey_config = 1;
+ }
+ s->pkblob = (char *)s->p;
+ s->p += s->pklen;
+ s->alglen = GET_32BIT(s->pkblob);
+ s->alg = s->pkblob + 4;
+ s->commentlen = GET_32BIT(s->p);
+ s->p += 4;
+ s->commentp = (char *)s->p;
+ s->p += s->commentlen;
+ ssh2_pkt_init(ssh, SSH2_MSG_USERAUTH_REQUEST);
+ ssh2_pkt_addstring(ssh, s->username);
+ ssh2_pkt_addstring(ssh, "ssh-connection"); /* service requested */
+ ssh2_pkt_addstring(ssh, "publickey"); /* method */
+ ssh2_pkt_addbool(ssh, FALSE); /* no signature included */
+ ssh2_pkt_addstring_start(ssh);
+ ssh2_pkt_addstring_data(ssh, s->alg, s->alglen);
+ ssh2_pkt_addstring_start(ssh);
+ ssh2_pkt_addstring_data(ssh, s->pkblob, s->pklen);
+ ssh2_pkt_send(ssh);
+
+ crWaitUntilV(ispkt);
+ if (ssh->pktin.type != SSH2_MSG_USERAUTH_PK_OK) {
+ logevent("Key refused");
+ continue;
+ }
+
+ if (flags & FLAG_VERBOSE) {
+ c_write_str(ssh, "Authenticating with "
+ "public key \"");
+ c_write(ssh, s->commentp, s->commentlen);
+ c_write_str(ssh, "\" from agent\r\n");
+ }
+
+ /*
+ * Server is willing to accept the key.
+ * Construct a SIGN_REQUEST.
+ */
+ ssh2_pkt_init(ssh, SSH2_MSG_USERAUTH_REQUEST);
+ ssh2_pkt_addstring(ssh, s->username);
+ ssh2_pkt_addstring(ssh, "ssh-connection"); /* service requested */
+ ssh2_pkt_addstring(ssh, "publickey"); /* method */
+ ssh2_pkt_addbool(ssh, TRUE);
+ ssh2_pkt_addstring_start(ssh);
+ ssh2_pkt_addstring_data(ssh, s->alg, s->alglen);
+ ssh2_pkt_addstring_start(ssh);
+ ssh2_pkt_addstring_data(ssh, s->pkblob, s->pklen);
+
+ s->siglen = ssh->pktout.length - 5 + 4 + 20;
+ if (ssh->remote_bugs & BUG_SSH2_PK_SESSIONID)
+ s->siglen -= 4;
+ s->len = 1; /* message type */
+ s->len += 4 + s->pklen; /* key blob */
+ s->len += 4 + s->siglen; /* data to sign */
+ s->len += 4; /* flags */
+ s->agentreq = smalloc(4 + s->len);
+ PUT_32BIT(s->agentreq, s->len);
+ s->q = s->agentreq + 4;
+ *s->q++ = SSH2_AGENTC_SIGN_REQUEST;
+ PUT_32BIT(s->q, s->pklen);
+ s->q += 4;
+ memcpy(s->q, s->pkblob, s->pklen);
+ s->q += s->pklen;
+ PUT_32BIT(s->q, s->siglen);
+ s->q += 4;
+ /* Now the data to be signed... */
+ if (!(ssh->remote_bugs & BUG_SSH2_PK_SESSIONID)) {
+ PUT_32BIT(s->q, 20);
+ s->q += 4;
+ }
+ memcpy(s->q, ssh->v2_session_id, 20);
+ s->q += 20;
+ memcpy(s->q, ssh->pktout.data + 5,
+ ssh->pktout.length - 5);
+ s->q += ssh->pktout.length - 5;
+ /* And finally the (zero) flags word. */
+ PUT_32BIT(s->q, 0);
+ agent_query(s->agentreq, s->len + 4, &vret, &s->retlen);
+ s->ret = vret;
+ sfree(s->agentreq);
+ if (s->ret) {
+ if (s->ret[4] == SSH2_AGENT_SIGN_RESPONSE) {
+ logevent("Sending Pageant's response");
+ ssh2_add_sigblob(ssh, s->pkblob, s->pklen,
+ s->ret + 9,
+ GET_32BIT(s->ret + 5));
+ ssh2_pkt_send(ssh);
+ s->authed = TRUE;
+ break;
+ } else {
+ logevent
+ ("Pageant failed to answer challenge");
+ sfree(s->ret);
+ }
+ }
+ }
+ if (s->authed)
+ continue;
+ }
+ }
+
+ if (!s->method && s->can_pubkey && s->publickey_blob
+ && !s->tried_pubkey_config) {
+ unsigned char *pub_blob;
+ char *algorithm, *comment;
+ int pub_blob_len;
+
+ s->tried_pubkey_config = TRUE;
+
+ ssh->pkt_ctx &= ~SSH2_PKTCTX_AUTH_MASK;
+ ssh->pkt_ctx |= SSH2_PKTCTX_PUBLICKEY;
+
+ /*
+ * Try the public key supplied in the configuration.
+ *
+ * First, offer the public blob to see if the server is
+ * willing to accept it.
+ */
+ pub_blob =
+ (unsigned char *)ssh2_userkey_loadpub(&ssh->cfg.keyfile,
+ &algorithm,
+ &pub_blob_len);
+ if (pub_blob) {
+ ssh2_pkt_init(ssh, SSH2_MSG_USERAUTH_REQUEST);
+ ssh2_pkt_addstring(ssh, s->username);
+ ssh2_pkt_addstring(ssh, "ssh-connection"); /* service requested */
+ ssh2_pkt_addstring(ssh, "publickey"); /* method */
+ ssh2_pkt_addbool(ssh, FALSE); /* no signature included */
+ ssh2_pkt_addstring(ssh, algorithm);
+ ssh2_pkt_addstring_start(ssh);
+ ssh2_pkt_addstring_data(ssh, (char *)pub_blob,
+ pub_blob_len);
+ ssh2_pkt_send(ssh);
+ logevent("Offered public key"); /* FIXME */
+
+ crWaitUntilV(ispkt);
+ if (ssh->pktin.type != SSH2_MSG_USERAUTH_PK_OK) {
+ s->gotit = TRUE;
+ s->type = AUTH_TYPE_PUBLICKEY_OFFER_LOUD;
+ continue; /* key refused; give up on it */
+ }
+
+ logevent("Offer of public key accepted");
+ /*
+ * Actually attempt a serious authentication using
+ * the key.
+ */
+ if (ssh2_userkey_encrypted(&ssh->cfg.keyfile, &comment)) {
+ sprintf(s->pwprompt,
+ "Passphrase for key \"%.100s\": ",
+ comment);
+ s->need_pw = TRUE;
+ } else {
+ s->need_pw = FALSE;
+ }
+ c_write_str(ssh, "Authenticating with public key \"");
+ c_write_str(ssh, comment);
+ c_write_str(ssh, "\"\r\n");
+ s->method = AUTH_PUBLICKEY_FILE;
+ }
+ }
+
+ if (!s->method && s->can_keyb_inter && !s->tried_keyb_inter) {
+ s->method = AUTH_KEYBOARD_INTERACTIVE;
+ s->type = AUTH_TYPE_KEYBOARD_INTERACTIVE;
+ s->tried_keyb_inter = TRUE;
+
+ ssh->pkt_ctx &= ~SSH2_PKTCTX_AUTH_MASK;
+ ssh->pkt_ctx |= SSH2_PKTCTX_KBDINTER;
+
+ ssh2_pkt_init(ssh, SSH2_MSG_USERAUTH_REQUEST);
+ ssh2_pkt_addstring(ssh, s->username);
+ ssh2_pkt_addstring(ssh, "ssh-connection"); /* service requested */
+ ssh2_pkt_addstring(ssh, "keyboard-interactive"); /* method */
+ ssh2_pkt_addstring(ssh, ""); /* lang */
+ ssh2_pkt_addstring(ssh, "");
+ ssh2_pkt_send(ssh);
+
+ crWaitUntilV(ispkt);
+ if (ssh->pktin.type != SSH2_MSG_USERAUTH_INFO_REQUEST) {
+ if (ssh->pktin.type == SSH2_MSG_USERAUTH_FAILURE)
+ s->gotit = TRUE;
+ logevent("Keyboard-interactive authentication refused");
+ s->type = AUTH_TYPE_KEYBOARD_INTERACTIVE_QUIET;
+ continue;
+ }
+
+ s->kbd_inter_running = TRUE;
+ s->curr_prompt = 0;
+ }
+
+ if (s->kbd_inter_running) {
+ s->method = AUTH_KEYBOARD_INTERACTIVE;
+ s->type = AUTH_TYPE_KEYBOARD_INTERACTIVE;
+ s->tried_keyb_inter = TRUE;
+
+ ssh->pkt_ctx &= ~SSH2_PKTCTX_AUTH_MASK;
+ ssh->pkt_ctx |= SSH2_PKTCTX_KBDINTER;
+
+ if (s->curr_prompt == 0) {
+ /*
+ * We've got a fresh USERAUTH_INFO_REQUEST.
+ * Display header data, and start going through
+ * the prompts.
+ */
+ char *name, *inst, *lang;
+ int name_len, inst_len, lang_len;
+
+ ssh2_pkt_getstring(ssh, &name, &name_len);
+ ssh2_pkt_getstring(ssh, &inst, &inst_len);
+ ssh2_pkt_getstring(ssh, &lang, &lang_len);
+ if (name_len > 0) {
+ c_write_untrusted(ssh, name, name_len);
+ c_write_str(ssh, "\r\n");
+ }
+ if (inst_len > 0) {
+ c_write_untrusted(ssh, inst, inst_len);
+ c_write_str(ssh, "\r\n");
+ }
+ s->num_prompts = ssh2_pkt_getuint32(ssh);
+ }