diff --git a/src/common_indep.c b/src/common_indep.c index aebd10c..4a029a0 100644 --- a/src/common_indep.c +++ b/src/common_indep.c @@ -1,480 +1,481 @@ /* common_indep.c - Common, platform indepentent routines used by GpgOL * Copyright (C) 2005, 2007, 2008 g10 Code GmbH * Copyright (C) 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 . */ #include "common_indep.h" #ifdef HAVE_W32_SYSTEM #include #endif +#include #include #include /* The base-64 list used for base64 encoding. */ static unsigned char bintoasc[64+1] = ("ABCDEFGHIJKLMNOPQRSTUVWXYZ" "abcdefghijklmnopqrstuvwxyz" "0123456789+/"); /* The reverse base-64 list used for base-64 decoding. */ static unsigned char const asctobin[256] = { 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x3e, 0xff, 0xff, 0xff, 0x3f, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x3a, 0x3b, 0x3c, 0x3d, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f, 0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, 0x28, 0x29, 0x2a, 0x2b, 0x2c, 0x2d, 0x2e, 0x2f, 0x30, 0x31, 0x32, 0x33, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff }; void out_of_core (void) { #ifdef HAVE_W32_SYSTEM MessageBox (NULL, "Out of core!", "Fatal Error", MB_OK); #endif abort (); } void* _xmalloc (size_t n) { void *p = malloc (n); if (!p) out_of_core (); return p; } void* _xcalloc (size_t m, size_t n) { void *p = calloc (m, n); if (!p) out_of_core (); return p; } void * _xrealloc (void *a, size_t n) { void *p = realloc (a, n); if (!p) out_of_core (); return p; } char* _xstrdup (const char *s) { char *p = _xmalloc (strlen (s)+1); strcpy (p, s); return p; } wchar_t* _xwcsdup (const wchar_t *s) { wchar_t *p = wcsdup (s); if (!s) { out_of_core (); } return p; } void _xfree (void *p) { if (p) free (p); } /* Strip off leading and trailing white spaces from STRING. Returns STRING. */ char * trim_spaces (char *arg_string) { char *string = arg_string; char *p, *mark; /* Find first non space character. */ for (p = string; *p && isascii (*p) && isspace (*p) ; p++ ) ; /* Move characters. */ for (mark = NULL; (*string = *p); string++, p++ ) { if (isascii (*p) && isspace (*p)) { if (!mark) mark = string; } else mark = NULL ; } if (mark) *mark = 0; return arg_string; } /* Assume STRING is a Latin-1 encoded and convert it to utf-8. Returns a newly malloced UTF-8 string. */ char * latin1_to_utf8 (const char *string) { const char *s; char *buffer, *p; size_t n; for (s=string, n=0; *s; s++) { n++; if (*s & 0x80) n++; } buffer = xmalloc (n + 1); for (s=string, p=buffer; *s; s++) { if (*s & 0x80) { *p++ = 0xc0 | ((*s >> 6) & 3); *p++ = 0x80 | (*s & 0x3f); } else *p++ = *s; } *p = 0; return buffer; } /* This function is similar to strncpy(). However it won't copy more than N - 1 characters and makes sure that a Nul is appended. With N given as 0, nothing will happen. With DEST given as NULL, memory will be allocated using xmalloc (i.e. if it runs out of core the function terminates). Returns DEST or a pointer to the allocated memory. */ char * mem2str (char *dest, const void *src, size_t n) { char *d; const char *s; if (n) { if (!dest) dest = xmalloc (n); d = dest; s = src ; for (n--; n && *s; n--) *d++ = *s++; *d = 0; } else if (!dest) { dest = xmalloc (1); *dest = 0; } return dest; } /* Strip off trailing white spaces from STRING. Returns STRING. */ char * trim_trailing_spaces (char *string) { char *p, *mark; for (mark=NULL, p=string; *p; p++) { if (strchr (" \t\r\n", *p )) { if (!mark) mark = p; } else mark = NULL; } if (mark) *mark = 0; return string; } /* Do in-place decoding of quoted-printable data of LENGTH in BUFFER. Returns the new length of the buffer and stores true at R_SLBRK if the line ended with a soft line break; false is stored if not. This fucntion asssumes that a complete line is passed in buffer. */ size_t qp_decode (char *buffer, size_t length, int *r_slbrk) { char *d, *s; if (r_slbrk) *r_slbrk = 0; /* Fixme: We should remove trailing white space first. */ for (s=d=buffer; length; length--) if (*s == '=') { if (length > 2 && hexdigitp (s+1) && hexdigitp (s+2)) { s++; *(unsigned char*)d++ = xtoi_2 (s); s += 2; length -= 2; } else if (length > 2 && s[1] == '\r' && s[2] == '\n') { /* Soft line break. */ s += 3; length -= 2; if (r_slbrk && length == 1) *r_slbrk = 1; } else if (length > 1 && s[1] == '\n') { /* Soft line break with only a Unix line terminator. */ s += 2; length -= 1; if (r_slbrk && length == 1) *r_slbrk = 1; } else if (length == 1) { /* Soft line break at the end of the line. */ s += 1; if (r_slbrk) *r_slbrk = 1; } else *d++ = *s++; } else *d++ = *s++; return d - buffer; } /* Return the a quoted printable encoded version of the input string. If outlen is not null the size of the quoted printable string is returned. String will be malloced and zero terminated. Aborts if the output is more then three times the size of the input. This is only basic and does not handle mutliline data. */ char * qp_encode (const char *input, size_t inlen, size_t *r_outlen) { size_t max_len = inlen * 3 +1; char *outbuf = xmalloc (max_len); size_t outlen = 0; const unsigned char *p; memset (outbuf, 0, max_len); for (p = input; inlen; p++, inlen--) { if (*p >= '!' && *p <= '~' && *p != '=') { outbuf[outlen++] = *p; } else if (*p == ' ') { /* Outlook does it this way */ outbuf[outlen++] = '_'; } else { outbuf[outlen++] = '='; outbuf[outlen++] = tohex ((*p>>4)&15); outbuf[outlen++] = tohex (*p&15); } if (outlen == max_len -1) { log_error ("Quoted printable too long. Bug."); r_outlen = NULL; xfree (outbuf); return NULL; } } if (r_outlen) *r_outlen = outlen; return outbuf; } /* Initialize the Base 64 decoder state. */ void b64_init (b64_state_t *state) { state->idx = 0; state->val = 0; state->stop_seen = 0; state->invalid_encoding = 0; } /* Do in-place decoding of base-64 data of LENGTH in BUFFER. Returns the new length of the buffer. STATE is required to return errors and to maintain the state of the decoder. */ size_t b64_decode (b64_state_t *state, char *buffer, size_t length) { int idx = state->idx; unsigned char val = state->val; int c; char *d, *s; if (state->stop_seen) return 0; for (s=d=buffer; length; length--, s++) { if (*s == '\n' || *s == ' ' || *s == '\r' || *s == '\t') continue; if (*s == '=') { /* Pad character: stop */ if (idx == 1) *d++ = val; state->stop_seen = 1; break; } if ((c = asctobin[*(unsigned char *)s]) == 255) { if (!state->invalid_encoding) log_debug ("%s: invalid base64 character %02X at pos %d skipped\n", __func__, *(unsigned char*)s, (int)(s-buffer)); state->invalid_encoding = 1; continue; } switch (idx) { case 0: val = c << 2; break; case 1: val |= (c>>4)&3; *d++ = val; val = (c<<4)&0xf0; break; case 2: val |= (c>>2)&15; *d++ = val; val = (c<<6)&0xc0; break; case 3: val |= c&0x3f; *d++ = val; break; } idx = (idx+1) % 4; } state->idx = idx; state->val = val; return d - buffer; } /* Base 64 encode the input. If input is null returns NULL otherwise a pointer to the malloced encoded string. */ char * b64_encode (const char *input, size_t length) { size_t out_len = 4 * ((length + 2) / 3); char *ret; int i, j; if (!length || !input) { return NULL; } ret = xmalloc (out_len); memset (ret, 0, out_len); for (i = 0, j = 0; i < length;) { unsigned int a = i < length ? (unsigned char)input[i++] : 0; unsigned int b = i < length ? (unsigned char)input[i++] : 0; unsigned int c = i < length ? (unsigned char)input[i++] : 0; unsigned int triple = (a << 0x10) + (b << 0x08) + c; ret[j++] = bintoasc[(triple >> 3 * 6) & 0x3F]; ret[j++] = bintoasc[(triple >> 2 * 6) & 0x3F]; ret[j++] = bintoasc[(triple >> 1 * 6) & 0x3F]; ret[j++] = bintoasc[(triple >> 0 * 6) & 0x3F]; } if (length % 3) { ret [j - 1] = '='; } if (length % 3 == 1) { ret [j - 2] = '='; } return ret; } /* Create a boundary. Note that mimemaker.c knows about the structure of the boundary (i.e. that it starts with "=-=") so that it can protect against accidently used boundaries within the content. */ char * generate_boundary (char *buffer) { char *p = buffer; int i; #if RAND_MAX < (64*2*BOUNDARYSIZE) #error RAND_MAX is way too small #endif *p++ = '='; *p++ = '-'; *p++ = '='; for (i=0; i < BOUNDARYSIZE-6; i++) *p++ = bintoasc[rand () % 64]; *p++ = '='; *p++ = '-'; *p++ = '='; *p = 0; return buffer; } diff --git a/src/cpphelp.cpp b/src/cpphelp.cpp index 30daa9e..434a368 100644 --- a/src/cpphelp.cpp +++ b/src/cpphelp.cpp @@ -1,316 +1,329 @@ /* @file cpphelp.h * @brief Common cpp helper stuff * * 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" +#ifdef HAVE_CONFIG_H +# include "config.h" +#endif #include "cpphelp.h" #include #include #include #include -#include "common.h" +#include "common_indep.h" #include #include #include -#include +#ifdef HAVE_W32_SYSTEM +# include "common.h" +# include +#else +#include "common_indep.h" +#endif void release_cArray (char **carray) { if (carray) { for (int idx = 0; carray[idx]; idx++) { xfree (carray[idx]); } xfree (carray); } } void rtrim(std::string &s) { s.erase (std::find_if (s.rbegin(), s.rend(), [] (int ch) { return !std::isspace(ch); }).base(), s.end()); } void ltrim(std::string &s) { s.erase (s.begin(), std::find_if (s.begin(), s.end(), [] (int ch) { return !std::isspace(ch); })); } void trim(std::string &s) { ltrim (s); rtrim (s); } void join(const std::vector& v, const char *c, std::string& s) { s.clear(); for (auto p = v.begin(); p != v.end(); ++p) { s += *p; if (p != v.end() - 1) { s += c; } } } char ** vector_to_cArray(const std::vector &vec) { char ** ret = (char**) xmalloc (sizeof (char*) * (vec.size() + 1)); for (size_t i = 0; i < vec.size(); i++) { ret[i] = xstrdup (vec[i].c_str()); } ret[vec.size()] = NULL; return ret; } std::vector cArray_to_vector(const char **cArray) { std::vector ret; if (!cArray) { return ret; } for (int i = 0; cArray[i]; i++) { ret.push_back (std::string (cArray[i])); } return ret; } bool in_de_vs_mode() { /* We cache the values only once. A change requires restart. This is because checking this is very expensive as gpgconf spawns each process to query the settings. */ static bool checked; static bool vs_mode; if (checked) { return vs_mode; } checked = true; GpgME::Error err; const auto components = GpgME::Configuration::Component::load (err); log_debug ("%s:%s: Checking for de-vs mode.", SRCNAME, __func__); if (err) { log_error ("%s:%s: Failed to get gpgconf components: %s", SRCNAME, __func__, err.asString ()); vs_mode = false; return vs_mode; } for (const auto &component: components) { if (component.name () && !strcmp (component.name (), "gpg")) { for (const auto &option: component.options ()) { if (option.name () && !strcmp (option.name (), "compliance") && option.currentValue ().stringValue () && +#ifdef HAVE_W32_SYSTEM !stricmp (option.currentValue ().stringValue (), "de-vs")) +#else + !strcasecmp (option.currentValue ().stringValue (), "de-vs")) +#endif { log_debug ("%s:%s: Detected de-vs mode", SRCNAME, __func__); vs_mode = true; return vs_mode; } } vs_mode = false; return vs_mode; } } vs_mode = false; return false; } +#ifdef HAVE_W32_SYSTEM std::map get_registry_subkeys (const char *path) { HKEY theKey; std::map ret; std::string regPath = GPGOL_REGPATH; regPath += "\\"; regPath += path; if (RegOpenKeyEx (HKEY_CURRENT_USER, regPath.c_str (), 0, KEY_ENUMERATE_SUB_KEYS | KEY_READ, &theKey) != ERROR_SUCCESS) { TRACEPOINT; return ret; } DWORD values = 0, maxValueName = 0, maxValueLen = 0; DWORD err = RegQueryInfoKey (theKey, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, &values, &maxValueName, &maxValueLen, nullptr, nullptr); if (err != ERROR_SUCCESS) { TRACEPOINT; RegCloseKey (theKey); return ret; } /* Add space for NULL */ maxValueName++; maxValueLen++; char name[maxValueName + 1]; char value[maxValueLen + 1]; for (int i = 0; i < values; i++) { DWORD nameLen = maxValueName; err = RegEnumValue (theKey, i, name, &nameLen, nullptr, nullptr, nullptr, nullptr); if (err != ERROR_SUCCESS) { TRACEPOINT; continue; } DWORD type; DWORD valueLen = maxValueLen; err = RegQueryValueEx (theKey, name, NULL, &type, (BYTE*)value, &valueLen); if (err != ERROR_SUCCESS) { TRACEPOINT; continue; } if (type != REG_SZ) { TRACEPOINT; continue; } ret.insert (std::make_pair (std::string (name, nameLen), std::string (value, valueLen))); } RegCloseKey (theKey); return ret; } +#endif template void internal_split (const std::string &s, char delim, Out result) { std::stringstream ss(s); std::string item; while (std::getline (ss, item, delim)) { *(result++) = item; } } std::vector gpgol_split (const std::string &s, char delim) { std::vector elems; internal_split (s, delim, std::back_inserter (elems)); return elems; } std::string string_to_hex(const std::string& input) { static const char* const lut = "0123456789ABCDEF"; size_t len = input.length(); std::string output; output.reserve (3 * len + (len * 3 / 26)); for (size_t i = 0; i < len; ++i) { const unsigned char c = input[i]; output.push_back (lut[c >> 4]); output.push_back (lut[c & 15]); output.push_back (' '); if (i % 26 == 0) { output.push_back ('\n'); } } return output; } bool is_binary (const std::string &input) { for (int i = 0; i < input.size() - 1; ++i) { const unsigned char c = input[i]; if (c < 32 && c != 0x0d && c != 0x0a) { return true; } } return false; } const char * to_cstr (const GpgME::Protocol &prot) { return prot == GpgME::CMS ? "S/MIME" : prot == GpgME::OpenPGP ? "OpenPGP" : "Unknown Protocol"; } diff --git a/src/cpphelp.h b/src/cpphelp.h index cd56349..de10dee 100644 --- a/src/cpphelp.h +++ b/src/cpphelp.h @@ -1,64 +1,66 @@ #ifndef CPPHELP_H #define CPPHELP_H /* @file cpphelp.h * @brief Common cpp helper stuff * * 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 #include #include #include /* Stuff that should be in common but is c++ so it does not fit in there. */ /* Release a null terminated char* array */ void release_cArray (char **carray); /* Trim whitespace from a string. */ void rtrim (std::string &s); void ltrim (std::string &s); void trim (std::string &s); /* Join a string vector */ void join(const std::vector& v, const char *c, std::string& s); /* Convert a string vector to a null terminated char array */ char **vector_to_cArray (const std::vector &vec); std::vector cArray_to_vector (const char **cArray); /* Check if we are in de_vs mode. */ bool in_de_vs_mode (); +#ifdef HAVE_W32_SYSTEM /* Get a map of all subkey value pairs in a registry key */ std::map get_registry_subkeys (const char *path); +#endif std::vector gpgol_split (const std::string &s, char delim); /* Convert a string to a hex representation */ std::string string_to_hex (const std::string& input); /* Check if a string contains a char < 32 */ bool is_binary (const std::string &input); /* Return a string repr of the GpgME Protocol */ const char *to_cstr (const GpgME::Protocol &prot); #endif // CPPHELP_H diff --git a/src/debug.cpp b/src/debug.cpp index fcc0450..e7981d3 100644 --- a/src/debug.cpp +++ b/src/debug.cpp @@ -1,351 +1,349 @@ /* debug.cpp - Debugging / Log helpers for GpgOL * Copyright (C) 2018 by 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 "common_indep.h" #include #include #include /* The malloced name of the logfile and the logging stream. If LOGFILE is NULL, no logging is done. */ static char *logfile; static FILE *logfp; GPGRT_LOCK_DEFINE (log_lock); /* Acquire the mutex for logging. Returns 0 on success. */ static int lock_log (void) { gpgrt_lock_lock (&log_lock); return 0; } /* Release the mutex for logging. No error return is done because this is a fatal error anyway and we have no means for proper notification. */ static void unlock_log (void) { gpgrt_lock_unlock (&log_lock); } const char * get_log_file (void) { return logfile? logfile : ""; } void set_log_file (const char *name) { -#ifdef HAVE_W32_SYSTEM if (!lock_log ()) { -#endif if (logfp) { fclose (logfp); logfp = NULL; } free (logfile); if (!name || *name == '\"' || !*name) logfile = NULL; else logfile = strdup (name); -#ifdef HAVE_W32_SYSTEM unlock_log (); } -#endif } static void do_log (const char *fmt, va_list a, int w32err, int err, const void *buf, size_t buflen) { if (!logfile) return; #ifdef HAVE_W32_SYSTEM if (!opt.enable_debug) return; if (lock_log ()) { OutputDebugStringA ("GpgOL: Failed to log."); return; } #endif if (!strcmp (logfile, "stdout")) { logfp = stdout; } else if (!strcmp (logfile, "stderr")) { logfp = stderr; } if (!logfp) logfp = fopen (logfile, "a+"); #ifdef HAVE_W32_SYSTEM if (!logfp) { unlock_log (); return; } char time_str[9]; SYSTEMTIME utc_time; GetSystemTime (&utc_time); if (GetTimeFormatA (LOCALE_INVARIANT, TIME_FORCE24HOURFORMAT | LOCALE_USE_CP_ACP, &utc_time, "HH:mm:ss", time_str, 9)) { fprintf (logfp, "%s/%lu/", time_str, (unsigned long)GetCurrentThreadId ()); } else { fprintf (logfp, "unknown/%lu/", (unsigned long)GetCurrentThreadId ()); } #endif if (err == 1) fputs ("ERROR/", logfp); vfprintf (logfp, fmt, a); #ifdef HAVE_W32_SYSTEM if (w32err) { char tmpbuf[256]; FormatMessage (FORMAT_MESSAGE_FROM_SYSTEM, NULL, w32err, MAKELANGID (LANG_NEUTRAL, SUBLANG_DEFAULT), tmpbuf, sizeof (tmpbuf)-1, NULL); fputs (": ", logfp); if (*tmpbuf && tmpbuf[strlen (tmpbuf)-1] == '\n') tmpbuf[strlen (tmpbuf)-1] = 0; if (*tmpbuf && tmpbuf[strlen (tmpbuf)-1] == '\r') tmpbuf[strlen (tmpbuf)-1] = 0; fprintf (logfp, "%s (%d)", tmpbuf, w32err); } +#else + (void) w32err; #endif if (buf) { const unsigned char *p = (const unsigned char*)buf; for ( ; buflen; buflen--, p++) fprintf (logfp, "%02X", *p); putc ('\n', logfp); } else if ( *fmt && fmt[strlen (fmt) - 1] != '\n') putc ('\n', logfp); fflush (logfp); #ifdef HAVE_W32_SYSTEM unlock_log (); #endif } const char * log_srcname (const char *file) { const char *s = strrchr (file, '/'); return s? s+1:file; } void log_debug (const char *fmt, ...) { va_list a; va_start (a, fmt); do_log (fmt, a, 0, 0, NULL, 0); va_end (a); } void log_error (const char *fmt, ...) { va_list a; va_start (a, fmt); do_log (fmt, a, 0, 1, NULL, 0); va_end (a); } void log_vdebug (const char *fmt, va_list a) { do_log (fmt, a, 0, 0, NULL, 0); } void log_hexdump (const void *buf, size_t buflen, const char *fmt, ...) { va_list a; va_start (a, fmt); do_log (fmt, a, 0, 2, buf, buflen); va_end (a); } #ifdef HAVE_W32_SYSTEM void log_debug_w32 (int w32err, const char *fmt, ...) { va_list a; if (w32err == -1) w32err = GetLastError (); va_start (a, fmt); do_log (fmt, a, w32err, 0, NULL, 0); va_end (a); } void log_error_w32 (int w32err, const char *fmt, ...) { va_list a; if (w32err == -1) w32err = GetLastError (); va_start (a, fmt); do_log (fmt, a, w32err, 1, NULL, 0); va_end (a); } static void do_log_window_info (HWND window, int level) { char buf[1024+1]; char name[200]; int nname; char *pname; DWORD pid; if (!window) return; GetWindowThreadProcessId (window, &pid); if (pid != GetCurrentProcessId ()) return; memset (buf, 0, sizeof (buf)); GetWindowText (window, buf, sizeof (buf)-1); nname = GetClassName (window, name, sizeof (name)-1); if (nname) pname = name; else pname = NULL; if (level == -1) log_debug (" parent=%p/%lu (%s) `%s'", window, (unsigned long)pid, pname? pname:"", buf); else log_debug (" %*shwnd=%p/%lu (%s) `%s'", level*2, "", window, (unsigned long)pid, pname? pname:"", buf); } /* Helper to log_window_hierarchy. */ static HWND do_log_window_hierarchy (HWND parent, int level) { HWND child; child = GetWindow (parent, GW_CHILD); while (child) { do_log_window_info (child, level); do_log_window_hierarchy (child, level+1); child = GetNextWindow (child, GW_HWNDNEXT); } return NULL; } /* Print a debug message using the format string FMT followed by the window hierarchy of WINDOW. */ void log_window_hierarchy (HWND window, const char *fmt, ...) { va_list a; va_start (a, fmt); do_log (fmt, a, 0, 0, NULL, 0); va_end (a); if (window) { do_log_window_info (window, -1); do_log_window_hierarchy (window, 0); } } #endif GPGRT_LOCK_DEFINE (anon_str_lock); /* Weel ok this survives unload but we don't want races and it makes a bit of sense to keep the strings constant. */ static std::unordered_map str_map; const char *anonstr (const char *data) { static int64_t cnt; if (opt.enable_debug & DBG_DATA) { return data; } if (!data) { return "gpgol_str_null"; } if (!strlen (data)) { return "gpgol_str_empty"; } gpgol_lock (&anon_str_lock); const std::string strData (data); auto it = str_map.find (strData); if (it == str_map.end ()) { const auto anon = std::string ("gpgol_string_") + std::to_string (++cnt); str_map.insert (std::make_pair (strData, anon)); it = str_map.find (strData); } // As the data is saved in our map we can return // the c_str as it won't be touched as const. gpgol_unlock (&anon_str_lock); return it->second.c_str(); } diff --git a/src/parsecontroller.cpp b/src/parsecontroller.cpp index 9c925f8..4a72a65 100644 --- a/src/parsecontroller.cpp +++ b/src/parsecontroller.cpp @@ -1,655 +1,657 @@ /* @file parsecontroller.cpp * @brief Parse a mail and decrypt / verify accordingly * * Copyright (C) 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 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 "parsecontroller.h" #include "attachment.h" #include "mimedataprovider.h" #include "keycache.h" #include #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 const char decrypt_template_html[] = { "" "" "" "" "" "" "
" "

