1
0
mirror of https://git.libssh.org/projects/libssh.git synced 2025-08-10 06:23:01 +03:00
Files
libssh/libssh/client.c
Andreas Schneider 160f6a08d0 Improve and document ssh_get_banner().
git-svn-id: svn+ssh://svn.berlios.de/svnroot/repos/libssh/trunk@483 7dcaeef0-15fb-0310-b436-a5af3365683c
2009-04-16 07:49:44 +00:00

448 lines
13 KiB
C

/*
* client.c - SSH client functions
*
* This file is part of the SSH Library
*
* Copyright (c) 2003-2008 by Aris Adamantiadis
*
* 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.
*
* vim: ts=2 sw=2 et cindent
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#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)
/**
* @internal
*
* @brief Get a banner from a socket.
*
* The caller has to free memroy.
*
* @param session The session to get the banner from.
*
* @return A newly allocated string with the banner or NULL on error.
*/
char *ssh_get_banner(SSH_SESSION *session) {
char buffer[128] = {0};
char *str = NULL;
int i;
enter_function();
for (i = 0; i < 127; i++) {
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';
str = strdup(buffer);
if (str == NULL) {
leave_function();
return NULL;
}
leave_function();
return str;
}
}
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 Sends a SSH banner to the server.
*
* @param session The SSH session to use.
*
* @param server Send client or server banner.
*
* @return 0 on success, < 0 on error.
*/
int ssh_send_banner(SSH_SESSION *session, int server) {
const char *banner;
char buffer[128] = {0};
enter_function();
banner = session->version == 1 ? CLIENTBANNER1 : CLIENTBANNER2;
if (session->options->banner) {
banner=session->options->banner;
}
if (server) {
session->serverbanner = strdup(banner);
if (session->serverbanner == NULL) {
return -1;
}
} else {
session->clientbanner = strdup(banner);
if (session->clientbanner == NULL) {
return -1;
}
}
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");
ret = make_sessionid(session);
if (ret != SSH_OK) {
leave_function();
return SSH_ERROR;
}
/* 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();
if (session->next_crypto == NULL) {
leave_function();
return SSH_ERROR;
}
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, const 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_OK) {
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) < 0) {
ssh_socket_close(session->socket);
session->alive=0;
leave_function();
return -1;
}
set_status(options,0.6);
ssh_list_kex(session, &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 SSH_STRINGIFY(LIBSSH_VERSION) " (c) 2003-2008 Aris Adamantiadis "
"(aris@0xbadc0de.be) Distributed under the LGPL, please refer to COPYING"
"file for informations about your rights";
}
/** @} */