diff --git a/build/VStudio/testing/winfsp-tests.vcxproj b/build/VStudio/testing/winfsp-tests.vcxproj index 6790d6b0..c6e5651b 100644 --- a/build/VStudio/testing/winfsp-tests.vcxproj +++ b/build/VStudio/testing/winfsp-tests.vcxproj @@ -191,6 +191,7 @@ + diff --git a/build/VStudio/testing/winfsp-tests.vcxproj.filters b/build/VStudio/testing/winfsp-tests.vcxproj.filters index 67ff3de6..b07e62e6 100644 --- a/build/VStudio/testing/winfsp-tests.vcxproj.filters +++ b/build/VStudio/testing/winfsp-tests.vcxproj.filters @@ -100,6 +100,9 @@ Source + + Source + diff --git a/build/VStudio/tools/launcher.vcxproj b/build/VStudio/tools/launcher.vcxproj index 7c0cf280..ae17af2e 100644 --- a/build/VStudio/tools/launcher.vcxproj +++ b/build/VStudio/tools/launcher.vcxproj @@ -186,6 +186,7 @@ + diff --git a/build/VStudio/tools/launcher.vcxproj.filters b/build/VStudio/tools/launcher.vcxproj.filters index d2ad1d56..1aed461c 100644 --- a/build/VStudio/tools/launcher.vcxproj.filters +++ b/build/VStudio/tools/launcher.vcxproj.filters @@ -16,6 +16,9 @@ Source + + Source + diff --git a/src/launcher/launcher.c b/src/launcher/launcher.c index 5a7bbed9..373424d3 100644 --- a/src/launcher/launcher.c +++ b/src/launcher/launcher.c @@ -503,24 +503,19 @@ static SVC_INSTANCE *SvcInstanceLookup(PWSTR ClassName, PWSTR InstanceName) return 0; } -static ULONG SvcInstanceArgumentLength(PWSTR Arg) +static inline ULONG SvcInstanceArgumentLength(PWSTR Arg, PWSTR Pattern) { - ULONG Length; + PWSTR PathTransform(PWSTR Dest, PWSTR Arg, PWSTR Pattern); - Length = 2; /* for beginning and ending quotes */ - for (PWSTR P = Arg; *P; P++) - if (L'"' != *P) - Length++; - - return Length; + return 2 + (ULONG)(UINT_PTR)PathTransform(0, Arg, Pattern); } -static PWSTR SvcInstanceArgumentCopy(PWSTR Dest, PWSTR Arg) +static inline PWSTR SvcInstanceArgumentCopy(PWSTR Dest, PWSTR Arg, PWSTR Pattern) { + PWSTR PathTransform(PWSTR Dest, PWSTR Arg, PWSTR Pattern); + *Dest++ = L'"'; - for (PWSTR P = Arg; *P; P++) - if (L'"' != *P) - *Dest++ = *P; + Dest = PathTransform(Dest, Arg, Pattern); *Dest++ = L'"'; return Dest; @@ -532,6 +527,7 @@ static NTSTATUS SvcInstanceReplaceArguments(PWSTR String, ULONG Argc, PWSTR *Arg PWSTR NewString = 0, P, Q; PWSTR EmptyArg = L""; ULONG Length; + PWSTR Pattern; *PNewString = 0; @@ -541,24 +537,36 @@ static NTSTATUS SvcInstanceReplaceArguments(PWSTR String, ULONG Argc, PWSTR *Arg switch (*P) { case L'%': + Pattern = 0; P++; + if (L'\\' == *P) + { + Pattern = ++P; + while (!(L'\0' == *P || + (L'0' <= *P && *P <= '9') || + (L'A' <= *P && *P <= 'Z'))) + P++; + } if (L'0' <= *P && *P <= '9') { if (Argc > (ULONG)(*P - L'0')) - Length += SvcInstanceArgumentLength(Argv[*P - L'0']); + Length += SvcInstanceArgumentLength(Argv[*P - L'0'], Pattern); else - Length += SvcInstanceArgumentLength(EmptyArg); + Length += SvcInstanceArgumentLength(EmptyArg, 0); } else if (L'U' == *P) { if (0 != SvcInstanceUserName()) - Length += SvcInstanceArgumentLength(SvcInstanceUserName()); + Length += SvcInstanceArgumentLength(SvcInstanceUserName(), Pattern); else - Length += SvcInstanceArgumentLength(EmptyArg); + Length += SvcInstanceArgumentLength(EmptyArg, 0); } else + if (*P) Length++; + else + P--; break; default: Length++; @@ -575,24 +583,36 @@ static NTSTATUS SvcInstanceReplaceArguments(PWSTR String, ULONG Argc, PWSTR *Arg switch (*P) { case L'%': + Pattern = 0; P++; + if (L'\\' == *P) + { + Pattern = ++P; + while (!(L'\0' == *P || + (L'0' <= *P && *P <= '9') || + (L'A' <= *P && *P <= 'Z'))) + P++; + } if (L'0' <= *P && *P <= '9') { if (Argc > (ULONG)(*P - L'0')) - Q = SvcInstanceArgumentCopy(Q, Argv[*P - L'0']); + Q = SvcInstanceArgumentCopy(Q, Argv[*P - L'0'], Pattern); else - Q = SvcInstanceArgumentCopy(Q, EmptyArg); + Q = SvcInstanceArgumentCopy(Q, EmptyArg, 0); } else if (L'U' == *P) { if (0 != SvcInstanceUserName()) - Q = SvcInstanceArgumentCopy(Q, SvcInstanceUserName()); + Q = SvcInstanceArgumentCopy(Q, SvcInstanceUserName(), Pattern); else - Q = SvcInstanceArgumentCopy(Q, EmptyArg); + Q = SvcInstanceArgumentCopy(Q, EmptyArg, 0); } else + if (*P) *Q++ = *P; + else + P--; break; default: *Q++ = *P; diff --git a/src/launcher/ptrans.c b/src/launcher/ptrans.c new file mode 100644 index 00000000..15320d1f --- /dev/null +++ b/src/launcher/ptrans.c @@ -0,0 +1,182 @@ +/** + * @file launcher/ptrans.c + * + * @copyright 2015-2019 Bill Zissimopoulos + */ +/* + * This file is part of WinFsp. + * + * You can redistribute it and/or modify it under the terms of the GNU + * General Public License version 3 as published by the Free Software + * Foundation. + * + * Licensees holding a valid commercial license may use this software + * in accordance with the commercial license agreement provided in + * conjunction with the software. The terms and conditions of any such + * commercial license agreement shall govern, supersede, and render + * ineffective any application of the GPLv3 license to this software, + * notwithstanding of any reference thereto in the software or + * associated repository. + */ + +/** + * Path Transformation Language + * + * Syntax: + * PERCENT BACKLASH replace-sep *(*sep component) [*sep UNDERSCORE] arg + * + * Arg: + * The command line argument that a rule is applied on. These are denoted + * by digits [0-9] or a capital letter (A-Z). + * + * Component: + * A path component denoted by a small letter (a-z). The letter a denotes + * the first path component, the letter b the second path component, etc. + * + * UNDERSCORE: + * The UNDERSCORE (_) denotes the "rest of the path". This is any path + * left after any path components explicitly mentioned by using small + * letters (a-z). + * + * Sep: + * A separator symbol that is used to separated components. + * E.g. slash (/), colon (:), etc. + * + * Replace-sep: + * A separator symbol that replaces the backslash (\) separator in the + * "rest of the path" (UNDERSCORE). E.g. slash (/), colon (:), etc. + * + * + * Examples: + * - %\/b:_1 + * - Transforms \rclone\REMOTE\PATH\TO\FILES to REMOTE:PATH/TO/FILES + * - %\/b:/_1 + * - Transforms \rclone\REMOTE\PATH\TO\FILES to REMOTE:/PATH/TO/FILES + * - %\/_1 + * - Transforms \P1\P2\P3 to /P1/P2/P3 + * - %\+_1 + * - Transforms \P1\P2\P3 to +P1+P2+P3 + * - %\\_1 + * - Transforms \P1\P2\P3 to \\P1\\P2\\P3 + * (Backslash is doubled up when used as a replacement separator!) + */ + +#include +#include + +static PWSTR PathCopy(PWSTR Dest, PWSTR Arg, PWSTR ArgEnd, BOOLEAN WriteDest, WCHAR Replacement) +{ + if (0 != Replacement) + { + for (PWSTR P = Arg, EndP = (0 != ArgEnd ? ArgEnd : (PWSTR)(UINT_PTR)~0); EndP > P && *P; P++) + if (L'\\' == *P) + { + if (L'\\' == Replacement) + { + if (WriteDest) + *Dest = Replacement; + Dest++; + } + + if (WriteDest) + *Dest = Replacement; + Dest++; + } + else if (L'"' != *P) + { + if (WriteDest) + *Dest = *P; + Dest++; + } + } + else + { + for (PWSTR P = Arg, EndP = (0 != ArgEnd ? ArgEnd : (PWSTR)(UINT_PTR)~0); EndP > P && *P; P++) + if (L'"' != *P) + { + if (WriteDest) + *Dest = *P; + Dest++; + } + } + + return Dest; +} + +static inline BOOLEAN PatternEnd(WCHAR C) +{ + return L'\0' == C || + (L'0' <= C && C <= '9') || + (L'A' <= C && C <= 'Z'); +} + +PWSTR PathTransform(PWSTR Dest, PWSTR Arg, PWSTR Pattern) +{ + BOOLEAN WriteDest = 0 != Dest; + WCHAR Replacement; + PWSTR Components[26][2]; + PWSTR Remainder = Arg; + ULONG RemainderIndex = 0; + PWSTR P; + + if (0 == Pattern) + return PathCopy(Dest, Arg, 0, WriteDest, 0); + + for (ULONG I = 0; 26 > I; I++) + Components[I][0] = 0; + + Replacement = *Pattern++; + if (PatternEnd(Replacement)) + return Dest; + + while (!PatternEnd(*Pattern)) + { + if (L'a' <= *Pattern && *Pattern <= 'z') + { + ULONG I = *Pattern - 'a', J; + if (0 == Components[I][0]) + { + P = Remainder; + J = RemainderIndex; + + while (L'\\' == *P) + P++; + + for (;;) + { + Components[J][0] = P; + while (*P && L'\\' != *P) + P++; + Components[J][1] = P; + + while (L'\\' == *P) + P++; + + if (I == J) + { + Remainder = P; + RemainderIndex = I + 1; + break; + } + + J++; + } + } + + Dest = PathCopy(Dest, Components[I][0], Components[I][1], WriteDest, Replacement); + } + else + if (L'_' == *Pattern) + Dest = PathCopy(Dest, Remainder, 0, WriteDest, Replacement); + else + { + if (WriteDest) + *Dest = *Pattern; + Dest++; + } + + Pattern++; + } + + return Dest; +} diff --git a/tst/launcher-tests/echo.c b/tst/launcher-tests/echo.c new file mode 100644 index 00000000..4780b85d --- /dev/null +++ b/tst/launcher-tests/echo.c @@ -0,0 +1,35 @@ +/* + * Compile: + * - cl -I"%ProgramFiles(x86)%\WinFsp\inc" "%ProgramFiles(x86)%\WinFsp\lib\winfsp-x64.lib" echo.c + * + * Register: + * - echo.reg (fix Executable path first) + * + * Run: + * - launchctl-x64 start echo 1 \foo\bar\baz + * + * Expect: + * - "\foo\bar\baz" "bar:baz" "DOMAIN\\USERNAME" + */ + +#include + +int wmain(int argc, wchar_t *argv[]) +{ + WCHAR buf[512], *bufp; + int len; + + bufp = buf; + for (int i = 0; argc > i; i++) + { + len = lstrlenW(argv[i]); + memcpy(bufp, argv[i], len * sizeof(WCHAR)); + bufp += len; + *bufp++ = '\n'; + } + *bufp = '\0'; + + FspServiceLog(EVENTLOG_INFORMATION_TYPE, L"%s", buf); + + return 0; +} diff --git a/tst/launcher-tests/echo.reg b/tst/launcher-tests/echo.reg new file mode 100644 index 00000000..0f5210aa Binary files /dev/null and b/tst/launcher-tests/echo.reg differ diff --git a/tst/secret/secret.c b/tst/launcher-tests/secret.c similarity index 100% rename from tst/secret/secret.c rename to tst/launcher-tests/secret.c diff --git a/tst/launcher-tests/secret.reg b/tst/launcher-tests/secret.reg new file mode 100644 index 00000000..f55351d1 Binary files /dev/null and b/tst/launcher-tests/secret.reg differ diff --git a/tst/secret/secret.reg b/tst/secret/secret.reg deleted file mode 100644 index d508c0cc..00000000 Binary files a/tst/secret/secret.reg and /dev/null differ diff --git a/tst/winfsp-tests/launcher-ptrans-test.c b/tst/winfsp-tests/launcher-ptrans-test.c new file mode 100644 index 00000000..9ee39b9d --- /dev/null +++ b/tst/winfsp-tests/launcher-ptrans-test.c @@ -0,0 +1,112 @@ +/** + * @file launcher-ptrans-test.c + * + * @copyright 2015-2019 Bill Zissimopoulos + */ +/* + * This file is part of WinFsp. + * + * You can redistribute it and/or modify it under the terms of the GNU + * General Public License version 3 as published by the Free Software + * Foundation. + * + * Licensees holding a valid commercial license may use this software + * in accordance with the commercial license agreement provided in + * conjunction with the software. The terms and conditions of any such + * commercial license agreement shall govern, supersede, and render + * ineffective any application of the GPLv3 license to this software, + * notwithstanding of any reference thereto in the software or + * associated repository. + */ + +#include +#include + +#include "winfsp-tests.h" + +#include + +static void launcher_ptrans_test(void) +{ + PWSTR ipaths[] = + { + L"", 0, + L"\\foo\\bar", 0, + L"", L"", + L"\\foo\\bar", L"", + L"\\foo\\bar", L"/", + L"\\foo\\bar", L"/_", + L"\\foo\\bar", L"/_1", + L"\\foo\\bar", L"\\_", + L"\\foo\\bar", L"\\_A", + L"\\foo\\bar", L"/a", + L"\\foo\\bar", L"/b", + L"\\foo\\bar", L"/c", + L"\\foo\\bar", L"/d", + L"\\foo\\bar", L"/a:b", + L"\\foo\\bar", L"/a:b&c!d", + L"\\foo\\bar", L"/b:a", + L"\\foo\\bar", L"/d!c&b:a", + L"\\foo\\bar", L"/a:_", + L"\\foo\\bar", L"/b:_", + L"\\foo\\bar", L"/c:_", + L"\\foo\\bar\\baz", L"/b:_", + L"\\foo\\bar\\baz\\bag", L"/b:_", + L"\\foo\\bar\\baz", L"/b:/_", + L"\\foo\\bar\\baz\\bag", L"/b:/_", + L"\\foo\\bar\\baz\\bag", L"/a:_:b:_", + L"\\foo\\bar\\baz\\bag", L"/_:_", + }; + PWSTR opaths[] = + { + L"", + L"\\foo\\bar", + L"", + L"", + L"", + L"/foo/bar", + L"/foo/bar", + L"\\\\foo\\\\bar", + L"\\\\foo\\\\bar", + L"foo", + L"bar", + L"", + L"", + L"foo:bar", + L"foo:bar&!", + L"bar:foo", + L"!&bar:foo", + L"foo:bar", + L"bar:", + L":", + L"bar:baz", + L"bar:baz/bag", + L"bar:/baz", + L"bar:/baz/bag", + L"foo:bar/baz/bag:bar:baz/bag", + L"/foo/bar/baz/bag:/foo/bar/baz/bag", + }; + + for (size_t i = 0; sizeof ipaths / (sizeof ipaths[0] * 2) > i; i++) + { + WCHAR Buf[1024]; + ULONG Length; + PWSTR Dest; + + Length = (ULONG)(UINT_PTR)PathTransform(0, ipaths[2 * i + 0], ipaths[2 * i + 1]); + ASSERT(Length == wcslen(opaths[i]) * sizeof(WCHAR)); + + Dest = PathTransform(Buf, ipaths[2 * i + 0], ipaths[2 * i + 1]); + *Dest = L'\0'; + ASSERT(Dest == Buf + wcslen(opaths[i])); + ASSERT(0 == wcscmp(Buf, opaths[i])); + } +} + +void launcher_ptrans_tests(void) +{ + if (OptExternal) + return; + + TEST_OPT(launcher_ptrans_test); +} diff --git a/tst/winfsp-tests/winfsp-tests.c b/tst/winfsp-tests/winfsp-tests.c index 57480565..06d9a61f 100644 --- a/tst/winfsp-tests/winfsp-tests.c +++ b/tst/winfsp-tests/winfsp-tests.c @@ -193,6 +193,7 @@ int main(int argc, char *argv[]) TESTSUITE(dirbuf_tests); TESTSUITE(version_tests); TESTSUITE(launch_tests); + TESTSUITE(launcher_ptrans_tests); TESTSUITE(mount_tests); TESTSUITE(timeout_tests); TESTSUITE(memfs_tests);