diff options
-rw-r--r-- | include/libssh/priv.h | 37 | ||||
-rw-r--r-- | libssh/keyfiles.c | 285 | ||||
-rw-r--r-- | libssh/match.c | 4 |
3 files changed, 195 insertions, 131 deletions
diff --git a/include/libssh/priv.h b/include/libssh/priv.h index 84b4320d..f8b769aa 100644 --- a/include/libssh/priv.h +++ b/include/libssh/priv.h @@ -245,7 +245,7 @@ struct ssh_options_struct { typedef struct ssh_crypto_struct { bignum e,f,x,k,y; unsigned char session_id[SHA_DIGEST_LEN]; - + unsigned char encryptIV[SHA_DIGEST_LEN*2]; unsigned char decryptIV[SHA_DIGEST_LEN*2]; @@ -307,32 +307,32 @@ struct ssh_session { /* status flags */ int closed; int closed_by_except; - - int connected; + + int connected; /* !=0 when the user got a session handle */ int alive; /* two previous are deprecated */ int auth_service_asked; - + /* socket status */ int blocking; // functions should block - - STRING *banner; /* that's the issue banner from + + STRING *banner; /* that's the issue banner from the server */ char *remotebanner; /* that's the SSH- banner from remote host. */ - char *discon_msg; /* disconnect message from + char *discon_msg; /* disconnect message from the remote host */ BUFFER *in_buffer; PACKET in_packet; BUFFER *out_buffer; - + /* the states are used by the nonblocking stuff to remember */ /* where it was before being interrupted */ int packet_state; int dh_handshake_state; STRING *dh_server_signature; //information used by dh_handshake. - + KEX server_kex; KEX client_kex; BUFFER *in_hashbuf; @@ -342,7 +342,7 @@ struct ssh_session { CHANNEL *channels; /* linked list of channels */ int maxchannel; - int exec_channel_opened; /* version 1 only. more + int exec_channel_opened; /* version 1 only. more info in channels1.c */ AGENT *agent; /* ssh agent */ @@ -353,7 +353,7 @@ struct ssh_session { PRIVATE_KEY *rsa_key; PRIVATE_KEY *dsa_key; /* auths accepted by server */ - int auth_methods; + int auth_methods; int hostkeys; /* contains type of host key wanted by client, in server impl */ struct ssh_message *ssh_message; /* ssh message */ int log_verbosity; /*cached copy of the option structure */ @@ -407,7 +407,7 @@ struct ssh_channel_request { u32 pxwidth; u32 pxheight; STRING *modes; - + /* env type request */ char *var_name; char *var_value; @@ -552,7 +552,7 @@ int packet_wait(SSH_SESSION *session,int type,int blocking); int packet_flush(SSH_SESSION *session, int enforce_blocking); /* connect.c */ SSH_SESSION *ssh_session_new(); -socket_t ssh_connect_host(SSH_SESSION *session, const char *host,const char +socket_t ssh_connect_host(SSH_SESSION *session, const char *host,const char *bind_addr, int port, long timeout, long usec); /* in kex.c */ @@ -586,7 +586,7 @@ SIGNATURE *signature_from_string(SSH_SESSION *session, STRING *signature,PUBLIC_ void signature_free(SIGNATURE *sign); STRING *ssh_do_sign_with_agent(struct ssh_session *session, struct buffer_struct *buf, struct public_key_struct *publickey); -STRING *ssh_do_sign(SSH_SESSION *session,BUFFER *sigbuf, +STRING *ssh_do_sign(SSH_SESSION *session,BUFFER *sigbuf, PRIVATE_KEY *privatekey); STRING *ssh_sign_session_id(SSH_SESSION *session, PRIVATE_KEY *privatekey); STRING *ssh_encrypt_rsa1(SSH_SESSION *session, STRING *data, PUBLIC_KEY *key); @@ -656,7 +656,7 @@ u32 ssh_crc32(char *buffer, int len); int ssh_userauth1_none(SSH_SESSION *session, char *username); int ssh_userauth1_offer_pubkey(SSH_SESSION *session, char *username, int type, STRING *pubkey); -int ssh_userauth1_password(SSH_SESSION *session, char *username, +int ssh_userauth1_password(SSH_SESSION *session, char *username, char *password); /* in misc.c */ /* gets the user home dir. */ @@ -669,7 +669,7 @@ u64 ntohll(u64); /* channels1.c */ int channel_open_session1(CHANNEL *channel); -int channel_request_pty_size1(CHANNEL *channel, char *terminal,int cols, +int channel_request_pty_size1(CHANNEL *channel, char *terminal,int cols, int rows); int channel_change_pty_size1(CHANNEL *channel, int cols, int rows); int channel_request_shell1(CHANNEL *channel); @@ -681,6 +681,9 @@ int channel_write1(CHANNEL *channel, void *data, int len); int ssh_handle_packets(SSH_SESSION *session); +/* match.c */ +int match_hostname(const char *host, const char *pattern, unsigned int len); + /* log.c */ #define _enter_function(sess) \ @@ -718,7 +721,7 @@ char *my_gcry_bn2dec(bignum bn); #endif /* !HAVE_LIBGCRYPT */ #ifdef __cplusplus -} +} #endif #endif /* _LIBSSH_PRIV_H */ diff --git a/libssh/keyfiles.c b/libssh/keyfiles.c index 3803f03d..1a145dea 100644 --- a/libssh/keyfiles.c +++ b/libssh/keyfiles.c @@ -28,6 +28,7 @@ MA 02111-1307, USA. */ #include <unistd.h> #include <stdlib.h> #include <fcntl.h> +#include <ctype.h> #include "libssh/priv.h" #ifdef HAVE_LIBGCRYPT #include <gcrypt.h> @@ -780,79 +781,160 @@ static int alldigits(char *s) } return 1; } +/** @} + */ -#define FOUND_OTHER ( (void *)-1) -#define FILE_NOT_FOUND ((void *)-2) -/* will return a token array containing [host,]ip keytype key */ -/* NULL if no match was found, FOUND_OTHER if the match is on an other */ -/* type of key (ie dsa if type was rsa) */ -static char **ssh_parse_knownhost(char *filename, char *hostname, char *type){ - FILE *file=fopen(filename,"r"); +/** \brief lowercases a string + * \arg string string to lowercase + * \internal + */ +static void lowercase(char *string){ + for (;*string;string++){ + *string=tolower(*string); + } +} +/** \brief frees a token array + * \internal + */ +static void tokens_free(char **tokens){ + free(tokens[0]); + /* It's not needed to free other pointers because tokens generated by + * space_tokenize fit all in one malloc + */ + free(tokens); +} +/** \brief returns one line of known host file + * will return a token array containing (host|ip) keytype key + * \internal + * \returns NULL if no match was found or the file was not found + * \returns found_type type of key (ie "dsa","ssh-rsa1"). Don't free that value. + */ + +static char **ssh_get_knownhost_line(SSH_SESSION *session,FILE **file, char *filename,char **found_type){ char buffer[4096]; char *ptr; - char *found_type; char **tokens; - char **ret=NULL; - if(!file) - return FILE_NOT_FOUND; - while(fgets(buffer,sizeof(buffer),file)){ + enter_function(); + if(!*file){ + *file=fopen(filename,"r"); + if(!file){ + leave_function(); + return NULL; + } + } + while(fgets(buffer,sizeof(buffer),*file)){ ptr=strchr(buffer,'\n'); if(ptr) *ptr=0; if((ptr=strchr(buffer,'\r'))) *ptr=0; - if(!buffer[0]) + if(!buffer[0] || buffer[0]=='#') continue; /* skip empty lines */ tokens=space_tokenize(buffer); if(!tokens[0] || !tokens[1] || !tokens[2]){ /* it should have at least 3 tokens */ - free(tokens[0]); - free(tokens); + tokens_free(tokens); continue; } - found_type = tokens[1]; + *found_type = tokens[1]; if(tokens[3]){ /* openssh rsa1 format has 4 tokens on the line. Recognize it by the fact that everything is all digits */ if (tokens[4]) { /* that's never valid */ - free(tokens[0]); - free(tokens); + tokens_free(tokens); continue; } if (alldigits(tokens[1]) && alldigits(tokens[2]) && alldigits(tokens[3])) { - found_type = "ssh-rsa1"; + *found_type = "ssh-rsa1"; } else { /* 3 tokens only, not four */ - free(tokens[0]); - free(tokens); + tokens_free(tokens); continue; } } - ptr=tokens[0]; - while(*ptr==' ') - ptr++; /* skip the initial spaces */ - /* we allow spaces or ',' to follow the hostname. It's generaly an IP */ - /* we don't care about ip, if the host key match there is no problem with ip */ - if(strncasecmp(ptr,hostname,strlen(hostname))==0){ - if(ptr[strlen(hostname)]==' ' || ptr[strlen(hostname)]=='\0' - || ptr[strlen(hostname)]==','){ - if(strcasecmp(found_type, type)==0){ - fclose(file); - return tokens; - } else { - ret=FOUND_OTHER; - } - } - } - /* not the good one */ - free(tokens[0]); - free(tokens); + leave_function(); + return tokens; } - fclose(file); - /* we did not find */ - return ret; + fclose(*file); + *file=NULL; + /* we did not find anything, end of file*/ + leave_function(); + return NULL; } -/** @} +/** \brief Check the public key in the known host line matches the + * public key of the currently connected server. + * \arg tokens list of tokens in the known_hosts line. + * \return 1 if the key matches + * \return 0 if the key doesn't match + * \return -1 on error + */ + +static int check_public_key(SSH_SESSION *session, char **tokens){ + char *pubkey_64; + BUFFER *pubkey_buffer; + STRING *pubkey=session->current_crypto->server_pubkey; + /* ok we found some public key in known hosts file. now un-base64it */ + if (alldigits(tokens[1])) { + /* openssh rsa1 format */ + bignum tmpbn; + int i; + unsigned int len; + STRING *tmpstring; + + pubkey_buffer = buffer_new(); + tmpstring = string_from_char("ssh-rsa1"); + buffer_add_ssh_string(pubkey_buffer, tmpstring); + + for (i = 2; i < 4; i++) { /* e, then n */ + tmpbn = NULL; + bignum_dec2bn(tokens[i], &tmpbn); + /* for some reason, make_bignum_string does not work + because of the padding which it does --kv */ + /* tmpstring = make_bignum_string(tmpbn); */ + /* do it manually instead */ + len = bignum_num_bytes(tmpbn); + tmpstring = malloc(4 + len); + tmpstring->size = htonl(len); +#ifdef HAVE_LIBGCRYPT + bignum_bn2bin(tmpbn, len, tmpstring->string); +#elif defined HAVE_LIBCRYPTO + bignum_bn2bin(tmpbn, tmpstring->string); +#endif + bignum_free(tmpbn); + buffer_add_ssh_string(pubkey_buffer, tmpstring); + free(tmpstring); + } + } else { + /* ssh-dss or ssh-rsa */ + pubkey_64=tokens[2]; + pubkey_buffer=base64_to_bin(pubkey_64); + } + + if(!pubkey_buffer){ + ssh_set_error(session,SSH_FATAL,"verifying that server is a known host : base 64 error"); + return -1; + } + if(buffer_get_len(pubkey_buffer)!=string_len(pubkey)){ + buffer_free(pubkey_buffer); + return 0; + } + /* now test that they are identical */ + if(memcmp(buffer_get(pubkey_buffer),pubkey->string,buffer_get_len(pubkey_buffer))!=0){ + buffer_free(pubkey_buffer); + return 0; + } + buffer_free(pubkey_buffer); + return 1; +} + + +/* How it's working : + * 1- we open the known host file and bitch if it doesn't exist + * 2- we need to examine each line of the file, until going on state SSH_SERVER_KNOWN_OK: + * - there's a match. if the key is good, state is SSH_SERVER_KNOWN_OK, + * else it's SSH_SERVER_KNOWN_CHANGED (or SSH_SERVER_FOUND_OTHER) + * - there's no match : no change + * - there's an antimatch : no change (that line is simply ignored) */ /** \addtogroup ssh_session * @{ */ @@ -874,80 +956,59 @@ static char **ssh_parse_knownhost(char *filename, char *hostname, char *type){ * \todo TODO this is a real mess. Clean this up someday */ int ssh_is_server_known(SSH_SESSION *session){ - char *pubkey_64; - BUFFER *pubkey_buffer; - STRING *pubkey=session->current_crypto->server_pubkey; + char **tokens; + char *host; + char *type; + int match; + FILE *file=NULL; + int ret=SSH_SERVER_NOT_KNOWN; + enter_function(); ssh_options_default_known_hosts_file(session->options); if(!session->options->host){ ssh_set_error(session,SSH_FATAL,"Can't verify host in known hosts if the hostname isn't known"); + leave_function(); return SSH_SERVER_ERROR; } - tokens=ssh_parse_knownhost(session->options->known_hosts_file, - session->options->host,session->current_crypto->server_pubkey_type); - if(tokens==NULL) - return SSH_SERVER_NOT_KNOWN; - if(tokens==FOUND_OTHER) - return SSH_SERVER_FOUND_OTHER; - if(tokens==FILE_NOT_FOUND){ - ssh_set_error(session,SSH_FATAL,"verifying that server is a known host : file %s not found",session->options->known_hosts_file); - return SSH_SERVER_ERROR; - } - /* ok we found some public key in known hosts file. now un-base64it */ - /* Some time, we may verify the IP address did not change. I honestly think */ - /* it's not an important matter as IP address are known not to be secure */ - /* and the crypto stuff is enough to prove the server's identity */ - if (alldigits(tokens[1])) { /* openssh rsa1 format */ - bignum tmpbn; - int i; - unsigned int len; - STRING *tmpstring; - - pubkey_buffer = buffer_new(); - tmpstring = string_from_char("ssh-rsa1"); - buffer_add_ssh_string(pubkey_buffer, tmpstring); - - for (i = 2; i < 4; i++) { /* e, then n */ - tmpbn = NULL; - bignum_dec2bn(tokens[i], &tmpbn); - /* for some reason, make_bignum_string does not work - because of the padding which it does --kv */ - /* tmpstring = make_bignum_string(tmpbn); */ - /* do it manually instead */ - len = bignum_num_bytes(tmpbn); - tmpstring = malloc(4 + len); - tmpstring->size = htonl(len); -#ifdef HAVE_LIBGCRYPT - bignum_bn2bin(tmpbn, len, tmpstring->string); -#elif defined HAVE_LIBCRYPTO - bignum_bn2bin(tmpbn, tmpstring->string); -#endif - bignum_free(tmpbn); - buffer_add_ssh_string(pubkey_buffer, tmpstring); - free(tmpstring); - } - } else { - pubkey_64=tokens[2]; - pubkey_buffer=base64_to_bin(pubkey_64); - } - /* at this point, we may free the tokens */ - free(tokens[0]); - free(tokens); - if(!pubkey_buffer){ - ssh_set_error(session,SSH_FATAL,"verifying that server is a known host : base 64 error"); - return SSH_SERVER_ERROR; - } - if(buffer_get_len(pubkey_buffer)!=string_len(pubkey)){ - buffer_free(pubkey_buffer); - return SSH_SERVER_KNOWN_CHANGED; - } - /* now test that they are identical */ - if(memcmp(buffer_get(pubkey_buffer),pubkey->string,buffer_get_len(pubkey_buffer))!=0){ - buffer_free(pubkey_buffer); - return SSH_SERVER_KNOWN_CHANGED; - } - buffer_free(pubkey_buffer); - return SSH_SERVER_KNOWN_OK; + host=strdup(session->options->host); + lowercase(host); + do { + tokens=ssh_get_knownhost_line(session,&file,session->options->known_hosts_file,&type); + // + /* End of file, return the current state */ + if(tokens==NULL) + break; + match=match_hostname(host,tokens[0],strlen(tokens[0])); + if(match){ + // We got a match. Now check the key type + if(strcmp(session->current_crypto->server_pubkey_type,type)!=0){ + // different type. We don't override the known_changed error which is more important + if(ret != SSH_SERVER_KNOWN_CHANGED) + ret= SSH_SERVER_FOUND_OTHER; + tokens_free(tokens); + continue; + } + // so we know the key type is good. We may get a good key or a bad key. + match=check_public_key(session,tokens); + tokens_free(tokens); + if(match<0){ + leave_function(); + return SSH_SERVER_ERROR; + } + if(match==1){ + fclose(file); + leave_function(); + return SSH_SERVER_KNOWN_OK; + } + if(match==0){ + /* We override the status with the wrong key state */ + ret=SSH_SERVER_KNOWN_CHANGED; + } + } + } while (1); + /* Return the current state at end of file */ + leave_function(); + return ret; } /** You generaly use it when ssh_is_server_known() answered SSH_SERVER_NOT_KNOWN diff --git a/libssh/match.c b/libssh/match.c index f3488a09..67f65e85 100644 --- a/libssh/match.c +++ b/libssh/match.c @@ -37,7 +37,7 @@ #include <sys/types.h> #include <ctype.h> #include <string.h> - +#include "libssh/priv.h" /* * Returns true if the given string matches the pattern (which may contain ? * and * as wildcards), and zero if it does not match. @@ -164,6 +164,6 @@ static int match_pattern_list(const char *string, const char *pattern, * indicate negation). Returns -1 if negation matches, 1 if there is * a positive match, 0 if there is no match at all. */ -static int match_hostname(const char *host, const char *pattern, unsigned int len) { +int match_hostname(const char *host, const char *pattern, unsigned int len) { return match_pattern_list(host, pattern, len, 1); } |