%s %s

" "
" "
%s" "
"}; const char decrypt_template[] = {"%s %s\n\n%s"}; using namespace GpgME; static bool expect_no_headers (msgtype_t type) { TSTART; TRETURN type != MSGTYPE_GPGOL_MULTIPART_SIGNED; } static bool expect_no_mime (msgtype_t type) { TSTART; TRETURN type == MSGTYPE_GPGOL_PGP_MESSAGE || type == MSGTYPE_GPGOL_CLEAR_SIGNED; } #ifdef BUILD_TESTS static void get_and_print_key_test (const char *fingerprint, GpgME::Protocol proto) { if (!fingerprint) { STRANGEPOINT; return; } auto ctx = std::unique_ptr (GpgME::Context::createForProtocol (proto)); if (!ctx) { STRANGEPOINT; return; } ctx->setKeyListMode (GpgME::KeyListMode::Local | GpgME::KeyListMode::Signatures | GpgME::KeyListMode::Validate | GpgME::KeyListMode::WithTofu); GpgME::Error err; const auto newKey = ctx->key (fingerprint, err, false); std::stringstream ss; ss << newKey; log_debug ("Key: %s", ss.str().c_str()); return; } #endif #ifdef HAVE_W32_SYSTEM ParseController::ParseController(LPSTREAM instream, msgtype_t type): m_inputprovider (new MimeDataProvider(instream, expect_no_headers(type))), m_outputprovider (new MimeDataProvider(expect_no_mime(type))), m_type (type), m_block_html (false) { TSTART; memdbg_ctor ("ParseController"); log_data ("%s:%s: Creating parser for stream: %p of type %i" " expect no headers: %i expect no mime: %i", SRCNAME, __func__, instream, type, expect_no_headers (type), expect_no_mime (type)); TRETURN; } #endif ParseController::ParseController(FILE *instream, msgtype_t type): m_inputprovider (new MimeDataProvider(instream, expect_no_headers(type))), m_outputprovider (new MimeDataProvider(expect_no_mime(type))), m_type (type), m_block_html (false) { TSTART; memdbg_ctor ("ParseController"); log_data ("%s:%s: Creating parser for stream: %p of type %i", SRCNAME, __func__, instream, type); TRETURN; } ParseController::~ParseController() { TSTART; log_debug ("%s:%s", SRCNAME, __func__); memdbg_dtor ("ParseController"); delete m_inputprovider; delete m_outputprovider; TRETURN; } static void operation_for_type(msgtype_t type, bool *decrypt, bool *verify) { *decrypt = false; *verify = false; switch (type) { case MSGTYPE_GPGOL_MULTIPART_ENCRYPTED: case MSGTYPE_GPGOL_PGP_MESSAGE: *decrypt = true; break; case MSGTYPE_GPGOL_MULTIPART_SIGNED: case MSGTYPE_GPGOL_CLEAR_SIGNED: *verify = true; break; case MSGTYPE_GPGOL_OPAQUE_SIGNED: *verify = true; break; case MSGTYPE_GPGOL_OPAQUE_ENCRYPTED: *decrypt = true; break; default: log_error ("%s:%s: Unknown data type: %i", SRCNAME, __func__, type); } } static bool is_smime (Data &data) { TSTART; data.seek (0, SEEK_SET); auto id = data.type(); data.seek (0, SEEK_SET); TRETURN id == Data::CMSSigned || id == Data::CMSEncrypted; } static std::string format_recipients(GpgME::DecryptionResult result, Protocol protocol) { TSTART; std::string msg; for (const auto recipient: result.recipients()) { auto ctx = Context::createForProtocol(protocol); Error e; if (!ctx) { /* Can't happen */ TRACEPOINT; continue; } const auto key = ctx->key(recipient.keyID(), e, false); delete ctx; if (!key.isNull() && key.numUserIDs() && !e) { msg += std::string("
") + key.userIDs()[0].id() + " (0x" + recipient.keyID() + ")"; continue; } msg += std::string("
") + _("Unknown Key:") + " 0x" + recipient.keyID(); } TRETURN msg; } static std::string format_error(GpgME::DecryptionResult result, Protocol protocol) { TSTART; char *buf; bool no_sec = false; std::string msg; if (result.error ().isCanceled () || result.error ().code () == GPG_ERR_NO_SECKEY) { msg = _("Decryption canceled or timed out."); } if (result.error ().code () == GPG_ERR_DECRYPT_FAILED || result.error ().code () == GPG_ERR_NO_SECKEY) { no_sec = true; for (const auto &recipient: result.recipients ()) { no_sec &= (recipient.status ().code () == GPG_ERR_NO_SECKEY); } } if (no_sec) { msg = _("No secret key found to decrypt the message. " "It is encrypted to the following keys:"); msg += format_recipients (result, protocol); } else { msg = _("Could not decrypt the data: "); if (result.isNull ()) { msg += _("Failed to parse the mail."); } else if (result.isLegacyCipherNoMDC()) { msg += _("Data is not integrity protected. " "Decrypting it could be a security problem. (no MDC)"); } else { msg += result.error().asString(); } } if (gpgrt_asprintf (&buf, opt.prefer_html ? decrypt_template_html : decrypt_template, protocol == OpenPGP ? "OpenPGP" : "S/MIME", _("Encrypted message (decryption not possible)"), msg.c_str()) == -1) { log_error ("%s:%s:Failed to Format error.", SRCNAME, __func__); TRETURN "Failed to Format error."; } msg = buf; memdbg_alloc (buf); xfree (buf); TRETURN msg; } void ParseController::setSender(const std::string &sender) { TSTART; m_sender = sender; TRETURN; } static bool is_valid_chksum(const GpgME::Signature &sig) { TSTART; const auto sum = sig.summary(); static unsigned int valid_mask = (unsigned int) ( GpgME::Signature::Valid | GpgME::Signature::Green | GpgME::Signature::KeyRevoked | GpgME::Signature::KeyExpired | GpgME::Signature::SigExpired | GpgME::Signature::CrlMissing | GpgME::Signature::CrlTooOld | GpgME::Signature::TofuConflict ); TRETURN sum & valid_mask; } /* Note on stability: Experiments have shown that we can have a crash if parse Returns at time that is not good for the state of Outlook. This happend in my test instance after a delay of > 1s < 3s with a < 1% chance :-/ So if you have really really bad luck this might still crash although it usually should be either much quicker or much slower (slower e.g. when pinentry is requrired). */ void ParseController::parse() { TSTART; // Wrap the input stream in an attachment / GpgME Data Protocol protocol; bool decrypt, verify; Data input (m_inputprovider); auto inputType = input.type (); if (inputType == Data::Type::PGPSigned) { verify = true; decrypt = false; } else { operation_for_type (m_type, &decrypt, &verify); } if ((m_inputprovider->signature() && is_smime (*m_inputprovider->signature())) || is_smime (input)) { protocol = Protocol::CMS; } else { protocol = Protocol::OpenPGP; } auto ctx = std::unique_ptr (Context::createForProtocol (protocol)); if (!ctx) { log_error ("%s:%s:Failed to create context. Installation broken.", SRCNAME, __func__); char *buf; const char *proto = protocol == OpenPGP ? "OpenPGP" : "S/MIME"; if (gpgrt_asprintf (&buf, opt.prefer_html ? decrypt_template_html : decrypt_template, proto, _("Encrypted message (decryption not possible)"), _("Failed to find GnuPG please ensure that GnuPG or " "Gpg4win is properly installed.")) == -1) { log_error ("%s:%s:Failed format error.", SRCNAME, __func__); /* Should never happen */ m_error = std::string("Bad installation"); } memdbg_alloc (buf); m_error = buf; xfree (buf); TRETURN; } /* Maybe a different option for this ? */ if (opt.autoretrieve) { ctx->setFlag("auto-key-retrieve", "1"); } ctx->setArmor(true); if (!m_sender.empty()) { ctx->setSender(m_sender.c_str()); } Data output (m_outputprovider); log_debug ("%s:%s:%p decrypt: %i verify: %i with protocol: %s sender: %s type: %i", SRCNAME, __func__, this, decrypt, verify, protocol == OpenPGP ? "OpenPGP" : protocol == CMS ? "CMS" : "Unknown", m_sender.empty() ? "none" : anonstr (m_sender.c_str()), inputType); if (decrypt) { input.seek (0, SEEK_SET); TRACEPOINT; auto combined_result = ctx->decryptAndVerify(input, output); log_debug ("%s:%s:%p decrypt / verify done.", SRCNAME, __func__, this); m_decrypt_result = combined_result.first; m_verify_result = combined_result.second; if ((!m_decrypt_result.error () && m_verify_result.signatures ().empty() && m_outputprovider->signature ()) || is_smime (output) || output.type() == Data::Type::PGPSigned) { TRACEPOINT; /* There is a signature in the output. So we have to verify it now as an extra step. */ input = Data (m_outputprovider); delete m_inputprovider; m_inputprovider = m_outputprovider; m_outputprovider = new MimeDataProvider(); output = Data(m_outputprovider); verify = true; TRACEPOINT; } else { verify = false; } TRACEPOINT; if (m_decrypt_result.error () || m_decrypt_result.isNull () || m_decrypt_result.error ().isCanceled ()) { m_error = format_error (m_decrypt_result, protocol); } } if (verify) { TRACEPOINT; GpgME::Data *sig = m_inputprovider->signature(); input.seek (0, SEEK_SET); if (sig) { sig->seek (0, SEEK_SET); TRACEPOINT; m_verify_result = ctx->verifyDetachedSignature(*sig, input); log_debug ("%s:%s:%p verify done.", SRCNAME, __func__, this); /* Copy the input to output to do a mime parsing. */ char buf[4096]; input.seek (0, SEEK_SET); output.seek (0, SEEK_SET); size_t nread; while ((nread = input.read (buf, 4096)) > 0) { output.write (buf, nread); } } else { TRACEPOINT; m_verify_result = ctx->verifyOpaqueSignature(input, output); TRACEPOINT; const auto sigs = m_verify_result.signatures(); bool allBad = sigs.size(); for (const auto s :sigs) { if (!(s.summary() & Signature::Red)) { allBad = false; break; } } #ifdef HAVE_W32_SYSTEM if (allBad) { log_debug ("%s:%s:%p inline verify error trying native to utf8.", SRCNAME, __func__, this); /* The proper solution would be to take the encoding from the mail / headers. Then convert the wchar body to that encoding. Verify, and convert it after verifcation to UTF-8 which the rest of the code expects. Or native_body from native ACP to InternetCodepage, then verify and convert the output back to utf8 as the rest expects. But as this is clearsigned and we don't really want that. Meh. */ char *utf8 = native_to_utf8 (input.toString().c_str()); if (utf8) { // Try again after conversion. ctx = std::unique_ptr (Context::createForProtocol (protocol)); ctx->setArmor (true); if (!m_sender.empty()) { ctx->setSender(m_sender.c_str()); } input = Data (utf8, strlen (utf8)); xfree (utf8); // Use a fresh output auto provider = new MimeDataProvider (true); // Warning: The dtor of the Data object touches // the provider. So we have to delete it after // the assignment. output = Data (provider); delete m_outputprovider; m_outputprovider = provider; // Try again m_verify_result = ctx->verifyOpaqueSignature(input, output); } } +#else +(void)allBad; #endif } } log_debug ("%s:%s:%p: decrypt err: %i verify err: %i", SRCNAME, __func__, this, m_decrypt_result.error().code(), m_verify_result.error().code()); bool has_valid_encrypted_checksum = false; /* Ensure that the Keys for the signatures are available and if it has a valid encrypted checksum. */ for (const auto sig: m_verify_result.signatures()) { TRACEPOINT; has_valid_encrypted_checksum = is_valid_chksum (sig); #ifndef BUILD_TESTS KeyCache::instance ()->update (sig.fingerprint (), protocol); #endif TRACEPOINT; } if (protocol == Protocol::CMS && decrypt && !m_decrypt_result.error() && !has_valid_encrypted_checksum) { log_debug ("%s:%s:%p Encrypted S/MIME without checksum. Block HTML.", SRCNAME, __func__, this); m_block_html = true; } if (opt.enable_debug & DBG_DATA) { std::stringstream ss; TRACEPOINT; ss << m_decrypt_result << '\n' << m_verify_result; for (const auto sig: m_verify_result.signatures()) { const auto key = sig.key(); if (key.isNull()) { #ifndef BUILD_TESTS ss << '\n' << "Cached key:\n" << KeyCache::instance()->getByFpr( sig.fingerprint(), false); #else get_and_print_key_test (sig.fingerprint (), protocol); #endif } else { ss << '\n' << key; } } log_debug ("Decrypt / Verify result: %s", ss.str().c_str()); } else { log_debug ("%s:%s:%p Decrypt / verify done errs: %i / %i numsigs: %i.", SRCNAME, __func__, this, m_decrypt_result.error().code(), m_verify_result.error().code(), m_verify_result.numSignatures()); } TRACEPOINT; if (m_outputprovider) { m_outputprovider->finalize (); } TRETURN; } const std::string ParseController::get_html_body () const { TSTART; if (m_outputprovider) { TRETURN m_outputprovider->get_html_body (); } else { TRETURN std::string(); } } const std::string ParseController::get_body () const { TSTART; if (m_outputprovider) { TRETURN m_outputprovider->get_body (); } else { TRETURN std::string(); } } const std::string ParseController::get_body_charset() const { TSTART; if (m_outputprovider) { TRETURN m_outputprovider->get_body_charset(); } else { TRETURN std::string(); } } const std::string ParseController::get_html_charset() const { TSTART; if (m_outputprovider) { TRETURN m_outputprovider->get_html_charset(); } else { TRETURN std::string(); } } std::vector > ParseController::get_attachments() const { TSTART; if (m_outputprovider) { TRETURN m_outputprovider->get_attachments(); } else { TRETURN std::vector >(); } } diff --git a/tests/Makefile.am b/tests/Makefile.am index 42ca92e..dfb96b0 100644 --- a/tests/Makefile.am +++ b/tests/Makefile.am @@ -1,66 +1,76 @@ # Makefile.am - Makefile for GpgOL tests. # Copyright (C) 2016 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 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 . ## Process this file with automake to produce Makefile.in GPG = gpg if HAVE_W32_SYSTEM TESTS = t-parser endif AM_LDFLAGS = @GPGME_LIBS@ -lgpgmepp AM_CFLAGS = -I$(top_srcdir)/src $(GPGME_CFLAGS) $(LIBASSUAN_CFLAGS) -DBUILD_TESTS +if !HAVE_W32_SYSTEM +AM_CXXFLAGS = -I$(top_srcdir)/src $(GPGME_CFLAGS) $(GPGME_CFLAGS)/gpgme++ \ + $(LIBASSUAN_CFLAGS) -std=c++11 -D_FILE_OFFSET_BITS=64 \ + -DBUILD_TESTS -DDATADIR=\"$(abs_srcdir)/data\" \ + -DGPGHOMEDIR=\"$(abs_srcdir)/gnupg_home\" +else AM_CXXFLAGS = -I$(top_srcdir)/src $(GPGME_CFLAGS) $(GPGME_CFLAGS)/gpgme++ \ $(LIBASSUAN_CFLAGS) -std=c++11 -D_FILE_OFFSET_BITS=64 \ -DBUILD_TESTS -LDADD = ../src/gpgol.la @GPG_ERROR_LIBS@ +endif +LDADD = @GPG_ERROR_LIBS@ + if HAVE_W32_SYSTEM run_parser_LDADD = \ -L ../src -lgpgmepp -lgpgme -lassuan -lgpg-error \ -lmapi32 -lshell32 -lgdi32 -lcomdlg32 \ -lole32 -loleaut32 -lws2_32 -ladvapi32 \ -luuid -lgdiplus -lrpcrt4 endif parser_SRC= ../src/parsecontroller.cpp \ ../src/parsecontroller.h \ ../src/attachment.cpp ../src/attachment.h \ ../src/mimedataprovider.h ../src/mimedataprovider.cpp \ ../src/rfc822parse.c ../src/rfc822parse.h \ ../src/rfc2047parse.c ../src/rfc2047parse.h \ ../src/common_indep.c ../src/common_indep.h \ ../src/debug.cpp ../src/debug.h \ ../src/memdbg.cpp ../src/memdbg.h \ ../src/cpphelp.cpp ../src/cpphelp.h \ - ../src/w32-gettext.cpp ../src/w32-gettext.h \ ../src/xmalloc.h if !HAVE_W32_SYSTEM t_parser_SOURCES = t-parser.cpp $(parser_SRC) -endif run_parser_SOURCES = run-parser.cpp $(parser_SRC) +else +run_parser_SOURCES = run-parser.cpp $(parser_SRC) \ + ../src/w32-gettext.cpp ../src/w32-gettext.h +endif if !HAVE_W32_SYSTEM noinst_PROGRAMS = t-parser run-parser else noinst_PROGRAMS = run-parser endif