Coverage Report

Created: 2026-06-10 06:26

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/libssh/src/config.c
Line
Count
Source
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
0
#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
  bool cli_supported;
64
};
65
66
static struct ssh_config_keyword_table_s ssh_config_keyword_table[] = {
67
    {"host", SOC_HOST, true},
68
    {"match", SOC_MATCH, false},
69
    {"hostname", SOC_HOSTNAME, true},
70
    {"port", SOC_PORT, true},
71
    {"user", SOC_USERNAME, true},
72
    {"identityfile", SOC_IDENTITY, true},
73
    {"ciphers", SOC_CIPHERS, true},
74
    {"macs", SOC_MACS, true},
75
    {"compression", SOC_COMPRESSION, true},
76
    {"connecttimeout", SOC_TIMEOUT, true},
77
    {"stricthostkeychecking", SOC_STRICTHOSTKEYCHECK, true},
78
    {"userknownhostsfile", SOC_KNOWNHOSTS, true},
79
    {"proxycommand", SOC_PROXYCOMMAND, true},
80
    {"gssapiserveridentity", SOC_GSSAPISERVERIDENTITY, false},
81
    {"gssapiclientidentity", SOC_GSSAPICLIENTIDENTITY, false},
82
    {"gssapidelegatecredentials", SOC_GSSAPIDELEGATECREDENTIALS, true},
83
    {"include", SOC_INCLUDE, true},
84
    {"bindaddress", SOC_BINDADDRESS, true},
85
    {"globalknownhostsfile", SOC_GLOBALKNOWNHOSTSFILE, true},
86
    {"loglevel", SOC_LOGLEVEL, true},
87
    {"hostkeyalgorithms", SOC_HOSTKEYALGORITHMS, true},
88
    {"kexalgorithms", SOC_KEXALGORITHMS, true},
89
    {"gssapiauthentication", SOC_GSSAPIAUTHENTICATION, true},
90
    {"kbdinteractiveauthentication", SOC_KBDINTERACTIVEAUTHENTICATION, true},
91
    {"challengeresponseauthentication", SOC_KBDINTERACTIVEAUTHENTICATION, true},
92
    {"passwordauthentication", SOC_PASSWORDAUTHENTICATION, true},
93
    {"pubkeyauthentication", SOC_PUBKEYAUTHENTICATION, true},
94
    {"addkeystoagent", SOC_UNSUPPORTED, true},
95
    {"addressfamily", SOC_ADDRESSFAMILY, true},
96
    {"batchmode", SOC_BATCHMODE, true},
97
    {"canonicaldomains", SOC_UNSUPPORTED, true},
98
    {"canonicalizefallbacklocal", SOC_UNSUPPORTED, true},
99
    {"canonicalizehostname", SOC_UNSUPPORTED, true},
100
    {"canonicalizemaxdots", SOC_UNSUPPORTED, true},
101
    {"canonicalizepermittedcnames", SOC_UNSUPPORTED, true},
102
    {"certificatefile", SOC_CERTIFICATE, true},
103
    {"checkhostip", SOC_UNSUPPORTED, true},
104
    {"connectionattempts", SOC_UNSUPPORTED, true},
105
    {"enablesshkeysign", SOC_UNSUPPORTED, true},
106
    {"fingerprinthash", SOC_UNSUPPORTED, true},
107
    {"forwardagent", SOC_UNSUPPORTED, true},
108
    {"hashknownhosts", SOC_UNSUPPORTED, true},
109
    {"hostbasedauthentication", SOC_UNSUPPORTED, true},
110
    {"hostbasedacceptedalgorithms", SOC_UNSUPPORTED, true},
111
    {"hostkeyalias", SOC_UNSUPPORTED, true},
112
    {"identitiesonly", SOC_IDENTITIESONLY, true},
113
    {"identityagent", SOC_IDENTITYAGENT, true},
114
    {"ipqos", SOC_UNSUPPORTED, true},
115
    {"kbdinteractivedevices", SOC_UNSUPPORTED, true},
116
    {"nohostauthenticationforlocalhost", SOC_UNSUPPORTED, true},
117
    {"numberofpasswordprompts", SOC_NUMBER_OF_PASSWORD_PROMPTS, true},
118
    {"pkcs11provider", SOC_UNSUPPORTED, true},
119
    {"preferredauthentications", SOC_PREFERRED_AUTHENTICATIONS, true},
120
    {"proxyjump", SOC_PROXYJUMP, true},
121
    {"proxyusefdpass", SOC_UNSUPPORTED, true},
122
    {"pubkeyacceptedalgorithms", SOC_PUBKEYACCEPTEDKEYTYPES, true},
123
    {"rekeylimit", SOC_REKEYLIMIT, true},
124
    {"remotecommand", SOC_UNSUPPORTED, true},
125
    {"revokedhostkeys", SOC_UNSUPPORTED, true},
126
    {"serveralivecountmax", SOC_UNSUPPORTED, true},
127
    {"serveraliveinterval", SOC_UNSUPPORTED, true},
128
    {"streamlocalbindmask", SOC_UNSUPPORTED, true},
129
    {"streamlocalbindunlink", SOC_UNSUPPORTED, true},
130
    {"syslogfacility", SOC_UNSUPPORTED, true},
131
    {"tcpkeepalive", SOC_UNSUPPORTED, true},
132
    {"updatehostkeys", SOC_UNSUPPORTED, true},
133
    {"verifyhostkeydns", SOC_UNSUPPORTED, true},
134
    {"visualhostkey", SOC_UNSUPPORTED, true},
135
    {"clearallforwardings", SOC_NA, true},
136
    {"controlmaster", SOC_NA, true},
137
    {"controlpersist", SOC_NA, true},
138
    {"controlpath", SOC_NA, true},
139
    {"dynamicforward", SOC_NA, true},
140
    {"escapechar", SOC_NA, true},
141
    {"exitonforwardfailure", SOC_NA, true},
142
    {"forwardx11", SOC_NA, true},
143
    {"forwardx11timeout", SOC_NA, true},
144
    {"forwardx11trusted", SOC_NA, true},
145
    {"gatewayports", SOC_NA, true},
146
    {"ignoreunknown", SOC_NA, true},
147
    {"localcommand", SOC_NA, true},
148
    {"localforward", SOC_NA, true},
149
    {"permitlocalcommand", SOC_NA, true},
150
    {"remoteforward", SOC_NA, true},
151
    {"requesttty", SOC_REQUEST_TTY, true},
152
    {"sendenv", SOC_NA, true},
153
    {"tunnel", SOC_NA, true},
154
    {"tunneldevice", SOC_NA, true},
155
    {"xauthlocation", SOC_NA, true},
156
    {"pubkeyacceptedkeytypes", SOC_PUBKEYACCEPTEDKEYTYPES, true},
157
    {"requiredrsasize", SOC_REQUIRED_RSA_SIZE, true},
158
    {"gssapikeyexchange", SOC_GSSAPIKEYEXCHANGE, true},
159
    {"gssapikexalgorithms", SOC_GSSAPIKEXALGORITHMS, true},
160
    {"tag", SOC_TAG, true},
161
    {NULL, SOC_UNKNOWN, false},
162
};
163
164
enum ssh_config_match_e {
165
    MATCH_UNKNOWN = -1,
166
    MATCH_ALL,
167
    MATCH_FINAL,
168
    MATCH_CANONICAL,
169
    MATCH_EXEC,
170
    MATCH_HOST,
171
    MATCH_ORIGINALHOST,
172
    MATCH_USER,
173
    MATCH_LOCALUSER,
174
    MATCH_LOCALNETWORK,
175
    MATCH_VERSION,
176
    MATCH_TAGGED,
177
};
178
179
struct ssh_config_match_keyword_table_s {
180
    const char *name;
181
    enum ssh_config_match_e opcode;
182
};
183
184
static struct ssh_config_match_keyword_table_s
185
    ssh_config_match_keyword_table[] = {
186
        {"all", MATCH_ALL},
187
        {"canonical", MATCH_CANONICAL},
188
        {"final", MATCH_FINAL},
189
        {"exec", MATCH_EXEC},
190
        {"host", MATCH_HOST},
191
        {"originalhost", MATCH_ORIGINALHOST},
192
        {"user", MATCH_USER},
193
        {"localuser", MATCH_LOCALUSER},
194
        {"localnetwork", MATCH_LOCALNETWORK},
195
        {"version", MATCH_VERSION},
196
        {"tagged", MATCH_TAGGED},
197
        {NULL, MATCH_UNKNOWN},
198
};
199
200
int ssh_config_parse_line(ssh_session session,
201
                          const char *line,
202
                          unsigned int count,
203
                          int *parsing,
204
                          unsigned int depth,
205
                          bool global);
206
207
static int ssh_config_parse_line_internal(ssh_session session,
208
                                          const char *line,
209
                                          unsigned int count,
210
                                          int *parsing,
211
                                          unsigned int depth,
212
                                          bool global,
213
                                          bool is_cli,
214
                                          bool fail_on_unknown);
215
216
int ssh_config_parse_line_cli(ssh_session session, const char *line);
217
218
enum ssh_config_opcode_e ssh_config_get_opcode(char *keyword)
219
0
{
220
0
    int i;
221
222
0
    for (i = 0; ssh_config_keyword_table[i].name != NULL; i++) {
223
0
        if (strcasecmp(keyword, ssh_config_keyword_table[i].name) == 0) {
224
0
            return ssh_config_keyword_table[i].opcode;
225
0
        }
226
0
    }
227
228
0
    return SOC_UNKNOWN;
229
0
}
230
231
static bool ssh_config_is_cli_supported(enum ssh_config_opcode_e opcode)
232
0
{
233
0
    int i;
234
235
0
    for (i = 0; ssh_config_keyword_table[i].name != NULL; i++) {
236
0
        if (opcode == ssh_config_keyword_table[i].opcode) {
237
0
            return ssh_config_keyword_table[i].cli_supported;
238
0
        }
239
0
    }
240
241
0
    return false;
242
0
}
243
244
0
#define LIBSSH_CONF_MAX_DEPTH 16
245
static void
246
local_parse_file(ssh_session session,
247
                 const char *filename,
248
                 int *parsing,
249
                 unsigned int depth,
250
                 bool global)
251
0
{
252
0
    FILE *f = NULL;
253
0
    char line[MAX_LINE_SIZE] = {0};
254
0
    unsigned int count = 0;
255
0
    int rv;
256
257
0
    if (depth > LIBSSH_CONF_MAX_DEPTH) {
258
0
        ssh_set_error(session, SSH_FATAL,
259
0
                      "ERROR - Too many levels of configuration includes "
260
0
                      "when processing file '%s'", filename);
261
0
        return;
262
0
    }
263
264
0
    f = ssh_strict_fopen(filename, SSH_MAX_CONFIG_FILE_SIZE);
265
0
    if (f == NULL) {
266
0
        SSH_LOG(SSH_LOG_RARE,
267
0
                "Failed to open included configuration file %s",
268
0
                filename);
269
0
        return;
270
0
    }
271
272
0
    SSH_LOG(SSH_LOG_PACKET, "Reading additional configuration data from %s", filename);
273
0
    while (fgets(line, sizeof(line), f)) {
274
0
        count++;
275
0
        rv = ssh_config_parse_line(session, line, count, parsing, depth, global);
276
0
        if (rv < 0) {
277
0
            fclose(f);
278
0
            return;
279
0
        }
280
0
    }
281
282
0
    fclose(f);
283
0
    return;
284
0
}
285
286
#if defined(HAVE_GLOB) && defined(HAVE_GLOB_GL_FLAGS_MEMBER)
287
static void local_parse_glob(ssh_session session,
288
                             const char *fileglob,
289
                             int *parsing,
290
                             unsigned int depth,
291
                             bool global)
