/*
cssh - cluster secure shell
Copyright (C) 2015-2021 Andreas Baumann
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program 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 General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see .
*/
#define _FILE_OFFSET_BITS 64
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include "port.h"
#include "progressbar.h"
#include "options.h"
#include "version.h"
#include "linenoise.h"
#define BUFSIZE 4096
#define DEFAULT_SSH_PORT 22
#define DEFAULT_CMD_SIZE 2048
#define DEFAULT_CLI_HISTORY_LENGTH 1024
#define DEFAULT_PROMPT "cssh> "
#define HISTORY_FILE ".cssh_history"
typedef enum connection_state_e {
CSSH_CONNECTION_STATE_UNCONNECTED,
CSSH_CONNECTION_STATE_CONNECTED,
CSSH_CONNECTION_STATE_ERROR
} connection_state_e;
typedef enum auth_state_e {
CSSH_AUTH_INIT,
CSSH_AUTH_VERIFY_HOST_DONE,
CSSH_AUTH_NONE_DONE,
CSSH_AUTH_BANNER_DONE,
CSSH_AUTH_PUBKEY_DONE_SUCCESS,
CSSH_AUTH_PUBKEY_DONE_FAILED,
CSSH_AUTH_PASSWORD_DONE_SUCCESS,
CSSH_AUTH_PASSWORD_DONE_FAILED,
CSSH_AUTH_DONE_SUCCESS,
CSSH_AUTH_DONE_FAILED
} auth_state_e;
typedef enum execution_mode_e {
CSSH_EXECUTE_UNKNOWN,
CSSH_EXECUTE_AS_SSH,
CSSH_EXECUTE_AS_SCP,
CSSH_EXECUTE_AS_CLI
} execution_mode_e;
typedef struct ssh_data_t {
ssh_channel channel;
char *stdout_buf;
char *stdout_bptr;
char *stderr_buf;
char *stderr_bptr;
bool eof_sent;
bool is_eof;
} ssh_data_t;
typedef enum copy_direction_e {
CSSH_COPY_DIRECTION_UPLOAD,
CSSH_COPY_DIRECTION_DOWNLOAD
} copy_direction_e;
typedef enum scp_read_state_e {
CSSH_SCP_READ_STATE_IDLE,
CSSH_SCP_READ_STATE_READING,
CSSH_SCP_READ_STATE_EOF
} scp_read_state_e;
typedef struct scp_dir_stack_entry_t {
char *dir;
struct scp_dir_stack_entry_t *next;
} scp_dir_stack_entry_t;
typedef struct scp_dir_stack_t {
struct scp_dir_stack_entry_t *head;
} scp_dir_stack_t;
typedef struct scp_data_t {
ssh_scp scp;
scp_read_state_e read_state;
scp_dir_stack_t dir_stack;
int fd;
char *filename;
char *buf;
uint64_t size;
uint64_t bytesReceived;
cssh_progressbar_t progressbar;
} scp_data_t;
static int push_dir_stack( scp_dir_stack_t *stack, const char *dir )
{
struct scp_dir_stack_entry_t *e = (struct scp_dir_stack_entry_t *)malloc( sizeof( scp_dir_stack_entry_t ) );
if( e == NULL ) {
return -1;
}
e->dir = strdup( dir );
if( e->dir == NULL ) {
free( e );
return -1;
}
e->next = stack->head;
stack->head = e;
return 0;
}
static void pop_dir_stack( scp_dir_stack_t *stack )
{
if( stack->head != NULL ) {
struct scp_dir_stack_entry_t *e = stack->head;
stack->head = stack->head->next;
free( e->dir );
free( e );
}
}
static char *top_dir_stack( scp_dir_stack_t *stack )
{
if( stack->head != NULL ) {
return stack->head->dir;
} else {
return NULL;
}
}
static void free_dir_stack( scp_dir_stack_t *stack )
{
while( stack->head != NULL ) {
pop_dir_stack( stack );
}
}
static void create_dir_stack( scp_dir_stack_t *stack )
{
stack->head = NULL;
}
static execution_mode_e determine_execution_mode( const char *argv0 )
{
char *b = ssh_basename( argv0 );
if( b == NULL ) {
return CSSH_EXECUTE_UNKNOWN;
}
if( strcmp( b, "cssh" ) == 0 ) {
free( b );
return CSSH_EXECUTE_AS_SSH;
} else if( strcmp( b, "cscp" ) == 0 ) {
free( b );
return CSSH_EXECUTE_AS_SCP;
}
free( b );
return CSSH_EXECUTE_UNKNOWN;
}
static int parse_options_and_arguments( int argc, char *argv[], struct gengetopt_args_info *args_info ) {
cmdline_parser_init( args_info );
if( cmdline_parser2( argc, argv, args_info, 1, 0, 1 ) != 0 ) {
cmdline_parser_free( args_info );
return 1;
}
return 0;
}
static bool get_yes_or_no( )
{
char buf[10];
ASK_AGAIN:
if( fgets( buf, sizeof( buf ), stdin ) == NULL ) {
fprintf( stderr, "ERROR: fgets failed in 'accept_server'\n" );
return -1;
}
if( strncmp( buf, "yes", 3 ) == 0 ) {
return true;
} else if( strncmp( buf, "no", 2 ) == 0 ) {
return false;
} else {
fprintf( stderr, "ERROR: expecting 'yes' or 'no' as answer\n" );
goto ASK_AGAIN;
}
return true;
}
static unsigned int get_default_ssh_port( )
{
struct servent *ent = getservbyname( "ssh", "tcp" );
if( ent != NULL ) {
return (unsigned int)ntohs( ent->s_port );
} else {
return DEFAULT_SSH_PORT;
}
}
static int accept_server( ssh_session session, unsigned char *hash, size_t hlen )
{
char *hexa;
hexa = ssh_get_hexa( hash, hlen );
fprintf( stderr, "The server is unknown. Do you trust the host key?\n" );
fprintf( stderr, "The server public hash key is: %s\n", hexa );
ssh_string_free_char( hexa );
if( !get_yes_or_no( ) ) {
fprintf( stderr, "INFO: Server key not accepted.. terminating now.\n" );
return -1;
}
fprintf( stderr, "Do you want to write this new key into the known host file for future usage?\n" );
if( get_yes_or_no( ) ) {
if( ssh_session_update_known_hosts( session ) != SSH_OK ) {
fprintf( stderr, "ERROR: error while writing known host file: %s\n",
strerror( errno ) );
return -1;
}
}
return 0;
}
static int verify_knownhost( ssh_session session )
{
int state;
state = ssh_session_is_known_server( session );
ssh_key srv_pubkey;
int rc = ssh_get_server_publickey( session, &srv_pubkey );
if( rc < 0 ) {
fprintf( stderr, "ERROR: Failed to get public key of host: %s\n",
ssh_get_error( session ) );
return -1;
}
unsigned char *hash;
size_t hlen;
rc = ssh_get_publickey_hash( srv_pubkey, SSH_PUBLICKEY_HASH_SHA1, &hash, &hlen );
if( rc < 0 ) {
fprintf( stderr, "ERROR: Failed to get hash of public key of host: %s\n",
ssh_get_error( session ) );
return -1;
}
ssh_key_free( srv_pubkey );
switch( state ) {
case SSH_SERVER_KNOWN_OK:
ssh_clean_pubkey_hash( &hash );
return 0;
case SSH_SERVER_KNOWN_CHANGED: {
char *hexa = ssh_get_hexa( hash, hlen );
fprintf( stderr, "ERROR: Host key for server changed : server's one is now:\n%s\n", hexa );
ssh_string_free_char( hexa );
ssh_clean_pubkey_hash( &hash );
} return -1;
case SSH_SERVER_FOUND_OTHER:
fprintf( stderr, "ERROR: The server gave us an unexpected host key, this could be an attack\n" );
return -1;
case SSH_SERVER_FILE_NOT_FOUND:
fprintf( stderr, "ERROR: Could not find a known host file.\n" );
rc = accept_server( session, hash, hlen );
ssh_clean_pubkey_hash( &hash );
return -1;
case SSH_SERVER_NOT_KNOWN:
fprintf( stderr, "ERROR: The server is unknown.\n" );
rc = accept_server( session, hash, hlen );
ssh_clean_pubkey_hash( &hash );
return -1;
case SSH_SERVER_ERROR:
fprintf( stderr, "ERROR: 'ssh_is_server_known' failed with: %s\n",
ssh_get_error( session ) );
ssh_clean_pubkey_hash( &hash );
return -1;
default:
ssh_clean_pubkey_hash( &hash );
fprintf( stderr, "ERROR: Unknown check host state %d\n", state );
return -1;
}
return 0;
}
static int authenticate_pubkey( ssh_session session )
{
int rc = ssh_userauth_publickey_auto( session, NULL, NULL );
switch( rc ) {
case SSH_AUTH_SUCCESS:
return SSH_AUTH_SUCCESS;
case SSH_AUTH_DENIED:
fprintf( stderr, "ERROR: authentication with public key denied\n" );
return SSH_AUTH_DENIED;
case SSH_AUTH_PARTIAL:
return SSH_AUTH_PARTIAL;
case SSH_AUTH_AGAIN:
return SSH_AUTH_AGAIN;
case SSH_AUTH_ERROR:
fprintf( stderr, "ERROR: authentication with public key failed: %s\n",
ssh_get_error( session ) );
return SSH_AUTH_ERROR;
}
return -1;
}
static int authenticate_password( ssh_session session, const char *host, const unsigned int port, const char *user )
{
char prompt[128];
char pass[128];
const char *prompt_user;
if( user != NULL ) {
prompt_user = user;
} else {
uid_t uid = getuid( );
struct passwd *pw = getpwuid( uid );
if( pw != NULL ) {
prompt_user = pw->pw_name;
} else {
prompt_user = "(unknown)";
}
}
if( strchr( host, '@' ) == NULL ) {
snprintf( prompt, sizeof( prompt ), "%s@%s:%d's password:",
prompt_user, host, port );
} else {
snprintf( prompt, sizeof( prompt ), "%s:%d's password:",
host, port );
}
memset( pass, 0, sizeof( pass ) );
if( ssh_getpass( prompt, pass, sizeof( pass ), 0, 0 ) < 0 ) {
fprintf( stderr, "\nERROR: ssh_getpass failed\n" );
return -1;
}
int rc = ssh_userauth_password( session, user, pass );
switch( rc ) {
case SSH_AUTH_SUCCESS:
return SSH_AUTH_SUCCESS;
case SSH_AUTH_PARTIAL:
return SSH_AUTH_PARTIAL;
case SSH_AUTH_DENIED:
fprintf( stderr, "ERROR: Password authentication failed.\n" );
return SSH_AUTH_DENIED;
case SSH_AUTH_AGAIN:
return SSH_AUTH_AGAIN;
case SSH_AUTH_ERROR:
fprintf( stderr, "ERROR: Failed to authenticate with password: %s\n",
ssh_get_error( session ) );
return SSH_AUTH_ERROR;
}
return -1;
}
static int read_hosts_file( const char *hosts_file, unsigned int default_port, char ***host, unsigned int **port, unsigned int *nof_hosts )
{
FILE *f;
char buf[255];
f = fopen( hosts_file, "r" );
if( f == NULL ) {
fprintf( stderr, "ERROR: Opening the host file '%s' failed: %s\n",
hosts_file, strerror( errno ) );
return -1;
}
*nof_hosts = 0;
unsigned int size_hosts = 2;
*host = (char **)calloc( size_hosts, sizeof( char * ) );
*port = (unsigned int *)calloc( size_hosts, sizeof( unsigned int ) );
if( *host == NULL || *port == NULL ) {
fprintf( stderr, "ERROR: Memory allocation failed in 'read_hosts_file'\n" );
return -1;
}
while( fgets( buf, sizeof( buf ), f ) != NULL ) {
if( strlen( buf ) > 0 && buf[strlen( buf )-1] == '\n' ) {
buf[strlen(buf)-1] = '\0';
}
// ignore comment lines
if( buf[0] == '#' ) {
continue;
}
// the user can be part of the hostname, libssh allows this,
// but we must parse the port separately
char *p = strchr( buf, ':' );
if( p != NULL ) {
(*port)[*nof_hosts] = atoi( p+1 );
*p = '\0';
} else {
(*port)[*nof_hosts] = default_port;
}
(*host)[*nof_hosts] = strdup( buf );
(*nof_hosts)++;
if( *nof_hosts >= size_hosts ) {
size_hosts *= 2;
*host = (char **)realloc( *host, size_hosts * sizeof( char *) );
*port = (unsigned int *)realloc( *port, size_hosts * sizeof( unsigned int ) );
if( *host == NULL || *port == NULL ) {
fprintf( stderr, "ERROR: Memory allocation failed in 'read_hosts_file'\n" );
return -1;
}
}
}
fclose( f );
return 0;
}
// TODO: cleanup also progressbarpool
static void finalize( ssh_session **session, ssh_data_t **ssh_data, scp_data_t **scp_data, char **host, unsigned int *port, const int nof_sessions, bool verbose )
{
for( unsigned int i = 0; i < nof_sessions; i++ ) {
if( ssh_is_connected( (*session)[i] ) ) {
if( verbose ) {
fprintf( stderr, "Disconnecting from '%s', port %d..\n", host[i], port[i] );
}
if( ssh_data != NULL ) {
ssh_channel *channel = &(*ssh_data)[i].channel;
if( ssh_channel_is_open( *channel ) ) {
ssh_channel_send_eof( *channel );
ssh_channel_close( *channel );
ssh_channel_free( *channel );
}
char *stdout_buf = (*ssh_data)[i].stdout_buf;
if( stdout_buf != NULL ) {
free( stdout_buf );
stdout_buf = NULL;
}
char *stderr_buf = (*ssh_data)[i].stderr_buf;
if( stderr_buf != NULL ) {
free( stderr_buf );
stderr_buf = NULL;
}
}
if( scp_data != NULL ) {
ssh_scp *scp = &(*scp_data)[i].scp;
if( scp != NULL ) {
ssh_scp_close( *scp );
ssh_scp_free( *scp );
scp = NULL;
}
char *filename = (*scp_data)[i].filename;
if( filename != NULL ) {
free( filename );
filename = NULL;
}
char *buf = (*scp_data)[i].buf;
if( buf != NULL ) {
free( buf );
buf = NULL;
}
scp_dir_stack_t *dir_stack = &(*scp_data)[i].dir_stack;
if( dir_stack != NULL ) {
free_dir_stack( dir_stack );
dir_stack = NULL;
}
}
ssh_disconnect( (*session)[i] );
if( verbose ) {
fprintf( stderr, "Disconnected from '%s', port %d..\n", host[i], port[i] );
}
}
ssh_free( (*session)[i] );
}
if( ssh_data != NULL ) {
free( *ssh_data );
*ssh_data = NULL;
}
if( scp_data != NULL ) {
free( *scp_data );
*scp_data = NULL;
}
free( *session );
for( unsigned int i = 0; i < nof_sessions; i++ ) {
free( host[i] );
}
free( *host );
free( port );
ssh_finalize( );
}
static const char *buffer_contains_a_line( const char *buffer, size_t bufsize )
{
for( const char *p = buffer; ( p < buffer + bufsize ) && *p != '\0'; p++ ) {
if( *p == '\n' ) {
return p;
}
}
return NULL;
}
static ssize_t output_buffer( FILE *f, const char *host, const unsigned int port, const char *buffer, const size_t bufsize, const bool tagging )
{
const char *b = buffer;
size_t bsize = bufsize;
ssize_t nwritten = 0;
const char *p;
while( ( b < buffer + bufsize ) && ( p = buffer_contains_a_line( b, bsize ) ) != NULL) {
if( tagging ) {
if( port != get_default_ssh_port( ) ) {
fprintf( f, "[%s:%d]: ", host, port );
} else {
fprintf( f, "[%s]: ", host );
}
fflush( f );
}
ssize_t n = p - b;
ssize_t wrc = fwrite( b, 1, n, f );
if( wrc < 0 ) {
return wrc;
}
if( wrc != n ) {
fprintf( stderr, "ERROR: Buffer write mismatch on %s (%zu != %zu)\n",
( f == stdout ) ? "stdout" : "stderr",
wrc, n );
return -1;
}
nwritten += n + 1;
bsize -= n + 1;
p++;
b = p;
fprintf( f, "\n" );
}
assert( nwritten <= bufsize );
return nwritten;
}
int main( int argc, char *argv[] )
{
struct gengetopt_args_info args_info;
ssh_session *session;
int rc;
execution_mode_e execution_mode = determine_execution_mode( argv[0] );
switch( execution_mode ) {
case CSSH_EXECUTE_UNKNOWN:
fprintf( stderr, "ERROR: binary name unknown, can't determine execution mode\n" );
exit( EXIT_FAILURE );
case CSSH_EXECUTE_AS_SSH:
break;
case CSSH_EXECUTE_AS_SCP:
break;
case CSSH_EXECUTE_AS_CLI:
break;
}
if( parse_options_and_arguments( argc, argv, &args_info ) != 0 ) {
exit( EXIT_FAILURE );
}
ssh_init( );
if( args_info.long_version_given ) {
printf( "cssh version: %s, Copyright (c) 2015-2021, GPLv3, Andreas Baumann \n", CSSH_VERSION );
printf( "libssh version: %s\n", ssh_copyright( ) );
printf( "linenoise: BSD license, Copyright (c) 2010-2014, Salvatore Sanfilippo and "
"Copyright (c) 2010-2013, Pieter Noordhuis \n" );
exit( EXIT_SUCCESS );
}
// command line parsing and reading of additional files like the hosts file
unsigned int nof_sessions = 0;
char **host = NULL;
unsigned int *port = NULL;
char *cmd = NULL;
copy_direction_e copy_direction = CSSH_COPY_DIRECTION_DOWNLOAD;
char *local_directory = NULL;
char *remote_directory = NULL;
bool first_interactive = true;
char history_filename[1024];
switch( execution_mode ) {
case CSSH_EXECUTE_AS_SSH: {
cmd = (char *)malloc( DEFAULT_CMD_SIZE );
unsigned int command_pos = 0;
// command line arguments are '[user@]host'
if( !args_info.hosts_file_given ) {
unsigned int default_port = get_default_ssh_port( );
if( args_info.port_given ) {
default_port = args_info.port_arg;
}
host = (char **)malloc( sizeof( char * ) );
port = (unsigned int *)malloc( sizeof( unsigned int ) );
if( args_info.inputs_num >= 1 ) {
command_pos++;
nof_sessions = 1;
host[0] = strdup( args_info.inputs[0] );
port[0] = default_port;
} else {
host[0] = strdup( "localhost" );
port[0] = default_port;
}
}
// compose command to be executed in SSH mode
cmd[0] = '\0';
if( args_info.inputs_num > 0 ) {
for( int i = command_pos; i < args_info.inputs_num; i++ ) {
if( i != command_pos ) {
strncat( cmd, " ", DEFAULT_CMD_SIZE - strlen( cmd ) - 1 );
}
strncat( cmd, args_info.inputs[i], DEFAULT_CMD_SIZE - strlen( cmd ) - 1 );
}
}
// later, this is the sign to get into CLI mode
if( cmd[0] == '\0' ) {
execution_mode = CSSH_EXECUTE_AS_CLI;
}
} break;
case CSSH_EXECUTE_AS_SCP: {
// in SCP mode read source and destination
if( args_info.inputs_num != 2 ) {
if( args_info.inputs_num < 2 ) {
fprintf( stderr, "ERROR: Expecting a source and a destination when copying, encountered less arguments\n" );
exit( EXIT_FAILURE );
} else {
fprintf( stderr, "ERROR: Expecting a source and a destination when copying, encountered more arguments\n" );
exit( EXIT_FAILURE );
}
}
// parse 2 arguments and see where we have a local
// path and where a host destination with path
// host destination can be explicit (one without -H hosts
// parameter) or it is a placeholder host user@host:/dira/dirb,
// so :/adir is a way or user@:/adir or host:/adir or user@host:adir
// also host: and user@host: for home dir on other side, this means
// we also have user@: for home on all hosts in -H hosts or
// just : for the home directory on all hosts in -H hosts.
// seems quite logical (at least to me :-))
unsigned int default_port = get_default_ssh_port( );
host = (char **)malloc( sizeof( char * ) );
port = (unsigned int *)malloc( sizeof( unsigned int ) );
if( args_info.port_given ) {
default_port = args_info.port_arg;
}
char *s = strdup( args_info.inputs[0] );
char *s2 = NULL;
char *p = s;
while( *p != '\0' ) {
if( *p == ':' ) {
*p = '\0';
s2 = p+1;
host[0] = strdup( s );
port[0] = default_port;
nof_sessions = 1;
copy_direction = CSSH_COPY_DIRECTION_DOWNLOAD;
}
p++;
}
if( s2 != NULL ) {
if( nof_sessions > 0 ) {
remote_directory = strdup( s2 );
} else {
local_directory = strdup( s2 );
}
} else {
local_directory = strdup( s );
}
free( s );
s = strdup( args_info.inputs[1] );
p = s;
s2 = NULL;
while( *p != '\0' ) {
if( *p == ':' ) {
if( nof_sessions == 0 ) {
*p = '\0';
s2 = p+1;
host[0] = strdup( s );
port[0] = default_port;
nof_sessions = 1;
copy_direction = CSSH_COPY_DIRECTION_UPLOAD;
} else {
free( s );
fprintf( stderr, "ERROR: specified a host for source and destination, please use a local directory for one of them\n" );
exit( EXIT_FAILURE );
}
}
p++;
}
if( s2 != NULL ) {
if( nof_sessions > 0 ) {
remote_directory = strdup( s2 );
} else {
local_directory = strdup( s2 );
}
} else {
local_directory = strdup( s );
}
free( s );
} break;
case CSSH_EXECUTE_AS_CLI:
case CSSH_EXECUTE_UNKNOWN:
break;
}
// till now we have a default host, port and user and exactly one session,
// now inspect the hosts file for hosts
if( args_info.hosts_file_given ) {
// the -l and -p parameters serve as defaults
// for the hosts in the file containing hosts
unsigned nof_hosts;
unsigned int default_port = get_default_ssh_port( );
if( args_info.port_given ) {
default_port = args_info.port_arg;
}
rc = read_hosts_file( args_info.hosts_file_arg, default_port, &host, &port, &nof_hosts );
if( rc < 0 ) {
exit( EXIT_SUCCESS );
}
nof_sessions = nof_hosts;
}
// initialize session with host, port and user parameters read
session = (ssh_session *)calloc( nof_sessions, sizeof( ssh_session ) );
if( session == NULL ) {
fprintf( stderr, "ERROR: Memory allocation failed for ssh_sessions\n" );
exit( EXIT_SUCCESS );
}
for( unsigned int i = 0; i < nof_sessions; i++ ) {
session[i] = ssh_new( );
if( session[i] == NULL ) {
exit( EXIT_FAILURE );
}
}
int verbosity = SSH_LOG_NOLOG;
if( args_info.verbose_given > 1 ) {
verbosity += args_info.verbose_given;
}
for( unsigned int i = 0; i < nof_sessions; i++ ) {
ssh_set_blocking( session[i], 0 );
ssh_options_set( session[i], SSH_OPTIONS_HOST, host[i] );
ssh_options_set( session[i], SSH_OPTIONS_PORT, &port[i] );
if( args_info.login_given > 0 ) {
ssh_options_set( session[i], SSH_OPTIONS_USER, args_info.login_arg );
}
ssh_options_set( session[i], SSH_OPTIONS_LOG_VERBOSITY, &verbosity );
}
for( unsigned int i = 0; i < nof_sessions; i++ ) {
if( args_info.verbose_given ) {
fprintf( stderr, "Connecting to '%s', port %d..\n", host[i], port[i] );
}
}
// asynchonous connection phase
connection_state_e *connection_state = (connection_state_e *)calloc( nof_sessions, sizeof( connection_state_e ) );
if( connection_state == NULL ) {
fprintf( stderr, "ERROR: Memory allocation failed for 'connection_state'\n" );
finalize( &session, NULL, NULL, host, port, nof_sessions, args_info.verbose_given > 0 );
exit( EXIT_SUCCESS );
}
unsigned int nof_connected = 0;
while( nof_connected < nof_sessions ) {
nof_connected = 0;
for( unsigned int i = 0; i < nof_sessions; i++ ) {
switch( connection_state[i] ) {
case CSSH_CONNECTION_STATE_UNCONNECTED:
rc = ssh_connect( session[i] );
if( rc == SSH_OK ) {
connection_state[i] = CSSH_CONNECTION_STATE_CONNECTED;
//~ ssh_set_blocking( session[i], 1 );
if( args_info.verbose_given ) {
fprintf( stderr, "Connected to '%s', port %d..\n", host[i], port[i] );
}
} else if( rc == SSH_AGAIN ) {
// not connected yet
} else {
fprintf( stderr, "ERROR: error connecting to '%s', port '%d': %s\n",
host[i], port[i], ssh_get_error( session[i] ) );
if( args_info.ignore_connect_errors_given ) {
connection_state[i] = CSSH_CONNECTION_STATE_ERROR;
} else {
finalize( &session, NULL, NULL, host, port, nof_sessions, args_info.verbose_given > 0 );
exit( EXIT_FAILURE );
}
}
break;
case CSSH_CONNECTION_STATE_CONNECTED:
case CSSH_CONNECTION_STATE_ERROR:
// good, already connected or we ignore errors
nof_connected++;
break;
}
}
cssh_msleep( 10 );
}
// authentication phase
auth_state_e *auth_state = (auth_state_e *)calloc( nof_sessions, sizeof( auth_state_e ) );
if( auth_state == NULL ) {
fprintf( stderr, "ERROR: Memory allocation failed for 'auth_state'\n" );
finalize( &session, NULL, NULL, host, port, nof_sessions, args_info.verbose_given > 0 );
exit( EXIT_FAILURE );
}
unsigned int nof_authenticated = 0;
while( nof_authenticated < nof_connected ) {
for( unsigned int i = 0; i < nof_sessions; i++ ) {
if( connection_state[i] != CSSH_CONNECTION_STATE_CONNECTED ) {
continue;
}
switch( auth_state[i] ) {
case CSSH_AUTH_INIT:
if( verify_knownhost( session[i] ) < 0 ) {
fprintf( stderr, "ERROR: closing connection to '%s', port '%d' due to security reasons\n",
host[i], port[i] );
finalize( &session, NULL, NULL, host, port, nof_sessions, args_info.verbose_given > 0 );
exit( EXIT_FAILURE );
}
auth_state[i] = CSSH_AUTH_VERIFY_HOST_DONE;
break;
case CSSH_AUTH_VERIFY_HOST_DONE:
// returns Failed to request "ssh-userauth" service, not handling SSH_AUTH_AGAIN correctly internally?
ssh_set_blocking( session[i], 1 );
rc = ssh_userauth_none( session[i], NULL );
if( rc == SSH_AUTH_SUCCESS ) {
auth_state[i] = CSSH_AUTH_NONE_DONE;
ssh_set_blocking( session[i], 1 );
} else if( rc == SSH_AUTH_AGAIN ) {
// ok, wait next iteration
} else if( rc == SSH_AUTH_DENIED ) {
auth_state[i] = CSSH_AUTH_NONE_DONE;
//~ ssh_set_blocking( session[i], 0 );
} else {
fprintf( stderr, "ERROR: ssh_userauth_none to '%s', port '%d' failed: %s\n",
host[i], port[i], ssh_get_error( session[i] ) );
finalize( &session, NULL, NULL, host, port, nof_sessions, args_info.verbose_given > 0 );
exit( EXIT_FAILURE );
}
break;
case CSSH_AUTH_NONE_DONE: {
ssh_set_blocking( session[i], 1 );
char *banner = ssh_get_issue_banner( session[i] );
if( banner ) {
puts( banner );
free( banner );
}
auth_state[i] = CSSH_AUTH_BANNER_DONE;
} break;
case CSSH_AUTH_BANNER_DONE:
rc = authenticate_pubkey( session[i] );
if( rc == SSH_AUTH_SUCCESS ) {
auth_state[i] = CSSH_AUTH_PUBKEY_DONE_SUCCESS;
} else if( rc == SSH_AUTH_AGAIN ) {
// ok, wait next iteration
} else if( rc == SSH_AUTH_DENIED ) {
auth_state[i] = CSSH_AUTH_PUBKEY_DONE_FAILED;
} else {
finalize( &session, NULL, NULL, host, port, nof_sessions, args_info.verbose_given > 0 );
exit( EXIT_FAILURE );
}
break;
case CSSH_AUTH_PUBKEY_DONE_SUCCESS:
auth_state[i] = CSSH_AUTH_DONE_SUCCESS;
break;
case CSSH_AUTH_PUBKEY_DONE_FAILED:
if( args_info.allow_password_authentication_given ) {
rc = authenticate_password( session[i], host[i], port[i], args_info.login_given ? args_info.login_arg : NULL );
if( rc == SSH_AUTH_SUCCESS ) {
auth_state[i] = CSSH_AUTH_PASSWORD_DONE_SUCCESS;
} else if( rc == SSH_AUTH_AGAIN ) {
// ok, wait next iteration
} else if( rc == SSH_AUTH_DENIED ) {
auth_state[i] = CSSH_AUTH_PASSWORD_DONE_FAILED;
} else {
finalize( &session, NULL, NULL, host, port, nof_sessions, args_info.verbose_given > 0 );
exit( EXIT_FAILURE );
}
} else {
auth_state[i] = CSSH_AUTH_DONE_FAILED;
}
break;
case CSSH_AUTH_PASSWORD_DONE_SUCCESS:
auth_state[i] = CSSH_AUTH_DONE_SUCCESS;
break;
case CSSH_AUTH_PASSWORD_DONE_FAILED:
auth_state[i] = CSSH_AUTH_DONE_FAILED;
break;
case CSSH_AUTH_DONE_SUCCESS:
// ok
nof_authenticated++;
break;
case CSSH_AUTH_DONE_FAILED:
// one authentication failed, bail out for now
fprintf( stderr, "ERROR: authentication failed for one host ('%s'), aborting now\n", host[i] );
if( args_info.ignore_authentication_errors_given ) {
connection_state[i] = CSSH_CONNECTION_STATE_ERROR;
} else {
finalize( &session, NULL, NULL, host, port, nof_sessions, args_info.verbose_given > 0 );
exit( EXIT_FAILURE );
}
}
}
cssh_msleep( 10 );
}
// rearange session data in case we ignore errornous hosts
unsigned int j = 0;
for( unsigned int i = 0; i < nof_sessions; i++ ) {
if( connection_state[i] == CSSH_CONNECTION_STATE_CONNECTED ) {
if( i != j ) {
connection_state[j] = connection_state[i];
auth_state[j] = auth_state[i];
session[j] = session[i];
host[j] = host[i];
port[j] = port[i];
}
j++;
} else {
fprintf( stderr, "ERROR: host '%s' has errors, ignoring it\n", host[i] );
}
}
if( j != nof_sessions ) {
nof_sessions = j;
connection_state = (connection_state_e *)realloc( connection_state, nof_sessions * sizeof( connection_state_e ) );
auth_state = (auth_state_e *)realloc( auth_state, nof_sessions * sizeof( auth_state_e ) );
session = (ssh_session *)realloc( session, nof_sessions * sizeof( ssh_session ) );
}
// in CLI mode get next command from linenoise
CLI_NEXT_CMD:
if( execution_mode == CSSH_EXECUTE_AS_CLI ) {
if( first_interactive ) {
puts( "Entering interactive shell mode.. Press Ctrl-D or Ctrl-C to terminate." );
first_interactive = false;
linenoiseSetMultiLine( 1 );
linenoiseHistorySetMaxLen( DEFAULT_CLI_HISTORY_LENGTH );
char *home = getenv( "HOME" );
if( home != NULL ) {
snprintf( history_filename, sizeof( history_filename ), "%s/%s", home, HISTORY_FILE );
linenoiseHistoryLoad( history_filename );
}
}
cmd = linenoise( DEFAULT_PROMPT );
if( cmd == NULL ) {
puts( "EOF received.. Terminating interactive mode." );
if( cmd != NULL ) linenoiseFree( cmd );
linenoiseHistorySave( history_filename );
goto SHELL_EOF;
}
linenoiseHistoryAdd( cmd );
}
// execution/copy phase
switch( execution_mode ) {
case CSSH_EXECUTE_AS_SSH:
case CSSH_EXECUTE_AS_CLI: {
// explicit low-level handling of channels in SSH mode
ssh_data_t *ssh_data = (ssh_data_t *)calloc( nof_sessions, sizeof( ssh_data_t ) );
if( ssh_data == NULL ) {
fprintf( stderr, "ERROR: Memory allocation failed for ssh_data array\n" );
finalize( &session, NULL, NULL, host, port, nof_sessions, args_info.verbose_given > 0 );
exit( EXIT_FAILURE );
}
for( unsigned int i = 0; i < nof_sessions; i++ ) {
ssh_data[i].channel = ssh_channel_new( session[i] );
if( ssh_data[i].channel == NULL ) {
fprintf( stderr, "ERROR: Unable to open SSH channel for host '%s': %s\n",
host[i], ssh_get_error( session[i] ) );
finalize( &session, &ssh_data, NULL, host, port, nof_sessions, args_info.verbose_given > 0 );
exit( EXIT_FAILURE );
}
}
for( unsigned int i = 0; i < nof_sessions; i++ ) {
rc = ssh_channel_open_session( ssh_data[i].channel );
if( rc != SSH_OK ) {
finalize( &session, &ssh_data, NULL, host, port, nof_sessions, args_info.verbose_given > 0 );
exit( EXIT_FAILURE );
}
}
// execute command on all channels
for( unsigned int i = 0; i < nof_sessions; i++ ) {
rc = ssh_channel_request_exec( ssh_data[i].channel, cmd );
if( rc != SSH_OK ) {
fprintf( stderr, "ERROR: Executing SSH command '%s' failed: %s\n",
cmd, ssh_get_error( session[i] ) );
finalize( &session, &ssh_data, NULL, host, port, nof_sessions, args_info.verbose_given > 0 );
exit( EXIT_FAILURE );
}
}
bool all_eof = false;
for( unsigned int i = 0; i < nof_sessions; i++ ) {
ssh_data[i].eof_sent = false;
ssh_data[i].is_eof = false;
ssh_data[i].stdout_buf = (char *)malloc( BUFSIZE );
if( ssh_data[i].stdout_buf == NULL ) {
fprintf( stderr, "ERROR: Memory allocation failed for receiving buffers of ssh_channels\n" );
finalize( &session, &ssh_data, NULL, host, port, nof_sessions, args_info.verbose_given > 0 );
exit( EXIT_FAILURE );
}
ssh_data[i].stderr_buf = (char *)malloc( BUFSIZE );
if( ssh_data[i].stderr_buf == NULL ) {
fprintf( stderr, "ERROR: Memory allocation failed for receiving buffers of ssh_channels\n" );
finalize( &session, &ssh_data, NULL, host, port, nof_sessions, args_info.verbose_given > 0 );
exit( EXIT_FAILURE );
}
ssh_data[i].stdout_bptr = ssh_data[i].stdout_buf;
ssh_data[i].stderr_bptr = ssh_data[i].stderr_buf;
}
while( !all_eof ) {
all_eof = true;
for( unsigned int i = 0; i < nof_sessions; i++ ) {
if( !ssh_channel_is_closed( ssh_data[i].channel ) && !ssh_channel_is_eof( ssh_data[i].channel ) ) {
all_eof = false;
}
}
// no stdin sent to commands on remote machines for now
for( unsigned int i = 0; i < nof_sessions; i++ ) {
if( ssh_data[i].is_eof && !ssh_data[i].eof_sent ) {
ssh_channel_send_eof( ssh_data[i].channel );
ssh_data[i].eof_sent = true;
continue;
}
}
for( unsigned int i = 0; i < nof_sessions; i++ ) {
// handle stdout
rc = ssh_channel_poll( ssh_data[i].channel, 0 );
if( rc == SSH_ERROR ) {
fprintf( stderr, "ERROR: ssh_channel_poll on stdout failed: %s\n",
ssh_get_error( session[i] ) );
finalize( &session, &ssh_data, NULL, host, port, nof_sessions, args_info.verbose_given > 0 );
exit( EXIT_FAILURE );
}
if( rc > 0 ) {
unsigned int nread = ssh_channel_read_nonblocking( ssh_data[i].channel, ssh_data[i].stdout_bptr, BUFSIZE - ( ssh_data[i].stdout_bptr - ssh_data[i].stdout_buf ), 0 );
if( nread == SSH_ERROR ) {
fprintf( stderr, "ERROR: ssh_channel_read_nonblocking on stdout failed: %s\n",
ssh_get_error( session[i] ) );
finalize( &session, &ssh_data, NULL, host, port, nof_sessions, args_info.verbose_given > 0 );
exit( EXIT_FAILURE );
}
if( nread == 0 ) {
ssh_data[i].is_eof = true;
}
if( nread > 0 ) {
ssize_t wrc = output_buffer( stdout, host[i], port[i], ssh_data[i].stdout_buf, nread + ( ssh_data[i].stdout_bptr - ssh_data[i].stdout_buf ), args_info.tagging_given );
if( wrc < 0 ) {
fprintf( stderr, "ERROR: while writing to stdout: %s\n",
strerror( errno ) );
finalize( &session, &ssh_data, NULL, host, port, nof_sessions, args_info.verbose_given > 0 );
exit( EXIT_FAILURE );
}
assert( wrc <= nread + ( ssh_data[i].stdout_bptr - ssh_data[i].stdout_buf ) );
memmove( ssh_data[i].stdout_buf, ssh_data[i].stdout_buf + wrc, nread + ( ssh_data[i].stdout_bptr - ssh_data[i].stdout_buf ) - wrc );
ssh_data[i].stdout_bptr = ssh_data[i].stdout_buf + nread + ( ssh_data[i].stdout_bptr - ssh_data[i].stdout_buf ) - wrc;
}
}
// handle stderr
rc = ssh_channel_poll( ssh_data[i].channel, 1 );
if( rc == SSH_ERROR ) {
fprintf( stderr, "ERROR: ssh_channel_poll on stderr failed: %s\n",
ssh_get_error( session[i] ) );
finalize( &session, &ssh_data, NULL, host, port, nof_sessions, args_info.verbose_given > 0 );
exit( EXIT_FAILURE );
}
if( rc > 0 ) {
unsigned int nread = ssh_channel_read_nonblocking( ssh_data[i].channel, ssh_data[i].stderr_bptr, BUFSIZE - ( ssh_data[i].stderr_bptr - ssh_data[i].stderr_buf ), 1 );
if( nread == SSH_ERROR ) {
fprintf( stderr, "ERROR: ssh_channel_read_nonblocking on stderr failed: %s\n",
ssh_get_error( session[i] ) );
finalize( &session, &ssh_data, NULL, host, port, nof_sessions, args_info.verbose_given > 0 );
exit( EXIT_FAILURE );
}
if( nread > 0 ) {
ssize_t wrc = output_buffer( stderr, host[i], port[i], ssh_data[i].stderr_buf, nread + ( ssh_data[i].stderr_bptr - ssh_data[i].stderr_buf ), args_info.tagging_given );
if( wrc < 0 ) {
fprintf( stderr, "ERROR: while writting to stderr: %s\n",
strerror( errno ) );
finalize( &session, &ssh_data, NULL, host, port, nof_sessions, args_info.verbose_given > 0 );
exit( EXIT_FAILURE );
}
assert( wrc <= nread + ( ssh_data[i].stderr_bptr - ssh_data[i].stderr_buf ) );
memmove( ssh_data[i].stderr_buf, ssh_data[i].stderr_buf + wrc, nread + ( ssh_data[i].stderr_bptr - ssh_data[i].stderr_buf ) - wrc );
ssh_data[i].stderr_bptr = ssh_data[i].stderr_buf + nread + ( ssh_data[i].stderr_bptr - ssh_data[i].stderr_buf ) - wrc;
}
if( nread == 0 ) {
ssh_data[i].is_eof = true;
}
}
}
cssh_msleep( 1 );
}
if( execution_mode == CSSH_EXECUTE_AS_CLI ) {
if( cmd != NULL ) linenoiseFree( cmd );
goto CLI_NEXT_CMD;
}
SHELL_EOF:
finalize( &session, &ssh_data, NULL, host, port, nof_sessions, args_info.verbose_given > 0 );
} break;
case CSSH_EXECUTE_AS_SCP: {
scp_data_t *scp_data = (scp_data_t *)calloc( nof_sessions, sizeof( scp_data_t ) );
if( scp_data == NULL ) {
fprintf( stderr, "ERROR: Memory allocation failed for scp_data array\n" );
finalize( &session, NULL, NULL, host, port, nof_sessions, args_info.verbose_given > 0 );
exit( EXIT_FAILURE );
}
for( unsigned int i = 0; i < nof_sessions; i++ ) {
scp_data[i].read_state = CSSH_SCP_READ_STATE_IDLE;
}
for( unsigned int i = 0; i < nof_sessions; i++ ) {
scp_data[i].scp = ssh_scp_new( session[i],
( ( copy_direction == CSSH_COPY_DIRECTION_UPLOAD ) ? SSH_SCP_WRITE : SSH_SCP_READ ) |
( ( args_info.recursive_given > 0 ) ? SSH_SCP_RECURSIVE : 0 ),
remote_directory );
if( scp_data[i].scp == NULL ) {
fprintf( stderr, "ERROR: Unable to open SCP channel: %s\n",
ssh_get_error( session[i] ) );
finalize( &session, NULL, &scp_data, host, port, nof_sessions, args_info.verbose_given > 0 );
exit( EXIT_FAILURE );
}
}
for( unsigned int i = 0; i < nof_sessions; i++ ) {
rc = ssh_scp_init( scp_data[i].scp );
if( rc != SSH_OK ) {
fprintf( stderr, "ERROR: Unable to initialize SCP sessions: %s\n",
ssh_get_error( session[i] ) );
finalize( &session, NULL, &scp_data, host, port, nof_sessions, args_info.verbose_given > 0 );
exit( EXIT_FAILURE );
}
}
for( unsigned int i = 0; i < nof_sessions; i++ ) {
scp_data[i].buf = (char *)malloc( BUFSIZE );
if( scp_data[i].buf == NULL ) {
fprintf( stderr, "ERROR: Memory allocation failed for receiving buffers of ssh_scp\n" );
finalize( &session, NULL, &scp_data, host, port, nof_sessions, args_info.verbose_given > 0 );
exit( EXIT_FAILURE );
}
}
cssh_progressbar_pool_t progressbars;
rc = create_progressbar_pool( &progressbars, nof_sessions );
if( rc < 0 ) {
fprintf( stderr, "ERROR: Creation of a pool of progress bars failed\n" );
finalize( &session, NULL, &scp_data, host, port, nof_sessions, args_info.verbose_given > 0 );
exit( EXIT_FAILURE );
}
bool all_eof = false;
for( unsigned int i = 0; i < nof_sessions; i++ ) {
create_dir_stack( &scp_data[i].dir_stack );
char *pwd = getcwd( NULL, 0 );
if( pwd == NULL ) {
fprintf( stderr, "ERROR: failed to determine working directory for SCP for host '%s': %s\n",
host[i], strerror( errno ) );
finalize( &session, NULL, &scp_data, host, port, nof_sessions, args_info.verbose_given > 0 );
exit( EXIT_FAILURE );
}
rc = push_dir_stack( &scp_data[i].dir_stack, pwd );
if( rc < 0 ) {
fprintf( stderr, "ERROR: failed to push current directory to stack for host '%s'\n",
host[i] );
free( pwd );
finalize( &session, NULL, &scp_data, host, port, nof_sessions, args_info.verbose_given > 0 );
exit( EXIT_FAILURE );
}
if( nof_sessions > 1 ) {
size_t len = strlen( pwd ) + 2 + strlen( host[i] );
char *full_path = (char *)malloc( len );
if( full_path == NULL ) {
fprintf( stderr, "ERROR: Memory allocation failed for full path of base directory for host '%s'\n",
host[i] );
free( pwd );
finalize( &session, NULL, &scp_data, host, port, nof_sessions, args_info.verbose_given > 0 );
exit( EXIT_FAILURE );
}
snprintf( full_path, len, "%s/%s", pwd, host[i] );
rc = push_dir_stack( &scp_data[i].dir_stack, full_path );
scp_data[i].filename = full_path;
if( rc < 0 ) {
fprintf( stderr, "ERROR: failed remember initial base directory '%s' in directory stack\n",
pwd );
free( pwd );
finalize( &session, NULL, &scp_data, host, port, nof_sessions, args_info.verbose_given > 0 );
exit( EXIT_FAILURE );
}
free( pwd );
rc = mkdir( full_path, 0750 );
if( rc < 0 ) {
if( errno != EEXIST ) {
fprintf( stderr, "ERROR: failed to create base directory '%s' for host '%s': %s\n",
full_path, host[i], strerror( errno ) );
finalize( &session, NULL, &scp_data, host, port, nof_sessions, args_info.verbose_given > 0 );
exit( EXIT_FAILURE );
}
}
} else {
scp_data[i].filename = pwd;
}
}
while( !all_eof ) {
switch( copy_direction ) {
case CSSH_COPY_DIRECTION_UPLOAD: {
fprintf( stderr, "ERROR: SCP upload has not been implemented yet.\n" );
finalize( &session, NULL, &scp_data, host, port, nof_sessions, args_info.verbose_given > 0 );
exit( EXIT_FAILURE );
} break;
case CSSH_COPY_DIRECTION_DOWNLOAD: {
for( unsigned int i = 0; i < nof_sessions; i++ ) {
switch( scp_data[i].read_state ) {
case CSSH_SCP_READ_STATE_IDLE:
rc = ssh_scp_pull_request( scp_data[i].scp );
switch( rc ) {
case SSH_SCP_REQUEST_NEWDIR: {
char *dir = top_dir_stack( &scp_data[i].dir_stack );
rc = chdir( dir );
if( rc < 0 ) {
fprintf( stderr, "ERROR: failed to change to directory '%s': %s\n",
dir, strerror( errno ) );
finalize( &session, NULL, &scp_data, host, port, nof_sessions, args_info.verbose_given > 0 );
exit( EXIT_FAILURE );
}
const char *filename = ssh_scp_request_get_filename( scp_data[i].scp );
int mode = ssh_scp_request_get_permissions( scp_data[i].scp );
if( args_info.verbose_given ) {
fprintf( stderr, "Receiving directory '%s' with permissions '0%o'\n",
filename, mode );
}
rc = ssh_scp_accept_request( scp_data[i].scp );
if( rc != SSH_OK ) {
fprintf( stderr, "ERROR: accepting request for directory '%s' failed: %s\n",
filename, ssh_get_error( session[i] ) );
finalize( &session, NULL, &scp_data, host, port, nof_sessions, args_info.verbose_given > 0 );
exit( EXIT_FAILURE );
}
rc = mkdir( filename, mode );
if( rc < 0 ) {
fprintf( stderr, "ERROR: failed to create directory '%s': %s\n",
filename, strerror( errno ) );
finalize( &session, NULL, &scp_data, host, port, nof_sessions, args_info.verbose_given > 0 );
exit( EXIT_FAILURE );
}
char *pwd = getcwd( NULL, 0 );
if( pwd == NULL ) {
fprintf( stderr, "ERROR: failed to determine working directory for SCP for host '%s': %s\n",
host[i], strerror( errno ) );
finalize( &session, NULL, &scp_data, host, port, nof_sessions, args_info.verbose_given > 0 );
exit( EXIT_FAILURE );
}
size_t len = strlen( pwd ) + 2 + strlen( filename );
char *full_path = (char *)malloc( len );
if( full_path == NULL ) {
fprintf( stderr, "ERROR: Memory allocation failed for full path of directory '%s'\n",
filename );
free( pwd );
finalize( &session, NULL, &scp_data, host, port, nof_sessions, args_info.verbose_given > 0 );
exit( EXIT_FAILURE );
}
snprintf( full_path, len, "%s/%s", pwd, filename );
rc = push_dir_stack( &scp_data[i].dir_stack, full_path );
free( pwd );
if( rc < 0 ) {
fprintf( stderr, "ERROR: failed remember directory '%s' in directory stack\n",
full_path );
free( full_path );
finalize( &session, NULL, &scp_data, host, port, nof_sessions, args_info.verbose_given > 0 );
exit( EXIT_FAILURE );
}
free( full_path );
} break;
case SSH_SCP_REQUEST_ENDDIR:
pop_dir_stack( &scp_data[i].dir_stack );
break;
case SSH_SCP_REQUEST_NEWFILE: {
const char *filename = ssh_scp_request_get_filename( scp_data[i].scp );
int mode = ssh_scp_request_get_permissions( scp_data[i].scp );
scp_data[i].size = ssh_scp_request_get_size64( scp_data[i].scp );
if( args_info.verbose_given ) {
fprintf( stderr, "Receiving file '%s' with permissions '0%o' of size '%"PRIu64"'\n",
filename, mode, scp_data[i].size );
}
rc = ssh_scp_accept_request( scp_data[i].scp );
if( rc != SSH_OK ) {
fprintf( stderr, "ERROR: accepting request for file '%s' failed: %s\n",
filename, ssh_get_error( session[i] ) );
finalize( &session, NULL, &scp_data, host, port, nof_sessions, args_info.verbose_given > 0 );
exit( EXIT_FAILURE );
}
char *dir = top_dir_stack( &scp_data[i].dir_stack );
rc = chdir( dir );
if( rc < 0 ) {
fprintf( stderr, "ERROR: failed to change to directory '%s': %s\n",
dir, strerror( errno ) );
finalize( &session, NULL, &scp_data, host, port, nof_sessions, args_info.verbose_given > 0 );
exit( EXIT_FAILURE );
}
size_t len = strlen( dir ) + 2 + strlen( filename );
char *full_path = (char *)malloc( len );
if( full_path == NULL ) {
fprintf( stderr, "ERROR: Memory allocation failed for full path of directory '%s'\n",
filename );
free( dir );
finalize( &session, NULL, &scp_data, host, port, nof_sessions, args_info.verbose_given > 0 );
exit( EXIT_FAILURE );
}
snprintf( full_path, len, "%s/%s", dir, filename );
int fd = open( full_path, O_WRONLY | O_CREAT | O_TRUNC, mode );
if( fd < 0 ) {
fprintf( stderr, "ERROR: Unable to open file '%s': %s\n",
full_path, strerror( errno ) );
finalize( &session, NULL, &scp_data, host, port, nof_sessions, args_info.verbose_given > 0 );
exit( EXIT_FAILURE );
}
scp_data[i].fd = fd;
scp_data[i].filename = full_path;
scp_data[i].bytesReceived = 0;
scp_data[i].read_state = CSSH_SCP_READ_STATE_READING;
len = strlen( host[i] ) + strlen( full_path ) + 5;
rc = create_progressbar( &scp_data[i].progressbar, 0, scp_data[i].size, len,
"[%s:%s]", host[i], full_path );
if( rc < 0 ) {
fprintf( stderr, "ERROR: Unable to create progress bar for host '%s' and file '%s'\n",
host[i], full_path );
finalize( &session, NULL, &scp_data, host, port, nof_sessions, args_info.verbose_given > 0 );
exit( EXIT_FAILURE );
}
rc = append_progressbar_to_pool( &progressbars, &scp_data[i].progressbar );
} break;
case SSH_SCP_REQUEST_EOF:
scp_data[i].read_state = CSSH_SCP_READ_STATE_EOF;
all_eof = true;
for( unsigned int i = 0; i < nof_sessions; i++ ) {
if( scp_data[i].read_state != CSSH_SCP_READ_STATE_EOF ) {
all_eof = false;
break;
}
}
break;
case SSH_ERROR:
fprintf( stderr, "ERROR: error from remote host '%s': %s\n",
host[i], ssh_get_error( session[i] ) );
finalize( &session, NULL, &scp_data, host, port, nof_sessions, args_info.verbose_given > 0 );
exit( EXIT_FAILURE );
break;
case SSH_SCP_REQUEST_WARNING:
fprintf( stderr, "WARNING: remote host '%s': %s\n",
host[i], ssh_scp_request_get_warning( scp_data[i].scp ) );
finalize( &session, NULL, &scp_data, host, port, nof_sessions, args_info.verbose_given > 0 );
exit( EXIT_FAILURE );
break;
}
break;
case CSSH_SCP_READ_STATE_READING:
if( scp_data[i].bytesReceived < scp_data[i].size ) {
rc = ssh_scp_read( scp_data[i].scp, scp_data[i].buf, BUFSIZE );
if( rc == SSH_ERROR ) {
fprintf( stderr, "ERROR: reading data for file '%s' failed: %s\n",
scp_data[i].filename, ssh_get_error( session[i] ) );
finalize( &session, NULL, &scp_data, host, port, nof_sessions, args_info.verbose_given > 0 );
exit( EXIT_FAILURE );
}
scp_data[i].bytesReceived += rc;
if( rc > 0 ) {
ssize_t r = write( scp_data[i].fd, scp_data[i].buf, rc );
if( r < 0 ) {
fprintf( stderr, "ERROR: writing data to file '%s' failed: %s\n",
scp_data[i].filename, strerror( errno ) );
finalize( &session, NULL, &scp_data, host, port, nof_sessions, args_info.verbose_given > 0 );
exit( EXIT_FAILURE );
}
}
if( args_info.progress_bar_given > 0 ) {
set_value_of_progressbar( &scp_data[i].progressbar, scp_data[i].bytesReceived );
}
} else {
rc = close( scp_data[i].fd );
if( rc < 0 ) {
fprintf( stderr, "ERROR: Unable to close file '%s': %s\n",
scp_data[i].filename, strerror( errno ) );
finalize( &session, NULL, &scp_data, host, port, nof_sessions, args_info.verbose_given > 0 );
exit( EXIT_FAILURE );
}
scp_data[i].read_state = CSSH_SCP_READ_STATE_IDLE;
scp_data[i].fd = 0;
free( scp_data[i].filename );
scp_data[i].filename = NULL;
remove_progressbar_from_pool( &progressbars, &scp_data[i].progressbar );
free_progressbar( &scp_data[i].progressbar );
}
break;
case CSSH_SCP_READ_STATE_EOF:
break;
}
}
if( args_info.progress_bar_given ) {
redraw_progressbars( &progressbars );
}
} break;
}
}
finalize( &session, NULL, &scp_data, host, port, nof_sessions, args_info.verbose_given > 0 );
} break;
case CSSH_EXECUTE_UNKNOWN:
break;
}
// the end
free( connection_state );
free( auth_state );
cmdline_parser_free( &args_info );
exit( EXIT_SUCCESS );
}