rofi  1.5.2
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  // Strip whitespace.
152  char *start = g_strstrip(&(buffer[0]));
153  // Find start.
154  if ( *start == '#' || *start == '@' ){
155  // skip comments or cert-authority or revoked items.
156  printf("Comment.\n");
157  continue;
158  }
159  if ( *start == '|' ) {
160  // Skip hashed hostnames.
161  continue;
162  }
163  if ( *start == '[' ) {
164  // Don't support port versions yet, TODO
165  continue;
166  }
167  // Find end of hostname set.
168  char *end = strstr ( start, " " );
169  if ( end == NULL ) {
170  // Something is wrong.
171  continue;
172  }
173  *end = '\0';
174  char *sep = start;
175  start = strsep(&sep,", " );
176  while ( start )
177  {
178  // Is this host name already in the list?
179  // We often get duplicates in hosts file, so lets check this.
180  int found = 0;
181  for ( unsigned int j = 0; j < ( *length ); j++ ) {
182  if ( !g_ascii_strcasecmp ( start, retv[j] ) ) {
183  found = 1;
184  break;
185  }
186  }
187 
188  if ( !found ) {
189  // Add this host name to the list.
190  retv = g_realloc ( retv, ( ( *length ) + 2 ) * sizeof ( char* ) );
191  retv[( *length )] = g_strdup ( start );
192  retv[( *length ) + 1] = NULL;
193  ( *length )++;
194  }
195  start = strsep(&sep,", " );
196  }
197  }
198  if ( buffer != NULL ) {
199  free ( buffer );
200  }
201  if ( fclose ( fd ) != 0 ) {
202  g_warning ( "Failed to close hosts file: '%s'", g_strerror ( errno ) );
203  }
204  }
205 
206  g_free ( path );
207  return retv;
208 }
209 
218 static char **read_hosts_file ( char ** retv, unsigned int *length )
219 {
220  // Read the hosts file.
221  FILE *fd = fopen ( "/etc/hosts", "r" );
222  if ( fd != NULL ) {
223  char *buffer = NULL;
224  size_t buffer_length = 0;
225  // Reading one line per time.
226  while ( getline ( &buffer, &buffer_length, fd ) > 0 ) {
227  // Evaluate one line.
228  unsigned int index = 0, ti = 0;
229  char *token = buffer;
230 
231  // Tokenize it.
232  do {
233  char c = buffer[index];
234  // Break on space, tab, newline and \0.
235  if ( c == ' ' || c == '\t' || c == '\n' || c == '\0' || c == '#' ) {
236  buffer[index] = '\0';
237  // Ignore empty tokens
238  if ( token[0] != '\0' ) {
239  ti++;
240  // and first token.
241  if ( ti > 1 ) {
242  // Is this host name already in the list?
243  // We often get duplicates in hosts file, so lets check this.
244  int found = 0;
245  for ( unsigned int j = 0; j < ( *length ); j++ ) {
246  if ( !g_ascii_strcasecmp ( token, retv[j] ) ) {
247  found = 1;
248  break;
249  }
250  }
251 
252  if ( !found ) {
253  // Add this host name to the list.
254  retv = g_realloc ( retv,
255  ( ( *length ) + 2 ) * sizeof ( char* ) );
256  retv[( *length )] = g_strdup ( token );
257  retv[( *length ) + 1] = NULL;
258  ( *length )++;
259  }
260  }
261  }
262  // Set start to next element.
263  token = &buffer[index + 1];
264  // Everything after comment ignore.
265  if ( c == '#' ) {
266  break;
267  }
268  }
269  // Skip to the next entry.
270  index++;
271  } while ( buffer[index] != '\0' && buffer[index] != '#' );
272  }
273  if ( buffer != NULL ) {
274  free ( buffer );
275  }
276  if ( fclose ( fd ) != 0 ) {
277  g_warning ( "Failed to close hosts file: '%s'", g_strerror ( errno ) );
278  }
279  }
280 
281  return retv;
282 }
283 
284 static void parse_ssh_config_file ( const char *filename, char ***retv, unsigned int *length, unsigned int num_favorites )
285 {
286  FILE *fd = fopen ( filename, "r" );
287 
288  g_debug ( "Parsing ssh config file: %s", filename );
289  if ( fd != NULL ) {
290  char *buffer = NULL;
291  size_t buffer_length = 0;
292  char *strtok_pointer = NULL;
293  while ( getline ( &buffer, &buffer_length, fd ) > 0 ) {
294  // Each line is either empty, a comment line starting with a '#'
295  // character or of the form "keyword [=] arguments", where there may
296  // be multiple (possibly quoted) arguments separated by whitespace.
297  // The keyword is separated from its arguments by whitespace OR by
298  // optional whitespace and a '=' character.
299  char *token = strtok_r ( buffer, SSH_TOKEN_DELIM, &strtok_pointer );
300 
301  // Skip empty lines and comment lines. Also skip lines where the
302  // keyword is not "Host".
303  if ( !token || *token == '#' ) {
304  continue;
305  }
306 
307  if ( g_strcmp0 ( token, "Include" ) == 0 ) {
308  token = strtok_r ( NULL, SSH_TOKEN_DELIM, &strtok_pointer );
309  g_debug ( "Found Include: %s", token );
310  gchar *path = rofi_expand_path ( token );
311  gchar *full_path = NULL;
312  if ( !g_path_is_absolute ( path ) ) {
313  char *dirname = g_path_get_dirname ( filename );
314  full_path = g_build_filename ( dirname, path, NULL );
315  g_free ( dirname );
316  }
317  else {
318  full_path = g_strdup ( path );
319  }
320  glob_t globbuf = { .gl_pathc = 0, .gl_pathv = NULL, .gl_offs = 0 };
321 
322  if ( glob ( full_path, 0, NULL, &globbuf ) == 0 ) {
323  for ( size_t iter = 0; iter < globbuf.gl_pathc; iter++ ) {
324  parse_ssh_config_file ( globbuf.gl_pathv[iter], retv, length, num_favorites );
325  }
326  }
327  globfree ( &globbuf );
328 
329  g_free ( full_path );
330  g_free ( path );
331  }
332  else if ( g_strcmp0 ( token, "Host" ) == 0 ) {
333  // Now we know that this is a "Host" line.
334  // The "Host" keyword is followed by one more host names separated
335  // by whitespace; while host names may be quoted with double quotes
336  // to represent host names containing spaces, we don't support this
337  // (how many host names contain spaces?).
338  while ( ( token = strtok_r ( NULL, SSH_TOKEN_DELIM, &strtok_pointer ) ) ) {
339  // We do not want to show wildcard entries, as you cannot ssh to them.
340  const char *const sep = "*?";
341  if ( *token == '!' || strpbrk ( token, sep ) ) {
342  continue;
343  }
344 
345  // If comment, skip from now on.
346  if ( *token == '#' ) {
347  break;
348  }
349 
350  // Is this host name already in the history file?
351  // This is a nice little penalty, but doable? time will tell.
352  // given num_favorites is max 25.
353  int found = 0;
354  for ( unsigned int j = 0; j < num_favorites; j++ ) {
355  if ( !g_ascii_strcasecmp ( token, ( *retv )[j] ) ) {
356  found = 1;
357  break;
358  }
359  }
360 
361  if ( found ) {
362  continue;
363  }
364 
365  // Add this host name to the list.
366  ( *retv ) = g_realloc ( ( *retv ), ( ( *length ) + 2 ) * sizeof ( char* ) );
367  ( *retv )[( *length )] = g_strdup ( token );
368  ( *retv )[( *length ) + 1] = NULL;
369  ( *length )++;
370  }
371  }
372  }
373  if ( buffer != NULL ) {
374  free ( buffer );
375  }
376 
377  if ( fclose ( fd ) != 0 ) {
378  g_warning ( "Failed to close ssh configuration file: '%s'", g_strerror ( errno ) );
379  }
380  }
381 }
382 
390 static char ** get_ssh ( unsigned int *length )
391 {
392  char **retv = NULL;
393  unsigned int num_favorites = 0;
394  char *path;
395 
396  if ( g_get_home_dir () == NULL ) {
397  return NULL;
398  }
399 
400  path = g_build_filename ( cache_dir, SSH_CACHE_FILE, NULL );
401  retv = history_get_list ( path, length );
402  g_free ( path );
403  num_favorites = ( *length );
404 
405  if ( config.parse_known_hosts == TRUE ) {
406  retv = read_known_hosts_file ( retv, length );
407  }
408  if ( config.parse_hosts == TRUE ) {
409  retv = read_hosts_file ( retv, length );
410  }
411 
412  const char *hd = g_get_home_dir ();
413  path = g_build_filename ( hd, ".ssh", "config", NULL );
414 
415  parse_ssh_config_file ( path, &retv, length, num_favorites );
416  g_free ( path );
417 
418  return retv;
419 }
420 
424 typedef struct
425 {
427  char **hosts_list;
429  unsigned int hosts_list_length;
431 
438 static int ssh_mode_init ( Mode *sw )
439 {
440  if ( mode_get_private_data ( sw ) == NULL ) {
441  SSHModePrivateData *pd = g_malloc0 ( sizeof ( *pd ) );
442  mode_set_private_data ( sw, (void *) pd );
443  pd->hosts_list = get_ssh ( &( pd->hosts_list_length ) );
444  }
445  return TRUE;
446 }
447 
455 static unsigned int ssh_mode_get_num_entries ( const Mode *sw )
456 {
457  const SSHModePrivateData *rmpd = (const SSHModePrivateData *) mode_get_private_data ( sw );
458  return rmpd->hosts_list_length;
459 }
465 static void ssh_mode_destroy ( Mode *sw )
466 {
468  if ( rmpd != NULL ) {
469  g_strfreev ( rmpd->hosts_list );
470  g_free ( rmpd );
471  mode_set_private_data ( sw, NULL );
472  }
473 }
474 
485 static ModeMode ssh_mode_result ( Mode *sw, int mretv, char **input, unsigned int selected_line )
486 {
487  ModeMode retv = MODE_EXIT;
489  if ( mretv & MENU_NEXT ) {
490  retv = NEXT_DIALOG;
491  }
492  else if ( mretv & MENU_PREVIOUS ) {
493  retv = PREVIOUS_DIALOG;
494  }
495  else if ( mretv & MENU_QUICK_SWITCH ) {
496  retv = ( mretv & MENU_LOWER_MASK );
497  }
498  else if ( ( mretv & MENU_OK ) && rmpd->hosts_list[selected_line] != NULL ) {
499  exec_ssh ( rmpd->hosts_list[selected_line] );
500  }
501  else if ( ( mretv & MENU_CUSTOM_INPUT ) && *input != NULL && *input[0] != '\0' ) {
502  exec_ssh ( *input );
503  }
504  else if ( ( mretv & MENU_ENTRY_DELETE ) && rmpd->hosts_list[selected_line] ) {
505  delete_ssh ( rmpd->hosts_list[selected_line] );
506  // Stay
507  retv = RELOAD_DIALOG;
508  ssh_mode_destroy ( sw );
509  ssh_mode_init ( sw );
510  }
511  return retv;
512 }
513 
526 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 )
527 {
529  return get_entry ? g_strdup ( rmpd->hosts_list[selected_line] ) : NULL;
530 }
531 
541 static int ssh_token_match ( const Mode *sw, rofi_int_matcher **tokens, unsigned int index )
542 {
544  return helper_token_match ( tokens, rmpd->hosts_list[index] );
545 }
546 #include "mode-private.h"
548 {
549  .name = "ssh",
550  .cfg_name_key = "display-ssh",
551  ._init = ssh_mode_init,
552  ._get_num_entries = ssh_mode_get_num_entries,
553  ._result = ssh_mode_result,
554  ._destroy = ssh_mode_destroy,
555  ._token_match = ssh_token_match,
556  ._get_display_value = _get_display_value,
557  ._get_completion = NULL,
558  ._preprocess_input = NULL,
559  .private_data = NULL,
560  .free = NULL
561 };
const char * cache_dir
Definition: rofi.c:83
#define SSH_TOKEN_DELIM
Definition: ssh.c:68
unsigned int hosts_list_length
Definition: ssh.c:429
Definition: mode.h:69
unsigned int parse_known_hosts
Definition: settings.h:138
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:526
void history_set(const char *filename, const char *entry)
Definition: history.c:178
char ** history_get_list(const char *filename, unsigned int *length)
Definition: history.c:311
static void exec_ssh(const char *host)
Definition: ssh.c:102
static char ** get_ssh(unsigned int *length)
Definition: ssh.c:390
ModeMode
Definition: mode.h:49
static unsigned int ssh_mode_get_num_entries(const Mode *sw)
Definition: ssh.c:455
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:993
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:284
void * mode_get_private_data(const Mode *mode)
Definition: mode.c:128
char * ssh_command
Definition: settings.h:85
void history_remove(const char *filename, const char *entry)
Definition: history.c:245
int helper_token_match(rofi_int_matcher *const *tokens, const char *input)
Definition: helper.c:474
static void ssh_mode_destroy(Mode *sw)
Definition: ssh.c:465
static int ssh_token_match(const Mode *sw, rofi_int_matcher **tokens, unsigned int index)
Definition: ssh.c:541
static char ** read_hosts_file(char **retv, unsigned int *length)
Definition: ssh.c:218
#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:485
char * rofi_expand_path(const char *input)
Definition: helper.c:687
static void delete_ssh(const char *host)
Definition: ssh.c:124
int helper_parse_setup(char *string, char ***output, int *length,...)
Definition: helper.c:108
void mode_set_private_data(Mode *mode, void *pd)
Definition: mode.c:134
unsigned int parse_hosts
Definition: settings.h:136
const gchar * name
Definition: helper.h:269
unsigned int start
Definition: rofi-types.h:225
Definition: mode.h:73
char ** hosts_list
Definition: ssh.c:427
Settings config
Mode ssh_mode
Definition: ssh.c:547
char * name
Definition: mode-private.h:156
static int ssh_mode_init(Mode *sw)
Definition: ssh.c:438
static int execshssh(const char *host)
Definition: ssh.c:77