292
0
{
293
0
    glob_t globbuf = {
294
0
        .gl_flags = 0,
295
0
    };
296
0
    int rt;
297
0
    size_t i;
298
299
0
    rt = glob(fileglob, GLOB_TILDE, NULL, &globbuf);
300
0
    if (rt == GLOB_NOMATCH) {
301
0
        globfree(&globbuf);
302
0
        return;
303
0
    } else if (rt != 0) {
304
0
        SSH_LOG(SSH_LOG_RARE, "Glob error: %s",
305
0
                fileglob);
306
0
        globfree(&globbuf);
307
0
        return;
308
0
    }
309
310
0
    for (i = 0; i < globbuf.gl_pathc; i++) {
311
0
        local_parse_file(session, globbuf.gl_pathv[i], parsing, depth, global);
312
0
    }
313
314
0
    globfree(&globbuf);
315
0
}
316
#endif /* HAVE_GLOB HAVE_GLOB_GL_FLAGS_MEMBER */
317
318
static enum ssh_config_match_e
319
ssh_config_get_match_opcode(const char *keyword)
320
0
{
321
0
    size_t i;
322
323
0
    for (i = 0; ssh_config_match_keyword_table[i].name != NULL; i++) {
324
0
        if (strcasecmp(keyword, ssh_config_match_keyword_table[i].name) == 0) {
325
0
            return ssh_config_match_keyword_table[i].opcode;
326
0
        }
327
0
    }
328
329
0
    return MATCH_UNKNOWN;
330
0
}
331
332
/**
333
 * @brief Convert a raw Match predicate result to the final boolean outcome.
334
 *
335
 * @param ok      Raw predicate result where a positive value means a match and
336
 *                zero or a negative value means no match.
337
 * @param negate  Whether the Match predicate was prefixed with '!'.
338
 *
339
 * @return 1 if the predicate matches after applying negation, 0 otherwise.
340
 */
341
static inline int ssh_config_match_result(int ok, bool negate)
342
0
{
343
0
    if (ok <= 0 && negate == true) {
344
0
        return 1;
345
0
    }
346
0
    if (ok > 0 && negate == false) {
347
0
        return 1;
348
0
    }
349
350
0
    return 0;
351
0
}
352
353
/**
354
 * @brief Evaluate a Match predicate against a value and pattern list.
355
 *
356
 * @param value    Value to match.
357
 * @param pattern  Pattern list used for comparison.
358
 * @param negate   Whether the Match predicate was prefixed with '!'.
359
 *
360
 * @return 1 if the predicate matches after applying negation, 0 otherwise.
361
 */
362
static int ssh_config_match(const char *value, const char *pattern, bool negate)
363
0
{
364
0
    int ok, result = 0;
365
366
0
    ok = match_pattern_list(value, pattern, strlen(pattern), 0);
367
0
    result = ssh_config_match_result(ok, negate);
368
0
    SSH_LOG(SSH_LOG_TRACE, "%s '%s' against pattern '%s'%s (ok=%d)",
369
0
            result == 1 ? "Matched" : "Not matched", value, pattern,
370
0
            negate == true ? " (negated)" : "", ok);
371
0
    return result;
372
0
}
373
374
/**
375
 * @brief Evaluate a Match tagged predicate against the current session tag.
376
 *
377
 * @param value    Current session tag. NULL is treated as an unset tag.
378
 * @param pattern  Pattern to compare against the current tag.
379
 * @param negate   Whether the Match predicate was prefixed with '!'.
380
 *
381
 * @return 1 if the predicate matches after applying negation, 0 otherwise.
382
 */
383
static int
384
ssh_config_match_tagged(const char *value, const char *pattern, bool negate)
385
0
{
386
0
    const char *tag = value != NULL ? value : "";
387
0
    int ok, result = 0;
388
0
    size_t pattern_len;
389
390
0
    if (tag[0] == '\0') {
391
        /* Match tagged treats an explicit empty pattern as "match unset tag".
392
         * The generic match_pattern_list() should still treat empty patterns
393
         * as no match for other predicates.
394
         */
395
0
        ok = (pattern[0] == '\0');
396
0
    } else {
397
0
        pattern_len = strlen(pattern);
398
0
        ok = match_pattern_list(tag, pattern, pattern_len, 0) > 0;
399
0
    }
400
401
0
    result = ssh_config_match_result(ok, negate);
402
0
    SSH_LOG(SSH_LOG_TRACE,
403
0
            "%s tag '%s' against pattern '%s'%s (ok=%d)",
404
0
            result == 1 ? "Matched" : "Not matched",
405
0
            tag,
406
0
            pattern,
407
0
            negate == true ? " (negated)" : "",
408
0
            ok);
409
0
    return result;
410
0
}
411
412
#ifdef WITH_EXEC
413
/* FIXME reuse the ssh_execute_command() from socket.c */
414
static int
415
ssh_exec_shell(char *cmd)
416
{
417
    char *shell = NULL;
418
    pid_t pid;
419
    int status, devnull, rc;
420
421
    shell = getenv("SHELL");
422
    if (shell == NULL || shell[0] == '\0') {
423
        shell = (char *)"/bin/sh";
424
    }
425
426
    rc = access(shell, X_OK);
427
    if (rc != 0) {
428
        SSH_LOG(SSH_LOG_WARN, "The shell '%s' is not executable", shell);
429
        return -1;
430
    }
431
432
    /* Need this to redirect subprocess stdin/out */
433
    devnull = open("/dev/null", O_RDWR);
434
    if (devnull == -1) {
435
        SSH_LOG_STRERROR(SSH_LOG_WARN, errno, "Failed to open(/dev/null): %s");
436
        return -1;
437
    }
438
439
    SSH_LOG(SSH_LOG_DEBUG, "Running command '%s'", cmd);
440
    pid = fork();
441
    if (pid == 0) { /* Child */
442
        char *argv[4];
443
444
        /* Redirect child stdin and stdout. Leave stderr */
445
        rc = dup2(devnull, STDIN_FILENO);
446
        if (rc == -1) {
447
            SSH_LOG_STRERROR(SSH_LOG_WARN, errno, "dup2: %s");
448
            exit(1);
449
        }
450
        rc = dup2(devnull, STDOUT_FILENO);
451
        if (rc == -1) {
452
            SSH_LOG_STRERROR(SSH_LOG_WARN, errno, "dup2: %s");
453
            exit(1);
454
        }
455
        if (devnull > STDERR_FILENO) {
456
            close(devnull);
457
        }
458
459
        argv[0] = shell;
460
        argv[1] = (char *) "-c";
461
        argv[2] = strdup(cmd);
462
        argv[3] = NULL;
463
464
        rc = execv(argv[0], argv);
465
        if (rc == -1) {
466
            SSH_LOG_STRERROR(SSH_LOG_WARN,
467
                             errno,
468
                             "Failed to execute command '%s': %s",
469
                             cmd);
470
            /* Die with signal to make this error apparent to parent. */
471
            signal(SIGTERM, SIG_DFL);
472
            kill(getpid(), SIGTERM);
473
            _exit(1);
474
        }
475
    }
476
477
    /* Parent */
478
    close(devnull);
479
    if (pid == -1) { /* Error */
480
        SSH_LOG_STRERROR(SSH_LOG_WARN, errno, "Failed to fork child: %s");
481
        return -1;
482
483
    }
484
485
    while (waitpid(pid, &status, 0) == -1) {
486
        if (errno != EINTR) {
487
            SSH_LOG_STRERROR(SSH_LOG_WARN, errno, "waitpid failed: %s");
488
            return -1;
489
        }
490
    }
491
    if (!WIFEXITED(status)) {
492
        SSH_LOG(SSH_LOG_WARN, "Command %s exited abnormally", cmd);
493
        return -1;
494
    }
495
    SSH_LOG(SSH_LOG_TRACE, "Command '%s' returned %d", cmd, WEXITSTATUS(status));
496
    return WEXITSTATUS(status);
497
}
498
499
static int
500
ssh_match_exec(ssh_session session, const char *command, bool negate)
501
{
502
    int rv, result = 0;
503
    char *cmd = NULL;
504
505
    /* TODO There should be more supported expansions */
506
    cmd = ssh_path_expand_escape(session, command);
507
    if (cmd == NULL) {
508
        return 0;
509
    }
510
    rv = ssh_exec_shell(cmd);
511
    if (rv > 0 && negate == true) {
512
        result = 1;
513
    } else if (rv == 0 && negate == false) {
514
        result = 1;
515
    }
516
    SSH_LOG(SSH_LOG_TRACE, "%s 'exec' command '%s'%s (rv=%d)",
517
            result == 1 ? "Matched" : "Not matched", cmd,
518
            negate == true ? " (negated)" : "", rv);
519
    free(cmd);
520
    return result;
521
}
522
#else
523
static int
524
ssh_match_exec(ssh_session session, const char *command, bool negate)
525
0
{
526
0
    (void)session;
527
0
    (void)command;
528
0
    (void)negate;
529
530
0
    SSH_LOG(SSH_LOG_TRACE,
531
0
            "Unsupported 'exec' command on Windows '%s'",
532
0
            command);
533
0
    return 0;
534
0
}
535
#endif /* WITH_EXEC */
536
537
/*
538
 * HostName recognizes %% and %h during config parsing. Other %X sequences are
539
 * left for deferred HostName expansion, and only a trailing bare '%' is
540
 * rejected here.
541
 */
542
static int ssh_config_scan_hostname_tokens(ssh_session session,
543
                                           const char *hostname,
544
                                           bool *needs_host,
545
                                           bool *has_unknown)
546
0
{
547
0
    const char *p = NULL;
548
549
0
    if (needs_host != NULL) {
550
0
        *needs_host = false;
551
0
    }
552
0
    if (has_unknown != NULL) {
553
0
        *has_unknown = false;
554
0
    }
555
556
0
    if (hostname == NULL) {
557
0
        ssh_set_error(session,
558
0
                      SSH_FATAL,
559
0
                      "Cannot scan HostName tokens from NULL input");
560
0
        return -1;
561
0
    }
562
563
0
    for (p = hostname; *p != '\0'; p++) {
564
0
        if (*p == '%') {
565
0
            if (p[1] == '\0') {
566
0
                ssh_set_error(session, SSH_FATAL, "Incomplete Hostname token");
567
0
                return -1;
568
0
            }
569
0
            switch (p[1]) {
570
0
            case '%':
571
0
                p++;
572
0
                continue;
573
0
            case 'h':
574
0
                if (needs_host != NULL) {
575
0
                    *needs_host = true;
576
0
                }
577
0
                p++;
578
0
                continue;
579
0
            default:
580
0
                if (has_unknown != NULL) {
581
0
                    *has_unknown = true;
582
0
                }
583
0
                p++;
584
0
                continue;
585
0
            }
586
0
        }
587
0
    }
588
589
0
    return 0;
590
0
}
591
592
static char *ssh_config_lowercase_hostname_pattern(const char *hostname)
593
0
{
594
0
    char *pattern = NULL;
595
0
    char *p = NULL;
596
0
    bool escape = false;
597
598
0
    if (hostname == NULL) {
599
0
        return NULL;
600
0
    }
601
602
0
    pattern = strdup(hostname);
603
0
    if (pattern == NULL) {
604
0
        return NULL;
605
0
    }
606
607
0
    for (p = pattern; *p != '\0'; p++) {
608
0
        if (escape) {
609
0
            escape = false;
610
0
            continue;
611
0
        }
612
0
        if (*p == '%') {
613
0
            escape = true;
614
0
            continue;
615
0
        }
616
0
        *p = tolower((unsigned char)*p);
617
0
    }
618
619
0
    return pattern;
620
0
}
621
622
/**
623
 * @brief: Parse the ProxyJump configuration line and if parsing,
624
 * stores the result in the configuration option
625
 *
626
 * @param[in]   session    The ssh session
627
 * @param[in]   s          The string to be parsed.
628
 * @param[in]   do_parsing Whether to parse or not.
629
 *
630
 * @returns     SSH_OK if the provided string is formatted and parsed correctly
631
 *              SSH_ERROR on failure
632
 */
