Ver código fonte

Bug 2206: Improve OpenSSH config file parsing, particularly quoted and escaped values

https://winscp.net/tracker/2206

Source commit: ffebc91c00525da10aa2dec817a0475ecfa33a6c
Martin Prikryl 2 anos atrás
pai
commit
730bfcaed7
1 arquivos alterados com 154 adições e 102 exclusões
  1. 154 102
      source/core/SessionData.cpp

+ 154 - 102
source/core/SessionData.cpp

@@ -95,25 +95,43 @@ static bool ParseOpensshDirective(const UnicodeString & ALine, UnicodeString & D
   bool Result = IsValidOpensshLine(ALine);
   if (Result)
   {
-    int From = 1;
-    wchar_t Equal = L'=';
-    UnicodeString Delimiters(UnicodeString(L" \t") + Equal);
-    wchar_t Delimiter;
     UnicodeString Line = Trim(ALine);
-    Directive = CopyToChars(Line, From, Delimiters, false, &Delimiter, false);
-    Result = !Directive.IsEmpty();
-    if (Result)
+    if (Line.SubString(1, 1) == "\"")
     {
-      Value = Line;
-      Value.Delete(1, Directive.Length() + 1);
-      Value = Trim(Value);
-      if ((Delimiter != Equal) && !Value.IsEmpty() && (Value[1] == Equal))
+      Line.Delete(1, 1);
+      int P = Line.Pos("\"");
+      Result = (P > 0);
+      if (Result)
       {
-        Value.Delete(1, 1);
-        Value = Trim(Value); // not sure about this, but for the directives we support it does not matter
+        Directive = Line.SubString(1, P - 1);
+        Line = MidStr(Line, P + 1).Trim();
       }
-      Result = !Value.IsEmpty();
     }
+    else
+    {
+      wchar_t Equal = L'=';
+      UnicodeString Whitespaces(L" \t");
+      UnicodeString Delimiters(Whitespaces + Equal);
+      int P = FindDelimiter(Delimiters, Line);
+      Result = (P > 0);
+      if (Result)
+      {
+        Directive = Line.SubString(1, P - 1);
+        Line.Delete(1, P - 1);
+        UnicodeString TrimChars = Delimiters;
+        while (!Line.IsEmpty() && Line.IsDelimiter(TrimChars, 1))
+        {
+          if (Line[1] == Equal)
+          {
+            TrimChars = Whitespaces;
+          }
+          Line.Delete(1, 1);
+        }
+      }
+    }
+
+    Value = Line;
+    Result = !Value.IsEmpty();
   }
   return Result;
 }
@@ -1554,13 +1572,44 @@ bool OpensshBoolValue(const UnicodeString & Value)
 //---------------------------------------------------------------------
 UnicodeString CutOpensshToken(UnicodeString & S)
 {
-  DebugAssert(!S.IsEmpty() && (S == Trim(S)));
-  int From = 1;
-  UnicodeString Result = CopyToChars(S, From, L" \t", false);
-  S.Delete(1, Result.Length());
-  S = Trim(S);
-  DebugAssert(!Result.IsEmpty() && (Result == Trim(Result)));
-  Result = Trim(Result);
+  const wchar_t NoQuote = L'\0';
+  wchar_t Quote = NoQuote;
+  UnicodeString Result;
+  int P = 1;
+  while (P <= S.Length())
+  {
+    wchar_t C = S[P];
+    if ((C == L'\\') &&
+        (P < S.Length()) &&
+        ((S[P + 1] == L'\'') ||
+         (S[P + 1] == L'\"') ||
+         (S[P + 1] == L'\\') ||
+         ((Quote == NoQuote) && S[P + 1] == L' ')))
+    {
+      Result += S[P + 1];
+      P++;
+    }
+    else if ((Quote == NoQuote) && ((C == L' ') || (C == L'\t')))
+    {
+      break;
+    }
+    else if ((Quote == NoQuote) && ((C == L'\'') || (C == L'"')))
+    {
+      Quote = C;
+    }
+    else if ((Quote != NoQuote) && (Quote == C))
+    {
+      Quote = NoQuote;
+    }
+    else
+    {
+      Result += C;
+    }
+    P++;
+  }
+
+  S = MidStr(S, P).Trim();
+
   return Result;
 }
 //---------------------------------------------------------------------
