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