Coverage Report

Created: 2026-05-23 07:14

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
20.9k
#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_UNSUPPORTED, true},
118
    {"pkcs11provider", SOC_UNSUPPORTED, true},
119
    {"preferredauthentications", SOC_UNSUPPORTED, 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_NA, 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
312k
{
220
312k
    int i;
221
222
29.3M
    for (i = 0; ssh_config_keyword_table[i].name != NULL; i++) {
223
29.0M
        if (strcasecmp(keyword, ssh_config_keyword_table[i].name) == 0) {
224
5.09k
            return ssh_config_keyword_table[i].opcode;
225
5.09k
        }
226
29.0M
    }
227
228
307k
    return SOC_UNKNOWN;
229
312k
}
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
19.9k
#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
19.9k
{
252
19.9k
    FILE *f = NULL;
253
19.9k
    char line[MAX_LINE_SIZE] = {0};
254
19.9k
    unsigned int count = 0;
255
19.9k
    int rv;
256
257
19.9k
    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
19.9k
    f = ssh_strict_fopen(filename, SSH_MAX_CONFIG_FILE_SIZE);
265
19.9k
    if (f == NULL) {
266
19.6k
        SSH_LOG(SSH_LOG_RARE,
267
19.6k
                "Failed to open included configuration file %s",
268
19.6k
                filename);
269
19.6k
        return;
270
19.6k
    }
271
272
289
    SSH_LOG(SSH_LOG_PACKET, "Reading additional configuration data from %s", filename);
273
389k
    while (fgets(line, sizeof(line), f)) {
274
389k
        count++;
275
389k
        rv = ssh_config_parse_line(session, line, count, parsing, depth, global);
276
389k
        if (rv < 0) {
277
0
            fclose(f);
278
0
            return;
279
0
        }
280
389k
    }
281
282
289
    fclose(f);
283
289
    return;
284
289
}
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
586
{
293
586
    glob_t globbuf = {
294
586
        .gl_flags = 0,
295
586
    };
296
586
    int rt;
297
586
    size_t i;
298
299
586
    rt = glob(fileglob, GLOB_TILDE, NULL, &globbuf);
300
586
    if (rt == GLOB_NOMATCH) {
301
518
        globfree(&globbuf);
302
518
        return;
303
518
    } 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
19.9k
    for (i = 0; i < globbuf.gl_pathc; i++) {
311
19.9k
        local_parse_file(session, globbuf.gl_pathv[i], parsing, depth, global);
312
19.9k
    }
313
314
68
    globfree(&globbuf);
315
68
}
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
961
{
321
961
    size_t i;
322
323
8.96k
    for (i = 0; ssh_config_match_keyword_table[i].name != NULL; i++) {
324
8.36k
        if (strcasecmp(keyword, ssh_config_match_keyword_table[i].name) == 0) {
325
364
            return ssh_config_match_keyword_table[i].opcode;
326
364
        }
327
8.36k
    }
328
329
597
    return MATCH_UNKNOWN;
330
961
}
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
239
{
343
239
    if (ok <= 0 && negate == true) {
344
0
        return 1;
345
0
    }
346
239
    if (ok > 0 && negate == false) {
347
55
        return 1;
348
55
    }
349
350
184
    return 0;
351
239
}
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
239
{
364
239
    int ok, result = 0;
365
366
239
    ok = match_pattern_list(value, pattern, strlen(pattern), 0);
367
239
    result = ssh_config_match_result(ok, negate);
368
239
    SSH_LOG(SSH_LOG_TRACE, "%s '%s' against pattern '%s'%s (ok=%d)",
369
239
            result == 1 ? "Matched" : "Not matched", value, pattern,
370
239
            negate == true ? " (negated)" : "", ok);
371
239
    return result;
372
239
}
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
122
{
547
122
    const char *p = NULL;
548
549
122
    if (needs_host != NULL) {
550
122
        *needs_host = false;
551
122
    }
552
122
    if (has_unknown != NULL) {
553
122
        *has_unknown = false;
554
122
    }
555
556
122
    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
17.0k
    for (p = hostname; *p != '\0'; p++) {
564
16.9k
        if (*p == '%') {
565
4.06k
            if (p[1] == '\0') {
566
4
                ssh_set_error(session, SSH_FATAL, "Incomplete Hostname token");
567
4
                return -1;
568
4
            }
569
4.06k
            switch (p[1]) {
570
525
            case '%':
571
525
                p++;
572
525
                continue;
573
501
            case 'h':
574
501
                if (needs_host != NULL) {
575
501
                    *needs_host = true;
576
501
                }
577
501
                p++;
578
501
                continue;
579
3.03k
            default:
580
3.03k
                if (has_unknown != NULL) {
581
3.03k
                    *has_unknown = true;
582
3.03k
                }
583
3.03k
                p++;
584
3.03k
                continue;
585
4.06k
            }
586
4.06k
        }
587
16.9k
    }
588
589
118
    return 0;
590
122
}
591
592
static char *ssh_config_lowercase_hostname_pattern(const char *hostname)
593
89
{
594
89
    char *pattern = NULL;
595
89
    char *p = NULL;
596
89
    bool escape = false;
597
598
89
    if (hostname == NULL) {
599
0
        return NULL;
600
0
    }
601
602
89
    pattern = strdup(hostname);
603
89
    if (pattern == NULL) {
604
0
        return NULL;
605
0
    }
606
607
17.6k
    for (p = pattern; *p != '\0'; p++) {
608
17.5k
        if (escape) {
609
3.33k
            escape = false;
610
3.33k
            continue;
611
3.33k
        }
612
14.2k
        if (*p == '%') {
613
3.33k
            escape = true;
614
3.33k
            continue;
615
3.33k
        }
616
10.8k
        *p = tolower((unsigned char)*p);
617
10.8k
    }
618
619
89
    return pattern;
620
89
}
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
69
{
636
69
    char *c = NULL, *cp = NULL, *endp = NULL;
637
69
    char *username = NULL;
638
69
    char *hostname = NULL;
639
69
    char *port = NULL;
640
69
    char *next = NULL;
641
69
    int cmp, rv = SSH_ERROR;
642
69
    struct ssh_jump_info_struct *jump_host = NULL;
643
69
    bool parse_entry = do_parsing;
644
69
    bool libssh_proxy_jump = ssh_libssh_proxy_jumps();
645
646
69
    if (do_parsing) {
647
39
        SAFE_FREE(session->opts.proxy_jumps_str);
648
39
        ssh_proxyjumps_free(session->opts.proxy_jumps);
649
39
    }
650
    /* Special value none disables the proxy */
651
69
    cmp = strcasecmp(s, "none");
652
69
    if (cmp == 0) {
653
2
        if (!libssh_proxy_jump && do_parsing) {
654
0
            ssh_options_set(session, SSH_OPTIONS_PROXYCOMMAND, s);
655
0
        }
656
2
        return SSH_OK;
657
2
    }
658
659
    /* This is comma-separated list of [user@]host[:port] entries */
660
67
    c = strdup(s);
661
67
    if (c == NULL) {
662
0
        ssh_set_error_oom(session);
663
0
        return SSH_ERROR;
664
0
    }
665
666
67
    if (do_parsing) {
667
        /* Store the whole string in session */
668
39
        SAFE_FREE(session->opts.proxy_jumps_str);
669
39
        session->opts.proxy_jumps_str = strdup(s);
670
39
        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
39
    }
676
677
67
    cp = c;
678
2.18k
    do {
679
2.18k
        endp = strchr(cp, ',');
680
2.18k
        if (endp != NULL) {
681
            /* Split out the token */
682
2.11k
            *endp = '\0';
683
2.11k
        }
684
2.18k
        if (parse_entry && libssh_proxy_jump) {
685
2.13k
            jump_host = calloc(1, sizeof(struct ssh_jump_info_struct));
686
2.13k
            if (jump_host == NULL) {
687
0
                ssh_set_error_oom(session);
688
0
                rv = SSH_ERROR;
689
0
                goto out;
690
0
            }
691
692
2.13k
            rv = ssh_config_parse_uri(cp,
693
2.13k
                                      &jump_host->username,
694
2.13k
                                      &jump_host->hostname,
695
2.13k
                                      &port,
696
2.13k
                                      false,
697
2.13k
                                      false);
698
2.13k
            if (rv != SSH_OK) {
699
15
                ssh_set_error_invalid(session);
700
15
                SAFE_FREE(jump_host);
701
15
                goto out;
702
15
            }
703
2.11k
            if (port == NULL) {
704
1.94k
                jump_host->port = 22;
705
1.94k
            } else {
706
175
                jump_host->port = strtol(port, NULL, 10);
707
175
                SAFE_FREE(port);
708
175
            }
709
710
            /* Prepend because we will recursively proxy jump */
711
2.11k
            rv = ssh_list_prepend(session->opts.proxy_jumps, jump_host);
712
2.11k
            if (rv != SSH_OK) {
713
0
                ssh_set_error_oom(session);
714
0
                SAFE_FREE(jump_host);
715
0
                goto out;
716
0
            }
717
2.11k
        } 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
50
        } else {
739
            /* The rest is just sanity-checked to avoid failures later */
740
50
            rv = ssh_config_parse_uri(cp, NULL, NULL, NULL, false, false);
741
50
            if (rv != SSH_OK) {
742
1
                ssh_set_error_invalid(session);
743
1
                goto out;
744
1
            }
745
50
        }
746
2.16k
        if (!libssh_proxy_jump) {
747
0
            parse_entry = 0;
748
0
        }
749
2.16k
        if (endp != NULL) {
750
2.11k
            cp = endp + 1;
751
2.11k
        } else {
752
51
            cp = NULL; /* end */
753
51
        }
754
2.16k
    } while (cp != NULL);
755
756
51
    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
51
    rv = SSH_OK;
780
781
67
out:
782
67
    if (rv != SSH_OK) {
783
16
        ssh_proxyjumps_free(session->opts.proxy_jumps);
784
16
    }
785
67
    SAFE_FREE(username);
786
67
    SAFE_FREE(hostname);
787
67
    SAFE_FREE(port);
788
67
    SAFE_FREE(next);
789
67
    SAFE_FREE(c);
790
67
    return rv;
791
51
}
792
793
static char *
794
ssh_config_make_absolute(ssh_session session,
795
                         const char *path,
796
                         bool global)
