1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
2 /*
3  * This file is part of the LibreOffice project.
4  *
5  * This Source Code Form is subject to the terms of the Mozilla Public
6  * License, v. 2.0. If a copy of the MPL was not distributed with this
7  * file, You can obtain one at http://mozilla.org/MPL/2.0/.
8  *
9  * This file incorporates work covered by the following license notice:
10  *
11  *   Licensed to the Apache Software Foundation (ASF) under one or more
12  *   contributor license agreements. See the NOTICE file distributed
13  *   with this work for additional information regarding copyright
14  *   ownership. The ASF licenses this file to you under the Apache
15  *   License, Version 2.0 (the "License"); you may not use this file
16  *   except in compliance with the License. You may obtain a copy of
17  *   the License at http://www.apache.org/licenses/LICENSE-2.0 .
18  */
19 
20 #include "loader.hxx"
21 #include <cassert>
22 #include <systools/win32/uwinapi.h>
23 #include <stdlib.h>
24 #include <string>
25 #include <vector>
26 #include <desktop/exithelper.h>
27 #include <tools/pathutils.hxx>
28 
29 #include <fstream>
30 #include <boost/property_tree/ptree.hpp>
31 #include <boost/property_tree/ini_parser.hpp>
32 
33 namespace {
34 
fail()35 void fail()
36 {
37     LPWSTR buf = nullptr;
38     FormatMessageW(
39         FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, nullptr,
40         GetLastError(), 0, reinterpret_cast< LPWSTR >(&buf), 0, nullptr);
41     MessageBoxW(nullptr, buf, nullptr, MB_OK | MB_ICONERROR);
42     HeapFree(GetProcessHeap(), 0, buf);
43     TerminateProcess(GetCurrentProcess(), 255);
44 }
45 
GetCommandArgs(int * pArgc)46 LPWSTR* GetCommandArgs(int* pArgc) { return CommandLineToArgvW(GetCommandLineW(), pArgc); }
47 
48 // tdf#120249: quotes in arguments need to be escaped; backslashes before quotes need doubling. See
49 // https://docs.microsoft.com/en-us/windows/desktop/api/shellapi/nf-shellapi-commandlinetoargvw
EscapeArg(LPCWSTR sArg)50 std::wstring EscapeArg(LPCWSTR sArg)
51 {
52     const size_t nOrigSize = wcslen(sArg);
53     LPCWSTR const end = sArg + nOrigSize;
54     std::wstring sResult(L"\"");
55 
56     LPCWSTR lastPosQuote = sArg;
57     LPCWSTR posQuote;
58     while ((posQuote = std::find(lastPosQuote, end, L'"')) != end)
59     {
60         LPCWSTR posBackslash = posQuote;
61         while (posBackslash != lastPosQuote && *(posBackslash - 1) == L'\\')
62             --posBackslash;
63 
64         sResult.append(lastPosQuote, posBackslash);
65         sResult.append((posQuote - posBackslash) * 2 + 1, L'\\'); // 2n+1 '\' to escape the '"'
66         sResult.append(1, L'"');
67         lastPosQuote = posQuote + 1;
68     }
69 
70     LPCWSTR posTrailingBackslashSeq = end;
71     while (posTrailingBackslashSeq != lastPosQuote && *(posTrailingBackslashSeq - 1) == L'\\')
72         --posTrailingBackslashSeq;
73     sResult.append(lastPosQuote, posTrailingBackslashSeq);
74     sResult.append((end - posTrailingBackslashSeq) * 2, L'\\'); // 2n '\' before closing '"'
75     sResult.append(1, L'"');
76 
77     return sResult;
78 }
79 
80 }
81 
82 namespace desktop_win32 {
83 
extendLoaderEnvironment(WCHAR * binPath,WCHAR * iniDirectory)84 void extendLoaderEnvironment(WCHAR * binPath, WCHAR * iniDirectory) {
85     if (!GetModuleFileNameW(nullptr, iniDirectory, MAX_PATH)) {
86         fail();
87     }
88     WCHAR * iniDirEnd = tools::filename(iniDirectory);
89     WCHAR name[MAX_PATH + MY_LENGTH(L".bin")];
90         // hopefully std::size_t is large enough to not overflow
91     WCHAR * nameEnd = name;
92     for (WCHAR * p = iniDirEnd; *p != L'\0'; ++p) {
93         *nameEnd++ = *p;
94     }
95     if (!(nameEnd - name >= 4 && nameEnd[-4] == L'.' &&
96          (((nameEnd[-3] == L'E' || nameEnd[-3] == L'e') &&
97            (nameEnd[-2] == L'X' || nameEnd[-2] == L'x') &&
98            (nameEnd[-1] == L'E' || nameEnd[-1] == L'e')) ||
99           ((nameEnd[-3] == L'C' || nameEnd[-3] == L'c') &&
100            (nameEnd[-2] == L'O' || nameEnd[-2] == L'o') &&
101            (nameEnd[-1] == L'M' || nameEnd[-1] == L'm')))))
102     {
103         *nameEnd = L'.';
104         nameEnd += 4;
105     }
106     nameEnd[-3] = 'b';
107     nameEnd[-2] = 'i';
108     nameEnd[-1] = 'n';
109     tools::buildPath(binPath, iniDirectory, iniDirEnd, name, nameEnd - name);
110     *iniDirEnd = L'\0';
111     std::size_t const maxEnv = 32767;
112     WCHAR env[maxEnv];
113     DWORD n = GetEnvironmentVariableW(L"PATH", env, maxEnv);
114     if ((n >= maxEnv || n == 0) && GetLastError() != ERROR_ENVVAR_NOT_FOUND) {
115         fail();
116     }
117     // must be first in PATH to override other entries
118     assert(*(iniDirEnd - 1) == L'\\'); // hence -1 below
119     if (wcsncmp(env, iniDirectory, iniDirEnd - iniDirectory - 1) != 0
120         || env[iniDirEnd - iniDirectory - 1] != L';')
121     {
122         WCHAR pad[MAX_PATH + maxEnv];
123             // hopefully std::size_t is large enough to not overflow
124         WCHAR * p = commandLineAppend(pad, iniDirectory, iniDirEnd - iniDirectory - 1);
125         if (n != 0) {
126             *p++ = L';';
127             for (DWORD i = 0; i <= n; ++i) {
128                 *p++ = env[i];
129             }
130         } else {
131             *p++ = L'\0';
132         }
133         if (!SetEnvironmentVariableW(L"PATH", pad)) {
134             fail();
135         }
136     }
137 }
138 
officeloader_impl(bool bAllowConsole)139 int officeloader_impl(bool bAllowConsole)
140 {
141     WCHAR szTargetFileName[MAX_PATH] = {};
142     WCHAR szIniDirectory[MAX_PATH];
143     STARTUPINFOW aStartupInfo;
144 
145     desktop_win32::extendLoaderEnvironment(szTargetFileName, szIniDirectory);
146 
147     ZeroMemory(&aStartupInfo, sizeof(aStartupInfo));
148     aStartupInfo.cb = sizeof(aStartupInfo);
149 
150     // Create process with same command line, environment and stdio handles which
151     // are directed to the created pipes
152     GetStartupInfoW(&aStartupInfo);
153 
154     DWORD dwExitCode = DWORD(-1);
155 
156     BOOL fSuccess = FALSE;
157     LPWSTR lpCommandLine = nullptr;
158     bool bFirst = true;
159     WCHAR cwd[MAX_PATH];
160     DWORD cwdLen = GetCurrentDirectoryW(MAX_PATH, cwd);
161     if (cwdLen >= MAX_PATH)
162     {
163         cwdLen = 0;
164     }
165     std::vector<std::wstring> aEscapedArgs;
166 
167     // read limit values from bootstrap.ini
168     unsigned int nMaxMemoryInMB = 0;
169     bool bExcludeChildProcesses = true;
170 
171     const WCHAR* szIniFile = L"\\bootstrap.ini";
172     const size_t nDirLen = wcslen(szIniDirectory);
173     if (wcslen(szIniFile) + nDirLen < MAX_PATH)
174     {
175         WCHAR szBootstrapIni[MAX_PATH];
176         wcscpy(szBootstrapIni, szIniDirectory);
177         wcscpy(&szBootstrapIni[nDirLen], szIniFile);
178 
179         try
180         {
181             boost::property_tree::ptree pt;
182             std::ifstream aFile(szBootstrapIni);
183             boost::property_tree::ini_parser::read_ini(aFile, pt);
184             nMaxMemoryInMB = pt.get("Win32.LimitMaximumMemoryInMB", nMaxMemoryInMB);
185             bExcludeChildProcesses = pt.get("Win32.ExcludeChildProcessesFromLimit", bExcludeChildProcesses);
186         }
187         catch (...)
188         {
189             nMaxMemoryInMB = 0;
190         }
191     }
192 
193     // create a Windows JobObject with a memory limit
194     HANDLE hJobObject = nullptr;
195     if (nMaxMemoryInMB > 0)
196     {
197         JOBOBJECT_EXTENDED_LIMIT_INFORMATION aJobLimit;
198         aJobLimit.BasicLimitInformation.LimitFlags = JOB_OBJECT_LIMIT_JOB_MEMORY;
199         if (bExcludeChildProcesses)
200             aJobLimit.BasicLimitInformation.LimitFlags |= JOB_OBJECT_LIMIT_SILENT_BREAKAWAY_OK;
201         aJobLimit.JobMemoryLimit = nMaxMemoryInMB * 1024 * 1024;
202         hJobObject = CreateJobObjectW(nullptr, nullptr);
203         if (hJobObject != nullptr)
204             SetInformationJobObject(hJobObject, JobObjectExtendedLimitInformation, &aJobLimit,
205                                     sizeof(JOBOBJECT_EXTENDED_LIMIT_INFORMATION));
206     }
207 
208     do
209     {
210         if (bFirst)
211         {
212             int argc = 0;
213             LPWSTR* argv = GetCommandArgs(&argc);
214             std::size_t n = 0;
215             for (int i = 0; i < argc; ++i)
216             {
217                 std::wstring sEscapedArg = EscapeArg(argv[i]);
218                 aEscapedArgs.push_back(sEscapedArg);
219                 n += sEscapedArg.length() + 1; // a space between args
220             }
221             LocalFree(argv);
222             n += MY_LENGTH(L" \"-env:OOO_CWD=2") + 4 * cwdLen + MY_LENGTH(L"\"") + 1;
223             // 4 * cwdLen: each char preceded by backslash, each trailing
224             // backslash doubled
225             lpCommandLine = new WCHAR[n];
226         }
227         WCHAR* p = desktop_win32::commandLineAppend(lpCommandLine, aEscapedArgs[0].c_str(),
228                                                     aEscapedArgs[0].length());
229         for (size_t i = 1; i < aEscapedArgs.size(); ++i)
230         {
231             const std::wstring& rArg = aEscapedArgs[i];
232             if (bFirst || EXITHELPER_NORMAL_RESTART == dwExitCode
233                 || wcsncmp(rArg.c_str(), MY_STRING(L"\"-env:")) == 0)
234             {
235                 p = desktop_win32::commandLineAppend(p, MY_STRING(L" "));
236                 p = desktop_win32::commandLineAppend(p, rArg.c_str(), rArg.length());
237             }
238         }
239 
240         p = desktop_win32::commandLineAppend(p, MY_STRING(L" \"-env:OOO_CWD="));
241         if (cwdLen == 0)
242         {
243             p = desktop_win32::commandLineAppend(p, MY_STRING(L"0"));
244         }
245         else
246         {
247             p = desktop_win32::commandLineAppend(p, MY_STRING(L"2"));
248             p = desktop_win32::commandLineAppendEncoded(p, cwd);
249         }
250         desktop_win32::commandLineAppend(p, MY_STRING(L"\""));
251         bFirst = false;
252 
253         WCHAR szParentProcessId[64]; // This is more than large enough for a 128 bit decimal value
254         BOOL bHeadlessMode(FALSE);
255 
256         {
257             // Check command line arguments for "--headless" parameter. We only
258             // set the environment variable "ATTACHED_PARENT_PROCESSID" for the headless
259             // mode as self-destruction of the soffice.bin process can lead to
260             // certain side-effects (log-off can result in data-loss, ".lock" is not deleted.
261             // See 138244 for more information.
262             int argc2;
263             LPWSTR* argv2 = GetCommandArgs(&argc2);
264 
265             if (argc2 > 1)
266             {
267                 int n;
268 
269                 for (n = 1; n < argc2; n++)
270                 {
271                     if (0 == wcsnicmp(argv2[n], L"-headless", 9)
272                         || 0 == wcsnicmp(argv2[n], L"--headless", 10))
273                     {
274                         bHeadlessMode = TRUE;
275                     }
276                 }
277             }
278 
279             LocalFree(argv2);
280         }
281 
282         if (_ltow(static_cast<long>(GetCurrentProcessId()), szParentProcessId, 10) && bHeadlessMode)
283             SetEnvironmentVariableW(L"ATTACHED_PARENT_PROCESSID", szParentProcessId);
284 
285         PROCESS_INFORMATION aProcessInfo;
286 
287         fSuccess = CreateProcessW(szTargetFileName, lpCommandLine, nullptr, nullptr, TRUE,
288                                   bAllowConsole ? 0 : DETACHED_PROCESS, nullptr, szIniDirectory,
289                                   &aStartupInfo, &aProcessInfo);
290 
291         if (fSuccess)
292         {
293             DWORD dwWaitResult;
294 
295             if (hJobObject)
296                 AssignProcessToJobObject(hJobObject, aProcessInfo.hProcess);
297 
298             do
299             {
300                 // On Windows XP it seems as the desktop calls WaitForInputIdle after "OpenWith" so
301                 // we have to do so as if we where processing any messages
302 
303                 dwWaitResult = MsgWaitForMultipleObjects(1, &aProcessInfo.hProcess, FALSE, INFINITE,
304                                                          QS_ALLEVENTS);
305 
306                 if (WAIT_OBJECT_0 + 1 == dwWaitResult)
307                 {
308                     MSG msg;
309 
310                     PeekMessageW(&msg, nullptr, 0, 0, PM_REMOVE);
311                 }
312             } while (WAIT_OBJECT_0 + 1 == dwWaitResult);
313 
314             dwExitCode = 0;
315             GetExitCodeProcess(aProcessInfo.hProcess, &dwExitCode);
316 
317             CloseHandle(aProcessInfo.hProcess);
318             CloseHandle(aProcessInfo.hThread);
319         }
320     } while (fSuccess
321              && (EXITHELPER_CRASH_WITH_RESTART == dwExitCode
322                  || EXITHELPER_NORMAL_RESTART == dwExitCode));
323 
324     if (hJobObject)
325         CloseHandle(hJobObject);
326 
327     delete[] lpCommandLine;
328 
329     return fSuccess ? dwExitCode : -1;
330 }
331 
332 }
333 
334 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */
335