w32: Add code to support a portable use of GnuPG.
authorWerner Koch <wk@gnupg.org>
Thu, 1 Aug 2013 09:20:48 +0000 (11:20 +0200)
committerWerner Koch <wk@gnupg.org>
Thu, 1 Aug 2013 09:20:48 +0000 (11:20 +0200)
* common/homedir.c (w32_bin_is_bin, w32_portable_app) [W32]: New.
(check_portable_app) [W32]: New.
(standard_homedir, default_homedir) [W32]: Support the portable flag.
(w32_rootdir, w32_commondir) [W32]: Ditto.
(gnupg_bindir, gnupg_cachedir, dirmngr_socket_name) [W32]: Ditto.
* common/logging.h (JNLIB_LOG_NO_REGISTRY): New.
* common/logging.c (no_registry): New variable.
(log_set_prefix, log_get_prefix): Set/get that variable.
(do_logv): Do not check the registry if that variable is set.
--

Beware: This code has not been tested because it is not yet possible
to build GnuPG 2.1 for Windows.  However, the code will be the base
for an implementation in 2.0.

A portable use of GnuPG under Windows means that GnuPG uses a home
directory depending on the location of the actual binary.  No registry
variables are considered.  The portable mode is enabled if in the
installation directory of the the binary "gpgconf.exe" and a
file "gpgconf.ctl" are found.  The latter file is empty or consists
only of empty or '#' comment lines.

Signed-off-by: Werner Koch <wk@gnupg.org>
NEWS
common/homedir.c
common/logging.c
common/logging.h
doc/opt-homedir.texi

diff --git a/NEWS b/NEWS
index 370d9c4..3b2016f 100644 (file)
--- a/NEWS
+++ b/NEWS
@@ -1,4 +1,4 @@
-Noteworthy changes in version 2.1.0beta4 (unreleased)
+Noteworthy changes in version 2.1.0-betaN (unreleased)
 -----------------------------------------------------
 
  * GPG now accepts a space separated fingerprint as a user ID.  This
@@ -20,6 +20,8 @@ Noteworthy changes in version 2.1.0beta4 (unreleased)
  * Better support fo CCID readers.  Now, internal CCID driver supports
    readers with no auto configuration feature.
 
+ * Support installation as portable application under Windows.
+
 
 Noteworthy changes in version 2.1.0beta3 (2011-12-20)
 -----------------------------------------------------
index 28e5c9a..9fec78f 100644 (file)
@@ -1,5 +1,6 @@
 /* homedir.c - Setup the home directory.
  * Copyright (C) 2004, 2006, 2007, 2010 Free Software Foundation, Inc.
+ * Copyright (C) 2013 Werner Koch
  *
  * This file is part of GnuPG.
  *
@@ -33,6 +34,9 @@
 #include <fcntl.h>
 
 #ifdef HAVE_W32_SYSTEM
+#include <winsock2.h>   /* Due to the stupid mingw64 requirement to
+                           include this header before windows.h which
+                           is often implicitly included.  */
 #include <shlobj.h>
 #ifndef CSIDL_APPDATA
 #define CSIDL_APPDATA 0x001a
 #include "util.h"
 #include "sysutils.h"
 
+#ifdef HAVE_W32_SYSTEM
+/* A flag used to indicate that a control file for gpgconf has been
+   detected.  Under Windows the presence of this file indicates a
+   portable installations and triggers several changes:
+
+   - The GNUGHOME directory is fixed relative to installation
+     directory.  All other means to set the home directory are ignore.
+
+   - All registry variables will be ignored.
+
+   This flag is not used on Unix systems.
+ */
+static int w32_portable_app;
+#endif /*HAVE_W32_SYSTEM*/
+
+#ifdef HAVE_W32_SYSTEM
+/* This flag is true if this process' binary has been installed under
+   bin and not in the root directory. */
+static int w32_bin_is_bin;
+#endif /*HAVE_W32_SYSTEM*/
+
+
+#ifdef HAVE_W32_SYSTEM
+static const char *w32_rootdir (void);
+#endif
+
+
 
 #ifdef HAVE_W32_SYSTEM
 static void
