1 // Main.cpp
2 
3 #include "StdAfx.h"
4 
5 #include "../../../Common/MyWindows.h"
6 
7 #include "../../../Common/MyInitGuid.h"
8 
9 #include "../../../Common/CommandLineParser.h"
10 #include "../../../Common/StringConvert.h"
11 #include "../../../Common/TextConfig.h"
12 
13 #include "../../../Windows/DLL.h"
14 #include "../../../Windows/ErrorMsg.h"
15 #include "../../../Windows/FileDir.h"
16 #include "../../../Windows/FileFind.h"
17 #include "../../../Windows/FileIO.h"
18 #include "../../../Windows/FileName.h"
19 #include "../../../Windows/NtCheck.h"
20 #include "../../../Windows/ResourceString.h"
21 
22 #include "../../UI/Explorer/MyMessages.h"
23 
24 #include "ExtractEngine.h"
25 
26 #include "../../../../C/DllSecur.h"
27 
28 #include "resource.h"
29 
30 using namespace NWindows;
31 using namespace NFile;
32 using namespace NDir;
33 
34 HINSTANCE g_hInstance;
35 
36 static CFSTR const kTempDirPrefix = FTEXT("7zS");
37 
38 #define _SHELL_EXECUTE
39 
ReadDataString(CFSTR fileName,LPCSTR startID,LPCSTR endID,AString & stringResult)40 static bool ReadDataString(CFSTR fileName, LPCSTR startID,
41     LPCSTR endID, AString &stringResult)
42 {
43   stringResult.Empty();
44   NIO::CInFile inFile;
45   if (!inFile.Open(fileName))
46     return false;
47   const int kBufferSize = (1 << 12);
48 
49   Byte buffer[kBufferSize];
50   int signatureStartSize = MyStringLen(startID);
51   int signatureEndSize = MyStringLen(endID);
52 
53   UInt32 numBytesPrev = 0;
54   bool writeMode = false;
55   UInt64 posTotal = 0;
56   for (;;)
57   {
58     if (posTotal > (1 << 20))
59       return (stringResult.IsEmpty());
60     UInt32 numReadBytes = kBufferSize - numBytesPrev;
61     UInt32 processedSize;
62     if (!inFile.Read(buffer + numBytesPrev, numReadBytes, processedSize))
63       return false;
64     if (processedSize == 0)
65       return true;
66     UInt32 numBytesInBuffer = numBytesPrev + processedSize;
67     UInt32 pos = 0;
68     for (;;)
69     {
70       if (writeMode)
71       {
72         if (pos > numBytesInBuffer - signatureEndSize)
73           break;
74         if (memcmp(buffer + pos, endID, signatureEndSize) == 0)
75           return true;
76         char b = buffer[pos];
77         if (b == 0)
78           return false;
79         stringResult += b;
80         pos++;
81       }
82       else
83       {
84         if (pos > numBytesInBuffer - signatureStartSize)
85           break;
86         if (memcmp(buffer + pos, startID, signatureStartSize) == 0)
87         {
88           writeMode = true;
89           pos += signatureStartSize;
90         }
91         else
92           pos++;
93       }
94     }
95     numBytesPrev = numBytesInBuffer - pos;
96     posTotal += pos;
97     memmove(buffer, buffer + pos, numBytesPrev);
98   }
99 }
100 
101 static char kStartID[] = { ',','!','@','I','n','s','t','a','l','l','@','!','U','T','F','-','8','!', 0 };
102 static char kEndID[]   = { ',','!','@','I','n','s','t','a','l','l','E','n','d','@','!', 0 };
103 
104 struct CInstallIDInit
105 {
CInstallIDInitCInstallIDInit106   CInstallIDInit()
107   {
108     kStartID[0] = ';';
109     kEndID[0] = ';';
110   };
111 } g_CInstallIDInit;
112 
113 
114 #define NT_CHECK_FAIL_ACTION ShowErrorMessage(L"Unsupported Windows version"); return 1;
115 
ShowErrorMessageSpec(const UString & name)116 static void ShowErrorMessageSpec(const UString &name)
117 {
118   UString message = NError::MyFormatMessage(::GetLastError());
119   int pos = message.Find(L"%1");
120   if (pos >= 0)
121   {
122     message.Delete(pos, 2);
123     message.Insert(pos, name);
124   }
125   ShowErrorMessage(NULL, message);
126 }
127 
128 /* BEGIN Mozilla customizations */
129 
130 static char const *
FindStrInBuf(char const * buf,size_t bufLen,char const * str)131 FindStrInBuf(char const * buf, size_t bufLen, char const * str)
132 {
133   size_t index = 0;
134   while (index < bufLen) {
135     char const * result = strstr(buf + index, str);
136     if (result) {
137       return result;
138     }
139     while ((buf[index] != '\0') && (index < bufLen)) {
140       index++;
141     }
142     index++;
143   }
144   return NULL;
145 }
146 
147 static bool
ReadPostSigningDataFromView(char const * view,DWORD size,AString & data)148 ReadPostSigningDataFromView(char const * view, DWORD size, AString& data)
149 {
150   // Find the offset and length of the certificate table,
151   // so we know the valid range to look for the token.
152   if (size < (0x3c + sizeof(UInt32))) {
153     return false;
154   }
155   UInt32 PEHeaderOffset = *(UInt32*)(view + 0x3c);
156   UInt32 optionalHeaderOffset = PEHeaderOffset + 24;
157   UInt32 certDirEntryOffset = 0;
158   if (size < (optionalHeaderOffset + sizeof(UInt16))) {
159     return false;
160   }
161   UInt16 magic = *(UInt16*)(view + optionalHeaderOffset);
162   if (magic == 0x010b) {
163     // 32-bit executable
164     certDirEntryOffset = optionalHeaderOffset + 128;
165   } else if (magic == 0x020b) {
166     // 64-bit executable; certain header fields are wider
167     certDirEntryOffset = optionalHeaderOffset + 144;
168   } else {
169     // Unknown executable
170     return false;
171   }
172   if (size < certDirEntryOffset + 8) {
173     return false;
174   }
175   UInt32 certTableOffset = *(UInt32*)(view + certDirEntryOffset);
176   UInt32 certTableLen = *(UInt32*)(view + certDirEntryOffset + sizeof(UInt32));
177   if (certTableOffset == 0 || certTableLen == 0 ||
178       size < (certTableOffset + certTableLen)) {
179     return false;
180   }
181 
182   char const token[] = "__MOZCUSTOM__:";
183   // We're searching for a string inside a binary blob,
184   // so a normal strstr that bails on the first NUL won't work.
185   char const * tokenPos = FindStrInBuf(view + certTableOffset,
186                                        certTableLen, token);
187   if (tokenPos) {
188     size_t tokenLen = (sizeof(token) / sizeof(token[0])) - 1;
189     data = AString(tokenPos + tokenLen);
190     return true;
191   }
192   return false;
193 }
194 
195 static bool
ReadPostSigningData(UString exePath,AString & data)196 ReadPostSigningData(UString exePath, AString& data)
197 {
198   bool retval = false;
199   HANDLE exeFile = CreateFileW(exePath, GENERIC_READ, FILE_SHARE_READ, NULL,
200                                OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
201   if (exeFile != INVALID_HANDLE_VALUE) {
202     HANDLE mapping = CreateFileMapping(exeFile, NULL, PAGE_READONLY, 0, 0, NULL);
203     if (mapping != INVALID_HANDLE_VALUE) {
204       // MSDN claims the return value on failure is NULL,
205       // but I've also seen it returned on success, so double-check.
206       if (mapping || GetLastError() == ERROR_SUCCESS) {
207         char * view = (char*)MapViewOfFile(mapping, FILE_MAP_READ, 0, 0, 0);
208         if (view) {
209           DWORD fileSize = GetFileSize(exeFile, NULL);
210           retval = ReadPostSigningDataFromView(view, fileSize, data);
211         }
212         CloseHandle(mapping);
213       }
214     }
215     CloseHandle(exeFile);
216   }
217   return retval;
218 }
219 
220 // Delayed load libraries are loaded when the first symbol is used.
221 // The following ensures that we load the delayed loaded libraries from the
222 // system directory.
223 struct AutoLoadSystemDependencies
224 {
AutoLoadSystemDependenciesAutoLoadSystemDependencies225   AutoLoadSystemDependencies()
226   {
227     HMODULE module = ::GetModuleHandleW(L"kernel32.dll");
228     if (module) {
229       // SetDefaultDllDirectories is always available on Windows 8 and above. It
230       // is also available on Windows Vista, Windows Server 2008, and
231       // Windows 7 when MS KB2533623 has been applied.
232       typedef BOOL (WINAPI *SetDefaultDllDirectoriesType)(DWORD);
233       SetDefaultDllDirectoriesType setDefaultDllDirectories =
234         (SetDefaultDllDirectoriesType) GetProcAddress(module, "SetDefaultDllDirectories");
235       if (setDefaultDllDirectories) {
236         setDefaultDllDirectories(0x0800 /* LOAD_LIBRARY_SEARCH_SYSTEM32 */ );
237         return;
238       }
239     }
240 
241     static LPCWSTR delayDLLs[] = { L"uxtheme.dll", L"userenv.dll",
242                                    L"setupapi.dll", L"apphelp.dll",
243                                    L"propsys.dll", L"dwmapi.dll",
244                                    L"cryptbase.dll", L"oleacc.dll",
245                                    L"clbcatq.dll" };
246     WCHAR systemDirectory[MAX_PATH + 1] = { L'\0' };
247     // If GetSystemDirectory fails we accept that we'll load the DLLs from the
248     // normal search path.
249     GetSystemDirectoryW(systemDirectory, MAX_PATH + 1);
250     size_t systemDirLen = wcslen(systemDirectory);
251 
252     // Make the system directory path terminate with a slash
253     if (systemDirectory[systemDirLen - 1] != L'\\' && systemDirLen) {
254       systemDirectory[systemDirLen] = L'\\';
255       ++systemDirLen;
256       // No need to re-NULL terminate
257     }
258 
259     // For each known DLL ensure it is loaded from the system32 directory
260     for (size_t i = 0; i < sizeof(delayDLLs) / sizeof(delayDLLs[0]); ++i) {
261       size_t fileLen = wcslen(delayDLLs[i]);
262       wcsncpy(systemDirectory + systemDirLen, delayDLLs[i],
263       MAX_PATH - systemDirLen);
264       if (systemDirLen + fileLen <= MAX_PATH) {
265         systemDirectory[systemDirLen + fileLen] = L'\0';
266       } else {
267         systemDirectory[MAX_PATH] = L'\0';
268       }
269       LPCWSTR fullModulePath = systemDirectory; // just for code readability
270       LoadLibraryW(fullModulePath);
271     }
272   }
273 } loadDLLs;
274 
275 BOOL
RemoveCurrentDirFromSearchPath()276 RemoveCurrentDirFromSearchPath()
277 {
278   // kernel32.dll is in the knownDLL list so it is safe to load without a full path
279   HMODULE kernel32 = LoadLibraryW(L"kernel32.dll");
280   if (!kernel32) {
281     return FALSE;
282   }
283 
284   typedef BOOL (WINAPI *SetDllDirectoryType)(LPCWSTR);
285   SetDllDirectoryType SetDllDirectoryFn =
286     (SetDllDirectoryType)GetProcAddress(kernel32, "SetDllDirectoryW");
287   if (!SetDllDirectoryFn) {
288     FreeLibrary(kernel32);
289     return FALSE;
290   }
291 
292   // If this call fails we can't do much about it, so ignore it.
293   // It is unlikely to fail and this is just a precaution anyway.
294   SetDllDirectoryFn(L"");
295   FreeLibrary(kernel32);
296   return TRUE;
297 }
298 
299 /* END Mozilla customizations */
300 
WinMain(HINSTANCE hInstance,HINSTANCE,LPWSTR,int)301 int APIENTRY WinMain(HINSTANCE hInstance, HINSTANCE /* hPrevInstance */,
302     #ifdef UNDER_CE
303     LPWSTR
304     #else
305     LPSTR
306     #endif
307     /* lpCmdLine */,int /* nCmdShow */)
308 {
309   /* BEGIN Mozilla customizations */
310   // Disable current directory from being in the search path.
311   // This call does not help with implicitly loaded DLLs.
312   if (!RemoveCurrentDirFromSearchPath()) {
313     WCHAR minOSTitle[512] = { '\0' };
314     WCHAR minOSText[512] = { '\0' };
315     LoadStringW(NULL, IDS_MIN_OS_TITLE, minOSTitle,
316                 sizeof(minOSTitle) / sizeof(minOSTitle[0]));
317     LoadStringW(NULL, IDS_MIN_OS_TEXT, minOSText,
318                 sizeof(minOSText) / sizeof(minOSText[0]));
319     MessageBoxW(NULL, minOSText, minOSTitle, MB_OK | MB_ICONERROR);
320     return 1;
321   }
322   /* END Mozilla customizations */
323 
324   g_hInstance = (HINSTANCE)hInstance;
325 
326   NT_CHECK
327 
328   // BEGIN Mozilla customizations
329   // Our AutoLoadSystemDependencies (see above) does the same job as the
330   // LoadSecurityDlls function, but slightly better because it runs as a static
331   // initializer, and it doesn't include LOAD_LIBRARY_SEARCH_USER_DIRS in
332   // the search path, which partially defeats the purpose of calling
333   // SetDefaultDllDirectories at all.
334   //#ifdef _WIN32
335   //LoadSecurityDlls();
336   //#endif
337   // END Mozilla customizations
338 
339   // InitCommonControls();
340 
341   UString archiveName, switches;
342   #ifdef _SHELL_EXECUTE
343   UString executeFile, executeParameters;
344   #endif
345   NCommandLineParser::SplitCommandLine(GetCommandLineW(), archiveName, switches);
346 
347   FString fullPath;
348   NDLL::MyGetModuleFileName(fullPath);
349 
350   switches.Trim();
351   bool assumeYes = false;
352   if (switches.IsPrefixedBy_Ascii_NoCase("-y"))
353   {
354     assumeYes = true;
355     switches = switches.Ptr(2);
356     switches.Trim();
357   }
358 
359   AString config;
360   if (!ReadDataString(fullPath, kStartID, kEndID, config))
361   {
362     if (!assumeYes)
363       ShowErrorMessage(L"Can't load config info");
364     return 1;
365   }
366 
367   UString dirPrefix ("." STRING_PATH_SEPARATOR);
368   UString appLaunched;
369   bool showProgress = true;
370 
371   /* BEGIN Mozilla customizations */
372   bool extractOnly = false;
373   if (switches.IsPrefixedBy_NoCase(L"/extractdir=")) {
374     assumeYes = true;
375     showProgress = false;
376     extractOnly = true;
377   } else if (!switches.IsEmpty()) {
378     showProgress = false;
379   }
380   /* END Mozilla customizations */
381 
382   if (!config.IsEmpty())
383   {
384     CObjectVector<CTextConfigPair> pairs;
385     if (!GetTextConfig(config, pairs))
386     {
387       if (!assumeYes)
388         ShowErrorMessage(L"Config failed");
389       return 1;
390     }
391     UString friendlyName = GetTextConfigValue(pairs, "Title");
392     UString installPrompt = GetTextConfigValue(pairs, "BeginPrompt");
393     UString progress = GetTextConfigValue(pairs, "Progress");
394     if (progress.IsEqualTo_Ascii_NoCase("no"))
395       showProgress = false;
396     int index = FindTextConfigItem(pairs, "Directory");
397     if (index >= 0)
398       dirPrefix = pairs[index].String;
399     if (!installPrompt.IsEmpty() && !assumeYes)
400     {
401       if (MessageBoxW(0, installPrompt, friendlyName, MB_YESNO |
402           MB_ICONQUESTION) != IDYES)
403         return 0;
404     }
405     appLaunched = GetTextConfigValue(pairs, "RunProgram");
406 
407     #ifdef _SHELL_EXECUTE
408     executeFile = GetTextConfigValue(pairs, "ExecuteFile");
409     executeParameters = GetTextConfigValue(pairs, "ExecuteParameters");
410     #endif
411   }
412 
413   CTempDir tempDir;
414   /* Mozilla customizations - Added !extractOnly */
415   if (!extractOnly && !tempDir.Create(kTempDirPrefix))
416   {
417     if (!assumeYes)
418       ShowErrorMessage(L"Can not create temp folder archive");
419     return 1;
420   }
421 
422   CCodecs *codecs = new CCodecs;
423   CMyComPtr<IUnknown> compressCodecsInfo = codecs;
424   {
425     HRESULT result = codecs->Load();
426     if (result != S_OK)
427     {
428       ShowErrorMessage(L"Can not load codecs");
429       return 1;
430     }
431   }
432 
433   /* BEGIN Mozilla customizations - added extractOnly  parameter support */
434   const FString tempDirPath = extractOnly ? switches.Ptr(12) : GetUnicodeString(tempDir.GetPath());
435   /* END Mozilla customizations */
436   // tempDirPath = L"M:\\1\\"; // to test low disk space
437   {
438     bool isCorrupt = false;
439     UString errorMessage;
440     HRESULT result = ExtractArchive(codecs, fullPath, tempDirPath, showProgress,
441       isCorrupt, errorMessage);
442 
443     if (result != S_OK)
444     {
445       if (!assumeYes)
446       {
447         if (result == S_FALSE || isCorrupt)
448         {
449           NWindows::MyLoadString(IDS_EXTRACTION_ERROR_MESSAGE, errorMessage);
450           result = E_FAIL;
451         }
452         if (result != E_ABORT)
453         {
454           if (errorMessage.IsEmpty())
455             errorMessage = NError::MyFormatMessage(result);
456           ::MessageBoxW(0, errorMessage, NWindows::MyLoadString(IDS_EXTRACTION_ERROR_TITLE), MB_ICONERROR);
457         }
458       }
459       return 1;
460     }
461   }
462 
463   /* BEGIN Mozilla customizations */
464   // Retrieve and store any data added to this file after signing.
465   {
466     AString postSigningData;
467     if (ReadPostSigningData(fullPath, postSigningData)) {
468       FString postSigningDataFilePath(tempDirPath);
469       NFile::NName::NormalizeDirPathPrefix(postSigningDataFilePath);
470       postSigningDataFilePath += L"postSigningData";
471 
472       NFile::NIO::COutFile postSigningDataFile;
473       postSigningDataFile.Create(postSigningDataFilePath, true);
474 
475       UInt32 written = 0;
476       postSigningDataFile.Write(postSigningData, postSigningData.Len(), written);
477     }
478   }
479 
480   if (extractOnly) {
481     return 0;
482   }
483   /* END Mozilla customizations */
484 
485   #ifndef UNDER_CE
486   CCurrentDirRestorer currentDirRestorer;
487   if (!SetCurrentDir(tempDirPath))
488     return 1;
489   #endif
490 
491   HANDLE hProcess = 0;
492 #ifdef _SHELL_EXECUTE
493   if (!executeFile.IsEmpty())
494   {
495     CSysString filePath (GetSystemString(executeFile));
496     SHELLEXECUTEINFO execInfo;
497     execInfo.cbSize = sizeof(execInfo);
498     execInfo.fMask = SEE_MASK_NOCLOSEPROCESS
499       #ifndef UNDER_CE
500       | SEE_MASK_FLAG_DDEWAIT
501       #endif
502       ;
503     execInfo.hwnd = NULL;
504     execInfo.lpVerb = NULL;
505     execInfo.lpFile = filePath;
506 
507     if (!switches.IsEmpty())
508     {
509       executeParameters.Add_Space_if_NotEmpty();
510       executeParameters += switches;
511     }
512 
513     CSysString parametersSys (GetSystemString(executeParameters));
514     if (parametersSys.IsEmpty())
515       execInfo.lpParameters = NULL;
516     else
517       execInfo.lpParameters = parametersSys;
518 
519     execInfo.lpDirectory = NULL;
520     execInfo.nShow = SW_SHOWNORMAL;
521     execInfo.hProcess = 0;
522     /* BOOL success = */ ::ShellExecuteEx(&execInfo);
523     UINT32 result = (UINT32)(UINT_PTR)execInfo.hInstApp;
524     if (result <= 32)
525     {
526       if (!assumeYes)
527         ShowErrorMessage(L"Can not open file");
528       return 1;
529     }
530     hProcess = execInfo.hProcess;
531   }
532   else
533 #endif
534   {
535     if (appLaunched.IsEmpty())
536     {
537       appLaunched = L"setup.exe";
538       if (!NFind::DoesFileExist(us2fs(appLaunched)))
539       {
540         if (!assumeYes)
541           ShowErrorMessage(L"Can not find setup.exe");
542         return 1;
543       }
544     }
545 
546     {
547       FString s2 = tempDirPath;
548       NName::NormalizeDirPathPrefix(s2);
549       appLaunched.Replace(L"%%T" WSTRING_PATH_SEPARATOR, fs2us(s2));
550     }
551 
552     UString appNameForError = appLaunched; // actually we need to rtemove parameters also
553 
554     appLaunched.Replace(L"%%T", fs2us(tempDirPath));
555 
556     if (!switches.IsEmpty())
557     {
558       appLaunched.Add_Space();
559       appLaunched += switches;
560     }
561     STARTUPINFO startupInfo;
562     startupInfo.cb = sizeof(startupInfo);
563     startupInfo.lpReserved = 0;
564     startupInfo.lpDesktop = 0;
565     startupInfo.lpTitle = 0;
566     startupInfo.dwFlags = 0;
567     startupInfo.cbReserved2 = 0;
568     startupInfo.lpReserved2 = 0;
569 
570     PROCESS_INFORMATION processInformation;
571 
572     CSysString appLaunchedSys (GetSystemString(dirPrefix + appLaunched));
573 
574     BOOL createResult = CreateProcess(NULL, (LPTSTR)(LPCTSTR)appLaunchedSys,
575       NULL, NULL, FALSE, 0, NULL, NULL /*tempDir.GetPath() */,
576       &startupInfo, &processInformation);
577     if (createResult == 0)
578     {
579       if (!assumeYes)
580       {
581         // we print name of exe file, if error message is
582         // ERROR_BAD_EXE_FORMAT: "%1 is not a valid Win32 application".
583         ShowErrorMessageSpec(appNameForError);
584       }
585       return 1;
586     }
587     ::CloseHandle(processInformation.hThread);
588     hProcess = processInformation.hProcess;
589   }
590   if (hProcess != 0)
591   {
592     WaitForSingleObject(hProcess, INFINITE);
593     ::CloseHandle(hProcess);
594   }
595   return 0;
596 }
597