From f87906241708adbbb0239b7815e2850727469f7c Mon Sep 17 00:00:00 2001 From: Dave Davenport Date: Sun, 10 Feb 2019 13:55:09 +0100 Subject: [SSH] Add support for parsing port number from known hosts file. * Store port number in history. * Parse the [host]:port format. * Update default ssh command to (optionally) add -p {port}. Fixes: #580 --- config/config.c | 2 +- source/dialogs/ssh.c | 125 ++++++++++++++++++++++++++++++++++----------------- source/helper.c | 48 +++++--------------- 3 files changed, 98 insertions(+), 77 deletions(-) diff --git a/config/config.c b/config/config.c index c5e569bc..68201147 100644 --- a/config/config.c +++ b/config/config.c @@ -58,7 +58,7 @@ Settings config = { .terminal_emulator = "rofi-sensible-terminal", .ssh_client = "ssh", /** Command when executing ssh. */ - .ssh_command = "{terminal} -e {ssh-client} {host}", + .ssh_command = "{terminal} -e {ssh-client} {host} [-p {port}]", /** Command when running */ .run_command = "{cmd}", /** Command used to list executable commands. empty -> internal */ diff --git a/source/dialogs/ssh.c b/source/dialogs/ssh.c index 486cced1..c719f393 100644 --- a/source/dialogs/ssh.c +++ b/source/dialogs/ssh.c @@ -67,6 +67,12 @@ */ #define SSH_TOKEN_DELIM "= \t\r\n" + +typedef struct _SshEntry { + char *hostname; + int port; +} SshEntry; + /** * @param host The host to connect too * @@ -74,24 +80,31 @@ * * @returns FALSE On failure, TRUE on success */ -static int execshssh ( const char *host ) +static int execshssh ( const SshEntry *entry) { char **args = NULL; int argsv = 0; + gchar *portstr = NULL; + if ( entry->port > 0 ) { + portstr = g_strdup_printf("%d", entry->port); + } + helper_parse_setup ( config.ssh_command, &args, &argsv, + "{host}", entry->hostname, + "{port}", portstr, + (char *) 0 ); + g_free ( portstr ); - helper_parse_setup ( config.ssh_command, &args, &argsv, "{host}", host, (char *) 0 ); - - gsize l = strlen ( "Connecting to '' via rofi" ) + strlen ( host ) + 1; + gsize l = strlen ( "Connecting to '' via rofi" ) + strlen ( entry->hostname ) + 1; gchar *desc = g_newa ( gchar, l ); - g_snprintf ( desc, l, "Connecting to '%s' via rofi", host ); + g_snprintf ( desc, l, "Connecting to '%s' via rofi", entry->hostname ); RofiHelperExecuteContext context = { .name = "ssh", .description = desc, .command = "ssh", }; - return helper_execute ( NULL, args, "ssh ", host, &context ); + return helper_execute ( NULL, args, "ssh ", entry->hostname, &context ); } /** @@ -99,20 +112,27 @@ static int execshssh ( const char *host ) * * SSH into the selected host, if successful update history. */ -static void exec_ssh ( const char *host ) +static void exec_ssh ( const SshEntry *entry ) { - if ( !host || !host[0] ) { + if ( !(entry->hostname )|| !(entry->hostname[0]) ) { return; } - if ( !execshssh ( host ) ) { + if ( !execshssh ( entry ) ) { return; } // This happens in non-critical time (After launching app) // It is allowed to be a bit slower. char *path = g_build_filename ( cache_dir, SSH_CACHE_FILE, NULL ); - history_set ( path, host ); + // TODO update. + if ( entry->port > 0 ) { + char *store = g_strdup_printf("%s:%d", entry->hostname, entry->port ); + history_set ( path, store ); + g_free ( store ); + } else { + history_set ( path, entry->hostname ); + } g_free ( path ); } @@ -139,7 +159,7 @@ static void delete_ssh ( const char *host ) * * @returns updated list of hosts. */ -static char **read_known_hosts_file ( char ** retv, unsigned int *length ) +static SshEntry *read_known_hosts_file ( SshEntry * retv, unsigned int *length ) { char *path = g_build_filename ( g_get_home_dir (), ".ssh", "known_hosts", NULL ); FILE *fd = fopen ( path, "r" ); @@ -160,10 +180,6 @@ static char **read_known_hosts_file ( char ** retv, unsigned int *length ) // Skip hashed hostnames. continue; } - if ( *start == '[' ) { - // Don't support port versions yet, TODO - continue; - } // Find end of hostname set. char *end = strstr ( start, " " ); if ( end == NULL ) { @@ -175,11 +191,20 @@ static char **read_known_hosts_file ( char ** retv, unsigned int *length ) start = strsep(&sep,", " ); while ( start ) { + int port = 0; + if ( start[0] == '[' ) { + start++; + char *end = strchr ( start, ']'); + if ( end[1] == ':' ){ + *end = '\0'; + port = atoi ( &(end[2]) ); + } + } // Is this host name already in the list? // We often get duplicates in hosts file, so lets check this. int found = 0; for ( unsigned int j = 0; j < ( *length ); j++ ) { - if ( !g_ascii_strcasecmp ( start, retv[j] ) ) { + if ( !g_ascii_strcasecmp ( start, retv[j].hostname ) ) { found = 1; break; } @@ -187,9 +212,11 @@ static char **read_known_hosts_file ( char ** retv, unsigned int *length ) if ( !found ) { // Add this host name to the list. - retv = g_realloc ( retv, ( ( *length ) + 2 ) * sizeof ( char* ) ); - retv[( *length )] = g_strdup ( start ); - retv[( *length ) + 1] = NULL; + retv = g_realloc ( retv, ( ( *length ) + 2 ) * sizeof ( SshEntry ) ); + retv[( *length )].hostname = g_strdup ( start ); + retv[( *length )].port = port; + retv[( *length ) + 1].hostname = NULL; + retv[( *length ) + 1].port = 0; ( *length )++; } start = strsep(&sep,", " ); @@ -215,7 +242,7 @@ static char **read_known_hosts_file ( char ** retv, unsigned int *length ) * * @returns an updated list with the added hosts. */ -static char **read_hosts_file ( char ** retv, unsigned int *length ) +static SshEntry *read_hosts_file ( SshEntry * retv, unsigned int *length ) { // Read the hosts file. FILE *fd = fopen ( "/etc/hosts", "r" ); @@ -243,7 +270,7 @@ static char **read_hosts_file ( char ** retv, unsigned int *length ) // We often get duplicates in hosts file, so lets check this. int found = 0; for ( unsigned int j = 0; j < ( *length ); j++ ) { - if ( !g_ascii_strcasecmp ( token, retv[j] ) ) { + if ( !g_ascii_strcasecmp ( token, retv[j].hostname ) ) { found = 1; break; } @@ -253,8 +280,8 @@ static char **read_hosts_file ( char ** retv, unsigned int *length ) // Add this host name to the list. retv = g_realloc ( retv, ( ( *length ) + 2 ) * sizeof ( char* ) ); - retv[( *length )] = g_strdup ( token ); - retv[( *length ) + 1] = NULL; + retv[( *length )].hostname = g_strdup ( token ); + retv[( *length ) + 1].hostname = NULL; ( *length )++; } } @@ -281,7 +308,7 @@ static char **read_hosts_file ( char ** retv, unsigned int *length ) return retv; } -static void parse_ssh_config_file ( const char *filename, char ***retv, unsigned int *length, unsigned int num_favorites ) +static void parse_ssh_config_file ( const char *filename, SshEntry **retv, unsigned int *length, unsigned int num_favorites ) { FILE *fd = fopen ( filename, "r" ); @@ -352,7 +379,7 @@ static void parse_ssh_config_file ( const char *filename, char ***retv, unsigned // given num_favorites is max 25. int found = 0; for ( unsigned int j = 0; j < num_favorites; j++ ) { - if ( !g_ascii_strcasecmp ( token, ( *retv )[j] ) ) { + if ( !g_ascii_strcasecmp ( token, ( *retv )[j].hostname ) ) { found = 1; break; } @@ -363,9 +390,9 @@ static void parse_ssh_config_file ( const char *filename, char ***retv, unsigned } // Add this host name to the list. - ( *retv ) = g_realloc ( ( *retv ), ( ( *length ) + 2 ) * sizeof ( char* ) ); - ( *retv )[( *length )] = g_strdup ( token ); - ( *retv )[( *length ) + 1] = NULL; + ( *retv ) = g_realloc ( ( *retv ), ( ( *length ) + 2 ) * sizeof ( SshEntry ) ); + ( *retv )[( *length )].hostname = g_strdup ( token ); + ( *retv )[( *length ) + 1].hostname = NULL; ( *length )++; } } @@ -387,9 +414,9 @@ static void parse_ssh_config_file ( const char *filename, char ***retv, unsigned * * @return an array of strings containing all the hosts. */ -static char ** get_ssh ( unsigned int *length ) +static SshEntry * get_ssh ( unsigned int *length ) { - char **retv = NULL; + SshEntry *retv = NULL; unsigned int num_favorites = 0; char *path; @@ -398,7 +425,21 @@ static char ** get_ssh ( unsigned int *length ) } path = g_build_filename ( cache_dir, SSH_CACHE_FILE, NULL ); - retv = history_get_list ( path, length ); + char **h = history_get_list ( path, length ); + + retv = malloc ( (*length)*sizeof(SshEntry)); + for ( unsigned int i = 0; i < (*length); i++ ){ + int port = 0; + char *portstr = strchr ( h[i], ':' ); + if ( portstr != NULL ) { + *portstr = '\0'; + port = atoi ( &(portstr[1]) ); + } + retv[i].hostname = h[i]; + retv[i].port = port; + } + g_free (h); + g_free ( path ); num_favorites = ( *length ); @@ -424,7 +465,7 @@ static char ** get_ssh ( unsigned int *length ) typedef struct { /** List if available ssh hosts.*/ - char **hosts_list; + SshEntry *hosts_list; /** Length of the #hosts_list.*/ unsigned int hosts_list_length; } SSHModePrivateData; @@ -466,7 +507,10 @@ static void ssh_mode_destroy ( Mode *sw ) { SSHModePrivateData *rmpd = (SSHModePrivateData *) mode_get_private_data ( sw ); if ( rmpd != NULL ) { - g_strfreev ( rmpd->hosts_list ); + for ( unsigned int i = 0; i < rmpd->hosts_list_length; i++ ){ + g_free( rmpd->hosts_list[i].hostname ); + } + g_free ( rmpd->hosts_list ); g_free ( rmpd ); mode_set_private_data ( sw, NULL ); } @@ -495,14 +539,15 @@ static ModeMode ssh_mode_result ( Mode *sw, int mretv, char **input, unsigned in else if ( mretv & MENU_QUICK_SWITCH ) { retv = ( mretv & MENU_LOWER_MASK ); } - else if ( ( mretv & MENU_OK ) && rmpd->hosts_list[selected_line] != NULL ) { - exec_ssh ( rmpd->hosts_list[selected_line] ); + else if ( ( mretv & MENU_OK ) && rmpd->hosts_list[selected_line].hostname != NULL ) { + exec_ssh ( &(rmpd->hosts_list[selected_line]) ); } else if ( ( mretv & MENU_CUSTOM_INPUT ) && *input != NULL && *input[0] != '\0' ) { - exec_ssh ( *input ); + SshEntry entry = { .hostname = *input, .port = 0 }; + exec_ssh ( &entry ); } - else if ( ( mretv & MENU_ENTRY_DELETE ) && rmpd->hosts_list[selected_line] ) { - delete_ssh ( rmpd->hosts_list[selected_line] ); + else if ( ( mretv & MENU_ENTRY_DELETE ) && rmpd->hosts_list[selected_line].hostname ) { + delete_ssh ( rmpd->hosts_list[selected_line].hostname ); // Stay retv = RELOAD_DIALOG; ssh_mode_destroy ( sw ); @@ -526,7 +571,7 @@ static ModeMode ssh_mode_result ( Mode *sw, int mretv, char **input, unsigned in static char *_get_display_value ( const Mode *sw, unsigned int selected_line, G_GNUC_UNUSED int *state, G_GNUC_UNUSED GList **attr_list, int get_entry ) { SSHModePrivateData *rmpd = (SSHModePrivateData *) mode_get_private_data ( sw ); - return get_entry ? g_strdup ( rmpd->hosts_list[selected_line] ) : NULL; + return get_entry ? g_strdup ( rmpd->hosts_list[selected_line].hostname ) : NULL; } /** @@ -541,7 +586,7 @@ static char *_get_display_value ( const Mode *sw, unsigned int selected_line, G_ static int ssh_token_match ( const Mode *sw, rofi_int_matcher **tokens, unsigned int index ) { SSHModePrivateData *rmpd = (SSHModePrivateData *) mode_get_private_data ( sw ); - return helper_token_match ( tokens, rmpd->hosts_list[index] ); + return helper_token_match ( tokens, rmpd->hosts_list[index].hostname ); } #include "mode-private.h" Mode ssh_mode = diff --git a/source/helper.c b/source/helper.c index 61bf5dd7..cb546588 100644 --- a/source/helper.c +++ b/source/helper.c @@ -71,39 +71,14 @@ static int stored_argc = 0; /** copy of the argv pointer for use in the commandline argument parser */ static char **stored_argv = NULL; +char *helper_string_replace_if_exists_v ( char * string, GHashTable *h ); + void cmd_set_arguments ( int argc, char **argv ) { stored_argc = argc; stored_argv = argv; } -/** - * @param info To Match information on. - * @param res The string being generated. - * @param data User data - * - * Replace the entries. This function gets called by g_regex_replace_eval. - * - * @returns TRUE to stop replacement, FALSE to continue - */ -static gboolean helper_eval_cb ( const GMatchInfo *info, GString *res, gpointer data ) -{ - gchar *match; - // Get the match - match = g_match_info_fetch ( info, 0 ); - if ( match != NULL ) { - // Lookup the match, so we can replace it. - gchar *r = g_hash_table_lookup ( (GHashTable *) data, match ); - if ( r != NULL ) { - // Append the replacement to the string. - g_string_append ( res, r ); - } - // Free match. - g_free ( match ); - } - // Continue replacement. - return FALSE; -} int helper_parse_setup ( char * string, char ***output, int *length, ... ) { @@ -129,11 +104,7 @@ int helper_parse_setup ( char * string, char ***output, int *length, ... ) } va_end ( ap ); - // Replace hits within {-\w+}. - GRegex *reg = g_regex_new ( "{[-\\w]+}", 0, 0, NULL ); - char *res = g_regex_replace_eval ( reg, string, -1, 0, 0, helper_eval_cb, h, NULL ); - // Free regex. - g_regex_unref ( reg ); + char *res = helper_string_replace_if_exists_v ( string, h ); // Destroy key-value storage. g_hash_table_destroy ( h ); // Parse the string into shell arguments. @@ -1283,12 +1254,11 @@ static gboolean helper_eval_cb2 ( const GMatchInfo *info, GString *res, gpointer char *helper_string_replace_if_exists ( char * string, ... ) { - GError *error = NULL; GHashTable *h; h = g_hash_table_new ( g_str_hash, g_str_equal ); - // Add list from variable arguments. va_list ap; va_start ( ap, string ); + // Add list from variable arguments. while ( 1 ) { char * key = va_arg ( ap, char * ); if ( key == (char *) 0 ) { @@ -1297,15 +1267,21 @@ char *helper_string_replace_if_exists ( char * string, ... ) char *value = va_arg ( ap, char * ); g_hash_table_insert ( h, key, value ); } + char *retv = helper_string_replace_if_exists_v ( string, h ); va_end ( ap ); + // Destroy key-value storage. + g_hash_table_destroy ( h ); + return retv; +} +char *helper_string_replace_if_exists_v ( char * string, GHashTable *h ) +{ + GError *error = NULL; // Replace hits within {-\w+}. GRegex *reg = g_regex_new ( "\\[(.*)({[-\\w]+})(.*)\\]|({[\\w-]+})", 0, 0, NULL ); char *res = g_regex_replace_eval ( reg, string, -1, 0, 0, helper_eval_cb2, h, NULL ); // Free regex. g_regex_unref ( reg ); - // Destroy key-value storage. - g_hash_table_destroy ( h ); // Throw error if shell parsing fails. if ( error ) { char *msg = g_strdup_printf ( "Failed to parse: '%s'\nError: '%s'", string, error->message ); -- cgit v1.2.3