diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index abe8f96..385b302 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -1,16 +1,19 @@ # Copyright (C) 2018 Intevation GmbH # # This file is Free Software under the GNU GPL (v>=2) # and comes with ABSOLUTELY NO WARRANTY! # See LICENSE.txt for details. include_directories(${Qt5Core_INCLUDE_DIRS}) include_directories(${CMAKE_CURRENT_SOURCE_DIR}) include_directories(util) add_subdirectory(resolver) add_subdirectory(overlayer) add_subdirectory(gpgolconfig) add_subdirectory(gpgolkeyadder) +if (WIN32) + add_subdirectory(gpga) +endif () diff --git a/src/gpga/CMakeLists.txt b/src/gpga/CMakeLists.txt new file mode 100644 index 0000000..1e8e8c3 --- /dev/null +++ b/src/gpga/CMakeLists.txt @@ -0,0 +1,28 @@ +# Copyright (C) 2018 Andre Heinecke +# +# This file is Free Software under the GNU GPL (v>=2) +# and comes with ABSOLUTELY NO WARRANTY! +# See LICENSE.txt for details. + +set(EXECUTABLE_NAME "gpga") + +set(EXECUTABLE_SRC + flange.cpp + ../util/debug.cpp + ../util/w32-util.cpp + gpga.def +) + +add_library (${EXECUTABLE_NAME} + ${EXECUTABLE_SRC} +) + +target_link_libraries(${EXECUTABLE_NAME} + PRIVATE Gpgmepp +) + +set_target_properties(${EXECUTABLE_NAME} PROPERTIES LINK_FLAGS "-municode") + +set_target_properties(${EXECUTABLE_NAME} PROPERTIES PREFIX "") + +install(TARGETS ${EXECUTABLE_NAME} DESTINATION bin) diff --git a/src/gpga/comhelp.h b/src/gpga/comhelp.h new file mode 100644 index 0000000..bafe203 --- /dev/null +++ b/src/gpga/comhelp.h @@ -0,0 +1,19 @@ +#ifndef COMHELP_H +#define COMHELP_H +/* Copyright (C) 2018 by Andre Heinecke + * + * This file is Free Software under the GNU GPL (v>=2) + * and comes with ABSOLUTELY NO WARRANTY! + * See LICENSE.txt for details. + */ + +#define DECLARE_IUNKNOWN \ + +#define SFGAO_FOLDER 0x20000000 +#define SFGAO_HASSUBFOLDER 0x80000000 +#define SFGAO_CANDELETE 0x00000020 +#define SFGAO_CANRENAME 0x00000010 +#define SFGAO_HASPROPSHEET 0x00000040 + + +#endif // COMHELP_H diff --git a/src/gpga/flange.cpp b/src/gpga/flange.cpp new file mode 100644 index 0000000..b77d6e5 --- /dev/null +++ b/src/gpga/flange.cpp @@ -0,0 +1,271 @@ +/* Copyright (C) 2018 by Andre Heinecke + * + * This file is Free Software under the GNU GPL (v>=2) + * and comes with ABSOLUTELY NO WARRANTY! + * See LICENSE.txt for details. + */ + +#include "flange.h" +#include "debug.h" +#include "comhelp.h" +#include "w32-util.h" + +#include "shlobj.h" + +/** @file Code to flange to the Windows explorer. + * + * This file contains the Windows and COM API requirements + * to properly work as an explorer extension. + */ + +#ifndef INITGUID +/* Include every header that defines a GUID below this + macro. Otherwise the GUID's will only be declared and + not defined. */ +#define INITGUID +#endif +#include + +#define MY_CLSID_STR "CCD955E4-5C16-4A33-AFDA-A8947A94946C" +DEFINE_GUID(MY_CLSID, 0xCCD955E4, 0x5C16, 0x4A33, + 0xAF, 0xDA, 0xA8, 0x94, 0x7A, 0x94, 0x94, 0x6C); +#define MY_PROGID "GpgArchive" + +static HINSTANCE s_hinst; +static FlangeFactory *s_factory; + +static void init_logging() +{ + static bool wasDone; + if (!wasDone) { + gpgrt_log_set_sink("c:\\tmp\\gpga.txt", nullptr, -1); + wasDone = true; + } +} + +/* Here we go */ +STDAPI DllGetClassObject (REFCLSID rclsid, REFIID riid, LPVOID* ppv) +{ + init_logging (); + + log_debug ("Get class object for clsid'" GUID_FMT "'", + GUID_ARG(riid)); + if (!ppv) { + return E_POINTER; + } + *ppv = nullptr; + + if (rclsid == MY_CLSID) { + return FlangeFactory::instance()->QueryInterface(riid, ppv); + } + + return CLASS_E_CLASSNOTAVAILABLE; +} + +STDMETHODIMP FlangeFactory::LockServer (BOOL lock) +{ + if (lock) { + AddRef(); + } else { + Release(); + } + return S_OK; +} + +STDMETHODIMP FlangeFactory::CreateInstance (LPUNKNOWN unknown, + REFIID riid, + LPVOID* ppvObj) +{ + log_debug ("CreateInstance with id '" GUID_FMT "'", + GUID_ARG(riid)); + + return E_NOINTERFACE; +} + +STDMETHODIMP FlangeFactory::QueryInterface (REFIID riid, void **ppv) +{ + log_debug ("QueryInterface:'" GUID_FMT "'", + GUID_ARG(riid)); + + if (!ppv) { + return E_POINTER; + } + + *ppv = nullptr; + + /* The static casts ensure that the virtual function table + layout of the returned object is correct. */ + if (riid == IID_IUnknown) + *ppv = static_cast (this); + else if (riid == IID_IClassFactory) + *ppv = static_cast (this); + else + return E_NOINTERFACE; + + reinterpret_cast(*ppv)->AddRef (); + + return S_OK; +} + +FlangeFactory *FlangeFactory::instance() +{ + if (!s_factory) { + s_factory = new FlangeFactory(); + } + return s_factory; +} + +STDMETHODIMP_(ULONG) FlangeFactory::Release() +{ + ULONG lCount = --m_lRef; + if (!lCount) { + delete this; + } + return lCount; +} + +FlangeFactory::~FlangeFactory() +{ + s_factory = nullptr; +} + +STDAPI DllCanUnloadNow() +{ + return s_factory == nullptr; +} + +STDAPI DllUnregisterServer() +{ + HKEY root_key; + + if (W32::isElevated()) { + root_key = HKEY_LOCAL_MACHINE; + } else { + root_key = HKEY_CURRENT_USER; + } + + log_debug("Un Registering shell extension."); + + RegDeleteKeyA(root_key, "Software\\Classes\\.gpga"); + RegDeleteKeyA(root_key, "Software\\Classes\\GPGArchive"); + RegDeleteKeyA(root_key, "Software\\Classes\\CLSID\\{" MY_CLSID_STR "}"); +} + +STDAPI DllRegisterServer() +{ + const char *root_key; + + const auto gpg4windir = W32::getGpg4winDir(); + + init_logging (); + + log_debug ("Debug"); + + if (gpg4windir.empty()) { + STRANGEPOINT; + return FALSE; + } + + char module_path[MAX_PATH + 1]; + if (!s_hinst || GetModuleFileNameA (s_hinst, module_path, + MAX_PATH) == MAX_PATH) { + STRANGEPOINT; + return FALSE; + } + + if (W32::isElevated()) { + root_key = "HKEY_LOCAL_MACHINE"; + } else { + root_key = "HKEY_CURRENT_USER"; + } + + log_debug("Registering shell extension."); + + /* Tar file is: {2B3256E4-49AA-11D3-8229-0050AE509054} */ + + /* Register File extension */ + if (!W32::writeRegStr(root_key, "Software\\Classes\\.gpga", + nullptr, MY_PROGID)) { + STRANGEPOINT; + return FALSE; + } + if (!W32::writeRegStr(root_key, "Software\\Classes\\GPGArchive", + nullptr, "GnuPG Archive Extension")) { + STRANGEPOINT; + return FALSE; + } + if (!W32::writeRegStr(root_key, "Software\\Classes\\GPGArchive", + "FriendlyTypeName", "GnuPG Archive")) { + STRANGEPOINT; + return FALSE; + } + if (!W32::writeRegStr(root_key, "Software\\Classes\\GPGArchive\\ShellEx", + nullptr, "")) { + STRANGEPOINT; + return FALSE; + } + if (!W32::writeRegStr(root_key, "Software\\Classes\\GPGArchive\\CLSID", + nullptr, "{" MY_CLSID_STR "}")) { + STRANGEPOINT; + return FALSE; + } + if (!W32::writeRegStr(root_key, "Software\\Classes\\GPGArchive\\CLSID", + nullptr, "{" MY_CLSID_STR "}")) { + STRANGEPOINT; + return FALSE; + } + if (!W32::writeRegStr(root_key, "Software\\Classes\\CLSID\\{" MY_CLSID_STR "}", + nullptr, "GnuPG Archive Extension")) { + STRANGEPOINT; + return FALSE; + } + std::string iconPath = gpg4windir + "\\share\\gpg4win\\file-ext.ico"; + if (!W32::writeRegStr(root_key, "Software\\Classes\\CLSID\\{" MY_CLSID_STR "}", + "Default Icon", iconPath.c_str())) { + STRANGEPOINT; + return FALSE; + } + if (!W32::writeRegStr(root_key, "Software\\Classes\\CLSID\\{" MY_CLSID_STR "}\\InprocServer32", + nullptr, module_path)) { + STRANGEPOINT; + return FALSE; + } + if (!W32::writeRegStr(root_key, "Software\\Classes\\CLSID\\{" MY_CLSID_STR "}\\InprocServer32", + "ThreadingModel", "Apartment")) { + STRANGEPOINT; + return FALSE; + } + if (!W32::writeRegStr(root_key, "Software\\Classes\\CLSID\\{" MY_CLSID_STR "}\\ProgID", + nullptr, MY_PROGID)) { + STRANGEPOINT; + return FALSE; + } + if (!W32::writeRegStr(root_key, "Software\\Classes\\CLSID\\{" MY_CLSID_STR "}\\Implemented Categories\\" + "{00021490-0000-0000-C000-000000000046}", + nullptr, "Browsable Shell Extension")) { + STRANGEPOINT; + return FALSE; + } + /* See: https://msdn.microsoft.com/en-us/library/windows/desktop/cc144093(v=vs.85).aspx */ + if (!W32::writeRegDword(root_key, "Software\\Classes\\CLSID\\{" MY_CLSID_STR "}\\ShellFolder", + "Attributes", SFGAO_FOLDER | SFGAO_CANRENAME | SFGAO_CANDELETE)) { + STRANGEPOINT; + return FALSE; + } + + /* Notify the shell about the change. */ + SHChangeNotify (SHCNE_ASSOCCHANGED, SHCNF_IDLIST, NULL, NULL); +} + +STDAPI DllMain (HINSTANCE hinst, DWORD reason, LPVOID) +{ + if (reason == DLL_PROCESS_ATTACH) { + s_hinst = hinst; + // Do init of GPGME + init_logging (); + } else if (reason == DLL_PROCESS_DETACH) { + log_debug ("Detached"); + } + + return TRUE; +} diff --git a/src/gpga/flange.h b/src/gpga/flange.h new file mode 100644 index 0000000..7801824 --- /dev/null +++ b/src/gpga/flange.h @@ -0,0 +1,36 @@ +#ifndef FLANGE_H +#define FLANGE_H +/* Copyright (C) 2018 by Andre Heinecke + * + * This file is Free Software under the GNU GPL (v>=2) + * and comes with ABSOLUTELY NO WARRANTY! + * See LICENSE.txt for details. + */ + +#include + +class FlangeFactory: public IClassFactory +{ +protected: + FlangeFactory(): m_lRef(0) {} + +public: + virtual ~FlangeFactory(); + + STDMETHODIMP QueryInterface (REFIID riid, LPVOID* ppvObj); + STDMETHODIMP_(ULONG) AddRef() { ++m_lRef; return m_lRef; }; + STDMETHODIMP_(ULONG) Release(); + + /* IClassFactory */ + STDMETHODIMP CreateInstance (LPUNKNOWN unknown, REFIID riid, + LPVOID* ppvObj); + STDMETHODIMP LockServer (BOOL lock); + + /* Custom */ + static FlangeFactory *instance(); + +private: + ULONG m_lRef; +}; + +#endif // FLANGE_H diff --git a/src/gpga/gpga.def b/src/gpga/gpga.def new file mode 100644 index 0000000..da453f3 --- /dev/null +++ b/src/gpga/gpga.def @@ -0,0 +1,7 @@ +LIBRARY "gpga" +DESCRIPTION 'GnuPG Archive Extension' + +EXPORTS + DllRegisterServer = DllRegisterServer@0 @1 PRIVATE + DllUnregisterServer = DllUnregisterServer@0 @2 PRIVATE + DllGetClassObject = DllGetClassObject@12 @3 PRIVATE diff --git a/src/util/debug.cpp b/src/util/debug.cpp new file mode 100644 index 0000000..1fec4cd --- /dev/null +++ b/src/util/debug.cpp @@ -0,0 +1,16 @@ +/* Copyright (C) 2018 by Andre Heinecke + * + * This file is Free Software under the GNU GPL (v>=2) + * and comes with ABSOLUTELY NO WARRANTY! + * See LICENSE.txt for details. + */ + +#include + +const char * +log_srcname (const char *file) +{ + const char *s = strrchr (file, '/'); + return s? s+1:file; +} + diff --git a/src/util/debug.h b/src/util/debug.h new file mode 100644 index 0000000..e68fca4 --- /dev/null +++ b/src/util/debug.h @@ -0,0 +1,29 @@ +#ifndef GPG4WIN_DEBUG_H +#define GPG4WIN_DEBUG_H +/* Copyright (C) 2018 by Andre Heinecke + * + * This file is Free Software under the GNU GPL (v>=2) + * and comes with ABSOLUTELY NO WARRANTY! + * See LICENSE.txt for details. + */ + + +#include + +const char *log_srcname (const char *s); + +#define SRCNAME log_srcname (__FILE__) + +#define log_debug(format, ...) \ + gpgrt_log_debug("%s:%s: " format, SRCNAME, __func__, ##__VA_ARGS__) + + +#define STRANGEPOINT log_debug ("%s:%s:%d:UNEXPECTED", \ + __LINE__); + +#define GUID_FMT "{%08lX-%04hX-%04hX-%02hX%02hX-%02hX%02hX%02hX%02hX%02hX%02hX}" +#define GUID_ARG(x) (x).Data1, (x).Data2, (x).Data3, (x).Data4[0], \ + (x).Data4[1], (x).Data4[2], (x).Data4[3], (x).Data4[4], \ + (x).Data4[5], (x).Data4[6], (x).Data4[7] + +#endif // GPG4WIN_DEBUG_H diff --git a/src/util/w32-util.cpp b/src/util/w32-util.cpp index cdb962a..884dafb 100644 --- a/src/util/w32-util.cpp +++ b/src/util/w32-util.cpp @@ -1,288 +1,320 @@ /* Copyright (C) 2018 by Intevation GmbH * * This file is Free Software under the GNU GPL (v>=2) * and comes with ABSOLUTELY NO WARRANTY! * See LICENSE.txt for details. */ #include #include "w32-util.h" #include #ifdef _WIN32 # include #endif -#include -#include - #define SLDIR "\\share\\locale" namespace W32 { std::string getGpg4winLocaleDir() { const auto instdir = getGpg4winDir(); if (instdir.empty()) { return std::string(); } return instdir + SLDIR; } std::string getGpg4winDir() { const auto tmp = readRegStr(nullptr, GPG4WIN_REGKEY_3, "Install Directory"); if (tmp.empty()) { return std::string(); } if (!access(tmp.c_str(), R_OK)) { return tmp; } else { fprintf (stderr, "Failed to access: %s\n", tmp.c_str()); } return std::string(); } /* Helper for read_w32_registry_string(). */ #ifdef _WIN32 static HKEY get_root_key(const char *root) { HKEY root_key; if( !root ) root_key = HKEY_CURRENT_USER; else if( !strcmp( root, "HKEY_CLASSES_ROOT" ) ) root_key = HKEY_CLASSES_ROOT; else if( !strcmp( root, "HKEY_CURRENT_USER" ) ) root_key = HKEY_CURRENT_USER; else if( !strcmp( root, "HKEY_LOCAL_MACHINE" ) ) root_key = HKEY_LOCAL_MACHINE; else if( !strcmp( root, "HKEY_USERS" ) ) root_key = HKEY_USERS; else if( !strcmp( root, "HKEY_PERFORMANCE_DATA" ) ) root_key = HKEY_PERFORMANCE_DATA; else if( !strcmp( root, "HKEY_CURRENT_CONFIG" ) ) root_key = HKEY_CURRENT_CONFIG; else return nullptr; return root_key; } #endif #if defined(_WIN64) #define CROSS_ACCESS KEY_WOW64_32KEY #else #define CROSS_ACCESS KEY_WOW64_64KEY #endif #ifdef _WIN32 std::string _readRegStr (HKEY root_key, const char *dir, const char *name, bool alternate) { HKEY key_handle; DWORD n1, nbytes, type; std::string ret; DWORD flags = KEY_READ; if (alternate) { flags |= CROSS_ACCESS; } if (RegOpenKeyExA(root_key, dir, 0, flags, &key_handle)) { return ret; } nbytes = 1; if (RegQueryValueExA(key_handle, name, 0, nullptr, nullptr, &nbytes)) { RegCloseKey (key_handle); return ret; } n1 = nbytes+1; char result[n1]; if (RegQueryValueExA(key_handle, name, 0, &type, (LPBYTE)result, &n1)) { RegCloseKey(key_handle); return ret; } RegCloseKey(key_handle); result[nbytes] = 0; /* make sure it is really a string */ ret = result; if (type == REG_EXPAND_SZ && strchr (result, '%')) { n1 += 1000; char tmp[n1 +1]; nbytes = ExpandEnvironmentStringsA(ret.c_str(), tmp, n1); if (nbytes && nbytes > n1) { n1 = nbytes; char tmp2[n1 +1]; nbytes = ExpandEnvironmentStringsA(result, tmp2, n1); if (nbytes && nbytes > n1) { /* oops - truncated, better don't expand at all */ return ret; } tmp2[nbytes] = 0; ret = tmp2; } else if (nbytes) { /* okay, reduce the length */ tmp[nbytes] = 0; ret = tmp; } } return ret; } #endif std::string readRegStr (const char *root, const char *dir, const char *name) { #ifndef _WIN32 (void)root; (void)dir; (void)name; return std::string(); #else HKEY root_key; std::string ret; if (!(root_key = get_root_key(root))) { return ret; } ret = _readRegStr (root_key, dir, name, false); if (ret.empty()) { // Try local machine as fallback. - qDebug() << "Fallback to HKLM for" << dir << name; ret = _readRegStr (HKEY_LOCAL_MACHINE, dir, name, false); if (ret.empty()) { // Try alternative registry view as fallback - qDebug() << "Fallback to HKLM alternative for" << dir << name; ret = _readRegStr (HKEY_LOCAL_MACHINE, dir, name, true); } } - qDebug() << "Returning:" << (ret.empty() ? "empty" : ret.c_str()); return ret; #endif } bool writeRegStr(const char *root, const char *path, const char *key, const char *val) { #ifndef _WIN32 (void) root; (void) path; (void) key; (void) val; return false; #else HKEY h, hk; int type; int ec; hk = get_root_key (root); if (!hk) { fprintf(stderr, "Failed to find root key.\n"); } DWORD flags = KEY_ALL_ACCESS; ec = RegCreateKeyExA(hk, path, 0, NULL, REG_OPTION_NON_VOLATILE, flags, NULL, &h, NULL); if (ec != ERROR_SUCCESS) { fprintf (stderr, "creating/opening registry key `%s' failed\n", path); return false; } type = strchr (val, '%')? REG_EXPAND_SZ : REG_SZ; ec = RegSetValueExA(h, key, 0, type, (const BYTE*)val, strlen (val)); if (ec != ERROR_SUCCESS) { fprintf (stderr, "saving registry key `%s'->`%s' failed\n", path, key); RegCloseKey(h); return false; } RegCloseKey(h); return true; #endif } +bool writeRegDword (const char *root, const char *path, const char *key, + unsigned long val) +{ +#ifndef _WIN32 + (void) root; (void) path; (void) key; (void) val; + return false; +#else + HKEY h, hk; + int ec; + + hk = get_root_key (root); + + if (!hk) { + fprintf(stderr, "Failed to find root key.\n"); + } + + DWORD flags = KEY_ALL_ACCESS; + + ec = RegCreateKeyExA(hk, path, 0, NULL, REG_OPTION_NON_VOLATILE, + flags, NULL, &h, NULL); + if (ec != ERROR_SUCCESS) + { + fprintf (stderr, "creating/opening registry key `%s' failed\n", path); + return false; + } + ec = RegSetValueExA(h, key, 0, REG_DWORD, (const BYTE*)&val, sizeof(val)); + if (ec != ERROR_SUCCESS) + { + fprintf (stderr, "saving registry key `%s'->`%s' failed\n", path, key); + RegCloseKey(h); + return false; + } + RegCloseKey(h); + return true; +#endif +} + + #ifdef _WIN32 static int has_high_integrity(HANDLE hToken) { PTOKEN_MANDATORY_LABEL integrity_label = NULL; DWORD integrity_level = 0, size = 0; if (hToken == NULL || hToken == INVALID_HANDLE_VALUE) { return 0; } /* Get the required size */ if (!GetTokenInformation (hToken, TokenIntegrityLevel, NULL, 0, &size)) { if (GetLastError() != ERROR_INSUFFICIENT_BUFFER) { return 0; } } integrity_label = (PTOKEN_MANDATORY_LABEL) LocalAlloc(0, size); if (integrity_label == NULL) { return 0; } if (!GetTokenInformation (hToken, TokenIntegrityLevel, integrity_label, size, &size)) { LocalFree(integrity_label); return 0; } /* Get the last integrity level */ integrity_level = *GetSidSubAuthority(integrity_label->Label.Sid, (DWORD)(UCHAR)(*GetSidSubAuthorityCount( integrity_label->Label.Sid) - 1)); LocalFree (integrity_label); return integrity_level >= SECURITY_MANDATORY_HIGH_RID; } #endif bool isElevated() { #ifdef _WIN32 int ret = 0; HANDLE hToken = NULL; if (OpenProcessToken (GetCurrentProcess(), TOKEN_QUERY, &hToken)) { DWORD elevation; DWORD cbSize = sizeof (DWORD); if (GetTokenInformation (hToken, TokenElevation, &elevation, sizeof (TokenElevation), &cbSize)) { ret = elevation; } } /* Elevation will be true and ElevationType TokenElevationTypeFull even if the token is a user token created by SAFER so we additionally check the integrity level of the token which will only be high in the real elevated process and medium otherwise. */ ret = ret && has_high_integrity (hToken); if (hToken) CloseHandle (hToken); return ret; #else return false; #endif } }// namespace diff --git a/src/util/w32-util.h b/src/util/w32-util.h index 998ae95..c358462 100644 --- a/src/util/w32-util.h +++ b/src/util/w32-util.h @@ -1,56 +1,62 @@ /* Copyright (C) 2018 by Intevation GmbH * * This file is Free Software under the GNU GPL (v>=2) * and comes with ABSOLUTELY NO WARRANTY! * See LICENSE.txt for details. */ #include /* The Registry key used by Gpg4win. */ #ifdef _WIN64 # define GPG4WIN_REGKEY_2 "Software\\Wow6432Node\\GNU\\GnuPG" #else # define GPG4WIN_REGKEY_2 "Software\\GNU\\GnuPG" #endif #ifdef _WIN64 # define GPG4WIN_REGKEY_3 "Software\\Wow6432Node\\Gpg4win" #else # define GPG4WIN_REGKEY_3 "Software\\Gpg4win" #endif #define GPGOL_REG_PATH "Software\\GNU\\GpgOL" namespace W32 { /* Get the locale dir of Gpg4win. */ std::string getGpg4winLocaleDir(); /** Get the Gpg4win Install directory. * * Looks for the Gpg4win 3.x registry key. * And checks that the directory can be read. * * @returns an empty string if no dir could be found. * **/ std::string getGpg4winDir(); /** Read a registry string value. If root is null first * HKEY_CURRENT_USER is searched and then it falls back * to HKEY_LOCAL_MACHINE . */ std::string readRegStr(const char *root, const char *path, const char *key); bool writeRegStr(const char *root, const char *path, const char *key, const char *val); +bool writeRegDword(const char *root, + const char *path, + const char *key, + unsigned long value); + + /** Call this to switch to the W64 registry. */ void setW64RegistryMode(bool value); bool isElevated(); } // namespace W32