root/src/vfs/sftpfs/config_parser.c

/* [previous][next][first][last][top][bottom][index][help]  */

DEFINITIONS

This source file includes following definitions.
  1. sftpfs_ssh_config_entity_free
  2. sftpfs_correct_file_name
  3. sftpsfs_expand_hostname
  4. sftpfs_fill_config_entity_from_string
  5. sftpfs_fill_config_entity_from_config
  6. sftpfs_get_config_entity
  7. sftpfs_fill_connection_data_from_config
  8. sftpfs_init_config_variables_patterns
  9. sftpfs_deinit_config_variables_patterns

   1 /* Virtual File System: SFTP file system.
   2    The SSH config parser
   3 
   4    Copyright (C) 2011-2025
   5    Free Software Foundation, Inc.
   6 
   7    Written by:
   8    Ilia Maslakov <il.smind@gmail.com>, 2011
   9    Slava Zanko <slavazanko@gmail.com>, 2011, 2012, 2013
  10 
  11    This file is part of the Midnight Commander.
  12 
  13    The Midnight Commander is free software: you can redistribute it
  14    and/or modify it under the terms of the GNU General Public License as
  15    published by the Free Software Foundation, either version 3 of the License,
  16    or (at your option) any later version.
  17 
  18    The Midnight Commander is distributed in the hope that it will be useful,
  19    but WITHOUT ANY WARRANTY; without even the implied warranty of
  20    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  21    GNU General Public License for more details.
  22 
  23    You should have received a copy of the GNU General Public License
  24    along with this program.  If not, see <http://www.gnu.org/licenses/>.
  25  */
  26 
  27 #include <config.h>
  28 #include <errno.h>
  29 #include <stddef.h>
  30 #include <stdlib.h>             /* atoi() */
  31 
  32 #include "lib/global.h"
  33 
  34 #include "lib/search.h"
  35 #include "lib/util.h"           /* tilde_expand() */
  36 #include "lib/vfs/utilvfs.h"
  37 
  38 #include "internal.h"
  39 
  40 /*** global variables ****************************************************************************/
  41 
  42 /*** file scope macro definitions ****************************************************************/
  43 
  44 #define SFTP_DEFAULT_PORT 22
  45 
  46 #ifndef SFTPFS_SSH_CONFIG
  47 #define SFTPFS_SSH_CONFIG "~/.ssh/config"
  48 #endif
  49 
  50 /*** file scope type declarations ****************************************************************/
  51 
  52 typedef struct
  53 {
  54     char *real_host;            /* host DNS name or ip address */
  55     int port;                   /* port for connect to host */
  56     char *user;                 /* the user to log in as */
  57     gboolean password_auth;     /* FALSE - no passwords allowed (default TRUE) */
  58     gboolean identities_only;   /* TRUE - no ssh agent (default FALSE) */
  59     gboolean pubkey_auth;       /* FALSE - disable public key authentication (default TRUE) */
  60     char *identity_file;        /* A file from which the user's DSA, ECDSA or DSA authentication identity is read. */
  61 } sftpfs_ssh_config_entity_t;
  62 
  63 enum config_var_type
  64 {
  65     STRING,
  66     INTEGER,
  67     BOOLEAN,
  68     FILENAME
  69 };
  70 
  71 /*** forward declarations (file scope functions) *************************************************/
  72 
  73 /*** file scope variables ************************************************************************/
  74 
  75 /* *INDENT-OFF* */
  76 static struct
  77 {
  78     const char *pattern;
  79     mc_search_t *pattern_regexp;
  80     enum config_var_type type;
  81     size_t offset;
  82 } config_variables[] =
  83 {
  84     {"^\\s*User\\s+(.*)$", NULL, STRING, offsetof (sftpfs_ssh_config_entity_t, user)},
  85     {"^\\s*HostName\\s+(.*)$", NULL, STRING, offsetof (sftpfs_ssh_config_entity_t, real_host)},
  86     {"^\\s*IdentitiesOnly\\s+(.*)$", NULL, BOOLEAN, offsetof (sftpfs_ssh_config_entity_t, identities_only)},
  87     {"^\\s*IdentityFile\\s+(.*)$", NULL, FILENAME, offsetof (sftpfs_ssh_config_entity_t, identity_file)},
  88     {"^\\s*Port\\s+(.*)$", NULL, INTEGER, offsetof (sftpfs_ssh_config_entity_t, port)},
  89     {"^\\s*PasswordAuthentication\\s+(.*)$", NULL, BOOLEAN, offsetof (sftpfs_ssh_config_entity_t, password_auth)},
  90     {"^\\s*PubkeyAuthentication\\s+(.*)$", NULL, STRING, offsetof (sftpfs_ssh_config_entity_t, pubkey_auth)},
  91     {NULL, NULL, 0, 0}
  92 };
  93 /* *INDENT-ON* */
  94 
  95 /* --------------------------------------------------------------------------------------------- */
  96 /*** file scope functions ************************************************************************/
  97 /* --------------------------------------------------------------------------------------------- */
  98 /**
  99  * Free one config entity.
 100  *
 101  * @param config_entity config entity structure
 102  */
 103 
 104 static void
 105 sftpfs_ssh_config_entity_free (sftpfs_ssh_config_entity_t *config_entity)
     /* [previous][next][first][last][top][bottom][index][help]  */
 106 {
 107     g_free (config_entity->real_host);
 108     g_free (config_entity->user);
 109     g_free (config_entity->identity_file);
 110     g_free (config_entity);
 111 }
 112 
 113 /* --------------------------------------------------------------------------------------------- */
 114 /**
 115  * Transform tilda (~) to full home dirname.
 116  *
 117  * @param filename file name with tilda
 118  * @return newly allocated file name with full home dirname
 119  */
 120 
 121 static char *
 122 sftpfs_correct_file_name (const char *filename)
     /* [previous][next][first][last][top][bottom][index][help]  */
 123 {
 124     vfs_path_t *vpath;
 125     char *fn;
 126 
 127     fn = tilde_expand (filename);
 128     vpath = vfs_path_from_str (fn);
 129     g_free (fn);
 130     return vfs_path_free (vpath, FALSE);
 131 }
 132 
 133 /* --------------------------------------------------------------------------------------------- */
 134 /**
 135  * Try to expand remote hostname (%h) token.
 136  *
 137  * @param host hostname string provided by user
 138  * @param real_host real hostname string retrieved from the corresponding HostName directive
 139  * @return newly allocated string with possibly expanded hostname
 140  */
 141 
 142 static char *
 143 sftpsfs_expand_hostname (const char *host, const char *real_host)
     /* [previous][next][first][last][top][bottom][index][help]  */
 144 {
 145     if (g_str_has_prefix (real_host, "%h"))
 146         return g_strconcat (host, real_host + 2, (char *) NULL);
 147 
 148     return g_strdup (real_host);
 149 }
 150 
 151 /* --------------------------------------------------------------------------------------------- */
 152 
 153 #define POINTER_TO_STRUCTURE_MEMBER(type)  \
 154     ((type) ((char *) config_entity + (size_t) config_variables[i].offset))
 155 
 156 /**
 157  * Parse string and filling one config entity by parsed data.
 158  *
 159  * @param config_entity config entity structure
 160  * @param buffer        string for parce
 161  */
 162 
 163 static void
 164 sftpfs_fill_config_entity_from_string (sftpfs_ssh_config_entity_t *config_entity, char *buffer)
     /* [previous][next][first][last][top][bottom][index][help]  */
 165 {
 166     int i;
 167 
 168     for (i = 0; config_variables[i].pattern != NULL; i++)
 169     {
 170         if (mc_search_run (config_variables[i].pattern_regexp, buffer, 0, strlen (buffer), NULL))
 171         {
 172             int value_offset;
 173             char *value;
 174 
 175             int *pointer_int;
 176             char **pointer_str;
 177             gboolean *pointer_bool;
 178 
 179             /* Calculate start of value in string */
 180             value_offset = mc_search_getstart_result_by_num (config_variables[i].pattern_regexp, 1);
 181             value = &buffer[value_offset];
 182 
 183             switch (config_variables[i].type)
 184             {
 185             case STRING:
 186                 pointer_str = POINTER_TO_STRUCTURE_MEMBER (char **);
 187                 *pointer_str = g_strdup (value);
 188                 break;
 189             case FILENAME:
 190                 pointer_str = POINTER_TO_STRUCTURE_MEMBER (char **);
 191                 *pointer_str = sftpfs_correct_file_name (value);
 192                 break;
 193             case INTEGER:
 194                 pointer_int = POINTER_TO_STRUCTURE_MEMBER (int *);
 195                 *pointer_int = atoi (value);
 196                 break;
 197             case BOOLEAN:
 198                 pointer_bool = POINTER_TO_STRUCTURE_MEMBER (gboolean *);
 199                 *pointer_bool = strcasecmp (value, "True") == 0;
 200                 break;
 201             default:
 202                 continue;
 203             }
 204             return;
 205         }
 206     }
 207 }
 208 
 209 #undef POINTER_TO_STRUCTURE_MEMBER
 210 
 211 /* --------------------------------------------------------------------------------------------- */
 212 /**
 213  * Fill one config entity from config file.
 214  *
 215  * @param ssh_config_handler file descriptor for the ssh config file
 216  * @param config_entity      config entity structure
 217  * @param vpath_element      path element with host data (hostname, port)
 218  * @param mcerror            pointer to the error handler
 219  * @return TRUE if config entity was filled successfully, FALSE otherwise
 220  */
 221 
 222 static gboolean
 223 sftpfs_fill_config_entity_from_config (FILE *ssh_config_handler,
     /* [previous][next][first][last][top][bottom][index][help]  */
 224                                        sftpfs_ssh_config_entity_t *config_entity,
 225                                        const vfs_path_element_t *vpath_element, GError **mcerror)
 226 {
 227     char buffer[BUF_MEDIUM];
 228     gboolean host_block_hit = FALSE;
 229     gboolean pattern_block_hit = FALSE;
 230     mc_search_t *host_regexp;
 231     gboolean ok = TRUE;
 232 
 233     mc_return_val_if_error (mcerror, FALSE);
 234 
 235     host_regexp = mc_search_new ("^\\s*host\\s+(.*)$", DEFAULT_CHARSET);
 236     host_regexp->search_type = MC_SEARCH_T_REGEX;
 237     host_regexp->is_case_sensitive = FALSE;
 238 
 239     while (TRUE)
 240     {
 241         char *cr;
 242 
 243         if (fgets (buffer, sizeof (buffer), ssh_config_handler) == NULL)
 244         {
 245             int e;
 246 
 247             e = errno;
 248 
 249             if (!feof (ssh_config_handler))
 250             {
 251                 mc_propagate_error (mcerror, e,
 252                                     _("sftp: an error occurred while reading %s: %s"),
 253                                     SFTPFS_SSH_CONFIG, strerror (e));
 254                 ok = FALSE;
 255                 goto done;
 256             }
 257 
 258             break;
 259         }
 260 
 261         cr = strrchr (buffer, '\n');
 262         if (cr != NULL)
 263             *cr = '\0';
 264 
 265         if (mc_search_run (host_regexp, buffer, 0, strlen (buffer), NULL))
 266         {
 267             const char *host_pattern;
 268             int host_pattern_offset;
 269 
 270             /* if previous host block exactly describe our connection */
 271             if (host_block_hit)
 272                 goto done;
 273 
 274             host_pattern_offset = mc_search_getstart_result_by_num (host_regexp, 1);
 275             host_pattern = &buffer[host_pattern_offset];
 276             if (strcmp (host_pattern, vpath_element->host) == 0)
 277             {
 278                 /* current host block describe our connection */
 279                 host_block_hit = TRUE;
 280             }
 281             else
 282             {
 283                 mc_search_t *pattern_regexp;
 284 
 285                 pattern_regexp = mc_search_new (host_pattern, DEFAULT_CHARSET);
 286                 pattern_regexp->search_type = MC_SEARCH_T_GLOB;
 287                 pattern_regexp->is_case_sensitive = FALSE;
 288                 pattern_regexp->is_entire_line = TRUE;
 289                 pattern_block_hit =
 290                     mc_search_run (pattern_regexp, vpath_element->host, 0,
 291                                    strlen (vpath_element->host), NULL);
 292                 mc_search_free (pattern_regexp);
 293             }
 294         }
 295         else if (pattern_block_hit || host_block_hit)
 296         {
 297             sftpfs_fill_config_entity_from_string (config_entity, buffer);
 298         }
 299     }
 300 
 301   done:
 302     mc_search_free (host_regexp);
 303     return ok;
 304 }
 305 
 306 /* --------------------------------------------------------------------------------------------- */
 307 /**
 308  * Open the ssh config file and fill config entity.
 309  *
 310  * @param vpath_element path element with host data (hostname, port)
 311  * @param mcerror       pointer to the error handler
 312  * @return newly allocated config entity structure
 313  */
 314 
 315 static sftpfs_ssh_config_entity_t *
 316 sftpfs_get_config_entity (const vfs_path_element_t *vpath_element, GError **mcerror)
     /* [previous][next][first][last][top][bottom][index][help]  */
 317 {
 318     sftpfs_ssh_config_entity_t *config_entity;
 319     FILE *ssh_config_handler;
 320     char *config_filename;
 321 
 322     mc_return_val_if_error (mcerror, FALSE);
 323 
 324     config_entity = g_new0 (sftpfs_ssh_config_entity_t, 1);
 325     config_entity->password_auth = TRUE;
 326     config_entity->identities_only = FALSE;
 327     config_entity->pubkey_auth = TRUE;
 328     config_entity->port = SFTP_DEFAULT_PORT;
 329 
 330     config_filename = sftpfs_correct_file_name (SFTPFS_SSH_CONFIG);
 331     ssh_config_handler = fopen (config_filename, "r");
 332     g_free (config_filename);
 333 
 334     if (ssh_config_handler != NULL)
 335     {
 336         gboolean ok;
 337 
 338         ok = sftpfs_fill_config_entity_from_config
 339             (ssh_config_handler, config_entity, vpath_element, mcerror);
 340         fclose (ssh_config_handler);
 341 
 342         if (!ok)
 343         {
 344             sftpfs_ssh_config_entity_free (config_entity);
 345             return NULL;
 346         }
 347     }
 348 
 349     if (config_entity->user == NULL)
 350     {
 351         config_entity->user = vfs_get_local_username ();
 352         if (config_entity->user == NULL)
 353         {
 354             sftpfs_ssh_config_entity_free (config_entity);
 355             config_entity = NULL;
 356             mc_propagate_error (mcerror, EPERM, "%s", _("sftp: Unable to get current user name."));
 357         }
 358     }
 359     return config_entity;
 360 }
 361 
 362 /* --------------------------------------------------------------------------------------------- */
 363 /*** public functions ****************************************************************************/
 364 /* --------------------------------------------------------------------------------------------- */
 365 /**
 366  * Reads data from the ssh config file related to connection.
 367  *
 368  * @param super connection data
 369  * @param error pointer to the error handler
 370  */
 371 
 372 void
 373 sftpfs_fill_connection_data_from_config (struct vfs_s_super *super, GError **mcerror)
     /* [previous][next][first][last][top][bottom][index][help]  */
 374 {
 375     sftpfs_super_t *sftpfs_super = SFTP_SUPER (super);
 376     sftpfs_ssh_config_entity_t *config_entity;
 377 
 378     mc_return_if_error (mcerror);
 379 
 380     config_entity = sftpfs_get_config_entity (super->path_element, mcerror);
 381     if (config_entity == NULL)
 382         return;
 383 
 384     sftpfs_super->config_auth_type = (config_entity->pubkey_auth) ? PUBKEY : 0;
 385     sftpfs_super->config_auth_type |= (config_entity->identities_only) ? 0 : AGENT;
 386     sftpfs_super->config_auth_type |= (config_entity->password_auth) ? PASSWORD : 0;
 387 
 388     if (super->path_element->port == 0)
 389         super->path_element->port = config_entity->port;
 390 
 391     if (super->path_element->user == NULL)
 392         super->path_element->user = g_strdup (config_entity->user);
 393 
 394     if (config_entity->real_host != NULL)
 395     {
 396         char *tmp_str = super->path_element->host;
 397 
 398         super->path_element->host =
 399             sftpsfs_expand_hostname (super->path_element->host, config_entity->real_host);
 400 
 401         g_free (tmp_str);
 402     }
 403 
 404     if (config_entity->identity_file != NULL)
 405     {
 406         sftpfs_super->privkey = g_strdup (config_entity->identity_file);
 407         sftpfs_super->pubkey = g_strdup_printf ("%s.pub", config_entity->identity_file);
 408     }
 409 
 410     sftpfs_ssh_config_entity_free (config_entity);
 411 }
 412 
 413 /* --------------------------------------------------------------------------------------------- */
 414 /**
 415  * Initialize the SSH config parser.
 416  */
 417 
 418 void
 419 sftpfs_init_config_variables_patterns (void)
     /* [previous][next][first][last][top][bottom][index][help]  */
 420 {
 421     int i;
 422 
 423     for (i = 0; config_variables[i].pattern != NULL; i++)
 424     {
 425         config_variables[i].pattern_regexp =
 426             mc_search_new (config_variables[i].pattern, DEFAULT_CHARSET);
 427         config_variables[i].pattern_regexp->search_type = MC_SEARCH_T_REGEX;
 428         config_variables[i].pattern_regexp->is_case_sensitive = FALSE;
 429     }
 430 }
 431 
 432 /* --------------------------------------------------------------------------------------------- */
 433 /**
 434  * Deinitialize the SSH config parser.
 435  */
 436 
 437 void
 438 sftpfs_deinit_config_variables_patterns (void)
     /* [previous][next][first][last][top][bottom][index][help]  */
 439 {
 440     int i;
 441 
 442     for (i = 0; config_variables[i].pattern != NULL; i++)
 443     {
 444         mc_search_free (config_variables[i].pattern_regexp);
 445         config_variables[i].pattern_regexp = NULL;
 446     }
 447 }
 448 
 449 /* --------------------------------------------------------------------------------------------- */

/* [previous][next][first][last][top][bottom][index][help]  */