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 <https://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
  61                           // is read.
  62 } sftpfs_ssh_config_entity_t;
  63 
  64 enum config_var_type
  65 {
  66     STRING,
  67     INTEGER,
  68     BOOLEAN,
  69     FILENAME
  70 };
  71 
  72 /*** forward declarations (file scope functions) *************************************************/
  73 
  74 /*** file scope variables ************************************************************************/
  75 
  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+(.*)$",
  85         NULL,
  86         STRING,
  87         offsetof (sftpfs_ssh_config_entity_t, user),
  88     },
  89     {
  90         "^\\s*HostName\\s+(.*)$",
  91         NULL,
  92         STRING,
  93         offsetof (sftpfs_ssh_config_entity_t, real_host),
  94     },
  95     {
  96         "^\\s*IdentitiesOnly\\s+(.*)$",
  97         NULL,
  98         BOOLEAN,
  99         offsetof (sftpfs_ssh_config_entity_t, identities_only),
 100     },
 101     {
 102         "^\\s*IdentityFile\\s+(.*)$",
 103         NULL,
 104         FILENAME,
 105         offsetof (sftpfs_ssh_config_entity_t, identity_file),
 106     },
 107     {
 108         "^\\s*Port\\s+(.*)$",
 109         NULL,
 110         INTEGER,
 111         offsetof (sftpfs_ssh_config_entity_t, port),
 112     },
 113     {
 114         "^\\s*PasswordAuthentication\\s+(.*)$",
 115         NULL,
 116         BOOLEAN,
 117         offsetof (sftpfs_ssh_config_entity_t, password_auth),
 118     },
 119     {
 120         "^\\s*PubkeyAuthentication\\s+(.*)$",
 121         NULL,
 122         STRING,
 123         offsetof (sftpfs_ssh_config_entity_t, pubkey_auth),
 124     },
 125     { NULL, NULL, 0, 0 },
 126 };
 127 
 128 /* --------------------------------------------------------------------------------------------- */
 129 /*** file scope functions ************************************************************************/
 130 /* --------------------------------------------------------------------------------------------- */
 131 /**
 132  * Free one config entity.
 133  *
 134  * @param config_entity config entity structure
 135  */
 136 
 137 static void
 138 sftpfs_ssh_config_entity_free (sftpfs_ssh_config_entity_t *config_entity)
     /* [previous][next][first][last][top][bottom][index][help]  */
 139 {
 140     g_free (config_entity->real_host);
 141     g_free (config_entity->user);
 142     g_free (config_entity->identity_file);
 143     g_free (config_entity);
 144 }
 145 
 146 /* --------------------------------------------------------------------------------------------- */
 147 /**
 148  * Transform tilda (~) to full home dirname.
 149  *
 150  * @param filename file name with tilda
 151  * @return newly allocated file name with full home dirname
 152  */
 153 
 154 static char *
 155 sftpfs_correct_file_name (const char *filename)
     /* [previous][next][first][last][top][bottom][index][help]  */
 156 {
 157     vfs_path_t *vpath;
 158     char *fn;
 159 
 160     fn = tilde_expand (filename);
 161     vpath = vfs_path_from_str (fn);
 162     g_free (fn);
 163     return vfs_path_free (vpath, FALSE);
 164 }
 165 
 166 /* --------------------------------------------------------------------------------------------- */
 167 /**
 168  * Try to expand remote hostname (%h) token.
 169  *
 170  * @param host hostname string provided by user
 171  * @param real_host real hostname string retrieved from the corresponding HostName directive
 172  * @return newly allocated string with possibly expanded hostname
 173  */
 174 
 175 static char *
 176 sftpsfs_expand_hostname (const char *host, const char *real_host)
     /* [previous][next][first][last][top][bottom][index][help]  */
 177 {
 178     if (g_str_has_prefix (real_host, "%h"))
 179         return g_strconcat (host, real_host + 2, (char *) NULL);
 180 
 181     return g_strdup (real_host);
 182 }
 183 
 184 /* --------------------------------------------------------------------------------------------- */
 185 
 186 #define POINTER_TO_STRUCTURE_MEMBER(type)                                                          \
 187     ((type) ((char *) config_entity + (size_t) config_variables[i].offset))
 188 
 189 /**
 190  * Parse string and filling one config entity by parsed data.
 191  *
 192  * @param config_entity config entity structure
 193  * @param buffer        string for parce
 194  */
 195 
 196 static void
 197 sftpfs_fill_config_entity_from_string (sftpfs_ssh_config_entity_t *config_entity, char *buffer)
     /* [previous][next][first][last][top][bottom][index][help]  */
 198 {
 199     int i;
 200 
 201     for (i = 0; config_variables[i].pattern != NULL; i++)
 202     {
 203         if (mc_search_run (config_variables[i].pattern_regexp, buffer, 0, strlen (buffer), NULL))
 204         {
 205             int value_offset;
 206             char *value;
 207 
 208             int *pointer_int;
 209             char **pointer_str;
 210             gboolean *pointer_bool;
 211 
 212             // Calculate start of value in string
 213             value_offset = mc_search_getstart_result_by_num (config_variables[i].pattern_regexp, 1);
 214             value = &buffer[value_offset];
 215 
 216             switch (config_variables[i].type)
 217             {
 218             case STRING:
 219                 pointer_str = POINTER_TO_STRUCTURE_MEMBER (char **);
 220                 *pointer_str = g_strdup (value);
 221                 break;
 222             case FILENAME:
 223                 pointer_str = POINTER_TO_STRUCTURE_MEMBER (char **);
 224                 *pointer_str = sftpfs_correct_file_name (value);
 225                 break;
 226             case INTEGER:
 227                 pointer_int = POINTER_TO_STRUCTURE_MEMBER (int *);
 228                 *pointer_int = atoi (value);
 229                 break;
 230             case BOOLEAN:
 231                 pointer_bool = POINTER_TO_STRUCTURE_MEMBER (gboolean *);
 232                 *pointer_bool = strcasecmp (value, "True") == 0;
 233                 break;
 234             default:
 235                 continue;
 236             }
 237             return;
 238         }
 239     }
 240 }
 241 
 242 #undef POINTER_TO_STRUCTURE_MEMBER
 243 
 244 /* --------------------------------------------------------------------------------------------- */
 245 /**
 246  * Fill one config entity from config file.
 247  *
 248  * @param ssh_config_handler file descriptor for the ssh config file
 249  * @param config_entity      config entity structure
 250  * @param vpath_element      path element with host data (hostname, port)
 251  * @param mcerror            pointer to the error handler
 252  * @return TRUE if config entity was filled successfully, FALSE otherwise
 253  */
 254 
 255 static gboolean
 256 sftpfs_fill_config_entity_from_config (FILE *ssh_config_handler,
     /* [previous][next][first][last][top][bottom][index][help]  */
 257                                        sftpfs_ssh_config_entity_t *config_entity,
 258                                        const vfs_path_element_t *vpath_element, GError **mcerror)
 259 {
 260     char buffer[BUF_MEDIUM];
 261     gboolean host_block_hit = FALSE;
 262     gboolean pattern_block_hit = FALSE;
 263     mc_search_t *host_regexp;
 264     gboolean ok = TRUE;
 265 
 266     mc_return_val_if_error (mcerror, FALSE);
 267 
 268     host_regexp = mc_search_new ("^\\s*host\\s+(.*)$", DEFAULT_CHARSET);
 269     host_regexp->search_type = MC_SEARCH_T_REGEX;
 270     host_regexp->is_case_sensitive = FALSE;
 271 
 272     while (TRUE)
 273     {
 274         char *cr;
 275 
 276         if (fgets (buffer, sizeof (buffer), ssh_config_handler) == NULL)
 277         {
 278             int e;
 279 
 280             e = errno;
 281 
 282             if (!feof (ssh_config_handler))
 283             {
 284                 mc_propagate_error (mcerror, e, _ ("sftp: an error occurred while reading %s: %s"),
 285                                     SFTPFS_SSH_CONFIG, strerror (e));
 286                 ok = FALSE;
 287                 goto done;
 288             }
 289 
 290             break;
 291         }
 292 
 293         cr = strrchr (buffer, '\n');
 294         if (cr != NULL)
 295             *cr = '\0';
 296 
 297         if (mc_search_run (host_regexp, buffer, 0, strlen (buffer), NULL))
 298         {
 299             const char *host_pattern;
 300             int host_pattern_offset;
 301 
 302             // if previous host block exactly describe our connection
 303             if (host_block_hit)
 304                 goto done;
 305 
 306             host_pattern_offset = mc_search_getstart_result_by_num (host_regexp, 1);
 307             host_pattern = &buffer[host_pattern_offset];
 308             if (strcmp (host_pattern, vpath_element->host) == 0)
 309             {
 310                 // current host block describe our connection
 311                 host_block_hit = TRUE;
 312             }
 313             else
 314             {
 315                 mc_search_t *pattern_regexp;
 316 
 317                 pattern_regexp = mc_search_new (host_pattern, DEFAULT_CHARSET);
 318                 pattern_regexp->search_type = MC_SEARCH_T_GLOB;
 319                 pattern_regexp->is_case_sensitive = FALSE;
 320                 pattern_regexp->is_entire_line = TRUE;
 321                 pattern_block_hit = mc_search_run (pattern_regexp, vpath_element->host, 0,
 322                                                    strlen (vpath_element->host), NULL);
 323                 mc_search_free (pattern_regexp);
 324             }
 325         }
 326         else if (pattern_block_hit || host_block_hit)
 327         {
 328             sftpfs_fill_config_entity_from_string (config_entity, buffer);
 329         }
 330     }
 331 
 332 done:
 333     mc_search_free (host_regexp);
 334     return ok;
 335 }
 336 
 337 /* --------------------------------------------------------------------------------------------- */
 338 /**
 339  * Open the ssh config file and fill config entity.
 340  *
 341  * @param vpath_element path element with host data (hostname, port)
 342  * @param mcerror       pointer to the error handler
 343  * @return newly allocated config entity structure
 344  */
 345 
 346 static sftpfs_ssh_config_entity_t *
 347 sftpfs_get_config_entity (const vfs_path_element_t *vpath_element, GError **mcerror)
     /* [previous][next][first][last][top][bottom][index][help]  */
 348 {
 349     sftpfs_ssh_config_entity_t *config_entity;
 350     FILE *ssh_config_handler;
 351     char *config_filename;
 352 
 353     mc_return_val_if_error (mcerror, FALSE);
 354 
 355     config_entity = g_new0 (sftpfs_ssh_config_entity_t, 1);
 356     config_entity->password_auth = TRUE;
 357     config_entity->identities_only = FALSE;
 358     config_entity->pubkey_auth = TRUE;
 359     config_entity->port = SFTP_DEFAULT_PORT;
 360 
 361     config_filename = sftpfs_correct_file_name (SFTPFS_SSH_CONFIG);
 362     ssh_config_handler = fopen (config_filename, "r");
 363     g_free (config_filename);
 364 
 365     if (ssh_config_handler != NULL)
 366     {
 367         gboolean ok;
 368 
 369         ok = sftpfs_fill_config_entity_from_config (ssh_config_handler, config_entity,
 370                                                     vpath_element, mcerror);
 371         fclose (ssh_config_handler);
 372 
 373         if (!ok)
 374         {
 375             sftpfs_ssh_config_entity_free (config_entity);
 376             return NULL;
 377         }
 378     }
 379 
 380     if (config_entity->user == NULL)
 381     {
 382         config_entity->user = vfs_get_local_username ();
 383         if (config_entity->user == NULL)
 384         {
 385             sftpfs_ssh_config_entity_free (config_entity);
 386             config_entity = NULL;
 387             mc_propagate_error (mcerror, EPERM, "%s", _ ("sftp: Unable to get current user name."));
 388         }
 389     }
 390     return config_entity;
 391 }
 392 
 393 /* --------------------------------------------------------------------------------------------- */
 394 /*** public functions ****************************************************************************/
 395 /* --------------------------------------------------------------------------------------------- */
 396 /**
 397  * Reads data from the ssh config file related to connection.
 398  *
 399  * @param super connection data
 400  * @param error pointer to the error handler
 401  */
 402 
 403 void
 404 sftpfs_fill_connection_data_from_config (struct vfs_s_super *super, GError **mcerror)
     /* [previous][next][first][last][top][bottom][index][help]  */
 405 {
 406     sftpfs_super_t *sftpfs_super = SFTP_SUPER (super);
 407     sftpfs_ssh_config_entity_t *config_entity;
 408 
 409     mc_return_if_error (mcerror);
 410 
 411     config_entity = sftpfs_get_config_entity (super->path_element, mcerror);
 412     if (config_entity == NULL)
 413         return;
 414 
 415     sftpfs_super->config_auth_type = (config_entity->pubkey_auth) ? PUBKEY : 0;
 416     sftpfs_super->config_auth_type |= (config_entity->identities_only) ? 0 : AGENT;
 417     sftpfs_super->config_auth_type |= (config_entity->password_auth) ? PASSWORD : 0;
 418 
 419     if (super->path_element->port == 0)
 420         super->path_element->port = config_entity->port;
 421 
 422     if (super->path_element->user == NULL)
 423         super->path_element->user = g_strdup (config_entity->user);
 424 
 425     if (config_entity->real_host != NULL)
 426     {
 427         char *tmp_str = super->path_element->host;
 428 
 429         super->path_element->host =
 430             sftpsfs_expand_hostname (super->path_element->host, config_entity->real_host);
 431 
 432         g_free (tmp_str);
 433     }
 434 
 435     if (config_entity->identity_file != NULL)
 436     {
 437         sftpfs_super->privkey = g_strdup (config_entity->identity_file);
 438         sftpfs_super->pubkey = g_strdup_printf ("%s.pub", config_entity->identity_file);
 439     }
 440 
 441     sftpfs_ssh_config_entity_free (config_entity);
 442 }
 443 
 444 /* --------------------------------------------------------------------------------------------- */
 445 /**
 446  * Initialize the SSH config parser.
 447  */
 448 
 449 void
 450 sftpfs_init_config_variables_patterns (void)
     /* [previous][next][first][last][top][bottom][index][help]  */
 451 {
 452     int i;
 453 
 454     for (i = 0; config_variables[i].pattern != NULL; i++)
 455     {
 456         config_variables[i].pattern_regexp =
 457             mc_search_new (config_variables[i].pattern, DEFAULT_CHARSET);
 458         config_variables[i].pattern_regexp->search_type = MC_SEARCH_T_REGEX;
 459         config_variables[i].pattern_regexp->is_case_sensitive = FALSE;
 460     }
 461 }
 462 
 463 /* --------------------------------------------------------------------------------------------- */
 464 /**
 465  * Deinitialize the SSH config parser.
 466  */
 467 
 468 void
 469 sftpfs_deinit_config_variables_patterns (void)
     /* [previous][next][first][last][top][bottom][index][help]  */
 470 {
 471     int i;
 472 
 473     for (i = 0; config_variables[i].pattern != NULL; i++)
 474     {
 475         mc_search_free (config_variables[i].pattern_regexp);
 476         config_variables[i].pattern_regexp = NULL;
 477     }
 478 }
 479 
 480 /* --------------------------------------------------------------------------------------------- */

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