/* * Creates a (possibly privileged) socket for use as the ssh connection. */ static int ssh_create_socket(int privileged, struct addrinfo *ai) { int sock, r, gaierr; struct addrinfo hints, *res = NULL; sock = socket(ai->ai_family, ai->ai_socktype, ai->ai_protocol); if (sock < 0) { ssh_error("socket: %s", strerror(errno)); return -1; } fcntl(sock, F_SETFD, FD_CLOEXEC); /* Bind the socket to an alternative local IP address */ if (options.bind_address == NULL && !privileged) return sock; if (options.bind_address) { memset(&hints, 0, sizeof(hints)); hints.ai_family = ai->ai_family; hints.ai_socktype = ai->ai_socktype; hints.ai_protocol = ai->ai_protocol; hints.ai_flags = AI_PASSIVE; gaierr = getaddrinfo(options.bind_address, NULL, &hints, &res); if (gaierr) { ssh_error("getaddrinfo: %s: %s", options.bind_address, ssh_gai_strerror(gaierr)); close(sock); return -1; } } /* * If we are running as root and want to connect to a privileged * port, bind our own socket to a privileged port. */ if (privileged) { PRIV_START; r = bindresvport_sa(sock, res ? res->ai_addr : NULL); PRIV_END; if (r < 0) { ssh_error("bindresvport_sa: af=%d %s", ai->ai_family, strerror(errno)); goto fail; } } else { if (bind(sock, res->ai_addr, res->ai_addrlen) < 0) { ssh_error("bind: %s: %s", options.bind_address, strerror(errno)); fail: close(sock); freeaddrinfo(res); return -1; } } if (res != NULL) freeaddrinfo(res); return sock; }
static int check_host_cert(const char *host, const Key *host_key) { const char *reason; if (key_cert_check_authority(host_key, 1, 0, host, &reason) != 0) { ssh_error("%s", reason); return 0; } if (buffer_len(host_key->cert->critical) != 0) { ssh_error("Certificate for %s contains unsupported " "critical options(s)", host); return 0; } return 1; }
static int _git_ssh_authenticate_session( LIBSSH2_SESSION* session, const char *user, git_cred* cred) { int rc; do { switch (cred->credtype) { case GIT_CREDTYPE_USERPASS_PLAINTEXT: { git_cred_userpass_plaintext *c = (git_cred_userpass_plaintext *)cred; user = c->username ? c->username : user; rc = libssh2_userauth_password(session, user, c->password); break; } case GIT_CREDTYPE_SSH_NONE: { git_cred_ssh_none *c = (git_cred_ssh_none *)cred; user = c->username ? c->username : user; libssh2_userauth_list(session, user, strlen(user)); if(libssh2_userauth_authenticated(session)){ rc = LIBSSH2_ERROR_NONE; } else { rc = LIBSSH2_ERROR_AUTHENTICATION_FAILED; } break; } case GIT_CREDTYPE_SSH_KEY: { git_cred_ssh_key *c = (git_cred_ssh_key *)cred; user = c->username ? c->username : user; if (c->privatekey) rc = libssh2_userauth_publickey_fromfile( session, c->username, c->publickey, c->privatekey, c->passphrase); else rc = ssh_agent_auth(session, c); break; } case GIT_CREDTYPE_SSH_CUSTOM: { git_cred_ssh_custom *c = (git_cred_ssh_custom *)cred; user = c->username ? c->username : user; rc = libssh2_userauth_publickey( session, c->username, (const unsigned char *)c->publickey, c->publickey_len, c->sign_callback, &c->sign_data); break; } default: rc = LIBSSH2_ERROR_AUTHENTICATION_FAILED; } } while (LIBSSH2_ERROR_EAGAIN == rc || LIBSSH2_ERROR_TIMEOUT == rc); if (rc != LIBSSH2_ERROR_NONE) { ssh_error(session, "Failed to authenticate SSH session"); return -1; } return 0; }
static int ssh_stream_write( git_smart_subtransport_stream *stream, const char *buffer, size_t len) { ssh_stream *s = (ssh_stream *)stream; size_t off = 0; ssize_t ret = 0; if (!s->sent_command && send_command(s) < 0) return -1; do { ret = libssh2_channel_write(s->channel, buffer + off, len - off); if (ret < 0) break; off += ret; } while (off < len); if (ret < 0) { ssh_error(s->session, "SSH could not write data"); return -1; } return 0; }
static int _git_ssh_session_create( LIBSSH2_SESSION** session, git_stream *io) { int rc = 0; LIBSSH2_SESSION* s; git_socket_stream *socket = (git_socket_stream *) io; assert(session); s = libssh2_session_init(); if (!s) { giterr_set(GITERR_NET, "failed to initialize SSH session"); return -1; } do { rc = libssh2_session_startup(s, socket->s); } while (LIBSSH2_ERROR_EAGAIN == rc || LIBSSH2_ERROR_TIMEOUT == rc); if (rc != LIBSSH2_ERROR_NONE) { ssh_error(s, "failed to start SSH session"); libssh2_session_free(s); return -1; } libssh2_session_set_blocking(s, 1); *session = s; return 0; }
int buffer_get_bignum2_ret(Buffer *buffer, BIGNUM *value) { int ret; if ((ret = sshbuf_get_bignum2(buffer, value)) != 0) { ssh_error("%s: %s", __func__, ssh_err(ret)); return -1; } return 0; }
int buffer_get_int64_ret(u_int64_t *v, Buffer *buffer) { int ret; if ((ret = sshbuf_get_u64(buffer, v)) != 0) { ssh_error("%s: %s", __func__, ssh_err(ret)); return -1; } return 0; }
int buffer_get_char_ret(char *v, Buffer *buffer) { int ret; if ((ret = sshbuf_get_u8(buffer, (u_char *)v)) != 0) { ssh_error("%s: %s", __func__, ssh_err(ret)); return -1; } return 0; }
int buffer_put_bignum_ret(Buffer *buffer, const BIGNUM *value) { int ret; if ((ret = sshbuf_put_bignum1(buffer, value)) != 0) { ssh_error("%s: %s", __func__, ssh_err(ret)); return -1; } return 0; }
static int ssh_agent_auth(LIBSSH2_SESSION *session, git_cred_ssh_key *c) { int rc = LIBSSH2_ERROR_NONE; struct libssh2_agent_publickey *curr, *prev = NULL; LIBSSH2_AGENT *agent = libssh2_agent_init(session); if (agent == NULL) return -1; rc = libssh2_agent_connect(agent); if (rc != LIBSSH2_ERROR_NONE) goto shutdown; rc = libssh2_agent_list_identities(agent); if (rc != LIBSSH2_ERROR_NONE) goto shutdown; while (1) { rc = libssh2_agent_get_identity(agent, &curr, prev); if (rc < 0) goto shutdown; /* rc is set to 1 whenever the ssh agent ran out of keys to check. * Set the error code to authentication failure rather than erroring * out with an untranslatable error code. */ if (rc == 1) { rc = LIBSSH2_ERROR_AUTHENTICATION_FAILED; goto shutdown; } rc = libssh2_agent_userauth(agent, c->username, curr); if (rc == 0) break; prev = curr; } shutdown: if (rc != LIBSSH2_ERROR_NONE) ssh_error(session, "error authenticating"); libssh2_agent_disconnect(agent); libssh2_agent_free(agent); return rc; }
static int ssh_stream_read( git_smart_subtransport_stream *stream, char *buffer, size_t buf_size, size_t *bytes_read) { int rc; ssh_stream *s = (ssh_stream *)stream; *bytes_read = 0; if (!s->sent_command && send_command(s) < 0) return -1; if ((rc = libssh2_channel_read(s->channel, buffer, buf_size)) < LIBSSH2_ERROR_NONE) { ssh_error(s->session, "SSH could not read data"); return -1; } /* * If we can't get anything out of stdout, it's typically a * not-found error, so read from stderr and signal EOF on * stderr. */ if (rc == 0) { if ((rc = libssh2_channel_read_stderr(s->channel, buffer, buf_size)) > 0) { giterr_set(GITERR_SSH, "%*s", rc, buffer); return GIT_EEOF; } else if (rc < LIBSSH2_ERROR_NONE) { ssh_error(s->session, "SSH could not read stderr"); return -1; } } *bytes_read = rc; return 0; }
const void * buffer_get_string_ptr_ret(Buffer *buffer, u_int *length_ptr) { size_t len; int ret; const u_char *value; if ((ret = sshbuf_get_string_direct(buffer, &value, &len)) != 0) { ssh_error("%s: %s", __func__, ssh_err(ret)); return NULL; } if (length_ptr != NULL) *length_ptr = len; /* Safe: sshbuf never stores len > 2^31 */ return value; }
static int list_auth_methods(int *out, LIBSSH2_SESSION *session, const char *username) { const char *list, *ptr; *out = 0; list = libssh2_userauth_list(session, username, strlen(username)); /* either error, or the remote accepts NONE auth, which is bizarre, let's punt */ if (list == NULL && !libssh2_userauth_authenticated(session)) { ssh_error(session, "Failed to retrieve list of SSH authentication methods"); return -1; } ptr = list; while (ptr) { if (*ptr == ',') ptr++; if (!git__prefixcmp(ptr, SSH_AUTH_PUBLICKEY)) { *out |= GIT_CREDTYPE_SSH_KEY; *out |= GIT_CREDTYPE_SSH_CUSTOM; #ifdef GIT_SSH_MEMORY_CREDENTIALS *out |= GIT_CREDTYPE_SSH_MEMORY; #endif ptr += strlen(SSH_AUTH_PUBLICKEY); continue; } if (!git__prefixcmp(ptr, SSH_AUTH_PASSWORD)) { *out |= GIT_CREDTYPE_USERPASS_PLAINTEXT; ptr += strlen(SSH_AUTH_PASSWORD); continue; } if (!git__prefixcmp(ptr, SSH_AUTH_KEYBOARD_INTERACTIVE)) { *out |= GIT_CREDTYPE_SSH_INTERACTIVE; ptr += strlen(SSH_AUTH_KEYBOARD_INTERACTIVE); continue; } /* Skipt it if we don't know it */ ptr = strchr(ptr, ','); } return 0; }
static int ssh_agent_auth(LIBSSH2_SESSION *session, git_cred_ssh_key *c) { int rc = LIBSSH2_ERROR_NONE; struct libssh2_agent_publickey *curr, *prev = NULL; LIBSSH2_AGENT *agent = libssh2_agent_init(session); if (agent == NULL) return -1; rc = libssh2_agent_connect(agent); if (rc != LIBSSH2_ERROR_NONE) goto shutdown; rc = libssh2_agent_list_identities(agent); if (rc != LIBSSH2_ERROR_NONE) goto shutdown; while (1) { rc = libssh2_agent_get_identity(agent, &curr, prev); if (rc < 0) goto shutdown; if (rc == 1) goto shutdown; rc = libssh2_agent_userauth(agent, c->username, curr); if (rc == 0) break; prev = curr; } shutdown: if (rc != LIBSSH2_ERROR_NONE) ssh_error(session, "error authenticating"); libssh2_agent_disconnect(agent); libssh2_agent_free(agent); return rc; }
static int ssh_stream_write( git_smart_subtransport_stream *stream, const char *buffer, size_t len) { ssh_stream *s = (ssh_stream *)stream; if (!s->sent_command && send_command(s) < 0) return -1; if (libssh2_channel_write(s->channel, buffer, len) < LIBSSH2_ERROR_NONE) { ssh_error(s->session, "SSH could not write data"); return -1; } return 0; }
static int ssh_rijndael_cbc(EVP_CIPHER_CTX *ctx, u_char *dest, const u_char *src, LIBCRYPTO_EVP_INL_TYPE len) { struct ssh_rijndael_ctx *c; u_char buf[RIJNDAEL_BLOCKSIZE]; u_char *cprev, *cnow, *plain, *ivp; int i, j, blocks = len / RIJNDAEL_BLOCKSIZE; if (len == 0) return (1); if (len % RIJNDAEL_BLOCKSIZE) fatal("ssh_rijndael_cbc: bad len %d", len); if ((c = EVP_CIPHER_CTX_get_app_data(ctx)) == NULL) { ssh_error("ssh_rijndael_cbc: no context"); return (0); } if (ctx->encrypt) { cnow = dest; plain = (u_char *)src; cprev = c->r_iv; for (i = 0; i < blocks; i++, plain+=RIJNDAEL_BLOCKSIZE, cnow+=RIJNDAEL_BLOCKSIZE) { for (j = 0; j < RIJNDAEL_BLOCKSIZE; j++) buf[j] = plain[j] ^ cprev[j]; rijndael_encrypt(&c->r_ctx, buf, cnow); cprev = cnow; } memcpy(c->r_iv, cprev, RIJNDAEL_BLOCKSIZE); } else { cnow = (u_char *) (src+len-RIJNDAEL_BLOCKSIZE); plain = dest+len-RIJNDAEL_BLOCKSIZE; memcpy(buf, cnow, RIJNDAEL_BLOCKSIZE); for (i = blocks; i > 0; i--, cnow-=RIJNDAEL_BLOCKSIZE, plain-=RIJNDAEL_BLOCKSIZE) { rijndael_decrypt(&c->r_ctx, cnow, plain); ivp = (i == 1) ? c->r_iv : cnow-RIJNDAEL_BLOCKSIZE; for (j = 0; j < RIJNDAEL_BLOCKSIZE; j++) plain[j] ^= ivp[j]; } memcpy(c->r_iv, buf, RIJNDAEL_BLOCKSIZE); } return (1); }
static void warn_changed_key(Key *host_key) { char *fp; fp = sshkey_fingerprint(host_key, options.fingerprint_hash, SSH_FP_DEFAULT); if (fp == NULL) fatal("%s: sshkey_fingerprint fail", __func__); ssh_error("@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@"); ssh_error("@ WARNING: REMOTE HOST IDENTIFICATION HAS CHANGED! @"); ssh_error("@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@"); ssh_error("IT IS POSSIBLE THAT SOMEONE IS DOING SOMETHING NASTY!"); ssh_error("Someone could be eavesdropping on you right now (man-in-the-middle attack)!"); ssh_error("It is also possible that a host key has just been changed."); ssh_error("The fingerprint for the %s key sent by the remote host is\n%s.", key_type(host_key), fp); ssh_error("Please contact your system administrator."); free(fp); }
int dh_pub_is_valid(DH *dh, BIGNUM *dh_pub) { int i; int n = BN_num_bits(dh_pub); int bits_set = 0; BIGNUM *tmp; if (dh_pub->neg) { logit("invalid public DH value: negative"); return 0; } if (BN_cmp(dh_pub, BN_value_one()) != 1) { /* pub_exp <= 1 */ logit("invalid public DH value: <= 1"); return 0; } if ((tmp = BN_new()) == NULL) { ssh_error("%s: BN_new failed", __func__); return 0; } if (!BN_sub(tmp, dh->p, BN_value_one()) || BN_cmp(dh_pub, tmp) != -1) { /* pub_exp > p-2 */ BN_clear_free(tmp); logit("invalid public DH value: >= p-1"); return 0; } BN_clear_free(tmp); for (i = 0; i <= n; i++) if (BN_is_bit_set(dh_pub, i)) bits_set++; debug2("bits set: %d/%d", bits_set, BN_num_bits(dh->p)); /* if g==2 and bits_set==1 then computing log_g(dh_pub) is trivial */ if (bits_set > 1) return 1; logit("invalid public DH value (%d/%d)", bits_set, BN_num_bits(dh->p)); return 0; }
/* Validate KEX method name list */ int kex_names_valid(const char *names) { char *s, *cp, *p; if (names == NULL || strcmp(names, "") == 0) return 0; if ((s = cp = strdup(names)) == NULL) return 0; for ((p = strsep(&cp, ",")); p && *p != '\0'; (p = strsep(&cp, ","))) { if (kex_alg_by_name(p) == NULL) { ssh_error("Unsupported KEX algorithm \"%.100s\"", p); free(s); return 0; } } debug3("kex names ok: [%s]", names); free(s); return 1; }
static int send_command(ssh_stream *s) { int error; git_buf request = GIT_BUF_INIT; error = gen_proto(&request, s->cmd, s->url); if (error < 0) goto cleanup; error = libssh2_channel_exec(s->channel, request.ptr); if (error < LIBSSH2_ERROR_NONE) { ssh_error(s->session, "SSH could not execute request"); goto cleanup; } s->sent_command = 1; cleanup: git_buf_free(&request); return error; }
static int ssh_stream_read( git_smart_subtransport_stream *stream, char *buffer, size_t buf_size, size_t *bytes_read) { int rc; ssh_stream *s = (ssh_stream *)stream; *bytes_read = 0; if (!s->sent_command && send_command(s) < 0) return -1; if ((rc = libssh2_channel_read(s->channel, buffer, buf_size)) < LIBSSH2_ERROR_NONE) { ssh_error(s->session, "SSH could not read data");; return -1; } *bytes_read = rc; return 0; }
/* * Execute a local command */ int ssh_local_cmd(const char *args) { char *shell; pid_t pid; int status; void (*osighand)(int); if (!options.permit_local_command || args == NULL || !*args) return (1); if ((shell = getenv("SHELL")) == NULL || *shell == '\0') shell = _PATH_BSHELL; osighand = signal(SIGCHLD, SIG_DFL); pid = fork(); if (pid == 0) { signal(SIGPIPE, SIG_DFL); debug3("Executing %s -c \"%s\"", shell, args); execl(shell, shell, "-c", args, (char *)NULL); ssh_error("Couldn't execute %s -c \"%s\": %s", shell, args, strerror(errno)); _exit(1); } else if (pid == -1) fatal("fork failed: %.100s", strerror(errno)); while (waitpid(pid, &status, 0) == -1) if (errno != EINTR) fatal("Couldn't wait for child: %s", strerror(errno)); signal(SIGCHLD, osighand); if (!WIFEXITED(status)) return (1); return (WEXITSTATUS(status)); }
static int _git_ssh_authenticate_session( LIBSSH2_SESSION* session, git_cred* cred) { int rc; do { giterr_clear(); switch (cred->credtype) { case GIT_CREDTYPE_USERPASS_PLAINTEXT: { git_cred_userpass_plaintext *c = (git_cred_userpass_plaintext *)cred; rc = libssh2_userauth_password(session, c->username, c->password); break; } case GIT_CREDTYPE_SSH_KEY: { git_cred_ssh_key *c = (git_cred_ssh_key *)cred; if (c->privatekey) rc = libssh2_userauth_publickey_fromfile( session, c->username, c->publickey, c->privatekey, c->passphrase); else rc = ssh_agent_auth(session, c); break; } case GIT_CREDTYPE_SSH_CUSTOM: { git_cred_ssh_custom *c = (git_cred_ssh_custom *)cred; rc = libssh2_userauth_publickey( session, c->username, (const unsigned char *)c->publickey, c->publickey_len, c->sign_callback, &c->payload); break; } case GIT_CREDTYPE_SSH_INTERACTIVE: { void **abstract = libssh2_session_abstract(session); git_cred_ssh_interactive *c = (git_cred_ssh_interactive *)cred; /* ideally, we should be able to set this by calling * libssh2_session_init_ex() instead of libssh2_session_init(). * libssh2's API is inconsistent here i.e. libssh2_userauth_publickey() * allows you to pass the `abstract` as part of the call, whereas * libssh2_userauth_keyboard_interactive() does not! * * The only way to set the `abstract` pointer is by calling * libssh2_session_abstract(), which will replace the existing * pointer as is done below. This is safe for now (at time of writing), * but may not be valid in future. */ *abstract = c->payload; rc = libssh2_userauth_keyboard_interactive( session, c->username, c->prompt_callback); break; } #ifdef GIT_SSH_MEMORY_CREDENTIALS case GIT_CREDTYPE_SSH_MEMORY: { git_cred_ssh_key *c = (git_cred_ssh_key *)cred; assert(c->username); assert(c->privatekey); rc = libssh2_userauth_publickey_frommemory( session, c->username, strlen(c->username), c->publickey, c->publickey ? strlen(c->publickey) : 0, c->privatekey, strlen(c->privatekey), c->passphrase); break; } #endif default: rc = LIBSSH2_ERROR_AUTHENTICATION_FAILED; } } while (LIBSSH2_ERROR_EAGAIN == rc || LIBSSH2_ERROR_TIMEOUT == rc); if (rc == LIBSSH2_ERROR_PASSWORD_EXPIRED || rc == LIBSSH2_ERROR_AUTHENTICATION_FAILED) return GIT_EAUTH; if (rc != LIBSSH2_ERROR_NONE) { if (!giterr_last()) ssh_error(session, "Failed to authenticate SSH session"); return -1; } return 0; }
/* ARGSUSED */ static int kex_protocol_error(int type, u_int32_t seq, void *ctxt) { ssh_error("Hm, kex protocol error: type %d seq %u", type, seq); return 0; }
static int _git_ssh_setup_conn( ssh_subtransport *t, const char *url, const char *cmd, git_smart_subtransport_stream **stream) { char *host=NULL, *port=NULL, *path=NULL, *user=NULL, *pass=NULL; const char *default_port="22"; ssh_stream *s; LIBSSH2_SESSION* session=NULL; LIBSSH2_CHANNEL* channel=NULL; *stream = NULL; if (ssh_stream_alloc(t, url, cmd, stream) < 0) return -1; s = (ssh_stream *)*stream; if (!git__prefixcmp(url, prefix_ssh)) { if (gitno_extract_url_parts(&host, &port, &path, &user, &pass, url, default_port) < 0) goto on_error; } else { if (git_ssh_extract_url_parts(&host, &user, url) < 0) goto on_error; port = git__strdup(default_port); GITERR_CHECK_ALLOC(port); } if (gitno_connect(&s->socket, host, port, 0) < 0) goto on_error; if (user && pass) { if (git_cred_userpass_plaintext_new(&t->cred, user, pass) < 0) goto on_error; } else if (t->owner->cred_acquire_cb) { if (t->owner->cred_acquire_cb( &t->cred, t->owner->url, user, GIT_CREDTYPE_USERPASS_PLAINTEXT | GIT_CREDTYPE_SSH_KEY | GIT_CREDTYPE_SSH_CUSTOM, t->owner->cred_acquire_payload) < 0) goto on_error; if (!t->cred) { giterr_set(GITERR_SSH, "Callback failed to initialize SSH credentials"); goto on_error; } } else { giterr_set(GITERR_SSH, "Cannot set up SSH connection without credentials"); goto on_error; } assert(t->cred); if (!user && !git_cred_has_username(t->cred)) { giterr_set_str(GITERR_NET, "Cannot authenticate without a username"); goto on_error; } if (_git_ssh_session_create(&session, s->socket) < 0) goto on_error; if (_git_ssh_authenticate_session(session, user, t->cred) < 0) goto on_error; channel = libssh2_channel_open_session(session); if (!channel) { ssh_error(session, "Failed to open SSH channel"); goto on_error; } libssh2_channel_set_blocking(channel, 1); s->session = session; s->channel = channel; t->current_stream = s; git__free(host); git__free(port); git__free(path); git__free(user); git__free(pass); return 0; on_error: s->session = NULL; s->channel = NULL; t->current_stream = NULL; if (*stream) ssh_stream_free(*stream); git__free(host); git__free(port); git__free(user); git__free(pass); if (session) libssh2_session_free(session); return -1; }
/* returns 0 if key verifies or -1 if key does NOT verify */ int verify_host_key(char *host, struct sockaddr *hostaddr, Key *host_key) { int r = -1, flags = 0; char *fp = NULL; struct sshkey *plain = NULL; if ((fp = sshkey_fingerprint(host_key, options.fingerprint_hash, SSH_FP_DEFAULT)) == NULL) { ssh_error("%s: fingerprint host key: %s", __func__, ssh_err(r)); r = -1; goto out; } debug("Server host key: %s %s", compat20 ? sshkey_ssh_name(host_key) : sshkey_type(host_key), fp); if (sshkey_equal(previous_host_key, host_key)) { debug2("%s: server host key %s %s matches cached key", __func__, sshkey_type(host_key), fp); r = 0; goto out; } /* Check in RevokedHostKeys file if specified */ if (options.revoked_host_keys != NULL) { r = sshkey_check_revoked(host_key, options.revoked_host_keys); switch (r) { case 0: break; /* not revoked */ case SSH_ERR_KEY_REVOKED: ssh_error("Host key %s %s revoked by file %s", sshkey_type(host_key), fp, options.revoked_host_keys); r = -1; goto out; default: ssh_error("Error checking host key %s %s in " "revoked keys file %s: %s", sshkey_type(host_key), fp, options.revoked_host_keys, ssh_err(r)); r = -1; goto out; } } if (options.verify_host_key_dns) { /* * XXX certs are not yet supported for DNS, so downgrade * them and try the plain key. */ if ((r = sshkey_from_private(host_key, &plain)) != 0) goto out; if (sshkey_is_cert(plain)) sshkey_drop_cert(plain); if (verify_host_key_dns(host, hostaddr, plain, &flags) == 0) { if (flags & DNS_VERIFY_FOUND) { if (options.verify_host_key_dns == 1 && flags & DNS_VERIFY_MATCH && flags & DNS_VERIFY_SECURE) { r = 0; goto out; } if (flags & DNS_VERIFY_MATCH) { matching_host_key_dns = 1; } else { warn_changed_key(plain); ssh_error("Update the SSHFP RR in DNS " "with the new host key to get rid " "of this message."); } } } } r = check_host_key(host, hostaddr, options.port, host_key, RDRW, options.user_hostfiles, options.num_user_hostfiles, options.system_hostfiles, options.num_system_hostfiles); out: sshkey_free(plain); free(fp); if (r == 0 && host_key != NULL) { key_free(previous_host_key); previous_host_key = key_from_private(host_key); } return r; }
static int _git_ssh_setup_conn( ssh_subtransport *t, const char *url, const char *cmd, git_smart_subtransport_stream **stream) { char *host=NULL, *port=NULL, *path=NULL, *user=NULL, *pass=NULL; const char *default_port="22"; int auth_methods, error = 0; ssh_stream *s; git_cred *cred = NULL; LIBSSH2_SESSION* session=NULL; LIBSSH2_CHANNEL* channel=NULL; *stream = NULL; if (ssh_stream_alloc(t, url, cmd, stream) < 0) return -1; s = (ssh_stream *)*stream; if (!git__prefixcmp(url, prefix_ssh)) { if ((error = gitno_extract_url_parts(&host, &port, &path, &user, &pass, url, default_port)) < 0) goto on_error; } else { if ((error = git_ssh_extract_url_parts(&host, &user, url)) < 0) goto on_error; port = git__strdup(default_port); GITERR_CHECK_ALLOC(port); } /* we need the username to ask for auth methods */ if (!user) { if ((error = request_creds(&cred, t, NULL, GIT_CREDTYPE_USERNAME)) < 0) goto on_error; user = git__strdup(((git_cred_username *) cred)->username); cred->free(cred); cred = NULL; if (!user) goto on_error; } else if (user && pass) { if ((error = git_cred_userpass_plaintext_new(&cred, user, pass)) < 0) goto on_error; } if ((error = gitno_connect(&s->socket, host, port, 0)) < 0) goto on_error; if ((error = _git_ssh_session_create(&session, s->socket)) < 0) goto on_error; if ((error = list_auth_methods(&auth_methods, session, user)) < 0) goto on_error; error = GIT_EAUTH; /* if we already have something to try */ if (cred && auth_methods & cred->credtype) error = _git_ssh_authenticate_session(session, cred); while (error == GIT_EAUTH) { if (cred) { cred->free(cred); cred = NULL; } if ((error = request_creds(&cred, t, user, auth_methods)) < 0) goto on_error; if (strcmp(user, git_cred__username(cred))) { giterr_set(GITERR_SSH, "username does not match previous request"); error = -1; goto on_error; } error = _git_ssh_authenticate_session(session, cred); } if (error < 0) goto on_error; channel = libssh2_channel_open_session(session); if (!channel) { error = -1; ssh_error(session, "Failed to open SSH channel"); goto on_error; } libssh2_channel_set_blocking(channel, 1); s->session = session; s->channel = channel; t->current_stream = s; if (cred) cred->free(cred); git__free(host); git__free(port); git__free(path); git__free(user); git__free(pass); return 0; on_error: s->session = NULL; s->channel = NULL; t->current_stream = NULL; if (*stream) ssh_stream_free(*stream); if (cred) cred->free(cred); git__free(host); git__free(port); git__free(user); git__free(pass); if (session) libssh2_session_free(session); return error; }
static int _git_ssh_setup_conn( ssh_subtransport *t, const char *url, const char *cmd, git_smart_subtransport_stream **stream) { char *host=NULL, *port=NULL, *path=NULL, *user=NULL, *pass=NULL; const char *default_port="22"; int auth_methods, error = 0; size_t i; ssh_stream *s; git_cred *cred = NULL; LIBSSH2_SESSION* session=NULL; LIBSSH2_CHANNEL* channel=NULL; t->current_stream = NULL; *stream = NULL; if (ssh_stream_alloc(t, url, cmd, stream) < 0) return -1; s = (ssh_stream *)*stream; s->session = NULL; s->channel = NULL; for (i = 0; i < ARRAY_SIZE(ssh_prefixes); ++i) { const char *p = ssh_prefixes[i]; if (!git__prefixcmp(url, p)) { if ((error = gitno_extract_url_parts(&host, &port, &path, &user, &pass, url, default_port)) < 0) goto done; goto post_extract; } } if ((error = git_ssh_extract_url_parts(&host, &user, url)) < 0) goto done; port = git__strdup(default_port); GITERR_CHECK_ALLOC(port); post_extract: if ((error = git_socket_stream_new(&s->io, host, port)) < 0 || (error = git_stream_connect(s->io)) < 0) goto done; if ((error = _git_ssh_session_create(&session, s->io)) < 0) goto done; if (t->owner->certificate_check_cb != NULL) { git_cert_hostkey cert = {{ 0 }}, *cert_ptr; const char *key; cert.parent.cert_type = GIT_CERT_HOSTKEY_LIBSSH2; key = libssh2_hostkey_hash(session, LIBSSH2_HOSTKEY_HASH_SHA1); if (key != NULL) { cert.type |= GIT_CERT_SSH_SHA1; memcpy(&cert.hash_sha1, key, 20); } key = libssh2_hostkey_hash(session, LIBSSH2_HOSTKEY_HASH_MD5); if (key != NULL) { cert.type |= GIT_CERT_SSH_MD5; memcpy(&cert.hash_md5, key, 16); } if (cert.type == 0) { giterr_set(GITERR_SSH, "unable to get the host key"); error = -1; goto done; } /* We don't currently trust any hostkeys */ giterr_clear(); cert_ptr = &cert; error = t->owner->certificate_check_cb((git_cert *) cert_ptr, 0, host, t->owner->message_cb_payload); if (error < 0) { if (!giterr_last()) giterr_set(GITERR_NET, "user cancelled hostkey check"); goto done; } } /* we need the username to ask for auth methods */ if (!user) { if ((error = request_creds(&cred, t, NULL, GIT_CREDTYPE_USERNAME)) < 0) goto done; user = git__strdup(((git_cred_username *) cred)->username); cred->free(cred); cred = NULL; if (!user) goto done; } else if (user && pass) { if ((error = git_cred_userpass_plaintext_new(&cred, user, pass)) < 0) goto done; } if ((error = list_auth_methods(&auth_methods, session, user)) < 0) goto done; error = GIT_EAUTH; /* if we already have something to try */ if (cred && auth_methods & cred->credtype) error = _git_ssh_authenticate_session(session, cred); while (error == GIT_EAUTH) { if (cred) { cred->free(cred); cred = NULL; } if ((error = request_creds(&cred, t, user, auth_methods)) < 0) goto done; if (strcmp(user, git_cred__username(cred))) { giterr_set(GITERR_SSH, "username does not match previous request"); error = -1; goto done; } error = _git_ssh_authenticate_session(session, cred); } if (error < 0) goto done; channel = libssh2_channel_open_session(session); if (!channel) { error = -1; ssh_error(session, "Failed to open SSH channel"); goto done; } libssh2_channel_set_blocking(channel, 1); s->session = session; s->channel = channel; t->current_stream = s; done: if (error < 0) { ssh_stream_free(*stream); if (session) libssh2_session_free(session); } if (cred) cred->free(cred); git__free(host); git__free(port); git__free(path); git__free(user); git__free(pass); return error; }
static int check_host_key(char *hostname, struct sockaddr *hostaddr, u_short port, Key *host_key, int readonly, char **user_hostfiles, u_int num_user_hostfiles, char **system_hostfiles, u_int num_system_hostfiles) { HostStatus host_status; HostStatus ip_status; Key *raw_key = NULL; char *ip = NULL, *host = NULL; char hostline[1000], *hostp, *fp, *ra; char msg[1024]; const char *type; const struct hostkey_entry *host_found, *ip_found; int len, cancelled_forwarding = 0; int local = sockaddr_is_local(hostaddr); int r, want_cert = key_is_cert(host_key), host_ip_differ = 0; int hostkey_trusted = 0; /* Known or explicitly accepted by user */ struct hostkeys *host_hostkeys, *ip_hostkeys; u_int i; /* * Force accepting of the host key for loopback/localhost. The * problem is that if the home directory is NFS-mounted to multiple * machines, localhost will refer to a different machine in each of * them, and the user will get bogus HOST_CHANGED warnings. This * essentially disables host authentication for localhost; however, * this is probably not a real problem. */ if (options.no_host_authentication_for_localhost == 1 && local && options.host_key_alias == NULL) { debug("Forcing accepting of host key for " "loopback/localhost."); return 0; } /* * Prepare the hostname and address strings used for hostkey lookup. * In some cases, these will have a port number appended. */ get_hostfile_hostname_ipaddr(hostname, hostaddr, port, &host, &ip); /* * Turn off check_host_ip if the connection is to localhost, via proxy * command or if we don't have a hostname to compare with */ if (options.check_host_ip && (local || strcmp(hostname, ip) == 0 || options.proxy_command != NULL)) options.check_host_ip = 0; host_hostkeys = init_hostkeys(); for (i = 0; i < num_user_hostfiles; i++) load_hostkeys(host_hostkeys, host, user_hostfiles[i]); for (i = 0; i < num_system_hostfiles; i++) load_hostkeys(host_hostkeys, host, system_hostfiles[i]); ip_hostkeys = NULL; if (!want_cert && options.check_host_ip) { ip_hostkeys = init_hostkeys(); for (i = 0; i < num_user_hostfiles; i++) load_hostkeys(ip_hostkeys, ip, user_hostfiles[i]); for (i = 0; i < num_system_hostfiles; i++) load_hostkeys(ip_hostkeys, ip, system_hostfiles[i]); } retry: /* Reload these as they may have changed on cert->key downgrade */ want_cert = key_is_cert(host_key); type = key_type(host_key); /* * Check if the host key is present in the user's list of known * hosts or in the systemwide list. */ host_status = check_key_in_hostkeys(host_hostkeys, host_key, &host_found); /* * Also perform check for the ip address, skip the check if we are * localhost, looking for a certificate, or the hostname was an ip * address to begin with. */ if (!want_cert && ip_hostkeys != NULL) { ip_status = check_key_in_hostkeys(ip_hostkeys, host_key, &ip_found); if (host_status == HOST_CHANGED && (ip_status != HOST_CHANGED || (ip_found != NULL && !key_equal(ip_found->key, host_found->key)))) host_ip_differ = 1; } else ip_status = host_status; switch (host_status) { case HOST_OK: /* The host is known and the key matches. */ debug("Host '%.200s' is known and matches the %s host %s.", host, type, want_cert ? "certificate" : "key"); debug("Found %s in %s:%lu", want_cert ? "CA key" : "key", host_found->file, host_found->line); if (want_cert && !check_host_cert(hostname, host_key)) goto fail; if (options.check_host_ip && ip_status == HOST_NEW) { if (readonly || want_cert) logit("%s host key for IP address " "'%.128s' not in list of known hosts.", type, ip); else if (!add_host_to_hostfile(user_hostfiles[0], ip, host_key, options.hash_known_hosts)) logit("Failed to add the %s host key for IP " "address '%.128s' to the list of known " "hosts (%.500s).", type, ip, user_hostfiles[0]); else logit("Warning: Permanently added the %s host " "key for IP address '%.128s' to the list " "of known hosts.", type, ip); } else if (options.visual_host_key) { fp = sshkey_fingerprint(host_key, options.fingerprint_hash, SSH_FP_DEFAULT); ra = sshkey_fingerprint(host_key, options.fingerprint_hash, SSH_FP_RANDOMART); if (fp == NULL || ra == NULL) fatal("%s: sshkey_fingerprint fail", __func__); logit("Host key fingerprint is %s\n%s\n", fp, ra); free(ra); free(fp); } hostkey_trusted = 1; break; case HOST_NEW: if (options.host_key_alias == NULL && port != 0 && port != SSH_DEFAULT_PORT) { debug("checking without port identifier"); if (check_host_key(hostname, hostaddr, 0, host_key, ROQUIET, user_hostfiles, num_user_hostfiles, system_hostfiles, num_system_hostfiles) == 0) { debug("found matching key w/out port"); break; } } if (readonly || want_cert) goto fail; /* The host is new. */ if (options.strict_host_key_checking == 1) { /* * User has requested strict host key checking. We * will not add the host key automatically. The only * alternative left is to abort. */ ssh_error("No %s host key is known for %.200s and you " "have requested strict checking.", type, host); goto fail; } else if (options.strict_host_key_checking == 2) { char msg1[1024], msg2[1024]; if (show_other_keys(host_hostkeys, host_key)) snprintf(msg1, sizeof(msg1), "\nbut keys of different type are already" " known for this host."); else snprintf(msg1, sizeof(msg1), "."); /* The default */ fp = sshkey_fingerprint(host_key, options.fingerprint_hash, SSH_FP_DEFAULT); ra = sshkey_fingerprint(host_key, options.fingerprint_hash, SSH_FP_RANDOMART); if (fp == NULL || ra == NULL) fatal("%s: sshkey_fingerprint fail", __func__); msg2[0] = '\0'; if (options.verify_host_key_dns) { if (matching_host_key_dns) snprintf(msg2, sizeof(msg2), "Matching host key fingerprint" " found in DNS.\n"); else snprintf(msg2, sizeof(msg2), "No matching host key fingerprint" " found in DNS.\n"); } snprintf(msg, sizeof(msg), "The authenticity of host '%.200s (%s)' can't be " "established%s\n" "%s key fingerprint is %s.%s%s\n%s" "Are you sure you want to continue connecting " "(yes/no)? ", host, ip, msg1, type, fp, options.visual_host_key ? "\n" : "", options.visual_host_key ? ra : "", msg2); free(ra); free(fp); if (!confirm(msg)) goto fail; hostkey_trusted = 1; /* user explicitly confirmed */ } /* * If not in strict mode, add the key automatically to the * local known_hosts file. */ if (options.check_host_ip && ip_status == HOST_NEW) { snprintf(hostline, sizeof(hostline), "%s,%s", host, ip); hostp = hostline; if (options.hash_known_hosts) { /* Add hash of host and IP separately */ r = add_host_to_hostfile(user_hostfiles[0], host, host_key, options.hash_known_hosts) && add_host_to_hostfile(user_hostfiles[0], ip, host_key, options.hash_known_hosts); } else { /* Add unhashed "host,ip" */ r = add_host_to_hostfile(user_hostfiles[0], hostline, host_key, options.hash_known_hosts); } } else { r = add_host_to_hostfile(user_hostfiles[0], host, host_key, options.hash_known_hosts); hostp = host; } if (!r) logit("Failed to add the host to the list of known " "hosts (%.500s).", user_hostfiles[0]); else logit("Warning: Permanently added '%.200s' (%s) to the " "list of known hosts.", hostp, type); break; case HOST_REVOKED: ssh_error("@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@"); ssh_error("@ WARNING: REVOKED HOST KEY DETECTED! @"); ssh_error("@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@"); ssh_error("The %s host key for %s is marked as revoked.", type, host); ssh_error("This could mean that a stolen key is being used to"); ssh_error("impersonate this host."); /* * If strict host key checking is in use, the user will have * to edit the key manually and we can only abort. */ if (options.strict_host_key_checking) { ssh_error("%s host key for %.200s was revoked and you have " "requested strict checking.", type, host); goto fail; } goto continue_unsafe; case HOST_CHANGED: if (want_cert) { /* * This is only a debug() since it is valid to have * CAs with wildcard DNS matches that don't match * all hosts that one might visit. */ debug("Host certificate authority does not " "match %s in %s:%lu", CA_MARKER, host_found->file, host_found->line); goto fail; } if (readonly == ROQUIET) goto fail; if (options.check_host_ip && host_ip_differ) { char *key_msg; if (ip_status == HOST_NEW) key_msg = "is unknown"; else if (ip_status == HOST_OK) key_msg = "is unchanged"; else key_msg = "has a different value"; ssh_error("@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@"); ssh_error("@ WARNING: POSSIBLE DNS SPOOFING DETECTED! @"); ssh_error("@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@"); ssh_error("The %s host key for %s has changed,", type, host); ssh_error("and the key for the corresponding IP address %s", ip); ssh_error("%s. This could either mean that", key_msg); ssh_error("DNS SPOOFING is happening or the IP address for the host"); ssh_error("and its host key have changed at the same time."); if (ip_status != HOST_NEW) ssh_error("Offending key for IP in %s:%lu", ip_found->file, ip_found->line); } /* The host key has changed. */ warn_changed_key(host_key); ssh_error("Add correct host key in %.100s to get rid of this message.", user_hostfiles[0]); ssh_error("Offending %s key in %s:%lu", key_type(host_found->key), host_found->file, host_found->line); /* * If strict host key checking is in use, the user will have * to edit the key manually and we can only abort. */ if (options.strict_host_key_checking) { ssh_error("%s host key for %.200s has changed and you have " "requested strict checking.", type, host); goto fail; } continue_unsafe: /* * If strict host key checking has not been requested, allow * the connection but without MITM-able authentication or * forwarding. */ if (options.password_authentication) { ssh_error("Password authentication is disabled to avoid " "man-in-the-middle attacks."); options.password_authentication = 0; cancelled_forwarding = 1; } if (options.kbd_interactive_authentication) { ssh_error("Keyboard-interactive authentication is disabled" " to avoid man-in-the-middle attacks."); options.kbd_interactive_authentication = 0; options.challenge_response_authentication = 0; cancelled_forwarding = 1; } if (options.challenge_response_authentication) { ssh_error("Challenge/response authentication is disabled" " to avoid man-in-the-middle attacks."); options.challenge_response_authentication = 0; cancelled_forwarding = 1; } if (options.forward_agent) { ssh_error("Agent forwarding is disabled to avoid " "man-in-the-middle attacks."); options.forward_agent = 0; cancelled_forwarding = 1; } if (options.forward_x11) { ssh_error("X11 forwarding is disabled to avoid " "man-in-the-middle attacks."); options.forward_x11 = 0; cancelled_forwarding = 1; } if (options.num_local_forwards > 0 || options.num_remote_forwards > 0) { ssh_error("Port forwarding is disabled to avoid " "man-in-the-middle attacks."); options.num_local_forwards = options.num_remote_forwards = 0; cancelled_forwarding = 1; } if (options.tun_open != SSH_TUNMODE_NO) { ssh_error("Tunnel forwarding is disabled to avoid " "man-in-the-middle attacks."); options.tun_open = SSH_TUNMODE_NO; cancelled_forwarding = 1; } if (options.exit_on_forward_failure && cancelled_forwarding) fatal("Error: forwarding disabled due to host key " "check failure"); /* * XXX Should permit the user to change to use the new id. * This could be done by converting the host key to an * identifying sentence, tell that the host identifies itself * by that sentence, and ask the user if he/she wishes to * accept the authentication. */ break; case HOST_FOUND: fatal("internal error"); break; } if (options.check_host_ip && host_status != HOST_CHANGED && ip_status == HOST_CHANGED) { snprintf(msg, sizeof(msg), "Warning: the %s host key for '%.200s' " "differs from the key for the IP address '%.128s'" "\nOffending key for IP in %s:%lu", type, host, ip, ip_found->file, ip_found->line); if (host_status == HOST_OK) { len = strlen(msg); snprintf(msg + len, sizeof(msg) - len, "\nMatching host key in %s:%lu", host_found->file, host_found->line); } if (options.strict_host_key_checking == 1) { logit("%s", msg); ssh_error("Exiting, you have requested strict checking."); goto fail; } else if (options.strict_host_key_checking == 2) { strlcat(msg, "\nAre you sure you want " "to continue connecting (yes/no)? ", sizeof(msg)); if (!confirm(msg)) goto fail; } else { logit("%s", msg); } } if (!hostkey_trusted && options.update_hostkeys) { debug("%s: hostkey not known or explicitly trusted: " "disabling UpdateHostkeys", __func__); options.update_hostkeys = 0; } free(ip); free(host); if (host_hostkeys != NULL) free_hostkeys(host_hostkeys); if (ip_hostkeys != NULL) free_hostkeys(ip_hostkeys); return 0; fail: if (want_cert && host_status != HOST_REVOKED) { /* * No matching certificate. Downgrade cert to raw key and * search normally. */ debug("No matching CA found. Retry with plain key"); raw_key = key_from_private(host_key); if (key_drop_cert(raw_key) != 0) fatal("Couldn't drop certificate"); host_key = raw_key; goto retry; } if (raw_key != NULL) key_free(raw_key); free(ip); free(host); if (host_hostkeys != NULL) free_hostkeys(host_hostkeys); if (ip_hostkeys != NULL) free_hostkeys(ip_hostkeys); return -1; }
/* * Opens a TCP/IP connection to the remote server on the given host. * The address of the remote host will be returned in hostaddr. * If port is 0, the default port will be used. If needpriv is true, * a privileged port will be allocated to make the connection. * This requires super-user privileges if needpriv is true. * Connection_attempts specifies the maximum number of tries (one per * second). If proxy_command is non-NULL, it specifies the command (with %h * and %p substituted for host and port, respectively) to use to contact * the daemon. */ static int ssh_connect_direct(const char *host, struct addrinfo *aitop, struct sockaddr_storage *hostaddr, u_short port, int family, int connection_attempts, int *timeout_ms, int want_keepalive, int needpriv) { int on = 1; int sock = -1, attempt; char ntop[NI_MAXHOST], strport[NI_MAXSERV]; struct addrinfo *ai; debug2("ssh_connect: needpriv %d", needpriv); for (attempt = 0; attempt < connection_attempts; attempt++) { if (attempt > 0) { /* Sleep a moment before retrying. */ sleep(1); debug("Trying again..."); } /* * Loop through addresses for this host, and try each one in * sequence until the connection succeeds. */ for (ai = aitop; ai; ai = ai->ai_next) { if (ai->ai_family != AF_INET && ai->ai_family != AF_INET6) continue; if (getnameinfo(ai->ai_addr, ai->ai_addrlen, ntop, sizeof(ntop), strport, sizeof(strport), NI_NUMERICHOST|NI_NUMERICSERV) != 0) { ssh_error("ssh_connect: getnameinfo failed"); continue; } debug("Connecting to %.200s [%.100s] port %s.", host, ntop, strport); /* Create a socket for connecting. */ sock = ssh_create_socket(needpriv, ai); if (sock < 0) /* Any error is already output */ continue; if (timeout_connect(sock, ai->ai_addr, ai->ai_addrlen, timeout_ms) >= 0) { /* Successful connection. */ memcpy(hostaddr, ai->ai_addr, ai->ai_addrlen); break; } else { debug("connect to address %s port %s: %s", ntop, strport, strerror(errno)); close(sock); sock = -1; } } if (sock != -1) break; /* Successful connection. */ } /* Return failure if we didn't get a successful connection. */ if (sock == -1) { ssh_error("ssh: connect to host %s port %s: %s", host, strport, strerror(errno)); return (-1); } debug("Connection established."); /* Set SO_KEEPALIVE if requested. */ if (want_keepalive && setsockopt(sock, SOL_SOCKET, SO_KEEPALIVE, (void *)&on, sizeof(on)) < 0) ssh_error("setsockopt SO_KEEPALIVE: %.100s", strerror(errno)); /* Set the connection. */ packet_set_connection(sock, sock); return 0; }