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 | } |