diff --git a/common/asshelp.c b/common/asshelp.c index 5c32c6ed3..c03e67bff 100644 --- a/common/asshelp.c +++ b/common/asshelp.c @@ -1,743 +1,710 @@ /* asshelp.c - Helper functions for Assuan * Copyright (C) 2002, 2004, 2007, 2009, 2010 Free Software Foundation, Inc. * * This file is part of GnuPG. * * This file is free software; you can redistribute it and/or modify * it under the terms of either * * - the GNU Lesser General Public License as published by the Free * Software Foundation; either version 3 of the License, or (at * your option) any later version. * * or * * - the GNU General Public License as published by the Free * Software Foundation; either version 2 of the License, or (at * your option) any later version. * * or both in parallel, as here. * * This file 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 General Public License * along with this program; if not, see . */ #include #include #include #include #include #include #ifdef HAVE_LOCALE_H #include #endif #include "i18n.h" #include "util.h" #include "exechelp.h" #include "sysutils.h" #include "status.h" #include "membuf.h" #include "asshelp.h" /* The type we use for lock_agent_spawning. */ #ifdef HAVE_W32_SYSTEM # define lock_spawn_t HANDLE #else # define lock_spawn_t dotlock_t #endif /* The time we wait until the agent or the dirmngr are ready for operation after we started them before giving up. */ #ifdef HAVE_W32CE_SYSTEM # define SECS_TO_WAIT_FOR_AGENT 30 # define SECS_TO_WAIT_FOR_DIRMNGR 30 #else # define SECS_TO_WAIT_FOR_AGENT 5 # define SECS_TO_WAIT_FOR_DIRMNGR 5 #endif /* A bitfield that specifies the assuan categories to log. This is identical to the default log handler of libassuan. We need to do it ourselves because we use a custom log handler and want to use the same assuan variables to select the categories to log. */ static int log_cats; #define TEST_LOG_CAT(x) (!! (log_cats & (1 << (x - 1)))) static int my_libassuan_log_handler (assuan_context_t ctx, void *hook, unsigned int cat, const char *msg) { unsigned int dbgval; (void)ctx; if (! TEST_LOG_CAT (cat)) return 0; dbgval = hook? *(unsigned int*)hook : 0; if (!(dbgval & 1024)) return 0; /* Assuan debugging is not enabled. */ if (msg) log_string (GPGRT_LOG_DEBUG, msg); return 1; } /* Setup libassuan to use our own logging functions. Should be used early at startup. */ void setup_libassuan_logging (unsigned int *debug_var_address) { char *flagstr; flagstr = getenv ("ASSUAN_DEBUG"); if (flagstr) log_cats = atoi (flagstr); else /* Default to log the control channel. */ log_cats = (1 << (ASSUAN_LOG_CONTROL - 1)); assuan_set_log_cb (my_libassuan_log_handler, debug_var_address); } /* Change the Libassuan log categories to those given by NEWCATS. NEWCATS is 0 the default category of ASSUAN_LOG_CONTROL is selected. Note, that setup_libassuan_logging overrides the values given here. */ void set_libassuan_log_cats (unsigned int newcats) { if (newcats) log_cats = newcats; else /* Default to log the control channel. */ log_cats = (1 << (ASSUAN_LOG_CONTROL - 1)); } static gpg_error_t send_one_option (assuan_context_t ctx, gpg_err_source_t errsource, const char *name, const char *value, int use_putenv) { gpg_error_t err; char *optstr; (void)errsource; if (!value || !*value) err = 0; /* Avoid sending empty strings. */ else if (asprintf (&optstr, "OPTION %s%s=%s", use_putenv? "putenv=":"", name, value) < 0) err = gpg_error_from_syserror (); else { err = assuan_transact (ctx, optstr, NULL, NULL, NULL, NULL, NULL, NULL); xfree (optstr); } return err; } /* Send the assuan commands pertaining to the pinentry environment. The OPT_* arguments are optional and may be used to override the defaults taken from the current locale. */ gpg_error_t send_pinentry_environment (assuan_context_t ctx, gpg_err_source_t errsource, const char *opt_lc_ctype, const char *opt_lc_messages, session_env_t session_env) { gpg_error_t err = 0; #if defined(HAVE_SETLOCALE) char *old_lc = NULL; #endif char *dft_lc = NULL; const char *dft_ttyname; int iterator; const char *name, *assname, *value; int is_default; iterator = 0; while ((name = session_env_list_stdenvnames (&iterator, &assname))) { value = session_env_getenv_or_default (session_env, name, NULL); if (!value) continue; if (assname) err = send_one_option (ctx, errsource, assname, value, 0); else { err = send_one_option (ctx, errsource, name, value, 1); if (gpg_err_code (err) == GPG_ERR_UNKNOWN_OPTION) err = 0; /* Server too old; can't pass the new envvars. */ } if (err) return err; } dft_ttyname = session_env_getenv_or_default (session_env, "GPG_TTY", &is_default); if (dft_ttyname && !is_default) dft_ttyname = NULL; /* We need the default value. */ /* Send the value for LC_CTYPE. */ #if defined(HAVE_SETLOCALE) && defined(LC_CTYPE) old_lc = setlocale (LC_CTYPE, NULL); if (old_lc) { old_lc = xtrystrdup (old_lc); if (!old_lc) return gpg_error_from_syserror (); } dft_lc = setlocale (LC_CTYPE, ""); #endif if (opt_lc_ctype || (dft_ttyname && dft_lc)) { err = send_one_option (ctx, errsource, "lc-ctype", opt_lc_ctype ? opt_lc_ctype : dft_lc, 0); } #if defined(HAVE_SETLOCALE) && defined(LC_CTYPE) if (old_lc) { setlocale (LC_CTYPE, old_lc); xfree (old_lc); } #endif if (err) return err; /* Send the value for LC_MESSAGES. */ #if defined(HAVE_SETLOCALE) && defined(LC_MESSAGES) old_lc = setlocale (LC_MESSAGES, NULL); if (old_lc) { old_lc = xtrystrdup (old_lc); if (!old_lc) return gpg_error_from_syserror (); } dft_lc = setlocale (LC_MESSAGES, ""); #endif if (opt_lc_messages || (dft_ttyname && dft_lc)) { err = send_one_option (ctx, errsource, "lc-messages", opt_lc_messages ? opt_lc_messages : dft_lc, 0); } #if defined(HAVE_SETLOCALE) && defined(LC_MESSAGES) if (old_lc) { setlocale (LC_MESSAGES, old_lc); xfree (old_lc); } #endif if (err) return err; return 0; } /* Lock a spawning process. The caller needs to provide the address of a variable to store the lock information and the name or the process. */ static gpg_error_t lock_spawning (lock_spawn_t *lock, const char *homedir, const char *name, int verbose) { #ifdef HAVE_W32_SYSTEM int waitrc; int timeout = (!strcmp (name, "agent") ? SECS_TO_WAIT_FOR_AGENT : SECS_TO_WAIT_FOR_DIRMNGR); (void)homedir; /* Not required. */ *lock = CreateMutexW (NULL, FALSE, !strcmp (name, "agent")? L"spawn_"GNUPG_NAME"_agent_sentinel": !strcmp (name, "dirmngr")? L"spawn_"GNUPG_NAME"_dirmngr_sentinel": /* */ L"spawn_"GNUPG_NAME"_unknown_sentinel"); if (!*lock) { log_error ("failed to create the spawn_%s mutex: %s\n", name, w32_strerror (-1)); return gpg_error (GPG_ERR_GENERAL); } retry: waitrc = WaitForSingleObject (*lock, 1000); if (waitrc == WAIT_OBJECT_0) return 0; if (waitrc == WAIT_TIMEOUT && timeout) { timeout--; if (verbose) log_info ("another process is trying to start the %s ... (%ds)\n", name, timeout); goto retry; } if (waitrc == WAIT_TIMEOUT) log_info ("error waiting for the spawn_%s mutex: timeout\n", name); else log_info ("error waiting for the spawn_%s mutex: (code=%d) %s\n", name, waitrc, w32_strerror (-1)); return gpg_error (GPG_ERR_GENERAL); #else /*!HAVE_W32_SYSTEM*/ char *fname; (void)verbose; *lock = NULL; fname = make_absfilename_try (homedir, !strcmp (name, "agent")? "gnupg_spawn_agent_sentinel": !strcmp (name, "dirmngr")? "gnupg_spawn_dirmngr_sentinel": /* */ "gnupg_spawn_unknown_sentinel", NULL); if (!fname) return gpg_error_from_syserror (); *lock = dotlock_create (fname, 0); xfree (fname); if (!*lock) return gpg_error_from_syserror (); /* FIXME: We should use a timeout of 5000 here - however make_dotlock does not yet support values other than -1 and 0. */ if (dotlock_take (*lock, -1)) return gpg_error_from_syserror (); return 0; #endif /*!HAVE_W32_SYSTEM*/ } /* Unlock the spawning process. */ static void unlock_spawning (lock_spawn_t *lock, const char *name) { if (*lock) { #ifdef HAVE_W32_SYSTEM if (!ReleaseMutex (*lock)) log_error ("failed to release the spawn_%s mutex: %s\n", name, w32_strerror (-1)); CloseHandle (*lock); #else /*!HAVE_W32_SYSTEM*/ (void)name; dotlock_destroy (*lock); #endif /*!HAVE_W32_SYSTEM*/ *lock = NULL; } } /* Try to connect to the agent via socket or start it if it is not running and AUTOSTART is set. Handle the server's initial greeting. Returns a new assuan context at R_CTX or an error code. */ gpg_error_t start_new_gpg_agent (assuan_context_t *r_ctx, gpg_err_source_t errsource, const char *agent_program, const char *opt_lc_ctype, const char *opt_lc_messages, session_env_t session_env, int autostart, int verbose, int debug, gpg_error_t (*status_cb)(ctrl_t, int, ...), ctrl_t status_cb_arg) { gpg_error_t err; assuan_context_t ctx; int did_success_msg = 0; char *sockname; const char *argv[6]; *r_ctx = NULL; err = assuan_new (&ctx); if (err) { log_error ("error allocating assuan context: %s\n", gpg_strerror (err)); return err; } sockname = make_filename_try (gnupg_socketdir (), GPG_AGENT_SOCK_NAME, NULL); if (!sockname) { err = gpg_err_make (errsource, gpg_err_code_from_syserror ()); assuan_release (ctx); return err; } err = assuan_socket_connect (ctx, sockname, 0, 0); if (err && autostart) { char *abs_homedir; lock_spawn_t lock; char *program = NULL; const char *program_arg = NULL; char *p; const char *s; int i; /* With no success start a new server. */ if (!agent_program || !*agent_program) agent_program = gnupg_module_name (GNUPG_MODULE_NAME_AGENT); else if ((s=strchr (agent_program, '|')) && s[1] == '-' && s[2]=='-') { /* Hack to insert an additional option on the command line. */ program = xtrystrdup (agent_program); if (!program) { gpg_error_t tmperr = gpg_err_make (errsource, gpg_err_code_from_syserror ()); xfree (sockname); assuan_release (ctx); return tmperr; } p = strchr (program, '|'); *p++ = 0; program_arg = p; } if (verbose) log_info (_("no running gpg-agent - starting '%s'\n"), agent_program); if (status_cb) status_cb (status_cb_arg, STATUS_PROGRESS, "starting_agent ? 0 0", NULL); /* We better pass an absolute home directory to the agent just in case gpg-agent does not convert the passed name to an absolute one (which it should do). */ abs_homedir = make_absfilename_try (gnupg_homedir (), NULL); if (!abs_homedir) { gpg_error_t tmperr = gpg_err_make (errsource, gpg_err_code_from_syserror ()); log_error ("error building filename: %s\n",gpg_strerror (tmperr)); xfree (sockname); assuan_release (ctx); xfree (program); return tmperr; } if (fflush (NULL)) { gpg_error_t tmperr = gpg_err_make (errsource, gpg_err_code_from_syserror ()); log_error ("error flushing pending output: %s\n", strerror (errno)); xfree (sockname); assuan_release (ctx); xfree (abs_homedir); xfree (program); return tmperr; } /* If the agent has been configured for use with a standard socket, an environment variable is not required and thus we we can savely start the agent here. */ i = 0; argv[i++] = "--homedir"; argv[i++] = abs_homedir; argv[i++] = "--use-standard-socket"; if (program_arg) argv[i++] = program_arg; argv[i++] = "--daemon"; argv[i++] = NULL; if (!(err = lock_spawning (&lock, gnupg_homedir (), "agent", verbose)) && assuan_socket_connect (ctx, sockname, 0, 0)) { err = gnupg_spawn_process_detached (program? program : agent_program, argv, NULL); if (err) log_error ("failed to start agent '%s': %s\n", agent_program, gpg_strerror (err)); else { for (i=0; i < SECS_TO_WAIT_FOR_AGENT; i++) { if (verbose) log_info (_("waiting for the agent to come up ... (%ds)\n"), SECS_TO_WAIT_FOR_AGENT - i); gnupg_sleep (1); err = assuan_socket_connect (ctx, sockname, 0, 0); if (!err) { if (verbose) { log_info (_("connection to agent established\n")); did_success_msg = 1; } break; } } } } unlock_spawning (&lock, "agent"); xfree (abs_homedir); xfree (program); } xfree (sockname); if (err) { if (autostart || gpg_err_code (err) != GPG_ERR_ASS_CONNECT_FAILED) log_error ("can't connect to the agent: %s\n", gpg_strerror (err)); assuan_release (ctx); return gpg_err_make (errsource, GPG_ERR_NO_AGENT); } if (debug && !did_success_msg) log_debug ("connection to agent established\n"); err = assuan_transact (ctx, "RESET", NULL, NULL, NULL, NULL, NULL, NULL); if (!err) { err = send_pinentry_environment (ctx, errsource, opt_lc_ctype, opt_lc_messages, session_env); if (gpg_err_code (err) == GPG_ERR_FORBIDDEN && gpg_err_source (err) == GPG_ERR_SOURCE_GPGAGENT) { /* Check whether we are in restricted mode. */ if (!assuan_transact (ctx, "GETINFO restricted", NULL, NULL, NULL, NULL, NULL, NULL)) { if (verbose) log_info (_("connection to agent is in restricted mode\n")); err = 0; } } } if (err) { assuan_release (ctx); return err; } *r_ctx = ctx; return 0; } /* Try to connect to the dirmngr via a socket. On platforms supporting it, start it up if needed and if AUTOSTART is true. Returns a new assuan context at R_CTX or an error code. */ gpg_error_t start_new_dirmngr (assuan_context_t *r_ctx, gpg_err_source_t errsource, const char *dirmngr_program, int autostart, int verbose, int debug, gpg_error_t (*status_cb)(ctrl_t, int, ...), ctrl_t status_cb_arg) { gpg_error_t err; assuan_context_t ctx; const char *sockname; int did_success_msg = 0; *r_ctx = NULL; err = assuan_new (&ctx); if (err) { log_error ("error allocating assuan context: %s\n", gpg_strerror (err)); return err; } - sockname = dirmngr_user_socket_name (); - if (sockname) - { - /* First try the local socket name and only if that fails try - the system socket. */ - err = assuan_socket_connect (ctx, sockname, 0, 0); - if (err) - sockname = dirmngr_sys_socket_name (); - } - else - sockname = dirmngr_sys_socket_name (); - + sockname = dirmngr_socket_name (); err = assuan_socket_connect (ctx, sockname, 0, 0); #ifdef USE_DIRMNGR_AUTO_START if (err && autostart) { lock_spawn_t lock; const char *argv[4]; - int try_system_daemon = 0; char *abs_homedir; - /* No connection: Try start a new Dirmngr. On Windows this will - fail because the Dirmngr is expected to be a system service. - However on WinCE we don't distinguish users and thus we can - start it. */ - - /* We prefer to start it as a user daemon. */ - sockname = dirmngr_user_socket_name (); - if (!sockname) - { - sockname = dirmngr_sys_socket_name (); - try_system_daemon = 1; - } - + /* No connection: Try start a new Dirmngr. */ if (!dirmngr_program || !*dirmngr_program) dirmngr_program = gnupg_module_name (GNUPG_MODULE_NAME_DIRMNGR); if (verbose) log_info (_("no running Dirmngr - starting '%s'\n"), dirmngr_program); if (status_cb) status_cb (status_cb_arg, STATUS_PROGRESS, "starting_dirmngr ? 0 0", NULL); abs_homedir = make_absfilename (gnupg_homedir (), NULL); if (!abs_homedir) { gpg_error_t tmperr = gpg_err_make (errsource, gpg_err_code_from_syserror ()); log_error ("error building filename: %s\n",gpg_strerror (tmperr)); assuan_release (ctx); return tmperr; } if (fflush (NULL)) { gpg_error_t tmperr = gpg_err_make (errsource, gpg_err_code_from_syserror ()); log_error ("error flushing pending output: %s\n", strerror (errno)); assuan_release (ctx); return tmperr; } argv[0] = "--daemon"; - if (try_system_daemon) - argv[1] = NULL; - else - { /* Try starting as user daemon - dirmngr does this if the - home directory is given on the command line. */ - argv[1] = "--homedir"; - argv[2] = abs_homedir; - argv[3] = NULL; - } - - /* On the use of HOMEDIR for locking: Under Windows HOMEDIR is - not used thus it does not matter. Under Unix we should - TRY_SYSTEM_DAEMON should never be true because - dirmngr_user_socket_name() won't return NULL. */ + /* Try starting the daemon. Versions of dirmngr < 2.1.15 do + * this only if the home directory is given on the command line. */ + argv[1] = "--homedir"; + argv[2] = abs_homedir; + argv[3] = NULL; if (!(err = lock_spawning (&lock, gnupg_homedir (), "dirmngr", verbose)) && assuan_socket_connect (ctx, sockname, 0, 0)) { err = gnupg_spawn_process_detached (dirmngr_program, argv, NULL); if (err) log_error ("failed to start the dirmngr '%s': %s\n", dirmngr_program, gpg_strerror (err)); else { int i; for (i=0; i < SECS_TO_WAIT_FOR_DIRMNGR; i++) { if (verbose) log_info (_("waiting for the dirmngr " "to come up ... (%ds)\n"), SECS_TO_WAIT_FOR_DIRMNGR - i); gnupg_sleep (1); err = assuan_socket_connect (ctx, sockname, 0, 0); if (!err) { if (verbose) { log_info (_("connection to the dirmngr" " established\n")); did_success_msg = 1; } break; } } } } unlock_spawning (&lock, "dirmngr"); xfree (abs_homedir); } #else (void)dirmngr_program; (void)verbose; (void)status_cb; (void)status_cb_arg; #endif /*USE_DIRMNGR_AUTO_START*/ if (err) { if (autostart || gpg_err_code (err) != GPG_ERR_ASS_CONNECT_FAILED) log_error ("connecting dirmngr at '%s' failed: %s\n", sockname, gpg_strerror (err)); assuan_release (ctx); return gpg_err_make (errsource, GPG_ERR_NO_DIRMNGR); } if (debug && !did_success_msg) log_debug ("connection to the dirmngr established\n"); *r_ctx = ctx; return 0; } /* Return the version of a server using "GETINFO version". On success 0 is returned and R_VERSION receives a malloced string with the version which must be freed by the caller. On error NULL is stored at R_VERSION and an error code returned. Mode is in general 0 but certian values may be used to modify the used version command: MODE == 0 = Use "GETINFO version" MODE == 2 - Use "SCD GETINFO version" */ gpg_error_t get_assuan_server_version (assuan_context_t ctx, int mode, char **r_version) { gpg_error_t err; membuf_t data; init_membuf (&data, 64); err = assuan_transact (ctx, mode == 2? "SCD GETINFO version" /**/ : "GETINFO version", put_membuf_cb, &data, NULL, NULL, NULL, NULL); if (err) { xfree (get_membuf (&data, NULL)); *r_version = NULL; } else { put_membuf (&data, "", 1); *r_version = get_membuf (&data, NULL); if (!*r_version) err = gpg_error_from_syserror (); } return err; } diff --git a/common/homedir.c b/common/homedir.c index 9a6902262..574561a6c 100644 --- a/common/homedir.c +++ b/common/homedir.c @@ -1,1021 +1,975 @@ /* homedir.c - Setup the home directory. * Copyright (C) 2004, 2006, 2007, 2010 Free Software Foundation, Inc. * Copyright (C) 2013, 2016 Werner Koch * * This file is part of GnuPG. * * This file is free software; you can redistribute it and/or modify * it under the terms of either * * - the GNU Lesser General Public License as published by the Free * Software Foundation; either version 3 of the License, or (at * your option) any later version. * * or * * - the GNU General Public License as published by the Free * Software Foundation; either version 2 of the License, or (at * your option) any later version. * * or both in parallel, as here. * * This file 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 General Public License * along with this program; if not, see . */ #include #include #include #include #include #ifdef HAVE_W32_SYSTEM #include /* Due to the stupid mingw64 requirement to include this header before windows.h which is often implicitly included. */ #include #ifndef CSIDL_APPDATA #define CSIDL_APPDATA 0x001a #endif #ifndef CSIDL_LOCAL_APPDATA #define CSIDL_LOCAL_APPDATA 0x001c #endif #ifndef CSIDL_COMMON_APPDATA #define CSIDL_COMMON_APPDATA 0x0023 #endif #ifndef CSIDL_FLAG_CREATE #define CSIDL_FLAG_CREATE 0x8000 #endif #endif /*HAVE_W32_SYSTEM*/ #ifdef HAVE_STAT #include /* for stat() */ #endif #include "util.h" #include "sysutils.h" #include "zb32.h" /* The GnuPG homedir. This is only accessed by the functions * gnupg_homedir and gnupg_set_homedir. Malloced. */ static char *the_gnupg_homedir; /* Flag indicating that home directory is not the default one. */ static byte non_default_homedir; #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 byte 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 as often used before GnuPG 2.1. */ static byte 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 w32_try_mkdir (const char *dir) { #ifdef HAVE_W32CE_SYSTEM wchar_t *wdir = utf8_to_wchar (dir); if (wdir) { CreateDirectory (wdir, NULL); xfree (wdir); } #else CreateDirectory (dir, NULL); #endif } #endif /* This is a helper function to load a Windows function from either of one DLLs. */ #ifdef HAVE_W32_SYSTEM 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; } #endif /*HAVE_W32_SYSTEM*/ /* Check whether DIR is the default homedir. */ static int is_gnupg_default_homedir (const char *dir) { int result; char *a = make_absfilename (dir, NULL); char *b = make_absfilename (GNUPG_DEFAULT_HOMEDIR, NULL); result = !compare_filenames (a, b); xfree (b); xfree (a); return result; } /* Get the standard home directory. In general this function should not be used as it does not consider a registry value (under W32) or the GNUPGHOME environment variable. It is better to use default_homedir(). */ const char * standard_homedir (void) { #ifdef HAVE_W32_SYSTEM static const char *dir; if (!dir) { const char *rdir; rdir = w32_rootdir (); if (w32_portable_app) { dir = xstrconcat (rdir, DIRSEP_S "home", NULL); } else { 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*/ return GNUPG_DEFAULT_HOMEDIR; #endif /*!HAVE_W32_SYSTEM*/ } /* Set up the default home directory. The usual --homedir option should be parsed later. */ const char * 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) { static const char *saved_dir; if (!saved_dir) { if (!dir || !*dir) { char *tmp; tmp = read_w32_registry_string (NULL, GNUPG_REGISTRY_DIR, "HomeDir"); if (tmp && !*tmp) { xfree (tmp); tmp = NULL; } if (tmp) saved_dir = tmp; } if (!saved_dir) saved_dir = standard_homedir (); } dir = saved_dir; } #endif /*HAVE_W32_SYSTEM*/ if (!dir || !*dir) dir = GNUPG_DEFAULT_HOMEDIR; else if (!is_gnupg_default_homedir (dir)) non_default_homedir = 1; return dir; } #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)) { 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 | GPGRT_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) { static int got_dir; static char dir[MAX_PATH+5]; if (!got_dir) { char *p; int rc; wchar_t wdir [MAX_PATH+5]; rc = GetModuleFileNameW (NULL, wdir, MAX_PATH); if (rc && WideCharToMultiByte (CP_UTF8, 0, wdir, -1, dir, MAX_PATH-4, NULL, NULL) < 0) rc = 0; if (!rc) { log_debug ("GetModuleFileName failed: %s\n", w32_strerror (-1)); *dir = 0; } got_dir = 1; p = strrchr (dir, DIRSEP_C); 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; w32_bin_is_bin = 1; } } if (!p) { log_debug ("bad filename '%s' returned for this process\n", dir); *dir = 0; } } if (*dir) return dir; /* Fallback to the hardwired value. */ return GNUPG_LIBEXECDIR; } static const char * w32_commondir (void) { static char *dir; 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) { char *tmp = xmalloc (strlen (path) + 4 +1); strcpy (stpcpy (tmp, path), "\\GNU"); dir = tmp; /* No auto create of the directory. Either the installer or the admin has to create these directories. */ } else { /* Ooops: Not defined - probably an old Windows version. Use the installation directory instead. */ dir = xstrdup (rdir); } } return dir; } #endif /*HAVE_W32_SYSTEM*/ /* Change the homedir. Some care must be taken to set this early * enough because previous calls to gnupg_homedir may else return a * different string. */ void gnupg_set_homedir (const char *newdir) { if (!newdir || !*newdir) newdir = default_homedir (); else if (!is_gnupg_default_homedir (newdir)) non_default_homedir = 1; xfree (the_gnupg_homedir); the_gnupg_homedir = make_absfilename (newdir, NULL);; } /* Return the homedir. The returned string is valid until another * gnupg-set-homedir call. This is always an absolute directory name. * The function replaces the former global var opt.homedir. */ const char * gnupg_homedir (void) { /* If a homedir has not been set, set it to the default. */ if (!the_gnupg_homedir) the_gnupg_homedir = make_absfilename (default_homedir (), NULL); return the_gnupg_homedir; } /* Return whether the home dir is the default one. */ int gnupg_default_homedir_p (void) { return !non_default_homedir; } /* Helper for gnupg-socketdir. This is a global function, so that * gpgconf can use it for its --create-socketdir command. If * SKIP_CHECKS is set permission checks etc. are not done. The * function always returns a malloced directory name and stores these * bit flags at R_INFO: * * 1 := Internal error, stat failed, out of core, etc. * 2 := No /run/user directory. * 4 := Directory not owned by the user, not a directory * or wrong permissions. * 8 := Same as 4 but for the subdir. * 16 := mkdir failed * 32 := Non default homedir; checking subdir. * 64 := Subdir does not exist. * 128 := Using homedir as fallback. */ char * _gnupg_socketdir_internal (int skip_checks, unsigned *r_info) { #if defined(HAVE_W32_SYSTEM) || !defined(HAVE_STAT) char *name; (void)skip_checks; *r_info = 0; name = xstrdup (gnupg_homedir ()); #else /* Unix and stat(2) available. */ static const char * const bases[] = { "/run", "/var/run", NULL}; int i; struct stat sb; char prefix[13 + 1 + 20 + 6 + 1]; const char *s; char *name = NULL; *r_info = 0; /* First make sure that non_default_homedir can be set. */ gnupg_homedir (); /* It has been suggested to first check XDG_RUNTIME_DIR envvar. * However, the specs state that the lifetime of the directory MUST * be bound to the user being logged in. Now GnuPG may also be run * as a background process with no (desktop) user logged in. Thus * we better don't do that. */ /* Check whether we have a /run/user dir. */ for (i=0; bases[i]; i++) { snprintf (prefix, sizeof prefix, "%s/user/%u", bases[i], (unsigned int)getuid ()); if (!stat (prefix, &sb) && S_ISDIR(sb.st_mode)) break; } if (!bases[i]) { *r_info |= 2; /* No /run/user directory. */ goto leave; } if (sb.st_uid != getuid ()) { *r_info |= 4; /* Not owned by the user. */ if (!skip_checks) goto leave; } if (strlen (prefix) + 7 >= sizeof prefix) { *r_info |= 1; /* Ooops: Buffer too short to append "/gnupg". */ goto leave; } strcat (prefix, "/gnupg"); /* Check whether the gnupg sub directory has proper permissions. */ if (stat (prefix, &sb)) { if (errno != ENOENT) { *r_info |= 1; /* stat failed. */ goto leave; } /* Try to create the directory and check again. */ if (gnupg_mkdir (prefix, "-rwx")) { *r_info |= 16; /* mkdir failed. */ goto leave; } if (stat (prefix, &sb)) { *r_info |= 1; /* stat failed. */ goto leave; } } /* Check that it is a directory, owned by the user, and only the * user has permissions to use it. */ if (!S_ISDIR(sb.st_mode) || sb.st_uid != getuid () || (sb.st_mode & (S_IRWXG|S_IRWXO))) { *r_info |= 4; /* Bad permissions or not a directory. */ if (!skip_checks) goto leave; } /* If a non default homedir is used, we check whether an * corresponding sub directory below the socket dir is available * and use that. We has the non default homedir to keep the new * subdir short enough. */ if (non_default_homedir) { char sha1buf[20]; char *suffix; *r_info |= 32; /* Testing subdir. */ s = gnupg_homedir (); gcry_md_hash_buffer (GCRY_MD_SHA1, sha1buf, s, strlen (s)); suffix = zb32_encode (sha1buf, 8*15); if (!suffix) { *r_info |= 1; /* Out of core etc. */ goto leave; } name = strconcat (prefix, "/d.", suffix, NULL); xfree (suffix); if (!name) { *r_info |= 1; /* Out of core etc. */ goto leave; } /* Stat that directory and check constraints. Note that we * do not auto create such a directory because we would not * have a way to remove it. Thus the directory needs to be * pre-created. The command * gpgconf --create-socketdir * can be used tocreate that directory. */ if (stat (name, &sb)) { if (errno != ENOENT) *r_info |= 1; /* stat failed. */ else *r_info |= 64; /* Subdir does not exist. */ if (!skip_checks) { xfree (name); name = NULL; goto leave; } } else if (!S_ISDIR(sb.st_mode) || sb.st_uid != getuid () || (sb.st_mode & (S_IRWXG|S_IRWXO))) { *r_info |= 8; /* Bad permissions or subdir is not a directory. */ if (!skip_checks) { xfree (name); name = NULL; goto leave; } } } else name = xstrdup (prefix); leave: /* If nothing works fall back to the homedir. */ if (!name) { *r_info |= 128; /* Fallback. */ name = xstrdup (gnupg_homedir ()); } #endif /* Unix */ return name; } /* * Return the name of the socket dir. That is the directory used for * the IPC local sockets. This is an absolute directory name. */ const char * gnupg_socketdir (void) { static char *name; if (!name) { unsigned int dummy; name = _gnupg_socketdir_internal (0, &dummy); } return name; } /* Return the name of the sysconfdir. This is a static string. This function is required because under Windows we can't simply compile it in. */ const char * gnupg_sysconfdir (void) { #ifdef HAVE_W32_SYSTEM static char *name; if (!name) { const char *s1, *s2; s1 = w32_commondir (); s2 = DIRSEP_S "etc" DIRSEP_S "gnupg"; name = xmalloc (strlen (s1) + strlen (s2) + 1); strcpy (stpcpy (name, s1), s2); } return name; #else /*!HAVE_W32_SYSTEM*/ return GNUPG_SYSCONFDIR; #endif /*!HAVE_W32_SYSTEM*/ } const char * gnupg_bindir (void) { #if defined (HAVE_W32CE_SYSTEM) static char *name; if (!name) name = xstrconcat (w32_rootdir (), DIRSEP_S "bin", NULL); return name; #elif defined(HAVE_W32_SYSTEM) 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*/ } /* Return the name of the libexec directory. The name is allocated in a static area on the first use. This function won't fail. */ const char * gnupg_libexecdir (void) { #ifdef HAVE_W32_SYSTEM return gnupg_bindir (); #else /*!HAVE_W32_SYSTEM*/ return GNUPG_LIBEXECDIR; #endif /*!HAVE_W32_SYSTEM*/ } const char * gnupg_libdir (void) { #ifdef HAVE_W32_SYSTEM static char *name; if (!name) name = xstrconcat (w32_rootdir (), DIRSEP_S "lib" DIRSEP_S "gnupg", NULL); return name; #else /*!HAVE_W32_SYSTEM*/ return GNUPG_LIBDIR; #endif /*!HAVE_W32_SYSTEM*/ } const char * gnupg_datadir (void) { #ifdef HAVE_W32_SYSTEM static char *name; if (!name) name = xstrconcat (w32_rootdir (), DIRSEP_S "share" DIRSEP_S "gnupg", NULL); return name; #else /*!HAVE_W32_SYSTEM*/ return GNUPG_DATADIR; #endif /*!HAVE_W32_SYSTEM*/ } const char * gnupg_localedir (void) { #ifdef HAVE_W32_SYSTEM static char *name; if (!name) name = xstrconcat (w32_rootdir (), DIRSEP_S "share" DIRSEP_S "locale", NULL); return name; #else /*!HAVE_W32_SYSTEM*/ return LOCALEDIR; #endif /*!HAVE_W32_SYSTEM*/ } /* Return the name of the cache directory. The name is allocated in a static area on the first use. Windows only: If the directory does not exist it is created. */ const char * gnupg_cachedir (void) { #ifdef HAVE_W32_SYSTEM static const char *dir; if (!dir) { const char *rdir; rdir = w32_rootdir (); if (w32_portable_app) { dir = xstrconcat (rdir, DIRSEP_S, "var", DIRSEP_S, "cache", DIRSEP_S, "gnupg", NULL); } else { 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); if (w32_shgetfolderpath (NULL, CSIDL_LOCAL_APPDATA|CSIDL_FLAG_CREATE, NULL, 0, path) >= 0) { char *tmp = xmalloc (strlen (path) + s1_len + 1); char *p; 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"); #endif } } } return dir; #else /*!HAVE_W32_SYSTEM*/ return GNUPG_LOCALSTATEDIR "/cache/" PACKAGE_NAME; #endif /*!HAVE_W32_SYSTEM*/ } -/* Return the system socket name used by DirMngr. */ +/* Return the user socket name used by DirMngr. */ const char * -dirmngr_sys_socket_name (void) -{ -#ifdef HAVE_W32_SYSTEM - static char *name; - - if (!name) - { - char *p; -# ifdef HAVE_W32CE_SYSTEM - const char *s1, *s2; - - s1 = default_homedir (); -# else - 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 DIRMNGR_SOCK_NAME; - name = xmalloc (strlen (s1) + strlen (s2) + 1); - strcpy (stpcpy (name, s1), s2); - for (p=name; *p; p++) - if (*p == '/') - *p = '\\'; - } - return name; -#else /*!HAVE_W32_SYSTEM*/ - return GNUPG_LOCALSTATEDIR "/run/" PACKAGE_NAME "/"DIRMNGR_SOCK_NAME; -#endif /*!HAVE_W32_SYSTEM*/ -} - - -/* Return the user socket name used by DirMngr. If a user specific - dirmngr installation is not supported, NULL is returned. */ -const char * -dirmngr_user_socket_name (void) +dirmngr_socket_name (void) { static char *name; if (!name) name = make_filename (gnupg_socketdir (), DIRMNGR_SOCK_NAME, NULL); return name; } /* Return the default pinentry name. If RESET is true the internal cache is first flushed. */ static const char * get_default_pinentry_name (int reset) { static struct { const char *(*rfnc)(void); const char *name; } names[] = { /* The first entry is what we return in case we found no other pinentry. */ { gnupg_bindir, DIRSEP_S "pinentry" EXEEXT_S }, #ifdef HAVE_W32_SYSTEM /* Try Gpg4win directory (with bin and without.) */ { w32_rootdir, "\\..\\Gpg4win\\bin\\pinentry.exe" }, { w32_rootdir, "\\..\\Gpg4win\\pinentry.exe" }, /* Try old Gpgwin directory. */ { w32_rootdir, "\\..\\GNU\\GnuPG\\pinentry.exe" }, /* Try a Pinentry from the common GNU dir. */ { w32_rootdir, "\\..\\GNU\\bin\\pinentry.exe" }, #endif /* Last chance is a pinentry-basic (which comes with the GnuPG 2.1 Windows installer). */ { gnupg_bindir, DIRSEP_S "pinentry-basic" EXEEXT_S } }; static char *name; if (reset) { xfree (name); name = NULL; } if (!name) { int i; for (i=0; i < DIM(names); i++) { char *name2; name2 = xstrconcat (names[i].rfnc (), names[i].name, NULL); if (!access (name2, F_OK)) { /* Use that pinentry. */ xfree (name); name = name2; break; } if (!i) /* Store the first as fallback return. */ name = name2; else xfree (name2); } } return name; } /* Return the file name of a helper tool. WHICH is one of the GNUPG_MODULE_NAME_foo constants. */ const char * gnupg_module_name (int which) { #define X(a,b) do { \ static char *name; \ if (!name) \ name = xstrconcat (gnupg_ ## a (), DIRSEP_S b EXEEXT_S, NULL); \ return name; \ } while (0) switch (which) { case GNUPG_MODULE_NAME_AGENT: #ifdef GNUPG_DEFAULT_AGENT return GNUPG_DEFAULT_AGENT; #else X(bindir, "gpg-agent"); #endif case GNUPG_MODULE_NAME_PINENTRY: #ifdef GNUPG_DEFAULT_PINENTRY return GNUPG_DEFAULT_PINENTRY; /* (Set by a configure option) */ #else return get_default_pinentry_name (0); #endif case GNUPG_MODULE_NAME_SCDAEMON: #ifdef GNUPG_DEFAULT_SCDAEMON return GNUPG_DEFAULT_SCDAEMON; #else X(libexecdir, "scdaemon"); #endif case GNUPG_MODULE_NAME_DIRMNGR: #ifdef GNUPG_DEFAULT_DIRMNGR return GNUPG_DEFAULT_DIRMNGR; #else X(bindir, DIRMNGR_NAME); #endif case GNUPG_MODULE_NAME_PROTECT_TOOL: #ifdef GNUPG_DEFAULT_PROTECT_TOOL return GNUPG_DEFAULT_PROTECT_TOOL; #else X(libexecdir, "gpg-protect-tool"); #endif case GNUPG_MODULE_NAME_DIRMNGR_LDAP: #ifdef GNUPG_DEFAULT_DIRMNGR_LDAP return GNUPG_DEFAULT_DIRMNGR_LDAP; #else X(libexecdir, "dirmngr_ldap"); #endif case GNUPG_MODULE_NAME_CHECK_PATTERN: X(libexecdir, "gpg-check-pattern"); case GNUPG_MODULE_NAME_GPGSM: X(bindir, "gpgsm"); case GNUPG_MODULE_NAME_GPG: #if USE_GPG2_HACK X(bindir, GPG_NAME "2"); #else X(bindir, GPG_NAME); #endif case GNUPG_MODULE_NAME_CONNECT_AGENT: X(bindir, "gpg-connect-agent"); case GNUPG_MODULE_NAME_GPGCONF: X(bindir, "gpgconf"); default: BUG (); } #undef X } /* Flush some of the cached module names. This is for example used by gpg-agent to allow configuring a different pinentry. */ void gnupg_module_name_flush_some (void) { (void)get_default_pinentry_name (1); } diff --git a/common/util.h b/common/util.h index 6680414d7..1c3cce9a8 100644 --- a/common/util.h +++ b/common/util.h @@ -1,361 +1,360 @@ /* util.h - Utility functions for GnuPG * Copyright (C) 2001, 2002, 2003, 2004, 2009 Free Software Foundation, Inc. * * This file is part of GnuPG. * * GnuPG is free software; you can redistribute it and/or modify it * under the terms of either * * - the GNU Lesser General Public License as published by the Free * Software Foundation; either version 3 of the License, or (at * your option) any later version. * * or * * - the GNU General Public License as published by the Free * Software Foundation; either version 2 of the License, or (at * your option) any later version. * * or both in parallel, as here. * * GnuPG 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 copies of the GNU General Public License * and the GNU Lesser General Public License along with this program; * if not, see . */ #ifndef GNUPG_COMMON_UTIL_H #define GNUPG_COMMON_UTIL_H #include /* We need this for the memory function protos. */ #include /* We need errno. */ #include /* We need gpg_error_t and estream. */ /* These error codes are used but not defined in the required * libgpg-error version. Define them here. * Example: (#if GPG_ERROR_VERSION_NUMBER < 0x011500 // 1.21) */ /* Hash function used with libksba. */ #define HASH_FNC ((void (*)(void *, const void*,size_t))gcry_md_write) /* Get all the stuff from jnlib. */ #include "../common/logging.h" #include "../common/argparse.h" #include "../common/stringhelp.h" #include "../common/mischelp.h" #include "../common/strlist.h" #include "../common/dotlock.h" #include "../common/utf8conv.h" #include "../common/dynload.h" #include "../common/fwddecl.h" #include "../common/utilproto.h" #include "gettime.h" /* Redefine asprintf by our estream version which uses our own memory allocator.. */ #define asprintf gpgrt_asprintf #define vasprintf gpgrt_vasprintf /* Due to a bug in mingw32's snprintf related to the 'l' modifier and for increased portability we use our snprintf on all systems. */ #undef snprintf #define snprintf gpgrt_snprintf /* Replacements for macros not available with libgpg-error < 1.20. */ /* We need this type even if we are not using libreadline and or we did not include libreadline in the current file. */ #ifndef GNUPG_LIBREADLINE_H_INCLUDED typedef char **rl_completion_func_t (const char *, int, int); #endif /*!GNUPG_LIBREADLINE_H_INCLUDED*/ /* Handy malloc macros - please use only them. */ #define xtrymalloc(a) gcry_malloc ((a)) #define xtrymalloc_secure(a) gcry_malloc_secure ((a)) #define xtrycalloc(a,b) gcry_calloc ((a),(b)) #define xtrycalloc_secure(a,b) gcry_calloc_secure ((a),(b)) #define xtryrealloc(a,b) gcry_realloc ((a),(b)) #define xtrystrdup(a) gcry_strdup ((a)) #define xfree(a) gcry_free ((a)) #define xfree_fnc gcry_free #define xmalloc(a) gcry_xmalloc ((a)) #define xmalloc_secure(a) gcry_xmalloc_secure ((a)) #define xcalloc(a,b) gcry_xcalloc ((a),(b)) #define xcalloc_secure(a,b) gcry_xcalloc_secure ((a),(b)) #define xrealloc(a,b) gcry_xrealloc ((a),(b)) #define xstrdup(a) gcry_xstrdup ((a)) /* For compatibility with gpg 1.4 we also define these: */ #define xmalloc_clear(a) gcry_xcalloc (1, (a)) #define xmalloc_secure_clear(a) gcry_xcalloc_secure (1, (a)) /* The default error source of the application. This is different from GPG_ERR_SOURCE_DEFAULT in that it does not depend on the source file and thus is usable in code shared by applications. Defined by init.c. */ extern gpg_err_source_t default_errsource; /* Convenience function to return a gpg-error code for memory allocation failures. This function makes sure that an error will be returned even if accidentally ERRNO is not set. */ static inline gpg_error_t out_of_core (void) { return gpg_error_from_syserror (); } /*-- yesno.c --*/ int answer_is_yes (const char *s); int answer_is_yes_no_default (const char *s, int def_answer); int answer_is_yes_no_quit (const char *s); int answer_is_okay_cancel (const char *s, int def_answer); /*-- xreadline.c --*/ ssize_t read_line (FILE *fp, char **addr_of_buffer, size_t *length_of_buffer, size_t *max_length); /*-- b64enc.c and b64dec.c --*/ struct b64state { unsigned int flags; int idx; int quad_count; FILE *fp; estream_t stream; char *title; unsigned char radbuf[4]; u32 crc; int stop_seen:1; int invalid_encoding:1; gpg_error_t lasterr; }; gpg_error_t b64enc_start (struct b64state *state, FILE *fp, const char *title); gpg_error_t b64enc_start_es (struct b64state *state, estream_t fp, const char *title); gpg_error_t b64enc_write (struct b64state *state, const void *buffer, size_t nbytes); gpg_error_t b64enc_finish (struct b64state *state); gpg_error_t b64dec_start (struct b64state *state, const char *title); gpg_error_t b64dec_proc (struct b64state *state, void *buffer, size_t length, size_t *r_nbytes); gpg_error_t b64dec_finish (struct b64state *state); /*-- sexputil.c */ char *canon_sexp_to_string (const unsigned char *canon, size_t canonlen); void log_printcanon (const char *text, const unsigned char *sexp, size_t sexplen); void log_printsexp (const char *text, gcry_sexp_t sexp); gpg_error_t make_canon_sexp (gcry_sexp_t sexp, unsigned char **r_buffer, size_t *r_buflen); gpg_error_t make_canon_sexp_pad (gcry_sexp_t sexp, int secure, unsigned char **r_buffer, size_t *r_buflen); gpg_error_t keygrip_from_canon_sexp (const unsigned char *key, size_t keylen, unsigned char *grip); int cmp_simple_canon_sexp (const unsigned char *a, const unsigned char *b); unsigned char *make_simple_sexp_from_hexstr (const char *line, size_t *nscanned); int hash_algo_from_sigval (const unsigned char *sigval); unsigned char *make_canon_sexp_from_rsa_pk (const void *m, size_t mlen, const void *e, size_t elen, size_t *r_len); gpg_error_t get_rsa_pk_from_canon_sexp (const unsigned char *keydata, size_t keydatalen, unsigned char const **r_n, size_t *r_nlen, unsigned char const **r_e, size_t *r_elen); gpg_error_t get_pk_algo_from_canon_sexp (const unsigned char *keydata, size_t keydatalen, const char **r_algo); int get_pk_algo_from_key (gcry_sexp_t key); /*-- convert.c --*/ int hex2bin (const char *string, void *buffer, size_t length); int hexcolon2bin (const char *string, void *buffer, size_t length); char *bin2hex (const void *buffer, size_t length, char *stringbuf); char *bin2hexcolon (const void *buffer, size_t length, char *stringbuf); const char *hex2str (const char *hexstring, char *buffer, size_t bufsize, size_t *buflen); char *hex2str_alloc (const char *hexstring, size_t *r_count); /*-- percent.c --*/ char *percent_plus_escape (const char *string); char *percent_plus_unescape (const char *string, int nulrepl); char *percent_unescape (const char *string, int nulrepl); size_t percent_plus_unescape_inplace (char *string, int nulrepl); size_t percent_unescape_inplace (char *string, int nulrepl); /*-- openpgp-oid.c --*/ gpg_error_t openpgp_oid_from_str (const char *string, gcry_mpi_t *r_mpi); char *openpgp_oid_to_str (gcry_mpi_t a); int openpgp_oid_is_ed25519 (gcry_mpi_t a); int openpgp_oid_is_crv25519 (gcry_mpi_t a); const char *openpgp_curve_to_oid (const char *name, unsigned int *r_nbits); const char *openpgp_oid_to_curve (const char *oid, int canon); const char *openpgp_enum_curves (int *idxp); const char *openpgp_is_curve_supported (const char *name, int *r_algo); /*-- homedir.c --*/ const char *standard_homedir (void); const char *default_homedir (void); void gnupg_set_homedir (const char *newdir); const char *gnupg_homedir (void); int gnupg_default_homedir_p (void); const char *gnupg_socketdir (void); const char *gnupg_sysconfdir (void); const char *gnupg_bindir (void); const char *gnupg_libexecdir (void); const char *gnupg_libdir (void); const char *gnupg_datadir (void); const char *gnupg_localedir (void); const char *gnupg_cachedir (void); -const char *dirmngr_sys_socket_name (void); -const char *dirmngr_user_socket_name (void); +const char *dirmngr_socket_name (void); char *_gnupg_socketdir_internal (int skip_checks, unsigned *r_info); /* All module names. We also include gpg and gpgsm for the sake for gpgconf. */ #define GNUPG_MODULE_NAME_AGENT 1 #define GNUPG_MODULE_NAME_PINENTRY 2 #define GNUPG_MODULE_NAME_SCDAEMON 3 #define GNUPG_MODULE_NAME_DIRMNGR 4 #define GNUPG_MODULE_NAME_PROTECT_TOOL 5 #define GNUPG_MODULE_NAME_CHECK_PATTERN 6 #define GNUPG_MODULE_NAME_GPGSM 7 #define GNUPG_MODULE_NAME_GPG 8 #define GNUPG_MODULE_NAME_CONNECT_AGENT 9 #define GNUPG_MODULE_NAME_GPGCONF 10 #define GNUPG_MODULE_NAME_DIRMNGR_LDAP 11 const char *gnupg_module_name (int which); void gnupg_module_name_flush_some (void); /*-- gpgrlhelp.c --*/ void gnupg_rl_initialize (void); /*-- helpfile.c --*/ char *gnupg_get_help_string (const char *key, int only_current_locale); /*-- localename.c --*/ const char *gnupg_messages_locale_name (void); /*-- miscellaneous.c --*/ /* This function is called at startup to tell libgcrypt to use our own logging subsystem. */ void setup_libgcrypt_logging (void); /* Print an out of core emssage and die. */ void xoutofcore (void); /* Same as estream_asprintf but die on memory failure. */ char *xasprintf (const char *fmt, ...) GPGRT_ATTR_PRINTF(1,2); /* This is now an alias to estream_asprintf. */ char *xtryasprintf (const char *fmt, ...) GPGRT_ATTR_PRINTF(1,2); /* Replacement for gcry_cipher_algo_name. */ const char *gnupg_cipher_algo_name (int algo); void obsolete_option (const char *configname, unsigned int configlineno, const char *name); const char *print_fname_stdout (const char *s); const char *print_fname_stdin (const char *s); void print_utf8_buffer3 (estream_t fp, const void *p, size_t n, const char *delim); void print_utf8_buffer2 (estream_t fp, const void *p, size_t n, int delim); void print_utf8_buffer (estream_t fp, const void *p, size_t n); void print_hexstring (FILE *fp, const void *buffer, size_t length, int reserved); char *try_make_printable_string (const void *p, size_t n, int delim); char *make_printable_string (const void *p, size_t n, int delim); int is_file_compressed (const char *s, int *ret_rc); int match_multistr (const char *multistr,const char *match); int gnupg_compare_version (const char *a, const char *b); struct debug_flags_s { unsigned int flag; const char *name; }; int parse_debug_flag (const char *string, unsigned int *debugvar, const struct debug_flags_s *flags); /*-- Simple replacement functions. */ /* We use the gnupg_ttyname macro to be safe not to run into conflicts which an extisting but broken ttyname. */ #if !defined(HAVE_TTYNAME) || defined(HAVE_BROKEN_TTYNAME) # define gnupg_ttyname(n) _gnupg_ttyname ((n)) /* Systems without ttyname (W32) will merely return NULL. */ static inline char * _gnupg_ttyname (int fd) { (void)fd; return NULL; } #else /*HAVE_TTYNAME*/ # define gnupg_ttyname(n) ttyname ((n)) #endif /*HAVE_TTYNAME */ #ifdef HAVE_W32CE_SYSTEM #define getpid() GetCurrentProcessId () char *_gnupg_getenv (const char *name); /* See sysutils.c */ #define getenv(a) _gnupg_getenv ((a)) char *_gnupg_setenv (const char *name); /* See sysutils.c */ #define setenv(a,b,c) _gnupg_setenv ((a),(b),(c)) int _gnupg_isatty (int fd); #define gnupg_isatty(a) _gnupg_isatty ((a)) #else #define gnupg_isatty(a) isatty ((a)) #endif /*-- Macros to replace ctype ones to avoid locale problems. --*/ #define spacep(p) (*(p) == ' ' || *(p) == '\t') #define digitp(p) (*(p) >= '0' && *(p) <= '9') #define alphap(p) ((*(p) >= 'A' && *(p) <= 'Z') \ || (*(p) >= 'a' && *(p) <= 'z')) #define alnump(p) (alphap (p) || digitp (p)) #define hexdigitp(a) (digitp (a) \ || (*(a) >= 'A' && *(a) <= 'F') \ || (*(a) >= 'a' && *(a) <= 'f')) /* Note this isn't identical to a C locale isspace() without \f and \v, but works for the purposes used here. */ #define ascii_isspace(a) ((a)==' ' || (a)=='\n' || (a)=='\r' || (a)=='\t') /* The atoi macros assume that the buffer has only valid digits. */ #define atoi_1(p) (*(p) - '0' ) #define atoi_2(p) ((atoi_1(p) * 10) + atoi_1((p)+1)) #define atoi_4(p) ((atoi_2(p) * 100) + atoi_2((p)+2)) #define xtoi_1(p) (*(p) <= '9'? (*(p)- '0'): \ *(p) <= 'F'? (*(p)-'A'+10):(*(p)-'a'+10)) #define xtoi_2(p) ((xtoi_1(p) * 16) + xtoi_1((p)+1)) #define xtoi_4(p) ((xtoi_2(p) * 256) + xtoi_2((p)+2)) #endif /*GNUPG_COMMON_UTIL_H*/ diff --git a/dirmngr/certcache.c b/dirmngr/certcache.c index 45be1f29f..9e741c131 100644 --- a/dirmngr/certcache.c +++ b/dirmngr/certcache.c @@ -1,1394 +1,1391 @@ /* certcache.c - Certificate caching * Copyright (C) 2004, 2005, 2007, 2008 g10 Code GmbH * * This file is part of DirMngr. * * DirMngr is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * DirMngr 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 General Public License * along with this program; if not, see . */ #include #include #include #include #include #include #include #include #include "dirmngr.h" #include "misc.h" #include "crlfetch.h" #include "certcache.h" #define MAX_EXTRA_CACHED_CERTS 1000 /* Constants used to classify search patterns. */ enum pattern_class { PATTERN_UNKNOWN = 0, PATTERN_EMAIL, PATTERN_EMAIL_SUBSTR, PATTERN_FINGERPRINT16, PATTERN_FINGERPRINT20, PATTERN_SHORT_KEYID, PATTERN_LONG_KEYID, PATTERN_SUBJECT, PATTERN_SERIALNO, PATTERN_SERIALNO_ISSUER, PATTERN_ISSUER, PATTERN_SUBSTR }; /* A certificate cache item. This consists of a the KSBA cert object and some meta data for easier lookup. We use a hash table to keep track of all items and use the (randomly distributed) first byte of the fingerprint directly as the hash which makes it pretty easy. */ struct cert_item_s { struct cert_item_s *next; /* Next item with the same hash value. */ ksba_cert_t cert; /* The KSBA cert object or NULL is this is not a valid item. */ unsigned char fpr[20]; /* The fingerprint of this object. */ char *issuer_dn; /* The malloced issuer DN. */ ksba_sexp_t sn; /* The malloced serial number */ char *subject_dn; /* The malloced subject DN - maybe NULL. */ struct { unsigned int loaded:1; /* It has been explicitly loaded. */ unsigned int trusted:1; /* This is a trusted root certificate. */ } flags; }; typedef struct cert_item_s *cert_item_t; /* The actual cert cache consisting of 256 slots for items indexed by the first byte of the fingerprint. */ static cert_item_t cert_cache[256]; /* This is the global cache_lock variable. In general locking is not needed but it would take extra efforts to make sure that no indirect use of npth functions is done, so we simply lock it always. Note: We can't use static initialization, as that is not available through w32-pth. */ static npth_rwlock_t cert_cache_lock; /* Flag to track whether the cache has been initialized. */ static int initialization_done; /* Total number of certificates loaded during initialization and cached during operation. */ static unsigned int total_loaded_certificates; static unsigned int total_extra_certificates; /* Helper to do the cache locking. */ static void init_cache_lock (void) { int err; err = npth_rwlock_init (&cert_cache_lock, NULL); if (err) log_fatal (_("can't initialize certificate cache lock: %s\n"), strerror (err)); } static void acquire_cache_read_lock (void) { int err; err = npth_rwlock_rdlock (&cert_cache_lock); if (err) log_fatal (_("can't acquire read lock on the certificate cache: %s\n"), strerror (err)); } static void acquire_cache_write_lock (void) { int err; err = npth_rwlock_wrlock (&cert_cache_lock); if (err) log_fatal (_("can't acquire write lock on the certificate cache: %s\n"), strerror (err)); } static void release_cache_lock (void) { int err; err = npth_rwlock_unlock (&cert_cache_lock); if (err) log_fatal (_("can't release lock on the certificate cache: %s\n"), strerror (err)); } /* Return false if both serial numbers match. Can't be used for sorting. */ static int compare_serialno (ksba_sexp_t serial1, ksba_sexp_t serial2 ) { unsigned char *a = serial1; unsigned char *b = serial2; return cmp_simple_canon_sexp (a, b); } /* Return a malloced canonical S-Expression with the serial number converted from the hex string HEXSN. Return NULL on memory error. */ ksba_sexp_t hexsn_to_sexp (const char *hexsn) { char *buffer, *p; size_t len; char numbuf[40]; len = unhexify (NULL, hexsn); snprintf (numbuf, sizeof numbuf, "(%u:", (unsigned int)len); buffer = xtrymalloc (strlen (numbuf) + len + 2 ); if (!buffer) return NULL; p = stpcpy (buffer, numbuf); len = unhexify (p, hexsn); p[len] = ')'; p[len+1] = 0; return buffer; } /* Compute the fingerprint of the certificate CERT and put it into the 20 bytes large buffer DIGEST. Return address of this buffer. */ unsigned char * cert_compute_fpr (ksba_cert_t cert, unsigned char *digest) { gpg_error_t err; gcry_md_hd_t md; err = gcry_md_open (&md, GCRY_MD_SHA1, 0); if (err) log_fatal ("gcry_md_open failed: %s\n", gpg_strerror (err)); err = ksba_cert_hash (cert, 0, HASH_FNC, md); if (err) { log_error ("oops: ksba_cert_hash failed: %s\n", gpg_strerror (err)); memset (digest, 0xff, 20); /* Use a dummy value. */ } else { gcry_md_final (md); memcpy (digest, gcry_md_read (md, GCRY_MD_SHA1), 20); } gcry_md_close (md); return digest; } /* Cleanup one slot. This releases all resourses but keeps the actual slot in the cache marked for reuse. */ static void clean_cache_slot (cert_item_t ci) { ksba_cert_t cert; if (!ci->cert) return; /* Already cleaned. */ ksba_free (ci->sn); ci->sn = NULL; ksba_free (ci->issuer_dn); ci->issuer_dn = NULL; ksba_free (ci->subject_dn); ci->subject_dn = NULL; cert = ci->cert; ci->cert = NULL; ksba_cert_release (cert); } /* Put the certificate CERT into the cache. It is assumed that the cache is locked while this function is called. If FPR_BUFFER is not NULL the fingerprint of the certificate will be stored there. FPR_BUFFER neds to point to a buffer of at least 20 bytes. The fingerprint will be stored on success or when the function returns gpg_err_code(GPG_ERR_DUP_VALUE). */ static gpg_error_t put_cert (ksba_cert_t cert, int is_loaded, int is_trusted, void *fpr_buffer) { unsigned char help_fpr_buffer[20], *fpr; cert_item_t ci; fpr = fpr_buffer? fpr_buffer : &help_fpr_buffer; /* If we already reached the caching limit, drop a couple of certs from the cache. Our dropping strategy is simple: We keep a static index counter and use this to start looking for certificates, then we drop 5 percent of the oldest certificates starting at that index. For a large cache this is a fair way of removing items. An LRU strategy would be better of course. Because we append new entries to the head of the list and we want to remove old ones first, we need to do this from the tail. The implementation is not very efficient but compared to the long time it takes to retrieve a certifciate from an external resource it seems to be reasonable. */ if (!is_loaded && total_extra_certificates >= MAX_EXTRA_CACHED_CERTS) { static int idx; cert_item_t ci_mark; int i; unsigned int drop_count; drop_count = MAX_EXTRA_CACHED_CERTS / 20; if (drop_count < 2) drop_count = 2; log_info (_("dropping %u certificates from the cache\n"), drop_count); assert (idx < 256); for (i=idx; drop_count; i = ((i+1)%256)) { ci_mark = NULL; for (ci = cert_cache[i]; ci; ci = ci->next) if (ci->cert && !ci->flags.loaded) ci_mark = ci; if (ci_mark) { clean_cache_slot (ci_mark); drop_count--; total_extra_certificates--; } } if (i==idx) idx++; else idx = i; idx %= 256; } cert_compute_fpr (cert, fpr); for (ci=cert_cache[*fpr]; ci; ci = ci->next) if (ci->cert && !memcmp (ci->fpr, fpr, 20)) return gpg_error (GPG_ERR_DUP_VALUE); /* Try to reuse an existing entry. */ for (ci=cert_cache[*fpr]; ci; ci = ci->next) if (!ci->cert) break; if (!ci) { /* No: Create a new entry. */ ci = xtrycalloc (1, sizeof *ci); if (!ci) return gpg_error_from_errno (errno); ci->next = cert_cache[*fpr]; cert_cache[*fpr] = ci; } else memset (&ci->flags, 0, sizeof ci->flags); ksba_cert_ref (cert); ci->cert = cert; memcpy (ci->fpr, fpr, 20); ci->sn = ksba_cert_get_serial (cert); ci->issuer_dn = ksba_cert_get_issuer (cert, 0); if (!ci->issuer_dn || !ci->sn) { clean_cache_slot (ci); return gpg_error (GPG_ERR_INV_CERT_OBJ); } ci->subject_dn = ksba_cert_get_subject (cert, 0); ci->flags.loaded = !!is_loaded; ci->flags.trusted = !!is_trusted; if (is_loaded) total_loaded_certificates++; else total_extra_certificates++; return 0; } /* Load certificates from the directory DIRNAME. All certificates matching the pattern "*.crt" or "*.der" are loaded. We assume that certificates are DER encoded and not PEM encapsulated. The cache should be in a locked state when calling this function. */ static gpg_error_t load_certs_from_dir (const char *dirname, int are_trusted) { gpg_error_t err; DIR *dir; struct dirent *ep; char *p; size_t n; estream_t fp; ksba_reader_t reader; ksba_cert_t cert; char *fname = NULL; dir = opendir (dirname); if (!dir) { - if (opt.system_daemon) - log_info (_("can't access directory '%s': %s\n"), - dirname, strerror (errno)); return 0; /* We do not consider this a severe error. */ } while ( (ep=readdir (dir)) ) { p = ep->d_name; if (*p == '.' || !*p) continue; /* Skip any hidden files and invalid entries. */ n = strlen (p); if ( n < 5 || (strcmp (p+n-4,".crt") && strcmp (p+n-4,".der"))) continue; /* Not the desired "*.crt" or "*.der" pattern. */ xfree (fname); fname = make_filename (dirname, p, NULL); fp = es_fopen (fname, "rb"); if (!fp) { log_error (_("can't open '%s': %s\n"), fname, strerror (errno)); continue; } err = create_estream_ksba_reader (&reader, fp); if (err) { es_fclose (fp); continue; } err = ksba_cert_new (&cert); if (!err) err = ksba_cert_read_der (cert, reader); ksba_reader_release (reader); es_fclose (fp); if (err) { log_error (_("can't parse certificate '%s': %s\n"), fname, gpg_strerror (err)); ksba_cert_release (cert); continue; } err = put_cert (cert, 1, are_trusted, NULL); if (gpg_err_code (err) == GPG_ERR_DUP_VALUE) log_info (_("certificate '%s' already cached\n"), fname); else if (!err) { if (are_trusted) log_info (_("trusted certificate '%s' loaded\n"), fname); else log_info (_("certificate '%s' loaded\n"), fname); if (opt.verbose) { p = get_fingerprint_hexstring_colon (cert); log_info (_(" SHA1 fingerprint = %s\n"), p); xfree (p); cert_log_name (_(" issuer ="), cert); cert_log_subject (_(" subject ="), cert); } } else log_error (_("error loading certificate '%s': %s\n"), fname, gpg_strerror (err)); ksba_cert_release (cert); } xfree (fname); closedir (dir); return 0; } /* Initialize the certificate cache if not yet done. */ void cert_cache_init (void) { char *dname; if (initialization_done) return; init_cache_lock (); acquire_cache_write_lock (); dname = make_filename (gnupg_sysconfdir (), "trusted-certs", NULL); load_certs_from_dir (dname, 1); xfree (dname); dname = make_filename (gnupg_sysconfdir (), "extra-certs", NULL); load_certs_from_dir (dname, 0); xfree (dname); initialization_done = 1; release_cache_lock (); cert_cache_print_stats (); } /* Deinitialize the certificate cache. With FULL set to true even the unused certificate slots are released. */ void cert_cache_deinit (int full) { cert_item_t ci, ci2; int i; if (!initialization_done) return; acquire_cache_write_lock (); for (i=0; i < 256; i++) for (ci=cert_cache[i]; ci; ci = ci->next) clean_cache_slot (ci); if (full) { for (i=0; i < 256; i++) { for (ci=cert_cache[i]; ci; ci = ci2) { ci2 = ci->next; xfree (ci); } cert_cache[i] = NULL; } } total_loaded_certificates = 0; total_extra_certificates = 0; initialization_done = 0; release_cache_lock (); } /* Print some statistics to the log file. */ void cert_cache_print_stats (void) { log_info (_("permanently loaded certificates: %u\n"), total_loaded_certificates); log_info (_(" runtime cached certificates: %u\n"), total_extra_certificates); } /* Put CERT into the certificate cache. */ gpg_error_t cache_cert (ksba_cert_t cert) { gpg_error_t err; acquire_cache_write_lock (); err = put_cert (cert, 0, 0, NULL); release_cache_lock (); if (gpg_err_code (err) == GPG_ERR_DUP_VALUE) log_info (_("certificate already cached\n")); else if (!err) log_info (_("certificate cached\n")); else log_error (_("error caching certificate: %s\n"), gpg_strerror (err)); return err; } /* Put CERT into the certificate cache and store the fingerprint of the certificate into FPR_BUFFER. If the certificate is already in the cache do not print a warning; just store the fingerprint. FPR_BUFFER needs to be at least 20 bytes. */ gpg_error_t cache_cert_silent (ksba_cert_t cert, void *fpr_buffer) { gpg_error_t err; acquire_cache_write_lock (); err = put_cert (cert, 0, 0, fpr_buffer); release_cache_lock (); if (gpg_err_code (err) == GPG_ERR_DUP_VALUE) err = 0; if (err) log_error (_("error caching certificate: %s\n"), gpg_strerror (err)); return err; } /* Return a certificate object for the given fingerprint. FPR is expected to be a 20 byte binary SHA-1 fingerprint. If no matching certificate is available in the cache NULL is returned. The caller must release a returned certificate. Note that although we are using reference counting the caller should not just compare the pointers to check for identical certificates. */ ksba_cert_t get_cert_byfpr (const unsigned char *fpr) { cert_item_t ci; acquire_cache_read_lock (); for (ci=cert_cache[*fpr]; ci; ci = ci->next) if (ci->cert && !memcmp (ci->fpr, fpr, 20)) { ksba_cert_ref (ci->cert); release_cache_lock (); return ci->cert; } release_cache_lock (); return NULL; } /* Return a certificate object for the given fingerprint. STRING is expected to be a SHA-1 fingerprint in standard hex notation with or without colons. If no matching certificate is available in the cache NULL is returned. The caller must release a returned certificate. Note that although we are using reference counting the caller should not just compare the pointers to check for identical certificates. */ ksba_cert_t get_cert_byhexfpr (const char *string) { unsigned char fpr[20]; const char *s; int i; if (strchr (string, ':')) { for (s=string,i=0; i < 20 && hexdigitp (s) && hexdigitp(s+1);) { if (s[2] && s[2] != ':') break; /* Invalid string. */ fpr[i++] = xtoi_2 (s); s += 2; if (i!= 20 && *s == ':') s++; } } else { for (s=string,i=0; i < 20 && hexdigitp (s) && hexdigitp(s+1); s+=2 ) fpr[i++] = xtoi_2 (s); } if (i!=20 || *s) { log_error (_("invalid SHA1 fingerprint string '%s'\n"), string); return NULL; } return get_cert_byfpr (fpr); } /* Return the certificate matching ISSUER_DN and SERIALNO. */ ksba_cert_t get_cert_bysn (const char *issuer_dn, ksba_sexp_t serialno) { /* Simple and inefficient implementation. fixme! */ cert_item_t ci; int i; acquire_cache_read_lock (); for (i=0; i < 256; i++) { for (ci=cert_cache[i]; ci; ci = ci->next) if (ci->cert && !strcmp (ci->issuer_dn, issuer_dn) && !compare_serialno (ci->sn, serialno)) { ksba_cert_ref (ci->cert); release_cache_lock (); return ci->cert; } } release_cache_lock (); return NULL; } /* Return the certificate matching ISSUER_DN. SEQ should initially be set to 0 and bumped up to get the next issuer with that DN. */ ksba_cert_t get_cert_byissuer (const char *issuer_dn, unsigned int seq) { /* Simple and very inefficient implementation and API. fixme! */ cert_item_t ci; int i; acquire_cache_read_lock (); for (i=0; i < 256; i++) { for (ci=cert_cache[i]; ci; ci = ci->next) if (ci->cert && !strcmp (ci->issuer_dn, issuer_dn)) if (!seq--) { ksba_cert_ref (ci->cert); release_cache_lock (); return ci->cert; } } release_cache_lock (); return NULL; } /* Return the certificate matching SUBJECT_DN. SEQ should initially be set to 0 and bumped up to get the next subject with that DN. */ ksba_cert_t get_cert_bysubject (const char *subject_dn, unsigned int seq) { /* Simple and very inefficient implementation and API. fixme! */ cert_item_t ci; int i; if (!subject_dn) return NULL; acquire_cache_read_lock (); for (i=0; i < 256; i++) { for (ci=cert_cache[i]; ci; ci = ci->next) if (ci->cert && ci->subject_dn && !strcmp (ci->subject_dn, subject_dn)) if (!seq--) { ksba_cert_ref (ci->cert); release_cache_lock (); return ci->cert; } } release_cache_lock (); return NULL; } /* Return a value describing the the class of PATTERN. The offset of the actual string to be used for the comparison is stored at R_OFFSET. The offset of the serialnumer is stored at R_SN_OFFSET. */ static enum pattern_class classify_pattern (const char *pattern, size_t *r_offset, size_t *r_sn_offset) { enum pattern_class result; const char *s; int hexprefix = 0; int hexlength; *r_offset = *r_sn_offset = 0; /* Skip leading spaces. */ for(s = pattern; *s && spacep (s); s++ ) ; switch (*s) { case 0: /* Empty string is an error. */ result = PATTERN_UNKNOWN; break; case '.': /* An email address, compare from end. */ result = PATTERN_UNKNOWN; /* Not implemented. */ break; case '<': /* An email address. */ result = PATTERN_EMAIL; s++; break; case '@': /* Part of an email address. */ result = PATTERN_EMAIL_SUBSTR; s++; break; case '=': /* Exact compare. */ result = PATTERN_UNKNOWN; /* Does not make sense for X.509. */ break; case '*': /* Case insensitive substring search. */ result = PATTERN_SUBSTR; s++; break; case '+': /* Compare individual words. */ result = PATTERN_UNKNOWN; /* Not implemented. */ break; case '/': /* Subject's DN. */ s++; if (!*s || spacep (s)) result = PATTERN_UNKNOWN; /* No DN or prefixed with a space. */ else result = PATTERN_SUBJECT; break; case '#': /* Serial number or issuer DN. */ { const char *si; s++; if ( *s == '/') { /* An issuer's DN is indicated by "#/" */ s++; if (!*s || spacep (s)) result = PATTERN_UNKNOWN; /* No DN or prefixed with a space. */ else result = PATTERN_ISSUER; } else { /* Serialnumber + optional issuer ID. */ for (si=s; *si && *si != '/'; si++) if (!strchr("01234567890abcdefABCDEF", *si)) break; if (*si && *si != '/') result = PATTERN_UNKNOWN; /* Invalid digit in serial number. */ else { *r_sn_offset = s - pattern; if (!*si) result = PATTERN_SERIALNO; else { s = si+1; if (!*s || spacep (s)) result = PATTERN_UNKNOWN; /* No DN or prefixed with a space. */ else result = PATTERN_SERIALNO_ISSUER; } } } } break; case ':': /* Unified fingerprint. */ { const char *se, *si; int i; se = strchr (++s, ':'); if (!se) result = PATTERN_UNKNOWN; else { for (i=0, si=s; si < se; si++, i++ ) if (!strchr("01234567890abcdefABCDEF", *si)) break; if ( si < se ) result = PATTERN_UNKNOWN; /* Invalid digit. */ else if (i == 32) result = PATTERN_FINGERPRINT16; else if (i == 40) result = PATTERN_FINGERPRINT20; else result = PATTERN_UNKNOWN; /* Invalid length for a fingerprint. */ } } break; case '&': /* Keygrip. */ result = PATTERN_UNKNOWN; /* Not implemented. */ break; default: if (s[0] == '0' && s[1] == 'x') { hexprefix = 1; s += 2; } hexlength = strspn(s, "0123456789abcdefABCDEF"); /* Check if a hexadecimal number is terminated by EOS or blank. */ if (hexlength && s[hexlength] && !spacep (s+hexlength)) { /* If the "0x" prefix is used a correct termination is required. */ if (hexprefix) { result = PATTERN_UNKNOWN; break; /* switch */ } hexlength = 0; /* Not a hex number. */ } if (hexlength == 8 || (!hexprefix && hexlength == 9 && *s == '0')) { if (hexlength == 9) s++; result = PATTERN_SHORT_KEYID; } else if (hexlength == 16 || (!hexprefix && hexlength == 17 && *s == '0')) { if (hexlength == 17) s++; result = PATTERN_LONG_KEYID; } else if (hexlength == 32 || (!hexprefix && hexlength == 33 && *s == '0')) { if (hexlength == 33) s++; result = PATTERN_FINGERPRINT16; } else if (hexlength == 40 || (!hexprefix && hexlength == 41 && *s == '0')) { if (hexlength == 41) s++; result = PATTERN_FINGERPRINT20; } else if (!hexprefix) { /* The fingerprints used with X.509 are often delimited by colons, so we try to single this case out. */ result = PATTERN_UNKNOWN; hexlength = strspn (s, ":0123456789abcdefABCDEF"); if (hexlength == 59 && (!s[hexlength] || spacep (s+hexlength))) { int i, c; for (i=0; i < 20; i++, s += 3) { c = hextobyte(s); if (c == -1 || (i < 19 && s[2] != ':')) break; } if (i == 20) result = PATTERN_FINGERPRINT20; } if (result == PATTERN_UNKNOWN) /* Default to substring match. */ { result = PATTERN_SUBSTR; } } else /* A hex number with a prefix but with a wrong length. */ result = PATTERN_UNKNOWN; } if (result != PATTERN_UNKNOWN) *r_offset = s - pattern; return result; } /* Given PATTERN, which is a string as used by GnuPG to specify a certificate, return all matching certificates by calling the supplied function RETFNC. */ gpg_error_t get_certs_bypattern (const char *pattern, gpg_error_t (*retfnc)(void*,ksba_cert_t), void *retfnc_data) { gpg_error_t err = GPG_ERR_BUG; enum pattern_class class; size_t offset, sn_offset; const char *hexserialno; ksba_sexp_t serialno = NULL; ksba_cert_t cert = NULL; unsigned int seq; if (!pattern || !retfnc) return gpg_error (GPG_ERR_INV_ARG); class = classify_pattern (pattern, &offset, &sn_offset); hexserialno = pattern + sn_offset; pattern += offset; switch (class) { case PATTERN_UNKNOWN: err = gpg_error (GPG_ERR_INV_NAME); break; case PATTERN_FINGERPRINT20: cert = get_cert_byhexfpr (pattern); err = cert? 0 : gpg_error (GPG_ERR_NOT_FOUND); break; case PATTERN_SERIALNO_ISSUER: serialno = hexsn_to_sexp (hexserialno); if (!serialno) err = gpg_error_from_syserror (); else { cert = get_cert_bysn (pattern, serialno); err = cert? 0 : gpg_error (GPG_ERR_NOT_FOUND); } break; case PATTERN_ISSUER: for (seq=0,err=0; !err && (cert = get_cert_byissuer (pattern, seq)); seq++) { err = retfnc (retfnc_data, cert); ksba_cert_release (cert); cert = NULL; } if (!err && !seq) err = gpg_error (GPG_ERR_NOT_FOUND); break; case PATTERN_SUBJECT: for (seq=0,err=0; !err && (cert = get_cert_bysubject (pattern, seq));seq++) { err = retfnc (retfnc_data, cert); ksba_cert_release (cert); cert = NULL; } if (!err && !seq) err = gpg_error (GPG_ERR_NOT_FOUND); break; case PATTERN_EMAIL: case PATTERN_EMAIL_SUBSTR: case PATTERN_FINGERPRINT16: case PATTERN_SHORT_KEYID: case PATTERN_LONG_KEYID: case PATTERN_SUBSTR: case PATTERN_SERIALNO: /* Not supported. */ err = gpg_error (GPG_ERR_INV_NAME); } if (!err && cert) err = retfnc (retfnc_data, cert); ksba_cert_release (cert); xfree (serialno); return err; } /* Return the certificate matching ISSUER_DN and SERIALNO; if it is not already in the cache, try to find it from other resources. */ ksba_cert_t find_cert_bysn (ctrl_t ctrl, const char *issuer_dn, ksba_sexp_t serialno) { gpg_error_t err; ksba_cert_t cert; cert_fetch_context_t context = NULL; char *hexsn, *buf; /* First check whether it has already been cached. */ cert = get_cert_bysn (issuer_dn, serialno); if (cert) return cert; /* Ask back to the service requester to return the certificate. This is because we can assume that he already used the certificate while checking for the CRL. */ hexsn = serial_hex (serialno); if (!hexsn) { log_error ("serial_hex() failed\n"); return NULL; } buf = xtrymalloc (1 + strlen (hexsn) + 1 + strlen (issuer_dn) + 1); if (!buf) { log_error ("can't allocate enough memory: %s\n", strerror (errno)); xfree (hexsn); return NULL; } strcpy (stpcpy (stpcpy (stpcpy (buf, "#"), hexsn),"/"), issuer_dn); xfree (hexsn); cert = get_cert_local (ctrl, buf); xfree (buf); if (cert) { cache_cert (cert); return cert; /* Done. */ } if (DBG_LOOKUP) log_debug ("find_cert_bysn: certificate not returned by caller" " - doing lookup\n"); /* Retrieve the certificate from external resources. */ while (!cert) { ksba_sexp_t sn; char *issdn; if (!context) { err = ca_cert_fetch (ctrl, &context, issuer_dn); if (err) { log_error (_("error fetching certificate by S/N: %s\n"), gpg_strerror (err)); break; } } err = fetch_next_ksba_cert (context, &cert); if (err) { log_error (_("error fetching certificate by S/N: %s\n"), gpg_strerror (err) ); break; } issdn = ksba_cert_get_issuer (cert, 0); if (strcmp (issuer_dn, issdn)) { log_debug ("find_cert_bysn: Ooops: issuer DN does not match\n"); ksba_cert_release (cert); cert = NULL; ksba_free (issdn); break; } sn = ksba_cert_get_serial (cert); if (DBG_LOOKUP) { log_debug (" considering certificate (#"); dump_serial (sn); log_printf ("/"); dump_string (issdn); log_printf (")\n"); } if (!compare_serialno (serialno, sn)) { ksba_free (sn); ksba_free (issdn); cache_cert (cert); if (DBG_LOOKUP) log_debug (" found\n"); break; /* Ready. */ } ksba_free (sn); ksba_free (issdn); ksba_cert_release (cert); cert = NULL; } end_cert_fetch (context); return cert; } /* Return the certificate matching SUBJECT_DN and (if not NULL) KEYID. If it is not already in the cache, try to find it from other resources. Note, that the external search does not work for user certificates because the LDAP lookup is on the caCertificate attribute. For our purposes this is just fine. */ ksba_cert_t find_cert_bysubject (ctrl_t ctrl, const char *subject_dn, ksba_sexp_t keyid) { gpg_error_t err; int seq; ksba_cert_t cert = NULL; cert_fetch_context_t context = NULL; ksba_sexp_t subj; /* If we have certificates from an OCSP request we first try to use them. This is because these certificates will really be the required ones and thus even in the case that they can't be uniquely located by the following code we can use them. This is for example required by Telesec certificates where a keyId is used but the issuer certificate comes without a subject keyId! */ if (ctrl->ocsp_certs && subject_dn) { cert_item_t ci; cert_ref_t cr; int i; /* For efficiency reasons we won't use get_cert_bysubject here. */ acquire_cache_read_lock (); for (i=0; i < 256; i++) for (ci=cert_cache[i]; ci; ci = ci->next) if (ci->cert && ci->subject_dn && !strcmp (ci->subject_dn, subject_dn)) for (cr=ctrl->ocsp_certs; cr; cr = cr->next) if (!memcmp (ci->fpr, cr->fpr, 20)) { ksba_cert_ref (ci->cert); release_cache_lock (); return ci->cert; /* We use this certificate. */ } release_cache_lock (); if (DBG_LOOKUP) log_debug ("find_cert_bysubject: certificate not in ocsp_certs\n"); } /* First we check whether the certificate is cached. */ for (seq=0; (cert = get_cert_bysubject (subject_dn, seq)); seq++) { if (!keyid) break; /* No keyid requested, so return the first one found. */ if (!ksba_cert_get_subj_key_id (cert, NULL, &subj) && !cmp_simple_canon_sexp (keyid, subj)) { xfree (subj); break; /* Found matching cert. */ } xfree (subj); ksba_cert_release (cert); } if (cert) return cert; /* Done. */ if (DBG_LOOKUP) log_debug ("find_cert_bysubject: certificate not in cache\n"); /* Ask back to the service requester to return the certificate. This is because we can assume that he already used the certificate while checking for the CRL. */ if (keyid) cert = get_cert_local_ski (ctrl, subject_dn, keyid); else { /* In contrast to get_cert_local_ski, get_cert_local uses any passed pattern, so we need to make sure that an exact subject search is done. */ char *buf; buf = xtrymalloc (1 + strlen (subject_dn) + 1); if (!buf) { log_error ("can't allocate enough memory: %s\n", strerror (errno)); return NULL; } strcpy (stpcpy (buf, "/"), subject_dn); cert = get_cert_local (ctrl, buf); xfree (buf); } if (cert) { cache_cert (cert); return cert; /* Done. */ } if (DBG_LOOKUP) log_debug ("find_cert_bysubject: certificate not returned by caller" " - doing lookup\n"); /* Locate the certificate using external resources. */ while (!cert) { char *subjdn; if (!context) { err = ca_cert_fetch (ctrl, &context, subject_dn); if (err) { log_error (_("error fetching certificate by subject: %s\n"), gpg_strerror (err)); break; } } err = fetch_next_ksba_cert (context, &cert); if (err) { log_error (_("error fetching certificate by subject: %s\n"), gpg_strerror (err) ); break; } subjdn = ksba_cert_get_subject (cert, 0); if (strcmp (subject_dn, subjdn)) { log_info ("find_cert_bysubject: subject DN does not match\n"); ksba_cert_release (cert); cert = NULL; ksba_free (subjdn); continue; } if (DBG_LOOKUP) { log_debug (" considering certificate (/"); dump_string (subjdn); log_printf (")\n"); } ksba_free (subjdn); /* If no key ID has been provided, we return the first match. */ if (!keyid) { cache_cert (cert); if (DBG_LOOKUP) log_debug (" found\n"); break; /* Ready. */ } /* With the key ID given we need to compare it. */ if (!ksba_cert_get_subj_key_id (cert, NULL, &subj)) { if (!cmp_simple_canon_sexp (keyid, subj)) { ksba_free (subj); cache_cert (cert); if (DBG_LOOKUP) log_debug (" found\n"); break; /* Ready. */ } } ksba_free (subj); ksba_cert_release (cert); cert = NULL; } end_cert_fetch (context); return cert; } /* Return 0 if the certificate is a trusted certificate. Returns GPG_ERR_NOT_TRUSTED if it is not trusted or other error codes in case of systems errors. */ gpg_error_t is_trusted_cert (ksba_cert_t cert) { unsigned char fpr[20]; cert_item_t ci; cert_compute_fpr (cert, fpr); acquire_cache_read_lock (); for (ci=cert_cache[*fpr]; ci; ci = ci->next) if (ci->cert && !memcmp (ci->fpr, fpr, 20)) { if (ci->flags.trusted) { release_cache_lock (); return 0; /* Yes, it is trusted. */ } break; } release_cache_lock (); return gpg_error (GPG_ERR_NOT_TRUSTED); } /* Given the certificate CERT locate the issuer for this certificate and return it at R_CERT. Returns 0 on success or GPG_ERR_NOT_FOUND. */ gpg_error_t find_issuing_cert (ctrl_t ctrl, ksba_cert_t cert, ksba_cert_t *r_cert) { gpg_error_t err; char *issuer_dn; ksba_cert_t issuer_cert = NULL; ksba_name_t authid; ksba_sexp_t authidno; ksba_sexp_t keyid; *r_cert = NULL; issuer_dn = ksba_cert_get_issuer (cert, 0); if (!issuer_dn) { log_error (_("no issuer found in certificate\n")); err = gpg_error (GPG_ERR_BAD_CERT); goto leave; } /* First we need to check whether we can return that certificate using the authorithyKeyIdentifier. */ err = ksba_cert_get_auth_key_id (cert, &keyid, &authid, &authidno); if (err) { log_info (_("error getting authorityKeyIdentifier: %s\n"), gpg_strerror (err)); } else { const char *s = ksba_name_enum (authid, 0); if (s && *authidno) { issuer_cert = find_cert_bysn (ctrl, s, authidno); } if (!issuer_cert && keyid) { /* Not found by issuer+s/n. Now that we have an AKI keyIdentifier look for a certificate with a matching SKI. */ issuer_cert = find_cert_bysubject (ctrl, issuer_dn, keyid); } /* Print a note so that the user does not feel too helpless when an issuer certificate was found and gpgsm prints BAD signature because it is not the correct one. */ if (!issuer_cert) { log_info ("issuer certificate "); if (keyid) { log_printf ("{"); dump_serial (keyid); log_printf ("} "); } if (authidno) { log_printf ("(#"); dump_serial (authidno); log_printf ("/"); dump_string (s); log_printf (") "); } log_printf ("not found using authorityKeyIdentifier\n"); } ksba_name_release (authid); xfree (authidno); xfree (keyid); } /* If this did not work, try just with the issuer's name and assume that there is only one such certificate. We only look into our cache then. */ if (err || !issuer_cert) { issuer_cert = get_cert_bysubject (issuer_dn, 0); if (issuer_cert) err = 0; } leave: if (!err && !issuer_cert) err = gpg_error (GPG_ERR_NOT_FOUND); xfree (issuer_dn); if (err) ksba_cert_release (issuer_cert); else *r_cert = issuer_cert; return err; } diff --git a/dirmngr/crlcache.c b/dirmngr/crlcache.c index af2a956d1..388754ba7 100644 --- a/dirmngr/crlcache.c +++ b/dirmngr/crlcache.c @@ -1,2584 +1,2584 @@ /* crlcache.c - LDAP access * Copyright (C) 2002 Klarälvdalens Datakonsult AB * Copyright (C) 2003, 2004, 2005, 2008 g10 Code GmbH * * This file is part of DirMngr. * * DirMngr is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * DirMngr 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 General Public License * along with this program; if not, see . */ /* 1. To keep track of the CRLs actually cached and to store the meta information of the CRLs a simple record oriented text file is used. Fields in the file are colon (':') separated and values containing colons or linefeeds are percent escaped (e.g. a colon itself is represented as "%3A"). The first field is a record type identifier, so that the file is useful to keep track of other meta data too. The name of the file is "DIR.txt". 1.1. Comment record Field 1: Constant beginning with "#". Other fields are not defined and such a record is simply skipped during processing. 1.2. Version record Field 1: Constant "v" Field 2: Version number of this file. Must be 1. This record must be the first non-comment record record and there shall only exist one record of this type. 1.3. CRL cache record Field 1: Constant "c", "u" or "i". A "c" or "u" indicate a valid cache entry, however "u" requires that a user root certificate check needs to be done. An "i" indicates an invalid cache entry which should not be used but still exists so that it can be updated at NEXT_UPDATE. Field 2: Hexadecimal encoded SHA-1 hash of the issuer DN using uppercase letters. Field 3: Issuer DN in RFC-2253 notation. Field 4: URL used to retrieve the corresponding CRL. Field 5: 15 character ISO timestamp with THIS_UPDATE. Field 6: 15 character ISO timestamp with NEXT_UPDATE. Field 7: Hexadecimal encoded MD-5 hash of the DB file to detect accidental modified (i.e. deleted and created) cache files. Field 8: optional CRL number as a hex string. Field 9: AuthorityKeyID.issuer, each Name separated by 0x01 Field 10: AuthorityKeyID.serial Field 11: Hex fingerprint of trust anchor if field 1 is 'u'. 2. Layout of the standard CRL Cache DB file: We use records of variable length with this structure n bytes Serialnumber (binary) used as key thus there is no need to store the length explicitly with DB2. 1 byte Reason for revocation (currently the KSBA reason flags are used) 15 bytes ISO date of revocation (e.g. 19980815T142000) Note that there is no terminating 0 stored. The filename used is the hexadecimal (using uppercase letters) SHA-1 hash value of the issuer DN prefixed with a "crl-" and suffixed with a ".db". Thus the length of the filename is 47. */ #include #include #include #include #include #include #include #include #include #include #ifndef HAVE_W32_SYSTEM #include #endif #ifdef MKDIR_TAKES_ONE_ARG #undef mkdir #define mkdir(a,b) mkdir(a) #endif #include "dirmngr.h" #include "validate.h" #include "certcache.h" #include "crlcache.h" #include "crlfetch.h" #include "misc.h" #include "cdb.h" /* Change this whenever the format changes */ -#define DBDIR_D (opt.system_daemon? "crls.d" : "dirmngr-cache.d") +#define DBDIR_D "crls.d" #define DBDIRFILE "DIR.txt" #define DBDIRVERSION 1 /* The number of DB files we may have open at one time. We need to limit this because there is no guarantee that the number of issuers has a upper limit. We are currently using mmap, so it is a good idea anyway to limit the number of opened cache files. */ #define MAX_OPEN_DB_FILES 5 static const char oidstr_crlNumber[] = "2.5.29.20"; static const char oidstr_issuingDistributionPoint[] = "2.5.29.28"; static const char oidstr_authorityKeyIdentifier[] = "2.5.29.35"; /* Definition of one cached item. */ struct crl_cache_entry_s { struct crl_cache_entry_s *next; int deleted; /* True if marked for deletion. */ int mark; /* Internally used by update_dir. */ unsigned int lineno;/* A 0 indicates a new entry. */ char *release_ptr; /* The actual allocated memory. */ char *url; /* Points into RELEASE_PTR. */ char *issuer; /* Ditto. */ char *issuer_hash; /* Ditto. */ char *dbfile_hash; /* MD5 sum of the cache file, points into RELEASE_PTR.*/ int invalid; /* Can't use this CRL. */ int user_trust_req; /* User supplied root certificate required. */ char *check_trust_anchor; /* Malloced fingerprint. */ ksba_isotime_t this_update; ksba_isotime_t next_update; ksba_isotime_t last_refresh; /* Use for the force_crl_refresh feature. */ char *crl_number; char *authority_issuer; char *authority_serialno; struct cdb *cdb; /* The cache file handle or NULL if not open. */ unsigned int cdb_use_count; /* Current use count. */ unsigned int cdb_lru_count; /* Used for LRU purposes. */ int dbfile_checked; /* Set to true if the dbfile_hash value has been checked one. */ }; /* Definition of the entire cache object. */ struct crl_cache_s { crl_cache_entry_t entries; }; typedef struct crl_cache_s *crl_cache_t; /* Prototypes. */ static crl_cache_entry_t find_entry (crl_cache_entry_t first, const char *issuer_hash); /* The currently loaded cache object. This is usually initialized right at startup. */ static crl_cache_t current_cache; /* Return the current cache object or bail out if it is has not yet been initialized. */ static crl_cache_t get_current_cache (void) { if (!current_cache) log_fatal ("CRL cache has not yet been initialized\n"); return current_cache; } /* Create ae directory if it does not yet exists. Returns on success, or -1 on error. */ static int create_directory_if_needed (const char *name) { DIR *dir; char *fname; fname = make_filename (opt.homedir_cache, name, NULL); dir = opendir (fname); if (!dir) { log_info (_("creating directory '%s'\n"), fname); if (mkdir (fname, S_IRUSR|S_IWUSR|S_IXUSR) ) { int save_errno = errno; log_error (_("error creating directory '%s': %s\n"), fname, strerror (errno)); xfree (fname); gpg_err_set_errno (save_errno); return -1; } } else closedir (dir); xfree (fname); return 0; } /* Remove all files from the cache directory. If FORCE is not true, some sanity checks on the filenames are done. Return 0 if everything went fine. */ static int cleanup_cache_dir (int force) { char *dname = make_filename (opt.homedir_cache, DBDIR_D, NULL); DIR *dir; struct dirent *de; int problem = 0; if (!force) { /* Very minor sanity checks. */ if (!strcmp (dname, "~/") || !strcmp (dname, "/" )) { log_error (_("ignoring database dir '%s'\n"), dname); xfree (dname); return -1; } } dir = opendir (dname); if (!dir) { log_error (_("error reading directory '%s': %s\n"), dname, strerror (errno)); xfree (dname); return -1; } while ((de = readdir (dir))) { if (strcmp (de->d_name, "." ) && strcmp (de->d_name, "..")) { char *cdbname = make_filename (dname, de->d_name, NULL); int okay; struct stat sbuf; if (force) okay = 1; else okay = (!stat (cdbname, &sbuf) && S_ISREG (sbuf.st_mode)); if (okay) { log_info (_("removing cache file '%s'\n"), cdbname); if (gnupg_remove (cdbname)) { log_error ("failed to remove '%s': %s\n", cdbname, strerror (errno)); problem = -1; } } else log_info (_("not removing file '%s'\n"), cdbname); xfree (cdbname); } } xfree (dname); closedir (dir); return problem; } /* Read the next line from the file FP and return the line in an malloced buffer. Return NULL on error or EOF. There is no limitation os the line length. The trailing linefeed has been removed, the function will read the last line of a file, even if that is not terminated by a LF. */ static char * next_line_from_file (estream_t fp, gpg_error_t *r_err) { char buf[300]; char *largebuf = NULL; size_t buflen; size_t len = 0; unsigned char *p; int c; char *tmpbuf; *r_err = 0; p = buf; buflen = sizeof buf - 1; while ((c=es_getc (fp)) != EOF && c != '\n') { if (len >= buflen) { if (!largebuf) { buflen += 1024; largebuf = xtrymalloc ( buflen + 1 ); if (!largebuf) { *r_err = gpg_error_from_syserror (); return NULL; } memcpy (largebuf, buf, len); } else { buflen += 1024; tmpbuf = xtryrealloc (largebuf, buflen + 1); if (!tmpbuf) { *r_err = gpg_error_from_syserror (); xfree (largebuf); return NULL; } largebuf = tmpbuf; } p = largebuf; } p[len++] = c; } if (c == EOF && !len) return NULL; p[len] = 0; if (largebuf) tmpbuf = xtryrealloc (largebuf, len+1); else tmpbuf = xtrystrdup (buf); if (!tmpbuf) { *r_err = gpg_error_from_syserror (); xfree (largebuf); } return tmpbuf; } /* Release one cache entry. */ static void release_one_cache_entry (crl_cache_entry_t entry) { if (entry) { if (entry->cdb) { int fd = cdb_fileno (entry->cdb); cdb_free (entry->cdb); xfree (entry->cdb); if (close (fd)) log_error (_("error closing cache file: %s\n"), strerror(errno)); } xfree (entry->release_ptr); xfree (entry->check_trust_anchor); xfree (entry); } } /* Release the CACHE object. */ static void release_cache (crl_cache_t cache) { crl_cache_entry_t entry, entry2; if (!cache) return; for (entry = cache->entries; entry; entry = entry2) { entry2 = entry->next; release_one_cache_entry (entry); } cache->entries = NULL; xfree (cache); } /* Open the dir file FNAME or create a new one if it does not yet exist. */ static estream_t open_dir_file (const char *fname) { estream_t fp; fp = es_fopen (fname, "r"); if (!fp) { log_error (_("failed to open cache dir file '%s': %s\n"), fname, strerror (errno)); /* Make sure that the directory exists, try to create if otherwise. */ if (create_directory_if_needed (NULL) || create_directory_if_needed (DBDIR_D)) return NULL; fp = es_fopen (fname, "w"); if (!fp) { log_error (_("error creating new cache dir file '%s': %s\n"), fname, strerror (errno)); return NULL; } es_fprintf (fp, "v:%d:\n", DBDIRVERSION); if (es_ferror (fp)) { log_error (_("error writing new cache dir file '%s': %s\n"), fname, strerror (errno)); es_fclose (fp); return NULL; } if (es_fclose (fp)) { log_error (_("error closing new cache dir file '%s': %s\n"), fname, strerror (errno)); return NULL; } log_info (_("new cache dir file '%s' created\n"), fname); fp = es_fopen (fname, "r"); if (!fp) { log_error (_("failed to re-open cache dir file '%s': %s\n"), fname, strerror (errno)); return NULL; } } return fp; } /* Helper for open_dir. */ static gpg_error_t check_dir_version (estream_t *fpadr, const char *fname, unsigned int *lineno, int cleanup_on_mismatch) { char *line; gpg_error_t lineerr = 0; estream_t fp = *fpadr; int created = 0; retry: while ((line = next_line_from_file (fp, &lineerr))) { ++*lineno; if (*line == 'v' && line[1] == ':') break; else if (*line != '#') { log_error (_("first record of '%s' is not the version\n"), fname); xfree (line); return gpg_error (GPG_ERR_CONFIGURATION); } xfree (line); } if (lineerr) return lineerr; /* The !line catches the case of an empty DIR file. We handle this the same as a non-matching version. */ if (!line || strtol (line+2, NULL, 10) != DBDIRVERSION) { if (!created && cleanup_on_mismatch) { log_error (_("old version of cache directory - cleaning up\n")); es_fclose (fp); *fpadr = NULL; if (!cleanup_cache_dir (1)) { *lineno = 0; fp = *fpadr = open_dir_file (fname); if (!fp) { xfree (line); return gpg_error (GPG_ERR_CONFIGURATION); } created = 1; goto retry; } } log_error (_("old version of cache directory - giving up\n")); xfree (line); return gpg_error (GPG_ERR_CONFIGURATION); } xfree (line); return 0; } /* Open the dir file and read in all available information. Store that in a newly allocated cache object and return that if everything worked out fine. Create the cache directory and the dir if it does not yet exist. Remove all files in that directory if the version does not match. */ static gpg_error_t open_dir (crl_cache_t *r_cache) { crl_cache_t cache; char *fname; char *line = NULL; gpg_error_t lineerr = 0; estream_t fp; crl_cache_entry_t entry, *entrytail; unsigned int lineno; gpg_error_t err = 0; int anyerr = 0; cache = xtrycalloc (1, sizeof *cache); if (!cache) return gpg_error_from_syserror (); fname = make_filename (opt.homedir_cache, DBDIR_D, DBDIRFILE, NULL); lineno = 0; fp = open_dir_file (fname); if (!fp) { err = gpg_error (GPG_ERR_CONFIGURATION); goto leave; } err = check_dir_version (&fp, fname, &lineno, 1); if (err) goto leave; /* Read in all supported entries from the dir file. */ cache->entries = NULL; entrytail = &cache->entries; xfree (line); while ((line = next_line_from_file (fp, &lineerr))) { int fieldno; char *p, *endp; lineno++; if ( *line == 'c' || *line == 'u' || *line == 'i' ) { entry = xtrycalloc (1, sizeof *entry); if (!entry) { err = gpg_error_from_syserror (); goto leave; } entry->lineno = lineno; entry->release_ptr = line; if (*line == 'i') { entry->invalid = atoi (line+1); if (entry->invalid < 1) entry->invalid = 1; } else if (*line == 'u') entry->user_trust_req = 1; for (fieldno=1, p = line; p; p = endp, fieldno++) { endp = strchr (p, ':'); if (endp) *endp++ = '\0'; switch (fieldno) { case 1: /* record type */ break; case 2: entry->issuer_hash = p; break; case 3: entry->issuer = unpercent_string (p); break; case 4: entry->url = unpercent_string (p); break; case 5: strncpy (entry->this_update, p, 15); entry->this_update[15] = 0; break; case 6: strncpy (entry->next_update, p, 15); entry->next_update[15] = 0; break; case 7: entry->dbfile_hash = p; break; case 8: if (*p) entry->crl_number = p; break; case 9: if (*p) entry->authority_issuer = unpercent_string (p); break; case 10: if (*p) entry->authority_serialno = unpercent_string (p); break; case 11: if (*p) entry->check_trust_anchor = xtrystrdup (p); break; default: if (*p) log_info (_("extra field detected in crl record of " "'%s' line %u\n"), fname, lineno); break; } } if (!entry->issuer_hash) { log_info (_("invalid line detected in '%s' line %u\n"), fname, lineno); xfree (entry); entry = NULL; } else if (find_entry (cache->entries, entry->issuer_hash)) { /* Fixme: The duplicate checking used is not very effective for large numbers of issuers. */ log_info (_("duplicate entry detected in '%s' line %u\n"), fname, lineno); xfree (entry); entry = NULL; } else { line = NULL; *entrytail = entry; entrytail = &entry->next; } } else if (*line == '#') ; else log_info (_("unsupported record type in '%s' line %u skipped\n"), fname, lineno); if (line) xfree (line); } if (lineerr) { err = lineerr; log_error (_("error reading '%s': %s\n"), fname, gpg_strerror (err)); goto leave; } if (es_ferror (fp)) { log_error (_("error reading '%s': %s\n"), fname, strerror (errno)); err = gpg_error (GPG_ERR_CONFIGURATION); goto leave; } /* Now do some basic checks on the data. */ for (entry = cache->entries; entry; entry = entry->next) { assert (entry->lineno); if (strlen (entry->issuer_hash) != 40) { anyerr++; log_error (_("invalid issuer hash in '%s' line %u\n"), fname, entry->lineno); } else if ( !*entry->issuer ) { anyerr++; log_error (_("no issuer DN in '%s' line %u\n"), fname, entry->lineno); } else if ( check_isotime (entry->this_update) || check_isotime (entry->next_update)) { anyerr++; log_error (_("invalid timestamp in '%s' line %u\n"), fname, entry->lineno); } /* Checks not leading to an immediate fail. */ if (strlen (entry->dbfile_hash) != 32) log_info (_("WARNING: invalid cache file hash in '%s' line %u\n"), fname, entry->lineno); } if (anyerr) { log_error (_("detected errors in cache dir file\n")); log_info (_("please check the reason and manually delete that file\n")); err = gpg_error (GPG_ERR_CONFIGURATION); } leave: es_fclose (fp); xfree (line); xfree (fname); if (err) { release_cache (cache); cache = NULL; } *r_cache = cache; return err; } static void write_percented_string (const char *s, estream_t fp) { for (; *s; s++) if (*s == ':') es_fputs ("%3A", fp); else if (*s == '\n') es_fputs ("%0A", fp); else if (*s == '\r') es_fputs ("%0D", fp); else es_putc (*s, fp); } static void write_dir_line_crl (estream_t fp, crl_cache_entry_t e) { if (e->invalid) es_fprintf (fp, "i%d", e->invalid); else if (e->user_trust_req) es_putc ('u', fp); else es_putc ('c', fp); es_putc (':', fp); es_fputs (e->issuer_hash, fp); es_putc (':', fp); write_percented_string (e->issuer, fp); es_putc (':', fp); write_percented_string (e->url, fp); es_putc (':', fp); es_fwrite (e->this_update, 15, 1, fp); es_putc (':', fp); es_fwrite (e->next_update, 15, 1, fp); es_putc (':', fp); es_fputs (e->dbfile_hash, fp); es_putc (':', fp); if (e->crl_number) es_fputs (e->crl_number, fp); es_putc (':', fp); if (e->authority_issuer) write_percented_string (e->authority_issuer, fp); es_putc (':', fp); if (e->authority_serialno) es_fputs (e->authority_serialno, fp); es_putc (':', fp); if (e->check_trust_anchor && e->user_trust_req) es_fputs (e->check_trust_anchor, fp); es_putc ('\n', fp); } /* Update the current dir file using the cache. */ static gpg_error_t update_dir (crl_cache_t cache) { char *fname = NULL; char *tmpfname = NULL; char *line = NULL; gpg_error_t lineerr = 0; estream_t fp; estream_t fpout = NULL; crl_cache_entry_t e; unsigned int lineno; gpg_error_t err = 0; fname = make_filename (opt.homedir_cache, DBDIR_D, DBDIRFILE, NULL); /* Fixme: Take an update file lock here. */ for (e= cache->entries; e; e = e->next) e->mark = 1; lineno = 0; fp = es_fopen (fname, "r"); if (!fp) { err = gpg_error_from_errno (errno); log_error (_("failed to open cache dir file '%s': %s\n"), fname, strerror (errno)); goto leave; } err = check_dir_version (&fp, fname, &lineno, 0); if (err) goto leave; es_rewind (fp); lineno = 0; /* Create a temporary DIR file. */ { char *tmpbuf, *p; const char *nodename; #ifndef HAVE_W32_SYSTEM struct utsname utsbuf; #endif #ifdef HAVE_W32_SYSTEM nodename = "unknown"; #else if (uname (&utsbuf)) nodename = "unknown"; else nodename = utsbuf.nodename; #endif gpgrt_asprintf (&tmpbuf, "DIR-tmp-%s-%u-%p.txt.tmp", nodename, (unsigned int)getpid (), &tmpbuf); if (!tmpbuf) { err = gpg_error_from_errno (errno); log_error (_("failed to create temporary cache dir file '%s': %s\n"), tmpfname, strerror (errno)); goto leave; } for (p=tmpbuf; *p; p++) if (*p == '/') *p = '.'; tmpfname = make_filename (opt.homedir_cache, DBDIR_D, tmpbuf, NULL); xfree (tmpbuf); } fpout = es_fopen (tmpfname, "w"); if (!fpout) { err = gpg_error_from_errno (errno); log_error (_("failed to create temporary cache dir file '%s': %s\n"), tmpfname, strerror (errno)); goto leave; } while ((line = next_line_from_file (fp, &lineerr))) { lineno++; if (*line == 'c' || *line == 'u' || *line == 'i') { /* Extract the issuer hash field. */ char *fieldp, *endp; fieldp = strchr (line, ':'); endp = fieldp? strchr (++fieldp, ':') : NULL; if (endp) { /* There should be no percent within the issuer hash field, thus we can compare it pretty easily. */ *endp = 0; e = find_entry ( cache->entries, fieldp); *endp = ':'; /* Restore original line. */ if (e && e->deleted) { /* Marked for deletion, so don't write it. */ e->mark = 0; } else if (e) { /* Yep, this is valid entry we know about; write it out */ write_dir_line_crl (fpout, e); e->mark = 0; } else { /* We ignore entries we don't have in our cache because they may have been added in the meantime by other instances of dirmngr. */ es_fprintf (fpout, "# Next line added by " "another process; our pid is %lu\n", (unsigned long)getpid ()); es_fputs (line, fpout); es_putc ('\n', fpout); } } else { es_fputs ("# Invalid line detected: ", fpout); es_fputs (line, fpout); es_putc ('\n', fpout); } } else { /* Write out all non CRL lines as they are. */ es_fputs (line, fpout); es_putc ('\n', fpout); } xfree (line); } if (!es_ferror (fp) && !es_ferror (fpout) && !lineerr) { /* Write out the remaining entries. */ for (e= cache->entries; e; e = e->next) if (e->mark) { if (!e->deleted) write_dir_line_crl (fpout, e); e->mark = 0; } } if (lineerr) { err = lineerr; log_error (_("error reading '%s': %s\n"), fname, gpg_strerror (err)); goto leave; } if (es_ferror (fp)) { err = gpg_error_from_errno (errno); log_error (_("error reading '%s': %s\n"), fname, strerror (errno)); } if (es_ferror (fpout)) { err = gpg_error_from_errno (errno); log_error (_("error writing '%s': %s\n"), tmpfname, strerror (errno)); } if (err) goto leave; /* Rename the files. */ es_fclose (fp); fp = NULL; if (es_fclose (fpout)) { err = gpg_error_from_errno (errno); log_error (_("error closing '%s': %s\n"), tmpfname, strerror (errno)); goto leave; } fpout = NULL; #ifdef HAVE_W32_SYSTEM /* No atomic mv on W32 systems. */ gnupg_remove (fname); #endif if (rename (tmpfname, fname)) { err = gpg_error_from_errno (errno); log_error (_("error renaming '%s' to '%s': %s\n"), tmpfname, fname, strerror (errno)); goto leave; } leave: /* Fixme: Relinquish update lock. */ xfree (line); es_fclose (fp); xfree (fname); if (fpout) { es_fclose (fpout); if (err && tmpfname) gnupg_remove (tmpfname); } xfree (tmpfname); return err; } /* Create the filename for the cache file from the 40 byte ISSUER_HASH string. Caller must release the return string. */ static char * make_db_file_name (const char *issuer_hash) { char bname[50]; assert (strlen (issuer_hash) == 40); memcpy (bname, "crl-", 4); memcpy (bname + 4, issuer_hash, 40); strcpy (bname + 44, ".db"); return make_filename (opt.homedir_cache, DBDIR_D, bname, NULL); } /* Hash the file FNAME and return the MD5 digest in MD5BUFFER. The caller must allocate MD%buffer wityh at least 16 bytes. Returns 0 on success. */ static int hash_dbfile (const char *fname, unsigned char *md5buffer) { estream_t fp; char *buffer; size_t n; gcry_md_hd_t md5; gpg_error_t err; buffer = xtrymalloc (65536); fp = buffer? es_fopen (fname, "rb") : NULL; if (!fp) { log_error (_("can't hash '%s': %s\n"), fname, strerror (errno)); xfree (buffer); return -1; } err = gcry_md_open (&md5, GCRY_MD_MD5, 0); if (err) { log_error (_("error setting up MD5 hash context: %s\n"), gpg_strerror (err)); xfree (buffer); es_fclose (fp); return -1; } /* We better hash some information about the cache file layout in. */ sprintf (buffer, "%.100s/%.100s:%d", DBDIR_D, DBDIRFILE, DBDIRVERSION); gcry_md_write (md5, buffer, strlen (buffer)); for (;;) { n = es_fread (buffer, 1, 65536, fp); if (n < 65536 && es_ferror (fp)) { log_error (_("error hashing '%s': %s\n"), fname, strerror (errno)); xfree (buffer); es_fclose (fp); gcry_md_close (md5); return -1; } if (!n) break; gcry_md_write (md5, buffer, n); } es_fclose (fp); xfree (buffer); gcry_md_final (md5); memcpy (md5buffer, gcry_md_read (md5, GCRY_MD_MD5), 16); gcry_md_close (md5); return 0; } /* Compare the file FNAME against the dexified MD5 hash MD5HASH and return 0 if they match. */ static int check_dbfile (const char *fname, const char *md5hexvalue) { unsigned char buffer1[16], buffer2[16]; if (strlen (md5hexvalue) != 32) { log_error (_("invalid formatted checksum for '%s'\n"), fname); return -1; } unhexify (buffer1, md5hexvalue); if (hash_dbfile (fname, buffer2)) return -1; return memcmp (buffer1, buffer2, 16); } /* Open the cache file for ENTRY. This function implements a caching strategy and might close unused cache files. It is required to use unlock_db_file after using the file. */ static struct cdb * lock_db_file (crl_cache_t cache, crl_cache_entry_t entry) { char *fname; int fd; int open_count; crl_cache_entry_t e; if (entry->cdb) { entry->cdb_use_count++; return entry->cdb; } for (open_count = 0, e = cache->entries; e; e = e->next) { if (e->cdb) open_count++; /* log_debug ("CACHE: cdb=%p use_count=%u lru_count=%u\n", */ /* e->cdb,e->cdb_use_count,e->cdb_lru_count); */ } /* If there are too many file open, find the least recent used DB file and close it. Note that for Pth thread safeness we need to use a loop here. */ while (open_count >= MAX_OPEN_DB_FILES ) { crl_cache_entry_t last_e = NULL; unsigned int last_lru = (unsigned int)(-1); for (e = cache->entries; e; e = e->next) if (e->cdb && !e->cdb_use_count && e->cdb_lru_count < last_lru) { last_lru = e->cdb_lru_count; last_e = e; } if (!last_e) { log_error (_("too many open cache files; can't open anymore\n")); return NULL; } /* log_debug ("CACHE: closing file at cdb=%p\n", last_e->cdb); */ fd = cdb_fileno (last_e->cdb); cdb_free (last_e->cdb); xfree (last_e->cdb); last_e->cdb = NULL; if (close (fd)) log_error (_("error closing cache file: %s\n"), strerror(errno)); open_count--; } fname = make_db_file_name (entry->issuer_hash); if (opt.verbose) log_info (_("opening cache file '%s'\n"), fname ); if (!entry->dbfile_checked) { if (!check_dbfile (fname, entry->dbfile_hash)) entry->dbfile_checked = 1; /* Note, in case of an error we don't print an error here but let require the caller to do that check. */ } entry->cdb = xtrycalloc (1, sizeof *entry->cdb); if (!entry->cdb) { xfree (fname); return NULL; } fd = open (fname, O_RDONLY); if (fd == -1) { log_error (_("error opening cache file '%s': %s\n"), fname, strerror (errno)); xfree (entry->cdb); entry->cdb = NULL; xfree (fname); return NULL; } if (cdb_init (entry->cdb, fd)) { log_error (_("error initializing cache file '%s' for reading: %s\n"), fname, strerror (errno)); xfree (entry->cdb); entry->cdb = NULL; close (fd); xfree (fname); return NULL; } xfree (fname); entry->cdb_use_count = 1; entry->cdb_lru_count = 0; return entry->cdb; } /* Unlock a cache file, so that it can be reused. */ static void unlock_db_file (crl_cache_t cache, crl_cache_entry_t entry) { if (!entry->cdb) log_error (_("calling unlock_db_file on a closed file\n")); else if (!entry->cdb_use_count) log_error (_("calling unlock_db_file on an unlocked file\n")); else { entry->cdb_use_count--; entry->cdb_lru_count++; } /* If the entry was marked for deletion in the meantime do it now. We do this for the sake of Pth thread safeness. */ if (!entry->cdb_use_count && entry->deleted) { crl_cache_entry_t eprev, enext; enext = entry->next; for (eprev = cache->entries; eprev && eprev->next != entry; eprev = eprev->next) ; assert (eprev); if (eprev == cache->entries) cache->entries = enext; else eprev->next = enext; /* FIXME: Do we leak ENTRY? */ } } /* Find ISSUER_HASH in our cache FIRST. This may be used to enumerate the linked list we use to keep the CRLs of an issuer. */ static crl_cache_entry_t find_entry (crl_cache_entry_t first, const char *issuer_hash) { while (first && (first->deleted || strcmp (issuer_hash, first->issuer_hash))) first = first->next; return first; } /* Create a new CRL cache. This function is usually called only once. never fail. */ void crl_cache_init(void) { crl_cache_t cache = NULL; gpg_error_t err; if (current_cache) { log_error ("crl cache has already been initialized - not doing twice\n"); return; } err = open_dir (&cache); if (err) log_fatal (_("failed to create a new cache object: %s\n"), gpg_strerror (err)); current_cache = cache; } /* Remove the cache information and all its resources. Note that we still keep the cache on disk. */ void crl_cache_deinit (void) { if (current_cache) { release_cache (current_cache); current_cache = NULL; } } /* Delete the cache from disk. Return 0 on success.*/ int crl_cache_flush (void) { int rc; rc = cleanup_cache_dir (0)? -1 : 0; return rc; } /* Check whether the certificate identified by ISSUER_HASH and SN/SNLEN is valid; i.e. not listed in our cache. With FORCE_REFRESH set to true, a new CRL will be retrieved even if the cache has not yet expired. We use a 30 minutes threshold here so that invoking this function several times won't load the CRL over and over. */ static crl_cache_result_t cache_isvalid (ctrl_t ctrl, const char *issuer_hash, const unsigned char *sn, size_t snlen, int force_refresh) { crl_cache_t cache = get_current_cache (); crl_cache_result_t retval; struct cdb *cdb; int rc; crl_cache_entry_t entry; gnupg_isotime_t current_time; size_t n; (void)ctrl; entry = find_entry (cache->entries, issuer_hash); if (!entry) { log_info (_("no CRL available for issuer id %s\n"), issuer_hash ); return CRL_CACHE_DONTKNOW; } gnupg_get_isotime (current_time); if (strcmp (entry->next_update, current_time) < 0 ) { log_info (_("cached CRL for issuer id %s too old; update required\n"), issuer_hash); return CRL_CACHE_DONTKNOW; } if (force_refresh) { gnupg_isotime_t tmptime; if (*entry->last_refresh) { gnupg_copy_time (tmptime, entry->last_refresh); add_seconds_to_isotime (tmptime, 30 * 60); if (strcmp (tmptime, current_time) < 0 ) { log_info (_("force-crl-refresh active and %d minutes passed for" " issuer id %s; update required\n"), 30, issuer_hash); return CRL_CACHE_DONTKNOW; } } else { log_info (_("force-crl-refresh active for" " issuer id %s; update required\n"), issuer_hash); return CRL_CACHE_DONTKNOW; } } if (entry->invalid) { log_info (_("available CRL for issuer ID %s can't be used\n"), issuer_hash); return CRL_CACHE_CANTUSE; } cdb = lock_db_file (cache, entry); if (!cdb) return CRL_CACHE_DONTKNOW; /* Hmmm, not the best error code. */ if (!entry->dbfile_checked) { log_error (_("cached CRL for issuer id %s tampered; we need to update\n") , issuer_hash); unlock_db_file (cache, entry); return CRL_CACHE_DONTKNOW; } rc = cdb_find (cdb, sn, snlen); if (rc == 1) { n = cdb_datalen (cdb); if (n != 16) { log_error (_("WARNING: invalid cache record length for S/N ")); log_printf ("0x"); log_printhex ("", sn, snlen); } else if (opt.verbose) { unsigned char record[16]; char *tmp = hexify_data (sn, snlen, 1); if (cdb_read (cdb, record, n, cdb_datapos (cdb))) log_error (_("problem reading cache record for S/N %s: %s\n"), tmp, strerror (errno)); else log_info (_("S/N %s is not valid; reason=%02X date=%.15s\n"), tmp, *record, record+1); xfree (tmp); } retval = CRL_CACHE_INVALID; } else if (!rc) { if (opt.verbose) { char *serialno = hexify_data (sn, snlen, 1); log_info (_("S/N %s is valid, it is not listed in the CRL\n"), serialno ); xfree (serialno); } retval = CRL_CACHE_VALID; } else { log_error (_("error getting data from cache file: %s\n"), strerror (errno)); retval = CRL_CACHE_DONTKNOW; } if (entry->user_trust_req && (retval == CRL_CACHE_VALID || retval == CRL_CACHE_INVALID)) { if (!entry->check_trust_anchor) { log_error ("inconsistent data on user trust check\n"); retval = CRL_CACHE_CANTUSE; } else if (get_istrusted_from_client (ctrl, entry->check_trust_anchor)) { if (opt.verbose) log_info ("no system trust and client does not trust either\n"); retval = CRL_CACHE_CANTUSE; } else { /* Okay, the CRL is considered valid by the client and thus we can return the result as is. */ } } unlock_db_file (cache, entry); return retval; } /* Check whether the certificate identified by ISSUER_HASH and SERIALNO is valid; i.e. not listed in our cache. With FORCE_REFRESH set to true, a new CRL will be retrieved even if the cache has not yet expired. We use a 30 minutes threshold here so that invoking this function several times won't load the CRL over and over. */ crl_cache_result_t crl_cache_isvalid (ctrl_t ctrl, const char *issuer_hash, const char *serialno, int force_refresh) { crl_cache_result_t result; unsigned char snbuf_buffer[50]; unsigned char *snbuf; size_t n; n = strlen (serialno)/2+1; if (n < sizeof snbuf_buffer - 1) snbuf = snbuf_buffer; else { snbuf = xtrymalloc (n); if (!snbuf) return CRL_CACHE_DONTKNOW; } n = unhexify (snbuf, serialno); result = cache_isvalid (ctrl, issuer_hash, snbuf, n, force_refresh); if (snbuf != snbuf_buffer) xfree (snbuf); return result; } /* Check whether the certificate CERT is valid; i.e. not listed in our cache. With FORCE_REFRESH set to true, a new CRL will be retrieved even if the cache has not yet expired. We use a 30 minutes threshold here so that invoking this function several times won't load the CRL over and over. */ gpg_error_t crl_cache_cert_isvalid (ctrl_t ctrl, ksba_cert_t cert, int force_refresh) { gpg_error_t err; crl_cache_result_t result; unsigned char issuerhash[20]; char issuerhash_hex[41]; ksba_sexp_t serial; unsigned char *sn; size_t snlen; char *endp, *tmp; int i; /* Compute the hash value of the issuer name. */ tmp = ksba_cert_get_issuer (cert, 0); if (!tmp) { log_error ("oops: issuer missing in certificate\n"); return gpg_error (GPG_ERR_INV_CERT_OBJ); } gcry_md_hash_buffer (GCRY_MD_SHA1, issuerhash, tmp, strlen (tmp)); xfree (tmp); for (i=0,tmp=issuerhash_hex; i < 20; i++, tmp += 2) sprintf (tmp, "%02X", issuerhash[i]); /* Get the serial number. */ serial = ksba_cert_get_serial (cert); if (!serial) { log_error ("oops: S/N missing in certificate\n"); return gpg_error (GPG_ERR_INV_CERT_OBJ); } sn = serial; if (*sn != '(') { log_error ("oops: invalid S/N\n"); xfree (serial); return gpg_error (GPG_ERR_INV_CERT_OBJ); } sn++; snlen = strtoul (sn, &endp, 10); sn = endp; if (*sn != ':') { log_error ("oops: invalid S/N\n"); xfree (serial); return gpg_error (GPG_ERR_INV_CERT_OBJ); } sn++; /* Check the cache. */ result = cache_isvalid (ctrl, issuerhash_hex, sn, snlen, force_refresh); switch (result) { case CRL_CACHE_VALID: err = 0; break; case CRL_CACHE_INVALID: err = gpg_error (GPG_ERR_CERT_REVOKED); break; case CRL_CACHE_DONTKNOW: err = gpg_error (GPG_ERR_NO_CRL_KNOWN); break; case CRL_CACHE_CANTUSE: err = gpg_error (GPG_ERR_NO_CRL_KNOWN); break; default: log_fatal ("cache_isvalid returned invalid status code %d\n", result); } xfree (serial); return err; } /* Prepare a hash context for the signature verification. Input is the CRL and the output is the hash context MD as well as the uses algorithm identifier ALGO. */ static gpg_error_t start_sig_check (ksba_crl_t crl, gcry_md_hd_t *md, int *algo) { gpg_error_t err; const char *algoid; algoid = ksba_crl_get_digest_algo (crl); *algo = gcry_md_map_name (algoid); if (!*algo) { log_error (_("unknown hash algorithm '%s'\n"), algoid? algoid:"?"); return gpg_error (GPG_ERR_DIGEST_ALGO); } err = gcry_md_open (md, *algo, 0); if (err) { log_error (_("gcry_md_open for algorithm %d failed: %s\n"), *algo, gcry_strerror (err)); return err; } if (DBG_HASHING) gcry_md_debug (*md, "hash.cert"); ksba_crl_set_hash_function (crl, HASH_FNC, *md); return 0; } /* Finish a hash context and verify the signature. This function should return 0 on a good signature, GPG_ERR_BAD_SIGNATURE if the signature does not verify or any other error code. CRL is the CRL object we are working on, MD the hash context and ISSUER_CERT the certificate of the CRL issuer. This function closes MD. */ static gpg_error_t finish_sig_check (ksba_crl_t crl, gcry_md_hd_t md, int algo, ksba_cert_t issuer_cert) { gpg_error_t err; ksba_sexp_t sigval = NULL, pubkey = NULL; const char *s; char algoname[50]; size_t n; gcry_sexp_t s_sig = NULL, s_hash = NULL, s_pkey = NULL; unsigned int i; /* This also stops debugging on the MD. */ gcry_md_final (md); /* Get and convert the signature value. */ sigval = ksba_crl_get_sig_val (crl); n = gcry_sexp_canon_len (sigval, 0, NULL, NULL); if (!n) { log_error (_("got an invalid S-expression from libksba\n")); err = gpg_error (GPG_ERR_INV_SEXP); goto leave; } err = gcry_sexp_sscan (&s_sig, NULL, sigval, n); if (err) { log_error (_("converting S-expression failed: %s\n"), gcry_strerror (err)); goto leave; } /* Get and convert the public key for the issuer certificate. */ if (DBG_X509) dump_cert ("crl_issuer_cert", issuer_cert); pubkey = ksba_cert_get_public_key (issuer_cert); n = gcry_sexp_canon_len (pubkey, 0, NULL, NULL); if (!n) { log_error (_("got an invalid S-expression from libksba\n")); err = gpg_error (GPG_ERR_INV_SEXP); goto leave; } err = gcry_sexp_sscan (&s_pkey, NULL, pubkey, n); if (err) { log_error (_("converting S-expression failed: %s\n"), gcry_strerror (err)); goto leave; } /* Create an S-expression with the actual hash value. */ s = gcry_md_algo_name (algo); for (i = 0; *s && i < sizeof(algoname) - 1; s++, i++) algoname[i] = ascii_tolower (*s); algoname[i] = 0; err = gcry_sexp_build (&s_hash, NULL, "(data(flags pkcs1)(hash %s %b))", algoname, gcry_md_get_algo_dlen (algo), gcry_md_read (md, algo)); if (err) { log_error (_("creating S-expression failed: %s\n"), gcry_strerror (err)); goto leave; } /* Pass this on to the signature verification. */ err = gcry_pk_verify (s_sig, s_hash, s_pkey); if (DBG_X509) log_debug ("gcry_pk_verify: %s\n", gpg_strerror (err)); leave: xfree (sigval); xfree (pubkey); gcry_sexp_release (s_sig); gcry_sexp_release (s_hash); gcry_sexp_release (s_pkey); gcry_md_close (md); return err; } /* Call this to match a start_sig_check that can not be completed normally. */ static void abort_sig_check (ksba_crl_t crl, gcry_md_hd_t md) { (void)crl; gcry_md_close (md); } /* Workhorse of the CRL loading machinery. The CRL is read using the CRL object and stored in the data base file DB with the name FNAME (only used for printing error messages). That DB should be a temporary one and not the actual one. If the function fails the caller should delete this temporary database file. CTRL is required to retrieve certificates using the general dirmngr callback service. R_CRLISSUER returns an allocated string with the crl-issuer DN, THIS_UPDATE and NEXT_UPDATE are filled with the corresponding data from the CRL. Note that these values might get set even if the CRL processing fails at a later step; thus the caller should free *R_ISSUER even if the function returns with an error. R_TRUST_ANCHOR is set on exit to NULL or a string with the hexified fingerprint of the root certificate, if checking this certificate for trustiness is required. */ static int crl_parse_insert (ctrl_t ctrl, ksba_crl_t crl, struct cdb_make *cdb, const char *fname, char **r_crlissuer, ksba_isotime_t thisupdate, ksba_isotime_t nextupdate, char **r_trust_anchor) { gpg_error_t err; ksba_stop_reason_t stopreason; ksba_cert_t crlissuer_cert = NULL; gcry_md_hd_t md = NULL; int algo = 0; size_t n; (void)fname; *r_crlissuer = NULL; *thisupdate = *nextupdate = 0; *r_trust_anchor = NULL; /* Start of the KSBA parser loop. */ do { err = ksba_crl_parse (crl, &stopreason); if (err) { log_error (_("ksba_crl_parse failed: %s\n"), gpg_strerror (err) ); goto failure; } switch (stopreason) { case KSBA_SR_BEGIN_ITEMS: { err = start_sig_check (crl, &md, &algo); if (err) goto failure; err = ksba_crl_get_update_times (crl, thisupdate, nextupdate); if (err) { log_error (_("error getting update times of CRL: %s\n"), gpg_strerror (err)); err = gpg_error (GPG_ERR_INV_CRL); goto failure; } if (opt.verbose || !*nextupdate) log_info (_("update times of this CRL: this=%s next=%s\n"), thisupdate, nextupdate); if (!*nextupdate) { log_info (_("nextUpdate not given; " "assuming a validity period of one day\n")); gnupg_copy_time (nextupdate, thisupdate); add_seconds_to_isotime (nextupdate, 86400); } } break; case KSBA_SR_GOT_ITEM: { ksba_sexp_t serial; const unsigned char *p; ksba_isotime_t rdate; ksba_crl_reason_t reason; int rc; unsigned char record[1+15]; err = ksba_crl_get_item (crl, &serial, rdate, &reason); if (err) { log_error (_("error getting CRL item: %s\n"), gpg_strerror (err)); err = gpg_error (GPG_ERR_INV_CRL); ksba_free (serial); goto failure; } p = serial_to_buffer (serial, &n); if (!p) BUG (); record[0] = (reason & 0xff); memcpy (record+1, rdate, 15); rc = cdb_make_add (cdb, p, n, record, 1+15); if (rc) { err = gpg_error_from_errno (errno); log_error (_("error inserting item into " "temporary cache file: %s\n"), strerror (errno)); goto failure; } ksba_free (serial); } break; case KSBA_SR_END_ITEMS: break; case KSBA_SR_READY: { char *crlissuer; ksba_name_t authid; ksba_sexp_t authidsn; ksba_sexp_t keyid; /* We need to look for the issuer only after having read all items. The issuer itselfs comes before the items but the optional authorityKeyIdentifier comes after the items. */ err = ksba_crl_get_issuer (crl, &crlissuer); if( err ) { log_error (_("no CRL issuer found in CRL: %s\n"), gpg_strerror (err) ); err = gpg_error (GPG_ERR_INV_CRL); goto failure; } /* Note: This should be released by ksba_free, not xfree. May need a memory reallocation dance. */ *r_crlissuer = crlissuer; /* (Do it here so we don't need to free it later) */ if (!ksba_crl_get_auth_key_id (crl, &keyid, &authid, &authidsn)) { const char *s; if (opt.verbose) log_info (_("locating CRL issuer certificate by " "authorityKeyIdentifier\n")); s = ksba_name_enum (authid, 0); if (s && *authidsn) crlissuer_cert = find_cert_bysn (ctrl, s, authidsn); if (!crlissuer_cert && keyid) crlissuer_cert = find_cert_bysubject (ctrl, crlissuer, keyid); if (!crlissuer_cert) { log_info ("CRL issuer certificate "); if (keyid) { log_printf ("{"); dump_serial (keyid); log_printf ("} "); } if (authidsn) { log_printf ("(#"); dump_serial (authidsn); log_printf ("/"); dump_string (s); log_printf (") "); } log_printf ("not found\n"); } ksba_name_release (authid); xfree (authidsn); xfree (keyid); } else crlissuer_cert = find_cert_bysubject (ctrl, crlissuer, NULL); err = 0; if (!crlissuer_cert) { err = gpg_error (GPG_ERR_MISSING_CERT); goto failure; } err = finish_sig_check (crl, md, algo, crlissuer_cert); if (err) { log_error (_("CRL signature verification failed: %s\n"), gpg_strerror (err)); goto failure; } md = NULL; err = validate_cert_chain (ctrl, crlissuer_cert, NULL, VALIDATE_MODE_CRL_RECURSIVE, r_trust_anchor); if (err) { log_error (_("error checking validity of CRL " "issuer certificate: %s\n"), gpg_strerror (err)); goto failure; } } break; default: log_debug ("crl_parse_insert: unknown stop reason\n"); err = gpg_error (GPG_ERR_BUG); goto failure; } } while (stopreason != KSBA_SR_READY); assert (!err); failure: if (md) abort_sig_check (crl, md); ksba_cert_release (crlissuer_cert); return err; } /* Return the crlNumber extension as an allocated hex string or NULL if there is none. */ static char * get_crl_number (ksba_crl_t crl) { gpg_error_t err; ksba_sexp_t number; char *string; err = ksba_crl_get_crl_number (crl, &number); if (err) return NULL; string = serial_hex (number); ksba_free (number); return string; } /* Return the authorityKeyIdentifier or NULL if it is not available. The issuer name may consists of several parts - they are delimted by 0x01. */ static char * get_auth_key_id (ksba_crl_t crl, char **serialno) { gpg_error_t err; ksba_name_t name; ksba_sexp_t sn; int idx; const char *s; char *string; size_t length; *serialno = NULL; err = ksba_crl_get_auth_key_id (crl, NULL, &name, &sn); if (err) return NULL; *serialno = serial_hex (sn); ksba_free (sn); if (!name) return xstrdup (""); length = 0; for (idx=0; (s = ksba_name_enum (name, idx)); idx++) { char *p = ksba_name_get_uri (name, idx); length += strlen (p?p:s) + 1; xfree (p); } string = xtrymalloc (length+1); if (string) { *string = 0; for (idx=0; (s = ksba_name_enum (name, idx)); idx++) { char *p = ksba_name_get_uri (name, idx); if (*string) strcat (string, "\x01"); strcat (string, p?p:s); xfree (p); } } ksba_name_release (name); return string; } /* Insert the CRL retrieved using URL into the cache specified by CACHE. The CRL itself will be read from the stream FP and is expected in binary format. Called by: crl_cache_load cmd_loadcrl --load-crl crl_cache_reload_crl cmd_isvalid cmd_checkcrl cmd_loadcrl --fetch-crl */ gpg_error_t crl_cache_insert (ctrl_t ctrl, const char *url, ksba_reader_t reader) { crl_cache_t cache = get_current_cache (); gpg_error_t err, err2; ksba_crl_t crl; char *fname = NULL; char *newfname = NULL; struct cdb_make cdb; int fd_cdb = -1; char *issuer = NULL; char *issuer_hash = NULL; ksba_isotime_t thisupdate, nextupdate; crl_cache_entry_t entry = NULL; crl_cache_entry_t e; gnupg_isotime_t current_time; char *checksum = NULL; int invalidate_crl = 0; int idx; const char *oid; int critical; char *trust_anchor = NULL; /* FIXME: We should acquire a mutex for the URL, so that we don't simultaneously enter the same CRL twice. However this needs to be interweaved with the checking function.*/ err2 = 0; err = ksba_crl_new (&crl); if (err) { log_error (_("ksba_crl_new failed: %s\n"), gpg_strerror (err)); goto leave; } err = ksba_crl_set_reader (crl, reader); if ( err ) { log_error (_("ksba_crl_set_reader failed: %s\n"), gpg_strerror (err)); goto leave; } /* Create a temporary cache file to load the CRL into. */ { char *tmpfname, *p; const char *nodename; #ifndef HAVE_W32_SYSTEM struct utsname utsbuf; #endif #ifdef HAVE_W32_SYSTEM nodename = "unknown"; #else if (uname (&utsbuf)) nodename = "unknown"; else nodename = utsbuf.nodename; #endif gpgrt_asprintf (&tmpfname, "crl-tmp-%s-%u-%p.db.tmp", nodename, (unsigned int)getpid (), &tmpfname); if (!tmpfname) { err = gpg_error_from_syserror (); goto leave; } for (p=tmpfname; *p; p++) if (*p == '/') *p = '.'; fname = make_filename (opt.homedir_cache, DBDIR_D, tmpfname, NULL); xfree (tmpfname); if (!gnupg_remove (fname)) log_info (_("removed stale temporary cache file '%s'\n"), fname); else if (errno != ENOENT) { err = gpg_error_from_syserror (); log_error (_("problem removing stale temporary cache file '%s': %s\n"), fname, gpg_strerror (err)); goto leave; } } fd_cdb = open (fname, O_WRONLY | O_CREAT | O_TRUNC, 0644); if (fd_cdb == -1) { err = gpg_error_from_errno (errno); log_error (_("error creating temporary cache file '%s': %s\n"), fname, strerror (errno)); goto leave; } cdb_make_start(&cdb, fd_cdb); err = crl_parse_insert (ctrl, crl, &cdb, fname, &issuer, thisupdate, nextupdate, &trust_anchor); if (err) { log_error (_("crl_parse_insert failed: %s\n"), gpg_strerror (err)); /* Error in cleanup ignored. */ cdb_make_finish (&cdb); goto leave; } /* Finish the database. */ if (cdb_make_finish (&cdb)) { err = gpg_error_from_errno (errno); log_error (_("error finishing temporary cache file '%s': %s\n"), fname, strerror (errno)); goto leave; } if (close (fd_cdb)) { err = gpg_error_from_errno (errno); log_error (_("error closing temporary cache file '%s': %s\n"), fname, strerror (errno)); goto leave; } fd_cdb = -1; /* Create a checksum. */ { unsigned char md5buf[16]; if (hash_dbfile (fname, md5buf)) { err = gpg_error (GPG_ERR_CHECKSUM); goto leave; } checksum = hexify_data (md5buf, 16, 0); } /* Check whether that new CRL is still not expired. */ gnupg_get_isotime (current_time); if (strcmp (nextupdate, current_time) < 0 ) { if (opt.force) log_info (_("WARNING: new CRL still too old; it expired on %s " "- loading anyway\n"), nextupdate); else { log_error (_("new CRL still too old; it expired on %s\n"), nextupdate); if (!err2) err2 = gpg_error (GPG_ERR_CRL_TOO_OLD); invalidate_crl |= 1; } } /* Check for unknown critical extensions. */ for (idx=0; !(err=ksba_crl_get_extension (crl, idx, &oid, &critical, NULL, NULL)); idx++) { if (!critical || !strcmp (oid, oidstr_authorityKeyIdentifier) || !strcmp (oid, oidstr_crlNumber) ) continue; log_error (_("unknown critical CRL extension %s\n"), oid); if (!err2) err2 = gpg_error (GPG_ERR_INV_CRL); invalidate_crl |= 2; } if (gpg_err_code (err) == GPG_ERR_EOF || gpg_err_code (err) == GPG_ERR_NO_DATA ) err = 0; if (err) { log_error (_("error reading CRL extensions: %s\n"), gpg_strerror (err)); err = gpg_error (GPG_ERR_INV_CRL); } /* Create an hex encoded SHA-1 hash of the issuer DN to be used as the key for the cache. */ issuer_hash = hashify_data (issuer, strlen (issuer)); /* Create an ENTRY. */ entry = xtrycalloc (1, sizeof *entry); if (!entry) { err = gpg_error_from_syserror (); goto leave; } entry->release_ptr = xtrymalloc (strlen (issuer_hash) + 1 + strlen (issuer) + 1 + strlen (url) + 1 + strlen (checksum) + 1); if (!entry->release_ptr) { err = gpg_error_from_syserror (); xfree (entry); entry = NULL; goto leave; } entry->issuer_hash = entry->release_ptr; entry->issuer = stpcpy (entry->issuer_hash, issuer_hash) + 1; entry->url = stpcpy (entry->issuer, issuer) + 1; entry->dbfile_hash = stpcpy (entry->url, url) + 1; strcpy (entry->dbfile_hash, checksum); gnupg_copy_time (entry->this_update, thisupdate); gnupg_copy_time (entry->next_update, nextupdate); gnupg_copy_time (entry->last_refresh, current_time); entry->crl_number = get_crl_number (crl); entry->authority_issuer = get_auth_key_id (crl, &entry->authority_serialno); entry->invalid = invalidate_crl; entry->user_trust_req = !!trust_anchor; entry->check_trust_anchor = trust_anchor; trust_anchor = NULL; /* Check whether we already have an entry for this issuer and mark it as deleted. We better use a loop, just in case duplicates got somehow into the list. */ for (e = cache->entries; (e=find_entry (e, entry->issuer_hash)); e = e->next) e->deleted = 1; /* Rename the temporary DB to the real name. */ newfname = make_db_file_name (entry->issuer_hash); if (opt.verbose) log_info (_("creating cache file '%s'\n"), newfname); /* Just in case close unused matching files. Actually we need this only under Windows but saving file descriptors is never bad. */ { int any; do { any = 0; for (e = cache->entries; e; e = e->next) if (!e->cdb_use_count && e->cdb && !strcmp (e->issuer_hash, entry->issuer_hash)) { int fd = cdb_fileno (e->cdb); cdb_free (e->cdb); xfree (e->cdb); e->cdb = NULL; if (close (fd)) log_error (_("error closing cache file: %s\n"), strerror(errno)); any = 1; break; } } while (any); } #ifdef HAVE_W32_SYSTEM gnupg_remove (newfname); #endif if (rename (fname, newfname)) { err = gpg_error_from_syserror (); log_error (_("problem renaming '%s' to '%s': %s\n"), fname, newfname, gpg_strerror (err)); goto leave; } xfree (fname); fname = NULL; /*(let the cleanup code not try to remove it)*/ /* Link the new entry in. */ entry->next = cache->entries; cache->entries = entry; entry = NULL; err = update_dir (cache); if (err) { log_error (_("updating the DIR file failed - " "cache entry will get lost with the next program start\n")); err = 0; /* Keep on running. */ } leave: release_one_cache_entry (entry); if (fd_cdb != -1) close (fd_cdb); if (fname) { gnupg_remove (fname); xfree (fname); } xfree (newfname); ksba_crl_release (crl); xfree (issuer); xfree (issuer_hash); xfree (checksum); xfree (trust_anchor); return err ? err : err2; } /* Print one cached entry E in a human readable format to stream FP. Return 0 on success. */ static gpg_error_t list_one_crl_entry (crl_cache_t cache, crl_cache_entry_t e, estream_t fp) { struct cdb_find cdbfp; struct cdb *cdb; int rc; int warn = 0; const unsigned char *s; es_fputs ("--------------------------------------------------------\n", fp ); es_fprintf (fp, _("Begin CRL dump (retrieved via %s)\n"), e->url ); es_fprintf (fp, " Issuer:\t%s\n", e->issuer ); es_fprintf (fp, " Issuer Hash:\t%s\n", e->issuer_hash ); es_fprintf (fp, " This Update:\t%s\n", e->this_update ); es_fprintf (fp, " Next Update:\t%s\n", e->next_update ); es_fprintf (fp, " CRL Number :\t%s\n", e->crl_number? e->crl_number: "none"); es_fprintf (fp, " AuthKeyId :\t%s\n", e->authority_serialno? e->authority_serialno:"none"); if (e->authority_serialno && e->authority_issuer) { es_fputs (" \t", fp); for (s=e->authority_issuer; *s; s++) if (*s == '\x01') es_fputs ("\n \t", fp); else es_putc (*s, fp); es_putc ('\n', fp); } es_fprintf (fp, " Trust Check:\t%s\n", !e->user_trust_req? "[system]" : e->check_trust_anchor? e->check_trust_anchor:"[missing]"); if ((e->invalid & 1)) es_fprintf (fp, _(" ERROR: The CRL will not be used " "because it was still too old after an update!\n")); if ((e->invalid & 2)) es_fprintf (fp, _(" ERROR: The CRL will not be used " "due to an unknown critical extension!\n")); if ((e->invalid & ~3)) es_fprintf (fp, _(" ERROR: The CRL will not be used\n")); cdb = lock_db_file (cache, e); if (!cdb) return gpg_error (GPG_ERR_GENERAL); if (!e->dbfile_checked) es_fprintf (fp, _(" ERROR: This cached CRL may have been tampered with!\n")); es_putc ('\n', fp); rc = cdb_findinit (&cdbfp, cdb, NULL, 0); while (!rc && (rc=cdb_findnext (&cdbfp)) > 0 ) { unsigned char keyrecord[256]; unsigned char record[16]; int reason; int any = 0; cdbi_t n; cdbi_t i; rc = 0; n = cdb_datalen (cdb); if (n != 16) { log_error (_(" WARNING: invalid cache record length\n")); warn = 1; continue; } if (cdb_read (cdb, record, n, cdb_datapos (cdb))) { log_error (_("problem reading cache record: %s\n"), strerror (errno)); warn = 1; continue; } n = cdb_keylen (cdb); if (n > sizeof keyrecord) n = sizeof keyrecord; if (cdb_read (cdb, keyrecord, n, cdb_keypos (cdb))) { log_error (_("problem reading cache key: %s\n"), strerror (errno)); warn = 1; continue; } reason = *record; es_fputs (" ", fp); for (i = 0; i < n; i++) es_fprintf (fp, "%02X", keyrecord[i]); es_fputs (":\t reasons( ", fp); if (reason & KSBA_CRLREASON_UNSPECIFIED) es_fputs( "unspecified ", fp ), any = 1; if (reason & KSBA_CRLREASON_KEY_COMPROMISE ) es_fputs( "key_compromise ", fp ), any = 1; if (reason & KSBA_CRLREASON_CA_COMPROMISE ) es_fputs( "ca_compromise ", fp ), any = 1; if (reason & KSBA_CRLREASON_AFFILIATION_CHANGED ) es_fputs( "affiliation_changed ", fp ), any = 1; if (reason & KSBA_CRLREASON_SUPERSEDED ) es_fputs( "superseded", fp ), any = 1; if (reason & KSBA_CRLREASON_CESSATION_OF_OPERATION ) es_fputs( "cessation_of_operation", fp ), any = 1; if (reason & KSBA_CRLREASON_CERTIFICATE_HOLD ) es_fputs( "certificate_hold", fp ), any = 1; if (reason && !any) es_fputs( "other", fp ); es_fprintf (fp, ") rdate: %.15s\n", record+1); } if (rc) log_error (_("error reading cache entry from db: %s\n"), strerror (rc)); unlock_db_file (cache, e); es_fprintf (fp, _("End CRL dump\n") ); es_putc ('\n', fp); return (rc||warn)? gpg_error (GPG_ERR_GENERAL) : 0; } /* Print the contents of the CRL CACHE in a human readable format to stream FP. */ gpg_error_t crl_cache_list (estream_t fp) { crl_cache_t cache = get_current_cache (); crl_cache_entry_t entry; gpg_error_t err = 0; for (entry = cache->entries; entry && !entry->deleted && !err; entry = entry->next ) err = list_one_crl_entry (cache, entry, fp); return err; } /* Load the CRL containing the file named FILENAME into our CRL cache. */ gpg_error_t crl_cache_load (ctrl_t ctrl, const char *filename) { gpg_error_t err; estream_t fp; ksba_reader_t reader; fp = es_fopen (filename, "r"); if (!fp) { err = gpg_error_from_errno (errno); log_error (_("can't open '%s': %s\n"), filename, strerror (errno)); return err; } err = create_estream_ksba_reader (&reader, fp); if (!err) { err = crl_cache_insert (ctrl, filename, reader); ksba_reader_release (reader); } es_fclose (fp); return err; } /* Locate the corresponding CRL for the certificate CERT, read and verify the CRL and store it in the cache. */ gpg_error_t crl_cache_reload_crl (ctrl_t ctrl, ksba_cert_t cert) { gpg_error_t err; ksba_reader_t reader = NULL; char *issuer = NULL; ksba_name_t distpoint = NULL; ksba_name_t issuername = NULL; char *distpoint_uri = NULL; char *issuername_uri = NULL; int any_dist_point = 0; int seq; /* Loop over all distribution points, get the CRLs and put them into the cache. */ if (opt.verbose) log_info ("checking distribution points\n"); seq = 0; while ( !(err = ksba_cert_get_crl_dist_point (cert, seq++, &distpoint, &issuername, NULL ))) { int name_seq; gpg_error_t last_err = 0; if (!distpoint && !issuername) { if (opt.verbose) log_info ("no issuer name and no distribution point\n"); break; /* Not allowed; i.e. an invalid certificate. We give up here and hope that the default method returns a suitable CRL. */ } xfree (issuername_uri); issuername_uri = NULL; /* Get the URIs. We do this in a loop to iterate over all names in the crlDP. */ for (name_seq=0; ksba_name_enum (distpoint, name_seq); name_seq++) { xfree (distpoint_uri); distpoint_uri = NULL; distpoint_uri = ksba_name_get_uri (distpoint, name_seq); if (!distpoint_uri) continue; if (!strncmp (distpoint_uri, "ldap:", 5) || !strncmp (distpoint_uri, "ldaps:", 6)) { if (opt.ignore_ldap_dp) continue; } else if (!strncmp (distpoint_uri, "http:", 5) || !strncmp (distpoint_uri, "https:", 6)) { if (opt.ignore_http_dp) continue; } else continue; /* Skip unknown schemes. */ any_dist_point = 1; if (opt.verbose) log_info ("fetching CRL from '%s'\n", distpoint_uri); err = crl_fetch (ctrl, distpoint_uri, &reader); if (err) { log_error (_("crl_fetch via DP failed: %s\n"), gpg_strerror (err)); last_err = err; continue; /* with the next name. */ } if (opt.verbose) log_info ("inserting CRL (reader %p)\n", reader); err = crl_cache_insert (ctrl, distpoint_uri, reader); if (err) { log_error (_("crl_cache_insert via DP failed: %s\n"), gpg_strerror (err)); last_err = err; continue; /* with the next name. */ } last_err = 0; break; /* Ready. */ } if (last_err) { err = last_err; goto leave; } ksba_name_release (distpoint); distpoint = NULL; /* We don't do anything with issuername_uri yet but we keep the code for documentation. */ issuername_uri = ksba_name_get_uri (issuername, 0); ksba_name_release (issuername); issuername = NULL; /* Close the reader. */ crl_close_reader (reader); reader = NULL; } if (gpg_err_code (err) == GPG_ERR_EOF) err = 0; /* If we did not found any distpoint, try something reasonable. */ if (!any_dist_point ) { if (opt.verbose) log_info ("no distribution point - trying issuer name\n"); crl_close_reader (reader); reader = NULL; issuer = ksba_cert_get_issuer (cert, 0); if (!issuer) { log_error ("oops: issuer missing in certificate\n"); err = gpg_error (GPG_ERR_INV_CERT_OBJ); goto leave; } if (opt.verbose) log_info ("fetching CRL from default location\n"); err = crl_fetch_default (ctrl, issuer, &reader); if (err) { log_error ("crl_fetch via issuer failed: %s\n", gpg_strerror (err)); goto leave; } if (opt.verbose) log_info ("inserting CRL (reader %p)\n", reader); err = crl_cache_insert (ctrl, "default location(s)", reader); if (err) { log_error (_("crl_cache_insert via issuer failed: %s\n"), gpg_strerror (err)); goto leave; } } leave: crl_close_reader (reader); xfree (distpoint_uri); xfree (issuername_uri); ksba_name_release (distpoint); ksba_name_release (issuername); ksba_free (issuer); return err; } diff --git a/dirmngr/dirmngr.c b/dirmngr/dirmngr.c index cb1742080..0667e59a2 100644 --- a/dirmngr/dirmngr.c +++ b/dirmngr/dirmngr.c @@ -1,2145 +1,1973 @@ /* dirmngr.c - Keyserver and X.509 LDAP access * Copyright (C) 2002 Klarälvdalens Datakonsult AB * Copyright (C) 2003, 2004, 2006, 2007, 2008, 2010, 2011 g10 Code GmbH * Copyright (C) 2014 Werner Koch * * This file is part of GnuPG. * * GnuPG is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3 of the License, or * (at your option) any later version. * * GnuPG 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 General Public License * along with this program; if not, see . */ #include #include #include #include #include #include #include #include #include #include #ifndef HAVE_W32_SYSTEM #include #include #endif #include #include #ifdef HAVE_SIGNAL_H # include #endif #include #include "dirmngr-err.h" #if HTTP_USE_NTBTLS # include #elif HTTP_USE_GNUTLS # include #endif /*HTTP_USE_GNUTLS*/ #define GNUPG_COMMON_NEED_AFLOCAL #include "dirmngr.h" #include #include "certcache.h" #include "crlcache.h" #include "crlfetch.h" #include "misc.h" #if USE_LDAP # include "ldapserver.h" #endif #include "asshelp.h" #if USE_LDAP # include "ldap-wrapper.h" #endif #include "../common/init.h" #include "gc-opt-flags.h" #include "dns-stuff.h" -/* The plain Windows version uses the windows service system. For - example to start the service you may use "sc start dirmngr". - WindowsCE does not support this; the service system over there is - based on a single process with all services being DLLs - we can't - support this easily. */ -#if defined(HAVE_W32_SYSTEM) && !defined(HAVE_W32CE_SYSTEM) -# define USE_W32_SERVICE 1 -#endif - #ifndef ENAMETOOLONG # define ENAMETOOLONG EINVAL #endif enum cmd_and_opt_values { aNull = 0, oCsh = 'c', oQuiet = 'q', oSh = 's', oVerbose = 'v', oNoVerbose = 500, aServer, aDaemon, - aService, aListCRLs, aLoadCRL, aFetchCRL, aShutdown, aFlush, aGPGConfList, aGPGConfTest, oOptions, oDebug, oDebugAll, oDebugWait, oDebugLevel, oGnutlsDebug, oNoGreeting, oNoOptions, oHomedir, oNoDetach, oLogFile, oBatch, oDisableHTTP, oDisableLDAP, oIgnoreLDAPDP, oIgnoreHTTPDP, oIgnoreOCSPSvcUrl, oHonorHTTPProxy, oHTTPProxy, oLDAPProxy, oOnlyLDAPProxy, oLDAPFile, oLDAPTimeout, oLDAPAddServers, oOCSPResponder, oOCSPSigner, oOCSPMaxClockSkew, oOCSPMaxPeriod, oOCSPCurrentPeriod, oMaxReplies, oHkpCaCert, oFakedSystemTime, oForce, oAllowOCSP, oSocketName, oLDAPWrapperProgram, oHTTPWrapperProgram, oIgnoreCertExtension, oUseTor, oKeyServer, oNameServer, aTest }; static ARGPARSE_OPTS opts[] = { ARGPARSE_group (300, N_("@Commands:\n ")), ARGPARSE_c (aServer, "server", N_("run in server mode (foreground)") ), ARGPARSE_c (aDaemon, "daemon", N_("run in daemon mode (background)") ), -#ifdef USE_W32_SERVICE - ARGPARSE_c (aService, "service", N_("run as windows service (background)")), -#endif ARGPARSE_c (aListCRLs, "list-crls", N_("list the contents of the CRL cache")), ARGPARSE_c (aLoadCRL, "load-crl", N_("|FILE|load CRL from FILE into cache")), ARGPARSE_c (aFetchCRL, "fetch-crl", N_("|URL|fetch a CRL from URL")), ARGPARSE_c (aShutdown, "shutdown", N_("shutdown the dirmngr")), ARGPARSE_c (aFlush, "flush", N_("flush the cache")), ARGPARSE_c (aGPGConfList, "gpgconf-list", "@"), ARGPARSE_c (aGPGConfTest, "gpgconf-test", "@"), ARGPARSE_group (301, N_("@\nOptions:\n ")), ARGPARSE_s_n (oVerbose, "verbose", N_("verbose")), ARGPARSE_s_n (oQuiet, "quiet", N_("be somewhat more quiet")), ARGPARSE_s_n (oSh, "sh", N_("sh-style command output")), ARGPARSE_s_n (oCsh, "csh", N_("csh-style command output")), ARGPARSE_s_s (oOptions, "options", N_("|FILE|read options from FILE")), ARGPARSE_s_s (oDebugLevel, "debug-level", N_("|LEVEL|set the debugging level to LEVEL")), ARGPARSE_s_n (oNoDetach, "no-detach", N_("do not detach from the console")), ARGPARSE_s_s (oLogFile, "log-file", N_("|FILE|write server mode logs to FILE")), ARGPARSE_s_n (oBatch, "batch", N_("run without asking a user")), ARGPARSE_s_n (oForce, "force", N_("force loading of outdated CRLs")), ARGPARSE_s_n (oAllowOCSP, "allow-ocsp", N_("allow sending OCSP requests")), ARGPARSE_s_n (oDisableHTTP, "disable-http", N_("inhibit the use of HTTP")), ARGPARSE_s_n (oDisableLDAP, "disable-ldap", N_("inhibit the use of LDAP")), ARGPARSE_s_n (oIgnoreHTTPDP,"ignore-http-dp", N_("ignore HTTP CRL distribution points")), ARGPARSE_s_n (oIgnoreLDAPDP,"ignore-ldap-dp", N_("ignore LDAP CRL distribution points")), ARGPARSE_s_n (oIgnoreOCSPSvcUrl, "ignore-ocsp-service-url", N_("ignore certificate contained OCSP service URLs")), ARGPARSE_s_s (oHTTPProxy, "http-proxy", N_("|URL|redirect all HTTP requests to URL")), ARGPARSE_s_s (oLDAPProxy, "ldap-proxy", N_("|HOST|use HOST for LDAP queries")), ARGPARSE_s_n (oOnlyLDAPProxy, "only-ldap-proxy", N_("do not use fallback hosts with --ldap-proxy")), ARGPARSE_s_s (oLDAPFile, "ldapserverlist-file", N_("|FILE|read LDAP server list from FILE")), ARGPARSE_s_n (oLDAPAddServers, "add-servers", N_("add new servers discovered in CRL distribution" " points to serverlist")), ARGPARSE_s_i (oLDAPTimeout, "ldaptimeout", N_("|N|set LDAP timeout to N seconds")), ARGPARSE_s_s (oOCSPResponder, "ocsp-responder", N_("|URL|use OCSP responder at URL")), ARGPARSE_s_s (oOCSPSigner, "ocsp-signer", N_("|FPR|OCSP response signed by FPR")), ARGPARSE_s_i (oOCSPMaxClockSkew, "ocsp-max-clock-skew", "@"), ARGPARSE_s_i (oOCSPMaxPeriod, "ocsp-max-period", "@"), ARGPARSE_s_i (oOCSPCurrentPeriod, "ocsp-current-period", "@"), ARGPARSE_s_i (oMaxReplies, "max-replies", N_("|N|do not return more than N items in one query")), ARGPARSE_s_s (oNameServer, "nameserver", "@"), ARGPARSE_s_s (oKeyServer, "keyserver", "@"), ARGPARSE_s_s (oHkpCaCert, "hkp-cacert", N_("|FILE|use the CA certificates in FILE for HKP over TLS")), ARGPARSE_s_n (oUseTor, "use-tor", N_("route all network traffic via Tor")), ARGPARSE_s_s (oSocketName, "socket-name", "@"), /* Only for debugging. */ ARGPARSE_s_u (oFakedSystemTime, "faked-system-time", "@"), /*(epoch time)*/ ARGPARSE_s_s (oDebug, "debug", "@"), ARGPARSE_s_n (oDebugAll, "debug-all", "@"), ARGPARSE_s_i (oGnutlsDebug, "gnutls-debug", "@"), ARGPARSE_s_i (oGnutlsDebug, "tls-debug", "@"), ARGPARSE_s_i (oDebugWait, "debug-wait", "@"), ARGPARSE_s_n (oNoGreeting, "no-greeting", "@"), ARGPARSE_s_s (oHomedir, "homedir", "@"), ARGPARSE_s_s (oLDAPWrapperProgram, "ldap-wrapper-program", "@"), ARGPARSE_s_s (oHTTPWrapperProgram, "http-wrapper-program", "@"), ARGPARSE_s_n (oHonorHTTPProxy, "honor-http-proxy", "@"), ARGPARSE_s_s (oIgnoreCertExtension,"ignore-cert-extension", "@"), ARGPARSE_group (302,N_("@\n(See the \"info\" manual for a complete listing " "of all commands and options)\n")), ARGPARSE_end () }; /* The list of supported debug flags. */ static struct debug_flags_s debug_flags [] = { { DBG_X509_VALUE , "x509" }, { DBG_CRYPTO_VALUE , "crypto" }, { DBG_MEMORY_VALUE , "memory" }, { DBG_CACHE_VALUE , "cache" }, { DBG_MEMSTAT_VALUE, "memstat" }, { DBG_HASHING_VALUE, "hashing" }, { DBG_IPC_VALUE , "ipc" }, { DBG_LOOKUP_VALUE , "lookup" }, { 77, NULL } /* 77 := Do not exit on "help" or "?". */ }; #define DEFAULT_MAX_REPLIES 10 #define DEFAULT_LDAP_TIMEOUT 100 /* arbitrary large timeout */ /* For the cleanup handler we need to keep track of the socket's name. */ static const char *socket_name; /* If the socket has been redirected, this is the name of the redirected socket.. */ static const char *redir_socket_name; /* We need to keep track of the server's nonces (these are dummies for POSIX systems). */ static assuan_sock_nonce_t socket_nonce; /* Only if this flag has been set will we remove the socket file. */ static int cleanup_socket; /* Keep track of the current log file so that we can avoid updating the log file after a SIGHUP if it didn't changed. Malloced. */ static char *current_logfile; /* Helper to implement --debug-level. */ static const char *debug_level; /* Helper to set the NTBTLS or GNUTLS log level. */ static int opt_gnutls_debug = -1; /* Flag indicating that a shutdown has been requested. */ static volatile int shutdown_pending; /* Counter for the active connections. */ static int active_connections; /* The timer tick used for housekeeping stuff. For Windows we use a longer period as the SetWaitableTimer seems to signal earlier than the 2 seconds. All values are in seconds. */ #if defined(HAVE_W32CE_SYSTEM) # define TIMERTICK_INTERVAL (60) #elif defined(HAVE_W32_SYSTEM) # define TIMERTICK_INTERVAL (4) #else # define TIMERTICK_INTERVAL (2) #endif #define HOUSEKEEPING_INTERVAL (600) /* This union is used to avoid compiler warnings in case a pointer is 64 bit and an int 32 bit. We store an integer in a pointer and get it back later (npth_getspecific et al.). */ union int_and_ptr_u { int aint; assuan_fd_t afd; void *aptr; }; /* The key used to store the current file descriptor in the thread local storage. We use this in conjunction with the log_set_pid_suffix_cb feature. */ #ifndef HAVE_W32_SYSTEM static int my_tlskey_current_fd; #endif /* Prototypes. */ static void cleanup (void); #if USE_LDAP static ldap_server_t parse_ldapserver_file (const char* filename); #endif /*USE_LDAP*/ static fingerprint_list_t parse_ocsp_signer (const char *string); static void handle_connections (assuan_fd_t listen_fd); /* NPth wrapper function definitions. */ ASSUAN_SYSTEM_NPTH_IMPL; static const char * my_strusage( int level ) { const char *p; switch ( level ) { case 11: p = "@DIRMNGR@ (@GNUPG@)"; break; case 13: p = VERSION; break; case 17: p = PRINTABLE_OS_NAME; break; /* TRANSLATORS: @EMAIL@ will get replaced by the actual bug reporting address. This is so that we can change the reporting address without breaking the translations. */ case 19: p = _("Please report bugs to <@EMAIL@>.\n"); break; case 49: p = PACKAGE_BUGREPORT; break; case 1: case 40: p = _("Usage: @DIRMNGR@ [options] (-h for help)"); break; case 41: p = _("Syntax: @DIRMNGR@ [options] [command [args]]\n" "Keyserver, CRL, and OCSP access for @GNUPG@\n"); break; default: p = NULL; } return p; } /* Callback from libksba to hash a provided buffer. Our current implementation does only allow SHA-1 for hashing. This may be extended by mapping the name, testing for algorithm availibility and adjust the length checks accordingly. */ static gpg_error_t my_ksba_hash_buffer (void *arg, const char *oid, const void *buffer, size_t length, size_t resultsize, unsigned char *result, size_t *resultlen) { (void)arg; if (oid && strcmp (oid, "1.3.14.3.2.26")) return gpg_error (GPG_ERR_NOT_SUPPORTED); if (resultsize < 20) return gpg_error (GPG_ERR_BUFFER_TOO_SHORT); gcry_md_hash_buffer (2, result, buffer, length); *resultlen = 20; return 0; } /* GNUTLS log function callback. */ #ifdef HTTP_USE_GNUTLS static void my_gnutls_log (int level, const char *text) { int n; n = strlen (text); while (n && text[n-1] == '\n') n--; log_debug ("gnutls:L%d: %.*s\n", level, n, text); } #endif /*HTTP_USE_GNUTLS*/ /* Setup the debugging. With a LEVEL of NULL only the active debug flags are propagated to the subsystems. With LEVEL set, a specific set of debug flags is set; thus overriding all flags already set. */ static void set_debug (void) { int numok = (debug_level && digitp (debug_level)); int numlvl = numok? atoi (debug_level) : 0; if (!debug_level) ; else if (!strcmp (debug_level, "none") || (numok && numlvl < 1)) opt.debug = 0; else if (!strcmp (debug_level, "basic") || (numok && numlvl <= 2)) opt.debug = DBG_IPC_VALUE; else if (!strcmp (debug_level, "advanced") || (numok && numlvl <= 5)) opt.debug = (DBG_IPC_VALUE|DBG_X509_VALUE|DBG_LOOKUP_VALUE); else if (!strcmp (debug_level, "expert") || (numok && numlvl <= 8)) opt.debug = (DBG_IPC_VALUE|DBG_X509_VALUE|DBG_LOOKUP_VALUE |DBG_CACHE_VALUE|DBG_CRYPTO_VALUE); else if (!strcmp (debug_level, "guru") || numok) { opt.debug = ~0; /* Unless the "guru" string has been used we don't want to allow hashing debugging. The rationale is that people tend to select the highest debug value and would then clutter their disk with debug files which may reveal confidential data. */ if (numok) opt.debug &= ~(DBG_HASHING_VALUE); } else { log_error (_("invalid debug-level '%s' given\n"), debug_level); log_info (_("valid debug levels are: %s\n"), "none, basic, advanced, expert, guru"); opt.debug = 0; /* Reset debugging, so that prior debug statements won't have an undesired effect. */ } if (opt.debug && !opt.verbose) { opt.verbose = 1; gcry_control (GCRYCTL_SET_VERBOSITY, (int)opt.verbose); } if (opt.debug && opt.quiet) opt.quiet = 0; if (opt.debug & DBG_CRYPTO_VALUE ) gcry_control (GCRYCTL_SET_DEBUG_FLAGS, 1); #if HTTP_USE_NTBTLS if (opt_gnutls_debug >= 0) { ntbtls_set_debug (opt_gnutls_debug, NULL, NULL); } #elif HTTP_USE_GNUTLS if (opt_gnutls_debug >= 0) { gnutls_global_set_log_function (my_gnutls_log); gnutls_global_set_log_level (opt_gnutls_debug); } #endif /*HTTP_USE_GNUTLS*/ if (opt.debug) parse_debug_flag (NULL, &opt.debug, debug_flags); } static void set_tor_mode (void) { if (opt.use_tor) { if (assuan_sock_set_flag (ASSUAN_INVALID_FD, "tor-mode", 1)) { log_error ("error enabling Tor mode: %s\n", strerror (errno)); log_info ("(is your Libassuan recent enough?)\n"); } } } static void wrong_args (const char *text) { es_fprintf (es_stderr, _("usage: %s [options] "), DIRMNGR_NAME); es_fputs (text, es_stderr); es_putc ('\n', es_stderr); dirmngr_exit (2); } /* Helper to stop the reaper thread for the ldap wrapper. */ static void shutdown_reaper (void) { #if USE_LDAP ldap_wrapper_wait_connections (); #endif } /* Handle options which are allowed to be reset after program start. Return true if the current option in PARGS could be handled and false if not. As a special feature, passing a value of NULL for PARGS, resets the options to the default. REREAD should be set true if it is not the initial option parsing. */ static int parse_rereadable_options (ARGPARSE_ARGS *pargs, int reread) { if (!pargs) { /* Reset mode. */ opt.quiet = 0; opt.verbose = 0; opt.debug = 0; opt.ldap_wrapper_program = NULL; opt.disable_http = 0; opt.disable_ldap = 0; opt.honor_http_proxy = 0; opt.http_proxy = NULL; opt.ldap_proxy = NULL; opt.only_ldap_proxy = 0; opt.ignore_http_dp = 0; opt.ignore_ldap_dp = 0; opt.ignore_ocsp_service_url = 0; opt.allow_ocsp = 0; opt.ocsp_responder = NULL; opt.ocsp_max_clock_skew = 10 * 60; /* 10 minutes. */ opt.ocsp_max_period = 90 * 86400; /* 90 days. */ opt.ocsp_current_period = 3 * 60 * 60; /* 3 hours. */ opt.max_replies = DEFAULT_MAX_REPLIES; while (opt.ocsp_signer) { fingerprint_list_t tmp = opt.ocsp_signer->next; xfree (opt.ocsp_signer); opt.ocsp_signer = tmp; } FREE_STRLIST (opt.ignored_cert_extensions); http_register_tls_ca (NULL); FREE_STRLIST (opt.keyserver); /* Note: We do not allow resetting of opt.use_tor at runtime. */ return 1; } switch (pargs->r_opt) { case oQuiet: opt.quiet = 1; break; case oVerbose: opt.verbose++; break; case oDebug: parse_debug_flag (pargs->r.ret_str, &opt.debug, debug_flags); break; case oDebugAll: opt.debug = ~0; break; case oDebugLevel: debug_level = pargs->r.ret_str; break; case oGnutlsDebug: opt_gnutls_debug = pargs->r.ret_int; break; case oLogFile: if (!reread) return 0; /* Not handled. */ if (!current_logfile || !pargs->r.ret_str || strcmp (current_logfile, pargs->r.ret_str)) { log_set_file (pargs->r.ret_str); xfree (current_logfile); current_logfile = xtrystrdup (pargs->r.ret_str); } break; case oLDAPWrapperProgram: opt.ldap_wrapper_program = pargs->r.ret_str; break; case oHTTPWrapperProgram: opt.http_wrapper_program = pargs->r.ret_str; break; case oDisableHTTP: opt.disable_http = 1; break; case oDisableLDAP: opt.disable_ldap = 1; break; case oHonorHTTPProxy: opt.honor_http_proxy = 1; break; case oHTTPProxy: opt.http_proxy = pargs->r.ret_str; break; case oLDAPProxy: opt.ldap_proxy = pargs->r.ret_str; break; case oOnlyLDAPProxy: opt.only_ldap_proxy = 1; break; case oIgnoreHTTPDP: opt.ignore_http_dp = 1; break; case oIgnoreLDAPDP: opt.ignore_ldap_dp = 1; break; case oIgnoreOCSPSvcUrl: opt.ignore_ocsp_service_url = 1; break; case oAllowOCSP: opt.allow_ocsp = 1; break; case oOCSPResponder: opt.ocsp_responder = pargs->r.ret_str; break; case oOCSPSigner: opt.ocsp_signer = parse_ocsp_signer (pargs->r.ret_str); break; case oOCSPMaxClockSkew: opt.ocsp_max_clock_skew = pargs->r.ret_int; break; case oOCSPMaxPeriod: opt.ocsp_max_period = pargs->r.ret_int; break; case oOCSPCurrentPeriod: opt.ocsp_current_period = pargs->r.ret_int; break; case oMaxReplies: opt.max_replies = pargs->r.ret_int; break; case oHkpCaCert: { char *tmpname; /* Do tilde expansion and print a warning if the file can't be accessed. */ tmpname = make_absfilename_try (pargs->r.ret_str, NULL); if (!tmpname || access (tmpname, F_OK)) log_info (_("can't access '%s': %s\n"), tmpname? tmpname : pargs->r.ret_str, gpg_strerror (gpg_error_from_syserror())); else http_register_tls_ca (tmpname); xfree (tmpname); } break; case oIgnoreCertExtension: add_to_strlist (&opt.ignored_cert_extensions, pargs->r.ret_str); break; case oUseTor: opt.use_tor = 1; break; case oKeyServer: if (*pargs->r.ret_str) add_to_strlist (&opt.keyserver, pargs->r.ret_str); break; case oNameServer: set_dns_nameserver (pargs->r.ret_str); break; default: return 0; /* Not handled. */ } return 1; /* Handled. */ } -#ifdef USE_W32_SERVICE -/* The global status of our service. */ -SERVICE_STATUS_HANDLE service_handle; -SERVICE_STATUS service_status; - -DWORD WINAPI -w32_service_control (DWORD control, DWORD event_type, LPVOID event_data, - LPVOID context) -{ - (void)event_type; - (void)event_data; - (void)context; - - /* event_type and event_data are not used here. */ - switch (control) - { - case SERVICE_CONTROL_SHUTDOWN: - /* For shutdown we will try to force termination. */ - service_status.dwCurrentState = SERVICE_STOP_PENDING; - SetServiceStatus (service_handle, &service_status); - shutdown_pending = 3; - break; - - case SERVICE_CONTROL_STOP: - service_status.dwCurrentState = SERVICE_STOP_PENDING; - SetServiceStatus (service_handle, &service_status); - shutdown_pending = 1; - break; - - default: - break; - } - return 0; -} -#endif /*USE_W32_SERVICE*/ - #ifndef HAVE_W32_SYSTEM static int pid_suffix_callback (unsigned long *r_suffix) { union int_and_ptr_u value; memset (&value, 0, sizeof value); value.aptr = npth_getspecific (my_tlskey_current_fd); *r_suffix = value.aint; return (*r_suffix != -1); /* Use decimal representation. */ } #endif /*!HAVE_W32_SYSTEM*/ -#ifdef USE_W32_SERVICE -# define main real_main -#endif int main (int argc, char **argv) { -#ifdef USE_W32_SERVICE -# undef main -#endif enum cmd_and_opt_values cmd = 0; ARGPARSE_ARGS pargs; int orig_argc; char **orig_argv; FILE *configfp = NULL; char *configname = NULL; const char *shell; unsigned configlineno; int parse_debug = 0; int default_config =1; int greeting = 0; int nogreeting = 0; int nodetach = 0; int csh_style = 0; char *logfile = NULL; #if USE_LDAP char *ldapfile = NULL; #endif /*USE_LDAP*/ int debug_wait = 0; int rc; - int homedir_seen = 0; struct assuan_malloc_hooks malloc_hooks; early_system_init (); - -#ifdef USE_W32_SERVICE - /* The option will be set by main() below if we should run as a - system daemon. */ - if (opt.system_service) - { - service_handle - = RegisterServiceCtrlHandlerEx ("DirMngr", - &w32_service_control, NULL /*FIXME*/); - if (service_handle == 0) - log_error ("failed to register service control handler: ec=%d", - (int) GetLastError ()); - service_status.dwServiceType = SERVICE_WIN32_OWN_PROCESS; - service_status.dwCurrentState = SERVICE_START_PENDING; - service_status.dwControlsAccepted - = SERVICE_ACCEPT_STOP | SERVICE_ACCEPT_SHUTDOWN; - service_status.dwWin32ExitCode = NO_ERROR; - service_status.dwServiceSpecificExitCode = NO_ERROR; - service_status.dwCheckPoint = 0; - service_status.dwWaitHint = 10000; /* 10 seconds timeout. */ - SetServiceStatus (service_handle, &service_status); - } -#endif /*USE_W32_SERVICE*/ - set_strusage (my_strusage); log_set_prefix (DIRMNGR_NAME, GPGRT_LOG_WITH_PREFIX | GPGRT_LOG_WITH_PID); /* Make sure that our subsystems are ready. */ i18n_init (); init_common_subsystems (&argc, &argv); npth_init (); gcry_control (GCRYCTL_DISABLE_SECMEM, 0); /* Check that the libraries are suitable. Do it here because the option parsing may need services of the libraries. */ if (!ksba_check_version (NEED_KSBA_VERSION) ) log_fatal( _("%s is too old (need %s, have %s)\n"), "libksba", NEED_KSBA_VERSION, ksba_check_version (NULL) ); ksba_set_malloc_hooks (gcry_malloc, gcry_realloc, gcry_free ); ksba_set_hash_buffer_function (my_ksba_hash_buffer, NULL); /* Init TLS library. */ #if HTTP_USE_NTBTLS if (!ntbtls_check_version (NEED_NTBTLS_VERSION) ) log_fatal( _("%s is too old (need %s, have %s)\n"), "ntbtls", NEED_NTBTLS_VERSION, ntbtls_check_version (NULL) ); #elif HTTP_USE_GNUTLS rc = gnutls_global_init (); if (rc) log_fatal ("gnutls_global_init failed: %s\n", gnutls_strerror (rc)); #endif /*HTTP_USE_GNUTLS*/ /* Init Assuan. */ malloc_hooks.malloc = gcry_malloc; malloc_hooks.realloc = gcry_realloc; malloc_hooks.free = gcry_free; assuan_set_malloc_hooks (&malloc_hooks); assuan_set_assuan_log_prefix (log_get_prefix (NULL)); assuan_set_gpg_err_source (GPG_ERR_SOURCE_DEFAULT); assuan_set_system_hooks (ASSUAN_SYSTEM_NPTH); assuan_sock_init (); setup_libassuan_logging (&opt.debug); setup_libgcrypt_logging (); /* Setup defaults. */ shell = getenv ("SHELL"); if (shell && strlen (shell) >= 3 && !strcmp (shell+strlen (shell)-3, "csh") ) csh_style = 1; /* Now with NPth running we can set the logging callback. Our windows implementation does not yet feature the NPth TLS functions. */ #ifndef HAVE_W32_SYSTEM if (npth_key_create (&my_tlskey_current_fd, NULL) == 0) if (npth_setspecific (my_tlskey_current_fd, NULL) == 0) log_set_pid_suffix_cb (pid_suffix_callback); #endif /*!HAVE_W32_SYSTEM*/ /* Reset rereadable options to default values. */ parse_rereadable_options (NULL, 0); /* LDAP defaults. */ opt.add_new_ldapservers = 0; opt.ldaptimeout = DEFAULT_LDAP_TIMEOUT; /* Other defaults. */ /* Check whether we have a config file given on the commandline */ orig_argc = argc; orig_argv = argv; pargs.argc = &argc; pargs.argv = &argv; pargs.flags= 1|(1<<6); /* do not remove the args, ignore version */ while (arg_parse( &pargs, opts)) { if (pargs.r_opt == oDebug || pargs.r_opt == oDebugAll) parse_debug++; else if (pargs.r_opt == oOptions) { /* Yes there is one, so we do not try the default one, but read the option file when it is encountered at the commandline */ default_config = 0; } else if (pargs.r_opt == oNoOptions) default_config = 0; /* --no-options */ else if (pargs.r_opt == oHomedir) { gnupg_set_homedir (pargs.r.ret_str); - homedir_seen = 1; } - else if (pargs.r_opt == aDaemon) - opt.system_daemon = 1; - else if (pargs.r_opt == aService) - { - /* Redundant. The main function takes care of it. */ - opt.system_service = 1; - opt.system_daemon = 1; - } -#ifdef HAVE_W32_SYSTEM - else if (pargs.r_opt == aGPGConfList || pargs.r_opt == aGPGConfTest) - /* We set this so we switch to the system configuration - directory below. This is a crutch to solve the problem - that the user configuration is never used on Windows. Also - see below at aGPGConfList. */ - opt.system_daemon = 1; -#endif - } - - /* If --daemon has been given on the command line but not --homedir, - we switch to /etc/gnupg as default home directory. Note, that - this also overrides the GNUPGHOME environment variable. */ - if (opt.system_daemon && !homedir_seen) - { -#ifdef HAVE_W32CE_SYSTEM - gnupg_set_homedir (DIRSEP_S "gnupg"); -#else - gnupg_set_homedir (gnupg_sysconfdir ()); -#endif - opt.homedir_cache = gnupg_cachedir (); - socket_name = dirmngr_sys_socket_name (); } - else if (dirmngr_user_socket_name ()) - socket_name = dirmngr_user_socket_name (); - else - socket_name = dirmngr_sys_socket_name (); + socket_name = dirmngr_socket_name (); if (default_config) configname = make_filename (gnupg_homedir (), DIRMNGR_NAME".conf", NULL ); argc = orig_argc; argv = orig_argv; pargs.argc = &argc; pargs.argv = &argv; pargs.flags= 1; /* do not remove the args */ next_pass: if (configname) { configlineno = 0; configfp = fopen (configname, "r"); if (!configfp) { if (default_config) { if( parse_debug ) log_info (_("Note: no default option file '%s'\n"), configname ); } else { log_error (_("option file '%s': %s\n"), configname, strerror(errno) ); exit(2); } xfree (configname); configname = NULL; } if (parse_debug && configname ) log_info (_("reading options from '%s'\n"), configname ); default_config = 0; } while (optfile_parse( configfp, configname, &configlineno, &pargs, opts) ) { if (parse_rereadable_options (&pargs, 0)) continue; /* Already handled */ switch (pargs.r_opt) { case aServer: case aDaemon: - case aService: case aShutdown: case aFlush: case aListCRLs: case aLoadCRL: case aFetchCRL: case aGPGConfList: case aGPGConfTest: cmd = pargs.r_opt; break; case oQuiet: opt.quiet = 1; break; case oVerbose: opt.verbose++; break; case oBatch: opt.batch=1; break; case oDebugWait: debug_wait = pargs.r.ret_int; break; case oOptions: /* Config files may not be nested (silently ignore them) */ if (!configfp) { xfree(configname); configname = xstrdup(pargs.r.ret_str); goto next_pass; } break; case oNoGreeting: nogreeting = 1; break; case oNoVerbose: opt.verbose = 0; break; case oNoOptions: break; /* no-options */ case oHomedir: /* Ignore this option here. */; break; case oNoDetach: nodetach = 1; break; case oLogFile: logfile = pargs.r.ret_str; break; case oCsh: csh_style = 1; break; case oSh: csh_style = 0; break; case oLDAPFile: # if USE_LDAP ldapfile = pargs.r.ret_str; # endif /*USE_LDAP*/ break; case oLDAPAddServers: opt.add_new_ldapservers = 1; break; case oLDAPTimeout: opt.ldaptimeout = pargs.r.ret_int; break; case oFakedSystemTime: gnupg_set_time ((time_t)pargs.r.ret_ulong, 0); break; case oForce: opt.force = 1; break; case oSocketName: socket_name = pargs.r.ret_str; break; default : pargs.err = configfp? 1:2; break; } } if (configfp) { fclose (configfp); configfp = NULL; /* Keep a copy of the name so that it can be read on SIGHUP. */ opt.config_filename = configname; configname = NULL; goto next_pass; } xfree (configname); configname = NULL; if (log_get_errorcount(0)) exit(2); if (nogreeting ) greeting = 0; if (!opt.homedir_cache) opt.homedir_cache = xstrdup (gnupg_homedir ()); if (greeting) { es_fprintf (es_stderr, "%s %s; %s\n", strusage(11), strusage(13), strusage(14) ); es_fprintf (es_stderr, "%s\n", strusage(15) ); } #ifdef IS_DEVELOPMENT_VERSION log_info ("NOTE: this is a development version!\n"); #endif if (opt.use_tor) { log_info ("WARNING: ***************************************\n"); log_info ("WARNING: Tor mode (--use-tor) MAY NOT FULLY WORK!\n"); log_info ("WARNING: ***************************************\n"); } /* Print a warning if an argument looks like an option. */ if (!opt.quiet && !(pargs.flags & ARGPARSE_FLAG_STOP_SEEN)) { int i; for (i=0; i < argc; i++) if (argv[i][0] == '-' && argv[i][1] == '-') log_info (_("Note: '%s' is not considered an option\n"), argv[i]); } if (!access ("/etc/"DIRMNGR_NAME, F_OK) && !strncmp (gnupg_homedir (), "/etc/", 5)) log_info ("NOTE: DirMngr is now a proper part of %s. The configuration and" " other directory names changed. Please check that no other version" " of dirmngr is still installed. To disable this warning, remove the" " directory '/etc/dirmngr'.\n", GNUPG_NAME); if (gnupg_faked_time_p ()) { gnupg_isotime_t tbuf; log_info (_("WARNING: running with faked system time: ")); gnupg_get_isotime (tbuf); dump_isotime (tbuf); log_printf ("\n"); } set_debug (); set_tor_mode (); /* Get LDAP server list from file. */ #if USE_LDAP if (!ldapfile) { ldapfile = make_filename (gnupg_homedir (), - opt.system_daemon? - "ldapservers.conf":"dirmngr_ldapservers.conf", + "dirmngr_ldapservers.conf", NULL); opt.ldapservers = parse_ldapserver_file (ldapfile); xfree (ldapfile); } else opt.ldapservers = parse_ldapserver_file (ldapfile); #endif /*USE_LDAP*/ #ifndef HAVE_W32_SYSTEM /* We need to ignore the PIPE signal because the we might log to a socket and that code handles EPIPE properly. The ldap wrapper also requires us to ignore this silly signal. Assuan would set this signal to ignore anyway.*/ signal (SIGPIPE, SIG_IGN); #endif /* Ready. Now to our duties. */ - if (!cmd && opt.system_service) - cmd = aDaemon; - else if (!cmd) + if (!cmd) cmd = aServer; rc = 0; if (cmd == aServer) { /* Note that this server mode is mainly useful for debugging. */ if (argc) wrong_args ("--server"); if (logfile) { log_set_file (logfile); log_set_prefix (NULL, GPGRT_LOG_WITH_TIME | GPGRT_LOG_WITH_PID); } if (debug_wait) { log_debug ("waiting for debugger - my pid is %u .....\n", (unsigned int)getpid()); gnupg_sleep (debug_wait); log_debug ("... okay\n"); } #if USE_LDAP ldap_wrapper_launch_thread (); #endif /*USE_LDAP*/ cert_cache_init (); crl_cache_init (); start_command_handler (ASSUAN_INVALID_FD); shutdown_reaper (); } else if (cmd == aDaemon) { assuan_fd_t fd; pid_t pid; int len; struct sockaddr_un serv_addr; if (argc) wrong_args ("--daemon"); /* Now start with logging to a file if this is desired. */ if (logfile) { log_set_file (logfile); log_set_prefix (NULL, (GPGRT_LOG_WITH_PREFIX |GPGRT_LOG_WITH_TIME |GPGRT_LOG_WITH_PID)); current_logfile = xstrdup (logfile); } #ifndef HAVE_W32_SYSTEM if (strchr (socket_name, ':')) { log_error (_("colons are not allowed in the socket name\n")); dirmngr_exit (1); } #endif fd = assuan_sock_new (AF_UNIX, SOCK_STREAM, 0); if (fd == ASSUAN_INVALID_FD) { log_error (_("can't create socket: %s\n"), strerror (errno)); cleanup (); dirmngr_exit (1); } { int redirected; if (assuan_sock_set_sockaddr_un (socket_name, (struct sockaddr*)&serv_addr, &redirected)) { if (errno == ENAMETOOLONG) log_error (_("socket name '%s' is too long\n"), socket_name); else log_error ("error preparing socket '%s': %s\n", socket_name, gpg_strerror (gpg_error_from_syserror ())); dirmngr_exit (1); } if (redirected) { redir_socket_name = xstrdup (serv_addr.sun_path); if (opt.verbose) log_info ("redirecting socket '%s' to '%s'\n", socket_name, redir_socket_name); } } len = SUN_LEN (&serv_addr); rc = assuan_sock_bind (fd, (struct sockaddr*) &serv_addr, len); if (rc == -1 && (errno == EADDRINUSE #ifdef HAVE_W32_SYSTEM || errno == EEXIST #endif )) { /* Fixme: We should test whether a dirmngr is already running. */ gnupg_remove (redir_socket_name? redir_socket_name : socket_name); rc = assuan_sock_bind (fd, (struct sockaddr*) &serv_addr, len); } if (rc != -1 && (rc = assuan_sock_get_nonce ((struct sockaddr*) &serv_addr, len, &socket_nonce))) log_error (_("error getting nonce for the socket\n")); if (rc == -1) { log_error (_("error binding socket to '%s': %s\n"), serv_addr.sun_path, gpg_strerror (gpg_error_from_errno (errno))); assuan_sock_close (fd); dirmngr_exit (1); } cleanup_socket = 1; if (gnupg_chmod (serv_addr.sun_path, "-rwx")) log_error (_("can't set permissions of '%s': %s\n"), serv_addr.sun_path, strerror (errno)); if (listen (FD2INT (fd), 5) == -1) { log_error (_("listen() failed: %s\n"), strerror (errno)); assuan_sock_close (fd); dirmngr_exit (1); } if (opt.verbose) log_info (_("listening on socket '%s'\n"), serv_addr.sun_path); es_fflush (NULL); /* Note: We keep the dirmngr_info output only for the sake of existing scripts which might use this to detect a successful start of the dirmngr. */ #ifdef HAVE_W32_SYSTEM (void)csh_style; (void)nodetach; pid = getpid (); es_printf ("set %s=%s;%lu;1\n", DIRMNGR_INFO_NAME, socket_name, (ulong) pid); #else pid = fork(); if (pid == (pid_t)-1) { log_fatal (_("error forking process: %s\n"), strerror (errno)); dirmngr_exit (1); } if (pid) { /* We are the parent */ char *infostr; /* Don't let cleanup() remove the socket - the child is responsible for doing that. */ cleanup_socket = 0; close (fd); /* Create the info string: :: */ if (asprintf (&infostr, "%s=%s:%lu:1", DIRMNGR_INFO_NAME, serv_addr.sun_path, (ulong)pid ) < 0) { log_error (_("out of core\n")); kill (pid, SIGTERM); dirmngr_exit (1); } /* Print the environment string, so that the caller can use shell's eval to set it. But see above. */ if (csh_style) { *strchr (infostr, '=') = ' '; es_printf ( "setenv %s;\n", infostr); } else { es_printf ( "%s; export %s;\n", infostr, DIRMNGR_INFO_NAME); } free (infostr); exit (0); /*NEVER REACHED*/ } /* end parent */ /* This is the child */ /* Detach from tty and put process into a new session */ if (!nodetach ) { int i; unsigned int oldflags; /* Close stdin, stdout and stderr unless it is the log stream */ for (i=0; i <= 2; i++) { if (!log_test_fd (i) && i != fd ) close (i); } if (setsid() == -1) { log_error ("setsid() failed: %s\n", strerror(errno) ); dirmngr_exit (1); } log_get_prefix (&oldflags); log_set_prefix (NULL, oldflags | GPGRT_LOG_RUN_DETACHED); opt.running_detached = 1; if (chdir("/")) { log_error ("chdir to / failed: %s\n", strerror (errno)); dirmngr_exit (1); } } #endif #if USE_LDAP ldap_wrapper_launch_thread (); #endif /*USE_LDAP*/ cert_cache_init (); crl_cache_init (); -#ifdef USE_W32_SERVICE - if (opt.system_service) - { - service_status.dwCurrentState = SERVICE_RUNNING; - SetServiceStatus (service_handle, &service_status); - } -#endif handle_connections (fd); assuan_sock_close (fd); shutdown_reaper (); -#ifdef USE_W32_SERVICE - if (opt.system_service) - { - service_status.dwCurrentState = SERVICE_STOPPED; - SetServiceStatus (service_handle, &service_status); - } -#endif } else if (cmd == aListCRLs) { /* Just list the CRL cache and exit. */ if (argc) wrong_args ("--list-crls"); #if USE_LDAP ldap_wrapper_launch_thread (); #endif /*USE_LDAP*/ crl_cache_init (); crl_cache_list (es_stdout); } else if (cmd == aLoadCRL) { struct server_control_s ctrlbuf; memset (&ctrlbuf, 0, sizeof ctrlbuf); dirmngr_init_default_ctrl (&ctrlbuf); #if USE_LDAP ldap_wrapper_launch_thread (); #endif /*USE_LDAP*/ cert_cache_init (); crl_cache_init (); if (!argc) rc = crl_cache_load (&ctrlbuf, NULL); else { for (; !rc && argc; argc--, argv++) rc = crl_cache_load (&ctrlbuf, *argv); } dirmngr_deinit_default_ctrl (&ctrlbuf); } else if (cmd == aFetchCRL) { ksba_reader_t reader; struct server_control_s ctrlbuf; if (argc != 1) wrong_args ("--fetch-crl URL"); memset (&ctrlbuf, 0, sizeof ctrlbuf); dirmngr_init_default_ctrl (&ctrlbuf); #if USE_LDAP ldap_wrapper_launch_thread (); #endif /*USE_LDAP*/ cert_cache_init (); crl_cache_init (); rc = crl_fetch (&ctrlbuf, argv[0], &reader); if (rc) log_error (_("fetching CRL from '%s' failed: %s\n"), argv[0], gpg_strerror (rc)); else { rc = crl_cache_insert (&ctrlbuf, argv[0], reader); if (rc) log_error (_("processing CRL from '%s' failed: %s\n"), argv[0], gpg_strerror (rc)); crl_close_reader (reader); } dirmngr_deinit_default_ctrl (&ctrlbuf); } else if (cmd == aFlush) { /* Delete cache and exit. */ if (argc) wrong_args ("--flush"); rc = crl_cache_flush(); } else if (cmd == aGPGConfTest) dirmngr_exit (0); else if (cmd == aGPGConfList) { unsigned long flags = 0; char *filename; char *filename_esc; #ifdef HAVE_W32_SYSTEM /* On Windows systems, dirmngr always runs as system daemon, and the per-user configuration is never used. So we short-cut everything to use the global system configuration of dirmngr above, and here we set the no change flag to make these read-only. */ flags |= GC_OPT_FLAG_NO_CHANGE; #endif /* First the configuration file. This is not an option, but it is vital information for GPG Conf. */ if (!opt.config_filename) opt.config_filename = make_filename (gnupg_homedir (), "dirmngr.conf", NULL ); filename = percent_escape (opt.config_filename, NULL); es_printf ("gpgconf-dirmngr.conf:%lu:\"%s\n", GC_OPT_FLAG_DEFAULT, filename); xfree (filename); es_printf ("verbose:%lu:\n", flags | GC_OPT_FLAG_NONE); es_printf ("quiet:%lu:\n", flags | GC_OPT_FLAG_NONE); es_printf ("debug-level:%lu:\"none\n", flags | GC_OPT_FLAG_DEFAULT); es_printf ("log-file:%lu:\n", flags | GC_OPT_FLAG_NONE); es_printf ("force:%lu:\n", flags | GC_OPT_FLAG_NONE); /* --csh and --sh are mutually exclusive, something we can not express in GPG Conf. --options is only usable from the command line, really. --debug-all interacts with --debug, and having both of them is thus problematic. --no-detach is also only usable on the command line. --batch is unused. */ filename = make_filename (gnupg_homedir (), - opt.system_daemon? - "ldapservers.conf":"dirmngr_ldapservers.conf", + "dirmngr_ldapservers.conf", NULL); filename_esc = percent_escape (filename, NULL); es_printf ("ldapserverlist-file:%lu:\"%s\n", flags | GC_OPT_FLAG_DEFAULT, filename_esc); xfree (filename_esc); xfree (filename); es_printf ("ldaptimeout:%lu:%u\n", flags | GC_OPT_FLAG_DEFAULT, DEFAULT_LDAP_TIMEOUT); es_printf ("max-replies:%lu:%u\n", flags | GC_OPT_FLAG_DEFAULT, DEFAULT_MAX_REPLIES); es_printf ("allow-ocsp:%lu:\n", flags | GC_OPT_FLAG_NONE); es_printf ("ocsp-responder:%lu:\n", flags | GC_OPT_FLAG_NONE); es_printf ("ocsp-signer:%lu:\n", flags | GC_OPT_FLAG_NONE); es_printf ("faked-system-time:%lu:\n", flags | GC_OPT_FLAG_NONE); es_printf ("no-greeting:%lu:\n", flags | GC_OPT_FLAG_NONE); es_printf ("disable-http:%lu:\n", flags | GC_OPT_FLAG_NONE); es_printf ("disable-ldap:%lu:\n", flags | GC_OPT_FLAG_NONE); es_printf ("honor-http-proxy:%lu\n", flags | GC_OPT_FLAG_NONE); es_printf ("http-proxy:%lu:\n", flags | GC_OPT_FLAG_NONE); es_printf ("ldap-proxy:%lu:\n", flags | GC_OPT_FLAG_NONE); es_printf ("only-ldap-proxy:%lu:\n", flags | GC_OPT_FLAG_NONE); es_printf ("ignore-ldap-dp:%lu:\n", flags | GC_OPT_FLAG_NONE); es_printf ("ignore-http-dp:%lu:\n", flags | GC_OPT_FLAG_NONE); es_printf ("ignore-ocsp-service-url:%lu:\n", flags | GC_OPT_FLAG_NONE); /* Note: The next one is to fix a typo in gpgconf - should be removed eventually. */ es_printf ("ignore-ocsp-servic-url:%lu:\n", flags | GC_OPT_FLAG_NONE); es_printf ("use-tor:%lu:\n", flags | GC_OPT_FLAG_NONE); es_printf ("keyserver:%lu:\n", flags | GC_OPT_FLAG_NONE); } cleanup (); return !!rc; } -#ifdef USE_W32_SERVICE -static void WINAPI -call_real_main (DWORD argc, LPSTR *argv) -{ - real_main (argc, argv); -} - -int -main (int argc, char *argv[]) -{ - int i; - - /* Find out if we run in daemon mode or on the command line. */ - for (i = 1; i < argc; i++) - if (!strcmp (argv[i], "--service")) - { - opt.system_service = 1; - opt.system_daemon = 1; - break; - } - - if (!opt.system_service) - return real_main (argc, argv); - else - { - SERVICE_TABLE_ENTRY DispatchTable [] = - { - { "DirMngr", &call_real_main }, - { NULL, NULL } - }; - - if (!StartServiceCtrlDispatcher (DispatchTable)) - return 1; - return 0; - } -} -#endif /*USE_W32_SERVICE*/ - - static void cleanup (void) { crl_cache_deinit (); cert_cache_deinit (1); #if USE_LDAP ldapserver_list_free (opt.ldapservers); #endif /*USE_LDAP*/ opt.ldapservers = NULL; if (cleanup_socket) { cleanup_socket = 0; if (redir_socket_name) gnupg_remove (redir_socket_name); else if (socket_name && *socket_name) gnupg_remove (socket_name); } } void dirmngr_exit (int rc) { cleanup (); exit (rc); } void dirmngr_init_default_ctrl (ctrl_t ctrl) { if (opt.http_proxy) ctrl->http_proxy = xstrdup (opt.http_proxy); } void dirmngr_deinit_default_ctrl (ctrl_t ctrl) { if (!ctrl) return; xfree (ctrl->http_proxy); ctrl->http_proxy = NULL; } /* Create a list of LDAP servers from the file FILENAME. Returns the list or NULL in case of errors. The format fo such a file is line oriented where empty lines and lines starting with a hash mark are ignored. All other lines are assumed to be colon seprated with these fields: 1. field: Hostname 2. field: Portnumber 3. field: Username 4. field: Password 5. field: Base DN */ #if USE_LDAP static ldap_server_t parse_ldapserver_file (const char* filename) { char buffer[1024]; char *p; ldap_server_t server, serverstart, *serverend; int c; unsigned int lineno = 0; estream_t fp; fp = es_fopen (filename, "r"); if (!fp) { log_error (_("error opening '%s': %s\n"), filename, strerror (errno)); return NULL; } serverstart = NULL; serverend = &serverstart; while (es_fgets (buffer, sizeof buffer, fp)) { lineno++; if (!*buffer || buffer[strlen(buffer)-1] != '\n') { if (*buffer && es_feof (fp)) ; /* Last line not terminated - continue. */ else { log_error (_("%s:%u: line too long - skipped\n"), filename, lineno); while ( (c=es_fgetc (fp)) != EOF && c != '\n') ; /* Skip until end of line. */ continue; } } /* Skip empty and comment lines.*/ for (p=buffer; spacep (p); p++) ; if (!*p || *p == '\n' || *p == '#') continue; /* Parse the colon separated fields. */ server = ldapserver_parse_one (buffer, filename, lineno); if (server) { *serverend = server; serverend = &server->next; } } if (es_ferror (fp)) log_error (_("error reading '%s': %s\n"), filename, strerror (errno)); es_fclose (fp); return serverstart; } #endif /*USE_LDAP*/ static fingerprint_list_t parse_ocsp_signer (const char *string) { gpg_error_t err; char *fname; estream_t fp; char line[256]; char *p; fingerprint_list_t list, *list_tail, item; unsigned int lnr = 0; int c, i, j; int errflag = 0; /* Check whether this is not a filename and treat it as a direct fingerprint specification. */ if (!strpbrk (string, "/.~\\")) { item = xcalloc (1, sizeof *item); for (i=j=0; (string[i] == ':' || hexdigitp (string+i)) && j < 40; i++) if ( string[i] != ':' ) item->hexfpr[j++] = string[i] >= 'a'? (string[i] & 0xdf): string[i]; item->hexfpr[j] = 0; if (j != 40 || !(spacep (string+i) || !string[i])) { log_error (_("%s:%u: invalid fingerprint detected\n"), "--ocsp-signer", 0); xfree (item); return NULL; } return item; } /* Well, it is a filename. */ if (*string == '/' || (*string == '~' && string[1] == '/')) fname = make_filename (string, NULL); else { if (string[0] == '.' && string[1] == '/' ) string += 2; fname = make_filename (gnupg_homedir (), string, NULL); } fp = es_fopen (fname, "r"); if (!fp) { err = gpg_error_from_syserror (); log_error (_("can't open '%s': %s\n"), fname, gpg_strerror (err)); xfree (fname); return NULL; } list = NULL; list_tail = &list; for (;;) { if (!es_fgets (line, DIM(line)-1, fp) ) { if (!es_feof (fp)) { err = gpg_error_from_syserror (); log_error (_("%s:%u: read error: %s\n"), fname, lnr, gpg_strerror (err)); errflag = 1; } es_fclose (fp); if (errflag) { while (list) { fingerprint_list_t tmp = list->next; xfree (list); list = tmp; } } xfree (fname); return list; /* Ready. */ } lnr++; if (!*line || line[strlen(line)-1] != '\n') { /* Eat until end of line. */ while ( (c=es_getc (fp)) != EOF && c != '\n') ; err = gpg_error (*line? GPG_ERR_LINE_TOO_LONG /* */: GPG_ERR_INCOMPLETE_LINE); log_error (_("%s:%u: read error: %s\n"), fname, lnr, gpg_strerror (err)); errflag = 1; continue; } /* Allow for empty lines and spaces */ for (p=line; spacep (p); p++) ; if (!*p || *p == '\n' || *p == '#') continue; item = xcalloc (1, sizeof *item); *list_tail = item; list_tail = &item->next; for (i=j=0; (p[i] == ':' || hexdigitp (p+i)) && j < 40; i++) if ( p[i] != ':' ) item->hexfpr[j++] = p[i] >= 'a'? (p[i] & 0xdf): p[i]; item->hexfpr[j] = 0; if (j != 40 || !(spacep (p+i) || p[i] == '\n')) { log_error (_("%s:%u: invalid fingerprint detected\n"), fname, lnr); errflag = 1; } i++; while (spacep (p+i)) i++; if (p[i] && p[i] != '\n') log_info (_("%s:%u: garbage at end of line ignored\n"), fname, lnr); } /*NOTREACHED*/ } /* Stuff used in daemon mode. */ /* Reread parts of the configuration. Note, that this function is obviously not thread-safe and should only be called from the NPTH signal handler. Fixme: Due to the way the argument parsing works, we create a memory leak here for all string type arguments. There is currently no clean way to tell whether the memory for the argument has been allocated or points into the process' original arguments. Unless we have a mechanism to tell this, we need to live on with this. */ static void reread_configuration (void) { ARGPARSE_ARGS pargs; FILE *fp; unsigned int configlineno = 0; int dummy; if (!opt.config_filename) return; /* No config file. */ fp = fopen (opt.config_filename, "r"); if (!fp) { log_error (_("option file '%s': %s\n"), opt.config_filename, strerror(errno) ); return; } parse_rereadable_options (NULL, 1); /* Start from the default values. */ memset (&pargs, 0, sizeof pargs); dummy = 0; pargs.argc = &dummy; pargs.flags = 1; /* do not remove the args */ while (optfile_parse (fp, opt.config_filename, &configlineno, &pargs, opts) ) { if (pargs.r_opt < -1) pargs.err = 1; /* Print a warning. */ else /* Try to parse this option - ignore unchangeable ones. */ parse_rereadable_options (&pargs, 1); } fclose (fp); set_debug (); set_tor_mode (); } /* A global function which allows us to trigger the reload stuff from other places. */ void dirmngr_sighup_action (void) { log_info (_("SIGHUP received - " "re-reading configuration and flushing caches\n")); reread_configuration (); cert_cache_deinit (0); crl_cache_deinit (); cert_cache_init (); crl_cache_init (); } /* The signal handler. */ #ifndef HAVE_W32_SYSTEM static void handle_signal (int signo) { switch (signo) { case SIGHUP: dirmngr_sighup_action (); break; case SIGUSR1: cert_cache_print_stats (); break; case SIGUSR2: log_info (_("SIGUSR2 received - no action defined\n")); break; case SIGTERM: if (!shutdown_pending) log_info (_("SIGTERM received - shutting down ...\n")); else log_info (_("SIGTERM received - still %d active connections\n"), active_connections); shutdown_pending++; if (shutdown_pending > 2) { log_info (_("shutdown forced\n")); log_info ("%s %s stopped\n", strusage(11), strusage(13) ); cleanup (); dirmngr_exit (0); } break; case SIGINT: log_info (_("SIGINT received - immediate shutdown\n")); log_info( "%s %s stopped\n", strusage(11), strusage(13)); cleanup (); dirmngr_exit (0); break; default: log_info (_("signal %d received - no action defined\n"), signo); } } #endif /*!HAVE_W32_SYSTEM*/ /* Thread to do the housekeeping. */ static void * housekeeping_thread (void *arg) { static int sentinel; time_t curtime; (void)arg; curtime = gnupg_get_time (); if (sentinel) { log_info ("housekeeping is already going on\n"); return NULL; } sentinel++; if (opt.verbose) log_info ("starting housekeeping\n"); ks_hkp_housekeeping (curtime); if (opt.verbose) log_info ("ready with housekeeping\n"); sentinel--; return NULL; } #if GPGRT_GCC_HAVE_PUSH_PRAGMA # pragma GCC push_options # pragma GCC optimize ("no-strict-overflow") #endif static int time_for_housekeeping_p (time_t curtime) { static time_t last_housekeeping; if (!last_housekeeping) last_housekeeping = curtime; if (last_housekeeping + HOUSEKEEPING_INTERVAL <= curtime || last_housekeeping > curtime /*(be prepared for y2038)*/) { last_housekeeping = curtime; return 1; } return 0; } #if GPGRT_GCC_HAVE_PUSH_PRAGMA # pragma GCC pop_options #endif /* This is the worker for the ticker. It is called every few seconds and may only do fast operations. */ static void handle_tick (void) { /* Under Windows we don't use signals and need a way for the loop to check for the shutdown flag. */ #ifdef HAVE_W32_SYSTEM if (shutdown_pending) log_info (_("SIGTERM received - shutting down ...\n")); if (shutdown_pending > 2) { log_info (_("shutdown forced\n")); log_info ("%s %s stopped\n", strusage(11), strusage(13) ); cleanup (); dirmngr_exit (0); } #endif /*HAVE_W32_SYSTEM*/ if (time_for_housekeeping_p (gnupg_get_time ())) { npth_t thread; npth_attr_t tattr; int err; err = npth_attr_init (&tattr); if (err) log_error ("error preparing housekeeping thread: %s\n", strerror (err)); else { npth_attr_setdetachstate (&tattr, NPTH_CREATE_DETACHED); err = npth_create (&thread, &tattr, housekeeping_thread, NULL); if (err) log_error ("error spawning housekeeping thread: %s\n", strerror (err)); npth_attr_destroy (&tattr); } } } /* Check the nonce on a new connection. This is a NOP unless we are using our Unix domain socket emulation under Windows. */ static int check_nonce (assuan_fd_t fd, assuan_sock_nonce_t *nonce) { if (assuan_sock_check_nonce (fd, nonce)) { log_info (_("error reading nonce on fd %d: %s\n"), FD2INT (fd), strerror (errno)); assuan_sock_close (fd); return -1; } else return 0; } /* Helper to call a connection's main function. */ static void * start_connection_thread (void *arg) { union int_and_ptr_u argval; gnupg_fd_t fd; memset (&argval, 0, sizeof argval); argval.aptr = arg; fd = argval.afd; if (check_nonce (fd, &socket_nonce)) { log_error ("handler nonce check FAILED\n"); return NULL; } #ifndef HAVE_W32_SYSTEM npth_setspecific (my_tlskey_current_fd, argval.aptr); #endif active_connections++; if (opt.verbose) log_info (_("handler for fd %d started\n"), FD2INT (fd)); start_command_handler (fd); if (opt.verbose) log_info (_("handler for fd %d terminated\n"), FD2INT (fd)); active_connections--; #ifndef HAVE_W32_SYSTEM argval.afd = ASSUAN_INVALID_FD; npth_setspecific (my_tlskey_current_fd, argval.aptr); #endif return NULL; } /* Main loop in daemon mode. */ static void handle_connections (assuan_fd_t listen_fd) { npth_attr_t tattr; #ifndef HAVE_W32_SYSTEM int signo; #endif struct sockaddr_un paddr; socklen_t plen = sizeof( paddr ); gnupg_fd_t fd; int nfd, ret; fd_set fdset, read_fdset; struct timespec abstime; struct timespec curtime; struct timespec timeout; int saved_errno; npth_attr_init (&tattr); npth_attr_setdetachstate (&tattr, NPTH_CREATE_DETACHED); #ifndef HAVE_W32_SYSTEM /* FIXME */ npth_sigev_init (); npth_sigev_add (SIGHUP); npth_sigev_add (SIGUSR1); npth_sigev_add (SIGUSR2); npth_sigev_add (SIGINT); npth_sigev_add (SIGTERM); npth_sigev_fini (); #endif /* Setup the fdset. It has only one member. This is because we use pth_select instead of pth_accept to properly sync timeouts with to full second. */ FD_ZERO (&fdset); FD_SET (FD2INT (listen_fd), &fdset); nfd = FD2INT (listen_fd); npth_clock_gettime (&abstime); abstime.tv_sec += TIMERTICK_INTERVAL; /* Main loop. */ for (;;) { /* Shutdown test. */ if (shutdown_pending) { if (!active_connections) break; /* ready */ /* Do not accept new connections but keep on running the loop to cope with the timer events. */ FD_ZERO (&fdset); } /* Take a copy of the fdset. */ read_fdset = fdset; npth_clock_gettime (&curtime); if (!(npth_timercmp (&curtime, &abstime, <))) { /* Timeout. */ handle_tick (); npth_clock_gettime (&abstime); abstime.tv_sec += TIMERTICK_INTERVAL; } npth_timersub (&abstime, &curtime, &timeout); #ifndef HAVE_W32_SYSTEM ret = npth_pselect (nfd+1, &read_fdset, NULL, NULL, &timeout, npth_sigev_sigmask()); saved_errno = errno; while (npth_sigev_get_pending(&signo)) handle_signal (signo); #else ret = npth_eselect (nfd+1, &read_fdset, NULL, NULL, &timeout, NULL, NULL); saved_errno = errno; #endif if (ret == -1 && saved_errno != EINTR) { log_error (_("npth_pselect failed: %s - waiting 1s\n"), strerror (saved_errno)); npth_sleep (1); continue; } if (ret <= 0) /* Interrupt or timeout. Will be handled when calculating the next timeout. */ continue; if (!shutdown_pending && FD_ISSET (FD2INT (listen_fd), &read_fdset)) { plen = sizeof paddr; fd = INT2FD (npth_accept (FD2INT(listen_fd), (struct sockaddr *)&paddr, &plen)); if (fd == GNUPG_INVALID_FD) { log_error ("accept failed: %s\n", strerror (errno)); } else { char threadname[50]; union int_and_ptr_u argval; npth_t thread; memset (&argval, 0, sizeof argval); argval.afd = fd; snprintf (threadname, sizeof threadname-1, "conn fd=%d", FD2INT(fd)); threadname[sizeof threadname -1] = 0; ret = npth_create (&thread, &tattr, start_connection_thread, argval.aptr); if (ret) { log_error ("error spawning connection handler: %s\n", strerror (ret) ); assuan_sock_close (fd); } npth_setname_np (thread, threadname); } fd = GNUPG_INVALID_FD; } } npth_attr_destroy (&tattr); cleanup (); log_info ("%s %s stopped\n", strusage(11), strusage(13)); } diff --git a/dirmngr/dirmngr.h b/dirmngr/dirmngr.h index 8d90ae439..612738613 100644 --- a/dirmngr/dirmngr.h +++ b/dirmngr/dirmngr.h @@ -1,208 +1,206 @@ /* dirmngr.h - Common definitions for the dirmngr * Copyright (C) 2002 Klarälvdalens Datakonsult AB * Copyright (C) 2004, 2015 g10 Code GmbH * Copyright (C) 2014 Werner Koch * * This file is part of GnuPG. * * GnuPG is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3 of the License, or * (at your option) any later version. * * GnuPG 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 General Public License * along with this program; if not, see . */ #ifndef DIRMNGR_H #define DIRMNGR_H #include "./dirmngr-err.h" #define map_assuan_err(a) \ map_assuan_err_with_source (GPG_ERR_SOURCE_DEFAULT, (a)) #include #include #include #include "../common/util.h" #include "../common/membuf.h" #include "../common/sysutils.h" /* (gnupg_fd_t) */ #include "../common/i18n.h" #include "http.h" /* (parsed_uri_t) */ /* This objects keeps information about a particular LDAP server and is used as item of a single linked list of servers. */ struct ldap_server_s { struct ldap_server_s* next; char *host; int port; char *user; char *pass; char *base; }; typedef struct ldap_server_s *ldap_server_t; /* This objects is used to build a list of URI consisting of the original and the parsed URI. */ struct uri_item_s { struct uri_item_s *next; parsed_uri_t parsed_uri; /* The broken down URI. */ char uri[1]; /* The original URI. */ }; typedef struct uri_item_s *uri_item_t; /* A list of fingerprints. */ struct fingerprint_list_s; typedef struct fingerprint_list_s *fingerprint_list_t; struct fingerprint_list_s { fingerprint_list_t next; char hexfpr[20+20+1]; }; /* A large struct named "opt" to keep global flags. */ struct { unsigned int debug; /* debug flags (DBG_foo_VALUE) */ int verbose; /* verbosity level */ int quiet; /* be as quiet as possible */ int dry_run; /* don't change any persistent data */ int batch; /* batch mode */ const char *homedir_cache; /* Dir for cache files (/var/cache/dirmngr). */ char *config_filename; /* Name of a config file, which will be reread on a HUP if it is not NULL. */ char *ldap_wrapper_program; /* Override value for the LDAP wrapper program. */ char *http_wrapper_program; /* Override value for the HTTP wrapper program. */ - int system_service; /* We are running as W32 service (implies daemon). */ - int system_daemon; /* We are running in system daemon mode. */ int running_detached; /* We are running in detached mode. */ int use_tor; /* Tor mode has been enabled. */ int force; /* Force loading outdated CRLs. */ int disable_http; /* Do not use HTTP at all. */ int disable_ldap; /* Do not use LDAP at all. */ int honor_http_proxy; /* Honor the http_proxy env variable. */ const char *http_proxy; /* The default HTTP proxy. */ const char *ldap_proxy; /* Use given LDAP proxy. */ int only_ldap_proxy; /* Only use the LDAP proxy; no fallback. */ int ignore_http_dp; /* Ignore HTTP CRL distribution points. */ int ignore_ldap_dp; /* Ignore LDAP CRL distribution points. */ int ignore_ocsp_service_url; /* Ignore OCSP service URLs as given in the certificate. */ /* A list of certificate extension OIDs which are ignored so that one can claim that a critical extension has been handled. One OID per string. */ strlist_t ignored_cert_extensions; int allow_ocsp; /* Allow using OCSP. */ int max_replies; unsigned int ldaptimeout; ldap_server_t ldapservers; int add_new_ldapservers; const char *ocsp_responder; /* Standard OCSP responder's URL. */ fingerprint_list_t ocsp_signer; /* The list of fingerprints with allowed standard OCSP signer certificates. */ unsigned int ocsp_max_clock_skew; /* Allowed seconds of clocks skew. */ unsigned int ocsp_max_period; /* Seconds a response is at maximum considered valid after thisUpdate. */ unsigned int ocsp_current_period; /* Seconds a response is considered current after nextUpdate. */ strlist_t keyserver; /* List of default keyservers. */ } opt; #define DBG_X509_VALUE 1 /* debug x.509 parsing */ #define DBG_CRYPTO_VALUE 4 /* debug low level crypto */ #define DBG_MEMORY_VALUE 32 /* debug memory allocation stuff */ #define DBG_CACHE_VALUE 64 /* debug the caching */ #define DBG_MEMSTAT_VALUE 128 /* show memory statistics */ #define DBG_HASHING_VALUE 512 /* debug hashing operations */ #define DBG_IPC_VALUE 1024 /* debug assuan communication */ #define DBG_LOOKUP_VALUE 8192 /* debug lookup details */ #define DBG_X509 (opt.debug & DBG_X509_VALUE) #define DBG_CRYPTO (opt.debug & DBG_CRYPTO_VALUE) #define DBG_MEMORY (opt.debug & DBG_MEMORY_VALUE) #define DBG_CACHE (opt.debug & DBG_CACHE_VALUE) #define DBG_HASHING (opt.debug & DBG_HASHING_VALUE) #define DBG_IPC (opt.debug & DBG_IPC_VALUE) #define DBG_LOOKUP (opt.debug & DBG_LOOKUP_VALUE) /* A simple list of certificate references. */ struct cert_ref_s { struct cert_ref_s *next; unsigned char fpr[20]; }; typedef struct cert_ref_s *cert_ref_t; /* Forward references; access only through server.c. */ struct server_local_s; /* Connection control structure. */ struct server_control_s { int refcount; /* Count additional references to this object. */ int no_server; /* We are not running under server control. */ int status_fd; /* Only for non-server mode. */ struct server_local_s *server_local; int force_crl_refresh; /* Always load a fresh CRL. */ int check_revocations_nest_level; /* Internal to check_revovations. */ cert_ref_t ocsp_certs; /* Certificates from the current OCSP response. */ int audit_events; /* Send audit events to client. */ char *http_proxy; /* The used http_proxy or NULL. */ }; /*-- dirmngr.c --*/ void dirmngr_exit( int ); /* Wrapper for exit() */ void dirmngr_init_default_ctrl (ctrl_t ctrl); void dirmngr_deinit_default_ctrl (ctrl_t ctrl); void dirmngr_sighup_action (void); /*-- Various housekeeping functions. --*/ void ks_hkp_housekeeping (time_t curtime); /*-- server.c --*/ ldap_server_t get_ldapservers_from_ctrl (ctrl_t ctrl); ksba_cert_t get_cert_local (ctrl_t ctrl, const char *issuer); ksba_cert_t get_issuing_cert_local (ctrl_t ctrl, const char *issuer); ksba_cert_t get_cert_local_ski (ctrl_t ctrl, const char *name, ksba_sexp_t keyid); gpg_error_t get_istrusted_from_client (ctrl_t ctrl, const char *hexfpr); void start_command_handler (gnupg_fd_t fd); gpg_error_t dirmngr_status (ctrl_t ctrl, const char *keyword, ...); gpg_error_t dirmngr_status_help (ctrl_t ctrl, const char *text); gpg_error_t dirmngr_tick (ctrl_t ctrl); #endif /*DIRMNGR_H*/ diff --git a/dirmngr/ocsp.c b/dirmngr/ocsp.c index 561b7d7d8..8c893aa47 100644 --- a/dirmngr/ocsp.c +++ b/dirmngr/ocsp.c @@ -1,805 +1,801 @@ /* ocsp.c - OCSP management * Copyright (C) 2004, 2007 g10 Code GmbH * * This file is part of DirMngr. * * DirMngr is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * DirMngr 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 General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA */ #include #include #include #include #include #include "dirmngr.h" #include "misc.h" #include "http.h" #include "validate.h" #include "certcache.h" #include "ocsp.h" /* The maximum size we allow as a response from an OCSP reponder. */ #define MAX_RESPONSE_SIZE 65536 static const char oidstr_ocsp[] = "1.3.6.1.5.5.7.48.1"; /* Telesec attribute used to implement a positive confirmation. CertHash ::= SEQUENCE { HashAlgorithm AlgorithmIdentifier, certificateHash OCTET STRING } */ static const char oidstr_certHash[] = "1.3.36.8.3.13"; /* Read from FP and return a newly allocated buffer in R_BUFFER with the entire data read from FP. */ static gpg_error_t read_response (estream_t fp, unsigned char **r_buffer, size_t *r_buflen) { gpg_error_t err; unsigned char *buffer; size_t bufsize, nbytes; *r_buffer = NULL; *r_buflen = 0; bufsize = 4096; buffer = xtrymalloc (bufsize); if (!buffer) return gpg_error_from_errno (errno); nbytes = 0; for (;;) { unsigned char *tmp; size_t nread = 0; assert (nbytes < bufsize); nread = es_fread (buffer+nbytes, 1, bufsize-nbytes, fp); if (nread < bufsize-nbytes && es_ferror (fp)) { err = gpg_error_from_errno (errno); log_error (_("error reading from responder: %s\n"), strerror (errno)); xfree (buffer); return err; } if ( !(nread == bufsize-nbytes && !es_feof (fp))) { /* Response successfully received. */ nbytes += nread; *r_buffer = buffer; *r_buflen = nbytes; return 0; } nbytes += nread; /* Need to enlarge the buffer. */ if (bufsize >= MAX_RESPONSE_SIZE) { log_error (_("response from server too large; limit is %d bytes\n"), MAX_RESPONSE_SIZE); xfree (buffer); return gpg_error (GPG_ERR_TOO_LARGE); } bufsize += 4096; tmp = xtryrealloc (buffer, bufsize); if (!tmp) { err = gpg_error_from_errno (errno); xfree (buffer); return err; } buffer = tmp; } } /* Construct an OCSP request, send it to the configured OCSP responder and parse the response. On success the OCSP context may be used to further process the response. */ static gpg_error_t do_ocsp_request (ctrl_t ctrl, ksba_ocsp_t ocsp, gcry_md_hd_t md, const char *url, ksba_cert_t cert, ksba_cert_t issuer_cert) { gpg_error_t err; unsigned char *request, *response; size_t requestlen, responselen; http_t http; ksba_ocsp_response_status_t response_status; const char *t; int redirects_left = 2; char *free_this = NULL; (void)ctrl; if (opt.use_tor) { /* For now we do not allow OCSP via Tor due to possible privacy concerns. Needs further research. */ log_error (_("OCSP request not possible due to Tor mode\n")); return gpg_error (GPG_ERR_NOT_SUPPORTED); } if (opt.disable_http) { log_error (_("OCSP request not possible due to disabled HTTP\n")); return gpg_error (GPG_ERR_NOT_SUPPORTED); } err = ksba_ocsp_add_target (ocsp, cert, issuer_cert); if (err) { log_error (_("error setting OCSP target: %s\n"), gpg_strerror (err)); return err; } { size_t n; unsigned char nonce[32]; n = ksba_ocsp_set_nonce (ocsp, NULL, 0); if (n > sizeof nonce) n = sizeof nonce; gcry_create_nonce (nonce, n); ksba_ocsp_set_nonce (ocsp, nonce, n); } err = ksba_ocsp_build_request (ocsp, &request, &requestlen); if (err) { log_error (_("error building OCSP request: %s\n"), gpg_strerror (err)); return err; } once_more: err = http_open (&http, HTTP_REQ_POST, url, NULL, NULL, ((opt.honor_http_proxy? HTTP_FLAG_TRY_PROXY:0) | (opt.use_tor? HTTP_FLAG_FORCE_TOR:0)), ctrl->http_proxy, NULL, NULL, NULL); if (err) { log_error (_("error connecting to '%s': %s\n"), url, gpg_strerror (err)); xfree (free_this); return err; } es_fprintf (http_get_write_ptr (http), "Content-Type: application/ocsp-request\r\n" "Content-Length: %lu\r\n", (unsigned long)requestlen ); http_start_data (http); if (es_fwrite (request, requestlen, 1, http_get_write_ptr (http)) != 1) { err = gpg_error_from_errno (errno); log_error ("error sending request to '%s': %s\n", url, strerror (errno)); http_close (http, 0); xfree (request); xfree (free_this); return err; } xfree (request); request = NULL; err = http_wait_response (http); if (err || http_get_status_code (http) != 200) { if (err) log_error (_("error reading HTTP response for '%s': %s\n"), url, gpg_strerror (err)); else { switch (http_get_status_code (http)) { case 301: case 302: { const char *s = http_get_header (http, "Location"); log_info (_("URL '%s' redirected to '%s' (%u)\n"), url, s?s:"[none]", http_get_status_code (http)); if (s && *s && redirects_left-- ) { xfree (free_this); url = NULL; free_this = xtrystrdup (s); if (!free_this) err = gpg_error_from_errno (errno); else { url = free_this; http_close (http, 0); goto once_more; } } else err = gpg_error (GPG_ERR_NO_DATA); log_error (_("too many redirections\n")); } break; default: log_error (_("error accessing '%s': http status %u\n"), url, http_get_status_code (http)); err = gpg_error (GPG_ERR_NO_DATA); break; } } http_close (http, 0); xfree (free_this); return err; } err = read_response (http_get_read_ptr (http), &response, &responselen); http_close (http, 0); if (err) { log_error (_("error reading HTTP response for '%s': %s\n"), url, gpg_strerror (err)); xfree (free_this); return err; } err = ksba_ocsp_parse_response (ocsp, response, responselen, &response_status); if (err) { log_error (_("error parsing OCSP response for '%s': %s\n"), url, gpg_strerror (err)); xfree (response); xfree (free_this); return err; } switch (response_status) { case KSBA_OCSP_RSPSTATUS_SUCCESS: t = "success"; break; case KSBA_OCSP_RSPSTATUS_MALFORMED: t = "malformed"; break; case KSBA_OCSP_RSPSTATUS_INTERNAL: t = "internal error"; break; case KSBA_OCSP_RSPSTATUS_TRYLATER: t = "try later"; break; case KSBA_OCSP_RSPSTATUS_SIGREQUIRED: t = "must sign request"; break; case KSBA_OCSP_RSPSTATUS_UNAUTHORIZED: t = "unauthorized"; break; case KSBA_OCSP_RSPSTATUS_REPLAYED: t = "replay detected"; break; case KSBA_OCSP_RSPSTATUS_OTHER: t = "other (unknown)"; break; case KSBA_OCSP_RSPSTATUS_NONE: t = "no status"; break; default: t = "[unknown status]"; break; } if (response_status == KSBA_OCSP_RSPSTATUS_SUCCESS) { if (opt.verbose) log_info (_("OCSP responder at '%s' status: %s\n"), url, t); err = ksba_ocsp_hash_response (ocsp, response, responselen, HASH_FNC, md); if (err) log_error (_("hashing the OCSP response for '%s' failed: %s\n"), url, gpg_strerror (err)); } else { log_error (_("OCSP responder at '%s' status: %s\n"), url, t); err = gpg_error (GPG_ERR_GENERAL); } xfree (response); xfree (free_this); return err; } /* Validate that CERT is indeed valid to sign an OCSP response. If SIGNER_FPR_LIST is not NULL we simply check that CERT matches one of the fingerprints in this list. */ static gpg_error_t validate_responder_cert (ctrl_t ctrl, ksba_cert_t cert, fingerprint_list_t signer_fpr_list) { gpg_error_t err; char *fpr; if (signer_fpr_list) { fpr = get_fingerprint_hexstring (cert); for (; signer_fpr_list && strcmp (signer_fpr_list->hexfpr, fpr); signer_fpr_list = signer_fpr_list->next) ; if (signer_fpr_list) err = 0; else { log_error (_("not signed by a default OCSP signer's certificate")); err = gpg_error (GPG_ERR_BAD_CA_CERT); } xfree (fpr); } - else if (opt.system_daemon) - { - err = validate_cert_chain (ctrl, cert, NULL, VALIDATE_MODE_OCSP, NULL); - } else { /* We avoid duplicating the entire certificate validation code from gpgsm here. Because we have no way calling back to the client and letting it compute the validity, we use the ugly hack of telling the client that the response will only be valid if the certificate given in this status message is valid. Note, that in theory we could simply ask the client via an inquire to validate a certificate but this might involve calling DirMngr again recursivly - we can't do that as of now (neither DirMngr nor gpgsm have the ability for concurrent access to DirMngr. */ /* FIXME: We should cache this certificate locally, so that the next call to dirmngr won't need to look it up - if this works at all. */ fpr = get_fingerprint_hexstring (cert); dirmngr_status (ctrl, "ONLY_VALID_IF_CERT_VALID", fpr, NULL); xfree (fpr); err = 0; } return err; } /* Helper for check_signature. */ static int check_signature_core (ctrl_t ctrl, ksba_cert_t cert, gcry_sexp_t s_sig, gcry_sexp_t s_hash, fingerprint_list_t signer_fpr_list) { gpg_error_t err; ksba_sexp_t pubkey; gcry_sexp_t s_pkey = NULL; pubkey = ksba_cert_get_public_key (cert); if (!pubkey) err = gpg_error (GPG_ERR_INV_OBJ); else err = canon_sexp_to_gcry (pubkey, &s_pkey); xfree (pubkey); if (!err) err = gcry_pk_verify (s_sig, s_hash, s_pkey); if (!err) err = validate_responder_cert (ctrl, cert, signer_fpr_list); if (!err) { gcry_sexp_release (s_pkey); return 0; /* Successfully verified the signature. */ } /* We simply ignore all errors. */ gcry_sexp_release (s_pkey); return -1; } /* Check the signature of an OCSP repsonse. OCSP is the context, S_SIG the signature value and MD the handle of the hash we used for the response. This function automagically finds the correct public key. If SIGNER_FPR_LIST is not NULL, the default OCSP reponder has been used and thus the certificate is one of those identified by the fingerprints. */ static gpg_error_t check_signature (ctrl_t ctrl, ksba_ocsp_t ocsp, gcry_sexp_t s_sig, gcry_md_hd_t md, fingerprint_list_t signer_fpr_list) { gpg_error_t err; int algo, cert_idx; gcry_sexp_t s_hash; ksba_cert_t cert; /* Create a suitable S-expression with the hash value of our response. */ gcry_md_final (md); algo = gcry_md_get_algo (md); if (algo != GCRY_MD_SHA1 ) { log_error (_("only SHA-1 is supported for OCSP responses\n")); return gpg_error (GPG_ERR_DIGEST_ALGO); } err = gcry_sexp_build (&s_hash, NULL, "(data(flags pkcs1)(hash sha1 %b))", gcry_md_get_algo_dlen (algo), gcry_md_read (md, algo)); if (err) { log_error (_("creating S-expression failed: %s\n"), gcry_strerror (err)); return err; } /* Get rid of old OCSP specific certificate references. */ release_ctrl_ocsp_certs (ctrl); if (signer_fpr_list && !signer_fpr_list->next) { /* There is exactly one signer fingerprint given. Thus we use the default OCSP responder's certificate and instantly know the certificate to use. */ cert = get_cert_byhexfpr (signer_fpr_list->hexfpr); if (!cert) cert = get_cert_local (ctrl, signer_fpr_list->hexfpr); if (cert) { err = check_signature_core (ctrl, cert, s_sig, s_hash, signer_fpr_list); ksba_cert_release (cert); cert = NULL; if (!err) { gcry_sexp_release (s_hash); return 0; /* Successfully verified the signature. */ } } } else { char *name; ksba_sexp_t keyid; /* Put all certificates included in the response into the cache and setup a list of those certificate which will later be preferred used when locating certificates. */ for (cert_idx=0; (cert = ksba_ocsp_get_cert (ocsp, cert_idx)); cert_idx++) { cert_ref_t cref; cref = xtrymalloc (sizeof *cref); if (!cref) log_error (_("allocating list item failed: %s\n"), gcry_strerror (err)); else if (!cache_cert_silent (cert, &cref->fpr)) { cref->next = ctrl->ocsp_certs; ctrl->ocsp_certs = cref; } else xfree (cref); } /* Get the certificate by means of the responder ID. */ err = ksba_ocsp_get_responder_id (ocsp, &name, &keyid); if (err) { log_error (_("error getting responder ID: %s\n"), gcry_strerror (err)); return err; } cert = find_cert_bysubject (ctrl, name, keyid); if (!cert) { log_error ("responder certificate "); if (name) log_printf ("'/%s' ", name); if (keyid) { log_printf ("{"); dump_serial (keyid); log_printf ("} "); } log_printf ("not found\n"); } ksba_free (name); ksba_free (keyid); if (cert) { err = check_signature_core (ctrl, cert, s_sig, s_hash, signer_fpr_list); ksba_cert_release (cert); if (!err) { gcry_sexp_release (s_hash); return 0; /* Successfully verified the signature. */ } } } gcry_sexp_release (s_hash); log_error (_("no suitable certificate found to verify the OCSP response\n")); return gpg_error (GPG_ERR_NO_PUBKEY); } /* Check whether the certificate either given by fingerprint CERT_FPR or directly through the CERT object is valid by running an OCSP transaction. With FORCE_DEFAULT_RESPONDER set only the configured default responder is used. */ gpg_error_t ocsp_isvalid (ctrl_t ctrl, ksba_cert_t cert, const char *cert_fpr, int force_default_responder) { gpg_error_t err; ksba_ocsp_t ocsp = NULL; ksba_cert_t issuer_cert = NULL; ksba_sexp_t sigval = NULL; gcry_sexp_t s_sig = NULL; ksba_isotime_t current_time; ksba_isotime_t this_update, next_update, revocation_time, produced_at; ksba_isotime_t tmp_time; ksba_status_t status; ksba_crl_reason_t reason; char *url_buffer = NULL; const char *url; gcry_md_hd_t md = NULL; int i, idx; char *oid; ksba_name_t name; fingerprint_list_t default_signer = NULL; /* Get the certificate. */ if (cert) { ksba_cert_ref (cert); err = find_issuing_cert (ctrl, cert, &issuer_cert); if (err) { log_error (_("issuer certificate not found: %s\n"), gpg_strerror (err)); goto leave; } } else { cert = get_cert_local (ctrl, cert_fpr); if (!cert) { log_error (_("caller did not return the target certificate\n")); err = gpg_error (GPG_ERR_GENERAL); goto leave; } issuer_cert = get_issuing_cert_local (ctrl, NULL); if (!issuer_cert) { log_error (_("caller did not return the issuing certificate\n")); err = gpg_error (GPG_ERR_GENERAL); goto leave; } } /* Create an OCSP instance. */ err = ksba_ocsp_new (&ocsp); if (err) { log_error (_("failed to allocate OCSP context: %s\n"), gpg_strerror (err)); goto leave; } /* Figure out the OCSP responder to use. 1. Try to get the reponder from the certificate. We do only take http and https style URIs into account. 2. If this fails use the default responder, if any. */ url = NULL; for (idx=0; !url && !opt.ignore_ocsp_service_url && !force_default_responder && !(err=ksba_cert_get_authority_info_access (cert, idx, &oid, &name)); idx++) { if ( !strcmp (oid, oidstr_ocsp) ) { for (i=0; !url && ksba_name_enum (name, i); i++) { char *p = ksba_name_get_uri (name, i); if (p && (!ascii_strncasecmp (p, "http:", 5) || !ascii_strncasecmp (p, "https:", 6))) url = url_buffer = p; else xfree (p); } } ksba_name_release (name); ksba_free (oid); } if (err && gpg_err_code (err) != GPG_ERR_EOF) { log_error (_("can't get authorityInfoAccess: %s\n"), gpg_strerror (err)); goto leave; } if (!url) { if (!opt.ocsp_responder || !*opt.ocsp_responder) { log_info (_("no default OCSP responder defined\n")); err = gpg_error (GPG_ERR_CONFIGURATION); goto leave; } if (!opt.ocsp_signer) { log_info (_("no default OCSP signer defined\n")); err = gpg_error (GPG_ERR_CONFIGURATION); goto leave; } url = opt.ocsp_responder; default_signer = opt.ocsp_signer; if (opt.verbose) log_info (_("using default OCSP responder '%s'\n"), url); } else { if (opt.verbose) log_info (_("using OCSP responder '%s'\n"), url); } /* Ask the OCSP responder. */ err = gcry_md_open (&md, GCRY_MD_SHA1, 0); if (err) { log_error (_("failed to establish a hashing context for OCSP: %s\n"), gpg_strerror (err)); goto leave; } err = do_ocsp_request (ctrl, ocsp, md, url, cert, issuer_cert); if (err) goto leave; /* We got a useful answer, check that the answer has a valid signature. */ sigval = ksba_ocsp_get_sig_val (ocsp, produced_at); if (!sigval || !*produced_at) { err = gpg_error (GPG_ERR_INV_OBJ); goto leave; } if ( (err = canon_sexp_to_gcry (sigval, &s_sig)) ) goto leave; xfree (sigval); sigval = NULL; err = check_signature (ctrl, ocsp, s_sig, md, default_signer); if (err) goto leave; /* We only support one certificate per request. Check that the answer matches the right certificate. */ err = ksba_ocsp_get_status (ocsp, cert, &status, this_update, next_update, revocation_time, &reason); if (err) { log_error (_("error getting OCSP status for target certificate: %s\n"), gpg_strerror (err)); goto leave; } /* In case the certificate has been revoked, we better invalidate our cached validation status. */ if (status == KSBA_STATUS_REVOKED) { time_t validated_at = 0; /* That is: No cached validation available. */ err = ksba_cert_set_user_data (cert, "validated_at", &validated_at, sizeof (validated_at)); if (err) { log_error ("set_user_data(validated_at) failed: %s\n", gpg_strerror (err)); err = 0; /* The certificate is anyway revoked, and that is a more important message than the failure of our cache. */ } } if (opt.verbose) { log_info (_("certificate status is: %s (this=%s next=%s)\n"), status == KSBA_STATUS_GOOD? _("good"): status == KSBA_STATUS_REVOKED? _("revoked"): status == KSBA_STATUS_UNKNOWN? _("unknown"): status == KSBA_STATUS_NONE? _("none"): "?", this_update, next_update); if (status == KSBA_STATUS_REVOKED) log_info (_("certificate has been revoked at: %s due to: %s\n"), revocation_time, reason == KSBA_CRLREASON_UNSPECIFIED? "unspecified": reason == KSBA_CRLREASON_KEY_COMPROMISE? "key compromise": reason == KSBA_CRLREASON_CA_COMPROMISE? "CA compromise": reason == KSBA_CRLREASON_AFFILIATION_CHANGED? "affiliation changed": reason == KSBA_CRLREASON_SUPERSEDED? "superseded": reason == KSBA_CRLREASON_CESSATION_OF_OPERATION? "cessation of operation": reason == KSBA_CRLREASON_CERTIFICATE_HOLD? "certificate on hold": reason == KSBA_CRLREASON_REMOVE_FROM_CRL? "removed from CRL": reason == KSBA_CRLREASON_PRIVILEGE_WITHDRAWN? "privilege withdrawn": reason == KSBA_CRLREASON_AA_COMPROMISE? "AA compromise": reason == KSBA_CRLREASON_OTHER? "other":"?"); } if (status == KSBA_STATUS_REVOKED) err = gpg_error (GPG_ERR_CERT_REVOKED); else if (status == KSBA_STATUS_UNKNOWN) err = gpg_error (GPG_ERR_NO_DATA); else if (status != KSBA_STATUS_GOOD) err = gpg_error (GPG_ERR_GENERAL); /* Allow for some clock skew. */ gnupg_get_isotime (current_time); add_seconds_to_isotime (current_time, opt.ocsp_max_clock_skew); if (strcmp (this_update, current_time) > 0 ) { log_error (_("OCSP responder returned a status in the future\n")); log_info ("used now: %s this_update: %s\n", current_time, this_update); if (!err) err = gpg_error (GPG_ERR_TIME_CONFLICT); } /* Check that THIS_UPDATE is not too far back in the past. */ gnupg_copy_time (tmp_time, this_update); add_seconds_to_isotime (tmp_time, opt.ocsp_max_period+opt.ocsp_max_clock_skew); if (!*tmp_time || strcmp (tmp_time, current_time) < 0 ) { log_error (_("OCSP responder returned a non-current status\n")); log_info ("used now: %s this_update: %s\n", current_time, this_update); if (!err) err = gpg_error (GPG_ERR_TIME_CONFLICT); } /* Check that we are not beyound NEXT_UPDATE (plus some extra time). */ if (*next_update) { gnupg_copy_time (tmp_time, next_update); add_seconds_to_isotime (tmp_time, opt.ocsp_current_period+opt.ocsp_max_clock_skew); if (!*tmp_time && strcmp (tmp_time, current_time) < 0 ) { log_error (_("OCSP responder returned an too old status\n")); log_info ("used now: %s next_update: %s\n", current_time, next_update); if (!err) err = gpg_error (GPG_ERR_TIME_CONFLICT); } } leave: gcry_md_close (md); gcry_sexp_release (s_sig); xfree (sigval); ksba_cert_release (issuer_cert); ksba_cert_release (cert); ksba_ocsp_release (ocsp); xfree (url_buffer); return err; } /* Release the list of OCSP certificates hold in the CTRL object. */ void release_ctrl_ocsp_certs (ctrl_t ctrl) { while (ctrl->ocsp_certs) { cert_ref_t tmp = ctrl->ocsp_certs->next; xfree (ctrl->ocsp_certs); ctrl->ocsp_certs = tmp; } } diff --git a/dirmngr/server.c b/dirmngr/server.c index db6f5a8ff..3ac41604b 100644 --- a/dirmngr/server.c +++ b/dirmngr/server.c @@ -1,2626 +1,2588 @@ /* server.c - LDAP and Keyserver access server * Copyright (C) 2002 Klarälvdalens Datakonsult AB * Copyright (C) 2003, 2004, 2005, 2007, 2008, 2009, 2011, 2015 g10 Code GmbH * Copyright (C) 2014, 2015, 2016 Werner Koch * * This file is part of GnuPG. * * GnuPG is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3 of the License, or * (at your option) any later version. * * GnuPG 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 General Public License * along with this program; if not, see . */ #include #include #include #include #include #include #include #include #include #include #include "dirmngr.h" #include #include "crlcache.h" #include "crlfetch.h" #if USE_LDAP # include "ldapserver.h" #endif #include "ocsp.h" #include "certcache.h" #include "validate.h" #include "misc.h" #if USE_LDAP # include "ldap-wrapper.h" #endif #include "ks-action.h" #include "ks-engine.h" /* (ks_hkp_print_hosttable) */ #if USE_LDAP # include "ldap-parse-uri.h" #endif #include "dns-stuff.h" #include "mbox-util.h" #include "zb32.h" #include "server-help.h" /* To avoid DoS attacks we limit the size of a certificate to something reasonable. The DoS was actually only an issue back when Dirmngr was a system service and not a user service. */ #define MAX_CERT_LENGTH (16*1024) /* The same goes for OpenPGP keyblocks, but here we need to allow for much longer blocks; a 200k keyblock is not too unusual for keys with a lot of signatures (e.g. 0x5b0358a2). 9C31503C6D866396 even has 770 KiB as of 2015-08-23. To avoid adding a runtime option we now use 20MiB which should really be enough. Well, a key with several pictures could be larger (the parser as a 18MiB limit for attribute packets) but it won't be nice to the keyservers to send them such large blobs. */ #define MAX_KEYBLOCK_LENGTH (20*1024*1024) #define PARM_ERROR(t) assuan_set_error (ctx, \ gpg_error (GPG_ERR_ASS_PARAMETER), (t)) #define set_error(e,t) assuan_set_error (ctx, gpg_error (e), (t)) /* Control structure per connection. */ struct server_local_s { /* Data used to associate an Assuan context with local server data */ assuan_context_t assuan_ctx; /* Per-session LDAP servers. */ ldap_server_t ldapservers; /* Per-session list of keyservers. */ uri_item_t keyservers; /* If this flag is set to true this dirmngr process will be terminated after the end of this session. */ int stopme; /* State variable private to is_tor_running. */ int tor_state; }; /* Cookie definition for assuan data line output. */ static gpgrt_ssize_t data_line_cookie_write (void *cookie, const void *buffer, size_t size); static int data_line_cookie_close (void *cookie); static es_cookie_io_functions_t data_line_cookie_functions = { NULL, data_line_cookie_write, NULL, data_line_cookie_close }; /* Accessor for the local ldapservers variable. */ ldap_server_t get_ldapservers_from_ctrl (ctrl_t ctrl) { if (ctrl && ctrl->server_local) return ctrl->server_local->ldapservers; else return NULL; } /* Release an uri_item_t list. */ static void release_uri_item_list (uri_item_t list) { while (list) { uri_item_t tmp = list->next; http_release_parsed_uri (list->parsed_uri); xfree (list); list = tmp; } } /* Release all configured keyserver info from CTRL. */ void release_ctrl_keyservers (ctrl_t ctrl) { if (! ctrl->server_local) return; release_uri_item_list (ctrl->server_local->keyservers); ctrl->server_local->keyservers = NULL; } /* Helper to print a message while leaving a command. */ static gpg_error_t leave_cmd (assuan_context_t ctx, gpg_error_t err) { if (err) { const char *name = assuan_get_command_name (ctx); if (!name) name = "?"; if (gpg_err_source (err) == GPG_ERR_SOURCE_DEFAULT) log_error ("command '%s' failed: %s\n", name, gpg_strerror (err)); else log_error ("command '%s' failed: %s <%s>\n", name, gpg_strerror (err), gpg_strsource (err)); } return err; } /* This is a wrapper around assuan_send_data which makes debugging the output in verbose mode easier. */ static gpg_error_t data_line_write (assuan_context_t ctx, const void *buffer_arg, size_t size) { const char *buffer = buffer_arg; gpg_error_t err; if (opt.verbose && buffer && size) { /* Ease reading of output by sending a physical line at each LF. */ const char *p; size_t n, nbytes; nbytes = size; do { p = memchr (buffer, '\n', nbytes); n = p ? (p - buffer) + 1 : nbytes; err = assuan_send_data (ctx, buffer, n); if (err) { gpg_err_set_errno (EIO); return err; } buffer += n; nbytes -= n; if (nbytes && (err=assuan_send_data (ctx, NULL, 0))) /* Flush line. */ { gpg_err_set_errno (EIO); return err; } } while (nbytes); } else { err = assuan_send_data (ctx, buffer, size); if (err) { gpg_err_set_errno (EIO); /* For use by data_line_cookie_write. */ return err; } } return 0; } /* A write handler used by es_fopencookie to write assuan data lines. */ static gpgrt_ssize_t data_line_cookie_write (void *cookie, const void *buffer, size_t size) { assuan_context_t ctx = cookie; if (data_line_write (ctx, buffer, size)) return -1; return (gpgrt_ssize_t)size; } static int data_line_cookie_close (void *cookie) { assuan_context_t ctx = cookie; if (assuan_send_data (ctx, NULL, 0)) { gpg_err_set_errno (EIO); return -1; } return 0; } /* Copy the % and + escaped string S into the buffer D and replace the escape sequences. Note, that it is sufficient to allocate the target string D as long as the source string S, i.e.: strlen(s)+1. Note further that if S contains an escaped binary Nul the resulting string D will contain the 0 as well as all other characters but it will be impossible to know whether this is the original EOS or a copied Nul. */ static void strcpy_escaped_plus (char *d, const unsigned char *s) { while (*s) { if (*s == '%' && s[1] && s[2]) { s++; *d++ = xtoi_2 ( s); s += 2; } else if (*s == '+') *d++ = ' ', s++; else *d++ = *s++; } *d = 0; } /* This function returns true if a Tor server is running. The sattus is cached for the current conenction. */ static int is_tor_running (ctrl_t ctrl) { /* Check whether we can connect to the proxy. */ if (!ctrl || !ctrl->server_local) return 0; /* Ooops. */ if (!ctrl->server_local->tor_state) { assuan_fd_t sock; sock = assuan_sock_connect_byname (NULL, 0, 0, NULL, ASSUAN_SOCK_TOR); if (sock == ASSUAN_INVALID_FD) ctrl->server_local->tor_state = -1; /* Not running. */ else { assuan_sock_close (sock); ctrl->server_local->tor_state = 1; /* Running. */ } } return (ctrl->server_local->tor_state > 0); } /* Return an error if the assuan context does not belong to the owner of the process or to root. On error FAILTEXT is set as Assuan error string. */ static gpg_error_t check_owner_permission (assuan_context_t ctx, const char *failtext) { #ifdef HAVE_W32_SYSTEM /* Under Windows the dirmngr is always run under the control of the user. */ (void)ctx; (void)failtext; #else gpg_err_code_t ec; assuan_peercred_t cred; ec = gpg_err_code (assuan_get_peercred (ctx, &cred)); if (!ec && cred->uid && cred->uid != getuid ()) ec = GPG_ERR_EPERM; if (ec) return set_error (ec, failtext); #endif return 0; } /* Common code for get_cert_local and get_issuer_cert_local. */ static ksba_cert_t do_get_cert_local (ctrl_t ctrl, const char *name, const char *command) { unsigned char *value; size_t valuelen; int rc; char *buf; ksba_cert_t cert; if (name) { buf = xmalloc ( strlen (command) + 1 + strlen(name) + 1); strcpy (stpcpy (stpcpy (buf, command), " "), name); } else buf = xstrdup (command); rc = assuan_inquire (ctrl->server_local->assuan_ctx, buf, &value, &valuelen, MAX_CERT_LENGTH); xfree (buf); if (rc) { log_error (_("assuan_inquire(%s) failed: %s\n"), command, gpg_strerror (rc)); return NULL; } if (!valuelen) { xfree (value); return NULL; } rc = ksba_cert_new (&cert); if (!rc) { rc = ksba_cert_init_from_mem (cert, value, valuelen); if (rc) { ksba_cert_release (cert); cert = NULL; } } xfree (value); return cert; } /* Ask back to return a certificate for name, given as a regular gpgsm certificate indentificates (e.g. fingerprint or one of the other methods). Alternatively, NULL may be used for NAME to return the current target certificate. Either return the certificate in a KSBA object or NULL if it is not available. */ ksba_cert_t get_cert_local (ctrl_t ctrl, const char *name) { if (!ctrl || !ctrl->server_local || !ctrl->server_local->assuan_ctx) { if (opt.debug) log_debug ("get_cert_local called w/o context\n"); return NULL; } return do_get_cert_local (ctrl, name, "SENDCERT"); } /* Ask back to return the issuing certificate for name, given as a regular gpgsm certificate indentificates (e.g. fingerprint or one of the other methods). Alternatively, NULL may be used for NAME to return thecurrent target certificate. Either return the certificate in a KSBA object or NULL if it is not available. */ ksba_cert_t get_issuing_cert_local (ctrl_t ctrl, const char *name) { if (!ctrl || !ctrl->server_local || !ctrl->server_local->assuan_ctx) { if (opt.debug) log_debug ("get_issuing_cert_local called w/o context\n"); return NULL; } return do_get_cert_local (ctrl, name, "SENDISSUERCERT"); } /* Ask back to return a certificate with subject NAME and a subjectKeyIdentifier of KEYID. */ ksba_cert_t get_cert_local_ski (ctrl_t ctrl, const char *name, ksba_sexp_t keyid) { unsigned char *value; size_t valuelen; int rc; char *buf; ksba_cert_t cert; char *hexkeyid; if (!ctrl || !ctrl->server_local || !ctrl->server_local->assuan_ctx) { if (opt.debug) log_debug ("get_cert_local_ski called w/o context\n"); return NULL; } if (!name || !keyid) { log_debug ("get_cert_local_ski called with insufficient arguments\n"); return NULL; } hexkeyid = serial_hex (keyid); if (!hexkeyid) { log_debug ("serial_hex() failed\n"); return NULL; } buf = xtrymalloc (15 + strlen (hexkeyid) + 2 + strlen(name) + 1); if (!buf) { log_error ("can't allocate enough memory: %s\n", strerror (errno)); xfree (hexkeyid); return NULL; } strcpy (stpcpy (stpcpy (stpcpy (buf, "SENDCERT_SKI "), hexkeyid)," /"),name); xfree (hexkeyid); rc = assuan_inquire (ctrl->server_local->assuan_ctx, buf, &value, &valuelen, MAX_CERT_LENGTH); xfree (buf); if (rc) { log_error (_("assuan_inquire(%s) failed: %s\n"), "SENDCERT_SKI", gpg_strerror (rc)); return NULL; } if (!valuelen) { xfree (value); return NULL; } rc = ksba_cert_new (&cert); if (!rc) { rc = ksba_cert_init_from_mem (cert, value, valuelen); if (rc) { ksba_cert_release (cert); cert = NULL; } } xfree (value); return cert; } /* Ask the client via an inquiry to check the istrusted status of the certificate specified by the hexified fingerprint HEXFPR. Returns 0 if the certificate is trusted by the client or an error code. */ gpg_error_t get_istrusted_from_client (ctrl_t ctrl, const char *hexfpr) { unsigned char *value; size_t valuelen; int rc; char request[100]; if (!ctrl || !ctrl->server_local || !ctrl->server_local->assuan_ctx || !hexfpr) return gpg_error (GPG_ERR_INV_ARG); snprintf (request, sizeof request, "ISTRUSTED %s", hexfpr); rc = assuan_inquire (ctrl->server_local->assuan_ctx, request, &value, &valuelen, 100); if (rc) { log_error (_("assuan_inquire(%s) failed: %s\n"), request, gpg_strerror (rc)); return rc; } /* The expected data is: "1" or "1 cruft" (not a C-string). */ if (valuelen && *value == '1' && (valuelen == 1 || spacep (value+1))) rc = 0; else rc = gpg_error (GPG_ERR_NOT_TRUSTED); xfree (value); return rc; } /* Ask the client to return the certificate associated with the current command. This is sometimes needed because the client usually sends us just the cert ID, assuming that the request can be satisfied from the cache, where the cert ID is used as key. */ static int inquire_cert_and_load_crl (assuan_context_t ctx) { ctrl_t ctrl = assuan_get_pointer (ctx); gpg_error_t err; unsigned char *value = NULL; size_t valuelen; ksba_cert_t cert = NULL; err = assuan_inquire( ctx, "SENDCERT", &value, &valuelen, 0); if (err) return err; /* { */ /* FILE *fp = fopen ("foo.der", "r"); */ /* value = xmalloc (2000); */ /* valuelen = fread (value, 1, 2000, fp); */ /* fclose (fp); */ /* } */ if (!valuelen) /* No data returned; return a comprehensible error. */ return gpg_error (GPG_ERR_MISSING_CERT); err = ksba_cert_new (&cert); if (err) goto leave; err = ksba_cert_init_from_mem (cert, value, valuelen); if(err) goto leave; xfree (value); value = NULL; err = crl_cache_reload_crl (ctrl, cert); leave: ksba_cert_release (cert); xfree (value); return err; } /* Handle OPTION commands. */ static gpg_error_t option_handler (assuan_context_t ctx, const char *key, const char *value) { ctrl_t ctrl = assuan_get_pointer (ctx); gpg_error_t err = 0; if (!strcmp (key, "force-crl-refresh")) { int i = *value? atoi (value) : 0; ctrl->force_crl_refresh = i; } else if (!strcmp (key, "audit-events")) { int i = *value? atoi (value) : 0; ctrl->audit_events = i; } else if (!strcmp (key, "http-proxy")) { xfree (ctrl->http_proxy); if (!*value || !strcmp (value, "none")) ctrl->http_proxy = NULL; else if (!(ctrl->http_proxy = xtrystrdup (value))) err = gpg_error_from_syserror (); } else if (!strcmp (key, "honor-keyserver-url-used")) { /* Return an error if we are running in Tor mode. */ if (opt.use_tor) err = gpg_error (GPG_ERR_FORBIDDEN); } else err = gpg_error (GPG_ERR_UNKNOWN_OPTION); return err; } static const char hlp_dns_cert[] = "DNS_CERT \n" "DNS_CERT --pka \n" "DNS_CERT --dane \n" "\n" "Return the CERT record for . is one of\n" " * Return the first record of any supported subtype\n" " PGP Return the first record of subtype PGP (3)\n" " IPGP Return the first record of subtype IPGP (6)\n" "If the content of a certificate is available (PGP) it is returned\n" "by data lines. Fingerprints and URLs are returned via status lines.\n" "In --pka mode the fingerprint and if available an URL is returned.\n" "In --dane mode the key is returned from RR type 61"; static gpg_error_t cmd_dns_cert (assuan_context_t ctx, char *line) { /* ctrl_t ctrl = assuan_get_pointer (ctx); */ gpg_error_t err = 0; int pka_mode, dane_mode; char *mbox = NULL; char *namebuf = NULL; char *encodedhash = NULL; const char *name; int certtype; char *p; void *key = NULL; size_t keylen; unsigned char *fpr = NULL; size_t fprlen; char *url = NULL; pka_mode = has_option (line, "--pka"); dane_mode = has_option (line, "--dane"); line = skip_options (line); if (pka_mode && dane_mode) { err = PARM_ERROR ("either --pka or --dane may be given"); goto leave; } if (pka_mode || dane_mode) ; /* No need to parse here - we do this later. */ else { p = strchr (line, ' '); if (!p) { err = PARM_ERROR ("missing arguments"); goto leave; } *p++ = 0; if (!strcmp (line, "*")) certtype = DNS_CERTTYPE_ANY; else if (!strcmp (line, "IPGP")) certtype = DNS_CERTTYPE_IPGP; else if (!strcmp (line, "PGP")) certtype = DNS_CERTTYPE_PGP; else { err = PARM_ERROR ("unknown subtype"); goto leave; } while (spacep (p)) p++; line = p; if (!*line) { err = PARM_ERROR ("name missing"); goto leave; } } if (opt.use_tor && (err = enable_dns_tormode (0))) { /* Tor mode is requested but the DNS code can't enable it. */ assuan_set_error (ctx, err, "error enabling Tor mode"); goto leave; } if (pka_mode || dane_mode) { char *domain; /* Points to mbox. */ char hashbuf[32]; /* For SHA-1 and SHA-256. */ /* We lowercase ascii characters but the DANE I-D does not allow this. FIXME: Check after the release of the RFC whether to change this. */ mbox = mailbox_from_userid (line); if (!mbox || !(domain = strchr (mbox, '@'))) { err = set_error (GPG_ERR_INV_USER_ID, "no mailbox in user id"); goto leave; } *domain++ = 0; if (pka_mode) { gcry_md_hash_buffer (GCRY_MD_SHA1, hashbuf, mbox, strlen (mbox)); encodedhash = zb32_encode (hashbuf, 8*20); if (!encodedhash) { err = gpg_error_from_syserror (); goto leave; } namebuf = strconcat (encodedhash, "._pka.", domain, NULL); if (!namebuf) { err = gpg_error_from_syserror (); goto leave; } name = namebuf; certtype = DNS_CERTTYPE_IPGP; } else { /* Note: The hash is truncated to 28 bytes and we lowercase the result only for aesthetic reasons. */ gcry_md_hash_buffer (GCRY_MD_SHA256, hashbuf, mbox, strlen (mbox)); encodedhash = bin2hex (hashbuf, 28, NULL); if (!encodedhash) { err = gpg_error_from_syserror (); goto leave; } ascii_strlwr (encodedhash); namebuf = strconcat (encodedhash, "._openpgpkey.", domain, NULL); if (!namebuf) { err = gpg_error_from_syserror (); goto leave; } name = namebuf; certtype = DNS_CERTTYPE_RR61; } } else name = line; err = get_dns_cert (name, certtype, &key, &keylen, &fpr, &fprlen, &url); if (err) goto leave; if (key) { err = data_line_write (ctx, key, keylen); if (err) goto leave; } if (fpr) { char *tmpstr; tmpstr = bin2hex (fpr, fprlen, NULL); if (!tmpstr) err = gpg_error_from_syserror (); else { err = assuan_write_status (ctx, "FPR", tmpstr); xfree (tmpstr); } if (err) goto leave; } if (url) { err = assuan_write_status (ctx, "URL", url); if (err) goto leave; } leave: xfree (key); xfree (fpr); xfree (url); xfree (mbox); xfree (namebuf); xfree (encodedhash); return leave_cmd (ctx, err); } static const char hlp_wkd_get[] = "WKD_GET [--submission-address] \n" "\n" "Return the key or the submission address for \n" "from a Web Key Directory."; static gpg_error_t cmd_wkd_get (assuan_context_t ctx, char *line) { ctrl_t ctrl = assuan_get_pointer (ctx); gpg_error_t err = 0; char *mbox = NULL; char *domain; /* Points to mbox. */ char sha1buf[20]; char *uri = NULL; char *encodedhash = NULL; int opt_submission_addr; opt_submission_addr = has_option (line, "--submission-address"); line = skip_options (line); mbox = mailbox_from_userid (line); if (!mbox || !(domain = strchr (mbox, '@'))) { err = set_error (GPG_ERR_INV_USER_ID, "no mailbox in user id"); goto leave; } *domain++ = 0; gcry_md_hash_buffer (GCRY_MD_SHA1, sha1buf, mbox, strlen (mbox)); encodedhash = zb32_encode (sha1buf, 8*20); if (!encodedhash) { err = gpg_error_from_syserror (); goto leave; } if (opt_submission_addr) { uri = strconcat ("https://", domain, "/.well-known/openpgpkey/submission-address", NULL); } else { uri = strconcat ("https://", domain, "/.well-known/openpgpkey/hu/", encodedhash, NULL); } if (!uri) { err = gpg_error_from_syserror (); goto leave; } /* Setup an output stream and perform the get. */ { estream_t outfp; outfp = es_fopencookie (ctx, "w", data_line_cookie_functions); if (!outfp) err = set_error (GPG_ERR_ASS_GENERAL, "error setting up a data stream"); else { err = ks_action_fetch (ctrl, uri, outfp); es_fclose (outfp); } } leave: xfree (uri); xfree (encodedhash); xfree (mbox); return leave_cmd (ctx, err); } static const char hlp_ldapserver[] = "LDAPSERVER \n" "\n" "Add a new LDAP server to the list of configured LDAP servers.\n" "DATA is in the same format as expected in the configure file."; static gpg_error_t cmd_ldapserver (assuan_context_t ctx, char *line) { #if USE_LDAP ctrl_t ctrl = assuan_get_pointer (ctx); ldap_server_t server; ldap_server_t *last_next_p; while (spacep (line)) line++; if (*line == '\0') return leave_cmd (ctx, PARM_ERROR (_("ldapserver missing"))); server = ldapserver_parse_one (line, "", 0); if (! server) return leave_cmd (ctx, gpg_error (GPG_ERR_INV_ARG)); last_next_p = &ctrl->server_local->ldapservers; while (*last_next_p) last_next_p = &(*last_next_p)->next; *last_next_p = server; return leave_cmd (ctx, 0); #else (void)line; return leave_cmd (ctx, gpg_error (GPG_ERR_NOT_IMPLEMENTED)); #endif } static const char hlp_isvalid[] = "ISVALID [--only-ocsp] [--force-default-responder]" " |\n" "\n" "This command checks whether the certificate identified by the\n" "certificate_id is valid. This is done by consulting CRLs or\n" "whatever has been configured. Note, that the returned error codes\n" "are from gpg-error.h. The command may callback using the inquire\n" "function. See the manual for details.\n" "\n" "The CERTIFICATE_ID is a hex encoded string consisting of two parts,\n" "delimited by a single dot. The first part is the SHA-1 hash of the\n" "issuer name and the second part the serial number.\n" "\n" "Alternatively the certificate's fingerprint may be given in which\n" "case an OCSP request is done before consulting the CRL.\n" "\n" "If the option --only-ocsp is given, no fallback to a CRL check will\n" "be used.\n" "\n" "If the option --force-default-responder is given, only the default\n" "OCSP responder will be used and any other methods of obtaining an\n" "OCSP responder URL won't be used."; static gpg_error_t cmd_isvalid (assuan_context_t ctx, char *line) { ctrl_t ctrl = assuan_get_pointer (ctx); char *issuerhash, *serialno; gpg_error_t err; int did_inquire = 0; int ocsp_mode = 0; int only_ocsp; int force_default_responder; only_ocsp = has_option (line, "--only-ocsp"); force_default_responder = has_option (line, "--force-default-responder"); line = skip_options (line); issuerhash = xstrdup (line); /* We need to work on a copy of the line because that same Assuan context may be used for an inquiry. That is because Assuan reuses its line buffer. */ serialno = strchr (issuerhash, '.'); if (serialno) *serialno++ = 0; else { char *endp = strchr (issuerhash, ' '); if (endp) *endp = 0; if (strlen (issuerhash) != 40) { xfree (issuerhash); return leave_cmd (ctx, PARM_ERROR (_("serialno missing in cert ID"))); } ocsp_mode = 1; } again: if (ocsp_mode) { /* Note, that we ignore the given issuer hash and instead rely on the current certificate semantics used with this command. */ if (!opt.allow_ocsp) err = gpg_error (GPG_ERR_NOT_SUPPORTED); else err = ocsp_isvalid (ctrl, NULL, NULL, force_default_responder); /* Fixme: If we got no ocsp response and --only-ocsp is not used we should fall back to CRL mode. Thus we need to clear OCSP_MODE, get the issuerhash and the serialno from the current certificate and jump to again. */ } else if (only_ocsp) err = gpg_error (GPG_ERR_NO_CRL_KNOWN); else { switch (crl_cache_isvalid (ctrl, issuerhash, serialno, ctrl->force_crl_refresh)) { case CRL_CACHE_VALID: err = 0; break; case CRL_CACHE_INVALID: err = gpg_error (GPG_ERR_CERT_REVOKED); break; case CRL_CACHE_DONTKNOW: if (did_inquire) err = gpg_error (GPG_ERR_NO_CRL_KNOWN); else if (!(err = inquire_cert_and_load_crl (ctx))) { did_inquire = 1; goto again; } break; case CRL_CACHE_CANTUSE: err = gpg_error (GPG_ERR_NO_CRL_KNOWN); break; default: log_fatal ("crl_cache_isvalid returned invalid code\n"); } } xfree (issuerhash); return leave_cmd (ctx, err); } /* If the line contains a SHA-1 fingerprint as the first argument, return the FPR vuffer on success. The function checks that the fingerprint consists of valid characters and prints and error message if it does not and returns NULL. Fingerprints are considered optional and thus no explicit error is returned. NULL is also returned if there is no fingerprint at all available. FPR must be a caller provided buffer of at least 20 bytes. Note that colons within the fingerprint are allowed to separate 2 hex digits; this allows for easier cutting and pasting using the usual fingerprint rendering. */ static unsigned char * get_fingerprint_from_line (const char *line, unsigned char *fpr) { const char *s; int i; for (s=line, i=0; *s && *s != ' '; s++ ) { if ( hexdigitp (s) && hexdigitp (s+1) ) { if ( i >= 20 ) return NULL; /* Fingerprint too long. */ fpr[i++] = xtoi_2 (s); s++; } else if ( *s != ':' ) return NULL; /* Invalid. */ } if ( i != 20 ) return NULL; /* Fingerprint to short. */ return fpr; } static const char hlp_checkcrl[] = "CHECKCRL []\n" "\n" "Check whether the certificate with FINGERPRINT (SHA-1 hash of the\n" "entire X.509 certificate blob) is valid or not by consulting the\n" "CRL responsible for this certificate. If the fingerprint has not\n" "been given or the certificate is not known, the function \n" "inquires the certificate using an\n" "\n" " INQUIRE TARGETCERT\n" "\n" "and the caller is expected to return the certificate for the\n" "request (which should match FINGERPRINT) as a binary blob.\n" "Processing then takes place without further interaction; in\n" "particular dirmngr tries to locate other required certificate by\n" "its own mechanism which includes a local certificate store as well\n" "as a list of trusted root certificates.\n" "\n" "The return value is the usual gpg-error code or 0 for ducesss;\n" "i.e. the certificate validity has been confirmed by a valid CRL."; static gpg_error_t cmd_checkcrl (assuan_context_t ctx, char *line) { ctrl_t ctrl = assuan_get_pointer (ctx); gpg_error_t err; unsigned char fprbuffer[20], *fpr; ksba_cert_t cert; fpr = get_fingerprint_from_line (line, fprbuffer); cert = fpr? get_cert_byfpr (fpr) : NULL; if (!cert) { /* We do not have this certificate yet or the fingerprint has not been given. Inquire it from the client. */ unsigned char *value = NULL; size_t valuelen; err = assuan_inquire (ctrl->server_local->assuan_ctx, "TARGETCERT", &value, &valuelen, MAX_CERT_LENGTH); if (err) { log_error (_("assuan_inquire failed: %s\n"), gpg_strerror (err)); goto leave; } if (!valuelen) /* No data returned; return a comprehensible error. */ err = gpg_error (GPG_ERR_MISSING_CERT); else { err = ksba_cert_new (&cert); if (!err) err = ksba_cert_init_from_mem (cert, value, valuelen); } xfree (value); if(err) goto leave; } assert (cert); err = crl_cache_cert_isvalid (ctrl, cert, ctrl->force_crl_refresh); if (gpg_err_code (err) == GPG_ERR_NO_CRL_KNOWN) { err = crl_cache_reload_crl (ctrl, cert); if (!err) err = crl_cache_cert_isvalid (ctrl, cert, 0); } leave: ksba_cert_release (cert); return leave_cmd (ctx, err); } static const char hlp_checkocsp[] = "CHECKOCSP [--force-default-responder] []\n" "\n" "Check whether the certificate with FINGERPRINT (SHA-1 hash of the\n" "entire X.509 certificate blob) is valid or not by asking an OCSP\n" "responder responsible for this certificate. The optional\n" "fingerprint may be used for a quick check in case an OCSP check has\n" "been done for this certificate recently (we always cache OCSP\n" "responses for a couple of minutes). If the fingerprint has not been\n" "given or there is no cached result, the function inquires the\n" "certificate using an\n" "\n" " INQUIRE TARGETCERT\n" "\n" "and the caller is expected to return the certificate for the\n" "request (which should match FINGERPRINT) as a binary blob.\n" "Processing then takes place without further interaction; in\n" "particular dirmngr tries to locate other required certificates by\n" "its own mechanism which includes a local certificate store as well\n" "as a list of trusted root certificates.\n" "\n" "If the option --force-default-responder is given, only the default\n" "OCSP responder will be used and any other methods of obtaining an\n" "OCSP responder URL won't be used.\n" "\n" "The return value is the usual gpg-error code or 0 for ducesss;\n" "i.e. the certificate validity has been confirmed by a valid CRL."; static gpg_error_t cmd_checkocsp (assuan_context_t ctx, char *line) { ctrl_t ctrl = assuan_get_pointer (ctx); gpg_error_t err; unsigned char fprbuffer[20], *fpr; ksba_cert_t cert; int force_default_responder; force_default_responder = has_option (line, "--force-default-responder"); line = skip_options (line); fpr = get_fingerprint_from_line (line, fprbuffer); cert = fpr? get_cert_byfpr (fpr) : NULL; if (!cert) { /* We do not have this certificate yet or the fingerprint has not been given. Inquire it from the client. */ unsigned char *value = NULL; size_t valuelen; err = assuan_inquire (ctrl->server_local->assuan_ctx, "TARGETCERT", &value, &valuelen, MAX_CERT_LENGTH); if (err) { log_error (_("assuan_inquire failed: %s\n"), gpg_strerror (err)); goto leave; } if (!valuelen) /* No data returned; return a comprehensible error. */ err = gpg_error (GPG_ERR_MISSING_CERT); else { err = ksba_cert_new (&cert); if (!err) err = ksba_cert_init_from_mem (cert, value, valuelen); } xfree (value); if(err) goto leave; } assert (cert); if (!opt.allow_ocsp) err = gpg_error (GPG_ERR_NOT_SUPPORTED); else err = ocsp_isvalid (ctrl, cert, NULL, force_default_responder); leave: ksba_cert_release (cert); return leave_cmd (ctx, err); } static int lookup_cert_by_url (assuan_context_t ctx, const char *url) { ctrl_t ctrl = assuan_get_pointer (ctx); gpg_error_t err = 0; unsigned char *value = NULL; size_t valuelen; /* Fetch single certificate given it's URL. */ err = fetch_cert_by_url (ctrl, url, &value, &valuelen); if (err) { log_error (_("fetch_cert_by_url failed: %s\n"), gpg_strerror (err)); goto leave; } /* Send the data, flush the buffer and then send an END. */ err = assuan_send_data (ctx, value, valuelen); if (!err) err = assuan_send_data (ctx, NULL, 0); if (!err) err = assuan_write_line (ctx, "END"); if (err) { log_error (_("error sending data: %s\n"), gpg_strerror (err)); goto leave; } leave: return err; } /* Send the certificate, flush the buffer and then send an END. */ static gpg_error_t return_one_cert (void *opaque, ksba_cert_t cert) { assuan_context_t ctx = opaque; gpg_error_t err; const unsigned char *der; size_t derlen; der = ksba_cert_get_image (cert, &derlen); if (!der) err = gpg_error (GPG_ERR_INV_CERT_OBJ); else { err = assuan_send_data (ctx, der, derlen); if (!err) err = assuan_send_data (ctx, NULL, 0); if (!err) err = assuan_write_line (ctx, "END"); } if (err) log_error (_("error sending data: %s\n"), gpg_strerror (err)); return err; } /* Lookup certificates from the internal cache or using the ldap servers. */ static int lookup_cert_by_pattern (assuan_context_t ctx, char *line, int single, int cache_only) { gpg_error_t err = 0; char *p; strlist_t sl, list = NULL; int truncated = 0, truncation_forced = 0; int count = 0; int local_count = 0; #if USE_LDAP ctrl_t ctrl = assuan_get_pointer (ctx); unsigned char *value = NULL; size_t valuelen; struct ldapserver_iter ldapserver_iter; cert_fetch_context_t fetch_context; #endif /*USE_LDAP*/ int any_no_data = 0; /* Break the line down into an STRLIST */ for (p=line; *p; line = p) { while (*p && *p != ' ') p++; if (*p) *p++ = 0; if (*line) { sl = xtrymalloc (sizeof *sl + strlen (line)); if (!sl) { err = gpg_error_from_errno (errno); goto leave; } memset (sl, 0, sizeof *sl); strcpy_escaped_plus (sl->d, line); sl->next = list; list = sl; } } /* First look through the internal cache. The certifcates retruned here are not counted towards the truncation limit. */ if (single && !cache_only) ; /* Do not read from the local cache in this case. */ else { for (sl=list; sl; sl = sl->next) { err = get_certs_bypattern (sl->d, return_one_cert, ctx); if (!err) local_count++; if (!err && single) goto ready; if (gpg_err_code (err) == GPG_ERR_NO_DATA) { err = 0; if (cache_only) any_no_data = 1; } else if (gpg_err_code (err) == GPG_ERR_INV_NAME && !cache_only) { /* No real fault because the internal pattern lookup can't yet cope with all types of pattern. */ err = 0; } if (err) goto ready; } } /* Loop over all configured servers unless we want only the certificates from the cache. */ #if USE_LDAP for (ldapserver_iter_begin (&ldapserver_iter, ctrl); !cache_only && !ldapserver_iter_end_p (&ldapserver_iter) && ldapserver_iter.server->host && !truncation_forced; ldapserver_iter_next (&ldapserver_iter)) { ldap_server_t ldapserver = ldapserver_iter.server; if (DBG_LOOKUP) log_debug ("cmd_lookup: trying %s:%d base=%s\n", ldapserver->host, ldapserver->port, ldapserver->base?ldapserver->base : "[default]"); /* Fetch certificates matching pattern */ err = start_cert_fetch (ctrl, &fetch_context, list, ldapserver); if ( gpg_err_code (err) == GPG_ERR_NO_DATA ) { if (DBG_LOOKUP) log_debug ("cmd_lookup: no data\n"); err = 0; any_no_data = 1; continue; } if (err) { log_error (_("start_cert_fetch failed: %s\n"), gpg_strerror (err)); goto leave; } /* Fetch the certificates for this query. */ while (!truncation_forced) { xfree (value); value = NULL; err = fetch_next_cert (fetch_context, &value, &valuelen); if (gpg_err_code (err) == GPG_ERR_NO_DATA ) { err = 0; any_no_data = 1; break; /* Ready. */ } if (gpg_err_code (err) == GPG_ERR_TRUNCATED) { truncated = 1; err = 0; break; /* Ready. */ } if (gpg_err_code (err) == GPG_ERR_EOF) { err = 0; break; /* Ready. */ } if (!err && !value) { err = gpg_error (GPG_ERR_BUG); goto leave; } if (err) { log_error (_("fetch_next_cert failed: %s\n"), gpg_strerror (err)); end_cert_fetch (fetch_context); goto leave; } if (DBG_LOOKUP) log_debug ("cmd_lookup: returning one cert%s\n", truncated? " (truncated)":""); /* Send the data, flush the buffer and then send an END line as a certificate delimiter. */ err = assuan_send_data (ctx, value, valuelen); if (!err) err = assuan_send_data (ctx, NULL, 0); if (!err) err = assuan_write_line (ctx, "END"); if (err) { log_error (_("error sending data: %s\n"), gpg_strerror (err)); end_cert_fetch (fetch_context); goto leave; } if (++count >= opt.max_replies ) { truncation_forced = 1; log_info (_("max_replies %d exceeded\n"), opt.max_replies ); } if (single) break; } end_cert_fetch (fetch_context); } #endif /*USE_LDAP*/ ready: if (truncated || truncation_forced) { char str[50]; sprintf (str, "%d", count); assuan_write_status (ctx, "TRUNCATED", str); } if (!err && !count && !local_count && any_no_data) err = gpg_error (GPG_ERR_NO_DATA); leave: free_strlist (list); return err; } static const char hlp_lookup[] = "LOOKUP [--url] [--single] [--cache-only] \n" "\n" "Lookup certificates matching PATTERN. With --url the pattern is\n" "expected to be one URL.\n" "\n" "If --url is not given: To allow for multiple patterns (which are ORed)\n" "quoting is required: Spaces are translated to \"+\" or \"%20\";\n" "obviously this requires that the usual escape quoting rules are applied.\n" "\n" "If --url is given no special escaping is required because URLs are\n" "already escaped this way.\n" "\n" "If --single is given the first and only the first match will be\n" "returned. If --cache-only is _not_ given, no local query will be\n" "done.\n" "\n" "If --cache-only is given no external lookup is done so that only\n" "certificates from the cache may get returned."; static gpg_error_t cmd_lookup (assuan_context_t ctx, char *line) { gpg_error_t err; int lookup_url, single, cache_only; lookup_url = has_leading_option (line, "--url"); single = has_leading_option (line, "--single"); cache_only = has_leading_option (line, "--cache-only"); line = skip_options (line); if (lookup_url && cache_only) err = gpg_error (GPG_ERR_NOT_FOUND); else if (lookup_url && single) err = gpg_error (GPG_ERR_NOT_IMPLEMENTED); else if (lookup_url) err = lookup_cert_by_url (ctx, line); else err = lookup_cert_by_pattern (ctx, line, single, cache_only); return leave_cmd (ctx, err); } static const char hlp_loadcrl[] = "LOADCRL [--url] \n" "\n" "Load the CRL in the file with name FILENAME into our cache. Note\n" "that FILENAME should be given with an absolute path because\n" "Dirmngrs cwd is not known. With --url the CRL is directly loaded\n" "from the given URL.\n" "\n" "This command is usually used by gpgsm using the invocation \"gpgsm\n" "--call-dirmngr loadcrl \". A direct invocation of Dirmngr\n" "is not useful because gpgsm might need to callback gpgsm to ask for\n" "the CA's certificate."; static gpg_error_t cmd_loadcrl (assuan_context_t ctx, char *line) { ctrl_t ctrl = assuan_get_pointer (ctx); gpg_error_t err = 0; int use_url = has_leading_option (line, "--url"); line = skip_options (line); if (use_url) { ksba_reader_t reader; err = crl_fetch (ctrl, line, &reader); if (err) log_error (_("fetching CRL from '%s' failed: %s\n"), line, gpg_strerror (err)); else { err = crl_cache_insert (ctrl, line, reader); if (err) log_error (_("processing CRL from '%s' failed: %s\n"), line, gpg_strerror (err)); crl_close_reader (reader); } } else { char *buf; buf = xtrymalloc (strlen (line)+1); if (!buf) err = gpg_error_from_syserror (); else { strcpy_escaped_plus (buf, line); err = crl_cache_load (ctrl, buf); xfree (buf); } } return leave_cmd (ctx, err); } static const char hlp_listcrls[] = "LISTCRLS\n" "\n" "List the content of all CRLs in a readable format. This command is\n" "usually used by gpgsm using the invocation \"gpgsm --call-dirmngr\n" "listcrls\". It may also be used directly using \"dirmngr\n" "--list-crls\"."; static gpg_error_t cmd_listcrls (assuan_context_t ctx, char *line) { gpg_error_t err; estream_t fp; (void)line; fp = es_fopencookie (ctx, "w", data_line_cookie_functions); if (!fp) err = set_error (GPG_ERR_ASS_GENERAL, "error setting up a data stream"); else { err = crl_cache_list (fp); es_fclose (fp); } return leave_cmd (ctx, err); } static const char hlp_cachecert[] = "CACHECERT\n" "\n" "Put a certificate into the internal cache. This command might be\n" "useful if a client knows in advance certificates required for a\n" "test and wants to make sure they get added to the internal cache.\n" "It is also helpful for debugging. To get the actual certificate,\n" "this command immediately inquires it using\n" "\n" " INQUIRE TARGETCERT\n" "\n" "and the caller is expected to return the certificate for the\n" "request as a binary blob."; static gpg_error_t cmd_cachecert (assuan_context_t ctx, char *line) { ctrl_t ctrl = assuan_get_pointer (ctx); gpg_error_t err; ksba_cert_t cert = NULL; unsigned char *value = NULL; size_t valuelen; (void)line; err = assuan_inquire (ctrl->server_local->assuan_ctx, "TARGETCERT", &value, &valuelen, MAX_CERT_LENGTH); if (err) { log_error (_("assuan_inquire failed: %s\n"), gpg_strerror (err)); goto leave; } if (!valuelen) /* No data returned; return a comprehensible error. */ err = gpg_error (GPG_ERR_MISSING_CERT); else { err = ksba_cert_new (&cert); if (!err) err = ksba_cert_init_from_mem (cert, value, valuelen); } xfree (value); if(err) goto leave; err = cache_cert (cert); leave: ksba_cert_release (cert); return leave_cmd (ctx, err); } static const char hlp_validate[] = "VALIDATE\n" "\n" "Validate a certificate using the certificate validation function\n" "used internally by dirmngr. This command is only useful for\n" "debugging. To get the actual certificate, this command immediately\n" "inquires it using\n" "\n" " INQUIRE TARGETCERT\n" "\n" "and the caller is expected to return the certificate for the\n" "request as a binary blob."; static gpg_error_t cmd_validate (assuan_context_t ctx, char *line) { ctrl_t ctrl = assuan_get_pointer (ctx); gpg_error_t err; ksba_cert_t cert = NULL; unsigned char *value = NULL; size_t valuelen; (void)line; err = assuan_inquire (ctrl->server_local->assuan_ctx, "TARGETCERT", &value, &valuelen, MAX_CERT_LENGTH); if (err) { log_error (_("assuan_inquire failed: %s\n"), gpg_strerror (err)); goto leave; } if (!valuelen) /* No data returned; return a comprehensible error. */ err = gpg_error (GPG_ERR_MISSING_CERT); else { err = ksba_cert_new (&cert); if (!err) err = ksba_cert_init_from_mem (cert, value, valuelen); } xfree (value); if(err) goto leave; /* If we have this certificate already in our cache, use the cached version for validation because this will take care of any cached results. */ { unsigned char fpr[20]; ksba_cert_t tmpcert; cert_compute_fpr (cert, fpr); tmpcert = get_cert_byfpr (fpr); if (tmpcert) { ksba_cert_release (cert); cert = tmpcert; } } err = validate_cert_chain (ctrl, cert, NULL, VALIDATE_MODE_CERT, NULL); leave: ksba_cert_release (cert); return leave_cmd (ctx, err); } /* Parse an keyserver URI and store it in a new uri item which is returned at R_ITEM. On error return an error code. */ static gpg_error_t make_keyserver_item (const char *uri, uri_item_t *r_item) { gpg_error_t err; uri_item_t item; *r_item = NULL; item = xtrymalloc (sizeof *item + strlen (uri)); if (!item) return gpg_error_from_syserror (); item->next = NULL; item->parsed_uri = NULL; strcpy (item->uri, uri); #if USE_LDAP if (ldap_uri_p (item->uri)) err = ldap_parse_uri (&item->parsed_uri, uri); else #endif { err = http_parse_uri (&item->parsed_uri, uri, 1); } if (err) xfree (item); else *r_item = item; return err; } /* If no keyserver is stored in CTRL but a global keyserver has been set, put that global keyserver into CTRL. We need use this function to help migrate from the old gpg based keyserver configuration to the new dirmngr based configuration. */ static gpg_error_t ensure_keyserver (ctrl_t ctrl) { gpg_error_t err; uri_item_t item; uri_item_t onion_items = NULL; uri_item_t plain_items = NULL; uri_item_t ui; strlist_t sl; if (ctrl->server_local->keyservers) return 0; /* Already set for this session. */ if (!opt.keyserver) return 0; /* No global option set. */ for (sl = opt.keyserver; sl; sl = sl->next) { err = make_keyserver_item (sl->d, &item); if (err) goto leave; if (item->parsed_uri->onion) { item->next = onion_items; onion_items = item; } else { item->next = plain_items; plain_items = item; } } /* Decide which to use. Note that the sesssion has no keyservers yet set. */ if (onion_items && !onion_items->next && plain_items && !plain_items->next) { /* If there is just one onion and one plain keyserver given, we take only one depending on whether Tor is running or not. */ if (is_tor_running (ctrl)) { ctrl->server_local->keyservers = onion_items; onion_items = NULL; } else { ctrl->server_local->keyservers = plain_items; plain_items = NULL; } } else if (!is_tor_running (ctrl)) { /* Tor is not running. It does not make sense to add Onion addresses. */ ctrl->server_local->keyservers = plain_items; plain_items = NULL; } else { /* In all other cases add all keyservers. */ ctrl->server_local->keyservers = onion_items; onion_items = NULL; for (ui = ctrl->server_local->keyservers; ui && ui->next; ui = ui->next) ; if (ui) ui->next = plain_items; else ctrl->server_local->keyservers = plain_items; plain_items = NULL; } leave: release_uri_item_list (onion_items); release_uri_item_list (plain_items); return err; } static const char hlp_keyserver[] = "KEYSERVER [] [|]\n" "Options are:\n" " --help\n" " --clear Remove all configured keyservers\n" " --resolve Resolve HKP host names and rotate\n" " --hosttable Print table of known hosts and pools\n" " --dead Mark as dead\n" " --alive Mark as alive\n" "\n" "If called without arguments list all configured keyserver URLs.\n" "If called with an URI add this as keyserver. Note that keyservers\n" "are configured on a per-session base. A default keyserver may already be\n" "present, thus the \"--clear\" option must be used to get full control.\n" "If \"--clear\" and an URI are used together the clear command is\n" "obviously executed first. A RESET command does not change the list\n" "of configured keyservers."; static gpg_error_t cmd_keyserver (assuan_context_t ctx, char *line) { ctrl_t ctrl = assuan_get_pointer (ctx); gpg_error_t err = 0; int clear_flag, add_flag, help_flag, host_flag, resolve_flag; int dead_flag, alive_flag; uri_item_t item = NULL; /* gcc 4.4.5 is not able to detect that it is always initialized. */ clear_flag = has_option (line, "--clear"); help_flag = has_option (line, "--help"); resolve_flag = has_option (line, "--resolve"); host_flag = has_option (line, "--hosttable"); dead_flag = has_option (line, "--dead"); alive_flag = has_option (line, "--alive"); line = skip_options (line); add_flag = !!*line; if (help_flag) { err = ks_action_help (ctrl, line); goto leave; } if (resolve_flag) { err = ensure_keyserver (ctrl); if (!err) err = ks_action_resolve (ctrl, ctrl->server_local->keyservers); if (err) goto leave; } if (alive_flag && dead_flag) { err = set_error (GPG_ERR_ASS_PARAMETER, "no support for zombies"); goto leave; } if (dead_flag) { err = check_owner_permission (ctx, "no permission to use --dead"); if (err) goto leave; } if (alive_flag || dead_flag) { if (!*line) { err = set_error (GPG_ERR_ASS_PARAMETER, "name of host missing"); goto leave; } err = ks_hkp_mark_host (ctrl, line, alive_flag); if (err) goto leave; } if (host_flag) { err = ks_hkp_print_hosttable (ctrl); if (err) goto leave; } if (resolve_flag || host_flag || alive_flag || dead_flag) goto leave; if (add_flag) { err = make_keyserver_item (line, &item); if (err) goto leave; } if (clear_flag) release_ctrl_keyservers (ctrl); if (add_flag) { item->next = ctrl->server_local->keyservers; ctrl->server_local->keyservers = item; } if (!add_flag && !clear_flag && !help_flag) { /* List configured keyservers. However, we first add a global keyserver. */ uri_item_t u; err = ensure_keyserver (ctrl); if (err) { assuan_set_error (ctx, err, "Bad keyserver configuration in dirmngr.conf"); goto leave; } for (u=ctrl->server_local->keyservers; u; u = u->next) dirmngr_status (ctrl, "KEYSERVER", u->uri, NULL); } err = 0; leave: return leave_cmd (ctx, err); } static const char hlp_ks_search[] = "KS_SEARCH {}\n" "\n" "Search the configured OpenPGP keyservers (see command KEYSERVER)\n" "for keys matching PATTERN"; static gpg_error_t cmd_ks_search (assuan_context_t ctx, char *line) { ctrl_t ctrl = assuan_get_pointer (ctx); gpg_error_t err; strlist_t list, sl; char *p; estream_t outfp; /* No options for now. */ line = skip_options (line); /* Break the line down into an strlist. Each pattern is percent-plus escaped. */ list = NULL; for (p=line; *p; line = p) { while (*p && *p != ' ') p++; if (*p) *p++ = 0; if (*line) { sl = xtrymalloc (sizeof *sl + strlen (line)); if (!sl) { err = gpg_error_from_syserror (); goto leave; } sl->flags = 0; strcpy_escaped_plus (sl->d, line); sl->next = list; list = sl; } } err = ensure_keyserver (ctrl); if (err) goto leave; /* Setup an output stream and perform the search. */ outfp = es_fopencookie (ctx, "w", data_line_cookie_functions); if (!outfp) err = set_error (GPG_ERR_ASS_GENERAL, "error setting up a data stream"); else { err = ks_action_search (ctrl, ctrl->server_local->keyservers, list, outfp); es_fclose (outfp); } leave: free_strlist (list); return leave_cmd (ctx, err); } static const char hlp_ks_get[] = "KS_GET {}\n" "\n" "Get the keys matching PATTERN from the configured OpenPGP keyservers\n" "(see command KEYSERVER). Each pattern should be a keyid, a fingerprint,\n" "or an exact name indicated by the '=' prefix."; static gpg_error_t cmd_ks_get (assuan_context_t ctx, char *line) { ctrl_t ctrl = assuan_get_pointer (ctx); gpg_error_t err; strlist_t list, sl; char *p; estream_t outfp; /* No options for now. */ line = skip_options (line); /* Break the line into a strlist. Each pattern is by definition percent-plus escaped. However we only support keyids and fingerprints and thus the client has no need to apply the escaping. */ list = NULL; for (p=line; *p; line = p) { while (*p && *p != ' ') p++; if (*p) *p++ = 0; if (*line) { sl = xtrymalloc (sizeof *sl + strlen (line)); if (!sl) { err = gpg_error_from_syserror (); goto leave; } sl->flags = 0; strcpy_escaped_plus (sl->d, line); sl->next = list; list = sl; } } err = ensure_keyserver (ctrl); if (err) goto leave; /* Setup an output stream and perform the get. */ outfp = es_fopencookie (ctx, "w", data_line_cookie_functions); if (!outfp) err = set_error (GPG_ERR_ASS_GENERAL, "error setting up a data stream"); else { err = ks_action_get (ctrl, ctrl->server_local->keyservers, list, outfp); es_fclose (outfp); } leave: free_strlist (list); return leave_cmd (ctx, err); } static const char hlp_ks_fetch[] = "KS_FETCH \n" "\n" "Get the key(s) from URL."; static gpg_error_t cmd_ks_fetch (assuan_context_t ctx, char *line) { ctrl_t ctrl = assuan_get_pointer (ctx); gpg_error_t err; estream_t outfp; /* No options for now. */ line = skip_options (line); err = ensure_keyserver (ctrl); /* FIXME: Why do we needs this here? */ if (err) goto leave; /* Setup an output stream and perform the get. */ outfp = es_fopencookie (ctx, "w", data_line_cookie_functions); if (!outfp) err = set_error (GPG_ERR_ASS_GENERAL, "error setting up a data stream"); else { err = ks_action_fetch (ctrl, line, outfp); es_fclose (outfp); } leave: return leave_cmd (ctx, err); } static const char hlp_ks_put[] = "KS_PUT\n" "\n" "Send a key to the configured OpenPGP keyservers. The actual key material\n" "is then requested by Dirmngr using\n" "\n" " INQUIRE KEYBLOCK\n" "\n" "The client shall respond with a binary version of the keyblock (e.g.,\n" "the output of `gpg --export KEYID'). For LDAP\n" "keyservers Dirmngr may ask for meta information of the provided keyblock\n" "using:\n" "\n" " INQUIRE KEYBLOCK_INFO\n" "\n" "The client shall respond with a colon delimited info lines (the output\n" "of 'for x in keys sigs; do gpg --list-$x --with-colons KEYID; done').\n"; static gpg_error_t cmd_ks_put (assuan_context_t ctx, char *line) { ctrl_t ctrl = assuan_get_pointer (ctx); gpg_error_t err; unsigned char *value = NULL; size_t valuelen; unsigned char *info = NULL; size_t infolen; /* No options for now. */ line = skip_options (line); err = ensure_keyserver (ctrl); if (err) goto leave; /* Ask for the key material. */ err = assuan_inquire (ctx, "KEYBLOCK", &value, &valuelen, MAX_KEYBLOCK_LENGTH); if (err) { log_error (_("assuan_inquire failed: %s\n"), gpg_strerror (err)); goto leave; } if (!valuelen) /* No data returned; return a comprehensible error. */ { err = gpg_error (GPG_ERR_MISSING_CERT); goto leave; } /* Ask for the key meta data. Not actually needed for HKP servers but we do it anyway to test the client implementaion. */ err = assuan_inquire (ctx, "KEYBLOCK_INFO", &info, &infolen, MAX_KEYBLOCK_LENGTH); if (err) { log_error (_("assuan_inquire failed: %s\n"), gpg_strerror (err)); goto leave; } /* Send the key. */ err = ks_action_put (ctrl, ctrl->server_local->keyservers, value, valuelen, info, infolen); leave: xfree (info); xfree (value); return leave_cmd (ctx, err); } static const char hlp_getinfo[] = "GETINFO \n" "\n" "Multi purpose command to return certain information. \n" "Supported values of WHAT are:\n" "\n" "version - Return the version of the program.\n" "pid - Return the process id of the server.\n" "tor - Return OK if running in Tor mode\n" "dnsinfo - Return info about the DNS resolver\n" "socket_name - Return the name of the socket.\n"; static gpg_error_t cmd_getinfo (assuan_context_t ctx, char *line) { ctrl_t ctrl = assuan_get_pointer (ctx); gpg_error_t err; if (!strcmp (line, "version")) { const char *s = VERSION; err = assuan_send_data (ctx, s, strlen (s)); } else if (!strcmp (line, "pid")) { char numbuf[50]; snprintf (numbuf, sizeof numbuf, "%lu", (unsigned long)getpid ()); err = assuan_send_data (ctx, numbuf, strlen (numbuf)); } else if (!strcmp (line, "socket_name")) { - const char *s = dirmngr_user_socket_name (); - - if (!s) - s = dirmngr_sys_socket_name (); - - if (s) - err = assuan_send_data (ctx, s, strlen (s)); - else - err = gpg_error (GPG_ERR_NO_DATA); + const char *s = dirmngr_socket_name (); + err = assuan_send_data (ctx, s, strlen (s)); } else if (!strcmp (line, "tor")) { if (opt.use_tor) { if (!is_tor_running (ctrl)) err = assuan_write_status (ctx, "NO_TOR", "Tor not running"); else err = 0; if (!err) assuan_set_okay_line (ctx, "- Tor mode is enabled"); } else err = set_error (GPG_ERR_FALSE, "Tor mode is NOT enabled"); } else if (!strcmp (line, "dnsinfo")) { #if USE_ADNS && HAVE_ADNS_IF_TORMODE assuan_set_okay_line (ctx, "- ADNS with Tor support"); #elif USE_ADNS assuan_set_okay_line (ctx, "- ADNS w/o Tor support"); #else assuan_set_okay_line (ctx, "- System resolver w/o Tor support"); #endif err = 0; } else err = set_error (GPG_ERR_ASS_PARAMETER, "unknown value for WHAT"); return leave_cmd (ctx, err); } static const char hlp_killdirmngr[] = "KILLDIRMNGR\n" "\n" "This command allows a user - given sufficient permissions -\n" "to kill this dirmngr process.\n"; static gpg_error_t cmd_killdirmngr (assuan_context_t ctx, char *line) { ctrl_t ctrl = assuan_get_pointer (ctx); - gpg_error_t err; (void)line; - if (opt.system_daemon) - { - if (opt.system_service) - err = set_error (GPG_ERR_NOT_SUPPORTED, - "can't do that whilst running as system service"); - else - err = check_owner_permission (ctx, - "no permission to kill this process"); - } - else - err = 0; - - if (!err) - { - ctrl->server_local->stopme = 1; - assuan_set_flag (ctx, ASSUAN_FORCE_CLOSE, 1); - err = gpg_error (GPG_ERR_EOF); - } - return err; + ctrl->server_local->stopme = 1; + assuan_set_flag (ctx, ASSUAN_FORCE_CLOSE, 1); + return gpg_error (GPG_ERR_EOF); } static const char hlp_reloaddirmngr[] = "RELOADDIRMNGR\n" "\n" "This command is an alternative to SIGHUP\n" "to reload the configuration."; static gpg_error_t cmd_reloaddirmngr (assuan_context_t ctx, char *line) { (void)ctx; (void)line; - if (opt.system_daemon) - { -#ifndef HAVE_W32_SYSTEM - { - gpg_error_t err; - - err = check_owner_permission (ctx, - "no permission to reload this process"); - if (err) - return err; - } -#endif - } - dirmngr_sighup_action (); return 0; } /* Tell the assuan library about our commands. */ static int register_commands (assuan_context_t ctx) { static struct { const char *name; assuan_handler_t handler; const char * const help; } table[] = { { "DNS_CERT", cmd_dns_cert, hlp_dns_cert }, { "WKD_GET", cmd_wkd_get, hlp_wkd_get }, { "LDAPSERVER", cmd_ldapserver, hlp_ldapserver }, { "ISVALID", cmd_isvalid, hlp_isvalid }, { "CHECKCRL", cmd_checkcrl, hlp_checkcrl }, { "CHECKOCSP", cmd_checkocsp, hlp_checkocsp }, { "LOOKUP", cmd_lookup, hlp_lookup }, { "LOADCRL", cmd_loadcrl, hlp_loadcrl }, { "LISTCRLS", cmd_listcrls, hlp_listcrls }, { "CACHECERT", cmd_cachecert, hlp_cachecert }, { "VALIDATE", cmd_validate, hlp_validate }, { "KEYSERVER", cmd_keyserver, hlp_keyserver }, { "KS_SEARCH", cmd_ks_search, hlp_ks_search }, { "KS_GET", cmd_ks_get, hlp_ks_get }, { "KS_FETCH", cmd_ks_fetch, hlp_ks_fetch }, { "KS_PUT", cmd_ks_put, hlp_ks_put }, { "GETINFO", cmd_getinfo, hlp_getinfo }, { "KILLDIRMNGR",cmd_killdirmngr,hlp_killdirmngr }, { "RELOADDIRMNGR",cmd_reloaddirmngr,hlp_reloaddirmngr }, { NULL, NULL } }; int i, j, rc; for (i=j=0; table[i].name; i++) { rc = assuan_register_command (ctx, table[i].name, table[i].handler, table[i].help); if (rc) return rc; } return 0; } /* Note that we do not reset the list of configured keyservers. */ static gpg_error_t reset_notify (assuan_context_t ctx, char *line) { ctrl_t ctrl = assuan_get_pointer (ctx); (void)line; #if USE_LDAP ldapserver_list_free (ctrl->server_local->ldapservers); #endif /*USE_LDAP*/ ctrl->server_local->ldapservers = NULL; return 0; } /* Startup the server and run the main command loop. With FD = -1, use stdin/stdout. */ void start_command_handler (assuan_fd_t fd) { static const char hello[] = "Dirmngr " VERSION " at your service"; static char *hello_line; int rc; assuan_context_t ctx; ctrl_t ctrl; ctrl = xtrycalloc (1, sizeof *ctrl); if (ctrl) ctrl->server_local = xtrycalloc (1, sizeof *ctrl->server_local); if (!ctrl || !ctrl->server_local) { log_error (_("can't allocate control structure: %s\n"), strerror (errno)); xfree (ctrl); return; } dirmngr_init_default_ctrl (ctrl); rc = assuan_new (&ctx); if (rc) { log_error (_("failed to allocate assuan context: %s\n"), gpg_strerror (rc)); dirmngr_exit (2); } if (fd == ASSUAN_INVALID_FD) { assuan_fd_t filedes[2]; filedes[0] = assuan_fdopen (0); filedes[1] = assuan_fdopen (1); rc = assuan_init_pipe_server (ctx, filedes); } else { rc = assuan_init_socket_server (ctx, fd, ASSUAN_SOCKET_SERVER_ACCEPTED); } if (rc) { assuan_release (ctx); log_error (_("failed to initialize the server: %s\n"), gpg_strerror(rc)); dirmngr_exit (2); } rc = register_commands (ctx); if (rc) { log_error (_("failed to the register commands with Assuan: %s\n"), gpg_strerror(rc)); dirmngr_exit (2); } if (!hello_line) { hello_line = xtryasprintf ("Home: %s\n" "Config: %s\n" "%s", gnupg_homedir (), opt.config_filename? opt.config_filename : "[none]", hello); } ctrl->server_local->assuan_ctx = ctx; assuan_set_pointer (ctx, ctrl); assuan_set_hello_line (ctx, hello_line); assuan_register_option_handler (ctx, option_handler); assuan_register_reset_notify (ctx, reset_notify); for (;;) { rc = assuan_accept (ctx); if (rc == -1) break; if (rc) { log_info (_("Assuan accept problem: %s\n"), gpg_strerror (rc)); break; } #ifndef HAVE_W32_SYSTEM if (opt.verbose) { assuan_peercred_t peercred; if (!assuan_get_peercred (ctx, &peercred)) log_info ("connection from process %ld (%ld:%ld)\n", (long)peercred->pid, (long)peercred->uid, (long)peercred->gid); } #endif rc = assuan_process (ctx); if (rc) { log_info (_("Assuan processing failed: %s\n"), gpg_strerror (rc)); continue; } } #if USE_LDAP ldap_wrapper_connection_cleanup (ctrl); ldapserver_list_free (ctrl->server_local->ldapservers); #endif /*USE_LDAP*/ ctrl->server_local->ldapservers = NULL; release_ctrl_keyservers (ctrl); ctrl->server_local->assuan_ctx = NULL; assuan_release (ctx); if (ctrl->server_local->stopme) dirmngr_exit (0); if (ctrl->refcount) log_error ("oops: connection control structure still referenced (%d)\n", ctrl->refcount); else { release_ctrl_ocsp_certs (ctrl); xfree (ctrl->server_local); dirmngr_deinit_default_ctrl (ctrl); xfree (ctrl); } } /* Send a status line back to the client. KEYWORD is the status keyword, the optional string arguments are blank separated added to the line, the last argument must be a NULL. */ gpg_error_t dirmngr_status (ctrl_t ctrl, const char *keyword, ...) { gpg_error_t err = 0; va_list arg_ptr; const char *text; va_start (arg_ptr, keyword); if (ctrl->server_local) { assuan_context_t ctx = ctrl->server_local->assuan_ctx; char buf[950], *p; size_t n; p = buf; n = 0; while ( (text = va_arg (arg_ptr, const char *)) ) { if (n) { *p++ = ' '; n++; } for ( ; *text && n < DIM (buf)-2; n++) *p++ = *text++; } *p = 0; err = assuan_write_status (ctx, keyword, buf); } va_end (arg_ptr); return err; } /* Print a help status line. TEXTLEN gives the length of the text from TEXT to be printed. The function splits text at LFs. */ gpg_error_t dirmngr_status_help (ctrl_t ctrl, const char *text) { gpg_error_t err = 0; if (ctrl->server_local) { assuan_context_t ctx = ctrl->server_local->assuan_ctx; char buf[950], *p; size_t n; do { p = buf; n = 0; for ( ; *text && *text != '\n' && n < DIM (buf)-2; n++) *p++ = *text++; if (*text == '\n') text++; *p = 0; err = assuan_write_status (ctx, "#", buf); } while (!err && *text); } return err; } /* Send a tick progress indicator back. Fixme: This is only done for the currently active channel. */ gpg_error_t dirmngr_tick (ctrl_t ctrl) { static time_t next_tick = 0; gpg_error_t err = 0; time_t now = time (NULL); if (!next_tick) { next_tick = now + 1; } else if ( now > next_tick ) { if (ctrl) { err = dirmngr_status (ctrl, "PROGRESS", "tick", "? 0 0", NULL); if (err) { /* Take this as in indication for a cancel request. */ err = gpg_error (GPG_ERR_CANCELED); } now = time (NULL); } next_tick = now + 1; } return err; } diff --git a/dirmngr/validate.c b/dirmngr/validate.c index 1a851b6ce..b3dc9d8c6 100644 --- a/dirmngr/validate.c +++ b/dirmngr/validate.c @@ -1,1159 +1,1155 @@ /* validate.c - Validate a certificate chain. * Copyright (C) 2001, 2003, 2004, 2008 Free Software Foundation, Inc. * Copyright (C) 2004, 2006, 2008 g10 Code GmbH * * This file is part of DirMngr. * * DirMngr is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * DirMngr 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 General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA */ #include #include #include #include #include #include #include "dirmngr.h" #include "certcache.h" #include "crlcache.h" #include "validate.h" #include "misc.h" /* While running the validation function we need to keep track of the certificates and the validation outcome of each. We use this type for it. */ struct chain_item_s { struct chain_item_s *next; ksba_cert_t cert; /* The certificate. */ unsigned char fpr[20]; /* Fingerprint of the certificate. */ int is_self_signed; /* This certificate is self-signed. */ int is_valid; /* The certifiate is valid except for revocations. */ }; typedef struct chain_item_s *chain_item_t; /* A couple of constants with Object Identifiers. */ static const char oid_kp_serverAuth[] = "1.3.6.1.5.5.7.3.1"; static const char oid_kp_clientAuth[] = "1.3.6.1.5.5.7.3.2"; static const char oid_kp_codeSigning[] = "1.3.6.1.5.5.7.3.3"; static const char oid_kp_emailProtection[]= "1.3.6.1.5.5.7.3.4"; static const char oid_kp_timeStamping[] = "1.3.6.1.5.5.7.3.8"; static const char oid_kp_ocspSigning[] = "1.3.6.1.5.5.7.3.9"; /* Prototypes. */ static gpg_error_t check_cert_sig (ksba_cert_t issuer_cert, ksba_cert_t cert); /* Check whether CERT contains critical extensions we don't know about. */ static gpg_error_t unknown_criticals (ksba_cert_t cert) { static const char *known[] = { "2.5.29.15", /* keyUsage */ "2.5.29.19", /* basic Constraints */ "2.5.29.32", /* certificatePolicies */ "2.5.29.37", /* extendedKeyUsage */ NULL }; int i, idx, crit; const char *oid; int unsupported; strlist_t sl; gpg_error_t err, rc; rc = 0; for (idx=0; !(err=ksba_cert_get_extension (cert, idx, &oid, &crit, NULL, NULL));idx++) { if (!crit) continue; for (i=0; known[i] && strcmp (known[i],oid); i++) ; unsupported = !known[i]; /* If this critical extension is not supported, check the list of to be ignored extensions to see whether we claim that it is supported. */ if (unsupported && opt.ignored_cert_extensions) { for (sl=opt.ignored_cert_extensions; sl && strcmp (sl->d, oid); sl = sl->next) ; if (sl) unsupported = 0; } if (unsupported) { log_error (_("critical certificate extension %s is not supported"), oid); rc = gpg_error (GPG_ERR_UNSUPPORTED_CERT); } } if (err && gpg_err_code (err) != GPG_ERR_EOF) rc = err; /* Such an error takes precendence. */ return rc; } /* Basic check for supported policies. */ static gpg_error_t check_cert_policy (ksba_cert_t cert) { static const char *allowed[] = { "2.289.9.9", NULL }; gpg_error_t err; int idx; char *p, *haystack; char *policies; int any_critical; err = ksba_cert_get_cert_policies (cert, &policies); if (gpg_err_code (err) == GPG_ERR_NO_DATA) return 0; /* No policy given. */ if (err) return err; /* STRING is a line delimited list of certifiate policies as stored in the certificate. The line itself is colon delimited where the first field is the OID of the policy and the second field either N or C for normal or critical extension */ if (opt.verbose > 1) log_info ("certificate's policy list: %s\n", policies); /* The check is very minimal but won't give false positives */ any_critical = !!strstr (policies, ":C"); /* See whether we find ALLOWED (which is an OID) in POLICIES */ for (idx=0; allowed[idx]; idx++) { for (haystack=policies; (p=strstr (haystack, allowed[idx])); haystack = p+1) { if ( !(p == policies || p[-1] == '\n') ) continue; /* Does not match the begin of a line. */ if (p[strlen (allowed[idx])] != ':') continue; /* The length does not match. */ /* Yep - it does match: Return okay. */ ksba_free (policies); return 0; } } if (!any_critical) { log_info (_("Note: non-critical certificate policy not allowed")); err = 0; } else { log_info (_("certificate policy not allowed")); err = gpg_error (GPG_ERR_NO_POLICY_MATCH); } ksba_free (policies); return err; } static gpg_error_t allowed_ca (ksba_cert_t cert, int *chainlen) { gpg_error_t err; int flag; err = ksba_cert_is_ca (cert, &flag, chainlen); if (err) return err; if (!flag) { if (!is_trusted_cert (cert)) { /* The German SigG Root CA's certificate does not flag itself as a CA; thus we relax this requirement if we trust a root CA. I think this is reasonable. Note, that gpgsm implements a far stricter scheme here. */ if (chainlen) *chainlen = 3; /* That is what the SigG implements. */ if (opt.verbose) log_info (_("accepting root CA not marked as a CA")); } else { log_error (_("issuer certificate is not marked as a CA")); return gpg_error (GPG_ERR_BAD_CA_CERT); } } return 0; } /* Helper for validate_cert_chain. */ static gpg_error_t check_revocations (ctrl_t ctrl, chain_item_t chain) { gpg_error_t err = 0; int any_revoked = 0; int any_no_crl = 0; int any_crl_too_old = 0; chain_item_t ci; assert (ctrl->check_revocations_nest_level >= 0); assert (chain); if (ctrl->check_revocations_nest_level > 10) { log_error (_("CRL checking too deeply nested\n")); return gpg_error(GPG_ERR_BAD_CERT_CHAIN); } ctrl->check_revocations_nest_level++; for (ci=chain; ci; ci = ci->next) { assert (ci->cert); if (ci == chain) { /* It does not make sense to check the root certificate for revocations. In almost all cases this will lead to a catch-22 as the root certificate is the final trust anchor for the certificates and the CRLs. We expect the user to remove root certificates from the list of trusted certificates in case they have been revoked. */ if (opt.verbose) cert_log_name (_("not checking CRL for"), ci->cert); continue; } if (opt.verbose) cert_log_name (_("checking CRL for"), ci->cert); err = crl_cache_cert_isvalid (ctrl, ci->cert, 0); if (gpg_err_code (err) == GPG_ERR_NO_CRL_KNOWN) { err = crl_cache_reload_crl (ctrl, ci->cert); if (!err) err = crl_cache_cert_isvalid (ctrl, ci->cert, 0); } switch (gpg_err_code (err)) { case 0: err = 0; break; case GPG_ERR_CERT_REVOKED: any_revoked = 1; err = 0; break; case GPG_ERR_NO_CRL_KNOWN: any_no_crl = 1; err = 0; break; case GPG_ERR_CRL_TOO_OLD: any_crl_too_old = 1; err = 0; break; default: break; } } ctrl->check_revocations_nest_level--; if (err) ; else if (any_revoked) err = gpg_error (GPG_ERR_CERT_REVOKED); else if (any_no_crl) err = gpg_error (GPG_ERR_NO_CRL_KNOWN); else if (any_crl_too_old) err = gpg_error (GPG_ERR_CRL_TOO_OLD); else err = 0; return err; } /* Check whether CERT is a root certificate. ISSUERDN and SUBJECTDN are the DNs already extracted by the caller from CERT. Returns True if this is the case. */ static int is_root_cert (ksba_cert_t cert, const char *issuerdn, const char *subjectdn) { gpg_error_t err; int result = 0; ksba_sexp_t serialno; ksba_sexp_t ak_keyid; ksba_name_t ak_name; ksba_sexp_t ak_sn; const char *ak_name_str; ksba_sexp_t subj_keyid = NULL; if (!issuerdn || !subjectdn) return 0; /* No. */ if (strcmp (issuerdn, subjectdn)) return 0; /* No. */ err = ksba_cert_get_auth_key_id (cert, &ak_keyid, &ak_name, &ak_sn); if (err) { if (gpg_err_code (err) == GPG_ERR_NO_DATA) return 1; /* Yes. Without a authorityKeyIdentifier this needs to be the Root certifcate (our trust anchor). */ log_error ("error getting authorityKeyIdentifier: %s\n", gpg_strerror (err)); return 0; /* Well, it is broken anyway. Return No. */ } serialno = ksba_cert_get_serial (cert); if (!serialno) { log_error ("error getting serialno: %s\n", gpg_strerror (err)); goto leave; } /* Check whether the auth name's matches the issuer name+sn. If that is the case this is a root certificate. */ ak_name_str = ksba_name_enum (ak_name, 0); if (ak_name_str && !strcmp (ak_name_str, issuerdn) && !cmp_simple_canon_sexp (ak_sn, serialno)) { result = 1; /* Right, CERT is self-signed. */ goto leave; } /* Similar for the ak_keyid. */ if (ak_keyid && !ksba_cert_get_subj_key_id (cert, NULL, &subj_keyid) && !cmp_simple_canon_sexp (ak_keyid, subj_keyid)) { result = 1; /* Right, CERT is self-signed. */ goto leave; } leave: ksba_free (subj_keyid); ksba_free (ak_keyid); ksba_name_release (ak_name); ksba_free (ak_sn); ksba_free (serialno); return result; } /* Validate the certificate CHAIN up to the trust anchor. Optionally return the closest expiration time in R_EXPTIME (this is useful for caching issues). MODE is one of the VALIDATE_MODE_* constants. + Note that VALIDATE_MODE_OCSP is not used due to the removal of the + system service in 2.1.15. Instead only the callback to gpgsm to + validate a certificate is used. + If R_TRUST_ANCHOR is not NULL and the validation would fail only because the root certificate is not trusted, the hexified fingerprint of that root certificate is stored at R_TRUST_ANCHOR and success is returned. The caller needs to free the value at R_TRUST_ANCHOR; in all other cases NULL is stored there. */ gpg_error_t validate_cert_chain (ctrl_t ctrl, ksba_cert_t cert, ksba_isotime_t r_exptime, int mode, char **r_trust_anchor) { gpg_error_t err = 0; int depth, maxdepth; char *issuer = NULL; char *subject = NULL; ksba_cert_t subject_cert = NULL, issuer_cert = NULL; ksba_isotime_t current_time; ksba_isotime_t exptime; int any_expired = 0; int any_no_policy_match = 0; chain_item_t chain; if (r_exptime) *r_exptime = 0; *exptime = 0; if (r_trust_anchor) *r_trust_anchor = NULL; - if (!opt.system_daemon) - { - /* For backward compatibility we only do this in daemon mode. */ - log_info (_("running in compatibility mode - " - "certificate chain not checked!\n")); - return 0; /* Okay. */ - } - if (DBG_X509) dump_cert ("subject", cert); /* May the target certificate be used for this purpose? */ switch (mode) { case VALIDATE_MODE_OCSP: err = cert_use_ocsp_p (cert); break; case VALIDATE_MODE_CRL: case VALIDATE_MODE_CRL_RECURSIVE: err = cert_use_crl_p (cert); break; default: err = 0; break; } if (err) return err; /* If we already validated the certificate not too long ago, we can avoid the excessive computations and lookups unless the caller asked for the expiration time. */ if (!r_exptime) { size_t buflen; time_t validated_at; err = ksba_cert_get_user_data (cert, "validated_at", &validated_at, sizeof (validated_at), &buflen); if (err || buflen != sizeof (validated_at) || !validated_at) err = 0; /* Not available or other error. */ else { /* If the validation is not older than 30 minutes we are ready. */ if (validated_at < gnupg_get_time () + (30*60)) { if (opt.verbose) log_info ("certificate is good (cached)\n"); /* Note, that we can't jump to leave here as this would falsely updated the validation timestamp. */ return 0; } } } /* Get the current time. */ gnupg_get_isotime (current_time); /* We walk up the chain until we find a trust anchor. */ subject_cert = cert; maxdepth = 10; chain = NULL; depth = 0; for (;;) { /* Get the subject and issuer name from the current certificate. */ ksba_free (issuer); ksba_free (subject); issuer = ksba_cert_get_issuer (subject_cert, 0); subject = ksba_cert_get_subject (subject_cert, 0); if (!issuer) { log_error (_("no issuer found in certificate\n")); err = gpg_error (GPG_ERR_BAD_CERT); goto leave; } /* Handle the notBefore and notAfter timestamps. */ { ksba_isotime_t not_before, not_after; err = ksba_cert_get_validity (subject_cert, 0, not_before); if (!err) err = ksba_cert_get_validity (subject_cert, 1, not_after); if (err) { log_error (_("certificate with invalid validity: %s"), gpg_strerror (err)); err = gpg_error (GPG_ERR_BAD_CERT); goto leave; } /* Keep track of the nearest expiration time in EXPTIME. */ if (*not_after) { if (!*exptime) gnupg_copy_time (exptime, not_after); else if (strcmp (not_after, exptime) < 0 ) gnupg_copy_time (exptime, not_after); } /* Check whether the certificate is already valid. */ if (*not_before && strcmp (current_time, not_before) < 0 ) { log_error (_("certificate not yet valid")); log_info ("(valid from "); dump_isotime (not_before); log_printf (")\n"); err = gpg_error (GPG_ERR_CERT_TOO_YOUNG); goto leave; } /* Now check whether the certificate has expired. */ if (*not_after && strcmp (current_time, not_after) > 0 ) { log_error (_("certificate has expired")); log_info ("(expired at "); dump_isotime (not_after); log_printf (")\n"); any_expired = 1; } } /* Do we have any critical extensions in the certificate we can't handle? */ err = unknown_criticals (subject_cert); if (err) goto leave; /* yes. */ /* Check that given policies are allowed. */ err = check_cert_policy (subject_cert); if (gpg_err_code (err) == GPG_ERR_NO_POLICY_MATCH) { any_no_policy_match = 1; err = 0; } else if (err) goto leave; /* Is this a self-signed certificate? */ if (is_root_cert ( subject_cert, issuer, subject)) { /* Yes, this is our trust anchor. */ if (check_cert_sig (subject_cert, subject_cert) ) { log_error (_("selfsigned certificate has a BAD signature")); err = gpg_error (depth? GPG_ERR_BAD_CERT_CHAIN : GPG_ERR_BAD_CERT); goto leave; } /* Is this certificate allowed to act as a CA. */ err = allowed_ca (subject_cert, NULL); if (err) goto leave; /* No. */ err = is_trusted_cert (subject_cert); if (!err) ; /* Yes we trust this cert. */ else if (gpg_err_code (err) == GPG_ERR_NOT_TRUSTED) { char *fpr; log_error (_("root certificate is not marked trusted")); fpr = get_fingerprint_hexstring (subject_cert); log_info (_("fingerprint=%s\n"), fpr? fpr : "?"); dump_cert ("issuer", subject_cert); if (r_trust_anchor) { /* Caller wants to do another trustiness check. */ *r_trust_anchor = fpr; err = 0; } else xfree (fpr); } else { log_error (_("checking trustworthiness of " "root certificate failed: %s\n"), gpg_strerror (err)); } if (err) goto leave; /* Prepend the certificate to our list. */ { chain_item_t ci; ci = xtrycalloc (1, sizeof *ci); if (!ci) { err = gpg_error_from_errno (errno); goto leave; } ksba_cert_ref (subject_cert); ci->cert = subject_cert; cert_compute_fpr (subject_cert, ci->fpr); ci->next = chain; chain = ci; } if (opt.verbose) { if (r_trust_anchor && *r_trust_anchor) log_info ("root certificate is good but not trusted\n"); else log_info ("root certificate is good and trusted\n"); } break; /* Okay: a self-signed certicate is an end-point. */ } /* To avoid loops, we use an arbitrary limit on the length of the chain. */ depth++; if (depth > maxdepth) { log_error (_("certificate chain too long\n")); err = gpg_error (GPG_ERR_BAD_CERT_CHAIN); goto leave; } /* Find the next cert up the tree. */ ksba_cert_release (issuer_cert); issuer_cert = NULL; err = find_issuing_cert (ctrl, subject_cert, &issuer_cert); if (err) { if (gpg_err_code (err) == GPG_ERR_NOT_FOUND) { log_error (_("issuer certificate not found")); log_info ("issuer certificate: #/"); dump_string (issuer); log_printf ("\n"); } else log_error (_("issuer certificate not found: %s\n"), gpg_strerror (err)); /* Use a better understandable error code. */ err = gpg_error (GPG_ERR_MISSING_ISSUER_CERT); goto leave; } /* try_another_cert: */ if (DBG_X509) { log_debug ("got issuer's certificate:\n"); dump_cert ("issuer", issuer_cert); } /* Now check the signature of the certificate. Well, we should delay this until later so that faked certificates can't be turned into a DoS easily. */ err = check_cert_sig (issuer_cert, subject_cert); if (err) { log_error (_("certificate has a BAD signature")); #if 0 if (gpg_err_code (err) == GPG_ERR_BAD_SIGNATURE) { /* We now try to find other issuer certificates which might have been used. This is required because some CAs are reusing the issuer and subject DN for new root certificates without using a authorityKeyIdentifier. */ rc = find_up (kh, subject_cert, issuer, 1); if (!rc) { ksba_cert_t tmp_cert; rc = keydb_get_cert (kh, &tmp_cert); if (rc || !compare_certs (issuer_cert, tmp_cert)) { /* The find next did not work or returned an identical certificate. We better stop here to avoid infinite checks. */ rc = gpg_error (GPG_ERR_BAD_SIGNATURE); ksba_cert_release (tmp_cert); } else { do_list (0, lm, fp, _("found another possible matching " "CA certificate - trying again")); ksba_cert_release (issuer_cert); issuer_cert = tmp_cert; goto try_another_cert; } } } #endif /* We give a more descriptive error code than the one returned from the signature checking. */ err = gpg_error (GPG_ERR_BAD_CERT_CHAIN); goto leave; } /* Check that the length of the chain is not longer than allowed by the CA. */ { int chainlen; err = allowed_ca (issuer_cert, &chainlen); if (err) goto leave; if (chainlen >= 0 && (depth - 1) > chainlen) { log_error (_("certificate chain longer than allowed by CA (%d)"), chainlen); err = gpg_error (GPG_ERR_BAD_CERT_CHAIN); goto leave; } } /* May that certificate be used for certification? */ err = cert_use_cert_p (issuer_cert); if (err) goto leave; /* No. */ /* Prepend the certificate to our list. */ { chain_item_t ci; ci = xtrycalloc (1, sizeof *ci); if (!ci) { err = gpg_error_from_errno (errno); goto leave; } ksba_cert_ref (subject_cert); ci->cert = subject_cert; cert_compute_fpr (subject_cert, ci->fpr); ci->next = chain; chain = ci; } if (opt.verbose) log_info (_("certificate is good\n")); /* Now to the next level up. */ subject_cert = issuer_cert; issuer_cert = NULL; } if (!err) { /* If we encountered an error somewhere during the checks, set the error code to the most critical one */ if (any_expired) err = gpg_error (GPG_ERR_CERT_EXPIRED); else if (any_no_policy_match) err = gpg_error (GPG_ERR_NO_POLICY_MATCH); } if (!err && opt.verbose) { chain_item_t citem; log_info (_("certificate chain is good\n")); for (citem = chain; citem; citem = citem->next) cert_log_name (" certificate", citem->cert); } if (!err && mode != VALIDATE_MODE_CRL) { /* Now that everything is fine, walk the chain and check each certificate for revocations. 1. item in the chain - The root certificate. 2. item - the CA below the root last item - the target certificate. Now for each certificate in the chain check whether it has been included in a CRL and thus be revoked. We don't do OCSP here because this does not seem to make much sense. This might become a recursive process and we should better cache our validity results to avoid double work. Far worse a catch-22 may happen for an improper setup hierarchy and we need a way to break up such a deadlock. */ err = check_revocations (ctrl, chain); } if (!err && opt.verbose) { if (r_trust_anchor && *r_trust_anchor) log_info ("target certificate may be valid\n"); else log_info ("target certificate is valid\n"); } else if (err && opt.verbose) log_info ("target certificate is NOT valid\n"); leave: if (!err && !(r_trust_anchor && *r_trust_anchor)) { /* With no error we can update the validation cache. We do this for all certificates in the chain. Note that we can't use the cache if the caller requested to check the trustiness of the root certificate himself. Adding such a feature would require us to also store the fingerprint of root certificate. */ chain_item_t citem; time_t validated_at = gnupg_get_time (); for (citem = chain; citem; citem = citem->next) { err = ksba_cert_set_user_data (citem->cert, "validated_at", &validated_at, sizeof (validated_at)); if (err) { log_error ("set_user_data(validated_at) failed: %s\n", gpg_strerror (err)); err = 0; } } } if (r_exptime) gnupg_copy_time (r_exptime, exptime); ksba_free (issuer); ksba_free (subject); ksba_cert_release (issuer_cert); if (subject_cert != cert) ksba_cert_release (subject_cert); while (chain) { chain_item_t ci_next = chain->next; if (chain->cert) ksba_cert_release (chain->cert); xfree (chain); chain = ci_next; } if (err && r_trust_anchor && *r_trust_anchor) { xfree (*r_trust_anchor); *r_trust_anchor = NULL; } return err; } /* Return the public key algorithm id from the S-expression PKEY. FIXME: libgcrypt should provide such a function. Note that this implementation uses the names as used by libksba. */ static int pk_algo_from_sexp (gcry_sexp_t pkey) { gcry_sexp_t l1, l2; const char *name; size_t n; int algo; l1 = gcry_sexp_find_token (pkey, "public-key", 0); if (!l1) return 0; /* Not found. */ l2 = gcry_sexp_cadr (l1); gcry_sexp_release (l1); name = gcry_sexp_nth_data (l2, 0, &n); if (!name) algo = 0; /* Not found. */ else if (n==3 && !memcmp (name, "rsa", 3)) algo = GCRY_PK_RSA; else if (n==3 && !memcmp (name, "dsa", 3)) algo = GCRY_PK_DSA; else if (n==13 && !memcmp (name, "ambiguous-rsa", 13)) algo = GCRY_PK_RSA; else algo = 0; gcry_sexp_release (l2); return algo; } /* Check the signature on CERT using the ISSUER_CERT. This function does only test the cryptographic signature and nothing else. It is assumed that the ISSUER_CERT is valid. */ static gpg_error_t check_cert_sig (ksba_cert_t issuer_cert, ksba_cert_t cert) { gpg_error_t err; const char *algoid; gcry_md_hd_t md; int i, algo; ksba_sexp_t p; size_t n; gcry_sexp_t s_sig, s_hash, s_pkey; const char *s; char algo_name[16+1]; /* hash algorithm name converted to lower case. */ int digestlen; unsigned char *digest; /* Hash the target certificate using the algorithm from that certificate. */ algoid = ksba_cert_get_digest_algo (cert); algo = gcry_md_map_name (algoid); if (!algo) { log_error (_("unknown hash algorithm '%s'\n"), algoid? algoid:"?"); return gpg_error (GPG_ERR_GENERAL); } s = gcry_md_algo_name (algo); for (i=0; *s && i < sizeof algo_name - 1; s++, i++) algo_name[i] = tolower (*s); algo_name[i] = 0; err = gcry_md_open (&md, algo, 0); if (err) { log_error ("md_open failed: %s\n", gpg_strerror (err)); return err; } if (DBG_HASHING) gcry_md_debug (md, "hash.cert"); err = ksba_cert_hash (cert, 1, HASH_FNC, md); if (err) { log_error ("ksba_cert_hash failed: %s\n", gpg_strerror (err)); gcry_md_close (md); return err; } gcry_md_final (md); /* Get the signature value out of the target certificate. */ p = ksba_cert_get_sig_val (cert); n = gcry_sexp_canon_len (p, 0, NULL, NULL); if (!n) { log_error ("libksba did not return a proper S-Exp\n"); gcry_md_close (md); ksba_free (p); return gpg_error (GPG_ERR_BUG); } if (DBG_CRYPTO) { int j; log_debug ("signature value:"); for (j=0; j < n; j++) log_printf (" %02X", p[j]); log_printf ("\n"); } err = gcry_sexp_sscan ( &s_sig, NULL, p, n); ksba_free (p); if (err) { log_error ("gcry_sexp_scan failed: %s\n", gpg_strerror (err)); gcry_md_close (md); return err; } /* Get the public key from the issuer certificate. */ p = ksba_cert_get_public_key (issuer_cert); n = gcry_sexp_canon_len (p, 0, NULL, NULL); if (!n) { log_error ("libksba did not return a proper S-Exp\n"); gcry_md_close (md); ksba_free (p); gcry_sexp_release (s_sig); return gpg_error (GPG_ERR_BUG); } err = gcry_sexp_sscan ( &s_pkey, NULL, p, n); ksba_free (p); if (err) { log_error ("gcry_sexp_scan failed: %s\n", gpg_strerror (err)); gcry_md_close (md); gcry_sexp_release (s_sig); return err; } /* Prepare the values for signature verification. At this point we have these values: S_PKEY - S-expression with the issuer's public key. S_SIG - Signature value as given in the certrificate. MD - Finalized hash context with hash of the certificate. ALGO_NAME - Lowercase hash algorithm name */ digestlen = gcry_md_get_algo_dlen (algo); digest = gcry_md_read (md, algo); if (pk_algo_from_sexp (s_pkey) == GCRY_PK_DSA) { if (digestlen != 20) { log_error (_("DSA requires the use of a 160 bit hash algorithm\n")); gcry_md_close (md); gcry_sexp_release (s_sig); gcry_sexp_release (s_pkey); return gpg_error (GPG_ERR_INTERNAL); } if ( gcry_sexp_build (&s_hash, NULL, "(data(flags raw)(value %b))", (int)digestlen, digest) ) BUG (); } else /* Not DSA. */ { if ( gcry_sexp_build (&s_hash, NULL, "(data(flags pkcs1)(hash %s %b))", algo_name, (int)digestlen, digest) ) BUG (); } err = gcry_pk_verify (s_sig, s_hash, s_pkey); if (DBG_X509) log_debug ("gcry_pk_verify: %s\n", gpg_strerror (err)); gcry_md_close (md); gcry_sexp_release (s_sig); gcry_sexp_release (s_hash); gcry_sexp_release (s_pkey); return err; } /* Return 0 if the cert is usable for encryption. A MODE of 0 checks for signing, a MODE of 1 checks for encryption, a MODE of 2 checks for verification and a MODE of 3 for decryption (just for debugging). MODE 4 is for certificate signing, MODE 5 for OCSP response signing, MODE 6 is for CRL signing. */ static int cert_usage_p (ksba_cert_t cert, int mode) { gpg_error_t err; unsigned int use; char *extkeyusages; int have_ocsp_signing = 0; err = ksba_cert_get_ext_key_usages (cert, &extkeyusages); if (gpg_err_code (err) == GPG_ERR_NO_DATA) err = 0; /* No policy given. */ if (!err) { unsigned int extusemask = ~0; /* Allow all. */ if (extkeyusages) { char *p, *pend; int any_critical = 0; extusemask = 0; p = extkeyusages; while (p && (pend=strchr (p, ':'))) { *pend++ = 0; /* Only care about critical flagged usages. */ if ( *pend == 'C' ) { any_critical = 1; if ( !strcmp (p, oid_kp_serverAuth)) extusemask |= (KSBA_KEYUSAGE_DIGITAL_SIGNATURE | KSBA_KEYUSAGE_KEY_ENCIPHERMENT | KSBA_KEYUSAGE_KEY_AGREEMENT); else if ( !strcmp (p, oid_kp_clientAuth)) extusemask |= (KSBA_KEYUSAGE_DIGITAL_SIGNATURE | KSBA_KEYUSAGE_KEY_AGREEMENT); else if ( !strcmp (p, oid_kp_codeSigning)) extusemask |= (KSBA_KEYUSAGE_DIGITAL_SIGNATURE); else if ( !strcmp (p, oid_kp_emailProtection)) extusemask |= (KSBA_KEYUSAGE_DIGITAL_SIGNATURE | KSBA_KEYUSAGE_NON_REPUDIATION | KSBA_KEYUSAGE_KEY_ENCIPHERMENT | KSBA_KEYUSAGE_KEY_AGREEMENT); else if ( !strcmp (p, oid_kp_timeStamping)) extusemask |= (KSBA_KEYUSAGE_DIGITAL_SIGNATURE | KSBA_KEYUSAGE_NON_REPUDIATION); } /* This is a hack to cope with OCSP. Note that we do not yet fully comply with the requirements and that the entire CRL/OCSP checking thing should undergo a thorough review and probably redesign. */ if ( !strcmp (p, oid_kp_ocspSigning)) have_ocsp_signing = 1; if ((p = strchr (pend, '\n'))) p++; } ksba_free (extkeyusages); extkeyusages = NULL; if (!any_critical) extusemask = ~0; /* Reset to the don't care mask. */ } err = ksba_cert_get_key_usage (cert, &use); if (gpg_err_code (err) == GPG_ERR_NO_DATA) { err = 0; if (opt.verbose && mode < 2) log_info (_("no key usage specified - assuming all usages\n")); use = ~0; } /* Apply extKeyUsage. */ use &= extusemask; } if (err) { log_error (_("error getting key usage information: %s\n"), gpg_strerror (err)); ksba_free (extkeyusages); return err; } if (mode == 4) { if ((use & (KSBA_KEYUSAGE_KEY_CERT_SIGN))) return 0; log_info (_("certificate should not have " "been used for certification\n")); return gpg_error (GPG_ERR_WRONG_KEY_USAGE); } if (mode == 5) { if (use != ~0 && (have_ocsp_signing || (use & (KSBA_KEYUSAGE_KEY_CERT_SIGN |KSBA_KEYUSAGE_CRL_SIGN)))) return 0; log_info (_("certificate should not have " "been used for OCSP response signing\n")); return gpg_error (GPG_ERR_WRONG_KEY_USAGE); } if (mode == 6) { if ((use & (KSBA_KEYUSAGE_CRL_SIGN))) return 0; log_info (_("certificate should not have " "been used for CRL signing\n")); return gpg_error (GPG_ERR_WRONG_KEY_USAGE); } if ((use & ((mode&1)? (KSBA_KEYUSAGE_KEY_ENCIPHERMENT|KSBA_KEYUSAGE_DATA_ENCIPHERMENT): (KSBA_KEYUSAGE_DIGITAL_SIGNATURE|KSBA_KEYUSAGE_NON_REPUDIATION))) ) return 0; log_info (mode==3? _("certificate should not have been used " "for encryption\n"): mode==2? _("certificate should not have been used for signing\n"): mode==1? _("certificate is not usable for encryption\n"): _("certificate is not usable for signing\n")); return gpg_error (GPG_ERR_WRONG_KEY_USAGE); } /* Return 0 if the certificate CERT is usable for certification. */ gpg_error_t cert_use_cert_p (ksba_cert_t cert) { return cert_usage_p (cert, 4); } /* Return 0 if the certificate CERT is usable for signing OCSP responses. */ gpg_error_t cert_use_ocsp_p (ksba_cert_t cert) { return cert_usage_p (cert, 5); } /* Return 0 if the certificate CERT is usable for signing CRLs. */ gpg_error_t cert_use_crl_p (ksba_cert_t cert) { return cert_usage_p (cert, 6); } diff --git a/doc/dirmngr.texi b/doc/dirmngr.texi index 033b5d3ff..d52fb892a 100644 --- a/doc/dirmngr.texi +++ b/doc/dirmngr.texi @@ -1,1120 +1,1099 @@ @c Copyright (C) 2002 Klar"alvdalens Datakonsult AB @c Copyright (C) 2004, 2005, 2006, 2007 g10 Code GmbH @c This is part of the GnuPG manual. @c For copying conditions, see the file gnupg.texi. @include defs.inc @node Invoking DIRMNGR @chapter Invoking DIRMNGR @cindex DIRMNGR command options @cindex command options @cindex options, DIRMNGR command @manpage dirmngr.8 @ifset manverb .B dirmngr \- CRL and OCSP daemon @end ifset @mansect synopsis @ifset manverb .B dirmngr .RI [ options ] .I command .RI [ args ] @end ifset @mansect description Since version 2.1 of GnuPG, @command{dirmngr} takes care of accessing the OpenPGP keyservers. As with previous versions it is also used as a server for managing and downloading certificate revocation lists (CRLs) for X.509 certificates, downloading X.509 certificates, and providing access to OCSP providers. Dirmngr is invoked internally by @command{gpg}, @command{gpgsm}, or via the @command{gpg-connect-agent} tool. -For historical reasons it is also possible to start @command{dirmngr} -in a system daemon mode which uses a different directory layout. -However, this mode is deprecated and may eventually be removed. - - @manpause @noindent @xref{Option Index},for an index to @command{DIRMNGR}'s commands and options. @mancont @menu * Dirmngr Commands:: List of all commands. * Dirmngr Options:: List of all options. * Dirmngr Configuration:: Configuration files. * Dirmngr Signals:: Use of signals. * Dirmngr Examples:: Some usage examples. * Dirmngr Protocol:: The protocol dirmngr uses. @end menu @node Dirmngr Commands @section Commands @mansect commands Commands are not distinguished from options except for the fact that only one command is allowed. @table @gnupgtabopt @item --version @opindex version Print the program version and licensing information. Note that you cannot abbreviate this command. @item --help, -h @opindex help Print a usage message summarizing the most useful command-line options. Not that you cannot abbreviate this command. @item --dump-options @opindex dump-options Print a list of all available options and commands. Note that you cannot abbreviate this command. @item --server @opindex server Run in server mode and wait for commands on the @code{stdin}. The default mode is to create a socket and listen for commands there. This is only used for testing. @item --daemon @opindex daemon Run in background daemon mode and listen for commands on a socket. Note that this also changes the default home directory and enables the internal certificate validation code. This mode is deprecated. @item --list-crls @opindex list-crls List the contents of the CRL cache on @code{stdout}. This is probably only useful for debugging purposes. @item --load-crl @var{file} @opindex load-crl This command requires a filename as additional argument, and it will make Dirmngr try to import the CRL in @var{file} into it's cache. Note, that this is only possible if Dirmngr is able to retrieve the CA's certificate directly by its own means. In general it is better to use @code{gpgsm}'s @code{--call-dirmngr loadcrl filename} command so that @code{gpgsm} can help dirmngr. @item --fetch-crl @var{url} @opindex fetch-crl This command requires an URL as additional argument, and it will make dirmngr try to retrieve an import the CRL from that @var{url} into it's cache. This is mainly useful for debugging purposes. The @command{dirmngr-client} provides the same feature for a running dirmngr. @item --shutdown @opindex shutdown This commands shuts down an running instance of Dirmngr. This command has currently no effect. @item --flush @opindex flush This command removes all CRLs from Dirmngr's cache. Client requests will thus trigger reading of fresh CRLs. @end table @mansect options @node Dirmngr Options @section Option Summary @table @gnupgtabopt @item --options @var{file} @opindex options Reads configuration from @var{file} instead of from the default per-user configuration file. The default configuration file is named @file{dirmngr.conf} and expected in the home directory. @item --homedir @var{dir} @opindex options Set the name of the home directory to @var{dir}. This option is only effective when used on the command line. The default depends on the running mode: @table @asis @item With @code{--daemon} given on the commandline the directory named @file{@value{SYSCONFDIR}} is used for configuration files and @file{@value{LOCALCACHEDIR}} for cached CRLs. @item Without @code{--daemon} given on the commandline the directory named @file{.gnupg} directly below the home directory of the user unless the environment variable @code{GNUPGHOME} has been set in which case its value will be used. All kind of data is stored below this directory. @end table @item -v @item --verbose @opindex v @opindex verbose Outputs additional information while running. You can increase the verbosity by giving several verbose commands to @sc{dirmngr}, such as @option{-vv}. @item --log-file @var{file} @opindex log-file Append all logging output to @var{file}. This is very helpful in seeing what the agent actually does. @item --debug-level @var{level} @opindex debug-level Select the debug level for investigating problems. @var{level} may be a numeric value or by a keyword: @table @code @item none No debugging at all. A value of less than 1 may be used instead of the keyword. @item basic Some basic debug messages. A value between 1 and 2 may be used instead of the keyword. @item advanced More verbose debug messages. A value between 3 and 5 may be used instead of the keyword. @item expert Even more detailed messages. A value between 6 and 8 may be used instead of the keyword. @item guru All of the debug messages you can get. A value greater than 8 may be used instead of the keyword. The creation of hash tracing files is only enabled if the keyword is used. @end table How these messages are mapped to the actual debugging flags is not specified and may change with newer releases of this program. They are however carefully selected to best aid in debugging. @item --debug @var{flags} @opindex debug This option is only useful for debugging and the behaviour may change at any time without notice. FLAGS are bit encoded and may be given in usual C-Syntax. @item --debug-all @opindex debug-all Same as @code{--debug=0xffffffff} @item --gnutls-debug @var{level} @opindex gnutls-debug Enable debugging of GNUTLS at @var{level}. @item --debug-wait @var{n} @opindex debug-wait When running in server mode, wait @var{n} seconds before entering the actual processing loop and print the pid. This gives time to attach a debugger. @item -s @itemx --sh @itemx -c @itemx --csh @opindex s @opindex sh @opindex c @opindex csh Format the info output in daemon mode for use with the standard Bourne shell respective the C-shell . The default ist to guess it based on the environment variable @code{SHELL} which is in almost all cases sufficient. @item --force @opindex force Enabling this option forces loading of expired CRLs; this is only useful for debugging. @item --use-tor @opindex use-tor This option switches Dirmngr and thus GnuPG into ``Tor mode'' to route all network access via Tor (an anonymity network). WARNING: As of now this still leaks the DNS queries; e.g. to lookup the hosts in a keyserver pool. Certain other features are disabled if this mode is active. @item --keyserver @var{name} @opindex keyserver Use @var{name} as your keyserver. This is the server that @command{gpg} communicates with to receive keys, send keys, and search for keys. The format of the @var{name} is a URI: `scheme:[//]keyservername[:port]' The scheme is the type of keyserver: "hkp" for the HTTP (or compatible) keyservers, "ldap" for the LDAP keyservers, or "mailto" for the Graff email keyserver. Note that your particular installation of GnuPG may have other keyserver types available as well. Keyserver schemes are case-insensitive. After the keyserver name, optional keyserver configuration options may be provided. These are the same as the @option{--keyserver-options} of @command{gpg}, but apply only to this particular keyserver. Most keyservers synchronize with each other, so there is generally no need to send keys to more than one server. The keyserver @code{hkp://keys.gnupg.net} uses round robin DNS to give a different keyserver each time you use it. If exactly two keyservers are configured and only one is a Tor hidden service (.onion), Dirmngr selects the keyserver to use depending on whether Tor is locally running or not. The check for a running Tor is done for each new connection. @item --nameserver @var{ipaddr} @opindex nameserver In ``Tor mode'' Dirmngr uses a public resolver via Tor to resolve DNS names. If the default public resolver, which is @code{8.8.8.8}, shall not be used a different one can be given using this option. Note that a numerical IP address must be given (IPv6 or IPv4) and that no error checking is done for @var{ipaddr}. DNS queries in Tor mode do only work if GnuPG as been build with ADNS support. @item --disable-ldap @opindex disable-ldap Entirely disables the use of LDAP. @item --disable-http @opindex disable-http Entirely disables the use of HTTP. @item --ignore-http-dp @opindex ignore-http-dp When looking for the location of a CRL, the to be tested certificate usually contains so called @dfn{CRL Distribution Point} (DP) entries which are URLs describing the way to access the CRL. The first found DP entry is used. With this option all entries using the @acronym{HTTP} scheme are ignored when looking for a suitable DP. @item --ignore-ldap-dp @opindex ignore-ldap-dp This is similar to @option{--ignore-http-dp} but ignores entries using the @acronym{LDAP} scheme. Both options may be combined resulting in ignoring DPs entirely. @item --ignore-ocsp-service-url @opindex ignore-ocsp-service-url Ignore all OCSP URLs contained in the certificate. The effect is to force the use of the default responder. @item --honor-http-proxy @opindex honor-http-proxy If the environment variable @env{http_proxy} has been set, use its value to access HTTP servers. @item --http-proxy @var{host}[:@var{port}] @opindex http-proxy @efindex http_proxy Use @var{host} and @var{port} to access HTTP servers. The use of this option overrides the environment variable @env{http_proxy} regardless whether @option{--honor-http-proxy} has been set. @item --ldap-proxy @var{host}[:@var{port}] @opindex ldap-proxy Use @var{host} and @var{port} to connect to LDAP servers. If @var{port} is omitted, port 389 (standard LDAP port) is used. This overrides any specified host and port part in a LDAP URL and will also be used if host and port have been omitted from the URL. @item --only-ldap-proxy @opindex only-ldap-proxy Never use anything else but the LDAP "proxy" as configured with @option{--ldap-proxy}. Usually @command{dirmngr} tries to use other configured LDAP server if the connection using the "proxy" failed. @item --ldapserverlist-file @var{file} @opindex ldapserverlist-file Read the list of LDAP servers to consult for CRLs and certificates from file instead of the default per-user ldap server list file. The default value for @var{file} is @file{dirmngr_ldapservers.conf} or @file{ldapservers.conf} when running in @option{--daemon} mode. This server list file contains one LDAP server per line in the format @sc{hostname:port:username:password:base_dn} Lines starting with a @samp{#} are comments. Note that as usual all strings entered are expected to be UTF-8 encoded. Obviously this will lead to problems if the password has originally been encoded as Latin-1. There is no other solution here than to put such a password in the binary encoding into the file (i.e. non-ascii characters won't show up readable).@footnote{The @command{gpgconf} tool might be helpful for frontends as it enables editing this configuration file using percent-escaped strings.} @item --ldaptimeout @var{secs} @opindex ldaptimeout Specify the number of seconds to wait for an LDAP query before timing out. The default is currently 100 seconds. 0 will never timeout. @item --add-servers @opindex add-servers This options makes dirmngr add any servers it discovers when validating certificates against CRLs to the internal list of servers to consult for certificates and CRLs. This options is useful when trying to validate a certificate that has a CRL distribution point that points to a server that is not already listed in the ldapserverlist. Dirmngr will always go to this server and try to download the CRL, but chances are high that the certificate used to sign the CRL is located on the same server. So if dirmngr doesn't add that new server to list, it will often not be able to verify the signature of the CRL unless the @code{--add-servers} option is used. Note: The current version of dirmngr has this option disabled by default. @item --allow-ocsp @opindex allow-ocsp This option enables OCSP support if requested by the client. OCSP requests are rejected by default because they may violate the privacy of the user; for example it is possible to track the time when a user is reading a mail. @item --ocsp-responder @var{url} @opindex ocsp-responder Use @var{url} as the default OCSP Responder if the certificate does not contain information about an assigned responder. Note, that @code{--ocsp-signer} must also be set to a valid certificate. @item --ocsp-signer @var{fpr}|@var{file} @opindex ocsp-signer Use the certificate with the fingerprint @var{fpr} to check the responses of the default OCSP Responder. Alternativly a filename can be given in which case the respinse is expected to be signed by one of the certificates described in that file. Any argument which contains a slash, dot or tilde is considered a filename. Usual filename expansion takes place: A tilde at the start followed by a slash is replaced by the content of @env{HOME}, no slash at start describes a relative filename which will be searched at the home directory. To make sure that the @var{file} is searched in the home directory, either prepend the name with "./" or use a name which contains a dot. If a response has been signed by a certificate described by these fingerprints no further check upon the validity of this certificate is done. The format of the @var{FILE} is a list of SHA-1 fingerprint, one per line with optional colons between the bytes. Empty lines and lines prefix with a hash mark are ignored. @item --ocsp-max-clock-skew @var{n} @opindex ocsp-max-clock-skew The number of seconds a skew between the OCSP responder and them local clock is accepted. Default is 600 (20 minutes). @item --ocsp-max-period @var{n} @opindex ocsp-max-period Seconds a response is at maximum considered valid after the time given in the thisUpdate field. Default is 7776000 (90 days). @item --ocsp-current-period @var{n} @opindex ocsp-current-period The number of seconds an OCSP response is considered valid after the time given in the NEXT_UPDATE datum. Default is 10800 (3 hours). @item --max-replies @var{n} @opindex max-replies Do not return more that @var{n} items in one query. The default is 10. @item --ignore-cert-extension @var{oid} @opindex ignore-cert-extension Add @var{oid} to the list of ignored certificate extensions. The @var{oid} is expected to be in dotted decimal form, like @code{2.5.29.3}. This option may be used more than once. Critical flagged certificate extensions matching one of the OIDs in the list are treated as if they are actually handled and thus the certificate won't be rejected due to an unknown critical extension. Use this option with care because extensions are usually flagged as critical for a reason. @item --hkp-cacert @var{file} Use the root certificates in @var{file} for verification of the TLS certificates used with @code{hkps} (keyserver access over TLS). If the file is in PEM format a suffix of @code{.pem} is expected for @var{file}. This option may be given multiple times to add more root certificates. Tilde expansion is supported. @end table @c @c Dirmngr Configuration @c @mansect files @node Dirmngr Configuration @section Configuration Dirmngr makes use of several directories when running in daemon mode: @table @file @item ~/.gnupg -@itemx /etc/gnupg -The first is the standard home directory for all configuration files. -In the deprecated system daemon mode the second directory is used instead. +This is the standard home directory for all configuration files. @item /etc/gnupg/trusted-certs This directory should be filled with certificates of Root CAs you are trusting in checking the CRLs and signing OCSP Responses. Usually these are the same certificates you use with the applications making use of dirmngr. It is expected that each of these certificate files contain exactly one @acronym{DER} encoded certificate in a file with the suffix @file{.crt} or @file{.der}. @command{dirmngr} reads those certificates on startup and when given a SIGHUP. Certificates which are not readable or do not make up a proper X.509 certificate are ignored; see the log file for details. Applications using dirmngr (e.g. gpgsm) can request these certificates to complete a trust chain in the same way as with the extra-certs directory (see below). Note that for OCSP responses the certificate specified using the option @option{--ocsp-signer} is always considered valid to sign OCSP requests. @item /etc/gnupg/extra-certs This directory may contain extra certificates which are preloaded into the interal cache on startup. Applications using dirmngr (e.g. gpgsm) can request cached certificates to complete a trust chain. This is convenient in cases you have a couple intermediate CA certificates or certificates ususally used to sign OCSP responses. These certificates are first tried before going out to the net to look for them. These certificates must also be @acronym{DER} encoded and suffixed with @file{.crt} or @file{.der}. -@item @value{LOCALRUNDIR} -This directory is only used in the deprecated system daemon mode. It -keeps the socket file for accessing @command{dirmngr} services. The -name of the socket file will be @file{S.dirmngr}. Make sure that this -directory has the proper permissions to let @command{dirmngr} create -the socket file and that eligible users may read and write to that -socket. - @item ~/.gnupg/crls.d -@itemx @value{LOCALCACHEDIR}/crls.d -The first directory is used to store cached CRLs. The @file{crls.d} +This directory is used to store cached CRLs. The @file{crls.d} part will be created by dirmngr if it does not exists but you need to -make sure that the upper directory exists. The second directory is -used instead in the deprecated systems daemon mode. +make sure that the upper directory exists. @end table @manpause To be able to see what's going on you should create the configure file @file{~/gnupg/dirmngr.conf} with at least one line: @example log-file ~/dirmngr.log @end example To be able to perform OCSP requests you probably want to add the line: @example allow-ocsp @end example To make sure that new options are read and that after the installation of a new GnuPG versions the installed dirmngr is running, you may want to kill an existing dirmngr first: @example gpgconf --kill dirmngr @end example You may check the log file to see whether all desired root certificates have been loaded correctly. @c @c Dirmngr Signals @c @mansect signals @node Dirmngr Signals @section Use of signals. A running @command{dirmngr} may be controlled by signals, i.e. using the @command{kill} command to send a signal to the process. Here is a list of supported signals: @table @gnupgtabopt @item SIGHUP @cpindex SIGHUP This signals flushes all internally cached CRLs as well as any cached certificates. Then the certificate cache is reinitialized as on startup. Options are re-read from the configuration file. Instead of sending this signal it is better to use @example gpgconf --reload dirmngr @end example @item SIGTERM @cpindex SIGTERM Shuts down the process but waits until all current requests are fulfilled. If the process has received 3 of these signals and requests are still pending, a shutdown is forced. You may also use @example gpgconf --kill dirmngr @end example instead of this signal @item SIGINT @cpindex SIGINT Shuts down the process immediately. @item SIGUSR1 @cpindex SIGUSR1 This prints some caching statistics to the log file. @end table @c @c Examples @c @mansect examples @node Dirmngr Examples @section Examples Here is an example on how to show dirmngr's internal table of OpenPGP keyserver addresses. The output is intended for debugging purposes and not part of a defined API. @example gpg-connect-agent --dirmngr 'keyserver --hosttable' /bye @end example To inhibit the use of a particular host you have noticed in one of the keyserver pools, you may use @example gpg-connect-agent --dirmngr 'keyserver --dead pgpkeys.bnd.de' /bye @end example The description of the @code{keyserver} command can be printed using @example gpg-connect-agent --dirmngr 'help keyserver' /bye @end example @c @c Assuan Protocol @c @manpause @node Dirmngr Protocol @section Dirmngr's Assuan Protocol Assuan is the IPC protocol used to access dirmngr. This is a description of the commands implemented by dirmngr. @menu * Dirmngr LOOKUP:: Look up a certificate via LDAP * Dirmngr ISVALID:: Validate a certificate using a CRL or OCSP. * Dirmngr CHECKCRL:: Validate a certificate using a CRL. * Dirmngr CHECKOCSP:: Validate a certificate using OCSP. * Dirmngr CACHECERT:: Put a certificate into the internal cache. * Dirmngr VALIDATE:: Validate a certificate for debugging. @end menu @node Dirmngr LOOKUP @subsection Return the certificate(s) found Lookup certificate. To allow multiple patterns (which are ORed) quoting is required: Spaces are to be translated into "+" or into "%20"; obviously this requires that the usual escape quoting rules are applied. The server responds with: @example S: D S: END S: D S: END S: OK @end example In this example 2 certificates are returned. The server may return any number of certificates; OK will also be returned when no certificates were found. The dirmngr might return a status line @example S: S TRUNCATED @end example To indicate that the output was truncated to N items due to a limitation of the server or by an arbitrary set limit. The option @option{--url} may be used if instead of a search pattern a complete URL to the certificate is known: @example C: LOOKUP --url CN%3DWerner%20Koch,o%3DIntevation%20GmbH,c%3DDE?userCertificate @end example If the option @option{--cache-only} is given, no external lookup is done so that only certificates from the cache are returned. With the option @option{--single}, the first and only the first match will be returned. Unless option @option{--cache-only} is also used, no local lookup will be done in this case. @node Dirmngr ISVALID @subsection Validate a certificate using a CRL or OCSP @example ISVALID [--only-ocsp] [--force-default-responder] @var{certid}|@var{certfpr} @end example Check whether the certificate described by the @var{certid} has been revoked. Due to caching, the Dirmngr is able to answer immediately in most cases. The @var{certid} is a hex encoded string consisting of two parts, delimited by a single dot. The first part is the SHA-1 hash of the issuer name and the second part the serial number. Alternatively the certificate's SHA-1 fingerprint @var{certfpr} may be given in which case an OCSP request is done before consulting the CRL. If the option @option{--only-ocsp} is given, no fallback to a CRL check will be used. If the option @option{--force-default-responder} is given, only the default OCSP responder will be used and any other methods of obtaining an OCSP responder URL won't be used. @noindent Common return values are: @table @code @item GPG_ERR_NO_ERROR (0) This is the positive answer: The certificate is not revoked and we have an up-to-date revocation list for that certificate. If OCSP was used the responder confirmed that the certificate has not been revoked. @item GPG_ERR_CERT_REVOKED This is the negative answer: The certificate has been revoked. Either it is in a CRL and that list is up to date or an OCSP responder informed us that it has been revoked. @item GPG_ERR_NO_CRL_KNOWN No CRL is known for this certificate or the CRL is not valid or out of date. @item GPG_ERR_NO_DATA The OCSP responder returned an ``unknown'' status. This means that it is not aware of the certificate's status. @item GPG_ERR_NOT_SUPPORTED This is commonly seen if OCSP support has not been enabled in the configuration. @end table If DirMngr has not enough information about the given certificate (which is the case for not yet cached certificates), it will will inquire the missing data: @example S: INQUIRE SENDCERT C: D C: END @end example A client should be aware that DirMngr may ask for more than one certificate. If Dirmngr has a certificate but the signature of the certificate could not been validated because the root certificate is not known to dirmngr as trusted, it may ask back to see whether the client trusts this the root certificate: @example S: INQUIRE ISTRUSTED C: D 1 C: END @end example Only this answer will let Dirmngr consider the CRL as valid. @node Dirmngr CHECKCRL @subsection Validate a certificate using a CRL Check whether the certificate with FINGERPRINT (SHA-1 hash of the entire X.509 certificate blob) is valid or not by consulting the CRL responsible for this certificate. If the fingerprint has not been given or the certificate is not know, the function inquires the certificate using: @example S: INQUIRE TARGETCERT C: D C: END @end example Thus the caller is expected to return the certificate for the request (which should match FINGERPRINT) as a binary blob. Processing then takes place without further interaction; in particular dirmngr tries to locate other required certificate by its own mechanism which includes a local certificate store as well as a list of trusted root certificates. @noindent The return code is 0 for success; i.e. the certificate has not been revoked or one of the usual error codes from libgpg-error. @node Dirmngr CHECKOCSP @subsection Validate a certificate using OCSP @example CHECKOCSP [--force-default-responder] [@var{fingerprint}] @end example Check whether the certificate with @var{fingerprint} (the SHA-1 hash of the entire X.509 certificate blob) is valid by consulting the appropiate OCSP responder. If the fingerprint has not been given or the certificate is not known by Dirmngr, the function inquires the certificate using: @example S: INQUIRE TARGETCERT C: D C: END @end example Thus the caller is expected to return the certificate for the request (which should match @var{fingerprint}) as a binary blob. Processing then takes place without further interaction; in particular dirmngr tries to locate other required certificates by its own mechanism which includes a local certificate store as well as a list of trusted root certificates. If the option @option{--force-default-responder} is given, only the default OCSP responder is used. This option is the per-command variant of the global option @option{--ignore-ocsp-service-url}. @noindent The return code is 0 for success; i.e. the certificate has not been revoked or one of the usual error codes from libgpg-error. @node Dirmngr CACHECERT @subsection Put a certificate into the internal cache Put a certificate into the internal cache. This command might be useful if a client knows in advance certificates required for a test and wnats to make sure they get added to the internal cache. It is also helpful for debugging. To get the actual certificate, this command immediately inquires it using @example S: INQUIRE TARGETCERT C: D C: END @end example Thus the caller is expected to return the certificate for the request as a binary blob. @noindent The return code is 0 for success; i.e. the certificate has not been succesfully cached or one of the usual error codes from libgpg-error. @node Dirmngr VALIDATE @subsection Validate a certificate for debugging Validate a certificate using the certificate validation function used internally by dirmngr. This command is only useful for debugging. To get the actual certificate, this command immediately inquires it using @example S: INQUIRE TARGETCERT C: D C: END @end example Thus the caller is expected to return the certificate for the request as a binary blob. @mansect see also @ifset isman @command{gpgsm}(1), @command{dirmngr-client}(1) @end ifset @include see-also-note.texi @c @c !!! UNDER CONSTRUCTION !!! @c @c @c @section Verifying a Certificate @c @c There are several ways to request services from Dirmngr. Almost all of @c them are done using the Assuan protocol. What we describe here is the @c Assuan command CHECKCRL as used for example by the dirmnr-client tool if @c invoked as @c @c @example @c dirmngr-client foo.crt @c @end example @c @c This command will send an Assuan request to an already running Dirmngr @c instance. foo.crt is expected to be a standard X.509 certificate and @c dirmngr will receive the Assuan command @c @c @example @c CHECKCRL @var [{fingerprint}] @c @end example @c @c @var{fingerprint} is optional and expected to be the SHA-1 has of the @c DER encoding of the certificate under question. It is to be HEX @c encoded. The rationale for sending the fingerprint is that it allows @c dirmngr to reply immediatly if it has already cached such a request. If @c this is not the case and no certificate has been found in dirmngr's @c internal certificate storage, dirmngr will request the certificate using @c the Assuan inquiry @c @c @example @c INQUIRE TARGETCERT @c @end example @c @c The caller (in our example dirmngr-client) is then expected to return @c the certificate for the request (which should match @var{fingerprint}) @c as a binary blob. @c @c Dirmngr now passes control to @code{crl_cache_cert_isvalid}. This @c function checks whether a CRL item exists for target certificate. These @c CRL items are kept in a database of already loaded and verified CRLs. @c This mechanism is called the CRL cache. Obviously timestamps are kept @c there with each item to cope with the expiration date of the CRL. The @c possible return values are: @code{0} to indicate that a valid CRL is @c available for the certificate and the certificate itself is not listed @c in this CRL, @code{GPG_ERR_CERT_REVOKED} to indicate that the certificate is @c listed in the CRL or @code{GPG_ERR_NO_CRL_KNOWN} in cases where no CRL or no @c information is available. The first two codes are immediatly returned to @c the caller and the processing of this request has been done. @c @c Only the @code{GPG_ERR_NO_CRL_KNOWN} needs more attention: Dirmngr now @c calls @code{clr_cache_reload_crl} and if this succeeds calls @c @code{crl_cache_cert_isvald) once more. All further errors are @c immediately returned to the caller. @c @c @code{crl_cache_reload_crl} is the actual heart of the CRL management. @c It locates the corresponding CRL for the target certificate, reads and @c verifies this CRL and stores it in the CRL cache. It works like this: @c @c * Loop over all crlDPs in the target certificate. @c * If the crlDP is invalid immediately terminate the loop. @c * Loop over all names in the current crlDP. @c * If the URL scheme is unknown or not enabled @c (--ignore-http-dp, --ignore-ldap-dp) continues with @c the next name. @c * @code{crl_fetch} is called to actually retrieve the CRL. @c In case of problems this name is ignore and we continue with @c the next name. Note that @code{crl_fetch} does only return @c a descriptor for the CRL for further reading so does the CRL @c does not yet end up in memory. @c * @code{crl_cache_insert} is called with that descriptor to @c actually read the CRL into the cache. See below for a @c description of this function. If there is any error (e.g. read @c problem, CRL not correctly signed or verification of signature @c not possible), this descriptor is rejected and we continue @c with the next name. If the CRL has been successfully loaded, @c the loop is terminated. @c * If no crlDP has been found in the previous loop use a default CRL. @c Note, that if any crlDP has been found but loading of the CRL failed, @c this condition is not true. @c * Try to load a CRL from all configured servers (ldapservers.conf) @c in turn. The first server returning a CRL is used. @c * @code(crl_cache_insert) is then used to actually insert the CRL @c into the cache. If this failed we give up immediatley without @c checking the rest of the servers from the first step. @c * Ready. @c @c @c The @code{crl_cache_insert} function takes care of reading the bulk of @c the CRL, parsing it and checking the signature. It works like this: A @c new database file is created using a temporary file name. The CRL @c parsing machinery is started and all items of the CRL are put into @c this database file. At the end the issuer certificate of the CRL @c needs to be retrieved. Three cases are to be distinguished: @c @c a) An authorityKeyIdentifier with an issuer and serialno exits: The @c certificate is retrieved using @code{find_cert_bysn}. If @c the certificate is in the certificate cache, it is directly @c returned. Then the requester (i.e. the client who requested the @c CRL check) is asked via the Assuan inquiry ``SENDCERT'' whether @c he can provide this certificate. If this succeed the returned @c certificate gets cached and returned. Note, that dirmngr does not @c verify in any way whether the expected certificate is returned. @c It is in the interest of the client to return a useful certificate @c as otherwise the service request will fail due to a bad signature. @c The last way to get the certificate is by looking it up at @c external resources. This is done using the @code{ca_cert_fetch} @c and @code{fetch_next_ksba_cert} and comparing the returned @c certificate to match the requested issuer and seriano (This is @c needed because the LDAP layer may return several certificates as @c LDAP as no standard way to retrieve by serial number). @c @c b) An authorityKeyIdentifier with a key ID exists: The certificate is @c retrieved using @code{find_cert_bysubject}. If the certificate is @c in the certificate cache, it is directly returned. Then the @c requester is asked via the Assuan inquiry ``SENDCERT_SKI'' whether @c he can provide this certificate. If this succeed the returned @c certificate gets cached and returned. Note, that dirmngr does not @c verify in any way whether the expected certificate is returned. @c It is in the interest of the client to return a useful certificate @c as otherwise the service request will fail due to a bad signature. @c The last way to get the certificate is by looking it up at @c external resources. This is done using the @code{ca_cert_fetch} @c and @code{fetch_next_ksba_cert} and comparing the returned @c certificate to match the requested subject and key ID. @c @c c) No authorityKeyIdentifier exits: The certificate is retrieved @c using @code{find_cert_bysubject} without the key ID argument. If @c the certificate is in the certificate cache the first one with a @c matching subject is is directly returned. Then the requester is @c asked via the Assuan inquiry ``SENDCERT'' and an exact @c specification of the subject whether he can @c provide this certificate. If this succeed the returned @c certificate gets cached and returned. Note, that dirmngr does not @c verify in any way whether the expected certificate is returned. @c It is in the interest of the client to return a useful certificate @c as otherwise the service request will fail due to a bad signature. @c The last way to get the certificate is by looking it up at @c external resources. This is done using the @code{ca_cert_fetch} @c and @code{fetch_next_ksba_cert} and comparing the returned @c certificate to match the requested subject; the first certificate @c with a matching subject is then returned. @c @c If no certificate was found, the function returns with the error @c GPG_ERR_MISSING_CERT. Now the signature is verified. If this fails, @c the erro is returned. On success the @code{validate_cert_chain} is @c used to verify that the certificate is actually valid. @c @c Here we may encounter a recursive situation: @c @code{validate_cert_chain} needs to look at other certificates and @c also at CRLs to check whether these other certificates and well, the @c CRL issuer certificate itself are not revoked. FIXME: We need to make @c sure that @code{validate_cert_chain} does not try to lookup the CRL we @c are currently processing. This would be a catch-22 and may indicate a @c broken PKI. However, due to overlapping expiring times and imprecise @c clocks thsi may actually happen. @c @c For historical reasons the Assuan command ISVALID is a bit different @c to CHECKCRL but this is mainly due to different calling conventions. @c In the end the same fucntionality is used, albeit hidden by a couple @c of indirection and argument and result code mangling. It furthere @c ingetrages OCSP checking depending on options are the way it is @c called. GPGSM still uses this command but might eventuall switch over @c to CHECKCRL and CHECKOCSP so that ISVALID can be retired. @c @c @c @section Validating a certificate @c @c We describe here how the internal function @code{validate_cert_chain} @c works. Note that mainly testing purposes this functionality may be @c called directly using @cmd{dirmngr-client --validate @file{foo.crt}}. @c -@c For backward compatibility this function returns success if Dirmngr is -@c not used as a system daemon. Thus not validating the certicates at -@c all. FIXME: This is definitely not correct and should be fixed ASAP. -@c @c The function takes the target certificate and a mode argument as @c parameters and returns an error code and optionally the closes @c expiration time of all certificates in the chain. @c @c We first check that the certificate may be used for the requested @c purpose (i.e. OCSP or CRL signing). If this is not the case @c GPG_ERR_WRONG_KEY_USAGE is returned. @c @c The next step is to find the trust anchor (root certificate) and to @c assemble the chain in memory: Starting with the target certificate, @c the expiration time is checked against the current date, unknown @c critical extensions are detected and certificate policies are matched @c (We only allow 2.289.9.9 but I have no clue about that OID and from @c where I got it - it does not even seem to be assigned - debug cruft?). @c @c Now if this certificate is a self-signed one, we have reached the @c trust anchor. In this case we check that the signature is good, the @c certificate is allowed to act as a CA, that it is a trusted one (by @c checking whether it is has been put into the trusted-certs @c configuration directory) and finally prepend into to our list @c representing the certificate chain. This steps ends then. @c @c If it is not a self-signed certificate, we check that the chain won't @c get too long (current limit is 100), if this is the case we terminate @c with the error GPG_ERR_BAD_CERT_CHAIN. @c @c Now the issuer's certificate is looked up: If an @c authorityKeyIdentifier is available, this one is used to locate the @c certificate either using issuer and serialnumber or subject DN @c (i.e. the issuer's DN) and the keyID. The functions @c @code{find_cert_bysn) and @code{find_cert_bysubject} are used @c respectively. The have already been described above under the @c description of @code{crl_cache_insert}. If no certificate was found @c or with no authorityKeyIdentifier, only the cache is consulted using @c @code{get_cert_bysubject}. The latter is is done under the assumption @c that a matching certificate has explicitly been put into the @c certificate cache. If the issuer's certificate could not be found, @c the validation terminates with the error code @code{GPG_ERR_MISSING_CERT}. @c @c If the issuer's certificate has been found, the signature of the @c actual certificate is checked and in case this fails the error @c #code{GPG_ERR_BAD_CERT_CHAIN} is returned. If the signature checks out, the @c maximum cahin length of the issueing certificate is checked as well as @c the capiblity of the certificate (i.e. whether he may be used for @c certificate signing). Then the certificate is prepended to our list @c representing the certificate chain. Finally the loop is continued now @c with the issuer's certificate as the current certificate. @c @c After the end of the loop and if no error as been encountered @c (i.e. the certificate chain has been assempled correctly), a check is @c done whether any certificate expired or a critical policy has not been @c met. In any of these cases the validation terminates with an @c appropriate error. @c @c Finally the function @code{check_revocations} is called to verify no @c certificate in the assempled chain has been revoked: This is an @c recursive process because a CRL has to be checked for each certificate @c in the chain except for the root certificate, of which we already know @c that it is trusted and we avoid checking a CRL here due to common @c setup problems and the assumption that a revoked root certifcate has @c been removed from the list of trusted certificates. @c @c @c @c @c @section Looking up certificates through LDAP. @c @c This describes the LDAP layer to retrieve certificates. @c the functions @code{ca_cert_fetch} and @code{fetch_next_ksba_cert} are @c used for this. The first one starts a search and the second one is @c used to retrieve certificate after certificate. @c diff --git a/sm/server.c b/sm/server.c index cdccff3c5..ce8085dad 100644 --- a/sm/server.c +++ b/sm/server.c @@ -1,1485 +1,1483 @@ /* server.c - Server mode and main entry point * Copyright (C) 2001, 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009, * 2010 Free Software Foundation, Inc. * * This file is part of GnuPG. * * GnuPG is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3 of the License, or * (at your option) any later version. * * GnuPG 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 General Public License * along with this program; if not, see . */ #include #include #include #include #include #include #include #include #include "gpgsm.h" #include #include "sysutils.h" #include "server-help.h" #define set_error(e,t) assuan_set_error (ctx, gpg_error (e), (t)) /* The filepointer for status message used in non-server mode */ static FILE *statusfp; /* Data used to assuciate an Assuan context with local server data */ struct server_local_s { assuan_context_t assuan_ctx; int message_fd; int list_internal; int list_external; int list_to_output; /* Write keylistings to the output fd. */ int enable_audit_log; /* Use an audit log. */ certlist_t recplist; certlist_t signerlist; certlist_t default_recplist; /* As set by main() - don't release. */ int allow_pinentry_notify; /* Set if pinentry notifications should be passed back to the client. */ int no_encrypt_to; /* Local version of option. */ }; /* Cookie definition for assuan data line output. */ static gpgrt_ssize_t data_line_cookie_write (void *cookie, const void *buffer, size_t size); static int data_line_cookie_close (void *cookie); static es_cookie_io_functions_t data_line_cookie_functions = { NULL, data_line_cookie_write, NULL, data_line_cookie_close }; static int command_has_option (const char *cmd, const char *cmdopt); /* Note that it is sufficient to allocate the target string D as long as the source string S, i.e.: strlen(s)+1; */ static void strcpy_escaped_plus (char *d, const char *s) { while (*s) { if (*s == '%' && s[1] && s[2]) { s++; *d++ = xtoi_2 (s); s += 2; } else if (*s == '+') *d++ = ' ', s++; else *d++ = *s++; } *d = 0; } /* A write handler used by es_fopencookie to write assuan data lines. */ static gpgrt_ssize_t data_line_cookie_write (void *cookie, const void *buffer, size_t size) { assuan_context_t ctx = cookie; if (assuan_send_data (ctx, buffer, size)) { gpg_err_set_errno (EIO); return -1; } return (gpgrt_ssize_t)size; } static int data_line_cookie_close (void *cookie) { assuan_context_t ctx = cookie; if (assuan_send_data (ctx, NULL, 0)) { gpg_err_set_errno (EIO); return -1; } return 0; } static void close_message_fd (ctrl_t ctrl) { if (ctrl->server_local->message_fd != -1) { #ifdef HAVE_W32CE_SYSTEM #warning Is this correct for W32/W32CE? #endif close (ctrl->server_local->message_fd); ctrl->server_local->message_fd = -1; } } /* Start a new audit session if this has been enabled. */ static gpg_error_t start_audit_session (ctrl_t ctrl) { audit_release (ctrl->audit); ctrl->audit = NULL; if (ctrl->server_local->enable_audit_log && !(ctrl->audit = audit_new ()) ) return gpg_error_from_syserror (); return 0; } static gpg_error_t option_handler (assuan_context_t ctx, const char *key, const char *value) { ctrl_t ctrl = assuan_get_pointer (ctx); gpg_error_t err = 0; if (!strcmp (key, "putenv")) { /* Change the session's environment to be used for the Pinentry. Valid values are: Delete envvar NAME = Set envvar NAME to the empty string = Set envvar NAME to VALUE */ err = session_env_putenv (opt.session_env, value); } else if (!strcmp (key, "display")) { err = session_env_setenv (opt.session_env, "DISPLAY", value); } else if (!strcmp (key, "ttyname")) { err = session_env_setenv (opt.session_env, "GPG_TTY", value); } else if (!strcmp (key, "ttytype")) { err = session_env_setenv (opt.session_env, "TERM", value); } else if (!strcmp (key, "lc-ctype")) { xfree (opt.lc_ctype); opt.lc_ctype = xtrystrdup (value); if (!opt.lc_ctype) err = gpg_error_from_syserror (); } else if (!strcmp (key, "lc-messages")) { xfree (opt.lc_messages); opt.lc_messages = xtrystrdup (value); if (!opt.lc_messages) err = gpg_error_from_syserror (); } else if (!strcmp (key, "xauthority")) { err = session_env_setenv (opt.session_env, "XAUTHORITY", value); } else if (!strcmp (key, "pinentry-user-data")) { err = session_env_setenv (opt.session_env, "PINENTRY_USER_DATA", value); } else if (!strcmp (key, "include-certs")) { int i = *value? atoi (value) : -1; if (ctrl->include_certs < -2) err = gpg_error (GPG_ERR_ASS_PARAMETER); else ctrl->include_certs = i; } else if (!strcmp (key, "list-mode")) { int i = *value? atoi (value) : 0; if (!i || i == 1) /* default and mode 1 */ { ctrl->server_local->list_internal = 1; ctrl->server_local->list_external = 0; } else if (i == 2) { ctrl->server_local->list_internal = 0; ctrl->server_local->list_external = 1; } else if (i == 3) { ctrl->server_local->list_internal = 1; ctrl->server_local->list_external = 1; } else err = gpg_error (GPG_ERR_ASS_PARAMETER); } else if (!strcmp (key, "list-to-output")) { int i = *value? atoi (value) : 0; ctrl->server_local->list_to_output = i; } else if (!strcmp (key, "with-validation")) { int i = *value? atoi (value) : 0; ctrl->with_validation = i; } else if (!strcmp (key, "with-secret")) { int i = *value? atoi (value) : 0; ctrl->with_secret = i; } else if (!strcmp (key, "validation-model")) { int i = gpgsm_parse_validation_model (value); if ( i >= 0 && i <= 2 ) ctrl->validation_model = i; else err = gpg_error (GPG_ERR_ASS_PARAMETER); } else if (!strcmp (key, "with-key-data")) { opt.with_key_data = 1; } else if (!strcmp (key, "enable-audit-log")) { int i = *value? atoi (value) : 0; ctrl->server_local->enable_audit_log = i; } else if (!strcmp (key, "allow-pinentry-notify")) { ctrl->server_local->allow_pinentry_notify = 1; } else if (!strcmp (key, "with-ephemeral-keys")) { int i = *value? atoi (value) : 0; ctrl->with_ephemeral_keys = i; } else if (!strcmp (key, "no-encrypt-to")) { ctrl->server_local->no_encrypt_to = 1; } else if (!strcmp (key, "offline")) { /* We ignore this option if gpgsm has been started with --disable-dirmngr (which also sets offline). */ if (!opt.disable_dirmngr) { int i = *value? !!atoi (value) : 1; ctrl->offline = i; } } else err = gpg_error (GPG_ERR_UNKNOWN_OPTION); return err; } static gpg_error_t reset_notify (assuan_context_t ctx, char *line) { ctrl_t ctrl = assuan_get_pointer (ctx); (void) line; gpgsm_release_certlist (ctrl->server_local->recplist); gpgsm_release_certlist (ctrl->server_local->signerlist); ctrl->server_local->recplist = NULL; ctrl->server_local->signerlist = NULL; close_message_fd (ctrl); assuan_close_input_fd (ctx); assuan_close_output_fd (ctx); return 0; } static gpg_error_t input_notify (assuan_context_t ctx, char *line) { ctrl_t ctrl = assuan_get_pointer (ctx); ctrl->autodetect_encoding = 0; ctrl->is_pem = 0; ctrl->is_base64 = 0; if (strstr (line, "--armor")) ctrl->is_pem = 1; else if (strstr (line, "--base64")) ctrl->is_base64 = 1; else if (strstr (line, "--binary")) ; else ctrl->autodetect_encoding = 1; return 0; } static gpg_error_t output_notify (assuan_context_t ctx, char *line) { ctrl_t ctrl = assuan_get_pointer (ctx); ctrl->create_pem = 0; ctrl->create_base64 = 0; if (strstr (line, "--armor")) ctrl->create_pem = 1; else if (strstr (line, "--base64")) ctrl->create_base64 = 1; /* just the raw output */ return 0; } static const char hlp_recipient[] = "RECIPIENT \n" "\n" "Set the recipient for the encryption. USERID shall be the\n" "internal representation of the key; the server may accept any other\n" "way of specification [we will support this]. If this is a valid and\n" "trusted recipient the server does respond with OK, otherwise the\n" "return is an ERR with the reason why the recipient can't be used,\n" "the encryption will then not be done for this recipient. If the\n" "policy is not to encrypt at all if not all recipients are valid, the\n" "client has to take care of this. All RECIPIENT commands are\n" "cumulative until a RESET or an successful ENCRYPT command."; static gpg_error_t cmd_recipient (assuan_context_t ctx, char *line) { ctrl_t ctrl = assuan_get_pointer (ctx); int rc; if (!ctrl->audit) rc = start_audit_session (ctrl); else rc = 0; if (!rc) rc = gpgsm_add_to_certlist (ctrl, line, 0, &ctrl->server_local->recplist, 0); if (rc) { gpgsm_status2 (ctrl, STATUS_INV_RECP, get_inv_recpsgnr_code (rc), line, NULL); } return rc; } static const char hlp_signer[] = "SIGNER \n" "\n" "Set the signer's keys for the signature creation. USERID should\n" "be the internal representation of the key; the server may accept any\n" "other way of specification [we will support this]. If this is a\n" "valid and usable signing key the server does respond with OK,\n" "otherwise it returns an ERR with the reason why the key can't be\n" "used, the signing will then not be done for this key. If the policy\n" "is not to sign at all if not all signer keys are valid, the client\n" "has to take care of this. All SIGNER commands are cumulative until\n" "a RESET but they are *not* reset by an SIGN command because it can\n" "be expected that set of signers are used for more than one sign\n" "operation."; static gpg_error_t cmd_signer (assuan_context_t ctx, char *line) { ctrl_t ctrl = assuan_get_pointer (ctx); int rc; rc = gpgsm_add_to_certlist (ctrl, line, 1, &ctrl->server_local->signerlist, 0); if (rc) { gpgsm_status2 (ctrl, STATUS_INV_SGNR, get_inv_recpsgnr_code (rc), line, NULL); /* For compatibiliy reasons we also issue the old code after the new one. */ gpgsm_status2 (ctrl, STATUS_INV_RECP, get_inv_recpsgnr_code (rc), line, NULL); } return rc; } static const char hlp_encrypt[] = "ENCRYPT \n" "\n" "Do the actual encryption process. Takes the plaintext from the INPUT\n" "command, writes to the ciphertext to the file descriptor set with\n" "the OUTPUT command, take the recipients form all the recipients set\n" "so far. If this command fails the clients should try to delete all\n" "output currently done or otherwise mark it as invalid. GPGSM does\n" "ensure that there won't be any security problem with leftover data\n" "on the output in this case.\n" "\n" "This command should in general not fail, as all necessary checks\n" "have been done while setting the recipients. The input and output\n" "pipes are closed."; static gpg_error_t cmd_encrypt (assuan_context_t ctx, char *line) { ctrl_t ctrl = assuan_get_pointer (ctx); certlist_t cl; int inp_fd, out_fd; estream_t out_fp; int rc; (void)line; inp_fd = translate_sys2libc_fd (assuan_get_input_fd (ctx), 0); if (inp_fd == -1) return set_error (GPG_ERR_ASS_NO_INPUT, NULL); out_fd = translate_sys2libc_fd (assuan_get_output_fd (ctx), 1); if (out_fd == -1) return set_error (GPG_ERR_ASS_NO_OUTPUT, NULL); out_fp = es_fdopen_nc (out_fd, "w"); if (!out_fp) return set_error (gpg_err_code_from_syserror (), "fdopen() failed"); /* Now add all encrypt-to marked recipients from the default list. */ rc = 0; if (!opt.no_encrypt_to && !ctrl->server_local->no_encrypt_to) { for (cl=ctrl->server_local->default_recplist; !rc && cl; cl = cl->next) if (cl->is_encrypt_to) rc = gpgsm_add_cert_to_certlist (ctrl, cl->cert, &ctrl->server_local->recplist, 1); } if (!rc) rc = ctrl->audit? 0 : start_audit_session (ctrl); if (!rc) rc = gpgsm_encrypt (assuan_get_pointer (ctx), ctrl->server_local->recplist, inp_fd, out_fp); es_fclose (out_fp); gpgsm_release_certlist (ctrl->server_local->recplist); ctrl->server_local->recplist = NULL; /* Close and reset the fd */ close_message_fd (ctrl); assuan_close_input_fd (ctx); assuan_close_output_fd (ctx); return rc; } static const char hlp_decrypt[] = "DECRYPT\n" "\n" "This performs the decrypt operation after doing some check on the\n" "internal state. (e.g. that only needed data has been set). Because\n" "it utilizes the GPG-Agent for the session key decryption, there is\n" "no need to ask the client for a protecting passphrase - GPG-Agent\n" "does take care of this by requesting this from the user."; static gpg_error_t cmd_decrypt (assuan_context_t ctx, char *line) { ctrl_t ctrl = assuan_get_pointer (ctx); int inp_fd, out_fd; estream_t out_fp; int rc; (void)line; inp_fd = translate_sys2libc_fd (assuan_get_input_fd (ctx), 0); if (inp_fd == -1) return set_error (GPG_ERR_ASS_NO_INPUT, NULL); out_fd = translate_sys2libc_fd (assuan_get_output_fd (ctx), 1); if (out_fd == -1) return set_error (GPG_ERR_ASS_NO_OUTPUT, NULL); out_fp = es_fdopen_nc (out_fd, "w"); if (!out_fp) return set_error (gpg_err_code_from_syserror (), "fdopen() failed"); rc = start_audit_session (ctrl); if (!rc) rc = gpgsm_decrypt (ctrl, inp_fd, out_fp); es_fclose (out_fp); /* Close and reset the fds. */ close_message_fd (ctrl); assuan_close_input_fd (ctx); assuan_close_output_fd (ctx); return rc; } static const char hlp_verify[] = "VERIFY\n" "\n" "This does a verify operation on the message send to the input FD.\n" "The result is written out using status lines. If an output FD was\n" "given, the signed text will be written to that.\n" "\n" "If the signature is a detached one, the server will inquire about\n" "the signed material and the client must provide it."; static gpg_error_t cmd_verify (assuan_context_t ctx, char *line) { int rc; ctrl_t ctrl = assuan_get_pointer (ctx); int fd = translate_sys2libc_fd (assuan_get_input_fd (ctx), 0); int out_fd = translate_sys2libc_fd (assuan_get_output_fd (ctx), 1); estream_t out_fp = NULL; (void)line; if (fd == -1) return set_error (GPG_ERR_ASS_NO_INPUT, NULL); if (out_fd != -1) { out_fp = es_fdopen_nc (out_fd, "w"); if (!out_fp) return set_error (gpg_err_code_from_syserror (), "fdopen() failed"); } rc = start_audit_session (ctrl); if (!rc) rc = gpgsm_verify (assuan_get_pointer (ctx), fd, ctrl->server_local->message_fd, out_fp); es_fclose (out_fp); /* Close and reset the fd. */ close_message_fd (ctrl); assuan_close_input_fd (ctx); assuan_close_output_fd (ctx); return rc; } static const char hlp_sign[] = "SIGN [--detached]\n" "\n" "Sign the data set with the INPUT command and write it to the sink\n" "set by OUTPUT. With \"--detached\", a detached signature is\n" "created (surprise)."; static gpg_error_t cmd_sign (assuan_context_t ctx, char *line) { ctrl_t ctrl = assuan_get_pointer (ctx); int inp_fd, out_fd; estream_t out_fp; int detached; int rc; inp_fd = translate_sys2libc_fd (assuan_get_input_fd (ctx), 0); if (inp_fd == -1) return set_error (GPG_ERR_ASS_NO_INPUT, NULL); out_fd = translate_sys2libc_fd (assuan_get_output_fd (ctx), 1); if (out_fd == -1) return set_error (GPG_ERR_ASS_NO_OUTPUT, NULL); detached = has_option (line, "--detached"); out_fp = es_fdopen_nc (out_fd, "w"); if (!out_fp) return set_error (GPG_ERR_ASS_GENERAL, "fdopen() failed"); rc = start_audit_session (ctrl); if (!rc) rc = gpgsm_sign (assuan_get_pointer (ctx), ctrl->server_local->signerlist, inp_fd, detached, out_fp); es_fclose (out_fp); /* close and reset the fd */ close_message_fd (ctrl); assuan_close_input_fd (ctx); assuan_close_output_fd (ctx); return rc; } static const char hlp_import[] = "IMPORT [--re-import]\n" "\n" "Import the certificates read form the input-fd, return status\n" "message for each imported one. The import checks the validity of\n" "the certificate but not of the entire chain. It is possible to\n" "import expired certificates.\n" "\n" "With the option --re-import the input data is expected to a be a LF\n" "separated list of fingerprints. The command will re-import these\n" "certificates, meaning that they are made permanent by removing\n" "their ephemeral flag."; static gpg_error_t cmd_import (assuan_context_t ctx, char *line) { ctrl_t ctrl = assuan_get_pointer (ctx); int rc; int fd = translate_sys2libc_fd (assuan_get_input_fd (ctx), 0); int reimport = has_option (line, "--re-import"); (void)line; if (fd == -1) return set_error (GPG_ERR_ASS_NO_INPUT, NULL); rc = gpgsm_import (assuan_get_pointer (ctx), fd, reimport); /* close and reset the fd */ close_message_fd (ctrl); assuan_close_input_fd (ctx); assuan_close_output_fd (ctx); return rc; } static const char hlp_export[] = "EXPORT [--data [--armor|--base64]] [--secret [--(raw|pkcs12)] [--] \n" "\n" "Export the certificates selected by PATTERN. With --data the output\n" "is returned using Assuan D lines; the default is to use the sink given\n" "by the last \"OUTPUT\" command. The options --armor or --base64 encode \n" "the output using the PEM respective a plain base-64 format; the default\n" "is a binary format which is only suitable for a single certificate.\n" "With --secret the secret key is exported using the PKCS#8 format,\n" "with --raw using PKCS#1, and with --pkcs12 as full PKCS#12 container."; static gpg_error_t cmd_export (assuan_context_t ctx, char *line) { ctrl_t ctrl = assuan_get_pointer (ctx); char *p; strlist_t list, sl; int use_data; int opt_secret; int opt_raw = 0; int opt_pkcs12 = 0; use_data = has_option (line, "--data"); if (use_data) { /* We need to override any possible setting done by an OUTPUT command. */ ctrl->create_pem = has_option (line, "--armor"); ctrl->create_base64 = has_option (line, "--base64"); } opt_secret = has_option (line, "--secret"); if (opt_secret) { opt_raw = has_option (line, "--raw"); opt_pkcs12 = has_option (line, "--pkcs12"); } line = skip_options (line); /* Break the line down into an strlist_t. */ list = NULL; for (p=line; *p; line = p) { while (*p && *p != ' ') p++; if (*p) *p++ = 0; if (*line) { sl = xtrymalloc (sizeof *sl + strlen (line)); if (!sl) { free_strlist (list); return out_of_core (); } sl->flags = 0; strcpy_escaped_plus (sl->d, line); sl->next = list; list = sl; } } if (opt_secret) { if (!list || !*list->d) return set_error (GPG_ERR_NO_DATA, "No key given"); if (list->next) return set_error (GPG_ERR_TOO_MANY, "Only one key allowed"); } if (use_data) { estream_t stream; stream = es_fopencookie (ctx, "w", data_line_cookie_functions); if (!stream) { free_strlist (list); return set_error (GPG_ERR_ASS_GENERAL, "error setting up a data stream"); } if (opt_secret) gpgsm_p12_export (ctrl, list->d, stream, opt_raw? 2 : opt_pkcs12 ? 0 : 1); else gpgsm_export (ctrl, list, stream); es_fclose (stream); } else { int fd = translate_sys2libc_fd (assuan_get_output_fd (ctx), 1); estream_t out_fp; if (fd == -1) { free_strlist (list); return set_error (GPG_ERR_ASS_NO_OUTPUT, NULL); } out_fp = es_fdopen_nc (fd, "w"); if (!out_fp) { free_strlist (list); return set_error (gpg_err_code_from_syserror (), "fdopen() failed"); } if (opt_secret) gpgsm_p12_export (ctrl, list->d, out_fp, opt_raw? 2 : opt_pkcs12 ? 0 : 1); else gpgsm_export (ctrl, list, out_fp); es_fclose (out_fp); } free_strlist (list); /* Close and reset the fds. */ close_message_fd (ctrl); assuan_close_input_fd (ctx); assuan_close_output_fd (ctx); return 0; } static const char hlp_delkeys[] = "DELKEYS \n" "\n" "Delete the certificates specified by PATTERNS. Each pattern shall be\n" "a percent-plus escaped certificate specification. Usually a\n" "fingerprint will be used for this."; static gpg_error_t cmd_delkeys (assuan_context_t ctx, char *line) { ctrl_t ctrl = assuan_get_pointer (ctx); char *p; strlist_t list, sl; int rc; /* break the line down into an strlist_t */ list = NULL; for (p=line; *p; line = p) { while (*p && *p != ' ') p++; if (*p) *p++ = 0; if (*line) { sl = xtrymalloc (sizeof *sl + strlen (line)); if (!sl) { free_strlist (list); return out_of_core (); } sl->flags = 0; strcpy_escaped_plus (sl->d, line); sl->next = list; list = sl; } } rc = gpgsm_delete (ctrl, list); free_strlist (list); /* close and reset the fd */ close_message_fd (ctrl); assuan_close_input_fd (ctx); assuan_close_output_fd (ctx); return rc; } static const char hlp_output[] = "OUTPUT FD[=]\n" "\n" "Set the file descriptor to write the output data to N. If N is not\n" "given and the operating system supports file descriptor passing, the\n" "file descriptor currently in flight will be used. See also the\n" "\"INPUT\" and \"MESSAGE\" commands."; static const char hlp_input[] = "INPUT FD[=]\n" "\n" "Set the file descriptor to read the input data to N. If N is not\n" "given and the operating system supports file descriptor passing, the\n" "file descriptor currently in flight will be used. See also the\n" "\"MESSAGE\" and \"OUTPUT\" commands."; static const char hlp_message[] = "MESSAGE FD[=]\n" "\n" "Set the file descriptor to read the message for a detached\n" "signatures to N. If N is not given and the operating system\n" "supports file descriptor passing, the file descriptor currently in\n" "flight will be used. See also the \"INPUT\" and \"OUTPUT\" commands."; static gpg_error_t cmd_message (assuan_context_t ctx, char *line) { int rc; gnupg_fd_t sysfd; int fd; ctrl_t ctrl = assuan_get_pointer (ctx); rc = assuan_command_parse_fd (ctx, line, &sysfd); if (rc) return rc; #ifdef HAVE_W32CE_SYSTEM sysfd = _assuan_w32ce_finish_pipe ((int)sysfd, 0); if (sysfd == INVALID_HANDLE_VALUE) return set_error (gpg_err_code_from_syserror (), "rvid conversion failed"); #endif fd = translate_sys2libc_fd (sysfd, 0); if (fd == -1) return set_error (GPG_ERR_ASS_NO_INPUT, NULL); ctrl->server_local->message_fd = fd; return 0; } static const char hlp_listkeys[] = "LISTKEYS []\n" "LISTSECRETKEYS []\n" "DUMPKEYS []\n" "DUMPSECRETKEYS []\n" "\n" "List all certificates or only those specified by PATTERNS. Each\n" "pattern shall be a percent-plus escaped certificate specification.\n" "The \"SECRET\" versions of the command filter the output to include\n" "only certificates where the secret key is available or a corresponding\n" "smartcard has been registered. The \"DUMP\" versions of the command\n" "are only useful for debugging. The output format is a percent escaped\n" "colon delimited listing as described in the manual.\n" "\n" "These \"OPTION\" command keys effect the output::\n" "\n" " \"list-mode\" set to 0: List only local certificates (default).\n" " 1: Ditto.\n" " 2: List only external certificates.\n" " 3: List local and external certificates.\n" "\n" " \"with-validation\" set to true: Validate each certificate.\n" "\n" " \"with-ephemeral-key\" set to true: Always include ephemeral\n" " certificates.\n" "\n" " \"list-to-output\" set to true: Write output to the file descriptor\n" " given by the last \"OUTPUT\" command."; static int do_listkeys (assuan_context_t ctx, char *line, int mode) { ctrl_t ctrl = assuan_get_pointer (ctx); estream_t fp; char *p; strlist_t list, sl; unsigned int listmode; gpg_error_t err; /* Break the line down into an strlist. */ list = NULL; for (p=line; *p; line = p) { while (*p && *p != ' ') p++; if (*p) *p++ = 0; if (*line) { sl = xtrymalloc (sizeof *sl + strlen (line)); if (!sl) { free_strlist (list); return out_of_core (); } sl->flags = 0; strcpy_escaped_plus (sl->d, line); sl->next = list; list = sl; } } if (ctrl->server_local->list_to_output) { int outfd = translate_sys2libc_fd (assuan_get_output_fd (ctx), 1); if ( outfd == -1 ) return set_error (GPG_ERR_ASS_NO_OUTPUT, NULL); fp = es_fdopen_nc (outfd, "w"); if (!fp) return set_error (gpg_err_code_from_syserror (), "es_fdopen() failed"); } else { fp = es_fopencookie (ctx, "w", data_line_cookie_functions); if (!fp) return set_error (GPG_ERR_ASS_GENERAL, "error setting up a data stream"); } ctrl->with_colons = 1; listmode = mode; if (ctrl->server_local->list_internal) listmode |= (1<<6); if (ctrl->server_local->list_external) listmode |= (1<<7); err = gpgsm_list_keys (assuan_get_pointer (ctx), list, fp, listmode); free_strlist (list); es_fclose (fp); if (ctrl->server_local->list_to_output) assuan_close_output_fd (ctx); return err; } static gpg_error_t cmd_listkeys (assuan_context_t ctx, char *line) { return do_listkeys (ctx, line, 3); } static gpg_error_t cmd_dumpkeys (assuan_context_t ctx, char *line) { return do_listkeys (ctx, line, 259); } static gpg_error_t cmd_listsecretkeys (assuan_context_t ctx, char *line) { return do_listkeys (ctx, line, 2); } static gpg_error_t cmd_dumpsecretkeys (assuan_context_t ctx, char *line) { return do_listkeys (ctx, line, 258); } static const char hlp_genkey[] = "GENKEY\n" "\n" "Read the parameters in native format from the input fd and write a\n" "certificate request to the output."; static gpg_error_t cmd_genkey (assuan_context_t ctx, char *line) { ctrl_t ctrl = assuan_get_pointer (ctx); int inp_fd, out_fd; estream_t in_stream, out_stream; int rc; (void)line; inp_fd = translate_sys2libc_fd (assuan_get_input_fd (ctx), 0); if (inp_fd == -1) return set_error (GPG_ERR_ASS_NO_INPUT, NULL); out_fd = translate_sys2libc_fd (assuan_get_output_fd (ctx), 1); if (out_fd == -1) return set_error (GPG_ERR_ASS_NO_OUTPUT, NULL); in_stream = es_fdopen_nc (inp_fd, "r"); if (!in_stream) return set_error (GPG_ERR_ASS_GENERAL, "es_fdopen failed"); out_stream = es_fdopen_nc (out_fd, "w"); if (!out_stream) { es_fclose (in_stream); return set_error (gpg_err_code_from_syserror (), "fdopen() failed"); } rc = gpgsm_genkey (ctrl, in_stream, out_stream); es_fclose (out_stream); es_fclose (in_stream); /* close and reset the fds */ assuan_close_input_fd (ctx); assuan_close_output_fd (ctx); return rc; } static const char hlp_getauditlog[] = "GETAUDITLOG [--data] [--html]\n" "\n" "If --data is used, the output is send using D-lines and not to the\n" "file descriptor given by an OUTPUT command.\n" "\n" "If --html is used the output is formatted as an XHTML block. This is\n" "designed to be incorporated into a HTML document."; static gpg_error_t cmd_getauditlog (assuan_context_t ctx, char *line) { ctrl_t ctrl = assuan_get_pointer (ctx); int out_fd; estream_t out_stream; int opt_data, opt_html; int rc; opt_data = has_option (line, "--data"); opt_html = has_option (line, "--html"); /* Not needed: line = skip_options (line); */ if (!ctrl->audit) return gpg_error (GPG_ERR_NO_DATA); if (opt_data) { out_stream = es_fopencookie (ctx, "w", data_line_cookie_functions); if (!out_stream) return set_error (GPG_ERR_ASS_GENERAL, "error setting up a data stream"); } else { out_fd = translate_sys2libc_fd (assuan_get_output_fd (ctx), 1); if (out_fd == -1) return set_error (GPG_ERR_ASS_NO_OUTPUT, NULL); out_stream = es_fdopen_nc (out_fd, "w"); if (!out_stream) { return set_error (GPG_ERR_ASS_GENERAL, "es_fdopen() failed"); } } audit_print_result (ctrl->audit, out_stream, opt_html); rc = 0; es_fclose (out_stream); /* Close and reset the fd. */ if (!opt_data) assuan_close_output_fd (ctx); return rc; } static const char hlp_getinfo[] = "GETINFO \n" "\n" "Multipurpose function to return a variety of information.\n" "Supported values for WHAT are:\n" "\n" " version - Return the version of the program.\n" " pid - Return the process id of the server.\n" " agent-check - Return success if the agent is running.\n" " cmd_has_option CMD OPT\n" " - Returns OK if the command CMD implements the option OPT.\n" " offline - Returns OK if the conenction is in offline mode."; static gpg_error_t cmd_getinfo (assuan_context_t ctx, char *line) { ctrl_t ctrl = assuan_get_pointer (ctx); int rc = 0; if (!strcmp (line, "version")) { const char *s = VERSION; rc = assuan_send_data (ctx, s, strlen (s)); } else if (!strcmp (line, "pid")) { char numbuf[50]; snprintf (numbuf, sizeof numbuf, "%lu", (unsigned long)getpid ()); rc = assuan_send_data (ctx, numbuf, strlen (numbuf)); } else if (!strcmp (line, "agent-check")) { rc = gpgsm_agent_send_nop (ctrl); } else if (!strncmp (line, "cmd_has_option", 14) && (line[14] == ' ' || line[14] == '\t' || !line[14])) { char *cmd, *cmdopt; line += 14; while (*line == ' ' || *line == '\t') line++; if (!*line) rc = gpg_error (GPG_ERR_MISSING_VALUE); else { cmd = line; while (*line && (*line != ' ' && *line != '\t')) line++; if (!*line) rc = gpg_error (GPG_ERR_MISSING_VALUE); else { *line++ = 0; while (*line == ' ' || *line == '\t') line++; if (!*line) rc = gpg_error (GPG_ERR_MISSING_VALUE); else { cmdopt = line; if (!command_has_option (cmd, cmdopt)) rc = gpg_error (GPG_ERR_GENERAL); } } } } else if (!strcmp (line, "offline")) { rc = ctrl->offline? 0 : gpg_error (GPG_ERR_GENERAL); } else rc = set_error (GPG_ERR_ASS_PARAMETER, "unknown value for WHAT"); return rc; } static const char hlp_passwd[] = "PASSWD \n" "\n" "Change the passphrase of the secret key for USERID."; static gpg_error_t cmd_passwd (assuan_context_t ctx, char *line) { ctrl_t ctrl = assuan_get_pointer (ctx); gpg_error_t err; ksba_cert_t cert = NULL; char *grip = NULL; line = skip_options (line); err = gpgsm_find_cert (line, NULL, &cert); if (err) ; else if (!(grip = gpgsm_get_keygrip_hexstring (cert))) err = gpg_error (GPG_ERR_INTERNAL); else { char *desc = gpgsm_format_keydesc (cert); err = gpgsm_agent_passwd (ctrl, grip, desc); xfree (desc); } xfree (grip); ksba_cert_release (cert); return err; } /* Return true if the command CMD implements the option OPT. */ static int command_has_option (const char *cmd, const char *cmdopt) { if (!strcmp (cmd, "IMPORT")) { if (!strcmp (cmdopt, "re-import")) return 1; } return 0; } /* Tell the assuan library about our commands */ static int register_commands (assuan_context_t ctx) { static struct { const char *name; assuan_handler_t handler; const char * const help; } table[] = { { "RECIPIENT", cmd_recipient, hlp_recipient }, { "SIGNER", cmd_signer, hlp_signer }, { "ENCRYPT", cmd_encrypt, hlp_encrypt }, { "DECRYPT", cmd_decrypt, hlp_decrypt }, { "VERIFY", cmd_verify, hlp_verify }, { "SIGN", cmd_sign, hlp_sign }, { "IMPORT", cmd_import, hlp_import }, { "EXPORT", cmd_export, hlp_export }, { "INPUT", NULL, hlp_input }, { "OUTPUT", NULL, hlp_output }, { "MESSAGE", cmd_message, hlp_message }, { "LISTKEYS", cmd_listkeys, hlp_listkeys }, { "DUMPKEYS", cmd_dumpkeys, hlp_listkeys }, { "LISTSECRETKEYS",cmd_listsecretkeys, hlp_listkeys }, { "DUMPSECRETKEYS",cmd_dumpsecretkeys, hlp_listkeys }, { "GENKEY", cmd_genkey, hlp_genkey }, { "DELKEYS", cmd_delkeys, hlp_delkeys }, { "GETAUDITLOG", cmd_getauditlog, hlp_getauditlog }, { "GETINFO", cmd_getinfo, hlp_getinfo }, { "PASSWD", cmd_passwd, hlp_passwd }, { NULL } }; int i, rc; for (i=0; table[i].name; i++) { rc = assuan_register_command (ctx, table[i].name, table[i].handler, table[i].help); if (rc) return rc; } return 0; } /* Startup the server. DEFAULT_RECPLIST is the list of recipients as set from the command line or config file. We only require those marked as encrypt-to. */ void gpgsm_server (certlist_t default_recplist) { int rc; assuan_fd_t filedes[2]; assuan_context_t ctx; struct server_control_s ctrl; static const char hello[] = ("GNU Privacy Guard's S/M server " VERSION " ready"); memset (&ctrl, 0, sizeof ctrl); gpgsm_init_default_ctrl (&ctrl); /* We use a pipe based server so that we can work from scripts. assuan_init_pipe_server will automagically detect when we are called with a socketpair and ignore FILEDES in this case. */ #ifdef HAVE_W32CE_SYSTEM #define SERVER_STDIN es_fileno(es_stdin) #define SERVER_STDOUT es_fileno(es_stdout) #else #define SERVER_STDIN 0 #define SERVER_STDOUT 1 #endif filedes[0] = assuan_fdopen (SERVER_STDIN); filedes[1] = assuan_fdopen (SERVER_STDOUT); rc = assuan_new (&ctx); if (rc) { log_error ("failed to allocate assuan context: %s\n", gpg_strerror (rc)); gpgsm_exit (2); } rc = assuan_init_pipe_server (ctx, filedes); if (rc) { log_error ("failed to initialize the server: %s\n", gpg_strerror (rc)); gpgsm_exit (2); } rc = register_commands (ctx); if (rc) { log_error ("failed to the register commands with Assuan: %s\n", gpg_strerror(rc)); gpgsm_exit (2); } if (opt.verbose || opt.debug) { char *tmp; /* Fixme: Use the really used socket name. */ if (asprintf (&tmp, "Home: %s\n" "Config: %s\n" "DirmngrInfo: %s\n" "%s", gnupg_homedir (), opt.config_filename, - (dirmngr_user_socket_name () - ? dirmngr_user_socket_name () - : dirmngr_sys_socket_name ()), + dirmngr_socket_name (), hello) > 0) { assuan_set_hello_line (ctx, tmp); free (tmp); } } else assuan_set_hello_line (ctx, hello); assuan_register_reset_notify (ctx, reset_notify); assuan_register_input_notify (ctx, input_notify); assuan_register_output_notify (ctx, output_notify); assuan_register_option_handler (ctx, option_handler); assuan_set_pointer (ctx, &ctrl); ctrl.server_local = xcalloc (1, sizeof *ctrl.server_local); ctrl.server_local->assuan_ctx = ctx; ctrl.server_local->message_fd = -1; ctrl.server_local->list_internal = 1; ctrl.server_local->list_external = 0; ctrl.server_local->default_recplist = default_recplist; for (;;) { rc = assuan_accept (ctx); if (rc == -1) { break; } else if (rc) { log_info ("Assuan accept problem: %s\n", gpg_strerror (rc)); break; } rc = assuan_process (ctx); if (rc) { log_info ("Assuan processing failed: %s\n", gpg_strerror (rc)); continue; } } gpgsm_release_certlist (ctrl.server_local->recplist); ctrl.server_local->recplist = NULL; gpgsm_release_certlist (ctrl.server_local->signerlist); ctrl.server_local->signerlist = NULL; xfree (ctrl.server_local); audit_release (ctrl.audit); ctrl.audit = NULL; assuan_release (ctx); } gpg_error_t gpgsm_status2 (ctrl_t ctrl, int no, ...) { gpg_error_t err = 0; va_list arg_ptr; const char *text; va_start (arg_ptr, no); if (ctrl->no_server && ctrl->status_fd == -1) ; /* No status wanted. */ else if (ctrl->no_server) { if (!statusfp) { if (ctrl->status_fd == 1) statusfp = stdout; else if (ctrl->status_fd == 2) statusfp = stderr; else statusfp = fdopen (ctrl->status_fd, "w"); if (!statusfp) { log_fatal ("can't open fd %d for status output: %s\n", ctrl->status_fd, strerror(errno)); } } fputs ("[GNUPG:] ", statusfp); fputs (get_status_string (no), statusfp); while ( (text = va_arg (arg_ptr, const char*) )) { putc ( ' ', statusfp ); for (; *text; text++) { if (*text == '\n') fputs ( "\\n", statusfp ); else if (*text == '\r') fputs ( "\\r", statusfp ); else putc ( *(const byte *)text, statusfp ); } } putc ('\n', statusfp); fflush (statusfp); } else { assuan_context_t ctx = ctrl->server_local->assuan_ctx; char buf[950], *p; size_t n; p = buf; n = 0; while ( (text = va_arg (arg_ptr, const char *)) ) { if (n) { *p++ = ' '; n++; } for ( ; *text && n < DIM (buf)-2; n++) *p++ = *text++; } *p = 0; err = assuan_write_status (ctx, get_status_string (no), buf); } va_end (arg_ptr); return err; } gpg_error_t gpgsm_status (ctrl_t ctrl, int no, const char *text) { return gpgsm_status2 (ctrl, no, text, NULL); } gpg_error_t gpgsm_status_with_err_code (ctrl_t ctrl, int no, const char *text, gpg_err_code_t ec) { char buf[30]; sprintf (buf, "%u", (unsigned int)ec); if (text) return gpgsm_status2 (ctrl, no, text, buf, NULL); else return gpgsm_status2 (ctrl, no, buf, NULL); } /* Helper to notify the client about Pinentry events. Because that might disturb some older clients, this is only done when enabled via an option. Returns an gpg error code. */ gpg_error_t gpgsm_proxy_pinentry_notify (ctrl_t ctrl, const unsigned char *line) { if (!ctrl || !ctrl->server_local || !ctrl->server_local->allow_pinentry_notify) return 0; return assuan_inquire (ctrl->server_local->assuan_ctx, line, NULL, NULL, 0); } diff --git a/tools/gpgconf.c b/tools/gpgconf.c index 69ea9c9cf..221e3e24f 100644 --- a/tools/gpgconf.c +++ b/tools/gpgconf.c @@ -1,493 +1,483 @@ /* gpgconf.c - Configuration utility for GnuPG * Copyright (C) 2003, 2007, 2009, 2011 Free Software Foundation, Inc. * * This file is part of GnuPG. * * GnuPG is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3 of the License, or * (at your option) any later version. * * GnuPG 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 General Public License * along with this program; if not, see . */ #include #include #include #include #include #include #include "gpgconf.h" #include "i18n.h" #include "sysutils.h" #include "../common/init.h" /* Constants to identify the commands and options. */ enum cmd_and_opt_values { aNull = 0, oDryRun = 'n', oOutput = 'o', oQuiet = 'q', oVerbose = 'v', oRuntime = 'r', oComponent = 'c', oNull = '0', oNoVerbose = 500, oHomedir, aListComponents, aCheckPrograms, aListOptions, aChangeOptions, aCheckOptions, aApplyDefaults, aListConfig, aCheckConfig, aListDirs, aLaunch, aKill, aCreateSocketDir, aRemoveSocketDir, aReload }; /* The list of commands and options. */ static ARGPARSE_OPTS opts[] = { { 300, NULL, 0, N_("@Commands:\n ") }, { aListComponents, "list-components", 256, N_("list all components") }, { aCheckPrograms, "check-programs", 256, N_("check all programs") }, { aListOptions, "list-options", 256, N_("|COMPONENT|list options") }, { aChangeOptions, "change-options", 256, N_("|COMPONENT|change options") }, { aCheckOptions, "check-options", 256, N_("|COMPONENT|check options") }, { aApplyDefaults, "apply-defaults", 256, N_("apply global default values") }, { aListDirs, "list-dirs", 256, N_("get the configuration directories for @GPGCONF@") }, { aListConfig, "list-config", 256, N_("list global configuration file") }, { aCheckConfig, "check-config", 256, N_("check global configuration file") }, { aReload, "reload", 256, N_("reload all or a given component")}, { aLaunch, "launch", 256, N_("launch a given component")}, { aKill, "kill", 256, N_("kill a given component")}, { aCreateSocketDir, "create-socketdir", 256, "@"}, { aRemoveSocketDir, "remove-socketdir", 256, "@"}, { 301, NULL, 0, N_("@\nOptions:\n ") }, { oOutput, "output", 2, N_("use as output file") }, { oVerbose, "verbose", 0, N_("verbose") }, { oQuiet, "quiet", 0, N_("quiet") }, { oDryRun, "dry-run", 0, N_("do not make any changes") }, { oRuntime, "runtime", 0, N_("activate changes at runtime, if possible") }, /* hidden options */ { oHomedir, "homedir", 2, "@" }, { oNull, "null", 0, "@" }, { oNoVerbose, "no-verbose", 0, "@"}, {0} }; /* Print usage information and and provide strings for help. */ static const char * my_strusage( int level ) { const char *p; switch (level) { case 11: p = "@GPGCONF@ (@GNUPG@)"; break; case 13: p = VERSION; break; case 17: p = PRINTABLE_OS_NAME; break; case 19: p = _("Please report bugs to <@EMAIL@>.\n"); break; case 1: case 40: p = _("Usage: @GPGCONF@ [options] (-h for help)"); break; case 41: p = _("Syntax: @GPGCONF@ [options]\n" "Manage configuration options for tools of the @GNUPG@ system\n"); break; default: p = NULL; break; } return p; } /* Return the fp for the output. This is usually stdout unless --output has been used. In the latter case this function opens that file. */ static estream_t get_outfp (estream_t *fp) { if (!*fp) { if (opt.outfile) { *fp = es_fopen (opt.outfile, "w"); if (!*fp) gc_error (1, errno, "can not open '%s'", opt.outfile); } else *fp = es_stdout; } return *fp; } static void list_dirs (estream_t fp, char **names) { static struct { const char *name; const char *(*fnc)(void); const char *extra; - int special; } list[] = { { "sysconfdir", gnupg_sysconfdir, NULL }, { "bindir", gnupg_bindir, NULL }, { "libexecdir", gnupg_libexecdir, NULL }, { "libdir", gnupg_libdir, NULL }, { "datadir", gnupg_datadir, NULL }, { "localedir", gnupg_localedir, NULL }, - { "dirmngr-socket", dirmngr_user_socket_name, NULL, 1 }, - { "dirmngr-socket", dirmngr_sys_socket_name, NULL, 2 }, - { "dirmngr-sys-socket", dirmngr_sys_socket_name, NULL, 1 }, + { "dirmngr-socket", dirmngr_socket_name, NULL,}, { "agent-ssh-socket", gnupg_socketdir, GPG_AGENT_SSH_SOCK_NAME }, { "agent-socket", gnupg_socketdir, GPG_AGENT_SOCK_NAME }, { "homedir", gnupg_homedir, NULL } }; int idx, j; char *tmp; const char *s; for (idx = 0; idx < DIM (list); idx++) { - if (list[idx].special == 1 && dirmngr_user_socket_name ()) - ; - else if (list[idx].special == 2 && !dirmngr_user_socket_name ()) - ; - else if (list[idx].special == 1 || list[idx].special == 2) - continue; - s = list[idx].fnc (); if (list[idx].extra) { tmp = make_filename (s, list[idx].extra, NULL); s = tmp; } else tmp = NULL; if (!names) es_fprintf (fp, "%s:%s\n", list[idx].name, gc_percent_escape (s)); else { for (j=0; names[j]; j++) if (!strcmp (names[j], list[idx].name)) { es_fputs (s, fp); es_putc (opt.null? '\0':'\n', fp); } } xfree (tmp); } } /* gpgconf main. */ int main (int argc, char **argv) { ARGPARSE_ARGS pargs; const char *fname; int no_more_options = 0; enum cmd_and_opt_values cmd = 0; estream_t outfp = NULL; early_system_init (); gnupg_reopen_std (GPGCONF_NAME); set_strusage (my_strusage); log_set_prefix (GPGCONF_NAME, GPGRT_LOG_WITH_PREFIX); /* Make sure that our subsystems are ready. */ i18n_init(); init_common_subsystems (&argc, &argv); /* Parse the command line. */ pargs.argc = &argc; pargs.argv = &argv; pargs.flags = 1; /* Do not remove the args. */ while (!no_more_options && optfile_parse (NULL, NULL, NULL, &pargs, opts)) { switch (pargs.r_opt) { case oOutput: opt.outfile = pargs.r.ret_str; break; case oQuiet: opt.quiet = 1; break; case oDryRun: opt.dry_run = 1; break; case oRuntime: opt.runtime = 1; break; case oVerbose: opt.verbose++; break; case oNoVerbose: opt.verbose = 0; break; case oHomedir: gnupg_set_homedir (pargs.r.ret_str); break; case oNull: opt.null = 1; break; case aListDirs: case aListComponents: case aCheckPrograms: case aListOptions: case aChangeOptions: case aCheckOptions: case aApplyDefaults: case aListConfig: case aCheckConfig: case aReload: case aLaunch: case aKill: case aCreateSocketDir: case aRemoveSocketDir: cmd = pargs.r_opt; break; default: pargs.err = 2; break; } } if (log_get_errorcount (0)) exit (2); /* Print a warning if an argument looks like an option. */ if (!opt.quiet && !(pargs.flags & ARGPARSE_FLAG_STOP_SEEN)) { int i; for (i=0; i < argc; i++) if (argv[i][0] == '-' && argv[i][1] == '-') log_info (_("Note: '%s' is not considered an option\n"), argv[i]); } fname = argc ? *argv : NULL; switch (cmd) { case aListComponents: default: /* List all components. */ gc_component_list_components (get_outfp (&outfp)); break; case aCheckPrograms: /* Check all programs. */ gc_check_programs (get_outfp (&outfp)); break; case aListOptions: case aChangeOptions: case aCheckOptions: if (!fname) { es_fprintf (es_stderr, _("usage: %s [options] "), GPGCONF_NAME); es_putc ('\n', es_stderr); es_fputs (_("Need one component argument"), es_stderr); es_putc ('\n', es_stderr); exit (2); } else { int idx = gc_component_find (fname); if (idx < 0) { es_fputs (_("Component not found"), es_stderr); es_putc ('\n', es_stderr); exit (1); } if (cmd == aCheckOptions) gc_component_check_options (idx, get_outfp (&outfp), NULL); else { gc_component_retrieve_options (idx); if (gc_process_gpgconf_conf (NULL, 1, 0, NULL)) exit (1); if (cmd == aListOptions) gc_component_list_options (idx, get_outfp (&outfp)); else if (cmd == aChangeOptions) gc_component_change_options (idx, es_stdin, get_outfp (&outfp)); } } break; case aLaunch: case aKill: if (!fname) { es_fprintf (es_stderr, _("usage: %s [options] "), GPGCONF_NAME); es_putc ('\n', es_stderr); es_fputs (_("Need one component argument"), es_stderr); es_putc ('\n', es_stderr); exit (2); } else { /* Launch/Kill a given component. */ int idx; idx = gc_component_find (fname); if (idx < 0) { es_fputs (_("Component not found"), es_stderr); es_putc ('\n', es_stderr); exit (1); } else if (cmd == aLaunch) { if (gc_component_launch (idx)) exit (1); } else { /* We don't error out if the kill failed because this command should do nothing if the component is not running. */ gc_component_kill (idx); } } break; case aReload: if (!fname) { /* Reload all. */ gc_component_reload (-1); } else { /* Reload given component. */ int idx; idx = gc_component_find (fname); if (idx < 0) { es_fputs (_("Component not found"), es_stderr); es_putc ('\n', es_stderr); exit (1); } else { gc_component_reload (idx); } } break; case aListConfig: if (gc_process_gpgconf_conf (fname, 0, 0, get_outfp (&outfp))) exit (1); break; case aCheckConfig: if (gc_process_gpgconf_conf (fname, 0, 0, NULL)) exit (1); break; case aApplyDefaults: if (fname) { es_fprintf (es_stderr, _("usage: %s [options] "), GPGCONF_NAME); es_putc ('\n', es_stderr); es_fputs (_("No argument allowed"), es_stderr); es_putc ('\n', es_stderr); exit (2); } gc_component_retrieve_options (-1); if (gc_process_gpgconf_conf (NULL, 1, 1, NULL)) exit (1); break; case aListDirs: /* Show the system configuration directories for gpgconf. */ get_outfp (&outfp); list_dirs (outfp, argc? argv : NULL); break; case aCreateSocketDir: { char *socketdir; unsigned int flags; /* Make sure that the top /run/user/UID/gnupg dir has been * created. */ gnupg_socketdir (); /* Check the /var/run dir. */ socketdir = _gnupg_socketdir_internal (1, &flags); if ((flags & 64) && !opt.dry_run) { /* No sub dir - create it. */ if (gnupg_mkdir (socketdir, "-rwx")) gc_error (1, errno, "error creating '%s'", socketdir); /* Try again. */ socketdir = _gnupg_socketdir_internal (1, &flags); } /* Give some info. */ if ( (flags & ~32) || opt.verbose || opt.dry_run) { log_info ("socketdir is '%s'\n", socketdir); if ((flags & 1)) log_info ("\tgeneral error\n"); if ((flags & 2)) log_info ("\tno /run/user dir\n"); if ((flags & 4)) log_info ("\tbad permissions\n"); if ((flags & 8)) log_info ("\tbad permissions (subdir)\n"); if ((flags & 16)) log_info ("\tmkdir failed\n"); if ((flags & 32)) log_info ("\tnon-default homedir\n"); if ((flags & 64)) log_info ("\tno such subdir\n"); if ((flags & 128)) log_info ("\tusing homedir as fallback\n"); } if ((flags & ~32) && !opt.dry_run) gc_error (1, 0, "error creating socket directory"); xfree (socketdir); } break; case aRemoveSocketDir: { char *socketdir; unsigned int flags; /* Check the /var/run dir. */ socketdir = _gnupg_socketdir_internal (1, &flags); if ((flags & 128)) log_info ("ignoring request to remove non /run/user socket dir\n"); else if (opt.dry_run) ; else if (rmdir (socketdir)) gc_error (1, errno, "error removing '%s'", socketdir); xfree (socketdir); } break; } if (outfp != es_stdout) if (es_fclose (outfp)) gc_error (1, errno, "error closing '%s'", opt.outfile); return 0; }