/* $Id: ssh.c,v 1.56 1999/08/10 13:13:09 levitte Exp $ */ #include "gnu_extras.h" #if defined(__DECC) || defined(__GNUC__) #include #endif #include #include #ifdef __DECC #include #endif #include #include #include #include #include #include #include #include #include #ifdef __GNUC__ #define __DECC #endif #include #include #include #include #include #include #ifdef __GNUC__ #undef __DECC #endif #include "fish.h" #include "ssh.h" #include "callback.h" #include "vms.h" #include "log.h" #include "util.h" #include "compress.h" #include "fishmsg.h" #include "user_info.h" #include "buffer.h" #include "hostfile.h" typedef struct { unsigned char cookie[8]; RSA *servkey; RSA *hostkey; unsigned long flags; unsigned long cipher_mask; unsigned long auth_mask; } ServKey; void *ssh_init(long (*down_write)(unsigned char *,long,void *,Erf,void*), void *down_state, Erf erf, void *erfp) { ssh_state *state; state = xmalloc(sizeof(ssh_state)); if (!state) { ssh_F_memfull(); return NULL; } state->down_write = down_write; state->down_state = down_state; state->up_dispatch = NULL; state->up_setconnid = NULL; state->up_state = NULL; state->dispatchread = 0; state->dispatchpkt = NULL; state->dispatchsizeread = 0; state->sshprefs = NULL; state->protophase = SSH_PHASE_VERSION_WAIT; state->encrypt = NULL; state->decrypt = NULL; state->cryptclean = NULL; state->term = NULL; state->termrows = state->termcols = state->termx = state->termy = 0; state->connid = NULL; state->userkey.bits = 0; state->info = 0; state->compress = 0; ERR_load_crypto_strings(); return state; } void ssh_initprefs(ssh_prefs *p) { char *cp; char default_directory[256]; p->host = NULL; p->port = 22; p->proxy_host = NULL; p->proxy_port = 80; get_user_info(&p->uai, 0); strcpy(default_directory, p->uai.device); strcat(default_directory, p->uai.directory); default_directory[strlen(default_directory)-1] = '\0'; cp = strchr(default_directory, ':') + 1; if (*cp == '[') strcat(default_directory, ".SSH]"); else strcat(default_directory, ".SSH>"); p->default_directory = xstrndup(default_directory, strlen(default_directory)); /* We need that directory anyhow... */ if (mkdir(p->default_directory,0) == 0) lib$signal(FISH_M_CREATED, 1, p->default_directory); else switch (errno) { case EEXIST: /* This is perfectly OK, we tried to create a directory that is already there... */ break; default: lib$signal(FISH_M_CREATEFAIL, 1, p->default_directory, vaxc$errno); break; } p->username = xstrdup(getenv("USER")); for (cp = p->username; *cp; cp++) *cp = tolower(*cp); p->password = 0; p->ciphers = 0; /* The user will specify later */ p->ttymodes = NULL; p->ttymodelen = 0; p->commands = 0; p->infile = 0; p->inmode = 0; p->outfile = 0; p->outmode = 0; p->force_tty = 0; p->bits = 1024; p->cipher_type = SSH_CIPHER_3DES; } void ssh_set_prefs(ssh_prefs *prefs, void *state_, Erf erf, void *erfp) { ssh_state *state = state_; state->sshprefs = prefs; state->infd = 0; if (prefs->infile) { if (prefs->inmode) state->infd = open(prefs->infile,O_RDONLY,0, "rfm=fix","mrs=512","rat=none"); else state->infd = open(prefs->infile,O_RDONLY,0); if (state->infd < 0) { lib$signal(FISH_M_OPENIN, 1, prefs->infile, vaxc$errno); exit(0); } } if (prefs->outfile) { if (prefs->outmode) state->outfd = open(prefs->outfile,O_CREAT|O_TRUNC|O_WRONLY,0, "rfm=fix","mrs=512","rat=none"); else state->outfd = open(prefs->outfile,O_CREAT|O_TRUNC|O_WRONLY,0); if (state->outfd < 0) { lib$signal(FISH_M_OPENOUT, 1, prefs->outfile, vaxc$errno); exit(0); } } if (prefs->proxy_host) { state->protophase = SSH_WAIT_HTTP_PROXY_STATUS; } } void ssh_clearprefs(ssh_prefs *p) { if (p->host) xfree(p->host); p->port = 0; if (p->username) xfree(p->username); if (p->default_directory) xfree(p->default_directory); if (p->ttymodes) xfree(p->ttymodes); memset(p, 0, sizeof(ssh_prefs)); } static int put_int(void *buf, unsigned long l); /* Send a SSH_CMSG_WINDOW_SIZE describing the current terminal */ static void send_term(ssh_state *state, Erf erf, void *erfp) { long ret; char _buf[16]; general_buffer buf; buf_init_static(&buf, _buf, sizeof _buf); buf_append_int(&buf, state->termrows); buf_append_int(&buf, state->termcols); buf_append_int(&buf, state->termx); buf_append_int(&buf, state->termy); ret = ssh_write_type(SSH_CMSG_WINDOW_SIZE, &buf, state, erf, erfp); if (ret < 16) { ssh_F_noconn(); } } void ssh_setterm(char *term, int rows, int cols, int x, int y, void *state_, Erf erf, void *erfp) { ssh_state *state = state_; CALLBACK_PROLOGUE if (state->term) { xfree(state->term); state->term = NULL; } if (term) { state->term = xmalloc(strlen(term)+1); if (!state->term) { ssh_F_memfull(); goto setterm_out; } strcpy(state->term, term); } state->termrows = rows; state->termcols = cols; state->termx = x; state->termy = y; /* Send the new terminal if it's appropriate to do so */ if (state->protophase == SSH_PHASE_INTERACTIVE) { send_term(state,erf,erfp); } setterm_out: ; CALLBACK_EPILOGUE } /* This is called from below, giving a textual description of the connection */ void ssh_setconnid(char *connid, void *state_, Erf erf, void *erfp) { ssh_state *state = state_; CALLBACK_PROLOGUE if (state->connid) { xfree(state->connid); state->connid = NULL; } if (connid) { state->connid = xmalloc(strlen(connid)+1+5); if (!state->connid) { ssh_F_memfull(); goto connid_out; } strcpy(state->connid, "SSH: "); strcpy(state->connid+5, connid); } if (state->up_setconnid) { (state->up_setconnid)(state->connid, state->up_state,erf,erfp); } connid_out: ; CALLBACK_EPILOGUE } void ssh_set_dispatch( void (*up_dispatch)(unsigned char *,long,void *,Erf,void*), void *up_state, void *state_, Erf erf, void *erfp) { ssh_state *state = state_; state->up_dispatch = up_dispatch; state->up_state = up_state; } void ssh_set_setconnid(void (*up_setconnid)(char *, void *, Erf, void *), void *state_, Erf erf, void *erfp) { ssh_state *state = state_; state->up_setconnid = up_setconnid; if (state->up_setconnid) { (state->up_setconnid)(state->connid, state->up_state,erf,erfp); } } static int get_servkey(general_buffer_reader *br, ServKey *sk) { unsigned long servbits, hostbits; long bdiff; sk->servkey = RSA_new(); sk->hostkey = RSA_new(); if (!bufread_bytes(br, sk->cookie, 8) || !bufread_int(br, &servbits) || !bufread_bn(br, &sk->servkey->e) || !bufread_bn(br, &sk->servkey->n) || !bufread_int(br, &hostbits) || !bufread_bn(br, &sk->hostkey->e) || !bufread_bn(br, &sk->hostkey->n) || !bufread_int(br, &sk->flags) || !bufread_int(br, &sk->cipher_mask) || !bufread_int(br, &sk->auth_mask)) goto servkey_err; servbits = BN_num_bits(sk->servkey->n); hostbits = BN_num_bits(sk->hostkey->n); bdiff = (long)servbits - (long)hostbits; if (bdiff < 0) bdiff = -bdiff; if (bdiff < 104) goto servkey_err; if (servbits < 512 || hostbits < 512) goto servkey_err; return SS$_NORMAL; servkey_err: if (sk->servkey) RSA_free(sk->servkey); sk->servkey = 0; if (sk->hostkey) RSA_free(sk->hostkey); sk->hostkey = 0; return FISH_M_BADPUBK; } static int gen_sesskey(ServKey *sk, unsigned char *sesskey, BIGNUM **encs, ssh_state *state) { unsigned char *sidbuf = NULL; int servlen, hostlen, longlen; int i; /* Generate the session id. The RFC says to do: MD5(servkey->n || hostkey->n || cookie) but the sshd distribution actually does: MD5(hostkey->n || servkey->n || cookie) We do it the "wrong" way (the way that's implemented). */ sidbuf = xmalloc(BN_num_bytes(sk->servkey->n) + BN_num_bytes(sk->hostkey->n) + 8); if (!sidbuf) goto gen_sesskey_err; hostlen = BN_bn2bin(sk->hostkey->n, sidbuf); servlen = BN_bn2bin(sk->servkey->n, sidbuf+hostlen); memmove(sidbuf+servlen+hostlen, sk->cookie, 8); RAND_seed(sidbuf, servlen+hostlen+8); MD5(sidbuf, servlen+hostlen+8, state->sessid); /* Generate session key: 32 random bytes */ RAND_bytes(sesskey, 32); /* Encrypt (sesskey XOR state->sessid) with the shorter, then the longer, of the servkey and hostkey. */ longlen = (hostlen > servlen) ? hostlen : servlen; memmove(sidbuf+longlen-32, sesskey, 32); for(i=0;i<16;++i) sidbuf[longlen-32+i] ^= state->sessid[i]; if (hostlen > servlen) { if (RSA_public_encrypt(32, sidbuf+hostlen-32, sidbuf+hostlen-servlen, sk->servkey, RSA_PKCS1_PADDING) < 0) goto gen_sesskey_rsaerr; if (RSA_public_encrypt(servlen, sidbuf+hostlen-servlen, sidbuf, sk->hostkey, RSA_PKCS1_PADDING) < 0) goto gen_sesskey_rsaerr; } else { if (RSA_public_encrypt(32, sidbuf+servlen-32, sidbuf+servlen-hostlen, sk->hostkey, RSA_PKCS1_PADDING) < 0) goto gen_sesskey_rsaerr; if (RSA_public_encrypt(hostlen, sidbuf+servlen-hostlen, sidbuf, sk->servkey, RSA_PKCS1_PADDING) < 0) goto gen_sesskey_rsaerr; } *encs = BN_bin2bn(sidbuf, longlen, NULL); if (!*encs) goto gen_sesskey_err; xfree(sidbuf); sidbuf = NULL; return SS$_NORMAL; gen_sesskey_rsaerr: { BIO *bio_err = 0; if (bio_err == 0) if ((bio_err=BIO_new(BIO_s_file())) != 0) BIO_set_fp(bio_err,stderr,BIO_NOCLOSE|BIO_FP_TEXT); ERR_print_errors(bio_err); } gen_sesskey_err: if (sidbuf) xfree(sidbuf); sidbuf = NULL; return FISH_M_NOSESSK; } /* This is the "state machine" routine that handles incoming packets. */ int ssh_handle_packet_type(ssh_msg_type type, general_buffer_reader *inbuf, ssh_state *state, Erf erf, void *erfp) { /* General output buffer */ general_buffer *outbuf; long ret; unsigned long len; unsigned long prefciphers; ssh_cipher_type use_cipher = SSH_CIPHER_3DES; char *trypw; unsigned long trypwlen; unsigned char *prefttymodes; int prefttymodelen; int termlen; unsigned long datalen; on_brief(log_verbose(type, inbuf, state)); /* Handle things that may be sent at any time */ switch(type) { case SSH_MSG_DISCONNECT: /* Dump the connection. */ if (bufread_int(inbuf, &len)) { char *s = (char *)bufread_chars_noadjust(inbuf); /* NUL-terminate the string (the memory is allocated because that's where the CRC used to live) */ s[bufread_remaining(inbuf)] = '\0'; if (len != 0) { lib$signal(FISH_M_CLOSED2 & ((1<<28)-1),1,s); } else lib$signal(FISH_M_CLOSED & ((1<<28)-1)); return FISH_M_CLOSED | 0x10000000; } return SS$_NORMAL; case SSH_MSG_IGNORE: return SS$_NORMAL; case SSH_MSG_DEBUG: if (state->sshprefs->debug) { char *buf; if (bufread_strdup(inbuf, &buf)) { ssh_debug(buf); xfree(buf); } } return SS$_NORMAL; default: break; } /* Where are we in the protocol? */ switch(state->protophase) { /* SSH_WAIT_HTTP_PROXY_STATUS Start -------------------------------- */ case SSH_WAIT_HTTP_PROXY_STATUS: /* We are expecting the server's version number */ switch(type) { case SSH_INTERNAL: { char *tmp_ptr, *tmp_ptr2 = NULL; /* See what we got */ if (bufread_remaining(inbuf) < 12 || bufread_remaining(inbuf) == 80 || strncmp((char *)bufread_chars_noadjust(inbuf), "HTTP/1.", 7)) { /* Nope */ state->protophase = SSH_PHASE_NONE; ssh_F_invproxy(); return FISH_M_INVPROXY | 0x10000000; } tmp_ptr = strchr((char *)bufread_chars_noadjust(inbuf),' '); if (tmp_ptr) { tmp_ptr2 = strchr(++tmp_ptr, ' '); if (tmp_ptr2 == NULL) tmp_ptr2 = tmp_ptr + strlen(tmp_ptr); if (strncmp(tmp_ptr, "200", tmp_ptr2 - tmp_ptr)) { /* Nope */ state->protophase = SSH_PHASE_NONE; ssh_F_noproxy((char *)bufread_chars_noadjust(inbuf)); return FISH_M_NOPROXY | 0x10000000; } } else { /* Nope */ state->protophase = SSH_PHASE_NONE; ssh_F_invproxyresp((char *)bufread_chars_noadjust(inbuf)); return FISH_M_INVPROXYRESP | 0x10000000; } /* At this point, it actually looks like the proxy will let us through, so let's boogie. */ state->protophase = SSH_WAIT_HTTP_PROXY_BLANK_LINE; break; } default: /* Unknown packets? */ ssh_F_invpckt(); return FISH_M_INVPCKT | 0x10000000; } /* SSH_WAIT_HTTP_PROXY_STATUS End ---------------------------------- */ /* SSH_WAIT_HTTP_PROXY_BLANK_LINE Start ---------------------------- */ case SSH_WAIT_HTTP_PROXY_BLANK_LINE: /* We are expecting the server's version number */ switch(type) { case SSH_INTERNAL: if (strncmp((char *)bufread_chars_noadjust(inbuf), "\r\n", 2) == 0 || strncmp((char *)bufread_chars_noadjust(inbuf), "\n", 1) == 0) /* We got it! */ state->protophase = SSH_PHASE_VERSION_WAIT; break; default: /* Unknown packets? */ ssh_F_invpckt(); return FISH_M_INVPCKT | 0x10000000; } break; /* SSH_WAIT_HTTP_PROXY_BLANK_LINE End ------------------------------ */ /* SSH_PHASE_VERSION_WAIT Start ------------------------------------ */ case SSH_PHASE_VERSION_WAIT: /* We are expecting the server's version number */ switch(type) { case SSH_INTERNAL: /* See what we got */ if (bufread_remaining(inbuf) < 9 || bufread_remaining(inbuf) == 80 || strncmp((char *)bufread_chars_noadjust(inbuf), "SSH-1.", 6)) { /* Nope */ state->protophase = SSH_PHASE_NONE; ssh_F_invprot(); return FISH_M_INVPROT | 0x10000000; } on_info(ssh_infof("Remote version: %.*s", bufread_remaining(inbuf), bufread_chars_noadjust(inbuf))); /* Looks good here; advance the phase */ state->protophase = SSH_PHASE_GET_KEYS; { char verbuf[1024]; general_buffer tmpbuf; unsigned int l; sprintf(verbuf,PILOTSSH_VERSTR_FMT,FISH_VERSION); l = strlen(verbuf); buf_init_static(&tmpbuf, verbuf, l+1); buf_adjust_amount_by(&tmpbuf, l); ret = ssh_write_type(SSH_INTERNAL, &tmpbuf, state, erf, erfp); if (ret < buf_amount(&tmpbuf)) { ssh_F_noconn(); return FISH_M_NOCONN | 0x10000000; } } on_info(ssh_info("Exchanging Keys")); break; default: /* Unknown packets? */ ssh_F_invpckt(); return FISH_M_INVPCKT | 0x10000000; } break; /* SSH_PHASE_VERSION_WAIT End -------------------------------------- */ /* SSH_PHASE_GET_KEYS Start ---------------------------------------- */ case SSH_PHASE_GET_KEYS: /* We are expecting the server's keys */ switch(type) { case SSH_SMSG_PUBLIC_KEY: { ServKey servkey; BIGNUM *enc_sesskey = NULL; int enc_sesskey_bytes; int status; char tmpbuf[256]; /* Get the keys */ if (!(status = get_servkey(inbuf, &servkey))) { lib$signal(status); return status | 0x10000000; } if (bufread_remaining(inbuf)) { RSA_free(servkey.servkey); RSA_free(servkey.hostkey); ssh_F_badpubk(); return FISH_M_BADPUBK | 0x10000000; } /* Create the session key */ if (!(status = gen_sesskey(&servkey, state->sesskey, &enc_sesskey, state))) { RSA_free(servkey.servkey); RSA_free(servkey.hostkey); lib$signal(status); return status | 0x10000000; } sprintf(tmpbuf, "%s%s", state->sshprefs->default_directory, "KNOWN_HOSTS.DAT"); status = find_host(state->sshprefs->host, servkey.hostkey, tmpbuf); if (status == FISH_M_HOSTOK) { on_state(ssh_infof("Host %s is known and matches the host key", state->sshprefs->host)); } else if (status == FISH_M_HOSTNEW) { char answer[2]; lib$signal(FISH_M_HOSTNEW, 1, state->sshprefs->host); if (!read_prompted("SYS$INPUT:", "Are you sure you want to continue? [y/N] ", answer, 2, 0, 1, &status)) { lib_put_output(""); lib$signal(status); } if (*answer != 'Y' && *answer != 'y') ssh_F_stopped("user abort"); status = add_host(state->sshprefs->host, servkey.hostkey, tmpbuf); if (!$VMS_STATUS_SUCCESS(status)) lib$signal(status); } else if (status == FISH_M_HOSTCHANGED) { char answer[2]; fprintf(stderr, "@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@\n" "@ WARNING: HOST IDENTIFICATION HAS CHANGED! @\n" "@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@\n" "IT IS POSSIBLE THAT SOMEONE IS DOING SOMETHING NASTY!\n" "Someone could be eavesdropping on you right now (man-in-the-middle attack)!\n" "It is also possible that the host key has just been changed.\n" "Please contact the system administrator for %s.\n" "Add correct host key in %s to get rid of this message.\n", state->sshprefs->host, tmpbuf); if (!read_prompted("SYS$INPUT:", "Are you sure you want to continue? [y/N] ", answer, 2, 0, 1, &status)) { lib_put_output(""); lib$signal(status); } if (*answer != 'Y' && *answer != 'y') ssh_F_stopped("user abort"); state->sshprefs->auths &= ~(SSH_AUTH_NONE | SSH_AUTH_RHOSTS | SSH_AUTH_PASSWORD); lib$signal(FISH_M_DISAUTHS, 1, "NONE, rhosts and password"); } RSA_free(servkey.servkey); RSA_free(servkey.hostkey); /* If no cipher is declared, use none... */ prefciphers = state->sshprefs ? state->sshprefs->ciphers : 0; /* 3DES is mandatory to support */ prefciphers |= (1 << SSH_CIPHER_3DES); #if 0 /* We might need it again */ ssh_debugf("My prefered ciphermask: 0x%X", prefciphers); ssh_debugf("Server's prefered ciphermask: 0x%X", servkey.cipher_mask); #endif servkey.cipher_mask &= prefciphers; #if 0 /* See above */ ssh_debugf("Resulting ciphermask: 0x%X", servkey.cipher_mask); #endif /* XXX: This code is getting bulky. Available ciphers should be handled in a more elegant fashion */ if ((servkey.cipher_mask & (1 << SSH_CIPHER_BLOWFISH))) { use_cipher = SSH_CIPHER_BLOWFISH; } else if ((servkey.cipher_mask & (1 << SSH_CIPHER_IDEA))) { use_cipher = SSH_CIPHER_IDEA; } else if ((servkey.cipher_mask & (1 << SSH_CIPHER_3DES))) { use_cipher = SSH_CIPHER_3DES; } else if ((servkey.cipher_mask & (1 << SSH_CIPHER_RC4))) { use_cipher = SSH_CIPHER_RC4; } else if ((servkey.cipher_mask & (1 << SSH_CIPHER_DES))) { use_cipher = SSH_CIPHER_DES; } else if ((servkey.cipher_mask & (1 << SSH_CIPHER_NONE))) { use_cipher = SSH_CIPHER_NONE; } else { BN_free(enc_sesskey); ssh_F_nocompc(); return FISH_M_NOCOMPC | 0x10000000; } state->used_cipher = use_cipher; state->auth_mask = servkey.auth_mask & state->sshprefs->auths; /* Send the SSH_CMSG_SESSION_KEY */ enc_sesskey_bytes = BN_num_bytes(enc_sesskey); outbuf = buf_init(15+enc_sesskey_bytes); if (!outbuf) { BN_free(enc_sesskey); ssh_F_memfull(); return FISH_M_MEMFULL | 0x10000000; } buf_append_bytes(outbuf, &use_cipher, 1); buf_append_bytes(outbuf, servkey.cookie, 8); buf_append_bn(outbuf, enc_sesskey); buf_append_bytes(outbuf, "\0\0\0\0", 4); BN_free(enc_sesskey); ret = ssh_write_type(SSH_CMSG_SESSION_KEY, outbuf, state, erf, erfp); if (ret < buf_amount(outbuf)) { buf_destroy(outbuf); outbuf = NULL; ssh_F_noconn(); return FISH_M_NOCONN | 0x10000000; } buf_destroy(outbuf); outbuf = NULL; /* Set up the crypto */ ssh_crypto_setup(use_cipher, state, 32, erf, erfp); break; } case SSH_SMSG_FAILURE: /* Hmmm. Something went wrong. */ ssh_F_nokeyex(); return FISH_M_NOKEYEX | 0x10000000; case SSH_SMSG_SUCCESS: /* OK; go on, as long as we're encrypting now */ if (1 || state->encrypt) { char *prefuser; unsigned long prefuserlen; /* XXX: The day Kerberos starts getting used, there will be alternate code to set prefuser. */ /* Send the USER message */ if (!state->sshprefs || !state->sshprefs->username || !*(state->sshprefs->username)) { /* No username was supplied */ ssh_F_nouser(); return FISH_M_NOUSER | 0x10000000; } else { prefuser = state->sshprefs->username; } prefuserlen = strlen(prefuser); outbuf = buf_init(prefuserlen+4); buf_append_int(outbuf, prefuserlen); buf_append_bytes(outbuf, prefuser, prefuserlen); ret = ssh_write_type(SSH_CMSG_USER, outbuf, state, erf, erfp); if (ret < buf_amount(outbuf)) { buf_destroy(outbuf); outbuf = NULL; ssh_F_noconn(); return FISH_M_NOCONN | 0x10000000; } buf_destroy(outbuf); outbuf = NULL; state->protophase = SSH_PHASE_AUTH; state->authphase = SSH_PASS_KERBEROS_TGT; state->triedpw = 0; break; } /* FALLTHROUGH */ default: /* Unknown packets? */ ssh_F_invpckt(); return FISH_M_INVPCKT | 0x10000000; } break; /* SSH_PHASE_GET_KEYS End ------------------------------------------ */ /* SSH_PHASE_AUTH Start -------------------------------------------- */ case SSH_PHASE_AUTH: /* We are expecting SUCCESS or FAILURE to our login attempts */ switch(type) { case SSH_SMSG_FAILURE: switch(state->authphase) { case SSH_PASS_KERBEROS_TGT: /* Not supported, so just trickle through */ state->authphase = SSH_PASS_AFS_TOKENS; case SSH_PASS_AFS_TOKENS: /* Not supported, so just trickle through */ state->authphase = SSH_AUTH_KERBEROS; case SSH_AUTH_KERBEROS: /* Not supported, so just trickle through */ state->authphase = SSH_AUTH_RHOSTS; case SSH_AUTH_RHOSTS: /* Not supported, so just trickle through */ state->authphase = SSH_AUTH_RHOSTS_RSA; if (state->auth_mask & (1 << SSH_AUTH_RHOSTS)) { char *local_user = getenv("USER"); int l = strlen(local_user); outbuf = buf_init(l+4); buf_append_int(outbuf, l); buf_append_bytes(outbuf, local_user, l); ret = ssh_write_type(SSH_CMSG_AUTH_RHOSTS, outbuf, state, erf, erfp); if (ret < buf_amount(outbuf)) { buf_destroy(outbuf); outbuf = NULL; ssh_F_noconn(); return FISH_M_NOCONN | 0x10000000; } buf_destroy(outbuf); outbuf = NULL; break; } case SSH_AUTH_RHOSTS_RSA: /* Not supported, so just trickle through */ state->authphase = SSH_AUTH_RSA; case SSH_AUTH_RSA: /* Default to go to next method */ state->authphase = SSH_AUTH_TIS; if (!(state->auth_mask & (1 << SSH_AUTH_RSA))) { on_info(ssh_info("RSA authentication disabled")); } else if (state->userkey.bits == 0) { int authlen; int status; on_info(ssh_info("Trying to authenticate with RSA")); userkey_destroy_static(&state->userkey); userkey_init_static(&state->userkey); status = userkey_read_keyfile(&state->userkey, state->sshprefs->identity_file, state->sshprefs->uai.uic); if (!$VMS_STATUS_SUCCESS(status)) goto no_rsa; state->authphase = SSH_AUTH_RSA; authlen = BN_num_bytes(state->userkey.key->n); outbuf = buf_init(authlen + 2); /* +2 suggested by wjm */ buf_append_bn(outbuf, state->userkey.key->n); ret = ssh_write_type(SSH_CMSG_AUTH_RSA, outbuf, state, erf, erfp); if (ret < buf_amount(outbuf)) { buf_destroy(outbuf); outbuf = NULL; userkey_destroy_static(&state->userkey); ssh_F_noconn(); return FISH_M_NOCONN | 0x10000000; } buf_destroy(outbuf); outbuf = NULL; break; no_rsa: ; } case SSH_AUTH_TIS: state->authphase = SSH_AUTH_PASSWORD; if (state->auth_mask & (1 << SSH_AUTH_TIS)) { outbuf = buf_init(4); buf_append_int(outbuf, 0); ret = ssh_write_type(SSH_CMSG_AUTH_TIS, outbuf, state, erf, erfp); if (ret < buf_amount(outbuf)) { buf_destroy(outbuf); outbuf = NULL; ssh_F_noconn(); return FISH_M_NOCONN | 0x10000000; } buf_destroy(outbuf); outbuf = NULL; break; } case SSH_AUTH_PASSWORD: if (state->auth_mask & (1 << SSH_AUTH_PASSWORD)) { char pwbuf[80] = ""; int status; /* Have we already tried the password? */ if (state->triedpw & TRIED_PASSWD) if (state->sshprefs->commands && state->sshprefs->password) { ssh_F_pssfail(); return FISH_M_PSSFAIL | 0x10000000; } else ssh_W_pssfail1(); else { if (state->sshprefs->password) { if (strlen(state->sshprefs->password) >= sizeof(pwbuf)) { ssh_F_pstoolng(); return FISH_M_PSTOOLNG | 0x10000000; } strcpy(pwbuf, state->sshprefs->password); } on_info(ssh_infof("Logging in as user %s\n", state->sshprefs->username)); } if (pwbuf[0] == '\0') { general_buffer *prompt = buf_init(0); buf_append_chars_nocount(prompt, state->sshprefs->username); buf_append_chars_nocount(prompt, "@"); buf_append_chars_nocount(prompt, state->sshprefs->host); buf_append_chars_nocount(prompt, "'s password: "); if (!read_prompted(input_device, buf_chars(prompt), pwbuf, sizeof(pwbuf), 0, 0, &status)) { buf_destroy(prompt); lib$signal(status); } buf_destroy(prompt); } trypw = pwbuf; trypwlen = strlen(trypw); outbuf = buf_init(trypwlen+4); buf_append_int(outbuf, trypwlen); buf_append_bytes(outbuf, trypw, trypwlen); ret = ssh_write_type(SSH_CMSG_AUTH_PASSWORD, outbuf, state, erf, erfp); state->triedpw |= TRIED_PASSWD; if (ret < buf_amount(outbuf)) { buf_destroy(outbuf); outbuf = NULL; ssh_F_noconn(); return FISH_M_NOCONN | 0x10000000; } buf_destroy(outbuf); outbuf = NULL; break; } default: /* All authentication methods have failed */ ssh_F_perm(); } break; case SSH_SMSG_SUCCESS: /* Oh, good; they let us in. Set a max packet size. */ outbuf = buf_init(4); buf_append_int(outbuf, MAX_SSH_PACKET_LEN); ret = ssh_write_type(SSH_CMSG_MAX_PACKET_SIZE, outbuf, state, erf, erfp); if (ret < buf_amount(outbuf)) { buf_destroy(outbuf); outbuf = NULL; ssh_F_noconn(); return FISH_M_NOCONN | 0x10000000; } buf_destroy(outbuf); outbuf = NULL; state->protophase = SSH_PHASE_PREP; state->prepphase = SSH_PREP_COMPRESSION; break; case SSH_SMSG_AUTH_RSA_CHALLENGE: { char pwbuf[2048]; general_buffer gpwbuf; BIGNUM *tmp; general_buffer *tmpbuf = 0; unsigned char *challenge; unsigned long challengelen; MD5_CTX md; unsigned char responsebuf[16]; general_buffer response; int status; buf_init_static(&gpwbuf, pwbuf, sizeof pwbuf); buf_init_static(&response, responsebuf, sizeof responsebuf); bufread_bn(inbuf, &tmp); challengelen = BN_num_bytes(tmp); challenge = xmalloc(challengelen); if (challenge == 0) { ssh_F_memfull(); return FISH_M_MEMFULL | 0x10000000; } BN_bn2bin(tmp, challenge); BN_clear_free(tmp); trypw = 0; pwbuf[0] = '\0'; /* Have we already tried the password? */ if (state->triedpw & TRIED_RSA) if (state->sshprefs->commands && state->sshprefs->password) { ssh_F_rsafail(); return FISH_M_RSAFAIL | 0x10000000; } else ssh_W_rsafail1(); else { if (state->sshprefs->password) { if (strlen(state->sshprefs->password) >= sizeof(pwbuf)) { ssh_F_ptoolong(); return FISH_M_PTOOLONG | 0x10000000; } buf_append_chars_nocount(&gpwbuf, state->sshprefs->password); } } while((status = userkey_decrypt_private_part(&state->userkey, &gpwbuf)) == FISH_M_UKBADPASS && trypw == 0) { /* bad password, so let's try to get one */ char prompt[73]; static char prefix[] = "Enter passphrase for RSA key '"; int pn = sizeof(prefix) - 1; int n = strlen(state->userkey.comment); memcpy(prompt, prefix, pn); if (n - pn - 4 > 73) { memcpy(prompt + pn, state->userkey.comment, 73 - pn - 7); memcpy(prompt + 73 - 7, "...': ", 7); } else { memcpy(prompt + pn, state->userkey.comment, n); memcpy(prompt + (n + pn), "': ", 4); } if (pwbuf[0] == '\0') { if (!read_prompted(input_device, prompt, pwbuf, sizeof(pwbuf), 0, 0, &status)) { lib$signal(status); } buf_adjust_amount_to(&gpwbuf, strlen(pwbuf)); } trypw = pwbuf; } if (status == SS$_NORMAL) { RSA_private_decrypt(challengelen, challenge, challenge, state->userkey.key, RSA_NO_PADDING); /* Compute response */ MD5_Init(&md); MD5_Update(&md, challenge+challengelen-32, 32); MD5_Update(&md, state->sessid, 16); MD5_Final(responsebuf, &md); } else if (status == FISH_M_UKBADPASS) { lib$signal(FISH_M_RSAFAIL2); } else { lib$signal(FISH_M_BADKEYFILE, 1, state->sshprefs->identity_file); } if (status != SS$_NORMAL) { /* We will still create a message, so protocol won't be broken */ memset(responsebuf, 0, 16); state->authphase = SSH_AUTH_TIS; } state->triedpw |= TRIED_RSA; userkey_destroy_static(&state->userkey); buf_adjust_amount_by(&response, 16); ret = ssh_write_type(SSH_CMSG_AUTH_RSA_RESPONSE, &response, state, erf, erfp); if (ret < response.amount) { ssh_F_noconn(); return FISH_M_NOCONN | 0x10000000; } break; } /* XXXXX This is NOT TESTED, and should probably be rewritten */ case SSH_SMSG_AUTH_TIS_CHALLENGE: { char pwbuf[80]; int status; extern tty_s *input_tty; /* Try a password */ input_tty = tty_noecho(input_device, &status); if (fgets(pwbuf, sizeof(pwbuf), stdin) == NULL) trypw = ""; else trypw = pwbuf; state->triedpw |= TRIED_TIS; tty_setmode(input_tty, &status); tty_close(input_tty, &status); input_tty = 0; if (strlen(pwbuf) > 0 && pwbuf[strlen(pwbuf)-1] == '\n') pwbuf[strlen(pwbuf)-1] = '\0'; trypwlen = strlen(trypw); outbuf = buf_init(trypwlen+4); buf_append_int(outbuf, trypwlen); buf_append_bytes(outbuf, trypw, trypwlen); ret = ssh_write_type(SSH_CMSG_AUTH_TIS_RESPONSE, outbuf, state, erf, erfp); if (ret < buf_amount(outbuf)) { buf_destroy(outbuf); outbuf = NULL; ssh_F_noconn(); return FISH_M_NOCONN | 0x10000000; } buf_destroy(outbuf); outbuf = NULL; break; } default: { ssh_F_invpckt1(type); return FISH_M_INVPCKT1 | 0x10000000; } } break; /* SSH_PHASE_AUTH End ---------------------------------------------- */ /* SSH_PHASE_PREP Start -------------------------------------------- */ /* This phase looks a little odd, as in the next subphase takes care of the answer from the previous one... */ case SSH_PHASE_PREP: switch(state->prepphase) { case SSH_PREP_COMPRESSION: switch(type) { /* We've sent the MAX_PACKET_SIZE request. Accept either answer. */ case SSH_SMSG_SUCCESS: case SSH_SMSG_FAILURE: /* Request compression */ /* Prepare the next phase already */ state->prepphase = SSH_PREP_PTY; if (state->sshprefs->compress_p) { outbuf = buf_init(4); buf_append_int(outbuf, state->sshprefs->compress_p); ret = ssh_write_type(SSH_CMSG_REQUEST_COMPRESSION, outbuf, state, erf, erfp); if (ret < buf_amount(outbuf)) { buf_destroy(outbuf); outbuf = NULL; ssh_F_noconn(); return FISH_M_NOCONN | 0x10000000; } buf_destroy(outbuf); outbuf = NULL; return SS$_NORMAL; } else type = SSH_INTERNAL; break; default: /* Unknown packets? */ ssh_F_invpckt(); return FISH_M_INVPCKT | 0x10000000; } case SSH_PREP_PTY: switch(type) { case SSH_SMSG_SUCCESS: if (state->sshprefs->compress_p) { state->compress = state->sshprefs->compress_p; compress_init(0, state->compress); decompress_init(0); } /* Trickle through, since we might request a PTY anyway */ case SSH_INTERNAL: case SSH_SMSG_FAILURE: /* Get a pty */ /* Prepare the next phase already */ state->prepphase = SSH_PREP_X11; if (state->sshprefs->commands && !state->sshprefs->force_tty) { /* We don't want a pty, so let's skip this and trickle through */ type = SSH_INTERNAL; break; } if (!state->sshprefs || !state->sshprefs->ttymodes) { prefttymodes = SSH_DEF_TTYMODES; prefttymodelen = SSH_DEF_TTYMODELEN; } else { prefttymodes = state->sshprefs->ttymodes; prefttymodelen = state->sshprefs->ttymodelen; } termlen = strlen(state->term ? state->term : SSH_DEF_TERM); outbuf = buf_init(prefttymodelen+20+termlen); buf_append_int(outbuf, termlen); buf_append_bytes(outbuf, state->term ? state->term : SSH_DEF_TERM, termlen); buf_append_int(outbuf, state->termrows); buf_append_int(outbuf, state->termcols); buf_append_int(outbuf, state->termx); buf_append_int(outbuf, state->termy); buf_append_bytes(outbuf, prefttymodes, prefttymodelen); ret = ssh_write_type(SSH_CMSG_REQUEST_PTY, outbuf, state, erf, erfp); if (ret < buf_amount(outbuf)) { buf_destroy(outbuf); outbuf = NULL; ssh_F_noconn(); return FISH_M_NOCONN | 0x10000000; } buf_destroy(outbuf); outbuf = NULL; return SS$_NORMAL; default: /* Unknown packets? */ ssh_F_invpckt(); return FISH_M_INVPCKT | 0x10000000; } case SSH_PREP_X11: switch(type) { case SSH_SMSG_FAILURE: ssh_F_pty(); return FISH_M_PTY | 0x10000000; case SSH_INTERNAL: case SSH_SMSG_SUCCESS: /* Not yet supported, just trickle through to the next subphase */ state->prepphase = SSH_PREP_PORT_FORWARD; type = SSH_INTERNAL; break; default: /* Unknown packets? */ ssh_F_invpckt(); return FISH_M_INVPCKT | 0x10000000; } case SSH_PREP_PORT_FORWARD: switch(type) { /* The previous phase isn't implemented, so accept either type. */ case SSH_SMSG_SUCCESS: case SSH_INTERNAL: case SSH_SMSG_FAILURE: /* Not yet supported, just trickle through to the next subphase */ state->prepphase = SSH_PREP_AGENT_FORWARD; type = SSH_INTERNAL; break; default: /* Unknown packets? */ ssh_F_invpckt(); return FISH_M_INVPCKT | 0x10000000; } case SSH_PREP_AGENT_FORWARD: switch(type) { /* The previous phase isn't implemented, so accept either type. */ case SSH_SMSG_SUCCESS: case SSH_INTERNAL: case SSH_SMSG_FAILURE: /* Not yet supported, just trickle through to the next subphase */ state->prepphase = SSH_PREP_SHELL_OR_CMD; type = SSH_INTERNAL; break; default: /* Unknown packets? */ ssh_F_invpckt(); return FISH_M_INVPCKT | 0x10000000; } case SSH_PREP_SHELL_OR_CMD: switch(type) { /* The previous phase isn't implemented, so accept either type. */ case SSH_SMSG_SUCCESS: case SSH_INTERNAL: case SSH_SMSG_FAILURE: { int l = 0; unsigned char *buf; /* Ready to send a command or go interactive. */ if (state->sshprefs->commands) { on_state(lib$signal(FISH_M_COMMAND, 1,state->sshprefs->commands)); l = strlen(state->sshprefs->commands); outbuf = buf_init(l+4); buf_append_int(outbuf, l); buf_append_bytes(outbuf, state->sshprefs->commands, l); ret = ssh_write_type(SSH_CMSG_EXEC_CMD, outbuf, state, erf, erfp); buf_destroy(outbuf); } else ret = ssh_write_type(SSH_CMSG_EXEC_SHELL, NULL, state, erf, erfp); if (ret < l) { ssh_F_noconn(); return FISH_M_NOCONN | 0x10000000; } state->protophase = SSH_PHASE_INTERACTIVE; connected(state, erf, erfp); return SS$_NORMAL; } default: /* Unknown packets? */ ssh_F_invpckt(); return FISH_M_INVPCKT | 0x10000000; } } break; /* SSH_PHASE_PREP End ---------------------------------------------- */ /* SSH_PHASE_INTERACTIVE Start ------------------------------------- */ case SSH_PHASE_INTERACTIVE: /* We expect data from the other side now */ switch(type) { case SSH_SMSG_STDOUT_DATA: case SSH_SMSG_STDERR_DATA: if (!bufread_int(inbuf, &datalen) || bufread_remaining(inbuf) != datalen) { ssh_F_badpckt(); return FISH_M_BADPCKT | 0x10000000; } if (state->up_dispatch) { (state->up_dispatch)(bufread_chars_noadjust(inbuf), bufread_remaining(inbuf), state->up_state, erf, erfp); } break; case SSH_SMSG_EXITSTATUS: /* The session is over. Send a confirmation and shut it down. */ ssh_write_type(SSH_CMSG_EXIT_CONFIRMATION, NULL, state, erf, erfp); lib$signal(FISH_M_CLOSED); sys$setef(done_ef); return FISH_M_CLOSED; default: /* Unknown packets? */ ssh_F_invpckt(); return FISH_M_INVPCKT | 0x10000000; } break; /* SSH_PHASE_INTERACTIVE End --------------------------------------- */ default: /* Unknown phase? */ ssh_F_invphase(); return FISH_M_INVPHASE | 0x10000000; } } /* Emacs local variables Local variables: eval: (set-c-style "BSD") end: */