/* 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 != 22 ) { 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 ); }