633
int
634
ssh_config_parse_proxy_jump(ssh_session session, const char *s, bool do_parsing)
635
0
{
636
0
    char *c = NULL, *cp = NULL, *endp = NULL;
637
0
    char *username = NULL;
638
0
    char *hostname = NULL;
639
0
    char *port = NULL;
640
0
    char *next = NULL;
641
0
    int cmp, rv = SSH_ERROR;
642
0
    struct ssh_jump_info_struct *jump_host = NULL;
643
0
    bool parse_entry = do_parsing;
644
0
    bool libssh_proxy_jump = ssh_libssh_proxy_jumps();
645
646
0
    if (do_parsing) {
647
0
        SAFE_FREE(session->opts.proxy_jumps_str);
648
0
        ssh_proxyjumps_free(session->opts.proxy_jumps);
649
0
    }
650
    /* Special value none disables the proxy */
651
0
    cmp = strcasecmp(s, "none");
652
0
    if (cmp == 0) {
653
0
        if (!libssh_proxy_jump && do_parsing) {
654
0
            ssh_options_set(session, SSH_OPTIONS_PROXYCOMMAND, s);
655
0
        }
656
0
        return SSH_OK;
657
0
    }
658
659
    /* This is comma-separated list of [user@]host[:port] entries */
660
0
    c = strdup(s);
661
0
    if (c == NULL) {
662
0
        ssh_set_error_oom(session);
663
0
        return SSH_ERROR;
664
0
    }
665
666
0
    if (do_parsing) {
667
        /* Store the whole string in session */
668
0
        SAFE_FREE(session->opts.proxy_jumps_str);
669
0
        session->opts.proxy_jumps_str = strdup(s);
670
0
        if (session->opts.proxy_jumps_str == NULL) {
671
0
            free(c);
672
0
            ssh_set_error_oom(session);
673
0
            return SSH_ERROR;
674
0
        }
675
0
    }
676
677
0
    cp = c;
678
0
    do {
679
0
        endp = strchr(cp, ',');
680
0
        if (endp != NULL) {
681
            /* Split out the token */
682
0
            *endp = '\0';
683
0
        }
684
0
        if (parse_entry && libssh_proxy_jump) {
685
0
            jump_host = calloc(1, sizeof(struct ssh_jump_info_struct));
686
0
            if (jump_host == NULL) {
687
0
                ssh_set_error_oom(session);
688
0
                rv = SSH_ERROR;
689
0
                goto out;
690
0
            }
691
692
0
            rv = ssh_config_parse_uri(cp,
693
0
                                      &jump_host->username,
694
0
                                      &jump_host->hostname,
695
0
                                      &port,
696
0
                                      false,
697
0
                                      false);
698
0
            if (rv != SSH_OK) {
699
0
                ssh_set_error_invalid(session);
700
0
                SAFE_FREE(jump_host);
701
0
                goto out;
702
0
            }
703
0
            if (port == NULL) {
704
0
                jump_host->port = 22;
705
0
            } else {
706
0
                jump_host->port = strtol(port, NULL, 10);
707
0
                SAFE_FREE(port);
708
0
            }
709
710
            /* Prepend because we will recursively proxy jump */
711
0
            rv = ssh_list_prepend(session->opts.proxy_jumps, jump_host);
712
0
            if (rv != SSH_OK) {
713
0
                ssh_set_error_oom(session);
714
0
                SAFE_FREE(jump_host);
715
0
                goto out;
716
0
            }
717
0
        } else if (parse_entry) {
718
            /* We actually care only about the first item */
719
0
            rv = ssh_config_parse_uri(cp,
720
0
                                      &username,
721
0
                                      &hostname,
722
0
                                      &port,
723
0
                                      false,
724
0
                                      false);
725
0
            if (rv != SSH_OK) {
726
0
                ssh_set_error_invalid(session);
727
0
                goto out;
728
0
            }
729
            /* The rest of the list needs to be passed on */
730
0
            if (endp != NULL) {
731
0
                next = strdup(endp + 1);
732
0
                if (next == NULL) {
733
0
                    ssh_set_error_oom(session);
734
0
                    rv = SSH_ERROR;
735
0
                    goto out;
736
0
                }
737
0
            }
738
0
        } else {
739
            /* The rest is just sanity-checked to avoid failures later */
740
0
            rv = ssh_config_parse_uri(cp, NULL, NULL, NULL, false, false);
741
0
            if (rv != SSH_OK) {
742
0
                ssh_set_error_invalid(session);
743
0
                goto out;
744
0
            }
745
0
        }
746
0
        if (!libssh_proxy_jump) {
747
0
            parse_entry = 0;
748
0
        }
749
0
        if (endp != NULL) {
750
0
            cp = endp + 1;
751
0
        } else {
752
0
            cp = NULL; /* end */
753
0
        }
754
0
    } while (cp != NULL);
755
756
0
    if (!libssh_proxy_jump && hostname != NULL && do_parsing) {
757
0
        char com[512] = {0};
758
759
0
        rv = snprintf(com, sizeof(com), "ssh%s%s%s%s%s%s -W '[%%h]:%%p' %s",
760
0
                      username ? " -l " : "",
761
0
                      username ? username : "",
762
0
                      port ? " -p " : "",
763
0
                      port ? port : "",
764
0
                      next ? " -J " : "",
765
0
                      next ? next : "",
766
0
                      hostname);
767
0
        if (rv < 0 || rv >= (int)sizeof(com)) {
768
0
            SSH_LOG(SSH_LOG_TRACE, "Too long ProxyJump configuration line");
769
0
            rv = SSH_ERROR;
770
0
            goto out;
771
0
        }
772
0
        rv = ssh_options_set(session, SSH_OPTIONS_PROXYCOMMAND, com);
773
0
        if (rv != SSH_OK) {
774
0
            ssh_set_error_oom(session);
775
0
            goto out;
776
0
        }
777
0
    }
778
779
0
    rv = SSH_OK;
780
781
0
out:
782
0
    if (rv != SSH_OK) {
783
0
        ssh_proxyjumps_free(session->opts.proxy_jumps);
784
0
    }
785
0
    SAFE_FREE(username);
786
0
    SAFE_FREE(hostname);
787
0
    SAFE_FREE(port);
788
0
    SAFE_FREE(next);
789
0
    SAFE_FREE(c);
790
0
    return rv;
791
0
}
792
793
static char *
794
ssh_config_make_absolute(ssh_session session,
795
                         const char *path,
796
                         bool global)
797
0
{
798
0
    size_t outlen = 0;
799
0
    char *out = NULL;
800
0
    int rv;
801
802
    /* Looks like absolute path */
803
0
    if (path[0] == '/') {
804
0
        return strdup(path);
805
0
    }
806
807
    /* relative path */
808
0
    if (global) {
809
        /* Parsing global config */
810
0
        outlen = strlen(path) + strlen("/etc/ssh/") + 1;
811
0
        out = malloc(outlen);
812
0
        if (out == NULL) {
813
0
            ssh_set_error_oom(session);
814
0
            return NULL;
815
0
        }
816
0
        rv = snprintf(out, outlen, "/etc/ssh/%s", path);
817
0
        if (rv < 1) {
818
0
            free(out);
819
0
            return NULL;
820
0
        }
821
0
        return out;
822
0
    }
823
824
    /* paths starting with tilde are already absolute */
825
0
    if (path[0] == '~') {
826
0
        return ssh_path_expand_tilde(path);
827
0
    }
828
829
    /* Parsing user config relative to home directory (generally ~/.ssh) */
830
0
    if (session->opts.sshdir == NULL) {
831
0
        ssh_set_error_invalid(session);
832
0
        return NULL;
833
0
    }
834
0
    outlen = strlen(path) + strlen(session->opts.sshdir) + 1 + 1;
835
0
    out = malloc(outlen);
836
0
    if (out == NULL) {
837
0
        ssh_set_error_oom(session);
838
0
        return NULL;
839
0
    }
840
0
    rv = snprintf(out, outlen, "%s/%s", session->opts.sshdir, path);
841
0
    if (rv < 1) {
842
0
        free(out);
843
0
        return NULL;
844
0
    }
845
0
    return out;
846
0
}
847
848
#ifdef HAVE_IFADDRS_H
849
/**
850
 * @brief Checks if host address matches the local network specified.
851
 *
852
 * Verify whether a local network interface address matches any of the CIDR
853
 * patterns.
854
 *
855
 * @param addrlist The CIDR pattern-list to be checked, can contain both
856
 *                 IPv4 and IPv6 addresses and has to be comma separated
857
 *                 (',' only, space after comma not allowed).
858
 *
859
 * @param negate   The negate condition. The return value is negated
860
 *                 (returns 1 instead of 0 and vice versa).
861
 *
862
 * @return 1 if match found.
863
 * @return 0 if no match found.
864
 * @return -1 on errors.
865
 */
