diff --git a/src/common.c b/src/common.c index f2bfc86..5d4c6fb 100644 --- a/src/common.c +++ b/src/common.c @@ -1,1013 +1,1030 @@ /* common.c - Common routines used by GpgOL * Copyright (C) 2005, 2007, 2008 g10 Code GmbH * 2015, 2016, 2017 Bundesamt für Sicherheit in der Informationstechnik * Software engineering by Intevation GmbH * * This file is part of GpgOL. * * GpgOL is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public License * as published by the Free Software Foundation; either version 2.1 * of the License, or (at your option) any later version. * * GpgOL is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this program; if not, see . */ #include #define OEMRESOURCE /* Required for OBM_CHECKBOXES. */ #include #include #ifndef CSIDL_APPDATA #define CSIDL_APPDATA 0x001a #endif #ifndef CSIDL_LOCAL_APPDATA #define CSIDL_LOCAL_APPDATA 0x001c #endif #ifndef CSIDL_FLAG_CREATE #define CSIDL_FLAG_CREATE 0x8000 #endif #include #include #include #include "common.h" HINSTANCE glob_hinst = NULL; void set_global_hinstance (HINSTANCE hinst) { glob_hinst = hinst; } /* Center the given window with the desktop window as the parent window. */ void center_window (HWND childwnd, HWND style) { HWND parwnd; RECT rchild, rparent; HDC hdc; int wchild, hchild, wparent, hparent; int wscreen, hscreen, xnew, ynew; int flags = SWP_NOSIZE | SWP_NOZORDER; parwnd = GetDesktopWindow (); GetWindowRect (childwnd, &rchild); wchild = rchild.right - rchild.left; hchild = rchild.bottom - rchild.top; GetWindowRect (parwnd, &rparent); wparent = rparent.right - rparent.left; hparent = rparent.bottom - rparent.top; hdc = GetDC (childwnd); wscreen = GetDeviceCaps (hdc, HORZRES); hscreen = GetDeviceCaps (hdc, VERTRES); ReleaseDC (childwnd, hdc); xnew = rparent.left + ((wparent - wchild) / 2); if (xnew < 0) xnew = 0; else if ((xnew+wchild) > wscreen) xnew = wscreen - wchild; ynew = rparent.top + ((hparent - hchild) / 2); if (ynew < 0) ynew = 0; else if ((ynew+hchild) > hscreen) ynew = hscreen - hchild; if (style == HWND_TOPMOST || style == HWND_NOTOPMOST) flags = SWP_NOMOVE | SWP_NOSIZE; SetWindowPos (childwnd, style? style : NULL, xnew, ynew, 0, 0, flags); } /* Return the system's bitmap of the check bar used which check boxes. If CHECKED is set, this check mark is returned; if it is not set, the one used for not-checked is returned. May return NULL on error. Taken from an example in the platform reference. Not used as of now. */ HBITMAP get_system_check_bitmap (int checked) { COLORREF bg_color; HBRUSH bg_brush, saved_dst_brush; HDC src_dc, dst_dc; WORD xsize, ysize; HBITMAP result, saved_dst_bitmap, saved_src_bitmap, checkboxes; BITMAP bitmap; RECT rect; bg_color = GetSysColor (COLOR_MENU); bg_brush = CreateSolidBrush (bg_color); src_dc = CreateCompatibleDC (NULL); dst_dc = CreateCompatibleDC (src_dc); xsize = GetSystemMetrics (SM_CXMENUCHECK); ysize = GetSystemMetrics (SM_CYMENUCHECK); result = CreateCompatibleBitmap(src_dc, xsize, ysize); saved_dst_brush = SelectObject (dst_dc, bg_brush); saved_dst_bitmap = SelectObject (dst_dc, result); PatBlt (dst_dc, 0, 0, xsize, ysize, PATCOPY); checkboxes = LoadBitmap (NULL, (LPTSTR)OBM_CHECKBOXES); saved_src_bitmap = SelectObject (src_dc, checkboxes); GetObject (checkboxes, sizeof (BITMAP), &bitmap); rect.top = 0; rect.bottom = (bitmap.bmHeight / 3); if (checked) { /* Select row 1, column 1. */ rect.left = 0; rect.right = (bitmap.bmWidth / 4); } else { /* Select row 1, column 2. */ rect.left = (bitmap.bmWidth / 4); rect.right = (bitmap.bmWidth / 4) * 2; } if ( ((rect.right - rect.left) > (int)xsize) || ((rect.bottom - rect.top) > (int)ysize) ) StretchBlt (dst_dc, 0, 0, xsize, ysize, src_dc, rect.left, rect.top, rect.right - rect.left, rect.bottom - rect.top, SRCCOPY); else BitBlt (dst_dc, 0, 0, rect.right - rect.left, rect.bottom - rect.top, src_dc, rect.left, rect.top, SRCCOPY); SelectObject (src_dc, saved_src_bitmap); SelectObject (dst_dc, saved_dst_brush); result = SelectObject (dst_dc, saved_dst_bitmap); DeleteObject (bg_brush); DeleteObject (src_dc); DeleteObject (dst_dc); return result; } /* Return the path to a file that should be worked with. Returns a malloced string (UTF-8) on success. HWND is the current Window. Title is a UTF-8 encoded string containing the dialog title and may be NULL. On error (i.e. cancel) NULL is returned. */ char * get_open_filename (HWND root, const char *title) { OPENFILENAMEW ofn; wchar_t fname[MAX_PATH+1]; wchar_t *wTitle = NULL; if (title) { wTitle = utf8_to_wchar2 (title, strlen(title)); } memset (fname, 0, sizeof (fname)); /* Set up the ofn structure */ memset (&ofn, 0, sizeof (ofn)); ofn.lStructSize = sizeof (ofn); ofn.hwndOwner = root; ofn.lpstrFile = fname; ofn.nMaxFile = MAX_PATH; ofn.lpstrTitle = wTitle; ofn.Flags = OFN_FILEMUSTEXIST | OFN_HIDEREADONLY; if (GetOpenFileNameW (&ofn)) { xfree (wTitle); return wchar_to_utf8_2 (fname, MAX_PATH); } xfree (wTitle); return NULL; } /* Return a filename to be used for saving an attachment. Returns a malloced string on success. HWND is the current Window and SRCNAME the filename to be used as suggestion. On error (i.e. cancel) NULL is returned. */ char * get_save_filename (HWND root, const char *srcname) { char filter[21] = "All Files (*.*)\0*.*\0\0"; char fname[MAX_PATH+1]; char filterBuf[32]; char* extSep; OPENFILENAME ofn; memset (fname, 0, sizeof (fname)); memset (filterBuf, 0, sizeof (filterBuf)); strncpy (fname, srcname, MAX_PATH-1); fname[MAX_PATH] = 0; if ((extSep = strrchr (srcname, '.')) && strlen (extSep) <= 4) { /* Windows removes the file extension by default so we need to set the first filter to the file extension. */ strcpy (filterBuf, extSep); strcpy (filterBuf + strlen (filterBuf) + 1, extSep); memcpy (filterBuf + strlen (extSep) * 2 + 2, filter, 21); } else memcpy (filterBuf, filter, 21); memset (&ofn, 0, sizeof (ofn)); ofn.lStructSize = sizeof (ofn); ofn.hwndOwner = root; ofn.lpstrFile = fname; ofn.nMaxFile = MAX_PATH; ofn.lpstrFileTitle = NULL; ofn.nMaxFileTitle = 0; ofn.Flags |= OFN_HIDEREADONLY | OFN_OVERWRITEPROMPT; ofn.lpstrTitle = _("GpgOL - Save attachment"); ofn.lpstrFilter = filterBuf; if (GetSaveFileName (&ofn)) return xstrdup (fname); return NULL; } +void +bring_to_front (HWND wid) +{ + if (wid) + { + if (!SetForegroundWindow (wid)) + { + log_debug ("%s:%s: SetForegroundWindow failed", SRCNAME, __func__); + /* Yet another fallback which will not work on some + * versions and is not recommended by msdn */ + if (!ShowWindow (wid, SW_SHOWNORMAL)) + { + log_debug ("%s:%s: ShowWindow failed.", SRCNAME, __func__); + } + } + } +} void fatal_error (const char *format, ...) { va_list arg_ptr; char buf[512]; va_start (arg_ptr, format); vsnprintf (buf, sizeof buf -1, format, arg_ptr); buf[sizeof buf - 1] = 0; va_end (arg_ptr); MessageBox (NULL, buf, "Fatal Error", MB_OK); abort (); } /* This is a helper function to load a Windows function from either of one DLLs. */ static HRESULT w32_shgetfolderpath (HWND a, int b, HANDLE c, DWORD d, LPSTR e) { static int initialized; static HRESULT (WINAPI * func)(HWND,int,HANDLE,DWORD,LPSTR); if (!initialized) { static char *dllnames[] = { "shell32.dll", "shfolder.dll", NULL }; void *handle; int i; initialized = 1; for (i=0, handle = NULL; !handle && dllnames[i]; i++) { handle = LoadLibrary (dllnames[i]); if (handle) { func = (HRESULT (WINAPI *)(HWND,int,HANDLE,DWORD,LPSTR)) GetProcAddress (handle, "SHGetFolderPathA"); if (!func) { FreeLibrary (handle); handle = NULL; } } } } if (func) return func (a,b,c,d,e); else return -1; } /* Same as above, but only convert the first LEN wchars. */ char * wchar_to_utf8_2 (const wchar_t *string, size_t len) { int n; char *result; /* Note, that CP_UTF8 is not defined in Windows versions earlier than NT.*/ n = WideCharToMultiByte (CP_UTF8, 0, string, len, NULL, 0, NULL, NULL); if (n < 0) return NULL; result = xmalloc (n+1); n = WideCharToMultiByte (CP_UTF8, 0, string, len, result, n, NULL, NULL); if (n < 0) { xfree (result); return NULL; } return result; } /* Same as above but convert only the first LEN characters. STRING must be at least LEN characters long. */ wchar_t * utf8_to_wchar2 (const char *string, size_t len) { int n; wchar_t *result; n = MultiByteToWideChar (CP_UTF8, 0, string, len, NULL, 0); if (n < 0) return NULL; result = xmalloc ((n+1) * sizeof *result); n = MultiByteToWideChar (CP_UTF8, 0, string, len, result, n); if (n < 0) { xfree (result); return NULL; } result[n] = 0; return result; } /* Helper for read_w32_registry_string(). */ 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 NULL; return root_key; } /* Return a string from the Win32 Registry or NULL in case of error. Caller must release the return value. A NULL for root is an alias for HKEY_CURRENT_USER, HKEY_LOCAL_MACHINE in turn. NOTE: The value is allocated with a plain malloc() - use free() and not the usual xfree(). */ char * read_w32_registry_string (const char *root, const char *dir, const char *name) { HKEY root_key, key_handle; DWORD n1, nbytes, type; char *result = NULL; if ( !(root_key = get_root_key(root) ) ) return NULL; if( RegOpenKeyEx( root_key, dir, 0, KEY_READ, &key_handle ) ) { if (root) return NULL; /* no need for a RegClose, so return direct */ /* It seems to be common practise to fall back to HKLM. */ if (RegOpenKeyEx (HKEY_LOCAL_MACHINE, dir, 0, KEY_READ, &key_handle) ) return NULL; /* still no need for a RegClose, so return direct */ } nbytes = 1; if( RegQueryValueEx( key_handle, name, 0, NULL, NULL, &nbytes ) ) { if (root) goto leave; /* Try to fallback to HKLM also vor a missing value. */ RegCloseKey (key_handle); if (RegOpenKeyEx (HKEY_LOCAL_MACHINE, dir, 0, KEY_READ, &key_handle) ) return NULL; /* Nope. */ if (RegQueryValueEx( key_handle, name, 0, NULL, NULL, &nbytes)) goto leave; } result = malloc( (n1=nbytes+1) ); if( !result ) goto leave; if( RegQueryValueEx( key_handle, name, 0, &type, result, &n1 ) ) { free(result); result = NULL; goto leave; } result[nbytes] = 0; /* make sure it is really a string */ if (type == REG_EXPAND_SZ && strchr (result, '%')) { char *tmp; n1 += 1000; tmp = malloc (n1+1); if (!tmp) goto leave; nbytes = ExpandEnvironmentStrings (result, tmp, n1); if (nbytes && nbytes > n1) { free (tmp); n1 = nbytes; tmp = malloc (n1 + 1); if (!tmp) goto leave; nbytes = ExpandEnvironmentStrings (result, tmp, n1); if (nbytes && nbytes > n1) { free (tmp); /* oops - truncated, better don't expand at all */ goto leave; } tmp[nbytes] = 0; free (result); result = tmp; } else if (nbytes) { /* okay, reduce the length */ tmp[nbytes] = 0; free (result); result = malloc (strlen (tmp)+1); if (!result) result = tmp; else { strcpy (result, tmp); free (tmp); } } else { /* error - don't expand */ free (tmp); } } leave: RegCloseKey( key_handle ); return result; } /* Get the standard home directory. In general this function should not be used as it does not consider a registry value or the GNUPGHOME environment variable. Please use default_homedir(). */ static const char * standard_homedir (void) { static char *dir; if (!dir) { char path[MAX_PATH]; /* It might be better to use LOCAL_APPDATA because this is defined as "non roaming" and thus more likely to be kept locally. For private keys this is desired. However, given that many users copy private keys anyway forth and back, using a system roaming services might be better than to let them do it manually. A security conscious user will anyway use the registry entry to have better control. */ if (w32_shgetfolderpath (NULL, CSIDL_APPDATA|CSIDL_FLAG_CREATE, NULL, 0, path) >= 0) { char *tmp = malloc (strlen (path) + 6 + 1); strcpy (tmp, path); strcat (tmp, "\\gnupg"); dir = tmp; /* Try to create the directory if it does not yet exists. */ if (access (dir, F_OK)) CreateDirectory (dir, NULL); } else dir = xstrdup ("C:\\gnupg"); } return dir; } /* Retrieve the default home directory. */ const char * default_homedir (void) { static char *dir; if (!dir) { dir = getenv ("GNUPGHOME"); if (!dir || !*dir) { char *tmp; tmp = read_w32_registry_string (NULL, GPG4WIN_REGKEY_3, "HomeDir"); if (!tmp) { tmp = read_w32_registry_string (NULL, GPG4WIN_REGKEY_2, "HomeDir"); } if (tmp && !*tmp) { free (tmp); tmp = NULL; } if (tmp) dir = tmp; else dir = xstrdup (standard_homedir ()); } else dir = xstrdup (dir); } return dir; } /* Return the data dir used for forms etc. Returns NULL on error. */ char * get_data_dir (void) { char *instdir; char *p; char *dname; instdir = get_gpg4win_dir(); if (!instdir) return NULL; /* Build the key: "/share/gpgol". */ #define SDDIR "\\share\\gpgol" dname = malloc (strlen (instdir) + strlen (SDDIR) + 1); if (!dname) { free (instdir); return NULL; } p = dname; strcpy (p, instdir); p += strlen (instdir); strcpy (p, SDDIR); free (instdir); #undef SDDIR return dname; } /* Percent-escape the string STR by replacing colons with '%3a'. If EXTRA is not NULL all characters in it are also escaped. */ char * percent_escape (const char *str, const char *extra) { int i, j; char *ptr; if (!str) return NULL; for (i=j=0; str[i]; i++) if (str[i] == ':' || str[i] == '%' || (extra && strchr (extra, str[i]))) j++; ptr = (char *) malloc (i + 2 * j + 1); i = 0; while (*str) { /* FIXME: Work around a bug in Kleo. */ if (*str == ':') { ptr[i++] = '%'; ptr[i++] = '3'; ptr[i++] = 'a'; } else { if (*str == '%') { ptr[i++] = '%'; ptr[i++] = '2'; ptr[i++] = '5'; } else if (extra && strchr (extra, *str)) { ptr[i++] = '%'; ptr[i++] = tohex_lower ((*str >> 4) & 15); ptr[i++] = tohex_lower (*str & 15); } else ptr[i++] = *str; } str++; } ptr[i] = '\0'; return ptr; } /* Fix linebreaks. This replaces all consecutive \r or \n characters by a single \n. There can be extremly weird combinations of linebreaks like \r\r\n\r\r\n at the end of each line when getting the body of a mail message. */ void fix_linebreaks (char *str, int *len) { char *src; char *dst; src = str; dst = str; while (*src) { if (*src == '\r' || *src == '\n') { do src++; while (*src == '\r' || *src == '\n'); *(dst++) = '\n'; } else { *(dst++) = *(src++); } } *dst = '\0'; *len = dst - str; } /* Get a pretty name for the file at path path. File extension will be set to work for the protocol as provided in protocol and depends on the signature setting. Set signature to 0 if the extension should not be a signature extension. Returns NULL on success. Caller must free result. */ wchar_t * get_pretty_attachment_name (wchar_t *path, protocol_t protocol, int signature) { wchar_t* pretty; wchar_t* buf; if (!path || !wcslen (path)) { log_error("%s:%s: No path given", SRCNAME, __func__); return NULL; } pretty = (wchar_t*) xmalloc ((MAX_PATH + 1) * sizeof (wchar_t)); memset (pretty, 0, (MAX_PATH + 1) * sizeof (wchar_t)); buf = wcsrchr (path, '\\') + 1; if (!buf || !*buf) { log_error("%s:%s: No filename found in path", SRCNAME, __func__); xfree (pretty); return NULL; } wcscpy (pretty, buf); buf = pretty + wcslen(pretty); if (signature) { if (protocol == PROTOCOL_SMIME) { *(buf++) = '.'; *(buf++) = 'p'; *(buf++) = '7'; *(buf++) = 's'; } else { *(buf++) = '.'; *(buf++) = 's'; *(buf++) = 'i'; *(buf++) = 'g'; } } else { if (protocol == PROTOCOL_SMIME) { *(buf++) = '.'; *(buf++) = 'p'; *(buf++) = '7'; *(buf++) = 'm'; } else { *(buf++) = '.'; *(buf++) = 'g'; *(buf++) = 'p'; *(buf++) = 'g'; } } return pretty; } /* Open a file in a temporary directory, take name as a suggestion and put the open Handle in outHandle. Returns the actually used file name in case there were other files with that name. */ wchar_t* get_tmp_outfile (wchar_t *name, HANDLE *outHandle) { wchar_t tmpPath[MAX_PATH]; wchar_t *outName; wchar_t *fileExt = NULL; int tries = 1; if (!name || !wcslen(name)) { log_error ("%s:%s: Needs a name.", SRCNAME, __func__); return NULL; } /* We should probably use the unicode variants here but this would mean adding OpenStreamOnFileW to out mapi */ if (!GetTempPathW (MAX_PATH, tmpPath)) { log_error ("%s:%s: Could not get tmp path.", SRCNAME, __func__); return NULL; } outName = (wchar_t*) xmalloc ((MAX_PATH + 1) * sizeof(wchar_t)); memset (outName, 0, (MAX_PATH + 1) * sizeof (wchar_t)); snwprintf (outName, MAX_PATH, L"%s%s", tmpPath, name); while ((*outHandle = CreateFileW (outName, GENERIC_WRITE | GENERIC_READ, FILE_SHARE_READ | FILE_SHARE_DELETE, NULL, CREATE_NEW, FILE_ATTRIBUTE_TEMPORARY, NULL)) == INVALID_HANDLE_VALUE) { wchar_t fnameBuf[MAX_PATH + 1]; wchar_t origName[MAX_PATH + 1]; memset (fnameBuf, 0, MAX_PATH + 1); memset (origName, 0, MAX_PATH + 1); snwprintf (origName, MAX_PATH, L"%s%s", tmpPath, name); fileExt = wcschr (wcsrchr(origName, '\\'), '.'); if (fileExt) { wcsncpy (fnameBuf, origName, fileExt - origName); } else { wcsncpy (fnameBuf, origName, wcslen (origName)); } snwprintf (outName, MAX_PATH, L"%s%i%s", fnameBuf, tries++, fileExt ? fileExt : L""); if (tries > 100) { /* You have to know when to give up,.. */ log_error ("%s:%s: Could not get a name out of 100 tries", SRCNAME, __func__); xfree (outName); return NULL; } } return outName; } /** Get the Gpg4win Install directory. * * Looks first for the Gpg4win 3.x registry key. Then for the Gpg4win * 2.x registry key. And checks that the directory can be read. * * @returns NULL if no dir could be found. Otherwise a malloced string. */ char * get_gpg4win_dir() { const char *g4win_keys[] = {GPG4WIN_REGKEY_3, GPG4WIN_REGKEY_2, NULL}; const char **key; for (key = g4win_keys; *key; key++) { char *tmp = read_w32_registry_string (NULL, *key, "Install Directory"); if (!tmp) { continue; } if (!access(tmp, R_OK)) { return tmp; } else { log_debug ("Failed to access: %s\n", tmp); xfree (tmp); } } return NULL; } static void epoch_to_file_time (unsigned long time, LPFILETIME pft) { LONGLONG ll; ll = Int32x32To64(time, 10000000) + 116444736000000000; pft->dwLowDateTime = (DWORD)ll; pft->dwHighDateTime = ll >> 32; } char * format_date_from_gpgme (unsigned long time) { wchar_t buf[256]; FILETIME ft; SYSTEMTIME st; epoch_to_file_time (time, &ft); FileTimeToSystemTime(&ft, &st); int ret = GetDateFormatEx (NULL, DATE_SHORTDATE, &st, NULL, buf, 256, NULL); if (ret == 0) { return NULL; } return wchar_to_utf8 (buf); } /* Return the name of the default UI server. This name is used to auto start an UI server if an initial connect failed. */ char * get_uiserver_name (void) { char *name = NULL; char *dir, *uiserver, *p; int extra_arglen = 9; const char * server_names[] = {"kleopatra.exe", "bin\\kleopatra.exe", "gpa.exe", "bin\\gpa.exe", NULL}; const char **tmp = NULL; dir = get_gpg4win_dir (); if (!dir) { log_error ("Failed to find gpg4win dir"); return NULL; } uiserver = read_w32_registry_string (NULL, GPG4WIN_REGKEY_3, "UI Server"); if (!uiserver) { uiserver = read_w32_registry_string (NULL, GPG4WIN_REGKEY_2, "UI Server"); } if (uiserver) { name = xmalloc (strlen (dir) + strlen (uiserver) + extra_arglen + 2); strcpy (stpcpy (stpcpy (name, dir), "\\"), uiserver); for (p = name; *p; p++) if (*p == '/') *p = '\\'; xfree (uiserver); } if (name && !access (name, F_OK)) { /* Set through registry and is accessible */ xfree(dir); return name; } /* Fallbacks */ for (tmp = server_names; *tmp; tmp++) { if (name) { xfree (name); } name = xmalloc (strlen (dir) + strlen (*tmp) + extra_arglen + 2); strcpy (stpcpy (stpcpy (name, dir), "\\"), *tmp); for (p = name; *p; p++) if (*p == '/') *p = '\\'; if (!access (name, F_OK)) { /* Found a viable candidate */ if (strstr (name, "kleopatra.exe")) { strcat (name, " --daemon"); } xfree (dir); return name; } } xfree (dir); log_error ("Failed to find a viable UIServer"); return NULL; } 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) { log_debug ("Invalid parameters."); return 0; } /* Get the required size */ if (!GetTokenInformation (hToken, TokenIntegrityLevel, NULL, 0, &size)) { if (GetLastError() != ERROR_INSUFFICIENT_BUFFER) { log_debug ("Failed to get required size.\n"); return 0; } } integrity_label = (PTOKEN_MANDATORY_LABEL) LocalAlloc(0, size); if (integrity_label == NULL) { log_debug ("Failed to allocate label. \n"); return 0; } if (!GetTokenInformation (hToken, TokenIntegrityLevel, integrity_label, size, &size)) { log_debug ("Failed to get integrity level.\n"); 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; } int is_elevated() { 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; } diff --git a/src/common.h b/src/common.h index 09c6072..d306d16 100644 --- a/src/common.h +++ b/src/common.h @@ -1,146 +1,147 @@ /* common.h - Common declarations for GpgOL * Copyright (C) 2004 Timo Schulz * Copyright (C) 2005, 2006, 2007, 2008 g10 Code GmbH * Copyright (C) 2015, 2016 by Bundesamt für Sicherheit in der Informationstechnik * Software engineering by Intevation GmbH * * This file is part of GpgOL. * * GpgOL is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public License * as published by the Free Software Foundation; either version 2.1 * of the License, or (at your option) any later version. * * GpgOL is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this program; if not, see . */ #ifndef GPGOL_COMMON_H #define GPGOL_COMMON_H #ifdef HAVE_CONFIG_H #include #endif #include #include "common_indep.h" #include #ifdef MIME_SEND # define MIME_UI_DEFAULT 1 #else # define MIME_UI_DEFAULT 0 #endif #ifdef __cplusplus extern "C" { #if 0 } #endif #endif extern HINSTANCE glob_hinst; extern UINT this_dll; /*-- common.c --*/ void set_global_hinstance (HINSTANCE hinst); void center_window (HWND childwnd, HWND style); HBITMAP get_system_check_bitmap (int checked); char *get_save_filename (HWND root, const char *srcname); char *get_open_filename (HWND root, const char *title); char *utf8_to_wincp (const char *string); const char *default_homedir (void); char *get_data_dir (void); char *get_gpg4win_dir (void); /* Get a temporary filename with and its name */ wchar_t *get_tmp_outfile (wchar_t *name, HANDLE *outHandle); wchar_t *get_pretty_attachment_name (wchar_t *path, protocol_t protocol, int signature); /*-- recipient-dialog.c --*/ unsigned int recipient_dialog_box (gpgme_key_t **ret_rset); unsigned int recipient_dialog_box2 (gpgme_key_t *fnd, char **unknown, gpgme_key_t **ret_rset); /*-- passphrase-dialog.c --*/ int signer_dialog_box (gpgme_key_t *r_key, char **r_passwd, int encrypting); gpgme_error_t passphrase_callback_box (void *opaque, const char *uid_hint, const char *pass_info, int prev_was_bad, int fd); void free_decrypt_key (struct passphrase_cb_s *ctx); const char *get_pubkey_algo_str (gpgme_pubkey_algo_t id); /*-- config-dialog.c --*/ void config_dialog_box (HWND parent); int store_extension_value (const char *key, const char *val); int load_extension_value (const char *key, char **val); /*-- verify-dialog.c --*/ int verify_dialog_box (gpgme_protocol_t protocol, gpgme_verify_result_t res, const char *filename); /*-- inspectors.cpp --*/ int initialize_inspectors (void); #if __GNUC__ >= 4 # define GPGOL_GCC_A_SENTINEL(a) __attribute__ ((sentinel(a))) #else # define GPGOL_GCC_A_SENTINEL(a) #endif /* i18n stuff */ #include "w32-gettext.h" #define _(a) gettext (a) #define N_(a) gettext_noop (a) /*-- common.c --*/ void fatal_error (const char *format, ...); char *wchar_to_utf8_2 (const wchar_t *string, size_t len); wchar_t *utf8_to_wchar2 (const char *string, size_t len); char *read_w32_registry_string (const char *root, const char *dir, const char *name); char *percent_escape (const char *str, const char *extra); void fix_linebreaks (char *str, int *len); /* Format a date from gpgme (seconds since epoch) with windows system locale. */ char *format_date_from_gpgme (unsigned long time); /* Get the name of the uiserver */ char *get_uiserver_name (void); int is_elevated (void); /*-- main.c --*/ const void *get_128bit_session_key (void); const void *get_64bit_session_marker (void); void *create_initialization_vector (size_t nbytes); void read_options (void); int write_options (void); extern int g_ol_version_major; void log_window_hierarchy (HWND window, const char *fmt, ...) __attribute__ ((format (printf,2,3))); +void bring_to_front (HWND wid); #ifdef __cplusplus } #endif #endif /*GPGOL_COMMON_H*/ diff --git a/src/cryptcontroller.cpp b/src/cryptcontroller.cpp index 9f6cdbf..389d307 100644 --- a/src/cryptcontroller.cpp +++ b/src/cryptcontroller.cpp @@ -1,796 +1,803 @@ /* @file cryptcontroller.cpp * @brief Helper to do crypto on a mail. * * Copyright (C) 2018 Intevation GmbH * * This file is part of GpgOL. * * GpgOL is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * GpgOL is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this program; if not, see . */ #include "config.h" #include "common.h" #include "cryptcontroller.h" #include "mail.h" #include "mapihelp.h" #include "mimemaker.h" #include #include #include #ifdef HAVE_W32_SYSTEM #include "common.h" /* We use UTF-8 internally. */ #undef _ # define _(a) utf8_gettext (a) #else # define _(a) a #endif #include #define DEBUG_RESOLVER 1 static int sink_data_write (sink_t sink, const void *data, size_t datalen) { GpgME::Data *d = static_cast(sink->cb_data); d->write (data, datalen); return 0; } /** We have some C Style cruft in here as this was historically how GpgOL worked directly in the MAPI data objects. To reduce the regression risk the new object oriented way for crypto reused as much as possible from this. */ CryptController::CryptController (Mail *mail, bool encrypt, bool sign, bool doInline, GpgME::Protocol proto): m_mail (mail), m_encrypt (encrypt), m_sign (sign), m_inline (doInline), m_crypto_success (false), m_proto (proto) { log_debug ("%s:%s: CryptController ctor for %p encrypt %i sign %i inline %i.", SRCNAME, __func__, mail, encrypt, sign, doInline); } CryptController::~CryptController() { log_debug ("%s:%s:%p", SRCNAME, __func__, m_mail); } int CryptController::collect_data () { /* Get the attachment info and the body. We need to do this before creating the engine's filter because sending the cancel to the engine with nothing for the engine to process. Will result in an error. This is actually a bug in our engine code but we better avoid triggering this bug because the engine sometimes hangs. Fixme: Needs a proper fix. */ /* Take the Body from the mail if possible. This is a fix for GnuPG-Bug-ID: T3614 because the body is not always properly updated in MAPI when sending. */ char *body = m_mail->take_cached_plain_body (); if (body && !*body) { xfree (body); body = NULL; } LPMESSAGE message = get_oom_base_message (m_mail->item ()); if (!message) { log_error ("%s:%s: Failed to get base message.", SRCNAME, __func__); } auto att_table = mapi_create_attach_table (message, 0); int n_att_usable = count_usable_attachments (att_table); if (!n_att_usable && !body) { log_debug ("%s:%s: encrypt empty message", SRCNAME, __func__); } if (n_att_usable && m_inline) { log_debug ("%s:%s: PGP Inline not supported for attachments." " Using PGP MIME", SRCNAME, __func__); m_inline = false; } else if (m_inline) { // Inline. Use Body as input and be done. m_input.write (body, strlen (body)); log_debug ("%s:%s: PGP Inline. Using cached body as input.", SRCNAME, __func__); gpgol_release (message); /* Set the input buffer to start. */ m_input.seek (0, SEEK_SET); return 0; } /* Set up the sink object to collect the mime structure */ struct sink_s sinkmem; sink_t sink = &sinkmem; memset (sink, 0, sizeof *sink); sink->cb_data = &m_input; sink->writefnc = sink_data_write; /* Collect the mime strucutre */ if (add_body_and_attachments (sink, message, att_table, m_mail, body, n_att_usable)) { log_error ("%s:%s: Collecting body and attachments failed.", SRCNAME, __func__); gpgol_release (message); return -1; } /* Message is no longer needed */ gpgol_release (message); /* Set the input buffer to start. */ m_input.seek (0, SEEK_SET); return 0; } static void release_carray (char **recipients) { int idx; if (recipients) { for (idx=0; recipients[idx]; idx++) xfree (recipients[idx]); xfree (recipients); } } static inline void rtrim(std::string &s) { s.erase(std::find_if(s.rbegin(), s.rend(), [](int ch) { return !std::isspace(ch); }).base(), s.end()); } int CryptController::lookup_fingerprints (const std::string &sigFpr, const std::vector recpFprs, GpgME::Protocol proto) { auto ctx = std::shared_ptr (GpgME::Context::createForProtocol (proto)); ctx->setKeyListMode (GpgME::Local); GpgME::Error err; if (!sigFpr.empty()) { m_signer_key = ctx->key (sigFpr.c_str (), err, true); if (err || m_signer_key.isNull () ) { log_error ("%s:%s: failed to lookup key for '%s'", SRCNAME, __func__, sigFpr.c_str ()); return -1; } // reset context ctx = std::shared_ptr (GpgME::Context::createForProtocol (proto)); ctx->setKeyListMode (GpgME::Local); } if (!recpFprs.size()) { return 0; } // Convert recipient fingerprints char **cRecps = (char**) xmalloc (sizeof (char*) * (recpFprs.size() + 1)); for (size_t i = 0; i < recpFprs.size(); i++) { cRecps[i] = strdup (recpFprs[i].c_str()); } cRecps[recpFprs.size()] = NULL; err = ctx->startKeyListing (const_cast (cRecps)); if (err) { log_error ("%s:%s: failed to start recipient keylisting", SRCNAME, __func__); return -1; } do { m_recipients.push_back(ctx->nextKey(err)); } while (!err); m_recipients.pop_back(); release_carray (cRecps); return 0; } int CryptController::parse_output (GpgME::Data &resolverOutput) { // Todo: Use Data::toString std::istringstream ss(resolverOutput.toString()); std::string line; GpgME::Protocol proto = GpgME::UnknownProtocol; std::string sigFpr; std::vector recpFprs; while (std::getline (ss, line)) { if (line == "cancel") { log_debug ("%s:%s: resolver canceled", SRCNAME, __func__); return -2; } if (line == "unencrypted") { log_debug ("%s:%s: FIXME resolver wants unencrypted", SRCNAME, __func__); return -1; } std::istringstream lss (line); // First is sig or enc std::string what; std::string how; std::string fingerprint; std::getline (lss, what, ':'); std::getline (lss, how, ':'); std::getline (lss, fingerprint, ':'); // Remove possible trailing newline / cr rtrim (fingerprint); if (proto == GpgME::UnknownProtocol) { proto = (how == "smime") ? GpgME::CMS : GpgME::OpenPGP; } if (what == "sig") { if (!sigFpr.empty ()) { log_error ("%s:%s: multiple signing keys not supported", SRCNAME, __func__); } sigFpr = fingerprint; continue; } if (what == "enc") { recpFprs.push_back (fingerprint); } } if (m_sign && sigFpr.empty()) { log_error ("%s:%s: Sign requested but no signing fingerprint", SRCNAME, __func__); return -1; } if (m_encrypt && !recpFprs.size()) { log_error ("%s:%s: Encrypt requested but no recipient fingerprints", SRCNAME, __func__); return -1; } return lookup_fingerprints (sigFpr, recpFprs, proto); } int CryptController::resolve_keys () { m_recipients.clear(); std::vector args; // Collect the arguments char *gpg4win_dir = get_gpg4win_dir (); if (!gpg4win_dir) { TRACEPOINT; return -1; } const auto resolver = std::string (gpg4win_dir) + "\\bin\\resolver.exe"; args.push_back (resolver); log_debug ("%s:%s: resolving keys with '%s'", SRCNAME, __func__, resolver.c_str ()); // We want debug output as OutputDebugString args.push_back (std::string ("--debug")); - // Pass the handle of the active window for raise / overlay. - args.push_back (std::string ("--hwnd")); // Yes passing it as int is ok. - args.push_back (std::to_string ((int) m_mail->get_window ())); + auto wnd = m_mail->get_window (); + if (wnd) + { + // Pass the handle of the active window for raise / overlay. + args.push_back (std::string ("--hwnd")); + args.push_back (std::to_string ((int) wnd)); + } // Set the overlay caption args.push_back (std::string ("--overlayText")); if (m_encrypt) { args.push_back (std::string (_("Resolving recipients..."))); } else if (m_sign) { args.push_back (std::string (_("Resolving signers..."))); } if (m_sign) { args.push_back (std::string ("--sign")); } const auto cached_sender = m_mail->get_cached_sender (); if (cached_sender.empty()) { log_error ("%s:%s: resolve keys without sender.", SRCNAME, __func__); } else { args.push_back (std::string ("--sender")); args.push_back (cached_sender); } if (m_encrypt) { args.push_back (std::string ("--encrypt")); } if (!opt.autoresolve) { args.push_back (std::string ("--alwaysShow")); } // Get the recipients that are cached from OOM char **recipients = m_mail->take_cached_recipients (); for (size_t i = 0; recipients && recipients[i]; i++) { args.push_back (GpgME::UserID::addrSpecFromString (recipients[i])); } release_carray (recipients); // Convert our collected vector to c strings // It's a bit overhead but should be quick for such small // data. char **cargs = (char**) xmalloc (sizeof (char*) * (args.size() + 1)); for (size_t i = 0; i < args.size(); i++) { cargs[i] = strdup (args[i].c_str()); } cargs[args.size()] = NULL; // Args are prepared. Spawn the resolver. auto ctx = GpgME::Context::createForEngine (GpgME::SpawnEngine); if (!ctx) { // can't happen release_carray (cargs); TRACEPOINT; return -1; } GpgME::Data mystdin (GpgME::Data::null), mystdout, mystderr; #ifdef DEBUG_RESOLVER log_debug ("Spawning args:"); for (size_t i = 0; cargs && cargs[i]; i++) { log_debug ("%i: '%s'", i, cargs[i]); } #endif GpgME::Error err = ctx->spawn (cargs[0], const_cast (cargs), mystdin, mystdout, mystderr, (GpgME::Context::SpawnFlags) ( GpgME::Context::SpawnAllowSetFg | GpgME::Context::SpawnShowWindow)); + // Somehow Qt messes up which window to bring back to front. + // So we do it manually. + bring_to_front (wnd); #ifdef DEBUG_RESOLVER log_debug ("Resolver stdout:\n'%s'", mystdout.toString ().c_str ()); log_debug ("Resolver stderr:\n'%s'", mystderr.toString ().c_str ()); #endif release_carray (cargs); if (err) { log_debug ("%s:%s: Resolver spawn finished Err code: %i asString: %s", SRCNAME, __func__, err.code(), err.asString()); } if (parse_output (mystdout)) { log_debug ("%s:%s: Failed to parse / resolve keys.", SRCNAME, __func__); log_debug ("Resolver stdout:\n'%s'", mystdout.toString ().c_str ()); log_debug ("Resolver stderr:\n'%s'", mystderr.toString ().c_str ()); return -1; } return 0; } int CryptController::do_crypto () { // TODO get recipients and sender and protocol. log_debug ("%s:%s:", SRCNAME, __func__); auto ctx = std::shared_ptr (GpgME::Context::createForProtocol(GpgME::OpenPGP)); if (!ctx) { log_error ("%s:%s: Failure to create context.", SRCNAME, __func__); return -1; } if (resolve_keys ()) { log_debug ("%s:%s: Failure to resolve keys.", SRCNAME, __func__); return -2; } if (!m_signer_key.isNull()) { ctx->addSigningKey (m_signer_key); } ctx->setTextMode (true); ctx->setArmor (true); if (m_encrypt && m_sign) { const auto result_pair = ctx->signAndEncrypt (m_recipients, m_input, m_output, GpgME::Context::AlwaysTrust); if (result_pair.first.error() || result_pair.second.error()) { log_error ("%s:%s: Encrypt / Sign error %s %s.", SRCNAME, __func__, result_pair.first.error().asString(), result_pair.second.error().asString()); return -1; } if (result_pair.first.error().isCanceled() || result_pair.second.error().isCanceled()) { log_debug ("%s:%s: User cancled", SRCNAME, __func__); return -2; } } else if (m_encrypt) { const auto result = ctx->encrypt (m_recipients, m_input, m_output, GpgME::Context::AlwaysTrust); if (result.error()) { log_error ("%s:%s: Encryption error %s.", SRCNAME, __func__, result.error().asString()); return -1; } if (result.error().isCanceled()) { log_debug ("%s:%s: User cancled", SRCNAME, __func__); return -2; } } else if (m_sign) { const auto result = ctx->sign (m_input, m_output, m_inline ? GpgME::Clearsigned : GpgME::Detached); if (result.error()) { log_error ("%s:%s: Signing error %s.", SRCNAME, __func__, result.error().asString()); return -1; } if (result.error().isCanceled()) { log_debug ("%s:%s: User cancled", SRCNAME, __func__); return -2; } } else { // ??? log_error ("%s:%s: unreachable code reached.", SRCNAME, __func__); } log_debug ("%s:%s: Crypto done sucessfuly.", SRCNAME, __func__); m_crypto_success = true; return 0; } static int write_data (sink_t sink, GpgME::Data &data) { if (!sink || !sink->writefnc) { return -1; } char buf[4096]; size_t nread; data.seek (0, SEEK_SET); while ((nread = data.read (buf, 4096)) > 0) { sink->writefnc (sink, buf, nread); } return 0; } static int create_sign_attach (sink_t sink, protocol_t protocol, GpgME::Data &signature, GpgME::Data &signedData) { char boundary[BOUNDARYSIZE+1]; char top_header[BOUNDARYSIZE+200]; int rc = 0; /* Write the top header. */ generate_boundary (boundary); create_top_signing_header (top_header, sizeof top_header, protocol, 1, boundary, protocol == PROTOCOL_SMIME ? "sha1":"pgp-sha1"); if ((rc = write_string (sink, top_header))) { TRACEPOINT; return rc; } /* Write the boundary so that it is not included in the hashing. */ if ((rc = write_boundary (sink, boundary, 0))) { TRACEPOINT; return rc; } /* Write the signed mime structure */ if ((rc = write_data (sink, signedData))) { TRACEPOINT; return rc; } /* Write the signature attachment */ if ((rc = write_boundary (sink, boundary, 0))) { TRACEPOINT; return rc; } if (protocol == PROTOCOL_OPENPGP) { rc = write_string (sink, "Content-Type: application/pgp-signature\r\n"); } else { rc = write_string (sink, "Content-Transfer-Encoding: base64\r\n" "Content-Type: application/pkcs7-signature\r\n"); /* rc = write_string (sink, */ /* "Content-Type: application/x-pkcs7-signature\r\n" */ /* "\tname=\"smime.p7s\"\r\n" */ /* "Content-Transfer-Encoding: base64\r\n" */ /* "Content-Disposition: attachment;\r\n" */ /* "\tfilename=\"smime.p7s\"\r\n"); */ } if (rc) { TRACEPOINT; return rc; } if ((rc = write_string (sink, "\r\n"))) { TRACEPOINT; return rc; } // Write the signature data if ((rc = write_data (sink, signature))) { TRACEPOINT; return rc; } // Add an extra linefeed with should not harm. if ((rc = write_string (sink, "\r\n"))) { TRACEPOINT; return rc; } /* Write the final boundary. */ if ((rc = write_boundary (sink, boundary, 1))) { TRACEPOINT; return rc; } return rc; } static int create_encrypt_attach (sink_t sink, protocol_t protocol, GpgME::Data &encryptedData) { char boundary[BOUNDARYSIZE+1]; int rc = create_top_encryption_header (sink, protocol, boundary, false); // From here on use goto failure pattern. if (rc) { log_error ("%s:%s: Failed to create top header.", SRCNAME, __func__); return rc; } rc = write_data (sink, encryptedData); if (rc) { log_error ("%s:%s: Failed to create top header.", SRCNAME, __func__); return rc; } /* Write the final boundary (for OpenPGP) and finish the attachment. */ if (*boundary && (rc = write_boundary (sink, boundary, 1))) { log_error ("%s:%s: Failed to write boundary.", SRCNAME, __func__); } return rc; } int CryptController::update_mail_mapi () { log_debug ("%s:%s", SRCNAME, __func__); if (m_inline) { // Nothing to do for inline. log_debug ("%s:%s: Inline mail. No MAPI update.", SRCNAME, __func__); return 0; } LPMESSAGE message = get_oom_base_message (m_mail->item()); if (!message) { log_error ("%s:%s: Failed to obtain message.", SRCNAME, __func__); return -1; } mapi_attach_item_t *att_table = mapi_create_attach_table (message, 0); // Set up the sink object for our MSOXSMIME attachment. struct sink_s sinkmem; sink_t sink = &sinkmem; memset (sink, 0, sizeof *sink); sink->cb_data = &m_input; sink->writefnc = sink_data_write; LPATTACH attach = create_mapi_attachment (message, sink); if (!attach) { log_error ("%s:%s: Failed to create moss attach.", SRCNAME, __func__); gpgol_release (message); return -1; } protocol_t protocol = m_proto == GpgME::CMS ? PROTOCOL_SMIME : PROTOCOL_OPENPGP; int rc = 0; if (m_sign && m_encrypt) { // FIXME we need some doubling here for S/MIME. rc = create_encrypt_attach (sink, protocol, m_output); } else if (m_encrypt) { rc = create_encrypt_attach (sink, protocol, m_output); } else if (m_sign) { rc = create_sign_attach (sink, protocol, m_output, m_input); } // Close our attachment if (!rc) { rc = close_mapi_attachment (&attach, sink); } // Set message class etc. if (!rc) { rc = finalize_message (message, att_table, protocol, m_encrypt ? 1 : 0, false); } // only on error. if (rc) { cancel_mapi_attachment (&attach, sink); } // cleanup mapi_release_attach_table (att_table); gpgol_release (attach); gpgol_release (message); return rc; } std::string CryptController::get_inline_data () { std::string ret; if (!m_inline) { return ret; } m_output.seek (0, SEEK_SET); char buf[4096]; size_t nread; while ((nread = m_output.read (buf, 4096)) > 0) { ret += std::string (buf, nread); } return ret; }