=================================================================== RCS file: /cvsrepo/anoncvs/cvs/src/usr.bin/ssh/sshconnect2.c,v retrieving revision 1.10.2.2 retrieving revision 1.10.2.3 diff -u -r1.10.2.2 -r1.10.2.3 --- src/usr.bin/ssh/sshconnect2.c 2000/09/01 18:23:24 1.10.2.2 +++ src/usr.bin/ssh/sshconnect2.c 2000/11/08 21:31:30 1.10.2.3 @@ -9,11 +9,6 @@ * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. - * 3. All advertising materials mentioning features or use of this software - * must display the following acknowledgement: - * This product includes software developed by Markus Friedl. - * 4. The name of the author may not be used to endorse or promote products - * derived from this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES @@ -28,7 +23,7 @@ */ #include "includes.h" -RCSID("$OpenBSD: sshconnect2.c,v 1.10.2.2 2000/09/01 18:23:24 jason Exp $"); +RCSID("$OpenBSD: sshconnect2.c,v 1.10.2.3 2000/11/08 21:31:30 jason Exp $"); #include #include @@ -42,7 +37,6 @@ #include "rsa.h" #include "buffer.h" #include "packet.h" -#include "cipher.h" #include "uidswap.h" #include "compat.h" #include "readconf.h" @@ -54,8 +48,13 @@ #include "dsa.h" #include "sshconnect.h" #include "authfile.h" +#include "cli.h" +#include "dispatch.h" #include "authfd.h" +void ssh_dh1_client(Kex *, char *, struct sockaddr *, Buffer *, Buffer *); +void ssh_dhgex_client(Kex *, char *, struct sockaddr *, Buffer *, Buffer *); + /* import */ extern char *client_version_string; extern char *server_version_string; @@ -69,9 +68,94 @@ int session_id2_len = 0; void -ssh_kex_dh(Kex *kex, char *host, struct sockaddr *hostaddr, - Buffer *client_kexinit, Buffer *server_kexinit) +ssh_kex2(char *host, struct sockaddr *hostaddr) { + int i, plen; + Kex *kex; + Buffer *client_kexinit, *server_kexinit; + char *sprop[PROPOSAL_MAX]; + + if (options.ciphers == NULL) { + if (options.cipher == SSH_CIPHER_3DES) { + options.ciphers = "3des-cbc"; + } else if (options.cipher == SSH_CIPHER_BLOWFISH) { + options.ciphers = "blowfish-cbc"; + } else if (options.cipher == SSH_CIPHER_DES) { + fatal("cipher DES not supported for protocol version 2"); + } + } + if (options.ciphers != NULL) { + myproposal[PROPOSAL_ENC_ALGS_CTOS] = + myproposal[PROPOSAL_ENC_ALGS_STOC] = options.ciphers; + } + if (options.compression) { + myproposal[PROPOSAL_COMP_ALGS_CTOS] = "zlib"; + myproposal[PROPOSAL_COMP_ALGS_STOC] = "zlib"; + } else { + myproposal[PROPOSAL_COMP_ALGS_CTOS] = "none"; + myproposal[PROPOSAL_COMP_ALGS_STOC] = "none"; + } + + /* buffers with raw kexinit messages */ + server_kexinit = xmalloc(sizeof(*server_kexinit)); + buffer_init(server_kexinit); + client_kexinit = kex_init(myproposal); + + /* algorithm negotiation */ + kex_exchange_kexinit(client_kexinit, server_kexinit, sprop); + kex = kex_choose_conf(myproposal, sprop, 0); + for (i = 0; i < PROPOSAL_MAX; i++) + xfree(sprop[i]); + + /* server authentication and session key agreement */ + switch(kex->kex_type) { + case DH_GRP1_SHA1: + ssh_dh1_client(kex, host, hostaddr, + client_kexinit, server_kexinit); + break; + case DH_GEX_SHA1: + ssh_dhgex_client(kex, host, hostaddr, client_kexinit, + server_kexinit); + break; + default: + fatal("Unsupported key exchange %d", kex->kex_type); + } + + buffer_free(client_kexinit); + buffer_free(server_kexinit); + xfree(client_kexinit); + xfree(server_kexinit); + + debug("Wait SSH2_MSG_NEWKEYS."); + packet_read_expect(&plen, SSH2_MSG_NEWKEYS); + packet_done(); + debug("GOT SSH2_MSG_NEWKEYS."); + + debug("send SSH2_MSG_NEWKEYS."); + packet_start(SSH2_MSG_NEWKEYS); + packet_send(); + packet_write_wait(); + debug("done: send SSH2_MSG_NEWKEYS."); + +#ifdef DEBUG_KEXDH + /* send 1st encrypted/maced/compressed message */ + packet_start(SSH2_MSG_IGNORE); + packet_put_cstring("markus"); + packet_send(); + packet_write_wait(); +#endif + debug("done: KEX2."); +} + +/* diffie-hellman-group1-sha1 */ + +void +ssh_dh1_client(Kex *kex, char *host, struct sockaddr *hostaddr, + Buffer *client_kexinit, Buffer *server_kexinit) +{ +#ifdef DEBUG_KEXDH + int i; +#endif int plen, dlen; unsigned int klen, kout; char *signature = NULL; @@ -95,11 +179,11 @@ #ifdef DEBUG_KEXDH fprintf(stderr, "\np= "); - bignum_print(dh->p); + BN_print_fp(stderr, dh->p); fprintf(stderr, "\ng= "); - bignum_print(dh->g); + BN_print_fp(stderr, dh->g); fprintf(stderr, "\npub= "); - bignum_print(dh->pub_key); + BN_print_fp(stderr, dh->pub_key); fprintf(stderr, "\n"); DHparams_print_fp(stderr, dh); #endif @@ -117,7 +201,7 @@ fatal("cannot decode server_host_key_blob"); check_host_key(host, hostaddr, server_host_key, - options.user_hostfile2, options.system_hostfile2); + options.user_hostfile2, options.system_hostfile2); /* DH paramter f, server public DH key */ dh_server_pub = BN_new(); @@ -127,7 +211,7 @@ #ifdef DEBUG_KEXDH fprintf(stderr, "\ndh_server_pub= "); - bignum_print(dh_server_pub); + BN_print_fp(stderr, dh_server_pub); fprintf(stderr, "\n"); debug("bits %d", BN_num_bits(dh_server_pub)); #endif @@ -187,80 +271,352 @@ memcpy(session_id2, hash, session_id2_len); } +/* diffie-hellman-group-exchange-sha1 */ + +/* + * Estimates the group order for a Diffie-Hellman group that has an + * attack complexity approximately the same as O(2**bits). Estimate + * with: O(exp(1.9223 * (ln q)^(1/3) (ln ln q)^(2/3))) + */ + +int +dh_estimate(int bits) +{ + + if (bits < 64) + return (512); /* O(2**63) */ + if (bits < 128) + return (1024); /* O(2**86) */ + if (bits < 192) + return (2048); /* O(2**116) */ + return (4096); /* O(2**156) */ +} + void -ssh_kex2(char *host, struct sockaddr *hostaddr) +ssh_dhgex_client(Kex *kex, char *host, struct sockaddr *hostaddr, + Buffer *client_kexinit, Buffer *server_kexinit) { - int i, plen; - Kex *kex; - Buffer *client_kexinit, *server_kexinit; - char *sprop[PROPOSAL_MAX]; +#ifdef DEBUG_KEXDH + int i; +#endif + int plen, dlen; + unsigned int klen, kout; + char *signature = NULL; + unsigned int slen, nbits; + char *server_host_key_blob = NULL; + Key *server_host_key; + unsigned int sbloblen; + DH *dh; + BIGNUM *dh_server_pub = 0; + BIGNUM *shared_secret = 0; + BIGNUM *p = 0, *g = 0; + unsigned char *kbuf; + unsigned char *hash; - if (options.ciphers != NULL) { - myproposal[PROPOSAL_ENC_ALGS_CTOS] = - myproposal[PROPOSAL_ENC_ALGS_STOC] = options.ciphers; - } else if (options.cipher == SSH_CIPHER_3DES) { - myproposal[PROPOSAL_ENC_ALGS_CTOS] = - myproposal[PROPOSAL_ENC_ALGS_STOC] = - (char *) cipher_name(SSH_CIPHER_3DES_CBC); - } else if (options.cipher == SSH_CIPHER_BLOWFISH) { - myproposal[PROPOSAL_ENC_ALGS_CTOS] = - myproposal[PROPOSAL_ENC_ALGS_STOC] = - (char *) cipher_name(SSH_CIPHER_BLOWFISH_CBC); - } - if (options.compression) { - myproposal[PROPOSAL_COMP_ALGS_CTOS] = "zlib"; - myproposal[PROPOSAL_COMP_ALGS_STOC] = "zlib"; - } else { - myproposal[PROPOSAL_COMP_ALGS_CTOS] = "none"; - myproposal[PROPOSAL_COMP_ALGS_STOC] = "none"; - } + nbits = dh_estimate(kex->enc[MODE_OUT].cipher->key_len * 8); - /* buffers with raw kexinit messages */ - server_kexinit = xmalloc(sizeof(*server_kexinit)); - buffer_init(server_kexinit); - client_kexinit = kex_init(myproposal); + debug("Sending SSH2_MSG_KEX_DH_GEX_REQUEST."); + packet_start(SSH2_MSG_KEX_DH_GEX_REQUEST); + packet_put_int(nbits); + packet_send(); + packet_write_wait(); - /* algorithm negotiation */ - kex_exchange_kexinit(client_kexinit, server_kexinit, sprop); - kex = kex_choose_conf(myproposal, sprop, 0); - for (i = 0; i < PROPOSAL_MAX; i++) - xfree(sprop[i]); +#ifdef DEBUG_KEXDH + fprintf(stderr, "\nnbits = %d", nbits); +#endif - /* server authentication and session key agreement */ - ssh_kex_dh(kex, host, hostaddr, client_kexinit, server_kexinit); + debug("Wait SSH2_MSG_KEX_DH_GEX_GROUP."); - buffer_free(client_kexinit); - buffer_free(server_kexinit); - xfree(client_kexinit); - xfree(server_kexinit); + packet_read_expect(&plen, SSH2_MSG_KEX_DH_GEX_GROUP); - debug("Wait SSH2_MSG_NEWKEYS."); - packet_read_expect(&plen, SSH2_MSG_NEWKEYS); - packet_done(); - debug("GOT SSH2_MSG_NEWKEYS."); + debug("Got SSH2_MSG_KEX_DH_GEX_GROUP."); - debug("send SSH2_MSG_NEWKEYS."); - packet_start(SSH2_MSG_NEWKEYS); - packet_send(); - packet_write_wait(); - debug("done: send SSH2_MSG_NEWKEYS."); + if ((p = BN_new()) == NULL) + fatal("BN_new"); + packet_get_bignum2(p, &dlen); + if ((g = BN_new()) == NULL) + fatal("BN_new"); + packet_get_bignum2(g, &dlen); + if ((dh = dh_new_group(g, p)) == NULL) + fatal("dh_new_group"); #ifdef DEBUG_KEXDH - /* send 1st encrypted/maced/compressed message */ - packet_start(SSH2_MSG_IGNORE); - packet_put_cstring("markus"); + fprintf(stderr, "\np= "); + BN_print_fp(stderr, dh->p); + fprintf(stderr, "\ng= "); + BN_print_fp(stderr, dh->g); + fprintf(stderr, "\npub= "); + BN_print_fp(stderr, dh->pub_key); + fprintf(stderr, "\n"); + DHparams_print_fp(stderr, dh); +#endif + + debug("Sending SSH2_MSG_KEX_DH_GEX_INIT."); + /* generate and send 'e', client DH public key */ + packet_start(SSH2_MSG_KEX_DH_GEX_INIT); + packet_put_bignum2(dh->pub_key); packet_send(); packet_write_wait(); + + debug("Wait SSH2_MSG_KEX_DH_GEX_REPLY."); + + packet_read_expect(&plen, SSH2_MSG_KEX_DH_GEX_REPLY); + + debug("Got SSH2_MSG_KEXDH_REPLY."); + + /* key, cert */ + server_host_key_blob = packet_get_string(&sbloblen); + server_host_key = dsa_key_from_blob(server_host_key_blob, sbloblen); + if (server_host_key == NULL) + fatal("cannot decode server_host_key_blob"); + + check_host_key(host, hostaddr, server_host_key, + options.user_hostfile2, options.system_hostfile2); + + /* DH paramter f, server public DH key */ + dh_server_pub = BN_new(); + if (dh_server_pub == NULL) + fatal("dh_server_pub == NULL"); + packet_get_bignum2(dh_server_pub, &dlen); + +#ifdef DEBUG_KEXDH + fprintf(stderr, "\ndh_server_pub= "); + BN_print_fp(stderr, dh_server_pub); + fprintf(stderr, "\n"); + debug("bits %d", BN_num_bits(dh_server_pub)); #endif - debug("done: KEX2."); + + /* signed H */ + signature = packet_get_string(&slen); + packet_done(); + + if (!dh_pub_is_valid(dh, dh_server_pub)) + packet_disconnect("bad server public DH value"); + + klen = DH_size(dh); + kbuf = xmalloc(klen); + kout = DH_compute_key(kbuf, dh_server_pub, dh); +#ifdef DEBUG_KEXDH + debug("shared secret: len %d/%d", klen, kout); + fprintf(stderr, "shared secret == "); + for (i = 0; i< kout; i++) + fprintf(stderr, "%02x", (kbuf[i])&0xff); + fprintf(stderr, "\n"); +#endif + shared_secret = BN_new(); + + BN_bin2bn(kbuf, kout, shared_secret); + memset(kbuf, 0, klen); + xfree(kbuf); + + /* calc and verify H */ + hash = kex_hash_gex( + client_version_string, + server_version_string, + buffer_ptr(client_kexinit), buffer_len(client_kexinit), + buffer_ptr(server_kexinit), buffer_len(server_kexinit), + server_host_key_blob, sbloblen, + nbits, dh->p, dh->g, + dh->pub_key, + dh_server_pub, + shared_secret + ); + xfree(server_host_key_blob); + DH_free(dh); +#ifdef DEBUG_KEXDH + fprintf(stderr, "hash == "); + for (i = 0; i< 20; i++) + fprintf(stderr, "%02x", (hash[i])&0xff); + fprintf(stderr, "\n"); +#endif + if (dsa_verify(server_host_key, (unsigned char *)signature, slen, hash, 20) != 1) + fatal("dsa_verify failed for server_host_key"); + key_free(server_host_key); + + kex_derive_keys(kex, hash, shared_secret); + packet_set_kex(kex); + + /* save session id */ + session_id2_len = 20; + session_id2 = xmalloc(session_id2_len); + memcpy(session_id2, hash, session_id2_len); } /* * Authenticate user */ + +typedef struct Authctxt Authctxt; +typedef struct Authmethod Authmethod; + +typedef int sign_cb_fn( + Authctxt *authctxt, Key *key, + unsigned char **sigp, int *lenp, unsigned char *data, int datalen); + +struct Authctxt { + const char *server_user; + const char *host; + const char *service; + AuthenticationConnection *agent; + Authmethod *method; + int success; +}; +struct Authmethod { + char *name; /* string to compare against server's list */ + int (*userauth)(Authctxt *authctxt); + int *enabled; /* flag in option struct that enables method */ + int *batch_flag; /* flag in option struct that disables method */ +}; + +void input_userauth_success(int type, int plen, void *ctxt); +void input_userauth_failure(int type, int plen, void *ctxt); +void input_userauth_error(int type, int plen, void *ctxt); +void input_userauth_info_req(int type, int plen, void *ctxt); + +int userauth_none(Authctxt *authctxt); +int userauth_pubkey(Authctxt *authctxt); +int userauth_passwd(Authctxt *authctxt); +int userauth_kbdint(Authctxt *authctxt); + +void authmethod_clear(); +Authmethod *authmethod_get(char *authlist); +Authmethod *authmethod_lookup(const char *name); + +Authmethod authmethods[] = { + {"publickey", + userauth_pubkey, + &options.dsa_authentication, + NULL}, + {"password", + userauth_passwd, + &options.password_authentication, + &options.batch_mode}, + {"keyboard-interactive", + userauth_kbdint, + &options.kbd_interactive_authentication, + &options.batch_mode}, + {"none", + userauth_none, + NULL, + NULL}, + {NULL, NULL, NULL, NULL} +}; + +void +ssh_userauth2(const char *server_user, char *host) +{ + Authctxt authctxt; + int type; + int plen; + + debug("send SSH2_MSG_SERVICE_REQUEST"); + packet_start(SSH2_MSG_SERVICE_REQUEST); + packet_put_cstring("ssh-userauth"); + packet_send(); + packet_write_wait(); + type = packet_read(&plen); + if (type != SSH2_MSG_SERVICE_ACCEPT) { + fatal("denied SSH2_MSG_SERVICE_ACCEPT: %d", type); + } + if (packet_remaining() > 0) { + char *reply = packet_get_string(&plen); + debug("service_accept: %s", reply); + xfree(reply); + packet_done(); + } else { + debug("buggy server: service_accept w/o service"); + } + packet_done(); + debug("got SSH2_MSG_SERVICE_ACCEPT"); + + /* setup authentication context */ + authctxt.agent = ssh_get_authentication_connection(); + authctxt.server_user = server_user; + authctxt.host = host; + authctxt.service = "ssh-connection"; /* service name */ + authctxt.success = 0; + authctxt.method = authmethod_lookup("none"); + if (authctxt.method == NULL) + fatal("ssh_userauth2: internal error: cannot send userauth none request"); + authmethod_clear(); + + /* initial userauth request */ + userauth_none(&authctxt); + + dispatch_init(&input_userauth_error); + dispatch_set(SSH2_MSG_USERAUTH_SUCCESS, &input_userauth_success); + dispatch_set(SSH2_MSG_USERAUTH_FAILURE, &input_userauth_failure); + dispatch_run(DISPATCH_BLOCK, &authctxt.success, &authctxt); /* loop until success */ + + if (authctxt.agent != NULL) + ssh_close_authentication_connection(authctxt.agent); + + debug("ssh-userauth2 successfull: method %s", authctxt.method->name); +} +void +input_userauth_error(int type, int plen, void *ctxt) +{ + fatal("input_userauth_error: bad message during authentication"); +} +void +input_userauth_success(int type, int plen, void *ctxt) +{ + Authctxt *authctxt = ctxt; + if (authctxt == NULL) + fatal("input_userauth_success: no authentication context"); + authctxt->success = 1; /* break out */ +} +void +input_userauth_failure(int type, int plen, void *ctxt) +{ + Authmethod *method = NULL; + Authctxt *authctxt = ctxt; + char *authlist = NULL; + int partial; + + if (authctxt == NULL) + fatal("input_userauth_failure: no authentication context"); + + authlist = packet_get_string(NULL); + partial = packet_get_char(); + packet_done(); + + if (partial != 0) + debug("partial success"); + debug("authentications that can continue: %s", authlist); + + for (;;) { + method = authmethod_get(authlist); + if (method == NULL) + fatal("Unable to find an authentication method"); + authctxt->method = method; + if (method->userauth(authctxt) != 0) { + debug2("we sent a %s packet, wait for reply", method->name); + break; + } else { + debug2("we did not send a packet, disable method"); + method->enabled = NULL; + } + } + xfree(authlist); +} + int -ssh2_try_passwd(const char *server_user, const char *host, const char *service) +userauth_none(Authctxt *authctxt) { + /* initial userauth request */ + packet_start(SSH2_MSG_USERAUTH_REQUEST); + packet_put_cstring(authctxt->server_user); + packet_put_cstring(authctxt->service); + packet_put_cstring(authctxt->method->name); + packet_send(); + packet_write_wait(); + return 1; +} + +int +userauth_passwd(Authctxt *authctxt) +{ static int attempt = 0; char prompt[80]; char *password; @@ -272,12 +628,12 @@ error("Permission denied, please try again."); snprintf(prompt, sizeof(prompt), "%.30s@%.40s's password: ", - server_user, host); + authctxt->server_user, authctxt->host); password = read_passphrase(prompt, 0); packet_start(SSH2_MSG_USERAUTH_REQUEST); - packet_put_cstring(server_user); - packet_put_cstring(service); - packet_put_cstring("password"); + packet_put_cstring(authctxt->server_user); + packet_put_cstring(authctxt->service); + packet_put_cstring(authctxt->method->name); packet_put_char(0); packet_put_cstring(password); memset(password, 0, strlen(password)); @@ -287,45 +643,40 @@ return 1; } -typedef int sign_fn( - Key *key, - unsigned char **sigp, int *lenp, - unsigned char *data, int datalen); - int -ssh2_sign_and_send_pubkey(Key *k, sign_fn *do_sign, - const char *server_user, const char *host, const char *service) +sign_and_send_pubkey(Authctxt *authctxt, Key *k, sign_cb_fn *sign_callback) { Buffer b; unsigned char *blob, *signature; int bloblen, slen; int skip = 0; int ret = -1; + int have_sig = 1; dsa_make_key_blob(k, &blob, &bloblen); /* data to be signed */ buffer_init(&b); - if (datafellows & SSH_COMPAT_SESSIONID_ENCODING) { - buffer_put_string(&b, session_id2, session_id2_len); - skip = buffer_len(&b); - } else { + if (datafellows & SSH_OLD_SESSIONID) { buffer_append(&b, session_id2, session_id2_len); skip = session_id2_len; + } else { + buffer_put_string(&b, session_id2, session_id2_len); + skip = buffer_len(&b); } buffer_put_char(&b, SSH2_MSG_USERAUTH_REQUEST); - buffer_put_cstring(&b, server_user); + buffer_put_cstring(&b, authctxt->server_user); buffer_put_cstring(&b, datafellows & SSH_BUG_PUBKEYAUTH ? "ssh-userauth" : - service); - buffer_put_cstring(&b, "publickey"); - buffer_put_char(&b, 1); + authctxt->service); + buffer_put_cstring(&b, authctxt->method->name); + buffer_put_char(&b, have_sig); buffer_put_cstring(&b, KEX_DSS); buffer_put_string(&b, blob, bloblen); /* generate signature */ - ret = do_sign(k, &signature, &slen, buffer_ptr(&b), buffer_len(&b)); + ret = (*sign_callback)(authctxt, k, &signature, &slen, buffer_ptr(&b), buffer_len(&b)); if (ret == -1) { xfree(blob); buffer_free(&b); @@ -338,10 +689,10 @@ buffer_clear(&b); buffer_append(&b, session_id2, session_id2_len); buffer_put_char(&b, SSH2_MSG_USERAUTH_REQUEST); - buffer_put_cstring(&b, server_user); - buffer_put_cstring(&b, service); - buffer_put_cstring(&b, "publickey"); - buffer_put_char(&b, 1); + buffer_put_cstring(&b, authctxt->server_user); + buffer_put_cstring(&b, authctxt->service); + buffer_put_cstring(&b, authctxt->method->name); + buffer_put_char(&b, have_sig); buffer_put_cstring(&b, KEX_DSS); buffer_put_string(&b, blob, bloblen); } @@ -352,7 +703,7 @@ /* skip session id and packet type */ if (buffer_len(&b) < skip + 1) - fatal("ssh2_try_pubkey: internal error"); + fatal("userauth_pubkey: internal error"); buffer_consume(&b, skip + 1); /* put remaining data from buffer into packet */ @@ -367,12 +718,18 @@ return 1; } +/* sign callback */ +int dsa_sign_cb(Authctxt *authctxt, Key *key, unsigned char **sigp, int *lenp, + unsigned char *data, int datalen) +{ + return dsa_sign(key, sigp, lenp, data, datalen); +} + int -ssh2_try_pubkey(char *filename, - const char *server_user, const char *host, const char *service) +userauth_pubkey_identity(Authctxt *authctxt, char *filename) { Key *k; - int ret = 0; + int i, ret, try_next; struct stat st; if (stat(filename, &st) != 0) { @@ -387,39 +744,42 @@ char *passphrase; char prompt[300]; snprintf(prompt, sizeof prompt, - "Enter passphrase for DSA key '%.100s': ", - filename); - passphrase = read_passphrase(prompt, 0); - success = load_private_key(filename, passphrase, k, NULL); - memset(passphrase, 0, strlen(passphrase)); - xfree(passphrase); + "Enter passphrase for %s key '%.100s': ", + key_type(k), filename); + for (i = 0; i < options.number_of_password_prompts; i++) { + passphrase = read_passphrase(prompt, 0); + if (strcmp(passphrase, "") != 0) { + success = load_private_key(filename, passphrase, k, NULL); + try_next = 0; + } else { + debug2("no passphrase given, try next key"); + try_next = 1; + } + memset(passphrase, 0, strlen(passphrase)); + xfree(passphrase); + if (success || try_next) + break; + debug2("bad passphrase given, try again..."); + } if (!success) { key_free(k); return 0; } } - ret = ssh2_sign_and_send_pubkey(k, dsa_sign, server_user, host, service); + ret = sign_and_send_pubkey(authctxt, k, dsa_sign_cb); key_free(k); return ret; } -int agent_sign( - Key *key, - unsigned char **sigp, int *lenp, +/* sign callback */ +int agent_sign_cb(Authctxt *authctxt, Key *key, unsigned char **sigp, int *lenp, unsigned char *data, int datalen) { - int ret = -1; - AuthenticationConnection *ac = ssh_get_authentication_connection(); - if (ac != NULL) { - ret = ssh_agent_sign(ac, key, sigp, lenp, data, datalen); - ssh_close_authentication_connection(ac); - } - return ret; + return ssh_agent_sign(authctxt->agent, key, sigp, lenp, data, datalen); } int -ssh2_try_agent(AuthenticationConnection *ac, - const char *server_user, const char *host, const char *service) +userauth_pubkey_agent(Authctxt *authctxt) { static int called = 0; char *comment; @@ -427,104 +787,243 @@ int ret; if (called == 0) { - k = ssh_get_first_identity(ac, &comment, 2); - called ++; + k = ssh_get_first_identity(authctxt->agent, &comment, 2); + called = 1; } else { - k = ssh_get_next_identity(ac, &comment, 2); + k = ssh_get_next_identity(authctxt->agent, &comment, 2); } - if (k == NULL) + if (k == NULL) { + debug2("no more DSA keys from agent"); return 0; + } debug("trying DSA agent key %s", comment); xfree(comment); - ret = ssh2_sign_and_send_pubkey(k, agent_sign, server_user, host, service); + ret = sign_and_send_pubkey(authctxt, k, agent_sign_cb); key_free(k); return ret; } -void -ssh_userauth2(const char *server_user, char *host) +int +userauth_pubkey(Authctxt *authctxt) { - AuthenticationConnection *ac = ssh_get_authentication_connection(); - int type; - int plen; - int sent; - unsigned int dlen; - int partial; - int i = 0; - char *auths; - char *service = "ssh-connection"; /* service name */ + static int idx = 0; + int sent = 0; - debug("send SSH2_MSG_SERVICE_REQUEST"); - packet_start(SSH2_MSG_SERVICE_REQUEST); - packet_put_cstring("ssh-userauth"); + if (authctxt->agent != NULL) + sent = userauth_pubkey_agent(authctxt); + while (sent == 0 && idx < options.num_identity_files2) + sent = userauth_pubkey_identity(authctxt, options.identity_files2[idx++]); + return sent; +} + +/* + * Send userauth request message specifying keyboard-interactive method. + */ +int +userauth_kbdint(Authctxt *authctxt) +{ + static int attempt = 0; + + if (attempt++ >= options.number_of_password_prompts) + return 0; + + debug2("userauth_kbdint"); + packet_start(SSH2_MSG_USERAUTH_REQUEST); + packet_put_cstring(authctxt->server_user); + packet_put_cstring(authctxt->service); + packet_put_cstring(authctxt->method->name); + packet_put_cstring(""); /* lang */ + packet_put_cstring(options.kbd_interactive_devices ? + options.kbd_interactive_devices : ""); packet_send(); packet_write_wait(); - type = packet_read(&plen); - if (type != SSH2_MSG_SERVICE_ACCEPT) { - fatal("denied SSH2_MSG_SERVICE_ACCEPT: %d", type); + dispatch_set(SSH2_MSG_USERAUTH_INFO_REQUEST, &input_userauth_info_req); + return 1; +} + +/* + * parse SSH2_MSG_USERAUTH_INFO_REQUEST, prompt user and send + * SSH2_MSG_USERAUTH_INFO_RESPONSE + */ +void +input_userauth_info_req(int type, int plen, void *ctxt) +{ + Authctxt *authctxt = ctxt; + char *name = NULL; + char *inst = NULL; + char *lang = NULL; + char *prompt = NULL; + char *response = NULL; + unsigned int num_prompts, i; + int echo = 0; + + debug2("input_userauth_info_req"); + + if (authctxt == NULL) + fatal("input_userauth_info_req: no authentication context"); + + name = packet_get_string(NULL); + inst = packet_get_string(NULL); + lang = packet_get_string(NULL); + + if (strlen(name) > 0) + cli_mesg(name); + xfree(name); + + if (strlen(inst) > 0) + cli_mesg(inst); + xfree(inst); + xfree(lang); /* unused */ + + num_prompts = packet_get_int(); + /* + * Begin to build info response packet based on prompts requested. + * We commit to providing the correct number of responses, so if + * further on we run into a problem that prevents this, we have to + * be sure and clean this up and send a correct error response. + */ + packet_start(SSH2_MSG_USERAUTH_INFO_RESPONSE); + packet_put_int(num_prompts); + + for (i = 0; i < num_prompts; i++) { + prompt = packet_get_string(NULL); + echo = packet_get_char(); + + response = cli_prompt(prompt, echo); + + packet_put_cstring(response); + memset(response, 0, strlen(response)); + xfree(response); + xfree(prompt); } - if (packet_remaining() > 0) { - char *reply = packet_get_string(&plen); - debug("service_accept: %s", reply); - xfree(reply); - } else { - /* payload empty for ssh-2.0.13 ?? */ - debug("buggy server: service_accept w/o service"); - } - packet_done(); - debug("got SSH2_MSG_SERVICE_ACCEPT"); + packet_done(); /* done with parsing incoming message. */ - /* INITIAL request for auth */ - packet_start(SSH2_MSG_USERAUTH_REQUEST); - packet_put_cstring(server_user); - packet_put_cstring(service); - packet_put_cstring("none"); packet_send(); packet_write_wait(); +} - for (;;) { - sent = 0; - type = packet_read(&plen); - if (type == SSH2_MSG_USERAUTH_SUCCESS) +/* find auth method */ + +#define DELIM "," + +static char *def_authlist = "publickey,password"; +static char *authlist_current = NULL; /* clean copy used for comparison */ +static char *authname_current = NULL; /* last used auth method */ +static char *authlist_working = NULL; /* copy that gets modified by strtok_r() */ +static char *authlist_state = NULL; /* state variable for strtok_r() */ + +/* + * Before starting to use a new authentication method list sent by the + * server, reset internal variables. This should also be called when + * finished processing server list to free resources. + */ +void +authmethod_clear() +{ + if (authlist_current != NULL) { + xfree(authlist_current); + authlist_current = NULL; + } + if (authlist_working != NULL) { + xfree(authlist_working); + authlist_working = NULL; + } + if (authname_current != NULL) { + xfree(authname_current); + authlist_state = NULL; + } + if (authlist_state != NULL) + authlist_state = NULL; + return; +} + +/* + * given auth method name, if configurable options permit this method fill + * in auth_ident field and return true, otherwise return false. + */ +int +authmethod_is_enabled(Authmethod *method) +{ + if (method == NULL) + return 0; + /* return false if options indicate this method is disabled */ + if (method->enabled == NULL || *method->enabled == 0) + return 0; + /* return false if batch mode is enabled but method needs interactive mode */ + if (method->batch_flag != NULL && *method->batch_flag != 0) + return 0; + return 1; +} + +Authmethod * +authmethod_lookup(const char *name) +{ + Authmethod *method = NULL; + if (name != NULL) + for (method = authmethods; method->name != NULL; method++) + if (strcmp(name, method->name) == 0) + return method; + debug2("Unrecognized authentication method name: %s", name ? name : "NULL"); + return NULL; +} + +/* + * Given the authentication method list sent by the server, return the + * next method we should try. If the server initially sends a nil list, + * use a built-in default list. If the server sends a nil list after + * previously sending a valid list, continue using the list originally + * sent. + */ + +Authmethod * +authmethod_get(char *authlist) +{ + char *name = NULL, *authname_old; + Authmethod *method = NULL; + + /* Use a suitable default if we're passed a nil list. */ + if (authlist == NULL || strlen(authlist) == 0) + authlist = def_authlist; + + if (authlist_current == NULL || strcmp(authlist, authlist_current) != 0) { + /* start over if passed a different list */ + debug3("start over, passed a different list"); + authmethod_clear(); + authlist_current = xstrdup(authlist); + authlist_working = xstrdup(authlist); + name = strtok_r(authlist_working, DELIM, &authlist_state); + } else { + /* + * try to use previously used authentication method + * or continue to use previously passed list + */ + name = (authname_current != NULL) ? + authname_current : strtok_r(NULL, DELIM, &authlist_state); + } + + while (name != NULL) { + debug3("authmethod_lookup %s", name); + method = authmethod_lookup(name); + if (method != NULL && authmethod_is_enabled(method)) { + debug3("authmethod_is_enabled %s", name); break; - if (type != SSH2_MSG_USERAUTH_FAILURE) - fatal("access denied: %d", type); - /* SSH2_MSG_USERAUTH_FAILURE means: try again */ - auths = packet_get_string(&dlen); - debug("authentications that can continue: %s", auths); - partial = packet_get_char(); - packet_done(); - if (partial) - debug("partial success"); - if (options.dsa_authentication && - strstr(auths, "publickey") != NULL) { - if (ac != NULL) - sent = ssh2_try_agent(ac, - server_user, host, service); - if (!sent) { - while (i < options.num_identity_files2) { - sent = ssh2_try_pubkey( - options.identity_files2[i++], - server_user, host, service); - if (sent) - break; - } - } } - if (!sent) { - if (options.password_authentication && - !options.batch_mode && - strstr(auths, "password") != NULL) { - sent = ssh2_try_passwd(server_user, host, service); - } - } - if (!sent) - fatal("Permission denied (%s).", auths); - xfree(auths); + name = strtok_r(NULL, DELIM, &authlist_state); + method = NULL; } - if (ac != NULL) - ssh_close_authentication_connection(ac); - packet_done(); - debug("ssh-userauth2 successfull"); + + authname_old = authname_current; + if (method != NULL) { + debug("next auth method to try is %s", name); + authname_current = xstrdup(name); + } else { + debug("no more auth methods to try"); + authname_current = NULL; + } + + if (authname_old != NULL) + xfree(authname_old); + + return (method); }