| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284 |
- /*
- * Packet protocol layer for the SSH-1 login phase (combining what
- * SSH-2 would think of as key exchange and user authentication).
- */
- #include <assert.h>
- #include "putty.h"
- #include "ssh.h"
- #include "mpint.h"
- #include "sshbpp.h"
- #include "sshppl.h"
- #include "sshcr.h"
- typedef struct agent_key {
- RSAKey key;
- strbuf *comment;
- ptrlen blob; /* only used during initial parsing of agent response */
- } agent_key;
- struct ssh1_login_state {
- int crState;
- PacketProtocolLayer *successor_layer;
- Conf *conf;
- char *savedhost;
- int savedport;
- bool try_agent_auth;
- int remote_protoflags;
- int local_protoflags;
- unsigned char session_key[32];
- char *username;
- agent_pending_query *auth_agent_query;
- int len;
- unsigned char *rsabuf;
- unsigned long supported_ciphers_mask, supported_auths_mask;
- bool tried_publickey, tried_agent;
- bool tis_auth_refused, ccard_auth_refused;
- unsigned char cookie[8];
- unsigned char session_id[16];
- int cipher_type;
- strbuf *publickey_blob;
- char *publickey_comment;
- bool privatekey_available, privatekey_encrypted;
- prompts_t *cur_prompt;
- int userpass_ret;
- char c;
- int pwpkt_type;
- void *agent_response_to_free;
- ptrlen agent_response;
- BinarySource asrc[1]; /* response from SSH agent */
- size_t agent_keys_len;
- agent_key *agent_keys;
- size_t agent_key_index, agent_key_limit;
- bool authed;
- RSAKey key;
- int dlgret;
- Filename *keyfile;
- RSAKey servkey, hostkey;
- bool want_user_input;
- StripCtrlChars *tis_scc;
- bool tis_scc_initialised;
- PacketProtocolLayer ppl;
- };
- static void ssh1_login_free(PacketProtocolLayer *);
- static void ssh1_login_process_queue(PacketProtocolLayer *);
- static void ssh1_login_dialog_callback(void *, int);
- static void ssh1_login_special_cmd(PacketProtocolLayer *ppl,
- SessionSpecialCode code, int arg);
- static bool ssh1_login_want_user_input(PacketProtocolLayer *ppl);
- static void ssh1_login_got_user_input(PacketProtocolLayer *ppl);
- static void ssh1_login_reconfigure(PacketProtocolLayer *ppl, Conf *conf);
- static unsigned int ssh1_login_winscp_query(PacketProtocolLayer *ppl, int query);
- static const PacketProtocolLayerVtable ssh1_login_vtable = {
- // WINSCP
- /*.free =*/ ssh1_login_free,
- /*.process_queue =*/ ssh1_login_process_queue,
- /*.get_specials =*/ ssh1_common_get_specials,
- /*.special_cmd =*/ ssh1_login_special_cmd,
- /*.want_user_input =*/ ssh1_login_want_user_input,
- /*.got_user_input =*/ ssh1_login_got_user_input,
- /*.reconfigure =*/ ssh1_login_reconfigure,
- /*.queued_data_size =*/ ssh_ppl_default_queued_data_size,
- /*.name =*/ NULL, /* no layer names in SSH-1 */
- ssh1_login_winscp_query,
- };
- static void ssh1_login_agent_query(struct ssh1_login_state *s, strbuf *req);
- static void ssh1_login_agent_callback(void *loginv, void *reply, int replylen);
- PacketProtocolLayer *ssh1_login_new(
- Conf *conf, const char *host, int port,
- PacketProtocolLayer *successor_layer)
- {
- struct ssh1_login_state *s = snew(struct ssh1_login_state);
- memset(s, 0, sizeof(*s));
- s->ppl.vt = &ssh1_login_vtable;
- s->conf = conf_copy(conf);
- s->savedhost = dupstr(host);
- s->savedport = port;
- s->successor_layer = successor_layer;
- return &s->ppl;
- }
- static void ssh1_login_free(PacketProtocolLayer *ppl)
- {
- struct ssh1_login_state *s =
- container_of(ppl, struct ssh1_login_state, ppl);
- if (s->successor_layer)
- ssh_ppl_free(s->successor_layer);
- conf_free(s->conf);
- sfree(s->savedhost);
- sfree(s->rsabuf);
- sfree(s->username);
- if (s->publickey_blob)
- strbuf_free(s->publickey_blob);
- sfree(s->publickey_comment);
- if (s->cur_prompt)
- free_prompts(s->cur_prompt);
- if (s->agent_keys) {
- size_t i; // WINSCP
- for (i = 0; i < s->agent_keys_len; i++) {
- freersakey(&s->agent_keys[i].key);
- strbuf_free(s->agent_keys[i].comment);
- }
- sfree(s->agent_keys);
- }
- sfree(s->agent_response_to_free);
- if (s->auth_agent_query)
- agent_cancel_query(s->auth_agent_query);
- sfree(s);
- }
- static bool ssh1_login_filter_queue(struct ssh1_login_state *s)
- {
- return ssh1_common_filter_queue(&s->ppl);
- }
- static PktIn *ssh1_login_pop(struct ssh1_login_state *s)
- {
- if (ssh1_login_filter_queue(s))
- return NULL;
- return pq_pop(s->ppl.in_pq);
- }
- static void ssh1_login_setup_tis_scc(struct ssh1_login_state *s);
- static void ssh1_login_process_queue(PacketProtocolLayer *ppl)
- {
- struct ssh1_login_state *s =
- container_of(ppl, struct ssh1_login_state, ppl);
- PktIn *pktin;
- PktOut *pkt;
- 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_filter_queue(s))
- return;
- crBegin(s->crState);
- crMaybeWaitUntilV((pktin = ssh1_login_pop(s)) != NULL);
- if (pktin->type != SSH1_SMSG_PUBLIC_KEY) {
- ssh_proto_error(s->ppl.ssh, "Public key packet not received");
- return;
- }
- ppl_logevent("Received public keys");
- {
- ptrlen pl = get_data(pktin, 8);
- memcpy(s->cookie, pl.ptr, pl.len);
- }
- get_rsa_ssh1_pub(pktin, &s->servkey, RSA_SSH1_EXPONENT_FIRST);
- get_rsa_ssh1_pub(pktin, &s->hostkey, RSA_SSH1_EXPONENT_FIRST);
- s->hostkey.comment = NULL; /* avoid confusing rsa_ssh1_fingerprint */
- /*
- * Log the host key fingerprint.
- */
- if (!get_err(pktin)) {
- char *fingerprint = rsa_ssh1_fingerprint(&s->hostkey);
- ppl_logevent("Host key fingerprint is:");
- ppl_logevent(" %s", fingerprint);
- sfree(fingerprint);
- }
- s->remote_protoflags = get_uint32(pktin);
- s->supported_ciphers_mask = get_uint32(pktin);
- s->supported_auths_mask = get_uint32(pktin);
- if (get_err(pktin)) {
- ssh_proto_error(s->ppl.ssh, "Bad SSH-1 public key packet");
- return;
- }
- if ((s->ppl.remote_bugs & BUG_CHOKES_ON_RSA))
- s->supported_auths_mask &= ~(1 << SSH1_AUTH_RSA);
- s->local_protoflags =
- s->remote_protoflags & SSH1_PROTOFLAGS_SUPPORTED;
- s->local_protoflags |= SSH1_PROTOFLAG_SCREEN_NUMBER;
- ssh1_compute_session_id(s->session_id, s->cookie,
- &s->hostkey, &s->servkey);
- random_read(s->session_key, 32);
- /*
- * Verify that the `bits' and `bytes' parameters match.
- */
- if (s->hostkey.bits > s->hostkey.bytes * 8 ||
- s->servkey.bits > s->servkey.bytes * 8) {
- ssh_proto_error(s->ppl.ssh, "SSH-1 public keys were badly formatted");
- return;
- }
- s->len = 32;
- if (s->len < s->hostkey.bytes)
- s->len = s->hostkey.bytes;
- if (s->len < s->servkey.bytes)
- s->len = s->servkey.bytes;
- s->rsabuf = snewn(s->len, unsigned char);
- /*
- * Verify the host key.
- */
- {
- /*
- * First format the key into a string.
- */
- char *keystr = rsastr_fmt(&s->hostkey);
- char **fingerprints = rsa_ssh1_fake_all_fingerprints(&s->hostkey);
- /* First check against manually configured host keys. */
- s->dlgret = verify_ssh_manual_host_key(s->conf, fingerprints, NULL);
- if (s->dlgret == 0) { /* did not match */
- ssh2_free_all_fingerprints(fingerprints);
- sfree(keystr);
- ssh_proto_error(s->ppl.ssh, "Host key did not appear in manually "
- "configured list");
- return;
- } else if (s->dlgret < 0) { /* none configured; use standard handling */
- char *keydisp = ssh1_pubkey_str(&s->hostkey);
- s->dlgret = seat_verify_ssh_host_key(
- s->ppl.seat, s->savedhost, s->savedport, "rsa", keystr,
- keydisp, fingerprints, ssh1_login_dialog_callback, s);
- sfree(keydisp);
- ssh2_free_all_fingerprints(fingerprints);
- sfree(keystr);
- #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");
- return;
- }
- } else {
- ssh2_free_all_fingerprints(fingerprints);
- sfree(keystr);
- }
- }
- for (i = 0; i < 32; i++) {
- s->rsabuf[i] = s->session_key[i];
- if (i < 16)
- s->rsabuf[i] ^= s->session_id[i];
- }
- {
- RSAKey *smaller = (s->hostkey.bytes > s->servkey.bytes ?
- &s->servkey : &s->hostkey);
- RSAKey *larger = (s->hostkey.bytes > s->servkey.bytes ?
- &s->hostkey : &s->servkey);
- if (!rsa_ssh1_encrypt(s->rsabuf, 32, smaller) ||
- !rsa_ssh1_encrypt(s->rsabuf, smaller->bytes, larger)) {
- ssh_proto_error(s->ppl.ssh, "SSH-1 public key encryptions failed "
- "due to bad formatting");
- return;
- }
- }
- ppl_logevent("Encrypted session key");
- {
- bool cipher_chosen = false, warn = false;
- const char *cipher_string = NULL;
- int i;
- for (i = 0; !cipher_chosen && i < CIPHER_MAX; i++) {
- int next_cipher = conf_get_int_int(
- s->conf, CONF_ssh_cipherlist, i);
- if (next_cipher == CIPHER_WARN) {
- /* If/when we choose a cipher, warn about it */
- warn = true;
- } else if (next_cipher == CIPHER_AES) {
- /* XXX Probably don't need to mention this. */
- ppl_logevent("AES not supported in SSH-1, skipping");
- } else {
- switch (next_cipher) {
- case CIPHER_3DES: s->cipher_type = SSH1_CIPHER_3DES;
- cipher_string = "3DES"; break;
- case CIPHER_BLOWFISH: s->cipher_type = SSH1_CIPHER_BLOWFISH;
- cipher_string = "Blowfish"; break;
- case CIPHER_DES: s->cipher_type = SSH1_CIPHER_DES;
- cipher_string = "single-DES"; break;
- }
- if (s->supported_ciphers_mask & (1 << s->cipher_type))
- cipher_chosen = true;
- }
- }
- if (!cipher_chosen) {
- if ((s->supported_ciphers_mask & (1 << SSH1_CIPHER_3DES)) == 0) {
- ssh_proto_error(s->ppl.ssh, "Server violates SSH-1 protocol "
- "by not supporting 3DES encryption");
- } else {
- /* shouldn't happen */
- ssh_sw_abort(s->ppl.ssh, "No supported ciphers found");
- }
- return;
- }
- /* Warn about chosen cipher if necessary. */
- if (warn) {
- s->dlgret = seat_confirm_weak_crypto_primitive(
- s->ppl.seat, "cipher", cipher_string,
- ssh1_login_dialog_callback, s);
- crMaybeWaitUntilV(s->dlgret >= 0);
- if (s->dlgret == 0) {
- ssh_user_close(s->ppl.ssh, "User aborted at cipher warning");
- return;
- }
- }
- }
- switch (s->cipher_type) {
- case SSH1_CIPHER_3DES:
- ppl_logevent("Using 3DES encryption");
- break;
- case SSH1_CIPHER_DES:
- ppl_logevent("Using single-DES encryption");
- break;
- case SSH1_CIPHER_BLOWFISH:
- ppl_logevent("Using Blowfish encryption");
- break;
- }
- pkt = ssh_bpp_new_pktout(s->ppl.bpp, SSH1_CMSG_SESSION_KEY);
- put_byte(pkt, s->cipher_type);
- put_data(pkt, s->cookie, 8);
- put_uint16(pkt, s->len * 8);
- put_data(pkt, s->rsabuf, s->len);
- put_uint32(pkt, s->local_protoflags);
- pq_push(s->ppl.out_pq, pkt);
- ppl_logevent("Trying to enable encryption...");
- sfree(s->rsabuf);
- s->rsabuf = NULL;
- /*
- * Force the BPP to synchronously marshal all packets up to and
- * including the SESSION_KEY into wire format, before we turn on
- * crypto.
- */
- ssh_bpp_handle_output(s->ppl.bpp);
- {
- 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);
- }
- freersakey(&s->servkey);
- freersakey(&s->hostkey);
- crMaybeWaitUntilV((pktin = ssh1_login_pop(s)) != NULL);
- if (pktin->type != SSH1_SMSG_SUCCESS) {
- ssh_proto_error(s->ppl.ssh, "Encryption not successfully enabled");
- return;
- }
- ppl_logevent("Successfully started encryption");
- if ((s->username = get_remote_username(s->conf)) == NULL) {
- s->cur_prompt = new_prompts();
- s->cur_prompt->to_server = true;
- s->cur_prompt->from_server = false;
- s->cur_prompt->name = dupstr("SSH login name");
- add_prompt(s->cur_prompt, dupstr("login as: "), true);
- s->userpass_ret = seat_get_userpass_input(
- s->ppl.seat, s->cur_prompt, NULL);
- while (1) {
- while (s->userpass_ret < 0 &&
- bufchain_size(s->ppl.user_input) > 0)
- s->userpass_ret = seat_get_userpass_input(
- s->ppl.seat, s->cur_prompt, s->ppl.user_input);
- if (s->userpass_ret >= 0)
- break;
- s->want_user_input = true;
- crReturnV;
- s->want_user_input = false;
- }
- if (!s->userpass_ret) {
- /*
- * Failed to get a username. Terminate.
- */
- ssh_user_close(s->ppl.ssh, "No username provided");
- return;
- }
- s->username = prompt_get_result(s->cur_prompt->prompts[0]);
- free_prompts(s->cur_prompt);
- s->cur_prompt = NULL;
- }
- pkt = ssh_bpp_new_pktout(s->ppl.bpp, SSH1_CMSG_USER);
- put_stringz(pkt, s->username);
- pq_push(s->ppl.out_pq, pkt);
- ppl_logevent(WINSCP_BOM "Sent username \"%s\"", s->username);
- if (seat_verbose(s->ppl.seat) || seat_interactive(s->ppl.seat))
- ppl_printf("Sent username \"%s\"\r\n", s->username);
- crMaybeWaitUntilV((pktin = ssh1_login_pop(s)) != NULL);
- if (!(s->supported_auths_mask & (1 << SSH1_AUTH_RSA))) {
- /* We must not attempt PK auth. Pretend we've already tried it. */
- s->tried_publickey = s->tried_agent = true;
- } else {
- s->tried_publickey = s->tried_agent = false;
- }
- s->tis_auth_refused = s->ccard_auth_refused = false;
- /*
- * Load the public half of any configured keyfile for later use.
- */
- s->keyfile = conf_get_filename(s->conf, CONF_keyfile);
- if (!filename_is_null(s->keyfile)) {
- int keytype;
- ppl_logevent(WINSCP_BOM "Reading key file \"%s\"", filename_to_str(s->keyfile));
- keytype = key_type(s->keyfile);
- if (keytype == SSH_KEYTYPE_SSH1 ||
- keytype == SSH_KEYTYPE_SSH1_PUBLIC) {
- const char *error;
- s->publickey_blob = strbuf_new();
- if (rsa1_loadpub_f(s->keyfile,
- BinarySink_UPCAST(s->publickey_blob),
- &s->publickey_comment, &error)) {
- s->privatekey_available = (keytype == SSH_KEYTYPE_SSH1);
- if (!s->privatekey_available)
- ppl_logevent("Key file contains public key only");
- s->privatekey_encrypted = rsa1_encrypted_f(s->keyfile, NULL);
- } else {
- ppl_logevent("Unable to load key (%s)", error);
- ppl_printf(WINSCP_BOM "Unable to load key file \"%s\" (%s)\r\n",
- filename_to_str(s->keyfile), error);
- strbuf_free(s->publickey_blob);
- s->publickey_blob = NULL;
- }
- } else {
- ppl_logevent("Unable to use this key file (%s)",
- key_type_to_str(keytype));
- ppl_printf(WINSCP_BOM "Unable to use key file \"%s\" (%s)\r\n",
- filename_to_str(s->keyfile),
- key_type_to_str(keytype));
- }
- }
- /* Check whether we're configured to try Pageant, and also whether
- * it's available. */
- s->try_agent_auth = (conf_get_bool(s->conf, CONF_tryagent) &&
- agent_exists());
- while (pktin->type == SSH1_SMSG_FAILURE) {
- s->pwpkt_type = SSH1_CMSG_AUTH_PASSWORD;
- if (s->try_agent_auth && !s->tried_agent) {
- /*
- * Attempt RSA authentication using Pageant.
- */
- s->authed = false;
- s->tried_agent = true;
- ppl_logevent("Pageant is running. Requesting keys.");
- /* Request the keys held by the agent. */
- {
- strbuf *request = strbuf_new_for_agent_query();
- put_byte(request, SSH1_AGENTC_REQUEST_RSA_IDENTITIES);
- ssh1_login_agent_query(s, request);
- strbuf_free(request);
- crMaybeWaitUntilV(!s->auth_agent_query);
- }
- BinarySource_BARE_INIT_PL(s->asrc, s->agent_response);
- get_uint32(s->asrc); /* skip length field */
- if (get_byte(s->asrc) == SSH1_AGENT_RSA_IDENTITIES_ANSWER) {
- size_t nkeys = get_uint32(s->asrc);
- size_t origpos = s->asrc->pos;
- /*
- * Check that the agent response is well formed.
- */
- { // WINSCP
- size_t i; // WINSCP
- for (i = 0; i < nkeys; i++) {
- get_rsa_ssh1_pub(s->asrc, NULL, RSA_SSH1_EXPONENT_FIRST);
- get_string(s->asrc); /* comment */
- if (get_err(s->asrc)) {
- ppl_logevent("Pageant's response was truncated");
- goto parsed_agent_query;
- }
- }
- } // WINSCP
- /*
- * Copy the list of public-key blobs out of the Pageant
- * response.
- */
- BinarySource_REWIND_TO(s->asrc, origpos);
- s->agent_keys_len = nkeys;
- s->agent_keys = snewn(s->agent_keys_len, agent_key);
- { // WINSCP
- size_t i; // WINSCP
- for (i = 0; i < nkeys; i++) {
- memset(&s->agent_keys[i].key, 0,
- sizeof(s->agent_keys[i].key));
- { // WINSCP
- const char *blobstart = get_ptr(s->asrc);
- get_rsa_ssh1_pub(s->asrc, &s->agent_keys[i].key,
- RSA_SSH1_EXPONENT_FIRST);
- { // WINSCP
- const char *blobend = get_ptr(s->asrc);
- s->agent_keys[i].comment = strbuf_new();
- put_datapl(s->agent_keys[i].comment, get_string(s->asrc));
- s->agent_keys[i].blob = make_ptrlen(
- blobstart, blobend - blobstart);
- } // WINSCP
- } // WINSCP
- }
- } // WINSCP
- ppl_logevent("Pageant has %"SIZEu" SSH-1 keys", nkeys);
- if (s->publickey_blob) {
- /*
- * If we've been given a specific public key blob,
- * filter the list of keys to try from the agent
- * down to only that one, or none if it's not
- * there.
- */
- ptrlen our_blob = ptrlen_from_strbuf(s->publickey_blob);
- size_t i;
- for (i = 0; i < nkeys; i++) {
- if (ptrlen_eq_ptrlen(our_blob, s->agent_keys[i].blob))
- break;
- }
- if (i < nkeys) {
- ppl_logevent("Pageant key #%"SIZEu" matches "
- "configured key file", i);
- s->agent_key_index = i;
- s->agent_key_limit = i+1;
- } else {
- ppl_logevent("Configured key file not in Pageant");
- s->agent_key_index = 0;
- s->agent_key_limit = 0;
- }
- } else {
- /*
- * Otherwise, try them all.
- */
- s->agent_key_index = 0;
- s->agent_key_limit = nkeys;
- }
- } else {
- ppl_logevent("Failed to get reply from Pageant");
- }
- parsed_agent_query:;
- for (; s->agent_key_index < s->agent_key_limit;
- s->agent_key_index++) {
- ppl_logevent("Trying Pageant key #%"SIZEu, s->agent_key_index);
- pkt = ssh_bpp_new_pktout(s->ppl.bpp, SSH1_CMSG_AUTH_RSA);
- put_mp_ssh1(pkt,
- s->agent_keys[s->agent_key_index].key.modulus);
- pq_push(s->ppl.out_pq, pkt);
- crMaybeWaitUntilV((pktin = ssh1_login_pop(s))
- != NULL);
- if (pktin->type != SSH1_SMSG_AUTH_RSA_CHALLENGE) {
- ppl_logevent("Key refused");
- continue;
- }
- ppl_logevent("Received RSA challenge");
- {
- mp_int *challenge = get_mp_ssh1(pktin);
- if (get_err(pktin)) {
- mp_free(challenge);
- ssh_proto_error(s->ppl.ssh, "Server's RSA challenge "
- "was badly formatted");
- return;
- }
- { // WINSCP
- strbuf *agentreq = strbuf_new_for_agent_query();
- put_byte(agentreq, SSH1_AGENTC_RSA_CHALLENGE);
- rsa_ssh1_public_blob(
- BinarySink_UPCAST(agentreq),
- &s->agent_keys[s->agent_key_index].key,
- RSA_SSH1_EXPONENT_FIRST);
- put_mp_ssh1(agentreq, challenge);
- mp_free(challenge);
- put_data(agentreq, s->session_id, 16);
- put_uint32(agentreq, 1); /* response format */
- ssh1_login_agent_query(s, agentreq);
- strbuf_free(agentreq);
- crMaybeWaitUntilV(!s->auth_agent_query);
- } // WINSCP
- }
- {
- const unsigned char *ret = s->agent_response.ptr;
- if (ret) {
- if (s->agent_response.len >= 5+16 &&
- ret[4] == SSH1_AGENT_RSA_RESPONSE) {
- ppl_logevent("Sending Pageant's response");
- pkt = ssh_bpp_new_pktout(
- s->ppl.bpp, SSH1_CMSG_AUTH_RSA_RESPONSE);
- put_data(pkt, ret + 5, 16);
- pq_push(s->ppl.out_pq, pkt);
- crMaybeWaitUntilV(
- (pktin = ssh1_login_pop(s))
- != NULL);
- if (pktin->type == SSH1_SMSG_SUCCESS) {
- ppl_logevent("Pageant's response "
- "accepted");
- if (seat_verbose(s->ppl.seat)) {
- ptrlen comment = ptrlen_from_strbuf(
- s->agent_keys[s->agent_key_index].
- comment);
- ppl_printf("Authenticated using RSA "
- "key \"%.*s\" from "
- "agent\r\n",
- PTRLEN_PRINTF(comment));
- }
- s->authed = true;
- } else
- ppl_logevent("Pageant's response not "
- "accepted");
- } else {
- ppl_logevent("Pageant failed to answer "
- "challenge");
- sfree((char *)ret);
- }
- } else {
- ppl_logevent("No reply received from Pageant");
- }
- }
- if (s->authed)
- break;
- }
- if (s->authed)
- break;
- }
- if (s->publickey_blob && s->privatekey_available &&
- !s->tried_publickey) {
- /*
- * Try public key authentication with the specified
- * key file.
- */
- bool got_passphrase; /* need not be kept over crReturn */
- if (seat_verbose(s->ppl.seat))
- ppl_printf("Trying public key authentication.\r\n");
- ppl_logevent(WINSCP_BOM "Trying public key \"%s\"",
- filename_to_str(s->keyfile));
- s->tried_publickey = true;
- got_passphrase = false;
- while (!got_passphrase) {
- /*
- * Get a passphrase, if necessary.
- */
- int retd;
- char *passphrase = NULL; /* only written after crReturn */
- const char *error;
- if (!s->privatekey_encrypted) {
- if (seat_verbose(s->ppl.seat))
- ppl_printf("No passphrase required.\r\n");
- passphrase = NULL;
- } else {
- s->cur_prompt = new_prompts();
- s->cur_prompt->to_server = false;
- s->cur_prompt->from_server = false;
- s->cur_prompt->name = dupstr("SSH key passphrase");
- add_prompt(s->cur_prompt,
- dupprintf("Passphrase for key \"%s\": ",
- s->publickey_comment), false);
- s->userpass_ret = seat_get_userpass_input(
- s->ppl.seat, s->cur_prompt, NULL);
- while (1) {
- while (s->userpass_ret < 0 &&
- bufchain_size(s->ppl.user_input) > 0)
- s->userpass_ret = seat_get_userpass_input(
- s->ppl.seat, s->cur_prompt, s->ppl.user_input);
- if (s->userpass_ret >= 0)
- break;
- s->want_user_input = true;
- crReturnV;
- s->want_user_input = false;
- }
- if (!s->userpass_ret) {
- /* Failed to get a passphrase. Terminate. */
- ssh_user_close(s->ppl.ssh,
- "User aborted at passphrase prompt");
- return;
- }
- passphrase = prompt_get_result(s->cur_prompt->prompts[0]);
- free_prompts(s->cur_prompt);
- s->cur_prompt = NULL;
- }
- /*
- * Try decrypting key with passphrase.
- */
- retd = rsa1_load_f(s->keyfile, &s->key, passphrase, &error);
- if (passphrase) {
- smemclr(passphrase, strlen(passphrase));
- sfree(passphrase);
- }
- if (retd == 1) {
- /* Correct passphrase. */
- got_passphrase = true;
- } else if (retd == 0) {
- ppl_printf(WINSCP_BOM "Couldn't load private key from %s (%s).\r\n",
- filename_to_str(s->keyfile), error);
- got_passphrase = false;
- break; /* go and try something else */
- } else if (retd == -1) {
- ppl_printf("Wrong passphrase.\r\n");
- got_passphrase = false;
- /* and try again */
- } else {
- unreachable("unexpected return from rsa1_load_f()");
- }
- }
- if (got_passphrase) {
- /*
- * Send a public key attempt.
- */
- pkt = ssh_bpp_new_pktout(s->ppl.bpp, SSH1_CMSG_AUTH_RSA);
- put_mp_ssh1(pkt, s->key.modulus);
- pq_push(s->ppl.out_pq, pkt);
- crMaybeWaitUntilV((pktin = ssh1_login_pop(s))
- != NULL);
- if (pktin->type == SSH1_SMSG_FAILURE) {
- ppl_printf("Server refused our public key.\r\n");
- continue; /* go and try something else */
- }
- if (pktin->type != SSH1_SMSG_AUTH_RSA_CHALLENGE) {
- ssh_proto_error(s->ppl.ssh, "Received unexpected packet"
- " in response to offer of public key, "
- "type %d (%s)", pktin->type,
- ssh1_pkt_type(pktin->type));
- return;
- }
- {
- int i;
- unsigned char buffer[32];
- mp_int *challenge, *response;
- challenge = get_mp_ssh1(pktin);
- if (get_err(pktin)) {
- mp_free(challenge);
- ssh_proto_error(s->ppl.ssh, "Server's RSA challenge "
- "was badly formatted");
- return;
- }
- response = rsa_ssh1_decrypt(challenge, &s->key);
- freersapriv(&s->key); /* burn the evidence */
- for (i = 0; i < 32; i++) {
- buffer[i] = mp_get_byte(response, 31 - i);
- }
- {
- ssh_hash *h = ssh_hash_new(&ssh_md5);
- put_data(h, buffer, 32);
- put_data(h, s->session_id, 16);
- ssh_hash_final(h, buffer);
- }
- pkt = ssh_bpp_new_pktout(
- s->ppl.bpp, SSH1_CMSG_AUTH_RSA_RESPONSE);
- put_data(pkt, buffer, 16);
- pq_push(s->ppl.out_pq, pkt);
- mp_free(challenge);
- mp_free(response);
- }
- crMaybeWaitUntilV((pktin = ssh1_login_pop(s))
- != NULL);
- if (pktin->type == SSH1_SMSG_FAILURE) {
- if (seat_verbose(s->ppl.seat))
- ppl_printf("Failed to authenticate with"
- " our public key.\r\n");
- continue; /* go and try something else */
- } else if (pktin->type != SSH1_SMSG_SUCCESS) {
- ssh_proto_error(s->ppl.ssh, "Received unexpected packet"
- " in response to RSA authentication, "
- "type %d (%s)", pktin->type,
- ssh1_pkt_type(pktin->type));
- return;
- }
- break; /* we're through! */
- }
- }
- /*
- * Otherwise, try various forms of password-like authentication.
- */
- s->cur_prompt = new_prompts();
- if (conf_get_bool(s->conf, CONF_try_tis_auth) &&
- (s->supported_auths_mask & (1 << SSH1_AUTH_TIS)) &&
- !s->tis_auth_refused) {
- ssh1_login_setup_tis_scc(s);
- s->pwpkt_type = SSH1_CMSG_AUTH_TIS_RESPONSE;
- ppl_logevent("Requested TIS authentication");
- pkt = ssh_bpp_new_pktout(s->ppl.bpp, SSH1_CMSG_AUTH_TIS);
- pq_push(s->ppl.out_pq, pkt);
- crMaybeWaitUntilV((pktin = ssh1_login_pop(s)) != NULL);
- if (pktin->type == SSH1_SMSG_FAILURE) {
- ppl_logevent("TIS authentication declined");
- if (seat_interactive(s->ppl.seat))
- ppl_printf("TIS authentication refused.\r\n");
- s->tis_auth_refused = true;
- continue;
- } else if (pktin->type == SSH1_SMSG_AUTH_TIS_CHALLENGE) {
- ptrlen challenge = get_string(pktin);
- if (get_err(pktin)) {
- ssh_proto_error(s->ppl.ssh, "TIS challenge packet was "
- "badly formed");
- return;
- }
- ppl_logevent("Received TIS challenge");
- s->cur_prompt->to_server = true;
- s->cur_prompt->from_server = true;
- s->cur_prompt->name = dupstr("SSH TIS authentication");
- { // WINSCP
- strbuf *sb = strbuf_new();
- put_datapl(sb, PTRLEN_LITERAL("\
- -- TIS authentication challenge from server: ---------------------------------\
- \r\n"));
- if (s->tis_scc) {
- stripctrl_retarget(s->tis_scc, BinarySink_UPCAST(sb));
- put_datapl(s->tis_scc, challenge);
- stripctrl_retarget(s->tis_scc, NULL);
- } else {
- put_datapl(sb, challenge);
- }
- if (!ptrlen_endswith(challenge, PTRLEN_LITERAL("\n"), NULL))
- put_datapl(sb, PTRLEN_LITERAL("\r\n"));
- put_datapl(sb, PTRLEN_LITERAL("\
- -- End of TIS authentication challenge from server: --------------------------\
- \r\n"));
- s->cur_prompt->instruction = strbuf_to_str(sb);
- s->cur_prompt->instr_reqd = true;
- add_prompt(s->cur_prompt, dupstr(
- "TIS authentication response: "), false);
- } // WINSCP
- } else {
- ssh_proto_error(s->ppl.ssh, "Received unexpected packet"
- " in response to TIS authentication, "
- "type %d (%s)", pktin->type,
- ssh1_pkt_type(pktin->type));
- return;
- }
- } else if (conf_get_bool(s->conf, CONF_try_tis_auth) &&
- (s->supported_auths_mask & (1 << SSH1_AUTH_CCARD)) &&
- !s->ccard_auth_refused) {
- ssh1_login_setup_tis_scc(s);
- s->pwpkt_type = SSH1_CMSG_AUTH_CCARD_RESPONSE;
- ppl_logevent("Requested CryptoCard authentication");
- pkt = ssh_bpp_new_pktout(s->ppl.bpp, SSH1_CMSG_AUTH_CCARD);
- pq_push(s->ppl.out_pq, pkt);
- crMaybeWaitUntilV((pktin = ssh1_login_pop(s)) != NULL);
- if (pktin->type == SSH1_SMSG_FAILURE) {
- ppl_logevent("CryptoCard authentication declined");
- ppl_printf("CryptoCard authentication refused.\r\n");
- s->ccard_auth_refused = true;
- continue;
- } else if (pktin->type == SSH1_SMSG_AUTH_CCARD_CHALLENGE) {
- ptrlen challenge = get_string(pktin);
- if (get_err(pktin)) {
- ssh_proto_error(s->ppl.ssh, "CryptoCard challenge packet "
- "was badly formed");
- return;
- }
- ppl_logevent("Received CryptoCard challenge");
- s->cur_prompt->to_server = true;
- s->cur_prompt->from_server = true;
- s->cur_prompt->name = dupstr("SSH CryptoCard authentication");
- { // WINSCP
- strbuf *sb = strbuf_new();
- put_datapl(sb, PTRLEN_LITERAL("\
- -- CryptoCard authentication challenge from server: --------------------------\
- \r\n"));
- if (s->tis_scc) {
- stripctrl_retarget(s->tis_scc, BinarySink_UPCAST(sb));
- put_datapl(s->tis_scc, challenge);
- stripctrl_retarget(s->tis_scc, NULL);
- } else {
- put_datapl(sb, challenge);
- }
- if (!ptrlen_endswith(challenge, PTRLEN_LITERAL("\n"), NULL))
- put_datapl(sb, PTRLEN_LITERAL("\r\n"));
- put_datapl(sb, PTRLEN_LITERAL("\
- -- End of CryptoCard authentication challenge from server: -------------------\
- \r\n"));
- s->cur_prompt->instruction = strbuf_to_str(sb);
- s->cur_prompt->instr_reqd = true;
- add_prompt(s->cur_prompt, dupstr(
- "CryptoCard authentication response: "), false);
- } // WINSCP
- } else {
- ssh_proto_error(s->ppl.ssh, "Received unexpected packet"
- " in response to TIS authentication, "
- "type %d (%s)", pktin->type,
- ssh1_pkt_type(pktin->type));
- return;
- }
- }
- if (s->pwpkt_type == SSH1_CMSG_AUTH_PASSWORD) {
- if ((s->supported_auths_mask & (1 << SSH1_AUTH_PASSWORD)) == 0) {
- ssh_sw_abort(s->ppl.ssh, "No supported authentication methods "
- "available");
- return;
- }
- s->cur_prompt->to_server = true;
- s->cur_prompt->from_server = false;
- s->cur_prompt->name = dupstr("SSH password");
- add_prompt(s->cur_prompt, dupprintf("%s@%s's password: ",
- s->username, s->savedhost),
- false);
- }
- /*
- * Show password prompt, having first obtained it via a TIS
- * or CryptoCard exchange if we're doing TIS or CryptoCard
- * authentication.
- */
- s->userpass_ret = seat_get_userpass_input(
- s->ppl.seat, s->cur_prompt, NULL);
- while (1) {
- while (s->userpass_ret < 0 &&
- bufchain_size(s->ppl.user_input) > 0)
- s->userpass_ret = seat_get_userpass_input(
- s->ppl.seat, s->cur_prompt, s->ppl.user_input);
- if (s->userpass_ret >= 0)
- break;
- s->want_user_input = true;
- crReturnV;
- s->want_user_input = false;
- }
- if (!s->userpass_ret) {
- /*
- * Failed to get a password (for example
- * because one was supplied on the command line
- * which has already failed to work). Terminate.
- */
- ssh_user_close(s->ppl.ssh, "User aborted at password prompt");
- return;
- }
- if (s->pwpkt_type == SSH1_CMSG_AUTH_PASSWORD) {
- /*
- * Defence against traffic analysis: we send a
- * whole bunch of packets containing strings of
- * different lengths. One of these strings is the
- * password, in a SSH1_CMSG_AUTH_PASSWORD packet.
- * The others are all random data in
- * SSH1_MSG_IGNORE packets. This way a passive
- * listener can't tell which is the password, and
- * hence can't deduce the password length.
- *
- * Anybody with a password length greater than 16
- * bytes is going to have enough entropy in their
- * password that a listener won't find it _that_
- * much help to know how long it is. So what we'll
- * do is:
- *
- * - if password length < 16, we send 15 packets
- * containing string lengths 1 through 15
- *
- * - otherwise, we let N be the nearest multiple
- * of 8 below the password length, and send 8
- * packets containing string lengths N through
- * N+7. This won't obscure the order of
- * magnitude of the password length, but it will
- * introduce a bit of extra uncertainty.
- *
- * A few servers can't deal with SSH1_MSG_IGNORE, at
- * least in this context. For these servers, we need
- * an alternative defence. We make use of the fact
- * that the password is interpreted as a C string:
- * so we can append a NUL, then some random data.
- *
- * A few servers can deal with neither SSH1_MSG_IGNORE
- * here _nor_ a padded password string.
- * For these servers we are left with no defences
- * against password length sniffing.
- */
- if (!(s->ppl.remote_bugs & BUG_CHOKES_ON_SSH1_IGNORE) &&
- !(s->ppl.remote_bugs & BUG_NEEDS_SSH1_PLAIN_PASSWORD)) {
- /*
- * The server can deal with SSH1_MSG_IGNORE, so
- * we can use the primary defence.
- */
- int bottom, top, pwlen, i;
- const char *pw = prompt_get_result_ref(
- s->cur_prompt->prompts[0]);
- pwlen = strlen(pw);
- if (pwlen < 16) {
- bottom = 0; /* zero length passwords are OK! :-) */
- top = 15;
- } else {
- bottom = pwlen & ~7;
- top = bottom + 7;
- }
- assert(pwlen >= bottom && pwlen <= top);
- for (i = bottom; i <= top; i++) {
- if (i == pwlen) {
- pkt = ssh_bpp_new_pktout(s->ppl.bpp, s->pwpkt_type);
- put_stringz(pkt, pw);
- pq_push(s->ppl.out_pq, pkt);
- } else {
- strbuf *random_data = strbuf_new_nm();
- random_read(strbuf_append(random_data, i), i);
- pkt = ssh_bpp_new_pktout(s->ppl.bpp, SSH1_MSG_IGNORE);
- put_stringsb(pkt, random_data);
- pq_push(s->ppl.out_pq, pkt);
- }
- }
- ppl_logevent("Sending password with camouflage packets");
- }
- else if (!(s->ppl.remote_bugs & BUG_NEEDS_SSH1_PLAIN_PASSWORD)) {
- /*
- * The server can't deal with SSH1_MSG_IGNORE
- * but can deal with padded passwords, so we
- * can use the secondary defence.
- */
- strbuf *padded_pw = strbuf_new_nm();
- ppl_logevent("Sending length-padded password");
- pkt = ssh_bpp_new_pktout(s->ppl.bpp, s->pwpkt_type);
- put_asciz(padded_pw, prompt_get_result_ref(
- s->cur_prompt->prompts[0]));
- { // WINSCP
- size_t pad = 63 & -(ssize_t)padded_pw->len; // WINSCP
- random_read(strbuf_append(padded_pw, pad), pad);
- put_stringsb(pkt, padded_pw);
- pq_push(s->ppl.out_pq, pkt);
- } // WINSCP
- } else {
- /*
- * The server is believed unable to cope with
- * any of our password camouflage methods.
- */
- ppl_logevent("Sending unpadded password");
- pkt = ssh_bpp_new_pktout(s->ppl.bpp, s->pwpkt_type);
- put_stringz(pkt, prompt_get_result_ref(
- s->cur_prompt->prompts[0]));
- pq_push(s->ppl.out_pq, pkt);
- }
- } else {
- pkt = ssh_bpp_new_pktout(s->ppl.bpp, s->pwpkt_type);
- put_stringz(pkt, prompt_get_result_ref(s->cur_prompt->prompts[0]));
- pq_push(s->ppl.out_pq, pkt);
- }
- ppl_logevent("Sent password");
- free_prompts(s->cur_prompt);
- s->cur_prompt = NULL;
- crMaybeWaitUntilV((pktin = ssh1_login_pop(s)) != NULL);
- if (pktin->type == SSH1_SMSG_FAILURE) {
- if (seat_verbose(s->ppl.seat))
- ppl_printf("Access denied\r\n");
- ppl_logevent("Authentication refused");
- } else if (pktin->type != SSH1_SMSG_SUCCESS) {
- ssh_proto_error(s->ppl.ssh, "Received unexpected packet"
- " in response to password authentication, type %d "
- "(%s)", pktin->type, ssh1_pkt_type(pktin->type));
- return;
- }
- }
- ppl_logevent("Authentication successful");
- if (conf_get_bool(s->conf, CONF_compression)) {
- ppl_logevent("Requesting compression");
- pkt = ssh_bpp_new_pktout(s->ppl.bpp, SSH1_CMSG_REQUEST_COMPRESSION);
- put_uint32(pkt, 6); /* gzip compression level */
- pq_push(s->ppl.out_pq, pkt);
- crMaybeWaitUntilV((pktin = ssh1_login_pop(s)) != NULL);
- if (pktin->type == SSH1_SMSG_SUCCESS) {
- /*
- * We don't have to actually do anything here: the SSH-1
- * BPP will take care of automatically starting the
- * compression, by recognising our outgoing request packet
- * and the success response. (Horrible, but it's the
- * easiest way to avoid race conditions if other packets
- * cross in transit.)
- */
- } else if (pktin->type == SSH1_SMSG_FAILURE) {
- ppl_logevent("Server refused to enable compression");
- ppl_printf("Server refused to compress\r\n");
- } else {
- ssh_proto_error(s->ppl.ssh, "Received unexpected packet"
- " in response to compression request, type %d "
- "(%s)", pktin->type, ssh1_pkt_type(pktin->type));
- return;
- }
- }
- 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;
- }
- static void ssh1_login_setup_tis_scc(struct ssh1_login_state *s)
- {
- if (s->tis_scc_initialised)
- return;
- s->tis_scc = seat_stripctrl_new(s->ppl.seat, NULL, SIC_KI_PROMPTS);
- if (s->tis_scc)
- stripctrl_enable_line_limiting(s->tis_scc);
- s->tis_scc_initialised = true;
- }
- static void ssh1_login_dialog_callback(void *loginv, int ret)
- {
- struct ssh1_login_state *s = (struct ssh1_login_state *)loginv;
- s->dlgret = ret;
- ssh_ppl_process_queue(&s->ppl);
- }
- static void ssh1_login_agent_query(struct ssh1_login_state *s, strbuf *req)
- {
- void *response;
- int response_len;
- sfree(s->agent_response_to_free);
- s->agent_response_to_free = NULL;
- s->auth_agent_query = agent_query(req, &response, &response_len,
- ssh1_login_agent_callback, s);
- if (!s->auth_agent_query)
- ssh1_login_agent_callback(s, response, response_len);
- }
- static void ssh1_login_agent_callback(void *loginv, void *reply, int replylen)
- {
- struct ssh1_login_state *s = (struct ssh1_login_state *)loginv;
- s->auth_agent_query = NULL;
- s->agent_response_to_free = reply;
- s->agent_response = make_ptrlen(reply, replylen);
- queue_idempotent_callback(&s->ppl.ic_process_queue);
- }
- static void ssh1_login_special_cmd(PacketProtocolLayer *ppl,
- SessionSpecialCode code, int arg)
- {
- struct ssh1_login_state *s =
- container_of(ppl, struct ssh1_login_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);
- }
- }
- }
- static bool ssh1_login_want_user_input(PacketProtocolLayer *ppl)
- {
- struct ssh1_login_state *s =
- container_of(ppl, struct ssh1_login_state, ppl);
- return s->want_user_input;
- }
- static void ssh1_login_got_user_input(PacketProtocolLayer *ppl)
- {
- struct ssh1_login_state *s =
- container_of(ppl, struct ssh1_login_state, ppl);
- if (s->want_user_input)
- queue_idempotent_callback(&s->ppl.ic_process_queue);
- }
- static void ssh1_login_reconfigure(PacketProtocolLayer *ppl, Conf *conf)
- {
- struct ssh1_login_state *s =
- container_of(ppl, struct ssh1_login_state, ppl);
- ssh_ppl_reconfigure(s->successor_layer, conf);
- }
- #include <puttyexp.h>
- static unsigned int ssh1_login_winscp_query(PacketProtocolLayer *ppl, int query)
- {
- struct ssh1_login_state *s =
- container_of(ppl, struct ssh1_login_state, ppl);
- if (query == WINSCP_QUERY_TIMER)
- {
- return 0;
- }
- else if (s->successor_layer->vt->winscp_query != NULL)
- {
- return ssh_ppl_winscp_query(s->successor_layer, query);
- }
- else
- {
- return 0;
- }
- }
|