866
static int
867
ssh_match_localnetwork(const char *addrlist, bool negate)
868
0
{
869
0
    struct ifaddrs *ifa = NULL, *ifaddrs = NULL;
870
0
    int r, found = 0;
871
0
    char address[NI_MAXHOST] = {0};
872
0
    socklen_t sa_len;
873
874
0
    r = getifaddrs(&ifaddrs);
875
0
    if (r != 0) {
876
0
        SSH_LOG_STRERROR(SSH_LOG_WARN,
877
0
                         errno,
878
0
                         "Match localnetwork: getifaddrs() failed: %s");
879
0
        return -1;
880
0
    }
881
882
0
    for (ifa = ifaddrs; ifa != NULL; ifa = ifa->ifa_next) {
883
0
        if (ifa->ifa_addr == NULL || (ifa->ifa_flags & IFF_UP) == 0) {
884
0
            continue;
885
0
        }
886
887
0
        switch (ifa->ifa_addr->sa_family) {
888
0
        case AF_INET:
889
0
            sa_len = sizeof(struct sockaddr_in);
890
0
            break;
891
0
        case AF_INET6:
892
0
            sa_len = sizeof(struct sockaddr_in6);
893
0
            break;
894
0
        default:
895
0
            SSH_LOG(SSH_LOG_TRACE,
896
0
                    "Interface %s: unsupported address family %d",
897
0
                    ifa->ifa_name,
898
0
                    ifa->ifa_addr->sa_family);
899
0
            continue;
900
0
        }
901
902
0
        r = getnameinfo(ifa->ifa_addr,
903
0
                        sa_len,
904
0
                        address,
905
0
                        sizeof(address),
906
0
                        NULL,
907
0
                        0,
908
0
                        NI_NUMERICHOST);
909
0
        if (r != 0) {
910
0
            SSH_LOG(SSH_LOG_TRACE,
911
0
                    "Interface %s getnameinfo failed: %s",
912
0
                    ifa->ifa_name,
913
0
                    gai_strerror(r));
914
0
            continue;
915
0
        }
916
0
        SSH_LOG(SSH_LOG_TRACE,
917
0
                "Interface %s address %s",
918
0
                ifa->ifa_name,
919
0
                address);
920
921
0
        r = match_cidr_address_list(address,
922
0
                                    addrlist,
923
0
                                    ifa->ifa_addr->sa_family);
924
0
        if (r == 1) {
925
0
            SSH_LOG(SSH_LOG_TRACE,
926
0
                    "Matched interface %s: address %s in %s",
927
0
                    ifa->ifa_name,
928
0
                    address,
929
0
                    addrlist);
930
0
            found = 1;
931
0
            break;
932
0
        }
933
0
    }
934
935
0
    freeifaddrs(ifaddrs);
936
937
0
    return (found == (negate ? 0 : 1));
938
0
}
939
#endif /* HAVE_IFADDRS_H */
940
941
static enum ssh_options_e
942
ssh_config_get_auth_option(enum ssh_config_opcode_e opcode)
943
0
{
944
0
    struct auth_option_map {
945
0
        enum ssh_config_opcode_e opcode;
946
0
        const char *name;
947
0
        enum ssh_options_e option;
948
0
    };
949
950
0
    static struct auth_option_map auth_options[] = {
951
0
        {
952
0
            SOC_GSSAPIAUTHENTICATION,
953
0
            "GSSAPIAuthentication",
954
0
            SSH_OPTIONS_GSSAPI_AUTH,
955
0
        },
956
0
        {
957
0
            SOC_KBDINTERACTIVEAUTHENTICATION,
958
0
            "KbdInteractiveAuthentication",
959
0
            SSH_OPTIONS_KBDINT_AUTH,
960
0
        },
961
0
        {
962
0
            SOC_PASSWORDAUTHENTICATION,
963
0
            "PasswordAuthentication",
964
0
            SSH_OPTIONS_PASSWORD_AUTH,
965
0
        },
966
0
        {
967
0
            SOC_PUBKEYAUTHENTICATION,
968
0
            "PubkeyAuthentication",
969
0
            SSH_OPTIONS_PUBKEY_AUTH,
970
0
        },
971
0
        {0, NULL, 0},
972
0
    };
973
974
0
    for (struct auth_option_map *map = auth_options; map->name != NULL; map++) {
975
0
        if (map->opcode == opcode) {
976
0
            return map->option;
977
0
        }
978
0
    }
979
0
    return -1;
980
0
}
981
982
static long ssh_config_convtime(const char *p, long notfound)
983
0
{
984
0
    char *endp = NULL;
985
0
    long total = 0;
986
0
    long value;
987
0
    long multiplier;
988
989
0
    if (p == NULL || *p == '\0') {
990
0
        return notfound;
991
0
    }
992
993
0
    while (*p != '\0') {
994
0
        errno = 0;
995
0
        value = strtol(p, &endp, 10);
996
0
        if (p == endp || errno != 0 || value < 0) {
997
0
            return notfound;
998
0
        }
999
1000
0
        switch (*endp) {
1001
0
        case '\0':
1002
0
        case 's':
1003
0
        case 'S':
1004
0
            multiplier = 1;
1005
0
            break;
1006
0
        case 'm':
1007
0
        case 'M':
1008
0
            multiplier = 60;
1009
0
            break;
1010
0
        case 'h':
1011
0
        case 'H':
1012
0
            multiplier = 60 * 60;
1013
0
            break;
1014
0
        case 'd':
1015
0
        case 'D':
1016
0
            multiplier = 24 * 60 * 60;
1017
0
            break;
1018
0
        case 'w':
1019
0
        case 'W':
1020
0
            multiplier = 7 * 24 * 60 * 60;
1021
0
            break;
1022
0
        default:
1023
0
            return notfound;
1024
0
        }
1025
1026
        /*
1027
         * OpenSSH accepts ConnectTimeout values only up to INT_MAX
1028
         * seconds: see openssh-portable/misc.c:convtime().
1029
         */
1030
0
        if (value > (INT_MAX - total) / multiplier) {
1031
0
            return notfound;
1032
0
        }
1033
0
        total += value * multiplier;
1034
1035
0
        if (*endp == '\0') {
1036
0
            p = endp;
1037
0
        } else {
1038
0
            p = endp + 1;
1039
0
        }
1040
0
    }
1041
1042
0
    return total;
1043
0
}
1044
1045
struct ssh_config_token_value_map {
1046
    const char *token;
1047
    int value;
1048
};
1049
1050
static int
1051
ssh_config_get_token_value(char **str,
1052
                           const struct ssh_config_token_value_map *map,
1053
                           int notfound)
1054
0
{
1055
0
    const char *p = NULL;
1056
0
    size_t i;
1057
1058
0
    p = ssh_config_get_str_tok(str, NULL);
1059
0
    if (p == NULL) {
1060
0
        return notfound;
1061
0
    }
1062
1063
0
    for (i = 0; map[i].token != NULL; i++) {
1064
0
        if (strcasecmp(p, map[i].token) == 0) {
1065
0
            return map[i].value;
1066
0
        }
1067
0
    }
1068
1069
0
    return notfound;
1070
0
}
1071
1072
/*
1073
 * OpenSSH keeps Compression on the narrower yes/no syntax, and accepts "yes"
1074
 * only when zlib support is available, so Compression does not use the
1075
 * broader true/false aliases handled by ssh_config_get_yesno().
1076
 */
1077
static const struct ssh_config_token_value_map compression_map[] = {
1078
#ifdef WITH_ZLIB
1079
    {"yes", 1},
1080
#endif /* WITH_ZLIB */
1081
    {"no", 0},
1082
    {NULL, 0},
1083
};
1084
1085
static int ssh_config_get_strict_hostkey(char **str, int notfound)
1086
0
{
1087
0
    static const struct ssh_config_token_value_map strict_hostkey_map[] = {
1088
0
        {"yes", SSH_STRICT_HOSTKEY_YES},
1089
0
        {"true", SSH_STRICT_HOSTKEY_YES},
1090
0
        {"no", SSH_STRICT_HOSTKEY_OFF},
1091
0
        {"false", SSH_STRICT_HOSTKEY_OFF},
1092
0
        {"off", SSH_STRICT_HOSTKEY_OFF},
1093
0
        {"ask", SSH_STRICT_HOSTKEY_ASK},
1094
0
        {"accept-new", SSH_STRICT_HOSTKEY_ACCEPT_NEW},
1095
0
        {NULL, 0},
1096
0
    };
1097
1098
0
    return ssh_config_get_token_value(str, strict_hostkey_map, notfound);
1099
0
}
1100
1101
static int ssh_config_get_pubkey_auth(char **str, int notfound)
1102
0
{
1103
0
    static const struct ssh_config_token_value_map pubkey_auth_map[] = {
1104
0
        {"yes", SSH_PUBKEY_AUTH_ALL},
1105
0
        {"true", SSH_PUBKEY_AUTH_ALL},
1106
0
        {"no", SSH_PUBKEY_AUTH_NO},
1107
0
        {"false", SSH_PUBKEY_AUTH_NO},
1108
0
        {"unbound", SSH_PUBKEY_AUTH_UNBOUND},
1109
0
        {"host-bound", SSH_PUBKEY_AUTH_HOST_BOUND},
1110
0
        {NULL, 0},
1111
0
    };
1112
1113
0
    return ssh_config_get_token_value(str, pubkey_auth_map, notfound);
1114
0
}
1115
#define CHECK_COND_OR_FAIL(cond, error_message)                \
1116
0
    if ((cond)) {                                              \
1117
0
        SSH_LOG(SSH_LOG_DEBUG,                                 \
1118
0
                "line %d: %s: %s",                             \
1119
0
                count,                                         \
1120
0
                error_message,                                 \
1121
0
                keyword);                                      \
1122
0
        if (fail_on_unknown) {                                 \
1123
0
            ssh_set_error(session,                             \
1124
0
                          SSH_FATAL,                           \
1125
0
                          is_cli ? "%s '%s' value on CLI"      \
1126
0
                                 : "%s '%s' value at line %d", \
1127
0
                          error_message,                       \
1128
0
                          keyword,                             \
1129
0
                          is_cli ? 0 : count);                 \
1130
0
            SAFE_FREE(x);                                      \
1131
0
            return SSH_ERROR;                                  \
1132
0
        }                                                      \
1133
0
        break;                                                 \
1134
0
    }
1135
1136
static int ssh_config_parse_line_internal(ssh_session session,
1137
                                          const char *line,
1138
                                          unsigned int count,
1139
                                          int *parsing,
1140
                                          unsigned int depth,
1141
                                          bool global,
1142
                                          bool is_cli,
1143
                                          bool fail_on_unknown)
