You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
pEpEngine/src/platform_windows.cpp

476 lines
12 KiB
C++

/// @file platform_windows.cpp
/// @brief Windows platform specification
/// @license This file is under GNU General Public License 3.0 - see LICENSE.txt
9 years ago
#define WIN32_LEAN_AND_MEAN
#ifndef UNICODE
#define UNICODE
#endif
#define _WIN32_WINNT 0x0600
#include <windows.h>
#define _CRT_RAND_S
#include <stdlib.h>
9 years ago
#include <assert.h>
8 years ago
#include <string.h>
9 years ago
#include <string>
#include <stdexcept>
#include "platform_windows.h"
4 years ago
#include "dynamic_api.h"
#include <fcntl.h>
#include <tchar.h>
#include <sys\stat.h>
9 years ago
fix ENGINE-956 It is now possible to use shell environment variables in PER_USER_DIRECTORY and PER_MACHINE_DIRECTORY, expanded at *run* time. This is meant to allow more freedom to the deployment engineer, without affecting development. Notice that, when PER_USER_DIRECTORY and PER_MACHINE_DIRECTORY are defined in the Makefile, dollar signs must be escaped (a dollar becomes a double dollar) because of *make* syntax: this has nothing to do with pEp engine code. Expansion follows the Unix shell $VARIABLE syntax: ${VARIABLE} is not supported. See _expand_variables. src/platform_unix.c contained some duplicated logic about caching path results into static variables; this change set would have added to the complexity by calling the new path expansion function, in many different places. Seize the occasion for factoring. Do not change the meaning of PER_USER_DIRECTORY yet; I would like to do it, since it is confusing and inconsistent with per_user_directory (PER_USER_DIRECTORY is a relative path on Unix, while per_user_directory is absolute), but the actual semantics with respect to $HOME and $PEP_HOME is complicated and I do not want to break it. New API function per_user_relative_directory. Remove the ugly "reset" argument from unix_local_db (which was conditionally defined according to NDEBUG!), used to force path re-computation in the test suite after changing an environment variable so as to work in a new "home" directory. In the place of this reset argument add clear API functions to the engine for handling the cache. My quick grapping and IRC interaction confirm that nobody was using the functionality out of the engine test suite. Adapt the test suite to use the new API. Tentatively add caching and expansion functionality to android_system_db as well.
2 years ago
#include "pEpEngine.h" // just for PEP_STATUS
4 years ago
#define LOCAL_DB_FILENAME "management.db"
#define SYSTEM_DB_FILENAME "system.db"
#define KEYS_DB "keys.db"
#define USER_FOLDER_PATH _per_user_directory()
#define SYSTEM_FOLDER_PATH _per_machine_directory()
4 years ago
9 years ago
#ifndef WC_ERR_INVALID_CHARS
#define WC_ERR_INVALID_CHARS 0x00000080 // error for invalid chars
#endif
9 years ago
using namespace std;
static string utf8_string(wstring wstr) {
string result;
if (wstr.length()) {
int size = WideCharToMultiByte(CP_UTF8, WC_ERR_INVALID_CHARS,
wstr.c_str(), -1, NULL, 0, NULL, NULL);
assert(size);
if (size) {
char *buf = new char[size];
WideCharToMultiByte(CP_UTF8, WC_ERR_INVALID_CHARS, wstr.c_str(),
-1, buf, size, NULL, NULL);
result = buf;
delete[] buf;
} else
throw out_of_range("input wstring is not valid"
" while converting UTF-16 to UTF-8.");
}
return result;
}
static wstring utf16_string(string str) {
wstring result;
if (str.length()) {
int size = MultiByteToWideChar(CP_UTF8, MB_ERR_INVALID_CHARS,
str.c_str(), -1, NULL, 0);
assert(size);
if (size) {
wchar_t * buf = new wchar_t[size];
MultiByteToWideChar(CP_UTF8, MB_ERR_INVALID_CHARS, str.c_str(), -1,
buf, size);
result = buf;
delete[] buf;
} else
throw out_of_range("input string is not valid"
" while converting UTF-8 to UTF-16.");
}
return result;
}
static bool readRegistryString(
HKEY hKey, LPCTSTR lpSubKey, LPCTSTR lpValueName, LPTSTR lpResult,
DWORD dwSize, LPCTSTR lpDefault
)
{
assert(lpResult);
HKEY theKey;
DWORD type;
DWORD bytesCopied = dwSize;
HRESULT result;
result = RegOpenKeyEx(hKey, lpSubKey, 0, KEY_READ, &theKey);
if (result != ERROR_SUCCESS) {
if (lpDefault) {
wcsncpy_s(lpResult, dwSize, lpDefault, _TRUNCATE);
return true;
}
else
return false;
}
result = RegQueryValueEx(theKey, lpValueName, NULL, &type,
9 years ago
(LPBYTE) lpResult, &bytesCopied);
if (result != ERROR_SUCCESS || (type != REG_EXPAND_SZ && type != REG_SZ)) {
if (lpDefault) {
wcsncpy_s(lpResult, dwSize, lpDefault, _TRUNCATE);
RegCloseKey(theKey);
return true;
}
else {
RegCloseKey(theKey);
return false;
}
}
RegCloseKey(theKey);
return true;
9 years ago
}
static const DWORD PATH_BUF_SIZE = 32768;
static inline string managementPath(const char *file_path, const char *file_name)
{
string path;
4 years ago
TCHAR tPath[PATH_BUF_SIZE];
9 years ago
DWORD length = ExpandEnvironmentStringsW(utf16_string(file_path).c_str(),
tPath, PATH_BUF_SIZE);
assert(length);
9 years ago
if (length == 0)
throw bad_alloc(); // BUG: there are other errors possible beside out of memory
9 years ago
CreateDirectory(tPath, NULL);
DWORD error = GetLastError();
9 years ago
path = utf8_string(tPath);
path += "\\";
path += file_name;
9 years ago
return path;
9 years ago
}
const char *_per_machine_directory(void)
{
static string path;
if (path.length())
return path.c_str();
TCHAR tPath[PATH_BUF_SIZE];
TCHAR tPath2[PATH_BUF_SIZE];
// Get SystemFolder Registry value and use if available
bool result = readRegistryString(HKEY_CURRENT_USER, TEXT("SOFTWARE\\pEp"),
TEXT("SystemFolder"), tPath2, PATH_BUF_SIZE, NULL);
DWORD length = 0;
// If no Registry value was found, use default
if (!result) {
length = ExpandEnvironmentStringsW(utf16_string(string(PER_MACHINE_DIRECTORY)).c_str(),
tPath, PATH_BUF_SIZE);
}
else {
length = ExpandEnvironmentStringsW(tPath2, tPath, PATH_BUF_SIZE);
}
assert(length);
if (length == 0)
throw bad_alloc(); // BUG: there are other errors possible beside out of memory
path = utf8_string(wstring(tPath, length));
return path.c_str();
}
const char *_per_user_directory(void)
{
static string path;
if (path.length())
return path.c_str();
TCHAR tPath[PATH_BUF_SIZE];
TCHAR tPath2[PATH_BUF_SIZE];
// Get UserFolder Registry value and use if available
bool result = readRegistryString(HKEY_CURRENT_USER, TEXT("SOFTWARE\\pEp"),
TEXT("UserFolder"), tPath2, PATH_BUF_SIZE, NULL);
DWORD length = 0;
// If no Registry value was found, use default
if (!result) {
length = ExpandEnvironmentStringsW(utf16_string(string(PER_USER_DIRECTORY)).c_str(),
tPath, PATH_BUF_SIZE);
}
else {
length = ExpandEnvironmentStringsW(tPath2, tPath, PATH_BUF_SIZE);
}
assert(length);
if (length == 0)
throw bad_alloc(); // BUG: there are other errors possible beside out of memory
path = utf8_string(wstring(tPath));
return path.c_str();
}
9 years ago
extern "C" {
4 years ago
DYNAMIC_API const char *per_user_directory(void)
{
return _per_user_directory();
4 years ago
}
DYNAMIC_API const char *per_machine_directory(void)
{
return _per_machine_directory();
4 years ago
}
9 years ago
void *dlopen(const char *filename, int flag) {
static TCHAR path[PATH_BUF_SIZE];
9 years ago
assert(filename);
assert(flag == RTLD_LAZY); // only lazy binding is implemented
// Look up GnuPG installation in current user scope
bool result = readRegistryString(HKEY_CURRENT_USER,
TEXT("SOFTWARE\\GnuPG"), TEXT("Install Directory"), path,
PATH_BUF_SIZE, NULL);
// If not found in current user, look up in local machine
if (!result)
result = readRegistryString(HKEY_LOCAL_MACHINE,
TEXT("SOFTWARE\\GnuPG"), TEXT("Install Directory"), path,
PATH_BUF_SIZE, NULL);
assert(result);
if (!result)
return NULL;
9 years ago
SetDllDirectory(TEXT(""));
BOOL _result = SetDllDirectory(path);
assert(_result != 0);
if (_result == 0)
return NULL;
HMODULE module = LoadLibrary(utf16_string(filename).c_str());
if (module == NULL) {
SetDllDirectory(NULL);
_tcscat_s(path, TEXT("\\bin"));
SetDllDirectory(TEXT(""));
_result = SetDllDirectory(path);
assert(_result != 0);
if (_result == 0)
return NULL;
module = LoadLibrary(utf16_string(filename).c_str());
}
9 years ago
SetDllDirectory(NULL);
if (module == NULL)
return NULL;
else
return (void *) module;
9 years ago
}
int dlclose(void *handle) {
if (FreeLibrary((HMODULE) handle))
return 0;
else
return 1;
9 years ago
}
void *dlsym(void *handle, const char *symbol) {
return (void *) (intptr_t) GetProcAddress((HMODULE) handle, symbol);
9 years ago
}
const char *windoze_keys_db(void) {
static string path;
if (path.length() == 0) {
path = managementPath(USER_FOLDER_PATH, KEYS_DB);
}
return path.c_str();
}
const char *windoze_local_db(void) {
static string path;
if (path.length() == 0)
path = managementPath(USER_FOLDER_PATH, LOCAL_DB_FILENAME);
9 years ago
return path.c_str();
}
const char *windoze_system_db(void) {
static string path;
if (path.length() == 0)
path = managementPath(PER_MACHINE_DIRECTORY, SYSTEM_DB_FILENAME);
9 years ago
return path.c_str();
}
long random(void)
{
unsigned int r;
errno_t e;
assert(sizeof(unsigned int) == sizeof(long)); // this is Windoze
do {
e = rand_s(&r);
} while (e);
return (long) (r & ((1U<<31)-1));
}
8 years ago
char *strndup(const char *s1, size_t n)
{
char *str = (char *) calloc(n + 1, 1);
8 years ago
if (str == NULL)
return NULL;
strncpy(str, s1, n);
return str;
}
8 years ago
char *stpcpy(char *dst, const char *src)
{
for (;; ++dst, ++src) {
*dst = *src;
if (*dst == 0)
break;
}
return dst;
}
size_t strlcpy(char* dst, const char* src, size_t size) {
size_t retval = strlen(src);
size_t size_to_copy = (retval < size ? retval : size - 1);
// strlcpy doc says src and dst not allowed to overlap, as
// it's undefined. So this is acceptable:
memcpy((void*)dst, (void*)src, size_to_copy); // no defined error return, but strcpy doesn't either
dst[size_to_copy] = '\0';
return retval;
}
size_t strlcat(char* dst, const char* src, size_t size) {
size_t start_len = strnlen(dst, size);
if (start_len == size)
return size; // no copy, no null termination in size bytes, according to spec
size_t add_len = strlen(src);
size_t retval = start_len + add_len;
size_t size_to_copy = (retval < size ? add_len : (size - start_len) - 1);
// strlcat doc says src and dst not allowed to overlap, as
// it's undefined. So this is acceptable:
memcpy((void*)(dst + start_len), (void*)src, size_to_copy); // no defined error return, but strcpy doesn't either
dst[start_len + size_to_copy] = '\0';
return retval;
}
char *strnstr(const char *big, const char *little, size_t len) {
if (big == NULL || little == NULL)
return NULL;
if (*little == '\0')
return (char*)big;
const char* curr_big = big;
size_t little_len = strlen(little);
size_t remaining = len;
const char* retval = NULL;
for (remaining = len; remaining >= little_len && *curr_big != '\0'; remaining--, curr_big++) {
// find first-char match
if (*curr_big != *little) {
continue;
}
retval = curr_big;
const char* inner_big = retval + 1;
const char* curr_little = little + 1;
3 years ago
size_t j;
for (j = 1; j < little_len; j++, inner_big++, curr_little++) {
if (*inner_big != *curr_little) {
retval = NULL;
break;
}
}
if (retval)
break;
}
return (char*)retval;
}
int mkstemp(char *templ)
{
char *pathname = _mktemp(templ);
if (!pathname)
return -1;
return _open(pathname, _O_RDWR | _O_CREAT | _O_EXCL, _S_IREAD | _S_IWRITE);
}
8 years ago
DYNAMIC_API time_t timegm(timestamp *timeptr)
{
3 years ago
assert(timeptr);
if (!timeptr)
return -1;
timeptr->tm_gmtoff = 0;
time_t result = _mkgmtime((struct tm *) timeptr);
3 years ago
if (result == -1)
return -1;
return result;
}
void uuid_generate_random(pEpUUID out)
7 years ago
{
RPC_STATUS rpc_status = UuidCreate(out);
assert(rpc_status == RPC_S_OK);
}
int uuid_parse(char *in, pEpUUID uu)
7 years ago
{
unsigned char *_in = (unsigned char *) in;
RPC_STATUS rpc_status = UuidFromStringA(_in, uu);
7 years ago
assert(rpc_status == RPC_S_OK);
if (rpc_status == RPC_S_INVALID_STRING_UUID)
return -1;
return 0;
}
void uuid_unparse_upper(pEpUUID uu, uuid_string_t out)
7 years ago
{
unsigned char *_out = (unsigned char*)out;
7 years ago
RPC_CSTR str;
RPC_STATUS rpc_status = UuidToStringA(uu, &str);
7 years ago
assert(rpc_status == RPC_S_OK);
if (rpc_status == RPC_S_OK) {
memcpy(out, str, 36);
out[36] = 0;
RpcStringFreeA(&str);
7 years ago
}
else { // if (rpc_status == RPC_S_OUT_OF_MEMORY)
memset(out, 0, 37);
}
}
void log_output_debug(const char *title,
const char *entity,
const char *description,
const char *comment)
{
const size_t size = 256;
char str[size];
snprintf(str, size, "*** %s %s %s %s\n", title, entity, description, comment);
OutputDebugStringA(str);
}
fix ENGINE-956 It is now possible to use shell environment variables in PER_USER_DIRECTORY and PER_MACHINE_DIRECTORY, expanded at *run* time. This is meant to allow more freedom to the deployment engineer, without affecting development. Notice that, when PER_USER_DIRECTORY and PER_MACHINE_DIRECTORY are defined in the Makefile, dollar signs must be escaped (a dollar becomes a double dollar) because of *make* syntax: this has nothing to do with pEp engine code. Expansion follows the Unix shell $VARIABLE syntax: ${VARIABLE} is not supported. See _expand_variables. src/platform_unix.c contained some duplicated logic about caching path results into static variables; this change set would have added to the complexity by calling the new path expansion function, in many different places. Seize the occasion for factoring. Do not change the meaning of PER_USER_DIRECTORY yet; I would like to do it, since it is confusing and inconsistent with per_user_directory (PER_USER_DIRECTORY is a relative path on Unix, while per_user_directory is absolute), but the actual semantics with respect to $HOME and $PEP_HOME is complicated and I do not want to break it. New API function per_user_relative_directory. Remove the ugly "reset" argument from unix_local_db (which was conditionally defined according to NDEBUG!), used to force path re-computation in the test suite after changing an environment variable so as to work in a new "home" directory. In the place of this reset argument add clear API functions to the engine for handling the cache. My quick grapping and IRC interaction confirm that nobody was using the functionality out of the engine test suite. Adapt the test suite to use the new API. Tentatively add caching and expansion functionality to android_system_db as well.
2 years ago
DYNAMIC_API PEP_STATUS reset_path_cache(void)
{
/* Do nothing and return success. This only exists for API compatibility
with the Unix version. */
return PEP_STATUS_OK;
}
DYNAMIC_API void clear_path_cache (void)
{
/* Like reset_path_cache, do nothing. */
}
} // "C"