Coverage Report

Created: 2025-07-04 06:59

/src/libssh/src/config.c
Line
Count
Source (jump to first uncovered line)
1
/*
2
 * config.c - parse the ssh config file
3
 *
4
 * This file is part of the SSH Library
5
 *
6
 * Copyright (c) 2009-2013    by Andreas Schneider <asn@cryptomilk.org>
7
 *
8
 * The SSH Library is free software; you can redistribute it and/or modify
9
 * it under the terms of the GNU Lesser General Public License as published by
10
 * the Free Software Foundation; either version 2.1 of the License, or (at your
11
 * option) any later version.
12
 *
13
 * The SSH Library is distributed in the hope that it will be useful, but
14
 * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
15
 * or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public
16
 * License for more details.
17
 *
18
 * You should have received a copy of the GNU Lesser General Public License
19
 * along with the SSH Library; see the file COPYING.  If not, write to
20
 * the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston,
21
 * MA 02111-1307, USA.
22
 */
23
24
#include "config.h"
25
26
#include <ctype.h>
27
#include <stdio.h>
28
#include <string.h>
29
#include <stdlib.h>
30
#ifdef HAVE_GLOB_H
31
# include <glob.h>
32
#endif
33
#include <stdbool.h>
34
#include <limits.h>
35
#ifndef _WIN32
36
# include <sys/types.h>
37
# include <sys/stat.h>
38
# include <fcntl.h>
39
# include <errno.h>
40
# include <signal.h>
41
# include <sys/wait.h>
42
# include <net/if.h>
43
# include <netinet/in.h>
44
#endif
45
#ifdef HAVE_IFADDRS_H
46
#include <ifaddrs.h>
47
#endif
48
49
#include "libssh/config_parser.h"
50
#include "libssh/config.h"
51
#include "libssh/priv.h"
52
#include "libssh/session.h"
53
#include "libssh/misc.h"
54
#include "libssh/options.h"
55
56
#ifndef MAX_LINE_SIZE
57
25.5k
#define MAX_LINE_SIZE 1024
58
#endif
59
60
struct ssh_config_keyword_table_s {
61
  const char *name;
62
  enum ssh_config_opcode_e opcode;
63
};
64
65
static struct ssh_config_keyword_table_s ssh_config_keyword_table[] = {
66
  { "host", SOC_HOST },
67
  { "match", SOC_MATCH },
68
  { "hostname", SOC_HOSTNAME },
69
  { "port", SOC_PORT },
70
  { "user", SOC_USERNAME },
71
  { "identityfile", SOC_IDENTITY },
72
  { "ciphers", SOC_CIPHERS },
73
  { "macs", SOC_MACS },
74
  { "compression", SOC_COMPRESSION },
75
  { "connecttimeout", SOC_TIMEOUT },
76
  { "stricthostkeychecking", SOC_STRICTHOSTKEYCHECK },
77
  { "userknownhostsfile", SOC_KNOWNHOSTS },
78
  { "proxycommand", SOC_PROXYCOMMAND },
79
  { "gssapiserveridentity", SOC_GSSAPISERVERIDENTITY },
80
  { "gssapiclientidentity", SOC_GSSAPICLIENTIDENTITY },
81
  { "gssapidelegatecredentials", SOC_GSSAPIDELEGATECREDENTIALS },
82
  { "include", SOC_INCLUDE },
83
  { "bindaddress", SOC_BINDADDRESS},
84
  { "globalknownhostsfile", SOC_GLOBALKNOWNHOSTSFILE},
85
  { "loglevel", SOC_LOGLEVEL},
86
  { "hostkeyalgorithms", SOC_HOSTKEYALGORITHMS},
87
  { "kexalgorithms", SOC_KEXALGORITHMS},
88
  { "gssapiauthentication", SOC_GSSAPIAUTHENTICATION},
89
  { "kbdinteractiveauthentication", SOC_KBDINTERACTIVEAUTHENTICATION},
90
  { "passwordauthentication", SOC_PASSWORDAUTHENTICATION},
91
  { "pubkeyauthentication", SOC_PUBKEYAUTHENTICATION},
92
  { "addkeystoagent", SOC_UNSUPPORTED},
93
  { "addressfamily", SOC_UNSUPPORTED},
94
  { "batchmode", SOC_UNSUPPORTED},
95
  { "canonicaldomains", SOC_UNSUPPORTED},
96
  { "canonicalizefallbacklocal", SOC_UNSUPPORTED},
97
  { "canonicalizehostname", SOC_UNSUPPORTED},
98
  { "canonicalizemaxdots", SOC_UNSUPPORTED},
99
  { "canonicalizepermittedcnames", SOC_UNSUPPORTED},
100
  { "certificatefile", SOC_CERTIFICATE},
101
  { "kbdinteractiveauthentication", SOC_UNSUPPORTED},
102
  { "checkhostip", SOC_UNSUPPORTED},
103
  { "connectionattempts", SOC_UNSUPPORTED},
104
  { "enablesshkeysign", SOC_UNSUPPORTED},
105
  { "fingerprinthash", SOC_UNSUPPORTED},
106
  { "forwardagent", SOC_UNSUPPORTED},
107
  { "hashknownhosts", SOC_UNSUPPORTED},
108
  { "hostbasedauthentication", SOC_UNSUPPORTED},
109
  { "hostbasedacceptedalgorithms", SOC_UNSUPPORTED},
110
  { "hostkeyalias", SOC_UNSUPPORTED},
111
  { "identitiesonly", SOC_IDENTITIESONLY},
112
  { "identityagent", SOC_IDENTITYAGENT},
113
  { "ipqos", SOC_UNSUPPORTED},
114
  { "kbdinteractivedevices", SOC_UNSUPPORTED},
115
  { "nohostauthenticationforlocalhost", SOC_UNSUPPORTED},
116
  { "numberofpasswordprompts", SOC_UNSUPPORTED},
117
  { "pkcs11provider", SOC_UNSUPPORTED},
118
  { "preferredauthentications", SOC_UNSUPPORTED},
119
  { "proxyjump", SOC_PROXYJUMP},
120
  { "proxyusefdpass", SOC_UNSUPPORTED},
121
  { "pubkeyacceptedalgorithms", SOC_PUBKEYACCEPTEDKEYTYPES},
122
  { "rekeylimit", SOC_REKEYLIMIT},
123
  { "remotecommand", SOC_UNSUPPORTED},
124
  { "revokedhostkeys", SOC_UNSUPPORTED},
125
  { "serveralivecountmax", SOC_UNSUPPORTED},
126
  { "serveraliveinterval", SOC_UNSUPPORTED},
127
  { "streamlocalbindmask", SOC_UNSUPPORTED},
128
  { "streamlocalbindunlink", SOC_UNSUPPORTED},
129
  { "syslogfacility", SOC_UNSUPPORTED},
130
  { "tcpkeepalive", SOC_UNSUPPORTED},
131
  { "updatehostkeys", SOC_UNSUPPORTED},
132
  { "verifyhostkeydns", SOC_UNSUPPORTED},
133
  { "visualhostkey", SOC_UNSUPPORTED},
134
  { "clearallforwardings", SOC_NA},
135
  { "controlmaster", SOC_NA},
136
  { "controlpersist", SOC_NA},
137
  { "controlpath", SOC_NA},
138
  { "dynamicforward", SOC_NA},
139
  { "escapechar", SOC_NA},
140
  { "exitonforwardfailure", SOC_NA},
141
  { "forwardx11", SOC_NA},
142
  { "forwardx11timeout", SOC_NA},
143
  { "forwardx11trusted", SOC_NA},
144
  { "gatewayports", SOC_NA},
145
  { "ignoreunknown", SOC_NA},
146
  { "localcommand", SOC_NA},
147
  { "localforward", SOC_NA},
148
  { "permitlocalcommand", SOC_NA},
149
  { "remoteforward", SOC_NA},
150
  { "requesttty", SOC_NA},
151
  { "sendenv", SOC_NA},
152
  { "tunnel", SOC_NA},
153
  { "tunneldevice", SOC_NA},
154
  { "xauthlocation", SOC_NA},
155
  { "pubkeyacceptedkeytypes", SOC_PUBKEYACCEPTEDKEYTYPES},
156
  { NULL, SOC_UNKNOWN }
157
};
158
159
enum ssh_config_match_e {
160
    MATCH_UNKNOWN = -1,
161
    MATCH_ALL,
162
    MATCH_FINAL,
163
    MATCH_CANONICAL,
164
    MATCH_EXEC,
165
    MATCH_HOST,
166
    MATCH_ORIGINALHOST,
167
    MATCH_USER,
168
    MATCH_LOCALUSER,
169
    MATCH_LOCALNETWORK
170
};
171
172
struct ssh_config_match_keyword_table_s {
173
    const char *name;
174
    enum ssh_config_match_e opcode;
175
};
176
177
static struct ssh_config_match_keyword_table_s
178
    ssh_config_match_keyword_table[] = {
179
        {"all", MATCH_ALL},
180
        {"canonical", MATCH_CANONICAL},
181
        {"final", MATCH_FINAL},
182
        {"exec", MATCH_EXEC},
183
        {"host", MATCH_HOST},
184
        {"originalhost", MATCH_ORIGINALHOST},
185
        {"user", MATCH_USER},
186
        {"localuser", MATCH_LOCALUSER},
187
        {"localnetwork", MATCH_LOCALNETWORK},
188
        {NULL, MATCH_UNKNOWN},
189
};
190
191
static int ssh_config_parse_line(ssh_session session, const char *line,
192
    unsigned int count, int *parsing, unsigned int depth, bool global);