@@ -124,28 +155,39 @@ standard_homedir (void)
 
   if (!dir)
     {
-      char path[MAX_PATH];
+      const char *rdir;
 
-      /* 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)
+      rdir = w32_rootdir ();
+      if (w32_portable_app)
         {
-          char *tmp = xmalloc (strlen (path) + 6 +1);
-          strcpy (stpcpy (tmp, path), "\\gnupg");
-          dir = tmp;
-
-          /* Try to create the directory if it does not yet exists.  */
-          if (access (dir, F_OK))
-            w32_try_mkdir (dir);
+          dir = xstrconcat (rdir, DIRSEP_S "home", NULL);
         }
       else
-        dir = GNUPG_DEFAULT_HOMEDIR;
+        {
+          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 = xmalloc (strlen (path) + 6 +1);
+              strcpy (stpcpy (tmp, path), "\\gnupg");
+              dir = tmp;
+
+              /* Try to create the directory if it does not yet exists.  */
+              if (access (dir, F_OK))
+                w32_try_mkdir (dir);
+            }
+          else
+            dir = GNUPG_DEFAULT_HOMEDIR;
+        }
     }
   return dir;
 #else/*!HAVE_W32_SYSTEM*/
@@ -160,6 +202,13 @@ default_homedir (void)
 {
   const char *dir;
 
+#ifdef HAVE_W32_SYSTEM
+  /* For a portable application we only use the standard homedir.  */
+  w32_rootdir ();
+  if (w32_portable_app)
+    return standard_homedir ();
+#endif /*HAVE_W32_SYSTEM*/
+
   dir = getenv ("GNUPGHOME");
 #ifdef HAVE_W32_SYSTEM
   if (!dir || !*dir)
@@ -197,6 +246,37 @@ default_homedir (void)
 
 
 #ifdef HAVE_W32_SYSTEM
+/* Check whether gpgconf is installed and if so read the gpgconf.ctl
+   file. */
+static void
+check_portable_app (const char *dir)
+{
+  char *fname;
+
+  fname = xstrconcat (dir, DIRSEP_S "gpgconf.exe", NULL);
+  if (access (fname, F_OK))
+    log_error ("required binary '%s' is not installed\n", fname);
+  else
+    {
+      strcpy (fname + strlen (fname) - 3, ".ctl");
+      if (!access (fname, F_OK))
+        {
+          /* gpgconf.ctl file found.  Record this fact.  */
+          w32_portable_app = 1;
+          {
+            unsigned int flags;
+            log_get_prefix (&flags);
+            log_set_prefix (NULL, (flags | JNLIB_LOG_NO_REGISTRY));
+          }
+          /* FIXME: We should read the file to detect special flags
+             and print a warning if we don't understand them  */
+        }
+    }
+  xfree (fname);
+}
+
+
+/* Determine the root directory of the gnupg installation on Windows.  */
 static const char *
 w32_rootdir (void)
 {
@@ -229,11 +309,17 @@ w32_rootdir (void)
       if (p)
         {
           *p = 0;
+
+          check_portable_app (dir);
+
           /* If we are installed below "bin" we strip that and use
              the top directory instead.  */
           p = strrchr (dir, DIRSEP_C);
           if (p && !strcmp (p+1, "bin"))
-            *p = 0;
+            {
+              *p = 0;
+              w32_bin_is_bin = 1;
+            }
         }
       if (!p)
         {
@@ -255,8 +341,17 @@ w32_commondir (void)
 
   if (!dir)
     {
+      const char *rdir;
       char path[MAX_PATH];
 
+      /* Make sure that w32_rootdir has been called so that we are
+         able to check the portable application flag.  The common dir
+         is the identical to the rootdir.  In that case there is also
+         no need to strdup its value.  */
+      rdir = w32_rootdir ();
+      if (w32_portable_app)
+        return rdir;
+
       if (w32_shgetfolderpath (NULL, CSIDL_COMMON_APPDATA,
                                NULL, 0, path) >= 0)
         {
@@ -270,7 +365,7 @@ w32_commondir (void)
         {
           /* Ooops: Not defined - probably an old Windows version.
              Use the installation directory instead.  */
-          dir = xstrdup (w32_rootdir ());
+          dir = xstrdup (rdir);
         }
     }
 
@@ -315,7 +410,19 @@ gnupg_bindir (void)
     name = xstrconcat (w32_rootdir (), DIRSEP_S "bin", NULL);
   return name;
 #elif defined(HAVE_W32_SYSTEM)
-  return w32_rootdir ();
+  const char *rdir;
+
+  rdir = w32_rootdir ();
+  if (w32_bin_is_bin)
+    {
+      static char *name;
+
+      if (!name)
+        name = xstrconcat (rdir, DIRSEP_S "bin", NULL);
+      return name;
+    }
+  else
+    return rdir;
 #else /*!HAVE_W32_SYSTEM*/
   return GNUPG_BINDIR;
 #endif /*!HAVE_W32_SYSTEM*/
@@ -390,41 +497,54 @@ gnupg_cachedir (void)
 
   if (!dir)
     {
-      char path[MAX_PATH];
-      const char *s1[] = { "GNU", "cache", "gnupg", NULL };
-      int s1_len;
-      const char **comp;
-
-      s1_len = 0;
-      for (comp = s1; *comp; comp++)
-        s1_len += 1 + strlen (*comp);
+      const char *rdir;
 
-      if (w32_shgetfolderpath (NULL, CSIDL_LOCAL_APPDATA|CSIDL_FLAG_CREATE,
-                               NULL, 0, path) >= 0)
+      rdir = w32_rootdir ();
+      if (w32_portable_app)
+        {
+          dir = xstrconcat (rdir,
+                            DIRSEP_S, "var",
+                            DIRSEP_S, "cache",
+                            DIRSEP_S, "gnupg", NULL);
+        }
+      else
         {
-          char *tmp = xmalloc (strlen (path) + s1_len + 1);
-         char *p;
+          char path[MAX_PATH];
+          const char *s1[] = { "GNU", "cache", "gnupg", NULL };
+          int s1_len;
+          const char **comp;
 
-         p = stpcpy (tmp, path);
+          s1_len = 0;
           for (comp = s1; *comp; comp++)
-           {
-             p = stpcpy (p, "\\");
-             p = stpcpy (p, *comp);
+            s1_len += 1 + strlen (*comp);
 
-             if (access (tmp, F_OK))
-               w32_try_mkdir (tmp);
-           }
+          if (w32_shgetfolderpath (NULL, CSIDL_LOCAL_APPDATA|CSIDL_FLAG_CREATE,
+                                   NULL, 0, path) >= 0)
+            {
+              char *tmp = xmalloc (strlen (path) + s1_len + 1);
+              char *p;
 
-          dir = tmp;
-        }
-      else
-        {
-          dir = "c:\\temp\\cache\\gnupg";
+              p = stpcpy (tmp, path);
+              for (comp = s1; *comp; comp++)
+                {
+                  p = stpcpy (p, "\\");
+                  p = stpcpy (p, *comp);
+
+                  if (access (tmp, F_OK))
+                    w32_try_mkdir (tmp);
+                }
+
+              dir = tmp;
+            }
+          else
+            {
+              dir = "c:\\temp\\cache\\gnupg";
 #ifdef HAVE_W32CE_SYSTEM
-          dir += 2;
-         w32_try_mkdir ("\\temp\\cache");
-         w32_try_mkdir ("\\temp\\cache\\gnupg");
+              dir += 2;
+              w32_try_mkdir ("\\temp\\cache");
+              w32_try_mkdir ("\\temp\\cache\\gnupg");
 #endif
+            }
         }
     }
   return dir;
@@ -449,16 +569,21 @@ dirmngr_socket_name (void)
 
       s1 = default_homedir ();
 # else
-      char s1[MAX_PATH];
-      const char *s2;
-
-      /* We need something akin CSIDL_COMMON_PROGRAMS, but local
-        (non-roaming).  This is becuase the file needs to be on the
-        local machine and makes only sense on that machine.
-        CSIDL_WINDOWS seems to be the only location which guarantees
-        that. */
-      if (w32_shgetfolderpath (NULL, CSIDL_WINDOWS, NULL, 0, s1) < 0)
-       strcpy (s1, "C:\\WINDOWS");
+      char s1buf[MAX_PATH];
+      const char *s1, *s2;
+
+      s1 = default_homedir ();
+      if (!w32_portable_app)
+        {
+          /* We need something akin CSIDL_COMMON_PROGRAMS, but local
+             (non-roaming).  This is because the file needs to be on
+             the local machine and makes only sense on that machine.
+             CSIDL_WINDOWS seems to be the only location which
+             guarantees that. */
+          if (w32_shgetfolderpath (NULL, CSIDL_WINDOWS, NULL, 0, s1buf) < 0)
+            strcpy (s1buf, "C:\\WINDOWS");
+          s1 = s1buf;
+        }
 # endif
       s2 = DIRSEP_S "S.dirmngr";
       name = xmalloc (strlen (s1) + strlen (s2) + 1);
index 73b0dbe..f78df91 100644 (file)
@@ -96,6 +96,9 @@ static char prefix_buffer[80];
 static int with_time;
 static int with_prefix;
 static int with_pid;
+#ifdef HAVE_W32_SYSTEM
+static int no_registry;
+#endif
 static int (*get_pid_suffix_cb)(unsigned long *r_value);
 static int running_detached;
 static int force_prefixes;
@@ -561,6 +564,9 @@ log_set_prefix (const char *text, unsigned int flags)
   with_time = (flags & JNLIB_LOG_WITH_TIME);
   with_pid  = (flags & JNLIB_LOG_WITH_PID);
   running_detached = (flags & JNLIB_LOG_RUN_DETACHED);
+#ifdef HAVE_W32_SYSTEM
+  no_registry = (flags & JNLIB_LOG_NO_REGISTRY);
+#endif
 }
 
 
@@ -578,6 +584,10 @@ log_get_prefix (unsigned int *flags)
         *flags |= JNLIB_LOG_WITH_PID;
       if (running_detached)
         *flags |= JNLIB_LOG_RUN_DETACHED;
+#ifdef HAVE_W32_SYSTEM
+      if (no_registry)
+        *flags |= JNLIB_LOG_NO_REGISTRY;
+#endif
     }
   return prefix_buffer;
 }
@@ -624,8 +634,10 @@ do_logv (int level, int ignore_arg_ptr, const char *fmt, va_list arg_ptr)
 #ifdef HAVE_W32_SYSTEM
       char *tmp;
 
-      tmp = read_w32_registry_string (NULL, "Software\\GNU\\GnuPG",
-                                            "DefaultLogFile");
+      tmp = (no_registry
+             ? NULL
+             : read_w32_registry_string (NULL, "Software\\GNU\\GnuPG",
+                                         "DefaultLogFile"));
       log_set_file (tmp && *tmp? tmp : NULL);
       jnlib_free (tmp);
 #else
index 89913e6..3b38f73 100644 (file)
@@ -42,6 +42,7 @@
 #define JNLIB_LOG_WITH_TIME    2
 #define JNLIB_LOG_WITH_PID     4
 #define JNLIB_LOG_RUN_DETACHED 256
+#define JNLIB_LOG_NO_REGISTRY  512
 
 int  log_get_errorcount (int clear);
 void log_inc_errorcount (void);
index e382f63..033a901 100644 (file)
@@ -5,6 +5,18 @@ Set the name of the home directory to @var{dir}. If this option is not
 used, the home directory defaults to @file{~/.gnupg}.  It is only
 recognized when given on the command line.  It also overrides any home
 directory stated through the environment variable @env{GNUPGHOME} or
-(on W32 systems) by means of the Registry entry
+(on Windows systems) by means of the Registry entry
 @var{HKCU\Software\GNU\GnuPG:HomeDir}.
 
+On Windows systems it is possible to install GnuPG as a portable
+application.  In this case only this command line option is
+considered, all other ways to set a home directory are ignored.
+
+To install GnuPG as a portable application under Windows, create an
+empty file name @file{gpgconf.ctl} in the same directory as the tool
+@file{gpgconf.exe}.  The root of the installation is than that
+directory; or, if @file{gpgconf.exe} has been installed directly below
+a directory named @file{bin}, its parent directory.  You also need to
+make sure that the following directories exist and are writable:
+@file{ROOT/home} for the GnuPG home and @file{ROOT/var/cache/gnupg}
+for internal cache files.