char *cksshv = "SSH support, 8.0.051, 26 Sep 2005"; /* C K O S S H . C -- OpenSSH Interface for Kermit 95 Copyright (C) 2001-2005, Trustees of Columbia University in the City of New York. All rights reserved. See the C-Kermit COPYING.TXT file or the copyright text in the ckcmai.c module for disclaimer and permissions. Portions Copyright (c) 2000-2002 Markus Friedl. Portions Copyright (c) 1999 Niels Provos. Portions Copyright (c) 1995 Tatu Ylonen , Espoo, Finland All rights reserved Includes work by Niels Provos to work with OpenSSL. */ #ifdef TO_DO_LIST . Kerberos 4 and 5 Auto-get/auto-destroy support . listen to SET TCP REVERSE-DNS-LOOKUPS . listen to SET TCP DNS-SRV-RECORDS . make SET TELNET ENV DISPLAY more portable or SSH equivalent . Implement service name processing in apply_kermit_settings() . add \f...()'s to process the .ssh\... key files (pub, priv, host) . document using Kermit file transfer to upload a public key identity file and append it to the authorized_keys file on the host . all strings are to be tranmitted as UTF-8. (username, passphrase, ...) need to examine what conversions are necessary. . do not close the SSH connection when the terminal session stops leave it open until an SSH CLOSE command is given. This will allow port forwarding and additional terminal sessions to be created on top of the SSH connection. . Incoming SSH connections SET HOST * #endif /* TO_DO_LIST */ #include "ckcdeb.h" #ifdef CK_SSL #ifdef SSHBUILTIN #ifndef OPENSSL_NO_KRB5 #define OPENSSL_NO_KRB5 #endif #include "ckuusr.h" #include "ckcnet.h" #ifdef OS2 /* OS/2 with TCP/IP */ #ifdef NT #define BSDSELECT #else /* NT */ #define IBMSELECT #endif /* NT */ #endif /* OS2 */ #include "ssh/includes.h" #include #include #ifdef KRB4 #define des_cblock Block #define const_des_cblock const Block #define des_key_schedule Schedule #ifdef KRB524 #ifdef NT #define _WINDOWS #endif /* NT */ #include "kerberosIV/krb.h" #ifndef OS2 _PROTOTYP(const char * krb_get_err_text_entry, (int)); #endif /* OS2 */ #else /* KRB524 */ #ifdef SOLARIS #ifndef sun /* for some reason the Makefile entries for the Solaris systems have -Usun */ #define sun #endif /* sun */ #endif /* SOLARIS */ #include "krb.h" #define krb_get_err_text_entry krb_get_err_text #endif /* KRB524 */ #else /* KRB4 */ #ifdef CK_SSL #define des_cblock Block #define const_des_cblock const Block #define des_key_schedule Schedule #endif /* CK_SSL */ #endif /* KRB4 */ #ifdef KRB5 #include #define GSSAPI #endif /* KRB5 */ #ifdef AFS #include #include "ssh/radix.h" #endif #ifndef HEADER_DES_H #define HEADER_DES_H #endif /* HEADER_DES_H */ #include "ck_ssl.h" #include "ssh/ssh.h" #include "ssh/ssh1.h" #include "ssh/ssh2.h" #include "ssh/compat.h" #include "ssh/cipher.h" #include "ssh/packet.h" #include "ssh/buffer.h" #include "ssh/bufaux.h" #include "ssh/uidswap.h" #include "ssh/channels.h" #include "ssh/key.h" #include "ssh/authfd.h" #include "ssh/authfile.h" #include "ssh/pathnames.h" #include "ssh/clientloop.h" #include "ssh/log.h" #include "ssh/readconf.h" #include "ssh/sshconnect.h" #include "ssh/tildexpand.h" #include "ssh/dispatch.h" #include "ssh/misc.h" #include "ssh/kex.h" #include "ssh/mac.h" #include "ssh/match.h" #ifndef OS2 #include "ssh/sshtty.h" #endif /* OS2 */ #include "ssh/version.h" #include "ssh/myproposal.h" #include "ssh/hostfile.h" #include "ssh/rsa.h" #include "ssh/mpaux.h" #include "ssh/uidswap.h" #include "ssh/authfile.h" #include "ssh/canohost.h" #ifdef GSSAPI #include "ssh/ssh-gss.h" #endif /* GSSAPI */ #ifdef CK_SRP #include "ssh/srp.h" #endif /* CK_SRP */ #ifdef SMARTCARD #include #include #include "ssh/scard.h" #endif #include "ckcker.h" #include "ckocon.h" #include "ckctel.h" #ifndef NO_KERBEROS #define MAP_KRB4 #define MAP_KRB5 #define MAP_GSSAPI #ifdef CK_ENCRYPTION #define MAP_DES #endif /* CK_ENCRYPTION */ #endif /* NO_KERBEROS */ #include "ckoath.h" #include "ckossh.h" #ifdef CK_SOCKS /* SOCKS Internet relay package */ #ifdef CK_SOCKS5 /* SOCKS 5 */ #define accept SOCKSaccept #define bind SOCKSbind #define connect SOCKSconnect #define getsockname SOCKSgetsockname #define listen SOCKSlisten #else /* Not SOCKS 5 */ #define accept Raccept #define bind Rbind #define connect Rconnect #define getsockname Rgetsockname #define listen Rlisten #endif /* CK_SOCKS5 */ #endif /* CK_SOCKS */ #ifndef NOHTTP extern char * tcp_http_proxy; /* Name[:port] of http proxy server */ extern int tcp_http_proxy_errno; extern char * tcp_http_proxy_user; extern char * tcp_http_proxy_pwd; extern char * tcp_http_proxy_agent; #define HTTPCPYL 1024 static char proxyhost[HTTPCPYL]; #endif /* NOHTTP */ #define ERRBUFSZ 1024 static char errbuf[ERRBUFSZ]; #ifdef NT extern int WSASafeToCancel; extern int win95selectbug; /* For TCP/IP stacks whose select() */ /* always fails on write requests such as Cisco and Quarterdeck */ #define stricmp _stricmp #endif /* NT */ extern int quiet, filecase; /* Flag indicating whether IPv4 or IPv6. This can be set on the command line. Default value is AF_UNSPEC means both IPv4 and IPv6. */ #ifdef IPV4_DEFAULT int IPv4or6 = AF_INET; #else int IPv4or6 = AF_UNSPEC; #endif /* Save the socket handle */ int ssh_sock = -1; /* Flag indicating whether debug mode is on. This can be set on the command line. */ int debug_flag = 0; /* Flag indicating whether a tty should be allocated */ int tty_flag = 0; int no_tty_flag = 0; int force_tty_flag = 0; /* don't exec a shell */ int no_shell_flag = 0; /* * Flag indicating that nothing should be read from stdin. This can be set * on the command line. */ int stdin_null_flag = 0; /* * Flag indicating that ssh should fork after authentication. This is useful * so that the passphrase can be entered manually, and then ssh goes to the * background. */ int fork_after_authentication_flag = 0; /* * General data structure for command line options and options configurable * in configuration files. See readconf.h. */ static Options options; static int opt_init = 0; /* * Name of the host we are connecting to. This is the name given on the * command line, or the HostName specified for the user-supplied name in a * configuration file. */ static char *host = 0; /* socket address the host resolves to */ struct sockaddr_storage hostaddr; /* * Flag to indicate that we have received a window change signal which has * not yet been processed. This will cause a message indicating the new * window size to be sent to the server a little later. This is volatile * because this is updated in a signal handler. */ static volatile int received_window_change_signal = 0; static volatile int received_signal = 0; static int sent_initial_ws = 0; static int ssh_height = 0; static int ssh_width = 0; /* Private host keys. */ Sensitive sensitive_data; /* command to be executed */ static Buffer command; static int buf_init = 0; /* Should we execute a command or invoke a subsystem? */ static int subsystem_flag = 0; /* Number of replies received for global requests */ static int client_global_request_id = 0; /* Flag indicating whether the user\'s terminal is in non-blocking mode. */ static int in_non_blocking_mode = 0; /* Common data for the client loop code. */ static int quit_pending = 0; /* Set to non-zero to quit the client loop. */ static int escape_char = 0; /* Escape character. */ static int escape_pending = 0; /* Last character was the escape character */ static int last_was_cr = 0; /* Last character was a newline. */ static int exit_status = 0; /* Used to store the exit status of the command. */ static int stdin_eof = 0; /* EOF has been encountered on standard error. */ static Buffer stdin_buffer; /* Buffer for stdin data. */ static Buffer stdout_buffer; /* Buffer for stdout data. */ static Buffer stderr_buffer; /* Buffer for stderr data. */ static u_long stdin_bytes = 0, stdout_bytes = 0, stderr_bytes = 0; static u_int buffer_high= 0; /* Soft max buffer size. */ static int connection_in = 0; /* Connection to server (input). */ static int connection_out = 0; /* Connection to server (output). */ static int need_rekeying = 0; /* Set to non-zero if rekeying is requested. */ static int session_closed = 0; /* In SSH2: login session closed. */ static void client_init_dispatch(void); int session_ident = -1; char *client_version_string = NULL; char *server_version_string = NULL; #ifndef NOTCPOPTS #ifdef SOL_SOCKET #ifdef TCP_NODELAY extern int tcp_nodelay; /* Nagle algorithm TCP_NODELAY */ #endif /* TCP_NODELAY */ #ifdef SO_DONTROUTE extern int tcp_dontroute; #endif /* SO_DONTROUTE */ #ifdef SO_LINGER extern int tcp_linger; /* SO_LINGER */ extern int tcp_linger_tmo; /* SO_LINGER timeout */ #endif /* SO_LINGER */ #ifdef SO_SNDBUF extern int tcp_sendbuf; #endif /* SO_SNDBUF */ #ifdef SO_RCVBUF extern int tcp_recvbuf; #endif /* SO_RCVBUF */ #ifdef SO_KEEPALIVE extern int tcp_keepalive; #endif /* SO_KEEPALIVE */ #endif /* SOL_SOCKET */ #endif /* NOTCPOPTS */ #ifdef CK_DNS_SRV static struct sockaddr * ssh_dns_addrs = NULL; static int ssh_dns_naddrs = 0; #endif /* CK_DNS_SRV */ extern char pwbuf[]; extern int pwflg, pwcrypt; /* * SSH2 key exchange */ u_char *session_id2 = NULL; int session_id2_len = 0; char *xxx_host; struct sockaddr *xxx_hostaddr; Kex *xxx_kex = NULL; #ifdef OS2 extern int CreateSocketPair(SOCKET *pair); SOCKET inPair[2] = { INVALID_SOCKET, INVALID_SOCKET }; SOCKET outPair[2] = { INVALID_SOCKET, INVALID_SOCKET }; SOCKET sock_stdin = INVALID_SOCKET; SOCKET sock_stdout = INVALID_SOCKET; SOCKET sock_stderr = INVALID_SOCKET; #define SOCK_OUT inPair[1] #define SOCK_IN outPair[0] /* BEGIN SSHDLL.C */ static int open_connection=0; extern char uidbuf[]; static HANDLE hClientLoopThread=NULL; static DWORD ClientLoopThreadID=0; static int clientloopID = 0; static DWORD WINAPI ClientLoopThread(void * dummy) { int rc, id = *((int *)dummy); rc = client_loop(1, options.escape_char, id); /* WaitForSingleObject(hClientLoopThread,INFINITE); */ if ( hClientLoopThread ) { CloseHandle(hClientLoopThread); hClientLoopThread = NULL; ClientLoopThreadID = 0; } return(rc); } #endif /* OS2 */ char * getterm() { static char ttn[64]; int i; #ifndef NOTERM extern char * tn_term; #ifdef OS2 extern int tt_type; extern int tt_type, max_tt; extern struct tt_info_rec tt_info[]; extern char ttname[]; if (!tn_term) { if (tt_type >= 0 && tt_type <= max_tt) { ckstrncpy(ttn,tt_info[tt_type].x_name,sizeof(ttn)); } else ttn[0] = '\0'; } #endif /* OS2 */ if (tn_term) { /* Terminal type override? */ debug(F110,"ssh_getterm",tn_term,0); if (*tn_term) ckstrncpy(ttn,tn_term,sizeof(ttn)); } else debug(F100,"ssh_getterm no term override","",0); #endif /* NOTERM */ #ifndef OS2 #ifndef datageneral if (!ttn[0]) { /* If no override, */ char * env = getenv("TERM"); /* get it from the environment. */ if ( env ) ckstrncpy(ttn,env,sizeof(ttn)); } #endif /* datageneral */ #endif /* OS2 */ if (!ttn[0]) ckstrncpy(ttn,"UNKNOWN",sizeof(ttn)); cklower(ttn); return(ttn); } /* _getwinsize() -- Get console size (like ioctl() with TIOCGWINSZ) /// LIMITATION /// xxx, x and y pixels of returned structure are not correct (calculated as fixed size 8x16 dot font). */ int _getwinsize( struct winsize *winsize ) { extern int tt_status[]; winsize->ws_col = VscrnGetWidth(VTERM); winsize->ws_row = VscrnGetHeight(VTERM) - (tt_status[VTERM]?1:0); winsize->ws_xpixel = winsize->ws_col * 8; /* xxx */ winsize->ws_ypixel = winsize->ws_row * 16; /* xxx */ return 0; } /* * Expands tildes in the file name. Returns data allocated by malloc. * Warning: this calls getpw*. */ static char * tilde_expand_filename(const char *filename, uid_t my_uid) { const char *cp; const char *home; u_int userlen; char *expanded; char user[100]; char userdir[MAXPATHLEN+1]; int len; /* Return immediately if no tilde. */ if (filename[0] != '~') return strdup(filename); /* Skip the tilde. */ filename++; /* Find where the username ends. */ cp = strchr(filename, '/'); if (cp) userlen = cp - filename; /* Something after username. */ else userlen = strlen(filename); /* Nothing after username. */ if (userlen > sizeof(user) - 1) { fatal("User name after tilde too long."); return NULL; } if (userlen) memcpy(user, filename, userlen); user[userlen] = 0; if (userlen == 0 || !ckstrcmp(user,(char *)GetLocalUser(),-1,filecase)) home = zhome(); else { char * homedrive = (char *)GetHomeDrive(); /* Tilde refers to someone elses home directory. */ snprintf(userdir,MAXPATHLEN+1, "%s:/Documents and Settings/%s/", homedrive[0]?homedrive:"C:",(char *)GetLocalUser()); home = userdir; } /* If referring to someones home directory, return it now. */ if (!cp) { /* Only home directory specified */ return strdup(home); } /* Build a path combining the specified directory and path. */ len = strlen(home) + strlen(cp + 1) + 2; if (len > MAXPATHLEN) { fatal("Home directory too long (%d > %d", len-1, MAXPATHLEN-1); return NULL; } expanded = malloc(len); snprintf(expanded, len, "%s%s", home, cp + 1); return expanded; } static int passwd_attempt = 0; static int kbdint_attempt = 0; static int srp_attempt = 0; static int external_attempt = 0; static int gssapi_attempt = 0; static int pubkey_agent_called = 0; static int pubkey_idx = 0; static int gssapi_mech = 0; void userauth_reset(void) { passwd_attempt = 0; kbdint_attempt = 0; srp_attempt = 0; external_attempt = 0; gssapi_attempt = 0; gssapi_mech = 0; pubkey_agent_called = 0; pubkey_idx = 0; } static int ssh_session(void); static int ssh_session2(void); static void load_public_identity_files(void); static void apply_kermit_settings(Options * options) { extern int tcp_dns_srv; int i, x; switch (ssh_ver) { case 1: options->protocol = SSH_PROTO_1; break; case 2: options->protocol = SSH_PROTO_2; break; default: options->protocol = SSH_PROTO_1|SSH_PROTO_2; } options->use_privileged_port = ssh_prp; options->forward_x11 = ssh_xfw; options->forward_agent = ssh_afw; options->gateway_ports = ssh_gwp; options->check_host_ip = ssh_chkip; options->strict_host_key_checking = ssh_shk; options->keepalives = tcp_keepalive; options->heartbeat_interval = ssh_hbt; if ( ssh_xal && *ssh_xal ) makestr(&options->xauth_location,ssh_xal); else makestr(&options->xauth_location,NULL); if ( ssh1_gnh && *ssh1_gnh ) makestr(&options->system_hostfile,ssh1_gnh); else makestr(&options->system_hostfile,NULL); if ( ssh1_unh && *ssh1_unh ) makestr(&options->user_hostfile,ssh1_unh); else makestr(&options->user_hostfile,NULL); if ( ssh2_gnh && *ssh2_gnh ) makestr(&options->system_hostfile2,ssh2_gnh); else makestr(&options->system_hostfile2,NULL); if ( ssh2_unh && *ssh2_unh ) makestr(&options->user_hostfile2,ssh2_unh); else makestr(&options->user_hostfile2,NULL); if ( ssh_idf_n > 0) { int i; for ( i=0; i < SSH_MAX_IDENTITY_FILES && i < ssh_idf_n ; i++ ) makestr(&options->identity_files[i],ssh_idf[i]); options->num_identity_files = i; } else options->num_identity_files = 0; options->rhosts_authentication = 1; options->rsa_authentication = 1; options->pubkey_authentication = 1; options->challenge_response_authentication = 1; options->password_authentication = 1; options->kbd_interactive_authentication = 1; options->rhosts_rsa_authentication = 1; options->hostbased_authentication = 1; #ifdef CK_SRP if ( ck_srp_is_installed() ) options->srp_authentication = 1; else options->srp_authentication = 0; #endif /* CK_SRP */ #ifdef KRB5 if ( ck_krb5_is_installed() ) { options->kerberos_tgt_passing = ssh_k5tgt; options->krb5_authentication = 1; } else { options->kerberos_tgt_passing = 0; options->krb5_authentication = 0; } #endif /* KRB5 */ #ifdef GSSAPI if ( ck_gssapi_is_installed() ) { options->gss_authentication = 1; options->gss_deleg_creds = ssh_gsd; } else { options->gss_deleg_creds = 0; options->gss_authentication = 1; } #endif /* GSSAPI */ #ifdef KRB4 if ( ck_krb4_is_installed() ) { options->krb4_authentication = 1; options->kerberos_tgt_passing = ssh_k4tgt; } else { options->krb4_authentication = 1; options->kerberos_tgt_passing = 0; } #endif /* KRB4 */ #ifdef AFS options->afs_token_passing = -1; #endif /* AFS */ /* options.log_level = SYSLOG_LEVEL_DEBUG[123], SYSLOG_LEVEL_QUIET */ options->log_level = ssh_vrb; /* Currently no escape character. Would be any ascii value. */ options->escape_char = SSH_ESCAPECHAR_NONE; if (ssh2_cif && *ssh2_cif) { makestr(&options->ciphers,ssh2_cif); options->cipher = SSH_CIPHER_ILLEGAL; } else makestr(&options->ciphers,NULL); if (ssh1_cif && *ssh1_cif) { /* SSH1 only */ options->cipher = cipher_number(ssh1_cif); if (options->cipher == -1) { fprintf(stderr, "Unknown cipher type '%s'\n", ssh1_cif); } else if (options->ciphers == NULL) { if (options->cipher == SSH_CIPHER_3DES) { options->ciphers= "3des-cbc"; } else if (options->cipher == SSH_CIPHER_BLOWFISH) { options->ciphers = "blowfish-cbc"; } else { options->ciphers = (char *)-1; } } } else options->cipher = -1; if ( ssh2_mac && *ssh2_mac ) makestr(&options->macs,ssh2_mac); else makestr(&options->macs,NULL); if ( ssh2_hka && *ssh2_hka ) { if ( key_names_valid2(ssh2_hka) ) makestr(&options->hostkeyalgorithms,ssh2_hka); else { printf("Invalid v2 host key algorithms: %s\n",ssh2_hka); makestr(&options->hostkeyalgorithms,NULL); } } else makestr(&options->hostkeyalgorithms,NULL); if ( ssh2_auth && *ssh2_auth ) makestr(&options->preferred_authentications,ssh2_auth); else makestr(&options->preferred_authentications,NULL); if (ssh_prt == NULL) ssh_prt = SSH_SERVICE_NAME; x = atoi(ssh_prt); if (x > 0) options->port = x; else { struct servent * service = NULL; #ifdef CK_DNS_SRV if (ssh_dns_addrs) { free(ssh_dns_addrs); ssh_dns_addrs = NULL; ssh_dns_naddrs = 0; } if (tcp_dns_srv && !quiet) { printf(" DNS SRV Lookup... "); fflush(stdout); } if (tcp_dns_srv && locate_srv_dns(ssh_hst, ssh_prt, "tcp", &ssh_dns_addrs, &ssh_dns_naddrs ) ) { /* Use the first one. Eventually we should cycle through all */ /* the returned IP addresses and port numbers. */ struct sockaddr_in *sin = NULL; #ifdef BETADEBUG int i; printf("\r\n"); for ( i=0;isin_addr), ntohs(sin->sin_port)); } #endif /* BETADEBUG */ sin = (struct sockaddr_in *) &ssh_dns_addrs[0]; options->port = ntohs(sin->sin_port); } else #endif /* CK_DNS_SRV */ { service = getservbyname(ssh_prt, "tcp"); if (service) options->port = ntohs(service->s_port); } } if (uidbuf[0]) makestr(&options->user,uidbuf); else { char tmp[BUFSIZ], prompt[256]; char *user; char *myname; int ok; myname = whoami(); if (!myname) myname = ""; if (myname[0]) ckmakxmsg(prompt,sizeof(prompt)," Name (",myname,"): ", NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL); else strcpy(prompt," Name: "); tmp[0] = '\0'; ok = uq_txt("Username required for SSH\n",prompt,1,NULL,tmp,sizeof(tmp), NULL,DEFAULT_UQ_TIMEOUT); if (!ok || (*tmp == '\0')) user = myname; else user = tmp; makestr(&options->user,user); } for ( i=0 ; i < ssh_pf_lcl_n ; i++ ) add_local_forward(options, ssh_pf_lcl[i].p1, ssh_pf_lcl[i].host, ssh_pf_lcl[i].p2); for ( i=0 ; i < ssh_pf_rmt_n ; i++ ) add_remote_forward(options, ssh_pf_rmt[i].p1, ssh_pf_rmt[i].host, ssh_pf_rmt[i].p2); if ( ssh_dyf ) add_local_forward(options, 1080, "socks4", 0); options->compression = ck_zlib_is_installed() ? ssh_cmp : 0; subsystem_flag = ssh_cas; } int ssh_open(VOID) { int i, opt, optind, type, cerr, fwd_port=0, fwd_host_port=0, authfd; char *optarg=NULL, *cp=NULL, buf[256]; struct _stat st; int interactive = 0, dummy; char *pw_dir=NULL, *pw_name=NULL; struct passwd *pw=NULL; Sensitive sensitive_data; if (!ck_ssleay_is_installed()) { printf("? SSH not available\r\n"); return -1; } /* Check that we got a host name. */ if (!ssh_hst) return(-114); myproposal[0] = KEX_DEFAULT_KEX; myproposal[1] = KEX_DEFAULT_PK_ALG; myproposal[2] = KEX_DEFAULT_ENCRYPT; myproposal[3] = KEX_DEFAULT_ENCRYPT; myproposal[4] = KEX_DEFAULT_MAC; myproposal[5] = KEX_DEFAULT_MAC; myproposal[6] = KEX_DEFAULT_COMP; myproposal[7] = KEX_DEFAULT_COMP; myproposal[8] = KEX_DEFAULT_LANG; myproposal[9] = KEX_DEFAULT_LANG; debug(F100,"ssh_open starting resets","",0); reset_compat(); channel_reset(); packet_reset(); authmethod_reset(); userauth_reset(); buffer_compress_uninit(); sent_initial_ws = 0; received_window_change_signal = 0; session_closed = 0; debug(F100,"ssh_open finished resets","",0); /* Initialize option structure to indicate that no values have been set. */ if ( !opt_init ) { initialize_options(&options); opt_init = 1; } /* Set options. */ host = ssh_hst; debug(F100,"ssh_open applying kermit settings","",0); apply_kermit_settings(&options); /* Initialize the command to execute on remote host. */ if ( buf_init ) buffer_clear(&command); else { buffer_init(&command); buf_init = 1; } /* Save the command to execute on the remote host in a buffer. There is no limit on the length of the command, except by the maximum packet size. Also sets the tty flag if there is no command. */ if ( ssh_cmd && *ssh_cmd ) { /* A command has been specified. Store it into the buffer. */ buffer_append(&command, ssh_cmd, strlen(ssh_cmd)); } /* Allocate a tty by default if no command specified. */ tty_flag = (buffer_len(&command) == 0); debug(F100,"ssh_open initalizing pw","",0); /* Get home directory and initialize struct passwd */ makestr(&pw_dir,zhome()); makestr(&pw_name, uidbuf); pw = malloc(sizeof(struct passwd)); if ( pw ) { pw->pw_name = pw_name; pw->pw_dir = pw_dir; pw->pw_passwd = NULL; pw->pw_shell = NULL; pw->pw_uid = 1; /* meaningless on windows */ pw->pw_gid = 1; /* ditto */ } debug(F100,"ssh_open log_init() 1","",0); /* Initialize "log" output. Since we are the client all output actually goes to the terminal. */ log_init("Kermit Builtin SSH", 1, SYSLOG_FACILITY_USER, 1); if (ssh_cfg) { /* Read per-user configuration file. pw_dir contains trailing slash */ sprintf(buf, "%.100s%s", pw_dir, (char *)_PATH_SSH_USER_CONFFILE); read_config_file(buf, host, &options); /* Read systemwide configuration file. */ read_config_file((char *)_PATH_HOST_CONFIG_FILE, host, &options); } debug(F100,"ssh_open calling fill_default_options()","",0); /* Fill configuration defaults. */ fill_default_options(&options); debug(F100,"ssh_open calling log_init() 2","",0); /* reinit */ log_init("Kermit Builtin SSH", options.log_level, SYSLOG_FACILITY_USER, 1); debug(F100,"ssh_open calling seed_rng()","",0); seed_rng(); if (options.user == NULL) options.user = strdup(pw_name); if (options.hostname != NULL) host = options.hostname; /* Disable rhosts authentication if not running as root. */ if (!options.use_privileged_port) options.rhosts_authentication = 0; /* Open a connection to the remote host. This needs root privileges if rhosts_authentication is true. Note that the random_state is not yet used by this call, although a pointer to it is stored, and thus it need not be initialized. */ cerr = ssh_connect(host, &hostaddr, options.port, IPv4or6, options.connection_attempts, options.use_privileged_port, NULL); if ( cerr ) { if (!quiet) { error("Could not connect to %s.",host); #ifdef COMMENT #ifdef KUI ckmakmsg(errbuf,ERRBUFSZ,"Could not connect to ",host,NULL,NULL); uq_ok(NULL, errbuf, 1, NULL, 0); #endif /* KUI */ #endif /* COMMENT */ } return -116; } /* Successful connection. */ /* * If we successfully made the connection, load the host private key * in case we will need it later for combined rsa-rhosts * authentication. This must be done before releasing extra * privileges, because the file is only readable by root. * If we cannot access the private keys, load the public keys * instead and try to execute the ssh-keysign helper instead. */ sensitive_data.nkeys = 0; sensitive_data.keys = NULL; sensitive_data.external_keysign = 0; if (options.rhosts_rsa_authentication || options.hostbased_authentication) { sensitive_data.nkeys = 3; sensitive_data.keys = malloc(sensitive_data.nkeys*sizeof(Key)); sensitive_data.keys[0] = key_load_private_type(KEY_RSA1, (char *)_PATH_HOST_KEY_FILE, "", NULL); sensitive_data.keys[1] = key_load_private_type(KEY_DSA, (char *)_PATH_HOST_DSA_KEY_FILE, "", NULL); sensitive_data.keys[2] = key_load_private_type(KEY_RSA, (char *)_PATH_HOST_RSA_KEY_FILE, "", NULL); if (options.hostbased_authentication == 1 && sensitive_data.keys[0] == NULL && sensitive_data.keys[1] == NULL && sensitive_data.keys[2] == NULL) { sensitive_data.keys[1] = key_load_public( (char *)_PATH_HOST_DSA_KEY_FILE, NULL); sensitive_data.keys[2] = key_load_public( (char *)_PATH_HOST_RSA_KEY_FILE, NULL); sensitive_data.external_keysign = 1; } } /* * Create ~/.ssh directory if it doesn\'t already exist. */ snprintf(buf, sizeof buf, "%s", (char *)_PATH_SSH_USER_DIR); if (stat(buf, &st) < 0) if (mkdir(buf, 0700) < 0) { error("Could not create directory '%.256s'.", buf); #ifdef KUI ckmakmsg(errbuf,ERRBUFSZ,"Could not create directory: \"", buf,"\"",NULL); uq_ok(NULL, errbuf, 1, NULL, 0); #endif /* KUI */ } /* load options.identity_files */ load_public_identity_files(); /* Expand ~ in known host file names. */ /* XXX mem-leaks: */ options.system_hostfile = tilde_expand_filename(options.system_hostfile, -1); options.user_hostfile = tilde_expand_filename(options.user_hostfile, -1); options.system_hostfile2 = tilde_expand_filename(options.system_hostfile2, -1); options.user_hostfile2 = tilde_expand_filename(options.user_hostfile2, -1); #ifdef SIGPIPE signal(SIGPIPE, SIG_IGN); #endif /* SIGPIPE */ /* Log into the remote system. This never returns if the login fails. */ cerr = ssh_login(&sensitive_data, host, (struct sockaddr *)&hostaddr, pw); /* We no longer need the private host keys. Clear them now. */ if (sensitive_data.nkeys != 0) { for (i = 0; i < sensitive_data.nkeys; i++) { if (sensitive_data.keys[i] != NULL) { /* Destroys contents safely */ debug3("clear hostkey %d", i); key_free(sensitive_data.keys[i]); sensitive_data.keys[i] = NULL; } } free(sensitive_data.keys); } for (i = 0; i < options.num_identity_files; i++) { if (options.identity_files[i]) { free(options.identity_files[i]); options.identity_files[i] = NULL; } if (options.identity_keys[i]) { key_free(options.identity_keys[i]); options.identity_keys[i] = NULL; } } free(pw); /* If ssh_login() failed, return. */ if (cerr) { if ( !quiet ) error("login failed"); #ifdef COMMENT #ifdef KUI uq_ok(NULL, "login failed", 1, NULL, 1); #endif /* KUI */ #endif /* COMMENT */ ssh_clos(); return(-122); } #ifdef OS2 CreateSocketPair(inPair); CreateSocketPair(outPair); sock_stdin = inPair[0]; sock_stdout = outPair[1]; sock_stderr = outPair[1]; #endif /* OS2 */ /* Enable Window Size reporting */ TELOPT_ME(TELOPT_NAWS) = 1; exit_status = compat20 ? ssh_session2() : ssh_session(); return exit_status; } #ifndef NOLOCAL _PROTOTYP(VOID slrestor,(VOID)); #endif /* NOLOCAL */ int ssh_clos() { if (quit_pending) { while (quit_pending) msleep(50); return(0); } if (hClientLoopThread) { quit_pending = 1; /* Instruct Client Loop to end */ ssh_toc(0); while (quit_pending && hClientLoopThread) msleep(50); open_connection = 0; quit_pending = 0; } packet_close(); if ( SOCK_IN != INVALID_SOCKET ) { closesocket(SOCK_IN); SOCK_IN = INVALID_SOCKET; } if ( SOCK_OUT != INVALID_SOCKET ) { closesocket(SOCK_OUT); SOCK_OUT = INVALID_SOCKET; } #ifndef NOLOCAL slrestor(); #endif /* NOLOCAL */ ssh_sock = -1; /* Exit with the status returned by the program on the remote side. */ return(0); } #ifndef NOBUF #ifdef OS2 #ifdef NT #define SSHIBUFL 64240 /* 44 * 1460 (MSS) */ #else #define SSHIBUFL 32120 /* 22 * 1460 (MSS) */ #endif /* NT */ #else /* OS2 */ #define SSHIBUFL 8191 /* Let's use 8K. */ #endif /* OS2 */ static CHAR sshibuf[SSHIBUFL+1]; static int sshibp = 0, sshibn = 0; #endif /* NOBUF */ int ssh_tchk(void) { long count = 0; int x = 0; long y; char c; if ( !open_connection ) return(-1); if (ioctlsocket(SOCK_IN,FIONREAD,&count) < 0) { #ifdef NT int s_errno = WSAGetLastError(); debug3("ssh_tchk socket = %u s_errno = %d",SOCK_IN, s_errno); #endif /* NT */ #ifndef NOBUF if ( sshibn > 0 ) { return(sshibn); } else #endif /* NOBUF */ { ttclos(0); return(-1); } } #ifndef NOBUF return(count+sshibn); #else return(count); #endif /* NOBUF */ } int ssh_flui(void) { char c; if ( !open_connection ) return(-1); /* Not safe to flush encrypted/compressed connections */ return(0); } int ssh_break(void) { if ( !open_connection ) return(-1); /* No equivalent to Telnet Break */ return(0); } static int sshbufr(void) { /* TT Buffer Read */ int count; #ifdef OS2 RequestTCPIPMutex(SEM_INDEFINITE_WAIT); #endif /* OS2 */ if (sshibn > 0) { /* Our internal buffer is not empty, */ #ifdef OS2 ReleaseTCPIPMutex(); #endif /* OS2 */ return(sshibn); /* so keep using it. */ } if ( !open_connection ) { #ifdef OS2 ReleaseTCPIPMutex(); #endif /* OS2 */ return(-2); } sshibp = 0; /* Else reset pointer to beginning */ #ifdef OS2 count = SSHIBUFL; #else /* Multinet, etc. */ count = ttchk(); /* Check network input buffer, */ if (sshibn > 0) { /* which can put a char there! */ debug(F111,"sshbufr","ttchk() returns",count); #ifdef OS2 ReleaseTCPIPMutex(); #endif /* OS2 */ return(sshibn); } if (count < 0) { /* Read error - connection closed */ #ifdef OS2 ReleaseTCPIPMutex(); #endif /* OS2 */ return(-2); } else if (count > SSHIBUFL) /* Too many to read */ count = SSHIBUFL; else if (count == 0) /* None, so force blocking read */ count = 1; #endif /* OS2 */ debug(F101,"sshbufr count 1","",count); /* This is for blocking reads */ #ifndef VMS #ifdef SO_OOBINLINE { int outofband = 0; #ifdef BELLSELECT if (select(128, NULL, NULL, efds, 0) > 0 && FD_ISSET(SOCK_IN, efds)) outofband = 1; #else #ifdef BSDSELECT fd_set efds; struct timeval tv; FD_ZERO(&efds); FD_SET(SOCK_IN, &efds); tv.tv_sec = tv.tv_usec = 0L; debug(F100,"Out-of-Band BSDSELECT","",0); #ifdef NT WSASafeToCancel = 1; #endif /* NT */ if (select(FD_SETSIZE, NULL, NULL, &efds, &tv) > 0 && FD_ISSET(SOCK_IN, &efds)) outofband = 1; #ifdef NT WSASafeToCancel = 0; #endif /* NT */ #else /* !BSDSELECT */ #ifdef IBMSELECT /* Is used by OS/2 ... */ /* ... and it came in handy! For our TCP/IP layer, it avoids all the fd_set */ /* and timeval stuff since this is the only place where it is used. */ int socket = SOCK_IN; debug(F100,"Out-of-Band IBMSELECT","",0); if ((select(&socket, 0, 0, 1, 0L) == 1) && (socket == SOCK_IN)) outofband = 1; #else /* !IBMSELECT */ /* If we can't use select(), then we use the regular alarm()/signal() timeout mechanism. */ debug(F101,"Out-of-Band data not supported","",0); outofband = 0; #endif /* IBMSELECT */ #endif /* BSDSELECT */ #endif /* BELLSELECT */ if (outofband) { /* Get the Urgent Data */ /* if OOBINLINE is disabled this should be only a single byte */ /* MS Winsock has a bug in Windows 95. Extra bytes are delivered */ /* That were never sent. */ #ifdef OS2 RequestTCPIPMutex(SEM_INDEFINITE_WAIT); #endif /* OS2 */ count = socket_recv(SOCK_IN,&sshibuf[sshibp+sshibn],count,MSG_OOB); #ifdef OS2 ReleaseTCPIPMutex(); #endif /* OS2 */ if (count <= 0) { int s_errno = socket_errno; debug(F101, "sshbufr socket_recv MSG_OOB","",count); debug(F101, "sshbufr socket_errno","",s_errno); #ifdef OS2ONLY if (count < 0 && (s_errno == 0 || s_errno == 23)) { /* These appear in OS/2 - don't know why */ /* ignore it and read as normal data */ /* and break, then we will attempt to read */ /* the port using normal read() techniques */ debug(F100,"sshbufr handing as in-band data","",0); count = 1; } else { ttclos(0); /* *** *** */ #ifdef OS2 ReleaseTCPIPMutex(); #endif /* OS2 */ return(-2); } #else /* OS2ONLY */ ttclos(0); /* *** *** */ #ifdef OS2 ReleaseTCPIPMutex(); #endif /* OS2 */ return(-2); #endif /* OS2ONLY */ } else { /* we got out-of-band data */ hexdump("sshbufr out-of-band chars",&sshibuf[sshibp+sshibn],count); #ifdef BETADEBUG bleep(BP_NOTE); #endif /* BETADEBUG */ { /* For any protocols we don't have a special out-of-band */ /* handler for, just put the bytes in the normal buffer */ /* and return */ sshibp += 0; /* Reset buffer pointer. */ sshibn += count; #ifdef DEBUG /* Got some bytes. */ debug(F101,"sshbufr count 2","",count); if (count > 0) sshibuf[sshibp+sshibn] = '\0'; debug(F111,"sshbufr sshibuf",sshibuf,sshibp); #endif /* DEBUG */ #ifdef OS2 ReleaseTCPIPMutex(); #endif /* OS2 */ return(sshibn); /* Return buffer count. */ } } } } #endif /* SO_OOBINLINE */ #endif /* VMS */ count = socket_read(SOCK_IN,&sshibuf[sshibp+sshibn],count); if (count <= 0) { int s_errno = socket_errno; debug(F101,"sshbufr socket_read","",count); debug(F101,"sshbufr socket_errno","",s_errno); #ifdef OS2 if (count == 0 || os2socketerror(s_errno) < 0) { ttclos(0); ReleaseTCPIPMutex(); return(-2); } ReleaseTCPIPMutex(); return(-1); #else /* OS2 */ ttclos(0); /* *** *** */ return(-2); #endif /* OS2 */ } else { sshibp = 0; /* Reset buffer pointer. */ sshibn += count; #ifdef DEBUG debug(F101,"sshbufr count 2","",count); /* Got some bytes. */ if (count > 0) sshibuf[sshibp+sshibn] = '\0'; debug(F111,"sshbufr sshibuf",&sshibuf[sshibp],sshibn); #endif /* DEBUG */ #ifdef OS2 ReleaseTCPIPMutex(); #endif /* OS2 */ return(sshibn); /* Return buffer count. */ } } int ssh_inc(int timo) { #ifdef NOBUF char c=0; fd_set rfds; struct timeval tv; int count; if ( !open_connection ) return(-1); begin: FD_ZERO(&rfds); FD_SET(SOCK_IN, &rfds); tv.tv_sec = tv.tv_usec = 0L; if (timo < 0) tv.tv_usec = (long) -timo * 1000L; else tv.tv_sec = timo; if (select(FD_SETSIZE, (fd_set *) &rfds, NULL, NULL, &tv) > 0 && FD_ISSET(SOCK_IN, &rfds)) { if ((count = recv(SOCK_IN,&c,1,0)) <= 0) { int s_errno = WSAGetLastError(); if (count == 0) { ttclos(0); /* if the connection was */ return -3; /* return a hard error */ } else { switch (s_errno) { case WSAETIMEDOUT: if ( timo == 0 ) goto begin; return(-1); case WSAECONNRESET: ttclos(0); /* *** *** */ return(-2); /* Connection is broken. */ case WSAECONNABORTED: ttclos(0); /* *** *** */ return(-2); /* Connection is broken. */ case WSAENETRESET: ttclos(0); /* *** *** */ return(-2); /* Connection is broken. */ case WSAENOTCONN: ttclos(0); /* *** *** */ return(-2); /* Connection is broken. */ case WSAEWOULDBLOCK: break; } if ( timo == 0 ) goto begin; return -1; /* Return a timeout */ } } return(c); /* Return char read */ } if ( timo == 0 ) goto begin; return(-1); /* Return a timeout */ #else /* NOBUF */ int x, c; #ifdef OS2 RequestTCPIPMutex(SEM_INDEFINITE_WAIT); #endif /* OS2 */ if (sshibn > 0) { /* Something in internal buffer? */ #ifdef COMMENT debug(F100,"ssh_inc char in buf","",0); /* Yes. */ #endif /* COMMENT */ x = 0; /* Success. */ } else { /* Else must read from network. */ x = -1; /* Assume failure. */ #ifdef DEBUG debug(F101,"ssh_inc goes to net, timo","",timo); #endif /* DEBUG */ { #ifdef BSDSELECT fd_set rfds; struct timeval tv; int timeout = timo < 0 ? -timo : 1000 * timo; debug(F101,"ssh_inc BSDSELECT","",timo); for ( ; timeout >= 0; timeout -= (timo ? 100 : 0)) { int rc; debug(F111,"ssh_inc","timeout",timeout); /* Don't move select() initialization out of the loop. */ FD_ZERO(&rfds); FD_SET(SOCK_IN, &rfds); tv.tv_sec = tv.tv_usec = 0L; if (timo) tv.tv_usec = (long) 100000L; else tv.tv_sec = 30; #ifdef NT WSASafeToCancel = 1; #endif /* NT */ rc = select(FD_SETSIZE, #ifndef __DECC (fd_set *) #endif /* __DECC */ &rfds, NULL, NULL, &tv); if (rc < 0) { int s_errno = socket_errno; debug(F111,"ssh_inc","select",rc); debug(F111,"ssh_inc","socket_errno",s_errno); if (s_errno) { #ifdef OS2 ReleaseTCPIPMutex(); #endif /* OS2 */ return(-1); } } debug(F111,"ssh_inc","select",rc); #ifdef NT WSASafeToCancel = 0; #endif /* NT */ if (!FD_ISSET(SOCK_IN, &rfds)) { #ifdef LEBUF if (le_inbuf() > 0) { timeout = -1; break; } #endif /* LEBUF */ /* If waiting forever we have no way of knowing if the */ /* socket closed so try writing a 0-length TCP packet */ /* which should force an error if the socket is closed */ if (!timo) { if ((rc = socket_write(SOCK_IN,"",0)) < 0) { int s_errno = socket_errno; debug(F101,"ssh_inc socket_write error","",s_errno); #ifdef OS2 if (os2socketerror(s_errno) < 0) { ReleaseTCPIPMutex(); return(-2); } ReleaseTCPIPMutex(); #endif /* OS2 */ return(-1); /* Call it an i/o error */ } } continue; } while (1) { if (sshbufr() < 0) { /* Keep trying to refill it. */ timeout = -1; break; /* Till we get an error. */ } if (sshibn > 0) { /* Or we get a character. */ x = 0; timeout = -1; break; } } } #ifdef NT WSASafeToCancel = 0; #endif /* NT */ #else /* !BSDSELECT */ #ifdef IBMSELECT /* Was used by OS/2, currently not used, but might come in handy some day... ... and it came in handy! For our TCP/IP layer, it avoids all the fd_set and timeval stuff since this is the only place where it is used. */ int socket = SOCK_IN; int timeout = timo < 0 ? -timo : 1000 * timo; debug(F101,"ssh_inc IBMSELECT","",timo); for ( ; timeout >= 0; timeout -= (timo ? 100 : 0)) { if (select(&socket, 1, 0, 0, 100L) == 1) { while (1) { if (sshbufr() < 0) { /* Keep trying to refill it. */ timeout = -1; break; /* Till we get an error. */ } if (sshibn > 0) { /* Or we get a character. */ x = 0; timeout = -1; break; } } } #ifdef LEBUF else if (le_inbuf() > 0) { timeout = -1; break; } #endif /* LEBUF */ } #else /* !IBMSELECT */ #ifdef WINSOCK /* Actually, under WinSock we have a better mechanism than select() */ /* for setting timeouts (SO_RCVTIMEO, SO_SNDTIMEO) */ SOCKET socket = SOCK_IN; debug(F101,"ssh_inc NTSELECT","",timo); if (setsockopt(socket, SOL_SOCKET, SO_RCVTIMEO, (char *)&timo, sizeof(timo)) == NO_ERROR) while (1) { if (sshbufr() < 0) /* Keep trying to refill it. */ break; /* Till we get an error. */ if (sshibn > 0) { /* Or we get a character. */ x = 0; break; } } #else /* WINSOCK */ /* If we can't use select(), then we use the regular alarm()/signal() timeout mechanism. */ debug(F101,"ssh_inc alarm","",timo); x = alrm_execute(ckjaddr(njbuf),timo,nettout,dossh_inc,failssh_inc); sshimoff(); /* Timer off. */ #endif /* WINSOCK */ #endif /* IBMSELECT */ #endif /* BSDSELECT */ } } #ifdef LEBUF if (le_inbuf() > 0) { /* If data was inserted into the */ if (le_getchar((CHAR *)&c) > 0) {/* Local Echo buffer while the */ #ifdef OS2 /* was taking place do not mix */ ReleaseTCPIPMutex(); /* the le data with the net data */ #endif /* OS2 */ return(c); } } #endif /* LEBUF */ if (x < 0) { /* Return -1 if we failed. */ debug(F100,"ssh_inc timed out","",0); #ifdef OS2 ReleaseTCPIPMutex(); #endif /* OS2 */ return(-1); } else { /* Otherwise */ c = sshibuf[sshibp]; /* Return the first char in sshibuf[] */ if (deblog) { #ifndef COMMENT debug(F101,"ssh_inc returning","",c); #endif /* COMMENT */ if (c == 0) { debug(F101,"ssh_inc 0 sshibn","",sshibn); debug(F101,"ssh_inc 0 sshibp","",sshibp); #ifdef BETADEBUG { #ifdef OS2 extern int tt_type_mode; if ( !ISVTNT(tt_type_mode) ) #endif /* OS2 */ hexdump("ssh_inc &ttbuf[sshibp]",&sshibuf[sshibp],sshibn); } #endif /* BETADEBUG */ } } sshibp++; sshibn--; #ifdef OS2 ReleaseTCPIPMutex(); #endif /* OS2 */ return(c); } #endif /* NOBUF */ } int ssh_xin(int n, char * buf) { #ifdef NOBUF int count; char c=0; fd_set rfds; struct timeval tv; if ( !open_connection ) return(-1); begin: FD_ZERO(&rfds); FD_SET(SOCK_IN, &rfds); tv.tv_sec = tv.tv_usec = 0L; /* No timeout */ if (select(FD_SETSIZE, (fd_set *) &rfds, NULL, NULL, &tv) > 0 && FD_ISSET(SOCK_IN, &rfds)) { if ((count = recv(SOCK_IN,buf,n,0)) <= 0) { int s_errno = WSAGetLastError(); if (count == 0) { ttclos(0); /* if the connection was */ return -3; /* return a hard error */ } else { switch (s_errno) { case WSAETIMEDOUT: return(-1); case WSAECONNRESET: ttclos(0); /* *** *** */ return(-2); /* Connection is broken. */ case WSAECONNABORTED: ttclos(0); /* *** *** */ return(-2); /* Connection is broken. */ case WSAENETRESET: ttclos(0); /* *** *** */ return(-2); /* Connection is broken. */ case WSAENOTCONN: ttclos(0); /* *** *** */ return(-2); /* Connection is broken. */ case WSAEWOULDBLOCK: break; } return 0; /* Return a timeout */ } } return(count); } return(0); #else /* NOBUF */ int len, i, j; int rc; if ( !open_connection ) { debug(F100,"ssh_xin socket is closed","",0); return(-2); } #ifdef OS2 RequestTCPIPMutex(SEM_INDEFINITE_WAIT); #endif /* OS2 */ if (sshibn == 0) if ((rc = sshbufr()) <= 0) { #ifdef OS2 ReleaseTCPIPMutex(); #endif /* OS2 */ return(rc); } if (sshibn <= n) { len = sshibn; memcpy(buf,&sshibuf[sshibp],len); /* safe */ sshibp += len; sshibn = 0; } else { memcpy(buf,&sshibuf[sshibp],n); /* safe */ sshibp += n; sshibn -= n; len = n; } #ifdef OS2 ReleaseTCPIPMutex(); #endif /* OS2 */ return(len); #endif /* NOBUF */ } int ssh_toc(int c) { char ch = c; fd_set wfds; struct timeval tv; if ( !open_connection ) return(-1); FD_ZERO(&wfds); FD_SET(SOCK_OUT, &wfds); tv.tv_sec = tv.tv_usec = 0L; tv.tv_sec = 60; if (!(select(FD_SETSIZE, NULL, &wfds, NULL, &tv) > 0 && FD_ISSET(SOCK_OUT, &wfds))) { return(-1); } if (send(SOCK_OUT,&ch,1,0) < 1) { int s_errno = WSAGetLastError(); switch (s_errno) { case WSAETIMEDOUT: return(-1); case WSAECONNRESET: ttclos(0); /* *** *** */ return(-2); /* Connection is broken. */ case WSAECONNABORTED: ttclos(0); /* *** *** */ return(-2); /* Connection is broken. */ case WSAENETRESET: ttclos(0); /* *** *** */ return(-2); /* Connection is broken. */ case WSAENOTCONN: ttclos(0); /* *** *** */ return(-2); /* Connection is broken. */ case WSAEWOULDBLOCK: break; } return(-1); } return(0); } int ssh_tol(char * buffer, int count) { fd_set wfds; struct timeval tv; if ( !open_connection ) return(-1); FD_ZERO(&wfds); FD_SET(SOCK_OUT, &wfds); tv.tv_sec = tv.tv_usec = 0L; tv.tv_sec = 60; if (!(select(FD_SETSIZE, NULL, &wfds, NULL, &tv) > 0 && FD_ISSET(SOCK_OUT, &wfds))) { return(-1); } if ((count = send(SOCK_OUT,buffer,count,0)) < 1) { int s_errno = WSAGetLastError(); /* maybe a function */ switch (s_errno) { case WSAETIMEDOUT: return(-1); case WSAECONNRESET: ttclos(0); /* *** *** */ return(-2); /* Connection is broken. */ case WSAECONNABORTED: ttclos(0); /* *** *** */ return(-2); /* Connection is broken. */ case WSAENETRESET: ttclos(0); /* *** *** */ return(-2); /* Connection is broken. */ case WSAENOTCONN: ttclos(0); /* *** *** */ return(-2); /* Connection is broken. */ case WSAEWOULDBLOCK: break; } return(-1); } return(count); } VOID ssh_terminfo(char * ttype, int h, int w) { static int height=0, width=0; if ( h != height || w != width ) { height = h; width = w; packet_start(SSH_CMSG_WINDOW_SIZE); packet_put_int(h); packet_put_int(w); packet_put_int(0); packet_put_int(0); packet_send(); } } const char * ssh_version(void) { static char msg[256]; sprintf(msg, "%s, SSH protocols %d.%d/%d.%d, OpenSSL 0x%8.8lx\n", SSH_VERSION, PROTOCOL_MAJOR_1, PROTOCOL_MINOR_1, PROTOCOL_MAJOR_2, PROTOCOL_MINOR_2, SSLeay()); return(msg); } const char * ssh_errorstr(int error) { switch ( error ) { case 0: return("success"); break; case -1: return("timeout"); break; case -2: return("connection closed"); case -100: return("usage error: matching parenthese not found"); case -101: return("usage error: missing operand"); case -102: return("usage error: option missing argument"); break; case -103: return("usage error: switch specified is more than one letter"); case -104: return("usage error: identity file does not exist"); case -105: return("usage error: too many identity files specified"); case -106: return(ssh_version()); case -107: return("usage error: Bad escape character"); case -108: return("usage error: unknown cipher type"); case -109: return("usage error: bad port"); case -110: return("usage error: bad remote forwarding specification"); case -111: return("usage error: bad local forwarding specification"); case -112: return("usage error: privileged ports can only be forwarded by root"); case -113: return("usage error: unknown switch"); case -114: return("usage error: missing host"); case -115: return("cannot fork into background without a command to execute"); case -116: return("connection failed"); case -117: return("unable to create directory $(HOME)/.ssh"); case -118: return("Compression level must be from 1 (fast) to 9 (slow, best)"); case -119: return("fork after authentication not implemented on Windows NT"); case -120: return("authentication failed"); case -121: return("unable to create socket pair"); case -122: return("login failure"); default: return("unknown"); } } int ssh_ttvt(void) { if ( !open_connection ) return(-1); return(0); } int ssh_ttpkt(void) { if ( !open_connection ) return(-1); return(0); } int ssh_ttres(void) { if ( !open_connection ) return(-1); return(0); } /* END SSHDLL.C */ #ifdef COMMENT /* Keep so we know what the SSH personality command line should look like */ VOID ssh_usage(void) { fprintf(stderr, "Usage: %s [options] host [command]\n", __progname); fprintf(stderr, "Options:\n"); fprintf(stderr, " -l user Log in using this user name.\n"); fprintf(stderr, " -n Redirect input from " _PATH_DEVNULL ".\n"); fprintf(stderr, " -A Enable authentication agent forwarding.\n"); fprintf(stderr, " -a Disable authentication agent forwarding.\n"); #ifdef AFS fprintf(stderr, " -k Disable Kerberos ticket and AFS token forwarding.\n"); #endif /* AFS */ fprintf(stderr, " -X Enable X11 connection forwarding.\n"); fprintf(stderr, " -x Disable X11 connection forwarding.\n"); fprintf(stderr, " -i file Identity for public key authentication " "(default: ~/.ssh/identity)\n"); fprintf(stderr, " -t Tty; allocate a tty even if command is given.\n"); fprintf(stderr, " -T Do not allocate a tty.\n"); fprintf(stderr, " -v Verbose; display verbose debugging messages.\n"); fprintf(stderr, " Multiple -v increases verbosity.\n"); fprintf(stderr, " -V Display version number only.\n"); fprintf(stderr, " -q Quiet; don't display any warning messages.\n"); fprintf(stderr, " -f Fork into background after authentication.\n"); fprintf(stderr, " -e char Set escape character; ``none'' = disable (default: ~).\n"); fprintf(stderr, " -c cipher Select encryption algorithm: " "``3des'', ``blowfish''\n"); fprintf(stderr, " -m macs Specify MAC algorithms for protocol version 2.\n"); fprintf(stderr, " -p port Connect to this port. Server must be on the same port.\n"); fprintf(stderr, " -L listen-port:host:port Forward local port to remote address\n"); fprintf(stderr, " -R listen-port:host:port Forward remote port to local address\n"); fprintf(stderr, " These cause %s to listen for connections on a port, and\n", __progname); fprintf(stderr, " forward them to the other side by connecting to host:port.\n"); fprintf(stderr, " -C Enable compression.\n"); fprintf(stderr, " -N Do not execute a shell or command.\n"); fprintf(stderr, " -g Allow remote hosts to connect to forwarded ports.\n"); fprintf(stderr, " -1 Force protocol version 1.\n"); fprintf(stderr, " -2 Force protocol version 2.\n"); fprintf(stderr, " -4 Use IPv4 only.\n"); fprintf(stderr, " -6 Use IPv6 only.\n"); fprintf(stderr, " -o 'option' Process the option as if it was read from a configuration file.\n"); fprintf(stderr, " -s Invoke command (mandatory) as SSH2 subsystem.\n"); exit(1); } #endif /* COMMENT */ static void x11_get_proto(char ** _proto, char **_data) { char line[512]; static char proto[512], data[512]; FILE *f; int got_data = 0, i; char *display; struct _stat st; *_proto = proto; *_data = data; proto[0] = data[0] = '\0'; #ifndef OS2 if (options.xauth_location || (stat(options.xauth_location, &st) == -1)) { debug("No xauth program."); } else { if ((display = tn_get_display()) == NULL) { debug("x11_get_proto: DISPLAY not set"); return; } /* Try to get Xauthority information for the display. */ if (strncmp(display, "localhost:", 10) == 0) snprintf(line, sizeof line, "%.100s list %.200s 2>" _PATH_DEVNULL, options.xauth_location, display+10); else snprintf(line, sizeof line, "%.100s list %.200s 2>" _PATH_DEVNULL, options.xauth_location, display); debug2("x11_get_proto: %s", line); f = _popen(line, "r"); if (f && fgets(line, sizeof(line), f) && sscanf(line, "%*s %511s %511s", proto, data) == 2) got_data = 1; if (f) _pclose(f); } #endif /* OS2 */ /* * If we didn't get authentication data, just make up some * data. The forwarding code will check the validity of the * response anyway, and substitute this data. The X11 * server, however, will ignore this fake data and use * whatever authentication mechanisms it was using otherwise * for the local connection. */ if (!got_data) { u_int32_t rand = 0; log("Warning: No xauth data; using fake authentication data for X11 forwarding"); strlcpy(proto, "MIT-MAGIC-COOKIE-1", sizeof(proto)); for (i = 0; i < 16; i++) { if (i % 4 == 0) rand = arc4random(); snprintf(data + 2 * i, sizeof(data) - 2 * i, "%02x", rand & 0xff); rand >>= 8; } } } void ssh_v2_rekey(void) { if (compat20 && !(datafellows & SSH_BUG_NOREKEY)) need_rekeying = 1; } int ssh_fwd_local_port(int port, char * host, int host_port) { return channel_setup_local_fwd_listener(port,host,host_port,ssh_gwp); } int ssh_fwd_remote_port(int port, char * host, int host_port) { return channel_request_remote_forwarding(port,host,host_port); } static void ssh_init_forwarding(void) { int success = 0; int i; /* Initiate local TCP/IP port forwardings. */ for (i = 0; i < options.num_local_forwards; i++) { debug(F111,"SSH: Connections to","local port", options.local_forwards[i].port); debug(F111,"SSH: Forwarded to", options.local_forwards[i].host, options.local_forwards[i].host_port); success += channel_setup_local_fwd_listener( options.local_forwards[i].port, options.local_forwards[i].host, options.local_forwards[i].host_port, options.gateway_ports); } if (i > 0 && success == 0) { error("Could not request local forwarding."); #ifdef KUI uq_ok(NULL, "Could not request local forwarding", 1, NULL, 0); #endif /* KUI */ } /* Initiate remote TCP/IP port forwardings. */ for (i = 0; i < options.num_remote_forwards; i++) { debug(F111,"SSH: Connections to","remote port", options.remote_forwards[i].port); debug(F111,"SSH: Forwarded to local address", options.remote_forwards[i].host, options.remote_forwards[i].host_port); channel_request_remote_forwarding(options.remote_forwards[i].port, options.remote_forwards[i].host, options.remote_forwards[i].host_port); } } static void check_agent_present(void) { if (options.forward_agent) { /* Clear agent forwarding if we don\'t have an agent. */ if (!ssh_agent_present()) options.forward_agent = 0; } } static int ssh_session(void) { int type; int interactive = 0; int have_tty = 0; struct winsize ws; char *cp; /* Enable compression if requested. */ if (options.compression) { debug(F111,"SSH","Requesting compression at level", options.compression_level); if (options.compression_level < 1 || options.compression_level > 9) { ssh_fatal("Compression level must be from 1 (fast) to 9 (slow, best)."); return -1; } /* Send the request. */ packet_start(SSH_CMSG_REQUEST_COMPRESSION); packet_put_int(options.compression_level); packet_send(); packet_write_wait(); type = packet_read(); if (type == SSH_SMSG_SUCCESS) packet_start_compression(options.compression_level); else if (type == SSH_SMSG_FAILURE) log("Warning: Remote host refused compression."); else { packet_disconnect("Protocol error waiting for compression response."); return(-1); } } /* Allocate a pseudo tty if appropriate. */ if (tty_flag) { debug(F110,"SSH","Requesting pty.",0); /* Start the packet. */ packet_start(SSH_CMSG_REQUEST_PTY); /* Store TERM in the packet. There is no limit on the length of the string. */ cp = getterm(); if (!cp) cp = ""; packet_put_cstring(cp); /* Store window size in the packet. */ #ifdef OS2 if (_getwinsize( &ws ) == -1) memset(&ws,0,sizeof(ws)); #else if (ioctl(fileno(stdin), TIOCGWINSZ, &ws) < 0) memset(&ws, 0, sizeof(ws)); #endif received_window_change_signal = 0; sent_initial_ws = 1; ssh_width = ws.ws_col; ssh_height = ws.ws_row; packet_put_int(ws.ws_row); packet_put_int(ws.ws_col); packet_put_int(ws.ws_xpixel); packet_put_int(ws.ws_ypixel); /* Store tty modes in the packet. */ tty_make_modes(fileno(stdin), NULL); /* Send the packet, and wait for it to leave. */ packet_send(); packet_write_wait(); /* Read response from the server. */ type = packet_read(); if (type == SSH_SMSG_SUCCESS) { interactive = 1; have_tty = 1; } else if (type == SSH_SMSG_FAILURE) log("Warning: Remote host failed or refused to allocate a pseudo tty."); else { packet_disconnect("Protocol error waiting for pty request response."); return -1; } } /* Request X11 forwarding if enabled. (display is checked in x11_get_proto) */ if (options.forward_x11) { char *proto, *data; /* Get reasonable local authentication information. */ x11_get_proto(&proto, &data); /* Request forwarding with authentication spoofing. */ debug(F110,"SSH","Requesting X11 forwarding with authentication spoofing.",0); x11_request_forwarding_with_spoofing(0, proto, data); /* Read response from the server. */ type = packet_read(); if (type == SSH_SMSG_SUCCESS) { interactive = 1; } else if (type == SSH_SMSG_FAILURE) { log("Warning: Remote host denied X11 forwarding."); } else { packet_disconnect("Protocol error waiting for X11 forwarding"); return -1; } } /* Tell the packet module whether this is an interactive session. */ packet_set_interactive(interactive); /* Request authentication agent forwarding if appropriate. */ check_agent_present(); if (options.forward_agent) { debug(F110,"SSH","Requesting authentication agent forwarding.",0); auth_request_forwarding(); /* Read response from the server. */ type = packet_read(); packet_check_eom(); if (type != SSH_SMSG_SUCCESS) log("Warning: Remote host denied authentication agent forwarding."); } /* Initiate port forwardings. */ ssh_init_forwarding(); /* If requested, let ssh continue in the background. */ if (fork_after_authentication_flag) { #ifdef OS2 fprintf(stderr, "fork after authentication is not implemented on Win32 environment\n"); exit(1); #else if (daemon(1, 1) < 0) fatal("daemon() failed: %.200s", strerror(errno)); #endif /* OS2 */ } /* * If a command was specified on the command line, execute the * command now. Otherwise request the server to start a shell. */ if (buffer_len(&command) > 0) { int len = buffer_len(&command); if (len > 900) len = 900; debug1("Sending command: %.*s", len, (u_char *)buffer_ptr(&command)); packet_start(SSH_CMSG_EXEC_CMD); packet_put_string(buffer_ptr(&command), buffer_len(&command)); packet_send(); packet_write_wait(); } else { debug1("Requesting shell."); packet_start(SSH_CMSG_EXEC_SHELL); packet_send(); packet_write_wait(); } /* Enter the interactive session. */ #ifdef OS2 /* Start the Client Loop Thread */ clientloopID = 0; hClientLoopThread = CreateThread(NULL, 0, ClientLoopThread, &clientloopID, 0, &ClientLoopThreadID); msleep(100); open_connection = 1; return(0); #else return client_loop(have_tty, tty_flag ? options.escape_char : SSH_ESCAPECHAR_NONE, 0); #endif /* OS2 */ } static int client_subsystem_reply(int type, u_int32_t seq, void *ctxt) { int id, len; id = packet_get_int(); len = buffer_len(&command); if (len > 900) len = 900; packet_check_eom(); if (type == SSH2_MSG_CHANNEL_FAILURE) { fatal("Request for subsystem '%.*s' failed on channel %d", len, (u_char *)buffer_ptr(&command), id); quit_pending = 1; return(-1); } return(0); } static int client_global_request_reply(int type, u_int32_t seq, void *ctxt) { int i; i = client_global_request_id++; if (i >= options.num_remote_forwards) { debug1("client_global_request_reply: too many replies %d > %d", i, options.num_remote_forwards); return 0; } debug1("remote forward %s for: listen %d, connect %s:%d", type == SSH2_MSG_REQUEST_SUCCESS ? "success" : "failure", options.remote_forwards[i].port, options.remote_forwards[i].host, options.remote_forwards[i].host_port); if (type == SSH2_MSG_REQUEST_FAILURE) log("Warning: remote port forwarding failed for listen port %d", options.remote_forwards[i].port); return 0; } /* request pty/x11/agent/tcpfwd/shell for channel */ static void ssh_session2_setup(int id, void *arg) { int len; int interactive = 0; #ifndef OS2 struct termios tio; #endif /* OS2 */ debug2("ssh_session2_setup: id: %d arg: ",id); if (tty_flag) { struct winsize ws; char *cp; cp = getterm(); if (!cp) cp = ""; /* Store window size in the packet. */ #ifdef OS2 if (_getwinsize( &ws ) == -1) memset(&ws,0,sizeof(ws)); #else if (ioctl(fileno(stdin), TIOCGWINSZ, &ws) < 0) memset(&ws, 0, sizeof(ws)); #endif received_window_change_signal = 0; sent_initial_ws = 1; ssh_width = ws.ws_col; ssh_height = ws.ws_row; channel_request_start(id, "pty-req", 0); packet_put_cstring(cp); packet_put_int(ws.ws_col); packet_put_int(ws.ws_row); packet_put_int(ws.ws_xpixel); packet_put_int(ws.ws_ypixel); #ifdef OS2 tty_make_modes(0,0); #else tio = get_saved_tio(); tty_make_modes(/*ignored*/ 0, &tio); #endif /* OS2 */ packet_send(); interactive = 1; /* XXX wait for reply */ } if (options.forward_x11) { char *proto, *data; /* Get reasonable local authentication information. */ x11_get_proto(&proto, &data); /* Request forwarding with authentication spoofing. */ debug1("Requesting X11 forwarding with authentication spoofing."); x11_request_forwarding_with_spoofing(id, proto, data); interactive = 1; /* XXX wait for reply */ } check_agent_present(); if (options.forward_agent) { debug1("Requesting authentication agent forwarding."); channel_request_start(id, "auth-agent-req@openssh.com", 0); packet_send(); } len = buffer_len(&command); if (len > 0) { if (len > 900) len = 900; if (subsystem_flag) { debug1("Sending subsystem: %.*s", len, (u_char *)buffer_ptr(&command)); channel_request_start(id, "subsystem", /*want reply*/ 1); /* register callback for reply */ /* XXX we assume that client_loop has already been called */ dispatch_set(SSH2_MSG_CHANNEL_FAILURE, &client_subsystem_reply); dispatch_set(SSH2_MSG_CHANNEL_SUCCESS, &client_subsystem_reply); } else { debug1("Sending command: %.*s",len, (u_char *)buffer_ptr(&command)); channel_request_start(id, "exec", 0); } packet_put_string(buffer_ptr(&command), buffer_len(&command)); packet_send(); } else { channel_request_start(id, "shell", 0); packet_send(); } packet_set_interactive(interactive); } int ssh_session2_open(void) { Channel *c; int window, packetmax; int in, out, err; #ifdef OS2 in = sock_stdin; out = sock_stdout; err = sock_stderr; #else if (stdin_null_flag) { in = open(_PATH_DEVNULL, O_RDONLY); } else { in = dup(STDIN_FILENO); } out = dup(STDOUT_FILENO); err = dup(STDERR_FILENO); if (in < 0 || out < 0 || err < 0) { fatal("dup() in/out/err failed"); return(-1); } /* enable nonblocking unless tty */ if (!isatty(in)) set_nonblock(in); if (!isatty(out)) set_nonblock(out); if (!isatty(err)) set_nonblock(err); #endif /* OS2 */ window = CHAN_SES_WINDOW_DEFAULT; packetmax = CHAN_SES_PACKET_DEFAULT; if (!tty_flag) { window >>= 1; packetmax >>= 1; } c = channel_new("session", SSH_CHANNEL_OPENING, in, out, err, window, packetmax, CHAN_EXTENDED_WRITE, strdup("client-session"), /*nonblock*/0); if ( c == NULL ) { debug1("ssh_session2_open: channel_new failed"); return -1; } debug3("ssh_session2_open: channel_new: %d", c->self); channel_send_open(c->self); if ( !no_shell_flag ) channel_register_confirm(c->self, ssh_session2_setup); return c->self; } static int ssh_session2(void) { int id = -1; /* XXX should be pre-session */ ssh_init_forwarding(); if ( !no_shell_flag || (datafellows & SSH_BUG_DUMMYCHAN) ) id = ssh_session2_open(); /* If requested, let ssh continue in the background. */ if (fork_after_authentication_flag) { #ifdef OS2 fprintf(stderr, "fork after authentication is not implemented on Win32 environment\n"); return(-1); #else if (daemon(1, 1) < 0) { fatal("daemon() failed: %.200s", strerror(errno)); return(-1); } #endif /* OS2 */ } #ifdef OS2 /* Start the Client Loop Thread */ clientloopID = id; hClientLoopThread = CreateThread(NULL, 0, ClientLoopThread, &clientloopID, 0, &ClientLoopThreadID); open_connection = 1; return(0); #else return client_loop(tty_flag, tty_flag ? options.escape_char : SSH_ESCAPECHAR_NONE, id); #endif /* OS2 */ } static void load_public_identity_files(void) { char *filename; int i = 0; Key *public; #ifdef SMARTCARD Key **keys; if (options.smartcard_device != NULL && options.num_identity_files < SSH_MAX_IDENTITY_FILES && (keys = sc_get_keys(options.smartcard_device, NULL)) != NULL ) { int count = 0; for (i = 0; keys[i] != NULL; i++) { count++; memmove(&options.identity_files[1], &options.identity_files[0], sizeof(char *) * (SSH_MAX_IDENTITY_FILES - 1)); memmove(&options.identity_keys[1], &options.identity_keys[0], sizeof(Key *) * (SSH_MAX_IDENTITY_FILES - 1)); options.num_identity_files++; options.identity_keys[0] = keys[i]; options.identity_files[0] = strdup("smartcard key");; } if (options.num_identity_files > SSH_MAX_IDENTITY_FILES) options.num_identity_files = SSH_MAX_IDENTITY_FILES; i = count; free(keys); } #endif /* SMARTCARD */ for (; i < options.num_identity_files; i++) { filename = tilde_expand_filename(options.identity_files[i], -1); public = key_load_public(filename, NULL); debug1("identity file: \"%s\" type: %d", filename, public ? public->type : -1); free(options.identity_files[i]); options.identity_files[i] = filename; options.identity_keys[i] = public; } } /* * Creates a (possibly privileged) socket for use as the ssh connection. */ int ssh_create_socket(int privileged, int family) { extern char * tcp_address; SOCKET sock; /* * If we are running as root and want to connect to a privileged * port, bind our own socket to a privileged port. */ if (privileged) { int p = IPPORT_RESERVED - 1; sock = rresvport_af(&p, family); if (sock < 0) error("rresvport: af=%d %.100s", family, strerror(errno)); else debug1("Allocated local port: %d", p); } else { sock = socket(family, SOCK_STREAM, 0); #ifdef OS2 if (sock == SOCKET_ERROR) error("socket: %.100s", sock_strerror(sock_lasterror())); #else if (sock < 0) error("socket: %.100s", strerror(errno)); #endif /* OS2 */ } #ifndef datageneral if (tcp_address && family == AF_INET) { struct sockaddr_in sin; #ifdef INADDRX struct in_addr inaddrx; #endif /* INADDRX */ int s_errno; debug1("ssh_create_socket binding socket to: %s",tcp_address); memset((char *)&sin,0,sizeof(sin)); sin.sin_family = family; #ifdef INADDRX inaddrx = inet_addr(tcp_address); sin.sin_addr.s_addr = *(unsigned long *)&inaddrx; #else sin.sin_addr.s_addr = inet_addr(tcp_address); #endif /* INADDRX */ sin.sin_port = 0; if (bind(sock, (struct sockaddr *)&sin, sizeof(sin)) < 0) { s_errno = WSAGetLastError(); /* Save error code */ #ifdef TCPIPLIB socket_close(sock); #else /* TCPIPLIB */ close(sock); #endif /* TCPIPLIB */ sock = -1; errno = s_errno; /* and report this error */ debug1("ssh_create_socket bind errno: %d",errno); return(-1); } } #endif /* datageneral */ return sock; } #ifndef INET6_ADDRSTRLEN /* for non IPv6 machines */ #define INET6_ADDRSTRLEN 46 #endif static const char * sockaddr_ntop(struct sockaddr *sa) { void *addr; static char addrbuf[INET6_ADDRSTRLEN]; switch (sa->sa_family) { case AF_INET: addr = &((struct sockaddr_in *)sa)->sin_addr; break; case AF_INET6: addr = &((struct sockaddr_in6 *)sa)->sin6_addr; break; default: /* This case should be protected against elsewhere */ abort(); } inet_ntop(sa->sa_family, addr, addrbuf, sizeof(addrbuf)); return addrbuf; } static int show_other_keys(const char *, Key *); /* * 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. * Return values: * 0 for OK * ECONNREFUSED if we got a "Connection Refused" by the peer on any address * ECONNABORTED if we failed without a "Connection refused" * Suitable error messages for the connection failure will already have been * printed. */ int ssh_connect(const char *host, struct sockaddr_storage * hostaddr, u_short port, int family, int connection_attempts, int needpriv, const char *proxy_command) { int gaierr; int on = 1; int attempt, i; char ntop[NI_MAXHOST], strport[NI_MAXSERV]; struct addrinfo hints, *ai = NULL, *aitop = NULL; struct servent *sp = NULL; /* * Did we get only other errors than "Connection refused" (which * should block fallback to rsh and similar), or did we get at least * one "Connection refused"? */ int full_failure = 1; #ifndef NOHTTP char * proxy_user=NULL, * proxy_pwd=NULL; char uid[UIDBUFLEN]; char pwd[256]; #endif /* NOHTTP */ #ifdef UNIX debug2("ssh_connect: needpriv %d", needpriv); #endif /* UNIX */ /* Get default port if port has not been set. */ if (port == 0) { sp = getservbyname(SSH_SERVICE_NAME, "tcp"); if (sp) port = ntohs(sp->s_port); else port = SSH_DEFAULT_PORT; } memset(&hints, 0, sizeof(hints)); hints.ai_family = family; hints.ai_socktype = SOCK_STREAM; snprintf(strport, sizeof strport, "%d", port); if ((gaierr = getaddrinfo(host, strport, &hints, &aitop)) != 0) { fatal("ssh: %.100s: %s", host, gai_strerror(gaierr)); return -1; } /* * Try to connect several times. On some machines, the first time * will sometimes fail. In general socket code appears to behave * quite magically on many machines. */ for (attempt = 0;;) { if (attempt > 0 && !quiet) printf("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) { error("ssh_connect: getnameinfo failed"); continue; } debug1("Connecting to %s:%s %s",host,strport,ntop); #ifndef NOHTTP if (tcp_http_proxy) { struct sockaddr_in sa_proxy; #ifdef OS2 char * agent = "Kermit 95"; /* Default user agent */ #else char * agent = "C-Kermit"; #endif /* OS2 */ register struct hostent *hp = 0; struct servent *destsp; char proxyhost[128], *p, *q; char proxyto[128]; #ifdef IP_TOS #ifdef IPTOS_THROUGHPUT int tos; #endif /* IPTOS_THROUGHPUT */ #endif /* IP_TOS */ int s; #ifdef DEBUG extern int debtim; int xdebtim; xdebtim = debtim; debtim = 1; #endif /* DEBUG */ ckmakmsg(proxyto,512,(char*)host,":",strport,NULL); memset((char *)&sa_proxy, 0, sizeof (sa_proxy)); for (p = tcp_http_proxy, q=proxyhost; *p != '\0' && *p != ':'; p++, q++) *q = *p; *q = '\0'; sa_proxy.sin_addr.s_addr = inet_addr(proxyhost); if (sa_proxy.sin_addr.s_addr != -1) { debug(F110,"ssh_connect A",proxyhost,0); sa_proxy.sin_family = AF_INET; } else { debug(F110,"ssh_connect B",proxyhost,0); hp = gethostbyname(proxyhost); #ifdef HADDRLIST hp = ck_copyhostent(hp); /* make safe copy that won't change */ #endif /* HADDRLIST */ if (hp == NULL) { fprintf(stderr, "ssh: %s: Unknown host", proxyhost); #ifdef DEBUG debtim = xdebtim; #endif /* DEBUG */ goto next_ai; } sa_proxy.sin_family = hp->h_addrtype; #ifdef HADDRLIST memcpy((char *)&sa_proxy.sin_addr, hp->h_addr_list[0], sizeof(sa_proxy.sin_addr)); #else /* HADDRLIST */ memcpy((char *)&sa_proxy.sin_addr, hp->h_addr, sizeof(sa_proxy.sin_addr)); #endif /* HADDRLIST */ } http_restart: ssh_sock = ssh_create_socket(needpriv, ai->ai_family); debug(F101,"ssh_connect socket","",ssh_sock); if (ssh_sock < 0) { perror("ssh: socket"); #ifdef DEBUG debtim = xdebtim; #endif /* DEBUG */ goto next_ai; } if (*p == ':') p++; else p = "http"; destsp = getservbyname(p,"tcp"); if (destsp) sa_proxy.sin_port = destsp->s_port; else if (p) sa_proxy.sin_port = htons(atoi(p)); else sa_proxy.sin_port = htons(80); errno = 0; #ifdef HADDRLIST debug(F100,"ssh_connect HADDRLIST","",0); while #else debug(F100,"ssh_connect no HADDRLIST","",0); if #endif /* HADDRLIST */ (connect(ssh_sock, (struct sockaddr *)&sa_proxy, sizeof (sa_proxy)) < 0) { debug(F101,"ssh_connect connect failed","",errno); #ifdef HADDRLIST if (hp && hp->h_addr_list[1]) { int oerrno = errno; fprintf(stderr, "ssh: connect to address %s: ", inet_ntoa(sa_proxy.sin_addr) ); errno = oerrno; perror((char *)0); hp->h_addr_list++; memcpy((char *)&sa_proxy.sin_addr, hp->h_addr_list[0], sizeof(sa_proxy.sin_addr)); if ( !quiet ) fprintf(stdout, "Trying %s...\n", inet_ntoa(sa_proxy.sin_addr)); shutdown(ssh_sock, SHUT_RDWR); #ifdef TCPIPLIB socket_close(ssh_sock); #else /* TCPIPLIB */ close(ssh_sock); #endif /* TCPIPLIB */ ssh_sock = ssh_create_socket(needpriv, ai->ai_family); if (ssh_sock < 0) { perror("ssh: socket"); #ifdef DEBUG debtim = xdebtim; #endif /* DEBUG */ goto next_ai; } continue; } #endif /* HADDRLIST */ perror("ssh: connect"); goto next_attempt; } if (http_connect(ssh_sock, tcp_http_proxy_agent ? tcp_http_proxy_agent : agent, NULL, tcp_http_proxy_user, tcp_http_proxy_pwd, 0, proxyto ) < 0) { shutdown(ssh_sock, SHUT_RDWR); #ifdef TCPIPLIB socket_close(ssh_sock); #else /* TCPIPLIB */ close(ssh_sock); #endif /* TCPIPLIB */ if (tcp_http_proxy_errno == 401 || tcp_http_proxy_errno == 407 ) { struct txtbox tb[2]; int ok; tb[0].t_buf = uid; tb[0].t_len = UIDBUFLEN; tb[0].t_lbl = "Proxy Userid: "; tb[0].t_dflt = NULL; tb[0].t_echo = 1; tb[1].t_buf = pwd; tb[1].t_len = 256; tb[1].t_lbl = "Proxy Passphrase: "; tb[1].t_dflt = NULL; tb[1].t_echo = 2; ok = uq_mtxt("Proxy Server Authentication Required\n", NULL, 2, tb); if (ok && uid[0]) { if ( !proxy_user ) { proxy_user = tcp_http_proxy_user; tcp_http_proxy_user = uid; } if ( !proxy_user ) { proxy_pwd = tcp_http_proxy_pwd; tcp_http_proxy_pwd = pwd; } goto http_restart; } } perror("ssh: connect"); goto next_attempt; } else { /* Successful connection. */ memcpy(hostaddr, ai->ai_addr, ai->ai_addrlen); break; } } else #endif /* NOHTTP */ { /* Create a socket for connecting. */ ssh_sock = ssh_create_socket(needpriv, ai->ai_family); if (ssh_sock < 0) continue; if (connect(ssh_sock, ai->ai_addr, ai->ai_addrlen) >= 0) { /* Successful connection. */ memcpy(hostaddr, ai->ai_addr, ai->ai_addrlen); break; } else { if (sock_lasterror() == WSAECONNREFUSED) full_failure = 0; log("ssh: connect to address %s port %s: %s", sockaddr_ntop(ai->ai_addr), strport, #ifdef OS2 sock_strerror(sock_lasterror()) #else strerror(errno) #endif /* OS2 */ ); /* * Close the failed socket; there appear to * be some problems when reusing a socket for * which connect() has already returned an * error. */ shutdown(ssh_sock, SHUT_RDWR); #ifdef OS2 closesocket(ssh_sock); #else close(ssh_sock); #endif /* OS2 */ } } next_ai: ; } if (ai) break; /* Successful connection. */ next_attempt: if ( ++attempt >= connection_attempts ) break; /* Sleep a moment before retrying. */ sleep(1); } #ifndef NOHTTP if ( proxy_user ) tcp_http_proxy_user = proxy_user; if ( proxy_pwd ) { memset(pwd,0,sizeof(pwd)); tcp_http_proxy_pwd = proxy_pwd; } #endif /* NOHTTP */ freeaddrinfo(aitop); /* Return failure if we didn't get a successful connection. */ if (attempt >= connection_attempts) { log("ssh: connect to host %s port %s: %s", host, strport, sock_strerror(sock_lasterror())); return full_failure ? WSAECONNABORTED : WSAECONNREFUSED; } debug1("Connection established."); #ifndef NOTCPOPTS #ifndef datageneral #ifdef SOL_SOCKET #ifdef TCP_NODELAY no_delay(ssh_sock,tcp_nodelay); #endif /* TCP_NODELAY */ #ifdef SO_KEEPALIVE keepalive(ssh_sock,tcp_keepalive); #endif /* SO_KEEPALIVE */ #ifdef SO_LINGER ck_linger(ssh_sock,tcp_linger, tcp_linger_tmo); #endif /* SO_LINGER */ #ifdef SO_SNDBUF sendbuf(ssh_sock,tcp_sendbuf); #endif /* SO_SNDBUF */ #ifdef SO_RCVBUF recvbuf(ssh_sock,tcp_recvbuf); #endif /* SO_RCVBUF */ #endif /* SOL_SOCKET */ #endif /* datageneral */ #endif /* NOTCPOPTS */ /* Set the connection. */ packet_set_connection(ssh_sock, ssh_sock); return 0; } static int minor1 = PROTOCOL_MINOR_1; char * ssh_proto_ver(void) { static char buf[16]; snprintf(buf, sizeof buf, "SSH-%d.%d", compat20 ? PROTOCOL_MAJOR_2 : PROTOCOL_MAJOR_1, compat20 ? PROTOCOL_MINOR_2 : minor1); return buf; } /* * Waits for the server identification string, and sends our own * identification string. */ static int ssh_exchange_identification(void) { char buf[256], remote_version[256]; /* must be same size! */ int remote_major, remote_minor, i, mismatch; int connection_in = packet_get_connection_in(); int connection_out = packet_get_connection_out(); minor1 = PROTOCOL_MINOR_1; /* Read other side\'s version identification. */ for (;;) { for (i = 0; i < sizeof(buf) - 1; i++) { #ifdef OS2 int len; #ifdef BSDSELECT fd_set rfds; struct timeval tv; int ready; FD_ZERO(&rfds); FD_SET(connection_in, &rfds); tv.tv_sec = 30; tv.tv_usec = 0L; ready = ((select(FD_SETSIZE, &rfds, NULL, NULL, &tv) > 0) && FD_ISSET(connection_in, &rfds)); if ( !ready ) { fatal("ssh_exchange_identification: select: 30 second timeout"); return -1; } #endif /* BSDSELECT */ len = recv(connection_in, &buf[i], 1, 0); if (len < 0) { fatal("ssh_exchange_identification: read: %.100s", sock_strerror(sock_lasterror())); return -1; } #else int len = atomicio(read, connection_in, &buf[i], 1); if (len < 0) { fatal("ssh_exchange_identification: read: %.100s", strerror(errno)); return -1; } #endif /* OS2 */ if (len != 1) { fatal("ssh_exchange_identification: Connection closed by remote host"); return -1; } if (buf[i] == '\r') { buf[i] = '\n'; buf[i + 1] = 0; continue; /**XXX wait for \n */ } if (buf[i] == '\n') { buf[i + 1] = 0; break; } } buf[sizeof(buf) - 1] = 0; if (strncmp(buf, "SSH-", 4) == 0) break; debug1("ssh_exchange_identification: %s", buf); } server_version_string = strdup(buf); /* * Check that the versions match. In future this might accept * several versions and set appropriate flags to handle them. */ if (sscanf(server_version_string, "SSH-%d.%d-%[^\n]\n", &remote_major, &remote_minor, remote_version) != 3) { fatal("Bad remote protocol version identification: '%.100s'", buf); return -1; } debug1("Remote protocol version (major) %d (minor) %d", remote_major, remote_minor); debug1("Remote software version: %s",remote_version); compat_datafellows(remote_version); mismatch = 0; switch(remote_major) { case 1: if (remote_minor == 99 && (options.protocol & SSH_PROTO_2) && !(options.protocol & SSH_PROTO_1_PREFERRED)) { enable_compat20(); break; } if (!(options.protocol & SSH_PROTO_1)) { mismatch = 1; break; } if (remote_minor < 3) { fatal("Remote machine has too old SSH software version."); return -1; } else if (remote_minor == 3 || remote_minor == 4) { /* We speak 1.3, too. */ enable_compat13(); minor1 = 3; if (options.forward_agent) { log("Agent forwarding disabled for protocol 1.3"); options.forward_agent = 0; } } break; case 2: if (options.protocol & SSH_PROTO_2) { enable_compat20(); break; } /* FALLTHROUGH */ default: mismatch = 1; break; } if (mismatch) fatal("Protocol major versions differ: %d vs. %d", (options.protocol & SSH_PROTO_2) ? PROTOCOL_MAJOR_2 : PROTOCOL_MAJOR_1, remote_major); /* Send our own protocol version identification. */ snprintf(buf, sizeof buf, "SSH-%d.%d-%.100s\n", compat20 ? PROTOCOL_MAJOR_2 : PROTOCOL_MAJOR_1, compat20 ? PROTOCOL_MINOR_2 : minor1, SSH_VERSION); #ifdef OS2 if (send(connection_out, buf, strlen(buf), 0) != strlen(buf)) { fatal("write: %.100s", sock_strerror(sock_lasterror())); return -1; } #else if (atomicio(write, connection_out, buf, strlen(buf)) != strlen(buf)) { fatal("write: %.100s", strerror(errno)); return -1; } #endif /* OS2 */ client_version_string = strdup(buf); chop(client_version_string); chop(server_version_string); debug1("Local version string: %s", client_version_string); return 0; } /* defaults to 'no' */ static int confirm(const char *preface, const char *prompt, int defval) { char buf[1024]; FILE *f; int retval = -1; if (options.batch_mode) return 0; retval = uq_ok((char *)preface, (char *)prompt, 3, NULL, defval); if ( retval < 0 ) retval = defval; return(retval); } /* * check whether the supplied host key is valid, return -1 if the key * is not valid. the user_hostfile will not be updated if 'readonly' is true. */ int check_host_key(char *host, struct sockaddr *hostaddr, Key *host_key, int readonly, const char *user_hostfile, const char *system_hostfile) { Key *file_key; char *type = key_type(host_key); char *ip = NULL; char hostline[1000], *hostp, *fp; HostStatus host_status; HostStatus ip_status; int local = 0, host_ip_differ = 0; int salen; char ntop[NI_MAXHOST]; char msg[1024]; int len, host_line, ip_line, has_keys; const char *host_file = NULL, *ip_file = NULL; /* * 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. */ /** hostaddr == 0! */ switch (hostaddr->sa_family) { case AF_INET: local = (ntohl(((struct sockaddr_in *)hostaddr)->sin_addr.s_addr) >> 24) == IN_LOOPBACKNET; salen = sizeof(struct sockaddr_in); break; case AF_INET6: local = IN6_IS_ADDR_LOOPBACK(&(((struct sockaddr_in6 *)hostaddr)->sin6_addr)); salen = sizeof(struct sockaddr_in6); break; default: local = 0; salen = sizeof(struct sockaddr_storage); break; } if (options.no_host_authentication_for_localhost == 1 && local && options.host_key_alias == NULL) { debug1("Forcing accepting of host key for " "loopback/localhost."); return 0; } /* * We don't have the remote ip-address for connections * using a proxy command */ if (!tcp_http_proxy) { if (getnameinfo(hostaddr, salen, ntop, sizeof(ntop), NULL, 0, NI_NUMERICHOST) != 0) { fatal("check_host_key: getnameinfo failed"); return -1; } ip = strdup(ntop); } else { ip = strdup(""); } /* * 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(host, ip) == 0 || tcp_http_proxy)) options.check_host_ip = 0; /* * Allow the user to record the key under a different name. This is * useful for ssh tunneling over forwarded connections or if you run * multiple sshd's on different ports on the same machine. */ if (options.host_key_alias != NULL) { host = options.host_key_alias; debug1("using hostkeyalias: %s", host); } /* * Store the host key from the known host file in here so that we can * compare it with the key for the IP address. */ file_key = key_new(host_key->type); /* * Check if the host key is present in the user\'s list of known * hosts or in the systemwide list. */ host_file = user_hostfile; host_status = check_host_in_hostfile(host_file, host, host_key, file_key, &host_line); if (host_status == HOST_NEW) { host_file = system_hostfile; host_status = check_host_in_hostfile(host_file, host, host_key, file_key, &host_line); } /* * Also perform check for the ip address, skip the check if we are * localhost or the hostname was an ip address to begin with */ if (options.check_host_ip) { Key *ip_key = key_new(host_key->type); ip_file = user_hostfile; ip_status = check_host_in_hostfile(ip_file, ip, host_key, ip_key, &ip_line); if (ip_status == HOST_NEW) { ip_file = system_hostfile; ip_status = check_host_in_hostfile(ip_file, ip, host_key, ip_key, &ip_line); } if (host_status == HOST_CHANGED && (ip_status != HOST_CHANGED || !key_equal(ip_key, file_key))) host_ip_differ = 1; key_free(ip_key); } else ip_status = host_status; key_free(file_key); switch (host_status) { case HOST_OK: /* The host is known and the key matches. */ debug1("Host is known: %s and matches the host key: %s found in %s (%d)", host,type,host_file,host_line); if (options.check_host_ip && ip_status == HOST_NEW) { if (readonly) log("%s host key for IP address " "'%.128s' not in list of known hosts.", type, ip); else if (!add_host_to_hostfile(user_hostfile, ip, host_key)) log("Failed to add the %s host key for IP address '%.128s' to the list of known hosts (%.30s).", type, ip, user_hostfile); else log("Warning: Permanently added the %s host key for IP address '%.128s' to the list of known hosts.", type, ip); } break; case HOST_NEW: /* The host is new. */ if ( readonly ) goto fail; 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. */ ckmakxmsg(errbuf,ERRBUFSZ, "No ",type," host key is known for ",host, " and\n" "you have requested strict host checking.\n" "If you wish to make an untrusted connection,\n" "SET SSH STRICT-HOST-KEY-CHECK OFF and try again.", NULL,NULL,NULL,NULL,NULL,NULL,NULL); error(errbuf); #ifdef KUI uq_ok(NULL, errbuf, 1, NULL, 0); #endif /* KUI */ goto fail; } else if (options.strict_host_key_checking == 2) { has_keys = show_other_keys(host, host_key); /* The default */ fp = key_fingerprint(host_key, SSH_FP_MD5, SSH_FP_HEX); snprintf(msg, sizeof(msg), "The authenticity of host '%.200s (%s)' can't be established%s\n" "%s key fingerprint is %s.\n", host, ip, has_keys ? ",\nbut keys of different type are already known for this host." : ".", type, fp); free(fp); if (!confirm(msg, "Are you sure you want to continue connecting (yes/no)? ", -1)) { error("Aborted by user!"); goto fail; } } else if (options.strict_host_key_checking != 0) { error("Invalid Strict Host Key Check value!"); goto fail; } if (options.check_host_ip && ip_status == HOST_NEW) { snprintf(hostline, sizeof(hostline), "%s,%s", host, ip); hostp = hostline; } else hostp = host; /* If not in strict mode, add the key automatically to the local known_hosts file. */ if (!add_host_to_hostfile(user_hostfile, hostp, host_key)) log("Failed to add the host to the list of known hosts (%.500s).", user_hostfile); else log("Warning: Permanently added '%.200s' (%s) to the list of known hosts.", hostp, type); break; case HOST_CHANGED: if (options.check_host_ip && host_ip_differ) { char *msg; if (ip_status == HOST_NEW) msg = "is unknown"; else if (ip_status == HOST_OK) msg = "is unchanged"; else msg = "has a different value"; ckmakxmsg(errbuf,ERRBUFSZ, "@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@\n" "@ WARNING: POSSIBLE DNS SPOOFING DETECTED! @\n" "@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@\n" "The ", type, " host key for ", host, " has changed,\n" "and the key for the according IP address ", ip, "\n", msg, ". This could either mean that\n", "DNS SPOOFING is happening or the IP address for the host\n" "and its host key have changed at the same time.\n", NULL, NULL); if ( ip_status != HOST_NEW ) { ckstrncat(errbuf, "Offending key for IP in ", ERRBUFSZ); ckstrncat(errbuf, ip_file, ERRBUFSZ); ckstrncat(errbuf, ":", ERRBUFSZ); ckstrncat(errbuf, ckitoa(ip_line), ERRBUFSZ); } error(errbuf); #ifdef KUI uq_ok(NULL, errbuf, 1, NULL, 0); #endif /* KUI */ } /* The host key has changed. */ fp = key_fingerprint(host_key, SSH_FP_MD5, SSH_FP_HEX); ckmakxmsg(errbuf,ERRBUFSZ, "@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@\n" "@ WARNING: REMOTE 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 ", type, " host key has just been changed.\n" "The fingerprint for the ", type, " key sent by the remote host is\n", fp, "\nPlease contact your system administrator.\n" "Add correct host key in ", (char*)user_hostfile, " to get rid of this message.\n" "Offending key in ", (char *)host_file, ":", ckitoa(host_line)); free(fp); error(errbuf); #ifdef KUI uq_ok(NULL, errbuf, 1, NULL, 0); #endif /* KUI */ /* * 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 == 1) { ckmakmsg(errbuf,ERRBUFSZ, type, " host key for ", host, "has changed and you have requested strict checking."); error(errbuf); #ifdef KUI uq_ok(NULL, errbuf, 1, NULL, 0); #endif /* KUI */ goto fail; } else if (options.strict_host_key_checking == 2) { /* The default */ char prompt[1024]; fp = key_fingerprint(host_key, SSH_FP_MD5, SSH_FP_HEX); snprintf(prompt, sizeof(prompt), "The authenticity of host '%.200s (%s)' can't be established.\n" "%s key fingerprint is %s.\n", host, ip, type, fp); free(fp); if (!confirm(prompt, "Are you sure you want to continue connecting (yes/no)? ", -1)) { error("Aborted by user!"); goto fail; } } else if (options.strict_host_key_checking != 0) { error("Invalid Strict Host Key Check value!"); goto fail; } /* * If strict host key checking has not been requested, allow * the connection but without password authentication or * agent forwarding. */ if (options.password_authentication) { error("Password authentication is disabled to avoid trojan horses."); options.password_authentication = 0; } if (options.forward_agent) { error("Agent forwarding is disabled to avoid trojan horses."); options.forward_agent = 0; } if (options.forward_x11) { error("X11 forwarding is disabled to avoid trojan horses."); options.forward_x11 = 0; } if (options.num_local_forwards > 0 || options.num_remote_forwards > 0) { error("Port forwarding is disabled to avoid trojan horses."); options.num_local_forwards = options.num_remote_forwards = 0; } /* * 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 whishes to * accept the authentication. */ break; case HOST_FOUND: error("Internal Error!"); goto fail; } if (options.check_host_ip && host_status != HOST_CHANGED && ip_status == HOST_CHANGED) { ckmakxmsg(errbuf,ERRBUFSZ,"Warning: the ", type, " host key for '", host, "' differs from the key for the IP address '", ip, "'\nOffending key for IP in ", (char *)ip_file, ":", ckitoa(ip_line), NULL, NULL); if (host_status == HOST_OK) { ckstrncat(errbuf, "\nMatching host key in ", ERRBUFSZ); ckstrncat(errbuf, host_file, ERRBUFSZ); ckstrncat(errbuf, ":", ERRBUFSZ); ckstrncat(errbuf, ckitoa(host_line), ERRBUFSZ); } if (options.strict_host_key_checking == 1) { log(errbuf); ckstrncat(errbuf, "\nConnection Failure: Strict Host Key Checking enabled.", ERRBUFSZ); error(errbuf); #ifdef KUI uq_ok(NULL, errbuf, 1, NULL, 0); #endif /* KUI */ goto fail; } else if (options.strict_host_key_checking == 2) { ckstrncat(errbuf,"\n", ERRBUFSZ); if (!confirm(errbuf, "Are you sure you want to continue connecting (yes/no)? ", -1)) { error("Aborted by user!"); goto fail; } } else if (options.strict_host_key_checking != 0) { log(errbuf); error("Invalid Strict Host Key Check value!"); goto fail; } else { log(errbuf); } } free(ip); return(0); fail: free(ip); return(-1); } int verify_host_key(char *host, struct sockaddr *hostaddr, Key *host_key) { struct _stat st; /* return ok if the key can be found in an old keyfile */ if (stat(options.system_hostfile2, &st) == 0 || stat(options.user_hostfile2, &st) == 0) { if (check_host_key(host, hostaddr, host_key, /*readonly*/ 1, options.user_hostfile2, options.system_hostfile2) == 0) return 0; } return check_host_key(host, hostaddr, host_key, /*readonly*/ 0, options.user_hostfile, options.system_hostfile); } /* * Starts a dialog with the server, and authenticates the current user on the * server. This does not need any extra privileges. The basic connection * to the server must already have been established before this is called. * If login fails, this function prints an error and never returns. * This function does not require super-user privileges. */ int ssh_login(Sensitive *sensitive, const char *orighost, struct sockaddr *hostaddr, struct passwd *pw) { char *host, *cp; char *server_user, *local_user; local_user = strdup(pw->pw_name); server_user = options.user ? options.user : local_user; /* Convert the user-supplied hostname into all lowercase. */ host = strdup(orighost); for (cp = host; *cp; cp++) if (isupper(*cp)) *cp = tolower(*cp); /* Exchange protocol version identification strings with the server. */ if (ssh_exchange_identification() < 0) return(-1); /* Put the connection into non-blocking mode. */ packet_set_nonblocking(); /* key exchange */ /* authenticate user */ if (compat20) { if (ssh_kex2(host, hostaddr) < 0) return -1; if (ssh_userauth2(local_user, server_user, host, sensitive) < 0) return -1; } else { if (ssh_kex(host, hostaddr) < 0) return -1; if (ssh_userauth1(local_user, server_user, host, sensitive) < 0) return -1; } return(0); } static int show_key_from_file(const char *file, const char *host, int keytype) { Key *found; char *fp; int line, ret; found = key_new(keytype); if ((ret = lookup_key_in_hostfile_by_type(file, host, keytype, found, &line))) { fp = key_fingerprint(found, SSH_FP_MD5, SSH_FP_HEX); log("WARNING: %s key found for host %s\n" "in %s:%d\n" "%s key fingerprint %s.", key_type(found), host, file, line, key_type(found), fp); free(fp); } key_free(found); return (ret); } /* print all known host keys for a given host, but skip keys of given type */ static int show_other_keys(const char *host, Key *key) { int type[] = { KEY_RSA1, KEY_RSA, KEY_DSA, -1}; int i, found = 0; for (i = 0; type[i] != -1; i++) { if (type[i] == key->type) continue; if (type[i] != KEY_RSA1 && show_key_from_file(options.user_hostfile2, host, type[i])) { found = 1; continue; } if (type[i] != KEY_RSA1 && show_key_from_file(options.system_hostfile2, host, type[i])) { found = 1; continue; } if (show_key_from_file(options.user_hostfile, host, type[i])) { found = 1; continue; } if (show_key_from_file(options.system_hostfile, host, type[i])) { found = 1; continue; } debug2("no key of type %d for host %s", type[i], host); } return (found); } void ssh_put_password(char *password) { int size; char *padded; if (datafellows & SSH_BUG_PASSWORDPAD) { packet_put_cstring(password); return; } size = roundup(strlen(password) + 1, 32); padded = malloc(size); memset(padded, 0, size); strlcpy(padded, password, size); packet_put_string(padded, size); memset(padded, 0, size); free(padded); } /* Session id for the current session. */ u_char session_id[16]; u_int supported_authentications = 0; #ifdef KRB5 krb5_context ssh_context = NULL; krb5_auth_context auth_context = NULL; #endif /* KRB5 */ /* * Checks if the user has an authentication agent, and if so, tries to * authenticate using the agent. */ static int try_agent_authentication(void) { int type; char *comment; AuthenticationConnection *auth; u_char response[16]; u_int i; Key *key; BIGNUM *challenge; /* Get connection to the agent. */ auth = ssh_get_authentication_connection(); if (!auth) return 0; challenge = BN_new(); if (challenge == NULL) { fatal("try_agent_authentication: BN_new_failed"); return(0); } /* Loop through identities served by the agent. */ for (key = ssh_get_first_identity(auth, &comment, 1); key != NULL; key = ssh_get_next_identity(auth, &comment, 1)) { /* Try this identity. */ debug1("Trying RSA authentication via agent with: %s", comment); free(comment); /* Tell the server that we are willing to authenticate using this key. */ packet_start(SSH_CMSG_AUTH_RSA); packet_put_bignum(key->rsa->n); packet_send(); packet_write_wait(); /* Wait for server's response. */ type = packet_read(); /* The server sends failure if it doesn\'t like our key or does not support RSA authentication. */ if (type == SSH_SMSG_FAILURE) { debug1("Server refused our key."); key_free(key); continue; } /* Otherwise it should have sent a challenge. */ if (type != SSH_SMSG_AUTH_RSA_CHALLENGE) { packet_disconnect("Protocol error during RSA authentication: %d", type); return 0; } packet_get_bignum(challenge); packet_check_eom(); debug1("Received RSA challenge from server."); /* Ask the agent to decrypt the challenge. */ if (!ssh_decrypt_challenge(auth, key, challenge, session_id, 1, response)) { /* * The agent failed to authenticate this identifier * although it advertised it supports this. Just * return a wrong value. */ log("Authentication agent failed to decrypt challenge."); memset(response, 0, sizeof(response)); } key_free(key); debug1("Sending response to RSA challenge."); /* Send the decrypted challenge back to the server. */ packet_start(SSH_CMSG_AUTH_RSA_RESPONSE); for (i = 0; i < 16; i++) packet_put_char(response[i]); packet_send(); packet_write_wait(); /* Wait for response from the server. */ type = packet_read(); /* The server returns success if it accepted the authentication. */ if (type == SSH_SMSG_SUCCESS) { ssh_close_authentication_connection(auth); BN_clear_free(challenge); debug1("RSA authentication accepted by server."); return 1; } /* Otherwise it should return failure. */ if (type != SSH_SMSG_FAILURE) { packet_disconnect("Protocol error waiting RSA auth response: %d", type); return 0; } } ssh_close_authentication_connection(auth); BN_clear_free(challenge); debug1("RSA authentication using agent refused."); return 0; } /* * Computes the proper response to a RSA challenge, and sends the response to * the server. */ static void respond_to_rsa_challenge(BIGNUM * challenge, RSA * prv) { u_char buf[32], response[16]; MD5_CTX md; int i, len; /* Decrypt the challenge using the private key. */ /* XXX think about Bleichenbacher, too */ if (rsa_private_decrypt(challenge, challenge, prv) <= 0) { packet_disconnect("respond_to_rsa_challenge: rsa_private_decrypt failed"); return; } /* Compute the response. */ /* The response is MD5 of decrypted challenge plus session id. */ len = BN_num_bytes(challenge); if (len <= 0 || len > sizeof(buf)) { packet_disconnect("respond_to_rsa_challenge: bad challenge length %d", len); return; } memset(buf, 0, sizeof(buf)); BN_bn2bin(challenge, buf + sizeof(buf) - len); MD5_Init(&md); MD5_Update(&md, buf, 32); MD5_Update(&md, session_id, 16); MD5_Final(response, &md); debug1("Sending response to host key RSA challenge."); /* Send the response back to the server. */ packet_start(SSH_CMSG_AUTH_RSA_RESPONSE); for (i = 0; i < 16; i++) packet_put_char(response[i]); packet_send(); packet_write_wait(); memset(buf, 0, sizeof(buf)); memset(response, 0, sizeof(response)); memset(&md, 0, sizeof(md)); } /* * Checks if the user has authentication file, and if so, tries to authenticate * the user using it. */ int try_rsa_authentication(int idx) { BIGNUM *challenge; Key *public, *private; char buf[300], passphrase[300]="", *comment, *authfile; int type, i, quit; public = options.identity_keys[idx]; authfile = options.identity_files[idx]; comment = strdup(authfile); debug1("Trying RSA authentication with key: %s", comment); /* Tell the server that we are willing to authenticate using this key. */ packet_start(SSH_CMSG_AUTH_RSA); packet_put_bignum(public->rsa->n); packet_send(); packet_write_wait(); /* Wait for server's response. */ type = packet_read(); /* * The server responds with failure if it doesn\'t like our key or * doesn\'t support RSA authentication. */ if (type == SSH_SMSG_FAILURE) { debug1("Server refused our key."); free(comment); return 0; } /* Otherwise, the server should respond with a challenge. */ if (type != SSH_SMSG_AUTH_RSA_CHALLENGE) { packet_disconnect("Protocol error during RSA authentication: %d", type); return 0; } /* Get the challenge from the packet. */ challenge = BN_new(); if (challenge == NULL) { fatal("try_rsa_authentication: BN_new_failed"); return(0); } packet_get_bignum(challenge); packet_check_eom(); debug1("Received RSA challenge from server."); /* * If the key is not stored in external hardware, we have to * load the private key. Try first with empty passphrase; if it * fails, ask for a passphrase. */ if (public->flags & KEY_FLAG_EXT) private = public; else private = key_load_private_type(KEY_RSA1, authfile, "", NULL); if (private == NULL && !options.batch_mode) { int ok; snprintf(buf, sizeof(buf), "Enter passphrase for RSA key '%.100s': ", comment); for (i = 0; i < options.number_of_password_prompts; i++) { if ( i == 0 && pwbuf[0] && pwflg ) { ckstrncpy(passphrase,pwbuf,sizeof(passphrase)); #ifdef OS2 if ( pwcrypt ) ck_encrypt((char *)passphrase); #endif /* OS2 */ } else { ok = uq_txt(buf,"Passphrase: ",2,NULL,passphrase, sizeof(passphrase),NULL,DEFAULT_UQ_TIMEOUT); if ( !ok ) { quit = 1; break; } } if (strcmp(passphrase, "") != 0) { private = key_load_private_type(KEY_RSA1, authfile, passphrase, NULL); quit = 0; } else { debug2("no passphrase given, try next key"); quit = 1; } memset(passphrase, 0, strlen(passphrase)); if (private != NULL || quit) break; debug2("bad passphrase given, try again..."); } } /* We no longer need the comment. */ free(comment); if (private == NULL) { if (!options.batch_mode) error("Bad passphrase."); #ifdef KUI uq_ok(NULL, "Bad passphrase.", 1, NULL, 0); #endif /* KUI */ /* Send a dummy response packet to avoid protocol error. */ packet_start(SSH_CMSG_AUTH_RSA_RESPONSE); for (i = 0; i < 16; i++) packet_put_char(0); packet_send(); packet_write_wait(); /* Expect the server to reject it... */ packet_read_expect(SSH_SMSG_FAILURE); BN_clear_free(challenge); return 0; } /* Compute and send a response to the challenge. */ respond_to_rsa_challenge(challenge, private->rsa); /* Destroy the private key unless it in external hardware. */ if (!(private->flags & KEY_FLAG_EXT)) key_free(private); /* We no longer need the challenge. */ BN_clear_free(challenge); /* Wait for response from the server. */ type = packet_read(); if (type == SSH_SMSG_SUCCESS) { debug1("RSA authentication accepted by server."); return 1; } if (type != SSH_SMSG_FAILURE) { packet_disconnect("Protocol error waiting RSA auth response: %d", type); return 0; } debug1("RSA authentication refused."); return 0; } /* * Tries to authenticate the user using combined rhosts or /etc/hosts.equiv * authentication and RSA host authentication. */ static int try_rhosts_rsa_authentication(const char *local_user, Key * host_key) { int type; BIGNUM *challenge; debug1("Trying rhosts or /etc/hosts.equiv with RSA host authentication."); /* Tell the server that we are willing to authenticate using this key. */ packet_start(SSH_CMSG_AUTH_RHOSTS_RSA); packet_put_cstring(local_user); packet_put_int(BN_num_bits(host_key->rsa->n)); packet_put_bignum(host_key->rsa->e); packet_put_bignum(host_key->rsa->n); packet_send(); packet_write_wait(); /* Wait for server's response. */ type = packet_read(); /* The server responds with failure if it doesn't admit our .rhosts authentication or doesn't know our host key. */ if (type == SSH_SMSG_FAILURE) { debug1("Server refused our rhosts authentication or host key."); return 0; } /* Otherwise, the server should respond with a challenge. */ if (type != SSH_SMSG_AUTH_RSA_CHALLENGE) { packet_disconnect("Protocol error during RSA authentication: %d", type); return 0; } /* Get the challenge from the packet. */ challenge = BN_new(); if (challenge == NULL) { fatal("try_rhosts_rsa_authentication: BN_new_failed"); return(0); } packet_get_bignum(challenge); packet_check_eom(); debug1("Received RSA challenge for host key from server."); /* Compute a response to the challenge. */ respond_to_rsa_challenge(challenge, host_key->rsa); /* We no longer need the challenge. */ BN_clear_free(challenge); /* Wait for response from the server. */ type = packet_read(); if (type == SSH_SMSG_SUCCESS) { debug1("Rhosts or /etc/hosts.equiv with RSA host authentication accepted by server."); return 1; } if (type != SSH_SMSG_FAILURE) { packet_disconnect("Protocol error waiting RSA auth response: %d", type); return 0; } debug1("Rhosts or /etc/hosts.equiv with RSA host authentication refused."); return 0; } #ifdef KRB5 static void krb5_cleanup(void *ignore) { if (auth_context) { krb5_auth_con_free(ssh_context, auth_context); auth_context = NULL; } if (ssh_context) { krb5_free_context(ssh_context); ssh_context = NULL; } } static int try_krb5_authentication(krb5_context *context, krb5_auth_context *auth_context) { krb5_error_code problem; const char *tkfile; struct _stat buf; krb5_ccache ccache = NULL; const char *remotehost; krb5_data ap; int type; krb5_ap_rep_enc_part *reply = NULL; int ret; memset(&ap, 0, sizeof(ap)); problem = krb5_init_context(context); if (problem) { debug1("try_krb5_authentication: krb5_init_context() returns: %s", krb5_get_err_text(*context,problem)); ret = 0; goto out; } problem = krb5_auth_con_init(*context, auth_context); if ( problem ) { debug1("Kerberos V5: krb5_auth_con_init failed"); ret = 0; goto out; } #ifndef HEIMDAL problem = krb5_auth_con_setflags(*context, *auth_context, KRB5_AUTH_CONTEXT_RET_TIME); if (problem) { debug1("Kerberos v5: krb5_auth_con_setflags failed"); ret = 0; goto out; } #endif tkfile = krb5_cc_default_name(*context); if (strncmp(tkfile, "FILE:", 5) == 0) { tkfile += 5; /* XXX ssh should not be suid root here but ...*/ if (stat(tkfile, &buf) == 0 #ifndef OS2 && getuid() != buf.st_uid #endif /* OS2 */ ) { debug1("Kerberos V5: could not get default ccache (permission denied): %s", tkfile); ret = 0; goto out; } } problem = krb5_cc_default(*context, &ccache); if (problem) { debug1("Kerberos v5: krb5_cc_default failed: %s", krb5_get_err_text(*context, problem)); ret = 0; goto out; } remotehost = get_canonical_hostname(1); problem = krb5_mk_req(*context, auth_context, AP_OPTS_MUTUAL_REQUIRED, "host", (char *)remotehost, NULL, ccache, &ap); if (problem) { debug1("Kerberos v5: krb5_mk_req failed: %s", krb5_get_err_text(*context, problem)); ret = 0; goto out; } packet_start(ssh_k5_is_k4 ? SSH_CMSG_AUTH_KRB4 : SSH_CMSG_AUTH_KRB5); packet_put_string((char *) ap.data, ap.length); packet_send(); packet_write_wait(); free(ap.data); ap.length = 0; type = packet_read(); switch (type) { case SSH_SMSG_FAILURE: /* Should really be SSH_SMSG_AUTH_KERBEROS_FAILURE */ debug1("Kerberos v5 authentication failed."); ret = 0; break; case SSH_SMSG_AUTH_KRB4_RESPONSE: /* for compatibility versions of OpenSSH which reuse the K4 message codes */ /* fallthrough */ case SSH_SMSG_AUTH_KRB5_RESPONSE: debug1("Kerberos v5 authentication accepted."); /* Get server's response. */ ap.data = packet_get_string((unsigned int *) &ap.length); packet_check_eom(); problem = krb5_rd_rep(*context, *auth_context, &ap, &reply); if (problem) { ret = 0; } ret = 1; break; default: packet_disconnect("Protocol error on Kerberos v5 response: %d", type); ret = 0; break; } out: if (ccache != NULL) krb5_cc_close(*context, ccache); if (reply != NULL) krb5_free_ap_rep_enc_part(*context, reply); if (ap.length > 0) { #ifdef HEIMDAL krb5_data_free(&ap); #else krb5_free_data_contents(*context, &ap); #endif } return (ret); } static void send_krb5_tgt(krb5_context context, krb5_auth_context auth_context) { int fd, type; krb5_error_code problem; krb5_data outbuf; krb5_ccache ccache = NULL; krb5_creds creds; #ifdef HEIMDAL krb5_kdc_flags flags; #else int forwardable; #endif const char *remotehost; memset(&creds, 0, sizeof(creds)); memset(&outbuf, 0, sizeof(outbuf)); fd = packet_get_connection_in(); #ifdef HEIMDAL problem = krb5_auth_con_setaddrs_from_fd(context, auth_context, &fd); if (problem) goto out; #else problem = krb5_auth_con_genaddrs(context, auth_context, fd, KRB5_AUTH_CONTEXT_GENERATE_REMOTE_FULL_ADDR | KRB5_AUTH_CONTEXT_GENERATE_LOCAL_FULL_ADDR); if (problem) { debug1("krb5_auth_con_genaddrs: %.100s", error_message(problem)); goto out; } #endif problem = krb5_cc_default(context, &ccache); if (problem) goto out; problem = krb5_cc_get_principal(context, ccache, &creds.client); if (problem) goto out; remotehost = get_canonical_hostname(1); #ifdef HEIMDAL problem = krb5_build_principal(context, &creds.server, strlen(creds.client->realm), creds.client->realm, "krbtgt", creds.client->realm, NULL); #else problem = krb5_build_principal(context, &creds.server, creds.client->realm.length, creds.client->realm.data, "krbtgt", creds.client->realm.data, NULL); #endif if (problem) goto out; creds.times.endtime = 0; #ifdef HEIMDAL flags.i = 0; flags.b.forwarded = 1; flags.b.forwardable = krb5_config_get_bool(context, NULL, "libdefaults", "forwardable", NULL); problem = krb5_get_forwarded_creds(context, auth_context, ccache, flags.i, remotehost, &creds, &outbuf); if (problem) goto out; #else forwardable = 1; problem = krb5_fwd_tgt_creds (context, auth_context, (char *)remotehost, creds.client, creds.server, ccache, forwardable, &outbuf); if (problem) goto out; #endif packet_start(ssh_k5_is_k4 ? SSH_CMSG_HAVE_KRB4_TGT : SSH_CMSG_HAVE_KRB5_TGT); packet_put_string((char *)outbuf.data, outbuf.length); packet_send(); packet_write_wait(); type = packet_read(); if (type == SSH_SMSG_SUCCESS) { char *pname; krb5_unparse_name(context, creds.client, &pname); debug1("Kerberos v5 TGT forwarded (%s).", pname); free(pname); } else debug1("Kerberos v5 TGT forwarding failed."); return; out: if (problem) debug1("Kerberos v5 TGT forwarding failed: %s", krb5_get_err_text(context, problem)); if (creds.client) krb5_free_principal(context, creds.client); if (creds.server) krb5_free_principal(context, creds.server); if (ccache) krb5_cc_close(context, ccache); if (outbuf.data) free(outbuf.data); } #endif /* KRB5 */ #ifdef KRB4 static int try_krb4_authentication(void) { KTEXT_ST auth; /* Kerberos data */ char *reply; char inst[INST_SZ]; char *realm; #ifdef OS2 LEASH_CREDENTIALS cred; #else /* OS2 */ CREDENTIALS cred; #endif /* OS2 */ int r, type; socklen_t slen; Schedule schedule; u_long checksum, cksum; MSG_DAT msg_data; struct sockaddr_in local, foreign; /* Don't do anything if we don't have any tickets. */ if (ck_krb4_get_tkts() < 1) return 0; ckstrncpy(inst, (char *) krb_get_phost((char *)get_canonical_hostname(1)), INST_SZ); realm = (char *) krb_realmofhost((char *)get_canonical_hostname(1)); if (!realm) { debug1("Kerberos V4: no realm for: %s", (char *)get_canonical_hostname(1)); return 0; } /* This can really be anything. */ checksum = (u_long) getpid(); r = krb_mk_req(&auth, KRB4_SERVICE_NAME, inst, realm, checksum); if (r != KSUCCESS) { debug1("Kerberos V4 krb_mk_req failed: %s (%d)", krb_get_err_text_entry(r), r); return 0; } /* Get session key to decrypt the server's reply with. */ r = krb_get_cred(KRB4_SERVICE_NAME, inst, realm, &cred); if (r != KSUCCESS) { debug1("Kerberos V4: get_cred failed: %s (%d)", krb_get_err_text_entry(r), r); return 0; } des_key_sched(cred.session, schedule); /* Send authentication info to server. */ packet_start(SSH_CMSG_AUTH_KRB4); packet_put_string((char *) auth.dat, auth.length); packet_send(); packet_write_wait(); /* Zero the buffer. */ (void) memset(auth.dat, 0, MAX_KTXT_LEN); slen = sizeof(local); memset(&local, 0, sizeof(local)); if (getsockname(packet_get_connection_in(), (struct sockaddr *) & local, &slen) < 0) debug1("getsockname failed: %s", #ifdef NT sock_strerror(sock_lasterror()) #else strerror(errno) #endif ); slen = sizeof(foreign); memset(&foreign, 0, sizeof(foreign)); if (getpeername(packet_get_connection_in(), (struct sockaddr *) & foreign, &slen) < 0) { debug1("getpeername failed: %s", #ifdef NT sock_strerror(sock_lasterror()) #else strerror(errno) #endif ); fatal_cleanup(); return(-1); } /* Get server reply. */ type = packet_read(); switch (type) { case SSH_SMSG_FAILURE: /* Should really be SSH_SMSG_AUTH_KRB4_FAILURE */ debug1("Kerberos V4 authentication failed."); return 0; break; case SSH_SMSG_AUTH_KRB4_RESPONSE: /* SSH_SMSG_AUTH_KRB4_SUCCESS */ debug1("Kerberos V4 authentication accepted."); /* Get server's response. */ reply = packet_get_string((u_int *) &auth.length); if ( auth.length >= MAX_KTXT_LEN ) { fatal("Keberos V4: Malformed response from server."); return -1; } memcpy(auth.dat, reply, auth.length); free(reply); packet_check_eom(); /* * If his response isn't properly encrypted with the session * key, and the decrypted checksum fails to match, he's * bogus. Bail out. */ r = krb_rd_priv(auth.dat, auth.length, schedule, #ifdef KRB524 cred.session, #else /* KRB524 */ &cred.session, #endif /* KRB524 */ &foreign, &local, &msg_data); if (r != KSUCCESS) { debug1("Kerberos V4 krb_rd_priv failed: %s (%d)", krb_get_err_text_entry(r),r); packet_disconnect("Kerberos V4 challenge failed!"); return 0; } /* Fetch the (incremented) checksum that we supplied in the request. */ (void) memcpy((char *) &cksum, (char *) msg_data.app_data, sizeof(cksum)); cksum = ntohl(cksum); /* If it matches, we're golden. */ if (cksum == checksum + 1) { debug1("Kerberos V4 challenge successful."); return 1; } else { packet_disconnect("Kerberos V4 challenge failed!"); return 0; } break; default: packet_disconnect("Protocol error on Kerberos V4 response: %d", type); return 0; } return 0; } #endif /* KRB4 */ #ifdef AFS static void send_krb4_tgt(void) { #ifdef OS2 LEASH_CREDENTIALS *creds; #else CREDENTIALS *creds; #endif char pname[ANAME_SZ], pinst[INST_SZ], prealm[REALM_SZ]; int problem, type; char buffer[8192]; struct _stat st; /* Don't do anything if we don't have any tickets. */ if (stat(tkt_string(), &st) < 0) return; creds = malloc(sizeof(*creds)); problem = krb_get_tf_fullname(TKT_FILE, pname, pinst, prealm); if (problem) goto out; problem = krb_get_cred("krbtgt", prealm, prealm, creds); if (problem) goto out; if (time(0) > krb_life_to_time(creds->issue_date, creds->lifetime)) { problem = RD_AP_EXP; goto out; } creds_to_radix(creds, (u_char *)buffer, sizeof(buffer)); packet_start(SSH_CMSG_HAVE_KRB4_TGT); packet_put_cstring(buffer); packet_send(); packet_write_wait(); type = packet_read(); if (type == SSH_SMSG_SUCCESS) debug1("Kerberos v4 TGT forwarded (%s%s%s@%s).", creds->pname, creds->pinst[0] ? "." : "", creds->pinst, creds->realm); else debug1("Kerberos v4 TGT rejected."); free(creds); return; out: debug1("Kerberos v4 TGT passing failed: %s", get_krb_err_txt_entry(problem)); free(creds); } static void send_afs_tokens(void) { #ifdef OS2 LEASH_CREDENTIALS creds; #else CREDENTIALS creds; #endif /* OS2 */ struct ViceIoctl parms; struct ClearToken ct; int i, type, len; char buf[2048], *p, *server_cell; char buffer[8192]; /* Move over ktc_GetToken, here's something leaner. */ for (i = 0; i < 100; i++) { /* just in case */ parms.in = (char *) &i; parms.in_size = sizeof(i); parms.out = buf; parms.out_size = sizeof(buf); if (k_pioctl(0, VIOCGETTOK, &parms, 0) != 0) break; p = buf; /* Get secret token. */ memcpy(&creds.ticket_st.length, p, sizeof(u_int)); if (creds.ticket_st.length > MAX_KTXT_LEN) break; p += sizeof(u_int); memcpy(creds.ticket_st.dat, p, creds.ticket_st.length); p += creds.ticket_st.length; /* Get clear token. */ memcpy(&len, p, sizeof(len)); if (len != sizeof(struct ClearToken)) break; p += sizeof(len); memcpy(&ct, p, len); p += len; p += sizeof(len); /* primary flag */ server_cell = p; /* Flesh out our credentials. */ strlcpy(creds.service, "afs", sizeof creds.service); creds.instance[0] = '\0'; strlcpy(creds.realm, server_cell, REALM_SZ); memcpy(creds.session, ct.HandShakeKey, DES_KEY_SZ); creds.issue_date = ct.BeginTimestamp; creds.lifetime = krb_time_to_life(creds.issue_date, ct.EndTimestamp); creds.kvno = ct.AuthHandle; snprintf(creds.pname, sizeof(creds.pname), "AFS ID %d", ct.ViceId); creds.pinst[0] = '\0'; /* Encode token, ship it off. */ if (creds_to_radix(&creds, (u_char *) buffer, sizeof buffer) <= 0) break; packet_start(SSH_CMSG_HAVE_AFS_TOKEN); packet_put_cstring(buffer); packet_send(); packet_write_wait(); /* Roger, Roger. Clearance, Clarence. What's your vector, Victor? */ type = packet_read(); if (type == SSH_SMSG_FAILURE) debug1("AFS token for cell rejected: %s", server_cell); else if (type != SSH_SMSG_SUCCESS) { packet_disconnect("Protocol error on AFS token response: %d", type); return 0; } } } #endif /* AFS */ /* * Tries to authenticate with any string-based challenge/response system. * Note that the client code is not tied to s/key or TIS. */ static int try_challenge_response_authentication(void) { int type, i; u_int clen; char prompt[1024], response[256]=""; char *challenge; debug1("Doing challenge response authentication."); for (i = 0; i < options.number_of_password_prompts; i++) { int ok; /* request a challenge */ packet_start(SSH_CMSG_AUTH_TIS); packet_send(); packet_write_wait(); type = packet_read(); if (type != SSH_SMSG_FAILURE && type != SSH_SMSG_AUTH_TIS_CHALLENGE) { packet_disconnect("Protocol error: got %d in response " "to SSH_CMSG_AUTH_TIS", type); return 0; } if (type != SSH_SMSG_AUTH_TIS_CHALLENGE) { debug1("No challenge."); return 0; } challenge = packet_get_string(&clen); packet_check_eom(); snprintf(prompt, sizeof prompt, "%s%s%s", i == 0 ? "" : "Permission denied, please try again.\n", challenge, strchr(challenge, '\n') ? "" : "\n"); free(challenge); if (i != 0) bleep(BP_FAIL); if ( i == 0 && pwbuf[0] && pwflg ) { if (options.cipher == SSH_CIPHER_NONE) log("WARNING: Encryption is disabled! " "Response will be transmitted in clear text."); ckstrncpy(response,pwbuf,sizeof(response)); #ifdef OS2 if ( pwcrypt ) ck_encrypt((char *)response); #endif /* OS2 */ ok = 1; } else { if (options.cipher == SSH_CIPHER_NONE) ckstrncat(prompt,"WARNING: Encryption is disabled! " "Response will be transmitted in clear text.\n",1024); ok = uq_txt(prompt,"Response: ",2,NULL,response, sizeof(response), NULL,DEFAULT_UQ_TIMEOUT); } if (!ok || strcmp(response, "") == 0) break; packet_start(SSH_CMSG_AUTH_TIS_RESPONSE); ssh_put_password(response); memset(response, 0, strlen(response)); packet_send(); packet_write_wait(); type = packet_read(); if (type == SSH_SMSG_SUCCESS) return 1; if (type != SSH_SMSG_FAILURE) { packet_disconnect("Protocol error: got %d in response " "to SSH_CMSG_AUTH_TIS_RESPONSE", type); return 0; } } /* failure */ return 0; } /* * Tries to authenticate with plain passwd authentication. */ static int try_password_authentication(const char *server_user, char *host) { int type, i, ok; char password[256]=""; char prompt[1024]; debug1("Doing password authentication."); for (i = 0; i < options.number_of_password_prompts; i++) { if (i != 0) bleep(BP_FAIL); snprintf(prompt, sizeof(prompt), "%s%.30s@%.128s's password: ", i == 0 ? "" : "Permission denied, please try again.\n", server_user, host); if ( i == 0 && pwbuf[0] && pwflg ) { if (options.cipher == SSH_CIPHER_NONE) log("WARNING: Encryption is disabled! Password will be transmitted in clear text."); ckstrncpy(password,pwbuf,sizeof(password)); #ifdef OS2 if ( pwcrypt ) ck_encrypt((char *)password); #endif /* OS2 */ ok = 1; } else { if (options.cipher == SSH_CIPHER_NONE) ckstrncat(prompt, "WARNING: Encryption is disabled! " "Password will be transmitted in clear text.\n",1024); ok = uq_txt(prompt,"Response: ",2,NULL,password, sizeof(password),NULL, DEFAULT_UQ_TIMEOUT); } packet_start(SSH_CMSG_AUTH_PASSWORD); if (ok) ssh_put_password(password); memset(password, 0, strlen(password)); packet_send(); packet_write_wait(); type = packet_read(); if (type == SSH_SMSG_SUCCESS) return 1; if (type != SSH_SMSG_FAILURE) { packet_disconnect("Protocol error: got %d in response to passwd auth", type); return 0; } } /* failure */ return 0; } /* * SSH1 key exchange */ int ssh_kex(char *host, struct sockaddr *hostaddr) { int i; BIGNUM *key; Key *host_key, *server_key; int bits, rbits; int ssh_cipher_default = SSH_CIPHER_3DES; u_char session_key[SSH_SESSION_KEY_LENGTH]; u_char cookie[8]; u_int supported_ciphers; u_int server_flags, client_flags; u_int32_t rand = 0; debug1("Waiting for server public key."); /* Wait for a public key packet from the server. */ packet_read_expect(SSH_SMSG_PUBLIC_KEY); /* Get cookie from the packet. */ for (i = 0; i < 8; i++) cookie[i] = packet_get_char(); /* Get the public key. */ server_key = key_new(KEY_RSA1); bits = packet_get_int(); packet_get_bignum(server_key->rsa->e); packet_get_bignum(server_key->rsa->n); rbits = BN_num_bits(server_key->rsa->n); if (bits != rbits) { log("Warning: Server lies about size of server public key: " "actual size is %d bits vs. announced %d.", rbits, bits); log("Warning: This may be due to an old implementation of ssh."); } /* Get the host key. */ host_key = key_new(KEY_RSA1); bits = packet_get_int(); packet_get_bignum(host_key->rsa->e); packet_get_bignum(host_key->rsa->n); rbits = BN_num_bits(host_key->rsa->n); if (bits != rbits) { log("Warning: Server lies about size of server host key: " "actual size is %d bits vs. announced %d.", rbits, bits); log("Warning: This may be due to an old implementation of ssh."); } /* Get protocol flags. */ server_flags = packet_get_int(); packet_set_protocol_flags(server_flags); supported_ciphers = packet_get_int(); supported_authentications = packet_get_int(); packet_check_eom(); debug1("Received server public key (%d bits) ", BN_num_bits(server_key->rsa->n)); debug1("and host key (%d bits).", BN_num_bits(host_key->rsa->n)); if (verify_host_key(host, hostaddr, host_key) == -1) { fatal("host key verification failed."); return(-1); } client_flags = SSH_PROTOFLAG_SCREEN_NUMBER | SSH_PROTOFLAG_HOST_IN_FWD_OPEN; compute_session_id(session_id, cookie, host_key->rsa->n, server_key->rsa->n); /* Generate a session key. */ arc4random_stir(); /* * Generate an encryption key for the session. The key is a 256 bit * random number, interpreted as a 32-byte key, with the least * significant 8 bits being the first byte of the key. */ for (i = 0; i < 32; i++) { if (i % 4 == 0) rand = arc4random(); session_key[i] = rand & 0xff; rand >>= 8; } /* * According to the protocol spec, the first byte of the session key * is the highest byte of the integer. The session key is xored with * the first 16 bytes of the session id. */ key = BN_new(); if (key == NULL) { fatal("ssh_kex: BN_new_failed"); return(0); } BN_set_word(key, 0); for (i = 0; i < SSH_SESSION_KEY_LENGTH; i++) { BN_lshift(key, key, 8); if (i < 16) BN_add_word(key, session_key[i] ^ session_id[i]); else BN_add_word(key, session_key[i]); } /* * Encrypt the integer using the public key and host key of the * server (key with smaller modulus first). */ if (BN_cmp(server_key->rsa->n, host_key->rsa->n) < 0) { /* Public key has smaller modulus. */ if (BN_num_bits(host_key->rsa->n) < BN_num_bits(server_key->rsa->n) + SSH_KEY_BITS_RESERVED) { fatal("respond_to_rsa_challenge: host_key %d < server_key %d + " "SSH_KEY_BITS_RESERVED %d", BN_num_bits(host_key->rsa->n), BN_num_bits(server_key->rsa->n), SSH_KEY_BITS_RESERVED); return -1; } rsa_public_encrypt(key, key, server_key->rsa); rsa_public_encrypt(key, key, host_key->rsa); } else { /* Host key has smaller modulus (or they are equal). */ if (BN_num_bits(server_key->rsa->n) < BN_num_bits(host_key->rsa->n) + SSH_KEY_BITS_RESERVED) { fatal("respond_to_rsa_challenge: server_key %d < host_key %d + " "SSH_KEY_BITS_RESERVED %d", BN_num_bits(server_key->rsa->n), BN_num_bits(host_key->rsa->n), SSH_KEY_BITS_RESERVED); return -1; } rsa_public_encrypt(key, key, host_key->rsa); rsa_public_encrypt(key, key, server_key->rsa); } /* Destroy the public keys since we no longer need them. */ RSA_free(server_key->rsa); RSA_free(host_key->rsa); if (options.cipher == SSH_CIPHER_NOT_SET) { if (cipher_mask_ssh1(1) & supported_ciphers & (1 << ssh_cipher_default)) options.cipher = ssh_cipher_default; } else if (options.cipher == SSH_CIPHER_ILLEGAL || !(cipher_mask_ssh1(1) & (1 << options.cipher))) { log("No valid SSH1 cipher, using %.100s instead.", cipher_name(ssh_cipher_default)); options.cipher = ssh_cipher_default; } /* Check that the selected cipher is supported. */ if (!(supported_ciphers & (1 << options.cipher))) { fatal("Selected cipher type %.100s not supported by server.", cipher_name(options.cipher)); return -1; } debug1("Encryption type", cipher_name(options.cipher)); /* Send the encrypted session key to the server. */ packet_start(SSH_CMSG_SESSION_KEY); packet_put_char(options.cipher); /* Send the cookie back to the server. */ for (i = 0; i < 8; i++) packet_put_char(cookie[i]); /* Send and destroy the encrypted encryption key integer. */ packet_put_bignum(key); BN_clear_free(key); /* Send protocol flags. */ packet_put_int(client_flags); /* Send the packet now. */ packet_send(); packet_write_wait(); debug1("Sent encrypted session key."); /* Set the encryption key. */ packet_set_encryption_key(session_key, SSH_SESSION_KEY_LENGTH, options.cipher); /* We will no longer need the session key here. Destroy any extra copies. */ memset(session_key, 0, sizeof(session_key)); /* * Expect a success message from the server. Note that this message * will be received in encrypted form. */ packet_read_expect(SSH_SMSG_SUCCESS); debug1("Received encrypted confirmation."); return 0; } /* * Authenticate user */ int ssh_userauth1(const char *local_user, const char *server_user, char *host, Sensitive *sensitive) { int i, type; #ifdef KRB5 krb5_context context = NULL; krb5_auth_context auth_context = NULL; #endif if (supported_authentications == 0) { fatal("ssh_userauth1: server supports no auth methods"); return -1; } /* Send the name of the user to log in as on the server. */ packet_start(SSH_CMSG_USER); packet_put_cstring(server_user); packet_send(); packet_write_wait(); /* * The server should respond with success if no authentication is * needed (the user has no password). Otherwise the server responds * with failure. */ type = packet_read(); /* check whether the connection was accepted without authentication. */ if (type == SSH_SMSG_SUCCESS) goto success; if (type != SSH_SMSG_FAILURE) { packet_disconnect("Protocol error: got %d in response to SSH_CMSG_USER", type); return -1; } #ifdef KRB5 if ((supported_authentications & (1 << (ssh_k5_is_k4 ? SSH_AUTH_KRB4 : SSH_AUTH_KRB5))) && options.krb5_authentication) { debug1("ssh_userauth1: Trying Kerberos V5 authentication."); if (try_krb5_authentication(&context, &auth_context)) { /* The server should respond with success or failure. */ type = packet_read(); if (type == SSH_SMSG_SUCCESS) goto success; if (type != SSH_SMSG_FAILURE) { if (auth_context) krb5_auth_con_free(context, auth_context); if (context) krb5_free_context(context); packet_disconnect("Protocol error: got %d in response to Kerberos V5 auth", type); return -1; } } } #endif /* KRB5 */ #ifdef KRB4 if ((supported_authentications & (1 << SSH_AUTH_KRB4)) && options.krb4_authentication) { debug1("Trying Kerberos V4 authentication."); if (try_krb4_authentication()) { /* The server should respond with success or failure. */ type = packet_read(); if (type == SSH_SMSG_SUCCESS) goto success; if (type != SSH_SMSG_FAILURE) { packet_disconnect("Protocol error: got %d in response to Kerberos V4 auth", type); return -1; } } } #endif /* KRB4 */ /* * Use rhosts authentication if running in privileged socket and we * do not wish to remain anonymous. */ if ((supported_authentications & (1 << SSH_AUTH_RHOSTS)) && options.rhosts_authentication) { debug1("Trying rhosts authentication."); packet_start(SSH_CMSG_AUTH_RHOSTS); packet_put_cstring(local_user); packet_send(); packet_write_wait(); /* The server should respond with success or failure. */ type = packet_read(); if (type == SSH_SMSG_SUCCESS) goto success; if (type != SSH_SMSG_FAILURE) { packet_disconnect("Protocol error: got %d in response to rhosts auth", type); return -1; } } /* * Try .rhosts or /etc/hosts.equiv authentication with RSA host * authentication. */ if ((supported_authentications & (1 << SSH_AUTH_RHOSTS_RSA)) && options.rhosts_rsa_authentication) { for (i = 0; i < sensitive->nkeys; i++) { if (sensitive->keys[i] != NULL && sensitive->keys[i]->type == KEY_RSA1 && try_rhosts_rsa_authentication(local_user, sensitive->keys[i])) goto success; } } /* Try RSA authentication if the server supports it. */ if ((supported_authentications & (1 << SSH_AUTH_RSA)) && options.rsa_authentication) { /* * Try RSA authentication using the authentication agent. The * agent is tried first because no passphrase is needed for * it, whereas identity files may require passphrases. */ if (try_agent_authentication()) goto success; /* Try RSA authentication for each identity. */ for (i = 0; i < options.num_identity_files; i++) if (options.identity_keys[i] != NULL && options.identity_keys[i]->type == KEY_RSA1 && try_rsa_authentication(i)) goto success; } /* Try challenge response authentication if the server supports it. */ if ((supported_authentications & (1 << SSH_AUTH_TIS)) && options.challenge_response_authentication && !options.batch_mode) { if (try_challenge_response_authentication()) goto success; } /* Try password authentication if the server supports it. */ if ((supported_authentications & (1 << SSH_AUTH_PASSWORD)) && options.password_authentication && !options.batch_mode) { if (try_password_authentication(server_user,host)) goto success; } /* All authentication methods have failed. Exit with an error message. */ fatal("Permission denied."); bleep(BP_FAIL); return -1; success: #ifdef KRB5 /* Try Kerberos v5 TGT passing. */ if ((supported_authentications & (1 << SSH_PASS_KERBEROS_TGT)) && options.kerberos_tgt_passing && context && auth_context) { if (options.cipher == SSH_CIPHER_NONE) log("WARNING: Encryption is disabled! Ticket will be transmitted in the clear!"); send_krb5_tgt(context, auth_context); } if (auth_context) krb5_auth_con_free(context, auth_context); if (context) krb5_free_context(context); #endif #ifdef AFS /* Try Kerberos v4 TGT passing if the server supports it. */ if ((supported_authentications & (1 << SSH_PASS_KERBEROS_TGT)) && options.kerberos_tgt_passing) { if (options.cipher == SSH_CIPHER_NONE) log("WARNING: Encryption is disabled! Ticket will be transmitted in the clear!"); send_krb4_tgt(); } /* Try AFS token passing if the server supports it. */ if ((supported_authentications & (1 << SSH_PASS_AFS_TOKEN)) && options.afs_token_passing && k_hasafs()) { if (options.cipher == SSH_CIPHER_NONE) log("WARNING: Encryption is disabled! Token will be transmitted in the clear!"); send_afs_tokens(); } #endif /* AFS */ return 0; } static int verify_host_key_callback(Key *hostkey) { if (verify_host_key(xxx_host, xxx_hostaddr, hostkey) < 0) return -1; return 0; } int ssh_kex2(char *host, struct sockaddr *hostaddr) { Kex *kex; #ifdef GSSAPI char *orig, *gss; int len; #endif xxx_host = host; xxx_hostaddr = hostaddr; #ifdef GSSAPI if ( ssh_gkx ) { /* Add the GSSAPI mechanisms currently supported on this client to * the key exchange algorithm proposal */ orig = myproposal[PROPOSAL_KEX_ALGS]; gss = ssh_gssapi_client_mechanisms(host); if (gss) { len = strlen(orig)+strlen(gss)+2; myproposal[PROPOSAL_KEX_ALGS]=malloc(len); snprintf(myproposal[PROPOSAL_KEX_ALGS],len,"%s,%s",gss,orig); } } #endif if (options.ciphers == (char *)-1) { log("No valid ciphers for protocol version 2 given, using defaults."); options.ciphers = NULL; } if (options.ciphers != NULL) { myproposal[PROPOSAL_ENC_ALGS_CTOS] = myproposal[PROPOSAL_ENC_ALGS_STOC] = options.ciphers; } myproposal[PROPOSAL_ENC_ALGS_CTOS] = compat_cipher_proposal(myproposal[PROPOSAL_ENC_ALGS_CTOS]); myproposal[PROPOSAL_ENC_ALGS_STOC] = compat_cipher_proposal(myproposal[PROPOSAL_ENC_ALGS_STOC]); if (options.compression) { myproposal[PROPOSAL_COMP_ALGS_CTOS] = myproposal[PROPOSAL_COMP_ALGS_STOC] = "zlib,none"; } else { myproposal[PROPOSAL_COMP_ALGS_CTOS] = myproposal[PROPOSAL_COMP_ALGS_STOC] = "none,zlib"; } if (options.macs != NULL) { myproposal[PROPOSAL_MAC_ALGS_CTOS] = myproposal[PROPOSAL_MAC_ALGS_STOC] = options.macs; } if (options.hostkeyalgorithms != NULL) myproposal[PROPOSAL_SERVER_HOST_KEY_ALGS] = options.hostkeyalgorithms; #ifdef GSSAPI if ( ssh_gkx ) { if (gss) { /* If we've got GSSAPI algorithms, then we also support the * 'null' hostkey, as a last resort */ orig=myproposal[PROPOSAL_SERVER_HOST_KEY_ALGS]; len = strlen(orig)+sizeof(",null"); myproposal[PROPOSAL_SERVER_HOST_KEY_ALGS]=malloc(len); snprintf(myproposal[PROPOSAL_SERVER_HOST_KEY_ALGS],len,"%s,null",orig); } } #endif /* start key exchange */ kex = kex_setup(myproposal); kex->kex[KEX_DH_GRP1_SHA1] = kexdh_client; kex->kex[KEX_DH_GEX_SHA1] = kexgex_client; #ifdef GSSAPI kex->kex[KEX_GSS_GRP1_SHA1] = kexgss_client; #endif kex->client_version_string=client_version_string; kex->server_version_string=server_version_string; kex->verify_host_key=&verify_host_key_callback; kex->host=host; #ifdef GSSAPI kex->options.gss_deleg_creds=options.gss_deleg_creds; #endif xxx_kex = kex; if (dispatch_run(DISPATCH_BLOCK, &kex->done, kex) < 0) return(-1); session_id2 = kex->session_id; session_id2_len = kex->session_id_len; #ifdef GSSAPI if ( kex->kex_type == KEX_GSS_GRP1_SHA1 ) datafellows |= SSH_BUG_NOREKEY; #endif #ifdef DEBUG_KEXDH /* send 1st encrypted/maced/compressed message */ packet_start(SSH2_MSG_IGNORE); packet_put_cstring("markus"); packet_send(); packet_write_wait(); #endif return (0); } /* * Authenticate user */ typedef struct Authctxt Authctxt; typedef struct Authmethod Authmethod; typedef int sign_cb_fn( Authctxt *authctxt, Key *key, u_char **sigp, u_int *lenp, u_char *data, u_int datalen); struct Authctxt { const char *server_user; const char *local_user; const char *host; const char *service; Authmethod *method; int success; char *authlist; /* pubkey */ Key *last_key; sign_cb_fn *last_key_sign; int last_key_hint; AuthenticationConnection *agent; /* hostbased */ Sensitive *sensitive; #ifdef CK_SRP int server_auth; /* SRP authenticates _both_ sides; * this is set when the client * authenticates the server. */ SRP_CTX *srp; #endif /* kbd-interactive */ int info_req_seen; /* generic */ void *methoddata; }; struct Authmethod { char *name; /* string to compare against server's list */ int (*userauth)(Authctxt *authctxt); int *enabled; /* flag in option struct that enables method */ int *batch_flag; /* flag in option struct that disables method */ }; int input_userauth_success(int type, u_int32_t seq, void *ctxt); int input_userauth_failure(int type, u_int32_t seq, void *ctxt); int input_userauth_banner(int type, u_int32_t seq, void *ctxt); int input_userauth_error(int type, u_int32_t seq, void *ctxt); int input_userauth_info_req(int type, u_int32_t seq, void *ctxt); int input_userauth_pk_ok(int type, u_int32_t seq, void *ctxt); int input_userauth_passwd_changereq(int, u_int32_t, void *); int userauth_none(Authctxt *authctxt); int userauth_pubkey(Authctxt *authctxt); int userauth_passwd(Authctxt *authctxt); int userauth_kbdint(Authctxt *authctxt); int userauth_hostbased(Authctxt *authctxt); int userauth(Authctxt *authctxt, char *authlist); #ifdef CK_SRP int userauth_srp(Authctxt *); int input_userauth_srp_reply(int type, u_int32_t packlen, void *ctxt); int input_userauth_srp_value(int type, u_int32_t packlen, void *ctxt); int input_userauth_srp_proof(int type, u_int32_t packlen, void *ctxt); #endif #ifdef GSSAPI int userauth_external(Authctxt *authctxt); int userauth_gssapi(Authctxt *authctxt); int input_gssapi_response(int type, u_int32_t plen, void *ctxt); int input_gssapi_token(int type, u_int32_t plen, void *ctxt); int input_gssapi_hash(int type, u_int32_t plen, void *ctxt); int input_gssapi_error(int, u_int32_t, void *); int input_gssapi_errtok(int, u_int32_t, void *); #endif static int sign_and_send_pubkey(Authctxt *authctxt, Key *k, sign_cb_fn *sign_callback); static void clear_auth_state(Authctxt *authctxt); static Authmethod *authmethod_get(char *authlist); static Authmethod *authmethod_lookup(const char *name); static char *authmethods_get(void); /* These two structures must be the same */ static Authmethod authmethods_data[] = { #ifdef GSSAPI {"external-keyx", userauth_external, &options.gss_authentication, NULL}, {"gssapi", userauth_gssapi, &options.gss_authentication, NULL}, #endif {"hostbased", userauth_hostbased, &options.hostbased_authentication, NULL}, {"publickey", userauth_pubkey, &options.pubkey_authentication, NULL}, #ifdef CK_SRP {SRP_GEX_SHA1, userauth_srp, &options.srp_authentication, &options.batch_mode}, #endif {"keyboard-interactive", userauth_kbdint, &options.kbd_interactive_authentication, &options.batch_mode}, {"password", userauth_passwd, &options.password_authentication, &options.batch_mode}, {"none", userauth_none, NULL, NULL}, {NULL, NULL, NULL, NULL} }; static Authmethod authmethods[] = { #ifdef GSSAPI {"external-keyx", userauth_external, &options.gss_authentication, NULL}, {"gssapi", userauth_gssapi, &options.gss_authentication, NULL}, #endif {"hostbased", userauth_hostbased, &options.hostbased_authentication, NULL}, {"publickey", userauth_pubkey, &options.pubkey_authentication, NULL}, #ifdef CK_SRP {SRP_GEX_SHA1, userauth_srp, &options.srp_authentication, &options.batch_mode}, #endif {"keyboard-interactive", userauth_kbdint, &options.kbd_interactive_authentication, &options.batch_mode}, {"password", userauth_passwd, &options.password_authentication, &options.batch_mode}, {"none", userauth_none, NULL, NULL}, {NULL, NULL, NULL, NULL} }; /* XXX internal state */ static Authmethod *current = NULL; static char *supported = NULL; static char *preferred = NULL; int authmethod_reset(void) { memcpy(&authmethods,&authmethods_data,sizeof(authmethods)); current = NULL; supported = NULL; preferred = NULL; return(0); } int ssh_userauth2(const char *local_user, const char *server_user, char *host, Sensitive *sensitive) { Authctxt authctxt; int type; if (options.challenge_response_authentication) options.kbd_interactive_authentication = 1; packet_start(SSH2_MSG_SERVICE_REQUEST); packet_put_cstring("ssh-userauth"); packet_send(); debug1("SSH2_MSG_SERVICE_REQUEST sent"); packet_write_wait(); type = packet_read(); if (type != SSH2_MSG_SERVICE_ACCEPT) { fatal("Service denied authentication request: %d", type); return -1; } if (packet_remaining() > 0) { char *reply = packet_get_string(NULL); debug2("service_accept: %s", reply); free(reply); } else { debug2("buggy server: service_accept w/o service"); } packet_check_eom(); debug1("SSH2_MSG_SERVICE_ACCEPT received"); if (options.preferred_authentications == NULL) options.preferred_authentications = authmethods_get(); /* setup authentication context */ memset(&authctxt, 0, sizeof(authctxt)); authctxt.agent = ssh_get_authentication_connection(); authctxt.server_user = server_user; authctxt.local_user = local_user; authctxt.host = host; authctxt.service = "ssh-connection"; /* service name */ authctxt.success = 0; authctxt.method = authmethod_lookup("none"); authctxt.authlist = NULL; authctxt.methoddata = NULL; authctxt.sensitive = sensitive; authctxt.info_req_seen = 0; if (authctxt.method == NULL) { fatal("ssh_userauth2: internal error: cannot send userauth none request"); return -1; } /* initial userauth request */ userauth_none(&authctxt); dispatch_init(&input_userauth_error); dispatch_set(SSH2_MSG_USERAUTH_SUCCESS, &input_userauth_success); dispatch_set(SSH2_MSG_USERAUTH_FAILURE, &input_userauth_failure); dispatch_set(SSH2_MSG_USERAUTH_BANNER, &input_userauth_banner); if (dispatch_run(DISPATCH_BLOCK, &authctxt.success, &authctxt) < 0) /* loop until success */ return -1; if (authctxt.agent != NULL) ssh_close_authentication_connection(authctxt.agent); debug1("Authentication succeeded (%s).", authctxt.method->name); return (0); } int userauth(Authctxt *authctxt, char *authlist) { if (authctxt->methoddata!=NULL) { free(authctxt->methoddata); authctxt->methoddata=NULL; } if (authlist == NULL) { authlist = authctxt->authlist; } else { if (authctxt->authlist) free(authctxt->authlist); authctxt->authlist = authlist; } for (;;) { Authmethod *method = authmethod_get(authlist); if (method == NULL) { fatal("Permission denied (%s).", authlist); bleep(BP_FAIL); return -1; } 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: %s",method->name); method->enabled = NULL; } } return 0; } int input_userauth_error(int type, u_int32_t seq, void *ctxt) { fatal("input_userauth_error: bad message during authentication: " "type %d", type); return -1; } int input_userauth_banner(int type, u_int32_t seq, void *ctxt) { char *msg, *lang; debug3("input_userauth_banner"); msg = packet_get_string(NULL); lang = packet_get_string(NULL); fprintf(stderr, "%s", msg); free(msg); free(lang); return 0; } int input_userauth_success(int type, u_int32_t seq, void *ctxt) { Authctxt *authctxt = ctxt; if (authctxt == NULL) { fatal("input_userauth_success: no authentication context"); return -1; } #ifdef CK_SRP if (strcmp(authctxt->method->name, SRP_GEX_SHA1) == 0 && !authctxt->server_auth) { fatal("SRP unauthenticated server sent a SUCCESS packet"); return(-1); } #endif if (authctxt->authlist) free(authctxt->authlist); if (authctxt->methoddata) free(authctxt->methoddata); clear_auth_state(authctxt); authctxt->success = 1; /* break out */ return 0; } int input_userauth_failure(int type, u_int32_t seq, void *ctxt) { Authctxt *authctxt = ctxt; char *authlist = NULL; int partial; if (authctxt == NULL) { fatal("input_userauth_failure: no authentication context"); return -1; } authlist = packet_get_string(NULL); partial = packet_get_char(); packet_check_eom(); if (partial != 0) log("Authenticated with partial success."); debug1("input_userauth_failure: authentications that can continue: %s", authlist); clear_auth_state(authctxt); if (userauth(authctxt, authlist) < 0) { return -1; } return 0; } int input_userauth_pk_ok(int type, u_int32_t seq, void *ctxt) { Authctxt *authctxt = ctxt; Key *key = NULL; Buffer b; int pktype, sent = 0; u_int alen, blen; char *pkalg, *fp; u_char *pkblob; if (authctxt == NULL) { fatal("input_userauth_pk_ok: no authentication context"); return -1; } 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_check_eom(); debug1("Server accepts key: pkalg: %s blen: %u lastkey: %p hint %d", pkalg, blen, authctxt->last_key, authctxt->last_key_hint); do { if (authctxt->last_key == NULL || authctxt->last_key_sign == NULL) { debug1("no last key or no sign cb"); break; } if ((pktype = key_type_from_name(pkalg)) == KEY_UNSPEC) { debug1("unknown pkalg %s", pkalg); break; } if ((key = key_from_blob(pkblob, blen)) == NULL) { debug1("no key from blob. pkalg %s", pkalg); break; } if ( key->type != pktype ) { error("input_userauth_pk_ok: type mismatch " "for decoded key (received %d, expected %d)", key->type, pktype); break; } fp = key_fingerprint(key, SSH_FP_MD5, SSH_FP_HEX); debug1("input_userauth_pk_ok: fp %s", fp); free(fp); if (!key_equal(key, authctxt->last_key)) { debug1("key != last_key"); break; } sent = sign_and_send_pubkey(authctxt, key, authctxt->last_key_sign); } while(0); if (key != NULL) key_free(key); free(pkalg); free(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) if (userauth(authctxt, NULL) < 0) return -1; return 0; } #ifdef CK_SRP void fatal_bignum_error(); int userauth_srp(Authctxt *authctxt) { if (srp_attempt++ >= options.number_of_password_prompts) return 0; #ifdef COMMENT /* Merged with the prompt */ if (srp_attempt != 1) { error("Permission denied, please try again."); bleep(BP_FAIL); } #endif /* COMMENT */ /* Allocate an SRP context and update the authentication context. */ authctxt->srp = SRP_CTX_new(); authctxt->server_auth = 0; /* We begin by sending an authorization request for the method. */ packet_start(SSH2_MSG_USERAUTH_REQUEST); packet_put_cstring(authctxt->server_user); packet_put_cstring(authctxt->service); packet_put_cstring(authctxt->method->name); packet_send(); /* Round 1 begins when we get the reply from the server. */ dispatch_set(SSH2_MSG_USERAUTH_SRP_REPLY, &input_userauth_srp_reply); return 1; } /* Round 1: get the SRP parameters and send the public value e. */ static int input_userauth_srp_reply(int type, u_int32_t seq, void *ctxt) { int i, plen; BIGNUM *tmp; Authctxt *authctxt; SRP_CTX *srp; /* Make sure we're where we're supposed to be. */ authctxt = ctxt; if (authctxt == NULL || authctxt->srp == NULL) { fatal("input_userauth_srp_reply: no context"); return -1; } srp = authctxt->srp; dispatch_set(SSH2_MSG_USERAUTH_SRP_REPLY, NULL); /* The server sent us the prime p, generator g, and salt. */ packet_get_bignum2(srp->p); packet_get_bignum2(srp->g); srp->salt = packet_get_string(&srp->slen); packet_check_eom(); /* * Make sure the group is OK. XXX note that there is no check * that this is really the group that the user chose when * he or she created the verifier (that requires persistent * client state). */ if (!is_safe_group(srp->p, srp->g)) { packet_disconnect("unknown/unsafe SRP group"); return -1; } /* We'll need p-1 later. */ if (!BN_sub(srp->pm1, srp->p, BN_value_one())) goto err; /* * The random exponent a is ALEN bits long, or the size of p, * whichever is less. */ plen = BN_num_bits(srp->p); i = ALEN; if (plen < ALEN) i = plen; /* * Generate the random exponent a. Loop until lg(p) < a < p-1. * This guarantees that 1 < e < p-1 and that e isn't some * obvious power of g. */ if ((tmp = BN_new()) == NULL) goto err; if (!BN_set_word(tmp, plen)) goto err; for (;;) { if (!BN_rand(srp->a, i, 0, 0)) goto err; if (BN_ucmp(srp->a, tmp) > 0 && BN_ucmp(srp->a, srp->pm1) < 0) break; } BN_free(tmp); /* Generate the public value e = g^a mod p. */ if (!BN_mod_exp(srp->e, srp->g, srp->a, srp->p, srp->ctx)) goto err; /* Send it. */ packet_start(SSH2_MSG_USERAUTH_SRP_VALUE); packet_put_bignum2(srp->e); packet_send(); /* Round 2 begins when we get the server's public value. */ dispatch_set(SSH2_MSG_USERAUTH_SRP_VALUE, &input_userauth_srp_value); return 0; err: fatal_bignum_error(); return -1; } static int input_userauth_srp_value(int type, u_int32_t seq, void *ctxt) { int i, plen, ok; char passphrase[256]="", prompt[300]; BIGNUM *x, *K; Authctxt *authctxt; SRP_CTX *srp; /* Make sure we're where we're supposed to be. */ authctxt = ctxt; if (authctxt == NULL || authctxt->srp == NULL) { fatal("input_userauth_srp_value: no context"); return -1; } srp = authctxt->srp; dispatch_set(SSH2_MSG_USERAUTH_SRP_VALUE, NULL); /* * The server sent f = (v + g^b) mod p, where v is the * user's verifier and b is another random number. */ packet_get_bignum2(srp->f); packet_check_eom(); /* Sanity check f. Abort if f is not in (1, p-1). */ if (BN_cmp(srp->f, BN_value_one()) <= 0 || BN_ucmp(srp->f, srp->pm1) >= 0) { packet_disconnect("bad SRP f value"); return -1; } /* Calculate u from f. */ srp->u = srp_u_calc(srp->f); if (srp->u == 0) { packet_disconnect("bad SRP u value"); return -1; } /* Get the passphrase from the user. */ /* * XXX it would actually be nice if we could display the prompt * and set no-echo mode earlier, and read the passphrase here, * since the calculations above can be lengthy and could happen * while the user is typing (we only need the passphrase in * memory at this point), but the cli_* routines don't have an * API for that. :-( */ if (srp_attempt != 1) bleep(BP_FAIL); if ( srp_attempt == 1 && pwbuf[0] && pwflg ) { ckstrncpy(passphrase,pwbuf,sizeof(passphrase)); #ifdef OS2 if ( pwcrypt ) ck_encrypt((char *)passphrase); #endif /* OS2 */ ok = 1; } else { snprintf(prompt, sizeof(prompt), "%sEnter %.30s@%.128s's SRP passphrase: ", srp_attempt == 1 ? "" : "Permission denied, please try again.\n", authctxt->server_user, authctxt->host); ok = uq_txt(prompt,"Passphrase: ",2,NULL,passphrase, sizeof(passphrase),NULL, DEFAULT_UQ_TIMEOUT); } plen = ok ? strlen(passphrase) : 0; /* Calculate x = HASH(salt | HASH(username | passphrase)). */ if ((x = BN_new()) == NULL) goto err; srp_x_calc(srp->salt, srp->slen, authctxt->server_user, ok ? passphrase : "", x); memset(passphrase, 0, sizeof(passphrase)); /* Calculate v = g^x mod p. */ if (!BN_mod_exp(srp->v, srp->g, x, srp->p, srp->ctx)) goto err; /* Now the shared secret K = (f - v) ^ (a + u * x) mod p. */ if (!BN_mul_word(x, srp->u)) goto err; if (!BN_add(x, srp->a, x)) /* x = (a + u * x) */ goto err; if ((K = BN_new()) == NULL) goto err; if (BN_copy(K, srp->f) == NULL) /* use K as temp */ goto err; if (BN_cmp(K, srp->v) < 0) { if (!BN_add(K, K, srp->p)) goto err; } if (!BN_sub(srp->a, K, srp->v)) /* a = (f - v) */ goto err; if (!BN_mod_exp(K, srp->a, x, srp->p, srp->ctx)) goto err; BN_clear_free(x); /* Calculate the exchange hash and proofs. */ srp_hash_calc(authctxt->server_user, authctxt->service, authctxt->method->name, srp, K); BN_clear_free(K); /* Send the client authenticator. */ packet_start(SSH2_MSG_USERAUTH_SRP_PROOF); packet_put_string(srp->m1, srp->m1len); packet_send(); /* Wait for the server's authenticator. */ dispatch_set(SSH2_MSG_USERAUTH_SRP_PROOF, &input_userauth_srp_proof); return 0; err: fatal_bignum_error(); return -1; } /* Round 3: check the proofs. */ static int input_userauth_srp_proof(int type, u_int32_t seq, void *ctxt) { int i; u_int mlen; u_char *mbuf; Authctxt *authctxt; SRP_CTX *srp; /* Make sure we're where we're supposed to be. */ authctxt = ctxt; if (authctxt == NULL || authctxt->srp == NULL) { fatal("input_userauth_srp_proof: no context"); return -1; } srp = authctxt->srp; dispatch_set(SSH2_MSG_USERAUTH_SRP_PROOF, NULL); /* Get the server's version. */ i = packet_get_char(); mbuf = NULL; if (i) mbuf = packet_get_string(&mlen); packet_check_eom(); /* If the flag is set, we were OK; check the server. */ if (i) { /* * If we were authenticated, but the server was not, * there is likely to be a serious problem. */ if (mlen != srp->m2len || memcmp(mbuf, srp->m2, mlen) != 0) { packet_disconnect("SRP server authentication failure!"); return -1; } authctxt->server_auth = 1; free(mbuf); } /* * Done. Free the SRP context, and return to await the server's * reply packet. */ SRP_CTX_free(srp); return 0; } #endif /* SRP */ #ifdef GSSAPI int userauth_gssapi(Authctxt *authctxt) { Gssctxt *gssctxt = NULL; static gss_OID_set supported = NULL; OM_uint32 min; int ok=0; /* Things work better if we send one mechanism at a time, rather * than them all at once. This means that if we fail at some point * in the middle of a negotiation, we can come back and try something * different. */ if ( datafellows & SSH_OLD_GSSAPI ) return 0; /* Before we offer a mechanism, check that we can support it. Don't * bother trying to get credentials - as the standard fallback will * deal with that kind of failure. */ if ( supported == NULL ) if (gss_indicate_mechs(&min, &supported) == GSS_S_UNAVAILABLE) return 0; while ( gssapi_mechcount && !ok ) { if ( gssctxt ) ssh_gssapi_delete_ctx(&gssctxt); ssh_gssapi_build_ctx(&gssctxt); ssh_gssapi_set_oid(gssctxt,&supported->elements[gssapi_mech]); /* The DER encoding below only works for lengths<128, * so check this here */ if ( supported->elements[gssapi_mech].length < 128 && !GSS_ERROR(ssh_gssapi_import_name(gssctxt, authctxt->host))) { ok = 1; /* Mechanism works */ } else { gssapi_mech++; } } if ( !ok ) return 0; authctxt->methoddata=(void *)gssctxt; 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_int(1); /* The newest gsskeyex draft stipulates that OIDs should * be DER encoded, so we need to add the object type and * length information back on */ if ( datafellows & SSH_BUG_GSSAPI_BER ) { packet_put_string(supported->elements[gssapi_mech].elements, supported->elements[gssapi_mech].length); } else { packet_put_int((supported->elements[gssapi_mech].length)+2); packet_put_char(0x06); packet_put_char(supported->elements[gssapi_mech].length); packet_put_raw(supported->elements[gssapi_mech].elements, supported->elements[gssapi_mech].length); } packet_send(); packet_write_wait(); dispatch_set(SSH2_MSG_USERAUTH_GSSAPI_RESPONSE,&input_gssapi_response); dispatch_set(SSH2_MSG_USERAUTH_GSSAPI_TOKEN,&input_gssapi_token); dispatch_set(SSH2_MSG_USERAUTH_GSSAPI_ERROR,&input_gssapi_error); dispatch_set(SSH2_MSG_USERAUTH_GSSAPI_ERRTOK,&input_gssapi_errtok); gssapi_mech++; /* Move along to the next candidate */ return 1; } int input_gssapi_response(int type, u_int32_t seq, void *ctxt) { Authctxt *authctxt = ctxt; Gssctxt *gssctxt; OM_uint32 status,ms; int oidlen; char *oidv; gss_buffer_desc send_tok; if (authctxt == NULL) { fatal("input_gssapi_response: no authentication context"); return -1; } gssctxt = authctxt->methoddata; /* Setup our OID */ oidv=packet_get_string(&oidlen); if ( datafellows & SSH_BUG_GSSAPI_BER ) { if ( !ssh_gssapi_check_oid(gssctxt,oidv,oidlen) ) { fatal("Server returned different OID than expected"); return -1; } ssh_gssapi_set_oid_data(gssctxt,oidv,oidlen); } else { if ( oidv[0] != 0x06 || oidv[1] != oidlen-2 ) { debug1("Badly encoded mechanism OID received"); clear_auth_state(authctxt); userauth(authctxt,NULL); return 0; } if ( !ssh_gssapi_check_oid(gssctxt,oidv+2,oidlen-2) ) { fatal("Server returned different OID than expected"); return -1; } ssh_gssapi_set_oid_data(gssctxt,oidv+2,oidlen-2); } packet_check_eom(); status = ssh_gssapi_init_ctx(gssctxt, options.gss_deleg_creds, GSS_C_NO_BUFFER, &send_tok, NULL); if (GSS_ERROR(status)) { if ( send_tok.length>0 ) { packet_start(SSH2_MSG_USERAUTH_GSSAPI_ERRTOK); packet_put_string(send_tok.value,send_tok.length); packet_send(); packet_write_wait(); } /* Start again with next method on list */ debug1("input_gssapi_response. Trying to start again"); clear_auth_state(authctxt); return userauth(authctxt,NULL); } /* We must have data to send */ packet_start(SSH2_MSG_USERAUTH_GSSAPI_TOKEN); packet_put_string(send_tok.value,send_tok.length); packet_send(); packet_write_wait(); gss_release_buffer(&ms, &send_tok); return 0; } int input_gssapi_token(int type, u_int32_t seq, void *ctxt) { Authctxt *authctxt = ctxt; Gssctxt *gssctxt; gss_buffer_desc send_tok,recv_tok; OM_uint32 status; if (authctxt == NULL) { fatal("input_gssapi_response: no authentication context"); return -1; } gssctxt = authctxt->methoddata; recv_tok.value=packet_get_string(&recv_tok.length); status=ssh_gssapi_init_ctx(gssctxt, options.gss_deleg_creds, &recv_tok, &send_tok, NULL); packet_check_eom(); if (GSS_ERROR(status)) { if ( send_tok.length>0 ) { packet_start(SSH2_MSG_USERAUTH_GSSAPI_ERRTOK); packet_put_string(send_tok.value,send_tok.length); packet_send(); packet_write_wait(); } /* Start again with the next method in the list */ debug1("input_gssapi_token. Trying to start again"); clear_auth_state(authctxt); return userauth(authctxt,NULL); } if (send_tok.length>0) { packet_start(SSH2_MSG_USERAUTH_GSSAPI_TOKEN); packet_put_string(send_tok.value,send_tok.length); packet_send(); packet_write_wait(); } if (status == GSS_S_COMPLETE) { /* If that succeeded, send a exchange complete message */ packet_start(SSH2_MSG_USERAUTH_GSSAPI_EXCHANGE_COMPLETE); packet_send(); packet_write_wait(); } return 0; } int input_gssapi_errtok(int type, u_int32_t plen, void *ctxt) { Authctxt *authctxt = ctxt; Gssctxt *gssctxt; gss_buffer_desc send_tok,recv_tok; OM_uint32 status; if (authctxt == NULL) { fatal("input_gssapi_response: no authentication context"); return -1; } gssctxt = authctxt->methoddata; recv_tok.value=packet_get_string(&recv_tok.length); /* Stick it into GSSAPI and see what it says */ status=ssh_gssapi_init_ctx(gssctxt, options.gss_deleg_creds, &recv_tok, &send_tok, NULL); packet_check_eom(); /* We can't send a packet to the server */ /* The draft says that we should wait for the server to fail * before starting the next authentication. So, we clear the * state, but don't do anything else */ clear_auth_state(authctxt); return 0; } int input_gssapi_error(int type, u_int32_t seq, void *ctxt) { OM_uint32 maj, min; char * msg; char * lang; maj=packet_get_int(); min=packet_get_int(); msg=packet_get_string(NULL); lang=packet_get_string(NULL); packet_check_eom(); xfprintf(stderr, "Server GSSAPI Error:\n%s\n", msg); free(msg); free(lang); return 0; } int userauth_external(Authctxt *authctxt) { if (external_attempt++ >= 1) return 0; debug2("userauth_external"); packet_start(SSH2_MSG_USERAUTH_REQUEST); packet_put_cstring(authctxt->server_user); packet_put_cstring(authctxt->service); packet_put_cstring(authctxt->method->name); packet_send(); packet_write_wait(); return 1; } #endif /* GSSAPI */ int userauth_none(Authctxt *authctxt) { /* initial userauth request */ debug2("userauth_none"); packet_start(SSH2_MSG_USERAUTH_REQUEST); packet_put_cstring(authctxt->server_user); packet_put_cstring(authctxt->service); packet_put_cstring(authctxt->method->name); packet_send(); return 1; } int userauth_passwd(Authctxt *authctxt) { char prompt[150]; char password[256]; int ok; if (passwd_attempt++ >= options.number_of_password_prompts) return 0; debug2("userauth_password"); if(passwd_attempt != 1) bleep(BP_FAIL); if ( passwd_attempt == 1 && pwbuf[0] && pwflg ) { ckstrncpy(password,pwbuf,sizeof(password)); #ifdef OS2 if ( pwcrypt ) ck_encrypt((char *)password); #endif /* OS2 */ ok = 1; } else { snprintf(prompt, sizeof(prompt), "%s%.30s@%.128s's password: ", passwd_attempt == 1 ? "" : "Permission denied, please try again.\n", authctxt->server_user, authctxt->host); ok = uq_txt(prompt,"Password: ",2,NULL,password, sizeof(password),NULL, DEFAULT_UQ_TIMEOUT); } 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(0); packet_put_cstring(ok ? password : ""); memset(password, 0, strlen(password)); packet_add_padding(64); packet_send(); dispatch_set(SSH2_MSG_USERAUTH_PASSWD_CHANGEREQ, &input_userauth_passwd_changereq); return 1; } /* * parse PASSWD_CHANGEREQ, prompt user and send SSH2_MSG_USERAUTH_REQUEST */ int input_userauth_passwd_changereq(int type, u_int32_t seq, void *ctxt) { Authctxt *authctxt = ctxt; char *info, *lang, oldpw[256]="", newpw[256]="", retype[256]=""; struct txtbox tb[3]; char prompt[150]; int ok; debug2("input_userauth_passwd_changereq"); if (authctxt == NULL) { fatal("input_userauth_passwd_changereq: " "no authentication context"); return 0; } info = packet_get_string(NULL); lang = packet_get_string(NULL); if (strlen(info) > 0) log("%s", info); free(info); free(lang); #ifdef COMMENT /* This uses three separate prompts. */ 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(1); /* additional info */ snprintf(prompt, sizeof(prompt), "Enter %.30s@%.128s's old password: ", authctxt->server_user, authctxt->host); ok = uq_txt(prompt,"Old Password: ",2,NULL, password, sizeof(password),NULL,DEFAULT_UQ_TIMEOUT); packet_put_cstring(password); memset(password, 0, strlen(password)); while (!password[0]) { snprintf(prompt, sizeof(prompt), "Enter %.30s@%.128s's new password: ", authctxt->server_user, authctxt->host); ok = uq_txt(prompt,"New Password: ",2,NULL, password, sizeof(password),NULL,DEFAULT_UQ_TIMEOUT); if (!ok || !password[0]) return 1; snprintf(prompt, sizeof(prompt), "Retype %.30s@%.128s's new password: ", authctxt->server_user, authctxt->host); ok = uq_txt(prompt,"Retype New Password: ",2,NULL, retype, sizeof(retype),NULL,DEFAULT_UQ_TIMEOUT); if (!ok) return 1; if (strcmp(password, retype) != 0) { memset(password, 0, strlen(password)); log("Mismatch; try again, EOF to quit."); } memset(retype, 0, strlen(retype)); } packet_put_cstring(password); memset(password, 0, strlen(password)); #else memset(tb,0,sizeof(struct txtbox) * 3); tb[0].t_buf = oldpw; tb[0].t_len = 256; tb[0].t_lbl = "Old Password: "; tb[0].t_dflt = NULL; tb[0].t_echo = 2; tb[1].t_buf = newpw; tb[1].t_len = 256; tb[1].t_lbl = "New Password: "; tb[1].t_dflt = NULL; tb[1].t_echo = 2; tb[2].t_buf = retype; tb[2].t_len = 256; tb[2].t_lbl = "New Password (again): "; tb[2].t_dflt = NULL; tb[2].t_echo = 2; snprintf(prompt, sizeof(prompt), "Password Change Requested for %.30s@%.128s.", authctxt->server_user, authctxt->host); while ( !newpw[0] ) { ok = uq_mtxt(prompt,NULL,3,tb); if (!ok) return 1; if (strcmp(newpw, retype) != 0) { memset(newpw, 0, strlen(newpw)); log("Mismatch; try again, EOF to quit."); } memset(retype, 0, strlen(retype)); } 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(1); /* additional info */ packet_put_cstring(oldpw); memset(oldpw, 0, strlen(oldpw)); packet_put_cstring(newpw); memset(newpw, 0, strlen(newpw)); #endif /* COMMENT */ packet_add_padding(64); packet_send(); dispatch_set(SSH2_MSG_USERAUTH_PASSWD_CHANGEREQ, &input_userauth_passwd_changereq); return 1; } void clear_auth_state(Authctxt *authctxt) { /* XXX clear authentication state */ dispatch_set(SSH2_MSG_USERAUTH_PASSWD_CHANGEREQ, NULL); #ifdef GSSAPI dispatch_set(SSH2_MSG_USERAUTH_GSSAPI_RESPONSE, NULL); dispatch_set(SSH2_MSG_USERAUTH_GSSAPI_TOKEN, NULL); dispatch_set(SSH2_MSG_USERAUTH_GSSAPI_ERROR, NULL); #endif 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; } static int sign_and_send_pubkey(Authctxt *authctxt, Key *k, sign_cb_fn *sign_callback) { Buffer b; u_char *blob, *signature; u_int bloblen, slen; int skip = 0; int ret = -1; 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"); return 0; } memset(&b,0,sizeof(Buffer)); /* data to be signed */ buffer_init(&b); if (datafellows & SSH_OLD_SESSIONID) { buffer_append(&b, session_id2, session_id2_len); skip = session_id2_len; } else { buffer_put_string(&b, session_id2, session_id2_len); skip = buffer_len(&b); } buffer_put_char(&b, SSH2_MSG_USERAUTH_REQUEST); buffer_put_cstring(&b, authctxt->server_user); buffer_put_cstring(&b, datafellows & SSH_BUG_PKSERVICE ? "ssh-userauth" : authctxt->service); if (datafellows & SSH_BUG_PKAUTH) { buffer_put_char(&b, have_sig); } else { buffer_put_cstring(&b, authctxt->method->name); buffer_put_char(&b, have_sig); buffer_put_cstring(&b, key_ssh_name(k)); } buffer_put_string(&b, blob, bloblen); /* generate signature */ ret = (*sign_callback)(authctxt, k, &signature, &slen, buffer_ptr(&b), buffer_len(&b)); if (ret == -1) { free(blob); buffer_free(&b); return 0; } #ifdef DEBUG_PK buffer_dump(&b); #endif 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); buffer_put_cstring(&b, authctxt->method->name); buffer_put_char(&b, have_sig); if (!(datafellows & SSH_BUG_PKAUTH)) buffer_put_cstring(&b, key_ssh_name(k)); buffer_put_string(&b, blob, bloblen); } free(blob); /* append signature */ buffer_put_string(&b, signature, slen); free(signature); /* skip session id and packet type */ if (buffer_len(&b) < skip + 1) { fatal("userauth_pubkey: internal error"); return -1; } buffer_consume(&b, skip + 1); /* put remaining data from buffer into packet */ packet_start(SSH2_MSG_USERAUTH_REQUEST); packet_put_raw(buffer_ptr(&b), buffer_len(&b)); buffer_free(&b); packet_send(); return 1; } static int send_pubkey_test(Authctxt *authctxt, Key *k, sign_cb_fn *sign_callback, int hint) { u_char *blob; u_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); free(blob); packet_send(); return 1; } static Key * load_identity_file(char *filename) { Key *private; char prompt[300], passphrase[256]=""; int quit, i, ok; struct _stat st; if (stat(filename, &st) < 0) { debug3("no such identity: %s", filename); return NULL; } private = key_load_private_type(KEY_UNSPEC, filename, "", NULL); if (private == NULL) { if (options.batch_mode) return NULL; for (i = 0; i < options.number_of_password_prompts; i++) { if ( i == 0 && pwbuf[0] && pwflg ) { ckstrncpy(passphrase,pwbuf,sizeof(passphrase)); #ifdef OS2 if ( pwcrypt ) ck_encrypt((char *)passphrase); #endif /* OS2 */ ok = 1; } else { snprintf(prompt, sizeof prompt, "%sEnter passphrase for key '%.100s': ", i == 0 ? "" : "Bad passphrase given, try again...\n", filename); ok = uq_txt(prompt,"Passphrase: ",2,NULL, passphrase, sizeof(passphrase),NULL,DEFAULT_UQ_TIMEOUT); } if (ok && strcmp(passphrase, "") != 0) { private = key_load_private_type(KEY_UNSPEC, filename, passphrase, NULL); quit = 0; } else { debug2("no passphrase given, try next key"); quit = 1; } memset(passphrase, 0, strlen(passphrase)); if (private != NULL || quit) break; debug2("bad passphrase given, try again..."); } } return private; } static int identity_sign_cb(Authctxt *authctxt, Key *key, u_char **sigp, u_int *lenp, u_char *data, u_int datalen) { Key *private; int idx, ret; idx = authctxt->last_key_hint; if (idx < 0) return -1; /* private key is stored in external hardware */ if (options.identity_keys[idx]->flags & KEY_FLAG_EXT) return key_sign(options.identity_keys[idx], sigp, lenp, data, datalen); 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; } static int agent_sign_cb(Authctxt *authctxt, Key *key, u_char **sigp, u_int *lenp, u_char *data, u_int datalen) { return ssh_agent_sign(authctxt->agent, key, sigp, lenp, data, datalen); } static int key_sign_cb(Authctxt *authctxt, Key *key, u_char **sigp, u_int *lenp, u_char *data, u_int datalen) { return key_sign(key, sigp, lenp, data, datalen); } static int userauth_pubkey_agent(Authctxt *authctxt) { int ret = 0; char *comment; Key *k; if (pubkey_agent_called == 0) { if (ssh_get_num_identities(authctxt->agent, 2) == 0) debug2("userauth_pubkey_agent: no keys at all"); pubkey_agent_called = 1; } k = ssh_get_next_identity(authctxt->agent, &comment, 2); if (k == NULL) { debug1("userauth_pubkey_agent: no more keys"); } else { debug1("Offering agent key %s", comment); free(comment); 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"); return ret; } int userauth_pubkey(Authctxt *authctxt) { int sent = 0; Key *key; char *filename; if (authctxt->agent != NULL) { do { sent = userauth_pubkey_agent(authctxt); } while(!sent && authctxt->agent->howmany > 0); } while (!sent && pubkey_idx < options.num_identity_files) { key = options.identity_keys[pubkey_idx]; filename = options.identity_files[pubkey_idx]; if (key == NULL) { debug1("Trying private key: %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) { debug1("Offering public key: %s", filename); sent = send_pubkey_test(authctxt, key, identity_sign_cb, pubkey_idx); } pubkey_idx++; } return sent; } /* * Send userauth request message specifying keyboard-interactive method. */ int userauth_kbdint(Authctxt *authctxt) { if (kbdint_attempt++ >= options.number_of_password_prompts) return 0; /* disable if no SSH2_MSG_USERAUTH_INFO_REQUEST has been seen */ if (kbdint_attempt > 1 && !authctxt->info_req_seen) { debug3("userauth_kbdint: disable: no info_req_seen"); dispatch_set(SSH2_MSG_USERAUTH_INFO_REQUEST, NULL); return 0; } debug2("userauth_kbdint"); packet_start(SSH2_MSG_USERAUTH_REQUEST); packet_put_cstring(authctxt->server_user); packet_put_cstring(authctxt->service); packet_put_cstring(authctxt->method->name); packet_put_cstring(""); /* lang */ packet_put_cstring(options.kbd_interactive_devices ? options.kbd_interactive_devices : ""); packet_send(); dispatch_set(SSH2_MSG_USERAUTH_INFO_REQUEST, &input_userauth_info_req); return 1; } /* * parse INFO_REQUEST, prompt user and send INFO_RESPONSE */ int input_userauth_info_req(int type, u_int32_t seq, void *ctxt) { Authctxt *authctxt = ctxt; char *name, *inst, *lang, *prompt, response[256]; u_int num_prompts, i; int echo = 0; struct txtbox * tb = NULL; debug2("input_userauth_info_req"); if (authctxt == NULL) { fatal("input_userauth_info_req: no authentication context"); return -1; } authctxt->info_req_seen = 1; name = packet_get_string(NULL); inst = packet_get_string(NULL); lang = packet_get_string(NULL); if (strlen(name) > 0) log("%s", name); if (strlen(inst) > 0) log("%s", inst); free(name); free(inst); free(lang); num_prompts = packet_get_int(); /* * Begin to build info response packet based on prompts requested. * We commit to providing the correct number of responses, so if * further on we run into a problem that prevents this, we have to * be sure and clean this up and send a correct error response. */ packet_start(SSH2_MSG_USERAUTH_INFO_RESPONSE); packet_put_int(num_prompts); debug2("input_userauth_info_req: num_prompts %d", num_prompts); if ( num_prompts == 1 && pwbuf[0] && pwflg ) { prompt = packet_get_string(NULL); echo = packet_get_char(); ckstrncpy(response,pwbuf,sizeof(response)); #ifdef OS2 if ( pwcrypt ) ck_encrypt((char *)response); #endif /* OS2 */ packet_put_cstring(response); memset(response, 0, strlen(response)); free(prompt); } else if (num_prompts > 0) { char preface[256]; tb = (struct txtbox *) malloc(sizeof(struct txtbox) * num_prompts); if ( tb != NULL ) { int ok; memset(tb,0,sizeof(struct txtbox) * num_prompts); for ( i=0; i < num_prompts; i++ ) { tb[i].t_buf = malloc(256); tb[i].t_len = 256; tb[i].t_lbl = packet_get_string(NULL); tb[i].t_dflt = NULL; tb[i].t_echo = (packet_get_char() ? 1 : 2); } ckmakmsg(preface,256,"SSH Input User Authentication for \"", (char *)authctxt->local_user,"\"\n",NULL); ok = uq_mtxt(preface,NULL,num_prompts,tb); for ( i=0; i < num_prompts; i++ ) { if (ok) packet_put_cstring(tb[i].t_buf); memset(tb[i].t_buf, 0, tb[i].t_len); free(tb[i].t_lbl); free(tb[i].t_buf); } free(tb); } } packet_check_eom(); /* done with parsing incoming message. */ packet_add_padding(64); packet_send(); return (0); } #ifdef UNIX static int ssh_keysign(Key *key, u_char **sigp, u_int *lenp, u_char *data, u_int datalen) { Buffer b; struct _stat st; pid_t pid; int to[2], from[2], status, version = 2; debug2("ssh_keysign called"); if (stat(_PATH_SSH_KEY_SIGN, &st) < 0) { error("ssh_keysign: no installed: %s", strerror(errno)); return -1; } if (fflush(stdout) != 0) error("ssh_keysign: fflush: %s", strerror(errno)); if (pipe(to) < 0) { error("ssh_keysign: pipe: %s", strerror(errno)); return -1; } if (pipe(from) < 0) { error("ssh_keysign: pipe: %s", strerror(errno)); return -1; } if ((pid = fork()) < 0) { error("ssh_keysign: fork: %s", strerror(errno)); return -1; } if (pid == 0) { seteuid(getuid()); setuid(getuid()); close(from[0]); if (dup2(from[1], STDOUT_FILENO) < 0) fatal("ssh_keysign: dup2: %s", strerror(errno)); close(to[1]); if (dup2(to[0], STDIN_FILENO) < 0) fatal("ssh_keysign: dup2: %s", strerror(errno)); close(from[1]); close(to[0]); execl(_PATH_SSH_KEY_SIGN, _PATH_SSH_KEY_SIGN, (char *) 0); fatal("ssh_keysign: exec(%s): %s", _PATH_SSH_KEY_SIGN, strerror(errno)); } close(from[1]); close(to[0]); buffer_init(&b); buffer_put_int(&b, packet_get_connection_in()); /* send # of socket */ buffer_put_string(&b, data, datalen); ssh_msg_send(to[1], version, &b); if (ssh_msg_recv(from[0], &b) < 0) { error("ssh_keysign: no reply"); buffer_clear(&b); return -1; } close(from[0]); close(to[1]); while (waitpid(pid, &status, 0) < 0) if (errno != EINTR) break; if (buffer_get_char(&b) != version) { error("ssh_keysign: bad version"); buffer_clear(&b); return -1; } *sigp = buffer_get_string(&b, lenp); buffer_clear(&b); return 0; } #endif /* UNIX */ int userauth_hostbased(Authctxt *authctxt) { Key *private = NULL; Sensitive * sensitive = authctxt->sensitive; Buffer b; u_char *signature, *blob; char *chost, *pkalg, *p; const char *service; u_int blen, slen; int ok, i, len, found = 0; /* check for a useful key */ for (i = 0; i < sensitive->nkeys; i++) { private = sensitive->keys[i]; if (private && private->type != KEY_RSA1) { found = 1; /* we take and free the key */ sensitive->keys[i] = NULL; break; } } if (!found) { debug1("No more client hostkeys for hostbased authentication."); return 0; } if (key_to_blob(private, &blob, &blen) == 0) { key_free(private); return 0; } /* figure out a name for the client host */ p = get_local_name(packet_get_connection_in()); if (p == NULL) { error("userauth_hostbased: cannot get local ipaddr/name"); key_free(private); return 0; } len = strlen(p) + 2; chost = malloc(len); strlcpy(chost, p, len); strlcat(chost, ".", len); free(p); debug2("userauth_hostbased: chost %s", chost); service = datafellows & SSH_BUG_HBSERVICE ? "ssh-userauth" : authctxt->service; pkalg = strdup(key_ssh_name(private)); buffer_init(&b); /* construct data */ buffer_put_string(&b, session_id2, session_id2_len); buffer_put_char(&b, SSH2_MSG_USERAUTH_REQUEST); buffer_put_cstring(&b, authctxt->server_user); buffer_put_cstring(&b, service); buffer_put_cstring(&b, authctxt->method->name); buffer_put_cstring(&b, pkalg); buffer_put_string(&b, blob, blen); buffer_put_cstring(&b, chost); buffer_put_cstring(&b, authctxt->local_user); #ifdef DEBUG_PK buffer_dump(&b); #endif #ifdef UNIX if ( sensitive->external_keysign ) ok = ssh_keysign(private, &signature, &slen, buffer_ptr(&b), buffer_len(&b)); else #endif /* UNIX */ ok = key_sign(private, &signature, &slen, buffer_ptr(&b), buffer_len(&b)); key_free(private); buffer_free(&b); if (ok != 0) { error("key_sign failed"); free(chost); free(pkalg); return 0; } packet_start(SSH2_MSG_USERAUTH_REQUEST); packet_put_cstring(authctxt->server_user); packet_put_cstring(authctxt->service); packet_put_cstring(authctxt->method->name); packet_put_cstring(pkalg); packet_put_string(blob, blen); packet_put_cstring(chost); packet_put_cstring(authctxt->local_user); packet_put_string(signature, slen); memset(signature, 's', slen); free(signature); free(chost); free(pkalg); packet_send(); return 1; } /* find auth method */ /* * given auth method name, if configurable options permit this method fill * in auth_ident field and return true, otherwise return false. */ static int authmethod_is_enabled(Authmethod *method) { if (method == NULL) return 0; /* return false if options indicate this method is disabled */ if (method->enabled == NULL || *method->enabled == 0) return 0; /* return false if batch mode is enabled but method needs interactive mode */ if (method->batch_flag != NULL && *method->batch_flag != 0) return 0; return 1; } static Authmethod * authmethod_lookup(const char *name) { Authmethod *method = NULL; if (name != NULL) for (method = authmethods; method->name != NULL; method++) if (strcmp(name, method->name) == 0) return method; debug2("Unrecognized authentication method name: %s", name ? name : "NULL"); return NULL; } /* * Given the authentication method list sent by the server, return the * next method we should try. If the server initially sends a nil list, * use a built-in default list. */ static Authmethod * authmethod_get(char *authlist) { char *name = NULL; u_int next; /* Use a suitable default if we're passed a nil list. */ if (authlist == NULL || strlen(authlist) == 0) authlist = options.preferred_authentications; if (supported == NULL || strcmp(authlist, supported) != 0) { debug3("start over, passed a different list %s", authlist); if (supported != NULL) free(supported); supported = strdup(authlist); preferred = options.preferred_authentications; debug3("preferred %s", preferred); current = NULL; } else if (current != NULL && authmethod_is_enabled(current)) return current; for (;;) { if ((name = match_list(preferred, supported, &next)) == NULL) { debug1("No more authentication methods to try."); current = NULL; return NULL; } preferred += next; debug3("authmethod_lookup %s", name); debug3("remaining preferred: %s", preferred); if ((current = authmethod_lookup(name)) != NULL && authmethod_is_enabled(current)) { debug3("authmethod_is_enabled %s", name); debug1("Next authentication method: %s", name); return current; } } /* NOT REACHED */ } static char * authmethods_get(void) { Authmethod *method = NULL; Buffer b; char *list; memset(&b,0,sizeof(Buffer)); buffer_init(&b); for (method = authmethods; method->name != NULL; method++) { if (authmethod_is_enabled(method)) { if ( buffer_len(&b) > 0 ) buffer_append(&b, ",", 1); buffer_append(&b, method->name, strlen(method->name)); } } buffer_append(&b, "\0", 1); list = strdup(buffer_ptr(&b)); buffer_free(&b); return list; } /* Format of the configuration file: # Configuration data is parsed as follows: # 1. command line options # 2. user-specific file # 3. system-wide file # Any configuration value is only changed the first time it is set. # Thus, host-specific definitions should be at the beginning of the # configuration file, and defaults at the end. # Host-specific declarations. These may override anything above. A single # host may match multiple declarations; these are processed in the order # that they are given in. Host *.ngs.fi ngs.fi User foo Host fake.com HostName another.host.name.real.org User blaah Port 34289 ForwardX11 no ForwardAgent no Host books.com RemoteForward 9999 shadows.cs.hut.fi:9999 Cipher 3des Host fascist.blob.com Port 23123 User tylonen RhostsAuthentication no PasswordAuthentication no Host puukko.hut.fi User t35124p ProxyCommand ssh-proxy %h %p Host *.fr PublicKeyAuthentication no Host *.su Cipher none PasswordAuthentication no # Defaults for various options Host * ForwardAgent no ForwardX11 no RhostsAuthentication yes PasswordAuthentication yes RSAAuthentication yes RhostsRSAAuthentication yes StrictHostKeyChecking yes KeepAlives no IdentityFile ~/.ssh/identity Port 22 EscapeChar ~ */ /* Keyword tokens. */ typedef enum { oBadOption, oForwardAgent, oForwardX11, oGatewayPorts, oRhostsAuthentication, oPasswordAuthentication, oRSAAuthentication, oChallengeResponseAuthentication, oXAuthLocation, #if defined(KRB4) oKrb4Authentication, #endif /* KRB4 */ #if defined(KRB5) oKrb5Authentication, #endif /* KRB5 */ #ifdef GSSAPI oGssAuthentication, oGssDelegateCreds, #ifdef GSI oGssGlobusDelegateLimitedCreds, #endif /* GSI */ #endif /* GSSAPI */ #if defined(AFS) || defined(KRB5) oKerberosTgtPassing, #endif #ifdef AFS oAFSTokenPassing, #endif #ifdef SRP oSRPAuthentication, #endif oIdentityFile, oHostName, oPort, oCipher, oRemoteForward, oLocalForward, oUser, oHost, oEscapeChar, oRhostsRSAAuthentication, oProxyCommand, oGlobalKnownHostsFile, oUserKnownHostsFile, oConnectionAttempts, oBatchMode, oCheckHostIP, oStrictHostKeyChecking, oCompression, oCompressionLevel, oKeepAlives, oHeartbeat, oNumberOfPasswordPrompts, oUsePrivilegedPort, oLogLevel, oCiphers, oProtocol, oMacs, oGlobalKnownHostsFile2, oUserKnownHostsFile2, oPubkeyAuthentication, oKbdInteractiveAuthentication, oKbdInteractiveDevices, oHostKeyAlias, oDynamicForward, oPreferredAuthentications, oHostbasedAuthentication, oHostKeyAlgorithms, oBindAddress, oSmartcardDevice, oClearAllForwardings, oNoHostAuthenticationForLocalhost, oEnableSSHKeysign, oDeprecated } OpCodes; /* Textual representations of the tokens. */ static struct { const char *name; OpCodes opcode; } keywords[] = { { "forwardagent", oForwardAgent }, { "forwardx11", oForwardX11 }, { "xauthlocation", oXAuthLocation }, { "gatewayports", oGatewayPorts }, { "useprivilegedport", oUsePrivilegedPort }, { "rhostsauthentication", oRhostsAuthentication }, { "passwordauthentication", oPasswordAuthentication }, { "kbdinteractiveauthentication", oKbdInteractiveAuthentication }, { "kbdinteractivedevices", oKbdInteractiveDevices }, { "rsaauthentication", oRSAAuthentication }, { "pubkeyauthentication", oPubkeyAuthentication }, { "dsaauthentication", oPubkeyAuthentication }, /* alias */ { "rhostsrsaauthentication", oRhostsRSAAuthentication }, { "hostbasedauthentication", oHostbasedAuthentication }, { "challengeresponseauthentication", oChallengeResponseAuthentication }, { "skeyauthentication", oChallengeResponseAuthentication }, /* alias */ { "tisauthentication", oChallengeResponseAuthentication }, /* alias */ #if defined(KRB4) { "krb4authentication", oKrb4Authentication }, #endif /* KRB4 */ #if defined(KRB5) { "krb5authentication", oKrb5Authentication }, #endif /* KRB5 */ #ifdef GSSAPI { "gssapiauthentication", oGssAuthentication }, { "gssapidelegatecredentials", oGssDelegateCreds }, #ifdef GSI /* For backwards compatability with old 1.2.27 client code */ { "forwardgssapiglobusproxy", oGssDelegateCreds }, /* alias */ { "forwardgssapiglobuslimitedproxy", oGssGlobusDelegateLimitedCreds }, #endif /* GSI */ #endif /* GSSAPI */ #if defined(AFS) || defined(KRB5) { "kerberostgtpassing", oKerberosTgtPassing }, #endif #ifdef AFS { "afstokenpassing", oAFSTokenPassing }, #endif #ifdef SRP { "srpauthentication", oSRPAuthentication }, #endif { "fallbacktorsh", oDeprecated }, { "usersh", oDeprecated }, { "identityfile", oIdentityFile }, { "identityfile2", oIdentityFile }, /* alias */ { "hostname", oHostName }, { "hostkeyalias", oHostKeyAlias }, { "proxycommand", oProxyCommand }, { "port", oPort }, { "cipher", oCipher }, { "ciphers", oCiphers }, { "macs", oMacs }, { "protocol", oProtocol }, { "remoteforward", oRemoteForward }, { "localforward", oLocalForward }, { "user", oUser }, { "host", oHost }, { "escapechar", oEscapeChar }, { "globalknownhostsfile", oGlobalKnownHostsFile }, { "userknownhostsfile", oUserKnownHostsFile }, /* obsolete */ { "globalknownhostsfile2", oGlobalKnownHostsFile2 }, { "userknownhostsfile2", oUserKnownHostsFile2 }, /* obsolete */ { "connectionattempts", oConnectionAttempts }, { "batchmode", oBatchMode }, { "checkhostip", oCheckHostIP }, { "stricthostkeychecking", oStrictHostKeyChecking }, { "compression", oCompression }, { "compressionlevel", oCompressionLevel }, { "keepalive", oKeepAlives }, { "heartbeat", oHeartbeat }, { "numberofpasswordprompts", oNumberOfPasswordPrompts }, { "loglevel", oLogLevel }, { "dynamicforward", oDynamicForward }, { "preferredauthentications", oPreferredAuthentications }, { "hostkeyalgorithms", oHostKeyAlgorithms }, { "bindaddress", oBindAddress }, { "smartcarddevice", oSmartcardDevice }, { "clearallforwardings", oClearAllForwardings }, { "enablesshkeysign", oEnableSSHKeysign }, { "nohostauthenticationforlocalhost", oNoHostAuthenticationForLocalhost }, { NULL, oBadOption } }; /* * Adds a local TCP/IP port forward to options. Never returns if there is an * error. */ int add_local_forward(Options *options, u_short port, const char *host, u_short host_port) { Forward *fwd; if (options->num_local_forwards >= SSH_MAX_FORWARDS_PER_DIRECTION) { fatal("Too many local forwards (max %d).", SSH_MAX_FORWARDS_PER_DIRECTION); return -1; } fwd = &options->local_forwards[options->num_local_forwards++]; fwd->port = port; fwd->host = strdup(host); fwd->host_port = host_port; return 0; } /* * Adds a remote TCP/IP port forward to options. Never returns if there is * an error. */ int add_remote_forward(Options *options, u_short port, const char *host, u_short host_port) { Forward *fwd; if (options->num_remote_forwards >= SSH_MAX_FORWARDS_PER_DIRECTION) { fatal("Too many remote forwards (max %d).", SSH_MAX_FORWARDS_PER_DIRECTION); return -1; } fwd = &options->remote_forwards[options->num_remote_forwards++]; fwd->port = port; fwd->host = strdup(host); fwd->host_port = host_port; return 0; } static void clear_forwardings(Options *options) { int i; for (i = 0; i < options->num_local_forwards; i++) free(options->local_forwards[i].host); options->num_local_forwards = 0; for (i = 0; i < options->num_remote_forwards; i++) free(options->remote_forwards[i].host); options->num_remote_forwards = 0; } /* * Returns the number of the token pointed to by cp or oBadOption. */ static OpCodes parse_token(const char *cp, const char *filename, int linenum) { u_int i; for (i = 0; keywords[i].name; i++) if (strcasecmp(cp, keywords[i].name) == 0) return keywords[i].opcode; error("%s: line %d: Bad configuration option: %s", filename, linenum, cp); return oBadOption; } /* * Processes a single option line as used in the configuration files. This * only sets those values that have not already been set. */ #define WHITESPACE " \t\r\n" int process_config_line(Options *options, const char *host, char *line, const char *filename, int linenum, int *activep) { char buf[256], *s, **charptr, *endofnumber, *keyword, *arg; int opcode, *intptr, value; size_t len; u_short fwd_port, fwd_host_port; char sfwd_host_port[6]; s = line; /* Get the keyword. (Each line is supposed to begin with a keyword). */ keyword = strdelim(&s); /* Ignore leading whitespace. */ if (*keyword == '\0') keyword = strdelim(&s); if (keyword == NULL || !*keyword || *keyword == '\n' || *keyword == '#') return 0; opcode = parse_token(keyword, filename, linenum); switch (opcode) { case oBadOption: /* don't panic, but count bad options */ return -1; /* NOTREACHED */ case oForwardAgent: intptr = &options->forward_agent; parse_flag: arg = strdelim(&s); if (!arg || *arg == '\0') { fatal("%.200s line %d: Missing yes/no argument.", filename, linenum); return -1; } value = 0; /* To avoid compiler warning... */ if (strcmp(arg, "yes") == 0 || strcmp(arg, "true") == 0) value = 1; else if (strcmp(arg, "no") == 0 || strcmp(arg, "false") == 0) value = 0; else fatal("%.200s line %d: Bad yes/no argument.", filename, linenum); if (*activep && *intptr == -1) *intptr = value; break; case oForwardX11: intptr = &options->forward_x11; goto parse_flag; case oGatewayPorts: intptr = &options->gateway_ports; goto parse_flag; case oUsePrivilegedPort: intptr = &options->use_privileged_port; goto parse_flag; case oRhostsAuthentication: intptr = &options->rhosts_authentication; goto parse_flag; case oPasswordAuthentication: intptr = &options->password_authentication; goto parse_flag; case oKbdInteractiveAuthentication: intptr = &options->kbd_interactive_authentication; goto parse_flag; case oKbdInteractiveDevices: charptr = &options->kbd_interactive_devices; goto parse_string; case oPubkeyAuthentication: intptr = &options->pubkey_authentication; goto parse_flag; case oRSAAuthentication: intptr = &options->rsa_authentication; goto parse_flag; case oRhostsRSAAuthentication: intptr = &options->rhosts_rsa_authentication; goto parse_flag; case oHostbasedAuthentication: intptr = &options->hostbased_authentication; goto parse_flag; case oChallengeResponseAuthentication: intptr = &options->challenge_response_authentication; goto parse_flag; #if defined (KRB4) case oKrb4Authentication: intptr = &options->krb4_authentication; goto parse_flag; #endif /* KRB4 / KRB5 */ #if defined(KRB5) case oKrb5Authentication: intptr = &options->krb5_authentication; goto parse_flag; #endif /* KRB4 / KRB5 */ #ifdef GSSAPI case oGssAuthentication: intptr = &options->gss_authentication; goto parse_flag; case oGssDelegateCreds: intptr = &options->gss_deleg_creds; goto parse_flag; #ifdef GSI case oGssGlobusDelegateLimitedCreds: intptr = &options->gss_globus_deleg_limited_proxy; goto parse_flag; #endif /* GSI */ #endif /* GSSAPI */ #if defined(AFS) || defined(KRB5) case oKerberosTgtPassing: intptr = &options->kerberos_tgt_passing; goto parse_flag; #endif #ifdef AFS case oAFSTokenPassing: intptr = &options->afs_token_passing; goto parse_flag; #endif #ifdef SRP case oSRPAuthentication: intptr = &options->srp_authentication; goto parse_flag; #endif case oBatchMode: intptr = &options->batch_mode; goto parse_flag; case oCheckHostIP: intptr = &options->check_host_ip; goto parse_flag; case oStrictHostKeyChecking: intptr = &options->strict_host_key_checking; arg = strdelim(&s); if (!arg || *arg == '\0') { fatal("%.200s line %d: Missing yes/no/ask argument.", filename, linenum); return -1; } value = 0; /* To avoid compiler warning... */ if (strcmp(arg, "yes") == 0 || strcmp(arg, "true") == 0) value = 1; else if (strcmp(arg, "no") == 0 || strcmp(arg, "false") == 0) value = 0; else if (strcmp(arg, "ask") == 0) value = 2; else { fatal("%.200s line %d: Bad yes/no/ask argument.", filename, linenum); return -1; } if (*activep && *intptr == -1) *intptr = value; break; case oCompression: intptr = &options->compression; goto parse_flag; case oKeepAlives: intptr = &options->keepalives; goto parse_flag; case oHeartbeat: intptr = &options->heartbeat_interval; goto parse_flag; case oNumberOfPasswordPrompts: intptr = &options->number_of_password_prompts; goto parse_int; case oCompressionLevel: intptr = &options->compression_level; goto parse_int; case oIdentityFile: arg = strdelim(&s); if (!arg || *arg == '\0') { fatal("%.200s line %d: Missing argument.", filename, linenum); return -1; } if (*activep) { intptr = &options->num_identity_files; if (*intptr >= SSH_MAX_IDENTITY_FILES) fatal("%.200s line %d: Too many identity files specified (max %d).", filename, linenum, SSH_MAX_IDENTITY_FILES); charptr = &options->identity_files[*intptr]; *charptr = strdup(arg); *intptr = *intptr + 1; } break; case oXAuthLocation: charptr=&options->xauth_location; goto parse_string; case oUser: charptr = &options->user; parse_string: arg = strdelim(&s); if (!arg || *arg == '\0') { fatal("%.200s line %d: Missing argument.", filename, linenum); return -1; } if (*activep && *charptr == NULL) *charptr = strdup(arg); break; case oGlobalKnownHostsFile: charptr = &options->system_hostfile; goto parse_string; case oUserKnownHostsFile: charptr = &options->user_hostfile; goto parse_string; case oGlobalKnownHostsFile2: charptr = &options->system_hostfile2; goto parse_string; case oUserKnownHostsFile2: charptr = &options->user_hostfile2; goto parse_string; case oHostName: charptr = &options->hostname; goto parse_string; case oHostKeyAlias: charptr = &options->host_key_alias; goto parse_string; case oPreferredAuthentications: charptr = &options->preferred_authentications; goto parse_string; case oBindAddress: charptr = &options->bind_address; goto parse_string; case oSmartcardDevice: charptr = &options->smartcard_device; goto parse_string; case oProxyCommand: charptr = &options->proxy_command; len = strspn(s, WHITESPACE "="); if (*activep && *charptr == NULL) *charptr = strdup(s + len); return 0; case oPort: intptr = &options->port; parse_int: arg = strdelim(&s); if (!arg || *arg == '\0') { fatal("%.200s line %d: Missing argument.", filename, linenum); return -1; } if (arg[0] < '0' || arg[0] > '9') { fatal("%.200s line %d: Bad number.", filename, linenum); return -1; } /* Octal, decimal, or hex format? */ value = strtol(arg, &endofnumber, 0); if (arg == endofnumber) { fatal("%.200s line %d: Bad number.", filename, linenum); return -1; } if (*activep && *intptr == -1) *intptr = value; break; case oConnectionAttempts: intptr = &options->connection_attempts; goto parse_int; case oCipher: intptr = &options->cipher; arg = strdelim(&s); if (!arg || *arg == '\0') { fatal("%.200s line %d: Missing argument.", filename, linenum); return -1; } value = cipher_number(arg); if (value == -1) { fatal("%.200s line %d: Bad cipher '%s'.", filename, linenum, arg ? arg : ""); return -1; } if (*activep && *intptr == -1) *intptr = value; break; case oCiphers: arg = strdelim(&s); if (!arg || *arg == '\0') { fatal("%.200s line %d: Missing argument.", filename, linenum); return -1; } if (!ciphers_valid(arg)) { fatal("%.200s line %d: Bad SSH2 cipher spec '%s'.", filename, linenum, arg ? arg : ""); return -1; } if (*activep && options->ciphers == NULL) options->ciphers = strdup(arg); break; case oMacs: arg = strdelim(&s); if (!arg || *arg == '\0') { fatal("%.200s line %d: Missing argument.", filename, linenum); return -1; } if (!mac_valid(arg)) { fatal("%.200s line %d: Bad SSH2 Mac spec '%s'.", filename, linenum, arg ? arg : ""); return -1; } if (*activep && options->macs == NULL) options->macs = strdup(arg); break; case oHostKeyAlgorithms: arg = strdelim(&s); if (!arg || *arg == '\0') { fatal("%.200s line %d: Missing argument.", filename, linenum); return -1; } if (!key_names_valid2(arg)) { fatal("%.200s line %d: Bad protocol 2 host key algorithms '%s'.", filename, linenum, arg ? arg : ""); return -1; } if (*activep && options->hostkeyalgorithms == NULL) options->hostkeyalgorithms = strdup(arg); break; case oProtocol: intptr = &options->protocol; arg = strdelim(&s); if (!arg || *arg == '\0') { fatal("%.200s line %d: Missing argument.", filename, linenum); return -1; } value = proto_spec(arg); if (value == SSH_PROTO_UNKNOWN) { fatal("%.200s line %d: Bad protocol spec '%s'.", filename, linenum, arg ? arg : ""); return -1; } if (*activep && *intptr == SSH_PROTO_UNKNOWN) *intptr = value; break; case oLogLevel: intptr = (int *) &options->log_level; arg = strdelim(&s); value = log_level_number(arg); if (value == SYSLOG_LEVEL_NOT_SET) { fatal("%.200s line %d: unsupported log level '%s'", filename, linenum, arg ? arg : ""); return -1; } if (*activep && (LogLevel) * intptr == SYSLOG_LEVEL_NOT_SET) *intptr = (LogLevel) value; break; case oLocalForward: case oRemoteForward: arg = strdelim(&s); if (!arg || *arg == '\0') fatal("%.200s line %d: Missing port argument.", filename, linenum); if ((fwd_port = a2port(arg)) == 0) fatal("%.200s line %d: Bad listen port.", filename, linenum); arg = strdelim(&s); if (!arg || *arg == '\0') fatal("%.200s line %d: Missing second argument.", filename, linenum); if (sscanf(arg, "%255[^:]:%5[0-9]", buf, sfwd_host_port) != 2 && sscanf(arg, "%255[^/]/%5[0-9]", buf, sfwd_host_port) != 2) fatal("%.200s line %d: Bad forwarding specification.", filename, linenum); if ((fwd_host_port = a2port(sfwd_host_port)) == 0) fatal("%.200s line %d: Bad forwarding port.", filename, linenum); if (*activep) { if (opcode == oLocalForward) add_local_forward(options, fwd_port, buf, fwd_host_port); else if (opcode == oRemoteForward) add_remote_forward(options, fwd_port, buf, fwd_host_port); } break; case oDynamicForward: arg = strdelim(&s); if (!arg || *arg == '\0') { fatal("%.200s line %d: Missing port argument.", filename, linenum); return -1; } fwd_port = a2port(arg); if (fwd_port == 0) { fatal("%.200s line %d: Badly formatted port number.", filename, linenum); return -1; } add_local_forward(options, fwd_port, "socks4", 0); break; case oClearAllForwardings: intptr = &options->clear_forwardings; goto parse_flag; case oHost: *activep = 0; while ((arg = strdelim(&s)) != NULL && *arg != '\0') if (match_pattern(host, arg)) { debug1("Applying options for %s", arg); *activep = 1; break; } /* Avoid garbage check below, as strdelim is done. */ return 0; case oEscapeChar: intptr = &options->escape_char; arg = strdelim(&s); if (!arg || *arg == '\0') { fatal("%.200s line %d: Missing argument.", filename, linenum); return -1; } if (arg[0] == '^' && arg[2] == 0 && (u_char) arg[1] >= 64 && (u_char) arg[1] < 128) value = (u_char) arg[1] & 31; else if (strlen(arg) == 1) value = (u_char) arg[0]; else if (strcmp(arg, "none") == 0) value = -2; else { fatal("%.200s line %d: Bad escape character.", filename, linenum); value = 0; /* Avoid compiler warning. */ return -1; } if (*activep && *intptr == -1) *intptr = value; break; case oEnableSSHKeysign: intptr = &options->enable_ssh_keysign; goto parse_flag; case oDeprecated: debug1("%s line %d: Deprecated option \"%s\"", filename, linenum, keyword); return 0; default: fatal("process_config_line: Unimplemented opcode %d", opcode); return -1; } /* Check that there is no garbage at end of line. */ if ((arg = strdelim(&s)) != NULL && *arg != '\0') { fatal("%.200s line %d: garbage at end of line; \"%.200s\".", filename, linenum, arg); return -1; } return 0; } /* * Reads the config file and modifies the options accordingly. Options * should already be initialized before this call. This never returns if * there is an error. If the file does not exist, this returns immediately. */ int read_config_file(const char *filename, const char *host, Options *options) { FILE *f; char line[1024]; int active, linenum; int bad_options = 0; /* Open the file. */ f = fopen(filename, "r"); if (!f) return 0; debug1("Reading configuration data from %s", filename); /* * Mark that we are now processing the options. This flag is turned * on/off by Host specifications. */ active = 1; linenum = 0; while (fgets(line, sizeof(line), f)) { /* Update line number counter. */ linenum++; if (process_config_line(options, host, line, filename, linenum, &active) != 0) bad_options++; } fclose(f); if (bad_options > 0) { fatal("%s: terminating, %d bad configuration options", filename, bad_options); return -1; } return 0; } /* * Initializes options to special values that indicate that they have not yet * been set. Read_config_file will only set options with this value. Options * are processed in the following order: command line, user config file, * system config file. Last, fill_default_options is called. */ void initialize_options(Options * options) { int i; memset(options,0,sizeof(Options)); for ( i=0; i < SSH_MAX_IDENTITY_FILES ; i++ ) { options->identity_files[i] = NULL; options->identity_keys[i] = NULL; } for ( i=0 ; i < SSH_MAX_FORWARDS_PER_DIRECTION ; i++ ) { options->local_forwards[i].port = 0; options->local_forwards[i].host = NULL; options->local_forwards[i].host_port = 0; options->remote_forwards[i].port = 0; options->remote_forwards[i].host = NULL; options->remote_forwards[i].host_port = 0; } options->forward_agent = -1; options->forward_x11 = -1; options->xauth_location = NULL; options->gateway_ports = -1; options->use_privileged_port = -1; options->rhosts_authentication = -1; options->rsa_authentication = -1; options->pubkey_authentication = -1; options->challenge_response_authentication = -1; #if defined(KRB4) options->krb4_authentication = -1; #endif #if defined(KRB5) options->krb5_authentication = -1; #endif #ifdef GSSAPI options->gss_authentication = -1; options->gss_deleg_creds = -1; #ifdef GSI options->gss_globus_deleg_limited_proxy = -1; #endif /* GSI */ #endif /* GSSAPI */ #if defined(AFS) || defined(KRB5) options->kerberos_tgt_passing = -1; #endif #ifdef AFS options->afs_token_passing = -1; #endif #ifdef SRP options->srp_authentication = -1; #endif options->password_authentication = -1; options->kbd_interactive_authentication = -1; options->kbd_interactive_devices = NULL; options->rhosts_rsa_authentication = -1; options->hostbased_authentication = -1; options->batch_mode = -1; options->check_host_ip = -1; options->strict_host_key_checking = -1; options->compression = -1; options->keepalives = -1; options->heartbeat_interval = -1; options->compression_level = -1; options->port = -1; options->connection_attempts = -1; options->number_of_password_prompts = -1; options->cipher = -1; options->ciphers = NULL; options->macs = NULL; options->hostkeyalgorithms = NULL; options->protocol = SSH_PROTO_UNKNOWN; options->num_identity_files = 0; options->hostname = NULL; options->host_key_alias = NULL; options->proxy_command = NULL; options->user = NULL; options->escape_char = -1; options->system_hostfile = NULL; options->user_hostfile = NULL; options->system_hostfile2 = NULL; options->user_hostfile2 = NULL; options->num_local_forwards = 0; options->num_remote_forwards = 0; options->clear_forwardings = -1; options->log_level = SYSLOG_LEVEL_NOT_SET; options->preferred_authentications = NULL; options->bind_address = NULL; options->smartcard_device = NULL; options->enable_ssh_keysign = -1; options->no_host_authentication_for_localhost = - 1; } /* * Called after processing other sources of option data, this fills those * options for which no value has been specified with their default values. */ void fill_default_options(Options * options) { int len; if ( !options ) return; debug(F101,"fill_default_options","",1); if (options->forward_agent == -1) options->forward_agent = 0; if (options->forward_x11 == -1) options->forward_x11 = 0; #ifdef _XAUTH_PATH debug(F101,"fill_default_options","",2); if (options->xauth_location == NULL) makestr(&options->xauth_location,_XAUTH_PATH); #endif /* XAUTH_PATH */ debug(F101,"fill_default_options","",3); if (options->gateway_ports == -1) options->gateway_ports = 0; if (options->use_privileged_port == -1) options->use_privileged_port = 0; if (options->rhosts_authentication == -1) options->rhosts_authentication = 0; if (options->rsa_authentication == -1) options->rsa_authentication = 1; if (options->pubkey_authentication == -1) options->pubkey_authentication = 1; if (options->challenge_response_authentication == -1) options->challenge_response_authentication = 0; #if defined(KRB4) if (options->krb4_authentication == -1) options->krb4_authentication = 1; #endif /* KRB4 / KRB5 */ #if defined(KRB5) if (options->krb5_authentication == -1) options->krb5_authentication = 1; #endif /* KRB4 / KRB5 */ #ifdef GSSAPI if (options->gss_authentication == -1) options->gss_authentication = 1; if (options->gss_deleg_creds == -1) options->gss_deleg_creds = 1; #ifdef GSI if (options->gss_globus_deleg_limited_proxy == -1) options->gss_globus_deleg_limited_proxy = 0; #endif /* GSI */ #endif /* GSSAPI */ #if defined(AFS) || defined(KRB5) #ifdef KRB5 if (options->kerberos_tgt_passing == -1) { #ifdef HEIMDAL krb5_context context; if (krb5_init_context(&context)) options->kerberos_tgt_passing = 0; else { options->kerberos_tgt_passing = krb5_config_get_bool(context, NULL, "libdefaults", "forward", NULL); krb5_free_context(context); } #else /* HEIMDAL */ options->kerberos_tgt_passing = 1; #endif /* HEIMDAL */ } #else /* KRB5 */ if (options->kerberos_tgt_passing == -1) options->kerberos_tgt_passing = 1; #endif /* KRB5 */ #endif /* AFS/KRB5 */ #ifdef AFS if (options->afs_token_passing == -1) options->afs_token_passing = 1; #endif /* AFS */ #ifdef SRP if (options->srp_authentication == -1) options->srp_authentication = 1; #endif if (options->password_authentication == -1) options->password_authentication = 1; if (options->kbd_interactive_authentication == -1) options->kbd_interactive_authentication = 1; if (options->rhosts_rsa_authentication == -1) options->rhosts_rsa_authentication = 0; if (options->hostbased_authentication == -1) options->hostbased_authentication = 0; if (options->batch_mode == -1) options->batch_mode = 0; if (options->check_host_ip == -1) options->check_host_ip = 1; if (options->strict_host_key_checking == -1) options->strict_host_key_checking = 2; /* 2 is default */ if (options->compression == -1) options->compression = 0; if (options->keepalives == -1) options->keepalives = 1; if (options->heartbeat_interval == -1) options->heartbeat_interval = 0; if (options->compression_level == -1) options->compression_level = 6; if (options->port == -1) options->port = 0; /* Filled in ssh_connect. */ if (options->connection_attempts == -1) options->connection_attempts = 1; if (options->number_of_password_prompts == -1) options->number_of_password_prompts = 3; /* Selected in ssh_login(). */ if (options->cipher == -1) options->cipher = SSH_CIPHER_NOT_SET; /* options->ciphers, default set in myproposals.h */ /* options->macs, default set in myproposals.h */ /* options->hostkeyalgorithms, default set in myproposals.h */ if (options->protocol == SSH_PROTO_UNKNOWN) options->protocol = SSH_PROTO_1|SSH_PROTO_2; if (options->num_identity_files == 0) { debug(F101,"fill_default_options","",4); if (options->protocol & SSH_PROTO_1) { makestr(&options->identity_files[options->num_identity_files++], (char *)_PATH_SSH_CLIENT_IDENTITY); } debug(F101,"fill_default_options","",5); if (options->protocol & SSH_PROTO_2) { makestr(&options->identity_files[options->num_identity_files++], (char *)_PATH_SSH_CLIENT_ID_RSA); makestr(&options->identity_files[options->num_identity_files++], (char *)_PATH_SSH_CLIENT_ID_DSA); } debug(F101,"fill_default_options","",6); } if (options->escape_char == -1) options->escape_char = '~'; if (options->system_hostfile == NULL) makestr(&options->system_hostfile,(char *)_PATH_SSH_SYSTEM_HOSTFILE); debug(F101,"fill_default_options","",7); if (options->user_hostfile == NULL) makestr(&options->user_hostfile,(char *)_PATH_SSH_USER_HOSTFILE); debug(F101,"fill_default_options","",8); if (options->system_hostfile2 == NULL) makestr(&options->system_hostfile2,(char *)_PATH_SSH_SYSTEM_HOSTFILE2); debug(F101,"fill_default_options","",9); if (options->user_hostfile2 == NULL) makestr(&options->user_hostfile2,(char *)_PATH_SSH_USER_HOSTFILE2); debug(F101,"fill_default_options","",10); if (options->log_level == SYSLOG_LEVEL_NOT_SET) options->log_level = SYSLOG_LEVEL_INFO; if (options->clear_forwardings == 1) clear_forwardings(options); if (options->no_host_authentication_for_localhost == - 1) options->no_host_authentication_for_localhost = 0; if (options->enable_ssh_keysign == -1) options->enable_ssh_keysign = 0; /* options->proxy_command should not be set by default */ /* options->user will be set in the main program if appropriate */ /* options->hostname will be set in the main program if appropriate */ /* options->host_key_alias should not be set by default */ /* options->preferred_authentications will be set in ssh */ } /* Restores stdin to blocking mode. */ static void leave_non_blocking(void) { #ifndef OS2 if (in_non_blocking_mode) { (void) fcntl(fileno(stdin), F_SETFL, 0); in_non_blocking_mode = 0; fatal_remove_cleanup((void (*) (void *)) leave_non_blocking, NULL); } #endif /* !OS2 */ } /* Puts stdin terminal in non-blocking mode. */ static void enter_non_blocking(void) { #ifndef OS2 in_non_blocking_mode = 1; (void) fcntl(fileno(stdin), F_SETFL, O_NONBLOCK); fatal_add_cleanup((void (*) (void *)) leave_non_blocking, NULL); #endif /* !OS2 */ } /* * Signal handler for the window change signal (SIGWINCH). This just sets a * flag indicating that the window has changed. */ #ifndef OS2 static void window_change_handler(int sig) { received_window_change_signal = 1; signal(SIGWINCH, window_change_handler); } #else static void ssh_window_change(void) { if (sent_initial_ws) received_window_change_signal = 1; } int ssh_snaws(void) { extern int tt_status[]; if (ssh_height != (VscrnGetHeight(VTERM) - (tt_status[VTERM]?1:0)) || ssh_width != VscrnGetWidth(VTERM)) ssh_window_change(); return 0; } #endif /* !OS2 */ /* * Signal handler for signals that cause the program to terminate. These * signals must be trapped to restore terminal modes. */ static void signal_handler(int sig) { received_signal = sig; quit_pending = 1; } /* * This is called when the interactive is entered. This checks if there is * an EOF coming on stdin. We must check this explicitly, as select() does * not appear to wake up when redirecting from /dev/null. */ static void client_check_initial_eof_on_stdin(void) { int len; char buf[1]; /* * If standard input is to be "redirected from /dev/null", we simply * mark that we have seen an EOF and send an EOF message to the * server. Otherwise, we try to read a single character; it appears * that for some files, such /dev/null, select() never wakes up for * read for this descriptor, which means that we never get EOF. This * way we will get the EOF if stdin comes from /dev/null or similar. */ if (stdin_null_flag) { /* Fake EOF on stdin. */ debug1("Sending eof."); stdin_eof = 1; packet_start(SSH_CMSG_EOF); packet_send(); } else { enter_non_blocking(); /* Check for immediate EOF on stdin. */ #ifdef OS2 len = recv(sock_stdin, buf, 1, 0); #else len = read(fileno(stdin), buf, 1); #endif /* OS2 */ if (len == 0) { /* EOF. Record that we have seen it and send EOF to server. */ debug1("Sending eof."); stdin_eof = 1; packet_start(SSH_CMSG_EOF); packet_send(); } else if (len > 0) { /* * Got data. We must store the data in the buffer, * and also process it as an escape character if * appropriate. */ if ((u_char) buf[0] == escape_char) escape_pending = 1; else buffer_append(&stdin_buffer, buf, 1); } leave_non_blocking(); } } /* * Make packets from buffered stdin data, and buffer them for sending to the * connection. */ static int client_make_packets_from_stdin_data(void) { u_int len; /* Send buffered stdin data to the server. */ while (buffer_len(&stdin_buffer) > 0 && packet_not_very_much_data_to_write()) { len = buffer_len(&stdin_buffer); /* Keep the packets at reasonable size. */ if (len > packet_get_maxsize()) len = packet_get_maxsize(); packet_start(SSH_CMSG_STDIN_DATA); packet_put_string(buffer_ptr(&stdin_buffer), len); packet_send(); if (buffer_consume(&stdin_buffer, len) < 0) return(-1); stdin_bytes += len; /* If we have a pending EOF, send it now. */ if (stdin_eof && buffer_len(&stdin_buffer) == 0) { packet_start(SSH_CMSG_EOF); packet_send(); } } return(0); } /* * Checks if the client window has changed, and sends a packet about it to * the server if so. The actual change is detected elsewhere (by a software * interrupt on Unix); this just checks the flag and sends a message if * appropriate. */ static void client_check_window_change(void) { struct winsize ws; if (! received_window_change_signal) return; /** XXX race */ received_window_change_signal = 0; #ifdef OS2 if (_getwinsize( &ws ) == -1) memset(&ws,0,sizeof(ws)); #else /* OS2 */ if (ioctl(fileno(stdin), TIOCGWINSZ, &ws) < 0) return; #endif /* OS2 */ debug2("client_check_window_change: changed"); received_window_change_signal = 0; ssh_width = ws.ws_col; ssh_height = ws.ws_row; if (compat20) { channel_request_start(session_ident, "window-change", 0); packet_put_int(ws.ws_col); packet_put_int(ws.ws_row); packet_put_int(ws.ws_xpixel); packet_put_int(ws.ws_ypixel); packet_send(); } else { packet_start(SSH_CMSG_WINDOW_SIZE); packet_put_int(ws.ws_row); packet_put_int(ws.ws_col); packet_put_int(ws.ws_xpixel); packet_put_int(ws.ws_ypixel); packet_send(); } } /* * Waits until the client can do something (some data becomes available on * one of the file descriptors). */ static time_t idle_time_last = 0; static void client_wait_until_can_do_something(fd_set **readsetp, fd_set **writesetp, int *maxfdp, int *nallocp, int rekeying) { int ret; struct timeval tv; /* Add any selections by the channel mechanism. */ channel_prepare_select(readsetp, writesetp, maxfdp, nallocp, rekeying); if (!compat20) { /* Read from the connection, unless our buffers are full. */ if (buffer_len(&stdout_buffer) < buffer_high && buffer_len(&stderr_buffer) < buffer_high && channel_not_very_much_buffered_data()) FD_SET(connection_in, *readsetp); /* * Read from stdin, unless we have seen EOF or have very much * buffered data to send to the server. */ if (!stdin_eof && packet_not_very_much_data_to_write()) { #ifdef OS2 FD_SET(sock_stdin, *readsetp); #else FD_SET(fileno(stdin), *readsetp); #endif /* OS2 */ } /* Select stdout/stderr if have data in buffer. */ if (buffer_len(&stdout_buffer) > 0) { #ifdef OS2 return; #else FD_SET(fileno(stdout), *writesetp); #endif /* OS2 */ } if (buffer_len(&stderr_buffer) > 0) { #ifdef OS2 return; #else FD_SET(fileno(stderr), *writesetp); #endif /* OS2 */ } } else { /* channel_prepare_select could have closed the last channel */ if (session_closed && (channel_still_open() <= 0) && !packet_have_data_to_write()) { /* clear mask since we did not call select() */ #ifdef OS2 FD_ZERO(*readsetp); FD_ZERO(*writesetp); #else memset(*readsetp, 0, *nallocp); memset(*writesetp, 0, *nallocp); #endif return; } else { FD_SET(connection_in, *readsetp); } } /* Select server connection if have data to write to the server. */ if (packet_have_data_to_write()) FD_SET(connection_out, *writesetp); /* * Wait for something to happen. This will suspend the process until * some selected descriptor can be read, written, or has some other * event pending. Note: if you want to implement SSH_MSG_IGNORE * messages to fool traffic analysis, this might be the place to do * it: just have a random timeout for the select, and send a random * SSH_MSG_IGNORE packet when the timeout expires. */ tv.tv_sec = 0; tv.tv_usec = 500 * 1000; /* time slot is 0.5 sec */ ret = select((*maxfdp)+1, *readsetp, *writesetp, NULL, NULL); if (ret < 0) { /* * We have to clear the select masks, because we return. * We have to return, because the mainloop checks for the flags * set by the signal handlers. */ #ifdef OS2 FD_ZERO(*readsetp); FD_ZERO(*writesetp); #else memset(*readsetp, 0, *nallocp); memset(*writesetp, 0, *nallocp); #endif /* OS2 */ if (errno == EINTR || quit_pending) return; /* Note: we might still have data in the buffers. */ debug1("select: %s", sock_strerror(sock_lasterror())); quit_pending = 1; } /* If the output channel has been silent for more than a specified * time, send a keepalive packet (heartbeat) to the server. * Keepalive packet is useful for keeping the connection over * IP masquerade / NAT boxes, firewalls, etc. * Some servers equipped with a watchdog timer require keepalive * packets (heartbeats) to detect link down. * * Note: Although the interval between keepalive packets is not * very precise, it's okay. * * Note: Some old servers may crash when they receive SSH_MSG_IGNORE. * Those who want to connect to such a server should turn this * function off by the option setting (e.g. Heartbeat 0). */ if (options.heartbeat_interval > 0) { if (FD_ISSET(connection_out,*writesetp)) { /* Update the time of last data transmission. */ idle_time_last = time(NULL); } else if (time(NULL) - idle_time_last >= (int)options.heartbeat_interval) { if (compat20) { packet_start(SSH2_MSG_IGNORE); } else { packet_start(SSH_MSG_IGNORE); } packet_put_string("", 0); packet_send(); /* fputs("*",stderr); */ } } } #ifndef OS2 static void client_suspend_self(Buffer *bin, Buffer *bout, Buffer *berr) { #ifndef OS2 struct winsize oldws, newws; #endif /* Flush stdout and stderr buffers. */ if (buffer_len(bout) > 0) atomicio(write, fileno(stdout), buffer_ptr(bout), buffer_len(bout)); if (buffer_len(berr) > 0) atomicio(write, fileno(stderr), buffer_ptr(berr), buffer_len(berr)); leave_raw_mode(); /* * Free (and clear) the buffer to reduce the amount of data that gets * written to swap. */ buffer_free(bin); buffer_free(bout); buffer_free(berr); #ifndef OS2 /* Save old window size. */ ioctl(fileno(stdin), TIOCGWINSZ, &oldws); /* Send the suspend signal to the program itself. */ kill(getpid(), SIGTSTP); /* Check if the window size has changed. */ if (ioctl(fileno(stdin), TIOCGWINSZ, &newws) >= 0 && (oldws.ws_row != newws.ws_row || oldws.ws_col != newws.ws_col || oldws.ws_xpixel != newws.ws_xpixel || oldws.ws_ypixel != newws.ws_ypixel)) received_window_change_signal = 1; #endif /* !OS2 */ /* OK, we have been continued by the user. Reinitialize buffers. */ buffer_init(bin); buffer_init(bout); buffer_init(berr); enter_raw_mode(); } #endif /* OS2 */ static void client_process_net_input(fd_set * readset) { int len; char buf[8192]; /* * Read input from the server, and add any such data to the buffer of * the packet subsystem. */ if (FD_ISSET(connection_in, readset)) { /* Read as much as possible. */ #ifdef OS2 len = recv(connection_in, buf, sizeof(buf),0); #else len = read(connection_in, buf, sizeof(buf)); #endif /* OS2 */ if (len == 0) { /* Received EOF. The remote host has closed the connection. */ xfprintf(stderr,"Connection to %.300s closed by remote host.\r\n", host); quit_pending = 1; return; } /* * There is a kernel bug on Solaris that causes select to * sometimes wake up even though there is no data available. */ #ifdef OS2 if (len < 0 && (sock_lasterror() == EAGAIN)) len = 0; #else if (len < 0 && (errno == EAGAIN || errno == EINTR)) len = 0; #endif /* OS2 */ if (len < 0) { /* An error has encountered. Perhaps there is a network problem. */ #ifdef OS2 debug1("Read from remote host %.300s: %.100s", host, sock_strerror(sock_lasterror())); #else snprintf(buf, sizeof buf, "Read from remote host %.300s: %.100s\r\n", host, strerror(errno)); buffer_append(&stderr_buffer, buf, strlen(buf)); #endif /* OS2 */ quit_pending = 1; return; } packet_process_incoming(buf, len); } } #ifdef UNIX static void process_cmdline(void) { void (*handler)(int); char *s, *cmd; u_short fwd_port, fwd_host_port; char buf[1024], sfwd_port[6], sfwd_host_port[6]; int local = 0; leave_raw_mode(); handler = signal(SIGINT, SIG_IGN); cmd = s = read_passphrase("\r\nssh> ", RP_ECHO); if (s == NULL) goto out; while (*s && isspace(*s)) s++; if (*s == 0) goto out; if (strlen(s) < 2 || s[0] != '-' || !(s[1] == 'L' || s[1] == 'R')) { log("Invalid command."); goto out; } if (s[1] == 'L') local = 1; if (!local && !compat20) { log("Not supported for SSH protocol version 1."); goto out; } s += 2; while (*s && isspace(*s)) s++; if (sscanf(s, "%5[0-9]:%255[^:]:%5[0-9]", sfwd_port, buf, sfwd_host_port) != 3 && sscanf(s, "%5[0-9]/%255[^/]/%5[0-9]", sfwd_port, buf, sfwd_host_port) != 3) { log("Bad forwarding specification."); goto out; } if ((fwd_port = a2port(sfwd_port)) == 0 || (fwd_host_port = a2port(sfwd_host_port)) == 0) { log("Bad forwarding port(s)."); goto out; } if (local) { if (channel_setup_local_fwd_listener(fwd_port, buf, fwd_host_port, options.gateway_ports) < 0) { log("Port forwarding failed."); goto out; } } else channel_request_remote_forwarding(fwd_port, buf, fwd_host_port); log("Forwarding port."); out: signal(SIGINT, handler); enter_raw_mode(); if (cmd) free(cmd); } #endif /* UNIX */ /* process the characters one by one */ static int process_escapes(Buffer *bin, Buffer *bout, Buffer *berr, char *buf, int len) { char string[1024]; pid_t pid; int bytes = 0; u_int i; u_char ch; char *s; for (i = 0; i < len; i++) { /* Get one character at a time. */ ch = buf[i]; if (escape_pending) { /* We have previously seen an escape character. */ /* Clear the flag now. */ escape_pending = 0; /* Process the escaped character. */ switch (ch) { case '.': /* Terminate the connection. */ snprintf(string, sizeof string, "%c.\r\n", escape_char); buffer_append(berr, string, strlen(string)); quit_pending = 1; return -1; #ifndef OS2 case 'Z' - 64: /* Suspend the program. */ /* Print a message to that effect to the user. */ snprintf(string, sizeof string, "%c^Z [suspend ssh]\r\n", escape_char); buffer_append(berr, string, strlen(string)); /* Restore terminal modes and suspend. */ client_suspend_self(bin, bout, berr); /* We have been continued. */ continue; #endif /* OS2 */ case 'R': if (compat20) { if (datafellows & SSH_BUG_NOREKEY) log("Server does not support re-keying"); else need_rekeying = 1; } continue; case '&': /* * Detach the program (continue to serve connections, * but put in background and no more new connections). */ /* Restore tty modes. */ leave_raw_mode(); /* Stop listening for new connections. */ channel_stop_listening(); snprintf(string, sizeof string, "%c& [backgrounded]\n", escape_char); buffer_append(berr, string, strlen(string)); #ifndef OS2 /* Fork into background. */ pid = fork(); if (pid < 0) { error("fork: %.100s", strerror(errno)); continue; } if (pid != 0) { /* This is the parent. */ /* The parent just exits. */ exit(0); } /* The child continues serving connections. */ #endif /* OS2 */ if (compat20) { buffer_append(bin, "\004", 1); /* fake EOF on stdin */ return -1; } else if (!stdin_eof) { /* * Sending SSH_CMSG_EOF alone does not always appear * to be enough. So we try to send an EOF character * first. */ packet_start(SSH_CMSG_STDIN_DATA); packet_put_string("\004", 1); packet_send(); /* Close stdin. */ stdin_eof = 1; if (buffer_len(bin) == 0) { packet_start(SSH_CMSG_EOF); packet_send(); } } continue; case '?': snprintf(string, sizeof string, "%c?\r\n" "Supported escape sequences:\r\n" "%c. - terminate connection\r\n" #ifdef UNIX "%cC - open a command line\r\n" #endif /* UNIX */ "%cR - Request rekey (SSH protocol 2 only)\r\n" "%c^Z - suspend ssh\r\n" "%c# - list forwarded connections\r\n" "%c& - background ssh (when waiting for connections to terminate)\r\n" "%c? - this message\r\n" "%c%c - send the escape character by typing it twice\r\n" "(Note that escapes are only recognized immediately after newline.)\r\n", escape_char,escape_char,escape_char,escape_char,escape_char, escape_char,escape_char,escape_char,escape_char,escape_char); buffer_append(berr, string, strlen(string)); continue; case '#': snprintf(string, sizeof string, "%c#\r\n", escape_char); buffer_append(berr, string, strlen(string)); s = channel_open_message(); buffer_append(berr, s, strlen(s)); free(s); s = NULL; continue; #ifdef UNIX case 'C': process_cmdline(); continue; #endif /* UNIX */ default: if (ch != escape_char) { buffer_put_char(bin, escape_char); bytes++; } /* Escaped characters fall through here */ break; } } else { /* * The previous character was not an escape char. Check if this * is an escape. */ if (last_was_cr && ch == escape_char) { /* It is. Set the flag and continue to next character. */ escape_pending = 1; continue; } } /* * Normal character. Record whether it was a newline, * and append it to the buffer. */ last_was_cr = (ch == '\r' || ch == '\n'); buffer_put_char(bin, ch); bytes++; } return bytes; } static void client_process_input(fd_set * readset) { int len; char buf[8192]; int fd; #ifdef OS2 fd = sock_stdin; #else fd = fileno(stdin); #endif /* OS2 */ /* Read input from stdin. */ if (FD_ISSET(fd, readset)) { /* Read as much as possible. */ #ifdef OS2 len = recv(fd, buf, sizeof(buf),0); #else len = read(fd, buf, sizeof(buf)); #endif /* OS2 */ if (len < 0 && #ifdef OS2 (sock_lasterror() == EAGAIN) #else (errno == EAGAIN || errno == EINTR) #endif /* OS2 */ ) return; /* we'll try again later */ if (len <= 0) { /* * Received EOF or error. They are treated * similarly, except that an error message is printed * if it was an error condition. */ if (len < 0) { #ifdef OS2 snprintf(buf, sizeof buf, "read: %.100s\r\n", sock_strerror(sock_lasterror())); #else snprintf(buf, sizeof buf, "read: %.100s\r\n", strerror(errno)); #endif /* OS2 */ buffer_append(&stderr_buffer, buf, strlen(buf)); } /* Mark that we have seen EOF. */ stdin_eof = 1; /* * Send an EOF message to the server unless there is * data in the buffer. If there is data in the * buffer, no message will be sent now. Code * elsewhere will send the EOF when the buffer * becomes empty if stdin_eof is set. */ if (buffer_len(&stdin_buffer) == 0) { packet_start(SSH_CMSG_EOF); packet_send(); } } else if (escape_char == SSH_ESCAPECHAR_NONE) { /* * Normal successful read, and no escape character. * Just append the data to buffer. */ buffer_append(&stdin_buffer, buf, len); } else { /* * Normal, successful read. But we have an escape character * and have to process the characters one by one. */ if (process_escapes(&stdin_buffer, &stdout_buffer, &stderr_buffer, buf, len) == -1) return; } } } static int client_process_output(fd_set * writeset) { int len; char buf[100]; int set; #ifdef OS2 set = buffer_len(&stdout_buffer) > 0; #else set = FD_ISSET(fileno(stdout), writeset); #endif /* OS2 */ /* Write buffered output to stdout. */ if (set) { #ifdef OS2 char * p = buffer_ptr(&stdout_buffer); len = buffer_len(&stdout_buffer); hexdump("SSH client_process_output stdout",p,len); send(sock_stdout,p,len,0); #else /* Write as much data as possible. */ len = write(fileno(stdout), buffer_ptr(&stdout_buffer), buffer_len(&stdout_buffer)); if (len <= 0) { if (errno == EINTR || errno == EAGAIN) len = 0; else { /* * An error or EOF was encountered. Put an * error message to stderr buffer. */ snprintf(buf, sizeof buf, "write stdout: %.50s\r\n", strerror(errno)); buffer_append(&stderr_buffer, buf, strlen(buf)); quit_pending = 1; return(0); } } #endif /* Consume printed data from the buffer. */ if (buffer_consume(&stdout_buffer, len) < 0) return(-1); stdout_bytes += len; } #ifdef OS2 set = buffer_len(&stderr_buffer) > 0; #else set = FD_ISSET(fileno(stderr), writeset); #endif /* OS2 */ /* Write buffered output to stderr. */ if (set) { /* Write as much data as possible. */ #ifdef OS2 char * p = buffer_ptr(&stderr_buffer); len = buffer_len(&stderr_buffer); hexdump("SSH client_process_output stderr",p,len); send(sock_stderr,p,len,0); #else len = write(fileno(stderr), buffer_ptr(&stderr_buffer), buffer_len(&stderr_buffer)); if (len <= 0) { if (errno == EINTR || errno == EAGAIN) len = 0; else { /* EOF or error, but can't even print error message. */ quit_pending = 1; return(0); } } #endif /* Consume printed characters from the buffer. */ if (buffer_consume(&stderr_buffer, len) < 0) return(-1); stderr_bytes += len; } return(0); } /* * Get packets from the connection input buffer, and process them as long as * there are packets available. * * Any unknown packets received during the actual * session cause the session to terminate. This is * intended to make debugging easier since no * confirmations are sent. Any compatible protocol * extensions must be negotiated during the * preparatory phase. */ static void client_process_buffered_input_packets(void) { dispatch_run(DISPATCH_NONBLOCK, &quit_pending, compat20 ? xxx_kex : NULL); } /* scan buf[] for '~' before sending data to the peer */ static int simple_escape_filter(Channel *c, char *buf, int len) { /* XXX we assume c->extended is writeable */ return process_escapes(&c->input, &c->output, &c->extended, buf, len); } static void client_channel_closed(int id, void *arg) { if (id != session_ident) error("client_channel_closed: id %d != session_ident %d", id, session_ident); channel_cancel_cleanup(id); session_closed = 1; if (in_raw_mode()) leave_raw_mode(); } /* * Implements the interactive session with the server. This is called after * the user has been authenticated, and a command has been started on the * remote host. If escape_char != SSH_ESCAPECHAR_NONE, it is the character used as an escape * character for terminating or suspending the session. */ int client_loop(int have_pty, int escape_char_arg, int ssh2_chan_id) { fd_set *readset = NULL, *writeset = NULL; time_t start_time, total_time, rekey_time; int max_fd = 0, max_fd2 = 0, len, rekeying = 0, nalloc = 0; char buf[100]; debug1("Entering interactive session."); #ifndef OS2 /* REALLY send debug messages to the application */ fflush(stderr); #endif /* OS2 */ start_time = rekey_time = idle_time_last = time(NULL); /* Initialize variables. */ escape_pending = 0; last_was_cr = 1; exit_status = -1; stdin_eof = 0; buffer_high = 64 * 1024; connection_in = packet_get_connection_in(); connection_out = packet_get_connection_out(); max_fd = MAX(connection_in, connection_out); if (!compat20) { #ifndef OS2 /* enable nonblocking unless tty */ if (!isatty(fileno(stdin))) set_nonblock(fileno(stdin)); if (!isatty(fileno(stdout))) set_nonblock(fileno(stdout)); if (!isatty(fileno(stderr))) set_nonblock(fileno(stderr)); max_fd = MAX(max_fd, fileno(stdin)); max_fd = MAX(max_fd, fileno(stdout)); max_fd = MAX(max_fd, fileno(stderr)); #endif /* OS2 */ stdin_bytes = 0; stdout_bytes = 0; stderr_bytes = 0; } quit_pending = 0; escape_char = escape_char_arg; /* Initialize buffers. */ buffer_init(&stdin_buffer); buffer_init(&stdout_buffer); buffer_init(&stderr_buffer); client_init_dispatch(); /* Set signal handlers to restore non-blocking mode. */ if (signal(SIGINT, SIG_IGN) != SIG_IGN) signal(SIGINT, signal_handler); if (signal(SIGTERM, SIG_IGN) != SIG_IGN) signal(SIGTERM, signal_handler); #ifndef OS2 if (signal(SIGQUIT, SIG_IGN) != SIG_IGN) signal(SIGQUIT, signal_handler); if (have_pty) signal(SIGWINCH, window_change_handler); if (have_pty) enter_raw_mode(); #endif /* !OS2 */ if (compat20) { session_ident = ssh2_chan_id; if (escape_char != SSH_ESCAPECHAR_NONE) channel_register_filter(session_ident, simple_escape_filter); if (session_ident != -1) channel_register_cleanup(session_ident, client_channel_closed); } #ifndef OS2 else { /* Check if we should immediately send eof on stdin. */ client_check_initial_eof_on_stdin(); } #endif /* OS2 */ #ifdef OS2 #ifdef NT SetThreadPrty(XYP_RTP,isWin95() ? 14 : 22); #else /* NT */ SetThreadPrty(XYP_RTP,3); #endif /* NT */ #endif /* OS2 */ /* Main loop of the client for the interactive session mode. */ while (!quit_pending) { /* Process buffered packets sent by the server. */ client_process_buffered_input_packets(); if (compat20 && session_closed && (channel_still_open() <= 0)) { debug1("client_loop: session_closed"); #ifdef OS2 /* Process output to stdout and stderr. Output to the connection is processed elsewhere (above). */ if ((buffer_len(&stdout_buffer) > 0) || (buffer_len(&stderr_buffer) > 0)) { client_process_output(writeset); } #endif /* OS2 */ break; } rekeying = (compat20 && xxx_kex != NULL && !xxx_kex->done); if (rekeying) { debug1("rekeying in progress"); rekey_time = time(NULL); } else { /* * Make packets of buffered stdin data, and buffer * them for sending to the server. */ if (!compat20) client_make_packets_from_stdin_data(); /* * Make packets from buffered channel data, and * enqueue them for sending to the server. */ if (packet_not_very_much_data_to_write()) channel_output_poll(); /* * Check if the window size has changed, and buffer a * message about it to the server if so. */ client_check_window_change(); if (quit_pending) break; } #ifdef OS2 /* Process output to stdout and stderr. Output to the connection is processed elsewhere (above). */ if ((buffer_len(&stdout_buffer) > 0) || (buffer_len(&stderr_buffer) > 0)) client_process_output(writeset); #endif /* OS2 */ /* * Wait until we have something to do (something becomes * available on one of the descriptors). */ max_fd2 = max_fd; client_wait_until_can_do_something(&readset, &writeset, &max_fd2, &nalloc, rekeying); if (quit_pending) break; /* Do channel operations unless rekeying in progress. */ if (!rekeying) { channel_after_select(readset, writeset); if ( compat20 && ssh2_ark && !(datafellows & SSH_BUG_NOREKEY)) { /* If auto-rekeying, rekey every hour */ time_t now_t = time(NULL); if ( now_t - rekey_time > 360 ) need_rekeying = 1; } if (need_rekeying) { debug1("user requests rekeying"); if ( xxx_kex ) { xxx_kex->done = 0; kex_send_kexinit(xxx_kex); } need_rekeying = 0; } } /* Buffer input from the connection. */ client_process_net_input(readset); if (quit_pending) break; if (!compat20) { /* Buffer data from stdin */ client_process_input(readset); /* * Process output to stdout and stderr. Output to * the connection is processed elsewhere (above). */ #ifdef OS2 if ((buffer_len(&stdout_buffer) > 0) || (buffer_len(&stderr_buffer) > 0)) #endif client_process_output(writeset); } /* Send as much buffered packet data as possible to the sender. */ if (FD_ISSET(connection_out, writeset)) packet_write_poll(); } if (readset) { free(readset); readset = NULL; } if (writeset) { free(writeset); writeset = NULL; } /* Terminate the session. */ #ifndef OS2 /* Stop watching for window change. */ if (have_pty) signal(SIGWINCH, SIG_DFL); #endif /* OS2 */ /* Stop listening for connections. */ channel_free_all(); if (have_pty) leave_raw_mode(); #ifndef OS2 /* restore blocking io */ if (!isatty(fileno(stdin))) unset_nonblock(fileno(stdin)); if (!isatty(fileno(stdout))) unset_nonblock(fileno(stdout)); if (!isatty(fileno(stderr))) unset_nonblock(fileno(stderr)); #endif /* OS2 */ if (received_signal) { if (in_non_blocking_mode) /* XXX */ leave_non_blocking(); fatal("Killed by signal %d.", (int) received_signal); return -1; } /* Output any buffered data for stdout. */ while (buffer_len(&stdout_buffer) > 0) { #ifdef OS2 char * p = buffer_ptr(&stdout_buffer); int i; len = buffer_len(&stdout_buffer); if (len > 0) send(sock_stdout,buffer_ptr(&stdout_buffer),len,0); #else len = write(fileno(stdout), buffer_ptr(&stdout_buffer), buffer_len(&stdout_buffer)); if (len <= 0) { error("Write failed flushing stdout buffer."); break; } #endif buffer_consume(&stdout_buffer, len); stdout_bytes += len; } /* Output any buffered data for stderr. */ while (buffer_len(&stderr_buffer) > 0) { #ifdef OS2 char * p = buffer_ptr(&stderr_buffer); int i; len = buffer_len(&stderr_buffer); if ( len > 0 ) send(sock_stderr,buffer_ptr(&stdout_buffer),len,0); #else len = write(fileno(stderr), buffer_ptr(&stderr_buffer), buffer_len(&stderr_buffer)); if (len <= 0) { error("Write failed flushing stderr buffer."); break; } #endif buffer_consume(&stderr_buffer, len); stderr_bytes += len; } if (!compat20) { /* Close the connection to the remote host. */ packet_disconnect("Client loop terminated"); closesocket(sock_stdin); closesocket(sock_stdout); closesocket(sock_stderr); } /* Clear and free any buffers. */ memset(buf, 0, sizeof(buf)); buffer_free(&stdin_buffer); buffer_free(&stdout_buffer); buffer_free(&stderr_buffer); if ( !compat20 ) { /* Report bytes transferred, and transfer rates. */ total_time = time(NULL) - start_time; debug1("Bytes Transferred %d stdin",stdin_bytes); debug1("Bytes Transferred %d stdout",stdout_bytes); debug1("Bytes Transferred %d stderr",stderr_bytes); debug1("Total Time %d seconds",total_time); if (total_time > 0) { debug1("Stdin %f BPS",(long)stdin_bytes/total_time); debug1("Stdout %f BPS",(long)stdout_bytes/total_time); debug1("Stderr %f BPS",(long)stderr_bytes/total_time); } } /* Return the exit status of the program. */ debug1("Exit status: %d", exit_status); quit_pending = 0; /* no longer pending */ return exit_status; } /*********/ static int client_input_stdout_data(int type, u_int32_t seq, void *ctxt) { u_int data_len; char *data = packet_get_string(&data_len); packet_check_eom(); buffer_append(&stdout_buffer, data, data_len); memset(data, 0, data_len); free(data); return 0; } static int client_input_stderr_data(int type, u_int32_t seq, void *ctxt) { u_int data_len; char *data = packet_get_string(&data_len); packet_check_eom(); buffer_append(&stderr_buffer, data, data_len); memset(data, 0, data_len); free(data); return 0; } static int client_input_exit_status(int type, u_int32_t seq, void *ctxt) { exit_status = packet_get_int(); packet_check_eom(); /* Acknowledge the exit. */ packet_start(SSH_CMSG_EXIT_CONFIRMATION); packet_send(); /* * Must wait for packet to be sent since we are * exiting the loop. */ packet_write_wait(); /* Flag that we want to exit. */ quit_pending = 1; return 0; } static Channel * client_request_forwarded_tcpip(const char *request_type, int rchan) { Channel* c = NULL; char *listen_address, *originator_address; int listen_port, originator_port; int sock; /* Get rest of the packet */ listen_address = packet_get_string(NULL); listen_port = packet_get_int(); originator_address = packet_get_string(NULL); originator_port = packet_get_int(); packet_done_null(); debug1("client_request_forwarded_tcpip: listen host %s port %d", listen_address, listen_port); debug1("client_request_forwarded_tcpip: originator host %s port %d", originator_address, originator_port); sock = channel_connect_by_listen_address(listen_port); if (sock < 0) { free(originator_address); free(listen_address); return NULL; } c = channel_new("forwarded-tcpip", SSH_CHANNEL_CONNECTING, sock, sock, -1, CHAN_TCP_WINDOW_DEFAULT, CHAN_TCP_WINDOW_DEFAULT, 0, strdup(originator_address), 1); free(originator_address); free(listen_address); return c; } static Channel* client_request_x11(const char *request_type, int rchan) { Channel *c = NULL; char *originator; int originator_port; int sock; if (!options.forward_x11) { error("Warning: ssh server tried X11 forwarding."); error("Warning: this is probably a break in attempt by a malicious server."); return NULL; } originator = packet_get_string(NULL); if (datafellows & SSH_BUG_X11FWD) { debug2("buggy server: x11 request w/o originator_port"); originator_port = 0; } else { originator_port = packet_get_int(); } packet_done_null(); /* XXX check permission */ debug1("client_request_x11: request from originator host %s port %d", originator, originator_port); free(originator); sock = x11_connect_display(); if (sock < 0) return NULL; c = channel_new("x11", SSH_CHANNEL_X11_OPEN, sock, sock, -1, CHAN_TCP_WINDOW_DEFAULT, CHAN_X11_PACKET_DEFAULT, 0, strdup("x11"), 1); c->force_drain = 1; return c; } static Channel* client_request_agent(const char *request_type, int rchan) { Channel *c = NULL; int sock; if (!options.forward_agent) { error("Warning: ssh server tried agent forwarding."); error("Warning: this is probably a break in attempt by a malicious server."); return NULL; } sock = ssh_get_authentication_socket(); if (sock < 0) return NULL; c = channel_new("authentication agent connection", SSH_CHANNEL_OPEN, sock, sock, -1, CHAN_X11_WINDOW_DEFAULT, CHAN_TCP_WINDOW_DEFAULT, 0, strdup("authentication agent connection"), 1); c->force_drain = 1; return c; } /* XXXX move to generic input handler */ static int client_input_channel_open(int type, u_int32_t seq, void *ctxt) { Channel *c = NULL; char *ctype; int rchan; u_int rmaxpack, rwindow, len; ctype = packet_get_string(&len); rchan = packet_get_int(); rwindow = packet_get_int(); rmaxpack = packet_get_int(); debug1("client_input_channel_open: ctype %s rchan %d window %d max %d", ctype,rchan,rwindow,rmaxpack); if (strcmp(ctype, "forwarded-tcpip") == 0) { c = client_request_forwarded_tcpip(ctype, rchan); } else if (strcmp(ctype, "x11") == 0) { c = client_request_x11(ctype, rchan); } else if (strcmp(ctype, "auth-agent@openssh.com") == 0) { c = client_request_agent(ctype, rchan); } /* XXX duplicate : */ if (c != NULL) { debug1("confirm %s", ctype); c->remote_id = rchan; c->remote_window = rwindow; c->remote_maxpacket = rmaxpack; packet_start(SSH2_MSG_CHANNEL_OPEN_CONFIRMATION); packet_put_int(c->remote_id); packet_put_int(c->self); packet_put_int(c->local_window); packet_put_int(c->local_maxpacket); packet_send(); } else { debug1("failure %s", ctype); packet_start(SSH2_MSG_CHANNEL_OPEN_FAILURE); packet_put_int(rchan); packet_put_int(SSH2_OPEN_ADMINISTRATIVELY_PROHIBITED); if (!(datafellows & SSH_BUG_OPENFAILURE)) { packet_put_cstring("open failed"); packet_put_cstring(""); } packet_send(); } free(ctype); return(0); } static int client_input_channel_req(int type, u_int32_t seq, void *ctxt) { Channel *c = NULL; int id, reply, success = 0; char *rtype; id = packet_get_int(); rtype = packet_get_string(NULL); reply = packet_get_char(); debug1("client_input_channel_req","channel %d rtype %s reply %s", id,rtype,reply); if (session_ident == -1) { error("client_input_channel_req: no channel %d", session_ident); } else if (id != session_ident) { error("client_input_channel_req: channel %d: wrong channel: %d", session_ident, id); } c = channel_lookup(id); if (c == NULL) { error("client_input_channel_req: channel %d: unknown channel", id); } else if (strcmp(rtype, "exit-status") == 0) { success = 1; exit_status = packet_get_int(); packet_check_eom(); } if (reply) { packet_start(success ? SSH2_MSG_CHANNEL_SUCCESS : SSH2_MSG_CHANNEL_FAILURE); packet_put_int(c->remote_id); packet_send(); } free(rtype); return (success ? 0 : -1); } static int client_input_global_request(int type, u_int32_t seq, void *ctxt) { char *rtype; int want_reply; int success = 0; rtype = packet_get_string(NULL); want_reply = packet_get_char(); debug1("client_input_global_request: rtype %s want_reply %d", rtype, want_reply); if (want_reply) { packet_start(success ? SSH2_MSG_REQUEST_SUCCESS : SSH2_MSG_REQUEST_FAILURE); packet_send(); packet_write_wait(); } free(rtype); return 0; } static void client_init_dispatch_20(void) { dispatch_init(&dispatch_protocol_error); dispatch_set(SSH2_MSG_CHANNEL_CLOSE, &channel_input_oclose); dispatch_set(SSH2_MSG_CHANNEL_DATA, &channel_input_data); dispatch_set(SSH2_MSG_CHANNEL_EOF, &channel_input_ieof); dispatch_set(SSH2_MSG_CHANNEL_EXTENDED_DATA, &channel_input_extended_data); dispatch_set(SSH2_MSG_CHANNEL_OPEN, &client_input_channel_open); dispatch_set(SSH2_MSG_CHANNEL_OPEN_CONFIRMATION, &channel_input_open_confirmation); dispatch_set(SSH2_MSG_CHANNEL_OPEN_FAILURE, &channel_input_open_failure); dispatch_set(SSH2_MSG_CHANNEL_REQUEST, &client_input_channel_req); dispatch_set(SSH2_MSG_CHANNEL_WINDOW_ADJUST, &channel_input_window_adjust); dispatch_set(SSH2_MSG_GLOBAL_REQUEST, &client_input_global_request); /* rekeying */ dispatch_set(SSH2_MSG_KEXINIT, &kex_input_kexinit); /* global request reply messages */ dispatch_set(SSH2_MSG_REQUEST_FAILURE, &client_global_request_reply); dispatch_set(SSH2_MSG_REQUEST_SUCCESS, &client_global_request_reply); } static void client_init_dispatch_13(void) { dispatch_init(NULL); dispatch_set(SSH_MSG_CHANNEL_CLOSE, &channel_input_close); dispatch_set(SSH_MSG_CHANNEL_CLOSE_CONFIRMATION, &channel_input_close_confirmation); dispatch_set(SSH_MSG_CHANNEL_DATA, &channel_input_data); dispatch_set(SSH_MSG_CHANNEL_OPEN_CONFIRMATION, &channel_input_open_confirmation); dispatch_set(SSH_MSG_CHANNEL_OPEN_FAILURE, &channel_input_open_failure); dispatch_set(SSH_MSG_PORT_OPEN, &channel_input_port_open); dispatch_set(SSH_SMSG_EXITSTATUS, &client_input_exit_status); dispatch_set(SSH_SMSG_STDERR_DATA, &client_input_stderr_data); dispatch_set(SSH_SMSG_STDOUT_DATA, &client_input_stdout_data); dispatch_set(SSH_SMSG_AGENT_OPEN, options.forward_agent ? &auth_input_open_request : &deny_input_open); dispatch_set(SSH_SMSG_X11_OPEN, options.forward_x11 ? &x11_input_open : &deny_input_open); } static void client_init_dispatch_15(void) { client_init_dispatch_13(); dispatch_set(SSH_MSG_CHANNEL_CLOSE, &channel_input_ieof); dispatch_set(SSH_MSG_CHANNEL_CLOSE_CONFIRMATION, &channel_input_oclose); } static void client_init_dispatch(void) { if (compat20) client_init_dispatch_20(); else if (compat13) client_init_dispatch_13(); else client_init_dispatch_15(); } /* SSH-KEYGEN */ static char * key_type_name(int type) { switch (type) { case KEY_RSA1: return "ssh1-rsa"; case KEY_RSA: return "ssh2-rsa"; case KEY_DSA: return "ssh2-dsa"; case KEY_NULL: return "null"; default: return "unspecified"; } } static char identity_file[1024]; char * sshkey_default_file(int type) { char buf[1024], buf2[400]; char *name = NULL; switch (type) { case KEY_RSA1: name = (char *)_PATH_SSH_CLIENT_IDENTITY; break; case KEY_SRP: name = (char *)_PATH_SSH_USER_VERIFIER; break; case KEY_DSA: name = (char *)_PATH_SSH_CLIENT_ID_DSA; break; case KEY_RSA: name = (char *)_PATH_SSH_CLIENT_ID_RSA; break; default: name = "unknown"; break; } snprintf(identity_file, sizeof identity_file, "%s", name); return identity_file; } static char * ask_filename(const char *prompt, int type) { char buf[1024], buf2[400]; int ok; snprintf(buf2, sizeof(buf2), "%s\n(%s)", prompt, sshkey_default_file(type)); ok = uq_txt(NULL,buf2,1,NULL,buf,sizeof(buf),sshkey_default_file(type),DEFAULT_UQ_TIMEOUT); if (ok && strcmp(buf, "") != 0) strlcpy(identity_file, buf, sizeof(identity_file)); return identity_file; } static Key * sshkey_load_identity_file(char *filename, char * identity_passphrase) { char pass[300]; Key *prv; prv = key_load_private(filename, "", NULL); if (prv == NULL) { if (identity_passphrase) ckstrncpy(pass,identity_passphrase,sizeof(pass)); else { char preface[256]; int ok; ckmakmsg(preface,256,"Loading SSH Identity File: \"", filename, "\"\n", NULL); ok = uq_txt(preface, "Enter passphrase: ", 2, NULL, pass,300,NULL,DEFAULT_UQ_TIMEOUT); if (!ok) pass[0] = '\0'; } prv = key_load_private(filename, pass, NULL); memset(pass, 0, strlen(pass)); } return prv; } #define SSH_COM_PUBLIC_BEGIN "---- BEGIN SSH2 PUBLIC KEY ----" #define SSH_COM_PUBLIC_END "---- END SSH2 PUBLIC KEY ----" #define SSH_COM_PRIVATE_BEGIN "---- BEGIN SSH2 ENCRYPTED PRIVATE KEY ----" #define SSH_COM_PRIVATE_KEY_MAGIC 0x3f6ff9eb int sshkey_display_public_as_ssh2(char * filename, char * identity_passphrase) { Key *k; int len; u_char *blob; struct _stat st; char kg_hostname[MAXHOSTNAMELEN]; if (filename == NULL) filename = ask_filename("Enter file in which the key is", KEY_UNSPEC); if (filename == NULL) return -1; if (stat(filename, &st) < 0) { perror(filename); return -1; } if ((k = key_load_public(filename, NULL)) == NULL) { if ((k = sshkey_load_identity_file(filename, identity_passphrase)) == NULL) { fprintf(stderr, "load failed\n"); return -1; } } if (gethostname(kg_hostname, sizeof(kg_hostname)) < 0) { perror("get_hostname"); return -1; } if (key_to_blob(k, &blob, &len) <= 0) { fprintf(stderr, "key_to_blob failed\n"); return -1; } printf("%s\n", SSH_COM_PUBLIC_BEGIN); printf("Comment: \"%d-bit %s, converted from OpenSSH by %s@%s\"\n", key_size(k), key_type(k), uidbuf, kg_hostname); dump_base64(stdout, blob, len); printf("%s\n", SSH_COM_PUBLIC_END); key_free(k); free(blob); return 0; } static int buffer_get_bignum_bits(Buffer *b, BIGNUM *value) { int bits = buffer_get_int(b); int bytes = (bits + 7) / 8; if (buffer_len(b) < bytes) { ssh_error(SSH_ERROR_BUFFER_TOO_SMALL, "buffer_get_bignum_bits: input buffer too small: " "need %d have %d", bytes, buffer_len(b)); return(-1); } BN_bin2bn((u_char *)buffer_ptr(b), bytes, value); buffer_consume(b, bytes); return(0); } static Key * do_convert_private_ssh2_from_blob(char *blob, int blen) { Buffer b; Key *key = NULL; char *type, *cipher; u_char *sig, data[] = "abcde12345"; int magic, rlen, ktype, i1, i2, i3, i4; u_int slen; u_long e; memset(&b,0,sizeof(Buffer)); buffer_init(&b); buffer_append(&b, blob, blen); magic = buffer_get_int(&b); if (magic != SSH_COM_PRIVATE_KEY_MAGIC) { error("bad magic 0x%x != 0x%x", magic, SSH_COM_PRIVATE_KEY_MAGIC); buffer_free(&b); return NULL; } i1 = buffer_get_int(&b); type = buffer_get_string(&b, NULL); cipher = buffer_get_string(&b, NULL); i2 = buffer_get_int(&b); i3 = buffer_get_int(&b); i4 = buffer_get_int(&b); debug1("ignore (%d %d %d %d)", i1,i2,i3,i4); if (strcmp(cipher, "none") != 0) { error("unsupported cipher %s", cipher); free(cipher); buffer_free(&b); free(type); return NULL; } free(cipher); if (strstr(type, "dsa")) { ktype = KEY_DSA; } else if (strstr(type, "rsa")) { ktype = KEY_RSA; } else { free(type); return NULL; } key = key_new_private(ktype); free(type); switch (key->type) { case KEY_DSA: if (buffer_get_bignum_bits(&b, key->dsa->p) < 0 || buffer_get_bignum_bits(&b, key->dsa->g) < 0 || buffer_get_bignum_bits(&b, key->dsa->q) < 0 || buffer_get_bignum_bits(&b, key->dsa->pub_key) < 0 || buffer_get_bignum_bits(&b, key->dsa->priv_key) < 0) return(NULL); break; case KEY_RSA: e = buffer_get_char(&b); if (e < 30) { e <<= 8; e += buffer_get_char(&b); e <<= 8; e += buffer_get_char(&b); } if (!BN_set_word(key->rsa->e, e)) { buffer_free(&b); key_free(key); return NULL; } if (buffer_get_bignum_bits(&b, key->rsa->d) < 0 || buffer_get_bignum_bits(&b, key->rsa->n) < 0 || buffer_get_bignum_bits(&b, key->rsa->iqmp) < 0 || buffer_get_bignum_bits(&b, key->rsa->q) < 0 || buffer_get_bignum_bits(&b, key->rsa->p) < 0) return(NULL); generate_additional_parameters(key->rsa); break; } rlen = buffer_len(&b); if(rlen != 0) error("do_convert_private_ssh2_from_blob: " "remaining bytes in key blob %d", rlen); buffer_free(&b); /* try the key */ key_sign(key, &sig, &slen, data, sizeof data); key_verify(key, sig, slen, data, sizeof data); free(sig); return key; } #ifdef COMMENT static int do_convert_from_ssh2(char * source, char * dest) { Key *k; int blen; char line[1024], *p; u_char blob[8096]; char encoded[8096]; struct _stat st; int escaped = 0, private = 0, ok; FILE *fp; if (filename == NULL) filename = ask_filename("Enter file in which the key is",KEY_UNSPEC); if (filename == NULL) return -1; if (stat(filename, &st) < 0) { perror(filename); return -1; } fp = fopen(filename, "r"); if (fp == NULL) { perror(filename); return -1; } encoded[0] = '\0'; while (fgets(line, sizeof(line), fp)) { if (!(p = strchr(line, '\n'))) { fprintf(stderr, "input line too long.\n"); return -1; } if (p > line && p[-1] == '\\') escaped++; if (strncmp(line, "----", 4) == 0 || strstr(line, ": ") != NULL) { if (strstr(line, SSH_COM_PRIVATE_BEGIN) != NULL) private = 1; if (strstr(line, " END ") != NULL) { break; } /* xfprintf(stderr, "ignore: %s", line); */ continue; } if (escaped) { escaped--; /* xfprintf(stderr, "escaped: %s", line); */ continue; } *p = '\0'; strlcat(encoded, line, sizeof(encoded)); } blen = uudecode(encoded, (u_char *)blob, sizeof(blob)); if (blen < 0) { xfprintf(stderr, "uudecode failed.\n"); return -1; } k = private ? do_convert_private_ssh2_from_blob(blob, blen) : key_from_blob(blob, blen); if (k == NULL) { fprintf(stderr, "decode blob failed.\n"); return -1; } ok = private ? (k->type == KEY_DSA ? PEM_write_DSAPrivateKey(stdout, (char *)k->dsa, NULL, NULL, 0, NULL, NULL) : PEM_write_RSAPrivateKey(stdout, (char *)k->rsa, NULL, NULL, 0, NULL, NULL)) : key_write(k, stdout); if (!ok) { fprintf(stderr, "key write failed"); return -1; } key_free(k); fprintf(stdout, "\n"); fclose(fp); return 0; } #endif /* COMMENT */ #ifdef SMARTCARD #define NUM_RSA_KEY_ELEMENTS 5+1 #define COPY_RSA_KEY(x, i) \ do { \ len = BN_num_bytes(prv->rsa->x); \ elements[i] = malloc(len); \ debug1("#bytes %d", len); \ if (BN_bn2bin(prv->rsa->x, elements[i]) < 0) \ goto done; \ } while(0) static int get_AUT0(char *aut0) { EVP_MD *evp_md = EVP_sha1(); EVP_MD_CTX md; char *pass; pass = read_passphrase("Enter passphrase for smartcard: ", RP_ALLOW_STDIN); if (pass == NULL) return -1; EVP_DigestInit(&md, evp_md); EVP_DigestUpdate(&md, pass, strlen(pass)); EVP_DigestFinal(&md, aut0, NULL); memset(pass, 0, strlen(pass)); free(pass); return 0; } int sshkey_do_upload(struct passwd *pw, const char *sc_reader_id, char *identity_passphrase) { Key *prv = NULL; struct _stat st; u_char *elements[NUM_RSA_KEY_ELEMENTS]; u_char key_fid[2]; u_char DEFAUT0[] = {0xad, 0x9f, 0x61, 0xfe, 0xfa, 0x20, 0xce, 0x63}; u_char AUT0[EVP_MAX_MD_SIZE]; int len, status = 1, i, fd = -1, ret; int sw = 0, cla = 0x00; for (i = 0; i < NUM_RSA_KEY_ELEMENTS; i++) elements[i] = NULL; if (!have_identity) ask_filename(pw, "Enter file in which the key is"); if (stat(identity_file, &st) < 0) { perror(identity_file); goto done; } prv = load_identity(identity_file, identity_passphrase); if (prv == NULL) { error("load failed"); goto done; } COPY_RSA_KEY(q, 0); COPY_RSA_KEY(p, 1); COPY_RSA_KEY(iqmp, 2); COPY_RSA_KEY(dmq1, 3); COPY_RSA_KEY(dmp1, 4); COPY_RSA_KEY(n, 5); len = BN_num_bytes(prv->rsa->n); fd = sectok_friendly_open(sc_reader_id, STONOWAIT, &sw); if (fd < 0) { error("sectok_open failed: %s", sectok_get_sw(sw)); goto done; } if (! sectok_cardpresent(fd)) { error("smartcard in reader %s not present", sc_reader_id); goto done; } ret = sectok_reset(fd, 0, NULL, &sw); if (ret <= 0) { error("sectok_reset failed: %s", sectok_get_sw(sw)); goto done; } if ((cla = cyberflex_inq_class(fd)) < 0) { error("cyberflex_inq_class failed"); goto done; } memcpy(AUT0, DEFAUT0, sizeof(DEFAUT0)); if (cyberflex_verify_AUT0(fd, cla, AUT0, sizeof(DEFAUT0)) < 0) { if (get_AUT0(AUT0) < 0 || cyberflex_verify_AUT0(fd, cla, AUT0, sizeof(DEFAUT0)) < 0) { error("cyberflex_verify_AUT0 failed"); goto done; } } key_fid[0] = 0x00; key_fid[1] = 0x12; if (cyberflex_load_rsa_priv(fd, cla, key_fid, 5, 8*len, elements, &sw) < 0) { error("cyberflex_load_rsa_priv failed: %s", sectok_get_sw(sw)); goto done; } if (!sectok_swOK(sw)) goto done; log("cyberflex_load_rsa_priv done"); key_fid[0] = 0x73; key_fid[1] = 0x68; if (cyberflex_load_rsa_pub(fd, cla, key_fid, len, elements[5], &sw) < 0) { error("cyberflex_load_rsa_pub failed: %s", sectok_get_sw(sw)); goto done; } if (!sectok_swOK(sw)) goto done; log("cyberflex_load_rsa_pub done"); status = 0; log("loading key done"); done: memset(elements[0], '\0', BN_num_bytes(prv->rsa->q)); memset(elements[1], '\0', BN_num_bytes(prv->rsa->p)); memset(elements[2], '\0', BN_num_bytes(prv->rsa->iqmp)); memset(elements[3], '\0', BN_num_bytes(prv->rsa->dmq1)); memset(elements[4], '\0', BN_num_bytes(prv->rsa->dmp1)); memset(elements[5], '\0', BN_num_bytes(prv->rsa->n)); if (prv) key_free(prv); for (i = 0; i < NUM_RSA_KEY_ELEMENTS; i++) if (elements[i]) free(elements[i]); if (fd != -1) sectok_close(fd); return(status); } int sshkey_do_download(struct passwd *pw, const char *sc_reader_id) { Key *pub = NULL; pub = sc_get_key(sc_reader_id); if (pub == NULL) fatal("cannot read public key from smartcard"); key_write(pub, stdout); key_free(pub); fprintf(stdout, "\n"); return(0); } #endif /* SMARTCARD */ int sshkey_display_public(char * filename, char * identity_passphrase) { Key *prv; struct _stat st; if (filename == NULL) filename = ask_filename("Enter file in which the key is",KEY_UNSPEC); if (filename == NULL) return -1; if (stat(filename, &st) < 0) { perror(filename); return -1; } prv = sshkey_load_identity_file(filename,identity_passphrase); if (prv == NULL) { fprintf(stderr, "load failed\n"); return -1; } if (!key_write(prv, stdout)) fprintf(stderr, "key_write failed"); key_free(prv); fprintf(stdout, "\n"); return 0; } int sshkey_display_fingerprint(char * filename, int babble) { FILE *f; Key *public; char *comment = NULL, *cp, *ep, line[16*1024], *fp; int i, skip = 0, num = 1, invalid = 1, rep, fptype; struct _stat st; fptype = babble ? SSH_FP_SHA1 : SSH_FP_MD5; rep = babble ? SSH_FP_BUBBLEBABBLE : SSH_FP_HEX; if (filename == NULL) filename = ask_filename("Enter file in which the key is",KEY_UNSPEC); if (filename == NULL) return -1; if (stat(filename, &st) < 0) { perror(filename); return -1; } /* Attempt to process the file as a V1 RSA key */ public = key_load_public(filename, &comment); if (public != NULL) { fp = key_fingerprint(public, fptype, rep); printf("%d %s %s\n", key_size(public), fp, comment); key_free(public); free(comment); free(fp); return 0; } if (comment) free(comment); /* Its a V2 key */ f = fopen(filename, "r"); if (f != NULL) { while (fgets(line, sizeof(line), f)) { i = strlen(line) - 1; if (line[i] != '\n') { error("line %d too long: %.40s...", num, line); skip = 1; continue; } num++; if (skip) { skip = 0; continue; } line[i] = '\0'; /* Skip leading whitespace, empty and comment lines. */ for (cp = line; *cp == ' ' || *cp == '\t'; cp++) ; if (!*cp || *cp == '\n' || *cp == '#') continue ; i = strtol(cp, &ep, 10); if (i == 0 || ep == NULL || (*ep != ' ' && *ep != '\t')) { int quoted = 0; comment = cp; for (; *cp && (quoted || (*cp != ' ' && *cp != '\t')); cp++) { if (*cp == '\\' && cp[1] == '"') cp++; /* Skip both */ else if (*cp == '"') quoted = !quoted; } if (!*cp) continue; *cp++ = '\0'; } ep = cp; public = key_new(KEY_RSA1); if (key_read(public, &cp) != 1) { cp = ep; key_free(public); public = key_new(KEY_UNSPEC); if (key_read(public, &cp) != 1) { key_free(public); continue; } } comment = *cp ? cp : comment; fp = key_fingerprint(public, fptype, rep); xprintf("%d %s %s\n", key_size(public), fp, comment ? comment : "no comment"); free(fp); key_free(public); invalid = 0; } fclose(f); } if (invalid) { printf("%s is not a valid key file.\n", filename); return -1; } return 0; } /* * Perform changing a passphrase. The argument is the passwd structure * for the current user. */ int sshkey_change_passphrase(char * filename, char * oldpp, char * newpp) { char *comment; char old_passphrase[300], passphrase1[300], passphrase2[300]; struct _stat st; Key *private; int ok; if (!filename) filename = ask_filename("Enter name of file in which the key is located.",KEY_DSA); if (filename == NULL) return -1; if (stat(filename, &st) < 0) { perror(filename); return -1; } /* Try to load the file with empty passphrase. */ private = key_load_private(filename, "", &comment); if (private == NULL) { if (oldpp) { strncpy(old_passphrase,oldpp,sizeof(old_passphrase)); ok = 1; } else { char preface[512]; ckmakmsg(preface,512,"Enter old passphrase to access '", filename,"'.\n",NULL); ok = uq_txt(preface,"Old Passphrase: ",2,NULL,old_passphrase, sizeof(old_passphrase),NULL,DEFAULT_UQ_TIMEOUT); } if ( !ok ) { printf("Cancelled\n"); return(-1); } private = key_load_private(filename, old_passphrase , &comment); memset(old_passphrase, 0, strlen(old_passphrase)); if (private == NULL) { printf("Bad passphrase.\n"); return -1; } } printf("Key has comment '%s'\n", comment); /* Ask the new passphrase (twice). */ if (newpp) { ckstrncpy(passphrase1,newpp,sizeof(passphrase1)); passphrase2[0] = '\0'; ok = 1; } else { struct txtbox tb[2]; tb[0].t_buf = passphrase1; tb[0].t_len = sizeof(passphrase1); tb[0].t_lbl = "New passphrase (empty for no passphrase): "; tb[0].t_dflt = NULL; tb[0].t_echo = 2; tb[1].t_buf = passphrase2; tb[1].t_len = sizeof(passphrase2); tb[1].t_lbl = "New passphrase (again): "; tb[1].t_dflt = NULL; tb[1].t_echo = 2; ok = uq_mtxt(NULL, NULL, 2, tb); if ( !ok ) { printf("Cancelled\n"); return(-1); } /* Verify that they are the same. */ if (strcmp(passphrase1, passphrase2) != 0) { memset(passphrase1, 0, strlen(passphrase1)); memset(passphrase2, 0, strlen(passphrase2)); printf("Pass phrases do not match. Try again.\n"); return -1; } /* Destroy the other copy. */ memset(passphrase2, 0, strlen(passphrase2)); } /* Save the file using the new passphrase. */ if (!key_save_private(private, filename, passphrase1, comment)) { printf("Saving the key failed: %s.\n", filename); memset(passphrase1, 0, strlen(passphrase1)); key_free(private); free(comment); return -1; } /* Destroy the passphrase and the copy of the key in memory. */ memset(passphrase1, 0, strlen(passphrase1)); key_free(private); /* Destroys contents */ free(comment); printf("Your identification has been saved with the new passphrase.\n"); return 0; } /* * Change the comment of a private key file. */ int sshkey_v1_change_comment(char * filename, char * new_comment, char * pp) { char buf[1024], *comment, passphrase[300], pubfile[300]; Key *private; Key *public; struct _stat st; FILE *f; int fd, ok; if (filename == NULL) filename = ask_filename("Enter file in which the key is",KEY_RSA1); if (filename == NULL) return -1; if (stat(filename, &st) < 0) { perror(filename); return -1; } private = key_load_private(filename, "", &comment); if (private == NULL) { if (pp) { strncpy(passphrase, pp, sizeof(passphrase)); ok = 1; } else { ok = uq_txt(NULL,"Enter passphrase: ",2,NULL, passphrase, sizeof(passphrase), NULL,DEFAULT_UQ_TIMEOUT); } if ( !ok ) { printf("Cancelled\n"); return(-1); } /* Try to load using the passphrase. */ private = key_load_private(filename, passphrase, &comment); if (private == NULL) { memset(passphrase, 0, strlen(passphrase)); printf("Bad passphrase.\n"); return -1; } } else { passphrase[0] = '\0'; } if (private->type != KEY_RSA1) { xfprintf(stderr, "Comments are only supported for RSA1 keys.\n"); key_free(private); return -1; } if (new_comment == NULL) { char preface[256]; int ok; ckmakmsg(preface,256,"Old Comment '",comment,"'\n",NULL); ok = uq_txt(preface,"New Comment: ",1,NULL,buf,sizeof(buf),NULL,DEFAULT_UQ_TIMEOUT); if ( !ok ) { printf("Cancelled\n"); memset(passphrase, 0, strlen(passphrase)); key_free(private); free(comment); } new_comment = buf; } else { printf("Old comment '%s'\n", comment); printf("New comment '%s'\n", new_comment); } /* Save the file using the new passphrase. */ if (!key_save_private(private, filename, passphrase, new_comment)) { printf("Saving the key failed: %s.\n", filename); memset(passphrase, 0, strlen(passphrase)); key_free(private); free(comment); return -1; } memset(passphrase, 0, strlen(passphrase)); public = key_from_private(private); key_free(private); ckstrncpy(pubfile,filename,sizeof(pubfile)); ckstrncat(pubfile,".pub",sizeof(pubfile)); fd = open(pubfile, _O_WRONLY | _O_CREAT | _O_TRUNC, 0644); if (fd == -1) { printf("Could not save your public key in %s\n", pubfile); return -1; } f = _fdopen(fd, "w"); if (f == NULL) { printf("_fdopen %s failed", pubfile); return -1; } if (!key_write(public, f)) fprintf(stderr, "write key failed"); key_free(public); fprintf(f, " %s\n", new_comment); fclose(f); free(comment); printf("The comment in your key file has been changed.\n"); return 0; } #ifdef CK_SRP /* Keep track of the number of different bit sizes we know. */ typedef struct bitslist BITSLIST; struct bitslist { int bits; int count; BITSLIST *next; }; static BITSLIST *Bits = NULL; static BITSLIST * bits_new(int bits, BITSLIST *next) { BITSLIST *p; p = malloc(sizeof(BITSLIST)); p->bits = bits; p->count = 1; p->next = next; return p; } static void bits_free(BITSLIST ** pBits) { BITSLIST * next; if ( pBits == NULL ) return; if ( *pBits == NULL ) return; next = (*pBits)->next; free(*pBits); (*pBits) = NULL; if ( next ) bits_free(&next); } static void bits_add(int bits) { BITSLIST *p, *pp = NULL; /* Do a little insertion sort, just for fun. */ if (Bits == NULL) { Bits = bits_new(bits, NULL); return; } for (p = Bits; p; p = p->next) { if (p->bits == bits) { p->count++; return; } if (p->bits > bits) break; pp = p; } if (pp == NULL) { p = Bits; Bits = bits_new(bits, p); } else { p = pp->next; pp->next = bits_new(bits, p); } } static void bits_dump(void) { BITSLIST *p; if (!Bits) { fprintf(stderr, "no known prime groups!\n"); exit(1); } printf("Known prime group sizes (count):\n"); for (p = Bits; p; p = p->next) printf("\t%d (%d)\n", p->bits, p->count); } #endif /* CK_SRP */ int sshkey_create(char * filename, int bits, char * pp, int type, char * cmd_comment) { char dotsshdir[16 * 1024], comment[1024], passphrase1[300], passphrase2[300]; char kg_hostname[MAXHOSTNAMELEN]; char pubfile[300]; Key *private, *public; int fd; struct _stat st; FILE *f; int ok; if (bits < 512 || bits > 32768) { printf("Bits has bad value.\n"); return -1; } if (type > KEY_SRP) { fprintf(stderr, "unsupported key type %d\n", type); return -1; } arc4random_stir(); if (filename == NULL) { filename = ask_filename("Enter file in which to save the key",type); if (filename == NULL) return -1; } /* Create ~/.ssh directory if it doesn't already exist. */ snprintf(dotsshdir, sizeof dotsshdir, "%s", (char *)_PATH_SSH_USER_DIR); if (strstr(filename, dotsshdir) != NULL && stat(dotsshdir, &st) < 0) { if (mkdir(dotsshdir, 0700) < 0) error("Could not create directory '%s'.", dotsshdir); else if (!quiet) printf("Created directory '%s'.\n", dotsshdir); } /* If the file already exists, ask the user to confirm. */ if (stat(filename, &st) >= 0) { char buf[300]; int x; snprintf(buf, sizeof buf, "%s already exists.\n Overwrite (y/n)?", filename); #ifdef COMMENT if (!getyesno(buf,0)) return -1; #else /* COMMENT */ x = uq_ok( "Creating SSH Key File", buf, 3, NULL, 0); if ( x <= 0 ) return(-1); #endif /* COMMENT */ } #ifdef CK_SRP if ( type == KEY_SRP ) { BIGNUM *p, *g, *s, *x, *v; BN_CTX *ctx, *ctx2; int i, j, c, which, slen; char * salt = NULL, *buf=NULL; FILE *outfile; /* Allocate BIGNUMs and stuff. */ if ((ctx = BN_CTX_new()) == NULL) goto err; if ((ctx2 = BN_CTX_new()) == NULL) goto err; BN_CTX_start(ctx); p = BN_CTX_get(ctx); g = BN_CTX_get(ctx); s = BN_CTX_get(ctx); x = BN_CTX_get(ctx); v = BN_CTX_get(ctx); if (v == NULL) goto err; /* * Count the number of primes we know that are bits long. * Just use the built-in values for now. Better would be * to include the external tables as well, ignoring any * duplicates. Maybe later. */ c = 0; for (i = 0; i < SRP_nparams; i++) { srp_get_param(i, p, g); j = BN_num_bits(p); bits_add(j); if (j == bits) c++; } if (c == 0) { printf("no primes found with %d bits\n", bits); bits_dump(); return(-1); } printf("%d candidate groups found.\n", c); /* Pick one, and get it. */ which = arc4random() % c; c = 0; for (i = 0; i < SRP_nparams; i++) { srp_get_param(i, p, g); j = BN_num_bits(p); if (j == bits) { if (c == which) break; c++; } } /* Pick a random salt. */ if (!BN_rand(s, SALTLEN, 0, 0)) goto err; slen = BN_num_bytes(s); salt = malloc(slen); if (!BN_bn2bin(s, salt)) goto err; if ( pp ) { ckstrncpy(passphrase1,pp,sizeof(passphrase1)); passphrase2[0] = '\0'; ok = 1; } else { struct txtbox tb[2]; tb[0].t_buf = passphrase1; tb[0].t_len = sizeof(passphrase1); tb[0].t_lbl = "New passphrase (empty for no passphrase): "; tb[0].t_dflt = NULL; tb[0].t_echo = 2; tb[1].t_buf = passphrase2; tb[1].t_len = sizeof(passphrase2); tb[1].t_lbl = "New passphrase (again): "; tb[1].t_dflt = NULL; tb[1].t_echo = 2; /* Get the (new) passphrase. */ snprintf(comment, sizeof(comment), "Enter SRP passphrase for '%s'\n", uidbuf); ok = uq_mtxt(comment, NULL, 2, tb); if ( !ok ) { printf("Cancelled\n"); return(-1); } if (strcmp(passphrase1, passphrase2) != 0) { memset(passphrase1, 0, strlen(passphrase1)); memset(passphrase2, 0, strlen(passphrase2)); fprintf(stderr,"passphrase mismatch, no action taken\n"); return(-1); } } /* Calculate x = HASH(salt | HASH(username | passphrase)). */ srp_x_calc(salt, slen, uidbuf, passphrase1, x); memset(passphrase1, 0, strlen(passphrase1)); memset(passphrase2, 0, strlen(passphrase2)); /* Calculate v = g^x mod p. */ if (!BN_mod_exp(v, g, x, p, ctx2)) goto err; /* Create the file. */ fd = open(filename, _O_WRONLY | _O_CREAT | _O_TRUNC, 0600); if (fd < 0) { fprintf(stderr, "can't create '%s'\n", filename); return(-1); } outfile = _fdopen(fd, "w"); if (outfile == NULL) { fprintf(stderr, "can't open '%s'\n", filename); close(fd); return(-1); } /* Convert to base64 and write it all out. */ buf = malloc(8 * 1024); fprintf(outfile, "%s:", uidbuf); srp_bn2tfmt(buf, v); fprintf(outfile, "%s:", buf); srp_bn2tfmt(buf, s); fprintf(outfile, "%s::", buf); srp_bn2tfmt(buf, p); fprintf(outfile, "%s:", buf); srp_bn2tfmt(buf, g); fprintf(outfile, "%s\n", buf); fclose(outfile); if ( Bits ) bits_free(&Bits); BN_CTX_end(ctx); BN_CTX_free(ctx); BN_CTX_free(ctx2); return 0; err: if ( Bits ) bits_free(&Bits); fprintf(stderr, "BIGNUM error\n"); return -1; } else #endif /* CK_SRP */ { if (!quiet) printf("Generating public/private %s key pair.\n", key_type_name(type)); private = key_generate(type, bits); if (private == NULL) { fprintf(stderr, "key_generate failed"); return -1; } public = key_from_private(private); /* Ask for a passphrase (twice). */ if (pp) ckstrncpy(passphrase1, pp, sizeof(passphrase1)); else { struct txtbox tb[2]; tb[0].t_buf = passphrase1; tb[0].t_len = sizeof(passphrase1); tb[0].t_lbl = "New passphrase (empty for no passphrase): "; tb[0].t_dflt = NULL; tb[0].t_echo = 2; tb[1].t_buf = passphrase2; tb[1].t_len = sizeof(passphrase2); tb[1].t_lbl = "New passphrase (again): "; tb[1].t_dflt = NULL; tb[1].t_echo = 2; /* Get the (new) passphrase. */ snprintf(comment, sizeof(comment), "Enter SSH passphrase\n"); ok = uq_mtxt(comment, NULL, 2, tb); if ( !ok ) { printf("Cancelled\n"); return(-1); } if (strcmp(passphrase1, passphrase2) != 0) { fprintf(stderr, "passphrase mismatch, no action taken\n"); return(-1); } if (strcmp(passphrase1, passphrase2) != 0) { memset(passphrase1, 0, strlen(passphrase1)); memset(passphrase2, 0, strlen(passphrase2)); fprintf(stderr,"passphrase mismatch, no action taken\n"); return(-1); } /* Clear the other copy of the passphrase. */ memset(passphrase2, 0, strlen(passphrase2)); } if (cmd_comment) { strlcpy(comment, cmd_comment, sizeof(comment)); } else { /* Create default commend field for the passphrase. */ if (gethostname(kg_hostname, sizeof(kg_hostname)) < 0) { perror("get_hostname"); return -1; } snprintf(comment, sizeof comment, "%s@%s", uidbuf, kg_hostname); } /* Save the key with the given passphrase and comment. */ if (!key_save_private(private, filename, passphrase1, comment)) { printf("Saving the key failed: %s.\n", filename); memset(passphrase1, 0, strlen(passphrase1)); return -1; } /* Clear the passphrase. */ memset(passphrase1, 0, strlen(passphrase1)); /* Clear the private key and the random number generator. */ key_free(private); arc4random_stir(); if (!quiet) printf("Your identification has been saved in %s.\n", filename); ckstrncpy(pubfile,filename,sizeof(pubfile)); ckstrncat(pubfile,".pub",sizeof(pubfile)); fd = open(pubfile, _O_WRONLY | _O_CREAT | _O_TRUNC, 0644); if (fd == -1) { printf("Could not save your public key in %s\n", pubfile); return -1; } f = _fdopen(fd, "w"); if (f == NULL) { printf("_fdopen %s failed", pubfile); return -1; } if (!key_write(public, f)) fprintf(stderr, "write key failed"); fprintf(f, " %s\n", comment); fclose(f); if (!quiet) { char *fp = key_fingerprint(public, SSH_FP_MD5, SSH_FP_HEX); printf("Your public key has been saved in %s.\n", pubfile); printf("The key fingerprint is:\n"); printf("%s %s\n", fp, comment); free(fp); } key_free(public); } return 0; } void ssh_start_agent(void) { extern char exedir[]; char buf[CKMAXPATH]; STARTUPINFO si; SECURITY_ATTRIBUTES sa; PROCESS_INFORMATION pi; ckstrncpy(buf,exedir,CKMAXPATH); ckstrncat(buf,"ssh-agent.exe",CKMAXPATH); memset( &si, 0, sizeof(STARTUPINFO) ); // Initialize struct si.cb = sizeof(STARTUPINFO); si.dwFlags = STARTF_USESTDHANDLES | STARTF_USESHOWWINDOW; si.wShowWindow = 0; // Don't show the console window (DOS box) printf("Starting SSH Agent..."); if (CreateProcess ( NULL, buf, NULL, NULL, FALSE, // bInheritHandler 0, NULL, NULL, &si, &pi)) { CloseHandle(pi.hProcess); CloseHandle(pi.hThread); sleep(3); printf("Complete\n"); return; } printf("Failed\n"); } int ssh_agent_delete_file(const char *filename) { Key *public; char *comment = NULL; int success; AuthenticationConnection *ac = NULL; /* At first, get a connection to the authentication agent. */ ac = ssh_get_authentication_connection(); if (ac == NULL) { ssh_start_agent(); ac = ssh_get_authentication_connection(); } if (ac == NULL) { xprintf("?Could not open a connection to authentication agent: ssh-agent.exe.\n"); return(-1); } public = key_load_public(filename, &comment); if (public == NULL) { xprintf("Bad key file %s\n", filename); return -1; } if (success = ssh_remove_identity(ac, public)) { if ( comment && strcmp(filename,comment)) xfprintf(stderr, "Identity removed: %s (%s)\n", filename, comment); else xfprintf(stderr, "Identity removed: %s\n", filename); } else xfprintf(stderr, "Could not remove identity: %s\n", filename); key_free(public); if (comment) free(comment); ssh_close_authentication_connection(ac); return(success ? 0 : -1); } /* Send a request to remove all identities. */ int ssh_agent_delete_all(void) { int success = 1; AuthenticationConnection *ac = NULL; /* At first, get a connection to the authentication agent. */ ac = ssh_get_authentication_connection(); if (ac == NULL) { ssh_start_agent(); ac = ssh_get_authentication_connection(); } if (ac == NULL) { xprintf("?Could not open a connection to authentication agent: ssh-agent.exe.\n"); return(-1); } if (!ssh_remove_all_identities(ac, 1)) success = 0; /* ignore error-code for ssh2 */ ssh_remove_all_identities(ac, 2); if (success) xfprintf(stderr, "All identities removed.\n"); else xfprintf(stderr, "Failed to remove all identities.\n"); ssh_close_authentication_connection(ac); return(success ? 0 : -1); } int ssh_agent_add_file(const char *filename) { struct _stat st; Key *private; char *comment = NULL; char msg[1024], prompt[1024]; char pass[300]=""; int success; AuthenticationConnection *ac = NULL; /* At first, get a connection to the authentication agent. */ ac = ssh_get_authentication_connection(); if (ac == NULL) { ssh_start_agent(); ac = ssh_get_authentication_connection(); } if (ac == NULL) { xprintf("?Could not open a connection to authentication agent: ssh-agent.exe.\n"); return(-1); } if (stat(filename, &st) < 0) { perror(filename); return(-1); } /* At first, try empty passphrase */ private = key_load_private(filename, "", &comment); /* try last */ if (private == NULL) { /* clear passphrase since it did not work */ snprintf(msg, sizeof msg, "Passphrase required to access %.200s\n", filename); if (comment != NULL) snprintf(prompt, sizeof prompt, "Enter passphrase for \"%.200s\": ", comment); else snprintf(prompt, sizeof prompt, "Enter passphrase: "); for (;;) { int ok = uq_txt(msg,prompt,2,NULL,pass,300,NULL,DEFAULT_UQ_TIMEOUT); if (!ok || strcmp(pass, "") == 0) { free(comment); memset(pass,0,strlen(pass)); return(0); } private = key_load_private(filename, pass, &comment); if (private != NULL) break; snprintf(msg, sizeof msg, "Bad passphrase, try again!\n" "Need passphrase for %.200s\n", filename); memset(pass,0,strlen(pass)); } } if (success = ssh_add_identity(ac, private, comment ? comment : filename)) { if ( comment && strcmp(filename,comment)) xfprintf(stderr, "Identity added: %s (%s)\n", filename, comment); else xfprintf(stderr, "Identity added: %s\n", filename); } else { xfprintf(stderr, "Could not add identity: %s\n", filename); } if (comment) free(comment); key_free(private); ssh_close_authentication_connection(ac); return(success ? 0 : -1); } int ssh_agent_list_identities(int do_fp) { Key *key; char *comment, *fp; int had_identities = 0; int version; AuthenticationConnection *ac = NULL; /* At first, get a connection to the authentication agent. */ ac = ssh_get_authentication_connection(); if (ac == NULL) { ssh_start_agent(); ac = ssh_get_authentication_connection(); } if (ac == NULL) { xprintf("?Could not open a connection to authentication agent: ssh-agent.exe.\n"); return(-1); } for (version = 1; version <= 2; version++) { for (key = ssh_get_first_identity(ac, &comment, version); key != NULL; key = ssh_get_next_identity(ac, &comment, version)) { had_identities = 1; if (do_fp) { fp = key_fingerprint(key, SSH_FP_MD5, SSH_FP_HEX); xprintf("%d %s %s (%s)\n", key_size(key), fp, comment, key_type(key)); free(fp); } else { if (!key_write(key, stdout)) xfprintf(stderr, "key_write failed"); xfprintf(stdout, " %s\n", comment); } key_free(key); free(comment); } } if (!had_identities) xprintf("The agent has no identities.\n"); ssh_close_authentication_connection(ac); return (0); } #endif /* SSHBUILTIN */ #endif /* CK_SSL */ #ifdef COMMENT ssh [-l login_name] hostname | user@hostname [command] ssh [ - afgknqstvxACNPTX1246] [ - b bind_address] [ - c cipher_spec] [ - e escape_char] [ - i identity_file] [-l login_name] [-m mac_spec] [-o option] [-p port] [-F config- file] [ - L port:host:hostport] [-R port:host:hostport] [-D port] hostname | user@hostname [command] The options are as follows: -a Disables forwarding of the authentication agent con- nection. -A Enables forwarding of the authentication agent connec- tion. This can also be specified on a per-host basis in a configuration file. -b bind_address Specify the interface to transmit from on machines with multiple interfaces or aliased addresses. -c blowfish|3des|des Selects the cipher to use for encrypting the session. 3des is used by default. It is believed to be secure. 3des (triple-des) is an encrypt-decrypt-encrypt triple with three different keys. blowfish is a fast block cipher, it appears very secure and is much faster than 3des. des is only supported in the ssh client for interoperability with legacy protocol 1 implementations that do not support the 3des cipher. Its use is strongly discouraged due to cryptographic weaknesses. -c cipher_spec Additionally, for protocol version 2 a comma-separated list of ciphers can be specified in order of preference. See Ciphers for more information. -e ch|^ch|none Sets the escape character for sessions with a pty (default: `~' ) . The escape character is only recog- nized at the beginning of a line. The escape character followed by a dot (`.') closes the connection, fol- lowed by control-Z suspends the connection, and fol- lowed by itself sends the escape character once. Set- ting the character to ``none'' disables any escapes and makes the session fully transparent. -f Requests ssh to go to background just before command execution. This is useful if ssh is going to ask for passwords or passphrases, but the user wants it in the background. This implies -n. The recommended way to start X11 programs at a remote site is with something like ssh -f host xterm. -g Allows remote hosts to connect to local forwarded ports. -i identity_file Selects a file from which the identity (private key) for RSA or DSA authentication is read. The default is $HOME/.ssh/identity for protocol version 1, and $HOME/.ssh/id_rsa and $HOME/.ssh/id_dsa for protocol version 2. Identity files may also be specified on a per-host basis in the configuration file. It is possi- ble to have multiple -i options (and multiple identi- ties specified in configuration files). -I smartcard_device Specifies which smartcard device to use. The argument is the device ssh should use to communicate with a smartcard used for storing the user's private RSA key. - k Disables forwarding of Kerberos tickets and AFS tokens. This may also be specified on a per-host basis in the configuration file. -l login_name Specifies the user to log in as on the remote machine. This also may be specified on a per-host basis in the configuration file. -m mac_spec Additionally, for protocol version 2 a comma-separated list of MAC (message authentication code) algorithms can be specified in order of preference. See the MACs keyword for more information. -n Redirects stdin from /dev/null (actually, prevents reading from stdin). This must be used when ssh is run in the background. A common trick is to use this to run X11 programs on a remote machine. For example, ssh -n shadows.cs.hut.fi emacs & will start an emacs on shadows.cs.hut.fi, and the X11 connection will be automatically forwarded over an encrypted channel. The ssh program will be put in the background. (This does not work if ssh needs to ask for a password or passphrase; see also the -f option.) -N Do not execute a remote command. This is useful for just forwarding ports (protocol version 2 only). -o option Can be used to give options in the format used in the configuration file. This is useful for specifying options for which there is no separate command-line flag. -p port Port to connect to on the remote host. This can be specified on a per-host basis in the configuration file. -P Use a non-privileged port for outgoing connections. This can be used if a firewall does not permit connec- tions from privileged ports. Note that this option turns off RhostsAuthentication and RhostsRSAAuthentica- tion for older servers. -q Quiet mode. Causes all warning and diagnostic mes- sages to be suppressed. -s May be used to request invocation of a subsystem on the remote system. Subsystems are a feature of the SSH2 protocol which facilitate the use of SSH as a secure transport for other applications (eg. sftp). The sub- system is specified as the remote command. -t Force pseudo-tty allocation. This can be used to exe- cute arbitrary screen-based programs on a remote machine, which can be very useful, e.g., when imple- menting menu services. Multiple -t options force tty allocation, even if ssh has no local tty. -T Disable pseudo-tty allocation. -v Verbose mode. Causes ssh to print debugging messages about its progress. This is helpful in debugging con- nection, authentication, and configuration problems. Multiple - v options increases the verbosity. Maximum is 3. -x Disables X11 forwarding. -X Enables X11 forwarding. This can also be specified on a per-host basis in a configuration file. -C Requests compression of all data (including stdin, stdout, stderr, and data for forwarded X11 and TCP/IP connections). The compression algorithm is the same used by gzip(1), and the ``level'' can be controlled by the CompressionLevel option (see below). Compression is desirable on modem lines and other slow connections, but will only slow down things on fast networks. The default value can be set on a host-by-host basis in the configuration files; see the Compression option below. -F configfile Specifies an alternative per-user configuration file. If a configuration file is given on the command line, the system-wide configuration file (/etc/ssh/ssh_config) will be ignored. The default for the per-user configuration file is $HOME/.ssh/config. -L port:host:hostport Specifies that the given port on the local (client) host is to be forwarded to the given host and port on the remote side. This works by allocating a socket to listen to port on the local side, and whenever a con- nection is made to this port, the connection is for- warded over the secure channel, and a connection is made to host port hostport from the remote machine. Port forwardings can also be specified in the confi- guration file. Only root can forward privileged ports. IPv6 addresses can be specified with an alternative syntax: port/host/hostport -R port:host:hostport Specifies that the given port on the remote (server) host is to be forwarded to the given host and port on the local side. This works by allocating a socket to listen to port on the remote side, and whenever a con- nection is made to this port, the connection is for- warded over the secure channel, and a connection is made to host port hostport from the local machine. Port forwardings can also be specified in the confi- guration file. Privileged ports can be forwarded only when logging in as root on the remote machine. IPv6 addresses can be specified with an alternative syntax: port/host/hostport -D port Specifies a local ``dynamic'' application-level port forwarding. This works by allocating a socket to listen to port on the local side, and whenever a con- nection is made to this port, the connection is for- warded over the secure channel, and the application protocol is then used to determine where to connect to from the remote machine. Currently the SOCKS4 protocol is supported, and ssh will act as a SOCKS4 server. Only root can forward privileged ports. Dynamic port forwardings can also be specified in the configuration file. -1 Forces ssh to try protocol version 1 only. -2 Forces ssh to try protocol version 2 only. -4 Forces ssh to use IPv4 addresses only. -6 Forces ssh to use IPv6 addresses only. #endif /* COMMENT */