193
194
25.8M
static enum ssh_config_opcode_e ssh_config_get_opcode(char *keyword) {
195
25.8M
  int i;
196
197
2.34G
  for (i = 0; ssh_config_keyword_table[i].name != NULL; i++) {
198
2.32G
    if (strcasecmp(keyword, ssh_config_keyword_table[i].name) == 0) {
199
9.95k
      return ssh_config_keyword_table[i].opcode;
200
9.95k
    }
201
2.32G
  }
202
203
25.8M
  return SOC_UNKNOWN;
204
25.8M
}
205
206
29.0k
#define LIBSSH_CONF_MAX_DEPTH 16
207
static void
208
local_parse_file(ssh_session session,
209
                 const char *filename,
210
                 int *parsing,
211
                 unsigned int depth,
212
                 bool global)
213
29.0k
{
214
29.0k
    FILE *f = NULL;
215
29.0k
    char line[MAX_LINE_SIZE] = {0};
216
29.0k
    unsigned int count = 0;
217
29.0k
    int rv;
218
219
29.0k
    if (depth > LIBSSH_CONF_MAX_DEPTH) {
220
0
        ssh_set_error(session, SSH_FATAL,
221
0
                      "ERROR - Too many levels of configuration includes "
222
0
                      "when processing file '%s'", filename);
223
0
        return;
224
0
    }
225
226
29.0k
    f = fopen(filename, "r");
227
29.0k
    if (f == NULL) {
228
0
        SSH_LOG(SSH_LOG_RARE, "Cannot find file %s to load",
229
0
                filename);
230
0
        return;
231
0
    }
232
233
29.0k
    SSH_LOG(SSH_LOG_PACKET, "Reading additional configuration data from %s", filename);
234
36.2M
    while (fgets(line, sizeof(line), f)) {
235
36.2M
        count++;
236
36.2M
        rv = ssh_config_parse_line(session, line, count, parsing, depth, global);
237
36.2M
        if (rv < 0) {
238
0
            fclose(f);
239
0
            return;
240
0
        }
241
36.2M
    }
242
243
29.0k
    fclose(f);
244
29.0k
    return;
245
29.0k
}
246
247
#if defined(HAVE_GLOB) && defined(HAVE_GLOB_GL_FLAGS_MEMBER)
248
static void local_parse_glob(ssh_session session,
249
                             const char *fileglob,
250
                             int *parsing,
251
                             unsigned int depth,
252
                             bool global)
253
829
{
254
829
    glob_t globbuf = {
255
829
        .gl_flags = 0,
256
829
    };
257
829
    int rt;
258
829
    size_t i;
259
260
829
    rt = glob(fileglob, GLOB_TILDE, NULL, &globbuf);
261
829
    if (rt == GLOB_NOMATCH) {
262
543
        globfree(&globbuf);
263
543
        return;
264
543
    } else if (rt != 0) {
265
0
        SSH_LOG(SSH_LOG_RARE, "Glob error: %s",
266
0
                fileglob);
267
0
        globfree(&globbuf);
268
0
        return;
269
0
    }
270
271
29.2k
    for (i = 0; i < globbuf.gl_pathc; i++) {
272
29.0k
        local_parse_file(session, globbuf.gl_pathv[i], parsing, depth, global);
273
29.0k
    }
274
275
286
    globfree(&globbuf);
276
286
}
277
#endif /* HAVE_GLOB HAVE_GLOB_GL_FLAGS_MEMBER */
278
279
static enum ssh_config_match_e
280
ssh_config_get_match_opcode(const char *keyword)
281
2.00k
{
282
2.00k
    size_t i;
283
284
18.3k
    for (i = 0; ssh_config_match_keyword_table[i].name != NULL; i++) {
285
16.7k
        if (strcasecmp(keyword, ssh_config_match_keyword_table[i].name) == 0) {
286
397
            return ssh_config_match_keyword_table[i].opcode;
287
397
        }
288
16.7k
    }
289
290
1.60k
    return MATCH_UNKNOWN;
291
2.00k
}
292
293
static int
294
ssh_config_match(char *value, const char *pattern, bool negate)
295
212
{
296
212
    int ok, result = 0;
297
298
212
    ok = match_pattern_list(value, pattern, strlen(pattern), 0);
299
212
    if (ok <= 0 && negate == true) {
300
2
        result = 1;
301
210
    } else if (ok > 0 && negate == false) {
302
0
        result = 1;
303
0
    }
304
212
    SSH_LOG(SSH_LOG_TRACE, "%s '%s' against pattern '%s'%s (ok=%d)",
305
212
            result == 1 ? "Matched" : "Not matched", value, pattern,
306
212
            negate == true ? " (negated)" : "", ok);
307
212
    return result;
308
212
}
309
310
#ifdef WITH_EXEC
311
/* FIXME reuse the ssh_execute_command() from socket.c */
312
static int
313
ssh_exec_shell(char *cmd)
314
{
315
    char *shell = NULL;
316
    pid_t pid;
317
    int status, devnull, rc;
318
    char err_msg[SSH_ERRNO_MSG_MAX] = {0};
319
320
    shell = getenv("SHELL");
321
    if (shell == NULL || shell[0] == '\0') {
322
        shell = (char *)"/bin/sh";
323
    }
324
325
    rc = access(shell, X_OK);
326
    if (rc != 0) {
327
        SSH_LOG(SSH_LOG_WARN, "The shell '%s' is not executable", shell);
328
        return -1;
329
    }
330
331
    /* Need this to redirect subprocess stdin/out */
332
    devnull = open("/dev/null", O_RDWR);
333
    if (devnull == -1) {
334
        SSH_LOG(SSH_LOG_WARN, "Failed to open(/dev/null): %s",
335
                ssh_strerror(errno, err_msg, SSH_ERRNO_MSG_MAX));
336
        return -1;
337
    }
338
339
    SSH_LOG(SSH_LOG_DEBUG, "Running command '%s'", cmd);
340
    pid = fork();
341
    if (pid == 0) { /* Child */
342
        char *argv[4];
343
344
        /* Redirect child stdin and stdout. Leave stderr */
345
        rc = dup2(devnull, STDIN_FILENO);
346
        if (rc == -1) {
347
            SSH_LOG(SSH_LOG_WARN, "dup2: %s",
348
                    ssh_strerror(errno, err_msg, SSH_ERRNO_MSG_MAX));
349
            exit(1);
350
        }
351
        rc = dup2(devnull, STDOUT_FILENO);
352
        if (rc == -1) {
353
            SSH_LOG(SSH_LOG_WARN, "dup2: %s",
354
                    ssh_strerror(errno, err_msg, SSH_ERRNO_MSG_MAX));
355
            exit(1);
356
        }
357
        if (devnull > STDERR_FILENO) {
358
            close(devnull);
359
        }
360
361
        argv[0] = shell;
362
        argv[1] = (char *) "-c";
363
        argv[2] = strdup(cmd);
364
        argv[3] = NULL;
365
366
        rc = execv(argv[0], argv);
367
        if (rc == -1) {
368
            SSH_LOG(SSH_LOG_WARN, "Failed to execute command '%s': %s", cmd,
369
                    ssh_strerror(errno, err_msg, SSH_ERRNO_MSG_MAX));
370
            /* Die with signal to make this error apparent to parent. */
371
            signal(SIGTERM, SIG_DFL);
372
            kill(getpid(), SIGTERM);
373
            _exit(1);
374
        }
375
    }
376
377
    /* Parent */
378
    close(devnull);
379
    if (pid == -1) { /* Error */
380
        SSH_LOG(SSH_LOG_WARN, "Failed to fork child: %s",
381
                ssh_strerror(errno, err_msg, SSH_ERRNO_MSG_MAX));
382
        return -1;
383
384
    }
385
386
    while (waitpid(pid, &status, 0) == -1) {
387
        if (errno != EINTR) {
388
            SSH_LOG(SSH_LOG_WARN, "waitpid failed: %s",
389
                    ssh_strerror(errno, err_msg, SSH_ERRNO_MSG_MAX));
390
            return -1;
391
        }
392
    }
393
    if (!WIFEXITED(status)) {
394
        SSH_LOG(SSH_LOG_WARN, "Command %s exited abnormally", cmd);
395
        return -1;
396
    }
397
    SSH_LOG(SSH_LOG_TRACE, "Command '%s' returned %d", cmd, WEXITSTATUS(status));
398
    return WEXITSTATUS(status);
399
}
400
401
static int
402
ssh_match_exec(ssh_session session, const char *command, bool negate)
403
{
404
    int rv, result = 0;
405
    char *cmd = NULL;
406
407
    /* TODO There should be more supported expansions */
408
    cmd = ssh_path_expand_escape(session, command);
409
    if (cmd == NULL) {
410
        return 0;
411
    }
412
    rv = ssh_exec_shell(cmd);
413
    if (rv > 0 && negate == true) {
414
        result = 1;
415
    } else if (rv == 0 && negate == false) {
416
        result = 1;
417
    }
418
    SSH_LOG(SSH_LOG_TRACE, "%s 'exec' command '%s'%s (rv=%d)",
419
            result == 1 ? "Matched" : "Not matched", cmd,
420
            negate == true ? " (negated)" : "", rv);
421
    free(cmd);
422
    return result;
423
}
424
#else
425
static int
426
ssh_match_exec(ssh_session session, const char *command, bool negate)
427
37
{
428
37
    (void)session;
429
37
    (void)command;
430
37
    (void)negate;
431
432
37
    SSH_LOG(SSH_LOG_TRACE,
433
37
            "Unsupported 'exec' command on Windows '%s'",
434
37
            command);
435
37
    return 0;
436
37
}
437
#endif /* WITH_EXEC */
438
439
/**
440
 * @brief: Parse the ProxyJump configuration line and if parsing,
441
 * stores the result in the configuration option
442
 *
443
 * @param[in]   session    The ssh session
444
 * @param[in]   s          The string to be parsed.
445
 * @param[in]   do_parsing Whether to parse or not.
446
 *
447
 * @returns     SSH_OK if the provided string is formatted and parsed correctly
448
 *              SSH_ERROR on failure
449
 */
