summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorWerner Koch <wk@gnupg.org>2024-03-21 15:41:17 +0100
committerWerner Koch <wk@gnupg.org>2024-03-21 15:47:43 +0100
commitfb3fe38d2831c29865b6e95b9319625a33875334 (patch)
treeecf23c41813ebbff88f795bfa4aeaea47c73e77e
parentgpg: Make sure a DECRYPTION_OKAY is never issued for a bad OCB tag. (diff)
downloadgnupg2-fb3fe38d2831c29865b6e95b9319625a33875334.tar.xz
gnupg2-fb3fe38d2831c29865b6e95b9319625a33875334.zip
common: Use a common gpgconf.ctl parser for Unix and Windows.
* common/homedir.c (gpgconf_ctl): new struct. (string_is_true): New. (parse_gpgconf_ctl): New. Based on the former code in unix_rootdir. (check_portable_app): Use parse_gpgconf_ctl and the new struct. (unix_rootdir): Ditto. -- This is a unification of the gpgconf.ctl mechanism. For backward compatibility we need to keep the empty (or actually only comments) method as used formerly under Windows. Iff one really wants a portable application the new portable keyword should be used, though. Noet that the Windows portable stuff has not been tested for quite some time.
-rw-r--r--common/homedir.c451
-rw-r--r--doc/tools.texi3
2 files changed, 252 insertions, 202 deletions
diff --git a/common/homedir.c b/common/homedir.c
index 6f99f3eab..d38c94a05 100644
--- a/common/homedir.c
+++ b/common/homedir.c
@@ -93,6 +93,21 @@ static char *the_gnupg_homedir;
static byte non_default_homedir;
+/* An object to store information taken from a gpgconf.ctl file. This
+ * is parsed early at startup time and never changed later. */
+static struct
+{
+ unsigned int checked:1; /* True if we have checked for a gpgconf.ctl. */
+ unsigned int found:1; /* True if a gpgconf.ctl was found. */
+ unsigned int empty:1; /* The file is empty except for comments. */
+ unsigned int valid:1; /* The entries in gpgconf.ctl are valid. */
+ unsigned int portable:1;/* Windows portable installation. */
+ char *rootdir; /* rootdir or NULL */
+ char *sysconfdir; /* sysconfdir or NULL */
+ char *socketdir; /* socketdir or NULL */
+} gpgconf_ctl;
+
+
#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
@@ -427,6 +442,212 @@ default_homedir (void)
}
+/* Return true if S can be inteprtated as true. This is uised for
+ * keywords in gpgconf.ctl. Spaces must have been trimmed. */
+static int
+string_is_true (const char *s)
+{
+ return (atoi (s)
+ || !ascii_strcasecmp (s, "yes")
+ || !ascii_strcasecmp (s, "true")
+ || !ascii_strcasecmp (s, "fact"));
+}
+
+/* This function is used to parse the gpgconf.ctl file and set the
+ * information ito the gpgconf_ctl structure. This is called once
+ * with the full filename of gpgconf.ctl. There are two callers: One
+ * used on Windows and one on Unix. No error return but diagnostics
+ * are printed. */
+static void
+parse_gpgconf_ctl (const char *fname)
+{
+ gpg_error_t err;
+ char *p;
+ char *line;
+ size_t linelen;
+ ssize_t length;
+ estream_t fp;
+ const char *name;
+ int anyitem = 0;
+ int ignoreall = 0;
+ char *rootdir = NULL;
+ char *sysconfdir = NULL;
+ char *socketdir = NULL;
+
+ if (gpgconf_ctl.checked)
+ return; /* Just in case this is called a second time. */
+ gpgconf_ctl.checked = 1;
+ gpgconf_ctl.found = 0;
+ gpgconf_ctl.valid = 0;
+ gpgconf_ctl.empty = 0;
+
+ if (gnupg_access (fname, F_OK))
+ return; /* No gpgconf.ctl file. */
+
+ /* log_info ("detected '%s'\n", buffer); */
+ fp = es_fopen (fname, "r");
+ if (!fp)
+ {
+ err = gpg_error_from_syserror ();
+ log_info ("error opening '%s': %s\n", fname, gpg_strerror (err));
+ return;
+ }
+ gpgconf_ctl.found = 1;
+
+ line = NULL;
+ linelen = 0;
+ while ((length = es_read_line (fp, &line, &linelen, NULL)) > 0)
+ {
+ static const char *names[] =
+ {
+ "rootdir",
+ "sysconfdir",
+ "socketdir",
+ "portable",
+ ".enable"
+ };
+ int i;
+ size_t n;
+
+ /* Strip NL and CR, if present. */
+ while (length > 0
+ && (line[length - 1] == '\n' || line[length - 1] == '\r'))
+ line[--length] = 0;
+ trim_spaces (line);
+ if (*line == '#' || !*line)
+ continue;
+ anyitem = 1;
+
+ /* Find the keyword. */
+ name = NULL;
+ p = NULL;
+ for (i=0; i < DIM (names); i++)
+ {
+ n = strlen (names[i]);
+ if (!strncmp (line, names[i], n))
+ {
+ while (line[n] == ' ' || line[n] == '\t')
+ n++;
+ if (line[n] == '=')
+ {
+ name = names[i];
+ p = line + n + 1;
+ break;
+ }
+ }
+ }
+ if (!name)
+ continue; /* Keyword not known. */
+
+ trim_spaces (p);
+ p = substitute_envvars (p);
+ if (!p)
+ {
+ err = gpg_error_from_syserror ();
+ log_info ("error getting %s from gpgconf.ctl: %s\n",
+ name, gpg_strerror (err));
+ }
+ else if (!strcmp (name, ".enable"))
+ {
+ if (string_is_true (p))
+ ; /* Yes, this file shall be used. */
+ else
+ ignoreall = 1; /* No, this file shall be ignored. */
+ xfree (p);
+ }
+ else if (!strcmp (name, "sysconfdir"))
+ {
+ xfree (sysconfdir);
+ sysconfdir = p;
+ }
+ else if (!strcmp (name, "socketdir"))
+ {
+ xfree (socketdir);
+ socketdir = p;
+ }
+ else if (!strcmp (name, "rootdir"))
+ {
+ xfree (rootdir);
+ rootdir = p;
+ }
+ else if (!strcmp (name, "portable"))
+ {
+ gpgconf_ctl.portable = string_is_true (p);
+ xfree (p);
+ }
+ else /* Unknown keyword. */
+ xfree (p);
+ }
+ if (es_ferror (fp))
+ {
+ err = gpg_error_from_syserror ();
+ log_info ("error reading '%s': %s\n", fname, gpg_strerror (err));
+ ignoreall = 1; /* Force all entries to invalid. */
+ }
+ es_fclose (fp);
+ xfree (line);
+
+ if (ignoreall)
+ ; /* Forced error. Note that .found is still set. */
+ else if (rootdir && (!*rootdir || *rootdir != '/'))
+ {
+ log_info ("invalid %s '%s' specified in gpgconf.ctl\n",
+ "rootdir", rootdir);
+ }
+ else if (sysconfdir && (!*sysconfdir || *sysconfdir != '/'))
+ {
+ log_info ("invalid %s '%s' specified in gpgconf.ctl\n",
+ "sysconfdir", sysconfdir);
+ }
+ else if (socketdir && (!*socketdir || *socketdir != '/'))
+ {
+ log_info ("invalid %s '%s' specified in gpgconf.ctl\n",
+ "socketdir", socketdir);
+ }
+ else
+ {
+ if (rootdir)
+ {
+ while (*rootdir && rootdir[strlen (rootdir)-1] == '/')
+ rootdir[strlen (rootdir)-1] = 0;
+ gpgconf_ctl.rootdir = rootdir;
+ gpgrt_annotate_leaked_object (gpgconf_ctl.rootdir);
+ /* log_info ("want rootdir '%s'\n", dir); */
+ }
+ if (sysconfdir)
+ {
+ while (*sysconfdir && sysconfdir[strlen (sysconfdir)-1] == '/')
+ sysconfdir[strlen (sysconfdir)-1] = 0;
+ gpgconf_ctl.sysconfdir = sysconfdir;
+ gpgrt_annotate_leaked_object (gpgconf_ctl.sysconfdir);
+ /* log_info ("want sysconfdir '%s'\n", sdir); */
+ }
+ if (socketdir)
+ {
+ while (*socketdir && socketdir[strlen (socketdir)-1] == '/')
+ socketdir[strlen (socketdir)-1] = 0;
+ gpgconf_ctl.socketdir = socketdir;
+ gpgrt_annotate_leaked_object (gpgconf_ctl.socketdir);
+ /* log_info ("want socketdir '%s'\n", s2dir); */
+ }
+ gpgconf_ctl.valid = 1;
+ }
+
+ gpgconf_ctl.empty = !anyitem;
+ if (!gpgconf_ctl.valid)
+ {
+ /* Error reading some entries - clear them all. */
+ xfree (rootdir);
+ xfree (sysconfdir);
+ xfree (socketdir);
+ gpgconf_ctl.rootdir = NULL;
+ gpgconf_ctl.sysconfdir = NULL;
+ gpgconf_ctl.socketdir = NULL;
+ }
+}
+
+
+
#ifdef HAVE_W32_SYSTEM
/* Check whether gpgconf is installed and if so read the gpgconf.ctl
file. */
@@ -439,17 +660,20 @@ check_portable_app (const char *dir)
if (!gnupg_access (fname, F_OK))
{
strcpy (fname + strlen (fname) - 3, "ctl");
- if (!gnupg_access (fname, F_OK))
+ parse_gpgconf_ctl (fname);
+ if ((gpgconf_ctl.found && gpgconf_ctl.empty)
+ || (gpgconf_ctl.valid && gpgconf_ctl.portable))
{
- /* gpgconf.ctl file found. Record this fact. */
+ unsigned int flags;
+
+ /* Classic gpgconf.ctl file found. This is a portable
+ * application. Note that if there are any items in that
+ * file we don't consider this a portable application unless
+ * the (later added) ".portable" keyword has also been
+ * seen. */
w32_portable_app = 1;
- {
- unsigned int flags;
- log_get_prefix (&flags);
- log_set_prefix (NULL, (flags | GPGRT_LOG_NO_REGISTRY));
- }
- /* FIXME: We should read the file to detect special flags
- and print a warning if we don't understand them */
+ log_get_prefix (&flags);
+ log_set_prefix (NULL, (flags | GPGRT_LOG_NO_REGISTRY));
}
}
xfree (fname);
@@ -528,28 +752,14 @@ w32_rootdir (void)
static const char *
unix_rootdir (enum wantdir_values wantdir)
{
- static int checked;
- static char *dir; /* for the rootdir */
- static char *sdir; /* for the sysconfdir */
- static char *s2dir; /* for the socketdir */
-
- if (!checked)
+ if (!gpgconf_ctl.checked)
{
char *p;
char *buffer;
size_t bufsize = 256-1;
int nread;
gpg_error_t err;
- char *line;
- size_t linelen;
- ssize_t length;
- estream_t fp;
- char *rootdir;
- char *sysconfdir;
- char *socketdir;
const char *name;
- int ignoreall = 0;
- int okay;
for (;;)
{
@@ -589,7 +799,7 @@ unix_rootdir (enum wantdir_values wantdir)
if (!*buffer)
{
xfree (buffer);
- checked = 1;
+ gpgconf_ctl.checked = 1;
return NULL; /* Error - assume no gpgconf.ctl. */
}
@@ -597,197 +807,36 @@ unix_rootdir (enum wantdir_values wantdir)
if (!p)
{
xfree (buffer);
- checked = 1;
+ gpgconf_ctl.checked = 1;
return NULL; /* Erroneous /proc - assume no gpgconf.ctl. */
}
*p = 0; /* BUFFER has the directory. */
- if ((p = strrchr (buffer, '/')))
- {
- /* Strip one part and expect the file below a bin dir. */
- *p = 0;
- p = xstrconcat (buffer, "/bin/gpgconf.ctl", NULL);
- xfree (buffer);
- buffer = p;
- }
- else /* !p */
+ if (!(p = strrchr (buffer, '/')))
{
/* Installed in the root which is not a good idea. Assume
* no gpgconf.ctl. */
xfree (buffer);
- checked = 1;
+ gpgconf_ctl.checked = 1;
return NULL;
}
- if (gnupg_access (buffer, F_OK))
- {
- /* No gpgconf.ctl file. */
- xfree (buffer);
- checked = 1;
- return NULL;
- }
- /* log_info ("detected '%s'\n", buffer); */
- fp = es_fopen (buffer, "r");
- if (!fp)
- {
- err = gpg_error_from_syserror ();
- log_info ("error opening '%s': %s\n", buffer, gpg_strerror (err));
- xfree (buffer);
- checked = 1;
- return NULL;
- }
-
- line = NULL;
- linelen = 0;
- rootdir = NULL;
- sysconfdir = NULL;
- socketdir = NULL;
- while ((length = es_read_line (fp, &line, &linelen, NULL)) > 0)
- {
- static const char *names[] =
- {
- "rootdir",
- "sysconfdir",
- "socketdir",
- ".enable"
- };
- int i;
- size_t n;
-
- /* Strip NL and CR, if present. */
- while (length > 0
- && (line[length - 1] == '\n' || line[length - 1] == '\r'))
- line[--length] = 0;
- trim_spaces (line);
- /* Find the stamement. */
- name = NULL;
- for (i=0; i < DIM (names); i++)
- {
- n = strlen (names[i]);
- if (!strncmp (line, names[i], n))
- {
- while (line[n] == ' ' || line[n] == '\t')
- n++;
- if (line[n] == '=')
- {
- name = names[i];
- p = line + n + 1;
- break;
- }
- }
- }
- if (!name)
- continue; /* Statement not known. */
-
- trim_spaces (p);
- p = substitute_envvars (p);
- if (!p)
- {
- err = gpg_error_from_syserror ();
- log_info ("error getting %s from gpgconf.ctl: %s\n",
- name, gpg_strerror (err));
- }
- else if (!strcmp (name, ".enable"))
- {
- if (atoi (p)
- || !ascii_strcasecmp (p, "yes")
- || !ascii_strcasecmp (p, "true")
- || !ascii_strcasecmp (p, "fact"))
- ; /* Yes, this file shall be used. */
- else
- ignoreall = 1; /* No, this file shall be ignored. */
- xfree (p);
- }
- else if (!strcmp (name, "sysconfdir"))
- {
- xfree (sysconfdir);
- sysconfdir = p;
- }
- else if (!strcmp (name, "socketdir"))
- {
- xfree (socketdir);
- socketdir = p;
- }
- else
- {
- xfree (rootdir);
- rootdir = p;
- }
- }
- if (es_ferror (fp))
- {
- err = gpg_error_from_syserror ();
- log_info ("error reading '%s': %s\n", buffer, gpg_strerror (err));
- es_fclose (fp);
- xfree (buffer);
- xfree (line);
- xfree (rootdir);
- xfree (sysconfdir);
- xfree (socketdir);
- checked = 1;
- return NULL;
- }
- es_fclose (fp);
+ /* Strip one part and expect the file below a bin dir. */
+ *p = 0;
+ p = xstrconcat (buffer, "/bin/gpgconf.ctl", NULL);
+ xfree (buffer);
+ buffer = p;
+ parse_gpgconf_ctl (buffer);
xfree (buffer);
- xfree (line);
-
- okay = 0;
- if (ignoreall)
- ;
- else if (!rootdir || !*rootdir || *rootdir != '/')
- {
- log_info ("invalid rootdir '%s' specified in gpgconf.ctl\n", rootdir);
- }
- else if (sysconfdir && (!*sysconfdir || *sysconfdir != '/'))
- {
- log_info ("invalid sysconfdir '%s' specified in gpgconf.ctl\n",
- sysconfdir);
- }
- else if (socketdir && (!*socketdir || *socketdir != '/'))
- {
- log_info ("invalid socketdir '%s' specified in gpgconf.ctl\n",
- socketdir);
- }
- else
- {
- okay = 1;
- while (*rootdir && rootdir[strlen (rootdir)-1] == '/')
- rootdir[strlen (rootdir)-1] = 0;
- dir = rootdir;
- gpgrt_annotate_leaked_object (dir);
- /* log_info ("want rootdir '%s'\n", dir); */
- if (sysconfdir)
- {
- while (*sysconfdir && sysconfdir[strlen (sysconfdir)-1] == '/')
- sysconfdir[strlen (sysconfdir)-1] = 0;
- sdir = sysconfdir;
- gpgrt_annotate_leaked_object (sdir);
- /* log_info ("want sysconfdir '%s'\n", sdir); */
- }
- if (socketdir)
- {
- while (*socketdir && socketdir[strlen (socketdir)-1] == '/')
- socketdir[strlen (socketdir)-1] = 0;
- s2dir = socketdir;
- gpgrt_annotate_leaked_object (s2dir);
- /* log_info ("want socketdir '%s'\n", s2dir); */
- }
- }
-
- if (!okay)
- {
- xfree (rootdir);
- xfree (sysconfdir);
- xfree (socketdir);
- dir = sdir = s2dir = NULL;
- }
- checked = 1;
}
+ if (!gpgconf_ctl.valid)
+ return NULL; /* No valid entries in gpgconf.ctl */
+
switch (wantdir)
{
- case WANTDIR_ROOT: return dir;
- case WANTDIR_SYSCONF: return sdir;
- case WANTDIR_SOCKET: return s2dir;
+ case WANTDIR_ROOT: return gpgconf_ctl.rootdir;
+ case WANTDIR_SYSCONF: return gpgconf_ctl.sysconfdir;
+ case WANTDIR_SOCKET: return gpgconf_ctl.socketdir;
}
return NULL; /* Not reached. */
diff --git a/doc/tools.texi b/doc/tools.texi
index 26c4c5f3d..ae960bdc9 100644
--- a/doc/tools.texi
+++ b/doc/tools.texi
@@ -1151,7 +1151,8 @@ More fields may be added in future to the output.
Under Windows this file is used to install GnuPG as a portable
application. An empty file named @file{gpgconf.ctl} is expected in
- the same directory as the tool @file{gpgconf.exe}. The root of the
+ the same directory as the tool @file{gpgconf.exe} or the file must
+ have a keyword @code{portable} with the value true. The root of the
installation is then 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