Coverage Report

Created: 2024-09-08 07:08

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