/* client.c file */ /* Copyright (c) 2003-2008 Aris Adamantiadis This file is part of the SSH Library The SSH Library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version. The SSH Library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with the SSH Library; see the file COPYING. If not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ #include #include #include #include "libssh/priv.h" #include "libssh/ssh2.h" #define set_status(opt,status) do {\ if (opt->connect_status_function) \ opt->connect_status_function(opt->connect_status_arg, status); \ } while (0) /* simply gets a banner from a socket */ char *ssh_get_banner(SSH_SESSION *session){ char buffer[128]; int i = 0; char *ret; enter_function(); while (i < 127) { if(ssh_socket_read(session->socket, &buffer[i], 1)!= SSH_OK){ ssh_set_error(session,SSH_FATAL,"Remote host closed connection"); leave_function(); return NULL; } if (buffer[i] == '\r') buffer[i] = 0; if (buffer[i] == '\n') { buffer[i] = 0; ret= strdup(buffer); leave_function(); return ret; } i++; } ssh_set_error(session,SSH_FATAL,"Too large banner"); leave_function(); return NULL; } static int ssh_analyze_banner(SSH_SESSION *session, int *ssh1, int *ssh2){ char *banner=session->serverbanner; if(strncmp(banner,"SSH-",4)!=0){ ssh_set_error(session,SSH_FATAL,"Protocol mismatch: %s",banner); return -1; } /* a typical banner is : * SSH-1.5-blah * SSH-1.99-blah * SSH-2.0-blah */ switch(banner[4]){ case '1': *ssh1=1; if(banner[6]=='9') *ssh2=1; else *ssh2=0; break; case '2': *ssh1=0; *ssh2=1; break; default: ssh_set_error(session,SSH_FATAL,"Protocol mismatch: %s",banner); return -1; } return 0; } /** \internal * \brief ssh_send_banner sends a SSH banner to the server */ int ssh_send_banner(SSH_SESSION *session,int server){ char *banner; char buffer[128]; enter_function(); banner=session->version==1?CLIENTBANNER1:CLIENTBANNER2; if(session->options->banner) banner=session->options->banner; if(server) session->serverbanner=strdup(banner); else session->clientbanner=strdup(banner); snprintf(buffer,128,"%s\r\n",banner); ssh_socket_write(session->socket,buffer,strlen(buffer)); ssh_socket_blocking_flush(session->socket); leave_function(); return 0; } #define DH_STATE_INIT 0 #define DH_STATE_INIT_TO_SEND 1 #define DH_STATE_INIT_SENT 2 #define DH_STATE_NEWKEYS_TO_SEND 3 #define DH_STATE_NEWKEYS_SENT 4 #define DH_STATE_FINISHED 5 static int dh_handshake(SSH_SESSION *session){ STRING *e,*f,*pubkey,*signature; int ret; enter_function(); switch(session->dh_handshake_state){ case DH_STATE_INIT: buffer_add_u8(session->out_buffer,SSH2_MSG_KEXDH_INIT); dh_generate_x(session); dh_generate_e(session); e=dh_get_e(session); buffer_add_ssh_string(session->out_buffer,e); ret=packet_send(session); free(e); session->dh_handshake_state=DH_STATE_INIT_TO_SEND; if(ret==SSH_ERROR){ leave_function(); return ret; } case DH_STATE_INIT_TO_SEND: ret=packet_flush(session,0); if(ret!=SSH_OK){ leave_function(); return ret; // SSH_ERROR or SSH_AGAIN } session->dh_handshake_state=DH_STATE_INIT_SENT; case DH_STATE_INIT_SENT: ret=packet_wait(session,SSH2_MSG_KEXDH_REPLY,1); if(ret != SSH_OK){ leave_function(); return ret; } pubkey=buffer_get_ssh_string(session->in_buffer); if(!pubkey){ ssh_set_error(session,SSH_FATAL,"No public key in packet"); leave_function(); return SSH_ERROR; } dh_import_pubkey(session,pubkey); f=buffer_get_ssh_string(session->in_buffer); if(!f){ ssh_set_error(session,SSH_FATAL,"No F number in packet"); leave_function(); return SSH_ERROR; } dh_import_f(session,f); free(f); if(!(signature=buffer_get_ssh_string(session->in_buffer))){ ssh_set_error(session,SSH_FATAL,"No signature in packet"); leave_function(); return SSH_ERROR; } session->dh_server_signature=signature; dh_build_k(session); // send the MSG_NEWKEYS buffer_add_u8(session->out_buffer,SSH2_MSG_NEWKEYS); packet_send(session); session->dh_handshake_state=DH_STATE_NEWKEYS_TO_SEND; case DH_STATE_NEWKEYS_TO_SEND: ret=packet_flush(session,0); if(ret != SSH_OK){ leave_function(); return ret; } ssh_log(session, SSH_LOG_RARE, "SSH_MSG_NEWKEYS sent\n"); session->dh_handshake_state=DH_STATE_NEWKEYS_SENT; case DH_STATE_NEWKEYS_SENT: ret=packet_wait(session,SSH2_MSG_NEWKEYS,1); if(ret != SSH_OK){ leave_function(); return ret; } ssh_log(session, SSH_LOG_RARE, "Got SSH_MSG_NEWKEYS\n"); make_sessionid(session); /* set the cryptographic functions for the next crypto */ /* (it is needed for generate_session_keys for key lenghts) */ if(crypt_set_algorithms(session)){ leave_function(); return SSH_ERROR; } generate_session_keys(session); /* verify the host's signature. XXX do it sooner */ signature=session->dh_server_signature; session->dh_server_signature=NULL; if(signature_verify(session,signature)){ free(signature); leave_function(); return SSH_ERROR; } free(signature); /* forget it for now ... */ /* once we got SSH2_MSG_NEWKEYS we can switch next_crypto and current_crypto */ if(session->current_crypto) crypto_free(session->current_crypto); /* XXX later, include a function to change keys */ session->current_crypto=session->next_crypto; session->next_crypto=crypto_new(); session->dh_handshake_state=DH_STATE_FINISHED; leave_function(); return SSH_OK; default: ssh_set_error(session,SSH_FATAL,"Invalid state in dh_handshake():%d",session->dh_handshake_state); leave_function(); return SSH_ERROR; } /* not reached */ leave_function(); return SSH_ERROR; } int ssh_service_request(SSH_SESSION *session,char *service){ STRING *service_s; enter_function(); buffer_add_u8(session->out_buffer,SSH2_MSG_SERVICE_REQUEST); service_s=string_from_char(service); buffer_add_ssh_string(session->out_buffer,service_s); free(service_s); packet_send(session); ssh_log(session, SSH_LOG_PACKET, "Sent SSH_MSG_SERVICE_REQUEST (service %s)\n", service); if(packet_wait(session,SSH2_MSG_SERVICE_ACCEPT,1)){ ssh_set_error(session,SSH_FATAL,"did not receive SERVICE_ACCEPT"); leave_function(); return -1; } ssh_log(session, SSH_LOG_PACKET, "Received SSH_MSG_SERVICE_ACCEPT (service %s)\n", service); leave_function(); return 0; } /** \addtogroup ssh_session * * @{ */ /** \brief connect to the ssh server * \param session ssh session * \return 0 on success, SSH_ERROR on error * \see ssh_new() * \see ssh_disconnect() */ int ssh_connect(SSH_SESSION *session){ int fd; int ssh1, ssh2; SSH_OPTIONS *options=session->options; if(!session->options){ ssh_set_error(session,SSH_FATAL,"Must set options before connect"); return SSH_ERROR; } enter_function(); session->alive=0; session->client=1; ssh_crypto_init(); ssh_socket_init(); if(options->fd==-1 && !options->host){ ssh_set_error(session,SSH_FATAL,"Hostname required"); leave_function(); return SSH_ERROR; } if(options->fd != -1) fd=options->fd; else fd=ssh_connect_host(session,options->host,options->bindaddr,options->port, options->timeout,options->timeout_usec); if(fd<0){ leave_function(); return -1; } set_status(options,0.2); ssh_socket_set_fd(session->socket,fd); session->alive=1; if(!(session->serverbanner=ssh_get_banner(session))){ ssh_socket_close(session->socket); session->alive=0; leave_function(); return -1; } set_status(options,0.4); ssh_log(session, SSH_LOG_RARE, "banner: %s\n", session->serverbanner); /* here we analyse the different protocols the server allows */ if(ssh_analyze_banner(session,&ssh1,&ssh2)){ ssh_socket_close(session->socket); session->alive=0; leave_function(); return -1; } /* here we decide which version of the protocol to use */ if(ssh2 && options->ssh2allowed) session->version=2; else if(ssh1 && options->ssh1allowed) session->version=1; else { ssh_set_error(session,SSH_FATAL, "no version of SSH protocol usable (banner: %s)", session->serverbanner); ssh_socket_close(session->socket); session->alive=0; leave_function(); return -1; } ssh_send_banner(session,0); set_status(options,0.5); switch(session->version){ case 2: if(ssh_get_kex(session,0)){ ssh_socket_close(session->socket); session->alive=0; leave_function(); return -1; } set_status(options,0.6); ssh_list_kex(&session->server_kex); if(set_kex(session)){ ssh_socket_close(session->socket); session->alive=0; leave_function(); return -1; } ssh_send_kex(session,0); set_status(options,0.8); if(dh_handshake(session)){ ssh_socket_close(session->socket); session->alive=0; leave_function(); return -1; } set_status(options,1.0); session->connected=1; break; case 1: if(ssh_get_kex1(session)){ ssh_socket_close(session->socket); session->alive=0; leave_function(); return -1; } set_status(options,0.6); session->connected=1; break; } leave_function(); return 0; } /** this is the banner showing a disclaimer to users who log in, * typically their right or the fact that they will be monitored * \brief get the issue banner from the server * \param session ssh session * \return NULL if there is no issue banner, else a string containing it. */ char *ssh_get_issue_banner(SSH_SESSION *session){ if(!session->banner) return NULL; return string_to_char(session->banner); } /** \brief disconnect from a session (client or server) * \param session ssh session */ void ssh_disconnect(SSH_SESSION *session){ STRING *str; enter_function(); if(ssh_socket_is_open(session->socket)) { buffer_add_u8(session->out_buffer,SSH2_MSG_DISCONNECT); buffer_add_u32(session->out_buffer,htonl(SSH2_DISCONNECT_BY_APPLICATION)); str=string_from_char("Bye Bye"); buffer_add_ssh_string(session->out_buffer,str); free(str); packet_send(session); ssh_socket_close(session->socket); } session->alive=0; leave_function(); ssh_cleanup(session); } const char *ssh_copyright(void) { return LIBSSH_VERSION " (c) 2003-2008 Aris Adamantiadis (aris@0xbadc0de.be)" " Distributed under the LGPL, please refer to COPYING file for informations" " about your rights" ; } /** @} */