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
36 #include "libssh/config_parser.h"
37 #include "libssh/config.h"
38 #include "libssh/priv.h"
39 #include "libssh/session.h"
40 #include "libssh/misc.h"
41 #include "libssh/options.h"
42
43 #define MAX_LINE_SIZE 1024
44
45 struct ssh_config_keyword_table_s {
46 const char *name;
47 enum ssh_config_opcode_e opcode;
48 };
49
50 static struct ssh_config_keyword_table_s ssh_config_keyword_table[] = {
51 { "host", SOC_HOST },
52 { "match", SOC_MATCH },
53 { "hostname", SOC_HOSTNAME },
54 { "port", SOC_PORT },
55 { "user", SOC_USERNAME },
56 { "identityfile", SOC_IDENTITY },
57 { "ciphers", SOC_CIPHERS },
58 { "macs", SOC_MACS },
59 { "compression", SOC_COMPRESSION },
60 { "connecttimeout", SOC_TIMEOUT },
61 { "protocol", SOC_PROTOCOL },
62 { "stricthostkeychecking", SOC_STRICTHOSTKEYCHECK },
63 { "userknownhostsfile", SOC_KNOWNHOSTS },
64 { "proxycommand", SOC_PROXYCOMMAND },
65 { "gssapiserveridentity", SOC_GSSAPISERVERIDENTITY },
66 { "gssapiclientidentity", SOC_GSSAPICLIENTIDENTITY },
67 { "gssapidelegatecredentials", SOC_GSSAPIDELEGATECREDENTIALS },
68 { "include", SOC_INCLUDE },
69 { "bindaddress", SOC_BINDADDRESS},
70 { "globalknownhostsfile", SOC_GLOBALKNOWNHOSTSFILE},
71 { "loglevel", SOC_LOGLEVEL},
72 { "hostkeyalgorithms", SOC_HOSTKEYALGORITHMS},
73 { "kexalgorithms", SOC_KEXALGORITHMS},
74 { "mac", SOC_UNSUPPORTED}, /* SSHv1 */
75 { "gssapiauthentication", SOC_GSSAPIAUTHENTICATION},
76 { "kbdinteractiveauthentication", SOC_KBDINTERACTIVEAUTHENTICATION},
77 { "passwordauthentication", SOC_PASSWORDAUTHENTICATION},
78 { "pubkeyauthentication", SOC_PUBKEYAUTHENTICATION},
79 { "addkeystoagent", SOC_UNSUPPORTED},
80 { "addressfamily", SOC_UNSUPPORTED},
81 { "batchmode", SOC_UNSUPPORTED},
82 { "canonicaldomains", SOC_UNSUPPORTED},
83 { "canonicalizefallbacklocal", SOC_UNSUPPORTED},
84 { "canonicalizehostname", SOC_UNSUPPORTED},
85 { "canonicalizemaxdots", SOC_UNSUPPORTED},
86 { "canonicalizepermittedcnames", SOC_UNSUPPORTED},
87 { "certificatefile", SOC_UNSUPPORTED},
88 { "challengeresponseauthentication", SOC_UNSUPPORTED},
89 { "checkhostip", SOC_UNSUPPORTED},
90 { "cipher", SOC_UNSUPPORTED}, /* SSHv1 */
91 { "compressionlevel", SOC_UNSUPPORTED}, /* SSHv1 */
92 { "connectionattempts", SOC_UNSUPPORTED},
93 { "enablesshkeysign", SOC_UNSUPPORTED},
94 { "fingerprinthash", SOC_UNSUPPORTED},
95 { "forwardagent", SOC_UNSUPPORTED},
96 { "gssapikeyexchange", SOC_UNSUPPORTED},
97 { "gssapirenewalforcesrekey", SOC_UNSUPPORTED},
98 { "gssapitrustdns", SOC_UNSUPPORTED},
99 { "hashknownhosts", SOC_UNSUPPORTED},
100 { "hostbasedauthentication", SOC_UNSUPPORTED},
101 { "hostbasedkeytypes", SOC_UNSUPPORTED},
102 { "hostkeyalias", SOC_UNSUPPORTED},
103 { "identitiesonly", SOC_UNSUPPORTED},
104 { "identityagent", SOC_UNSUPPORTED},
105 { "ipqos", SOC_UNSUPPORTED},
106 { "kbdinteractivedevices", SOC_UNSUPPORTED},
107 { "nohostauthenticationforlocalhost", SOC_UNSUPPORTED},
108 { "numberofpasswordprompts", SOC_UNSUPPORTED},
109 { "pkcs11provider", SOC_UNSUPPORTED},
110 { "preferredauthentications", SOC_UNSUPPORTED},
111 { "proxyjump", SOC_PROXYJUMP},
112 { "proxyusefdpass", SOC_UNSUPPORTED},
113 { "pubkeyacceptedtypes", SOC_PUBKEYACCEPTEDTYPES},
114 { "rekeylimit", SOC_REKEYLIMIT},
115 { "remotecommand", SOC_UNSUPPORTED},
116 { "revokedhostkeys", SOC_UNSUPPORTED},
117 { "rhostsrsaauthentication", SOC_UNSUPPORTED},
118 { "rsaauthentication", SOC_UNSUPPORTED}, /* SSHv1 */
119 { "serveralivecountmax", SOC_UNSUPPORTED},
120 { "serveraliveinterval", SOC_UNSUPPORTED},
121 { "streamlocalbindmask", SOC_UNSUPPORTED},
122 { "streamlocalbindunlink", SOC_UNSUPPORTED},
123 { "syslogfacility", SOC_UNSUPPORTED},
124 { "tcpkeepalive", SOC_UNSUPPORTED},
125 { "updatehostkeys", SOC_UNSUPPORTED},
126 { "useprivilegedport", SOC_UNSUPPORTED},
127 { "verifyhostkeydns", SOC_UNSUPPORTED},
128 { "visualhostkey", SOC_UNSUPPORTED},
129 { "clearallforwardings", SOC_NA},
130 { "controlmaster", SOC_NA},
131 { "controlpersist", SOC_NA},
132 { "controlpath", SOC_NA},
133 { "dynamicforward", SOC_NA},
134 { "escapechar", SOC_NA},
135 { "exitonforwardfailure", SOC_NA},
136 { "forwardx11", SOC_NA},
137 { "forwardx11timeout", SOC_NA},
138 { "forwardx11trusted", SOC_NA},
139 { "gatewayports", SOC_NA},
140 { "ignoreunknown", SOC_NA},
141 { "localcommand", SOC_NA},
142 { "localforward", SOC_NA},
143 { "permitlocalcommand", SOC_NA},
144 { "remoteforward", SOC_NA},
145 { "requesttty", SOC_NA},
146 { "sendenv", SOC_NA},
147 { "tunnel", SOC_NA},
148 { "tunneldevice", SOC_NA},
149 { "xauthlocation", SOC_NA},
150 { "pubkeyacceptedkeytypes", SOC_PUBKEYACCEPTEDTYPES},
151 { NULL, SOC_UNKNOWN }
152 };
153
154 enum ssh_config_match_e {
155 MATCH_UNKNOWN = -1,
156 MATCH_ALL,
157 MATCH_FINAL,
158 MATCH_CANONICAL,
159 MATCH_EXEC,
160 MATCH_HOST,
161 MATCH_ORIGINALHOST,
162 MATCH_USER,
163 MATCH_LOCALUSER
164 };
165
166 struct ssh_config_match_keyword_table_s {
167 const char *name;
168 enum ssh_config_match_e opcode;
169 };
170
171 static struct ssh_config_match_keyword_table_s ssh_config_match_keyword_table[] = {
172 { "all", MATCH_ALL },
173 { "canonical", MATCH_CANONICAL },
174 { "final", MATCH_FINAL },
175 { "exec", MATCH_EXEC },
176 { "host", MATCH_HOST },
177 { "originalhost", MATCH_ORIGINALHOST },
178 { "user", MATCH_USER },
179 { "localuser", MATCH_LOCALUSER },
180 { NULL, MATCH_UNKNOWN },
181 };
182
183 static int ssh_config_parse_line(ssh_session session, const char *line,
184 unsigned int count, int *parsing);
185
ssh_config_get_opcode(char * keyword)186 static enum ssh_config_opcode_e ssh_config_get_opcode(char *keyword) {
187 int i;
188
189 for (i = 0; ssh_config_keyword_table[i].name != NULL; i++) {
190 if (strcasecmp(keyword, ssh_config_keyword_table[i].name) == 0) {
191 return ssh_config_keyword_table[i].opcode;
192 }
193 }
194
195 return SOC_UNKNOWN;
196 }
197
198 static void
local_parse_file(ssh_session session,const char * filename,int * parsing)199 local_parse_file(ssh_session session,
200 const char *filename,
201 int *parsing)
202 {
203 FILE *f;
204 char line[MAX_LINE_SIZE] = {0};
205 unsigned int count = 0;
206 int rv;
207
208 f = fopen(filename, "r");
209 if (f == NULL) {
210 SSH_LOG(SSH_LOG_RARE, "Cannot find file %s to load",
211 filename);
212 return;
213 }
214
215 SSH_LOG(SSH_LOG_PACKET, "Reading additional configuration data from %s", filename);
216 while (fgets(line, sizeof(line), f)) {
217 count++;
218 rv = ssh_config_parse_line(session, line, count, parsing);
219 if (rv < 0) {
220 fclose(f);
221 return;
222 }
223 }
224
225 fclose(f);
226 return;
227 }
228
229 #if defined(HAVE_GLOB) && defined(HAVE_GLOB_GL_FLAGS_MEMBER)
local_parse_glob(ssh_session session,const char * fileglob,int * parsing)230 static void local_parse_glob(ssh_session session,
231 const char *fileglob,
232 int *parsing)
233 {
234 glob_t globbuf = {
235 .gl_flags = 0,
236 };
237 int rt;
238 size_t i;
239
240 rt = glob(fileglob, GLOB_TILDE, NULL, &globbuf);
241 if (rt == GLOB_NOMATCH) {
242 globfree(&globbuf);
243 return;
244 } else if (rt != 0) {
245 SSH_LOG(SSH_LOG_RARE, "Glob error: %s",
246 fileglob);
247 globfree(&globbuf);
248 return;
249 }
250
251 for (i = 0; i < globbuf.gl_pathc; i++) {
252 local_parse_file(session, globbuf.gl_pathv[i], parsing);
253 }
254
255 globfree(&globbuf);
256 }
257 #endif /* HAVE_GLOB HAVE_GLOB_GL_FLAGS_MEMBER */
258
259 static enum ssh_config_match_e
ssh_config_get_match_opcode(const char * keyword)260 ssh_config_get_match_opcode(const char *keyword)
261 {
262 size_t i;
263
264 for (i = 0; ssh_config_match_keyword_table[i].name != NULL; i++) {
265 if (strcasecmp(keyword, ssh_config_match_keyword_table[i].name) == 0) {
266 return ssh_config_match_keyword_table[i].opcode;
267 }
268 }
269
270 return MATCH_UNKNOWN;
271 }
272
273 static int
ssh_config_match(char * value,const char * pattern,bool negate)274 ssh_config_match(char *value, const char *pattern, bool negate)
275 {
276 int ok, result = 0;
277
278 ok = match_pattern_list(value, pattern, strlen(pattern), 0);
279 if (ok <= 0 && negate == true) {
280 result = 1;
281 } else if (ok > 0 && negate == false) {
282 result = 1;
283 }
284 SSH_LOG(SSH_LOG_TRACE, "%s '%s' against pattern '%s'%s (ok=%d)",
285 result == 1 ? "Matched" : "Not matched", value, pattern,
286 negate == true ? " (negated)" : "", ok);
287 return result;
288 }
289
290 /* @brief: Parse the ProxyJump configuration line and if parsing,
291 * stores the result in the configuration option
292 */
293 static int
ssh_config_parse_proxy_jump(ssh_session session,const char * s,bool do_parsing)294 ssh_config_parse_proxy_jump(ssh_session session, const char *s, bool do_parsing)
295 {
296 char *c = NULL, *cp = NULL, *endp = NULL;
297 char *username = NULL;
298 char *hostname = NULL;
299 char *port = NULL;
300 char *next = NULL;
301 int cmp, rv = SSH_ERROR;
302 bool parse_entry = do_parsing;
303
304 /* Special value none disables the proxy */
305 cmp = strcasecmp(s, "none");
306 if (cmp == 0 && do_parsing) {
307 ssh_options_set(session, SSH_OPTIONS_PROXYCOMMAND, s);
308 return SSH_OK;
309 }
310
311 /* This is comma-separated list of [user@]host[:port] entries */
312 c = strdup(s);
313 if (c == NULL) {
314 ssh_set_error_oom(session);
315 return SSH_ERROR;
316 }
317
318 cp = c;
319 do {
320 endp = strchr(cp, ',');
321 if (endp != NULL) {
322 /* Split out the token */
323 *endp = '\0';
324 }
325 if (parse_entry) {
326 /* We actually care only about the first item */
327 rv = ssh_config_parse_uri(cp, &username, &hostname, &port);
328 /* The rest of the list needs to be passed on */
329 if (endp != NULL) {
330 next = strdup(endp + 1);
331 if (next == NULL) {
332 ssh_set_error_oom(session);
333 rv = SSH_ERROR;
334 }
335 }
336 } else {
337 /* The rest is just sanity-checked to avoid failures later */
338 rv = ssh_config_parse_uri(cp, NULL, NULL, NULL);
339 }
340 if (rv != SSH_OK) {
341 goto out;
342 }
343 parse_entry = 0;
344 if (endp != NULL) {
345 cp = endp + 1;
346 } else {
347 cp = NULL; /* end */
348 }
349 } while (cp != NULL);
350
351 if (hostname != NULL && do_parsing) {
352 char com[512] = {0};
353
354 rv = snprintf(com, sizeof(com), "ssh%s%s%s%s%s%s -W [%%h]:%%p %s",
355 username ? " -l " : "",
356 username ? username : "",
357 port ? " -p " : "",
358 port ? port : "",
359 next ? " -J " : "",
360 next ? next : "",
361 hostname);
362 if (rv < 0 || rv >= (int)sizeof(com)) {
363 SSH_LOG(SSH_LOG_WARN, "Too long ProxyJump configuration line");
364 rv = SSH_ERROR;
365 goto out;
366 }
367 ssh_options_set(session, SSH_OPTIONS_PROXYCOMMAND, com);
368 }
369 rv = SSH_OK;
370
371 out:
372 SAFE_FREE(username);
373 SAFE_FREE(hostname);
374 SAFE_FREE(port);
375 SAFE_FREE(next);
376 SAFE_FREE(c);
377 return rv;
378 }
379
380 static int
ssh_config_parse_line(ssh_session session,const char * line,unsigned int count,int * parsing)381 ssh_config_parse_line(ssh_session session,
382 const char *line,
383 unsigned int count,
384 int *parsing)
385 {
386 enum ssh_config_opcode_e opcode;
387 const char *p = NULL, *p2 = NULL;
388 char *s = NULL, *x = NULL;
389 char *keyword = NULL;
390 char *lowerhost = NULL;
391 size_t len;
392 int i, rv;
393 uint8_t *seen = session->opts.options_seen;
394 long l;
395 int64_t ll;
396
397 /* Ignore empty lines */
398 if (line == NULL || *line == '\0') {
399 return 0;
400 }
401
402 x = s = strdup(line);
403 if (s == NULL) {
404 ssh_set_error_oom(session);
405 return -1;
406 }
407
408 /* Remove trailing spaces */
409 for (len = strlen(s) - 1; len > 0; len--) {
410 if (! isspace(s[len])) {
411 break;
412 }
413 s[len] = '\0';
414 }
415
416 keyword = ssh_config_get_token(&s);
417 if (keyword == NULL || *keyword == '#' ||
418 *keyword == '\0' || *keyword == '\n') {
419 SAFE_FREE(x);
420 return 0;
421 }
422
423 opcode = ssh_config_get_opcode(keyword);
424 if (*parsing == 1 &&
425 opcode != SOC_HOST &&
426 opcode != SOC_MATCH &&
427 opcode != SOC_INCLUDE &&
428 opcode != SOC_IDENTITY &&
429 opcode > SOC_UNSUPPORTED) { /* Ignore all unknown types here */
430 /* Skip all the options that were already applied */
431 if (seen[opcode] != 0) {
432 SAFE_FREE(x);
433 return 0;
434 }
435 seen[opcode] = 1;
436 }
437
438 switch (opcode) {
439 case SOC_INCLUDE: /* recursive include of other files */
440
441 p = ssh_config_get_str_tok(&s, NULL);
442 if (p && *parsing) {
443 #if defined(HAVE_GLOB) && defined(HAVE_GLOB_GL_FLAGS_MEMBER)
444 local_parse_glob(session, p, parsing);
445 #else
446 local_parse_file(session, p, parsing);
447 #endif /* HAVE_GLOB */
448 }
449 break;
450
451 case SOC_MATCH: {
452 bool negate;
453 int result = 1;
454 size_t args = 0;
455 enum ssh_config_match_e opt;
456 char *localuser = NULL;
457
458 *parsing = 0;
459 do {
460 p = p2 = ssh_config_get_str_tok(&s, NULL);
461 if (p == NULL || p[0] == '\0') {
462 break;
463 }
464 args++;
465 SSH_LOG(SSH_LOG_TRACE, "line %d: Processing Match keyword '%s'",
466 count, p);
467
468 /* If the option is prefixed with ! the result should be negated */
469 negate = false;
470 if (p[0] == '!') {
471 negate = true;
472 p++;
473 }
474
475 opt = ssh_config_get_match_opcode(p);
476 switch (opt) {
477 case MATCH_ALL:
478 p = ssh_config_get_str_tok(&s, NULL);
479 if (args <= 2 && (p == NULL || p[0] == '\0')) {
480 /* The first or second, but last argument. The "all" keyword
481 * can be prefixed with either "final" or "canonical"
482 * keywords which do not have any effect here. */
483 if (negate == true) {
484 result = 0;
485 }
486 break;
487 }
488
489 ssh_set_error(session, SSH_FATAL,
490 "line %d: ERROR - Match all cannot be combined with "
491 "other Match attributes", count);
492 SAFE_FREE(x);
493 return -1;
494
495 case MATCH_FINAL:
496 case MATCH_CANONICAL:
497 SSH_LOG(SSH_LOG_WARN,
498 "line %d: Unsupported Match keyword '%s', skipping",
499 count,
500 p);
501 /* Not set any result here -- the result is dependent on the
502 * following matches after this keyword */
503 break;
504
505 case MATCH_EXEC:
506 /* Skip to the end of line as unsupported */
507 p = ssh_config_get_cmd(&s);
508 if (p == NULL || p[0] == '\0') {
509 SSH_LOG(SSH_LOG_WARN, "line %d: Match keyword "
510 "'%s' requires argument", count, p2);
511 SAFE_FREE(x);
512 return -1;
513 }
514 args++;
515 SSH_LOG(SSH_LOG_WARN,
516 "line %d: Unsupported Match keyword '%s', ignoring",
517 count,
518 p2);
519 result = 0;
520 break;
521
522 case MATCH_LOCALUSER:
523 /* Here we match only one argument */
524 p = ssh_config_get_str_tok(&s, NULL);
525 if (p == NULL || p[0] == '\0') {
526 ssh_set_error(session, SSH_FATAL,
527 "line %d: ERROR - Match user keyword "
528 "requires argument", count);
529 SAFE_FREE(x);
530 return -1;
531 }
532 localuser = ssh_get_local_username();
533 if (localuser == NULL) {
534 SSH_LOG(SSH_LOG_WARN, "line %d: Can not get local username "
535 "for conditional matching.", count);
536 SAFE_FREE(x);
537 return -1;
538 }
539 result &= ssh_config_match(localuser, p, negate);
540 SAFE_FREE(localuser);
541 args++;
542 break;
543
544 case MATCH_ORIGINALHOST:
545 /* Skip one argument */
546 p = ssh_config_get_str_tok(&s, NULL);
547 if (p == NULL || p[0] == '\0') {
548 SSH_LOG(SSH_LOG_WARN, "line %d: Match keyword "
549 "'%s' requires argument", count, p2);
550 SAFE_FREE(x);
551 return -1;
552 }
553 args++;
554 SSH_LOG(SSH_LOG_WARN,
555 "line %d: Unsupported Match keyword '%s', ignoring",
556 count,
557 p2);
558 result = 0;
559 break;
560
561 case MATCH_HOST:
562 /* Here we match only one argument */
563 p = ssh_config_get_str_tok(&s, NULL);
564 if (p == NULL || p[0] == '\0') {
565 ssh_set_error(session, SSH_FATAL,
566 "line %d: ERROR - Match host keyword "
567 "requires argument", count);
568 SAFE_FREE(x);
569 return -1;
570 }
571 result &= ssh_config_match(session->opts.host, p, negate);
572 args++;
573 break;
574
575 case MATCH_USER:
576 /* Here we match only one argument */
577 p = ssh_config_get_str_tok(&s, NULL);
578 if (p == NULL || p[0] == '\0') {
579 ssh_set_error(session, SSH_FATAL,
580 "line %d: ERROR - Match user keyword "
581 "requires argument", count);
582 SAFE_FREE(x);
583 return -1;
584 }
585 result &= ssh_config_match(session->opts.username, p, negate);
586 args++;
587 break;
588
589 case MATCH_UNKNOWN:
590 default:
591 ssh_set_error(session, SSH_FATAL,
592 "ERROR - Unknown argument '%s' for Match keyword", p);
593 SAFE_FREE(x);
594 return -1;
595 }
596 } while (p != NULL && p[0] != '\0');
597 if (args == 0) {
598 ssh_set_error(session, SSH_FATAL,
599 "ERROR - Match keyword requires an argument");
600 SAFE_FREE(x);
601 return -1;
602 }
603 *parsing = result;
604 break;
605 }
606 case SOC_HOST: {
607 int ok = 0, result = -1;
608
609 *parsing = 0;
610 lowerhost = (session->opts.host) ? ssh_lowercase(session->opts.host) : NULL;
611 for (p = ssh_config_get_str_tok(&s, NULL);
612 p != NULL && p[0] != '\0';
613 p = ssh_config_get_str_tok(&s, NULL)) {
614 if (ok >= 0) {
615 ok = match_hostname(lowerhost, p, strlen(p));
616 if (result == -1 && ok < 0) {
617 result = 0;
618 } else if (result == -1 && ok > 0) {
619 result = 1;
620 }
621 }
622 }
623 SAFE_FREE(lowerhost);
624 if (result != -1) {
625 *parsing = result;
626 }
627 break;
628 }
629 case SOC_HOSTNAME:
630 p = ssh_config_get_str_tok(&s, NULL);
631 if (p && *parsing) {
632 char *z = ssh_path_expand_escape(session, p);
633 if (z == NULL) {
634 z = strdup(p);
635 }
636 ssh_options_set(session, SSH_OPTIONS_HOST, z);
637 free(z);
638 }
639 break;
640 case SOC_PORT:
641 p = ssh_config_get_str_tok(&s, NULL);
642 if (p && *parsing) {
643 ssh_options_set(session, SSH_OPTIONS_PORT_STR, p);
644 }
645 break;
646 case SOC_USERNAME:
647 if (session->opts.username == NULL) {
648 p = ssh_config_get_str_tok(&s, NULL);
649 if (p && *parsing) {
650 ssh_options_set(session, SSH_OPTIONS_USER, p);
651 }
652 }
653 break;
654 case SOC_IDENTITY:
655 p = ssh_config_get_str_tok(&s, NULL);
656 if (p && *parsing) {
657 ssh_options_set(session, SSH_OPTIONS_ADD_IDENTITY, p);
658 }
659 break;
660 case SOC_CIPHERS:
661 p = ssh_config_get_str_tok(&s, NULL);
662 if (p && *parsing) {
663 ssh_options_set(session, SSH_OPTIONS_CIPHERS_C_S, p);
664 ssh_options_set(session, SSH_OPTIONS_CIPHERS_S_C, p);
665 }
666 break;
667 case SOC_MACS:
668 p = ssh_config_get_str_tok(&s, NULL);
669 if (p && *parsing) {
670 ssh_options_set(session, SSH_OPTIONS_HMAC_C_S, p);
671 ssh_options_set(session, SSH_OPTIONS_HMAC_S_C, p);
672 }
673 break;
674 case SOC_COMPRESSION:
675 i = ssh_config_get_yesno(&s, -1);
676 if (i >= 0 && *parsing) {
677 if (i) {
678 ssh_options_set(session, SSH_OPTIONS_COMPRESSION, "yes");
679 } else {
680 ssh_options_set(session, SSH_OPTIONS_COMPRESSION, "no");
681 }
682 }
683 break;
684 case SOC_PROTOCOL:
685 p = ssh_config_get_str_tok(&s, NULL);
686 if (p && *parsing) {
687 char *a, *b;
688 b = strdup(p);
689 if (b == NULL) {
690 SAFE_FREE(x);
691 ssh_set_error_oom(session);
692 return -1;
693 }
694 i = 0;
695 ssh_options_set(session, SSH_OPTIONS_SSH2, &i);
696
697 for (a = strtok(b, ","); a; a = strtok(NULL, ",")) {
698 switch (atoi(a)) {
699 case 1:
700 break;
701 case 2:
702 i = 1;
703 ssh_options_set(session, SSH_OPTIONS_SSH2, &i);
704 break;
705 default:
706 break;
707 }
708 }
709 SAFE_FREE(b);
710 }
711 break;
712 case SOC_TIMEOUT:
713 l = ssh_config_get_long(&s, -1);
714 if (l >= 0 && *parsing) {
715 ssh_options_set(session, SSH_OPTIONS_TIMEOUT, &l);
716 }
717 break;
718 case SOC_STRICTHOSTKEYCHECK:
719 i = ssh_config_get_yesno(&s, -1);
720 if (i >= 0 && *parsing) {
721 ssh_options_set(session, SSH_OPTIONS_STRICTHOSTKEYCHECK, &i);
722 }
723 break;
724 case SOC_KNOWNHOSTS:
725 p = ssh_config_get_str_tok(&s, NULL);
726 if (p && *parsing) {
727 ssh_options_set(session, SSH_OPTIONS_KNOWNHOSTS, p);
728 }
729 break;
730 case SOC_PROXYCOMMAND:
731 p = ssh_config_get_cmd(&s);
732 /* We share the seen value with the ProxyJump */
733 if (p && *parsing && !seen[SOC_PROXYJUMP]) {
734 ssh_options_set(session, SSH_OPTIONS_PROXYCOMMAND, p);
735 }
736 break;
737 case SOC_PROXYJUMP:
738 p = ssh_config_get_str_tok(&s, NULL);
739 if (p == NULL) {
740 SAFE_FREE(x);
741 return -1;
742 }
743 /* We share the seen value with the ProxyCommand */
744 rv = ssh_config_parse_proxy_jump(session, p,
745 (*parsing && !seen[SOC_PROXYCOMMAND]));
746 if (rv != SSH_OK) {
747 SAFE_FREE(x);
748 return -1;
749 }
750 break;
751 case SOC_GSSAPISERVERIDENTITY:
752 p = ssh_config_get_str_tok(&s, NULL);
753 if (p && *parsing) {
754 ssh_options_set(session, SSH_OPTIONS_GSSAPI_SERVER_IDENTITY, p);
755 }
756 break;
757 case SOC_GSSAPICLIENTIDENTITY:
758 p = ssh_config_get_str_tok(&s, NULL);
759 if (p && *parsing) {
760 ssh_options_set(session, SSH_OPTIONS_GSSAPI_CLIENT_IDENTITY, p);
761 }
762 break;
763 case SOC_GSSAPIDELEGATECREDENTIALS:
764 i = ssh_config_get_yesno(&s, -1);
765 if (i >=0 && *parsing) {
766 ssh_options_set(session, SSH_OPTIONS_GSSAPI_DELEGATE_CREDENTIALS, &i);
767 }
768 break;
769 case SOC_BINDADDRESS:
770 p = ssh_config_get_str_tok(&s, NULL);
771 if (p && *parsing) {
772 ssh_options_set(session, SSH_OPTIONS_BINDADDR, p);
773 }
774 break;
775 case SOC_GLOBALKNOWNHOSTSFILE:
776 p = ssh_config_get_str_tok(&s, NULL);
777 if (p && *parsing) {
778 ssh_options_set(session, SSH_OPTIONS_GLOBAL_KNOWNHOSTS, p);
779 }
780 break;
781 case SOC_LOGLEVEL:
782 p = ssh_config_get_str_tok(&s, NULL);
783 if (p && *parsing) {
784 int value = -1;
785
786 if (strcasecmp(p, "quiet") == 0) {
787 value = SSH_LOG_NONE;
788 } else if (strcasecmp(p, "fatal") == 0 ||
789 strcasecmp(p, "error")== 0 ||
790 strcasecmp(p, "info") == 0) {
791 value = SSH_LOG_WARN;
792 } else if (strcasecmp(p, "verbose") == 0) {
793 value = SSH_LOG_INFO;
794 } else if (strcasecmp(p, "DEBUG") == 0 ||
795 strcasecmp(p, "DEBUG1") == 0) {
796 value = SSH_LOG_DEBUG;
797 } else if (strcasecmp(p, "DEBUG2") == 0 ||
798 strcasecmp(p, "DEBUG3") == 0) {
799 value = SSH_LOG_TRACE;
800 }
801 if (value != -1) {
802 ssh_options_set(session, SSH_OPTIONS_LOG_VERBOSITY, &value);
803 }
804 }
805 break;
806 case SOC_HOSTKEYALGORITHMS:
807 p = ssh_config_get_str_tok(&s, NULL);
808 if (p && *parsing) {
809 ssh_options_set(session, SSH_OPTIONS_HOSTKEYS, p);
810 }
811 break;
812 case SOC_PUBKEYACCEPTEDTYPES:
813 p = ssh_config_get_str_tok(&s, NULL);
814 if (p && *parsing) {
815 ssh_options_set(session, SSH_OPTIONS_PUBLICKEY_ACCEPTED_TYPES, p);
816 }
817 break;
818 case SOC_KEXALGORITHMS:
819 p = ssh_config_get_str_tok(&s, NULL);
820 if (p && *parsing) {
821 ssh_options_set(session, SSH_OPTIONS_KEY_EXCHANGE, p);
822 }
823 break;
824 case SOC_REKEYLIMIT:
825 /* Parse the data limit */
826 p = ssh_config_get_str_tok(&s, NULL);
827 if (p == NULL) {
828 break;
829 } else if (strcmp(p, "default") == 0) {
830 /* Default rekey limits enforced automaticaly */
831 ll = 0;
832 } else {
833 char *endp = NULL;
834 ll = strtoll(p, &endp, 10);
835 if (p == endp || ll < 0) {
836 /* No number or negative */
837 SSH_LOG(SSH_LOG_WARN, "Invalid argument to rekey limit");
838 break;
839 }
840 switch (*endp) {
841 case 'G':
842 if (ll > LLONG_MAX / 1024) {
843 SSH_LOG(SSH_LOG_WARN, "Possible overflow of rekey limit");
844 ll = -1;
845 break;
846 }
847 ll = ll * 1024;
848 FALL_THROUGH;
849 case 'M':
850 if (ll > LLONG_MAX / 1024) {
851 SSH_LOG(SSH_LOG_WARN, "Possible overflow of rekey limit");
852 ll = -1;
853 break;
854 }
855 ll = ll * 1024;
856 FALL_THROUGH;
857 case 'K':
858 if (ll > LLONG_MAX / 1024) {
859 SSH_LOG(SSH_LOG_WARN, "Possible overflow of rekey limit");
860 ll = -1;
861 break;
862 }
863 ll = ll * 1024;
864 endp++;
865 FALL_THROUGH;
866 case '\0':
867 /* just the number */
868 break;
869 default:
870 /* Invalid suffix */
871 ll = -1;
872 break;
873 }
874 if (*endp != ' ' && *endp != '\0') {
875 SSH_LOG(SSH_LOG_WARN,
876 "Invalid trailing characters after the rekey limit: %s",
877 endp);
878 break;
879 }
880 }
881 if (ll > -1 && *parsing) {
882 uint64_t v = (uint64_t)ll;
883 ssh_options_set(session, SSH_OPTIONS_REKEY_DATA, &v);
884 }
885 /* Parse the time limit */
886 p = ssh_config_get_str_tok(&s, NULL);
887 if (p == NULL) {
888 break;
889 } else if (strcmp(p, "none") == 0) {
890 ll = 0;
891 } else {
892 char *endp = NULL;
893 ll = strtoll(p, &endp, 10);
894 if (p == endp || ll < 0) {
895 /* No number or negative */
896 SSH_LOG(SSH_LOG_WARN, "Invalid argument to rekey limit");
897 break;
898 }
899 switch (*endp) {
900 case 'w':
901 case 'W':
902 if (ll > LLONG_MAX / 7) {
903 SSH_LOG(SSH_LOG_WARN, "Possible overflow of rekey limit");
904 ll = -1;
905 break;
906 }
907 ll = ll * 7;
908 FALL_THROUGH;
909 case 'd':
910 case 'D':
911 if (ll > LLONG_MAX / 24) {
912 SSH_LOG(SSH_LOG_WARN, "Possible overflow of rekey limit");
913 ll = -1;
914 break;
915 }
916 ll = ll * 24;
917 FALL_THROUGH;
918 case 'h':
919 case 'H':
920 if (ll > LLONG_MAX / 60) {
921 SSH_LOG(SSH_LOG_WARN, "Possible overflow of rekey limit");
922 ll = -1;
923 break;
924 }
925 ll = ll * 60;
926 FALL_THROUGH;
927 case 'm':
928 case 'M':
929 if (ll > LLONG_MAX / 60) {
930 SSH_LOG(SSH_LOG_WARN, "Possible overflow of rekey limit");
931 ll = -1;
932 break;
933 }
934 ll = ll * 60;
935 FALL_THROUGH;
936 case 's':
937 case 'S':
938 endp++;
939 FALL_THROUGH;
940 case '\0':
941 /* just the number */
942 break;
943 default:
944 /* Invalid suffix */
945 ll = -1;
946 break;
947 }
948 if (*endp != '\0') {
949 SSH_LOG(SSH_LOG_WARN, "Invalid trailing characters after the"
950 " rekey limit: %s", endp);
951 break;
952 }
953 }
954 if (ll > -1 && *parsing) {
955 uint32_t v = (uint32_t)ll;
956 ssh_options_set(session, SSH_OPTIONS_REKEY_TIME, &v);
957 }
958 break;
959 case SOC_GSSAPIAUTHENTICATION:
960 case SOC_KBDINTERACTIVEAUTHENTICATION:
961 case SOC_PASSWORDAUTHENTICATION:
962 case SOC_PUBKEYAUTHENTICATION:
963 i = ssh_config_get_yesno(&s, 0);
964 if (i>=0 && *parsing) {
965 switch(opcode){
966 case SOC_GSSAPIAUTHENTICATION:
967 ssh_options_set(session, SSH_OPTIONS_GSSAPI_AUTH, &i);
968 break;
969 case SOC_KBDINTERACTIVEAUTHENTICATION:
970 ssh_options_set(session, SSH_OPTIONS_KBDINT_AUTH, &i);
971 break;
972 case SOC_PASSWORDAUTHENTICATION:
973 ssh_options_set(session, SSH_OPTIONS_PASSWORD_AUTH, &i);
974 break;
975 case SOC_PUBKEYAUTHENTICATION:
976 ssh_options_set(session, SSH_OPTIONS_PUBKEY_AUTH, &i);
977 break;
978 /* make gcc happy */
979 default:
980 break;
981 }
982 }
983 break;
984 case SOC_NA:
985 SSH_LOG(SSH_LOG_INFO, "Unapplicable option: %s, line: %d",
986 keyword, count);
987 break;
988 case SOC_UNSUPPORTED:
989 SSH_LOG(SSH_LOG_RARE, "Unsupported option: %s, line: %d",
990 keyword, count);
991 break;
992 case SOC_UNKNOWN:
993 SSH_LOG(SSH_LOG_WARN, "Unknown option: %s, line: %d",
994 keyword, count);
995 break;
996 default:
997 ssh_set_error(session, SSH_FATAL, "ERROR - unimplemented opcode: %d",
998 opcode);
999 SAFE_FREE(x);
1000 return -1;
1001 break;
1002 }
1003
1004 SAFE_FREE(x);
1005 return 0;
1006 }
1007
1008 /* @brief Parse configuration file and set the options to the given session
1009 *
1010 * @params[in] session The ssh session
1011 * @params[in] filename The path to the ssh configuration file
1012 *
1013 * @returns 0 on successful parsing the configuration file, -1 on error
1014 */
ssh_config_parse_file(ssh_session session,const char * filename)1015 int ssh_config_parse_file(ssh_session session, const char *filename)
1016 {
1017 char line[MAX_LINE_SIZE] = {0};
1018 unsigned int count = 0;
1019 FILE *f;
1020 int parsing, rv;
1021
1022 f = fopen(filename, "r");
1023 if (f == NULL) {
1024 return 0;
1025 }
1026
1027 SSH_LOG(SSH_LOG_PACKET, "Reading configuration data from %s", filename);
1028
1029 parsing = 1;
1030 while (fgets(line, sizeof(line), f)) {
1031 count++;
1032 rv = ssh_config_parse_line(session, line, count, &parsing);
1033 if (rv < 0) {
1034 fclose(f);
1035 return -1;
1036 }
1037 }
1038
1039 fclose(f);
1040 return 0;
1041 }
1042