@@ -1571,17 +1620,15 @@ void TSessionData::ImportFromOpenssh(TStrings * Lines)
   for (int Index = 0; Index < Lines->Count; Index++)
   {
     UnicodeString Line = Lines->Strings[Index];
-    UnicodeString Directive, Value;
-    if (ParseOpensshDirective(Line, Directive, Value))
+    UnicodeString Directive, Args;
+    if (ParseOpensshDirective(Line, Directive, Args))
     {
       if (SameText(Directive, OpensshHostDirective))
       {
-        DebugAssert(!Value.IsEmpty() && (Value == Trim(Value)));
-
         SkippingSection = true;
-        while (!Value.IsEmpty())
+        while (!Args.IsEmpty())
         {
-          UnicodeString M = CutOpensshToken(Value);
+          UnicodeString M = CutOpensshToken(Args);
           bool Negated = DebugAlwaysTrue(!M.IsEmpty()) && (M[1] == L'!');
           if (Negated)
           {
@@ -1613,95 +1660,100 @@ void TSessionData::ImportFromOpenssh(TStrings * Lines)
       }
       else if (!SkippingSection && (UsedDirectives->IndexOf(Directive) < 0))
       {
-        if (SameText(Directive, L"AddressFamily"))
+        UnicodeString Value = CutOpensshToken(Args);
+        // All the directives we support allow only one token
+        if (Args.IsEmpty())
         {
-          if (SameText(Value, L"inet"))
+          if (SameText(Directive, L"AddressFamily"))
           {
-            AddressFamily = afIPv4;
+            if (SameText(Value, L"inet"))
+            {
+              AddressFamily = afIPv4;
+            }
+            else if (SameText(Value, L"inet6"))
+            {
+              AddressFamily = afIPv6;
+            }
+            else
+            {
+              AddressFamily = afAuto;
+            }
           }
-          else if (SameText(Value, L"inet6"))
+          else if (SameText(Directive, L"BindAddress"))
           {
-            AddressFamily = afIPv6;
+            SourceAddress = Value;
           }
-          else
+          else if (SameText(Directive, L"Compression"))
           {
-            AddressFamily = afAuto;
+            Compression = OpensshBoolValue(Value);
           }
-        }
-        else if (SameText(Directive, L"BindAddress"))
-        {
-          SourceAddress = Value;
-        }
-        else if (SameText(Directive, L"Compression"))
-        {
-          Compression = OpensshBoolValue(Value);
-        }
-        else if (SameText(Directive, L"ForwardAgent"))
-        {
-          AgentFwd = OpensshBoolValue(Value);
-        }
-        else if (SameText(Directive, L"GSSAPIAuthentication"))
-        {
-          AuthGSSAPI = OpensshBoolValue(Value);
-        }
-        else if (SameText(Directive, L"GSSAPIDelegateCredentials"))
-        {
-          AuthGSSAPIKEX = OpensshBoolValue(Value);
-        }
-        else if (SameText(Directive, L"Hostname"))
-        {
-          HostName = Value;
-        }
-        else if (SameText(Directive, L"IdentityFile"))
-        {
-          // It's likely there would be forward slashes in OpenSSH config file and our load/save dialogs
-          // (e.g. when converting keys) work suboptimally when working with forward slashes.
-          UnicodeString Path = GetNormalizedPath(Value);
-          const UnicodeString HomePathPrefix = L"~";
-          if (StartsStr(HomePathPrefix, Path + L"\\"))
+          else if (SameText(Directive, L"ForwardAgent"))
           {
-            Path =
-              GetShellFolderPath(CSIDL_PROFILE) +
-              Path.SubString(HomePathPrefix.Length() + 1, Path.Length() - HomePathPrefix.Length());
+            AgentFwd = OpensshBoolValue(Value);
           }
-          PublicKeyFile = Path;
-        }
-        else if (SameText(Directive, L"KbdInteractiveAuthentication"))
-        {
-          AuthKI = OpensshBoolValue(Value);
-        }
-        else if (SameText(Directive, L"Port"))
-        {
-          PortNumber = StrToInt(Value);
-        }
-        else if (SameText(Directive, L"User"))
-        {
-          UserName = Value;
-        }
-        else if (SameText(Directive, L"ProxyJump"))
-        {
-          UnicodeString Jump = Value;
-          // multiple jumps are not supported
-          if (Jump.Pos(L",") == 0)
+          else if (SameText(Directive, L"GSSAPIAuthentication"))
+          {
+            AuthGSSAPI = OpensshBoolValue(Value);
+          }
+          else if (SameText(Directive, L"GSSAPIDelegateCredentials"))
+          {
+            AuthGSSAPIKEX = OpensshBoolValue(Value);
+          }
+          else if (SameText(Directive, L"Hostname"))
+          {
+            HostName = Value;
+          }
+          else if (SameText(Directive, L"IdentityFile"))
+          {
+            // It's likely there would be forward slashes in OpenSSH config file and our load/save dialogs
+            // (e.g. when converting keys) work suboptimally when working with forward slashes.
+            UnicodeString Path = GetNormalizedPath(Value);
+            const UnicodeString HomePathPrefix = L"~";
+            if (StartsStr(HomePathPrefix, Path + L"\\"))
+            {
+              Path =
+                GetShellFolderPath(CSIDL_PROFILE) +
+                Path.SubString(HomePathPrefix.Length() + 1, Path.Length() - HomePathPrefix.Length());
+            }
+            PublicKeyFile = Path;
+          }
+          else if (SameText(Directive, L"KbdInteractiveAuthentication"))
+          {
+            AuthKI = OpensshBoolValue(Value);
+          }
+          else if (SameText(Directive, L"Port"))
+          {
+            PortNumber = StrToInt(Value);
+          }
+          else if (SameText(Directive, L"User"))
+          {
+            UserName = Value;
+          }
+          else if (SameText(Directive, L"ProxyJump"))
           {
-            std::unique_ptr<TSessionData> JumpData(new TSessionData(EmptyStr));
-            bool DefaultsOnly;
-            if ((JumpData->ParseUrl(Jump, NULL, NULL, DefaultsOnly, NULL, NULL, NULL, 0)) &&
-                !JumpData->HostName.IsEmpty())
+            UnicodeString Jump = Value;
+            // multiple jumps are not supported
+            if (Jump.Pos(L",") == 0)
             {
-              JumpData->Name = JumpData->HostName;
-              JumpData->ImportFromOpenssh(Lines);
-
-              Tunnel = true;
-              TunnelHostName = JumpData->HostName;
-              TunnelPortNumber = JumpData->PortNumber;
-              TunnelUserName = JumpData->UserName;
-              TunnelPassword = JumpData->Password;
-              TunnelPublicKeyFile = JumpData->PublicKeyFile;
+              std::unique_ptr<TSessionData> JumpData(new TSessionData(EmptyStr));
+              bool DefaultsOnly;
+              if ((JumpData->ParseUrl(Jump, NULL, NULL, DefaultsOnly, NULL, NULL, NULL, 0)) &&
+                  !JumpData->HostName.IsEmpty())
+              {
+                JumpData->Name = JumpData->HostName;
+                JumpData->ImportFromOpenssh(Lines);
+
+                Tunnel = true;
+                TunnelHostName = JumpData->HostName;
+                TunnelPortNumber = JumpData->PortNumber;
+                TunnelUserName = JumpData->UserName;
+                TunnelPassword = JumpData->Password;
+                TunnelPublicKeyFile = JumpData->PublicKeyFile;
+              }
             }
           }
+          UsedDirectives->Add(Directive);
         }
-        UsedDirectives->Add(Directive);
       }
     }
   }