450
int
451
ssh_config_parse_proxy_jump(ssh_session session, const char *s, bool do_parsing)
452
59
{
453
59
    char *c = NULL, *cp = NULL, *endp = NULL;
454
59
    char *username = NULL;
455
59
    char *hostname = NULL;
456
59
    char *port = NULL;
457
59
    char *next = NULL;
458
59
    int cmp, rv = SSH_ERROR;
459
59
    struct ssh_jump_info_struct *jump_host = NULL;
460
59
    bool parse_entry = do_parsing;
461
59
    bool libssh_proxy_jump = ssh_libssh_proxy_jumps();
462
463
    /* Special value none disables the proxy */
464
59
    cmp = strcasecmp(s, "none");
465
59
    if (cmp == 0) {
466
7
        if (!libssh_proxy_jump && do_parsing) {
467
0
            ssh_options_set(session, SSH_OPTIONS_PROXYCOMMAND, s);
468
0
        }
469
7
        return SSH_OK;
470
7
    }
471
472
    /* This is comma-separated list of [user@]host[:port] entries */
473
52
    c = strdup(s);
474
52
    if (c == NULL) {
475
0
        ssh_set_error_oom(session);
476
0
        return SSH_ERROR;
477
0
    }
478
479
52
    cp = c;
480
1.11k
    do {
481
1.11k
        endp = strchr(cp, ',');
482
1.11k
        if (endp != NULL) {
483
            /* Split out the token */
484
1.06k
            *endp = '\0';
485
1.06k
        }
486
1.11k
        if (parse_entry && libssh_proxy_jump) {
487
1.05k
            jump_host = calloc(1, sizeof(struct ssh_jump_info_struct));
488
1.05k
            if (jump_host == NULL) {
489
0
                ssh_set_error_oom(session);
490
0
                rv = SSH_ERROR;
491
0
                goto out;
492
0
            }
493
494
1.05k
            rv = ssh_config_parse_uri(cp,
495
1.05k
                                      &jump_host->username,
496
1.05k
                                      &jump_host->hostname,
497
1.05k
                                      &port,
498
1.05k
                                      false);
499
1.05k
            if (rv != SSH_OK) {
500
23
                ssh_set_error_invalid(session);
501
23
                SAFE_FREE(jump_host);
502
23
                goto out;
503
23
            }
504
1.03k
            if (port == NULL) {
505
786
                jump_host->port = 22;
506
786
            } else {
507
250
                jump_host->port = strtol(port, NULL, 10);
508
250
                SAFE_FREE(port);
509
250
            }
510
511
            /* Prepend because we will recursively proxy jump */
512
1.03k
            rv = ssh_list_prepend(session->opts.proxy_jumps, jump_host);
513
1.03k
            if (rv != SSH_OK) {
514
0
                ssh_set_error_oom(session);
515
0
                SAFE_FREE(jump_host);
516
0
                goto out;
517
0
            }
518
1.03k
        } else if (parse_entry) {
519
            /* We actually care only about the first item */
520
0
            rv = ssh_config_parse_uri(cp, &username, &hostname, &port, false);
521
0
            if (rv != SSH_OK) {
522
0
                ssh_set_error_invalid(session);
523
0
                goto out;
524
0
            }
525
            /* The rest of the list needs to be passed on */
526
0
            if (endp != NULL) {
527
0
                next = strdup(endp + 1);
528
0
                if (next == NULL) {
529
0
                    ssh_set_error_oom(session);
530
0
                    rv = SSH_ERROR;
531
0
                    goto out;
532
0
                }
533
0
            }
534
53
        } else {
535
            /* The rest is just sanity-checked to avoid failures later */
536
53
            rv = ssh_config_parse_uri(cp, NULL, NULL, NULL, false);
537
53
            if (rv != SSH_OK) {
538
2
                ssh_set_error_invalid(session);
539
2
                goto out;
540
2
            }
541
53
        }
542
1.08k
        if (!libssh_proxy_jump) {
543
0
            parse_entry = 0;
544
0
        }
545
1.08k
        if (endp != NULL) {
546
1.06k
            cp = endp + 1;
547
1.06k
        } else {
548
27
            cp = NULL; /* end */
549
27
        }
550
1.08k
    } while (cp != NULL);
551
552
27
    if (!libssh_proxy_jump && hostname != NULL && do_parsing) {
553
0
        char com[512] = {0};
554
555
0
        rv = snprintf(com, sizeof(com), "ssh%s%s%s%s%s%s -W '[%%h]:%%p' %s",
556
0
                      username ? " -l " : "",
557
0
                      username ? username : "",
558
0
                      port ? " -p " : "",
559
0
                      port ? port : "",
560
0
                      next ? " -J " : "",
561
0
                      next ? next : "",
562
0
                      hostname);
563
0
        if (rv < 0 || rv >= (int)sizeof(com)) {
564
0
            SSH_LOG(SSH_LOG_TRACE, "Too long ProxyJump configuration line");
565
0
            rv = SSH_ERROR;
566
0
            goto out;
567
0
        }
568
0
        rv = ssh_options_set(session, SSH_OPTIONS_PROXYCOMMAND, com);
569
0
        if (rv != SSH_OK) {
570
0
            ssh_set_error_oom(session);
571
0
            goto out;
572
0
        }
573
0
    }
574
575
27
    rv = SSH_OK;
576
577
52
out:
578
52
    if (rv != SSH_OK) {
579
25
        ssh_proxyjumps_free(session->opts.proxy_jumps);
580
25
    }
581
52
    SAFE_FREE(username);
582
52
    SAFE_FREE(hostname);
583
52
    SAFE_FREE(port);
584
52
    SAFE_FREE(next);
585
52
    SAFE_FREE(c);
586
52
    return rv;
587
27
}
588
589
static char *
590
ssh_config_make_absolute(ssh_session session,
591
                         const char *path,
592
                         bool global)
