rofi  1.5.1
ssh.c
Go to the documentation of this file.
1 /*
2  * rofi
3  *
4  * MIT/X11 License
5  * Copyright © 2013-2017 Qball Cow <qball@gmpclient.org>
6  *
7  * Permission is hereby granted, free of charge, to any person obtaining
8  * a copy of this software and associated documentation files (the
9  * "Software"), to deal in the Software without restriction, including
10  * without limitation the rights to use, copy, modify, merge, publish,
11  * distribute, sublicense, and/or sell copies of the Software, and to
12  * permit persons to whom the Software is furnished to do so, subject to
13  * the following conditions:
14  *
15  * The above copyright notice and this permission notice shall be
16  * included in all copies or substantial portions of the Software.
17  *
18  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
19  * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
20  * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
21  * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
22  * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
23  * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
24  * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
25  *
26  */
27 
36 #define G_LOG_DOMAIN "Dialogs.Ssh"
37 
38 #include <config.h>
39 #include <glib.h>
40 #include <stdlib.h>
41 #include <stdio.h>
42 
43 #include <unistd.h>
44 #include <signal.h>
45 #include <sys/types.h>
46 #include <dirent.h>
47 #include <strings.h>
48 #include <string.h>
49 #include <ctype.h>
50 #include <errno.h>
51 #include <helper.h>
52 #include <glob.h>
53 
54 #include "rofi.h"
55 #include "settings.h"
56 #include "history.h"
57 #include "dialogs/ssh.h"
58 
62 #define SSH_CACHE_FILE "rofi-2.sshcache"
63 
68 #define SSH_TOKEN_DELIM "= \t\r\n"
69 
77 static int execshssh ( const char *host )
78 {
79  char **args = NULL;
80  int argsv = 0;
81 
82  helper_parse_setup ( config.ssh_command, &args, &argsv, "{host}", host, (char *) 0 );
83 
84  gsize l = strlen ( "Connecting to '' via rofi" ) + strlen ( host ) + 1;
85  gchar *desc = g_newa ( gchar, l );
86 
87  g_snprintf ( desc, l, "Connecting to '%s' via rofi", host );
88 
89  RofiHelperExecuteContext context = {
90  .name = "ssh",
91  .description = desc,
92  .command = "ssh",
93  };
94  return helper_execute ( NULL, args, "ssh ", host, &context );
95 }
96 
102 static void exec_ssh ( const char *host )
103 {
104  if ( !host || !host[0] ) {
105  return;
106  }
107 
108  if ( !execshssh ( host ) ) {
109  return;
110  }
111 
112  // This happens in non-critical time (After launching app)
113  // It is allowed to be a bit slower.
114  char *path = g_build_filename ( cache_dir, SSH_CACHE_FILE, NULL );
115  history_set ( path, host );
116  g_free ( path );
117 }
118 
124 static void delete_ssh ( const char *host )
125 {
126  if ( !host || !host[0] ) {
127  return;
128  }
129  char *path = g_build_filename ( cache_dir, SSH_CACHE_FILE, NULL );
130  history_remove ( path, host );
131  g_free ( path );
132 }
133 
142 static char **read_known_hosts_file ( char ** retv, unsigned int *length )
143 {
144  char *path = g_build_filename ( g_get_home_dir (), ".ssh", "known_hosts", NULL );
145  FILE *fd = fopen ( path, "r" );
146  if ( fd != NULL ) {
147  char *buffer = NULL;
148  size_t buffer_length = 0;
149  // Reading one line per time.
150  while ( getline ( &buffer, &buffer_length, fd ) > 0 ) {
151  char *sep = strstr ( buffer, "," );
152 
153  if ( sep != NULL ) {
154  *sep = '\0';
155  // Is this host name already in the list?
156  // We often get duplicates in hosts file, so lets check this.
157  int found = 0;
158  for ( unsigned int j = 0; j < ( *length ); j++ ) {
159  if ( !g_ascii_strcasecmp ( buffer, retv[j] ) ) {
160  found = 1;
161  break;
162  }
163  }
164 
165  if ( !found ) {
166  // Add this host name to the list.
167  retv = g_realloc ( retv, ( ( *length ) + 2 ) * sizeof ( char* ) );
168  retv[( *length )] = g_strdup ( buffer );
169  retv[( *length ) + 1] = NULL;
170  ( *length )++;
171  }
172  }
173  }
174  if ( buffer != NULL ) {
175  free ( buffer );
176  }
177  if ( fclose ( fd ) != 0 ) {
178  g_warning ( "Failed to close hosts file: '%s'", g_strerror ( errno ) );
179  }
180  }
181 
182  g_free ( path );
183  return retv;
184 }
185 
194 static char **read_hosts_file ( char ** retv, unsigned int *length )
195 {
196  // Read the hosts file.
197  FILE *fd = fopen ( "/etc/hosts", "r" );
198  if ( fd != NULL ) {
199  char *buffer = NULL;
200  size_t buffer_length = 0;
201  // Reading one line per time.
202  while ( getline ( &buffer, &buffer_length, fd ) > 0 ) {
203  // Evaluate one line.
204  unsigned int index = 0, ti = 0;
205  char *token = buffer;
206 
207  // Tokenize it.
208  do {
209  char c = buffer[index];
210  // Break on space, tab, newline and \0.
211  if ( c == ' ' || c == '\t' || c == '\n' || c == '\0' || c == '#' ) {
212  buffer[index] = '\0';
213  // Ignore empty tokens
214  if ( token[0] != '\0' ) {
215  ti++;
216  // and first token.
217  if ( ti > 1 ) {
218  // Is this host name already in the list?
219  // We often get duplicates in hosts file, so lets check this.
220  int found = 0;
221  for ( unsigned int j = 0; j < ( *length ); j++ ) {
222  if ( !g_ascii_strcasecmp ( token, retv[j] ) ) {
223  found = 1;
224  break;
225  }
226  }
227 
228  if ( !found ) {
229  // Add this host name to the list.
230  retv = g_realloc ( retv,
231  ( ( *length ) + 2 ) * sizeof ( char* ) );
232  retv[( *length )] = g_strdup ( token );
233  retv[( *length ) + 1] = NULL;
234  ( *length )++;
235  }
236  }
237  }
238  // Set start to next element.
239  token = &buffer[index + 1];
240  // Everything after comment ignore.
241  if ( c == '#' ) {
242  break;
243  }
244  }
245  // Skip to the next entry.
246  index++;
247  } while ( buffer[index] != '\0' && buffer[index] != '#' );
248  }
249  if ( buffer != NULL ) {
250  free ( buffer );
251  }
252  if ( fclose ( fd ) != 0 ) {
253  g_warning ( "Failed to close hosts file: '%s'", g_strerror ( errno ) );
254  }
255  }
256 
257  return retv;
258 }
259 
260 static void parse_ssh_config_file ( const char *filename, char ***retv, unsigned int *length, unsigned int num_favorites )
261 {
262  FILE *fd = fopen ( filename, "r" );
263 
264  g_debug ( "Parsing ssh config file: %s", filename );
265  if ( fd != NULL ) {
266  char *buffer = NULL;
267  size_t buffer_length = 0;
268  char *strtok_pointer = NULL;
269  while ( getline ( &buffer, &buffer_length, fd ) > 0 ) {
270  // Each line is either empty, a comment line starting with a '#'
271  // character or of the form "keyword [=] arguments", where there may
272  // be multiple (possibly quoted) arguments separated by whitespace.
273  // The keyword is separated from its arguments by whitespace OR by
274  // optional whitespace and a '=' character.
275  char *token = strtok_r ( buffer, SSH_TOKEN_DELIM, &strtok_pointer );
276 
277  // Skip empty lines and comment lines. Also skip lines where the
278  // keyword is not "Host".
279  if ( !token || *token == '#' ) {
280  continue;
281  }
282 
283  if ( g_strcmp0 ( token, "Include" ) == 0 ) {
284  token = strtok_r ( NULL, SSH_TOKEN_DELIM, &strtok_pointer );
285  g_debug ( "Found Include: %s", token );
286  gchar *path = rofi_expand_path ( token );
287  gchar *full_path = NULL;
288  if ( !g_path_is_absolute ( path ) ) {
289  char *dirname = g_path_get_dirname ( filename );
290  full_path = g_build_filename ( dirname, path, NULL );
291  g_free ( dirname );
292  }
293  else {
294  full_path = g_strdup ( path );
295  }
296  glob_t globbuf = { .gl_pathc = 0, .gl_pathv = NULL, .gl_offs = 0 };
297 
298  if ( glob ( full_path, 0, NULL, &globbuf ) == 0 ) {
299  for ( size_t iter = 0; iter < globbuf.gl_pathc; iter++ ) {
300  parse_ssh_config_file ( globbuf.gl_pathv[iter], retv, length, num_favorites );
301  }
302  }
303  globfree ( &globbuf );
304 
305  g_free ( full_path );
306  g_free ( path );
307  }
308  else if ( g_strcmp0 ( token, "Host" ) == 0 ) {
309  // Now we know that this is a "Host" line.
310  // The "Host" keyword is followed by one more host names separated
311  // by whitespace; while host names may be quoted with double quotes
312  // to represent host names containing spaces, we don't support this
313  // (how many host names contain spaces?).
314  while ( ( token = strtok_r ( NULL, SSH_TOKEN_DELIM, &strtok_pointer ) ) ) {
315  // We do not want to show wildcard entries, as you cannot ssh to them.
316  const char *const sep = "*?";
317  if ( *token == '!' || strpbrk ( token, sep ) ) {
318  continue;
319  }
320 
321  // If comment, skip from now on.
322  if ( *token == '#' ) {
323  break;
324  }
325 
326  // Is this host name already in the history file?
327  // This is a nice little penalty, but doable? time will tell.
328  // given num_favorites is max 25.
329  int found = 0;
330  for ( unsigned int j = 0; j < num_favorites; j++ ) {
331  if ( !g_ascii_strcasecmp ( token, ( *retv )[j] ) ) {
332  found = 1;
333  break;
334  }
335  }
336 
337  if ( found ) {
338  continue;
339  }
340 
341  // Add this host name to the list.
342  ( *retv ) = g_realloc ( ( *retv ), ( ( *length ) + 2 ) * sizeof ( char* ) );
343  ( *retv )[( *length )] = g_strdup ( token );
344  ( *retv )[( *length ) + 1] = NULL;
345  ( *length )++;
346  }
347  }
348  }
349  if ( buffer != NULL ) {
350  free ( buffer );
351  }
352 
353  if ( fclose ( fd ) != 0 ) {
354  g_warning ( "Failed to close ssh configuration file: '%s'", g_strerror ( errno ) );
355  }
356  }
357 }
358 
366 static char ** get_ssh ( unsigned int *length )
367 {
368  char **retv = NULL;
369  unsigned int num_favorites = 0;
370  char *path;
371 
372  if ( g_get_home_dir () == NULL ) {
373  return NULL;
374  }
375 
376  path = g_build_filename ( cache_dir, SSH_CACHE_FILE, NULL );
377  retv = history_get_list ( path, length );
378  g_free ( path );
379  num_favorites = ( *length );
380 
381  if ( config.parse_known_hosts == TRUE ) {
382  retv = read_known_hosts_file ( retv, length );
383  }
384  if ( config.parse_hosts == TRUE ) {
385  retv = read_hosts_file ( retv, length );
386  }
387 
388  const char *hd = g_get_home_dir ();
389  path = g_build_filename ( hd, ".ssh", "config", NULL );
390 
391  parse_ssh_config_file ( path, &retv, length, num_favorites );
392  g_free ( path );
393 
394  return retv;
395 }
396 
400 typedef struct
401 {
403  char **hosts_list;
405  unsigned int hosts_list_length;
407 
414 static int ssh_mode_init ( Mode *sw )
415 {
416  if ( mode_get_private_data ( sw ) == NULL ) {
417  SSHModePrivateData *pd = g_malloc0 ( sizeof ( *pd ) );
418  mode_set_private_data ( sw, (void *) pd );
419  pd->hosts_list = get_ssh ( &( pd->hosts_list_length ) );
420  }
421  return TRUE;
422 }
423 
431 static unsigned int ssh_mode_get_num_entries ( const Mode *sw )
432 {
433  const SSHModePrivateData *rmpd = (const SSHModePrivateData *) mode_get_private_data ( sw );
434  return rmpd->hosts_list_length;
435 }
441 static void ssh_mode_destroy ( Mode *sw )
442 {
444  if ( rmpd != NULL ) {
445  g_strfreev ( rmpd->hosts_list );
446  g_free ( rmpd );
447  mode_set_private_data ( sw, NULL );
448  }
449 }
450 
461 static ModeMode ssh_mode_result ( Mode *sw, int mretv, char **input, unsigned int selected_line )
462 {
463  ModeMode retv = MODE_EXIT;
465  if ( mretv & MENU_NEXT ) {
466  retv = NEXT_DIALOG;
467  }
468  else if ( mretv & MENU_PREVIOUS ) {
469  retv = PREVIOUS_DIALOG;
470  }
471  else if ( mretv & MENU_QUICK_SWITCH ) {
472  retv = ( mretv & MENU_LOWER_MASK );
473  }
474  else if ( ( mretv & MENU_OK ) && rmpd->hosts_list[selected_line] != NULL ) {
475  exec_ssh ( rmpd->hosts_list[selected_line] );
476  }
477  else if ( ( mretv & MENU_CUSTOM_INPUT ) && *input != NULL && *input[0] != '\0' ) {
478  exec_ssh ( *input );
479  }
480  else if ( ( mretv & MENU_ENTRY_DELETE ) && rmpd->hosts_list[selected_line] ) {
481  delete_ssh ( rmpd->hosts_list[selected_line] );
482  // Stay
483  retv = RELOAD_DIALOG;
484  ssh_mode_destroy ( sw );
485  ssh_mode_init ( sw );
486  }
487  return retv;
488 }
489 
490 
503 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 )
504 {
506  return get_entry ? g_strdup ( rmpd->hosts_list[selected_line] ) : NULL;
507 }
508 
518 static int ssh_token_match ( const Mode *sw, rofi_int_matcher **tokens, unsigned int index )
519 {
521  return helper_token_match ( tokens, rmpd->hosts_list[index] );
522 }
523 #include "mode-private.h"
525 {
526  .name = "ssh",
527  .cfg_name_key = "display-ssh",
528  ._init = ssh_mode_init,
529  ._get_num_entries = ssh_mode_get_num_entries,
530  ._result = ssh_mode_result,
531  ._destroy = ssh_mode_destroy,
532  ._token_match = ssh_token_match,
533  ._get_display_value = _get_display_value,
534  ._get_completion = NULL,
535  ._preprocess_input = NULL,
536  .private_data = NULL,
537  .free = NULL
538 };
const char * cache_dir
Definition: rofi.c:80
#define SSH_TOKEN_DELIM
Definition: ssh.c:68
unsigned int hosts_list_length
Definition: ssh.c:405
Definition: mode.h:69
unsigned int parse_known_hosts
Definition: settings.h:126
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)
Definition: ssh.c:503
void history_set(const char *filename, const char *entry)
Definition: history.c:175
char ** history_get_list(const char *filename, unsigned int *length)
Definition: history.c:308
static void exec_ssh(const char *host)
Definition: ssh.c:102
static char ** get_ssh(unsigned int *length)
Definition: ssh.c:366
ModeMode
Definition: mode.h:49
static unsigned int ssh_mode_get_num_entries(const Mode *sw)
Definition: ssh.c:431
static char ** read_known_hosts_file(char **retv, unsigned int *length)
Definition: ssh.c:142
gboolean helper_execute(const char *wd, char **args, const char *error_precmd, const char *error_cmd, RofiHelperExecuteContext *context)
Definition: helper.c:975
Definition: mode.h:52
static void parse_ssh_config_file(const char *filename, char ***retv, unsigned int *length, unsigned int num_favorites)
Definition: ssh.c:260
void * mode_get_private_data(const Mode *mode)
Definition: mode.c:128
char * ssh_command
Definition: settings.h:79
void history_remove(const char *filename, const char *entry)
Definition: history.c:242
int helper_token_match(rofi_int_matcher *const *tokens, const char *input)
Definition: helper.c:473
static void ssh_mode_destroy(Mode *sw)
Definition: ssh.c:441
static int ssh_token_match(const Mode *sw, rofi_int_matcher **tokens, unsigned int index)
Definition: ssh.c:518
static char ** read_hosts_file(char **retv, unsigned int *length)
Definition: ssh.c:194
#define SSH_CACHE_FILE
Definition: ssh.c:62
static ModeMode ssh_mode_result(Mode *sw, int mretv, char **input, unsigned int selected_line)
Definition: ssh.c:461
char * rofi_expand_path(const char *input)
Definition: helper.c:669
static void delete_ssh(const char *host)
Definition: ssh.c:124
int helper_parse_setup(char *string, char ***output, int *length,...)
Definition: helper.c:107
void mode_set_private_data(Mode *mode, void *pd)
Definition: mode.c:134
unsigned int parse_hosts
Definition: settings.h:124
const gchar * name
Definition: helper.h:269
Definition: mode.h:73
char ** hosts_list
Definition: ssh.c:403
Settings config
Mode ssh_mode
Definition: ssh.c:524
char * name
Definition: mode-private.h:156
static int ssh_mode_init(Mode *sw)
Definition: ssh.c:414
static int execshssh(const char *host)
Definition: ssh.c:77