797
595
{
798
595
    size_t outlen = 0;
799
595
    char *out = NULL;
800
595
    int rv;
801
802
    /* Looks like absolute path */
803
595
    if (path[0] == '/') {
804
104
        return strdup(path);
805
104
    }
806
807
    /* relative path */
808
491
    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
491
    if (path[0] == '~') {
826
459
        return ssh_path_expand_tilde(path);
827
459
    }
828
829
    /* Parsing user config relative to home directory (generally ~/.ssh) */
830
32
    if (session->opts.sshdir == NULL) {
831
0
        ssh_set_error_invalid(session);
832
0
        return NULL;
833
0
    }
834
32
    outlen = strlen(path) + strlen(session->opts.sshdir) + 1 + 1;
835
32
    out = malloc(outlen);
836
32
    if (out == NULL) {
837
0
        ssh_set_error_oom(session);
838
0
        return NULL;
839
0
    }
840
32
    rv = snprintf(out, outlen, "%s/%s", session->opts.sshdir, path);
841
32
    if (rv < 1) {
842
0
        free(out);
843
0
        return NULL;
844
0
    }
845
32
    return out;
846
32
}
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
37
{
944
37
    struct auth_option_map {
945
37
        enum ssh_config_opcode_e opcode;
946
37
        const char *name;
947
37
        enum ssh_options_e option;
948
37
    };
949
950
37
    static struct auth_option_map auth_options[] = {
951
37
        {
952
37
            SOC_GSSAPIAUTHENTICATION,
953
37
            "GSSAPIAuthentication",
954
37
            SSH_OPTIONS_GSSAPI_AUTH,
955
37
        },
956
37
        {
957
37
            SOC_KBDINTERACTIVEAUTHENTICATION,
958
37
            "KbdInteractiveAuthentication",
959
37
            SSH_OPTIONS_KBDINT_AUTH,
960
37
        },
961
37
        {
962
37
            SOC_PASSWORDAUTHENTICATION,
963
37
            "PasswordAuthentication",
964
37
            SSH_OPTIONS_PASSWORD_AUTH,
965
37
        },
966
37
        {
967
37
            SOC_PUBKEYAUTHENTICATION,
968
37
            "PubkeyAuthentication",
969
37
            SSH_OPTIONS_PUBKEY_AUTH,
970
37
        },
971
37
        {0, NULL, 0},
972
37
    };
973
974
59
    for (struct auth_option_map *map = auth_options; map->name != NULL; map++) {
975
59
        if (map->opcode == opcode) {
976
37
            return map->option;
977
37
        }
978
59
    }
979
0
    return -1;
980
37
}
981
982
static long ssh_config_convtime(const char *p, long notfound)
983
2
{
984
2
    char *endp = NULL;
985
2
    long total = 0;
986
2
    long value;
987
2
    long multiplier;
988
989
2
    if (p == NULL || *p == '\0') {
990
0
        return notfound;
991
0
    }
992
993
2
    while (*p != '\0') {
994
2
        errno = 0;
995
2
        value = strtol(p, &endp, 10);
996
2
        if (p == endp || errno != 0 || value < 0) {
997
2
            return notfound;
998
2
        }
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
2
}
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
74
{
1055
74
    const char *p = NULL;
1056
74
    size_t i;
1057
1058
74
    p = ssh_config_get_str_tok(str, NULL);
1059
74
    if (p == NULL) {
1060
2
        return notfound;
1061
2
    }
1062
1063
539
    for (i = 0; map[i].token != NULL; i++) {
1064
467
        if (strcasecmp(p, map[i].token) == 0) {
1065
0
            return map[i].value;
1066
0
        }
1067
467
    }
1068
1069
72
    return notfound;
1070
72
}
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
63
{
1087
63
    static const struct ssh_config_token_value_map strict_hostkey_map[] = {
1088
63
        {"yes", SSH_STRICT_HOSTKEY_YES},
1089
63
        {"true", SSH_STRICT_HOSTKEY_YES},
1090
63
        {"no", SSH_STRICT_HOSTKEY_OFF},
1091
63
        {"false", SSH_STRICT_HOSTKEY_OFF},
1092
63
        {"off", SSH_STRICT_HOSTKEY_OFF},
1093
63
        {"ask", SSH_STRICT_HOSTKEY_ASK},
1094
63
        {"accept-new", SSH_STRICT_HOSTKEY_ACCEPT_NEW},
1095
63
        {NULL, 0},
1096
63
    };
1097
1098
63
    return ssh_config_get_token_value(str, strict_hostkey_map, notfound);
1099
63
}
1100
1101
static int ssh_config_get_pubkey_auth(char **str, int notfound)
1102
4
{
1103
4
    static const struct ssh_config_token_value_map pubkey_auth_map[] = {
1104
4
        {"yes", SSH_PUBKEY_AUTH_ALL},
1105
4
        {"true", SSH_PUBKEY_AUTH_ALL},
1106
4
        {"no", SSH_PUBKEY_AUTH_NO},
1107
4
        {"false", SSH_PUBKEY_AUTH_NO},
1108
4
        {"unbound", SSH_PUBKEY_AUTH_UNBOUND},
1109
4
        {"host-bound", SSH_PUBKEY_AUTH_HOST_BOUND},
1110
4
        {NULL, 0},
1111
4
    };
1112
1113
4
    return ssh_config_get_token_value(str, pubkey_auth_map, notfound);
1114
4
}
1115
#define CHECK_COND_OR_FAIL(cond, error_message)                \
1116
311k
    if ((cond)) {                                              \
1117
308k
        SSH_LOG(SSH_LOG_DEBUG,                                 \
1118
308k
                "line %d: %s: %s",                             \
1119
308k
                count,                                         \
1120
308k
                error_message,                                 \
1121
308k
                keyword);                                      \
1122
308k
        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
308k
        break;                                                 \
1134
308k
    }
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
410k
{
1145
410k
  enum ssh_config_opcode_e opcode;
1146
410k
  const char *p = NULL, *p2 = NULL;
1147
410k
  char *s = NULL, *x = NULL;
1148
410k
  char *keyword = NULL;
1149
410k
  char *lowerhost = NULL;
1150
410k
  size_t len;
1151
410k
  int i, rv, cmp;
1152
410k
  uint8_t *seen = session->opts.options_seen;
1153
410k
  long l;
1154
410k
  int64_t ll;
1155
1156
  /* Ignore empty lines */
1157
410k
  if (line == NULL || *line == '\0') {
1158
83.7k
      if (is_cli) {
1159
0
          return SSH_ERROR;
1160
0
      }
1161
83.7k
    return 0;
1162
83.7k
  }
1163
1164
326k
  x = s = strdup(line);
1165
326k
  if (s == NULL) {
1166
0
    ssh_set_error_oom(session);
1167
0
    return -1;
1168
0
  }
1169
1170
  /* Remove trailing spaces */
1171
521k
  for (len = strlen(s) - 1; len > 0; len--) {
1172
492k
    if (! isspace(s[len])) {
1173
297k
      break;
1174
297k
    }
1175
195k
    s[len] = '\0';
1176
195k
  }
1177
1178
326k
  keyword = ssh_config_get_token(&s);
1179
326k
  if (keyword == NULL || *keyword == '#' ||
1180
321k
      *keyword == '\0' || *keyword == '\n') {
1181
13.3k
    SAFE_FREE(x);
1182
13.3k
    return 0;
1183
13.3k
  }
1184
1185
312k
  opcode = ssh_config_get_opcode(keyword);
1186
312k
  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
312k
  if (*parsing == 1 &&
1197
194k
      opcode != SOC_HOST &&
1198
194k
      opcode != SOC_MATCH &&
1199
194k
      opcode != SOC_INCLUDE &&
1200
194k
      opcode != SOC_IDENTITY &&
1201
193k
      opcode != SOC_CERTIFICATE &&
1202
191k
      opcode > SOC_UNSUPPORTED &&
1203
590
      opcode < SOC_MAX) { /* Ignore all unknown types here */
1204
      /* Skip all the options that were already applied */
1205
590
      if (seen[opcode] != 0) {
1206
182
          SAFE_FREE(x);
1207
182
          return 0;
1208
182
      }
1209
408
      seen[opcode] = 1;
1210
408
  }
1211
1212
312k
  switch (opcode) {
1213
617
    case SOC_INCLUDE: /* recursive include of other files */
1214
1215
617
      p = ssh_config_get_str_tok(&s, NULL);
1216
617
      if (p && *parsing) {
1217
595
        char *path = ssh_config_make_absolute(session, p, global);
1218
595
        if (path == NULL) {
1219
9
          SSH_LOG(SSH_LOG_WARN, "line %d: Failed to allocate memory "
1220
9
                  "for the include path expansion", count);
1221
9
          SAFE_FREE(x);
1222
9
          return -1;
1223
9
        }
1224
586
#if defined(HAVE_GLOB) && defined(HAVE_GLOB_GL_FLAGS_MEMBER)
1225
586
        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
586
        free(path);
1230
586
      }
1231
608
      break;
1232
1233
713
    case SOC_MATCH: {
1234
713
        bool negate;
1235
713
        int match_error = 0;
1236
713
        int result = 1;
1237
713
        size_t args = 0;
1238
713
        enum ssh_config_match_e opt;
1239
713
        struct ssh_config_token_info keyword_info;
1240
713
        struct ssh_config_token_info arg_info;
1241
713
        char *localuser = NULL;
1242
713
        const char *version = NULL;
1243
1244
713
        *parsing = 0;
1245
1.55k
        do {
1246
1.55k
            p = p2 = ssh_config_get_token_info(&s, &keyword_info);
1247
1.55k
            if (!keyword_info.found || p[0] == '\0') {
1248
590
                break;
1249
590
            }
1250
961
            args++;
1251
961
            SSH_LOG(SSH_LOG_DEBUG, "line %d: Processing Match keyword '%s'",
1252
961
                    count, p);
1253
1254
            /* If the option is prefixed with ! the result should be negated */
1255
961
            negate = false;
1256
961
            if (p[0] == '!') {
1257
190
                negate = true;
1258
190
                p++;
1259
190
            }
1260
1261
961
            opt = ssh_config_get_match_opcode(p);
1262
961
            switch (opt) {
1263
108
            case MATCH_ALL:
1264
108
                p = ssh_config_get_str_tok(&s, NULL);
1265
108
                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
107
                    if (negate == true) {
1270
81
                        result = 0;
1271
81
                    }
1272
107
                    break;
1273
107
                }
1274
1275
1
                ssh_set_error(session, SSH_FATAL,
1276
1
                              "line %d: ERROR - Match all cannot be combined with "
1277
1
                              "other Match attributes", count);
1278
1
                match_error = -1;
1279
1
                goto out_match;
1280
1281
0
            case MATCH_FINAL:
1282
3
            case MATCH_CANONICAL:
1283
3
                SSH_LOG(SSH_LOG_DEBUG,
1284
3
                        "line %d: Unsupported Match keyword '%s', skipping",
1285
3
                        count,
1286
3
                        p);
1287
                /* Not set any result here -- the result is dependent on the
1288
                 * following matches after this keyword */
1289
3
                break;
1290
1291
1
            case MATCH_EXEC:
1292
                /* Skip one argument (including in quotes) */
1293
1
                p = ssh_config_get_token(&s);
1294
1
                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
1
                if (result != 1) {
1301
1
                    SSH_LOG(SSH_LOG_DEBUG, "line %d: Skipped match exec "
1302
1
                            "'%s' as previous conditions already failed.",
1303
1
                            count, p2);
1304
1
                    continue;
1305
1
                }
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
53
            case MATCH_HOST:
1352
                /* Here we match only one argument */
1353
53
                p = ssh_config_get_str_tok(&s, NULL);
1354
53
                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
53
                result &= ssh_config_match(session->opts.host
1362
53
                                               ? session->opts.host
1363
53
                                               : session->opts.originalhost,
1364
53
                                           p,
1365
53
                                           negate);
1366
53
                args++;
1367
53
                break;
1368
1369
186
            case MATCH_USER:
1370
                /* Here we match only one argument */
1371
186
                p = ssh_config_get_str_tok(&s, NULL);
1372
186
                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
186
                result &= ssh_config_match(session->opts.username, p, negate);
1380
186
                args++;
1381
186
                break;
1382
1383
13
            case MATCH_LOCALNETWORK:
1384
                /* Here we match only one argument */
1385
13
                p = ssh_config_get_str_tok(&s, NULL);
1386
13
                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
13
#ifdef HAVE_IFADDRS_H
1396
13
                rv = match_cidr_address_list(NULL, p, -1);
1397
13
                if (rv == -1) {
1398
13
                    ssh_set_error(session,
1399
13
                                  SSH_FATAL,
1400
13
                                  "line %d: ERROR - List invalid entry: %s",
1401
13
                                  count,
1402
13
                                  p);
1403
13
                    match_error = -1;
1404
13
                    goto out_match;
1405
13
                }
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
597
            case MATCH_UNKNOWN:
1493
597
            default:
1494
597
                SSH_LOG(SSH_LOG_WARN,
1495
597
                        "Unknown argument '%s' for Match keyword. Not matching",
1496
597
                        p);
1497
597
                result = 0;
1498
597
                break;
1499
961
            }
1500
961
        } while (p != NULL && p[0] != '\0');
1501
699
        if (args == 0) {
1502
12
            SSH_LOG(SSH_LOG_WARN,
1503
12
                    "ERROR - Match keyword requires an argument. Not matching");
1504
12
            result = 0;
1505
12
        }
1506
713
    out_match:
1507
713
        SAFE_FREE(localuser);
1508
713
        if (match_error != 0) {
1509
14
            SAFE_FREE(x);
1510
14
            return -1;
1511
14
        }
1512
699
        *parsing = result;
1513
699
        break;
1514
713
    }
1515
153
    case SOC_HOST: {
1516
153
        int ok = 0, result = -1;
1517
1518
153
        *parsing = 0;
1519
153
        lowerhost = (session->opts.originalhost)
1520
153
                        ? ssh_lowercase(session->opts.originalhost)
1521
153
                        : NULL;
1522
153
        for (p = ssh_config_get_str_tok(&s, NULL);
1523
769
             p != NULL && p[0] != '\0';
1524
616
             p = ssh_config_get_str_tok(&s, NULL)) {
1525
616
             if (ok >= 0) {
1526
603
               ok = match_hostname(lowerhost, p, strlen(p));
1527
603
               if (result == -1 && ok < 0) {
1528
8
                   result = 0;
1529
595
               } else if (result == -1 && ok > 0) {
1530
2
                   result = 1;
1531
2
               }
1532
603
            }
1533
616
        }
1534
153
        SAFE_FREE(lowerhost);
1535
153
        if (result != -1) {
1536
10
            *parsing = result;
1537
10
        }
1538
153
        break;
1539
713
    }
1540
1
    case SOC_TAG:
1541
1
        p = ssh_config_get_str_tok(&s, NULL);
1542
1
        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
1
        if (*parsing) {
1551
1
            char *tag = NULL;
1552
1553
1
            tag = strdup(p);
1554
1
            if (tag == NULL) {
1555
0
                ssh_set_error_oom(session);
1556
0
                SAFE_FREE(x);
1557
0
                return SSH_ERROR;
1558
0
            }
1559
1
            SAFE_FREE(session->opts.tag);
1560
1
            session->opts.tag = tag;
1561
1
        }
1562
1
        break;
1563
148
    case SOC_HOSTNAME:
1564
148
        p = ssh_config_get_str_tok(&s, NULL);
1565
148
        CHECK_COND_OR_FAIL(p == NULL, "Missing argument");
1566
148
        if (*parsing) {
1567
122
            int rc;
1568
122
            bool had_expansion = strchr(p, '%') != NULL;
1569
122
            bool needs_host = false;
1570
122
            bool has_unknown = false;
1571
122
            rc = ssh_config_scan_hostname_tokens(session,
1572
122
                                                 p,
1573
122
                                                 &needs_host,
1574
122
                                                 &has_unknown);
1575
122
            if (rc < 0) {
1576
4
                SAFE_FREE(x);
1577
4
                return -1;
1578
4
            }
1579
118
            if (had_expansion) {
1580
94
                if (!has_unknown &&
1581
5
                    (!needs_host || session->opts.host != NULL ||
1582
5
                     session->opts.originalhost != NULL)) {
1583
5
                    char *expanded = ssh_path_expand_hostname(session, p);
1584
5
                    if (expanded == NULL) {
1585
0
                        SAFE_FREE(x);
1586
0
                        return -1;
1587
0
                    }
1588
5
                    session->opts.config_hostname_only = true;
1589
5
                    rv = ssh_options_set(session, SSH_OPTIONS_HOST, expanded);
1590
5
                    session->opts.config_hostname_only = false;
1591
5
                    free(expanded);
1592
5
                    if (rv != SSH_OK) {
1593
                        /* Expanded HostName values remain fatal if host
1594
                         * validation rejects the resulting hostname.
1595
                         */
1596
5
                        SAFE_FREE(x);
1597
5
                        return -1;
1598
5
                    }
1599
89
                } else {
1600
89
                    char *hostname_pattern =
1601
89
                        ssh_config_lowercase_hostname_pattern(p);
1602
89
                    if (hostname_pattern == NULL) {
1603
0
                        ssh_set_error_oom(session);
1604
0
                        SAFE_FREE(x);
1605
0
                        return -1;
1606
0
                    }
1607
89
                    SAFE_FREE(session->opts.config_hostname);
1608
89
                    session->opts.config_hostname = hostname_pattern;
1609
89
                }
1610
94
            } else {
1611
24
                char *lower = ssh_lowercase(p);
1612
24
                if (lower == NULL) {
1613
0
                    ssh_set_error_oom(session);
1614
0
                    SAFE_FREE(x);
1615
0
                    return -1;
1616
0
                }
1617
24
                session->opts.config_hostname_only = true;
1618
24
                ssh_options_set(session, SSH_OPTIONS_HOST, lower);
1619
24
                session->opts.config_hostname_only = false;
1620
24
                free(lower);
1621
24
            }
1622
118
        }
1623
139
        break;
1624
139
    case SOC_PORT:
1625
25
        l = ssh_config_get_long(&s, -1);
1626
25
        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
169
    case SOC_USERNAME:
1633
169
        p = ssh_config_get_str_tok(&s, NULL);
1634
169
        CHECK_COND_OR_FAIL(p == NULL, "Missing argument");
1635
167
        if (*parsing) {
1636
32
            ssh_options_set(session, SSH_OPTIONS_USER, p);
1637
32
        }
1638
167
        break;
1639
573
    case SOC_IDENTITY:
1640
573
      p = ssh_config_get_str_tok(&s, NULL);
1641
573
      CHECK_COND_OR_FAIL(p == NULL, "Missing argument");
1642
573
      if (*parsing) {
1643
570
          ssh_options_set(session, SSH_OPTIONS_ADD_IDENTITY, p);
1644
570
      }
1645
573
      break;
1646
21
    case SOC_CIPHERS:
1647
21
      p = ssh_config_get_str_tok(&s, NULL);
1648
21
      CHECK_COND_OR_FAIL(p == NULL, "Missing argument");
1649
16
      if (*parsing) {
1650
16
          ssh_options_set(session, SSH_OPTIONS_CIPHERS_C_S, p);
1651
16
          ssh_options_set(session, SSH_OPTIONS_CIPHERS_S_C, p);
1652
16
      }
1653
16
      break;
1654
76
    case SOC_MACS:
1655
76
      p = ssh_config_get_str_tok(&s, NULL);
1656
76
      CHECK_COND_OR_FAIL(p == NULL, "Missing argument");
1657
73
      if (*parsing) {
1658
65
          ssh_options_set(session, SSH_OPTIONS_HMAC_C_S, p);
1659
65
          ssh_options_set(session, SSH_OPTIONS_HMAC_S_C, p);
1660
65
      }
1661
73
      break;
1662
7
    case SOC_COMPRESSION:
1663
7
      i = ssh_config_get_token_value(&s, compression_map, -1);
1664
7
      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
4
    case SOC_TIMEOUT:
1674
4
        p = ssh_config_get_str_tok(&s, NULL);
1675
4
        CHECK_COND_OR_FAIL(p == NULL, "Missing argument");
1676
2
        cmp = strcmp(p, "none");
1677
2
        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
2
        l = ssh_config_convtime(p, -1);
1685
2
        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
63
    case SOC_STRICTHOSTKEYCHECK:
1691
63
      i = ssh_config_get_strict_hostkey(&s, -1);
1692
63
      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
69
    case SOC_PROXYJUMP:
1713
69
        p = ssh_config_get_str_tok(&s, NULL);
1714
69
        if (p == NULL) {
1715
0
            SAFE_FREE(x);
1716
0
            return -1;
1717
0
        }
1718
        /* We share the seen value with the ProxyCommand */
1719
69
        rv = ssh_config_parse_proxy_jump(session,
1720
69
                                         p,
1721
69
                                         (*parsing && !seen[SOC_PROXYCOMMAND]));
1722
69
        if (rv != SSH_OK) {
1723
16
            SAFE_FREE(x);
1724
16
            return -1;
1725
16
        }
1726
53
        break;
1727
53
    case SOC_GSSAPISERVERIDENTITY:
1728
2
      p = ssh_config_get_str_tok(&s, NULL);
1729
2
      CHECK_COND_OR_FAIL(p == NULL, "Missing argument");
1730
2
      if (*parsing) {
1731
2
          ssh_options_set(session, SSH_OPTIONS_GSSAPI_SERVER_IDENTITY, p);
1732
2
      }
1733
2
      break;
1734
5
    case SOC_GSSAPICLIENTIDENTITY:
1735
5
      p = ssh_config_get_str_tok(&s, NULL);
1736
5
      CHECK_COND_OR_FAIL(p == NULL, "Missing argument");
1737
5
      if (*parsing) {
1738
1
          ssh_options_set(session, SSH_OPTIONS_GSSAPI_CLIENT_IDENTITY, p);
1739
1
      }
1740
5
      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
48
    case SOC_BINDADDRESS:
1749
48
        p = ssh_config_get_str_tok(&s, NULL);
1750
48
        CHECK_COND_OR_FAIL(p == NULL, "Missing argument");
1751
16
        if (*parsing) {
1752
0
            ssh_options_set(session, SSH_OPTIONS_BINDADDR, p);
1753
0
        }
1754
16
        break;
1755
7
    case SOC_GLOBALKNOWNHOSTSFILE:
1756
7
        p = ssh_config_get_str_tok(&s, NULL);
1757
7
        CHECK_COND_OR_FAIL(p == NULL, "Missing argument");
1758
4
        if (*parsing) {
1759
3
            ssh_options_set(session, SSH_OPTIONS_GLOBAL_KNOWNHOSTS, p);
1760
3
        }
1761
4
        break;
1762
19
    case SOC_LOGLEVEL:
1763
19
        p = ssh_config_get_str_tok(&s, NULL);
1764
19
        CHECK_COND_OR_FAIL(p == NULL, "Missing argument");
1765
5
        if (*parsing) {
1766
5
            int value = -1;
1767
1768
5
            if (strcasecmp(p, "quiet") == 0) {
1769
0
                value = SSH_LOG_NONE;
1770
5
            } else if (strcasecmp(p, "fatal") == 0 ||
1771
5
                    strcasecmp(p, "error")== 0) {
1772
0
                value = SSH_LOG_WARN;
1773
5
            } else if (strcasecmp(p, "verbose") == 0 ||
1774
5
                    strcasecmp(p, "info") == 0) {
1775
0
                value = SSH_LOG_INFO;
1776
5
            } else if (strcasecmp(p, "DEBUG") == 0 ||
1777
5
                    strcasecmp(p, "DEBUG1") == 0) {
1778
0
                value = SSH_LOG_DEBUG;
1779
5
            } else if (strcasecmp(p, "DEBUG2") == 0 ||
1780
5
                    strcasecmp(p, "DEBUG3") == 0) {
1781
0
                value = SSH_LOG_TRACE;
1782
0
            }
1783
5
            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
47
    case SOC_HOSTKEYALGORITHMS:
1790
47
        p = ssh_config_get_str_tok(&s, NULL);
1791
47
        CHECK_COND_OR_FAIL(p == NULL, "Missing argument");
1792
18
        if (*parsing) {
1793
18
            ssh_options_set(session, SSH_OPTIONS_HOSTKEYS, p);
1794
18
        }
1795
18
        break;
1796
32
    case SOC_PUBKEYACCEPTEDKEYTYPES:
1797
32
        p = ssh_config_get_str_tok(&s, NULL);
1798
32
        CHECK_COND_OR_FAIL(p == NULL, "Missing argument");
1799
27
        if (*parsing) {
1800
26
            ssh_options_set(session, SSH_OPTIONS_PUBLICKEY_ACCEPTED_TYPES, p);
1801
26
        }
1802
27
        break;
1803
9
    case SOC_KEXALGORITHMS:
1804
9
        p = ssh_config_get_str_tok(&s, NULL);
1805
9
        CHECK_COND_OR_FAIL(p == NULL, "Missing argument");
1806
6
        if (*parsing) {
1807
6
            ssh_options_set(session, SSH_OPTIONS_KEY_EXCHANGE, p);
1808
6
        }
1809
6
        break;
1810
338
    case SOC_REKEYLIMIT:
1811
        /* Parse the data limit */
1812
338
        p = ssh_config_get_str_tok(&s, NULL);
1813
338
        if (p == NULL) {
1814
3
            CHECK_COND_OR_FAIL(1, "Missing data limit");
1815
0
            break;
1816
335
        } else if (strcmp(p, "default") == 0) {
1817
            /* Default rekey limits enforced automatically */
1818
0
            ll = 0;
1819
335
        } else {
1820
335
            char *endp = NULL;
1821
335
            ll = strtoll(p, &endp, 10);
1822
335
            if (p == endp || ll < 0) {
1823
3
                CHECK_COND_OR_FAIL(1, "Invalid data limit");
1824
0
                break;
1825
3
            }
1826
332
            switch (*endp) {
1827
0
            case 'g':
1828
1
            case 'G':
1829
1
                if (ll > LLONG_MAX / 1024) {
1830
1
                    SSH_LOG(SSH_LOG_TRACE, "Possible overflow of rekey limit");
1831
1
                    ll = -1;
1832
1
                    break;
1833
1
                }
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
317
            case '\0':
1856
                /* just the number */
1857
317
                break;
1858
14
            default:
1859
                /* Ignore invalid suffix and trailing garbage */
1860
14
                SSH_LOG(SSH_LOG_TRACE, "Ignoring invalid suffix");
1861
14
                break;
1862
332
            }
1863
332
        }
1864
332
        CHECK_COND_OR_FAIL(ll < 0, "Invalid data limit");
1865
331
        CHECK_COND_OR_FAIL(ll > 0 && ll < 16, "RekeyLimit too small");
1866
237
        if (*parsing) {
1867
14
            uint64_t v = (uint64_t)ll;
1868
14
            ssh_options_set(session, SSH_OPTIONS_REKEY_DATA, &v);
1869
14
        }
1870
        /* Parse the time limit */
1871
237
        p = ssh_config_get_str_tok(&s, NULL);
1872
237
        if (p == NULL) {
1873
2
            break;
1874
235
        } else if (strcmp(p, "none") == 0) {
1875
0
            ll = 0;
1876
235
        } else {
1877
235
            char *endp = NULL;
1878
235
            ll = strtoll(p, &endp, 10);
1879
235
            if (p == endp || ll < 0) {
1880
                /* No number or negative */
1881
39
                CHECK_COND_OR_FAIL(1, "Invalid time limit");
1882
0
                break;
1883
39
            }
1884
196
            switch (*endp) {
1885
31
            case 'w':
1886
31
            case 'W':
1887
31
                if (ll > LLONG_MAX / 7) {
1888
30
                    SSH_LOG(SSH_LOG_TRACE, "Possible overflow of rekey limit");
1889
30
                    ll = -1;
1890
30
                    break;
1891
30
                }
1892
1
                ll = ll * 7;
1893
1
                FALL_THROUGH;
1894
6
            case 'd':
1895
16
            case 'D':
1896
16
                if (ll > LLONG_MAX / 24) {
1897
16
                    SSH_LOG(SSH_LOG_TRACE, "Possible overflow of rekey limit");
1898
16
                    ll = -1;
1899
16
                    break;
1900
16
                }
1901
0
                ll = ll * 24;
1902
0
                FALL_THROUGH;
1903
3
            case 'h':
1904
3
            case 'H':
1905
3
                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
3
                ll = ll * 60;
1911
3
                FALL_THROUGH;
1912
30
            case 'm':
1913
35
            case 'M':
1914
35
                if (ll > LLONG_MAX / 60) {
1915
10
                    SSH_LOG(SSH_LOG_TRACE, "Possible overflow of rekey limit");
1916
10
                    ll = -1;
1917
10
                    break;
1918
10
                }
1919
25
                ll = ll * 60;
1920
25
                FALL_THROUGH;
1921
25
            case 's':
1922
55
            case 'S':
1923
55
                endp++;
1924
55
                FALL_THROUGH;
1925
84
            case '\0':
1926
                /* just the number */
1927
84
                break;
1928
56
            default:
1929
                /* Ignore invalid suffix and trailing garbage */
1930
56
                SSH_LOG(SSH_LOG_TRACE, "Ignoring invalid suffix");
1931
56
                break;
1932
196
            }
1933
196
        }
1934
196
        CHECK_COND_OR_FAIL(ll < 0, "Invalid time limit");
1935
140
        if (ll > -1 && *parsing) {
1936
4
            uint32_t v = (uint32_t)ll;
1937
4
            ssh_options_set(session, SSH_OPTIONS_REKEY_TIME, &v);
1938
4
        }
1939
140
        break;
1940
19
    case SOC_GSSAPIAUTHENTICATION:
1941
33
    case SOC_KBDINTERACTIVEAUTHENTICATION:
1942
37
    case SOC_PASSWORDAUTHENTICATION: {
1943
37
        enum ssh_options_e option = ssh_config_get_auth_option(opcode);
1944
37
        i = ssh_config_get_yesno(&s, -1);
1945
1946
37
        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
37
    }
1952
4
    case SOC_PUBKEYAUTHENTICATION: {
1953
4
        i = ssh_config_get_pubkey_auth(&s, -1);
1954
4
        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
4
    }
1960
55
    case SOC_NA:
1961
55
        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
307k
    case SOC_UNKNOWN:
1967
307k
        CHECK_COND_OR_FAIL(1, "Unknown option");
1968
0
        break;
1969
15
    case SOC_IDENTITYAGENT:
1970
15
      p = ssh_config_get_str_tok(&s, NULL);
1971
15
      CHECK_COND_OR_FAIL(p == NULL, "Missing argument");
1972
1
      if (*parsing) {
1973
1
          ssh_options_set(session, SSH_OPTIONS_IDENTITY_AGENT, p);
1974
1
      }
1975
1
      break;
1976
37
    case SOC_IDENTITIESONLY:
1977
37
      i = ssh_config_get_yesno(&s, -1);
1978
37
      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_CONTROLMASTER:
1993
0
      p = ssh_config_get_str_tok(&s, NULL);
1994
0
      CHECK_COND_OR_FAIL(p == NULL, "ControlMaster");
1995
0
      if (*parsing) {
1996
0
          int value = -1;
1997
1998
0
          if (strcasecmp(p, "auto") == 0) {
1999
0
              value = SSH_CONTROL_MASTER_AUTO;
2000
0
          } else if (strcasecmp(p, "yes") == 0) {
2001
0
              value = SSH_CONTROL_MASTER_YES;
2002
0
          } else if (strcasecmp(p, "no") == 0) {
2003
0
              value = SSH_CONTROL_MASTER_NO;
2004
0
          } else if (strcasecmp(p, "autoask") == 0) {
2005
0
              value = SSH_CONTROL_MASTER_AUTOASK;
2006
0
          } else if (strcasecmp(p, "ask") == 0) {
2007
0
              value = SSH_CONTROL_MASTER_ASK;
2008
0
          }
2009
2010
0
          CHECK_COND_OR_FAIL(value == -1, "Invalid argument");
2011
0
          if (value != -1) {
2012
0
              ssh_options_set(session, SSH_OPTIONS_CONTROL_MASTER, &value);
2013
0
          }
2014
0
      }
2015
0
      break;
2016
0
    case SOC_CONTROLPATH:
2017
0
      p = ssh_config_get_str_tok(&s, NULL);
2018
0
      if (p == NULL) {
2019
0
        SAFE_FREE(x);
2020
0
        return -1;
2021
0
      }
2022
0
      if (*parsing) {
2023
0
          ssh_options_set(session, SSH_OPTIONS_CONTROL_PATH, p);
2024
0
      }
2025
0
      break;
2026
1.60k
    case SOC_CERTIFICATE:
2027
1.60k
        p = ssh_config_get_str_tok(&s, NULL);
2028
1.60k
        CHECK_COND_OR_FAIL(p == NULL, "Missing argument");
2029
1.60k
        if (*parsing) {
2030
1.60k
            ssh_options_set(session, SSH_OPTIONS_CERTIFICATE, p);
2031
1.60k
        }
2032
1.60k
        break;
2033
1
    case SOC_GSSAPIKEYEXCHANGE: {
2034
1
        i = ssh_config_get_yesno(&s, -1);
2035
1
        CHECK_COND_OR_FAIL(i < 0, "Invalid argument");
2036
0
        if (*parsing) {
2037
0
            bool b = (i == 1) ? true : false;
2038
0
            ssh_options_set(session, SSH_OPTIONS_GSSAPI_KEY_EXCHANGE, &b);
2039
0
        }
2040
0
        break;
2041
1
    }
2042
3
    case SOC_GSSAPIKEXALGORITHMS:
2043
3
        p = ssh_config_get_str_tok(&s, NULL);
2044
3
        CHECK_COND_OR_FAIL(p == NULL, "Missing argument");
2045
1
        if (*parsing) {
2046
1
            ssh_options_set(session, SSH_OPTIONS_GSSAPI_KEY_EXCHANGE_ALGS, p);
2047
1
        }
2048
1
        break;
2049
4
    case SOC_REQUIRED_RSA_SIZE:
2050
4
        l = ssh_config_get_long(&s, -1);
2051
4
        CHECK_COND_OR_FAIL(l < 0 || l > INT_MAX, "Invalid argument");
2052
0
        if (*parsing) {
2053
0
            i = (int)l;
2054
0
            ssh_options_set(session, SSH_OPTIONS_RSA_MIN_SIZE, &i);
2055
0
        }
2056
0
        break;
2057
7
    case SOC_ADDRESSFAMILY:
2058
7
        p = ssh_config_get_str_tok(&s, NULL);
2059
7
        if (p == NULL) {
2060
0
            SSH_LOG(SSH_LOG_WARNING,
2061
0
                    "line %d: no argument after keyword \"addressfamily\"",
2062
0
                    count);
2063
0
            SAFE_FREE(x);
2064
0
            return SSH_ERROR;
2065
0
        }
2066
7
        if (*parsing) {
2067
7
            int value = -1;
2068
2069
7
            if (strcasecmp(p, "any") == 0) {
2070
0
                value = SSH_ADDRESS_FAMILY_ANY;
2071
7
            } else if (strcasecmp(p, "inet") == 0) {
2072
0
                value = SSH_ADDRESS_FAMILY_INET;
2073
7
            } else if (strcasecmp(p, "inet6") == 0) {
2074
0
                value = SSH_ADDRESS_FAMILY_INET6;
2075
7
            } else {
2076
7
                SSH_LOG(SSH_LOG_WARNING,
2077
7
                        "line %d: invalid argument \"%s\"",
2078
7
                        count,
2079
7
                        p);
2080
7
                SAFE_FREE(x);
2081
7
                return SSH_ERROR;
2082
7
            }
2083
0
            ssh_options_set(session, SSH_OPTIONS_ADDRESS_FAMILY, &value);
2084
0
        }
2085
0
        break;
2086
0
    default:
2087
0
      ssh_set_error(session, SSH_FATAL, "ERROR - unimplemented opcode: %d",
2088
0
              opcode);
2089
0
      SAFE_FREE(x);
2090
0
      return -1;
2091
0
      break;
2092
312k
  }
2093
2094
312k
  SAFE_FREE(x);
2095
312k
  return 0;
2096
312k
}
2097
2098
#undef CHECK_COND_OR_FAIL
2099
2100
int ssh_config_parse_line(ssh_session session,
2101
                          const char *line,
2102
                          unsigned int count,
2103
                          int *parsing,
2104
                          unsigned int depth,
2105
                          bool global)
2106
410k
{
2107
410k
    return ssh_config_parse_line_internal(session,
2108
410k
                                          line,
2109
410k
                                          count,
2110
410k
                                          parsing,
2111
410k
                                          depth,
2112
410k
                                          global,
2113
410k
                                          false,
2114
410k
                                          false);
2115
410k
}
2116
2117
int ssh_config_parse_line_cli(ssh_session session, const char *line)
2118
0
{
2119
0
    int parsing = 1;
2120
0
    return ssh_config_parse_line_internal(session,
2121
0
                                          line,
2122
0
                                          0,
2123
0
                                          &parsing,
2124
0
                                          0,
2125
0
                                          false,
2126
0
                                          true,
2127
0
                                          true);
2128
0
}
2129
2130
/**
2131
 * @brief Parse configuration from a file pointer
2132
 *
2133
 * @param[in] session   The ssh session
2134
 * @param[in] fp        A valid file pointer
2135
 * @param[in] global    Whether the config is global or not
2136
 *
2137
 * @returns    0 on successful parsing the configuration file, -1 on error
2138
 */
2139
int ssh_config_parse(ssh_session session, FILE *fp, bool global)
2140
0
{
2141
0
    char line[MAX_LINE_SIZE] = {0};
2142
0
    unsigned int count = 0;
2143
0
    int parsing, rv;
2144
2145
0
    parsing = 1;
2146
0
    while (fgets(line, sizeof(line), fp)) {
2147
0
        count++;
2148
0
        rv = ssh_config_parse_line(session, line, count, &parsing, 0, global);
2149
0
        if (rv < 0) {
2150
0
            return -1;
2151
0
        }
2152
0
    }
2153
2154
0
    return 0;
2155
0
}
2156
2157
/**
2158
 * @brief Parse configuration file and set the options to the given session
2159
 *
2160
 * @param[in] session   The ssh session
2161
 * @param[in] filename  The path to the ssh configuration file
2162
 *
2163
 * @returns    0 on successful parsing the configuration file, -1 on error
2164
 */
2165
int ssh_config_parse_file(ssh_session session, const char *filename)
2166
0
{
2167
0
    FILE *fp = NULL;
2168
0
    int rv;
2169
0
    bool global = 0;
2170
2171
0
    fp = ssh_strict_fopen(filename, SSH_MAX_CONFIG_FILE_SIZE);
2172
0
    if (fp == NULL) {
2173
        /* The underlying function logs the reasons */
2174
0
        return 0;
2175
0
    }
2176
2177
0
    rv = strcmp(filename, GLOBAL_CLIENT_CONFIG);
2178
#ifdef USR_GLOBAL_CLIENT_CONFIG
2179
    if (rv != 0) {
2180
        rv = strcmp(filename, USR_GLOBAL_CLIENT_CONFIG);
2181
    }
2182
#endif
2183
2184
0
    if (rv == 0) {
2185
0
        global = true;
2186
0
    }
2187
2188
0
    SSH_LOG(SSH_LOG_PACKET, "Reading configuration data from %s", filename);
2189
2190
0
    rv = ssh_config_parse(session, fp, global);
2191
2192
0
    fclose(fp);
2193
0
    return rv;
2194
0
}
2195
2196
/**
2197
 * @brief Parse configuration string and set the options to the given session
2198
 *
2199
 * @param[in] session   The ssh session
2200
 * @param[in] input     Null terminated string containing the configuration
2201
 *
2202
 * @returns    SSH_OK on successful parsing the configuration string,
2203
 *             SSH_ERROR on error
2204
 */
2205
int ssh_config_parse_string(ssh_session session, const char *input)
2206
478
{
2207
478
    char line[MAX_LINE_SIZE] = {0};
2208
478
    const char *c = input, *line_start = input;
2209
478
    unsigned int line_num = 0;
2210
478
    size_t line_len;
2211
478
    int parsing, rv;
2212
2213
478
    SSH_LOG(SSH_LOG_DEBUG, "Reading configuration data from string:");
2214
478
    SSH_LOG(SSH_LOG_DEBUG, "START\n%s\nEND", input);
2215
2216
478
    parsing = 1;
2217
20.9k
    while (1) {
2218
20.9k
        line_num++;
2219
20.9k
        line_start = c;
2220
20.9k
        c = strchr(line_start, '\n');
2221
20.9k
        if (c == NULL) {
2222
            /* if there is no newline at the end of the string */
2223
468
            c = strchr(line_start, '\0');
2224
468
        }
2225
20.9k
        if (c == NULL) {
2226
            /* should not happen, would mean a string without trailing '\0' */
2227
0
            SSH_LOG(SSH_LOG_TRACE, "No trailing '\\0' in config string");
2228
0
            return SSH_ERROR;
2229
0
        }
2230
20.9k
        line_len = c - line_start;
2231
20.9k
        if (line_len > MAX_LINE_SIZE - 1) {
2232
10
            SSH_LOG(SSH_LOG_TRACE,
2233
10
                    "Line %u too long: %zu characters",
2234
10
                    line_num,
2235
10
                    line_len);
2236
10
            return SSH_ERROR;
2237
10
        }
2238
20.8k
        memcpy(line, line_start, line_len);
2239
20.8k
        line[line_len] = '\0';
2240
20.8k
        SSH_LOG(SSH_LOG_DEBUG, "Line %u: %s", line_num, line);
2241
20.8k
        rv = ssh_config_parse_line(session, line, line_num, &parsing, 0, false);
2242
20.8k
        if (rv < 0) {
2243
55
            return SSH_ERROR;
2244
55
        }
2245
20.8k
        if (*c == '\0') {
2246
413
            break;
2247
413
        }
2248
20.4k
        c++;
2249
20.4k
    }
2250
2251
413
    return SSH_OK;
2252
478
}