593
842
{
594
842
    size_t outlen = 0;
595
842
    char *out = NULL;
596
842
    int rv;
597
598
    /* Looks like absolute path */
599
842
    if (path[0] == '/') {
600
458
        return strdup(path);
601
458
    }
602
603
    /* relative path */
604
384
    if (global) {
605
        /* Parsing global config */
606
0
        outlen = strlen(path) + strlen("/etc/ssh/") + 1;
607
0
        out = malloc(outlen);
608
0
        if (out == NULL) {
609
0
            ssh_set_error_oom(session);
610
0
            return NULL;
611
0
        }
612
0
        rv = snprintf(out, outlen, "/etc/ssh/%s", path);
613
0
        if (rv < 1) {
614
0
            free(out);
615
0
            return NULL;
616
0
        }
617
0
        return out;
618
0
    }
619
620
    /* paths starting with tilde are already absolute */
621
384
    if (path[0] == '~') {
622
313
        return ssh_path_expand_tilde(path);
623
313
    }
624
625
    /* Parsing user config relative to home directory (generally ~/.ssh) */
626
71
    if (session->opts.sshdir == NULL) {
627
0
        ssh_set_error_invalid(session);
628
0
        return NULL;
629
0
    }
630
71
    outlen = strlen(path) + strlen(session->opts.sshdir) + 1 + 1;
631
71
    out = malloc(outlen);
632
71
    if (out == NULL) {
633
0
        ssh_set_error_oom(session);
634
0
        return NULL;
635
0
    }
636
71
    rv = snprintf(out, outlen, "%s/%s", session->opts.sshdir, path);
637
71
    if (rv < 1) {
638
0
        free(out);
639
0
        return NULL;
640
0
    }
641
71
    return out;
642
71
}
643
644
#ifdef HAVE_IFADDRS_H
645
/**
646
 * @brief Checks if host address matches the local network specified.
647
 *
648
 * Verify whether a local network interface address matches any of the CIDR
649
 * patterns.
650
 *
651
 * @param addrlist The CIDR pattern-list to be checked, can contain both
652
 *                 IPv4 and IPv6 addresses and has to be comma separated
653
 *                 (',' only, space after comma not allowed).
654
 *
655
 * @param negate   The negate condition. The return value is negated
656
 *                 (returns 1 instead of 0 and vice versa).
657
 *
658
 * @return 1 if match found.
659
 * @return 0 if no match found.
660
 * @return -1 on errors.
661
 */
662
static int
663
ssh_match_localnetwork(const char *addrlist, bool negate)
664
0
{
665
0
    struct ifaddrs *ifa = NULL, *ifaddrs = NULL;
666
0
    int r, found = 0;
667
0
    char address[NI_MAXHOST], err_msg[SSH_ERRNO_MSG_MAX] = {0};
668
0
    socklen_t sa_len;
669
670
0
    r = getifaddrs(&ifaddrs);
671
0
    if (r != 0) {
672
0
        SSH_LOG(SSH_LOG_WARN,
673
0
                "Match localnetwork: getifaddrs() failed: %s",
674
0
                ssh_strerror(r, err_msg, SSH_ERRNO_MSG_MAX));
675
0
        return -1;
676
0
    }
677
678
0
    for (ifa = ifaddrs; ifa != NULL; ifa = ifa->ifa_next) {
679
0
        if (ifa->ifa_addr == NULL || (ifa->ifa_flags & IFF_UP) == 0) {
680
0
            continue;
681
0
        }
682
683
0
        switch (ifa->ifa_addr->sa_family) {
684
0
        case AF_INET:
685
0
            sa_len = sizeof(struct sockaddr_in);
686
0
            break;
687
0
        case AF_INET6:
688
0
            sa_len = sizeof(struct sockaddr_in6);
689
0
            break;
690
0
        default:
691
0
            SSH_LOG(SSH_LOG_TRACE,
692
0
                    "Interface %s: unsupported address family %d",
693
0
                    ifa->ifa_name,
694
0
                    ifa->ifa_addr->sa_family);
695
0
            continue;
696
0
        }
697
698
0
        r = getnameinfo(ifa->ifa_addr,
699
0
                        sa_len,
700
0
                        address,
701
0
                        sizeof(address),
702
0
                        NULL,
703
0
                        0,
704
0
                        NI_NUMERICHOST);
705
0
        if (r != 0) {
706
0
            SSH_LOG(SSH_LOG_TRACE,
707
0
                    "Interface %s getnameinfo failed: %s",
708
0
                    ifa->ifa_name,
709
0
                    gai_strerror(r));
710
0
            continue;
711
0
        }
712
0
        SSH_LOG(SSH_LOG_TRACE,
713
0
                "Interface %s address %s",
714
0
                ifa->ifa_name,
715
0
                address);
716
717
0
        r = match_cidr_address_list(address,
718
0
                                    addrlist,
719
0
                                    ifa->ifa_addr->sa_family);
720
0
        if (r == 1) {
721
0
            SSH_LOG(SSH_LOG_TRACE,
722
0
                    "Matched interface %s: address %s in %s",
723
0
                    ifa->ifa_name,
724
0
                    address,
725
0
                    addrlist);
726
0
            found = 1;
727
0
            break;
728
0
        }
729
0
    }
730
731
0
    freeifaddrs(ifaddrs);
732
733
0
    return (found == (negate ? 0 : 1));
734
0
}
735
#endif /* HAVE_IFADDRS_H */
736
737
static int
738
ssh_config_parse_line(ssh_session session,
739
                      const char *line,
740
                      unsigned int count,
741
                      int *parsing,
742
                      unsigned int depth,
743
                      bool global)