1144
0
{
1145
0
  enum ssh_config_opcode_e opcode;
1146
0
  const char *p = NULL, *p2 = NULL;
1147
0
  char *s = NULL, *x = NULL;
1148
0
  char *keyword = NULL;
1149
0
  char *lowerhost = NULL;
1150
0
  size_t len;
1151
0
  int i, rv, cmp;
1152
0
  uint8_t *seen = session->opts.options_seen;
1153
0
  long l;
1154
0
  int64_t ll;
1155
1156
  /* Ignore empty lines */
1157
0
  if (line == NULL || *line == '\0') {
1158
0
      if (is_cli) {
1159
0
          return SSH_ERROR;
1160
0
      }
1161
0
    return 0;
1162
0
  }
1163
1164
0
  x = s = strdup(line);
1165
0
  if (s == NULL) {
1166
0
    ssh_set_error_oom(session);
1167
0
    return -1;
1168
0
  }
1169
1170
  /* Remove trailing spaces */
1171
0
  for (len = strlen(s) - 1; len > 0; len--) {
1172
0
    if (! isspace(s[len])) {
1173
0
      break;
1174
0
    }
1175
0
    s[len] = '\0';
1176
0
  }
1177
1178
0
  keyword = ssh_config_get_token(&s);
1179
0
  if (keyword == NULL || *keyword == '#' ||
1180
0
      *keyword == '\0' || *keyword == '\n') {
1181
0
    SAFE_FREE(x);
1182
0
    return 0;
1183
0
  }
1184
1185
0
  opcode = ssh_config_get_opcode(keyword);
1186
0
  if (is_cli && !ssh_config_is_cli_supported(opcode)) {
1187
0
      ssh_set_error(
1188
0
          session,
1189
0
          SSH_FATAL,
1190
0
          "Option '%s' is not supported in command-line configuration",
1191
0
          keyword);
1192
0
      SAFE_FREE(x);
1193
0
      return SSH_ERROR;
1194
0
  }
1195
1196
0
  if (*parsing == 1 &&
1197
0
      opcode != SOC_HOST &&
1198
0
      opcode != SOC_MATCH &&
1199
0
      opcode != SOC_INCLUDE &&
1200
0
      opcode != SOC_IDENTITY &&
1201
0
      opcode != SOC_CERTIFICATE &&
1202
0
      opcode > SOC_UNSUPPORTED &&
1203
0
      opcode < SOC_MAX) { /* Ignore all unknown types here */
1204
      /* Skip all the options that were already applied */
1205
0
      if (seen[opcode] != 0) {
1206
0
          SAFE_FREE(x);
1207
0
          return 0;
1208
0
      }
1209
0
      seen[opcode] = 1;
1210
0
  }
1211
1212
0
  switch (opcode) {
1213
0
    case SOC_INCLUDE: /* recursive include of other files */
1214
1215
0
      p = ssh_config_get_str_tok(&s, NULL);
1216
0
      if (p && *parsing) {
1217
0
        char *path = ssh_config_make_absolute(session, p, global);
1218
0
        if (path == NULL) {
1219
0
          SSH_LOG(SSH_LOG_WARN, "line %d: Failed to allocate memory "
1220
0
                  "for the include path expansion", count);
1221
0
          SAFE_FREE(x);
1222
0
          return -1;
1223
0
        }
1224
0
#if defined(HAVE_GLOB) && defined(HAVE_GLOB_GL_FLAGS_MEMBER)
1225
0
        local_parse_glob(session, path, parsing, depth + 1, global);
1226
#else
1227
        local_parse_file(session, path, parsing, depth + 1, global);
1228
#endif /* HAVE_GLOB */
1229
0
        free(path);
1230
0
      }
1231
0
      break;
1232
1233
0
    case SOC_MATCH: {
1234
0
        bool negate;
1235
0
        int match_error = 0;
1236
0
        int result = 1;
1237
0
        size_t args = 0;
1238
0
        enum ssh_config_match_e opt;
1239
0
        struct ssh_config_token_info keyword_info;
1240
0
        struct ssh_config_token_info arg_info;
1241
0
        char *localuser = NULL;
1242
0
        const char *version = NULL;
1243
1244
0
        *parsing = 0;
1245
0
        do {
1246
0
            p = p2 = ssh_config_get_token_info(&s, &keyword_info);
1247
0
            if (!keyword_info.found || p[0] == '\0') {
1248
0
                break;
1249
0
            }
1250
0
            args++;
1251
0
            SSH_LOG(SSH_LOG_DEBUG, "line %d: Processing Match keyword '%s'",
1252
0
                    count, p);
1253
1254
            /* If the option is prefixed with ! the result should be negated */
1255
0
            negate = false;
1256
0
            if (p[0] == '!') {
1257
0
                negate = true;
1258
0
                p++;
1259
0
            }
1260
1261
0
            opt = ssh_config_get_match_opcode(p);
1262
0
            switch (opt) {
1263
0
            case MATCH_ALL:
1264
0
                p = ssh_config_get_str_tok(&s, NULL);
1265
0
                if (args <= 2 && (p == NULL || p[0] == '\0')) {
1266
                    /* The first or second, but last argument. The "all" keyword
1267
                     * can be prefixed with either "final" or "canonical"
1268
                     * keywords which do not have any effect here. */
1269
0
                    if (negate == true) {
1270
0
                        result = 0;
1271
0
                    }
1272
0
                    break;
1273
0
                }
1274
1275
0
                ssh_set_error(session, SSH_FATAL,
1276
0
                              "line %d: ERROR - Match all cannot be combined with "
1277
0
                              "other Match attributes", count);
1278
0
                match_error = -1;
1279
0
                goto out_match;
1280
1281
0
            case MATCH_FINAL:
1282
0
            case MATCH_CANONICAL:
1283
0
                SSH_LOG(SSH_LOG_DEBUG,
1284
0
                        "line %d: Unsupported Match keyword '%s', skipping",
1285
0
                        count,
1286
0
                        p);
1287
                /* Not set any result here -- the result is dependent on the
1288
                 * following matches after this keyword */
1289
0
                break;
1290
1291
0
            case MATCH_EXEC:
1292
                /* Skip one argument (including in quotes) */
1293
0
                p = ssh_config_get_token(&s);
1294
0
                if (p == NULL || p[0] == '\0') {
1295
0
                    SSH_LOG(SSH_LOG_TRACE, "line %d: Match keyword "
1296
0
                            "'%s' requires argument", count, p2);
1297
0
                    match_error = -1;
1298
0
                    goto out_match;
1299
0
                }
1300
0
                if (result != 1) {
1301
0
                    SSH_LOG(SSH_LOG_DEBUG, "line %d: Skipped match exec "
1302
0
                            "'%s' as previous conditions already failed.",
1303
0
                            count, p2);
1304
0
                    continue;
1305
0
                }
1306
0
                result &= ssh_match_exec(session, p, negate);
1307
0
                args++;
1308
0
                break;
1309
1310
0
            case MATCH_LOCALUSER:
1311
                /* Here we match only one argument */
1312
0
                p = ssh_config_get_str_tok(&s, NULL);
1313
0
                if (p == NULL || p[0] == '\0') {
1314
0
                    ssh_set_error(session,
1315
0
                                  SSH_FATAL,
1316
0
                                  "line %d: ERROR - Match localuser keyword "
1317
0
                                  "requires argument",
1318
0
                                  count);
1319
0
                    match_error = -1;
1320
0
                    goto out_match;
1321
0
                }
1322
0
                localuser = ssh_get_local_username();
1323
0
                if (localuser == NULL) {
1324
0
                    SSH_LOG(SSH_LOG_TRACE, "line %d: Can not get local username "
1325
0
                            "for conditional matching.", count);
1326
0
                    match_error = -1;
1327
0
                    goto out_match;
1328
0
                }
1329
0
                result &= ssh_config_match(localuser, p, negate);
1330
0
                SAFE_FREE(localuser);
1331
0
                args++;
1332
0
                break;
1333
1334
0
            case MATCH_ORIGINALHOST:
1335
                /* Here we match only one argument */
1336
0
                p = ssh_config_get_str_tok(&s, NULL);
1337
0
                if (p == NULL || p[0] == '\0') {
1338
0
                    ssh_set_error(session,
1339
0
                                  SSH_FATAL,
1340
0
                                  "line %d: ERROR - Match originalhost keyword "
1341
0
                                  "requires argument",
1342
0
                                  count);
1343
0
                    match_error = -1;
1344
0
                    goto out_match;
1345
0
                }
1346
0
                result &=
1347
0
                    ssh_config_match(session->opts.originalhost, p, negate);
1348
0
                args++;
1349
0
                break;
1350
1351
0
            case MATCH_HOST:
1352
                /* Here we match only one argument */
1353
0
                p = ssh_config_get_str_tok(&s, NULL);
1354
0
                if (p == NULL || p[0] == '\0') {
1355
0
                    ssh_set_error(session, SSH_FATAL,
1356
0
                                  "line %d: ERROR - Match host keyword "
1357
0
                                  "requires argument", count);
1358
0
                    match_error = -1;
1359
0
                    goto out_match;
1360
0
                }
1361
0
                result &= ssh_config_match(session->opts.host
1362
0
                                               ? session->opts.host
1363
0
                                               : session->opts.originalhost,
1364
0
                                           p,
1365
0
                                           negate);
1366
0
                args++;
1367
0
                break;
1368
1369
0
            case MATCH_USER:
1370
                /* Here we match only one argument */
1371
0
                p = ssh_config_get_str_tok(&s, NULL);
1372
0
                if (p == NULL || p[0] == '\0') {
1373
0
                    ssh_set_error(session, SSH_FATAL,
1374
0
                                  "line %d: ERROR - Match user keyword "
1375
0
                                  "requires argument", count);
1376
0
                    match_error = -1;
1377
0
                    goto out_match;
1378
0
                }
1379
0
                result &= ssh_config_match(session->opts.username, p, negate);
1380
0
                args++;
1381
0
                break;
1382
1383
0
            case MATCH_LOCALNETWORK:
1384
                /* Here we match only one argument */
1385
0
                p = ssh_config_get_str_tok(&s, NULL);
1386
0
                if (p == NULL || p[0] == '\0') {
1387
0
                    ssh_set_error(session,
1388
0
                                  SSH_FATAL,
1389
0
                                  "line %d: ERROR - Match local network keyword"
1390
0
                                  "requires argument",
1391
0
                                  count);
1392
0
                    match_error = -1;
1393
0
                    goto out_match;
1394
0
                }
1395
0
#ifdef HAVE_IFADDRS_H
1396
0
                rv = match_cidr_address_list(NULL, p, -1);
1397
0
                if (rv == -1) {
1398
0
                    ssh_set_error(session,
1399
0
                                  SSH_FATAL,
1400
0
                                  "line %d: ERROR - List invalid entry: %s",
1401
0
                                  count,
1402
0
                                  p);
1403
0
                    match_error = -1;
1404
0
                    goto out_match;
1405
0
                }
1406
0
                rv = ssh_match_localnetwork(p, negate);
1407
0
                if (rv == -1) {
1408
0
                    ssh_set_error(session,
1409
0
                                  SSH_FATAL,
1410
0
                                  "line %d: ERROR - Error while retrieving "
1411
0
                                  "network interface information -"
1412
0
                                  " List entry: %s",
1413
0
                                  count,
1414
0
                                  p);
1415
0
                    match_error = -1;
1416
0
                    goto out_match;
1417
0
                }
1418
1419
0
                result &= rv;
1420
#else /* HAVE_IFADDRS_H */
1421
                ssh_set_error(session,
1422
                              SSH_FATAL,
1423
                              "line %d: ERROR - match localnetwork "
1424
                              "not supported on this platform",
1425
                              count);
1426
                match_error = -1;
1427
                goto out_match;
1428
#endif /* HAVE_IFADDRS_H */
1429
0
                args++;
1430
0
                break;
1431
1432
0
            case MATCH_VERSION:
1433
                /* Here we match only one argument */
1434
0
                p = ssh_config_get_str_tok(&s, NULL);
1435
0
                if (p == NULL || p[0] == '\0') {
1436
0
                    ssh_set_error(session,
1437
0
                                  SSH_FATAL,
1438
0
                                  "line %d: ERROR - Match version keyword "
1439
0
                                  "requires argument",
1440
0
                                  count);
1441
0
                    match_error = -1;
1442
0
                    goto out_match;
1443
0
                }
1444
0
                version = "libssh_" SSH_STRINGIFY(LIBSSH_VERSION);
1445
0
                result &= ssh_config_match(version, p, negate);
1446
0
                args++;
1447
0
                break;
1448
1449
0
            case MATCH_TAGGED:
1450
                /* Here we match exactly one argument.
1451
                 * An explicit empty argument ('tagged=' or 'tagged ""') is
1452
                 * allowed, but a missing argument is an error.
1453
                 */
1454
0
                p = ssh_config_get_token_info(&s, &arg_info);
1455
0
                if (arg_info.invalid) {
1456
0
                    ssh_set_error(session,
1457
0
                                  SSH_FATAL,
1458
0
                                  "line %d: ERROR - Match tagged keyword "
1459
0
                                  "contains unterminated quotes",
1460
0
                                  count);
1461
0
                    match_error = -1;
1462
0
                    goto out_match;
1463
0
                }
1464
                /* found=false means there was no argument token at all. An
1465
                 * explicit empty token ("") still reports found=true.
1466
                 */
1467
0
                if (!arg_info.found) {
1468
0
                    if (keyword_info.had_equal) {
1469
0
                        p = "";
1470
0
                    } else {
1471
0
                        ssh_set_error(session,
1472
0
                                      SSH_FATAL,
1473
0
                                      "line %d: ERROR - Match tagged keyword "
1474
0
                                      "requires argument",
1475
0
                                      count);
1476
0
                        match_error = -1;
1477
0
                        goto out_match;
1478
0
                    }
1479
0
                }
1480
0
                result &= ssh_config_match_tagged(session->opts.tag, p, negate);
1481
                /* MATCH_TAGGED explicitly allows an empty pattern ('tagged='
1482
                 * or 'tagged ""') to mean "match when no tag is set". That
1483
                 * leaves p pointing at "", so the outer do-while loop would
1484
                 * terminate early and skip any remaining predicates on the
1485
                 * same Match line. Restore p to the keyword token before
1486
                 * iterating.
1487
                 */
1488
0
                p = p2;
1489
0
                args++;
1490
0
                break;
1491
1492
0
            case MATCH_UNKNOWN:
1493
0
            default:
1494
0
                SSH_LOG(SSH_LOG_WARN,
1495
0
                        "Unknown argument '%s' for Match keyword. Not matching",
1496
0
                        p);
1497
0
                result = 0;
1498
0
                break;
1499
0
            }
1500
0
        } while (p != NULL && p[0] != '\0');
1501
0
        if (args == 0) {
1502
0
            SSH_LOG(SSH_LOG_WARN,
1503
0
                    "ERROR - Match keyword requires an argument. Not matching");
1504
0
            result = 0;
1505
0
        }
1506
0
    out_match:
1507
0
        SAFE_FREE(localuser);
1508
0
        if (match_error != 0) {
1509
0
            SAFE_FREE(x);
1510
0
            return -1;
1511
0
        }
1512
0
        *parsing = result;
1513
0
        break;
1514
0
    }
1515
0
    case SOC_HOST: {
1516
0
        int ok = 0, result = -1;
1517
1518
0
        *parsing = 0;
1519
0
        lowerhost = (session->opts.originalhost)
1520
0
                        ? ssh_lowercase(session->opts.originalhost)
1521
0
                        : NULL;
1522
0
        for (p = ssh_config_get_str_tok(&s, NULL);
1523
0
             p != NULL && p[0] != '\0';
1524
0
             p = ssh_config_get_str_tok(&s, NULL)) {
1525
0
             if (ok >= 0) {
1526
0
               ok = match_hostname(lowerhost, p, strlen(p));
1527
0
               if (result == -1 && ok < 0) {
1528
0
                   result = 0;
1529
0
               } else if (result == -1 && ok > 0) {
1530
0
                   result = 1;
1531
0
               }
1532
0
            }
1533
0
        }
1534
0
        SAFE_FREE(lowerhost);
1535
0
        if (result != -1) {
1536
0
            *parsing = result;
1537
0
        }
1538
0
        break;
1539
0
    }
1540
0
    case SOC_TAG:
1541
0
        p = ssh_config_get_str_tok(&s, NULL);
1542
0
        if (p == NULL) {
1543
0
            ssh_set_error(session,
1544
0
                          SSH_FATAL,
1545
0
                          "line %d: ERROR - Tag keyword requires argument",
1546
0
                          count);
1547
0
            SAFE_FREE(x);
1548
0
            return SSH_ERROR;
1549
0
        }
1550
0
        if (*parsing) {
1551
0
            char *tag = NULL;
1552
1553
0
            tag = strdup(p);
1554
0
            if (tag == NULL) {
1555
0
                ssh_set_error_oom(session);
1556
0
                SAFE_FREE(x);
1557
0
                return SSH_ERROR;
1558
0
            }
1559
0
            SAFE_FREE(session->opts.tag);
1560
0
            session->opts.tag = tag;
1561
0
        }
1562
0
        break;
1563
0
    case SOC_HOSTNAME:
1564
0
        p = ssh_config_get_str_tok(&s, NULL);
1565
0
        CHECK_COND_OR_FAIL(p == NULL, "Missing argument");
1566
0
        if (*parsing) {
1567
0
            int rc;
1568
0
            bool had_expansion = strchr(p, '%') != NULL;
1569
0
            bool needs_host = false;
1570
0
            bool has_unknown = false;
1571
0
            rc = ssh_config_scan_hostname_tokens(session,
1572
0
                                                 p,
1573
0
                                                 &needs_host,
1574
0
                                                 &has_unknown);
1575
0
            if (rc < 0) {
1576
0
                SAFE_FREE(x);
1577
0
                return -1;
1578
0
            }
1579
0
            if (had_expansion) {
1580
0
                if (!has_unknown &&
1581
0
                    (!needs_host || session->opts.host != NULL ||
1582
0
                     session->opts.originalhost != NULL)) {
1583
0
                    char *expanded = ssh_path_expand_hostname(session, p);
1584
0
                    if (expanded == NULL) {
1585
0
                        SAFE_FREE(x);
1586
0
                        return -1;
1587
0
                    }
1588
0
                    session->opts.config_hostname_only = true;
1589
0
                    rv = ssh_options_set(session, SSH_OPTIONS_HOST, expanded);
1590
0
                    session->opts.config_hostname_only = false;
1591
0
                    free(expanded);
1592
0
                    if (rv != SSH_OK) {
1593
                        /* Expanded HostName values remain fatal if host
1594
                         * validation rejects the resulting hostname.
1595
                         */
1596
0
                        SAFE_FREE(x);
1597
0
                        return -1;
1598
0
                    }
1599
0
                } else {
1600
0
                    char *hostname_pattern =
1601
0
                        ssh_config_lowercase_hostname_pattern(p);
1602
0
                    if (hostname_pattern == NULL) {
1603
0
                        ssh_set_error_oom(session);
1604
0
                        SAFE_FREE(x);
1605
0
                        return -1;
1606
0
                    }
1607
0
                    SAFE_FREE(session->opts.config_hostname);
1608
0
                    session->opts.config_hostname = hostname_pattern;
1609
0
                }
1610
0
            } else {
1611
0
                char *lower = ssh_lowercase(p);
1612
0
                if (lower == NULL) {
1613
0
                    ssh_set_error_oom(session);
1614
0
                    SAFE_FREE(x);
1615
0
                    return -1;
1616
0
                }
1617
0
                session->opts.config_hostname_only = true;
1618
0
                ssh_options_set(session, SSH_OPTIONS_HOST, lower);
1619
0
                session->opts.config_hostname_only = false;
1620
0
                free(lower);
1621
0
            }
1622
0
        }
1623
0
        break;
1624
0
    case SOC_PORT:
1625
0
        l = ssh_config_get_long(&s, -1);
1626
0
        CHECK_COND_OR_FAIL(l <= 0 || l > 65535, "Invalid argument");
1627
0
        if (*parsing) {
1628
0
            i = (int)l;
1629
0
            ssh_options_set(session, SSH_OPTIONS_PORT, &i);
1630
0
        }
1631
0
        break;
1632
0
    case SOC_USERNAME:
1633
0
        p = ssh_config_get_str_tok(&s, NULL);
1634
0
        CHECK_COND_OR_FAIL(p == NULL, "Missing argument");
1635
0
        if (*parsing) {
1636
0
            ssh_options_set(session, SSH_OPTIONS_USER, p);
1637
0
        }
1638
0
        break;
1639
0
    case SOC_IDENTITY:
1640
0
      p = ssh_config_get_str_tok(&s, NULL);
1641
0
      CHECK_COND_OR_FAIL(p == NULL, "Missing argument");
1642
0
      if (*parsing) {
1643
0
          ssh_options_set(session, SSH_OPTIONS_ADD_IDENTITY, p);
1644
0
      }
1645
0
      break;
1646
0
    case SOC_CIPHERS:
1647
0
      p = ssh_config_get_str_tok(&s, NULL);
1648
0
      CHECK_COND_OR_FAIL(p == NULL, "Missing argument");
1649
0
      if (*parsing) {
1650
0
          ssh_options_set(session, SSH_OPTIONS_CIPHERS_C_S, p);
1651
0
          ssh_options_set(session, SSH_OPTIONS_CIPHERS_S_C, p);
1652
0
      }
1653
0
      break;
1654
0
    case SOC_MACS:
1655
0
      p = ssh_config_get_str_tok(&s, NULL);
1656
0
      CHECK_COND_OR_FAIL(p == NULL, "Missing argument");
1657
0
      if (*parsing) {
1658
0
          ssh_options_set(session, SSH_OPTIONS_HMAC_C_S, p);
1659
0
          ssh_options_set(session, SSH_OPTIONS_HMAC_S_C, p);
1660
0
      }
1661
0
      break;
1662
0
    case SOC_COMPRESSION:
1663
0
      i = ssh_config_get_token_value(&s, compression_map, -1);
1664
0
      CHECK_COND_OR_FAIL(i < 0, "Invalid argument");
1665
0
      if (*parsing) {
1666
0
          if (i) {
1667
0
              ssh_options_set(session, SSH_OPTIONS_COMPRESSION, "yes");
1668
0
          } else {
1669
0
              ssh_options_set(session, SSH_OPTIONS_COMPRESSION, "no");
1670
0
          }
1671
0
      }
1672
0
      break;
1673
0
    case SOC_TIMEOUT:
1674
0
        p = ssh_config_get_str_tok(&s, NULL);
1675
0
        CHECK_COND_OR_FAIL(p == NULL, "Missing argument");
1676
0
        cmp = strcmp(p, "none");
1677
0
        if (cmp == 0) {
1678
0
            if (*parsing) {
1679
0
                session->opts.timeout = (unsigned long)SSH_TIMEOUT_INFINITE;
1680
0
                session->opts.timeout_usec = 0;
1681
0
            }
1682
0
            break;
1683
0
        }
1684
0
        l = ssh_config_convtime(p, -1);
1685
0
        CHECK_COND_OR_FAIL(l < 0, "Invalid argument");
1686
0
        if (*parsing) {
1687
0
            ssh_options_set(session, SSH_OPTIONS_TIMEOUT, &l);
1688
0
        }
1689
0
        break;
1690
0
    case SOC_STRICTHOSTKEYCHECK:
1691
0
      i = ssh_config_get_strict_hostkey(&s, -1);
1692
0
      CHECK_COND_OR_FAIL(i < 0, "Invalid argument");
1693
0
      if (*parsing) {
1694
0
          ssh_options_set(session, SSH_OPTIONS_STRICTHOSTKEYCHECK, &i);
1695
0
      }
1696
0
      break;
1697
0
    case SOC_KNOWNHOSTS:
1698
0
      p = ssh_config_get_str_tok(&s, NULL);
1699
0
      CHECK_COND_OR_FAIL(p == NULL, "Missing argument");
1700
0
      if (*parsing) {
1701
0
          ssh_options_set(session, SSH_OPTIONS_KNOWNHOSTS, p);
1702
0
      }
1703
0
      break;
1704
0
    case SOC_PROXYCOMMAND:
1705
0
      p = ssh_config_get_cmd(&s);
1706
0
      CHECK_COND_OR_FAIL(p == NULL, "Missing argument");
1707
      /* We share the seen value with the ProxyJump */
1708
0
      if (*parsing && !seen[SOC_PROXYJUMP]) {
1709
0
          ssh_options_set(session, SSH_OPTIONS_PROXYCOMMAND, p);
1710
0
      }
1711
0
      break;
1712
0
    case SOC_PROXYJUMP:
1713
0
        p = ssh_config_get_str_tok(&s, NULL);
1714
0
        if (p == NULL) {
1715
0
            SAFE_FREE(x);
1716
0
            return -1;
1717
0
        }
1718
        /* We share the seen value with the ProxyCommand */
1719
0
        rv = ssh_config_parse_proxy_jump(session,
1720
0
                                         p,
1721
0
                                         (*parsing && !seen[SOC_PROXYCOMMAND]));
1722
0
        if (rv != SSH_OK) {
1723
0
            SAFE_FREE(x);
1724
0
            return -1;
1725
0
        }
1726
0
        break;
1727
0
    case SOC_GSSAPISERVERIDENTITY:
1728
0
      p = ssh_config_get_str_tok(&s, NULL);
1729
0
      CHECK_COND_OR_FAIL(p == NULL, "Missing argument");
1730
0
      if (*parsing) {
1731
0
          ssh_options_set(session, SSH_OPTIONS_GSSAPI_SERVER_IDENTITY, p);
1732
0
      }
1733
0
      break;
1734
0
    case SOC_GSSAPICLIENTIDENTITY:
1735
0
      p = ssh_config_get_str_tok(&s, NULL);
1736
0
      CHECK_COND_OR_FAIL(p == NULL, "Missing argument");
1737
0
      if (*parsing) {
1738
0
          ssh_options_set(session, SSH_OPTIONS_GSSAPI_CLIENT_IDENTITY, p);
1739
0
      }
1740
0
      break;
1741
0
    case SOC_GSSAPIDELEGATECREDENTIALS:
1742
0
      i = ssh_config_get_yesno(&s, -1);
1743
0
      CHECK_COND_OR_FAIL(i < 0, "Invalid argument");
1744
0
      if (*parsing) {
1745
0
          ssh_options_set(session, SSH_OPTIONS_GSSAPI_DELEGATE_CREDENTIALS, &i);
1746
0
      }
1747
0
      break;
1748
0
    case SOC_BINDADDRESS:
1749
0
        p = ssh_config_get_str_tok(&s, NULL);
1750
0
        CHECK_COND_OR_FAIL(p == NULL, "Missing argument");
1751
0
        if (*parsing) {
1752
0
            ssh_options_set(session, SSH_OPTIONS_BINDADDR, p);
1753
0
        }
1754
0
        break;
1755
0
    case SOC_GLOBALKNOWNHOSTSFILE:
1756
0
        p = ssh_config_get_str_tok(&s, NULL);
1757
0
        CHECK_COND_OR_FAIL(p == NULL, "Missing argument");
1758
0
        if (*parsing) {
1759
0
            ssh_options_set(session, SSH_OPTIONS_GLOBAL_KNOWNHOSTS, p);
1760
0
        }
1761
0
        break;
1762
0
    case SOC_LOGLEVEL:
1763
0
        p = ssh_config_get_str_tok(&s, NULL);
1764
0
        CHECK_COND_OR_FAIL(p == NULL, "Missing argument");
1765
0
        if (*parsing) {
1766
0
            int value = -1;
1767
1768
0
            if (strcasecmp(p, "quiet") == 0) {
1769
0
                value = SSH_LOG_NONE;
1770
0
            } else if (strcasecmp(p, "fatal") == 0 ||
1771
0
                    strcasecmp(p, "error")== 0) {
1772
0
                value = SSH_LOG_WARN;
1773
0
            } else if (strcasecmp(p, "verbose") == 0 ||
1774
0
                    strcasecmp(p, "info") == 0) {
1775
0
                value = SSH_LOG_INFO;
1776
0
            } else if (strcasecmp(p, "DEBUG") == 0 ||
1777
0
                    strcasecmp(p, "DEBUG1") == 0) {
1778
0
                value = SSH_LOG_DEBUG;
1779
0
            } else if (strcasecmp(p, "DEBUG2") == 0 ||
1780
0
                    strcasecmp(p, "DEBUG3") == 0) {
1781
0
                value = SSH_LOG_TRACE;
1782
0
            }
1783
0
            CHECK_COND_OR_FAIL(value == -1, "Invalid value");
1784
0
            if (value != -1) {
1785
0
                ssh_options_set(session, SSH_OPTIONS_LOG_VERBOSITY, &value);
1786
0
            }
1787
0
        }
1788
0
        break;
1789
0
    case SOC_HOSTKEYALGORITHMS:
1790
0
        p = ssh_config_get_str_tok(&s, NULL);
1791
0
        CHECK_COND_OR_FAIL(p == NULL, "Missing argument");
1792
0
        if (*parsing) {
1793
0
            ssh_options_set(session, SSH_OPTIONS_HOSTKEYS, p);
1794
0
        }
1795
0
        break;
1796
0
    case SOC_PUBKEYACCEPTEDKEYTYPES:
1797
0
        p = ssh_config_get_str_tok(&s, NULL);
1798
0
        CHECK_COND_OR_FAIL(p == NULL, "Missing argument");
1799
0
        if (*parsing) {
1800
0
            ssh_options_set(session, SSH_OPTIONS_PUBLICKEY_ACCEPTED_TYPES, p);
1801
0
        }
1802
0
        break;
1803
0
    case SOC_KEXALGORITHMS:
1804
0
        p = ssh_config_get_str_tok(&s, NULL);
1805
0
        CHECK_COND_OR_FAIL(p == NULL, "Missing argument");
1806
0
        if (*parsing) {
1807
0
            ssh_options_set(session, SSH_OPTIONS_KEY_EXCHANGE, p);
1808
0
        }
1809
0
        break;
1810
0
    case SOC_REKEYLIMIT:
1811
        /* Parse the data limit */
1812
0
        p = ssh_config_get_str_tok(&s, NULL);
1813
0
        if (p == NULL) {
1814
0
            CHECK_COND_OR_FAIL(1, "Missing data limit");
1815
0
            break;
1816
0
        } else if (strcmp(p, "default") == 0) {
1817
            /* Default rekey limits enforced automatically */
1818
0
            ll = 0;
1819
0
        } else {
1820
0
            char *endp = NULL;
1821
0
            ll = strtoll(p, &endp, 10);
1822
0
            if (p == endp || ll < 0) {
1823
0
                CHECK_COND_OR_FAIL(1, "Invalid data limit");
1824
0
                break;
1825
0
            }
1826
0
            switch (*endp) {
1827
0
            case 'g':
1828
0
            case 'G':
1829
0
                if (ll > LLONG_MAX / 1024) {
1830
0
                    SSH_LOG(SSH_LOG_TRACE, "Possible overflow of rekey limit");
1831
0
                    ll = -1;
1832
0
                    break;
1833
0
                }
1834
0
                ll = ll * 1024;
1835
0
                FALL_THROUGH;
1836
0
            case 'm':
1837
0
            case 'M':
1838
0
                if (ll > LLONG_MAX / 1024) {
1839
0
                    SSH_LOG(SSH_LOG_TRACE, "Possible overflow of rekey limit");
1840
0
                    ll = -1;
1841
0
                    break;
1842
0
                }
1843
0
                ll = ll * 1024;
1844
0
                FALL_THROUGH;
1845
0
            case 'k':
1846
0
            case 'K':
1847
0
                if (ll > LLONG_MAX / 1024) {
1848
0
                    SSH_LOG(SSH_LOG_TRACE, "Possible overflow of rekey limit");
1849
0
                    ll = -1;
1850
0
                    break;
1851
0
                }
1852
0
                ll = ll * 1024;
1853
0
                endp++;
1854
0
                FALL_THROUGH;
1855
0
            case '\0':
1856
                /* just the number */
1857
0
                break;
1858
0
            default:
1859
                /* Ignore invalid suffix and trailing garbage */
1860
0
                SSH_LOG(SSH_LOG_TRACE, "Ignoring invalid suffix");
1861
0
                break;
1862
0
            }
1863
0
        }
1864
0
        CHECK_COND_OR_FAIL(ll < 0, "Invalid data limit");
1865
0
        CHECK_COND_OR_FAIL(ll > 0 && ll < 16, "RekeyLimit too small");
1866
0
        if (*parsing) {
1867
0
            uint64_t v = (uint64_t)ll;
1868
0
            ssh_options_set(session, SSH_OPTIONS_REKEY_DATA, &v);
1869
0
        }
1870
        /* Parse the time limit */
1871
0
        p = ssh_config_get_str_tok(&s, NULL);
1872
0
        if (p == NULL) {
1873
0
            break;
1874
0
        } else if (strcmp(p, "none") == 0) {
1875
0
            ll = 0;
1876
0
        } else {
1877
0
            char *endp = NULL;
1878
0
            ll = strtoll(p, &endp, 10);
1879
0
            if (p == endp || ll < 0) {
1880
                /* No number or negative */
1881
0
                CHECK_COND_OR_FAIL(1, "Invalid time limit");
1882
0
                break;
1883
0
            }
1884
0
            switch (*endp) {
1885
0
            case 'w':
1886
0
            case 'W':
1887
0
                if (ll > LLONG_MAX / 7) {
1888
0
                    SSH_LOG(SSH_LOG_TRACE, "Possible overflow of rekey limit");
1889
0
                    ll = -1;
1890
0
                    break;
1891
0
                }
1892
0
                ll = ll * 7;
1893
0
                FALL_THROUGH;
1894
0
            case 'd':
1895
0
            case 'D':
1896
0
                if (ll > LLONG_MAX / 24) {
1897
0
                    SSH_LOG(SSH_LOG_TRACE, "Possible overflow of rekey limit");
1898
0
                    ll = -1;
1899
0
                    break;
1900
0
                }
1901
0
                ll = ll * 24;
1902
0
                FALL_THROUGH;
1903
0
            case 'h':
1904
0
            case 'H':
1905
0
                if (ll > LLONG_MAX / 60) {
1906
0
                    SSH_LOG(SSH_LOG_TRACE, "Possible overflow of rekey limit");
1907
0
                    ll = -1;
1908
0
                    break;
1909
0
                }
1910
0
                ll = ll * 60;
1911
0
                FALL_THROUGH;
1912
0
            case 'm':
1913
0
            case 'M':
1914
0
                if (ll > LLONG_MAX / 60) {
1915
0
                    SSH_LOG(SSH_LOG_TRACE, "Possible overflow of rekey limit");
1916
0
                    ll = -1;
1917
0
                    break;
1918
0
                }
1919
0
                ll = ll * 60;
1920
0
                FALL_THROUGH;
1921
0
            case 's':
1922
0
            case 'S':
1923
0
                endp++;
1924
0
                FALL_THROUGH;
1925
0
            case '\0':
1926
                /* just the number */
1927
0
                break;
1928
0
            default:
1929
                /* Ignore invalid suffix and trailing garbage */
1930
0
                SSH_LOG(SSH_LOG_TRACE, "Ignoring invalid suffix");
1931
0
                break;
1932
0
            }
1933
0
        }
1934
0
        CHECK_COND_OR_FAIL(ll < 0, "Invalid time limit");
1935
0
        if (ll > -1 && *parsing) {
1936
0
            uint32_t v = (uint32_t)ll;
1937
0
            ssh_options_set(session, SSH_OPTIONS_REKEY_TIME, &v);
1938
0
        }
1939
0
        break;
1940
0
    case SOC_GSSAPIAUTHENTICATION:
1941
0
    case SOC_KBDINTERACTIVEAUTHENTICATION:
1942
0
    case SOC_PASSWORDAUTHENTICATION: {
1943
0
        enum ssh_options_e option = ssh_config_get_auth_option(opcode);
1944
0
        i = ssh_config_get_yesno(&s, -1);
1945
1946
0
        CHECK_COND_OR_FAIL(i < 0, "Authentication option");
1947
0
        if (*parsing) {
1948
0
            ssh_options_set(session, option, &i);
1949
0
        }
1950
0
        break;
1951
0
    }
1952
0
    case SOC_PUBKEYAUTHENTICATION: {
1953
0
        i = ssh_config_get_pubkey_auth(&s, -1);
1954
0
        CHECK_COND_OR_FAIL(i < 0, "Authentication option");
1955
0
        if (*parsing) {
1956
0
            ssh_options_set(session, SSH_OPTIONS_PUBKEY_AUTH, &i);
1957
0
        }
1958
0
        break;
1959
0
    }
1960
0
    case SOC_NA:
1961
0
        CHECK_COND_OR_FAIL(1, "Unapplicable option");
1962
0
        break;
1963
0
    case SOC_UNSUPPORTED:
1964
0
        CHECK_COND_OR_FAIL(1, "Unsupported option");
1965
0
        break;
1966
0
    case SOC_UNKNOWN:
1967
0
        CHECK_COND_OR_FAIL(1, "Unknown option");
1968
0
        break;
1969
0
    case SOC_IDENTITYAGENT:
1970
0
      p = ssh_config_get_str_tok(&s, NULL);
1971
0
      CHECK_COND_OR_FAIL(p == NULL, "Missing argument");
1972
0
      if (*parsing) {
1973
0
          ssh_options_set(session, SSH_OPTIONS_IDENTITY_AGENT, p);
1974
0
      }
1975
0
      break;
1976
0
    case SOC_IDENTITIESONLY:
1977
0
      i = ssh_config_get_yesno(&s, -1);
1978
0
      CHECK_COND_OR_FAIL(i < 0, "Invalid argument");
1979
0
      if (*parsing) {
1980
0
          bool b = i;
1981
0
          ssh_options_set(session, SSH_OPTIONS_IDENTITIES_ONLY, &b);
1982
0
      }
1983
0
      break;
1984
0
    case SOC_BATCHMODE:
1985
0
        i = ssh_config_get_yesno(&s, -1);
1986
0
        CHECK_COND_OR_FAIL(i < 0, "Invalid argument");
1987
0
        if (*parsing) {
1988
0
            bool b = i;
1989
0
            ssh_options_set(session, SSH_OPTIONS_BATCH_MODE, &b);
1990
0
        }
1991
0
        break;
1992
0
    case SOC_PREFERRED_AUTHENTICATIONS:
1993
0
        p = ssh_config_get_str_tok(&s, NULL);
1994
0
        CHECK_COND_OR_FAIL(p == NULL, "Missing argument");
1995
0
        if (*parsing) {
1996
0
            ssh_options_set(session, SSH_OPTIONS_PREFERRED_AUTHENTICATIONS, p);
1997
0
        }
1998
0
        break;
1999
0
    case SOC_NUMBER_OF_PASSWORD_PROMPTS:
2000
0
        l = ssh_config_get_long(&s, -1);
2001
0
        CHECK_COND_OR_FAIL(l <= 0 || l > INT_MAX, "Invalid argument");
2002
0
        if (*parsing) {
2003
0
            i = (int)l;
2004
0
            ssh_options_set(session, SSH_OPTIONS_NUMBER_OF_PASSWORD_PROMPTS, &i);
2005
0
        }
2006
0
        break;
2007
0
    case SOC_REQUEST_TTY: {
2008
0
        static const struct ssh_config_token_value_map request_tty_map[] = {
2009
0
            {"no", SSH_REQUEST_TTY_NO},
2010
0
            {"yes", SSH_REQUEST_TTY_YES},
2011
0
            {"auto", SSH_REQUEST_TTY_AUTO},
2012
0
            {"force", SSH_REQUEST_TTY_FORCE},
2013
0
            {NULL, 0},
2014
0
        };
2015
0
        i = ssh_config_get_token_value(&s, request_tty_map, -1);
2016
0
        CHECK_COND_OR_FAIL(i < 0, "Invalid argument");
2017
0
        if (*parsing) {
2018
0
            ssh_options_set(session, SSH_OPTIONS_REQUEST_TTY, &i);
2019
0
        }
2020
0
        break;
2021
0
    }
2022
0
    case SOC_CONTROLMASTER:
2023
0
      p = ssh_config_get_str_tok(&s, NULL);
2024
0
      CHECK_COND_OR_FAIL(p == NULL, "ControlMaster");
2025
0
      if (*parsing) {
2026
0
          int value = -1;
2027
2028
0
          if (strcasecmp(p, "auto") == 0) {
2029
0
              value = SSH_CONTROL_MASTER_AUTO;
2030
0
          } else if (strcasecmp(p, "yes") == 0) {
2031
0
              value = SSH_CONTROL_MASTER_YES;
2032
0
          } else if (strcasecmp(p, "no") == 0) {
2033
0
              value = SSH_CONTROL_MASTER_NO;
2034
0
          } else if (strcasecmp(p, "autoask") == 0) {
2035
0
              value = SSH_CONTROL_MASTER_AUTOASK;
2036
0
          } else if (strcasecmp(p, "ask") == 0) {
2037
0
              value = SSH_CONTROL_MASTER_ASK;
2038
0
          }
2039
2040
0
          CHECK_COND_OR_FAIL(value == -1, "Invalid argument");
2041
0
          if (value != -1) {
2042
0
              ssh_options_set(session, SSH_OPTIONS_CONTROL_MASTER, &value);
2043
0
          }
2044
0
      }
2045
0
      break;
2046
0
    case SOC_CONTROLPATH:
2047
0
      p = ssh_config_get_str_tok(&s, NULL);
2048
0
      if (p == NULL) {
2049
0
        SAFE_FREE(x);
2050
0
        return -1;
2051
0
      }
2052
0
      if (*parsing) {
2053
0
          ssh_options_set(session, SSH_OPTIONS_CONTROL_PATH, p);
2054
0
      }
2055
0
      break;
2056
0
    case SOC_CERTIFICATE:
2057
0
        p = ssh_config_get_str_tok(&s, NULL);
2058
0
        CHECK_COND_OR_FAIL(p == NULL, "Missing argument");
2059
0
        if (*parsing) {
2060
0
            ssh_options_set(session, SSH_OPTIONS_CERTIFICATE, p);
2061
0
        }
2062
0
        break;
2063
0
    case SOC_GSSAPIKEYEXCHANGE: {
2064
0
        i = ssh_config_get_yesno(&s, -1);
2065
0
        CHECK_COND_OR_FAIL(i < 0, "Invalid argument");
2066
0
        if (*parsing) {
2067
0
            bool b = (i == 1) ? true : false;
2068
0
            ssh_options_set(session, SSH_OPTIONS_GSSAPI_KEY_EXCHANGE, &b);
2069
0
        }
2070
0
        break;
2071
0
    }
2072
0
    case SOC_GSSAPIKEXALGORITHMS:
2073
0
        p = ssh_config_get_str_tok(&s, NULL);
2074
0
        CHECK_COND_OR_FAIL(p == NULL, "Missing argument");
2075
0
        if (*parsing) {
2076
0
            ssh_options_set(session, SSH_OPTIONS_GSSAPI_KEY_EXCHANGE_ALGS, p);
2077
0
        }
2078
0
        break;
2079
0
    case SOC_REQUIRED_RSA_SIZE:
2080
0
        l = ssh_config_get_long(&s, -1);
2081
0
        CHECK_COND_OR_FAIL(l < 0 || l > INT_MAX, "Invalid argument");
2082
0
        if (*parsing) {
2083
0
            i = (int)l;
2084
0
            ssh_options_set(session, SSH_OPTIONS_RSA_MIN_SIZE, &i);
2085
0
        }
2086
0
        break;
2087
0
    case SOC_ADDRESSFAMILY:
2088
0
        p = ssh_config_get_str_tok(&s, NULL);
2089
0
        if (p == NULL) {
2090
0
            SSH_LOG(SSH_LOG_WARNING,
2091
0
                    "line %d: no argument after keyword \"addressfamily\"",
2092
0
                    count);
2093
0
            SAFE_FREE(x);
2094
0
            return SSH_ERROR;
2095
0
        }
2096
0
        if (*parsing) {
2097
0
            int value = -1;
2098
2099
0
            if (strcasecmp(p, "any") == 0) {
2100
0
                value = SSH_ADDRESS_FAMILY_ANY;
2101
0
            } else if (strcasecmp(p, "inet") == 0) {
2102
0
                value = SSH_ADDRESS_FAMILY_INET;
2103
0
            } else if (strcasecmp(p, "inet6") == 0) {
2104
0
                value = SSH_ADDRESS_FAMILY_INET6;
2105
0
            } else {
2106
0
                SSH_LOG(SSH_LOG_WARNING,
2107
0
                        "line %d: invalid argument \"%s\"",
2108
0
                        count,
2109
0
                        p);
2110
0
                SAFE_FREE(x);
2111
0
                return SSH_ERROR;
2112
0
            }
2113
0
            ssh_options_set(session, SSH_OPTIONS_ADDRESS_FAMILY, &value);
2114
0
        }
2115
0
        break;
2116
0
    default:
2117
0
      ssh_set_error(session, SSH_FATAL, "ERROR - unimplemented opcode: %d",
2118
0
              opcode);
2119
0
      SAFE_FREE(x);
2120
0
      return -1;
2121
0
      break;
2122
0
  }
2123
2124
0
  SAFE_FREE(x);
2125
0
  return 0;
2126
0
}
2127
2128
#undef CHECK_COND_OR_FAIL
2129
2130
int ssh_config_parse_line(ssh_session session,
2131
                          const char *line,
2132
                          unsigned int count,
2133
                          int *parsing,
2134
                          unsigned int depth,
2135
                          bool global)
