diff --git a/src/get-path.c b/src/get-path.c index 9768c11..34cb1c2 100644 --- a/src/get-path.c +++ b/src/get-path.c @@ -1,551 +1,551 @@ /* agent.c - Talking to gpg-agent. * Copyright (C) 2008 g10 Code GmbH * * This file is part of Scute. * * Scute 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. * * Scute 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 . * SPDX-License-Identifier: LGPL-2.1-or-later */ #if HAVE_CONFIG_H #include #endif #include #include #include #ifdef HAVE_W32_SYSTEM # include # include # include #endif #include #include "debug.h" #include "support.h" /* Malloced string with GnuPG's version. NULL if gnupg is notproperly * installed. */ static char *gnupg_version_string; #ifndef HAVE_STPCPY static char * my_stpcpy (char *a, const char *b) { while( *b ) *a++ = *b++; *a = 0; return (char*)a; } # undef stpcpy # define stpcpy(a,b) my_stpcpy ((a), (b)) #endif /* !HAVE_STPCPY */ #ifdef HAVE_W32_SYSTEM #define RTLD_LAZY 0 static __inline__ void * dlopen (const char * name, int flag) { void * hd = LoadLibrary (name); (void)flag; return hd; } static __inline__ void * dlsym (void * hd, const char * sym) { if (hd && sym) { void * fnc = GetProcAddress (hd, sym); if (!fnc) return NULL; return fnc; } return NULL; } static __inline__ int dlclose (void * hd) { if (hd) { FreeLibrary (hd); return 0; } return -1; } /* Return a string from the W32 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. */ static 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 ) 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; 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; } /* This is a helper function to load and run 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 = dlopen (dllnames[i], RTLD_LAZY); if (handle) { func = dlsym (handle, "SHGetFolderPathA"); if (!func) { dlclose (handle); handle = NULL; } } } } if (func) return func (a,b,c,d,e); else return -1; } static char * find_program_in_inst_dir (const char *name) { char *result = NULL; char *tmp; tmp = read_w32_registry_string ("HKEY_LOCAL_MACHINE", "Software\\GNU\\GnuPG", "Install Directory"); if (!tmp) return NULL; result = malloc (strlen (tmp) + 1 + strlen (name) + 1); if (!result) { free (tmp); return NULL; } strcpy (stpcpy (stpcpy (result, tmp), "\\"), name); free (tmp); if (access (result, F_OK)) { free (result); return NULL; } return result; } static char * find_program_at_standard_place (const char *name) { char path[MAX_PATH]; char *result = NULL; if (w32_shgetfolderpath (NULL, CSIDL_PROGRAM_FILES, NULL, 0, path) >= 0) { result = malloc (strlen (path) + 1 + strlen (name) + 1); if (result) { strcpy (stpcpy (stpcpy (result, path), "\\"), name); if (access (result, F_OK)) { free (result); result = NULL; } } } return result; } #endif /* Read a line form the output of COMMAND via popen and return that * line at BUFFER which has been allocated by the caller with BUFSIZE * bytes. On success BUFFER contains a string with the first line. * Command and buffer may have the same address. If no output is * expected BUFFER can be given as NULL. */ gpg_error_t read_first_line (const char *command, char *buffer, size_t bufsize) { gpg_error_t err; FILE *fp; if (buffer && bufsize < 2) return gpg_error (GPG_ERR_BUFFER_TOO_SHORT); #ifdef HAVE_W32_SYSTEM fp = _popen (command, "r"); #else fp = popen (command, "r"); #endif if (fp) { - int i, c; + int i, c = 0; if (buffer) { for (i=0; i < bufsize - 1 && (c=getc(fp)) != EOF && c != '\n'; i++) buffer[i] = c; buffer [i] = 0; /* Terminate string. */ if (c == EOF && ferror (fp)) err = gpg_error_from_syserror (); /* Read error. */ else if (!(i < bufsize - 1)) err = gpg_error (GPG_ERR_NO_AGENT); /* Path too long. */ else if (!i || c != '\n') err = gpg_error (GPG_ERR_NO_AGENT); /* No terminating LF. */ else err = 0; } else err = 0; pclose (fp); } else { err = gpg_error_from_syserror (); DEBUG (DBG_CRIT, "popen(%s) failed: %s", command, gpg_strerror (err)); } return err; } /* Extract the version string of a program from STRING. The version * number is expected to be in GNU style format: * * foo 1.2.3 * foo (bar system) 1.2.3 * foo 1.2.3 cruft * foo (bar system) 1.2.3 cruft. * * Spaces and tabs are skipped and used as delimiters, a term in * (nested) parenthesis before the version string is skipped, the * version string may consist of any non-space and non-tab characters * but needs to start with a digit. */ static const char * extract_version_string (const char *string, size_t *r_len) { const char *s; int count, len; for (s=string; *s; s++) if (*s == ' ' || *s == '\t') break; while (*s == ' ' || *s == '\t') s++; if (*s == '(') { for (count=1, s++; count && *s; s++) if (*s == '(') count++; else if (*s == ')') count--; } /* For robustness we look for a digit. */ while ( *s && !(*s >= '0' && *s <= '9') ) s++; if (*s >= '0' && *s <= '9') { for (len=0; s[len]; len++) if (s[len] == ' ' || s[len] == '\t') break; } else len = 0; *r_len = len; return s; } /* Return the file name of the gpgconf utility. As a side-effect the * version number of gnupg is also figured out the first time this * function is called. */ const char * get_gpgconf_path (void) { static const char *pgmname; #ifdef HAVE_W32_SYSTEM if (!pgmname) pgmname = find_program_in_inst_dir ("gpgconf.exe"); if (!pgmname) pgmname = find_program_at_standard_place ("GNU\\GnuPG\\gpgconf.exe"); #endif if (!pgmname) pgmname = "gpgconf"; if (!gnupg_version_string) { char buffer[512]; const char *s; size_t n; snprintf (buffer, sizeof buffer, "%s --version", pgmname); if (!read_first_line (buffer, buffer, sizeof buffer)) { s = extract_version_string (buffer, &n); gnupg_version_string = malloc (n+1); if (gnupg_version_string) { memcpy (gnupg_version_string, s, n); gnupg_version_string[n] = 0; } } } return pgmname; } /* Return the version of GnuPG. */ int get_gnupg_version (int *minor) { int major; const char *s; if (!gnupg_version_string) { *minor = 0; return 0; } major = atoi (gnupg_version_string); s = strchr (gnupg_version_string, '.'); *minor = s? atoi (s+1) : 0; return major; } /* Return true if GnuPG is older than MAJOR.MINOR.MICRO. */ int is_gnupg_older_than (int major, int minor, int micro) { int my_major, my_minor, my_micro; const char *s; if (!gnupg_version_string) return 1; my_minor = my_micro = 0; my_major = atoi (gnupg_version_string); s = strchr (gnupg_version_string, '.'); if (s) { my_minor = atoi (++s); s = strchr (s, '.'); if (s) my_micro = atoi (++s); } if (my_major < major) return 1; if (my_major > major) return 0; if (my_minor < minor) return 1; if (my_minor > minor) return 0; if (my_micro < micro) return 1; return 0; } /* Return the bindir where the main binaries are installed. This may * return NULL. */ static const char * get_bindir (void) { static char *bindir; gpg_error_t err = 0; char buffer[512]; if (!bindir) { snprintf (buffer, sizeof buffer, "%s --list-dirs bindir", get_gpgconf_path ()); err = read_first_line (buffer, buffer, sizeof buffer); if (!err) { bindir = strdup (buffer); if (!bindir) err = gpg_error_from_syserror (); } if (err) DEBUG (DBG_CRIT, "error locating GnuPG's installation directory: %s", gpg_strerror (err)); } return bindir; } const char * get_gpgsm_path (void) { static char *pgmname; #ifdef HAVE_W32_SYSTEM static const char gpgsm[] = "gpgsm.exe"; #else static const char gpgsm[] = "gpgsm"; #endif if (!pgmname) { char *buffer; const char *bindir = get_bindir (); if (!bindir) return gpgsm; /* Error fallback without any path component. */ buffer = malloc (strlen (bindir) + 1 + strlen (gpgsm) + 1); if (!buffer) return gpgsm; /* Error fallback. */ strcpy (stpcpy (stpcpy (buffer, bindir), "/"), gpgsm); pgmname = buffer; } return pgmname; }