=================================================================== RCS file: /cvsrepo/anoncvs/cvs/src/usr.bin/ssh/sshconnect2.c,v retrieving revision 1.10.2.4 retrieving revision 1.10.2.5 diff -u -r1.10.2.4 -r1.10.2.5 --- src/usr.bin/ssh/sshconnect2.c 2001/03/12 15:44:17 1.10.2.4 +++ src/usr.bin/ssh/sshconnect2.c 2001/03/21 18:53:16 1.10.2.5 @@ -23,7 +23,7 @@ */ #include "includes.h" -RCSID("$OpenBSD: sshconnect2.c,v 1.10.2.4 2001/03/12 15:44:17 jason Exp $"); +RCSID("$OpenBSD: sshconnect2.c,v 1.10.2.5 2001/03/21 18:53:16 jason Exp $"); #include #include @@ -51,6 +51,7 @@ #include "log.h" #include "readconf.h" #include "readpass.h" +#include "match.h" void ssh_dh1_client(Kex *, char *, struct sockaddr *, Buffer *, Buffer *); void ssh_dhgex_client(Kex *, char *, struct sockaddr *, Buffer *, Buffer *); @@ -171,7 +172,7 @@ debug("Sending SSH2_MSG_KEXDH_INIT."); /* generate and send 'e', client DH public key */ dh = dh_new_group1(); - dh_gen_key(dh); + dh_gen_key(dh, kex->we_need * 8); packet_start(SSH2_MSG_KEXDH_INIT); packet_put_bignum2(dh->pub_key); packet_send(); @@ -316,7 +317,7 @@ u_char *kbuf; u_char *hash; - nbits = dh_estimate(kex->enc[MODE_OUT].cipher->key_len * 8); + nbits = dh_estimate(kex->we_need * 8); debug("Sending SSH2_MSG_KEX_DH_GEX_REQUEST."); packet_start(SSH2_MSG_KEX_DH_GEX_REQUEST); @@ -342,7 +343,7 @@ packet_get_bignum2(g, &dlen); dh = dh_new_group(g, p); - dh_gen_key(dh); + dh_gen_key(dh, kex->we_need * 8); #ifdef DEBUG_KEXDH fprintf(stderr, "\np= "); @@ -467,6 +468,10 @@ AuthenticationConnection *agent; Authmethod *method; int success; + char *authlist; + Key *last_key; + sign_cb_fn *last_key_sign; + int last_key_hint; }; struct Authmethod { char *name; /* string to compare against server's list */ @@ -480,15 +485,23 @@ void input_userauth_banner(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); +void input_userauth_pk_ok(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(void); +void userauth(Authctxt *authctxt, char *authlist); + +int +sign_and_send_pubkey(Authctxt *authctxt, Key *k, + sign_cb_fn *sign_callback); +void clear_auth_state(Authctxt *authctxt); + Authmethod *authmethod_get(char *authlist); Authmethod *authmethod_lookup(const char *name); +char *authmethods_get(void); Authmethod authmethods[] = { {"publickey", @@ -539,6 +552,9 @@ packet_done(); debug("got SSH2_MSG_SERVICE_ACCEPT"); + if (options.preferred_authentications == NULL) + options.preferred_authentications = authmethods_get(); + /* setup authentication context */ authctxt.agent = ssh_get_authentication_connection(); authctxt.server_user = server_user; @@ -546,9 +562,9 @@ authctxt.service = "ssh-connection"; /* service name */ authctxt.success = 0; authctxt.method = authmethod_lookup("none"); + authctxt.authlist = NULL; if (authctxt.method == NULL) fatal("ssh_userauth2: internal error: cannot send userauth none request"); - authmethod_clear(); /* initial userauth request */ userauth_none(&authctxt); @@ -565,6 +581,30 @@ debug("ssh-userauth2 successful: method %s", authctxt.method->name); } void +userauth(Authctxt *authctxt, char *authlist) +{ + if (authlist == NULL) { + authlist = authctxt->authlist; + } else { + if (authctxt->authlist) + xfree(authctxt->authlist); + authctxt->authlist = authlist; + } + for (;;) { + Authmethod *method = authmethod_get(authlist); + if (method == NULL) + fatal("Permission denied (%s).", authlist); + 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; + } + } +} +void input_userauth_error(int type, int plen, void *ctxt) { fatal("input_userauth_error: bad message during authentication: " @@ -587,12 +627,14 @@ Authctxt *authctxt = ctxt; if (authctxt == NULL) fatal("input_userauth_success: no authentication context"); + if (authctxt->authlist) + xfree(authctxt->authlist); + clear_auth_state(authctxt); 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; @@ -608,20 +650,75 @@ log("Authenticated with partial success."); debug("authentications that can continue: %s", authlist); - for (;;) { - method = authmethod_get(authlist); - if (method == NULL) - fatal("Permission denied (%s).", authlist); - authctxt->method = method; - if (method->userauth(authctxt) != 0) { - debug2("we sent a %s packet, wait for reply", method->name); + clear_auth_state(authctxt); + userauth(authctxt, authlist); +} +void +input_userauth_pk_ok(int type, int plen, void *ctxt) +{ + Authctxt *authctxt = ctxt; + Key *key = NULL; + Buffer b; + int alen, blen, pktype, sent = 0; + char *pkalg, *pkblob, *fp; + + if (authctxt == NULL) + fatal("input_userauth_pk_ok: no authentication context"); + if (datafellows & SSH_BUG_PKOK) { + /* this is similar to SSH_BUG_PKAUTH */ + debug2("input_userauth_pk_ok: SSH_BUG_PKOK"); + pkblob = packet_get_string(&blen); + buffer_init(&b); + buffer_append(&b, pkblob, blen); + pkalg = buffer_get_string(&b, &alen); + buffer_free(&b); + } else { + pkalg = packet_get_string(&alen); + pkblob = packet_get_string(&blen); + } + packet_done(); + + debug("input_userauth_pk_ok: pkalg %s blen %d lastkey %p hint %d", + pkalg, blen, authctxt->last_key, authctxt->last_key_hint); + + do { + if (authctxt->last_key == NULL || + authctxt->last_key_sign == NULL) { + debug("no last key or no sign cb"); break; - } else { - debug2("we did not send a packet, disable method"); - method->enabled = NULL; } - } - xfree(authlist); + if ((pktype = key_type_from_name(pkalg)) == KEY_UNSPEC) { + debug("unknown pkalg %s", pkalg); + break; + } + if ((key = key_from_blob(pkblob, blen)) == NULL) { + debug("no key from blob. pkalg %s", pkalg); + break; + } + fp = key_fingerprint(key, SSH_FP_MD5, SSH_FP_HEX); + debug2("input_userauth_pk_ok: fp %s", fp); + xfree(fp); + if (!key_equal(key, authctxt->last_key)) { + debug("key != last_key"); + break; + } + sent = sign_and_send_pubkey(authctxt, key, + authctxt->last_key_sign); + } while(0); + + if (key != NULL) + key_free(key); + xfree(pkalg); + xfree(pkblob); + + /* unregister */ + clear_auth_state(authctxt); + dispatch_set(SSH2_MSG_USERAUTH_PK_OK, NULL); + + /* try another method if we did not send a packet*/ + if (sent == 0) + userauth(authctxt, NULL); + } int @@ -633,7 +730,6 @@ packet_put_cstring(authctxt->service); packet_put_cstring(authctxt->method->name); packet_send(); - packet_write_wait(); return 1; } @@ -658,14 +754,27 @@ packet_put_cstring(authctxt->service); packet_put_cstring(authctxt->method->name); packet_put_char(0); - ssh_put_password(password); + packet_put_cstring(password); memset(password, 0, strlen(password)); xfree(password); + packet_inject_ignore(64); packet_send(); - packet_write_wait(); return 1; } +void +clear_auth_state(Authctxt *authctxt) +{ + /* XXX clear authentication state */ + if (authctxt->last_key != NULL && authctxt->last_key_hint == -1) { + debug3("clear_auth_state: key_free %p", authctxt->last_key); + key_free(authctxt->last_key); + } + authctxt->last_key = NULL; + authctxt->last_key_hint = -2; + authctxt->last_key_sign = NULL; +} + int sign_and_send_pubkey(Authctxt *authctxt, Key *k, sign_cb_fn *sign_callback) { @@ -677,6 +786,7 @@ int have_sig = 1; debug3("sign_and_send_pubkey"); + if (key_to_blob(k, &blob, &bloblen) == 0) { /* we cannot handle this key */ debug3("sign_and_send_pubkey: cannot handle key"); @@ -707,7 +817,8 @@ buffer_put_string(&b, blob, bloblen); /* generate signature */ - ret = (*sign_callback)(authctxt, 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); @@ -719,6 +830,7 @@ if (datafellows & SSH_BUG_PKSERVICE) { buffer_clear(&b); buffer_append(&b, session_id2, session_id2_len); + skip = session_id2_len; buffer_put_char(&b, SSH2_MSG_USERAUTH_REQUEST); buffer_put_cstring(&b, authctxt->server_user); buffer_put_cstring(&b, authctxt->service); @@ -729,6 +841,7 @@ buffer_put_string(&b, blob, bloblen); } xfree(blob); + /* append signature */ buffer_put_string(&b, signature, slen); xfree(signature); @@ -742,76 +855,118 @@ packet_start(SSH2_MSG_USERAUTH_REQUEST); packet_put_raw(buffer_ptr(&b), buffer_len(&b)); buffer_free(&b); - - /* send */ packet_send(); - packet_write_wait(); return 1; } -/* sign callback */ -int key_sign_cb(Authctxt *authctxt, Key *key, u_char **sigp, int *lenp, - u_char *data, int datalen) +int +send_pubkey_test(Authctxt *authctxt, Key *k, sign_cb_fn *sign_callback, + int hint) { - return key_sign(key, sigp, lenp, data, datalen); + u_char *blob; + int bloblen, have_sig = 0; + + debug3("send_pubkey_test"); + + if (key_to_blob(k, &blob, &bloblen) == 0) { + /* we cannot handle this key */ + debug3("send_pubkey_test: cannot handle key"); + return 0; + } + /* register callback for USERAUTH_PK_OK message */ + authctxt->last_key_sign = sign_callback; + authctxt->last_key_hint = hint; + authctxt->last_key = k; + dispatch_set(SSH2_MSG_USERAUTH_PK_OK, &input_userauth_pk_ok); + + 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_char(have_sig); + if (!(datafellows & SSH_BUG_PKAUTH)) + packet_put_cstring(key_ssh_name(k)); + packet_put_string(blob, bloblen); + xfree(blob); + packet_send(); + return 1; } -int -userauth_pubkey_identity(Authctxt *authctxt, char *filename) +Key * +load_identity_file(char *filename) { - Key *k; - int i, ret, try_next, success = 0; + Key *private; + char prompt[300], *passphrase; + int success = 0, quit, i; struct stat st; - char *passphrase; - char prompt[300]; - if (stat(filename, &st) != 0) { - debug("key does not exist: %s", filename); - return 0; + if (stat(filename, &st) < 0) { + debug3("no such identity: %s", filename); + return NULL; } - debug("try pubkey: %s", filename); - - k = key_new(KEY_UNSPEC); - if (!load_private_key(filename, "", k, NULL)) { + private = key_new(KEY_UNSPEC); + if (!load_private_key(filename, "", private, NULL)) { if (options.batch_mode) { - key_free(k); - return 0; + key_free(private); + return NULL; } snprintf(prompt, sizeof prompt, "Enter passphrase for key '%.100s': ", 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; + success = load_private_key(filename, + passphrase, private, NULL); + quit = 0; } else { debug2("no passphrase given, try next key"); - try_next = 1; + quit = 1; } memset(passphrase, 0, strlen(passphrase)); xfree(passphrase); - if (success || try_next) + if (success || quit) break; debug2("bad passphrase given, try again..."); } if (!success) { - key_free(k); - return 0; + key_free(private); + return NULL; } } - ret = sign_and_send_pubkey(authctxt, k, key_sign_cb); - key_free(k); + return private; +} + +int +identity_sign_cb(Authctxt *authctxt, Key *key, u_char **sigp, int *lenp, + u_char *data, int datalen) +{ + Key *private; + int idx, ret; + + idx = authctxt->last_key_hint; + if (idx < 0) + return -1; + private = load_identity_file(options.identity_files[idx]); + if (private == NULL) + return -1; + ret = key_sign(private, sigp, lenp, data, datalen); + key_free(private); return ret; } -/* sign callback */ int agent_sign_cb(Authctxt *authctxt, Key *key, u_char **sigp, int *lenp, u_char *data, int datalen) { return ssh_agent_sign(authctxt->agent, key, sigp, lenp, data, datalen); } +int key_sign_cb(Authctxt *authctxt, Key *key, u_char **sigp, int *lenp, + u_char *data, int datalen) +{ + return key_sign(key, sigp, lenp, data, datalen); +} + int userauth_pubkey_agent(Authctxt *authctxt) { @@ -829,10 +984,11 @@ if (k == NULL) { debug2("userauth_pubkey_agent: no more keys"); } else { - debug("userauth_pubkey_agent: trying agent key %s", comment); + debug("userauth_pubkey_agent: testing agent key %s", comment); xfree(comment); - ret = sign_and_send_pubkey(authctxt, k, agent_sign_cb); - key_free(k); + ret = send_pubkey_test(authctxt, k, agent_sign_cb, -1); + if (ret == 0) + key_free(k); } if (ret == 0) debug2("userauth_pubkey_agent: no message sent"); @@ -844,6 +1000,8 @@ { static int idx = 0; int sent = 0; + Key *key; + char *filename; if (authctxt->agent != NULL) { do { @@ -851,9 +1009,21 @@ } while(!sent && authctxt->agent->howmany > 0); } while (!sent && idx < options.num_identity_files) { - if (options.identity_files_type[idx] != KEY_RSA1) - sent = userauth_pubkey_identity(authctxt, - options.identity_files[idx]); + key = options.identity_keys[idx]; + filename = options.identity_files[idx]; + if (key == NULL) { + debug("try privkey: %s", filename); + key = load_identity_file(filename); + if (key != NULL) { + sent = sign_and_send_pubkey(authctxt, key, + key_sign_cb); + key_free(key); + } + } else if (key->type != KEY_RSA1) { + debug("try pubkey: %s", filename); + sent = send_pubkey_test(authctxt, key, + identity_sign_cb, idx); + } idx++; } return sent; @@ -879,7 +1049,6 @@ packet_put_cstring(options.kbd_interactive_devices ? options.kbd_interactive_devices : ""); packet_send(); - packet_write_wait(); dispatch_set(SSH2_MSG_USERAUTH_INFO_REQUEST, &input_userauth_info_req); return 1; @@ -928,53 +1097,20 @@ response = cli_prompt(prompt, echo); - ssh_put_password(response); + packet_put_cstring(response); memset(response, 0, strlen(response)); xfree(response); xfree(prompt); } packet_done(); /* done with parsing incoming message. */ + packet_inject_ignore(64); packet_send(); - packet_write_wait(); } /* 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(void) -{ - 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); - authname_current = 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. */ @@ -1004,62 +1140,70 @@ return NULL; } +/* XXX internal state */ +static Authmethod *current = NULL; +static char *supported = NULL; +static char *preferred = 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. + * use a built-in default list. */ - Authmethod * authmethod_get(char *authlist) { - char *name = NULL, *authname_old; - Authmethod *method = NULL; + char *name = NULL; + int next; + /* Use a suitable default if we're passed a nil list. */ if (authlist == NULL || strlen(authlist) == 0) - authlist = def_authlist; + authlist = options.preferred_authentications; - 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); - } + if (supported == NULL || strcmp(authlist, supported) != 0) { + debug3("start over, passed a different list %s", authlist); + if (supported != NULL) + xfree(supported); + supported = xstrdup(authlist); + preferred = options.preferred_authentications; + debug3("preferred %s", preferred); + current = NULL; + } else if (current != NULL && authmethod_is_enabled(current)) + return current; - while (name != NULL) { + for (;;) { + if ((name = match_list(preferred, supported, &next)) == NULL) { + debug("no more auth methods to try"); + current = NULL; + return NULL; + } + preferred += next; debug3("authmethod_lookup %s", name); - method = authmethod_lookup(name); - if (method != NULL && authmethod_is_enabled(method)) { + debug3("remaining preferred: %s", preferred); + if ((current = authmethod_lookup(name)) != NULL && + authmethod_is_enabled(current)) { debug3("authmethod_is_enabled %s", name); - break; + debug("next auth method to try is %s", name); + return current; } - name = strtok_r(NULL, DELIM, &authlist_state); - method = NULL; } +} - 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); +#define DELIM "," +char * +authmethods_get(void) +{ + Authmethod *method = NULL; + char buf[1024]; - return (method); + buf[0] = '\0'; + for (method = authmethods; method->name != NULL; method++) { + if (authmethod_is_enabled(method)) { + if (buf[0] != '\0') + strlcat(buf, DELIM, sizeof buf); + strlcat(buf, method->name, sizeof buf); + } + } + return xstrdup(buf); }