2136
0
{
2137
0
    return ssh_config_parse_line_internal(session,
2138
0
                                          line,
2139
0
                                          count,
2140
0
                                          parsing,
2141
0
                                          depth,
2142
0
                                          global,
2143
0
                                          false,
2144
0
                                          false);
2145
0
}
2146
2147
int ssh_config_parse_line_cli(ssh_session session, const char *line)
2148
0
{
2149
0
    int parsing = 1;
2150
0
    return ssh_config_parse_line_internal(session,
2151
0
                                          line,
2152
0
                                          0,
2153
0
                                          &parsing,
2154
0
                                          0,
2155
0
                                          false,
2156
0
                                          true,
2157
0
                                          true);
2158
0
}
2159
2160
/**
2161
 * @brief Parse configuration from a file pointer
2162
 *
2163
 * @param[in] session   The ssh session
2164
 * @param[in] fp        A valid file pointer
2165
 * @param[in] global    Whether the config is global or not
2166
 *
2167
 * @returns    0 on successful parsing the configuration file, -1 on error
2168
 */
2169
int ssh_config_parse(ssh_session session, FILE *fp, bool global)
2170
0
{
2171
0
    char line[MAX_LINE_SIZE] = {0};
2172
0
    unsigned int count = 0;
2173
0
    int parsing, rv;
2174
2175
0
    parsing = 1;
2176
0
    while (fgets(line, sizeof(line), fp)) {
2177
0
        count++;
2178
0
        rv = ssh_config_parse_line(session, line, count, &parsing, 0, global);
2179
0
        if (rv < 0) {
2180
0
            return -1;
2181
0
        }
2182
0
    }
2183
2184
0
    return 0;
2185
0
}
2186
2187
/**
2188
 * @brief Parse configuration file and set the options to the given session
2189
 *
2190
 * @param[in] session   The ssh session
2191
 * @param[in] filename  The path to the ssh configuration file
2192
 *
2193
 * @returns    0 on successful parsing the configuration file, -1 on error
2194
 */