744
36.2M
{
745
36.2M
  enum ssh_config_opcode_e opcode;
746
36.2M
  const char *p = NULL, *p2 = NULL;
747
36.2M
  char *s = NULL, *x = NULL;
748
36.2M
  char *keyword = NULL;
749
36.2M
  char *lowerhost = NULL;
750
36.2M
  size_t len;
751
36.2M
  int i, rv;
752
36.2M
  uint8_t *seen = session->opts.options_seen;
753
36.2M
  long l;
754
36.2M
  int64_t ll;
755
756
  /* Ignore empty lines */
757
36.2M
  if (line == NULL || *line == '\0') {
758
9.64M
    return 0;
759
9.64M
  }
760
761
26.6M
  x = s = strdup(line);
762
26.6M
  if (s == NULL) {
763
0
    ssh_set_error_oom(session);
764
0
    return -1;
765
0
  }
766
767
  /* Remove trailing spaces */
768
35.1M
  for (len = strlen(s) - 1; len > 0; len--) {
769
31.3M
    if (! isspace(s[len])) {
770
22.8M
      break;
771
22.8M
    }
772
8.53M
    s[len] = '\0';
773
8.53M
  }
774
775
26.6M
  keyword = ssh_config_get_token(&s);
776
26.6M
  if (keyword == NULL || *keyword == '#' ||
777
26.6M
      *keyword == '\0' || *keyword == '\n') {
778
796k
    SAFE_FREE(x);
779
796k
    return 0;
780
796k
  }
781
782
25.8M
  opcode = ssh_config_get_opcode(keyword);
783
25.8M
  if (*parsing == 1 &&
784
25.8M
      opcode != SOC_HOST &&
785
25.8M
      opcode != SOC_MATCH &&
786
25.8M
      opcode != SOC_INCLUDE &&
787
25.8M
      opcode != SOC_IDENTITY &&
788
25.8M
      opcode != SOC_CERTIFICATE &&
789
25.8M
      opcode > SOC_UNSUPPORTED &&
790
25.8M
      opcode < SOC_MAX) { /* Ignore all unknown types here */
791
      /* Skip all the options that were already applied */
792
539
      if (seen[opcode] != 0) {
793
157
          SAFE_FREE(x);
794
157
          return 0;
795
157
      }
796
382
      seen[opcode] = 1;
797
382
  }
798
799
25.8M
  switch (opcode) {
800
3.85k
    case SOC_INCLUDE: /* recursive include of other files */
801
802
3.85k
      p = ssh_config_get_str_tok(&s, NULL);
803
3.85k
      if (p && *parsing) {
804
842
        char *path = ssh_config_make_absolute(session, p, global);
805
842
        if (path == NULL) {
806
13
          SSH_LOG(SSH_LOG_WARN, "line %d: Failed to allocate memory "
807
13
                  "for the include path expansion", count);
808
13
          SAFE_FREE(x);
809
13
          return -1;
810
13
        }
811
829
#if defined(HAVE_GLOB) && defined(HAVE_GLOB_GL_FLAGS_MEMBER)
812
829
        local_parse_glob(session, path, parsing, depth + 1, global);
813
#else
814
        local_parse_file(session, path, parsing, depth + 1, global);
815
#endif /* HAVE_GLOB */
816
829
        free(path);
817
829
      }
818
3.84k
      break;
819
820
3.84k
    case SOC_MATCH: {
821
568
        bool negate;
822
568
        int result = 1;
823
568
        size_t args = 0;
824
568
        enum ssh_config_match_e opt;
825
568
        char *localuser = NULL;
826
827
568
        *parsing = 0;
828
2.52k
        do {
829
2.52k
            p = p2 = ssh_config_get_str_tok(&s, NULL);
830
2.52k
            if (p == NULL || p[0] == '\0') {
831
526
                break;
832
526
            }
833
2.00k
            args++;
834
2.00k
            SSH_LOG(SSH_LOG_DEBUG, "line %d: Processing Match keyword '%s'",
835
2.00k
                    count, p);
836
837
            /* If the option is prefixed with ! the result should be negated */
838
2.00k
            negate = false;
839
2.00k
            if (p[0] == '!') {
840
81
                negate = true;
841
81
                p++;
842
81
            }
843
844
2.00k
            opt = ssh_config_get_match_opcode(p);
845
2.00k
            switch (opt) {
846
13
            case MATCH_ALL:
847
13
                p = ssh_config_get_str_tok(&s, NULL);
848
13
                if (args <= 2 && (p == NULL || p[0] == '\0')) {
849
                    /* The first or second, but last argument. The "all" keyword
850
                     * can be prefixed with either "final" or "canonical"
851
                     * keywords which do not have any effect here. */
852
12
                    if (negate == true) {
853
9
                        result = 0;
854
9
                    }
855
12
                    break;
856
12
                }
857
858
1
                ssh_set_error(session, SSH_FATAL,
859
1
                              "line %d: ERROR - Match all cannot be combined with "
860
1
                              "other Match attributes", count);
861
1
                SAFE_FREE(x);
862
1
                return -1;
863
864
104
            case MATCH_FINAL:
865
107
            case MATCH_CANONICAL:
866
107
                SSH_LOG(SSH_LOG_DEBUG,
867
107
                        "line %d: Unsupported Match keyword '%s', skipping",
868
107
                        count,
869
107
                        p);
870
                /* Not set any result here -- the result is dependent on the
871
                 * following matches after this keyword */
872
107
                break;
873
874
37
            case MATCH_EXEC:
875
                /* Skip one argument (including in quotes) */
876
37
                p = ssh_config_get_token(&s);
877
37
                if (p == NULL || p[0] == '\0') {
878
0
                    SSH_LOG(SSH_LOG_TRACE, "line %d: Match keyword "
879
0
                            "'%s' requires argument", count, p2);
880
0
                    SAFE_FREE(x);
881
0
                    return -1;
882
0
                }
883
37
                if (result != 1) {
884
0
                    SSH_LOG(SSH_LOG_DEBUG, "line %d: Skipped match exec "
885
0
                            "'%s' as previous conditions already failed.",
886
0
                            count, p2);
887
0
                    continue;
888
0
                }
889
37
                result &= ssh_match_exec(session, p, negate);
890
37
                args++;
891
37
                break;
892
893
171
            case MATCH_LOCALUSER:
894
                /* Here we match only one argument */
895
171
                p = ssh_config_get_str_tok(&s, NULL);
896
171
                if (p == NULL || p[0] == '\0') {
897
0
                    ssh_set_error(session,
898
0
                                  SSH_FATAL,
899
0
                                  "line %d: ERROR - Match localuser keyword "
900
0
                                  "requires argument",
901
0
                                  count);
902
0
                    SAFE_FREE(x);
903
0
                    return -1;
904
0
                }
905
171
                localuser = ssh_get_local_username();
906
171
                if (localuser == NULL) {
907
0
                    SSH_LOG(SSH_LOG_TRACE, "line %d: Can not get local username "
908
0
                            "for conditional matching.", count);
909
0
                    SAFE_FREE(x);
910
0
                    return -1;
911
0
                }
912
171
                result &= ssh_config_match(localuser, p, negate);
913
171
                SAFE_FREE(localuser);
914
171
                args++;
915
171
                break;
916
917
10
            case MATCH_ORIGINALHOST:
918
                /* Skip one argument */
919
10
                p = ssh_config_get_str_tok(&s, NULL);
920
10
                if (p == NULL || p[0] == '\0') {
921
0
                    SSH_LOG(SSH_LOG_TRACE, "line %d: Match keyword "
922
0
                            "'%s' requires argument", count, p2);
923
0
                    SAFE_FREE(x);
924
0
                    return -1;
925
0
                }
926
10
                args++;
927
10
                SSH_LOG(SSH_LOG_TRACE,
928
10
                        "line %d: Unsupported Match keyword '%s', ignoring",
929
10
                        count,
930
10
                        p2);
931
10
                result = 0;
932
10
                break;
933
934
7
            case MATCH_HOST:
935
                /* Here we match only one argument */
936
7
                p = ssh_config_get_str_tok(&s, NULL);
937
7
                if (p == NULL || p[0] == '\0') {
938
0
                    ssh_set_error(session, SSH_FATAL,
939
0
                                  "line %d: ERROR - Match host keyword "
940
0
                                  "requires argument", count);
941
0
                    SAFE_FREE(x);
942
0
                    return -1;
943
0
                }
944
7
                result &= ssh_config_match(session->opts.host, p, negate);
945
7
                args++;
946
7
                break;
947
948
34
            case MATCH_USER:
949
                /* Here we match only one argument */
950
34
                p = ssh_config_get_str_tok(&s, NULL);
951
34
                if (p == NULL || p[0] == '\0') {
952
0
                    ssh_set_error(session, SSH_FATAL,
953
0
                                  "line %d: ERROR - Match user keyword "
954
0
                                  "requires argument", count);
955
0
                    SAFE_FREE(x);
956
0
                    return -1;
957
0
                }
958
34
                result &= ssh_config_match(session->opts.username, p, negate);
959
34
                args++;
960
34
                break;
961
962
18
            case MATCH_LOCALNETWORK:
963
                /* Here we match only one argument */
964
18
                p = ssh_config_get_str_tok(&s, NULL);
965
18
                if (p == NULL || p[0] == '\0') {
966
0
                    ssh_set_error(session,
967
0
                                  SSH_FATAL,
968
0
                                  "line %d: ERROR - Match local network keyword"
969
0
                                  "requires argument",
970
0
                                  count);
971
0
                    SAFE_FREE(x);
972
0
                    return -1;
973
0
                }
974
18
#ifdef HAVE_IFADDRS_H
975
18
                rv = match_cidr_address_list(NULL, p, -1);
976
18
                if (rv == -1) {
977
18
                    ssh_set_error(session,
978
18
                                  SSH_FATAL,
979
18
                                  "line %d: ERROR - List invalid entry: %s",
980
18
                                  count,
981
18
                                  p);
982
18
                    SAFE_FREE(x);
983
18
                    return -1;
984
18
                }
985
0
                rv = ssh_match_localnetwork(p, negate);
986
0
                if (rv == -1) {
987
0
                    ssh_set_error(session,
988
0
                                  SSH_FATAL,
989
0
                                  "line %d: ERROR - Error while retrieving "
990
0
                                  "network interface information -"
991
0
                                  " List entry: %s",
992
0
                                  count,
993
0
                                  p);
994
0
                    SAFE_FREE(x);
995
0
                    return -1;
996
0
                }
997
998
0
                result &= rv;
999
#else /* HAVE_IFADDRS_H */
1000
                ssh_set_error(session,
1001
                              SSH_FATAL,
1002
                              "line %d: ERROR - match localnetwork "
1003
                              "not supported on this platform",
1004
                              count);
1005
                SAFE_FREE(x);
1006
                return -1;
1007
#endif /* HAVE_IFADDRS_H */
1008
0
                args++;
1009
0
                break;
1010
1011
1.60k
            case MATCH_UNKNOWN:
1012
1.60k
            default:
1013
1.60k
                SSH_LOG(SSH_LOG_WARN,
1014
1.60k
                        "Unknown argument '%s' for Match keyword. Not matching",
1015
1.60k
                        p);
1016
1.60k
                result = 0;
1017
1.60k
                break;
1018
2.00k
            }
1019
2.00k
        } while (p != NULL && p[0] != '\0');
1020
549
        if (args == 0) {
1021
64
            SSH_LOG(SSH_LOG_WARN,
1022
64
                    "ERROR - Match keyword requires an argument. Not matching");
1023
64
            result = 0;
1024
64
        }
1025
549
        *parsing = result;
1026
549
        break;
1027
568
    }
1028
614
    case SOC_HOST: {
1029
614
        int ok = 0, result = -1;
1030
1031
614
        *parsing = 0;
1032
614
        lowerhost = (session->opts.host) ? ssh_lowercase(session->opts.host) : NULL;
1033
614
        for (p = ssh_config_get_str_tok(&s, NULL);
1034
3.06k
             p != NULL && p[0] != '\0';
1035
2.44k
             p = ssh_config_get_str_tok(&s, NULL)) {
1036
2.44k
             if (ok >= 0) {
1037
2.43k
               ok = match_hostname(lowerhost, p, strlen(p));
1038
2.43k
               if (result == -1 && ok < 0) {
1039
5
                   result = 0;
1040
2.42k
               } else if (result == -1 && ok > 0) {
1041
140
                   result = 1;
1042
140
               }
1043
2.43k
            }
1044
2.44k
        }
1045
614
        SAFE_FREE(lowerhost);
1046
614
        if (result != -1) {
1047
145
            *parsing = result;
1048
145
        }
1049
614
        break;
1050
568
    }
1051
201
    case SOC_HOSTNAME:
1052
201
      p = ssh_config_get_str_tok(&s, NULL);
1053
201
      if (p && *parsing) {
1054
85
        char *z = ssh_path_expand_escape(session, p);
1055
85
        if (z == NULL) {
1056
20
            z = strdup(p);
1057
20
        }
1058
85
        ssh_options_set(session, SSH_OPTIONS_HOST, z);
1059
85
        free(z);
1060
85
      }
1061
201
      break;
1062
32
    case SOC_PORT:
1063
32
        p = ssh_config_get_str_tok(&s, NULL);
1064
32
        if (p && *parsing) {
1065
11
            ssh_options_set(session, SSH_OPTIONS_PORT_STR, p);
1066
11
        }
1067
32
        break;
1068
130
    case SOC_USERNAME:
1069
130
      if (session->opts.username == NULL) {
1070
130
          p = ssh_config_get_str_tok(&s, NULL);
1071
130
          if (p && *parsing) {
1072
19
            ssh_options_set(session, SSH_OPTIONS_USER, p);
1073
19
         }
1074
130
      }
1075
130
      break;
1076
219
    case SOC_IDENTITY:
1077
219
      p = ssh_config_get_str_tok(&s, NULL);
1078
219
      if (p && *parsing) {
1079
171
        ssh_options_set(session, SSH_OPTIONS_ADD_IDENTITY, p);
1080
171
      }
1081
219
      break;
1082
46
    case SOC_CIPHERS:
1083
46
      p = ssh_config_get_str_tok(&s, NULL);
1084
46
      if (p && *parsing) {
1085
12
        ssh_options_set(session, SSH_OPTIONS_CIPHERS_C_S, p);
1086
12
        ssh_options_set(session, SSH_OPTIONS_CIPHERS_S_C, p);
1087
12
      }
1088
46
      break;
1089
204
    case SOC_MACS:
1090
204
      p = ssh_config_get_str_tok(&s, NULL);
1091
204
      if (p && *parsing) {
1092
67
        ssh_options_set(session, SSH_OPTIONS_HMAC_C_S, p);
1093
67
        ssh_options_set(session, SSH_OPTIONS_HMAC_S_C, p);
1094
67
      }
1095
204
      break;
1096
124
    case SOC_COMPRESSION:
1097
124
      i = ssh_config_get_yesno(&s, -1);
1098
124
      if (i >= 0 && *parsing) {
1099
2
        if (i) {
1100
1
          ssh_options_set(session, SSH_OPTIONS_COMPRESSION, "yes");
1101
1
        } else {
1102
1
          ssh_options_set(session, SSH_OPTIONS_COMPRESSION, "no");
1103
1
        }
1104
2
      }
1105
124
      break;
1106
108
    case SOC_TIMEOUT:
1107
108
      l = ssh_config_get_long(&s, -1);
1108
108
      if (l >= 0 && *parsing) {
1109
7
        ssh_options_set(session, SSH_OPTIONS_TIMEOUT, &l);
1110
7
      }
1111
108
      break;
1112
64
    case SOC_STRICTHOSTKEYCHECK:
1113
64
      i = ssh_config_get_yesno(&s, -1);
1114
64
      if (i >= 0 && *parsing) {
1115
0
        ssh_options_set(session, SSH_OPTIONS_STRICTHOSTKEYCHECK, &i);
1116
0
      }
1117
64
      break;
1118
11
    case SOC_KNOWNHOSTS:
1119
11
      p = ssh_config_get_str_tok(&s, NULL);
1120
11
      if (p && *parsing) {
1121
0
        ssh_options_set(session, SSH_OPTIONS_KNOWNHOSTS, p);
1122
0
      }
1123
11
      break;
1124
48
    case SOC_PROXYCOMMAND:
1125
48
      p = ssh_config_get_cmd(&s);
1126
      /* We share the seen value with the ProxyJump */
1127
48
      if (p && *parsing && !seen[SOC_PROXYJUMP]) {
1128
4
        ssh_options_set(session, SSH_OPTIONS_PROXYCOMMAND, p);
1129
4
      }
1130
48
      break;
1131
59
    case SOC_PROXYJUMP:
1132
59
        p = ssh_config_get_str_tok(&s, NULL);
1133
59
        if (p == NULL) {
1134
0
            SAFE_FREE(x);
1135
0
            return -1;
1136
0
        }
1137
        /* We share the seen value with the ProxyCommand */
1138
59
        rv = ssh_config_parse_proxy_jump(session,
1139
59
                                         p,
1140
59
                                         (*parsing && !seen[SOC_PROXYCOMMAND]));
1141
59
        if (rv != SSH_OK) {
1142
25
            SAFE_FREE(x);
1143
25
            return -1;
1144
25
        }
1145
34
        break;
1146
47
    case SOC_GSSAPISERVERIDENTITY:
1147
47
      p = ssh_config_get_str_tok(&s, NULL);
1148
47
      if (p && *parsing) {
1149
1
        ssh_options_set(session, SSH_OPTIONS_GSSAPI_SERVER_IDENTITY, p);
1150
1
      }
1151
47
      break;
1152
30
    case SOC_GSSAPICLIENTIDENTITY:
1153
30
      p = ssh_config_get_str_tok(&s, NULL);
1154
30
      if (p && *parsing) {
1155
0
        ssh_options_set(session, SSH_OPTIONS_GSSAPI_CLIENT_IDENTITY, p);
1156
0
      }
1157
30
      break;
1158
92
    case SOC_GSSAPIDELEGATECREDENTIALS:
1159
92
      i = ssh_config_get_yesno(&s, -1);
1160
92
      if (i >=0 && *parsing) {
1161
0
        ssh_options_set(session, SSH_OPTIONS_GSSAPI_DELEGATE_CREDENTIALS, &i);
1162
0
      }
1163
92
      break;
1164
32
    case SOC_BINDADDRESS:
1165
32
        p = ssh_config_get_str_tok(&s, NULL);
1166
32
        if (p && *parsing) {
1167
0
            ssh_options_set(session, SSH_OPTIONS_BINDADDR, p);
1168
0
        }
1169
32
        break;
1170
180
    case SOC_GLOBALKNOWNHOSTSFILE:
1171
180
        p = ssh_config_get_str_tok(&s, NULL);
1172
180
        if (p && *parsing) {
1173
2
            ssh_options_set(session, SSH_OPTIONS_GLOBAL_KNOWNHOSTS, p);
1174
2
        }
1175
180
        break;
1176
7
    case SOC_LOGLEVEL:
1177
7
        p = ssh_config_get_str_tok(&s, NULL);
1178
7
        if (p && *parsing) {
1179
4
            int value = -1;
1180
1181
4
            if (strcasecmp(p, "quiet") == 0) {
1182
0
                value = SSH_LOG_NONE;
1183
4
            } else if (strcasecmp(p, "fatal") == 0 ||
1184
4
                    strcasecmp(p, "error")== 0) {
1185
0
                value = SSH_LOG_WARN;
1186
4
            } else if (strcasecmp(p, "verbose") == 0 ||
1187
4
                    strcasecmp(p, "info") == 0) {
1188
0
                value = SSH_LOG_INFO;
1189
4
            } else if (strcasecmp(p, "DEBUG") == 0 ||
1190
4
                    strcasecmp(p, "DEBUG1") == 0) {
1191
0
                value = SSH_LOG_DEBUG;
1192
4
            } else if (strcasecmp(p, "DEBUG2") == 0 ||
1193
4
                    strcasecmp(p, "DEBUG3") == 0) {
1194
0
                value = SSH_LOG_TRACE;
1195
0
            }
1196
4
            if (value != -1) {
1197
0
                ssh_options_set(session, SSH_OPTIONS_LOG_VERBOSITY, &value);
1198
0
            }
1199
4
        }
1200
7
        break;
1201
55
    case SOC_HOSTKEYALGORITHMS:
1202
55
        p = ssh_config_get_str_tok(&s, NULL);
1203
55
        if (p && *parsing) {
1204
18
            ssh_options_set(session, SSH_OPTIONS_HOSTKEYS, p);
1205
18
        }
1206
55
        break;
1207
48
    case SOC_PUBKEYACCEPTEDKEYTYPES:
1208
48
        p = ssh_config_get_str_tok(&s, NULL);
1209
48
        if (p && *parsing) {
1210
24
            ssh_options_set(session, SSH_OPTIONS_PUBLICKEY_ACCEPTED_TYPES, p);
1211
24
        }
1212
48
        break;
1213
39
    case SOC_KEXALGORITHMS:
1214
39
        p = ssh_config_get_str_tok(&s, NULL);
1215
39
        if (p && *parsing) {
1216
4
            ssh_options_set(session, SSH_OPTIONS_KEY_EXCHANGE, p);
1217
4
        }
1218
39
        break;
1219
799
    case SOC_REKEYLIMIT:
1220
        /* Parse the data limit */
1221
799
        p = ssh_config_get_str_tok(&s, NULL);
1222
799
        if (p == NULL) {
1223
44
            break;
1224
755
        } else if (strcmp(p, "default") == 0) {
1225
            /* Default rekey limits enforced automatically */
1226
20
            ll = 0;
1227
735
        } else {
1228
735
            char *endp = NULL;
1229
735
            ll = strtoll(p, &endp, 10);
1230
735
            if (p == endp || ll < 0) {
1231
                /* No number or negative */
1232
70
                SSH_LOG(SSH_LOG_TRACE, "Invalid argument to rekey limit");
1233
70
                break;
1234
70
            }
1235
665
            switch (*endp) {
1236
35
            case 'G':
1237
35
                if (ll > LLONG_MAX / 1024) {
1238
9
                    SSH_LOG(SSH_LOG_TRACE, "Possible overflow of rekey limit");
1239
9
                    ll = -1;
1240
9
                    break;
1241
9
                }
1242
26
                ll = ll * 1024;
1243
26
                FALL_THROUGH;
1244
110
            case 'M':
1245
110
                if (ll > LLONG_MAX / 1024) {
1246
36
                    SSH_LOG(SSH_LOG_TRACE, "Possible overflow of rekey limit");
1247
36
                    ll = -1;
1248
36
                    break;
1249
36
                }
1250
74
                ll = ll * 1024;
1251
74
                FALL_THROUGH;
1252
86
            case 'K':
1253
86
                if (ll > LLONG_MAX / 1024) {
1254
2
                    SSH_LOG(SSH_LOG_TRACE, "Possible overflow of rekey limit");
1255
2
                    ll = -1;
1256
2
                    break;
1257
2
                }
1258
84
                ll = ll * 1024;
1259
84
                endp++;
1260
84
                FALL_THROUGH;
1261
519
            case '\0':
1262
                /* just the number */
1263
519
                break;
1264
99
            default:
1265
                /* Invalid suffix */
1266
99
                ll = -1;
1267
99
                break;
1268
665
            }
1269
665
            if (*endp != ' ' && *endp != '\0') {
1270
160
                SSH_LOG(SSH_LOG_TRACE,
1271
160
                        "Invalid trailing characters after the rekey limit: %s",
1272
160
                        endp);
1273
160
                break;
1274
160
            }
1275
665
        }
1276
525
        if (ll > -1 && *parsing) {
1277
24
            uint64_t v = (uint64_t)ll;
1278
24
            ssh_options_set(session, SSH_OPTIONS_REKEY_DATA, &v);
1279
24
        }
1280
        /* Parse the time limit */
1281
525
        p = ssh_config_get_str_tok(&s, NULL);
1282
525
        if (p == NULL) {
1283
57
            break;
1284
468
        } else if (strcmp(p, "none") == 0) {
1285
30
            ll = 0;
1286
438
        } else {
1287
438
            char *endp = NULL;
1288
438
            ll = strtoll(p, &endp, 10);
1289
438
            if (p == endp || ll < 0) {
1290
                /* No number or negative */
1291
37
                SSH_LOG(SSH_LOG_TRACE, "Invalid argument to rekey limit");
1292
37
                break;
1293
37
            }
1294
401
            switch (*endp) {
1295
1
            case 'w':
1296
250
            case 'W':
1297
250
                if (ll > LLONG_MAX / 7) {
1298
82
                    SSH_LOG(SSH_LOG_TRACE, "Possible overflow of rekey limit");
1299
82
                    ll = -1;
1300
82
                    break;
1301
82
                }
1302
168
                ll = ll * 7;
1303
168
                FALL_THROUGH;
1304
179
            case 'd':
1305
180
            case 'D':
1306
180
                if (ll > LLONG_MAX / 24) {
1307
6
                    SSH_LOG(SSH_LOG_TRACE, "Possible overflow of rekey limit");
1308
6
                    ll = -1;
1309
6
                    break;
1310
6
                }
1311
174
                ll = ll * 24;
1312
174
                FALL_THROUGH;
1313
182
            case 'h':
1314
196
            case 'H':
1315
196
                if (ll > LLONG_MAX / 60) {
1316
11
                    SSH_LOG(SSH_LOG_TRACE, "Possible overflow of rekey limit");
1317
11
                    ll = -1;
1318
11
                    break;
1319
11
                }
1320
185
                ll = ll * 60;
1321
185
                FALL_THROUGH;
1322
211
            case 'm':
1323
214
            case 'M':
1324
214
                if (ll > LLONG_MAX / 60) {
1325
20
                    SSH_LOG(SSH_LOG_TRACE, "Possible overflow of rekey limit");
1326
20
                    ll = -1;
1327
20
                    break;
1328
20
                }
1329
194
                ll = ll * 60;
1330
194
                FALL_THROUGH;
1331
204
            case 's':
1332
223
            case 'S':
1333
223
                endp++;
1334
223
                FALL_THROUGH;
1335
235
            case '\0':
1336
                /* just the number */
1337
235
                break;
1338
47
            default:
1339
                /* Invalid suffix */
1340
47
                ll = -1;
1341
47
                break;
1342
401
            }
1343
401
            if (*endp != '\0') {
1344
379
                SSH_LOG(SSH_LOG_TRACE, "Invalid trailing characters after the"
1345
379
                        " rekey limit: %s", endp);
1346
379
                break;
1347
379
            }
1348
401
        }
1349
52
        if (ll > -1 && *parsing) {
1350
6
            uint32_t v = (uint32_t)ll;
1351
6
            ssh_options_set(session, SSH_OPTIONS_REKEY_TIME, &v);
1352
6
        }
1353
52
        break;
1354
4
    case SOC_GSSAPIAUTHENTICATION:
1355
9
    case SOC_KBDINTERACTIVEAUTHENTICATION:
1356
17
    case SOC_PASSWORDAUTHENTICATION:
1357
33
    case SOC_PUBKEYAUTHENTICATION:
1358
33
        i = ssh_config_get_yesno(&s, 0);
1359
33
        if (i>=0 && *parsing) {
1360
14
            switch(opcode){
1361
4
            case SOC_GSSAPIAUTHENTICATION:
1362
4
                ssh_options_set(session, SSH_OPTIONS_GSSAPI_AUTH, &i);
1363
4
                break;
1364
4
            case SOC_KBDINTERACTIVEAUTHENTICATION:
1365
4
                ssh_options_set(session, SSH_OPTIONS_KBDINT_AUTH, &i);
1366
4
                break;
1367
1
            case SOC_PASSWORDAUTHENTICATION:
1368
1
                ssh_options_set(session, SSH_OPTIONS_PASSWORD_AUTH, &i);
1369
1
                break;
1370
5
            case SOC_PUBKEYAUTHENTICATION:
1371
5
                ssh_options_set(session, SSH_OPTIONS_PUBKEY_AUTH, &i);
1372
5
                break;
1373
            /* make gcc happy */
1374
0
            default:
1375
0
                break;
1376
14
            }
1377
14
        }
1378
33
        break;
1379
33
    case SOC_NA:
1380
32
      SSH_LOG(SSH_LOG_TRACE, "Unapplicable option: %s, line: %d",
1381
32
              keyword, count);
1382
32
      break;
1383
120
    case SOC_UNSUPPORTED:
1384
120
      SSH_LOG(SSH_LOG_RARE, "Unsupported option: %s, line: %d",
1385
120
              keyword, count);
1386
120
      break;
1387
25.8M
    case SOC_UNKNOWN:
1388
25.8M
      SSH_LOG(SSH_LOG_TRACE, "Unknown option: %s, line: %d",
1389
25.8M
              keyword, count);
1390
25.8M
      break;
1391
341
    case SOC_IDENTITYAGENT:
1392
341
      p = ssh_config_get_str_tok(&s, NULL);
1393
341
      if (p && *parsing) {
1394
1
          ssh_options_set(session, SSH_OPTIONS_IDENTITY_AGENT, p);
1395
1
      }
1396
341
      break;
1397
30
    case SOC_IDENTITIESONLY:
1398
30
      i = ssh_config_get_yesno(&s, -1);
1399
30
      if (i >= 0 && *parsing) {
1400
1
        bool b = i;
1401
1
        ssh_options_set(session, SSH_OPTIONS_IDENTITIES_ONLY, &b);
1402
1
      }
1403
30
      break;
1404
0
    case SOC_CONTROLMASTER:
1405
0
      p = ssh_config_get_str_tok(&s, NULL);
1406
0
      if (p && *parsing) {
1407
0
          int value = -1;
1408
1409
0
          if (strcasecmp(p, "auto") == 0) {
1410
0
              value = SSH_CONTROL_MASTER_AUTO;
1411
0
          } else if (strcasecmp(p, "yes") == 0) {
1412
0
              value = SSH_CONTROL_MASTER_YES;
1413
0
          } else if (strcasecmp(p, "no") == 0) {
1414
0
              value = SSH_CONTROL_MASTER_NO;
1415
0
          } else if (strcasecmp(p, "autoask") == 0) {
1416
0
              value = SSH_CONTROL_MASTER_AUTOASK;
1417
0
          } else if (strcasecmp(p, "ask") == 0) {
1418
0
              value = SSH_CONTROL_MASTER_ASK;
1419
0
          }
1420
1421
0
          if (value != -1) {
1422
0
              ssh_options_set(session, SSH_OPTIONS_CONTROL_MASTER, &value);
1423
0
          }
1424
0
      }
1425
0
      break;
1426
0
    case SOC_CONTROLPATH:
1427
0
      p = ssh_config_get_str_tok(&s, NULL);
1428
0
      if (p == NULL) {
1429
0
        SAFE_FREE(x);
1430
0
        return -1;
1431
0
      }
1432
0
      if (*parsing) {
1433
0
          ssh_options_set(session, SSH_OPTIONS_CONTROL_PATH, p);
1434
0
      }
1435
0
      break;
1436
1.63k
    case SOC_CERTIFICATE:
1437
1.63k
        p = ssh_config_get_str_tok(&s, NULL);
1438
1.63k
        if (p && *parsing) {
1439
1.52k
            ssh_options_set(session, SSH_OPTIONS_CERTIFICATE, p);
1440
1.52k
        }
1441
1.63k
        break;
1442
0
    default:
1443
0
      ssh_set_error(session, SSH_FATAL, "ERROR - unimplemented opcode: %d",
1444
0
              opcode);
1445
0
      SAFE_FREE(x);
1446
0
      return -1;
1447
0
      break;
1448
25.8M
  }
1449
1450
25.8M
  SAFE_FREE(x);
1451
25.8M
  return 0;
1452
25.8M
}
1453
1454
/* @brief Parse configuration from a file pointer
1455
 *
1456
 * @params[in] session   The ssh session
1457
 * @params[in] fp        A valid file pointer
1458
 * @params[in] global    Whether the config is global or not
1459
 *
1460
 * @returns    0 on successful parsing the configuration file, -1 on error
1461
 */
