rofi 2.1.0
ssh.c
Go to the documentation of this file.
1/*
2 * rofi
3 *
4 * MIT/X11 License
5 * Copyright © 2013-2023 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
32
36#define G_LOG_DOMAIN "Modes.Ssh"
37
38#include "config.h"
39#include <glib.h>
40#include <stdio.h>
41#include <stdlib.h>
42
43#include <ctype.h>
44#include <dirent.h>
45#include <errno.h>
46#include <glob.h>
47#include <helper.h>
48#include <signal.h>
49#include <string.h>
50#include <strings.h>
51#include <sys/types.h>
52#include <unistd.h>
53
54#include "history.h"
55#include "modes/ssh.h"
56#include "rofi.h"
57#include "settings.h"
58
62typedef struct _SshEntry {
64 char *hostname;
65 char **aliases;
67 int port;
79
83#define SSH_CACHE_FILE "rofi-2.sshcache"
84
89#define SSH_TOKEN_DELIM "= \t\r\n"
90
98static int execshssh(const SshEntry *entry) {
99 char **args = NULL;
100 int argsv = 0;
101 gchar *portstr = NULL;
102 if (entry->port > 0) {
103 portstr = g_strdup_printf("%d", entry->port);
104 }
105 helper_parse_setup(config.ssh_command, &args, &argsv, "{host}",
106 entry->hostname, "{port}", portstr, (char *)0);
107 g_free(portstr);
108
109 gsize l = strlen("Connecting to '' via rofi") + strlen(entry->hostname) + 1;
110 gchar *desc = g_newa(gchar, l);
111
112 g_snprintf(desc, l, "Connecting to '%s' via rofi", entry->hostname);
113
114 RofiHelperExecuteContext context = {
115 .name = "ssh",
116 .description = desc,
117 .command = "ssh",
118 };
119 return helper_execute(NULL, args, "ssh ", entry->hostname, &context);
120}
121
127static void exec_ssh(const SshEntry *entry) {
128 if (!(entry->hostname) || !(entry->hostname[0])) {
129 return;
130 }
131
132 if (!execshssh(entry)) {
133 return;
134 }
135
136 // This happens in non-critical time (After launching app)
137 // It is allowed to be a bit slower.
138 char *path = g_build_filename(cache_dir, SSH_CACHE_FILE, NULL);
139 // TODO update.
140 GString *str = g_string_new(entry->hostname);
141 g_string_append_printf(str, "\x1F%d", entry->port);
142 for (int x = 0; entry->aliases && entry->aliases[x]; x++) {
143 g_string_append_printf(str, "\x1F%s", entry->aliases[x]);
144 }
145 history_set(path, str->str);
146 g_string_free(str, TRUE);
147 g_free(path);
148}
149
155static void delete_ssh(const char *host) {
156 if (!host || !host[0]) {
157 return;
158 }
159 char *path = g_build_filename(cache_dir, SSH_CACHE_FILE, NULL);
160 history_remove(path, host);
161 g_free(path);
162}
163
173static SshEntry *read_known_hosts_file(const char *path, SshEntry *retv,
174 unsigned int *length) {
175 FILE *fd = fopen(path, "r");
176 if (fd != NULL) {
177 char *buffer = NULL;
178 size_t buffer_length = 0;
179 // Reading one line per time.
180 while (getline(&buffer, &buffer_length, fd) > 0) {
181 // Strip whitespace.
182 char *start = g_strstrip(&(buffer[0]));
183 // Find start.
184 if (*start == '#' || *start == '@') {
185 // skip comments or cert-authority or revoked items.
186 continue;
187 }
188 if (*start == '|') {
189 // Skip hashed hostnames.
190 continue;
191 }
192 // Find end of hostname set.
193 char *end = strstr(start, " ");
194 if (end == NULL) {
195 // Something is wrong.
196 continue;
197 }
198 *end = '\0';
199 char *sep = start;
200 start = strsep(&sep, ", ");
201 while (start) {
202 int port = 0;
203 if (start[0] == '[') {
204 start++;
205 char *strend = strchr(start, ']');
206 if (strend == NULL)
207 {
208 // If strend is NULL, then the entry is malformed. So it should be
209 // skipped.
210 start = strsep(&sep, ", ");
211 continue;
212 }
213 if (strend[1] == ':')
214 {
215 *strend = '\0';
216 errno = 0;
217 gchar *endptr = NULL;
218 gint64 number = g_ascii_strtoll(&(strend[2]), &endptr, 10);
219 if (errno != 0) {
220 g_warning("Failed to parse port number: %s.", &(strend[2]));
221 } else if (endptr == &(strend[2])) {
222 g_warning("Failed to parse port number: %s, invalid number.",
223 &(strend[2]));
224 } else if (number < 0 || number > 65535) {
225 g_warning("Failed to parse port number: %s, out of range.",
226 &(strend[2]));
227 } else {
228 port = number;
229 }
230 }
231 }
232 // Is this host name already in the list?
233 // We often get duplicates in hosts file, so lets check this.
234 int found = 0;
235 for (unsigned int j = 0; j < (*length); j++) {
236 if (!g_ascii_strcasecmp(start, retv[j].hostname)) {
237 found = 1;
238 break;
239 }
240 }
241
242 if (!found) {
243 // Add this host name to the list.
244 retv = g_realloc(retv, ((*length) + 2) * sizeof(SshEntry));
245 retv[(*length)].hostname = g_strdup(start);
246 retv[(*length)].aliases = NULL;
247 retv[(*length)].port = port;
248 retv[(*length) + 1].hostname = NULL;
249 retv[(*length) + 1].aliases = NULL;
250 retv[(*length) + 1].port = 0;
251 (*length)++;
252 }
253 start = strsep(&sep, ", ");
254 }
255 }
256 if (buffer != NULL) {
257 free(buffer);
258 }
259 if (fclose(fd) != 0) {
260 g_warning("Failed to close hosts file: '%s'", g_strerror(errno));
261 }
262 } else {
263 g_debug("Failed to open KnownHostFile: '%s'", path);
264 }
265
266 return retv;
267}
268
277static SshEntry *read_hosts_file(SshEntry *retv, unsigned int *length) {
278 // Read the hosts file.
279 FILE *fd = fopen("/etc/hosts", "r");
280 if (fd != NULL) {
281 char *buffer = NULL;
282 size_t buffer_length = 0;
283 // Reading one line per time.
284 while (getline(&buffer, &buffer_length, fd) > 0) {
285 // Evaluate one line.
286 unsigned int index = 0, ti = 0;
287 char *token = buffer;
288
289 // Tokenize it.
290 do {
291 char c = buffer[index];
292 // Break on space, tab, newline and \0.
293 if (c == ' ' || c == '\t' || c == '\n' || c == '\0' || c == '#') {
294 buffer[index] = '\0';
295 // Ignore empty tokens
296 if (token[0] != '\0') {
297 ti++;
298 // and first token.
299 if (ti > 1) {
300 // Is this host name already in the list?
301 // We often get duplicates in hosts file, so lets check this.
302 int found = 0;
303 for (unsigned int j = 0; j < (*length); j++) {
304 if (!g_ascii_strcasecmp(token, retv[j].hostname)) {
305 found = 1;
306 break;
307 }
308 }
309
310 if (!found) {
311 // Add this host name to the list.
312 retv = g_realloc(retv, ((*length) + 2) * sizeof(SshEntry));
313 retv[(*length)].hostname = g_strdup(token);
314 retv[(*length)].aliases = NULL;
315 retv[(*length)].port = 0;
316 retv[(*length) + 1].hostname = NULL;
317 retv[(*length) + 1].aliases = NULL;
318 (*length)++;
319 }
320 }
321 }
322 // Set start to next element.
323 token = &buffer[index + 1];
324 // Everything after comment ignore.
325 if (c == '#') {
326 break;
327 }
328 }
329 // Skip to the next entry.
330 index++;
331 } while (buffer[index] != '\0' && buffer[index] != '#');
332 }
333 if (buffer != NULL) {
334 free(buffer);
335 }
336 if (fclose(fd) != 0) {
337 g_warning("Failed to close hosts file: '%s'", g_strerror(errno));
338 }
339 }
340
341 return retv;
342}
343
344static void add_known_hosts_file(SSHModePrivateData *pd, const char *token) {
345 GList *item =
346 g_list_find_custom(pd->user_known_hosts, token, (GCompareFunc)g_strcmp0);
347 if (item == NULL) {
348 g_debug("Add '%s' to UserKnownHost list", token);
349 pd->user_known_hosts = g_list_append(pd->user_known_hosts, g_strdup(token));
350 } else {
351 g_debug("File '%s' already in UserKnownHostsFile list", token);
352 }
353}
354
355static void parse_ssh_config_file(SSHModePrivateData *pd, const char *filename,
356 SshEntry **retv, unsigned int *length,
357 unsigned int num_favorites) {
358 FILE *fd = fopen(filename, "r");
359
360 g_debug("Parsing ssh config file: %s", filename);
361 if (fd != NULL) {
362 char *buffer = NULL;
363 size_t buffer_length = 0;
364 char *strtok_pointer = NULL;
365 while (getline(&buffer, &buffer_length, fd) > 0) {
366 // Each line is either empty, a comment line starting with a '#'
367 // character or of the form "keyword [=] arguments", where there may
368 // be multiple (possibly quoted) arguments separated by whitespace.
369 // The keyword is separated from its arguments by whitespace OR by
370 // optional whitespace and a '=' character.
371 char *token = strtok_r(buffer, SSH_TOKEN_DELIM, &strtok_pointer);
372 // Skip empty lines and comment lines. Also skip lines where the
373 // keyword is not "Host".
374 if (!token || *token == '#') {
375 continue;
376 }
377 char *low_token = g_ascii_strdown(token, -1);
378 if (g_strcmp0(low_token, "include") == 0) {
379 token = strtok_r(NULL, SSH_TOKEN_DELIM, &strtok_pointer);
380 g_debug("Found Include: %s", token);
381 gchar *path = rofi_expand_path(token);
382 gchar *full_path = NULL;
383 if (!g_path_is_absolute(path)) {
384 char *dirname = g_path_get_dirname(filename);
385 full_path = g_build_filename(dirname, path, NULL);
386 g_free(dirname);
387 } else {
388 full_path = g_strdup(path);
389 }
390 glob_t globbuf = {.gl_pathc = 0, .gl_pathv = NULL, .gl_offs = 0};
391
392 if (glob(full_path, 0, NULL, &globbuf) == 0) {
393 for (size_t iter = 0; iter < globbuf.gl_pathc; iter++) {
394 parse_ssh_config_file(pd, globbuf.gl_pathv[iter], retv, length,
395 num_favorites);
396 }
397 }
398 globfree(&globbuf);
399
400 g_free(full_path);
401 g_free(path);
402 } else if (g_strcmp0(low_token, "userknownhostsfile") == 0) {
403 while ((token = strtok_r(NULL, SSH_TOKEN_DELIM, &strtok_pointer))) {
404 g_debug("Found extra UserKnownHostsFile: %s", token);
405 add_known_hosts_file(pd, token);
406 }
407 } else if (g_strcmp0(low_token, "host") == 0) {
408 // Now we know that this is a "Host" line.
409 // The "Host" keyword is followed by one more host names separated
410 // by whitespace; while host names may be quoted with double quotes
411 // to represent host names containing spaces, we don't support this
412 // (how many host names contain spaces?).
413 int aliases = 0;
414 while ((token = strtok_r(NULL, SSH_TOKEN_DELIM, &strtok_pointer))) {
415 // We do not want to show wildcard entries, as you cannot ssh to them.
416 const char *const sep = "*?";
417 if (*token == '!' || strpbrk(token, sep)) {
418 continue;
419 }
420
421 // If comment, skip from now on.
422 if (*token == '#') {
423 break;
424 }
425
426 // Is this host name already in the history file?
427 // This is a nice little penalty, but doable? time will tell.
428 // given num_favorites is max 25.
429 int found = 0;
430 for (unsigned int j = 0; j < num_favorites; j++) {
431 if (!g_ascii_strcasecmp(token, (*retv)[j].hostname)) {
432 found = 1;
433 break;
434 }
435 if ((*retv)[j].aliases != NULL) {
436 for (int x = 0; (*retv)[j].aliases[x] != NULL; x++) {
437 if (!g_ascii_strcasecmp(token, (*retv)[j].aliases[x])) {
438 found = 1;
439 break;
440 }
441 }
442 }
443 }
444
445 if (found) {
446 continue;
447 }
448
449 if (aliases == 0) {
450 // Add this host name to the list.
451 (*retv) = g_realloc((*retv), ((*length) + 2) * sizeof(SshEntry));
452 (*retv)[(*length)].hostname = g_strdup(token);
453 (*retv)[(*length)].aliases = NULL;
454 (*retv)[(*length)].port = 0;
455 (*retv)[(*length) + 1].hostname = NULL;
456 (*retv)[(*length) + 1].aliases = NULL;
457 (*length)++;
458 aliases = 1;
459 } else {
460 int index = (*length) - 1;
461 int l = 0;
462 if ((*retv)[index].aliases != NULL) {
463 l = g_strv_length((*retv)[index].aliases);
464 }
465 (*retv)[index].aliases =
466 g_realloc((*retv)[index].aliases, (l + 2) * sizeof(char *));
467 (*retv)[index].aliases[l] = g_strdup(token);
468 (*retv)[index].aliases[l + 1] = NULL;
469 }
470 }
471 }
472 g_free(low_token);
473 }
474 if (buffer != NULL) {
475 free(buffer);
476 }
477
478 if (fclose(fd) != 0) {
479 g_warning("Failed to close ssh configuration file: '%s'",
480 g_strerror(errno));
481 }
482 }
483}
484
493static SshEntry *get_ssh(SSHModePrivateData *pd, unsigned int *length) {
494 SshEntry *retv = NULL;
495 unsigned int num_favorites = 0;
496 char *path;
497
498 if (g_get_home_dir() == NULL) {
499 return NULL;
500 }
501
502 path = g_build_filename(cache_dir, SSH_CACHE_FILE, NULL);
503 char **h = history_get_list(path, length);
504
505 retv = g_malloc0((*length) * sizeof(SshEntry));
506 for (unsigned int i = 0; i < (*length); i++) {
507 int port = 0;
508 char **ssplit = g_strsplit(h[i], "\x1F", -1);
509 guint ssplit_len = g_strv_length(ssplit);
510 if (ssplit_len > 1) {
511 errno = 0;
512 gchar *endptr = NULL;
513 gint64 number = g_ascii_strtoll(ssplit[1], &endptr, 10);
514 if (errno != 0) {
515 g_warning("Failed to parse port number: %s.", (ssplit[1]));
516 } else if (endptr == (ssplit[1])) {
517 g_warning("Failed to parse port number: %s, invalid number.",
518 (ssplit[1]));
519 } else if (number < 0 || number > 65535) {
520 g_warning("Failed to parse port number: %s, out of range.",
521 (ssplit[1]));
522 } else {
523 port = number;
524 }
525 }
526 retv[i].hostname = g_strdup(ssplit[0]);
527 retv[i].port = port;
528 retv[i].aliases = NULL;
529 gint naliases = ssplit_len - 2;
530 if (naliases > 0) {
531 retv[i].aliases = g_malloc0(sizeof(char *) * (naliases + 1));
532 for (gint j = 0; j < naliases; j++) {
533 retv[i].aliases[j] = g_strdup(ssplit[2 + j]);
534 }
535 }
536 g_strfreev(ssplit);
537 }
538 g_free(h);
539
540 g_free(path);
541 num_favorites = (*length);
542
543 const char *hd = g_get_home_dir();
544 path = g_build_filename(hd, ".ssh", "config", NULL);
545 parse_ssh_config_file(pd, path, &retv, length, num_favorites);
546
547 if (config.parse_known_hosts == TRUE) {
548 char *known_hosts_path =
549 g_build_filename(g_get_home_dir(), ".ssh", "known_hosts", NULL);
550 retv = read_known_hosts_file(known_hosts_path, retv, length);
551 g_free(known_hosts_path);
552 for (GList *iter = g_list_first(pd->user_known_hosts); iter;
553 iter = g_list_next(iter)) {
554 char *user_known_hosts_path = rofi_expand_path((const char *)iter->data);
555 retv = read_known_hosts_file((const char *)user_known_hosts_path, retv,
556 length);
557 g_free(user_known_hosts_path);
558 }
559 }
560 if (config.parse_hosts == TRUE) {
561 retv = read_hosts_file(retv, length);
562 }
563
564 g_free(path);
565
566 return retv;
567}
568
575static int ssh_mode_init(Mode *sw) {
576 if (mode_get_private_data(sw) == NULL) {
577 SSHModePrivateData *pd = g_malloc0(sizeof(*pd));
578 mode_set_private_data(sw, (void *)pd);
579 pd->hosts_list = get_ssh(pd, &(pd->hosts_list_length));
580 }
581 return TRUE;
582}
583
591static unsigned int ssh_mode_get_num_entries(const Mode *sw) {
592 const SSHModePrivateData *rmpd =
594 return rmpd->hosts_list_length;
595}
596
602static void ssh_mode_destroy(Mode *sw) {
604 if (rmpd != NULL) {
605 for (unsigned int i = 0; i < rmpd->hosts_list_length; i++) {
606 g_free(rmpd->hosts_list[i].hostname);
607 g_strfreev(rmpd->hosts_list[i].aliases);
608 }
609 g_list_free_full(rmpd->user_known_hosts, g_free);
610 g_free(rmpd->hosts_list);
611 g_free(rmpd);
612 mode_set_private_data(sw, NULL);
613 }
614}
615
626static ModeMode ssh_mode_result(Mode *sw, int mretv, char **input,
627 unsigned int selected_line) {
628 ModeMode retv = MODE_EXIT;
630 if ((mretv & MENU_OK) && rmpd->hosts_list[selected_line].hostname != NULL) {
631 exec_ssh(&(rmpd->hosts_list[selected_line]));
632 } else if ((mretv & MENU_CUSTOM_INPUT) && *input != NULL &&
633 *input[0] != '\0') {
634 SshEntry entry = {.hostname = *input, .port = 0};
635 exec_ssh(&entry);
636 } else if ((mretv & MENU_ENTRY_DELETE) &&
637 rmpd->hosts_list[selected_line].hostname) {
638 delete_ssh(rmpd->hosts_list[selected_line].hostname);
639 // Stay
640 retv = RELOAD_DIALOG;
642 ssh_mode_init(sw);
643 } else if (mretv & MENU_CUSTOM_COMMAND) {
644 retv = (mretv & MENU_LOWER_MASK);
645 }
646 return retv;
647}
648
661static char *_get_display_value(const Mode *sw, unsigned int selected_line,
662 G_GNUC_UNUSED int *state,
663 G_GNUC_UNUSED GList **attr_list,
664 int get_entry) {
666 if (get_entry) {
667 GString *str = g_string_new(rmpd->hosts_list[selected_line].hostname);
668
669 if (rmpd->hosts_list[selected_line].aliases) {
670 g_string_append(str, " (");
671 for (int i = 0; rmpd->hosts_list[selected_line].aliases[i]; i++) {
672 g_string_append_printf(str, " %s",
673 rmpd->hosts_list[selected_line].aliases[i]);
674 if (rmpd->hosts_list[selected_line].aliases[i + 1] != NULL) {
675 g_string_append(str, ",");
676 }
677 }
678 g_string_append(str, " )");
679 }
680
681 char *cstr = str->str;
682 g_string_free(str, FALSE);
683 return cstr;
684 }
685
686 return NULL;
687}
688
698static int ssh_token_match(const Mode *sw, rofi_int_matcher **tokens,
699 unsigned int index) {
701 int s = helper_token_match(tokens, rmpd->hosts_list[index].hostname);
702 for (int i = 0;
703 rmpd->hosts_list[index].aliases && rmpd->hosts_list[index].aliases[i];
704 i++) {
705 s |= helper_token_match(tokens, rmpd->hosts_list[index].aliases[i]);
706 }
707 return s;
708}
709#include "mode-private.h"
710Mode ssh_mode = {.name = "ssh",
711 .cfg_name_key = "display-ssh",
712 ._init = ssh_mode_init,
713 ._get_num_entries = ssh_mode_get_num_entries,
714 ._result = ssh_mode_result,
715 ._destroy = ssh_mode_destroy,
716 ._token_match = ssh_token_match,
717 ._get_display_value = _get_display_value,
718 ._get_completion = NULL,
719 ._preprocess_input = NULL,
720 .private_data = NULL,
721 .free = NULL,
722 .type = MODE_TYPE_SWITCHER};
723
gboolean helper_execute(const char *wd, char **args, const char *error_precmd, const char *error_cmd, RofiHelperExecuteContext *context)
Definition helper.c:1424
int helper_parse_setup(char *string, char ***output, int *length,...)
Definition helper.c:102
char * rofi_expand_path(const char *input)
Definition helper.c:784
int helper_token_match(rofi_int_matcher *const *tokens, const char *input)
Definition helper.c:541
void history_set(const char *filename, const char *entry)
Definition history.c:181
void history_remove(const char *filename, const char *entry)
Definition history.c:263
char ** history_get_list(const char *filename, unsigned int *length)
Definition history.c:327
struct rofi_mode Mode
Definition mode.h:49
void * mode_get_private_data(const Mode *mode)
Definition mode.c:176
void mode_set_private_data(Mode *mode, void *pd)
Definition mode.c:181
ModeMode
Definition mode.h:54
@ MENU_CUSTOM_COMMAND
Definition mode.h:84
@ MENU_LOWER_MASK
Definition mode.h:92
@ MENU_ENTRY_DELETE
Definition mode.h:80
@ MENU_OK
Definition mode.h:72
@ MENU_CUSTOM_INPUT
Definition mode.h:78
@ MODE_EXIT
Definition mode.h:56
@ RELOAD_DIALOG
Definition mode.h:60
const char * cache_dir
Definition rofi.c:86
static int ssh_mode_init(Mode *sw)
Definition ssh.c:575
static int ssh_token_match(const Mode *sw, rofi_int_matcher **tokens, unsigned int index)
Definition ssh.c:698
static void add_known_hosts_file(SSHModePrivateData *pd, const char *token)
Definition ssh.c:344
static void exec_ssh(const SshEntry *entry)
Definition ssh.c:127
#define SSH_CACHE_FILE
Definition ssh.c:83
static int execshssh(const SshEntry *entry)
Definition ssh.c:98
static void delete_ssh(const char *host)
Definition ssh.c:155
static SshEntry * read_known_hosts_file(const char *path, SshEntry *retv, unsigned int *length)
Definition ssh.c:173
static SshEntry * read_hosts_file(SshEntry *retv, unsigned int *length)
Definition ssh.c:277
#define SSH_TOKEN_DELIM
Definition ssh.c:89
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:661
static ModeMode ssh_mode_result(Mode *sw, int mretv, char **input, unsigned int selected_line)
Definition ssh.c:626
static SshEntry * get_ssh(SSHModePrivateData *pd, unsigned int *length)
Definition ssh.c:493
struct _SshEntry SshEntry
static void ssh_mode_destroy(Mode *sw)
Definition ssh.c:602
static void parse_ssh_config_file(SSHModePrivateData *pd, const char *filename, SshEntry **retv, unsigned int *length, unsigned int num_favorites)
Definition ssh.c:355
Mode ssh_mode
Definition ssh.c:710
static unsigned int ssh_mode_get_num_entries(const Mode *sw)
Definition ssh.c:591
@ MODE_TYPE_SWITCHER
struct rofi_int_matcher_t rofi_int_matcher
Settings config
SshEntry * hosts_list
Definition ssh.c:75
unsigned int hosts_list_length
Definition ssh.c:77
GList * user_known_hosts
Definition ssh.c:73
unsigned int parse_known_hosts
Definition settings.h:160
unsigned int parse_hosts
Definition settings.h:158
char * ssh_command
Definition settings.h:90
int port
Definition ssh.c:67
char * hostname
Definition ssh.c:64
char ** aliases
Definition ssh.c:65