2195
int ssh_config_parse_file(ssh_session session, const char *filename)
2196
0
{
2197
0
    FILE *fp = NULL;
2198
0
    int rv;
2199
0
    bool global = 0;
2200
2201
0
    fp = ssh_strict_fopen(filename, SSH_MAX_CONFIG_FILE_SIZE);
2202
0
    if (fp == NULL) {
2203
        /* The underlying function logs the reasons */
2204
0
        return 0;
2205
0
    }
2206
2207
0
    rv = strcmp(filename, GLOBAL_CLIENT_CONFIG);
2208
#ifdef USR_GLOBAL_CLIENT_CONFIG
2209
    if (rv != 0) {
2210
        rv = strcmp(filename, USR_GLOBAL_CLIENT_CONFIG);
2211
    }
2212
#endif
2213
2214
0
    if (rv == 0) {
2215
0
        global = true;
2216
0
    }
2217
2218
0
    SSH_LOG(SSH_LOG_PACKET, "Reading configuration data from %s", filename);
2219
2220
0
    rv = ssh_config_parse(session, fp, global);
2221
2222
0
    fclose(fp);
2223
0
    return rv;
2224
0
}
2225
2226
/**
2227
 * @brief Parse configuration string and set the options to the given session
2228
 *
2229
 * @param[in] session   The ssh session
2230
 * @param[in] input     Null terminated string containing the configuration
2231
 *
2232
 * @returns    SSH_OK on successful parsing the configuration string,
2233
 *             SSH_ERROR on error
2234
 */