1462
int ssh_config_parse(ssh_session session, FILE *fp, bool global)
1463
0
{
1464
0
    char line[MAX_LINE_SIZE] = {0};
1465
0
    unsigned int count = 0;
1466
0
    int parsing, rv;
1467
1468
0
    parsing = 1;
1469
0
    while (fgets(line, sizeof(line), fp)) {
1470
0
        count++;
1471
0
        rv = ssh_config_parse_line(session, line, count, &parsing, 0, global);
1472
0
        if (rv < 0) {
1473
0
            return -1;
1474
0
        }
1475
0
    }
1476
1477
0
    return 0;
1478
0
}
1479
1480
/* @brief Parse configuration file and set the options to the given session
1481
 *
1482
 * @params[in] session   The ssh session
1483
 * @params[in] filename  The path to the ssh configuration file
1484
 *
1485
 * @returns    0 on successful parsing the configuration file, -1 on error
1486
 */
1487
int ssh_config_parse_file(ssh_session session, const char *filename)
1488
0
{
1489
0
    FILE *fp = NULL;
1490
0
    int rv;
1491
0
    bool global = 0;
1492
1493
0
    fp = fopen(filename, "r");
1494
0
    if (fp == NULL) {
1495
0
        return 0;
1496
0
    }
1497
1498
0
    rv = strcmp(filename, GLOBAL_CLIENT_CONFIG);
1499
#ifdef USR_GLOBAL_CLIENT_CONFIG
1500
    if (rv != 0) {
1501
        rv = strcmp(filename, USR_GLOBAL_CLIENT_CONFIG);
1502
    }
1503
#endif
1504
1505
0
    if (rv == 0) {
1506
0
        global = true;
1507
0
    }
1508
1509
0
    SSH_LOG(SSH_LOG_PACKET, "Reading configuration data from %s", filename);
1510
1511
0
    rv = ssh_config_parse(session, fp, global);
1512
1513
0
    fclose(fp);
1514
0
    return rv;
1515
0
}
1516
1517
/* @brief Parse configuration string and set the options to the given session
1518
 *
1519
 * @params[in] session   The ssh session
1520
 * @params[in] input     Null terminated string containing the configuration
1521
 *
1522
 * @returns    SSH_OK on successful parsing the configuration string,
1523
 *             SSH_ERROR on error
1524
 */