2235
int ssh_config_parse_string(ssh_session session, const char *input)
2236
0
{
2237
0
    char line[MAX_LINE_SIZE] = {0};
2238
0
    const char *c = input, *line_start = input;
2239
0
    unsigned int line_num = 0;
2240
0
    size_t line_len;
2241
0
    int parsing, rv;
2242
2243
0
    SSH_LOG(SSH_LOG_DEBUG, "Reading configuration data from string:");
2244
0
    SSH_LOG(SSH_LOG_DEBUG, "START\n%s\nEND", input);
2245
2246
0
    parsing = 1;
2247
0
    while (1) {
2248
0
        line_num++;
2249
0
        line_start = c;
2250
0
        c = strchr(line_start, '\n');
2251
0
        if (c == NULL) {
2252
            /* if there is no newline at the end of the string */
2253
0
            c = strchr(line_start, '\0');
2254
0
        }
2255
0
        if (c == NULL) {
2256
            /* should not happen, would mean a string without trailing '\0' */
2257
0
            SSH_LOG(SSH_LOG_TRACE, "No trailing '\\0' in config string");
2258
0
            return SSH_ERROR;
2259
0
        }
2260
0
        line_len = c - line_start;
2261
0
        if (line_len > MAX_LINE_SIZE - 1) {
2262
0
            SSH_LOG(SSH_LOG_TRACE,
2263
0
                    "Line %u too long: %zu characters",
2264
0
                    line_num,
2265
0
                    line_len);
2266
0
            return SSH_ERROR;
2267
0
        }
2268
0
        memcpy(line, line_start, line_len);
2269
0
        line[line_len] = '\0';
2270
0
        SSH_LOG(SSH_LOG_DEBUG, "Line %u: %s", line_num, line);
2271
0
        rv = ssh_config_parse_line(session, line, line_num, &parsing, 0, false);
2272
0
        if (rv < 0) {
2273
0
            return SSH_ERROR;
2274
0
        }
2275
0
        if (*c == '\0') {
2276
0
            break;
2277
0
        }
2278
0
        c++;
2279
0
    }
2280
2281
0
    return SSH_OK;
2282
0
}