1525
int ssh_config_parse_string(ssh_session session, const char *input)
1526
507
{
1527
507
    char line[MAX_LINE_SIZE] = {0};
1528
507
    const char *c = input, *line_start = input;
1529
507
    unsigned int line_num = 0;
1530
507
    size_t line_len;
1531
507
    int parsing, rv;
1532
1533
507
    SSH_LOG(SSH_LOG_DEBUG, "Reading configuration data from string:");
1534
507
    SSH_LOG(SSH_LOG_DEBUG, "START\n%s\nEND", input);
1535
1536
507
    parsing = 1;
1537
25.5k
    while (1) {
1538
25.5k
        line_num++;
1539
25.5k
        line_start = c;
1540
25.5k
        c = strchr(line_start, '\n');
1541
25.5k
        if (c == NULL) {
1542
            /* if there is no newline at the end of the string */
1543
498
            c = strchr(line_start, '\0');
1544
498
        }
1545
25.5k
        if (c == NULL) {
1546
            /* should not happen, would mean a string without trailing '\0' */
1547
0
            SSH_LOG(SSH_LOG_TRACE, "No trailing '\\0' in config string");
1548
0
            return SSH_ERROR;
1549
0
        }
1550
25.5k
        line_len = c - line_start;
1551
25.5k
        if (line_len > MAX_LINE_SIZE - 1) {
1552
9
            SSH_LOG(SSH_LOG_TRACE,
1553
9
                    "Line %u too long: %zu characters",
1554
9
                    line_num,
1555
9
                    line_len);
1556
9
            return SSH_ERROR;
1557
9
        }
1558
25.5k
        memcpy(line, line_start, line_len);
1559
25.5k
        line[line_len] = '\0';
1560
25.5k
        SSH_LOG(SSH_LOG_DEBUG, "Line %u: %s", line_num, line);
1561
25.5k
        rv = ssh_config_parse_line(session, line, line_num, &parsing, 0, false);
1562
25.5k
        if (rv < 0) {
1563
57
            return SSH_ERROR;
1564
57
        }
1565
25.5k
        if (*c == '\0') {
1566
441
            break;
1567
441
        }
1568
25.0k
        c++;
1569
25.0k
    }
1570
1571
441
    return SSH_OK;
1572
507
}